Compare commits

...
12 Commits
Author SHA1 Message Date
prafull-opensignlabsandGitHub da4b604710 Merge pull request #1785 from OpenSignLabs/updates-15425322075
handle crashing pattern for text input widgets
2025-06-04 00:17:30 +05:30
prafull-opensignlabs 82d518b3c9 Merge pull request #739 from OpenSignLabs/staging
disable signing request email when send_email flag is false
2025-06-03 18:45:49 +00:00
prafull-opensignlabsandGitHub dcbde2b661 Merge pull request #1784 from OpenSignLabs/updates-15419310132
v2.23.1
2025-06-03 19:28:35 +05:30
prafull-opensignlabs 27b0b426ad v2.23.1
Enable widget placement on click in desktop view
Disable signature types not working
Change icon of agreement sign checkbox
Update dependencies
2025-06-03 13:55:57 +00:00
prafull-opensignlabsandGitHub 8788afaa8e Merge pull request #1766 from OpenSignLabs/updates-15350472621
fix: add verify-document route
2025-05-30 21:19:35 +05:30
prafull-opensignlabs 428aa65b38 Merge pull request #710 from OpenSignLabs/staging
fix: add verify-document route
2025-05-30 15:45:27 +00:00
AmolandGitHub 295d942427 feat: add deploy to digitalocean button 2025-05-30 20:33:43 +05:30
prafull-opensignlabsandGitHub c4e28e6e6e Merge pull request #1764 from OpenSignLabs/updates-15349515719
v2.23.0
2025-05-30 20:26:41 +05:30
prafull-opensignlabs b19fb29b82 Merge pull request #708 from OpenSignLabs/staging
refactor: unnecessary code
2025-05-30 14:54:53 +00:00
prafull-opensignlabsandGitHub d888897b62 Update README.md 2025-05-20 20:10:44 +05:30
prafull-opensignlabsandGitHub c4779c14c1 feat: add all three docker command 2025-05-14 19:11:18 +05:30
prafull-opensignlabsandGitHub e48869cb2f feat: add deploy to digitalocean button 2025-05-14 18:36:47 +05:30
46 changed files with 3435 additions and 623 deletions
+17 -1
View File
@@ -73,12 +73,28 @@ Welcome to OpenSign, the premier open source docusign alternative - document e-s
---
### Installation
### Deploy
Note: The default MongoDB instance used in deployment is not persistant and will be cleared on every restart. To retain your data, configure and supply your own MongoDB connection URL.
#### DigitalOcean
[![Deploy on DigitalOcean](https://www.deploytodo.com/do-btn-blue.svg)](https://cloud.digitalocean.com/apps/new?repo=https://github.com/OpenSignLabs/Deploy-OpenSign-to-Digital-Ocean/tree/main&refcode=30db1c901ab0)
#### Docker
The simplest way to install OpenSign on your own server is using official docker images by running the following command -
**Command for linux/MacOS**
```
export HOST_URL=https://opensign.yourdomain.com && curl --remote-name-all https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/docker-compose.yml https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/Caddyfile https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/.env.local_dev && mv .env.local_dev .env.prod && docker compose up --force-recreate
```
**Command for Windows (Powershell)**
```
$env:HOST_URL="https://opensign.yourdomain.com"; Invoke-WebRequest -Uri https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/docker-compose.yml -OutFile docker-compose.yml; Invoke-WebRequest -Uri https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/Caddyfile -OutFile Caddyfile; Invoke-WebRequest -Uri https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/.env.local_dev -OutFile .env.local_dev; Rename-Item -Path .env.local_dev -NewName .env.prod; docker compose up --force-recreate
```
**Command for Windows (CMD/Terminal)**
```
set HOST_URL=https://opensign.yourdomain.com && curl -O https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/docker-compose.yml && curl -O https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/Caddyfile && curl -O https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/.env.local_dev && rename .env.local_dev .env.prod && docker compose up --force-recreate
```
Make sure that you have `Docker` and `git` installed before you run this command -
Please refer to the [Installation Guide](https://docs.opensignlabs.com/docs/self-host/docker/run-locally/) for detailed instructions on how to install OpenSign on your system.
-25
View File
@@ -1,25 +0,0 @@
# Use an official Node runtime as the base image
FROM node:18
# Set the working directory inside the container
WORKDIR /usr/src/app
# Copy package.json and package-lock.json first to leverage Docker cache
COPY ./package*.json ./
# Install application dependencies
RUN npm install
# Copy the current directory contents into the container
COPY ./ .
COPY ./.husky .
# Make port 3000 available to the world outside this container
EXPOSE 3000
# Define environment variables if needed
# ENV NODE_ENV production
# Run the application
ENTRYPOINT npm run start
+9
View File
@@ -13,6 +13,10 @@ RUN npm install
# Copy the current directory contents into the container
COPY apps/OpenSign/ .
COPY apps/OpenSign/.husky .
COPY apps/OpenSign/entrypoint.sh .
# make the entrypoint.sh file executable
RUN chmod +x entrypoint.sh
# Define environment variables if needed
ENV NODE_ENV=production
@@ -20,8 +24,13 @@ ENV GENERATE_SOURCEMAP=false
# build
RUN npm run build
# Inject env.js loader into index.html
RUN sed -i '/<head>/a\<script src="/env.js"></script>' build/index.html
# Make port 3000 available to the world outside this container
EXPOSE 3000
ENTRYPOINT ["./entrypoint.sh"]
# Run the application
CMD ["npm", "start"]
+29
View File
@@ -0,0 +1,29 @@
#!/bin/sh
ENV_FILE=./build/env.js
DOTENV_FILE=./.env.prod # ✅ use .env.prod
echo "Generating runtime env file at $ENV_FILE..."
echo "window.RUNTIME_ENV = {" > $ENV_FILE
# List of keys to include
RUNTIME_KEYS="REACT_APP_SERVERURL"
for key in $RUNTIME_KEYS; do
# First check docker env (-e), fallback to .env file
value=$(printenv "$key")
if [ -z "$value" ] && [ -f "$DOTENV_FILE" ]; then
# fallback: read from .env
value=$(grep "^$key=" "$DOTENV_FILE" | cut -d '=' -f2- | tr -d '\r\n' | sed 's/"/\\"/g')
else
value=$(echo "$value" | sed 's/"/\\"/g')
fi
echo " $key: \"$value\"," >> $ENV_FILE
done
echo "};" >> $ENV_FILE
exec "$@"
+692 -3
View File
@@ -25,6 +25,7 @@
"moment": "^2.30.1",
"parse": "^6.1.1",
"pdf-lib": "^1.17.1",
"pkijs": "^3.0.8",
"print-js": "^1.6.0",
"prismjs": "^1.30.0",
"radix-ui": "^1.4.2",
@@ -128,6 +129,31 @@
"node": ">=6.0.0"
}
},
"node_modules/@asamuzakjp/css-color": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz",
"integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"@csstools/css-calc": "^2.1.3",
"@csstools/css-color-parser": "^3.0.9",
"@csstools/css-parser-algorithms": "^3.0.4",
"@csstools/css-tokenizer": "^3.0.3",
"lru-cache": "^10.4.3"
}
},
"node_modules/@asamuzakjp/css-color/node_modules/lru-cache": {
"version": "10.4.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
"dev": true,
"license": "ISC",
"optional": true,
"peer": true
},
"node_modules/@babel/code-frame": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
@@ -1878,6 +1904,131 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/@csstools/color-helpers": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.0.2.tgz",
"integrity": "sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/csstools"
},
{
"type": "opencollective",
"url": "https://opencollective.com/csstools"
}
],
"license": "MIT-0",
"optional": true,
"peer": true,
"engines": {
"node": ">=18"
}
},
"node_modules/@csstools/css-calc": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz",
"integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/csstools"
},
{
"type": "opencollective",
"url": "https://opencollective.com/csstools"
}
],
"license": "MIT",
"optional": true,
"peer": true,
"engines": {
"node": ">=18"
},
"peerDependencies": {
"@csstools/css-parser-algorithms": "^3.0.5",
"@csstools/css-tokenizer": "^3.0.4"
}
},
"node_modules/@csstools/css-color-parser": {
"version": "3.0.10",
"resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.10.tgz",
"integrity": "sha512-TiJ5Ajr6WRd1r8HSiwJvZBiJOqtH86aHpUjq5aEKWHiII2Qfjqd/HCWKPOW8EP4vcspXbHnXrwIDlu5savQipg==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/csstools"
},
{
"type": "opencollective",
"url": "https://opencollective.com/csstools"
}
],
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"@csstools/color-helpers": "^5.0.2",
"@csstools/css-calc": "^2.1.4"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"@csstools/css-parser-algorithms": "^3.0.5",
"@csstools/css-tokenizer": "^3.0.4"
}
},
"node_modules/@csstools/css-parser-algorithms": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz",
"integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/csstools"
},
{
"type": "opencollective",
"url": "https://opencollective.com/csstools"
}
],
"license": "MIT",
"optional": true,
"peer": true,
"engines": {
"node": ">=18"
},
"peerDependencies": {
"@csstools/css-tokenizer": "^3.0.4"
}
},
"node_modules/@csstools/css-tokenizer": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz",
"integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/csstools"
},
{
"type": "opencollective",
"url": "https://opencollective.com/csstools"
}
],
"license": "MIT",
"optional": true,
"peer": true,
"engines": {
"node": ">=18"
}
},
"node_modules/@emotion/babel-plugin": {
"version": "11.13.5",
"resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz",
@@ -2922,6 +3073,18 @@
"integrity": "sha512-IUWKVciDJI/BMWDWnh7j0Ngd0N8q9ySRAwm84aDqIE07qpmdZ7x1rkIpBaU1yHSNqNYHeh1Rxsl+LC3CY4f0KA==",
"license": "MIT"
},
"node_modules/@noble/hashes": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
"integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==",
"license": "MIT",
"engines": {
"node": "^14.21.3 || >=16"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -5032,6 +5195,18 @@
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
"node_modules/agent-base": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz",
"integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"engines": {
"node": ">= 14"
}
},
"node_modules/ajv": {
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
@@ -5349,6 +5524,26 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/asn1js": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.6.tgz",
"integrity": "sha512-UOCGPYbl0tv8+006qks/dTgV9ajs97X2p0FAbyS2iyCRrmLSRolDaHdp+v/CLgnzHc3fVB+CwYiUmei7ndFcgA==",
"license": "BSD-3-Clause",
"dependencies": {
"pvtsutils": "^1.3.6",
"pvutils": "^1.1.3",
"tslib": "^2.8.1"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/asn1js/node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD"
},
"node_modules/assertion-error": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
@@ -5854,6 +6049,15 @@
"node": ">= 0.8"
}
},
"node_modules/bytestreamjs": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/bytestreamjs/-/bytestreamjs-2.0.1.tgz",
"integrity": "sha512-U1Z/ob71V/bXfVABvNr/Kumf5VyeQRBEm6Txb0PQ6S7V5GpBM3w4Cbqz/xPDicR5tN0uvDifng8C+5qECeGwyQ==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/cac": {
"version": "6.7.14",
"resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
@@ -6831,6 +7035,22 @@
"node": ">=4"
}
},
"node_modules/cssstyle": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.3.1.tgz",
"integrity": "sha512-ZgW+Jgdd7i52AaLYCriF8Mxqft0gD/R9i9wi6RWBhs1pqdPEzPjym7rvRKi397WmQFf3SlyUsszhw+VVCbx79Q==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"@asamuzakjp/css-color": "^3.1.2",
"rrweb-cssom": "^0.8.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/csstype": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
@@ -6966,6 +7186,65 @@
"url": "https://opencollective.com/daisyui"
}
},
"node_modules/data-urls": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz",
"integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"whatwg-mimetype": "^4.0.0",
"whatwg-url": "^14.0.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/data-urls/node_modules/tr46": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz",
"integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"punycode": "^2.3.1"
},
"engines": {
"node": ">=18"
}
},
"node_modules/data-urls/node_modules/webidl-conversions": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
"integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
"dev": true,
"license": "BSD-2-Clause",
"optional": true,
"peer": true,
"engines": {
"node": ">=12"
}
},
"node_modules/data-urls/node_modules/whatwg-url": {
"version": "14.2.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz",
"integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"tr46": "^5.1.0",
"webidl-conversions": "^7.0.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/data-view-buffer": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz",
@@ -7056,6 +7335,15 @@
}
}
},
"node_modules/decimal.js": {
"version": "10.5.0",
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.5.0.tgz",
"integrity": "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true
},
"node_modules/decompress-response": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
@@ -8825,6 +9113,21 @@
"node": ">=0.10.0"
}
},
"node_modules/html-encoding-sniffer": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz",
"integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"whatwg-encoding": "^3.1.1"
},
"engines": {
"node": ">=18"
}
},
"node_modules/html-parse-stringify": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
@@ -8834,6 +9137,38 @@
"void-elements": "3.1.0"
}
},
"node_modules/http-proxy-agent": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
"integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"agent-base": "^7.1.0",
"debug": "^4.3.4"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/https-proxy-agent": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
"integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"agent-base": "^7.1.2",
"debug": "4"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/i18next": {
"version": "23.16.8",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-23.16.8.tgz",
@@ -9402,6 +9737,15 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-potential-custom-element-name": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
"integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true
},
"node_modules/is-regex": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
@@ -9710,6 +10054,91 @@
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/jsdom": {
"version": "26.1.0",
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz",
"integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"cssstyle": "^4.2.1",
"data-urls": "^5.0.0",
"decimal.js": "^10.5.0",
"html-encoding-sniffer": "^4.0.0",
"http-proxy-agent": "^7.0.2",
"https-proxy-agent": "^7.0.6",
"is-potential-custom-element-name": "^1.0.1",
"nwsapi": "^2.2.16",
"parse5": "^7.2.1",
"rrweb-cssom": "^0.8.0",
"saxes": "^6.0.0",
"symbol-tree": "^3.2.4",
"tough-cookie": "^5.1.1",
"w3c-xmlserializer": "^5.0.0",
"webidl-conversions": "^7.0.0",
"whatwg-encoding": "^3.1.1",
"whatwg-mimetype": "^4.0.0",
"whatwg-url": "^14.1.1",
"ws": "^8.18.0",
"xml-name-validator": "^5.0.0"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"canvas": "^3.0.0"
},
"peerDependenciesMeta": {
"canvas": {
"optional": true
}
}
},
"node_modules/jsdom/node_modules/tr46": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz",
"integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"punycode": "^2.3.1"
},
"engines": {
"node": ">=18"
}
},
"node_modules/jsdom/node_modules/webidl-conversions": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
"integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
"dev": true,
"license": "BSD-2-Clause",
"optional": true,
"peer": true,
"engines": {
"node": ">=12"
}
},
"node_modules/jsdom/node_modules/whatwg-url": {
"version": "14.2.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz",
"integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"tr46": "^5.1.0",
"webidl-conversions": "^7.0.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/jsesc": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
@@ -10820,6 +11249,15 @@
"node": ">=0.10.0"
}
},
"node_modules/nwsapi": {
"version": "2.2.20",
"resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.20.tgz",
"integrity": "sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true
},
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@@ -11178,6 +11616,36 @@
}
}
},
"node_modules/parse5": {
"version": "7.3.0",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
"integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"entities": "^6.0.0"
},
"funding": {
"url": "https://github.com/inikulin/parse5?sponsor=1"
}
},
"node_modules/parse5/node_modules/entities": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-6.0.0.tgz",
"integrity": "sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw==",
"dev": true,
"license": "BSD-2-Clause",
"optional": true,
"peer": true,
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@@ -11361,6 +11829,29 @@
"node": ">= 6"
}
},
"node_modules/pkijs": {
"version": "3.2.5",
"resolved": "https://registry.npmjs.org/pkijs/-/pkijs-3.2.5.tgz",
"integrity": "sha512-WX0la7n7CbnguuaIQoT4Fc0IJckPDOUldzOwlZ0nwpOcySS+Six/tXBdc0RX17J5o1To0SAr3xDJjDLsOfDFQA==",
"license": "BSD-3-Clause",
"dependencies": {
"@noble/hashes": "^1.4.0",
"asn1js": "^3.0.5",
"bytestreamjs": "^2.0.0",
"pvtsutils": "^1.3.2",
"pvutils": "^1.1.3",
"tslib": "^2.6.3"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/pkijs/node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD"
},
"node_modules/platform": {
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/platform/-/platform-1.3.6.tgz",
@@ -11869,6 +12360,30 @@
"node": ">=6"
}
},
"node_modules/pvtsutils": {
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.6.tgz",
"integrity": "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==",
"license": "MIT",
"dependencies": {
"tslib": "^2.8.1"
}
},
"node_modules/pvtsutils/node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD"
},
"node_modules/pvutils": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.3.tgz",
"integrity": "sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==",
"license": "MIT",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@@ -14431,6 +14946,15 @@
"estree-walker": "^0.6.1"
}
},
"node_modules/rrweb-cssom": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz",
"integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true
},
"node_modules/run-async": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz",
@@ -14564,6 +15088,21 @@
"dev": true,
"license": "MIT"
},
"node_modules/saxes": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
"integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==",
"dev": true,
"license": "ISC",
"optional": true,
"peer": true,
"dependencies": {
"xmlchars": "^2.2.0"
},
"engines": {
"node": ">=v12.22.7"
}
},
"node_modules/scheduler": {
"version": "0.23.2",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
@@ -15485,6 +16024,15 @@
"dev": true,
"license": "MIT"
},
"node_modules/symbol-tree": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
"integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true
},
"node_modules/synckit": {
"version": "0.11.6",
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.6.tgz",
@@ -15581,9 +16129,9 @@
}
},
"node_modules/tar-fs": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.2.tgz",
"integrity": "sha512-EsaAXwxmx8UB7FRKqeozqEPop69DXcmYwTQwXvyAPF352HJsPdkVhvTaDPYqfNgruveJIJy3TA2l+2zj8LJIJA==",
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.3.tgz",
"integrity": "sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg==",
"license": "MIT",
"optional": true,
"dependencies": {
@@ -15820,6 +16368,30 @@
"node": ">=14.0.0"
}
},
"node_modules/tldts": {
"version": "6.1.86",
"resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz",
"integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"tldts-core": "^6.1.86"
},
"bin": {
"tldts": "bin/cli.js"
}
},
"node_modules/tldts-core": {
"version": "6.1.86",
"resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz",
"integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true
},
"node_modules/tmp": {
"version": "0.0.33",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
@@ -15846,6 +16418,21 @@
"node": ">=8.0"
}
},
"node_modules/tough-cookie": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz",
"integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==",
"dev": true,
"license": "BSD-3-Clause",
"optional": true,
"peer": true,
"dependencies": {
"tldts": "^6.1.32"
},
"engines": {
"node": ">=16"
}
},
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
@@ -16540,6 +17127,21 @@
"node": ">=0.10.0"
}
},
"node_modules/w3c-xmlserializer": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz",
"integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"xml-name-validator": "^5.0.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/warning": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
@@ -16672,6 +17274,48 @@
"node": ">=4.0"
}
},
"node_modules/whatwg-encoding": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
"integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"iconv-lite": "0.6.3"
},
"engines": {
"node": ">=18"
}
},
"node_modules/whatwg-encoding/node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/whatwg-mimetype": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz",
"integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"engines": {
"node": ">=18"
}
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
@@ -16929,6 +17573,30 @@
"devOptional": true,
"license": "ISC"
},
"node_modules/ws": {
"version": "8.18.2",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz",
"integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/xlsx": {
"version": "0.20.3",
"resolved": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz",
@@ -16941,6 +17609,27 @@
"node": ">=0.8"
}
},
"node_modules/xml-name-validator": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz",
"integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==",
"dev": true,
"license": "Apache-2.0",
"optional": true,
"peer": true,
"engines": {
"node": ">=18"
}
},
"node_modules/xmlchars": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true
},
"node_modules/xmlhttprequest": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz",
+2 -1
View File
@@ -4,11 +4,11 @@
"private": true,
"dependencies": {
"@formkit/auto-animate": "^0.8.2",
"@imgly/background-removal": "^1.6.0",
"@lottiefiles/dotlottie-react": "^0.13.5",
"@pdf-lib/fontkit": "^1.1.1",
"@radix-ui/themes": "^3.2.1",
"@reduxjs/toolkit": "^2.8.2",
"@imgly/background-removal": "^1.6.0",
"axios": "^1.9.0",
"date-fns-tz": "^3.2.0",
"file-saver": "^2.0.5",
@@ -20,6 +20,7 @@
"moment": "^2.30.1",
"parse": "^6.1.1",
"pdf-lib": "^1.17.1",
"pkijs": "^3.0.8",
"print-js": "^1.6.0",
"prismjs": "^1.30.0",
"radix-ui": "^1.4.2",
@@ -1,6 +1,7 @@
{
"header-news": "Neue Funktion: Benutzer des Teams-Plans können jetzt ihre eigenen AWS S3-Buckets für die Dateispeicherung integrieren",
"header-news-btn": "Jetzt einrichten",
"sandbox-news": "Dies ist eine Sandbox-Umgebung. Bitte nicht für produktive Zwecke verwenden.",
"create-account": "Konto erstellen",
"login": "Anmelden",
"language": "Sprache",
@@ -675,7 +676,7 @@
"public-template-mssg-1": "Um OpenSign in Ihr React- oder Next.js-Projekt zu integrieren, führen Sie einfach den folgenden Befehl aus:",
"public-template-mssg-2": "Stellen Sie sicher, dass npm oder yarn in Ihrem Projekt eingerichtet ist. Wenn Sie Yarn verwenden, können Sie npm install durch yarn add @opensign/react ersetzen.",
"public-template-mssg-3": "Benötigen Sie weitere Details oder Beispiele?",
"public-template-mssg-4": "Besuchen Sie die",
"public-template-mssg-4": "Besuchen Sie die ",
"public-template-mssg-5": " npm für die neuesten Updates, detaillierte Dokumentationen und Versionshistorie.",
"public-template-mssg-6": "Bevor Sie diesen Code-Schnipsel verwenden können, müssen Sie diese Vorlage öffentlich machen.",
"public-template-mssg-7": "Bevor Sie einen öffentlichen Link generieren können, müssen Sie diese Vorlage öffentlich machen.",
@@ -980,5 +981,46 @@
"finish-mssg": "Sind Sie sicher, dass Sie das Dokument abschließen möchten?",
"review": "Überprüfen",
"next-field": "Nächstes Feld",
"required-mssg": "{{leftRequiredWidget}} von {{totalWidget}} Feldern übrig"
"required-mssg": "{{leftRequiredWidget}} von {{totalWidget}} Feldern übrig",
"verify-document-signature": "Dokumentensignatur überprüfen",
"select-pdf-document": "PDF-Dokument auswählen",
"selected-file": "Ausgewählte Datei",
"verify-signature": "Signatur überprüfen",
"verification-status": "Überprüfungsstatus",
"verification-in-progress": "Überprüfung läuft...",
"verification-results-will-appear-here": "Überprüfungsergebnisse werden hier angezeigt",
"please-select-pdf": "Bitte wählen Sie eine gültige PDF-Datei aus",
"please-select-file-to-verify": "Bitte wählen Sie eine Datei zur Überprüfung aus",
"no-signature-found": "Keine Signatur im Dokument gefunden",
"error-verifying-pdf": "Fehler beim Überprüfen der PDF",
"signature-valid-basic": "Signatur ist gültig",
"signature-invalid-basic": "Signatur ist ungültig",
"all-signatures-verified-convincing": "Dokument überprüft: Alle Signaturen wurden erfolgreich validiert.",
"some-signatures-invalid-basic": "Einige Signaturen sind ungültig",
"no-signatures-processed": "Keine Signaturen verarbeitet",
"unnamed-signature-field": "Unbenanntes Signaturfeld",
"error-processing-signature": "Fehler beim Verarbeiten der Signatur",
"signer-info-not-available": "Signaturinformationen nicht verfügbar",
"cert-validity-not-checked": "Gültigkeit des Zertifikats nicht geprüft",
"valid": "Gültig",
"expired-or-not-yet-valid": "Abgelaufen oder noch nicht gültig",
"valid-from": "Gültig von",
"to": "bis",
"signer": "Unterzeichner",
"issuer": "Aussteller",
"not-available": "Nicht verfügbar",
"not-performed": "Nicht durchgeführt",
"missing-acrofield-dict": "Fehlendes Acrofield-Wörterbuch",
"signature-dictionary-not-found-or-invalid": "Signaturwörterbuch nicht gefunden oder ungültig",
"missing-or-invalid-byterange": "Fehlender oder ungültiger ByteRange",
"missing-or-invalid-contents": "Fehlender oder ungültiger Inhalt",
"missing-signature-contents": "Fehlender Signaturinhalt",
"invalid-signature-hex-format": "Ungültiges Signatur-Hex-Format",
"unsupported-signature-format-not-signeddata": "Nicht unterstütztes Signaturformat - nicht SignedData",
"signer-certificate-not-found": "Unterzeichnerzertifikat nicht gefunden",
"no-certificates-in-signature": "Keine Zertifikate in der Signatur",
"no-signer-info-in-pkcs7": "Keine Signaturinformationen in PKCS#7",
"could-not-parse-signer-info": "Signaturinformationen konnten nicht analysiert werden",
"not-calculated": "Nicht berechnet",
"not-found-in-signature": "Nicht in Signatur gefunden"
}
@@ -1,6 +1,7 @@
{
"header-news": "New feature: Protect your account with Two-Factor Authentication (2FA) and enjoy the future of login with Passkeys — no passwords needed.",
"header-news-btn": "Setup now",
"sandbox-news": "This is a sandbox environment. Please do not use it for production purposes.",
"create-account": "Create account",
"login": "Login",
"language": "Language",
@@ -675,7 +676,7 @@
"public-template-mssg-1": "To integrate OpenSign into your React or Next.js project, simply run the following command:",
"public-template-mssg-2": "Ensure you have npm or yarn set up in your project. If you're using Yarn, you can replace npm install with yarn add @opensign/react.",
"public-template-mssg-3": "Need more details or examples?",
"public-template-mssg-4": "Visit the",
"public-template-mssg-4": "Visit the ",
"public-template-mssg-5": " npm for the latest updates, detailed documentation, and version history.",
"public-template-mssg-6": "Before you can use this code snippet, you must make this template public.",
"public-template-mssg-7": "Before you can generate a public link you must make this template public.",
@@ -980,5 +981,46 @@
"finish-mssg":" Are you sure you want to finish the document ?",
"review":"Review",
"next-field":"Next Field",
"required-mssg":"{{leftRequiredWidget}} of {{totalWidget}} fields left"
"required-mssg":"{{leftRequiredWidget}} of {{totalWidget}} fields left",
"verify-document-signature": "Verify Document Signature",
"select-pdf-document": "Select PDF Document",
"selected-file": "Selected file",
"verify-signature": "Verify Signature",
"verification-status": "Verification Status",
"verification-in-progress": "Verification in progress...",
"verification-results-will-appear-here": "Verification results will appear here",
"please-select-pdf": "Please select a valid PDF file",
"please-select-file-to-verify": "Please select a file to verify",
"no-signature-found": "No signature found in the document",
"error-verifying-pdf": "Error verifying PDF",
"signature-valid-basic": "Signature is valid",
"signature-invalid-basic": "Signature is invalid",
"all-signatures-verified-convincing": "Document Verified: All signatures have been successfully validated.",
"some-signatures-invalid-basic": "Some signatures are invalid",
"no-signatures-processed": "No signatures were processed",
"unnamed-signature-field": "Unnamed Signature Field",
"error-processing-signature": "Error processing signature",
"signer-info-not-available": "Signer information not available",
"cert-validity-not-checked": "Certificate validity not checked",
"valid": "Valid",
"expired-or-not-yet-valid": "Expired or not yet valid",
"valid-from": "Valid from",
"to": "to",
"signer": "Signer",
"issuer": "Issuer",
"not-available": "Not available",
"not-performed": "Not performed",
"missing-acrofield-dict": "Missing acrofield dictionary",
"signature-dictionary-not-found-or-invalid": "Signature dictionary not found or invalid",
"missing-or-invalid-byterange": "Missing or invalid ByteRange",
"missing-or-invalid-contents": "Missing or invalid Contents",
"missing-signature-contents": "Missing signature contents",
"invalid-signature-hex-format": "Invalid signature hex format",
"unsupported-signature-format-not-signeddata": "Unsupported signature format - not SignedData",
"signer-certificate-not-found": "Signer certificate not found",
"no-certificates-in-signature": "No certificates in signature",
"no-signer-info-in-pkcs7": "No signer info in PKCS#7",
"could-not-parse-signer-info": "Could not parse signer info",
"not-calculated": "Not calculated",
"not-found-in-signature": "Not found in signature"
}
@@ -1,6 +1,7 @@
{
"header-news": "Nueva característica: los usuarios del plan Teams ahora pueden integrar sus propios depósitos de AWS S3 para el almacenamiento de archivos",
"header-news-btn": "Configurar ahora",
"sandbox-news": "Este es un entorno sandbox. Por favor, no lo utilice con fines de producción.",
"create-account": "Crear cuenta",
"login": "Iniciar sesión",
"language": "Idioma",
@@ -675,7 +676,7 @@
"public-template-mssg-1": "Para integrar OpenSign a tu proyecto React o Next.js, simplemente ejecuta los siguientes comandos:",
"public-template-mssg-2": "Asegúrate de tener «npm» o «yarn» configurado en tu proyecto. Si estás usando «yarn», puedes reemplazar «npm install» con «yarn add @opensign/react».",
"public-template-mssg-3": "¿Necesitas más detalles o ejemplos?",
"public-template-mssg-4": "Visita la",
"public-template-mssg-4": "Visita la ",
"public-template-mssg-5": " «npm» para las últimas actualizaciones, documentación detallada e historial de versiones.",
"public-template-mssg-6": "Antes de que puedas usar este fragmento de código, debes convertir esta plantilla en pública.",
"public-template-mssg-7": "Antes de poder generar un enlace público, debes hacer que esta plantilla sea pública.",
@@ -980,5 +981,46 @@
"finish-mssg": "¿Está seguro de que desea finalizar el documento?",
"review": "Revisar",
"next-field": "Siguiente campo",
"required-mssg": "{{leftRequiredWidget}} de {{totalWidget}} campos restantes"
"required-mssg": "{{leftRequiredWidget}} de {{totalWidget}} campos restantes",
"verify-document-signature": "Verificar firma del documento",
"select-pdf-document": "Seleccionar documento PDF",
"selected-file": "Archivo seleccionado",
"verify-signature": "Verificar firma",
"verification-status": "Estado de verificación",
"verification-in-progress": "Verificación en curso...",
"verification-results-will-appear-here": "Los resultados de la verificación aparecerán aquí",
"please-select-pdf": "Por favor, seleccione un archivo PDF válido",
"please-select-file-to-verify": "Por favor, seleccione un archivo para verificar",
"no-signature-found": "No se encontró ninguna firma en el documento",
"error-verifying-pdf": "Error al verificar el PDF",
"signature-valid-basic": "La firma es válida",
"signature-invalid-basic": "La firma no es válida",
"all-signatures-verified-convincing": "Documento verificado: Todas las firmas han sido validadas exitosamente.",
"some-signatures-invalid-basic": "Algunas firmas no son válidas",
"no-signatures-processed": "No se procesaron firmas",
"unnamed-signature-field": "Campo de firma sin nombre",
"error-processing-signature": "Error al procesar la firma",
"signer-info-not-available": "Información del firmante no disponible",
"cert-validity-not-checked": "Validez del certificado no verificada",
"valid": "Válido",
"expired-or-not-yet-valid": "Caducado o aún no válido",
"valid-from": "Válido desde",
"to": "hasta",
"signer": "Firmante",
"issuer": "Emisor",
"not-available": "No disponible",
"not-performed": "No realizado",
"missing-acrofield-dict": "Falta el diccionario Acrofield",
"signature-dictionary-not-found-or-invalid": "Diccionario de firmas no encontrado o inválido",
"missing-or-invalid-byterange": "ByteRange faltante o inválido",
"missing-or-invalid-contents": "Contenido faltante o inválido",
"missing-signature-contents": "Falta el contenido de la firma",
"invalid-signature-hex-format": "Formato hexadecimal de firma inválido",
"unsupported-signature-format-not-signeddata": "Formato de firma no compatible - no SignedData",
"signer-certificate-not-found": "Certificado del firmante no encontrado",
"no-certificates-in-signature": "No hay certificados en la firma",
"no-signer-info-in-pkcs7": "No hay información del firmante en PKCS#7",
"could-not-parse-signer-info": "No se pudo analizar la información del firmante",
"not-calculated": "No calculado",
"not-found-in-signature": "No encontrado en la firma"
}
@@ -1,6 +1,7 @@
{
"header-news": "Nouvelle fonctionnalité : les utilisateurs du forfait Teams peuvent désormais intégrer leurs propres compartiments AWS S3 pour le stockage de fichiers",
"header-news-btn": "Configurer maintenant",
"sandbox-news": "Ceci est un environnement sandbox. Veuillez ne pas l'utiliser à des fins de production.",
"create-account": "Créer un compte",
"login": "Se Connecter",
"language": "Langue",
@@ -675,7 +676,7 @@
"public-template-mssg-1": "Pour intégrer OpenSign dans votre projet React ou Next.js, exécutez simplement la commande suivante :",
"public-template-mssg-2": "Assurez-vous que npm ou Yarn est configuré dans votre projet. Si vous utilisez Yarn, vous pouvez remplacer npm install par Yarn Add @opensign/react.",
"public-template-mssg-3": "Besoin de plus de détails ou d'exemples ?",
"public-template-mssg-4": "Visitez le",
"public-template-mssg-4": "Visitez le ",
"public-template-mssg-5": "npm pour les dernières mises à jour, une documentation détaillée et l'historique des versions.",
"public-template-mssg-6": "Avant de pouvoir utiliser cet extrait de code, vous devez rendre ce modèle public.",
"public-template-mssg-7": "Avant de pouvoir générer un lien public, vous devez rendre ce modèle public.",
@@ -980,5 +981,46 @@
"finish-mssg": "Êtes-vous sûr de vouloir terminer le document ?",
"review": "Revoir",
"next-field": "Champ suivant",
"required-mssg":"{{leftRequiredWidget}} champs sur {{totalWidget}} restants"
"required-mssg":"{{leftRequiredWidget}} champs sur {{totalWidget}} restants",
"verify-document-signature": "Vérifier la signature du document",
"select-pdf-document": "Sélectionner le document PDF",
"selected-file": "Fichier sélectionné",
"verify-signature": "Vérifier la signature",
"verification-status": "État de la vérification",
"verification-in-progress": "Vérification en cours...",
"verification-results-will-appear-here": "Les résultats de la vérification apparaîtront ici",
"please-select-pdf": "Veuillez sélectionner un fichier PDF valide",
"please-select-file-to-verify": "Veuillez sélectionner un fichier à vérifier",
"no-signature-found": "Aucune signature trouvée dans le document",
"error-verifying-pdf": "Erreur lors de la vérification du PDF",
"signature-valid-basic": "La signature est valide",
"signature-invalid-basic": "La signature est invalide",
"all-signatures-verified-convincing": "Document vérifié : Toutes les signatures ont été validées avec succès.",
"some-signatures-invalid-basic": "Certaines signatures sont invalides",
"no-signatures-processed": "Aucune signature traitée",
"unnamed-signature-field": "Champ de signature sans nom",
"error-processing-signature": "Erreur lors du traitement de la signature",
"signer-info-not-available": "Informations sur le signataire non disponibles",
"cert-validity-not-checked": "Validité du certificat non vérifiée",
"valid": "Valide",
"expired-or-not-yet-valid": "Expiré ou pas encore valide",
"valid-from": "Valide du",
"to": "au",
"signer": "Signataire",
"issuer": "Émetteur",
"not-available": "Non disponible",
"not-performed": "Non effectué",
"missing-acrofield-dict": "Dictionnaire Acrofield manquant",
"signature-dictionary-not-found-or-invalid": "Dictionnaire de signatures introuvable ou invalide",
"missing-or-invalid-byterange": "ByteRange manquant ou invalide",
"missing-or-invalid-contents": "Contenu manquant ou invalide",
"missing-signature-contents": "Contenu de la signature manquant",
"invalid-signature-hex-format": "Format hexadécimal de signature invalide",
"unsupported-signature-format-not-signeddata": "Format de signature non pris en charge - pas SignedData",
"signer-certificate-not-found": "Certificat du signataire introuvable",
"no-certificates-in-signature": "Aucun certificat dans la signature",
"no-signer-info-in-pkcs7": "Aucune information sur le signataire dans PKCS#7",
"could-not-parse-signer-info": "Impossible d'analyser les informations sur le signataire",
"not-calculated": "Non calculé",
"not-found-in-signature": "Introuvable dans la signature"
}
File diff suppressed because it is too large Load Diff
@@ -1,6 +1,7 @@
{
"header-news": "Nuova funzionalità: Gli utenti del piano Teams possono ora integrare i propri bucket AWS S3 per l'archiviazione dei file",
"header-news-btn": "Configura Ora",
"sandbox-news": "Questo è un ambiente sandbox. Si prega di non utilizzarlo per scopi di produzione.",
"create-account": "Crea Account",
"login": "Accedi",
"language": "Lingua",
@@ -675,7 +676,7 @@
"public-template-mssg-1": "Per integrare OpenSign nel tuo progetto React o Next.js, esegui semplicemente il seguente comando:",
"public-template-mssg-2": "Assicurati di avere npm o yarn configurato nel tuo progetto. Se usi Yarn, puoi sostituire npm install con yarn add @opensign/react.",
"public-template-mssg-3": "Hai bisogno di maggiori dettagli o esempi?",
"public-template-mssg-4": "Visita il",
"public-template-mssg-4": "Visita il ",
"public-template-mssg-5": " npm per gli aggiornamenti più recenti, documentazione dettagliata e cronologia delle versioni.",
"public-template-mssg-6": "Prima di poter utilizzare questo frammento di codice, devi rendere questo modello pubblico.",
"public-template-mssg-7": "Prima di poter generare un link pubblico, devi rendere questo modello pubblico.",
@@ -980,5 +981,46 @@
"finish-mssg": "Sei sicuro di voler completare il documento?",
"review": "Rivedere",
"next-field": "Campo successivo",
"required-mssg":"{{leftRequiredWidget}} di {{totalWidget}} campi rimanenti"
"required-mssg":"{{leftRequiredWidget}} di {{totalWidget}} campi rimanenti",
"verify-document-signature": "Verifica firma documento",
"select-pdf-document": "Seleziona documento PDF",
"selected-file": "File selezionato",
"verify-signature": "Verifica firma",
"verification-status": "Stato verifica",
"verification-in-progress": "Verifica in corso...",
"verification-results-will-appear-here": "I risultati della verifica appariranno qui",
"please-select-pdf": "Seleziona un file PDF valido",
"please-select-file-to-verify": "Seleziona un file da verificare",
"no-signature-found": "Nessuna firma trovata nel documento",
"error-verifying-pdf": "Errore durante la verifica del PDF",
"signature-valid-basic": "La firma è valida",
"signature-invalid-basic": "La firma non è valida",
"all-signatures-verified-convincing": "Documento Verificato: Tutte le firme sono state validate con successo.",
"some-signatures-invalid-basic": "Alcune firme non sono valide",
"no-signatures-processed": "Nessuna firma elaborata",
"unnamed-signature-field": "Campo firma senza nome",
"error-processing-signature": "Errore durante l'elaborazione della firma",
"signer-info-not-available": "Informazioni firmatario non disponibili",
"cert-validity-not-checked": "Validità certificato non verificata",
"valid": "Valido",
"expired-or-not-yet-valid": "Scaduto o non ancora valido",
"valid-from": "Valido dal",
"to": "al",
"signer": "Firmatario",
"issuer": "Emittente",
"not-available": "Non disponibile",
"not-performed": "Non eseguito",
"missing-acrofield-dict": "Dizionario Acrofield mancante",
"signature-dictionary-not-found-or-invalid": "Dizionario firme non trovato o non valido",
"missing-or-invalid-byterange": "ByteRange mancante o non valido",
"missing-or-invalid-contents": "Contenuto mancante o non valido",
"missing-signature-contents": "Contenuto firma mancante",
"invalid-signature-hex-format": "Formato esadecimale firma non valido",
"unsupported-signature-format-not-signeddata": "Formato firma non supportato - non SignedData",
"signer-certificate-not-found": "Certificato firmatario non trovato",
"no-certificates-in-signature": "Nessun certificato nella firma",
"no-signer-info-in-pkcs7": "Nessuna informazione firmatario in PKCS#7",
"could-not-parse-signer-info": "Impossibile analizzare le informazioni del firmatario",
"not-calculated": "Non calcolato",
"not-found-in-signature": "Non trovato nella firma"
}
+15 -11
View File
@@ -1,4 +1,4 @@
import React, { useState, useEffect, lazy } from "react";
import { useState, useEffect, lazy } from "react";
import { Routes, Route, BrowserRouter } from "react-router";
import { pdfjs } from "react-pdf";
import Form from "./pages/Form";
@@ -30,7 +30,7 @@ const AddAdmin = lazy(() => import("./pages/AddAdmin"));
const UpdateExistUserAdmin = lazy(() => import("./pages/UpdateExistUserAdmin"));
const Preferences = lazy(() => import("./pages/Preferences"));
const Login = lazy(() => import("./pages/Login"));
const VerifyDocument = lazy(() => import("./pages/VerifyDocument"));
pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/legacy/build/pdf.worker.min.mjs`;
const AppLoader = () => {
return (
@@ -106,10 +106,10 @@ function App() {
element={<LazyPage Page={GuestLogin} />}
/>
<Route path="/debugpdf" element={<LazyPage Page={DebugPdf} />} />
<Route
path="/forgetpassword"
element={<LazyPage Page={ForgetPassword} />}
/>
<Route
path="/forgetpassword"
element={<LazyPage Page={ForgetPassword} />}
/>
<Route
element={
<ValidateSession>
@@ -117,10 +117,10 @@ function App() {
</ValidateSession>
}
>
<Route
path="/changepassword"
element={<LazyPage Page={ChangePassword} />}
/>
<Route
path="/changepassword"
element={<LazyPage Page={ChangePassword} />}
/>
<Route path="/form/:id" element={<Form />} />
<Route path="/report/:id" element={<Report />} />
<Route path="/dashboard/:id" element={<Dashboard />} />
@@ -165,7 +165,11 @@ function App() {
path="/recipientSignPdf/:docId"
element={<PdfRequestFiles />}
/>
<Route path="/users" element={<UserList />} />
<Route path="/users" element={<UserList />} />
<Route
path="/verify-document"
element={<LazyPage Page={VerifyDocument} />}
/>
<Route
path="/preferences"
element={<LazyPage Page={Preferences} />}
+15 -4
View File
@@ -1,4 +1,4 @@
import React, { useState, useEffect } from "react";
import { useState, useEffect } from "react";
import dp from "../assets/images/dp.png";
import FullScreenButton from "./FullScreenButton";
import { useNavigate } from "react-router";
@@ -172,15 +172,26 @@ const Header = ({ showSidebar, setIsMenu, isConsole }) => {
<i className="fa-light fa-user"></i> {t("profile")}
</span>
</li>
<li
onClick={() => {
setIsOpen(false);
navigate("/changepassword");
}}
>
<span>
<i className="fa-light fa-lock"></i>{" "}
{t("change-password")}
</span>
</li>
<li
onClick={() => {
setIsOpen(false);
navigate("/changepassword");
navigate("/verify-document");
}}
>
<span>
<i className="fa-light fa-lock"></i>{" "}
{t("change-password")}
<i className="fa-light fa-check-square"></i>{" "}
{t("verify-document")}
</span>
</li>
</>
@@ -12,19 +12,32 @@ function AgreementSign(props) {
<div className="op-modal op-modal-open absolute z-[448]">
<div className="w-[95%] md:w-[60%] lg:w-[40%] op-modal-box overflow-y-auto hide-scrollbar text-sm p-4">
<div className="flex flex-row items-center">
<input
data-tut="IsAgree"
className="mr-3 op-checkbox op-checkbox-m"
type="checkbox"
value={isChecked}
onChange={(e) => {
setIsChecked(e.target.checked);
if (e.target.checked) {
props.setIsAgreeTour(false);
}
props.showFirstWidget();
}}
/>
<label className="inline-flex justify-center items-center cursor-pointer mb-0">
{/* 1) This div becomes the “fake” checkbox */}
<div
data-tut="IsAgree"
className={`w-6 h-6 border-2 mr-3 rounded-full flex text-center items-center justify-center ${isChecked ? "op-border-primary" : "border-red-500"}`}
>
{isChecked ? (
<span className="op-text-primary text-sm font-bold"></span>
) : (
<span className="text-red-500 text-sm font-bold">X</span>
)}
</div>
{/* 2) Visually hide the native checkbox but keep it in the DOM */}
<input
className="sr-only"
type="checkbox"
checked={isChecked}
onChange={(e) => {
setIsChecked(e.target.checked);
if (e.target.checked) {
props.setIsAgreeTour(false);
}
props.showFirstWidget();
}}
/>
</label>
<div className="text-[11px] md:text-base">
<span>{t("agree-p1")}</span>
<span
@@ -22,7 +22,7 @@ function DropdownWidgetOption(props) {
const resetState = () => {
setDropdownOptionList(["option-1", "option-2"]);
setDropdownName( props.currWidgetsDetails?.options?.name || props.type);
setDropdownName(props.currWidgetsDetails?.options?.name || props.type);
setIsReadOnly(false);
setIsHideLabel(false);
setMinCount(0);
@@ -115,7 +115,7 @@ function DropdownWidgetOption(props) {
defaultData,
isHideLabel
);
resetState()
resetState();
};
@@ -1,4 +1,4 @@
import React, {
import {
useState,
useRef,
} from "react";
@@ -1,6 +1,5 @@
import React, { useState, useEffect, useRef } from "react";
import { useState, useEffect, useRef } from "react";
import BorderResize from "./BorderResize";
import PlaceholderBorder from "./PlaceholderBorder";
import { Rnd } from "react-rnd";
import {
changeDateToMomentFormat,
@@ -23,6 +22,7 @@ import ModalUi from "../../primitives/ModalUi";
import { useTranslation } from "react-i18next";
import { useDispatch } from "react-redux";
import { setIsShowModal } from "../../redux/reducers/widgetSlice";
import { themeColor } from "../../constant/const";
const selectFormat = (data) => {
switch (data) {
@@ -78,7 +78,6 @@ function Placeholder(props) {
const dispatch = useDispatch();
const widgetData =
props.pos?.options?.defaultValue || props.pos?.options?.response;
const [placeholderBorder, setPlaceholderBorder] = useState({ w: 0, h: 0 });
const [isDateModal, setIsDateModal] = useState(false);
const [containerScale, setContainerScale] = useState();
const holdTimeout = useRef(null);
@@ -93,10 +92,6 @@ function Placeholder(props) {
)
: new Date();
const [getCheckboxRenderWidth, setGetCheckboxRenderWidth] = useState({
width: null,
height: null
});
useEffect(() => {
const getPdfPageWidth = props.pdfOriginalWH.find(
(data) => data.pageNumber === props.pageNumber
@@ -127,18 +122,6 @@ function Placeholder(props) {
}
}, [widgetData]);
const handleGetDaynamicWH = () => {
if (
props?.pos?.type === "checkbox" ||
props?.pos?.type === radioButtonWidget
) {
const rndElement = document.getElementById(props.pos.key);
if (rndElement) {
const { width, height } = rndElement.getBoundingClientRect();
setGetCheckboxRenderWidth({ width: width, height: height });
}
}
};
//function change format array list with selected date and format
const changeDateFormat = () => {
const updateDate = [];
@@ -309,7 +292,6 @@ function Placeholder(props) {
//The else condition is used to handle the case when the user clicks on a widget and open signature pad to draw sign
props.setCurrWidgetsDetails && props.setCurrWidgetsDetails(props.pos);
handleWidgetIdandPopup();
handleGetDaynamicWH();
}
};
//`handleOnClickSettingIcon` is used set current widget details and open setting of it
@@ -823,7 +805,6 @@ function Placeholder(props) {
background: handleBackground()
}}
onDrag={() => {
handleGetDaynamicWH();
props.handleTabDrag && props.handleTabDrag(props.pos.key);
}}
size={{
@@ -838,7 +819,10 @@ function Placeholder(props) {
? "auto"
: props.posHeight(props.pos, props.isSignYourself)
}}
minHeight={calculateFont(props.pos.options?.fontSize, true)}
minHeight={
props.pos.type !== "checkbox" &&
calculateFont(props.pos.options?.fontSize, true)
}
maxHeight="auto"
onResizeStart={() => {
props.setIsResize && props.setIsResize(true);
@@ -873,12 +857,6 @@ function Placeholder(props) {
x: xPos(props.pos, props.isSignYourself),
y: yPos(props.pos, props.isSignYourself)
}}
onResize={(e, direction, ref) => {
setPlaceholderBorder({
w: ref.offsetWidth / (props.scale * containerScale),
h: ref.offsetHeight / (props.scale * containerScale)
});
}}
disableDragging={handleDragging()}
>
{props.pos.key === props?.currWidgetsDetails?.key &&
@@ -911,40 +889,27 @@ function Placeholder(props) {
props.pos.key === props?.currWidgetsDetails?.key && <BorderResize />
)}
{/* 1- Show a border if props.pos.key === props?.currWidgetsDetails?.key, indicating the current user's selected widget.
2- If props.isShowBorder is true, display borders for all widgets.
3- Use the combination of props?.isAlllowModify and !props?.assignedWidgetId.includes(props.pos.key) to determine when to show borders:
1- When isAlllowModify is true, show borders.
2- Do not display border for widgets already assigned (props.assignedWidgetId.includes(props.pos.key) is true).
*/}
{props.pos.key === props?.currWidgetsDetails?.key &&
(props.isShowBorder ||
(props?.isAlllowModify &&
!props?.assignedWidgetId.includes(props.pos.key))) && (
<PlaceholderBorder
pos={props.pos}
isPlaceholder={props.isPlaceholder}
getCheckboxRenderWidth={getCheckboxRenderWidth}
scale={props.scale}
containerScale={containerScale}
placeholderBorder={placeholderBorder}
/>
)}
{/* 1- Show a ouline if props.pos.key === props?.currWidgetsDetails?.key, indicating the current user's selected widget.
2- If props.isShowBorder is true, display ouline for all widgets.
3- Use the combination of props?.isAlllowModify and !props?.assignedWidgetId.includes(props.pos.key) to determine when to show ouline:
3.1- When isAlllowModify is true, show ouline.
3.2- Do not display ouline for widgets already assigned (props.assignedWidgetId.includes(props.pos.key) is true).
*/}
<div
className="flex items-stretch justify-center"
className={`${
props.pos.key === props?.currWidgetsDetails?.key &&
(props.isShowBorder ||
(props?.isAlllowModify &&
!props?.assignedWidgetId.includes(props.pos.key)))
? "outline-[0.3px] outline-dashed outline-offset-[10px]"
: ""
} flex items-stretch justify-center`}
style={{
outlineColor: themeColor,
left: xPos(props.pos, props.isSignYourself),
top: yPos(props.pos, props.isSignYourself),
width:
props.pos.type === radioButtonWidget ||
props.pos.type === "checkbox"
? "auto"
: props.posWidth(props.pos, props.isSignYourself),
height:
props.pos.type === radioButtonWidget ||
props.pos.type === "checkbox"
? "auto"
: props.posHeight(props.pos, props.isSignYourself),
width: "100%",
height: "100%",
zIndex: "10"
}}
onTouchEnd={() => handleOnClickPlaceholder()}
@@ -1,56 +0,0 @@
import React from "react";
import { themeColor } from "../../constant/const";
import {
defaultWidthHeight,
isMobile,
radioButtonWidget,
resizeBorderExtraWidth,
textWidget
} from "../../constant/Utils";
function PlaceholderBorder(props) {
const getResizeBorderExtraWidth = resizeBorderExtraWidth();
const defaultWidth = defaultWidthHeight(props.pos.type).width;
const defaultHeight = defaultWidthHeight(props.pos.type).height;
const width = () => {
const getWidth =
props.placeholderBorder.w || props.pos.Width || defaultWidth;
return (
getWidth * props.scale * props.containerScale + getResizeBorderExtraWidth
);
};
const height = () => {
const getHeight =
props.placeholderBorder.h || props.pos.Height || defaultHeight;
return (
getHeight * props.scale * props.containerScale + getResizeBorderExtraWidth
);
};
const handleMinWidth = () => {
if (props.pos.type === "checkbox" || props.pos.type === radioButtonWidget) {
return props.getCheckboxRenderWidth.width + getResizeBorderExtraWidth;
} else {
return width();
}
};
const handleMinHeight = () => {
if (props.pos.type === "checkbox" || props.pos.type === radioButtonWidget) {
return props.getCheckboxRenderWidth.height + getResizeBorderExtraWidth;
} else {
return height();
}
};
return (
<div
className="absolute inline-block w-[14px] h-[14px] border-[0.2px] overflow-hidden border-dashed"
style={{
borderColor: themeColor,
minWidth: handleMinWidth() || 0,
minHeight: handleMinHeight() || 0
}}
></div>
);
}
export default PlaceholderBorder;
@@ -98,22 +98,6 @@ function PlaceholderType(props) {
}
};
//handle height on enter press in text area
// const handleEnterPress = (e) => {
// const height = 18;
// if (e.key === "Enter") {
// //function to save height of text area
// onChangeHeightOfTextArea(
// height,
// props.pos.type,
// props.pos.key,
// props.xyPosition,
// props.index,
// props.setXyPosition,
// props.data && props.data?.Id
// );
// }
// };
switch (type) {
case "signature":
return props.pos.SignUrl ? (
@@ -168,34 +152,34 @@ function PlaceholderType(props) {
<div style={{ zIndex: props.isSignYourself && "99" }}>
{props.pos.options?.values?.map((data, ind) => {
return (
<div
key={ind}
className="select-none-cls flex items-center text-center gap-0.5 pointer-events-none"
>
<input
id={`checkbox-${props.pos.key + ind}`}
style={{ width: fontSize, height: fontSize }}
className={`${
ind === 0 ? "mt-0" : "mt-[5px]"
} flex justify-center op-checkbox rounded-[1px] `}
disabled={
props.isNeedSign &&
(props.pos.options?.isReadOnly ||
props.data?.signerObjId !== props.signerObjId)
}
type="checkbox"
readOnly
checked={!!selectCheckbox(ind, selectedCheckbox)}
/>
{!props.pos.options?.isHideLabel && (
<label
htmlFor={`checkbox-${props.pos.key + ind}`}
style={{ fontSize: fontSize, color: fontColor }}
className="text-xs mb-0 text-center"
>
{data}
</label>
)}
<div key={ind} className="select-none-cls pointer-events-none">
<label
htmlFor={`checkbox-${props.pos.key + ind}`}
style={{ fontSize: fontSize, color: fontColor }}
className={`mb-0 flex items-center gap-1 ${
ind > 0 ? "mt-[3px]" : "mt-[0px]"
}`}
>
<input
id={`checkbox-${props.pos.key + ind}`}
style={{
width: fontSize,
height: fontSize
}}
className="op-checkbox rounded-[1px]"
disabled={
props.isNeedSign &&
(props.pos.options?.isReadOnly ||
props.data?.signerObjId !== props.signerObjId)
}
type="checkbox"
readOnly
checked={!!selectCheckbox(ind, selectedCheckbox)}
/>
{!props.pos.options?.isHideLabel && (
<span className="leading-none">{data}</span>
)}
</label>
</div>
);
})}
@@ -243,7 +227,6 @@ function PlaceholderType(props) {
<i className="fa-light fa-circle-chevron-down mr-1 "></i>
</div>
);
case "initials":
return props.pos.SignUrl ? (
<img
@@ -442,38 +425,39 @@ function PlaceholderType(props) {
<div>
{props.pos.options?.values.map((data, ind) => {
return (
<div
key={ind}
className="select-none-cls flex items-center text-center gap-0.5 pointer-events-none"
>
<input
readOnly
id={`radio-${props.pos.key + ind}`}
<div key={ind} className="select-none-cls pointer-events-none">
<label
htmlFor={`radio-${props.pos.key + ind}`}
style={{
width: fontSize,
height: fontSize,
marginTop: ind > 0 ? "10px" : "0px"
fontSize: fontSize,
color: fontColor,
marginTop: ind > 0 ? "5px" : "0px"
}}
className={`op-radio rounded-full border- border-black appearance-none bg-white inline-block align-middle relative ${
handleRadioCheck(data) ? "checked-radio" : ""
}`}
type="radio"
disabled={
props.isNeedSign &&
(props.pos.options?.isReadOnly ||
props.data?.signerObjId !== props.signerObjId)
}
checked={handleRadioCheck(data)}
/>
{!props.pos.options?.isHideLabel && (
<label
htmlFor={`radio-${props.pos.key + ind}`}
style={{ fontSize: fontSize, color: fontColor }}
className="text-xs mb-0"
>
{data}
</label>
)}
className="text-xs mb-0 flex items-center gap-1 "
>
<input
readOnly
id={`radio-${props.pos.key + ind}`}
style={{
width: fontSize,
height: fontSize,
lineHeight: 2
}}
className={`op-radio rounded-full border- border-black appearance-none bg-white inline-block align-middle relative ${
handleRadioCheck(data) ? "checked-radio" : ""
}`}
type="radio"
disabled={
props.isNeedSign &&
(props.pos.options?.isReadOnly ||
props.data?.signerObjId !== props.signerObjId)
}
checked={handleRadioCheck(data)}
/>
{!props.pos.options?.isHideLabel && (
<span className="leading-none">{data}</span>
)}
</label>
</div>
);
})}
@@ -9,7 +9,8 @@ function SelectLanguage(props) {
{ value: "es", text: "Española" }, //spanish
{ value: "fr", text: "Français" }, //french
{ value: "it", text: "Italiano" }, //italian
{ value: "de", text: "Deutsch" } //german
{ value: "de", text: "Deutsch" }, //german
{ value: "hi", text: "हिन्दी" } //hindi
];
const defaultLanguage = i18next.language || "en";
const [lang, setLang] = useState(defaultLanguage);
@@ -241,6 +241,7 @@ function WidgetComponent(props) {
handleDivClick={props.handleDivClick}
handleMouseLeave={props.handleMouseLeave}
signRef={signRef}
addPositionOfSignature={props.addPositionOfSignature}
/>
</div>
</div>
@@ -14,7 +14,6 @@ import {
onSaveSign,
radioButtonWidget,
selectCheckbox,
signatureTypes,
textInputWidget,
textWidget,
years
@@ -85,7 +84,8 @@ function WidgetsValueModal(props) {
setXyPosition,
isSave,
setUniqueId,
tempSignerId
tempSignerId,
signatureTypes
} = props;
const [penColor, setPenColor] = useState("blue");
const [isOptional, setIsOptional] = useState(true);
@@ -231,7 +231,9 @@ function WidgetsValueModal(props) {
setXyPosition,
uniqueId,
false,
data?.format
data?.format,
currWidgetsDetails?.options?.fontSize || 12,
currWidgetsDetails?.options?.fontColor || "black"
);
setSelectDate({ date: date, format: data?.format });
};
@@ -398,7 +400,7 @@ function WidgetsValueModal(props) {
if (getIndex !== -1) {
setIsSignTypes(true);
const tab = signatureTypes[getIndex].name;
const tab = signatureTypes?.[getIndex].name;
if (tab === "draw") {
setIsTab("draw");
setSignatureType("draw");
@@ -425,7 +427,7 @@ function WidgetsValueModal(props) {
}
}
function isTabEnabled(tabName) {
const isEnabled = signatureTypes.find((x) => x.name === tabName)?.enabled;
const isEnabled = signatureTypes?.find((x) => x.name === tabName)?.enabled;
return isEnabled;
}
@@ -615,21 +617,19 @@ function WidgetsValueModal(props) {
setImage();
handleTab();
};
const autoSignAll = () => {
return (
<label className="cursor-pointer flex items-center text-sm">
<input
className="mr-2 md:mr-3 op-checkbox op-checkbox-xs md:op-checkbox-sm"
type="checkbox"
value={isAutoSign}
onChange={(e) => {
setIsAutoSign(e.target.checked);
}}
/>
{t("auto-sign-mssg")}
</label>
);
};
const autoSignAll = (
<label className="mb-0 cursor-pointer flex items-center text-sm">
<input
className="mr-2 md:mr-3 op-checkbox op-checkbox-xs md:op-checkbox-sm"
type="checkbox"
value={isAutoSign}
onChange={(e) => {
setIsAutoSign(e.target.checked);
}}
/>
{t("auto-sign-mssg")}
</label>
);
useEffect(() => {
const loadFont = async () => {
@@ -683,51 +683,64 @@ function WidgetsValueModal(props) {
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isTab]);
//function for convert input text value in image
// function for convert input text value in image
const convertToImg = async (fontStyle, text, color) => {
//get text content to convert in image
const textContent = text;
const fontfamily = fontStyle
? fontStyle
: fontSelect
? fontSelect
: "Fasthand";
const fontSizeValue = "40px";
//creating span for getting text content width
// 1) Read the max widget dimensions:
const maxWidth = currWidgetsDetails.Width; // e.g. 150 px
const maxHeight = currWidgetsDetails.Height; // e.g. 40 px
// 2) Pick a “baseline” font size for measurement:
const baselineFontSizePx = 40;
const chosenFontFamily = fontStyle || fontSelect || "Fasthand";
const fillColor = color || penColor;
// 3) Create a temporary <span> (hidden) to measure the text at 40px:
const span = document.createElement("span");
span.textContent = textContent;
span.style.font = `${fontSizeValue} ${fontfamily}`; // here put your text size and font family
span.style.color = color ? color : penColor;
span.style.display = "hidden";
document.body.appendChild(span); // Replace 'container' with the ID of the container element
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);
//create canvas to render text in canvas and convert in image
const canvasElement = document.createElement("canvas");
// Draw the text content on the canvas
const ctx = canvasElement.getContext("2d");
// 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 addExtraWidth = currWidgetsDetails?.type === "initials" ? 10 : 50;
const width = span.offsetWidth + addExtraWidth;
const height = span.offsetHeight;
setTextWidth(width);
setTextHeight(height);
const font = span.style["font"];
// Set the canvas dimensions to match the span
canvasElement.width = width * pixelRatio;
canvasElement.height = height * pixelRatio;
const canvas = document.createElement("canvas");
// You can customize text styles if needed
ctx.font = font;
ctx.fillStyle = color ? color : penColor; // Set the text color
// ★ 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";
ctx.scale(pixelRatio, pixelRatio);
// Draw the content of the span onto the canvas
ctx.fillText(span.textContent, width / 2, height / 2); // Adjust the x,y-coordinate as needed
//remove span tag
document.body.removeChild(span);
// Convert the canvas to image data
const dataUrl = canvasElement.toDataURL("image/png");
// ★ 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);
};
const PenColorComponent = (props) => {
@@ -857,7 +870,7 @@ function WidgetsValueModal(props) {
]);
const savesigncheckbox = (
<label className="cursor-pointer flex items-center mb-0 text-center text-[11px] md:text-base">
<label className="cursor-pointer flex items-center mb-0 text-center text-sm">
<input
className="mr-2 md:mr-3 op-checkbox op-checkbox-xs md:op-checkbox-sm"
type="checkbox"
@@ -1059,7 +1072,14 @@ function WidgetsValueModal(props) {
</div>
</div>
)}
<div className="mt-4 h-full">
<div
className={`${
currWidgetsDetails?.type === "stamp" ||
currWidgetsDetails?.type === "image"
? ""
: "mt-3"
} h-full`}
>
{isDefaultSign ? (
<>
{currWidgetsDetails?.type !== "initials" &&
@@ -1085,7 +1105,7 @@ function WidgetsValueModal(props) {
{/* Standalone autoSignAll for "My Signature/Initials" (isDefaultSign) if conditions met */}
{setIsAutoSign && uniqueId && (
<div className="flex justify-center my-2">
{autoSignAll()}
{autoSignAll}
</div>
)}
</>
@@ -1114,7 +1134,7 @@ function WidgetsValueModal(props) {
{/* Standalone autoSignAll for "My Signature/Initials" (isDefaultSign) if conditions met */}
{setIsAutoSign && uniqueId && (
<div className="flex justify-center my-2">
{autoSignAll()}
{autoSignAll}
</div>
)}
</>
@@ -1163,9 +1183,8 @@ function WidgetsValueModal(props) {
["image", "stamp"].includes(
currWidgetsDetails?.type
)))) && (
<div className="flex justify-center items-center space-x-4 my-2">
{setIsAutoSign && uniqueId && autoSignAll()}
<div className="flex justify-center items-center gap-x-2 my-2">
{setIsAutoSign && uniqueId && autoSignAll}
{image &&
(isImageSelect ||
["image", "stamp"].includes(
@@ -1173,7 +1192,7 @@ function WidgetsValueModal(props) {
)) && (
<label
htmlFor={`removeBgToggleModal-${currWidgetsDetails?.key}`}
className="cursor-pointer flex items-center text-sm"
className="mb-0 cursor-pointer flex items-center text-sm"
>
<input
type="checkbox"
@@ -1253,16 +1272,18 @@ function WidgetsValueModal(props) {
<div className="flex flex-row justify-between mt-[10px]">
<PenColorComponent />
</div>
<div className="flex flex-col mt-2">
<div className="flex flex-row ml-1 mt-2 gap-x-3">
{/* Standalone autoSignAll for "Type" tab if conditions met */}
{setIsAutoSign && uniqueId && (
<div className="flex justify-start my-1">
{autoSignAll()}
{autoSignAll}
</div>
)}
{accesstoken && (
<div className="flex justify-start my-1">
{saveSignCheckbox?.isVisible && savesigncheckbox}
</div>
)}
{accesstoken &&
saveSignCheckbox?.isVisible &&
savesigncheckbox}
</div>
</>
) : (
@@ -1287,16 +1308,18 @@ function WidgetsValueModal(props) {
<div className="flex flex-row justify-between mt-[10px]">
<PenColorComponent />
</div>
<div className="flex flex-col mt-2">
<div className="flex flex-row ml-1 mt-1 gap-x-3">
{/* Standalone autoSignAll for "Draw" tab if conditions met */}
{setIsAutoSign && uniqueId && (
<div className="flex justify-start my-1">
{autoSignAll()}
{autoSignAll}
</div>
)}
{accesstoken && (
<div className="flex justify-start my-1">
{saveSignCheckbox?.isVisible && savesigncheckbox}
</div>
)}
{accesstoken &&
saveSignCheckbox?.isVisible &&
savesigncheckbox}
</div>
</>
)}
@@ -1324,50 +1347,45 @@ function WidgetsValueModal(props) {
);
case "checkbox":
return (
<div className="border-[1px] border-gray-300 rounded-[2px] p-1 px-3">
<div className="border-[1px] border-gray-300 rounded-[2px] pt-1 px-2.5">
{currWidgetsDetails?.options?.values?.map((data, ind) => {
return (
<div
key={ind}
className=" select-none-cls flex items-center text-center gap-0.5"
>
<input
id={`checkbox-${currWidgetsDetails?.key + ind}`}
className={`${
ind === 0 ? "mt-0" : "mt-[5px]"
} op-checkbox op-checkbox-sm rounded-[1px] `}
type="checkbox"
checked={!!selectCheckbox(ind, selectedCheckbox)}
onChange={(e) => {
if (e.target.checked) {
const maxRequired =
currWidgetsDetails?.options?.validation
?.maxRequiredCount;
const maxCountInt =
maxRequired && parseInt(maxRequired);
if (maxCountInt > 0) {
if (
selectedCheckbox &&
selectedCheckbox?.length <= maxCountInt - 1
) {
<div key={ind} className=" select-none-cls">
<label
htmlFor={`checkbox-${currWidgetsDetails?.key + ind}`}
className="text-xs flex items-center gap-1"
>
<input
id={`checkbox-${currWidgetsDetails?.key + ind}`}
className={`${
ind === 0 ? "mt-0" : "mt-[5px]"
} op-checkbox op-checkbox-xs rounded-[1px] mt-1`}
type="checkbox"
checked={!!selectCheckbox(ind, selectedCheckbox)}
onChange={(e) => {
if (e.target.checked) {
const maxRequired =
currWidgetsDetails?.options?.validation
?.maxRequiredCount;
const maxCountInt =
maxRequired && parseInt(maxRequired);
if (maxCountInt > 0) {
if (
selectedCheckbox &&
selectedCheckbox?.length <= maxCountInt - 1
) {
handleCheckboxValue(e.target.checked, ind);
}
} else {
handleCheckboxValue(e.target.checked, ind);
}
} else {
handleCheckboxValue(e.target.checked, ind);
}
} else {
handleCheckboxValue(e.target.checked, ind);
}
}}
/>
{!currWidgetsDetails?.options?.isHideLabel && (
<label
htmlFor={`checkbox-${currWidgetsDetails?.key + ind}`}
className="text-xs mb-0 text-center"
>
{data}
</label>
)}
}}
/>
{data}
</label>
</div>
);
})}
@@ -1512,35 +1530,26 @@ function WidgetsValueModal(props) {
);
case radioButtonWidget:
return (
<div className="border-[1px] border-gray-300 rounded-[2px] p-1 px-3">
<div className="border-[1px] border-gray-300 rounded-[2px] pt-1 px-2.5">
{currWidgetsDetails?.options?.values.map((data, ind) => {
return (
<div
key={ind}
className="select-none-cls flex items-center text-center gap-0.5"
>
<input
id={`radio-${currWidgetsDetails?.key + ind}`}
style={{
marginTop: ind > 0 ? "10px" : "0px"
}}
className={`flex justify-center op-radio`}
type="radio"
value={data}
checked={handleRadioCheck(data)}
onChange={(e) => {
handleCheckRadio(e.target.value);
}}
/>
{!currWidgetsDetails?.options?.isHideLabel && (
<label
htmlFor={`radio-${currWidgetsDetails?.key + ind}`}
// style={{ fontSize: fontSize, color: fontColor }}
className="text-xs mb-0"
>
{data}
</label>
)}
<div key={ind} className="select-none-cls">
<label
htmlFor={`radio-${currWidgetsDetails?.key + ind}`}
className="cursor-pointer flex items-center text-sm gap-1"
>
<input
id={`radio-${currWidgetsDetails?.key + ind}`}
className={`op-radio op-radio-xs mt-1`}
type="radio"
value={data}
checked={handleRadioCheck(data)}
onChange={(e) => {
handleCheckRadio(e.target.value);
}}
/>
<span>{data}</span>
</label>
</div>
);
})}
@@ -1652,7 +1661,7 @@ function WidgetsValueModal(props) {
const validateExpression = (regexValidation) => {
if (widgetValue && regexValidation) {
let regexObject = regexValidation;
if (props.pos?.options?.validation?.type === "regex") {
if (currWidgetsDetails?.options?.validation?.type === "regex") {
regexObject = RegexParser(regexValidation);
}
let isValidate = regexObject.test(widgetValue);
@@ -1675,7 +1684,12 @@ function WidgetsValueModal(props) {
validateExpression(regexValidation);
break;
default:
regexValidation = props.pos?.options?.validation?.pattern || "";
// Grab the current pattern (if it exists)
const pattern = currWidgetsDetails?.options?.validation?.pattern;
// Removed `backwordSupportPattern` (/^[a-zA-Z0-9s]+$/) — it blocked spaces and special characters.
const backwordSupportPattern =
pattern && pattern === "/^[a-zA-Z0-9s]+$/" ? "" : pattern; // If it matches exactly '/^[a-zA-Z0-9s]+$/', clear it
regexValidation = backwordSupportPattern || "";
validateExpression(regexValidation);
break;
}
@@ -1795,11 +1809,11 @@ function WidgetsValueModal(props) {
<ModalUi
isOpen={true}
handleClose={() => !isShowValidation && handleclose()}
position="bottom"
>
<div className="h-[100%] p-[20px]">
<div className="h-[100%] p-[18px]">
{isFinish ? (
<>
{" "}
<div className="p-1 mt-3">
<span className="text-base">{t("finish-mssg")}</span>
</div>
@@ -1824,7 +1838,7 @@ function WidgetsValueModal(props) {
</>
) : (
<>
<div className="p-1 m-1">
<div>
<div className="relative inline-block">
<span className="text-base">
{currWidgetsDetails?.options?.name || widgetTypeTranslation}
@@ -1835,7 +1849,7 @@ function WidgetsValueModal(props) {
</span>
)}
</div>
<div className="flex flex-col justify-center m-2 mt-4">
<div className="flex flex-col justify-center m-2 mt-3">
<div className="flex justify-center">
{getWidgetType(currWidgetsDetails?.type)}
</div>
@@ -1852,15 +1866,18 @@ function WidgetsValueModal(props) {
) ? (
<button
type="button"
className="op-btn op-btn-ghost mr-1 mt-[2px]"
className="op-btn op-btn-ghost op-btn-sm mr-1"
onClick={() => handleClear()}
>
{t("clear")}
</button>
) : (
<div className="w-[80px]"></div>
<button
type="button"
className="op-btn op-btn-ghost op-btn-sm mr-1 cursor-default"
></button>
)}
<div className="flex items-center gap-2">
<div className="flex items-center gap-2">
{!isSave && <HandleRequiredField />}
{isSave ? (
<button
@@ -1888,7 +1905,7 @@ function WidgetsValueModal(props) {
) : (
<button
type="button"
className="op-btn op-btn-primary op-btn-sm"
className="op-btn op-btn-primary op-btn-sm text-xs md:text-sm"
onClick={() => {
handleClickOnNext();
}}
@@ -2,9 +2,19 @@ import React, { useEffect, useState } from "react";
import AsyncSelect from "react-select/async";
import { useTranslation } from "react-i18next";
import axios from "axios";
import { handleUnlinkSigner } from "../../../constant/Utils";
const SelectSigners = (props) => {
const { t } = useTranslation();
const {
signerPos,
setSignerPos,
signersData,
setSignersData,
uniqueId,
isRemove,
handleAddUser
} = props;
const [userList, setUserList] = useState([]);
const [selected, setSelected] = useState();
const [userData, setUserData] = useState({});
@@ -30,7 +40,7 @@ const SelectSigners = (props) => {
//checking if user select no signer option from dropdown
if (item) {
//checking selected signer is already assign to the document or not
const alreadyAssign = props.signersData.some(
const alreadyAssign = signersData.some(
(item2) => item2.objectId === item.value
);
if (alreadyAssign) {
@@ -49,7 +59,7 @@ const SelectSigners = (props) => {
};
const handleAdd = () => {
if (userData && userData.objectId) {
props.details(userData);
handleAddUser(userData);
if (props.closePopup) {
props.closePopup();
}
@@ -60,7 +70,13 @@ const SelectSigners = (props) => {
};
//function to use remove signer from assigned widgets in create template flow
const handleRemove = () => {
props.handleUnlinkSigner();
handleUnlinkSigner(
signerPos,
setSignerPos,
signersData,
setSignersData,
uniqueId
);
if (props.closePopup) {
props.closePopup();
}
@@ -161,7 +177,7 @@ const SelectSigners = (props) => {
<button className="op-btn op-btn-primary" onClick={() => handleAdd()}>
{t("submit")}
</button>
{props.isExistSigner && props.handleUnlinkSigner && (
{props.isExistSigner && isRemove && (
<button
className="op-btn op-btn-accent op-btn-outline"
onClick={() => handleRemove()}
+109 -43
View File
@@ -1,6 +1,5 @@
import axios from "axios";
import moment from "moment";
import React from "react";
import { PDFDocument, rgb, degrees } from "pdf-lib";
import Parse from "parse";
import { appInfo } from "./appinfo";
@@ -21,7 +20,9 @@ export const isTabAndMobile = window.innerWidth < 1023;
export const textInputWidget = "text input";
export const textWidget = "text";
export const radioButtonWidget = "radio button";
export function getEnv() {
return window?.RUNTIME_ENV || {};
}
//function for create list of year for date widget
export const range = (start, end, step) => {
@@ -510,10 +511,6 @@ export const defaultWidthHeight = (type) => {
}
};
export const resizeBorderExtraWidth = () => {
return 20;
};
export async function getBase64FromUrl(url, autosign) {
const data = await fetch(url);
const blob = await data.blob();
@@ -686,10 +683,14 @@ export const signPdfFun = async (
};
export const randomId = () => {
const randomBytes = crypto.getRandomValues(new Uint16Array(1));
const randomValue = randomBytes[0];
const randomDigit = 1000 + (randomValue % 9000);
return randomDigit;
// 1. Grab a cryptographically-secure 32-bit random value
const randomBytes = crypto.getRandomValues(new Uint32Array(1));
const raw = randomBytes[0]; // 0 … 4 294 967 295
// 2. Collapse into a 90 000 000-wide band (0…89 999 999), then shift to 10 000 000…99 999 999
const eightDigit = 10_000_000 + (raw % 90_000_000);
return eightDigit;
};
export const createDocument = async (
@@ -1114,7 +1115,7 @@ export const addInitialData = (signerPos, setXyPosition, value, userId) => {
};
//function for embed document id
export const embedDocId = async (pdfDoc, documentId, allPages) => {
export const embedDocId = async (pdfOriginalWH, pdfDoc, documentId) => {
const appName =
"OpenSign™";
// `fontBytes` is used to embed custom font in pdf
@@ -1123,19 +1124,21 @@ export const embedDocId = async (pdfDoc, documentId, allPages) => {
);
pdfDoc.registerFontkit(fontkit);
const font = await pdfDoc.embedFont(fontBytes, { subset: true });
for (let i = 0; i < allPages; i++) {
//pdfOriginalWH contained all pdf's pages width and height
for (let i = 0; i < pdfOriginalWH?.length; i++) {
const fontSize = 10;
const textContent =
documentId && `${appName} DocumentId: ${documentId} `;
const pages = pdfDoc.getPages();
const page = pages[i];
const getSize = pdfOriginalWH[i];
try {
const getObj = compensateRotation(
page.getRotation().angle,
10,
5,
1,
page.getSize(),
getSize,
fontSize,
rgb(0.5, 0.5, 0.5),
font,
@@ -1537,7 +1540,13 @@ const getWidgetsFontColor = (type) => {
}
};
//function for embed multiple signature using pdf-lib
export const multiSignEmbed = async (widgets, pdfDoc, signyourself, scale) => {
export const multiSignEmbed = async (
pdfOriginalWH,
widgets,
pdfDoc,
signyourself,
scale
) => {
// `fontBytes` is used to embed custom font in pdf
const fontBytes = await fileasbytes(
"https://cdn.opensignlabs.com/webfonts/times.ttf"
@@ -1546,6 +1555,11 @@ export const multiSignEmbed = async (widgets, pdfDoc, signyourself, scale) => {
const font = await pdfDoc.embedFont(fontBytes, { subset: true });
let hasError = false;
for (let item of widgets) {
//pdfOriginalWH contained all pdf's pages width and height
//'getSize' is used to get particular pdf's page width and height
const getSize = pdfOriginalWH.find(
(page) => page?.pageNumber === item?.pageNumber
);
if (hasError) break; // Stop the outer loop if an error occurred
const typeExist = item.pos.some((data) => data?.type);
let updateItem;
@@ -1665,10 +1679,11 @@ export const multiSignEmbed = async (widgets, pdfDoc, signyourself, scale) => {
].includes(position.type);
if (position.type === "checkbox") {
let checkboxGapFromTop, isCheck;
let y = yPos(position);
const optionsFontSize = fontSize || 13;
const checkboxSize = fontSize;
const checkboxTextGapFromLeft = fontSize + 5 || 22;
let y = yPos(position) + 2;
//calculate checkbox size to draw on pdf
const checkboxSize = fontSize - 1;
//calculate gap between checkbox and options
const checkboxTextGapFromLeft = fontSize + 5;
if (position?.options?.values.length > 0) {
position?.options?.values.forEach((item, ind) => {
const checkboxRandomId = "checkbox" + randomId();
@@ -1680,13 +1695,11 @@ export const multiSignEmbed = async (widgets, pdfDoc, signyourself, scale) => {
} else if (position?.options?.defaultValue) {
isCheck = position?.options?.defaultValue?.includes(ind);
}
const checkbox = form.createCheckBox(checkboxRandomId);
if (ind > 0) {
y = y + checkboxGapFromTop;
} else {
checkboxGapFromTop = fontSize + 5 || 26;
checkboxGapFromTop = fontSize + 3.2;
}
if (!position?.options?.isHideLabel) {
@@ -1694,10 +1707,10 @@ export const multiSignEmbed = async (widgets, pdfDoc, signyourself, scale) => {
const optionsPosition = compensateRotation(
page.getRotation().angle,
xPos(position) + checkboxTextGapFromLeft,
y,
y - 3,
1,
page.getSize(),
optionsFontSize,
getSize,
fontSize,
updateColorInRgb,
font,
page
@@ -1710,7 +1723,7 @@ export const multiSignEmbed = async (widgets, pdfDoc, signyourself, scale) => {
width: checkboxSize,
height: checkboxSize
};
checkboxObj = getWidgetPosition(page, checkboxObj, 1);
checkboxObj = getWidgetPosition(page, checkboxObj, 1, getSize);
checkbox.addToPage(page, checkboxObj);
//applied which checkbox should be checked
@@ -1780,7 +1793,7 @@ export const multiSignEmbed = async (widgets, pdfDoc, signyourself, scale) => {
: NewbreakTextIntoLines(textContent, fixedWidth);
// Set initial y-coordinate for the first line
let x = xPos(position);
let y = yPos(position);
let y = yPos(position) - 4;
// Embed each line on the page
for (const line of lines) {
const textPosition = compensateRotation(
@@ -1788,7 +1801,7 @@ export const multiSignEmbed = async (widgets, pdfDoc, signyourself, scale) => {
x,
y,
1,
page.getSize(),
getSize,
fontSize,
updateColorInRgb,
font,
@@ -1822,7 +1835,12 @@ export const multiSignEmbed = async (widgets, pdfDoc, signyourself, scale) => {
width: widgetWidth,
height: widgetHeight
};
const dropdownOption = getWidgetPosition(page, dropdownObj, 1);
const dropdownOption = getWidgetPosition(
page,
dropdownObj,
1,
getSize
);
const dropdownSelected = { ...dropdownOption, font: font };
dropdown.defaultUpdateAppearances(font);
dropdown.addToPage(page, dropdownSelected);
@@ -1830,28 +1848,33 @@ export const multiSignEmbed = async (widgets, pdfDoc, signyourself, scale) => {
} else if (position.type === radioButtonWidget) {
const radioRandomId = "radio" + randomId();
const radioGroup = form.createRadioGroup(radioRandomId);
let radioOptionGapFromTop;
const optionsFontSize = fontSize || 13;
const radioTextGapFromLeft = fontSize + 5 || 20;
//draw radio button on document and options if hide label is enable
let radioButtonFromTop;
//getting radio buttons options text font size
const optionsFontSize = fontSize;
//calculate value of gap between radio button and options
const radioTextGapFromLeft = fontSize + 6;
//getting radio button font size
const radioSize = fontSize;
//getting position of radio widget in y direction
let y = yPos(position);
//on the basic of option's length create radio button and message
if (position?.options?.values.length > 0) {
position?.options?.values.forEach((item, ind) => {
if (ind > 0) {
y = y + radioOptionGapFromTop;
y = y + radioButtonFromTop;
} else {
radioOptionGapFromTop = fontSize + 10 || 25;
radioButtonFromTop = fontSize + 6;
}
if (!position?.options?.isHideLabel) {
// below line of code is used to embed label with radio button in pdf
const optionsPosition = compensateRotation(
page.getRotation().angle,
xPos(position) + radioTextGapFromLeft,
y,
y - 2,
1,
page.getSize(),
optionsFontSize,
getSize,
fontSize,
updateColorInRgb,
font,
page
@@ -1860,13 +1883,13 @@ export const multiSignEmbed = async (widgets, pdfDoc, signyourself, scale) => {
page.drawText(item, optionsPosition);
}
let radioObj = {
x: xPos(position),
x: xPos(position) + 2,
y: y,
width: radioSize,
height: radioSize
};
radioObj = getWidgetPosition(page, radioObj, 1);
radioObj = getWidgetPosition(page, radioObj, 1, getSize);
radioGroup.addOptionToPage(item, page, radioObj);
});
}
@@ -1884,7 +1907,7 @@ export const multiSignEmbed = async (widgets, pdfDoc, signyourself, scale) => {
height: widgetHeight
};
const imageOptions = getWidgetPosition(page, signature, 1);
const imageOptions = getWidgetPosition(page, signature, 1, getSize);
page.drawImage(img, imageOptions);
}
} catch (err) {
@@ -2611,7 +2634,7 @@ function compensateRotation(
}
// `getWidgetPosition` is used to calulcate position of image type widget like x, y, width, height for pdf-lib
function getWidgetPosition(page, image, sizeRatio) {
function getWidgetPosition(page, image, sizeRatio, getSize) {
let pageWidth;
// pageHeight;
if ([90, 270].includes(page.getRotation().angle)) {
@@ -2635,7 +2658,7 @@ function getWidgetPosition(page, image, sizeRatio) {
imageX,
imageYFromTop,
1,
page.getSize(),
getSize,
imageHeight
);
@@ -3066,7 +3089,7 @@ export const mailTemplate = (param) => {
"</td></tr><tr><td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Note</td><td></td><td style='color:#626363;font-weight:bold'>" +
param.note +
"</td></tr><tr><td></td><td></td></tr></table></div> <div style='margin-left:70px'><a target=_blank href=" +
param.sigingUrl +
param.signingUrl +
"><button style='padding:12px;background-color:#d46b0f;color:white;border:0px;font-weight:bold;margin-top:30px'>Sign here</button></a></div><div style='display:flex;justify-content:center;margin-top:10px'></div></div></div><div><p> This is an automated email from " +
appName +
". For any queries regarding this email, please contact the sender " +
@@ -3161,3 +3184,46 @@ export const checkRegularExpress = (validateType, setValidatePlaceholder) => {
setValidatePlaceholder("please enter value");
}
};
//function to use unlink signer from widgets
export const handleUnlinkSigner = (
signerPos,
setSignerPos,
signersdata,
setSignersData,
uniqueId
) => {
//remove existing signer's details from 'signerPos' array
const updatePlaceHolder = signerPos.map((x) => {
if (x.Id === uniqueId) {
return { ...x, signerPtr: {}, signerObjId: "" };
}
return { ...x };
});
setSignerPos(updatePlaceHolder);
//remove existing signer's details from 'signersdata' array and keep role and id
const updateSigner = signersdata.map((item) => {
if (item.Id == uniqueId) {
return { Role: item.Role, Id: item.Id, blockColor: item.blockColor };
}
return item;
});
setSignersData(updateSigner);
};
//function is used to get pdf original width and height
export const getOriginalWH = async (pdf) => {
let pdfWHObj = [];
//get total page number
const totalPages = pdf?.numPages;
//according to page number get all pdf's pages width and height
for (let index = 0; index < totalPages; index++) {
try {
const getPage = await pdf.getPage(index + 1);
const width = getPage?.view[2];
const height = getPage?.view[3];
pdfWHObj.push({ pageNumber: index + 1, width, height });
} catch (e) {
console.log(`Error getting page ${index + 1} of PDF: ${e.message}`);
}
}
return pdfWHObj;
};
+6 -3
View File
@@ -1,9 +1,12 @@
import logo from "../assets/images/logo.png";
import { getEnv } from "./Utils";
export function serverUrl_fn() {
let baseUrl = process.env.REACT_APP_SERVERURL
? process.env.REACT_APP_SERVERURL
: window.location.origin + "/api/app";
const env = getEnv();
const serverurl = env?.REACT_APP_SERVERURL
? env.REACT_APP_SERVERURL // env.REACT_APP_SERVERURL is used for prod
: process.env.REACT_APP_SERVERURL; // process.env.REACT_APP_SERVERURL is used for dev (locally)
let baseUrl = serverurl ? serverurl : window.location.origin + "/api/app";
return baseUrl;
}
export const appInfo = {
+1 -1
View File
@@ -25,7 +25,7 @@ i18n
interpolation: {
escapeValue: false // Not needed for react as it escapes by default
},
whitelist: ["en", "es", "fr", "it", "de"] // List of allowed languages
whitelist: ["en", "es", "fr", "it", "de", "hi"] // List of allowed languages
});
export default i18n;
+12 -9
View File
@@ -1,3 +1,13 @@
const userssetting = [
{
icon: "fa-light fa-users fa-fw",
title: "Users",
target: "_self",
pageType: "",
description: "",
objectId: "users"
}
];
export const subSetting = [
{
icon: "fa-light fa-sliders",
@@ -7,14 +17,7 @@ export const subSetting = [
description: "",
objectId: "preferences"
},
{
icon: "fa-light fa-users fa-fw",
title: "Users",
target: "_self",
pageType: "",
description: "",
objectId: "users"
}
...userssetting
];
const sidebarList = [
@@ -65,7 +68,7 @@ const sidebarList = [
pageType: "report",
description: "",
objectId: "6TeaPr321t"
},
}
]
},
{
+2 -2
View File
@@ -455,7 +455,7 @@ const Forms = (props) => {
setBcc([]);
setFolder({ ObjectId: "", Name: "" });
const notifySign =
extUserData?.NotifyOnSignatures
extUserData?.NotifyOnSignatures !== undefined
? extUserData?.NotifyOnSignatures
: true;
setFormData({
@@ -534,7 +534,7 @@ const Forms = (props) => {
setBcc([]);
setFolder({ ObjectId: "", Name: "" });
const notifySign =
extUserData?.NotifyOnSignatures
extUserData?.NotifyOnSignatures !== undefined
? extUserData?.NotifyOnSignatures
: true;
let obj = {
+24 -23
View File
@@ -68,6 +68,7 @@ function Login() {
setState({ ...state, loading: false, alertType: type, alertMsg: msg });
setTimeout(() => setState({ ...state, alertMsg: "" }), 2000);
};
const checkUserExt = async () => {
const app = await getAppLogo();
if (app?.error === "invalid_json") {
@@ -82,11 +83,11 @@ function Login() {
} else {
setImage(appInfo?.applogo || undefined);
}
dispatch(fetchAppInfo());
if (localStorage.getItem("accesstoken")) {
setState({ ...state, loading: true });
GetLoginData();
}
dispatch(fetchAppInfo());
};
const handleChange = (event) => {
let { name, value } = event.target;
@@ -96,20 +97,15 @@ function Login() {
setState({ ...state, [name]: value });
};
const handleSubmit = async (event) => {
localStorage.removeItem("accesstoken");
event.preventDefault();
const handleLogin = async (
) => {
const email = state?.email
const password = state?.password
if (!emailRegex.test(state.email)) {
alert("Please enter a valid email address.");
return;
}
const { email, password } = state;
if (!email || !password) {
return;
}
localStorage.removeItem("accesstoken");
try {
setState({ ...state, loading: true });
localStorage.setItem("appLogo", appInfo.applogo);
@@ -132,6 +128,14 @@ function Login() {
showToast("danger", "Invalid username/password or region");
}
};
const handleLoginBtn = async (event) => {
event.preventDefault();
if (!emailRegex.test(state.email)) {
alert("Please enter a valid email address.");
return;
}
await handleLogin();
};
const setThirdpartyLoader = (value) => {
setState({ ...state, thirdpartyLoader: value });
@@ -275,7 +279,6 @@ function Login() {
const userInformation = JSON.parse(
localStorage.getItem("UserInformation")
);
// console.log("payload ", payload);
if (payload && payload.sessionToken) {
const params = {
userDetails: {
@@ -289,7 +292,6 @@ function Login() {
}
};
const userSignUp = await Parse.Cloud.run("usersignup", params);
// console.log("userSignUp ", userSignUp);
if (userSignUp && userSignUp.sessionToken) {
const LocalUserDetails = {
name: userInformation.name,
@@ -430,7 +432,7 @@ function Login() {
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-2">
<div>
<form onSubmit={handleSubmit} aria-label="Login Form">
<form onSubmit={handleLoginBtn} aria-label="Login Form">
<h1 className="text-[30px] mt-6">{t("welcome")}</h1>
<fieldset>
<legend className="text-[12px] text-[#878787]">
@@ -488,15 +490,14 @@ function Login() {
)}
</span>
</div>
<div className="relative mt-1">
<NavLink
to="/forgetpassword"
className="text-[13px] op-link op-link-primary underline-offset-1 focus:outline-none ml-1"
>
{t("forgot-password")}
</NavLink>
</div>
<div className="relative mt-1">
<NavLink
to="/forgetpassword"
className="text-[13px] op-link op-link-primary underline-offset-1 focus:outline-none ml-1"
>
{t("forgot-password")}
</NavLink>
</div>
</div>
</fieldset>
<div className="grid grid-cols-1 md:grid-cols-2 gap-2 text-center text-xs font-bold mt-2">
+22 -18
View File
@@ -1,4 +1,4 @@
import React, { useState, useRef, useEffect } from "react";
import { useState, useRef, useEffect } from "react";
import { PDFDocument } from "pdf-lib";
import "../styles/signature.css";
import Parse from "parse";
@@ -46,7 +46,8 @@ import {
textWidget,
mailTemplate,
updateDateWidgetsRes,
widgetDataValue
widgetDataValue,
getOriginalWH
} from "../constant/Utils";
import Header from "../components/pdf/PdfHeader";
import RenderPdf from "../components/pdf/RenderPdf";
@@ -816,11 +817,13 @@ function PdfRequestFiles(
//embed document's object id to all pages in pdf document
if (!HeaderDocId) {
if (!isDocId) {
await embedDocId(pdfDoc, docId, allPages);
//pdfOriginalWH contained all pdf's pages width,height & pagenumber in array format
await embedDocId(pdfOriginalWH, pdfDoc, docId);
}
}
//embed multi signature in pdf
//embed all widgets in document
const pdfBytes = await multiSignEmbed(
pdfOriginalWH,
widgets,
pdfDoc,
isSignYourSelfFlow,
@@ -862,7 +865,10 @@ function PdfRequestFiles(
const user = usermail?.Email
? usermail
: pdfDetails?.[0]?.Signers[newIndex];
if (sendmail !== "false" && sendInOrder) {
if (
sendmail !== "false" &&
sendInOrder
) {
const requestBody =
updatedDoc.updatedPdfDetails?.[0]?.RequestBody;
const requestSubject =
@@ -916,7 +922,7 @@ function PdfRequestFiles(
const htmlReqBody =
"<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body>" +
replacedRequestBody +
"</body> </html>";
"</body></html>";
const variables = {
document_title: documentName,
@@ -929,7 +935,7 @@ function PdfRequestFiles(
receiver_phone: user?.Phone || "",
expiry_date: localExpireDate,
company_name: orgName,
signing_url: `<a href=${signPdf} target=_blank>Sign here</a>`
signing_url: signPdf
};
replaceVar = replaceMailVaribles(
requestSubject,
@@ -944,7 +950,7 @@ function PdfRequestFiles(
title: documentName,
organization: orgName,
localExpireDate: localExpireDate,
sigingUrl: signPdf
signingUrl: signPdf
};
let params = {
replyto: senderEmail || "",
@@ -1169,14 +1175,7 @@ function PdfRequestFiles(
];
//function for get pdf page details
const pageDetails = async (pdf) => {
let pdfWHObj = [];
const totalPages = pdf.numPages; // Get the total number of pages
for (let index = 0; index < totalPages; index++) {
const getPage = await pdf.getPage(index + 1);
const scale = 1;
const { width, height } = getPage.getViewport({ scale });
pdfWHObj.push({ pageNumber: index + 1, width, height });
}
const pdfWHObj = await getOriginalWH(pdf);
setPdfOriginalWH(pdfWHObj);
setPdfLoad(true);
};
@@ -1568,11 +1567,15 @@ function PdfRequestFiles(
const widgetValue = widgetDataValue(dragTypeValue, parseUser);
//adding and updating drop position in array when user drop signature button in div
if (item === "onclick") {
// `getBoundingClientRect()` is used to get accurate measurement width, height of the Pdf div
const divWidth = divRef.current.getBoundingClientRect().width;
const divHeight = divRef.current.getBoundingClientRect().height;
// `getBoundingClientRect()` is used to get accurate measurement height of the div
// Compute the pixelspace center within the PDF viewport:
const centerX_Pixels = divWidth / 2 - widgetWidth / 2;
const xPosition_Final = centerX_Pixels / (containerScale * scale);
dropObj = {
//onclick put placeholder center on pdf
xPosition: widgetWidth / 4 + containerWH.width / 2,
xPosition: xPosition_Final,
yPosition: widgetHeight + divHeight / 2,
isStamp:
(dragTypeValue === "stamp" || dragTypeValue === "image") && true,
@@ -2263,6 +2266,7 @@ function PdfRequestFiles(
index={pageNumber}
setUniqueId={setUniqueId}
tempSignerId={tempSignerId}
signatureTypes={signatureType}
/>
)}
<DownloadPdfZip
+15 -14
View File
@@ -44,7 +44,8 @@ import {
handleSignatureType,
getBase64FromUrl,
generatePdfName,
mailTemplate
mailTemplate,
getOriginalWH
} from "../constant/Utils";
import RenderPdf from "../components/pdf/RenderPdf";
import { useNavigate } from "react-router";
@@ -523,11 +524,15 @@ function PlaceHolderSign() {
defaultWidthHeight(dragTypeValue).height * containerScale;
//adding and updating drop position in array when user drop signature button in div
if (item === "onclick") {
// `getBoundingClientRect()` is used to get accurate measurement width, height of the Pdf div
const divWidth = divRef.current.getBoundingClientRect().width;
const divHeight = divRef.current.getBoundingClientRect().height;
// `getBoundingClientRect()` is used to get accurate measurement height of the div
// Compute the pixelspace center within the PDF viewport:
const centerX_Pixels = divWidth / 2 - widgetWidth / 2;
const xPosition_Final = centerX_Pixels / (containerScale * scale);
dropObj = {
//onclick put placeholder center on pdf
xPosition: widgetWidth / 4 + containerWH.width / 2,
xPosition: xPosition_Final,
yPosition: widgetHeight + divHeight / 2,
isStamp:
(dragTypeValue === "stamp" || dragTypeValue === "image") && true,
@@ -671,14 +676,7 @@ function PlaceHolderSign() {
//function for get pdf page details
const pageDetails = async (pdf) => {
let pdfWHObj = [];
const totalPages = pdf?.numPages;
for (let index = 0; index < totalPages; index++) {
const getPage = await pdf.getPage(index + 1);
const scale = 1;
const { width, height } = getPage.getViewport({ scale });
pdfWHObj.push({ pageNumber: index + 1, width, height });
}
const pdfWHObj = await getOriginalWH(pdf);
setPdfOriginalWH(pdfWHObj);
setPdfLoad(true);
};
@@ -862,7 +860,9 @@ function PlaceHolderSign() {
});
const isSignYourSelfFlow = false;
try {
//pdfOriginalWH contained all pdf's pages width,height & pagenumber in array format
const pdfBase64 = await multiSignEmbed(
pdfOriginalWH,
placeholder,
pdfDoc,
isSignYourSelfFlow,
@@ -1220,7 +1220,7 @@ function PlaceHolderSign() {
receiver_phone: signerMail[i]?.Phone || "",
expiry_date: localExpireDate,
company_name: orgName,
signing_url: `<a href=${signPdf} target=_blank>Sign here</a>`
signing_url: signPdf
};
replaceVar = replaceMailVaribles(
requestSubject,
@@ -1249,7 +1249,7 @@ function PlaceHolderSign() {
receiver_phone: signerMail[i]?.Phone || "",
expiry_date: localExpireDate,
company_name: orgName,
signing_url: `<a href=${signPdf} target=_blank>Sign here</a>`
signing_url: signPdf
};
replaceVar = replaceMailVaribles(mailSubject, htmlReqBody, variables);
}
@@ -1260,7 +1260,7 @@ function PlaceHolderSign() {
title: documentName,
organization: orgName,
localExpireDate: localExpireDate,
sigingUrl: signPdf
signingUrl: signPdf
};
let params = {
extUserId: owner?.objectId,
@@ -2593,6 +2593,7 @@ function PlaceHolderSign() {
isSave={true}
tempSignerId={tempSignerId}
setUniqueId={setUniqueId}
signatureTypes={signatureType}
/>
)}
<ModalUi
+16 -16
View File
@@ -1,4 +1,4 @@
import React, { useState, useRef, useEffect } from "react";
import { useState, useRef, useEffect } from "react";
import { PDFDocument } from "pdf-lib";
import "../styles/signature.css";
import Parse from "parse";
@@ -32,12 +32,13 @@ import {
onClickZoomIn,
onClickZoomOut,
rotatePdfPage,
signatureTypes,
getBase64FromUrl,
convertBase64ToFile,
generatePdfName,
handleRemoveWidgets,
addWidgetSelfsignOptions
addWidgetSelfsignOptions,
getOriginalWH,
signatureTypes
} from "../constant/Utils";
import { useParams } from "react-router";
import Tour from "../primitives/Tour";
@@ -365,15 +366,19 @@ function SignYourSelf() {
);
//adding and updating drop position in array when user drop signature button in div
if (item === "onclick") {
// `getBoundingClientRect()` is used to get accurate measurement height of the div
// `getBoundingClientRect()` is used to get accurate measurement width, height of the Pdf div
const divHeight = divRef.current.getBoundingClientRect().height;
const divWidth = divRef.current.getBoundingClientRect().width;
const getWidth = widgetTypeExist
? calculateInitialWidthHeight(widgetValue).getWidth
: defaultWidthHeight(dragTypeValue).width;
const getHeight = defaultWidthHeight(dragTypeValue).height;
// Compute the pixelspace center within the PDF viewport:
const centerX_Pixels = divWidth / 2 - getWidth / 2;
const xPosition_Final = centerX_Pixels / (containerScale * scale);
dropObj = {
xPosition: getWidth / 2 + containerWH.width / 2,
xPosition: xPosition_Final,
yPosition: getHeight + divHeight / 2,
isStamp:
(dragTypeValue === "stamp" || dragTypeValue === "image") && true,
@@ -626,10 +631,12 @@ function SignYourSelf() {
const HeaderDocId = extUserPtr?.HeaderDocId;
//embed document's object id to all pages in pdf document
if (!HeaderDocId) {
await embedDocId(pdfDoc, documentId, allPages);
//pdfOriginalWH contained all pdf's pages width,height & pagenumber in array format
await embedDocId(pdfOriginalWH, pdfDoc, documentId);
}
//embed multi signature in pdf
//embed all widgets in document
const pdfBytes = await multiSignEmbed(
pdfOriginalWH,
xyPosition,
pdfDoc,
isSignYourSelfFlow,
@@ -738,7 +745,6 @@ function SignYourSelf() {
getDocumentDetails(false);
}
};
//function for save x and y position and show signature tab on that position
const handleTabDrag = (key) => {
setDragKey(key);
@@ -789,14 +795,7 @@ function SignYourSelf() {
};
//function for get pdf page details
const pageDetails = async (pdf) => {
let pdfWHObj = [];
const totalPages = pdf?.numPages;
for (let index = 0; index < totalPages; index++) {
const getPage = await pdf.getPage(index + 1);
const scale = 1;
const { width, height } = getPage.getViewport({ scale });
pdfWHObj.push({ pageNumber: index + 1, width, height });
}
const pdfWHObj = await getOriginalWH(pdf);
setPdfOriginalWH(pdfWHObj);
setPdfLoad(true);
};
@@ -1336,6 +1335,7 @@ function SignYourSelf() {
currWidgetsDetails={currWidgetsDetails}
index={index}
isSave={true}
signatureTypes={signatureTypes}
/>
)}
<RotateAlert
+14 -31
View File
@@ -38,7 +38,8 @@ import {
convertPdfArrayBuffer,
generatePdfName,
textWidget,
multiSignEmbed
multiSignEmbed,
getOriginalWH
} from "../constant/Utils";
import RenderPdf from "../components/pdf/RenderPdf";
import "../styles/AddUser.css";
@@ -375,11 +376,15 @@ const TemplatePlaceholder = () => {
filterSignerPos;
let placeHolder;
if (item === "onclick") {
// `getBoundingClientRect()` is used to get accurate measurement height of the div
// `getBoundingClientRect()` is used to get accurate measurement width, height of the Pdf div
const divWidth = divRef.current.getBoundingClientRect().width;
const divHeight = divRef.current.getBoundingClientRect().height;
// Compute the pixelspace center within the PDF viewport:
const centerX_Pixels = divWidth / 2 - widgetWidth / 2;
const xPosition_Final = centerX_Pixels / (containerScale * scale);
dropObj = {
//onclick put placeholder center on pdf
xPosition: widgetWidth / 4 + containerWH.width / 2,
xPosition: xPosition_Final,
yPosition: widgetHeight + divHeight / 2,
isStamp:
(dragTypeValue === "stamp" || dragTypeValue === "image") && true,
@@ -534,14 +539,7 @@ const TemplatePlaceholder = () => {
//function for get pdf page details
const pageDetails = async (pdf) => {
let pdfWHObj = [];
const totalPages = pdf?.numPages;
for (let index = 0; index < totalPages; index++) {
const getPage = await pdf.getPage(index + 1);
const scale = 1;
const { width, height } = getPage.getViewport({ scale });
pdfWHObj.push({ pageNumber: index + 1, width, height });
}
const pdfWHObj = await getOriginalWH(pdf);
setPdfOriginalWH(pdfWHObj);
setPdfLoad(true);
};
@@ -832,7 +830,9 @@ const TemplatePlaceholder = () => {
});
const isSignYourSelfFlow = false;
try {
//pdfOriginalWH contained all pdf's pages width,height & pagenumber in array format
const pdfBase64 = await multiSignEmbed(
pdfOriginalWH,
placeholder,
pdfDoc,
isSignYourSelfFlow,
@@ -1149,25 +1149,6 @@ const TemplatePlaceholder = () => {
const handleLinkUser = (id) => {
setIsAddUser({ [id]: true });
};
//function to use unlink signer from widgets
const handleUnlinkSigner = () => {
//remove existing signer's details from 'signerPos' array
const updatePlaceHolder = signerPos.map((x) => {
if (x.Id === uniqueId) {
return { ...x, signerPtr: {}, signerObjId: "" };
}
return { ...x };
});
setSignerPos(updatePlaceHolder);
//remove existing signer's details from 'signersdata' array and keep role and id
const updateSigner = signersdata.map((item) => {
if (item.Id == uniqueId) {
return { Role: item.Role, Id: item.Id, blockColor: item.blockColor };
}
return item;
});
setSignersData(updateSigner);
};
// `handleAddUser` is used to adduser
const handleAddUser = (data) => {
const signerPtr = {
@@ -1950,7 +1931,9 @@ const TemplatePlaceholder = () => {
closePopup={closePopup}
signersData={signersdata}
signerPos={signerPos}
handleUnlinkSigner={handleUnlinkSigner}
setSignerPos={setSignerPos}
setSignersData={setSignersData}
isRemove={true}
/>
)}
</div>
+3 -3
View File
@@ -423,9 +423,9 @@ function UserProfile() {
onClick={() =>
editmode ? handleCancel() : navigate("/changepassword")
}
className={`op-btn ${
editmode ? "op-btn-ghost w-[100px]" : "op-btn-secondary"
}`}
className={
`op-btn ${editmode ? "op-btn-ghost w-[100px]" : "op-btn-secondary"}`
}
>
{editmode ? t("cancel") : t("change-password")}
</button>
+864
View File
@@ -0,0 +1,864 @@
import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { PDFDocument, PDFName, PDFSignature, PDFRef, PDFDict } from 'pdf-lib'; // Updated import
import * as asn1js from 'asn1js';
import { Certificate, ContentInfo, SignedData, IssuerAndSerialNumber } from 'pkijs';
// import * as jsrsasign from 'jsrsasign'; // Removed for dynamic loading
const VerifyDocument = () => {
const { t } = useTranslation();
const [selectedFile, setSelectedFile] = useState(null);
const [fileBuffer, setFileBuffer] = useState(null);
const [verificationResult, setVerificationResult] = useState('');
const [detailedResults, setDetailedResults] = useState([]);
const [collapsedSections, setCollapsedSections] = useState({});
// const [jsrsasignStatus, setJsrsasignStatus] = useState('loading'); // Removed
// OID to human-readable label mapping
const oidMapping = {
'2.5.4.6': 'Country',
'2.5.4.10': 'Organization',
'2.5.4.11': 'Organizational Unit',
'2.5.4.17': 'Postal Code',
'2.5.4.8': 'State',
'2.5.4.7': 'Locality', // Alternative for City
'2.5.4.9': 'City',
'2.5.4.51': 'Address',
'2.5.4.3': 'Common Name',
'2.5.4.4': 'Surname',
'2.5.4.5': 'Serial Number',
'2.5.4.12': 'Title',
'2.5.4.13': 'Description',
'2.5.4.16': 'Postal Address',
'2.5.4.18': 'Post Office Box',
'2.5.4.20': 'Telephone Number',
'1.2.840.113549.1.9.1': 'Email Address',
// Common alternative OIDs
'C': 'Country',
'O': 'Organization',
'OU': 'Organizational Unit',
'CN': 'Common Name',
'ST': 'State',
'L': 'Locality',
'STREET': 'Address',
'emailAddress': 'Email Address',
'serialNumber': 'Serial Number'
};
// Function to parse certificate subject/issuer into structured data
const parseCertificateInfo = (certString) => {
if (!certString) return {};
const parsed = {};
// Find all OID patterns and their positions
const oidPattern = /(\d+\.\d+\.\d+\.\d+|\w+)=/g;
const matches = [];
let match;
while ((match = oidPattern.exec(certString)) !== null) {
matches.push({
oid: match[1],
startIndex: match.index,
equalIndex: match.index + match[1].length
});
}
// Extract value for each OID
for (let i = 0; i < matches.length; i++) {
const currentMatch = matches[i];
const nextMatch = matches[i + 1];
const valueStart = currentMatch.equalIndex + 1; // Skip the "=" character
const valueEnd = nextMatch ? nextMatch.startIndex - 2 : certString.length; // -2 to remove ", " before next OID
const value = certString.substring(valueStart, valueEnd).trim();
const label = oidMapping[currentMatch.oid] || currentMatch.oid;
parsed[label] = value;
}
return parsed;
};
// Function to determine if status should show success icon
const isSuccessStatus = (status) => {
const successTerms = ['valid', 'success', 'parsed', 'verified'];
const errorTerms = ['error', 'invalid', 'failed', 'expired'];
const statusLower = status.toLowerCase();
// Check for explicit error terms first
if (errorTerms.some(term => statusLower.includes(term))) {
return false;
}
// Check for success terms
return successTerms.some(term => statusLower.includes(term));
};
// Function to determine if certificate validity should show success icon
const isCertificateValid = (validityText) => {
const validityLower = validityText.toLowerCase();
// If it contains "valid" and doesn't contain negative terms
return validityLower.includes('valid') &&
!validityLower.includes('expired') &&
!validityLower.includes('not yet valid') &&
!validityLower.includes('invalid');
};
// Toggle collapsible sections
const toggleSection = (signatureIndex, section) => {
const key = `${signatureIndex}-${section}`;
setCollapsedSections(prev => ({
...prev,
[key]: !prev[key]
}));
};
// useEffect for jsrsasign loading removed
const handleFileChange = (event) => {
const file = event.target.files[0];
if (file && file.type === 'application/pdf') {
setSelectedFile(file);
setVerificationResult('');
setDetailedResults([]);
const reader = new FileReader();
reader.onload = (e) => {
setFileBuffer(e.target.result);
};
reader.readAsArrayBuffer(file);
} else {
setSelectedFile(null);
setFileBuffer(null);
setDetailedResults([]);
setVerificationResult(t('please-select-pdf'));
}
};
const parseSignature = async (pdfDoc) => {
const signatureFields = pdfDoc.getForm().getFields().filter(field => field instanceof PDFSignature); // Updated filter logic
if (!signatureFields.length) {
return { error: t('no-signature-found') };
}
const results = [];
for (const field of signatureFields) {
try {
if (!field.acroField || !field.acroField.dict) {
results.push({
name: field.getName() || t('unnamed-signature-field'),
status: t('error-processing-signature'),
errorDetails: t('missing-acrofield-dict'),
signerInfo: t('signer-info-not-available'),
certificateSubject: '',
certificateIssuer: '',
certificateValidity: t('cert-validity-not-checked'),
isCertificateDateValid: false,
calculatedDocumentHash: t('not-available'),
messageDigestInSignature: t('not-available'),
hashComparisonResult: t('not-performed'),
authenticatedAttributesSignatureResult: t('not-performed'),
});
continue;
}
// New logic to determine the actual signature dictionary
const fieldDict = field.acroField.dict;
const vEntry = fieldDict.get(PDFName.of('V'));
let actualSignatureDict = null;
if (vEntry) {
if (vEntry instanceof PDFRef) {
const lookedUp = pdfDoc.context.lookup(vEntry);
if (lookedUp instanceof PDFDict) {
actualSignatureDict = lookedUp;
}
} else if (vEntry instanceof PDFDict) {
actualSignatureDict = vEntry;
}
}
// Use actualSignatureDict if found, otherwise behavior might be problematic (as per existing logic)
// If actualSignatureDict is null, subsequent checks for byteRangeObject etc. will fail,
// leading to an error message for this signature, which is acceptable.
const signatureDict = actualSignatureDict;
// Check if signatureDict is null (meaning actualSignatureDict was not resolved)
// and push an error if it is, before trying to get ByteRange or Contents.
if (!signatureDict) {
results.push({
name: field.getName() || t('unnamed-signature-field'),
status: t('error-processing-signature'),
errorDetails: t('signature-dictionary-not-found-or-invalid'), // New error message
signerInfo: t('signer-info-not-available'),
certificateSubject: '',
certificateIssuer: '',
certificateValidity: t('cert-validity-not-checked'),
isCertificateDateValid: false,
calculatedDocumentHash: t('not-available'),
messageDigestInSignature: t('not-available'),
hashComparisonResult: t('not-performed'),
authenticatedAttributesSignatureResult: t('not-performed'),
});
continue;
}
const byteRangeObject = signatureDict.get(PDFName.of('ByteRange'));
let byteRange; // Will be assigned after validation
// Comprehensive validation for byteRangeObject and its contents
if (!byteRangeObject ||
!byteRangeObject.array ||
!Array.isArray(byteRangeObject.array) ||
byteRangeObject.array.length === 0 ||
byteRangeObject.array.length % 2 !== 0) {
results.push({
name: field.getName() || t('unnamed-signature-field'),
status: t('error-processing-signature'),
errorDetails: t('missing-or-invalid-byterange'),
signerInfo: t('signer-info-not-available'),
certificateSubject: '',
certificateIssuer: '',
certificateValidity: t('cert-validity-not-checked'),
isCertificateDateValid: false,
calculatedDocumentHash: t('not-available'),
messageDigestInSignature: t('not-available'),
hashComparisonResult: t('not-performed'),
authenticatedAttributesSignatureResult: t('not-performed'),
});
continue;
}
const byteRangeNumbers = [];
let byteRangeIsValid = true;
for (const pdfObject of byteRangeObject.array) {
if (!pdfObject || typeof pdfObject.asNumber !== 'function') {
byteRangeIsValid = false;
break;
}
const num = pdfObject.asNumber();
if (!Number.isFinite(num)) { // Checks for NaN, Infinity, -Infinity
byteRangeIsValid = false;
break;
}
byteRangeNumbers.push(num);
}
if (!byteRangeIsValid) {
results.push({
name: field.getName() || t('unnamed-signature-field'),
status: t('error-processing-signature'),
errorDetails: t('missing-or-invalid-byterange'), // Or a more specific error like "ByteRange contains non-numeric values"
signerInfo: t('signer-info-not-available'),
certificateSubject: '',
certificateIssuer: '',
certificateValidity: t('cert-validity-not-checked'),
isCertificateDateValid: false,
calculatedDocumentHash: t('not-available'),
messageDigestInSignature: t('not-available'),
hashComparisonResult: t('not-performed'),
authenticatedAttributesSignatureResult: t('not-performed'),
});
continue;
}
byteRange = byteRangeNumbers; // Assign the validated numbers to byteRange
const contentsObject = signatureDict.get(PDFName.of('Contents'));
if (!contentsObject || typeof contentsObject.asString !== 'function') {
results.push({
name: field.getName() || t('unnamed-signature-field'),
status: t('error-processing-signature'),
errorDetails: t('missing-or-invalid-contents'),
signerInfo: t('signer-info-not-available'),
certificateSubject: '',
certificateIssuer: '',
certificateValidity: t('cert-validity-not-checked'),
isCertificateDateValid: false,
calculatedDocumentHash: t('not-available'),
messageDigestInSignature: t('not-available'),
hashComparisonResult: t('not-performed'),
authenticatedAttributesSignatureResult: t('not-performed'),
});
continue;
}
const contents = contentsObject.asString();
// The old basic check can be removed now as the more specific checks above cover these cases.
// if (!byteRange || !contents) { ... }
// Calculate totalSignedLength for accurate buffer initialization
let totalSignedLength = 0;
for (let i = 1; i < byteRange.length; i += 2) {
totalSignedLength += byteRange[i];
}
if (totalSignedLength <= 0) {
results.push({
name: field.getName() || t('unnamed-signature-field'),
status: t('error-processing-signature'),
errorDetails: t('missing-or-invalid-byterange'), // totalSignedLength being non-positive implies invalid ByteRange
signerInfo: t('signer-info-not-available'),
certificateSubject: '',
certificateIssuer: '',
certificateValidity: t('cert-validity-not-checked'),
isCertificateDateValid: false,
calculatedDocumentHash: t('not-available'),
messageDigestInSignature: t('not-available'),
hashComparisonResult: t('not-performed'),
authenticatedAttributesSignatureResult: t('not-performed'),
});
continue;
}
const pdfSignedDataBytes = new Uint8Array(totalSignedLength);
let offset = 0;
let reconstructionFailed = false;
for (let i = 0; i < byteRange.length; i += 2) {
const start = byteRange[i];
const length = byteRange[i+1];
if (start < 0 || length <= 0 || start + length > fileBuffer.byteLength) {
reconstructionFailed = true;
break;
}
pdfSignedDataBytes.set(new Uint8Array(fileBuffer.slice(start, start + length)), offset);
offset += length;
}
if (reconstructionFailed) {
results.push({
name: field.getName() || t('unnamed-signature-field'),
status: t('error-processing-signature'),
errorDetails: t('missing-or-invalid-byterange'), // Error during reconstruction due to invalid segment
signerInfo: t('signer-info-not-available'),
certificateSubject: '',
certificateIssuer: '',
certificateValidity: t('cert-validity-not-checked'),
isCertificateDateValid: false,
calculatedDocumentHash: t('not-available'),
messageDigestInSignature: t('not-available'),
hashComparisonResult: t('not-performed'),
authenticatedAttributesSignatureResult: t('not-performed'),
});
continue;
}
// Remove leading/trailing null bytes from hex if present from PDF content
const pkcs7Hex = contents.trim();
if (!pkcs7Hex) {
results.push({
name: field.getName() || t('unnamed-signature-field'),
status: t('signature-invalid-basic'),
errorDetails: t('missing-signature-contents'), // New i18n key
signerInfo: t('signer-info-not-available'),
certificateSubject: '',
certificateIssuer: '',
certificateValidity: t('cert-validity-not-checked'),
});
continue;
}
// Convert hex string to ArrayBuffer
let cmsContentBuffer;
try {
cmsContentBuffer = new Uint8Array(pkcs7Hex.match(/.{1,2}/g).map(byte => parseInt(byte, 16))).buffer;
} catch (hexError) {
// console.error('Error converting hex string to ArrayBuffer:', hexError); // Removed
results.push({
name: field.getName() || t('unnamed-signature-field'),
status: t('signature-invalid-basic'),
errorDetails: t('invalid-signature-hex-format'), // New i18n key
signerInfo: t('signer-info-not-available'),
certificateSubject: '',
certificateIssuer: '',
certificateValidity: t('cert-validity-not-checked'),
});
continue;
}
// Parse the CMS ContentInfo
const asn1 = asn1js.fromBER(cmsContentBuffer);
if (asn1.offset === -1) {
// console.error('Error parsing ASN.1 from signature data'); // Removed
results.push({
name: field.getName() || t('unnamed-signature-field'),
status: t('signature-invalid-basic'),
errorDetails: 'ASN.1 parsing error from signature data.',
signerInfo: t('signer-info-not-available'),
certificateSubject: '',
certificateIssuer: '',
certificateValidity: t('cert-validity-not-checked'),
});
continue;
}
const cmsContentInfo = new ContentInfo({ schema: asn1.result });
if (String(cmsContentInfo.contentType).trim() !== String(ContentInfo.SIGNED_DATA).trim()) {
// console.error('Not a SignedData content type. Actual type:', cmsContentInfo.contentType, 'Expected:', ContentInfo.SIGNED_DATA); // Removed
results.push({
name: field.getName() || t('unnamed-signature-field'),
status: t('signature-invalid-basic'),
errorDetails: t('unsupported-signature-format-not-signeddata'), // New i18n key
signerInfo: t('signer-info-not-available'),
certificateSubject: '',
certificateIssuer: '',
certificateValidity: t('cert-validity-not-checked'),
});
continue;
}
const signedData = new SignedData({ schema: cmsContentInfo.content });
let signerInfoText = t('signer-info-not-available');
let certSubject = '';
let certIssuer = '';
let certValidity = t('cert-validity-not-checked');
let isValid = false;
if (signedData.signerInfos && signedData.signerInfos.length > 0) {
const signerInfo = signedData.signerInfos[0];
if (signedData.certificates && signedData.certificates.length > 0) {
let signerCertificate = null;
for (const cert of signedData.certificates) {
if (cert instanceof Certificate) {
const issuerAndSerialNumber = signerInfo.sid;
if (issuerAndSerialNumber instanceof IssuerAndSerialNumber) {
let certMatch = true;
if (cert.issuer.typesAndValues.length === issuerAndSerialNumber.issuer.typesAndValues.length) {
for (let i = 0; i < cert.issuer.typesAndValues.length; i++) {
if (cert.issuer.typesAndValues[i].type !== issuerAndSerialNumber.issuer.typesAndValues[i].type ||
cert.issuer.typesAndValues[i].value.valueBlock.value !== issuerAndSerialNumber.issuer.typesAndValues[i].value.valueBlock.value) {
certMatch = false;
break;
}
}
} else {
certMatch = false;
}
if (certMatch && cert.serialNumber.valueBlock.valueHexView.join('') === issuerAndSerialNumber.serialNumber.valueBlock.valueHexView.join('')) {
signerCertificate = cert;
break;
}
}
}
}
if (signerCertificate) {
certSubject = signerCertificate.subject.typesAndValues.map(tv => `${tv.type}=${tv.value.valueBlock.value}`).join(', ');
certIssuer = signerCertificate.issuer.typesAndValues.map(tv => `${tv.type}=${tv.value.valueBlock.value}`).join(', ');
signerInfoText = `${t('signer')}: ${certSubject}, ${t('issuer')}: ${certIssuer}`;
const notBefore = signerCertificate.notBefore.value;
const notAfter = signerCertificate.notAfter.value;
const currentDate = new Date();
certValidity = `${t('valid-from')} ${notBefore.toLocaleDateString()} ${t('to')} ${notAfter.toLocaleDateString()}`;
if (currentDate < notBefore || currentDate > notAfter) {
certValidity += ` (${t('expired-or-not-yet-valid')})`;
isValid = false; // Explicitly false if expired
} else {
certValidity += ` (${t('valid')})`;
isValid = true;
}
} else {
signerInfoText = t('signer-certificate-not-found'); // New i18n key
}
} else {
signerInfoText = t('no-certificates-in-signature'); // New i18n key
}
} else {
signerInfoText = t('no-signer-info-in-pkcs7'); // Re-use existing key, or make new one
}
results.push({
name: field.getName() || t('unnamed-signature-field'),
status: isValid ? t('signature-valid-basic') : t('signature-invalid-basic'),
signerInfo: signerInfoText,
certificateSubject: certSubject,
certificateIssuer: certIssuer,
certificateValidity: certValidity,
errorDetails: !isValid && signerInfoText === t('signer-info-not-available') ? t('could-not-parse-signer-info') : undefined, // New i18n key
});
} catch (e) {
console.error('Error processing signature field with pkijs:', field.getName(), e);
results.push({
name: field.getName() || t('unnamed-signature-field'),
status: t('error-processing-signature'),
errorDetails: e.message,
signerInfo: t('signer-info-not-available'),
certificateSubject: '',
certificateIssuer: '',
certificateValidity: t('cert-validity-not-checked'),
});
}
}
return { results };
};
const handleVerifyDocument = async () => {
// Removed jsrsasignStatus check
if (!fileBuffer) {
setVerificationResult(t('please-select-file-to-verify'));
setDetailedResults([]);
return;
}
setVerificationResult(t('verification-in-progress'));
setDetailedResults([]);
try {
// Removed window.KJUR and window.X509 check
const pdfDoc = await PDFDocument.load(fileBuffer, { ignoreEncryption: true });
const signatureInfo = await parseSignature(pdfDoc);
if (signatureInfo.error) {
setVerificationResult(signatureInfo.error);
} else if (signatureInfo.results && signatureInfo.results.length > 0) {
setDetailedResults(signatureInfo.results);
// Overall status can be determined by checking if all signatures are valid
const allValid = signatureInfo.results.every(res => res.status === t('signature-valid-basic'));
setVerificationResult(allValid ? t('all-signatures-verified-convincing') : t('some-signatures-invalid-basic'));
} else {
setVerificationResult(t('no-signatures-processed')); // Should be caught by no-signature-found earlier
}
} catch (e) {
console.error('Error during PDF processing or signature verification:', e);
setVerificationResult(`${t('error-verifying-pdf')}: ${e.message}`);
setDetailedResults([]);
}
};
return (
<div className="container mx-auto p-6 bg-base-100 shadow-xl rounded-lg mt-10">
<style>{`
.checkmark__circle {
stroke-dasharray: 166;
stroke-dashoffset: 166;
stroke-width: 2;
stroke-miterlimit: 10;
stroke: #7ac142; /* Green color */
fill: none;
animation: stroke 0.6s cubic-bezier(0.65, 0, 0.45, 1) forwards;
}
.checkmark {
width: 56px;
height: 56px;
border-radius: 50%;
display: block;
stroke-width: 2;
stroke: #fff; /* White check path */
stroke-miterlimit: 10;
margin: 10px auto; /* Example margin */
box-shadow: inset 0px 0px 0px #7ac142;
animation: fill .4s ease-in-out .4s forwards, scale .3s ease-in-out .9s both;
}
.checkmark__check {
transform-origin: 50% 50%;
stroke-dasharray: 48;
stroke-dashoffset: 48;
animation: stroke 0.3s cubic-bezier(0.65, 0, 0.45, 1) 0.8s forwards;
}
@keyframes stroke {
100% {
stroke-dashoffset: 0;
}
}
@keyframes scale {
0%, 100% {
transform: none;
}
50% {
transform: scale3d(1.1, 1.1, 1);
}
}
@keyframes fill {
100% {
box-shadow: inset 0px 0px 0px 30px #7ac142;
}
}
`}</style>
<h1 className="text-3xl font-bold mb-6 text-center text-base-content">
{t('verify-document-signature')}
</h1>
<div className="mb-6 p-6 border border-base-300 rounded-lg bg-base-200/30 shadow-sm">
<label
htmlFor="document-upload"
className="block text-lg font-medium text-base-content mb-2"
>
{t('select-pdf-document')}
</label>
<input
type="file"
id="document-upload"
accept=".pdf"
onChange={handleFileChange}
className="file-input file-input-bordered file-input-primary w-full max-w-xs"
/>
{selectedFile && (
<p className="mt-2 text-sm text-base-content w-full truncate">
{t('selected-file')}: {selectedFile.name}
</p>
)}
</div>
<div className="text-center mb-6">
<button
onClick={handleVerifyDocument}
className="op-btn op-btn-primary op-btn-md"
disabled={!selectedFile || verificationResult === t('verification-in-progress')}
>
{/* Removed jsrsasignStatus === 'loading' condition for spinner */}
{verificationResult === t('verification-in-progress') ? (
<span className="loading loading-spinner"></span>
) : (
t('verify-signature')
)}
</button>
</div>
{verificationResult && verificationResult !== t('verification-in-progress') && (
<div className="mt-8 p-6 border border-base-300 rounded-lg bg-base-200 shadow-md min-h-[120px] flex flex-col items-center justify-center">
<h2 className="text-2xl font-bold mb-4 text-base-content text-center">
{t('verification-status')}
</h2>
{verificationResult === "Document Verified: All signatures have been successfully validated." && (
<div className="flex flex-col items-center my-4">
<svg className="checkmark" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 52 52">
<circle className="checkmark__circle" cx="26" cy="26" r="25" fill="none"/>
<path className="checkmark__check" fill="none" d="M14.1 27.2l7.1 7.2 16.7-16.8"/>
</svg>
</div>
)}
<p className="text-lg text-base-content mb-4 text-center">{verificationResult}</p>
{detailedResults.length > 0 && (
<div className="w-full space-y-6">
{detailedResults.map((res, index) => {
const signerInfo = parseCertificateInfo(res.certificateSubject);
const issuerInfo = parseCertificateInfo(res.certificateIssuer);
return (
<div key={index} className="bg-white border border-gray-200 rounded-xl shadow-lg overflow-hidden">
{/* Header Section */}
<div className="bg-gradient-to-r from-blue-600 to-indigo-600 text-white p-6">
<div className="flex items-center space-x-3">
<span className="text-2xl">🔏</span>
<div>
<h4 className="text-xl font-bold">Signature Details</h4>
<p className="text-blue-100 text-sm">Digital Certificate Information</p>
</div>
</div>
</div>
{/* Basic Info Section */}
<div className="p-6 border-b border-gray-100">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<span className="text-sm font-medium text-gray-500 uppercase tracking-wide">Field Name</span>
<p className="mt-1 text-lg font-semibold text-gray-900 font-mono">{res.name}</p>
</div>
<div>
<span className="text-sm font-medium text-gray-500 uppercase tracking-wide">Overall Status</span>
<div className="mt-1 flex items-center space-x-2">
<span className={`text-lg ${isSuccessStatus(res.status) ? 'text-green-600' : 'text-red-600'}`}>
{isSuccessStatus(res.status) ? '✅' : '❌'}
</span>
<span className="text-lg font-semibold text-gray-900">{res.status}</span>
</div>
</div>
</div>
</div>
{/* Signer Information Section */}
{Object.keys(signerInfo).length > 0 && (
<div className="border-b border-gray-100">
<button
onClick={() => toggleSection(index, 'signer')}
className="w-full px-6 py-4 text-left hover:bg-gray-50 transition-colors duration-200 focus:outline-none focus:bg-gray-50"
>
<div className="flex items-center justify-between">
<div className="flex items-center space-x-3">
<span className="text-xl">📇</span>
<h5 className="text-lg font-semibold text-gray-900">Signer Information</h5>
</div>
<svg
className={`w-5 h-5 text-gray-400 transition-transform duration-200 ${
collapsedSections[`${index}-signer`] ? 'transform rotate-180' : ''
}`}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
</div>
</button>
{!collapsedSections[`${index}-signer`] && (
<div className="px-6 pb-6">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{Object.entries(signerInfo).map(([label, value]) => (
<div key={label} className="bg-gray-50 rounded-lg p-4">
<span className="text-xs font-medium text-gray-500 uppercase tracking-wide">{label}</span>
<p className="mt-1 text-sm font-mono text-gray-900 break-all">{value}</p>
</div>
))}
</div>
</div>
)}
</div>
)}
{/* Issuer Information Section */}
{Object.keys(issuerInfo).length > 0 && (
<div className="border-b border-gray-100">
<button
onClick={() => toggleSection(index, 'issuer')}
className="w-full px-6 py-4 text-left hover:bg-gray-50 transition-colors duration-200 focus:outline-none focus:bg-gray-50"
>
<div className="flex items-center justify-between">
<div className="flex items-center space-x-3">
<span className="text-xl">🏢</span>
<h5 className="text-lg font-semibold text-gray-900">Issuer Details</h5>
</div>
<svg
className={`w-5 h-5 text-gray-400 transition-transform duration-200 ${
collapsedSections[`${index}-issuer`] ? 'transform rotate-180' : ''
}`}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
</div>
</button>
{!collapsedSections[`${index}-issuer`] && (
<div className="px-6 pb-6">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{Object.entries(issuerInfo).map(([label, value]) => (
<div key={label} className="bg-gray-50 rounded-lg p-4">
<span className="text-xs font-medium text-gray-500 uppercase tracking-wide">{label}</span>
<p className="mt-1 text-sm font-mono text-gray-900 break-all">{value}</p>
</div>
))}
</div>
</div>
)}
</div>
)}
{/* Certificate Validity Section */}
{res.certificateValidity && (
<div className="p-6 bg-gray-50">
<div className="flex items-center space-x-3 mb-4">
<span className="text-xl">🕒</span>
<h5 className="text-lg font-semibold text-gray-900">Certificate Validity</h5>
</div>
<div className="bg-white rounded-lg p-4 border">
<div className="flex items-center space-x-2">
<span className={`text-lg ${isCertificateValid(res.certificateValidity) ? 'text-green-600' : 'text-red-600'}`}>
{isCertificateValid(res.certificateValidity) ? '✅' : '❌'}
</span>
<span className="text-sm font-mono text-gray-900">{res.certificateValidity}</span>
</div>
</div>
</div>
)}
{/* Technical Details Section (if any) */}
{(res.calculatedDocumentHash || res.messageDigestInSignature || res.hashComparisonResult || res.authenticatedAttributesSignatureResult || res.errorDetails || res.certificateSubject || res.certificateIssuer) && (
<div className="p-6 bg-gray-50 border-t">
<details className="group">
<summary className="flex items-center justify-between cursor-pointer text-sm font-medium text-gray-700 hover:text-gray-900">
<span>🔧 Technical Details</span>
<svg className="w-4 h-4 transition-transform group-open:rotate-180" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
</summary>
<div className="mt-4 space-y-3">
{/* Raw Certificate Data */}
{res.certificateSubject && (
<div className="bg-white rounded p-3 border">
<span className="text-xs font-medium text-gray-500 uppercase tracking-wide block mb-1">Raw Certificate Subject</span>
<code className="text-xs text-gray-800 break-all bg-gray-100 p-2 rounded block">{res.certificateSubject}</code>
</div>
)}
{res.certificateIssuer && (
<div className="bg-white rounded p-3 border">
<span className="text-xs font-medium text-gray-500 uppercase tracking-wide block mb-1">Raw Certificate Issuer</span>
<code className="text-xs text-gray-800 break-all bg-gray-100 p-2 rounded block">{res.certificateIssuer}</code>
</div>
)}
{res.calculatedDocumentHash && res.calculatedDocumentHash !== t('not-available') && res.calculatedDocumentHash !== t('not-calculated') && (
<div className="bg-white rounded p-3 border">
<span className="text-xs font-medium text-gray-500 uppercase tracking-wide block mb-1">Calculated Document Hash</span>
<code className="text-xs text-gray-800 break-all bg-gray-100 p-2 rounded block">{res.calculatedDocumentHash}</code>
</div>
)}
{res.messageDigestInSignature && res.messageDigestInSignature !== t('not-available') && res.messageDigestInSignature !== t('not-found-in-signature') && (
<div className="bg-white rounded p-3 border">
<span className="text-xs font-medium text-gray-500 uppercase tracking-wide block mb-1">Message Digest in Signature</span>
<code className="text-xs text-gray-800 break-all bg-gray-100 p-2 rounded block">{res.messageDigestInSignature}</code>
</div>
)}
{res.hashComparisonResult && res.hashComparisonResult !== t('not-performed') && (
<div className="bg-white rounded p-3 border">
<span className="text-xs font-medium text-gray-500 uppercase tracking-wide block mb-1">Hash Comparison</span>
<span className="text-sm text-gray-800">{res.hashComparisonResult}</span>
</div>
)}
{res.authenticatedAttributesSignatureResult && res.authenticatedAttributesSignatureResult !== t('not-performed') && (
<div className="bg-white rounded p-3 border">
<span className="text-xs font-medium text-gray-500 uppercase tracking-wide block mb-1">Attributes Signature Verification</span>
<span className="text-sm text-gray-800">{res.authenticatedAttributesSignatureResult}</span>
</div>
)}
{res.errorDetails && (
<div className="bg-red-50 border border-red-200 rounded p-3">
<span className="text-xs font-medium text-red-600 uppercase tracking-wide block mb-1">Error Details</span>
<span className="text-sm text-red-800">{res.errorDetails}</span>
</div>
)}
</div>
</details>
</div>
)}
</div>
);
})}
</div>
)}
</div>
)}
{verificationResult === t('verification-in-progress') && (
<div className="mt-8 p-4 border border-base-300 rounded-lg bg-base-200 min-h-[100px] flex justify-center items-center">
<span className="loading loading-lg loading-dots"></span>
</div>
)}
{!verificationResult && !selectedFile && (
<div className="mt-8 p-4 border border-base-300 rounded-lg bg-base-200 min-h-[100px] flex justify-center items-center"> {/* Added flex for centering */}
<p className="text-base-content/60 italic text-center">{t('verification-results-will-appear-here')}</p>
</div>
)}
</div>
);
};
export default VerifyDocument;
@@ -679,7 +679,7 @@ const ReportTable = (props) => {
receiver_phone: userDetails?.Phone || "",
expiry_date: localExpireDate,
company_name: doc.ExtUserPtr.Company,
signing_url: `<a href=${signPdf} target=_blank>Sign here</a>`
signing_url: signPdf
};
const res = replaceMailVaribles(subject, "", variables);
setMail((prev) => ({ ...prev, subject: res.subject }));
@@ -710,7 +710,7 @@ const ReportTable = (props) => {
receiver_phone: userDetails?.Phone || "",
expiry_date: localExpireDate,
company_name: doc.ExtUserPtr.Company,
signing_url: `<a href=${signPdf} target=_blank>Sign here</a>`
signing_url: signPdf
};
const res = replaceMailVaribles("", body, variables);
@@ -754,7 +754,7 @@ const ReportTable = (props) => {
receiver_phone: user?.signerPtr?.Phone || "",
expiry_date: localExpireDate,
company_name: doc?.ExtUserPtr?.Company || "",
signing_url: `<a href=${signPdf} target=_blank>Sign here</a>`
signing_url: signPdf
};
const subject =
doc?.RequestSubject ||
@@ -25,12 +25,10 @@ const LinkUserModal = (props) => {
handleClose={props.closePopup}
>
<SelectSigners
details={props.handleAddUser}
{...props}
closePopup={props.closePopup}
signersData={props?.signersData}
isContact={isContact}
setIsContact={setIsContact}
handleUnlinkSigner={props.handleUnlinkSigner}
isExistSigner={isExistSigner}
/>
{isContact && (
+7 -2
View File
@@ -8,13 +8,18 @@ const ModalUi = ({
handleClose,
showHeader = true,
showClose = true,
reduceWidth
reduceWidth,
position
}) => {
const width = reduceWidth;
const isBottom = position === "bottom" ? "items-end pb-2" : "";
return (
<>
{isOpen && (
<dialog id="selectSignerModal" className="op-modal op-modal-open">
<dialog
id="selectSignerModal"
className={`${isBottom} op-modal op-modal-open`}
>
<div
className={`${
width || "md:min-w-[500px]"
-27
View File
@@ -1,27 +0,0 @@
# Use an official Node runtime as the base image
FROM node:20
# Set the working directory inside the container
WORKDIR /usr/src/app
# Copy package.json and package-lock.json first to leverage Docker cache
COPY ./package*.json ./
# Install application dependencies
RUN npm install
# If you have native dependencies, you'll need extra tools. Uncomment the following line if needed.
# RUN apk add --no-cache make gcc g++ python3
# Copy the current directory contents into the container
COPY ./ .
# Make port 8080 available to the world outside this container
EXPOSE 8080
# Define environment variables if needed
# ENV NODE_ENV production
# ENV DATABASE_URL mongodb://db:27017
# Run the application
ENTRYPOINT npm start
+1 -1
View File
@@ -252,7 +252,7 @@ export const mailTemplate = param => {
"</td></tr><tr><td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Note</td><td></td><td style='color:#626363;font-weight:bold'>" +
param.note +
"</td></tr><tr><td></td><td></td></tr></table></div> <div style='margin-left:70px'><a target=_blank href=" +
param.sigingUrl +
param.signingUrl +
"><button style='padding:12px;background-color:#d46b0f;color:white;border:0px;font-weight:bold;margin-top:30px'>Sign here</button></a></div><div style='display:flex;justify-content:center;margin-top:10px'></div></div></div><div><p> This is an automated email from " +
AppName +
'. For any queries regarding this email, please contact the sender ' +
@@ -4,9 +4,9 @@ export default async function Newsletter(request) {
const email = request.params?.email?.toLowerCase()?.replace(/\s/g, '');
const domain = request.params.domain;
try {
const envAppId = process.env.REACT_APP_APPID || 'opensign';
const envAppId = 'opensign';
const headers = { 'Content-Type': 'application/json', 'X-Parse-Application-Id': envAppId };
const envProdServer = process.env.REACT_APP_SERVERURL || 'https://app.opensignlabs.com/api/app';
const envProdServer = 'https://app.opensignlabs.com/api/app';
const newsletter = await axios.post(
`${envProdServer}/classes/Newsletter`,
{ Name: name, Email: email, Domain: domain },
@@ -14,7 +14,7 @@ async function deductcount(docsCount, extUserId) {
}
async function sendMail(document, publicUrl) {
//sessionToken
const baseUrl = new URL(publicUrl); //process.env.PUBLIC_URL
const baseUrl = new URL(publicUrl);
// console.log("pdfDetails", pdfDetails);
const timeToCompleteDays = document?.TimeToCompleteDays || 15;
@@ -71,7 +71,7 @@ async function sendMail(document, publicUrl) {
receiver_phone: existSigner?.Phone || '',
expiry_date: localExpireDate,
company_name: orgName,
signing_url: `<a href=${signPdf} target=_blank>Sign here</a>`,
signing_url: signPdf,
};
replaceVar = replaceMailVaribles(mailSubject, htmlReqBody, variables);
}
@@ -82,7 +82,7 @@ async function sendMail(document, publicUrl) {
title: document.Name,
organization: orgName,
localExpireDate: localExpireDate,
sigingUrl: signPdf,
signingUrl: signPdf,
};
let params = {
extUserId: document.ExtUserPtr.objectId,
@@ -116,7 +116,7 @@ async function sendNotifyMail(doc, signUser, mailProvider, publicUrl) {
const creatorEmail = doc.ExtUserPtr.Email;
const signerName = signUser.Name;
const signerEmail = signUser.Email;
const viewDocUrl = `${publicUrl}/recipientSignPdf/${doc.objectId}`; // ` ${process.env.PUBLIC_URL}/recipientSignPdf/${doc.objectId}`;
const viewDocUrl = `${publicUrl}/recipientSignPdf/${doc.objectId}`;
const subject = `Document "${pdfName}" has been signed by ${signerName}`;
const body =
"<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8'/></head><body><div style='background-color:#f5f5f5;padding:20px'><div style='background-color:white'>" +
+1 -1
View File
@@ -161,7 +161,7 @@ app.use(express.urlencoded({ limit: '50mb', extended: true }));
app.use(function (req, res, next) {
req.headers['x-real-ip'] = getUserIP(req);
const publicUrl = 'https://' + req?.get('host');
req.headers['public_url'] = publicUrl; // process.env.PUBLIC_URL
req.headers['public_url'] = publicUrl;
next();
});
function getUserIP(request) {
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "open_sign_server",
"version": "1.4.0",
"version": "2.21.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "open_sign_server",
"version": "1.4.0",
"version": "2.21.1",
"license": "MIT",
"dependencies": {
"@aws-sdk/client-s3": "^3.812.0",