Compare commits

...
Author SHA1 Message Date
nxglabs e7d3453744 Merge pull request #1064 from nxglabs/signers_completed_main 2025-07-29 13:30:45 +00:00
prafull-opensignlabsandGitHub 47cc0ef580 Merge pull request #1858 from OpenSignLabs/updates-16525889391
remove checkbox from sign agreement screen
2025-07-25 21:18:33 +05:30
prafull-opensignlabs 04f71be6a2 Merge pull request #1044 from nxglabs/raktima-patch-main-2
fix: remove agreement checkbox and remove agreement checkbox tour
2025-07-25 15:38:38 +00:00
prafull-opensignlabsandGitHub 07603f4bc2 Merge pull request #1856 from OpenSignLabs/imgbot
[ImgBot] Optimize images
2025-07-24 14:40:19 +05:30
ImgBotApp ea16180551 [ImgBot] Optimize images
*Total -- 487.98kb -> 238.96kb (51.03%)

/apps/OpenSign/public/logo512.png -- 10.72kb -> 2.72kb (74.62%)
/apps/OpenSignServer/logo.png -- 86.48kb -> 24.83kb (71.28%)
/apps/OpenSign/src/assets/images/logo.png -- 86.48kb -> 24.83kb (71.28%)
/apps/OpenSign/public/static/js/assets/images/logo.2a7bff0c1189183fafe71d7d5b94e0cd.png -- 86.48kb -> 24.83kb (71.28%)
/apps/OpenSign/public/logo192.png -- 2.13kb -> 0.65kb (69.69%)
/apps/OpenSign/src/assets/images/dp.png -- 12.83kb -> 4.25kb (66.9%)
/apps/OpenSign/src/assets/images/folder.png -- 4.39kb -> 1.93kb (56.05%)
/apps/OpenSign/public/static/js/assets/images/logo-dark.png -- 95.21kb -> 57.21kb (39.91%)
/apps/OpenSignServer/public/assets/images/parse-logo.png -- 5.16kb -> 3.81kb (26.21%)
/apps/OpenSign/src/assets/images/pdf3.png -- 11.66kb -> 9.77kb (16.21%)
/apps/OpenSign/src/assets/images/login_img.svg -- 40.75kb -> 39.10kb (4.04%)
/apps/OpenSign/src/assets/images/recreatedoc.png -- 40.69kb -> 40.08kb (1.48%)
/apps/OpenSign/src/assets/images/pad.svg -- 5.00kb -> 4.94kb (1.07%)

Signed-off-by: ImgBotApp <ImgBotHelp@gmail.com>
2025-07-24 08:28:02 +00:00
prafull-opensignlabsandGitHub 75ad566d54 Merge pull request #1841 from OpenSignLabs/updates-16144605887
v2.26.1
2025-07-08 19:01:19 +05:30
nxglabs fda9fb534b Merge pull request #963 from nxglabs/staging
v2.26.1
2025-07-08 13:27:37 +00:00
prafull-opensignlabsandGitHub cb3e31ebbf Merge pull request #1838 from OpenSignLabs/updates-16124490027
dark-theme logo
2025-07-07 23:43:31 +05:30
nxglabs 400525debb Merge pull request #960 from nxglabs/fix_customlogo
fix: custom applogo not visible in header
2025-07-07 18:07:24 +00:00
prafull-opensignlabsandGitHub 1cc017bb05 Merge pull request #1829 from OpenSignLabs/updates-16070658635
fix: quicksend screen get black out while sending document
2025-07-04 15:06:16 +05:30
prafull-opensignlabs 531dd11185 Merge pull request #942 from nxglabs/staging
fix: quicksend screen get black out while sending document
2025-07-04 09:32:50 +00:00
prafull-opensignlabsandGitHub 1e80754750 Merge pull request #1827 from OpenSignLabs/updates-16068582405
revert: css of drive
2025-07-04 13:14:27 +05:30
prafull-opensignlabs 0d286938c7 Merge pull request #940 from nxglabs/staging
revert: css of drive
2025-07-04 07:43:32 +00:00
prafull-opensignlabsandGitHub 360c13df25 Merge pull request #1825 from OpenSignLabs/updates-16055306436
v2.26.0
2025-07-04 10:35:33 +05:30
nxglabs 71b9bd6367 Merge pull request #937 from nxglabs/tenant_mailtemplate
fix: if user not provide default value use it from template in createdocument from template API
2025-07-03 16:04:35 +00:00
prafull-opensignlabsandGitHub 4a5b35e147 Merge pull request #1818 from OpenSignLabs/updates-15966812948
fix: handle undefine appId
2025-06-30 13:09:02 +05:30
prafull-opensignlabs 7930a8f439 Merge pull request #917 from nxglabs/handle_appid
fix: handle undefine appId
2025-06-30 07:37:32 +00:00
60 changed files with 2501 additions and 1432 deletions
+316 -266
View File
File diff suppressed because it is too large Load Diff
+21 -21
View File
@@ -5,15 +5,15 @@
"dependencies": { "dependencies": {
"@formkit/auto-animate": "^0.8.2", "@formkit/auto-animate": "^0.8.2",
"@imgly/background-removal": "^1.6.0", "@imgly/background-removal": "^1.6.0",
"@lottiefiles/dotlottie-react": "^0.14.0", "@lottiefiles/dotlottie-react": "^0.14.2",
"@pdf-lib/fontkit": "^1.1.1", "@pdf-lib/fontkit": "^1.1.1",
"@radix-ui/themes": "^3.2.1", "@radix-ui/themes": "^3.2.1",
"@reduxjs/toolkit": "^2.8.2", "@reduxjs/toolkit": "^2.8.2",
"axios": "^1.9.0", "axios": "^1.10.0",
"date-fns-tz": "^3.2.0", "date-fns-tz": "^3.2.0",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"i18next": "^23.16.8", "i18next": "^25.3.0",
"i18next-browser-languagedetector": "^8.1.0", "i18next-browser-languagedetector": "^8.2.0",
"i18next-http-backend": "^3.0.2", "i18next-http-backend": "^3.0.2",
"jszip": "^3.10.1", "jszip": "^3.10.1",
"jwt-decode": "^4.0.0", "jwt-decode": "^4.0.0",
@@ -36,24 +36,24 @@
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-gtm-module": "^2.0.11", "react-gtm-module": "^2.0.11",
"react-helmet": "^6.1.0", "react-helmet": "^6.1.0",
"react-i18next": "^15.5.2", "react-i18next": "^15.5.3",
"react-konva": "^18.2.10", "react-konva": "^18.2.10",
"react-pdf": "^9.2.1", "react-pdf": "^9.2.1",
"react-quill-new": "^3.4.6", "react-quill-new": "^3.4.6",
"react-redux": "^9.2.0", "react-redux": "^9.2.0",
"react-rnd": "^10.5.2", "react-rnd": "^10.5.2",
"react-router": "^7.6.1", "react-router": "^7.6.3",
"react-scrollbars-custom": "^4.1.1", "react-scrollbars-custom": "^4.1.1",
"react-select": "^5.10.1", "react-select": "^5.10.1",
"react-signature-canvas": "^1.1.0-alpha.2", "react-signature-canvas": "^1.1.0-alpha.2",
"react-timezone-select": "^3.2.8", "react-timezone-select": "^3.2.8",
"react-tooltip": "^5.28.1", "react-tooltip": "^5.29.1",
"reactour": "^1.19.4", "reactour": "^1.19.4",
"redux": "^5.0.1", "redux": "^5.0.1",
"regex-parser": "^2.3.1", "regex-parser": "^2.3.1",
"serve": "^14.2.4", "serve": "^14.2.4",
"styled-components": "^5.3.11", "styled-components": "^5.3.11",
"web-vitals": "^5.0.2", "web-vitals": "^5.0.3",
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz" "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz"
}, },
"scripts": { "scripts": {
@@ -94,38 +94,38 @@
} }
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.27.4", "@babel/core": "^7.27.7",
"@babel/preset-env": "^7.27.2", "@babel/preset-env": "^7.27.2",
"@babel/preset-react": "^7.27.1", "@babel/preset-react": "^7.27.1",
"@babel/runtime-corejs2": "^7.27.4", "@babel/runtime-corejs2": "^7.27.6",
"@testing-library/jest-dom": "^6.6.3", "@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.3.0", "@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^14.6.1", "@testing-library/user-event": "^14.6.1",
"@types/react": "^18.3.23", "@types/react": "^18.3.23",
"@vitejs/plugin-react": "^4.5.1", "@vitejs/plugin-react": "^4.6.0",
"@vitejs/plugin-react-swc": "^3.10.1", "@vitejs/plugin-react-swc": "^3.10.2",
"@vitest/ui": "^3.2.0", "@vitest/ui": "^3.2.4",
"autoprefixer": "^10.4.21", "autoprefixer": "^10.4.21",
"babel-loader": "^10.0.0", "babel-loader": "^10.0.0",
"commitizen": "^4.3.1", "commitizen": "^4.3.1",
"concurrently": "^9.1.2", "concurrently": "^9.2.0",
"css-loader": "^7.1.2", "css-loader": "^7.1.2",
"daisyui": "^4.12.24", "daisyui": "^4.12.24",
"dotenv": "^16.5.0", "dotenv": "^16.6.1",
"eslint": "^9.28.0", "eslint": "^9.30.0",
"eslint-plugin-prettier": "^5.4.1", "eslint-plugin-prettier": "^5.5.1",
"eslint-plugin-react": "^7.37.5", "eslint-plugin-react": "^7.37.5",
"jsdom": "^26.1.0", "jsdom": "^26.1.0",
"lint-staged": "^16.1.0", "lint-staged": "^16.1.2",
"postcss": "^8.5.4", "postcss": "^8.5.6",
"prettier": "^3.5.3", "prettier": "^3.6.2",
"pretty-quick": "^4.2.2", "pretty-quick": "^4.2.2",
"rollup-plugin-node-polyfills": "^0.2.1", "rollup-plugin-node-polyfills": "^0.2.1",
"tailwindcss": "^3.4.17", "tailwindcss": "^3.4.17",
"vite": "^6.3.5", "vite": "^6.3.5",
"vite-plugin-svgr": "^4.3.0", "vite-plugin-svgr": "^4.3.0",
"vite-tsconfig-paths": "^5.1.4", "vite-tsconfig-paths": "^5.1.4",
"vitest": "^3.2.0" "vitest": "^3.2.4"
}, },
"engines": { "engines": {
"node": "18 || 20 || 22" "node": "18 || 20 || 22"
@@ -379,6 +379,7 @@
"certificate": "Zertifikat", "certificate": "Zertifikat",
"decline": "Ablehnen", "decline": "Ablehnen",
"finish": "Fertigstellen", "finish": "Fertigstellen",
"done": "Fertig",
"mail": "E-Mail", "mail": "E-Mail",
"sign-now": "Jetzt unterzeichnen", "sign-now": "Jetzt unterzeichnen",
"successfully-signed": "Erfolgreich unterzeichnet!", "successfully-signed": "Erfolgreich unterzeichnet!",
@@ -761,6 +762,8 @@
"Rotation-alert": "Seite drehen", "Rotation-alert": "Seite drehen",
"bulk-import": "Massenimport", "bulk-import": "Massenimport",
"contacts-file": "Kontaktdatei (xlsx, csv)", "contacts-file": "Kontaktdatei (xlsx, csv)",
"import-guideline": "Laden Sie eine CSV- oder Excel-Datei mit den Spalten Name, Email und optional Phone hoch. Es werden nur die ersten 100 Kontakte importiert.",
"download-sample": "Beispieldatei herunterladen",
"100-records-only": "Derzeit können Sie nur bis zu 100 Datensätze importieren.", "100-records-only": "Derzeit können Sie nur bis zu 100 Datensätze importieren.",
"csv-excel-support-only": "Laden Sie eine Datei in einem der folgenden Formate hoch: CSV, XLSX oder XLS.", "csv-excel-support-only": "Laden Sie eine Datei in einem der folgenden Formate hoch: CSV, XLSX oder XLS.",
"contact-imported": "{{imported}} Kontakte wurden importiert. {{failed}} Kontakte konnten nicht importiert werden.", "contact-imported": "{{imported}} Kontakte wurden importiert. {{failed}} Kontakte konnten nicht importiert werden.",
@@ -1033,5 +1036,12 @@
"choose-one":"Wählen Sie eine aus", "choose-one":"Wählen Sie eine aus",
"search-templates": "Vorlagen durchsuchen…", "search-templates": "Vorlagen durchsuchen…",
"search-documents": "Dokumente suchen…", "search-documents": "Dokumente suchen…",
"search-contacts": "Kontakte durchsuchen…" "search-contacts": "Kontakte durchsuchen…",
"invalid-email-found": "Ungültige E-Mail gefunden: {{email}}",
"duplicate-email-found": "Doppelte E-Mail gefunden: {{email}}",
"vertical": "Vertikal",
"horizontal": "Horizontal",
"billing": "Abrechnung",
"console": "Konsole",
"action-prohibited": "Diese Aktion ist für Ihre E-Mail-Domain nicht erlaubt. Bitte wenden Sie sich an Ihren Administrator, um Hilfe zu erhalten."
} }
@@ -379,6 +379,7 @@
"certificate": "Certificate", "certificate": "Certificate",
"decline": "Decline", "decline": "Decline",
"finish": "Finish", "finish": "Finish",
"done": "Done",
"mail": "Mail", "mail": "Mail",
"sign-now": "Sign now", "sign-now": "Sign now",
"successfully-signed": "Successfully signed!", "successfully-signed": "Successfully signed!",
@@ -761,6 +762,8 @@
"Rotation-alert": "Rotate page", "Rotation-alert": "Rotate page",
"bulk-import": "Bulk import", "bulk-import": "Bulk import",
"contacts-file": "Contacts file (xlsx, csv)", "contacts-file": "Contacts file (xlsx, csv)",
"import-guideline": "Upload a CSV or Excel file with columns Name, Email and optional Phone. Only the first 100 records will be imported.",
"download-sample": "Download sample file",
"100-records-only": "Currently you can only import up to 100 records.", "100-records-only": "Currently you can only import up to 100 records.",
"csv-excel-support-only": "Upload a file in one of the following formats: CSV, XLSX or XLS.", "csv-excel-support-only": "Upload a file in one of the following formats: CSV, XLSX or XLS.",
"contact-imported": "{{imported}} contacts were imported. {{failed}} contacts failed to import.", "contact-imported": "{{imported}} contacts were imported. {{failed}} contacts failed to import.",
@@ -1033,5 +1036,12 @@
"choose-one":"Choose One", "choose-one":"Choose One",
"search-templates": "Search templates…", "search-templates": "Search templates…",
"search-documents": "Search documents…", "search-documents": "Search documents…",
"search-contacts": "Search contacts…" "search-contacts": "Search contacts…",
"invalid-email-found": "Invalid email found: {{email}}",
"duplicate-email-found": "Duplicate email found: {{email}}",
"vertical": "Vertical",
"horizontal": "Horizontal",
"billing": "Billing",
"console": "Console",
"action-prohibited": "This action isn't allowed for your email domain. Please contact your administrator for assistance."
} }
@@ -380,6 +380,7 @@
"certificate": "Certificado", "certificate": "Certificado",
"decline": "Rechazar", "decline": "Rechazar",
"finish": "Finalizar", "finish": "Finalizar",
"done": "Hecho",
"mail": "Correo", "mail": "Correo",
"sign-now": "Firmar ahora", "sign-now": "Firmar ahora",
"successfully-signed": "¡Firmado exitosamente!", "successfully-signed": "¡Firmado exitosamente!",
@@ -761,6 +762,8 @@
"Rotation-alert": "Girar página", "Rotation-alert": "Girar página",
"bulk-import": "Importación masiva", "bulk-import": "Importación masiva",
"contacts-file": "Archivo de contactos (xlsx, csv)", "contacts-file": "Archivo de contactos (xlsx, csv)",
"import-guideline": "Sube un archivo CSV o Excel con las columnas Name, Email y opcionalmente Phone. Solo se importarán los primeros 100 contactos.",
"download-sample": "Descargar archivo de ejemplo",
"100-records-only": "Currently you can only import up to 100 records.", "100-records-only": "Currently you can only import up to 100 records.",
"csv-excel-support-only": "Cargue un archivo en uno de los siguientes formatos: CSV, XLSX o XLS.", "csv-excel-support-only": "Cargue un archivo en uno de los siguientes formatos: CSV, XLSX o XLS.",
"contact-imported": "Se importaron {{imported}} contactos. {{failed}} contactos no pudieron importarse.", "contact-imported": "Se importaron {{imported}} contactos. {{failed}} contactos no pudieron importarse.",
@@ -1033,5 +1036,12 @@
"choose-one":"Elige uno", "choose-one":"Elige uno",
"search-templates": "Buscar plantillas…", "search-templates": "Buscar plantillas…",
"search-documents": "Buscar documentos…", "search-documents": "Buscar documentos…",
"search-contacts": "Buscar contactos…" "search-contacts": "Buscar contactos…",
"invalid-email-found": "Correo electrónico no válido encontrado: {{email}}",
"duplicate-email-found": "Correo electrónico duplicado encontrado: {{email}}",
"vertical": "Vertical",
"horizontal": "Horizontal",
"billing": "Facturación",
"console": "Consola",
"action-prohibited": "Esta acción no está permitida para su dominio de correo electrónico. Por favor, contacte con su administrador para obtener ayuda."
} }
@@ -379,6 +379,7 @@
"certificate": "Certificat", "certificate": "Certificat",
"decline": "refusé", "decline": "refusé",
"finish": "terminé", "finish": "terminé",
"done": "Terminé",
"mail": "Mail", "mail": "Mail",
"sign-now": "Signez maintenant", "sign-now": "Signez maintenant",
"successfully-signed": "Signé avec succès !", "successfully-signed": "Signé avec succès !",
@@ -761,6 +762,8 @@
"Rotation-alert": "Faire pivoter la page", "Rotation-alert": "Faire pivoter la page",
"bulk-import": "Importation en masse", "bulk-import": "Importation en masse",
"contacts-file": "Fichier de contacts (xlsx, csv)", "contacts-file": "Fichier de contacts (xlsx, csv)",
"import-guideline": "Téléchargez un fichier CSV ou Excel avec les colonnes Name, Email et éventuellement Phone. Seuls les 100 premiers contacts seront importés.",
"download-sample": "Télécharger un fichier d'exemple",
"100-records-only": "Actuellement, vous ne pouvez importer que 100 enregistrements.", "100-records-only": "Actuellement, vous ne pouvez importer que 100 enregistrements.",
"csv-excel-support-only": "Veuillez télécharger un fichier dans l'un des formats suivants : CSV, XLSX ou XLS.", "csv-excel-support-only": "Veuillez télécharger un fichier dans l'un des formats suivants : CSV, XLSX ou XLS.",
"contact-imported": "{{imported}} contacts ont été importés. {{failed}} contacts n'ont pas pu être importés.", "contact-imported": "{{imported}} contacts ont été importés. {{failed}} contacts n'ont pas pu être importés.",
@@ -1033,5 +1036,12 @@
"choose-one":"Choisissez-en un", "choose-one":"Choisissez-en un",
"search-templates": "Rechercher des modèles…", "search-templates": "Rechercher des modèles…",
"search-documents": "Rechercher des documents…", "search-documents": "Rechercher des documents…",
"search-contacts": "Rechercher des contacts…" "search-contacts": "Rechercher des contacts…",
"invalid-email-found": "Adresse e-mail invalide trouvée : {{email}}",
"duplicate-email-found": "Adresse e-mail en double trouvée : {{email}}",
"vertical": "Vertical",
"horizontal": "Horizontal",
"billing": "Facturation",
"console": "Console",
"action-prohibited": "Cette action n'est pas autorisée pour votre domaine de messagerie. Veuillez contacter votre administrateur pour obtenir de l'aide."
} }
@@ -355,7 +355,7 @@
"widgets-name": { "widgets-name": {
"signature": "हस्ताक्षर", "signature": "हस्ताक्षर",
"stamp": "मोहर", "stamp": "मोहर",
"initials": "हस्ताक्षर", "initials": "प्रारंभिक अक्षर",
"name": "नाम", "name": "नाम",
"job title": "पद", "job title": "पद",
"company": "कंपनी", "company": "कंपनी",
@@ -379,6 +379,7 @@
"certificate": "प्रमाण पत्र", "certificate": "प्रमाण पत्र",
"decline": "अस्वीकार करें", "decline": "अस्वीकार करें",
"finish": "समाप्त करें", "finish": "समाप्त करें",
"done": "हो गया",
"mail": "मेल", "mail": "मेल",
"sign-now": "अभी हस्ताक्षर करें", "sign-now": "अभी हस्ताक्षर करें",
"successfully-signed": "सफलतापूर्वक हस्ताक्षर किए गए!", "successfully-signed": "सफलतापूर्वक हस्ताक्षर किए गए!",
@@ -389,7 +390,7 @@
"Email-verified-alert-2": "ईमेल पहले से ही सत्यापित है।", "Email-verified-alert-2": "ईमेल पहले से ही सत्यापित है।",
"upload-stamp-image": "मोहर छवि अपलोड करें", "upload-stamp-image": "मोहर छवि अपलोड करें",
"draw": "आरेखित करें", "draw": "आरेखित करें",
"type": "प्रकार", "type": "टाइप",
"color-type": { "color-type": {
"red": "लाल", "red": "लाल",
"blue": "नीला", "blue": "नीला",
@@ -761,6 +762,8 @@
"Rotation-alert": "पृष्ठ घुमाएँ", "Rotation-alert": "पृष्ठ घुमाएँ",
"bulk-import": "थोक आयात", "bulk-import": "थोक आयात",
"contacts-file": "संपर्क फ़ाइल (xlsx, csv)", "contacts-file": "संपर्क फ़ाइल (xlsx, csv)",
"import-guideline": "नाम, ईमेल और वैकल्पिक फोन कॉलम वाली CSV या Excel फ़ाइल अपलोड करें। केवल पहले 100 संपर्क आयात किए जाएंगे.",
"download-sample": "उदाहरण फ़ाइल डाउनलोड करें",
"100-records-only": "वर्तमान में आप केवल 100 रिकॉर्ड तक आयात कर सकते हैं।", "100-records-only": "वर्तमान में आप केवल 100 रिकॉर्ड तक आयात कर सकते हैं।",
"csv-excel-support-only": "निम्नलिखित प्रारूपों में से किसी एक में फ़ाइल अपलोड करें: CSV, XLSX या XLS।", "csv-excel-support-only": "निम्नलिखित प्रारूपों में से किसी एक में फ़ाइल अपलोड करें: CSV, XLSX या XLS।",
"contact-imported": "{{imported}} संपर्क आयात किए गए। {{failed}} संपर्क आयात करने में विफल रहे।", "contact-imported": "{{imported}} संपर्क आयात किए गए। {{failed}} संपर्क आयात करने में विफल रहे।",
@@ -1033,5 +1036,12 @@
"choose-one":"एक का चयन", "choose-one":"एक का चयन",
"search-templates": "टेम्पलेट खोजें…", "search-templates": "टेम्पलेट खोजें…",
"search-documents": "दस्तावेज़ खोजें…", "search-documents": "दस्तावेज़ खोजें…",
"search-contacts": "संपर्क खोजें…" "search-contacts": "संपर्क खोजें…",
"invalid-email-found": "अमान्य ईमेल पाया गया: {{email}}",
"duplicate-email-found": "डुप्लिकेट ईमेल पाया गया: {{email}}",
"vertical": "वर्टिकल",
"horizontal": "हॉरिज़ॉन्टल",
"billing": "बिलिंग",
"console": "कंसोल",
"action-prohibited": "यह क्रिया आपके ईमेल डोमेन के लिए अनुमत नहीं है। कृपया सहायता के लिए अपने व्यवस्थापक से संपर्क करें।"
} }
@@ -379,6 +379,7 @@
"certificate": "Certificato", "certificate": "Certificato",
"decline": "Rifiuta", "decline": "Rifiuta",
"finish": "Completa", "finish": "Completa",
"done": "Fatto",
"mail": "Email", "mail": "Email",
"sign-now": "Firma ora", "sign-now": "Firma ora",
"successfully-signed": "Firmato con successo!", "successfully-signed": "Firmato con successo!",
@@ -761,6 +762,8 @@
"Rotation-alert": "Ruota pagina", "Rotation-alert": "Ruota pagina",
"bulk-import": "Importazione massiva", "bulk-import": "Importazione massiva",
"contacts-file": "File contatti (xlsx, csv)", "contacts-file": "File contatti (xlsx, csv)",
"import-guideline": "Carica un file CSV o Excel con le colonne Name, Email e opzionale Phone. Verranno importati solo i primi 100 contatti.",
"download-sample": "Scarica file di esempio",
"100-records-only": "Attualmente puoi importare solo fino a 100 record.", "100-records-only": "Attualmente puoi importare solo fino a 100 record.",
"csv-excel-support-only": "Carica un file nei seguenti formati: CSV, XLSX o XLS.", "csv-excel-support-only": "Carica un file nei seguenti formati: CSV, XLSX o XLS.",
"contact-imported": "{{imported}} contatti importati. {{failed}} contatti non sono stati importati.", "contact-imported": "{{imported}} contatti importati. {{failed}} contatti non sono stati importati.",
@@ -1033,5 +1036,12 @@
"choose-one":"Scegline uno", "choose-one":"Scegline uno",
"search-templates": "Cerca modelli…", "search-templates": "Cerca modelli…",
"search-documents": "Cerca documenti…", "search-documents": "Cerca documenti…",
"search-contacts": "Cerca contatti…" "search-contacts": "Cerca contatti…",
"invalid-email-found": "Email non valida trovata: {{email}}",
"duplicate-email-found": "Email duplicata trovata: {{email}}",
"vertical": "Verticale",
"horizontal": "Orizzontale",
"billing": "Fatturazione",
"console": "Console",
"action-prohibited": "Questa azione non è consentita per il tuo dominio email. Contatta il tuo amministratore per ricevere assistenza."
} }
+4
View File
@@ -0,0 +1,4 @@
Name,Email,Phone
John Doe,john@example.com,1234567890
Jane Smith,jane@example.com,9876543210
Foo Bar,foo@example.com,5555555555
1 Name Email Phone
2 John Doe john@example.com 1234567890
3 Jane Smith jane@example.com 9876543210
4 Foo Bar foo@example.com 5555555555
+22 -5
View File
@@ -15,6 +15,8 @@ const BulkSendUi = (props) => {
const [isSignatureExist, setIsSignatureExist] = useState(); const [isSignatureExist, setIsSignatureExist] = useState();
const [isDisableBulkSend, setIsDisableBulkSend] = useState(false); const [isDisableBulkSend, setIsDisableBulkSend] = useState(false);
const [isLoader, setIsLoader] = useState(false); const [isLoader, setIsLoader] = useState(false);
const [signers, setSigners] = useState([]);
const [emails, setEmails] = useState([]);
useEffect(() => { useEffect(() => {
signatureExist(); signatureExist();
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -47,7 +49,14 @@ const BulkSendUi = (props) => {
(() => { (() => {
if (props?.Placeholders?.length > 0) { if (props?.Placeholders?.length > 0) {
let users = []; let users = [];
let emails = [];
props?.Placeholders?.forEach((element) => { props?.Placeholders?.forEach((element) => {
const signerEmail = element?.email || element?.signerPtr?.Email;
// only add when there's a non-empty signerEmail
if (signerEmail) {
emails = [...emails, signerEmail];
}
if (!element.signerObjId) { if (!element.signerObjId) {
users = [ users = [
...users, ...users,
@@ -60,7 +69,10 @@ const BulkSendUi = (props) => {
]; ];
} }
}); });
setEmails(emails);
setForms((prevForms) => [...prevForms, { Id: 1, fields: users }]); setForms((prevForms) => [...prevForms, { Id: 1, fields: users }]);
const signer = props.item?.Signers?.filter((x) => x?.objectId);
setSigners(signer);
} }
})(); })();
// eslint-disable-next-line // eslint-disable-next-line
@@ -82,10 +94,16 @@ const BulkSendUi = (props) => {
function validateEmails(data) { function validateEmails(data) {
for (const item of data) { for (const item of data) {
let email = "";
for (const field of item.fields) { for (const field of item.fields) {
if (!emailRegex.test(field.email)) { if (!emailRegex.test(field.email)) {
alert(`Invalid email found: ${field.email}`); alert(t("invalid-email-found", { email: field.email }));
return false; return false;
} else if (email === field.email || emails?.includes(field.email)) {
alert(t("duplicate-email-found", { email: field.email }));
return false;
} else {
email = field.email;
} }
} }
} }
@@ -143,15 +161,14 @@ const BulkSendUi = (props) => {
Documents.push({ Documents.push({
...props.item, ...props.item,
Placeholders: updatedPlaceholders, Placeholders: updatedPlaceholders,
Signers: props.item.Signers Signers: signers ? [...signers, ...existSigner] : [...existSigner]
? [...props.item.Signers, ...existSigner]
: [...existSigner]
}); });
} else { } else {
Documents.push({ Documents.push({
...props.item, ...props.item,
Placeholders: updatedPlaceholders, Placeholders: updatedPlaceholders,
SignatureType: props.signatureType SignatureType: props.signatureType,
Signers: signers
}); });
} }
}); });
+17 -15
View File
@@ -19,8 +19,13 @@ const Header = ({ showSidebar, setIsMenu, isConsole }) => {
const { width } = useWindowSize(); const { width } = useWindowSize();
const username = localStorage.getItem("username") || ""; const username = localStorage.getItem("username") || "";
const image = localStorage.getItem("profileImg") || dp; const image = localStorage.getItem("profileImg") || dp;
const isAdmin =
localStorage.getItem("Extand_Class") &&
JSON.parse(localStorage.getItem("Extand_Class"))?.[0]?.UserRole ===
"contracts_Admin";
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const [applogo, setAppLogo] = useState(""); const [applogo, setAppLogo] = useState("");
const [isDarkTheme, setIsDarkTheme] = useState();
const toggleDropdown = () => { const toggleDropdown = () => {
setIsOpen(!isOpen); setIsOpen(!isOpen);
@@ -88,22 +93,15 @@ const Header = ({ showSidebar, setIsMenu, isConsole }) => {
useEffect(() => { useEffect(() => {
const updateLogoForTheme = () => { const updateThemeStatus = () => {
const isDarkMode = const isDarkTheme =
document.documentElement.getAttribute("data-theme") === "opensigndark"; document.documentElement.getAttribute("data-theme") === "opensigndark";
const logo = isDarkMode setIsDarkTheme(isDarkTheme);
? "/static/js/assets/images/logo-dark.png" // Path to the dark mode logo
: appInfo.applogo; // Use current logo for light mode
if (applogo !== logo) {
setAppLogo(logo);
}
}; };
updateThemeStatus();
// Set the logo immediately based on the current theme
updateLogoForTheme();
const observer = new MutationObserver(() => { const observer = new MutationObserver(() => {
updateLogoForTheme(); updateThemeStatus();
}); });
observer.observe(document.documentElement, { observer.observe(document.documentElement, {
@@ -112,11 +110,11 @@ const Header = ({ showSidebar, setIsMenu, isConsole }) => {
}); });
return () => observer.disconnect(); return () => observer.disconnect();
}, [applogo]); }, []);
return ( return (
<div> <div>
<div className="op-navbar bg-base-100 shadow"> <div className="op-navbar bg-base-100 shadow touch-none">
<div className="flex-none"> <div className="flex-none">
<button <button
className="op-btn op-btn-square op-btn-ghost focus:outline-none hover:bg-transparent op-btn-sm no-animation" className="op-btn op-btn-square op-btn-ghost focus:outline-none hover:bg-transparent op-btn-sm no-animation"
@@ -130,7 +128,11 @@ const Header = ({ showSidebar, setIsMenu, isConsole }) => {
{applogo && ( {applogo && (
<img <img
className="object-contain h-full w-auto" className="object-contain h-full w-auto"
src={applogo} src={
isDarkTheme
? "/static/js/assets/images/logo-dark.png"
: applogo
}
alt="logo" alt="logo"
/> />
)} )}
@@ -148,7 +148,7 @@ const DashboardCard = (props) => {
); );
let arr = []; let arr = [];
for (const obj of listData) { for (const obj of listData) {
const isSigner = obj.Signers.some( const isSigner = obj.Signers?.some(
(item) => item.UserId.objectId === currentUser.id (item) => item.UserId.objectId === currentUser.id
); );
if (isSigner) { if (isSigner) {
@@ -134,7 +134,7 @@ function DashboardReport(props) {
const listData = res.data?.result.filter((x) => x.Signers.length > 0); const listData = res.data?.result.filter((x) => x.Signers.length > 0);
let arr = []; let arr = [];
for (const obj of listData) { for (const obj of listData) {
const isSigner = obj.Signers.some( const isSigner = obj.Signers?.some(
(item) => item.UserId.objectId === currentUser (item) => item.UserId.objectId === currentUser
); );
if (isSigner) { if (isSigner) {
@@ -405,7 +405,7 @@ function DriveBody(props) {
</tr> </tr>
) )
) : listType === "list" && data.Type === "Folder" ? ( ) : listType === "list" && data.Type === "Folder" ? (
<div key={ind} className="relative w-[100px] h-[100px] mx-2 my-3"> <div className="relative w-[100px] h-[100px] mx-2 my-3">
<ContextMenu.Root> <ContextMenu.Root>
<ContextMenu.Trigger className="flex flex-col justify-center items-center select-none-cls"> <ContextMenu.Trigger className="flex flex-col justify-center items-center select-none-cls">
{/* folder */} {/* folder */}
@@ -447,7 +447,6 @@ function DriveBody(props) {
)} )}
</div> </div>
</ContextMenu.Trigger> </ContextMenu.Trigger>
<ContextMenu.Portal> <ContextMenu.Portal>
<ContextMenu.Content <ContextMenu.Content
className="ContextMenuContent" className="ContextMenuContent"
@@ -641,7 +640,7 @@ function DriveBody(props) {
) : ( ) : (
<div className="flex flex-row flex-wrap items-center mt-1 pb-[20px] mx-[5px]"> <div className="flex flex-row flex-wrap items-center mt-1 pb-[20px] mx-[5px]">
{props.pdfData.map((data, ind) => { {props.pdfData.map((data, ind) => {
return <div key={ind}>{handleFolderData(data, ind, "list")}</div>; return <React.Fragment key={ind}>{handleFolderData(data, ind, "list")}</React.Fragment>;
})} })}
</div> </div>
)} )}
@@ -9,7 +9,6 @@ function AgreementContent(props) {
const h2Style = "text-base-content font-medium text-lg"; const h2Style = "text-base-content font-medium text-lg";
const ulStyle = "list-disc px-4 py-3"; const ulStyle = "list-disc px-4 py-3";
const handleOnclick = () => { const handleOnclick = () => {
props.setIsAgreeTour(false);
props.setIsAgree(true); props.setIsAgree(true);
props.setIsShowAgreeTerms(false); props.setIsShowAgreeTerms(false);
props.showFirstWidget(); props.showFirstWidget();
@@ -4,7 +4,6 @@ import AgreementContent from "./AgreementContent";
function AgreementSign(props) { function AgreementSign(props) {
const { t } = useTranslation(); const { t } = useTranslation();
const [isChecked, setIsChecked] = useState(false);
const [isShowAgreeTerms, setIsShowAgreeTerms] = useState(false); const [isShowAgreeTerms, setIsShowAgreeTerms] = useState(false);
return ( return (
@@ -12,39 +11,12 @@ function AgreementSign(props) {
<div className="op-modal op-modal-open absolute z-[448]"> <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="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"> <div className="flex flex-row items-center">
<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 text-base-content"> <div className="text-[11px] md:text-base text-base-content">
<span>{t("agree-p1")}</span> <span>{t("agree-p1")}</span>
<span <span
className="font-bold text-blue-600 cursor-pointer" className="font-bold text-blue-600 cursor-pointer"
onClick={() => { onClick={() => {
setIsShowAgreeTerms(true); setIsShowAgreeTerms(true);
props.setIsAgreeTour(false);
}} }}
> >
{t("agree-p2")} {t("agree-p2")}
@@ -52,15 +24,11 @@ function AgreementSign(props) {
<span> {t("agree-p3")}</span> <span> {t("agree-p3")}</span>
</div> </div>
</div> </div>
<div className="flex ml-[35px] mt-3"> <div className="flex mt-3">
<button <button
onClick={() => { onClick={() => {
if (isChecked) { props.setIsAgree(true);
props.setIsAgreeTour(false); props.showFirstWidget()
props.setIsAgree(true);
} else {
props.setIsAgreeTour(true);
}
}} }}
className="op-btn op-btn-primary op-btn-sm w-full md:w-auto" className="op-btn op-btn-primary op-btn-sm w-full md:w-auto"
> >
@@ -74,7 +42,6 @@ function AgreementSign(props) {
</div> </div>
{isShowAgreeTerms && ( {isShowAgreeTerms && (
<AgreementContent <AgreementContent
setIsAgreeTour={props.setIsAgreeTour}
setIsAgree={props.setIsAgree} setIsAgree={props.setIsAgree}
setIsShowAgreeTerms={setIsShowAgreeTerms} setIsShowAgreeTerms={setIsShowAgreeTerms}
showFirstWidget={props.showFirstWidget} showFirstWidget={props.showFirstWidget}
@@ -3,43 +3,39 @@ import { useTranslation } from "react-i18next";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
function DefaultSignature(props) { function DefaultSignature(props) {
const { t } = useTranslation(); const { t } = useTranslation();
const defaultSignImg = useSelector((state) => state.widget.defaultSignImg); const defaultSignImg = useSelector((state) => state.widget.defaultSignImg);
const myInitial = useSelector((state) => state.widget.myInitial) const myInitial = useSelector((state) => state.widget.myInitial);
const tabName = ["my-signature", "my-initials"]; const tabName = ["my-signature", "my-initials"];
const [activeTab, setActiveTab] = useState(0); const [activeTab, setActiveTab] = useState(0);
const confirmToaddDefaultSign = (type) => { const confirmToaddDefaultSign = (type) => {
if (!props.isAgree) { if (props?.xyPosition.length > 0) {
props.setIsAgreeTour(true); //check signature or initial widgets exist or not for auto signing
} else { const getCurrentSignerXY = props?.xyPosition.filter(
if (props?.xyPosition.length > 0) { (data) => data.Id === props.uniqueId
//check signature or initial widgets exist or not for auto signing );
const getCurrentSignerXY = props?.xyPosition.filter( const checkIsSignInitialExist = getCurrentSignerXY?.every(
(data) => data.Id === props.uniqueId (placeholderObj) =>
); placeholderObj?.placeHolder?.some((placeholder) =>
const checkIsSignInitialExist = getCurrentSignerXY?.every( placeholder?.pos?.some((posItem) => posItem?.type === type)
(placeholderObj) => )
placeholderObj?.placeHolder?.some((placeholder) => );
placeholder?.pos?.some((posItem) => posItem?.type === type) if (checkIsSignInitialExist) {
) props?.setDefaultSignAlert({
); isShow: true,
if (checkIsSignInitialExist) { alertMessage: t("default-sign-alert", { widgetsType: type }),
props?.setDefaultSignAlert({ type: type
isShow: true, });
alertMessage: t("default-sign-alert", { widgetsType: type }),
type: type
});
} else {
props?.setDefaultSignAlert({
isShow: true,
alertMessage: t("defaultSign-alert", { widgetsType: type })
});
}
} else { } else {
props?.setDefaultSignAlert({ props?.setDefaultSignAlert({
isShow: true, isShow: true,
alertMessage: t("please-select-position!") alertMessage: t("defaultSign-alert", { widgetsType: type })
}); });
} }
} else {
props?.setDefaultSignAlert({
isShow: true,
alertMessage: t("please-select-position!")
});
} }
}; };
@@ -389,8 +389,8 @@ function DropdownWidgetOption(props) {
checked={layout.toLowerCase() === data.toLowerCase()} checked={layout.toLowerCase() === data.toLowerCase()}
onChange={() => setLayout(data.toLowerCase())} onChange={() => setLayout(data.toLowerCase())}
/> />
<label className="text-[13px] font-500 mb-0 capitalize"> <label className="text-[13px] font-500 mb-0">
{data} {t(data)}
</label> </label>
</div> </div>
))} ))}
@@ -167,7 +167,7 @@ function Header(props) {
{isMobile && props?.isShowHeader ? ( {isMobile && props?.isShowHeader ? (
<div <div
id="navbar" id="navbar"
className="stickyHead" className="stickyHead touch-none"
style={{ style={{
width: window.innerWidth + "px" width: window.innerWidth + "px"
}} }}
@@ -14,7 +14,8 @@ import {
radioButtonWidget, radioButtonWidget,
textInputWidget, textInputWidget,
cellsWidget, cellsWidget,
textWidget textWidget,
selectFormat
} from "../../constant/Utils"; } from "../../constant/Utils";
import PlaceholderType from "./PlaceholderType"; import PlaceholderType from "./PlaceholderType";
import moment from "moment"; import moment from "moment";
@@ -25,37 +26,6 @@ import { useDispatch } from "react-redux";
import { setIsShowModal } from "../../redux/reducers/widgetSlice"; import { setIsShowModal } from "../../redux/reducers/widgetSlice";
import { themeColor } from "../../constant/const"; import { themeColor } from "../../constant/const";
const selectFormat = (data) => {
switch (data) {
case "L":
return "MM/dd/yyyy";
case "MM/DD/YYYY":
return "MM/dd/yyyy";
case "DD-MM-YYYY":
return "dd-MM-yyyy";
case "DD/MM/YYYY":
return "dd/MM/yyyy";
case "LL":
return "MMMM dd, yyyy";
case "DD MMM, YYYY":
return "dd MMM, yyyy";
case "YYYY-MM-DD":
return "yyyy-MM-dd";
case "MM-DD-YYYY":
return "MM-dd-yyyy";
case "MM.DD.YYYY":
return "MM.dd.yyyy";
case "MMM DD, YYYY":
return "MMM dd, yyyy";
case "MMMM DD, YYYY":
return "MMMM dd, yyyy";
case "DD MMMM, YYYY":
return "dd MMMM, yyyy";
default:
return "MM/dd/yyyy";
}
};
//function to get default format //function to get default format
const getDefaultFormat = (dateFormat) => dateFormat || "MM/dd/yyyy"; const getDefaultFormat = (dateFormat) => dateFormat || "MM/dd/yyyy";
@@ -110,7 +80,9 @@ function Placeholder(props) {
"MMM DD, YYYY", "MMM DD, YYYY",
"LL", "LL",
"DD MMM, YYYY", "DD MMM, YYYY",
"DD MMMM, YYYY" "DD MMMM, YYYY",
"DD.MM.YYYY",
"DD/MM/YYYY"
]; ];
useEffect(() => { useEffect(() => {
@@ -132,15 +104,18 @@ function Placeholder(props) {
if (selectDate && selectDate.format === "dd-MM-yyyy") { if (selectDate && selectDate.format === "dd-MM-yyyy") {
const [day, month, year] = selectDate.date.split("-"); const [day, month, year] = selectDate.date.split("-");
date = new Date(`${year}-${month}-${day}`); date = new Date(`${year}-${month}-${day}`);
} else if (selectDate && selectDate.format === "dd.MM.yyyy") {
const [day, month, year] = selectDate.date.split(".");
date = new Date(`${year}.${month}.${day}`);
} else if (selectDate && selectDate.format === "dd/MM/yyyy") {
const [day, month, year] = selectDate.date.split("/");
date = new Date(`${year}/${month}/${day}`);
} else { } else {
date = new Date(selectDate?.date); date = new Date(selectDate?.date);
} }
const milliseconds = date.getTime(); const milliseconds = date.getTime();
const newDate = moment(milliseconds).format(data); const newDate = moment(milliseconds).format(data);
const dateObj = { const dateObj = { date: newDate, format: selectFormat(data) };
date: newDate,
format: selectFormat(data)
};
updateDate.push(dateObj); updateDate.push(dateObj);
}); });
setDateFormat(updateDate); setDateFormat(updateDate);
@@ -205,13 +180,8 @@ function Placeholder(props) {
const widgetClickHandler = () => { const widgetClickHandler = () => {
props.setCurrWidgetsDetails && props.setCurrWidgetsDetails(props.pos); 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?.data?.signerObjId === props?.signerObjId && !props.isDragging) {
if (!props.isAgree && !props.isSelfSign) { handleWidgetIdandPopup();
props.setIsAgreeTour && props.setIsAgreeTour(true);
} else {
handleWidgetIdandPopup();
}
} //condition for open contact details popup when sign templet from public signing flow } //condition for open contact details popup when sign templet from public signing flow
else if (props?.uniqueId && props.data?.Id === props?.uniqueId) { else if (props?.uniqueId && props.data?.Id === props?.uniqueId) {
handleWidgetIdandPopup(); handleWidgetIdandPopup();
@@ -391,9 +361,12 @@ function Placeholder(props) {
//function to save date and format on local array onchange date and onclick format //function to save date and format on local array onchange date and onclick format
const handleSaveDate = (data, isDateChange) => { const handleSaveDate = (data, isDateChange) => {
const isSpecialDateFormat =
data?.format &&
["dd-MM-yyyy", "dd.MM.yyyy", "dd/MM/yyyy"].includes(data?.format);
let updateDate = data.date; let updateDate = data.date;
let date; let date;
if (data?.format === "dd-MM-yyyy") { if (isSpecialDateFormat) {
date = isDateChange date = isDateChange
? moment(updateDate).format(changeDateToMomentFormat(data.format)) ? moment(updateDate).format(changeDateToMomentFormat(data.format))
: updateDate; : updateDate;
@@ -859,8 +832,16 @@ function Placeholder(props) {
props.isNeedSign && props.data?.Id !== props?.uniqueId && "0.4", props.isNeedSign && props.data?.Id !== props?.uniqueId && "0.4",
background: handleBackground() background: handleBackground()
}} }}
onDrag={() => { onDrag={(_, d) => {
props.handleTabDrag && props.handleTabDrag(props.pos.key); props.handleTabDrag && props.handleTabDrag(props.pos.key);
props.showGuidelines &&
props.showGuidelines(
true,
d.x,
d.y,
d.node.offsetWidth,
d.node.offsetHeight
);
}} }}
size={{ size={{
width: width:
@@ -881,8 +862,26 @@ function Placeholder(props) {
calculateFont(props.pos.options?.fontSize, true) calculateFont(props.pos.options?.fontSize, true)
} }
maxHeight="auto" maxHeight="auto"
onResizeStart={() => { onResizeStart={(e, dir, ref) => {
props.setIsResize && props.setIsResize(true); props.setIsResize && props.setIsResize(true);
props.showGuidelines &&
props.showGuidelines(
true,
xPos(props.pos, props.isSignYourself),
yPos(props.pos, props.isSignYourself),
ref.offsetWidth,
ref.offsetHeight
);
}}
onResize={(e, dir, ref, delta, position) => {
props.showGuidelines &&
props.showGuidelines(
true,
position.x,
position.y,
ref.offsetWidth,
ref.offsetHeight
);
}} }}
onResizeStop={(e, direction, ref) => { onResizeStop={(e, direction, ref) => {
setTimeout(() => { setTimeout(() => {
@@ -900,6 +899,7 @@ function Placeholder(props) {
props.data && props.data.Id, props.data && props.data.Id,
props.isResize props.isResize
); );
props.showGuidelines && props.showGuidelines(false);
}} }}
onDragStop={(event, dragElement) => { onDragStop={(event, dragElement) => {
props.handleStop && props.handleStop &&
@@ -909,6 +909,9 @@ function Placeholder(props) {
props.data?.Id, props.data?.Id,
props.pos?.key props.pos?.key
); );
props.isDragging &&
props.showGuidelines &&
props.showGuidelines(false);
}} }}
position={{ position={{
x: xPos(props.pos, props.isSignYourself), x: xPos(props.pos, props.isSignYourself),
@@ -945,7 +948,6 @@ function Placeholder(props) {
![radioButtonWidget, "checkbox"].includes(props.pos.type) && ![radioButtonWidget, "checkbox"].includes(props.pos.type) &&
props.pos.key === props?.currWidgetsDetails?.key && <BorderResize /> props.pos.key === props?.currWidgetsDetails?.key && <BorderResize />
)} )}
{/* 1- Show a border if props.pos.key === props?.currWidgetsDetails?.key, indicating the current user's selected widget. {/* 1- Show a border if props.pos.key === props?.currWidgetsDetails?.key, indicating the current user's selected widget.
2- If props.isShowBorder is true, display border for all widgets. 2- If props.isShowBorder is true, display border for all widgets.
3- Use the combination of props?.isAlllowModify and !props?.assignedWidgetId.includes(props.pos.key) to determine when to show border: 3- Use the combination of props?.isAlllowModify and !props?.assignedWidgetId.includes(props.pos.key) to determine when to show border:
@@ -1,4 +1,4 @@
import React, { useEffect, useState, forwardRef, useRef } from "react"; import React, { useEffect, useState, forwardRef } from "react";
import { import {
getMonth, getMonth,
getYear, getYear,
@@ -36,7 +36,6 @@ function PlaceholderType(props) {
const widgetData = const widgetData =
props.pos?.options?.response ?? props.pos?.options?.defaultValue ?? ""; props.pos?.options?.response ?? props.pos?.options?.defaultValue ?? "";
const widgetTypeTranslation = t(`widgets-name.${props?.pos?.type}`); const widgetTypeTranslation = t(`widgets-name.${props?.pos?.type}`);
const inputRef = useRef(null);
const [widgetValue, setwidgetValue] = useState(); const [widgetValue, setwidgetValue] = useState();
const [selectedCheckbox, setSelectedCheckbox] = useState([]); const [selectedCheckbox, setSelectedCheckbox] = useState([]);
const [hint, setHint] = useState(""); const [hint, setHint] = useState("");
@@ -109,7 +108,7 @@ function PlaceholderType(props) {
alt="signature" alt="signature"
draggable="false" draggable="false"
src={props.pos.SignUrl} src={props.pos.SignUrl}
className="w-full h-full select-none-cls " className={`${props.pos.signatureType !== "type" ? "object-contain" : ""} w-full h-full select-none-cls`}
/> />
) : ( ) : (
<div className={widgetCls}> <div className={widgetCls}>
@@ -133,7 +132,7 @@ function PlaceholderType(props) {
alt="stamp" alt="stamp"
draggable="false" draggable="false"
src={props.pos.SignUrl} src={props.pos.SignUrl}
className="w-full h-full select-none-cls" className="w-full h-full select-none-cls object-contain"
/> />
) : ( ) : (
<div className={widgetCls}> <div className={widgetCls}>
@@ -158,7 +157,7 @@ function PlaceholderType(props) {
const checkBoxWrapperClass = `flex items-start ${ const checkBoxWrapperClass = `flex items-start ${
checkBoxLayout === "horizontal" checkBoxLayout === "horizontal"
? `flex-row flex-wrap ${isMultipleCheckbox ? "gap-x-2" : ""}` ? `flex-row flex-wrap ${isMultipleCheckbox ? "gap-x-2" : ""}`
: `flex-col ${isMultipleCheckbox ? "gap-y-[5px]" : ""}` : `flex-col ${isMultipleCheckbox ? "gap-y-[3px]" : ""}`
}`; // Using gap-y-1 for consistency, adjust if needed }`; // Using gap-y-1 for consistency, adjust if needed
return ( return (
@@ -193,7 +192,6 @@ function PlaceholderType(props) {
case textInputWidget: case textInputWidget:
return props.isSignYourself || iswidgetEnable ? ( return props.isSignYourself || iswidgetEnable ? (
<textarea <textarea
ref={inputRef}
placeholder={hint || t("widgets-name.text")} placeholder={hint || t("widgets-name.text")}
rows={1} rows={1}
value={widgetValue} value={widgetValue}
@@ -255,7 +253,7 @@ function PlaceholderType(props) {
alt="initials" alt="initials"
draggable="false" draggable="false"
src={props.pos.SignUrl} src={props.pos.SignUrl}
className="w-full h-full select-none-cls" className={`${props.pos.signatureType !== "type" ? "object-contain" : ""} w-full h-full select-none-cls`}
/> />
) : ( ) : (
<div className={widgetCls}> <div className={widgetCls}>
@@ -277,7 +275,6 @@ function PlaceholderType(props) {
return iswidgetEnable ? ( return iswidgetEnable ? (
<textarea <textarea
readOnly readOnly
ref={inputRef}
placeholder={hint || widgetTypeTranslation} placeholder={hint || widgetTypeTranslation}
rows={1} rows={1}
value={widgetValue} value={widgetValue}
@@ -300,7 +297,6 @@ function PlaceholderType(props) {
return iswidgetEnable ? ( return iswidgetEnable ? (
<textarea <textarea
readOnly readOnly
ref={inputRef}
placeholder={hint || widgetTypeTranslation} placeholder={hint || widgetTypeTranslation}
rows={1} rows={1}
value={widgetValue} value={widgetValue}
@@ -323,7 +319,6 @@ function PlaceholderType(props) {
return iswidgetEnable ? ( return iswidgetEnable ? (
<textarea <textarea
readOnly readOnly
ref={inputRef}
placeholder={hint || widgetTypeTranslation} placeholder={hint || widgetTypeTranslation}
rows={1} rows={1}
value={widgetValue} value={widgetValue}
@@ -407,7 +402,7 @@ function PlaceholderType(props) {
alt="image" alt="image"
draggable="false" draggable="false"
src={props.pos.SignUrl} src={props.pos.SignUrl}
className="w-full h-full select-none-cls" className="w-full h-full select-none-cls object-contain"
/> />
) : ( ) : (
<div className={widgetCls}> <div className={widgetCls}>
@@ -429,7 +424,6 @@ function PlaceholderType(props) {
return iswidgetEnable ? ( return iswidgetEnable ? (
<textarea <textarea
readOnly readOnly
ref={inputRef}
placeholder={hint || widgetTypeTranslation} placeholder={hint || widgetTypeTranslation}
rows={1} rows={1}
value={widgetValue} value={widgetValue}
@@ -453,7 +447,7 @@ function PlaceholderType(props) {
const isOnlyOneBtn = props.pos.options?.values?.length > 0 ? true : false; const isOnlyOneBtn = props.pos.options?.values?.length > 0 ? true : false;
const radioWrapperClass = `flex items-start ${ const radioWrapperClass = `flex items-start ${
radioLayout === "horizontal" radioLayout === "horizontal"
? `flex-row flex-wrap ${isOnlyOneBtn ? "gap-x-2" : ""}` ? `flex-row flex-wrap ${isOnlyOneBtn ? "gap-x-[10px]" : ""}`
: `flex-col ${isOnlyOneBtn ? "gap-y-[5px]" : ""}` : `flex-col ${isOnlyOneBtn ? "gap-y-[5px]" : ""}`
}`; // Using gap-y-1 for consistency, adjust if needed }`; // Using gap-y-1 for consistency, adjust if needed
return ( return (
@@ -82,7 +82,7 @@ const RecipientList = (props) => {
}; };
return ( return (
<> <>
{props.signersdata.length > 0 && {props.signersdata?.length > 0 &&
props.signersdata.map((obj, ind) => { props.signersdata.map((obj, ind) => {
return ( return (
<div <div
@@ -209,8 +209,8 @@ function RenderAllPdfPage(props) {
onChange={handleFileUpload} onChange={handleFileUpload}
/> />
<i className="fa-light fa-plus text-gray-500"></i> <i className="fa-light fa-plus text-gray-500"></i>
<span className="text-xs lg:text-sm text-base-content "> <span className="text-xs lg:text-sm text-base-content">
Add pages {t("add-pages")}
</span> </span>
</button> </button>
)} )}
+66 -6
View File
@@ -1,4 +1,4 @@
import React, { useState } from "react"; import React, { useState, useRef } from "react";
import RSC from "react-scrollbars-custom"; import RSC from "react-scrollbars-custom";
import { Document, Page } from "react-pdf"; import { Document, Page } from "react-pdf";
import { import {
@@ -11,13 +11,45 @@ import {
import Placeholder from "./Placeholder"; import Placeholder from "./Placeholder";
import Alert from "../../primitives/Alert"; import Alert from "../../primitives/Alert";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import usePdfPinchZoom from "../../hook/usePdfPinchZoom";
function RenderPdf(props) { function RenderPdf(props) {
const { t } = useTranslation(); const { t } = useTranslation();
const [scaledHeight, setScaledHeight] = useState(); const [scaledHeight, setScaledHeight] = useState();
const [guideline, setGuideline] = useState({
show: false,
x1: 0,
x2: 0,
y1: 0,
y2: 0
});
//check isGuestSigner is present in local if yes than handle login flow header in mobile view //check isGuestSigner is present in local if yes than handle login flow header in mobile view
const isGuestSigner = localStorage.getItem("isGuestSigner"); const isGuestSigner = localStorage.getItem("isGuestSigner");
const pdfContainerRef = useRef(null);
// enable pinch to zoom only on actual pdf wrapper
usePdfPinchZoom(
pdfContainerRef,
props.scale,
props.setScale,
props.setZoomPercent
);
const handleGuideline = (isShow, x = 0, y = 0, width = 0, height = 0) => {
if (isShow) {
setGuideline({
show: true,
x1: x,
x2: x + width,
y1: y,
y2: y + height
});
} else {
setGuideline({ show: false, x1: 0, x2: 0, y1: 0, y2: 0 });
}
};
// handle signature block width and height according to screen // handle signature block width and height according to screen
const posWidth = (pos, signYourself) => { const posWidth = (pos, signYourself) => {
const containerScale = getContainerScale( const containerScale = getContainerScale(
@@ -115,6 +147,7 @@ function RenderPdf(props) {
isSignYourself={false} isSignYourself={false}
posWidth={posWidth} posWidth={posWidth}
posHeight={posHeight} posHeight={posHeight}
showGuidelines={handleGuideline}
isDragging={props.isDragging} isDragging={props.isDragging}
pdfDetails={props.pdfDetails} pdfDetails={props.pdfDetails}
unSignedWidgetId={props.unSignedWidgetId} unSignedWidgetId={props.unSignedWidgetId}
@@ -127,8 +160,6 @@ function RenderPdf(props) {
ispublicTemplate={props.ispublicTemplate} ispublicTemplate={props.ispublicTemplate}
handleUserDetails={props.handleUserDetails} handleUserDetails={props.handleUserDetails}
isResize={props.isResize} isResize={props.isResize}
setIsAgreeTour={props.setIsAgreeTour}
isAgree={props.isAgree}
handleTabDrag={props.handleTabDrag} handleTabDrag={props.handleTabDrag}
handleStop={props.handleStop} handleStop={props.handleStop}
setUniqueId={props.setUniqueId} setUniqueId={props.setUniqueId}
@@ -204,14 +235,17 @@ function RenderPdf(props) {
data-tut={isMobile ? "reactourForth" : undefined} data-tut={isMobile ? "reactourForth" : undefined}
className={ className={
isMobile isMobile
? `${isGuestSigner ? "30px" : ""} border-[0.1px] border-[#ebe8e8] overflow-x-auto` ? `${isGuestSigner ? "30px" : ""} border-[0.1px] border-[#ebe8e8] overflow-x-auto relative`
: "" : "relative"
} }
style={{ style={{
width: width:
props.containerWH?.width && props.containerWH?.width * props.scale props.containerWH?.width && props.containerWH?.width * props.scale
}} }}
ref={props.drop} ref={(node) => {
pdfContainerRef.current = node;
props.drop && props.drop(node);
}}
id="container" id="container"
> >
{props.pdfLoad !== false && {props.pdfLoad !== false &&
@@ -258,6 +292,7 @@ function RenderPdf(props) {
isSignYourself={false} isSignYourself={false}
posWidth={posWidth} posWidth={posWidth}
posHeight={posHeight} posHeight={posHeight}
showGuidelines={handleGuideline}
isDragging={props.isDragging} isDragging={props.isDragging}
setIsValidate={props.setIsValidate} setIsValidate={props.setIsValidate}
setIsRadio={props.setIsRadio} setIsRadio={props.setIsRadio}
@@ -325,6 +360,7 @@ function RenderPdf(props) {
isSignYourself={true} isSignYourself={true}
posWidth={posWidth} posWidth={posWidth}
posHeight={posHeight} posHeight={posHeight}
showGuidelines={handleGuideline}
pdfDetails={props.pdfDetails[0]} pdfDetails={props.pdfDetails[0]}
isDragging={props.isDragging} isDragging={props.isDragging}
setIsCheckbox={props.setIsCheckbox} setIsCheckbox={props.setIsCheckbox}
@@ -389,6 +425,30 @@ function RenderPdf(props) {
}} }}
/> />
</Document> </Document>
{guideline.show && (
<>
{/* top guide */}
<div
className="absolute pointer-events-none z-[1000] left-0 w-full border-t-[1px] border-dashed border-[#3b82f6]"
style={{ top: guideline.y1 }}
/>
{/* bottom guide */}
<div
className="absolute pointer-events-none z-[1000] left-0 w-full border-t-[1px] border-dashed border-[#3b82f6]"
style={{ top: guideline.y2 }}
/>
{/* left guide */}
<div
className="absolute pointer-events-none z-[1000] top-0 h-full border-l-[1px] border-dashed border-[#3b82f6]"
style={{ left: guideline.x1 }}
/>
{/* right guide */}
<div
className="absolute pointer-events-none z-[1000] top-0 h-full border-l-[1px] border-dashed border-[#3b82f6]"
style={{ left: guideline.x2 }}
/>
</>
)}
</div> </div>
</RSC> </RSC>
</> </>
@@ -17,7 +17,9 @@ import {
textInputWidget, textInputWidget,
cellsWidget, cellsWidget,
textWidget, textWidget,
years years,
convertTextToImg,
convertJpegToPng
} from "../../constant/Utils"; } from "../../constant/Utils";
import CellsWidget from "./CellsWidget"; import CellsWidget from "./CellsWidget";
import DatePicker from "react-datepicker"; import DatePicker from "react-datepicker";
@@ -67,6 +69,11 @@ const fontOptions = [
{ value: "Delicious Handrawn" } { value: "Delicious Handrawn" }
// Add more font options as needed // Add more font options as needed
]; ];
const allColor = ["blue", "red", "black"];
const textInputcls =
"op-input op-input-bordered op-input-sm focus:outline-none text-base-content hover:border-base-content w-full text-xs";
const isTabCls = "bg-[#002864] text-white rounded-[15px] px-[10px] py-[4px]";
function WidgetsValueModal(props) { function WidgetsValueModal(props) {
const dispatch = useDispatch(); const dispatch = useDispatch();
const saveSignCheckbox = useSelector( const saveSignCheckbox = useSelector(
@@ -104,7 +111,6 @@ function WidgetsValueModal(props) {
const [signature, setSignature] = useState(); const [signature, setSignature] = useState();
const [isImageSelect, setIsImageSelect] = useState(false); const [isImageSelect, setIsImageSelect] = useState(false);
const [isFinish, setIsFinish] = useState(false); const [isFinish, setIsFinish] = useState(false);
const [imgWH, setImgWH] = useState({});
const [fontSelect, setFontSelect] = useState(fontOptions[0].value); const [fontSelect, setFontSelect] = useState(fontOptions[0].value);
const [isSavedSign, setIsSavedSign] = useState(false); const [isSavedSign, setIsSavedSign] = useState(false);
const [isAutoSign, setIsAutoSign] = useState(false); const [isAutoSign, setIsAutoSign] = useState(false);
@@ -167,10 +173,7 @@ function WidgetsValueModal(props) {
) )
: new Date() : new Date()
); );
const allColor = ["blue", "red", "black"];
const textInputcls =
"op-input op-input-bordered op-input-sm focus:outline-none text-base-content hover:border-base-content w-full text-xs";
const isTabCls = "bg-[#002864] text-white rounded-[15px] px-[10px] py-[4px]";
useEffect(() => { useEffect(() => {
if ( if (
["name", "email", "job title", "company", textInputWidget].includes( ["name", "email", "job title", "company", textInputWidget].includes(
@@ -187,6 +190,8 @@ function WidgetsValueModal(props) {
} else { } else {
setHint(currWidgetsDetails?.type); setHint(currWidgetsDetails?.type);
} }
} else if (["signature", "initials"].includes(currWidgetsDetails?.type)) {
setIsTab(currWidgetsDetails?.signatureType);
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [currWidgetsDetails]); // Added currWidgetsDetails to dependency array for reset logic }, [currWidgetsDetails]); // Added currWidgetsDetails to dependency array for reset logic
@@ -252,11 +257,11 @@ function WidgetsValueModal(props) {
setOriginalImage(null); // Reset original image when a new file is selected setOriginalImage(null); // Reset original image when a new file is selected
setRemoveBgEnabled(false); // Optionally reset toggle setRemoveBgEnabled(false); // Optionally reset toggle
// Ensure compressedFileSize calls setImage, which then triggers the useEffect for BG removal. // Ensure compressedFileSize calls setImage, which then triggers the useEffect for BG removal.
compressedFileSize(file, setImgWH, setImage); compressedFileSize(file, setImage);
} }
}; };
//function is used to save image,stamp widgets data //function is used to save image,stamp widgets data
const handleSaveImage = () => { const handleSaveImage = (signatureType) => {
const widgetsType = currWidgetsDetails?.type; const widgetsType = currWidgetsDetails?.type;
//`isApplyAll` is used when user edit stamp then updated signature apply all existing drawn signatures //`isApplyAll` is used when user edit stamp then updated signature apply all existing drawn signatures
const isApplyAll = true; const isApplyAll = true;
@@ -273,10 +278,10 @@ function WidgetsValueModal(props) {
// Get updated placeholder list // Get updated placeholder list
const updatedPlaceholders = onSaveImage( const updatedPlaceholders = onSaveImage(
signatureType,
signer.placeHolder, signer.placeHolder,
index, index,
currWidgetsDetails?.key, currWidgetsDetails?.key,
imgWH,
image, image,
isAutoSign, isAutoSign,
widgetsType, widgetsType,
@@ -294,11 +299,13 @@ function WidgetsValueModal(props) {
return object.pageNumber === pageNumber; return object.pageNumber === pageNumber;
}); });
const getImage = onSaveImage( const getImage = onSaveImage(
signatureType,
props?.xyPosition, props?.xyPosition,
index, index,
currWidgetsDetails?.key, currWidgetsDetails?.key,
imgWH, image,
image false,
widgetsType
); );
setXyPosition(getImage); setXyPosition(getImage);
} }
@@ -306,36 +313,28 @@ function WidgetsValueModal(props) {
}; };
//function is used to save draw type or initial type signature //function is used to save draw type or initial type signature
const handleSaveSignature = ( const handleSaveSignature = async (
type, signType,
isDefaultSign, isDefaultSign,
width, width,
height, height,
typedSignature typedSignature
) => { ) => {
//get current click widget type // get current click widget type
const widgetsType = currWidgetsDetails?.type; const widgetsType = currWidgetsDetails?.type;
//if there are any width and height then it will typed signature // if there are any width and height then it will typed signature
const isTypeText = width && height ? true : false; const isTypeText = width && height ? true : false;
//final getting signature url via default select sign/default initial sign/self draw sign // final getting signature url via default select sign/default initial sign/self draw sign
const signatureImg = isDefaultSign const signUrl = isDefaultSign
? isDefaultSign === "initials" ? isDefaultSign === "initials"
? myInitial ? await convertJpegToPng(myInitial, "myinitials") // default initials set through my signature tab
: defaultSignImg : await convertJpegToPng(defaultSignImg, "mysign") // default signature set through my signature tab
: signature; : signature; // signature done by user thorugh draw, upload, typed
const signatureImg = signUrl;
let imgWH = { width: width ? width : "", height: height ? height : "" }; let imgWH = { width: width ? width : "", height: height ? height : "" };
setIsImageSelect(false); setIsImageSelect(false);
setImage(); setImage();
//set default signature image width and height // `isApplyAll` is used when user edit signature/initial then updated signature apply all existing drawn signatures
if (isDefaultSign) {
const img = new Image();
img.src = defaultSignImg;
if (img.complete) {
imgWH = { width: img.width, height: img.height };
}
}
//`isApplyAll` is used when user edit signature/initial then updated signature apply all existing drawn signatures
const isApplyAll = true; const isApplyAll = true;
if (uniqueId) { if (uniqueId) {
setXyPosition((prevState) => setXyPosition((prevState) =>
@@ -345,20 +344,20 @@ function WidgetsValueModal(props) {
const placeholderIndex = signer.placeHolder.findIndex( const placeholderIndex = signer.placeHolder.findIndex(
(x) => x.pageNumber === pageNumber (x) => x.pageNumber === pageNumber
); );
const updatedPlaceholders = onSaveSign( const updatedPlaceholders = onSaveSign(
type, signType,
signer.placeHolder, signer.placeHolder,
placeholderIndex, placeholderIndex,
currWidgetsDetails?.key, currWidgetsDetails?.key,
signatureImg, signatureImg,
imgWH, imgWH,
isDefaultSign,
isTypeText, isTypeText,
typedSignature, typedSignature,
isAutoSign, isAutoSign,
widgetsType, widgetsType,
isApplyAll isApplyAll,
fontSelect,
penColor
); );
return { return {
...signer, ...signer,
@@ -371,15 +370,16 @@ function WidgetsValueModal(props) {
return object.pageNumber === pageNumber; return object.pageNumber === pageNumber;
}); });
const getUpdatePosition = onSaveSign( const getUpdatePosition = onSaveSign(
type, signType,
props?.xyPosition, props?.xyPosition,
index, index,
currWidgetsDetails?.key, currWidgetsDetails?.key,
signatureImg, signatureImg,
imgWH, imgWH,
isDefaultSign,
isTypeText, isTypeText,
typedSignature typedSignature,
false,
widgetsType
); );
setXyPosition(getUpdatePosition); setXyPosition(getUpdatePosition);
} }
@@ -405,7 +405,9 @@ function WidgetsValueModal(props) {
if (getIndex !== -1) { if (getIndex !== -1) {
setIsSignTypes(true); setIsSignTypes(true);
const tab = signatureTypes?.[getIndex].name; const tab = signatureTypes?.[getIndex].name;
if (tab === "draw") { if (currWidgetsDetails?.signatureType === "type") {
setIsTab("type");
} else if (tab === "draw") {
setIsTab("draw"); setIsTab("draw");
} else if (tab === "upload") { } else if (tab === "upload") {
setIsImageSelect(true); setIsImageSelect(true);
@@ -525,7 +527,7 @@ function WidgetsValueModal(props) {
} }
}; };
// `handlesavesign` is used to save signaute, initials, stamp as a default // `handlesavesign` is used to save signature, initials, stamp as a default
const handleSaveSign = async () => { const handleSaveSign = async () => {
if (signature || image?.src) { if (signature || image?.src) {
setIsLoader(true); setIsLoader(true);
@@ -546,7 +548,7 @@ function WidgetsValueModal(props) {
objectId: User?.id objectId: User?.id
}; };
if (imageUrl) { if (imageUrl) {
// below code is used to save or update default signaute, initials, stamp // below code is used to save or update default signature, initials, stamp
try { try {
const signCls = new Parse.Object("contracts_Signature"); const signCls = new Parse.Object("contracts_Signature");
if (props?.saveSignCheckbox?.signId) { if (props?.saveSignCheckbox?.signId) {
@@ -619,7 +621,7 @@ function WidgetsValueModal(props) {
if (isTab === "type") { if (isTab === "type") {
setSignature(""); setSignature("");
handleSaveSignature( handleSaveSignature(
null, "type",
false, false,
!currWidgetsDetails?.type === "initials" && textWidth > 150 !currWidgetsDetails?.type === "initials" && textWidth > 150
? 150 ? 150
@@ -632,13 +634,14 @@ function WidgetsValueModal(props) {
} else { } else {
setSignature(""); setSignature("");
canvasRef?.current?.clear(); canvasRef?.current?.clear();
handleSaveSignature(isTab); const tab = isTab === "uploadImage" ? "image" : isTab;
handleSaveSignature(tab);
} }
} }
setPenColor("blue"); setPenColor("blue");
} else { } else {
setSignature(""); setSignature("");
handleSaveImage(); handleSaveImage("image");
} }
setIsImageSelect(false); setIsImageSelect(false);
setIsDefaultSign(false); setIsDefaultSign(false);
@@ -694,91 +697,43 @@ function WidgetsValueModal(props) {
} }
} }
if (isTab === "type") { if (isTab === "type") {
//trim user name or typed name value to show in initial signature
const trimmedName = typedSignature const trimmedName = typedSignature
? typedSignature?.trim() ? typedSignature?.trim()
: currentUserName?.trim(); : currentUserName?.trim();
//get full name of user
const fullUserName = typedSignature || currentUserName;
const firstCharacter = trimmedName?.charAt(0); const firstCharacter = trimmedName?.charAt(0);
const userName = const userName =
currWidgetsDetails?.type === "initials" currWidgetsDetails?.type === "initials" ? firstCharacter : fullUserName;
? firstCharacter
: typedSignature;
const signatureValue = currWidgetsDetails?.typeSignature; const signatureValue = currWidgetsDetails?.typeSignature;
setTypedSignature(signatureValue || userName || ""); setTypedSignature(signatureValue || userName || "");
convertToImg(fontSelect, userName); convertToImg(fontSelect, signatureValue || userName || "");
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [isTab]); }, [isTab]);
// function for convert input text value in image // function for convert input text value in image
const convertToImg = async (fontStyle, text, color) => { const convertToImg = async (fontStyle, text, color) => {
// 1) Read the max widget dimensions: const maxWidth = currWidgetsDetails?.Width;
const maxWidth = currWidgetsDetails.Width; // e.g. 150 px const maxHeight = currWidgetsDetails?.Height;
const maxHeight = currWidgetsDetails.Height; // e.g. 40 px const widgetDims = { Width: maxWidth, Height: maxHeight };
const fontFamily = fontStyle || fontSelect || "Fasthand";
// 2) Pick a “baseline” font size for measurement:
const baselineFontSizePx = 40;
const chosenFontFamily = fontStyle || fontSelect || "Fasthand";
const fillColor = color || penColor; const fillColor = color || penColor;
const dataUrl = convertTextToImg(fontFamily, text, fillColor, widgetDims);
// 3) Create a temporary <span> (hidden) to measure the text at 40px:
const span = document.createElement("span");
span.textContent = text;
span.style.font = `${baselineFontSizePx}px ${chosenFontFamily}`;
span.style.visibility = "hidden"; // keep it in the DOM so offsetWidth/Height works
span.style.whiteSpace = "nowrap"; // so we measure a single line
document.body.appendChild(span);
// Measured size at 40px:
const measuredWidth = span.offsetWidth;
const measuredHeight = span.offsetHeight;
document.body.removeChild(span);
// 4) Compute uniform scale so that 40pxsized text fits inside (maxWidth × maxHeight):
const scaleX = maxWidth / measuredWidth;
const scaleY = maxHeight / measuredHeight;
const scale = Math.min(scaleX, scaleY, 1); // never scale up beyond 1
// 5) Final text size in **CSS px**:
const finalFontSizePx = baselineFontSizePx * scale;
// 6) Create a <canvas> that is ALWAYS maxWidth × maxHeight in **CSS px**,
// but use devicePixelRatio for sharpness.
const pixelRatio = window.devicePixelRatio || 1;
const canvas = document.createElement("canvas");
// ★ Instead of using `finalTextWidth/Height`, force it to be the max box:
canvas.width = Math.ceil(maxWidth * pixelRatio);
canvas.height = Math.ceil(maxHeight * pixelRatio);
const ctx = canvas.getContext("2d");
ctx.scale(pixelRatio, pixelRatio);
// 7) Draw the text **centered** inside the full maxWidth×maxHeight box:
ctx.font = `${finalFontSizePx}px ${chosenFontFamily}`;
ctx.fillStyle = fillColor;
ctx.textAlign = "center";
ctx.textBaseline = "middle";
// ★ Center = (maxWidth/2, maxHeight/2):
const centerX = maxWidth / 2;
const centerY = maxHeight / 2;
ctx.fillText(text, centerX, centerY);
// 8) Export to a PNG data-URL:
const dataUrl = canvas.toDataURL("image/png");
setSignature(dataUrl); setSignature(dataUrl);
setTextWidth(maxWidth);
setTextHeight(maxHeight);
}; };
const PenColorComponent = (props) => { const PenColorComponent = () => {
return ( return (
<div className="flex flex-row items-center m-[5px] gap-3"> <div className="flex flex-row items-center m-[5px] gap-3">
<span className="text-base-content">Options</span> <span className="text-base-content">{t("options")}</span>
{allColor.map((data, key) => { {allColor.map((data, key) => {
return ( return (
<i <i
key={key} key={key}
onClick={() => { onClick={() => {
props?.convertToImg && convertToImg(fontSelect, typedSignature, data);
props?.convertToImg(fontSelect, typedSignature, data);
setPenColor(allColor[key]); setPenColor(allColor[key]);
}} }}
className={`border-b-[2px] cursor-pointer ${key === 0 && penColor === "blue" ? "border-blue-600" : key === 1 && penColor === "red" ? "border-red-500" : key === 2 && penColor === "black" ? "border-black" : "border-white"} text-[${data}] text-[16px] fa-light fa-pen-nib`} className={`border-b-[2px] cursor-pointer ${key === 0 && penColor === "blue" ? "border-blue-600" : key === 1 && penColor === "red" ? "border-red-500" : key === 2 && penColor === "black" ? "border-black" : "border-white"} text-[${data}] text-[16px] fa-light fa-pen-nib`}
@@ -789,17 +744,6 @@ function WidgetsValueModal(props) {
); );
}; };
// `handleCancelBtn` function trigger when user click on cross button
const handleCancelBtn = () => {
setPenColor("blue");
setIsImageSelect(false);
setIsDefaultSign(false);
setImage(null);
setOriginalImage(null); // Add this
setRemoveBgEnabled(false); // Add this
handleTab();
};
// Effect to store original image and apply initial BG removal if enabled // Effect to store original image and apply initial BG removal if enabled
useEffect(() => { useEffect(() => {
if ( if (
@@ -902,7 +846,7 @@ function WidgetsValueModal(props) {
checked={isSavedSign} checked={isSavedSign}
onChange={(e) => setIsSavedSign(e.target.checked)} onChange={(e) => setIsSavedSign(e.target.checked)}
/> />
{t("save")} {currWidgetsDetails?.type} {t("save")} {t(`widgets-name.${currWidgetsDetails?.type}`)}
</label> </label>
); );
//function for set checked and unchecked value of checkbox //function for set checked and unchecked value of checkbox
@@ -1048,10 +992,28 @@ function WidgetsValueModal(props) {
currWidgetsDetails?.type !== "image" && ( currWidgetsDetails?.type !== "image" && (
<div className="text-base-content rounded-[4px] tabWidth"> <div className="text-base-content rounded-[4px] tabWidth">
<div className="ml-3 flex justify-start gap-4 text-[11px] md:text-base my-[3px]"> <div className="ml-3 flex justify-start gap-4 text-[11px] md:text-base my-[3px]">
<> {currWidgetsDetails?.type !== "initials" &&
{currWidgetsDetails?.type !== "initials" && defaultSignImg &&
defaultSignImg && isTabEnabled("default") ? (
isTabEnabled("default") ? ( <div>
<span
onClick={() => {
setIsDefaultSign(true);
setIsImageSelect(true);
setIsTab("mysignature");
setImage();
}}
className={`${
isTab === "mysignature" && `${isTabCls}`
} ml-[2px] cursor-pointer`}
>
{t("my-signature")}
</span>
</div>
) : (
currWidgetsDetails?.type === "initials" &&
myInitial &&
isTabEnabled("default") && (
<div> <div>
<span <span
onClick={() => { onClick={() => {
@@ -1062,84 +1024,64 @@ function WidgetsValueModal(props) {
}} }}
className={`${ className={`${
isTab === "mysignature" && `${isTabCls}` isTab === "mysignature" && `${isTabCls}`
} ml-[2px] cursor-pointer`}
>
{t("my-signature")}
</span>
</div>
) : (
currWidgetsDetails?.type === "initials" &&
myInitial &&
isTabEnabled("default") && (
<div>
<span
onClick={() => {
setIsDefaultSign(true);
setIsImageSelect(true);
setIsTab("mysignature");
setImage();
}}
className={`${
isTab === "mysignature" && `${isTabCls}`
} ml-[2px] cursor-pointer`}
>
{t("my-initials")}
</span>
</div>
)
)}
{isTabEnabled("draw") && (
<div>
<span
onClick={() => {
setIsDefaultSign(false);
setIsImageSelect(false);
setIsTab("draw");
setImage();
setSignature("");
}}
className={`${
isTab === "draw" && `${isTabCls}`
} ml-[2px] cursor-pointer`} } ml-[2px] cursor-pointer`}
> >
{t("draw")} {t("my-initials")}
</span> </span>
</div> </div>
)} )
{isTabEnabled("upload") && ( )}
<div> {isTabEnabled("draw") && (
<span <div>
onClick={() => { <span
setIsDefaultSign(false); onClick={() => {
setIsImageSelect(true); setIsDefaultSign(false);
setIsTab("uploadImage"); setIsImageSelect(false);
}} setIsTab("draw");
className={`${ setImage();
isTab === "uploadImage" && `${isTabCls}` setSignature("");
} ml-[2px] cursor-pointer`} }}
> className={`${
{t("upload-image")} isTab === "draw" && `${isTabCls}`
</span> } ml-[2px] cursor-pointer`}
</div> >
)} {t("draw")}
{isTabEnabled("typed") && ( </span>
<div> </div>
<span )}
onClick={() => { {isTabEnabled("upload") && (
setIsDefaultSign(false); <div>
setIsImageSelect(false); <span
setIsTab("type"); onClick={() => {
setImage(); setIsDefaultSign(false);
}} setIsImageSelect(true);
className={`${ setIsTab("uploadImage");
isTab === "type" && `${isTabCls}` }}
} ml-[2px] cursor-pointer`} className={`${
> isTab === "uploadImage" && `${isTabCls}`
{t("type")} } ml-[2px] cursor-pointer`}
</span> >
</div> {t("upload-image")}
)} </span>
</> </div>
)}
{isTabEnabled("typed") && (
<div>
<span
onClick={() => {
setIsDefaultSign(false);
setIsImageSelect(false);
setIsTab("type");
setImage();
}}
className={`${
isTab === "type" && `${isTabCls}`
} ml-[2px] cursor-pointer`}
>
{t("type")}
</span>
</div>
)}
</div> </div>
</div> </div>
)} )}
@@ -1174,7 +1116,7 @@ function WidgetsValueModal(props) {
</div> </div>
</div> </div>
{/* Standalone autoSignAll for "My Signature/Initials" (isDefaultSign) if conditions met */} {/* Standalone autoSignAll for "My Signature/Initials" (isDefaultSign) if conditions met */}
{setIsAutoSign && uniqueId && ( {uniqueId && (
<div className="flex justify-center my-2"> <div className="flex justify-center my-2">
{autoSignAll} {autoSignAll}
</div> </div>
@@ -1203,7 +1145,7 @@ function WidgetsValueModal(props) {
</div> </div>
</div> </div>
{/* Standalone autoSignAll for "My Signature/Initials" (isDefaultSign) if conditions met */} {/* Standalone autoSignAll for "My Signature/Initials" (isDefaultSign) if conditions met */}
{setIsAutoSign && uniqueId && ( {uniqueId && (
<div className="flex justify-center my-2"> <div className="flex justify-center my-2">
{autoSignAll} {autoSignAll}
</div> </div>
@@ -1228,8 +1170,10 @@ function WidgetsValueModal(props) {
ref={imageRef} ref={imageRef}
hidden hidden
/> />
<i className="fa-light fa-cloud-upload-alt uploadImgLogo"></i> <i className="fa-light fa-cloud-upload-alt uploadImgLogo text-base-content"></i>
<div className="text-[10px]">{t("upload")}</div> <div className="text-[10px] text-base-content">
{t("upload")}
</div>
</div> </div>
</div> </div>
) : ( ) : (
@@ -1248,14 +1192,14 @@ function WidgetsValueModal(props) {
</div> </div>
</div> </div>
{/* Shared container for autoSignAll and removeBackground when image is displayed for upload/stamp/image */} {/* Shared container for autoSignAll and removeBackground when image is displayed for upload/stamp/image */}
{((setIsAutoSign && uniqueId) || {(uniqueId ||
(image && (image &&
(isImageSelect || (isImageSelect ||
["image", "stamp"].includes( ["image", "stamp"].includes(
currWidgetsDetails?.type currWidgetsDetails?.type
)))) && ( )))) && (
<div className="flex justify-center items-center gap-x-2 my-2"> <div className="flex justify-center items-center gap-x-2 my-2">
{setIsAutoSign && uniqueId && autoSignAll} {uniqueId && autoSignAll}
{image && {image &&
(isImageSelect || (isImageSelect ||
["image", "stamp"].includes( ["image", "stamp"].includes(
@@ -1345,7 +1289,7 @@ function WidgetsValueModal(props) {
</div> </div>
<div className="flex flex-row ml-1 mt-2 gap-x-3 text-base-content"> <div className="flex flex-row ml-1 mt-2 gap-x-3 text-base-content">
{/* Standalone autoSignAll for "Type" tab if conditions met */} {/* Standalone autoSignAll for "Type" tab if conditions met */}
{setIsAutoSign && uniqueId && ( {uniqueId && (
<div className="flex justify-start my-1"> <div className="flex justify-start my-1">
{autoSignAll} {autoSignAll}
</div> </div>
@@ -1375,13 +1319,12 @@ function WidgetsValueModal(props) {
dotSize={1} dotSize={1}
/> />
</div> </div>
<div className="flex flex-row justify-between mt-[10px]"> <div className="flex flex-row justify-between mt-[10px]">
<PenColorComponent /> <PenColorComponent />
</div> </div>
<div className="flex flex-row ml-1 mt-1 gap-x-3 text-base-content"> <div className="flex flex-row ml-1 mt-1 gap-x-3 text-base-content">
{/* Standalone autoSignAll for "Draw" tab if conditions met */} {/* Standalone autoSignAll for "Draw" tab if conditions met */}
{setIsAutoSign && uniqueId && ( {uniqueId && (
<div className="flex justify-start my-1"> <div className="flex justify-start my-1">
{autoSignAll} {autoSignAll}
</div> </div>
@@ -1778,6 +1721,7 @@ function WidgetsValueModal(props) {
break; break;
} }
}; };
// console.log("currwidgetdetails ", currWidgetsDetails )
//function too use on click on next/finish button then update modal UI according to current widgets //function too use on click on next/finish button then update modal UI according to current widgets
const handleClickOnNext = (isFinishDoc) => { const handleClickOnNext = (isFinishDoc) => {
if ( if (
@@ -2038,7 +1982,7 @@ function WidgetsValueModal(props) {
handleClickOnNext(isFinishDoc); handleClickOnNext(isFinishDoc);
}} }}
> >
{t("finish")} {t("done")}
</button> </button>
) : ( ) : (
<button <button
@@ -18,16 +18,16 @@ const Menu = ({ item, isOpen, closeSidebar }) => {
className={({ isActive }) => className={({ isActive }) =>
`${ `${
isActive ? " bg-base-300 text-base-content" : "" isActive ? " bg-base-300 text-base-content" : ""
} flex items-center justify-start text-left p-3 lg:p-4 text-base-content hover:text-base-content focus:bg-base-300 hover:bg-base-300 hover:no-underline focus:outline-none` } flex gap-x-5 items-center justify-start text-left p-3 text-base-content hover:text-base-content focus:bg-base-300 hover:bg-base-300 hover:no-underline focus:outline-none`
} }
onClick={closeSidebar} onClick={closeSidebar}
tabIndex={isOpen ? 0 : -1} tabIndex={isOpen ? 0 : -1}
role="menuitem" role="menuitem"
> >
<span className="w-[20px] h-[20px] flex justify-center"> <span className="w-[20px] h-[20px] flex justify-center">
<i className={`${item.icon} text-[18px]`} aria-hidden="true"></i> <i className={`${item.icon} text-[20px]`} aria-hidden="true"></i>
</span> </span>
<span className="ml-3 lg:ml-4"> <span className="flex items-center mb-0.5">
{t(`sidebar.${item.title}`, { appName: drivename })} {t(`sidebar.${item.title}`, { appName: drivename })}
</span> </span>
</NavLink> </NavLink>
@@ -12,16 +12,16 @@ const Submenu = ({ item, closeSidebar, toggleSubmenu, submenuOpen }) => {
<li role="none" className="my-0.5"> <li role="none" className="my-0.5">
<button <button
onClick={() => toggleSubmenu(item.title)} onClick={() => toggleSubmenu(item.title)}
className="flex items-center justify-start text-left p-3 lg:p-4 text-base-content hover:text-base-content focus:bg-base-300 hover:bg-base-300 hover:no-underline focus:outline-none " className="flex gap-x-5 items-center justify-start text-left p-3 text-base-content hover:text-base-content focus:bg-base-300 hover:bg-base-300 hover:no-underline focus:outline-none"
aria-expanded={submenuOpen} aria-expanded={submenuOpen}
aria-haspopup="true" aria-haspopup="true"
aria-controls={`submenu-${title}`} aria-controls={`submenu-${title}`}
> >
<span className="w-[20px] h-[20px] flex justify-center"> <span className="w-[20px] h-[20px] flex justify-center">
<i className={`${icon} text-[18px]`}></i> <i className={`${icon} text-[20px]`}></i>
</span> </span>
<div className="flex justify-between items-center w-full"> <div className="flex justify-between items-center w-full">
<span className="ml-3 lg:ml-4 text-start"> <span className="flex items-center mb-0.5">
{t(`sidebar.${item.title}`, { appName })} {t(`sidebar.${item.title}`, { appName })}
</span> </span>
<i <i
@@ -46,20 +46,20 @@ const Submenu = ({ item, closeSidebar, toggleSubmenu, submenuOpen }) => {
} }
className={({ isActive }) => className={({ isActive }) =>
`${ `${
isActive ? "bg-base-300 text-base-content" : "" isActive ? "bg-base-300 text-base-content " : ""
} flex items-center justify-start text-left pl-6 md:pl-8 py-2 text-sm cursor-pointer text-base-content hover:text-base-content focus:bg-base-300 hover:bg-base-300 hover:no-underline focus:outline-none` } pl-4 flex items-center gap-x-5 py-2 text-sm cursor-pointer text-base-content hover:text-base-content focus:bg-base-300 hover:bg-base-300 hover:no-underline focus:outline-none`
} }
onClick={closeSidebar} onClick={closeSidebar}
role="menuitem" role="menuitem"
tabIndex={submenuOpen ? 0 : -1} tabIndex={submenuOpen ? 0 : -1}
> >
<span className="w-[15px] h-[15px] flex justify-center"> <span className="w-[18px] h-[18px] flex justify-center">
<i <i
className={`${childItem.icon} text-[18px]`} className={`${childItem.icon} text-[18px]`}
aria-hidden="true" aria-hidden="true"
></i> ></i>
</span> </span>
<span className="ml-3 lg:ml-4"> <span className="mb-0.5">
{t(`sidebar.${item.title}-Children.${childItem.title}`, { {t(`sidebar.${item.title}-Children.${childItem.title}`, {
appName: drivename appName: drivename
})} })}
+359 -135
View File
@@ -241,6 +241,13 @@ export const handleImageResize = (
signerId, signerId,
showResize showResize
) => { ) => {
// Compute widget dimensions only once
const { offsetWidth, offsetHeight } = ref;
const factor = scale * containerScale || 1;
const widgetWidth = offsetWidth / factor;
const widgetHeight = offsetHeight / factor;
const widgetDims = { Width: widgetWidth, Height: widgetHeight };
const filterSignerPos = signerPos.filter((data) => data.Id === signerId); const filterSignerPos = signerPos.filter((data) => data.Id === signerId);
if (filterSignerPos.length > 0) { if (filterSignerPos.length > 0) {
const getPlaceHolder = filterSignerPos[0].placeHolder; const getPlaceHolder = filterSignerPos[0].placeHolder;
@@ -252,12 +259,28 @@ export const handleImageResize = (
const getPosData = getXYdata; const getPosData = getXYdata;
const addSignPos = getPosData.map((url) => { const addSignPos = getPosData.map((url) => {
if (url.key === key) { if (url.key === key) {
return { // Base fields for every resized signature
const base = {
...url, ...url,
Width: ref.offsetWidth / (containerScale * scale), Width: widgetWidth,
Height: ref.offsetHeight / (containerScale * scale), Height: widgetHeight,
IsResize: showResize ? true : false IsResize: showResize ? true : false
}; };
// If it's a “type” signature, regenerate the image and options
if (url.typeSignature && url.signatureType === "type") {
const signImg = convertTextToImg(
url.typeFont ?? "Fasthand",
url.typeSignature,
url.fontColor ?? "blue",
widgetDims
);
return {
...base,
SignUrl: signImg,
options: { ...url.options, response: signImg }
};
}
return base;
} }
return url; return url;
}); });
@@ -280,6 +303,7 @@ export const handleImageResize = (
} }
} }
}; };
export const widgets = [ export const widgets = [
{ type: "signature", icon: "fa-light fa-pen-nib", iconSize: "20px" }, { type: "signature", icon: "fa-light fa-pen-nib", iconSize: "20px" },
{ type: "stamp", icon: "fa-light fa-stamp", iconSize: "19px" }, { type: "stamp", icon: "fa-light fa-stamp", iconSize: "19px" },
@@ -336,6 +360,8 @@ export const selectFormat = (data) => {
return "MMMM dd, yyyy"; return "MMMM dd, yyyy";
case "DD MMMM, YYYY": case "DD MMMM, YYYY":
return "dd MMMM, yyyy"; return "dd MMMM, yyyy";
case "DD.MM.YYYY":
return "dd.MM.yyyy";
default: default:
return "MM/dd/yyyy"; return "MM/dd/yyyy";
} }
@@ -363,6 +389,8 @@ export const changeDateToMomentFormat = (format) => {
return "MMM DD, YYYY"; return "MMM DD, YYYY";
case "dd MMMM, yyyy": case "dd MMMM, yyyy":
return "DD MMMM, YYYY"; return "DD MMMM, YYYY";
case "dd.MM.yyyy":
return "DD.MM.YYYY";
default: default:
return "L"; return "L";
} }
@@ -590,7 +618,6 @@ export async function getBase64FromIMG(url) {
reader.readAsDataURL(blob); reader.readAsDataURL(blob);
reader.onloadend = function () { reader.onloadend = function () {
const pdfBase = this.result; const pdfBase = this.result;
resolve(pdfBase); resolve(pdfBase);
}; };
}); });
@@ -632,27 +659,57 @@ export const handleSignYourselfImageResize = (
containerScale, containerScale,
scale scale
) => { ) => {
const getXYdata = xyPosition[index].pos; // Guard against bad index
const getPosData = getXYdata; if (!xyPosition[index]) {
const addSign = getPosData.map((url) => { console.log(`ImageResize invalid index ${index}`);
if (url.key === key) { return;
return { }
...url,
Width: ref.offsetWidth / (scale * containerScale),
Height: ref.offsetHeight / (scale * containerScale),
IsResize: true
};
}
return url;
});
const newUpdateUrl = xyPosition.map((obj, ind) => { // Compute widget dimensions only once
if (ind === index) { const { offsetWidth, offsetHeight } = ref;
return { ...obj, pos: addSign }; const factor = scale * containerScale || 1;
} const widgetWidth = offsetWidth / factor;
return obj; const widgetHeight = offsetHeight / factor;
const widgetDims = { Width: widgetWidth, Height: widgetHeight };
// Single pass to update only the targeted index/key
const updated = xyPosition.map((item, idx) => {
if (idx !== index) return item;
return {
...item,
pos: item.pos.map((url) => {
if (url.key !== key) return url;
// Base fields for every resized signature
const base = {
...url,
Width: widgetWidth,
Height: widgetHeight,
IsResize: true
};
// If it's a “type” signature, regenerate the image and options
if (url.typeSignature && url.signatureType === "type") {
const signImg = convertTextToImg(
url.typeFont ?? "Fasthand",
url.typeSignature,
url.fontColor ?? "blue",
widgetDims
);
return {
...base,
SignUrl: signImg,
options: { ...url.options, response: signImg }
};
}
return base;
})
};
}); });
setXyPosition(newUpdateUrl); setXyPosition(updated);
}; };
//function for call cloud function signPdf and generate digital signature //function for call cloud function signPdf and generate digital signature
@@ -1205,57 +1262,108 @@ export const embedDocId = async (pdfOriginalWH, pdfDoc, documentId) => {
} }
}; };
//function for save button to save signature or image url // function for convert input text value in image
export function convertTextToImg(fontStyle, text, color, widgetDims) {
// read your widget dimensions:
const maxWidth = widgetDims.Width;
const maxHeight = widgetDims.Height;
const baselineFontSizePx = maxHeight;
const fontFamily = fontStyle || "Fasthand";
const fillColor = color || "blue";
// 1. Use a temporary canvas to measure actual text size
const tempCanvas = document.createElement("canvas");
const tempCtx = tempCanvas.getContext("2d");
tempCtx.font = `${baselineFontSizePx}px ${fontFamily}`;
const metrics = tempCtx.measureText(text);
const actualHeight =
metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;
const actualWidth = metrics.width;
// const renderscale = 3;
// 2. Scale font size so text height fills canvas
const scaleX = maxWidth / actualWidth;
const scaleY = maxHeight / actualHeight;
const scale = Math.min(scaleX, scaleY, 1);
// final font-size
// const finalFontSizePx = baselineFontSizePx * scale * renderscale;
const finalFontSizePx = baselineFontSizePx * scale;
// 3. Create the final canvas
const pxRatio = window.devicePixelRatio * 2 || 3;
const canvas = document.createElement("canvas");
canvas.width = Math.ceil(maxWidth * pxRatio);
canvas.height = Math.ceil(maxHeight * pxRatio);
const ctx = canvas.getContext("2d");
ctx.scale(pxRatio, pxRatio);
// 4. Setup text with final font
ctx.font = `${finalFontSizePx}px ${fontFamily}`;
ctx.fillStyle = fillColor;
ctx.textAlign = "center";
ctx.textBaseline = "alphabetic";
const newMetrics = ctx.measureText(text);
const ascent = newMetrics.actualBoundingBoxAscent;
const descent = newMetrics.actualBoundingBoxDescent;
// 5. Align text vertically so it touches top and bottom
const y = maxHeight / 2 + (ascent - descent) / 2;
ctx.clearRect(0, 0, maxWidth, maxHeight);
ctx.fillText(text, maxWidth / 2, y);
// 6. Output image
const dataUrl = canvas.toDataURL("image/png");
return dataUrl;
}
// `onSaveSign` trigger on save button to save for signature, initials widget for type: draw, typed, default
export function onSaveSign( export function onSaveSign(
type, type,
xyPosition, xyPosition,
index, index,
signKey, signKey,
signatureImg, signatureImg,
defaultImgWH, updatedImgWH,
isDefaultSign,
isTypeText, isTypeText,
typedSignature, typedSignature,
isAutoSign, isAutoSign,
widgetsType, widgetsType,
isApplyAll isApplyAll,
typeFont,
fontColor
) { ) {
let getIMGWH, posWidth, posHeight; let getIMGWH, posWidth, posHeight;
let getXYdata = xyPosition[index].pos; let getXYdata = xyPosition[index].pos;
const isSignOrInitials =
widgetsType && ["signature", "initials"].includes(widgetsType);
const updateXYData = getXYdata.map((position) => { const updateXYData = getXYdata.map((position) => {
if (position.key === signKey) { if (position.key === signKey) {
if (isDefaultSign === "initials") { if (isTypeText) {
let imgWH = { width: position.Width, height: position.Height };
getIMGWH = calculateImgAspectRatio(imgWH, position);
} else if (isDefaultSign === "default") {
getIMGWH = calculateImgAspectRatio(defaultImgWH, position);
} else if (isTypeText) {
getIMGWH = { getIMGWH = {
newWidth: defaultImgWH.width, newWidth: updatedImgWH.width,
newHeight: defaultImgWH.height newHeight: updatedImgWH.height
}; };
} else {
const defaultWidth = defaultWidthHeight(position?.type).width;
const defaultHeight = defaultWidthHeight(position?.type).height;
getIMGWH = calculateImgAspectRatio(
{
width: defaultWidth ? defaultWidth : 150,
height: defaultHeight ? defaultHeight : 60
},
position
);
} }
posWidth = getIMGWH ? getIMGWH.newWidth : 150; posWidth = getIMGWH ? getIMGWH.newWidth : 150;
posHeight = getIMGWH ? getIMGWH.newHeight : 60; posHeight = getIMGWH ? getIMGWH.newHeight : 60;
const widgetDims = { Width: posWidth, Height: posHeight };
const signImg = typedSignature
? convertTextToImg(typeFont, typedSignature, fontColor, widgetDims)
: signatureImg;
return { return {
...position, ...position,
Width: posWidth, ...(type === "type" ? { Width: posWidth } : {}),
Height: posHeight, ...(type === "type" ? { Height: posHeight } : {}),
SignUrl: signatureImg, SignUrl: signImg,
...(type && { signatureType: type }), ...(isSignOrInitials && { signatureType: type || "" }),
options: { ...position.options, response: signatureImg }, options: { ...position.options, response: signImg },
...(typedSignature && { typeSignature: typedSignature }) ...(typedSignature && {
typeSignature: typedSignature,
typeFont: typeFont ?? "Fasthand",
fontColor: fontColor ?? "blue"
})
}; };
} }
return position; return position;
@@ -1267,24 +1375,30 @@ export function onSaveSign(
} }
return obj; return obj;
}); });
//condition when draw/upload signature/initials then apply it all related to widgets (signature,image,typed signature or default signature) //condition when draw/upload signature/initials then apply it all related to widgets (draw, typed signature or default signature)
if (isApplyAll || isAutoSign) { if (isApplyAll || isAutoSign) {
const updatedArray = updateXYposition.map((page) => ({ const updatedArray = updateXYposition.map((page) => ({
...page, ...page,
pos: page.pos.map( pos: page.pos.map((item) => {
(item) => if (item.type === widgetsType) {
item.type === widgetsType const widgetDims = { Width: item.Width, Height: item.Height };
? { const signImg = typedSignature
...item, ? convertTextToImg(typeFont, typedSignature, fontColor, widgetDims)
Width: posWidth, : signatureImg;
Height: posHeight, return {
SignUrl: signatureImg, ...item,
...(type && { signatureType: type }), SignUrl: signImg,
options: { ...item.options, response: signatureImg }, ...(isSignOrInitials && { signatureType: type || "" }),
...(typedSignature && { typeSignature: typedSignature }) options: { ...item.options, response: signImg },
} ...(typedSignature && {
: item // Otherwise, keep it unchanged typeSignature: typedSignature,
) typeFont: typeFont || "Fasthand",
fontColor: fontColor || "blue"
})
};
}
return item; // Otherwise, keep it unchanged
})
})); }));
return updatedArray; return updatedArray;
} else { } else {
@@ -1292,6 +1406,54 @@ export function onSaveSign(
} }
} }
/**
* Scales and centers a base64encoded image into a fixedsize widget
* and returns a new base64 PNG.
*
* @param {string} base64Image A data-URL (e.g. "data:image/png;base64,…")
* @param {{ Width: number, Height: number }} widgetDims
* @returns {Promise<string>} A Promise that resolves to a data-URL of the new image
*/
export async function convertBase64ToImg(base64Image, widgetDims) {
const { Width: maxWidth, Height: maxHeight } = widgetDims;
// Detect input format from data URL
const match = base64Image.match(/^data:(image\/(png|jpeg|jpg|webp));base64,/);
const inputMime = match ? match[1] : "image/png"; // fallback to PNG if unknown
// 1. Load the image off-DOM
const img = new Image();
img.src = base64Image;
await new Promise((resolve, reject) => {
img.onload = resolve;
img.onerror = reject;
});
// 2. Compute scale to fit within widget (preserving aspect ratio)
const { naturalWidth: imgW, naturalHeight: imgH } = img;
const scale = Math.min(maxWidth / imgW, maxHeight / imgH, 1);
const drawW = imgW * scale;
const drawH = imgH * scale;
// 3. Prepare a high-DPI canvas
const pxRatio = (window.devicePixelRatio || 1) * 2;
const canvas = document.createElement("canvas");
canvas.width = Math.ceil(maxWidth * pxRatio);
canvas.height = Math.ceil(maxHeight * pxRatio);
const ctx = canvas.getContext("2d");
ctx.scale(pxRatio, pxRatio);
ctx.clearRect(0, 0, maxWidth, maxHeight);
// 4. Center the image in the widget rectangle
const x = (maxWidth - drawW) / 2;
const y = (maxHeight - drawH) / 2;
ctx.drawImage(img, x, y, drawW, drawH);
// 5. Return new base64 in same format as input
const quality =
inputMime.includes("jpeg") || inputMime.includes("jpg") ? 0.9 : undefined;
return canvas.toDataURL(inputMime, quality);
}
//function to use After setting the signature URL for the first signature widget, clicking on subsequent //function to use After setting the signature URL for the first signature widget, clicking on subsequent
//signature widgets should automatically apply and display the signature. apply for initial,signature and stamp widget //signature widgets should automatically apply and display the signature. apply for initial,signature and stamp widget
export const handleCopySignUrl = ( export const handleCopySignUrl = (
@@ -1318,8 +1480,8 @@ export const handleCopySignUrl = (
if (x.key === currentPos.key) { if (x.key === currentPos.key) {
return { return {
...x, ...x,
Width: existSignPosition.Width, // Width: existSignPosition.Width,
Height: existSignPosition.Height, // Height: existSignPosition.Height,
SignUrl: existSignPosition.SignUrl, SignUrl: existSignPosition.SignUrl,
signatureType: existSignPosition.signatureType, signatureType: existSignPosition.signatureType,
options: { ...x.options, response: existSignPosition.SignUrl }, options: { ...x.options, response: existSignPosition.SignUrl },
@@ -1351,39 +1513,33 @@ export const calculateImgAspectRatio = (imgWH, pos) => {
const aspectRatio = imgWH.width / imgWH.height; const aspectRatio = imgWH.width / imgWH.height;
newWidth = aspectRatio * placeholderHeight; newWidth = aspectRatio * placeholderHeight;
newHeight = placeholderHeight; newHeight = placeholderHeight;
return { newHeight, newWidth }; return { newHeight, newWidth };
}; };
//function for upload stamp or image // `onSaveImage` trigger for upload image through stamp, image, signature, initials widgets
export function onSaveImage( export function onSaveImage(
signatureType,
xyPosition, xyPosition,
index, index,
signKey, signKey,
imgWH,
image, image,
isAutoSign, isAutoSign,
widgetsType, widgetsType,
isApplyAll isApplyAll
) { ) {
let getIMGWH; // let getIMGWH;
const isSignOrInitials =
widgetsType && ["signature", "initials"].includes(widgetsType);
//get current page position //get current page position
const getXYData = xyPosition[index].pos; const getXYData = xyPosition[index].pos;
const updateXYData = getXYData.map((position) => { const updateXYData = getXYData.map((position) => {
if (position.key === signKey) { if (position.key === signKey) {
getIMGWH = calculateImgAspectRatio(imgWH, position);
return { return {
...position, ...position,
Width: getIMGWH.newWidth,
Height: getIMGWH.newHeight,
SignUrl: image.src, SignUrl: image.src,
ImageType: image.imgType, ImageType: image.imgType,
options: { ...(isSignOrInitials && { signatureType: signatureType || "" }),
...position.options, options: { ...position.options, response: image.src }
response: image.src
}
}; };
} }
return position; return position;
@@ -1395,17 +1551,16 @@ export function onSaveImage(
} }
return obj; return obj;
}); });
//condition when user upload(stamp) then apply it all related to widgets // condition when user upload(stamp) then apply it all related to widgets
if (isApplyAll || isAutoSign) { if (isApplyAll || isAutoSign) {
const updatedArray = updateXYposition.map((page) => ({ const updatedArray = updateXYposition.map((page) => ({
...page, ...page,
pos: page.pos.map( pos: page.pos.map(
(item) => (item) =>
// below condition to exclude apply all for image widget
item.type === widgetsType && item.type !== "image" item.type === widgetsType && item.type !== "image"
? { ? {
...item, ...item,
Width: getIMGWH.newWidth,
Height: getIMGWH.newHeight,
SignUrl: image.src, SignUrl: image.src,
ImageType: image.imgType, ImageType: image.imgType,
options: { options: {
@@ -1423,7 +1578,7 @@ export function onSaveImage(
} }
//function for select image and upload image //function for select image and upload image
export const onImageSelect = (setImgWH, setImage, file) => { export const onImageSelect = (setImage, file) => {
const reader = new FileReader(); const reader = new FileReader();
reader.readAsDataURL(file); reader.readAsDataURL(file);
reader.onloadend = function (e) { reader.onloadend = function (e) {
@@ -1442,13 +1597,12 @@ export const onImageSelect = (setImgWH, setImage, file) => {
width = 184 * imgR; width = 184 * imgR;
height = 184; height = 184;
} }
setImgWH({ width: width, height: height });
}; };
image.src = reader.result; image.src = reader.result;
setImage({ src: image.src, imgType: file.type }); setImage({ src: image.src, imgType: file.type });
}; };
}; };
export const compressedFileSize = (file, setImgWH, setImage) => { export const compressedFileSize = (file, setImage) => {
// Create a new FileReader instance to read the uploaded file // Create a new FileReader instance to read the uploaded file
const reader = new FileReader(); const reader = new FileReader();
@@ -1473,7 +1627,7 @@ export const compressedFileSize = (file, setImgWH, setImage) => {
// Draw the image onto the canvas with the specified dimensions // Draw the image onto the canvas with the specified dimensions
ctx.drawImage(img, 0, 0, canvas.width, canvas.height); ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
// Get the original file type (default to "image/png" if not recognized) // Get the original file type (default to "image/png" if not recognized)
const fileType = file.type || "image/png"; const fileType = "image/png"; //file.type || "image/png"
// Compress the image and convert it into a Blob // Compress the image and convert it into a Blob
canvas.toBlob( canvas.toBlob(
(blob) => { (blob) => {
@@ -1483,7 +1637,7 @@ export const compressedFileSize = (file, setImgWH, setImage) => {
lastModified: Date.now() // Update the last modified timestamp lastModified: Date.now() // Update the last modified timestamp
}); });
// Pass the compressed file to a custom function for further processing // Pass the compressed file to a custom function for further processing
onImageSelect(setImgWH, setImage, compressedFile); onImageSelect(setImage, compressedFile);
}, },
fileType, // Output format for the compressed image fileType, // Output format for the compressed image
0.3 // Compression quality (30%) 0.3 // Compression quality (30%)
@@ -1552,13 +1706,7 @@ const getWidgetsFontColor = (type) => {
} }
}; };
//function for embed multiple signature using pdf-lib //function for embed multiple signature using pdf-lib
export const multiSignEmbed = async ( export const multiSignEmbed = async (widgets, pdfDoc, signyourself, scale) => {
pdfOriginalWH,
widgets,
pdfDoc,
signyourself,
scale
) => {
// `fontBytes` is used to embed custom font in pdf // `fontBytes` is used to embed custom font in pdf
const fontBytes = await fileasbytes( const fontBytes = await fileasbytes(
"https://cdn.opensignlabs.com/webfonts/times.ttf" "https://cdn.opensignlabs.com/webfonts/times.ttf"
@@ -1567,15 +1715,9 @@ export const multiSignEmbed = async (
const font = await pdfDoc.embedFont(fontBytes, { subset: true }); const font = await pdfDoc.embedFont(fontBytes, { subset: true });
let hasError = false; let hasError = false;
for (let item of widgets) { 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 if (hasError) break; // Stop the outer loop if an error occurred
const typeExist = item.pos.some((data) => data?.type); const typeExist = item.pos.some((data) => data?.type);
let updateItem; let updateItem;
if (typeExist) { if (typeExist) {
if (signyourself) { if (signyourself) {
updateItem = item.pos; updateItem = item.pos;
@@ -1601,14 +1743,31 @@ export const multiSignEmbed = async (
const pages = pdfDoc.getPages(); const pages = pdfDoc.getPages();
const form = pdfDoc.getForm(); const form = pdfDoc.getForm();
const page = pages[pageNo - 1]; const page = pages[pageNo - 1];
// `page.getCropBox()` returns the visible area of the PDF page.
// It provides an object with the following properties:
// - x: the x-coordinate of the lower-left corner of the visible area
// - y: the y-coordinate of the lower-left corner of the visible area
// - width: the width of the visible area (x1 - x0)
// - height: the height of the visible area (y1 - y0)
// If the page does not explicitly define a CropBox, this method returns
// the MediaBox instead, which represents the full physical size of the page.
//
// Example CropBox: [0, 7.92, 612, 799.92]
// => x = 0, y = 7.92, width = 612 - 0 = 612, height = 799.92 - 7.92 = 792
// This is equivalent to `react-pdf`'s `page.view` array: [x0, y0, x1, y1]
const { _, y, width, height } = page.getCropBox();
const originalHeight = height + y;
const getSize = { height: originalHeight, width: width };
const images = await Promise.all( const images = await Promise.all(
widgetsPositionArr.map(async (widget) => { widgetsPositionArr.map(async (widget) => {
// `SignUrl` this is wrong nomenclature and maintain for older code in this options we save base64 of signature image from sign pad // `SignUrl` this is wrong nomenclature and maintain for older code in this options we save base64 of signature image from sign pad
let signbase64 = widget.SignUrl && widget.SignUrl; let signbase64 = widget.SignUrl && widget.SignUrl;
const widgetDims = { Width: widget.Width, Height: widget.Height };
if (signbase64) { if (signbase64) {
let arr = signbase64.split(","), let arr = signbase64.split(","),
mime = arr[0].match(/:(.*?);/)[1]; mime = arr[0].match(/:(.*?);/)[1];
const res = await fetch(signbase64); const signatureImg = await convertBase64ToImg(signbase64, widgetDims);
const res = await fetch(signatureImg);
const arrayBuffer = await res.arrayBuffer(); const arrayBuffer = await res.arrayBuffer();
const obj = { mimetype: mime, arrayBuffer: arrayBuffer }; const obj = { mimetype: mime, arrayBuffer: arrayBuffer };
return obj; return obj;
@@ -1701,8 +1860,8 @@ export const multiSignEmbed = async (
let currentY = yPos(position) + 2; let currentY = yPos(position) + 2;
// Size and spacing settings // Size and spacing settings
const checkboxSize = fontSize - 1; // checkbox diameter const checkboxSize = fontSize - 1; // checkbox diameter
const checkboxTextGapFromLeft = fontSize + 5; // gap between box and its label const checkboxTextGapFromLeft = fontSize + 3.4; // gap between box and its label
const verticalGap = fontSize + 3.2; // gap between two rows (vertical layout) const verticalGap = fontSize + 3.4; // gap between two rows (vertical layout)
let horizontalGap = 0; // will compute after drawing each label let horizontalGap = 0; // will compute after drawing each label
if (position?.options?.values.length > 0) { if (position?.options?.values.length > 0) {
position.options.values.forEach((item, ind) => { position.options.values.forEach((item, ind) => {
@@ -1766,8 +1925,10 @@ export const multiSignEmbed = async (
// Measure the width of this label text at `fontSize` // Measure the width of this label text at `fontSize`
const textWidth = font.widthOfTextAtSize(item, fontSize); const textWidth = font.widthOfTextAtSize(item, fontSize);
// Next checkbox should come after: [box] + gap + [label text] + extra 10pt padding // Next checkbox should come after: [box] + gap + [label text] + extra 10pt padding
horizontalGap = const gap = position.options?.isHideLabel
checkboxSize + checkboxTextGapFromLeft + textWidth; ? checkboxTextGapFromLeft - 5
: checkboxTextGapFromLeft + textWidth;
horizontalGap = checkboxSize + gap;
} }
}); });
} }
@@ -1912,13 +2073,13 @@ export const multiSignEmbed = async (
const radioGroup = form.createRadioGroup(radioRandomId); const radioGroup = form.createRadioGroup(radioRandomId);
//getting radio buttons options text font size //getting radio buttons options text font size
const optionsFontSize = fontSize; // font size for option text const optionsFontSize = fontSize; // font size for option text
const radioTextGapFromLeft = fontSize + 6; // gap between circle and its label const radioTextGapFromLeft = fontSize + 3; // gap between circle and its label
const radioSize = fontSize; // circle diameter (square of width×height) const radioSize = fontSize; // circle diameter (square of width×height)
// Initial “cursor” positions (from your existing helpers) // Initial “cursor” positions (from your existing helpers)
let currentX = xPos(position) + 2; let currentX = xPos(position) + 2;
let currentY = yPos(position); let currentY = yPos(position) + 3;
// Vertical gap between two radiorows // Vertical gap between two radiorows
const verticalGap = fontSize + 6; const verticalGap = fontSize + 3;
// Well compute horizontalGap on the fly—after drawing each label // Well compute horizontalGap on the fly—after drawing each label
// Initialize to zero (will be set after first option is placed) // Initialize to zero (will be set after first option is placed)
let horizontalGap = 0; let horizontalGap = 0;
@@ -1974,7 +2135,10 @@ export const multiSignEmbed = async (
const textWidth = font.widthOfTextAtSize(item, optionsFontSize); const textWidth = font.widthOfTextAtSize(item, optionsFontSize);
// radioSize = the circle. radioTextGapFromLeft = gap between circle and label. // radioSize = the circle. radioTextGapFromLeft = gap between circle and label.
// Add a small extra padding (e.g. 10pt) before placing next circle. // Add a small extra padding (e.g. 10pt) before placing next circle.
horizontalGap = radioSize + radioTextGapFromLeft + textWidth; const gap = position?.options?.isHideLabel
? radioTextGapFromLeft - 6
: radioTextGapFromLeft + textWidth;
horizontalGap = radioSize + gap;
} }
}); });
} }
@@ -2131,28 +2295,25 @@ export const addDefaultSignatureImg = (xyPosition, defaultSignImg, type) => {
const img = new Image(); const img = new Image();
img.src = defaultSignImg; img.src = defaultSignImg;
if (img.complete) { if (img.complete) {
imgWH = { imgWH = { width: img.width, height: img.height };
width: img.width,
height: img.height
};
} }
let xyDefaultPos = []; let xyDefaultPos = [];
for (let i = 0; i < xyPosition.length; i++) { for (let i = 0; i < xyPosition.length; i++) {
let getIMGWH; // let getIMGWH;
const getXYdata = xyPosition[i].pos; const getXYdata = xyPosition[i].pos;
const getPageNo = xyPosition[i].pageNumber; const getPageNo = xyPosition[i].pageNumber;
const getPosData = getXYdata; const getPosData = getXYdata;
const addSign = getPosData.map((position) => { const addSign = getPosData.map((position) => {
getIMGWH = calculateImgAspectRatio(imgWH, position); // getIMGWH = calculateImgAspectRatio(imgWH, position);
if (position.type) { if (position.type) {
if (position?.type === type) { if (position?.type === type) {
return { return {
...position, ...position,
SignUrl: defaultSignImg, SignUrl: defaultSignImg,
Width: getIMGWH.newWidth, // Width: getIMGWH.newWidth,
Height: getIMGWH.newHeight, // Height: getIMGWH.newHeight,
ImageType: "default", ImageType: "default",
options: { options: {
...position.options, ...position.options,
@@ -2164,8 +2325,8 @@ export const addDefaultSignatureImg = (xyPosition, defaultSignImg, type) => {
return { return {
...position, ...position,
SignUrl: defaultSignImg, SignUrl: defaultSignImg,
Width: getIMGWH.newWidth, // Width: getIMGWH.newWidth,
Height: getIMGWH.newHeight, // Height: getIMGWH.newHeight,
ImageType: "default", ImageType: "default",
options: { options: {
...position.options, ...position.options,
@@ -2273,7 +2434,10 @@ export const getAppLogo = async () => {
}); });
if (tenant) { if (tenant) {
localStorage.setItem("appname", "OpenSign™"); localStorage.setItem("appname", "OpenSign™");
return { logo: tenant?.logo, user: tenant?.user }; return {
logo: tenant?.logo,
user: tenant?.user
};
} }
} catch (err) { } catch (err) {
console.log("err in getlogo ", err); console.log("err in getlogo ", err);
@@ -2725,9 +2889,9 @@ function getWidgetPosition(page, image, sizeRatio, getSize) {
let pageWidth; let pageWidth;
// pageHeight; // pageHeight;
if ([90, 270].includes(page.getRotation().angle)) { if ([90, 270].includes(page.getRotation().angle)) {
pageWidth = page.getHeight(); pageWidth = getSize?.height || page.getHeight();
} else { } else {
pageWidth = page.getWidth(); pageWidth = getSize?.width || page.getWidth();
} }
// eslint-disable-next-line // eslint-disable-next-line
if (!image?.hasOwnProperty("vpWidth")) { if (!image?.hasOwnProperty("vpWidth")) {
@@ -2864,8 +3028,9 @@ export const convertBase64ToFile = async (
} }
}; };
export const onClickZoomIn = (scale, zoomPercent, setScale, setZoomPercent) => { export const onClickZoomIn = (scale, zoomPercent, setScale, setZoomPercent) => {
setScale(scale + 0.1 * scale); const newPercent = zoomPercent + 10;
setZoomPercent(zoomPercent + 10); setZoomPercent(newPercent);
setScale(1 + newPercent / 100);
}; };
export const onClickZoomOut = ( export const onClickZoomOut = (
zoomPercent, zoomPercent,
@@ -2874,12 +3039,9 @@ export const onClickZoomOut = (
setScale setScale
) => { ) => {
if (zoomPercent > 0) { if (zoomPercent > 0) {
if (zoomPercent === 10) { const newPercent = Math.max(0, zoomPercent - 10);
setScale(1); setZoomPercent(newPercent);
} else { setScale(1 + newPercent / 100);
setScale(scale - 0.1 * scale);
}
setZoomPercent(zoomPercent - 10);
} }
}; };
@@ -3325,8 +3487,13 @@ export const getOriginalWH = async (pdf) => {
for (let index = 0; index < totalPages; index++) { for (let index = 0; index < totalPages; index++) {
try { try {
const getPage = await pdf.getPage(index + 1); const getPage = await pdf.getPage(index + 1);
const width = getPage?.view[2]; const scale = 1;
const height = getPage?.view[3]; //getting extra height which is removed from viewPort height (letter type pdf have originial h-799.92 but using getPage.getViewport getting height-792 something diffrence-7.92px)
//getPage?.view[1] store diffrence of height (Page starts 7.92 from bottom)
const y0 = getPage?.view[1] || 0;
let { width, height } = getPage.getViewport({ scale });
//adding it on viewPort height then get original height ex- 792+7.92 = 799.92 orignial height
height = height + y0;
pdfWHObj.push({ pageNumber: index + 1, width, height }); pdfWHObj.push({ pageNumber: index + 1, width, height });
} catch (e) { } catch (e) {
console.log(`Error getting page ${index + 1} of PDF: ${e.message}`); console.log(`Error getting page ${index + 1} of PDF: ${e.message}`);
@@ -3506,3 +3673,60 @@ export function getFileAsArrayBuffer(file) {
reader.readAsArrayBuffer(file); reader.readAsArrayBuffer(file);
}); });
} }
/**
* Converts a JPEG/JPG File/Blob into a PNG File.
*
* @param {string} base64Image - A data-URL (e.g. "data:image/jpeg;base64,…")
* @param {string} filename - Desired filename (e.g. "input").
* @returns {Promise<File>} - A promise that resolves with a new PNG File.
*/
export function convertJpegToPng(base64Image, filename) {
const arr = base64Image.split(",");
const mimeMatch = arr[0].match(/:(.*?);/);
if (!mimeMatch) throw new Error("Invalid dataURL");
const mime = mimeMatch[1];
const type = mime.split("/")[1];
if (type === "png") {
return base64Image;
} else {
const bstr = atob(arr[1]);
let n = bstr.length;
const u8arr = new Uint8Array(n);
while (n--) u8arr[n] = bstr.charCodeAt(n);
const inputFile = new File([u8arr], `${filename}.${type}`, { type: mime });
return new Promise((resolve, reject) => {
// ensure its JPEG/JPG (you can remove this check if not needed)
if (!/image\/jpe?g/.test(inputFile.type)) {
return reject(new Error("Input must be a JPEG or JPG image"));
}
// Read the file as a data URL
const reader = new FileReader();
reader.onerror = () => reject(new Error("Failed to read the file"));
reader.onload = () => {
const img = new Image();
img.onerror = () => reject(new Error("Failed to load image"));
img.onload = () => {
// draw image onto a canvas
const canvas = document.createElement("canvas");
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);
// directly get PNG as base64 data URL
try {
const pngDataUrl = canvas.toDataURL("image/png");
resolve(pngDataUrl);
} catch (err) {
reject(new Error("Failed to convert canvas to PNG"));
}
};
img.src = reader.result;
};
reader.readAsDataURL(inputFile);
});
}
}
+110
View File
@@ -0,0 +1,110 @@
import { useEffect, useRef } from "react";
export default function usePdfPinchZoom(ref, scale, setScale, setZoomPercent) {
const initialDistanceRef = useRef(null);
const startScaleRef = useRef(scale);
const currentScaleRef = useRef(scale);
const pinchScaleRef = useRef(null); // stores relative scale during a gesture
const frameRef = useRef(null);
// keep a reference to the latest scale value
useEffect(() => {
const element = ref.current;
currentScaleRef.current = scale;
// once React commits the new scale after a pinch we clear the transform
if (element && !pinchScaleRef.current) {
element.style.transform = "";
}
}, [scale, ref]);
useEffect(() => {
const element = ref.current;
if (!element) return;
const getDistance = (touches) => {
const [touch1, touch2] = touches;
const dx = touch1.clientX - touch2.clientX;
const dy = touch1.clientY - touch2.clientY;
return Math.hypot(dx, dy);
};
const handleTouchStart = (e) => {
if (e.touches.length === 2) {
initialDistanceRef.current = getDistance(e.touches);
startScaleRef.current = currentScaleRef.current;
element.style.transformOrigin = "0 0";
}
};
const handleTouchMove = (e) => {
if (e.touches.length === 2 && initialDistanceRef.current) {
e.preventDefault();
const newDistance = getDistance(e.touches);
const pinchScale = newDistance / initialDistanceRef.current;
const newScale = Math.max(1, startScaleRef.current * pinchScale);
// store relative scale so it can be applied on top of current width
pinchScaleRef.current = newScale / startScaleRef.current;
if (!frameRef.current) {
frameRef.current = requestAnimationFrame(() => {
element.style.transform = `scale(${pinchScaleRef.current})`;
frameRef.current = null;
});
}
}
};
const handleTouchEnd = () => {
if (pinchScaleRef.current) {
let relativeScale = pinchScaleRef.current;
const inlineTransform = element.style.transform;
if (inlineTransform && inlineTransform.startsWith("scale(")) {
const value = parseFloat(inlineTransform.slice(6, -1));
if (!isNaN(value)) {
relativeScale = value;
}
}
const finalScale = Math.max(1, startScaleRef.current * relativeScale);
const percent = parseFloat(((finalScale - 1) * 100).toFixed(2));
const unchanged =
Math.abs(finalScale - currentScaleRef.current) < 0.001;
pinchScaleRef.current = null;
if (frameRef.current) {
cancelAnimationFrame(frameRef.current);
frameRef.current = null;
}
if (unchanged) {
// if scale didn't actually change, reset transform right away
element.style.transform = "";
} else {
// keep transform applied until state update reflects the new scale
element.style.transform = `scale(${relativeScale})`;
}
setScale(finalScale);
setZoomPercent(percent);
// ensure refs reflect the latest value before next gesture starts
currentScaleRef.current = finalScale;
startScaleRef.current = finalScale;
}
initialDistanceRef.current = null;
};
element.addEventListener("touchstart", handleTouchStart, {
passive: false
});
element.addEventListener("touchmove", handleTouchMove, { passive: false });
element.addEventListener("touchend", handleTouchEnd);
element.addEventListener("touchcancel", handleTouchEnd);
return () => {
element.removeEventListener("touchstart", handleTouchStart);
element.removeEventListener("touchmove", handleTouchMove);
element.removeEventListener("touchend", handleTouchEnd);
element.removeEventListener("touchcancel", handleTouchEnd);
};
}, [ref, setScale, setZoomPercent]);
}
+5 -1
View File
@@ -125,7 +125,11 @@ function Login() {
} }
} catch (error) { } catch (error) {
console.error("Error while logging in user", error); console.error("Error while logging in user", error);
showToast("danger", "Invalid username/password or region"); if (error?.code === 1001) {
showToast("danger", t("action-prohibited"));
} else {
showToast("danger", "Invalid username/password or region");
}
} }
}; };
const handleLoginBtn = async (event) => { const handleLoginBtn = async (event) => {
+1 -30
View File
@@ -144,7 +144,6 @@ function PdfRequestFiles(
const [signatureType, setSignatureType] = useState([]); const [signatureType, setSignatureType] = useState([]);
const [pdfBase64Url, setPdfBase64Url] = useState(""); const [pdfBase64Url, setPdfBase64Url] = useState("");
const [isAgree, setIsAgree] = useState(false); const [isAgree, setIsAgree] = useState(false);
const [isAgreeTour, setIsAgreeTour] = useState(false);
const [redirectTimeLeft, setRedirectTimeLeft] = useState(5); const [redirectTimeLeft, setRedirectTimeLeft] = useState(5);
const [isredirectCanceled, setIsredirectCanceled] = useState(true); const [isredirectCanceled, setIsredirectCanceled] = useState(true);
const [isDragging, setIsDragging] = useState(false); const [isDragging, setIsDragging] = useState(false);
@@ -693,7 +692,6 @@ function PdfRequestFiles(
} }
//embed all widgets in document //embed all widgets in document
const pdfBytes = await multiSignEmbed( const pdfBytes = await multiSignEmbed(
pdfOriginalWH,
widgets, widgets,
pdfDoc, pdfDoc,
isSignYourSelfFlow, isSignYourSelfFlow,
@@ -1380,17 +1378,6 @@ function PdfRequestFiles(
alert(t("expiry-date-error")); alert(t("expiry-date-error"));
} }
}; };
const AgreementTour = [
{
selector: '[data-tut="IsAgree"]',
content: () => <p className="p-0">{t("agrrement-alert")}</p>,
position: "top",
style: { fontSize: "13px" }
}
];
const handleCloseAgreeTour = () => {
setIsAgreeTour(false);
};
// `handleRedirectCancel` is used to cancel redirecting to redirectUrl // `handleRedirectCancel` is used to cancel redirecting to redirectUrl
const handleRedirectCancel = () => { const handleRedirectCancel = () => {
setIsredirectCanceled(true); setIsredirectCanceled(true);
@@ -1600,7 +1587,7 @@ function PdfRequestFiles(
} }
} }
}; };
//function to get first widget and page number to assign currect signer and tour message //function to get first widget id and page number to assign correct signer and show tour message
const showFirstWidget = () => { const showFirstWidget = () => {
if (!requestSignTour) { if (!requestSignTour) {
const getCurrentUserPlaceholder = signerPos?.find( const getCurrentUserPlaceholder = signerPos?.find(
@@ -1645,21 +1632,9 @@ function PdfRequestFiles(
!isDecline?.isDeclined && ( !isDecline?.isDeclined && (
<AgreementSign <AgreementSign
setIsAgree={setIsAgree} setIsAgree={setIsAgree}
setIsAgreeTour={setIsAgreeTour}
showFirstWidget={showFirstWidget} showFirstWidget={showFirstWidget}
/> />
)} )}
<Tour
showNumber={false}
showNavigation={false}
showNavigationNumber={false}
onRequestClose={handleCloseAgreeTour}
steps={AgreementTour}
isOpen={isAgreeTour}
rounded={5}
closeWithMask={false}
/>
{isUiLoading && ( {isUiLoading && (
<div className="absolute h-[100vh] w-full flex flex-col justify-center items-center z-[999] bg-[#e6f2f2] bg-opacity-80"> <div className="absolute h-[100vh] w-full flex flex-col justify-center items-center z-[999] bg-[#e6f2f2] bg-opacity-80">
<Loader /> <Loader />
@@ -2012,8 +1987,6 @@ function PdfRequestFiles(
scale={scale} scale={scale}
uniqueId={uniqueId} uniqueId={uniqueId}
pdfBase64Url={pdfBase64Url} pdfBase64Url={pdfBase64Url}
setIsAgreeTour={setIsAgreeTour}
isAgree={isAgree}
handleTabDrag={handleTabDrag} handleTabDrag={handleTabDrag}
handleStop={handleStop} handleStop={handleStop}
isDragging={isDragging} isDragging={isDragging}
@@ -2096,8 +2069,6 @@ function PdfRequestFiles(
signatureType?.find((x) => x.name === "default") signatureType?.find((x) => x.name === "default")
?.enabled || false ?.enabled || false
} }
isAgree={isAgree}
setIsAgreeTour={setIsAgreeTour}
/> />
)} )}
</div> </div>
+8 -5
View File
@@ -863,7 +863,6 @@ function PlaceHolderSign() {
try { try {
//pdfOriginalWH contained all pdf's pages width,height & pagenumber in array format //pdfOriginalWH contained all pdf's pages width,height & pagenumber in array format
const pdfBase64 = await multiSignEmbed( const pdfBase64 = await multiSignEmbed(
pdfOriginalWH,
placeholder, placeholder,
pdfDoc, pdfDoc,
isSignYourSelfFlow, isSignYourSelfFlow,
@@ -1745,14 +1744,14 @@ function PlaceHolderSign() {
objectId: data.objectId objectId: data.objectId
}; };
const updatePlaceHolder = signerPos.map((x) => { const updatePlaceHolder = signerPos.map((x) => {
if (x.Id === id) { if (x.Id === id || x.signerObjId === id) {
return { ...x, signerPtr: signerPtr, signerObjId: data.objectId }; return { ...x, signerPtr: signerPtr, signerObjId: data.objectId };
} }
return { ...x }; return { ...x };
}); });
setSignerPos(updatePlaceHolder); setSignerPos(updatePlaceHolder);
const updateSigner = signersdata.map((x) => { const updateSigner = signersdata.map((x) => {
if (x.Id === id) { if (x.Id === id || x.objectId === id) {
return { ...x, ...data, className: "contracts_Contactbook" }; return { ...x, ...data, className: "contracts_Contactbook" };
} }
return { ...x }; return { ...x };
@@ -1767,7 +1766,9 @@ function PlaceHolderSign() {
} }
} }
setSignersData(updateSigner); setSignersData(updateSigner);
const index = signersdata.findIndex((x) => x.Id === id); const index = signersdata.findIndex(
(x) => x.Id === id || x.objectId === id
);
setIsSelectId(index); setIsSelectId(index);
} }
if (isNewContact.status) { if (isNewContact.status) {
@@ -1931,7 +1932,9 @@ function PlaceHolderSign() {
}; };
//`handleInputChange` function to get signers list from dropdown //`handleInputChange` function to get signers list from dropdown
const handleInputChange = (item, id) => { const handleInputChange = (item, id) => {
const signerExist = forms.some((x) => x.value === item.value); const signerExist = signersdata?.some(
(x) => x.objectId && x.objectId === item.value
);
if (signerExist) { if (signerExist) {
alert(t("already-exist-signer")); alert(t("already-exist-signer"));
} else { } else {
+70 -3
View File
@@ -56,6 +56,10 @@ const Preferences = () => {
const [dateFormat, setDateFormat] = useState("MM/DD/YYYY"); const [dateFormat, setDateFormat] = useState("MM/DD/YYYY");
const [is12HourTime, setIs12HourTime] = useState(false); const [is12HourTime, setIs12HourTime] = useState(false);
const [isLTVEnabled, setIsLTVEnabled] = useState(false); const [isLTVEnabled, setIsLTVEnabled] = useState(false);
const [isDefaultMail, setIsDefaultMail] = useState({
requestMail: false,
completionMail: false
});
useEffect(() => { useEffect(() => {
fetchSignType(); fetchSignType();
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -227,6 +231,7 @@ const Preferences = () => {
setRequestSubject( setRequestSubject(
`{{sender_name}} has requested you to sign {{document_title}}` `{{sender_name}} has requested you to sign {{document_title}}`
); );
setIsDefaultMail((prev) => ({ ...prev, requestMail: true }));
} }
setDefaultReqHtml({ setDefaultReqHtml({
body: defaultRequestBody, body: defaultRequestBody,
@@ -241,6 +246,7 @@ const Preferences = () => {
setCompletionSubject( setCompletionSubject(
`Document {{document_title}} has been signed by all parties` `Document {{document_title}} has been signed by all parties`
); );
setIsDefaultMail((prev) => ({ ...prev, completionMail: true }));
} }
setDefaultCompHtml({ setDefaultCompHtml({
body: defaultCompletionBody, body: defaultCompletionBody,
@@ -318,13 +324,45 @@ const Preferences = () => {
}; };
//function to use reset form //function to use reset form
const handleReset = (request, completion) => { const handleReset = async (request, completion) => {
if (request) { let extUser =
localStorage.getItem("Extand_Class") &&
JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
handleModifyMail(request);
if (request && !isDefaultMail?.requestMail) {
setRequestBody(defaultReqHtml?.body); setRequestBody(defaultReqHtml?.body);
setRequestSubject(defaultReqHtml?.subject); setRequestSubject(defaultReqHtml?.subject);
} else if (completion) { try {
await Parse.Cloud.run("updatetenant", {
tenantId: tenantId,
details: { RequestBody: "", RequestSubject: "" }
});
if (extUser && extUser?.objectId) {
extUser.TenantId.RequestBody = "";
extUser.TenantId.RequestSubject = "";
const _extUser = JSON.parse(JSON.stringify(extUser));
localStorage.setItem("Extand_Class", JSON.stringify([_extUser]));
}
} catch (err) {
console.log("Err in reseting request mail", err);
}
} else if (completion && !isDefaultMail?.completionMail) {
setCompletionSubject(defaultCompHtml?.subject); setCompletionSubject(defaultCompHtml?.subject);
SetCompletionBody(defaultCompHtml?.body); SetCompletionBody(defaultCompHtml?.body);
try {
await Parse.Cloud.run("updatetenant", {
tenantId: tenantId,
details: { CompletionBody: "", CompletionSubject: "" }
});
if (extUser && extUser?.objectId) {
extUser.TenantId.CompletionBody = "";
extUser.TenantId.CompletionSubject = "";
const _extUser = JSON.parse(JSON.stringify(extUser));
localStorage.setItem("Extand_Class", JSON.stringify([_extUser]));
}
} catch (err) {
console.log("Err in reseting completion mail", err);
}
} }
}; };
//function for handle ontext change and save again text in delta //function for handle ontext change and save again text in delta
@@ -344,6 +382,11 @@ const Preferences = () => {
const handleTourInput = () => setIsTourEnabled(!isTourEnabled); const handleTourInput = () => setIsTourEnabled(!isTourEnabled);
const handleSendinOrderInput = () => setSendinOrder(!sendinOrder); const handleSendinOrderInput = () => setSendinOrder(!sendinOrder);
const tabName = (ind) => tab.find((t, i) => i === ind)?.name; const tabName = (ind) => tab.find((t, i) => i === ind)?.name;
const handleModifyMail = (mode) => {
mode === "request"
? setIsDefaultMail((p) => ({ ...p, requestMail: !p?.requestMail }))
: setIsDefaultMail((p) => ({ ...p, completionMail: !p?.completionMail }));
};
return ( return (
<React.Fragment> <React.Fragment>
<Title title={t("Preferences")} /> <Title title={t("Preferences")} />
@@ -737,6 +780,18 @@ const Preferences = () => {
{t("request-email")} {t("request-email")}
</h1> </h1>
<div className="relative mt-2 mb-4"> <div className="relative mt-2 mb-4">
{
isDefaultMail?.requestMail && (
<div className="absolute backdrop-blur-[2px] flex w-full h-full justify-center items-center bg-black/10 rounded-box select-none z-20">
<button
onClick={() => handleModifyMail("request")}
className="op-btn op-btn-primary shadow-lg"
>
Modify
</button>
</div>
)
}
<form <form
onSubmit={handleSaveRequestEmail} onSubmit={handleSaveRequestEmail}
className="p-3 border-[1px] border-base-content rounded-box" className="p-3 border-[1px] border-base-content rounded-box"
@@ -801,6 +856,18 @@ const Preferences = () => {
{t("completion-email")} {t("completion-email")}
</h1> </h1>
<div className="relative my-2"> <div className="relative my-2">
{
isDefaultMail?.completionMail && (
<div className="absolute backdrop-blur-[2px] flex w-full h-full justify-center items-center bg-black/10 rounded-box select-none z-20">
<button
onClick={() => handleModifyMail("completion")}
className="op-btn op-btn-primary shadow-lg"
>
Modify
</button>
</div>
)
}
<form <form
onSubmit={handleSaveCompletionEmail} onSubmit={handleSaveCompletionEmail}
className="p-3 border-[1px] border-base-content rounded-box" className="p-3 border-[1px] border-base-content rounded-box"
+1 -1
View File
@@ -180,7 +180,7 @@ const Report = () => {
const listData = res.data?.result.filter((x) => x.Signers.length > 0); const listData = res.data?.result.filter((x) => x.Signers.length > 0);
let arr = []; let arr = [];
for (const obj of listData) { for (const obj of listData) {
const isSigner = obj.Signers.some( const isSigner = obj?.Signers?.some(
(item) => item.UserId.objectId === currentUser (item) => item.UserId.objectId === currentUser
); );
if (isSigner) { if (isSigner) {
@@ -648,7 +648,6 @@ function SignYourSelf() {
} }
//embed all widgets in document //embed all widgets in document
const pdfBytes = await multiSignEmbed( const pdfBytes = await multiSignEmbed(
pdfOriginalWH,
xyPosition, xyPosition,
pdfDoc, pdfDoc,
isSignYourSelfFlow, isSignYourSelfFlow,
@@ -833,7 +833,6 @@ const TemplatePlaceholder = () => {
try { try {
//pdfOriginalWH contained all pdf's pages width,height & pagenumber in array format //pdfOriginalWH contained all pdf's pages width,height & pagenumber in array format
const pdfBase64 = await multiSignEmbed( const pdfBase64 = await multiSignEmbed(
pdfOriginalWH,
placeholder, placeholder,
pdfDoc, pdfDoc,
isSignYourSelfFlow, isSignYourSelfFlow,
@@ -91,6 +91,7 @@ const ReportTable = (props) => {
const [contact, setContact] = useState({ Name: "", Email: "", Phone: "" }); const [contact, setContact] = useState({ Name: "", Email: "", Phone: "" });
const [isSuccess, setIsSuccess] = useState({}); const [isSuccess, setIsSuccess] = useState({});
const [templateId, setTemplateId] = useState(""); const [templateId, setTemplateId] = useState("");
const [sortOrder, setSortOrder] = useState("asc");
const isTemplateReport = props.ReportName === "Templates"; const isTemplateReport = props.ReportName === "Templates";
const recordsPerPage = 5; const recordsPerPage = 5;
const startIndex = (currentPage - 1) * props.docPerPage; const startIndex = (currentPage - 1) * props.docPerPage;
@@ -437,7 +438,22 @@ const ReportTable = (props) => {
// Get current list // Get current list
const indexOfLastDoc = currentPage * props.docPerPage; const indexOfLastDoc = currentPage * props.docPerPage;
const indexOfFirstDoc = indexOfLastDoc - props.docPerPage; const indexOfFirstDoc = indexOfLastDoc - props.docPerPage;
const currentList = props.List?.slice(indexOfFirstDoc, indexOfLastDoc); const sortedList = React.useMemo(() => {
if (props.ReportName === "Contactbook") {
const contacts = [...props.List];
contacts.sort((a, b) => {
const nameA = a?.Name?.toLowerCase() || "";
const nameB = b?.Name?.toLowerCase() || "";
if (nameA < nameB) return sortOrder === "asc" ? -1 : 1;
if (nameA > nameB) return sortOrder === "asc" ? 1 : -1;
return 0;
});
return contacts;
}
return props.List;
}, [props.List, sortOrder, props.ReportName]);
const currentList = sortedList?.slice(indexOfFirstDoc, indexOfLastDoc);
// Change page // Change page
const paginateFront = () => { const paginateFront = () => {
@@ -457,6 +473,10 @@ const ReportTable = (props) => {
setIsContactform(!isContactform); setIsContactform(!isContactform);
}; };
const toggleSortOrder = () => {
setSortOrder(sortOrder === "asc" ? "desc" : "asc");
};
const handleUserData = (data) => { const handleUserData = (data) => {
props.setList((prevData) => [data, ...prevData]); props.setList((prevData) => [data, ...prevData]);
}; };
@@ -974,6 +994,7 @@ const ReportTable = (props) => {
const timezone = extClass?.[0]?.Timezone || ""; const timezone = extClass?.[0]?.Timezone || "";
const DateFormat = extClass?.[0]?.DateFormat || "MM/DD/YYYY"; const DateFormat = extClass?.[0]?.DateFormat || "MM/DD/YYYY";
const Is12Hr = extClass?.[0]?.Is12HourTime || false; const Is12Hr = extClass?.[0]?.Is12HourTime || false;
const isCompletedReport = props?.ReportName === "Completed Documents";
const signers = item?.Placeholders?.map((x, i) => { const signers = item?.Placeholders?.map((x, i) => {
const audit = item?.AuditTrail?.find( const audit = item?.AuditTrail?.find(
(audit) => audit?.UserPtr?.objectId === x.signerObjId (audit) => audit?.UserPtr?.objectId === x.signerObjId
@@ -1001,20 +1022,22 @@ const ReportTable = (props) => {
key={i} key={i}
className="text-sm font-medium flex flex-row gap-2 items-center" className="text-sm font-medium flex flex-row gap-2 items-center"
> >
<button {!isCompletedReport && (
onClick={() => setIsModal({ [`${item.objectId}_${i}`]: true })} <button
className={`${ onClick={() => setIsModal({ [`${item.objectId}_${i}`]: true })}
x.Activity === "SIGNED" className={`${
? "op-border-primary op-text-primary" x.Activity === "SIGNED"
: x.Activity === "VIEWED" ? "op-border-primary op-text-primary"
? "border-green-400 text-green-400" : x.Activity === "VIEWED"
: "border-base-content text-base-content" ? "border-green-400 text-green-400"
} focus:outline-none border-2 w-[60px] h-[30px] text-[11px] rounded-full`} : "border-base-content text-base-content"
> } focus:outline-none border-2 w-[60px] h-[30px] text-[11px] rounded-full`}
{x?.Activity?.toUpperCase() || "-"} >
</button> {x?.Activity?.toUpperCase() || "-"}
</button>
)}
<div className="py-2 font-bold text-[12px]">{x?.Email || "-"}</div> <div className="py-2 font-bold text-[12px]">{x?.Email || "-"}</div>
{isModal[`${item.objectId}_${i}`] && ( {!isCompletedReport && isModal[`${item.objectId}_${i}`] && (
<ModalUi <ModalUi
isOpen isOpen
title={t("document-logs")} title={t("document-logs")}
@@ -1536,6 +1559,17 @@ const ReportTable = (props) => {
required required
className="op-file-input op-file-input-bordered op-file-input-sm focus:outline-none hover:border-base-content w-full text-xs" className="op-file-input op-file-input-bordered op-file-input-sm focus:outline-none hover:border-base-content w-full text-xs"
/> />
<p className="mt-1 ml-2 text-[11px] text-gray-600">
{t("import-guideline")}{" "}
<a
href="/sample_contacts.csv"
target="_blank"
rel="noopener noreferrer"
className="text-primary underline"
>
{t("download-sample")}
</a>
</p>
</div> </div>
<div className="text-md m-2"> <div className="text-md m-2">
<div className="flex flex-col md:flex-row gap-1"> <div className="flex flex-col md:flex-row gap-1">
@@ -1624,7 +1658,25 @@ const ReportTable = (props) => {
<tr className="border-y-[1px]"> <tr className="border-y-[1px]">
{props.heading?.map((item, index) => ( {props.heading?.map((item, index) => (
<React.Fragment key={index}> <React.Fragment key={index}>
<th className="p-2">{t(`report-heading.${item}`)}</th> <th className="p-2">
{t(`report-heading.${item}`)}
{props.ReportName === "Contactbook" &&
item === "Name" && (
<button
type="button"
onClick={toggleSortOrder}
className="ml-1"
>
<i
className={
sortOrder === "asc"
? "fa-light fa-arrow-down-a-z"
: "fa-light fa-arrow-up-a-z"
}
></i>
</button>
)}
</th>
</React.Fragment> </React.Fragment>
))} ))}
{props.actions?.length > 0 && ( {props.actions?.length > 0 && (
@@ -1759,12 +1811,16 @@ const ReportTable = (props) => {
</td> </td>
)} )}
{props.heading.includes("Signers") && {props.heading.includes("Signers") &&
["In-progress documents", "Need your sign"].includes( [
props.ReportName "In-progress documents",
) ? ( "Need your sign",
"Completed Documents"
].includes(props.ReportName) ? (
<td className="px-1 py-2"> <td className="px-1 py-2">
{!item?.IsSignyourself && item?.Placeholders && ( {!item?.IsSignyourself && item?.Placeholders ? (
<>{formatStatusRow(item)}</> <>{formatStatusRow(item)}</>
) : (
<>-</>
)} )}
</td> </td>
) : ( ) : (
@@ -1817,7 +1873,7 @@ const ReportTable = (props) => {
{/* template report */} {/* template report */}
{isOption[item.objectId] && {isOption[item.objectId] &&
act.action === "option" && ( act.action === "option" && (
<ul className="absolute -right-1 top-auto z-[70] w-52 op-dropdown-content op-menu shadow-black/20 shadow bg-base-100 text-base-content rounded-box"> <ul className="absolute -right-1 top-auto z-[70] w-52 op-dropdown-content op-menu op-menu-sm shadow-black/20 shadow bg-base-100 text-base-content rounded-box">
{act.subaction?.map((subact) => ( {act.subaction?.map((subact) => (
<li <li
key={subact.btnId} key={subact.btnId}
@@ -1908,7 +1964,7 @@ const ReportTable = (props) => {
{/* doc report */} {/* doc report */}
{isOption[item.objectId] && {isOption[item.objectId] &&
act.action === "option" && ( act.action === "option" && (
<ul className="absolute -right-1 top-auto z-[70] w-max op-dropdown-content op-menu shadow-black/20 shadow bg-base-100 text-base-content rounded-box"> <ul className="absolute -right-1 top-auto z-[70] w-max op-dropdown-content op-menu op-menu-sm shadow-black/20 shadow bg-base-100 text-base-content rounded-box">
{act.subaction?.map( {act.subaction?.map(
(subact) => (subact) =>
!restrictBtn( !restrictBtn(
+3 -2
View File
@@ -1,4 +1,4 @@
import { defineConfig } from "vite"; import { defineConfig, splitVendorChunkPlugin } from "vite";
import react from "@vitejs/plugin-react"; import react from "@vitejs/plugin-react";
import svgr from "vite-plugin-svgr"; import svgr from "vite-plugin-svgr";
import { resolve } from "path"; import { resolve } from "path";
@@ -12,7 +12,8 @@ export default defineConfig(({ command, mode }) => {
return { return {
plugins: [ plugins: [
react(), react(),
svgr() // Transform SVGs into React components svgr(), // Transform SVGs into React components
splitVendorChunkPlugin()
], ],
resolve: { resolve: {
alias: { alias: {
+3
View File
@@ -7,6 +7,7 @@ import axios from 'axios';
dotenv.config(); dotenv.config();
export const cloudServerUrl = 'http://localhost:8080/app'; export const cloudServerUrl = 'http://localhost:8080/app';
export const serverAppId = process.env.APP_ID || 'opensign';
export const appName = 'OpenSign™'; export const appName = 'OpenSign™';
export const MAX_NAME_LENGTH = 250; export const MAX_NAME_LENGTH = 250;
@@ -288,6 +289,8 @@ export const selectFormat = data => {
return 'MMMM dd, yyyy'; return 'MMMM dd, yyyy';
case 'DD MMMM, YYYY': case 'DD MMMM, YYYY':
return 'dd MMMM, yyyy'; return 'dd MMMM, yyyy';
case 'DD.MM.YYYY':
return 'dd.MM.yyyy';
default: default:
return 'MM/dd/yyyy'; return 'MM/dd/yyyy';
} }
@@ -3,7 +3,7 @@ import axios from 'axios';
import multer from 'multer'; import multer from 'multer';
import libre from 'libreoffice-convert'; import libre from 'libreoffice-convert';
import util from 'node:util'; import util from 'node:util';
import { cloudServerUrl, getSecureUrl } from '../../Utils.js'; import { cloudServerUrl, getSecureUrl, serverAppId } from '../../Utils.js';
libre.convertAsync = util.promisify(libre.convert); libre.convertAsync = util.promisify(libre.convert);
@@ -29,7 +29,7 @@ function generatePdfName(length) {
export default async function docxtopdf(req, res) { export default async function docxtopdf(req, res) {
const serverUrl = cloudServerUrl; const serverUrl = cloudServerUrl;
const appId = process.env.APP_ID; const appId = serverAppId;
const masterKey = process.env.MASTER_KEY; const masterKey = process.env.MASTER_KEY;
const inputPath = req.file.path; const inputPath = req.file.path;
const name = generatePdfName(16); const name = generatePdfName(16);
@@ -3,7 +3,7 @@ import multer from 'multer';
import multerS3 from 'multer-s3'; import multerS3 from 'multer-s3';
import aws from 'aws-sdk'; import aws from 'aws-sdk';
import dotenv from 'dotenv'; import dotenv from 'dotenv';
import { cloudServerUrl, useLocal } from '../../Utils.js'; import { cloudServerUrl, serverAppId, useLocal } from '../../Utils.js';
dotenv.config(); dotenv.config();
function sanitizeFileName(fileName) { function sanitizeFileName(fileName) {
@@ -50,7 +50,7 @@ async function uploadFile(req, res) {
const DO_SPACE = process.env.DO_SPACE; const DO_SPACE = process.env.DO_SPACE;
const parseBaseUrl = cloudServerUrl; //process.env.SERVER_URL; const parseBaseUrl = cloudServerUrl; //process.env.SERVER_URL;
const parseAppId = process.env.APP_ID; const parseAppId = serverAppId;
let fileStorage; let fileStorage;
if (useLocal === 'true') { if (useLocal === 'true') {
fileStorage = multer.diskStorage({ fileStorage = multer.diskStorage({
@@ -1,7 +1,7 @@
import axios from 'axios'; import axios from 'axios';
import { cloudServerUrl } from '../../Utils.js'; import { cloudServerUrl, serverAppId } from '../../Utils.js';
const serverUrl = cloudServerUrl; //process.env.SERVER_URL; const serverUrl = cloudServerUrl; //process.env.SERVER_URL;
const APPID = process.env.APP_ID; const APPID = serverAppId;
const masterKEY = process.env.MASTER_KEY; const masterKEY = process.env.MASTER_KEY;
async function addTeamAndOrg(extUser) { async function addTeamAndOrg(extUser) {
try { try {
@@ -1,10 +1,10 @@
import axios from 'axios'; import axios from 'axios';
import { cloudServerUrl } from '../../Utils.js'; import { cloudServerUrl, serverAppId } from '../../Utils.js';
async function AuthLoginAsMail(request) { async function AuthLoginAsMail(request) {
try { try {
//function for login user using user objectId without touching user's password //function for login user using user objectId without touching user's password
const serverUrl = cloudServerUrl; //process.env.SERVER_URL; const serverUrl = cloudServerUrl; //process.env.SERVER_URL;
const APPID = process.env.APP_ID; const APPID = serverAppId;
const masterKEY = process.env.MASTER_KEY; const masterKEY = process.env.MASTER_KEY;
let otpN = request.params.otp; let otpN = request.params.otp;
@@ -92,7 +92,7 @@ async function AuthLoginAsMail(request) {
} catch (err) { } catch (err) {
console.log('err in Auth'); console.log('err in Auth');
console.log(err); console.log(err);
return 'Result not found', err; return 'Result not found';
} }
} }
export default AuthLoginAsMail; export default AuthLoginAsMail;
@@ -1,5 +1,5 @@
import axios from 'axios'; import axios from 'axios';
import { appName, cloudServerUrl } from '../../Utils.js'; import { appName, cloudServerUrl, serverAppId } from '../../Utils.js';
export default async function forwardDoc(request) { export default async function forwardDoc(request) {
try { try {
@@ -58,7 +58,7 @@ export default async function forwardDoc(request) {
mailRes = await axios.post(`${cloudServerUrl}/functions/sendmailv3`, params, { mailRes = await axios.post(`${cloudServerUrl}/functions/sendmailv3`, params, {
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'X-Parse-Application-Id': process.env.APP_ID, 'X-Parse-Application-Id': serverAppId,
'X-Parse-Master-Key': process.env.MASTER_KEY, 'X-Parse-Master-Key': process.env.MASTER_KEY,
}, },
}); });
@@ -1,5 +1,5 @@
import axios from 'axios'; import axios from 'axios';
import { cloudServerUrl } from '../../Utils.js'; import { cloudServerUrl, serverAppId } from '../../Utils.js';
export default async function GetTemplate(request) { export default async function GetTemplate(request) {
const serverUrl = cloudServerUrl; //process.env.SERVER_URL; const serverUrl = cloudServerUrl; //process.env.SERVER_URL;
@@ -10,7 +10,7 @@ export default async function GetTemplate(request) {
if (sessiontoken) { if (sessiontoken) {
const userRes = await axios.get(serverUrl + '/users/me', { const userRes = await axios.get(serverUrl + '/users/me', {
headers: { headers: {
'X-Parse-Application-Id': process.env.APP_ID, 'X-Parse-Application-Id': serverAppId,
'X-Parse-Session-Token': sessiontoken, 'X-Parse-Session-Token': sessiontoken,
}, },
}); });
@@ -1,6 +1,6 @@
import axios from 'axios'; import axios from 'axios';
import { cloudServerUrl } from '../../Utils.js'; import { cloudServerUrl, serverAppId } from '../../Utils.js';
const appId = process.env.APP_ID; const appId = serverAppId;
const masterkey = process.env.MASTER_KEY; const masterkey = process.env.MASTER_KEY;
export default async function createBatchContact(req) { export default async function createBatchContact(req) {
if (!req?.user) { if (!req?.user) {
@@ -1,7 +1,7 @@
import axios from 'axios'; import axios from 'axios';
import { cloudServerUrl, mailTemplate, replaceMailVaribles } from '../../Utils.js'; import { cloudServerUrl, mailTemplate, replaceMailVaribles, serverAppId } from '../../Utils.js';
const serverUrl = cloudServerUrl; //process.env.SERVER_URL; const serverUrl = cloudServerUrl; //process.env.SERVER_URL;
const appId = process.env.APP_ID; const appId = serverAppId;
async function deductcount(docsCount, extUserId) { async function deductcount(docsCount, extUserId) {
try { try {
const extCls = new Parse.Object('contracts_Users'); const extCls = new Parse.Object('contracts_Users');
@@ -84,7 +84,7 @@ async function sendMail(document, publicUrl) {
}; };
let params = { let params = {
extUserId: document.ExtUserPtr.objectId, extUserId: document.ExtUserPtr.objectId,
recipient: objectId ? existSigner?.Email : signerMail[i].email, recipient: existSigner?.Email || signerMail[i].email,
subject: replaceVar?.subject ? replaceVar?.subject : mailTemplate(mailparam).subject, subject: replaceVar?.subject ? replaceVar?.subject : mailTemplate(mailparam).subject,
from: document.ExtUserPtr.Email, from: document.ExtUserPtr.Email,
replyto: senderEmail || '', replyto: senderEmail || '',
@@ -1,5 +1,5 @@
import axios from 'axios'; import axios from 'axios';
import { cloudServerUrl } from '../../Utils.js'; import { cloudServerUrl, serverAppId } from '../../Utils.js';
export default async function getDocument(request) { export default async function getDocument(request) {
const serverUrl = cloudServerUrl; //process.env.SERVER_URL; const serverUrl = cloudServerUrl; //process.env.SERVER_URL;
const docId = request.params.docId; const docId = request.params.docId;
@@ -30,7 +30,7 @@ export default async function getDocument(request) {
try { try {
const userRes = await axios.get(serverUrl + '/users/me', { const userRes = await axios.get(serverUrl + '/users/me', {
headers: { headers: {
'X-Parse-Application-Id': process.env.APP_ID, 'X-Parse-Application-Id': serverAppId,
'X-Parse-Session-Token': sessiontoken, 'X-Parse-Session-Token': sessiontoken,
}, },
}); });
@@ -1,8 +1,8 @@
import axios from 'axios'; import axios from 'axios';
import { cloudServerUrl } from '../../Utils.js'; import { cloudServerUrl, serverAppId } from '../../Utils.js';
export default async function getDrive(request) { export default async function getDrive(request) {
const serverUrl = cloudServerUrl; //process.env.SERVER_URL; const serverUrl = cloudServerUrl; //process.env.SERVER_URL;
const appId = process.env.APP_ID; const appId = serverAppId;
const limit = request.params.limit; const limit = request.params.limit;
const skip = request.params.skip; const skip = request.params.skip;
const docId = request.params.docId; const docId = request.params.docId;
@@ -1,4 +1,4 @@
import { cloudServerUrl } from '../../Utils.js'; import { cloudServerUrl, serverAppId } from '../../Utils.js';
import reportJson from './reportsJson.js'; import reportJson from './reportsJson.js';
import axios from 'axios'; import axios from 'axios';
@@ -14,7 +14,7 @@ export default async function getReport(request) {
const searchTerm = request.params.searchTerm || ''; const searchTerm = request.params.searchTerm || '';
const serverUrl = cloudServerUrl; //process.env.SERVER_URL; const serverUrl = cloudServerUrl; //process.env.SERVER_URL;
const appId = process.env.APP_ID; const appId = serverAppId;
const masterKey = process.env.MASTER_KEY; const masterKey = process.env.MASTER_KEY;
const sessionToken = request.headers['sessiontoken'] || request.headers['x-parse-session-token']; const sessionToken = request.headers['sessiontoken'] || request.headers['x-parse-session-token'];
try { try {
@@ -1,25 +1,5 @@
// `saveRoleContact` is used to save user in contracts_Guest role and create contact // `saveRoleContact` is used to save user in contracts_Guest role and create contact
const saveRoleContact = async contact => { const saveRoleContact = async contact => {
try {
const Role = new Parse.Query(Parse.Role);
const guestRole = await Role.equalTo('name', 'contracts_Guest').first();
if (guestRole) {
// Check if the user is already in the role
const relation = guestRole.relation('users');
const usersInRoleQuery = relation.query();
usersInRoleQuery.equalTo('objectId', contact.UserId.objectId);
const usersInRole = await usersInRoleQuery.find();
if (usersInRole.length > 0) {
console.log('User already added to Guest role.');
} else {
relation.add({ __type: 'Pointer', className: '_User', id: contact.UserId.objectId });
await guestRole.save(null, { useMasterKey: true });
// console.log('User added to Guest role successfully.');
}
}
} catch (err) {
console.log('err in role save', err);
}
const contactQuery = new Parse.Object('contracts_Contactbook'); const contactQuery = new Parse.Object('contracts_Contactbook');
contactQuery.set('Name', contact.Name); contactQuery.set('Name', contact.Name);
contactQuery.set('Email', contact.Email); contactQuery.set('Email', contact.Email);
@@ -7,6 +7,7 @@ import {
saveFileUsage, saveFileUsage,
getSecureUrl, getSecureUrl,
appName, appName,
serverAppId,
} from '../../../Utils.js'; } from '../../../Utils.js';
import GenerateCertificate from './GenerateCertificate.js'; import GenerateCertificate from './GenerateCertificate.js';
import { pdflibAddPlaceholder } from '@signpdf/placeholder-pdf-lib'; import { pdflibAddPlaceholder } from '@signpdf/placeholder-pdf-lib';
@@ -15,7 +16,7 @@ import { SignPdf } from '@signpdf/signpdf';
import { P12Signer } from '@signpdf/signer-p12'; import { P12Signer } from '@signpdf/signer-p12';
const serverUrl = cloudServerUrl; // process.env.SERVER_URL; const serverUrl = cloudServerUrl; // process.env.SERVER_URL;
const APPID = process.env.APP_ID; const APPID = serverAppId;
const masterKEY = process.env.MASTER_KEY; const masterKEY = process.env.MASTER_KEY;
const eSignName = 'OpenSign'; const eSignName = 'OpenSign';
const eSigncontact = 'hello@opensignlabs.com'; const eSigncontact = 'hello@opensignlabs.com';
@@ -59,6 +59,7 @@ export default async function saveAsTemplate(request) {
options: { options: {
...p.options, ...p.options,
status: 'required', status: 'required',
...(p?.options?.defaultValue ? { defaultValue: '' } : {}),
}, },
})), })),
})); }));
@@ -73,10 +74,28 @@ export default async function saveAsTemplate(request) {
}; };
templateCls.set('Placeholders', [placeHolders]); templateCls.set('Placeholders', [placeHolders]);
} else { } else {
const placeHolders = _docRes?.Placeholders?.map((x, i) => { const placeHolders = _docRes?.Placeholders.map((signer, signerIndex) => ({
const email = x.email ? { email: '' } : {}; // copy everything else, then overwrite these fields:
return { ...x, signerObjId: '', signerPtr: {}, Role: 'Role ' + (i + 1), ...email }; ...signer,
}); signerObjId: '',
signerPtr: {},
Role: `Role ${signerIndex + 1}`,
email: '',
// rebuild placeHolder/pages
placeHolder: (signer.placeHolder || []).map(page => ({
...page,
pos: (page.pos || []).map(widget => {
// if there is a defaultValue in options, zero it out
if (widget.options && widget.options.defaultValue !== undefined) {
return { ...widget, options: { ...widget.options, defaultValue: '' } }; // reset only the value
}
// otherwise, return the widget unchanged
return widget;
}),
})),
}));
templateCls.set('Placeholders', placeHolders); templateCls.set('Placeholders', placeHolders);
} }
} }
@@ -38,7 +38,6 @@ const makeEmail = async (
replyto, replyto,
testPdf testPdf
) => { ) => {
const publicUrl = new URL(process.env.SERVER_URL);
const htmlContent = html; const htmlContent = html;
const boundary = 'boundary_' + Date.now().toString(16); const boundary = 'boundary_' + Date.now().toString(16);
const bccHeader = bcc && bcc.length > 0 ? `BCC: ${bcc.join(',')}\n` : ''; // Construct BCC header if provided const bccHeader = bcc && bcc.length > 0 ? `BCC: ${bcc.join(',')}\n` : ''; // Construct BCC header if provided
@@ -8,12 +8,15 @@ export default async function updateTenant(request) {
if (!request.user) { if (!request.user) {
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'unauthorized'); throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'unauthorized');
} }
const validKeys = ['CompletionBody', 'CompletionSubject', 'RequestBody', 'RequestSubject'];
try { try {
const tenant = new Parse.Object('partners_Tenant'); const tenant = new Parse.Object('partners_Tenant');
tenant.id = tenantId; tenant.id = tenantId;
// Update tenant details // Update tenant details
Object.keys(details).forEach(key => { Object.keys(details).forEach(key => {
tenant.set(key, details?.[key]); if (validKeys.includes(key)) {
tenant.set(key, details?.[key]);
}
}); });
const tenantRes = await tenant.save(null, { useMasterKey: true }); const tenantRes = await tenant.save(null, { useMasterKey: true });
@@ -1,7 +1,7 @@
import axios from 'axios'; import axios from 'axios';
import { cloudServerUrl } from '../../Utils.js'; import { cloudServerUrl, serverAppId } from '../../Utils.js';
const serverUrl = cloudServerUrl; //process.env.SERVER_URL; const serverUrl = cloudServerUrl; //process.env.SERVER_URL;
const APPID = process.env.APP_ID; const APPID = serverAppId;
const masterKEY = process.env.MASTER_KEY; const masterKEY = process.env.MASTER_KEY;
async function saveUser(userDetails) { async function saveUser(userDetails) {
@@ -43,9 +43,9 @@ async function saveUser(userDetails) {
} }
export default async function usersignup(request) { export default async function usersignup(request) {
const userDetails = request.params.userDetails; const userDetails = request.params.userDetails;
const user = await saveUser(userDetails);
try { try {
const user = await saveUser(userDetails);
const extClass = userDetails.role.split('_')[0]; const extClass = userDetails.role.split('_')[0];
const extQuery = new Parse.Query(extClass + '_Users'); const extQuery = new Parse.Query(extClass + '_Users');
@@ -0,0 +1,19 @@
/**
*
* @param {Parse} Parse
*/
exports.up = async Parse => {
const schema = new Parse.Schema('contracts_Subscriptions');
schema.addNumber('CreditAlertLevel');
return schema.update();
};
/**
*
* @param {Parse} Parse
*/
exports.down = async Parse => {
const schema = new Parse.Schema('contracts_Subscriptions');
schema.deleteField('CreditAlertLevel');
return schema.update();
};
+4 -5
View File
@@ -15,7 +15,7 @@ import AWS from 'aws-sdk';
import { app as customRoute } from './cloud/customRoute/customApp.js'; import { app as customRoute } from './cloud/customRoute/customApp.js';
import { exec } from 'child_process'; import { exec } from 'child_process';
import { createTransport } from 'nodemailer'; import { createTransport } from 'nodemailer';
import { appName, cloudServerUrl, smtpenable, smtpsecure, useLocal } from './Utils.js'; import { appName, cloudServerUrl, serverAppId, smtpenable, smtpsecure, useLocal } from './Utils.js';
import { SSOAuth } from './auth/authadapter.js'; import { SSOAuth } from './auth/authadapter.js';
import createContactIndex from './migrationdb/createContactIndex.js'; import createContactIndex from './migrationdb/createContactIndex.js';
import { validateSignedLocalUrl } from './cloud/parsefunction/getSignedUrl.js'; import { validateSignedLocalUrl } from './cloud/parsefunction/getSignedUrl.js';
@@ -97,7 +97,7 @@ export const config = {
cloud: function () { cloud: function () {
import('./cloud/main.js'); import('./cloud/main.js');
}, },
appId: process.env.APP_ID || 'opensign', appId: serverAppId,
logLevel: ['error'], logLevel: ['error'],
maxLimit: 500, maxLimit: 500,
maxUploadSize: '30mb', maxUploadSize: '30mb',
@@ -232,10 +232,9 @@ if (!process.env.TESTING) {
const isWindows = process.platform === 'win32'; const isWindows = process.platform === 'win32';
// console.log('isWindows', isWindows); // console.log('isWindows', isWindows);
createContactIndex(); createContactIndex();
const migrate = isWindows const migrate = isWindows
? `set APPLICATION_ID=${process.env.APP_ID}&& set SERVER_URL=${cloudServerUrl}&& set MASTER_KEY=${process.env.MASTER_KEY}&& npx parse-dbtool migrate` ? `set APPLICATION_ID=${serverAppId}&& set SERVER_URL=${cloudServerUrl}&& set MASTER_KEY=${process.env.MASTER_KEY}&& npx parse-dbtool migrate`
: `APPLICATION_ID=${process.env.APP_ID} SERVER_URL=${cloudServerUrl} MASTER_KEY=${process.env.MASTER_KEY} npx parse-dbtool migrate`; : `APPLICATION_ID=${serverAppId} SERVER_URL=${cloudServerUrl} MASTER_KEY=${process.env.MASTER_KEY} npx parse-dbtool migrate`;
exec(migrate, (error, stdout, stderr) => { exec(migrate, (error, stdout, stderr) => {
if (error) { if (error) {
console.error(`Error: ${error.message}`); console.error(`Error: ${error.message}`);
+1005 -488
View File
File diff suppressed because it is too large Load Diff
+11 -10
View File
@@ -18,8 +18,8 @@
"watch": "nodemon index.js" "watch": "nodemon index.js"
}, },
"dependencies": { "dependencies": {
"@aws-sdk/client-s3": "^3.828.0", "@aws-sdk/client-s3": "^3.840.0",
"@aws-sdk/s3-request-presigner": "^3.828.0", "@aws-sdk/s3-request-presigner": "^3.840.0",
"@parse/fs-files-adapter": "^3.0.0", "@parse/fs-files-adapter": "^3.0.0",
"@parse/s3-files-adapter": "^4.2.0", "@parse/s3-files-adapter": "^4.2.0",
"@pdf-lib/fontkit": "^1.1.1", "@pdf-lib/fontkit": "^1.1.1",
@@ -31,42 +31,43 @@
"coherentpdf": "^2.5.5", "coherentpdf": "^2.5.5",
"cors": "^2.8.5", "cors": "^2.8.5",
"date-fns-tz": "^3.2.0", "date-fns-tz": "^3.2.0",
"dotenv": "^16.5.0", "dotenv": "^16.6.1",
"express": "^5.1.0", "express": "^5.1.0",
"form-data": "^4.0.3", "form-data": "^4.0.3",
"generate-api-key": "^1.0.2", "generate-api-key": "^1.0.2",
"googleapis": "^150.0.1", "googleapis": "^150.0.1",
"libreoffice-convert": "^1.6.1", "libreoffice-convert": "^1.6.1",
"mailgun.js": "^12.0.2", "mailgun.js": "^12.0.3",
"mongodb": "^6.17.0", "mongodb": "^6.17.0",
"multer": "^2.0.1", "multer": "^2.0.1",
"multer-s3": "^3.0.1", "multer-s3": "^3.0.1",
"node-forge": "^1.3.1", "node-forge": "^1.3.1",
"nodemailer": "^7.0.3", "nodemailer": "^7.0.4",
"parse": "^6.1.1", "parse": "^6.1.1",
"parse-dbtool": "^1.2.0", "parse-dbtool": "^1.2.0",
"parse-server": "^8.2.1", "parse-server": "^8.2.1",
"parse-server-api-mail-adapter": "^4.1.0", "parse-server-api-mail-adapter": "^4.1.0",
"pdf-lib": "^1.17.1", "pdf-lib": "^1.17.1",
"posthog-node": "^5.1.0", "posthog-node": "^5.1.1",
"qrcode": "^1.5.4", "qrcode": "^1.5.4",
"rate-limiter-flexible": "^7.1.1", "rate-limiter-flexible": "^7.1.1",
"speakeasy": "^2.0.0", "speakeasy": "^2.0.0",
"ws": "^8.18.2" "ws": "^8.18.3"
}, },
"type": "module", "type": "module",
"devDependencies": { "devDependencies": {
"@babel/eslint-parser": "^7.27.5", "@babel/eslint-parser": "^7.27.5",
"eslint": "^9.29.0", "eslint": "^9.29.0",
"jasmine": "^5.8.0", "jasmine": "^5.8.0",
"mongodb-runner": "^5.9.0", "mongodb-runner": "^5.9.2",
"nodemon": "^3.1.10", "nodemon": "^3.1.10",
"nyc": "^17.1.0", "nyc": "^17.1.0",
"prettier": "^3.5.3" "prettier": "^3.6.2"
}, },
"overrides": { "overrides": {
"ws": "$ws", "ws": "$ws",
"parse": "$parse" "parse": "$parse",
"form-data": "$form-data"
}, },
"engines": { "engines": {
"node": "18 || 20 || 22" "node": "18 || 20 || 22"