Compare commits

...
Author SHA1 Message Date
prafull-opensignlabsandGitHub 18a4c8523a Update Docker.yml 2025-04-25 13:03:50 +05:30
prafull-opensignlabsandGitHub f7b39409a8 Merge pull request #1692 from OpenSignLabs/updates-14590419587
fix: normalize emails, migrate user logic to cloud function
2025-04-22 14:09:18 +05:30
prafull-opensignlabs 6283524d2f Merge pull request #524 from OpenSignLabs/publicurl_adduser
fix: correct public URL, normalize emails, migrate user/orgAdmin logic to cloud function
2025-04-22 08:36:56 +00:00
prafull-opensignlabsandGitHub 216288fd09 Merge pull request #1689 from OpenSignLabs/updates-14509601184
v2.18.0
2025-04-17 12:06:43 +05:30
prafull-opensignlabs 3b1d8a48b7 v2.18.0
New Features:
1. Added support to recreate a new document from declined document without having to redo everything from scratch.
2. Added replace pdf file feature for Templates. PDF underlying a Template can now be replaced while keeping all the fields the same.
3. Introduced the ability to replace pdf file for in UpdateTemplate and CreateDocumentfromtemplate API.
4. Revamped the preferences menu UI.

Bug Fixes:
1. Fixed issue with capital letters in contact emails via API.
2. Better validations & error handling in APIs
2025-04-17 06:34:06 +00:00
prafull-opensignlabsandGitHub ec68770ff0 Merge pull request #1681 from OpenSignLabs/updates-14398222591
v2.17.0
2025-04-11 13:20:43 +05:30
prafull-opensignlabs f5d2027e3a refactor: resolve warnings 2025-04-11 07:45:23 +00:00
prafull-opensignlabsandGitHub 8af0010f55 Merge pull request #1672 from OpenSignLabs/updates-14279056892
v2.16.0
2025-04-05 11:36:59 +05:30
prafull-opensignlabs d7b7ad6285 Merge pull request 2025-04-05 06:01:24 +00:00
prafull-opensignlabsandGitHub fca45ced38 Merge pull request #1663 from OpenSignLabs/updates-14122971328
v2.15.0
2025-03-28 11:37:55 +05:30
prafull-opensignlabs ac8ab70246 v2.15.0
v2.15.0
2025-03-28 06:02:14 +00:00
prafull-opensignlabsandGitHub 8373679f27 Merge pull request #1661 from OpenSignLabs/do_deploy
feat: update proxy file
2025-03-24 13:47:33 +05:30
prafull-opensignlabs 0a23aabbe4 feat: update proxy file 2025-03-24 13:45:45 +05:30
prafull-opensignlabsandGitHub 4a85599703 Merge pull request #1660 from OpenSignLabs/updates-14030275569
feat: changes to implement one click deployment
2025-03-24 13:42:41 +05:30
prafull-opensignlabs 3e9b84de72 feat: changes to implement one click deployment
feat: changes to implement one click deployment
2025-03-24 08:11:52 +00:00
prafull-opensignlabsandGitHub 4313d8d0e4 Merge pull request #1654 from OpenSignLabs/updates-14016837992
refactor: remove unnecessary code
2025-03-23 13:11:28 +05:30
prafull-opensignlabs 3088fdd163 Merge pull request #403 from OpenSignLabs/staging
refactor: remove unnecessary code
2025-03-23 07:40:01 +00:00
ZathielandGitHub 8c86fe27ec Merge pull request #1616 from OpenSignLabs/dependabot/npm_and_yarn/apps/OpenSign/i18next-browser-languagedetector-8.0.4
build(deps): bump i18next-browser-languagedetector from 8.0.2 to 8.0.4 in /apps/OpenSign
2025-03-21 21:50:38 +01:00
ZathielandGitHub 01e09998e5 Merge pull request #1650 from OpenSignLabs/dependabot/npm_and_yarn/apps/OpenSignServer/npm_and_yarn-1c007eb283
build(deps): bump axios from 1.8.3 to 1.8.4 in /apps/OpenSignServer in the npm_and_yarn group across 1 directory
2025-03-21 21:49:55 +01:00
ZathielandGitHub 55fdb52bf3 Merge pull request #1651 from OpenSignLabs/dependabot/npm_and_yarn/apps/OpenSign/npm_and_yarn-1c007eb283
build(deps): bump axios from 1.8.3 to 1.8.4 in /apps/OpenSign in the npm_and_yarn group across 1 directory
2025-03-21 21:49:17 +01:00
dependabot[bot]andGitHub 08f46a393b build(deps): bump axios
Bumps the npm_and_yarn group with 1 update in the /apps/OpenSign directory: [axios](https://github.com/axios/axios).


Updates `axios` from 1.8.3 to 1.8.4
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.8.3...v1.8.4)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: direct:production
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-21 14:41:30 +00:00
dependabot[bot]andGitHub 9345da543a build(deps): bump axios
Bumps the npm_and_yarn group with 1 update in the /apps/OpenSignServer directory: [axios](https://github.com/axios/axios).


Updates `axios` from 1.8.3 to 1.8.4
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.8.3...v1.8.4)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: direct:production
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-21 14:41:20 +00:00
dependabot[bot]andGitHub 208891e10d build(deps): bump i18next-browser-languagedetector in /apps/OpenSign
Bumps [i18next-browser-languagedetector](https://github.com/i18next/i18next-browser-languageDetector) from 8.0.2 to 8.0.4.
- [Changelog](https://github.com/i18next/i18next-browser-languageDetector/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/i18next-browser-languageDetector/compare/v8.0.2...v8.0.4)

---
updated-dependencies:
- dependency-name: i18next-browser-languagedetector
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-21 14:41:17 +00:00
prafull-opensignlabsandGitHub db0fa61b2b Merge pull request #1648 from OpenSignLabs/updates-13994222227
v2.14.0
2025-03-21 20:09:57 +05:30
prafull-opensignlabs 221f098de6 v2.14.0
v2.14.0
2025-03-21 14:38:32 +00:00
prafull-opensignlabsandGitHub 9658761ede Merge pull request #1646 from OpenSignLabs/prafull-opensignlabs-patch-2
add deprecated warning for env
2025-03-20 16:03:28 +05:30
prafull-opensignlabsandGitHub 6aad832e37 refacctor: add deprecated warning for env 2025-03-20 11:52:18 +05:30
prafull-opensignlabsandGitHub e4b6be51d9 Merge pull request #1644 from OpenSignLabs/updates-13949958694
Merge pull request #395 from OpenSignLabs
2025-03-19 20:41:10 +05:30
prafull-opensignlabs a3b2c797fb Merge pull request #395 from OpenSignLabs/fix_dailyemailquota
fix: dailyemailquota not updating for user and default value not updating of checkbox in API
2025-03-19 15:08:50 +00:00
ZathielandGitHub 22d27abb76 Merge pull request #1631 from OpenSignLabs/dependabot/npm_and_yarn/apps/OpenSignServer/prettier-3.5.3
build(deps-dev): bump prettier from 3.5.0 to 3.5.3 in /apps/OpenSignServer
2025-03-16 20:06:24 +01:00
dependabot[bot]andGitHub 45e0679ca7 build(deps-dev): bump prettier in /apps/OpenSignServer
Bumps [prettier](https://github.com/prettier/prettier) from 3.5.0 to 3.5.3.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/3.5.0...3.5.3)

---
updated-dependencies:
- dependency-name: prettier
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-10 00:48:25 +00:00
prafull-opensignlabsandGitHub 2b2aa20deb Merge pull request #1627 from OpenSignLabs/updates-13647519163
Merge pull request #357 from OpenSignLabs/revert_mail
2025-03-04 11:50:01 +05:30
prafull-opensignlabs 8d6ed3d2b5 Merge pull request #357 from OpenSignLabs/revert_mail
revert: revert mail sending
2025-03-04 06:19:26 +00:00
prafull-opensignlabsandGitHub 6064f66cdf Merge pull request #1625 from mubix/fix-install-doc
Fix Install Documentation
2025-03-04 11:36:42 +05:30
prafull-opensignlabsandGitHub 0ba29eb799 Merge pull request #1626 from OpenSignLabs/updates-13647292049
Merge pull request #356 from OpenSignLabs/correct_cond
2025-03-04 11:34:02 +05:30
prafull-opensignlabs d0dd571478 Merge pull request #356 from OpenSignLabs/mismatch_attach
fix: correct condition
2025-03-04 06:02:50 +00:00
Rob FullerandGitHub ddb63d81a7 fix install doc 2025-03-03 14:04:43 -05:00
prafull-opensignlabsandGitHub f78451d3f9 Merge pull request #1621 from OpenSignLabs/updates-13637250862
Merge pull request #349 from OpenSignLabs/crash_pdf
2025-03-04 00:08:15 +05:30
ZathielandGitHub 28630e2d23 Merge pull request #1618 from OpenSignLabs/dependabot/npm_and_yarn/apps/OpenSignServer/mongodb-6.14.0
build(deps): bump mongodb from 6.13.0 to 6.14.0 in /apps/OpenSignServer
2025-03-03 19:00:15 +01:00
ZathielandGitHub c5022eaee6 Merge pull request #1620 from OpenSignLabs/dependabot/npm_and_yarn/apps/OpenSignServer/posthog-node-4.8.1
build(deps): bump posthog-node from 4.5.0 to 4.8.1 in /apps/OpenSignServer
2025-03-03 18:58:01 +01:00
prafull-opensignlabs 1b7f07a2c4 Merge pull request #349 from OpenSignLabs/mismatch_attach
feat: restrict emails
2025-03-03 17:57:33 +00:00
dependabot[bot]andGitHub 2484c67406 build(deps): bump posthog-node in /apps/OpenSignServer
Bumps [posthog-node](https://github.com/PostHog/posthog-js-lite/tree/HEAD/posthog-node) from 4.5.0 to 4.8.1.
- [Release notes](https://github.com/PostHog/posthog-js-lite/releases)
- [Changelog](https://github.com/PostHog/posthog-js-lite/blob/main/posthog-node/CHANGELOG.md)
- [Commits](https://github.com/PostHog/posthog-js-lite/commits/posthog-node-v4.8.1/posthog-node)

---
updated-dependencies:
- dependency-name: posthog-node
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-03 00:42:08 +00:00
dependabot[bot]andGitHub abfc3f32f2 build(deps): bump mongodb from 6.13.0 to 6.14.0 in /apps/OpenSignServer
Bumps [mongodb](https://github.com/mongodb/node-mongodb-native) from 6.13.0 to 6.14.0.
- [Release notes](https://github.com/mongodb/node-mongodb-native/releases)
- [Changelog](https://github.com/mongodb/node-mongodb-native/blob/main/HISTORY.md)
- [Commits](https://github.com/mongodb/node-mongodb-native/compare/v6.13.0...v6.14.0)

---
updated-dependencies:
- dependency-name: mongodb
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-03 00:41:39 +00:00
prafull-opensignlabsandGitHub 5c06ff4242 Merge pull request #1613 from OpenSignLabs/updates-13494498125
Merge pull request #343 from OpenSignLabs/staging
2025-02-24 14:44:07 +05:30
prafull-opensignlabs 1899acfffc Merge pull request #343 from OpenSignLabs/staging
fix: selected date resets to the current date when navigating between document pages.
2025-02-24 09:12:24 +00:00
ZathielandGitHub 796ff058c8 Merge pull request #1581 from OpenSignLabs/dependabot/npm_and_yarn/apps/OpenSign/webpack-cli-6.0.1 2025-02-24 09:57:37 +01:00
ZathielandGitHub c88cb5357c Merge pull request #1609 from OpenSignLabs/dependabot/npm_and_yarn/apps/OpenSignServer/ws-8.18.1 2025-02-24 09:56:17 +01:00
dependabot[bot]andGitHub a6a0325d4b build(deps): bump ws from 8.18.0 to 8.18.1 in /apps/OpenSignServer
Bumps [ws](https://github.com/websockets/ws) from 8.18.0 to 8.18.1.
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/8.18.0...8.18.1)

---
updated-dependencies:
- dependency-name: ws
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-24 08:47:11 +00:00
ZathielandGitHub b18d17c44d Merge pull request #1610 from OpenSignLabs/dependabot/npm_and_yarn/apps/OpenSignServer/babel/eslint-parser-7.26.8 2025-02-24 09:45:44 +01:00
ZathielandGitHub a73779e74b Merge pull request #1612 from OpenSignLabs/dependabot/npm_and_yarn/apps/OpenSign/i18next-browser-languagedetector-8.0.4 2025-02-24 09:44:44 +01:00
dependabot[bot]andGitHub 5b943c1965 build(deps): bump i18next-browser-languagedetector in /apps/OpenSign
Bumps [i18next-browser-languagedetector](https://github.com/i18next/i18next-browser-languageDetector) from 8.0.2 to 8.0.4.
- [Changelog](https://github.com/i18next/i18next-browser-languageDetector/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/i18next-browser-languageDetector/compare/v8.0.2...v8.0.4)

---
updated-dependencies:
- dependency-name: i18next-browser-languagedetector
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-24 00:52:19 +00:00
dependabot[bot]andGitHub f73844d730 build(deps-dev): bump @babel/eslint-parser in /apps/OpenSignServer
Bumps [@babel/eslint-parser](https://github.com/babel/babel/tree/HEAD/eslint/babel-eslint-parser) from 7.26.5 to 7.26.8.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.26.8/eslint/babel-eslint-parser)

---
updated-dependencies:
- dependency-name: "@babel/eslint-parser"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-24 00:08:02 +00:00
prafull-opensignlabsandGitHub aaadab540a Merge pull request #1607 from OpenSignLabs/updates-13456995803
Merge pull request #339 from OpenSignLabs/staging
2025-02-21 18:11:59 +05:30
prafull-opensignlabsandGitHub 83bc466df4 Merge pull request #1606 from OpenSignLabs/updates-13455918957
Merge pull request #336 from OpenSignLabs/staging
2025-02-21 18:10:43 +05:30
prafull-opensignlabs d85ed954f1 Merge pull request #339 from OpenSignLabs/staging
fix: remove logo from certificate for white labeled user
2025-02-21 12:40:33 +00:00
ZathielandGitHub 369deb6e26 Merge pull request #1584 from OpenSignLabs/dependabot/npm_and_yarn/apps/OpenSign/regex-parser-2.3.1 2025-02-21 12:44:54 +01:00
ZathielandGitHub 295d1af910 Merge pull request #1589 from OpenSignLabs/dependabot/npm_and_yarn/apps/OpenSignServer/eslint-9.20.1 2025-02-21 12:41:57 +01:00
dependabot[bot]andGitHub 080d7c08dc build(deps-dev): bump eslint in /apps/OpenSignServer
Bumps [eslint](https://github.com/eslint/eslint) from 9.20.0 to 9.20.1.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v9.20.0...v9.20.1)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-21 11:41:35 +00:00
ZathielandGitHub c877f9fc5d Merge pull request #1590 from OpenSignLabs/dependabot/npm_and_yarn/apps/OpenSignServer/babel/eslint-parser-7.26.8 2025-02-21 12:40:28 +01:00
prafull-opensignlabs 99c7a58d62 Merge pull request #336 from OpenSignLabs/staging
style: correct logo aspect ratio in guest login
2025-02-21 11:32:27 +00:00
dependabot[bot]andGitHub 97642bf857 build(deps-dev): bump webpack-cli from 5.1.4 to 6.0.1 in /apps/OpenSign
Bumps [webpack-cli](https://github.com/webpack/webpack-cli) from 5.1.4 to 6.0.1.
- [Release notes](https://github.com/webpack/webpack-cli/releases)
- [Changelog](https://github.com/webpack/webpack-cli/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack/webpack-cli/compare/webpack-cli@5.1.4...webpack-cli@6.0.1)

---
updated-dependencies:
- dependency-name: webpack-cli
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-21 07:21:34 +00:00
dependabot[bot]andGitHub 2f67ac7b0a build(deps): bump regex-parser from 2.3.0 to 2.3.1 in /apps/OpenSign
Bumps [regex-parser](https://github.com/IonicaBizau/regex-parser.js) from 2.3.0 to 2.3.1.
- [Release notes](https://github.com/IonicaBizau/regex-parser.js/releases)
- [Commits](https://github.com/IonicaBizau/regex-parser.js/compare/2.3.0...2.3.1)

---
updated-dependencies:
- dependency-name: regex-parser
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-21 07:21:32 +00:00
prafull-opensignlabsandGitHub 23aecb45d9 Merge pull request #1601 from OpenSignLabs/updates-13451970686
v2.13.1
2025-02-21 12:50:13 +05:30
prafull-opensignlabs 92b507e621 v2.13.1
fix: Adapter prototype don't match expected prototype during
fix: drive get crash if signer array is null
2025-02-21 07:18:47 +00:00
prafull-opensignlabsandGitHub 1f6c2c7d38 Merge pull request #1597 from OpenSignLabs/updates-13420682171
Merge pull request #316 from OpenSignLabs/patch
2025-02-21 12:45:33 +05:30
prafull-opensignlabs c1be197346 Merge pull request #316 from OpenSignLabs/fix_emailresetdate
fix: Adapter prototype don't match expected prototype
2025-02-19 19:22:54 +00:00
dependabot[bot]andGitHub 24a95e8852 build(deps-dev): bump @babel/eslint-parser in /apps/OpenSignServer
Bumps [@babel/eslint-parser](https://github.com/babel/babel/tree/HEAD/eslint/babel-eslint-parser) from 7.26.5 to 7.26.8.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.26.8/eslint/babel-eslint-parser)

---
updated-dependencies:
- dependency-name: "@babel/eslint-parser"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-17 00:56:20 +00:00
122 changed files with 8828 additions and 8345 deletions
+4 -3
View File
@@ -8,13 +8,11 @@ PUBLIC_URL=https://localhost:3001
GENERATE_SOURCEMAP=false
# Set it to the URL from where APIs will be accessible, for local development it should be localhost:3000/api/app (use your local port number instead)
# REACT_APP_SERVERURL=http://localhost:8080/app
# A 12 character long random app identifier. The value of this should be same as APP_ID which is a variable used by backend API.
# (DEPRECATED) This should not be changed if provided; it should be 'opensign'.
REACT_APP_APPID=opensign
# Backend ExpressJS config ****************************************************************************************************************************************************************************************
# A 12 character long random app identifier. The value of this should be same as REACT_APP_APPID which is a variable used by Frontend React App.
APP_ID=opensign
# Name of the app. It will be visible in the verification emails sent out.
appName=open_sign_server
# A 12 character long random secret key that allows access to all the data. It is used in Parse dashboard config to view all the data in the database.
@@ -111,3 +109,6 @@ CRUxFgQUDYlgGVxSxuOknhQc256x3++7BDwwMTAhMAkGBSsOAwIaBQAEFFjASdYl
# Provide Pass pharse of above PFX or p12 document
PASS_PHRASE=opensign
# (DEPRECATED) This should not be changed if provided; it should be 'opensign'.
APP_ID=opensign
+1
View File
@@ -3,6 +3,7 @@ on:
push:
branches:
- 'main'
- 'staging'
jobs:
docker:
+2 -2
View File
@@ -1,7 +1,7 @@
{$HOST_URL} {
reverse_proxy client:3000
handle_path /app/* {
handle_path /api/* {
reverse_proxy server:8080
rewrite * /app{uri}
rewrite * {uri}
}
}
+1 -1
View File
@@ -77,7 +77,7 @@ Welcome to OpenSign, the premier open source docusign alternative - document e-s
The simplest way to install OpenSign on your own server is using official docker images by running the following command -
```
export HOST_URL=https://opensign.yourdomain.com && curl --remote-name-all https://raw.githubusercontent.com/OpenSignLabs/OpenSign/docker_beta/docker-compose.yml https://raw.githubusercontent.com/OpenSignLabs/OpenSign/docker_beta/Caddyfile https://raw.githubusercontent.com/OpenSignLabs/OpenSign/docker_beta/.env.local_dev && mv .env.local_dev .env.prod && docker compose up --force-recreate
export HOST_URL=https://opensign.yourdomain.com && curl --remote-name-all https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/docker-compose.yml https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/Caddyfile https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/.env.local_dev && mv .env.local_dev .env.prod && docker compose up --force-recreate
```
Make sure that you have `Docker` and `git` installed before you run this command -
+2 -3
View File
@@ -1,5 +1,5 @@
# Use an official Node runtime as the base image
FROM node:18
FROM node:22.14.0
# Set the working directory inside the container
WORKDIR /usr/src/app
@@ -16,8 +16,7 @@ COPY apps/OpenSign/.husky .
# Define environment variables if needed
ENV NODE_ENV=production
ENV REACT_APP_DEPLOYMENT=free_selfhost
ENV GENERATE_SOURCEMAP=false
# build
RUN npm run build
+297 -911
View File
File diff suppressed because it is too large Load Diff
+26 -26
View File
@@ -4,17 +4,17 @@
"private": true,
"dependencies": {
"@formkit/auto-animate": "^0.8.2",
"@lottiefiles/dotlottie-react": "^0.12.3",
"@lottiefiles/dotlottie-react": "^0.13.2",
"@pdf-lib/fontkit": "^1.1.1",
"@radix-ui/themes": "^3.1.6",
"@react-pdf/renderer": "^4.1.6",
"@reduxjs/toolkit": "^2.5.1",
"axios": "^1.7.9",
"css-minimizer-webpack-plugin": "^7.0.0",
"axios": "^1.8.4",
"css-minimizer-webpack-plugin": "^7.0.2",
"date-fns-tz": "^3.2.0",
"file-saver": "^2.0.5",
"i18next": "^23.16.8",
"i18next-browser-languagedetector": "^8.0.2",
"i18next-http-backend": "^3.0.1",
"i18next-browser-languagedetector": "^8.0.4",
"i18next-http-backend": "^3.0.2",
"jszip": "^3.10.1",
"jwt-decode": "^4.0.0",
"moment": "^2.30.1",
@@ -25,7 +25,7 @@
"radix-ui": "^1.0.1",
"react": "^18.2.0",
"react-bootstrap": "^2.10.9",
"react-confetti": "^6.2.2",
"react-confetti": "^6.4.0",
"react-cookie": "^7.2.2",
"react-datepicker": "^7.6.0",
"react-dnd": "^16.0.1",
@@ -35,16 +35,16 @@
"react-dom": "^18.2.0",
"react-gtm-module": "^2.0.11",
"react-helmet": "^6.1.0",
"react-i18next": "^15.4.0",
"react-i18next": "^15.4.1",
"react-konva": "^18.2.10",
"react-pdf": "^9.2.1",
"react-quill-new": "^3.3.3",
"react-quill-new": "^3.4.6",
"react-redux": "^9.2.0",
"react-rnd": "^10.4.14",
"react-rnd": "^10.5.2",
"react-router": "^7.1.5",
"react-scripts": "5.0.1",
"react-scripts": "^5.0.1",
"react-scrollbars-custom": "^4.1.1",
"react-select": "^5.10.0",
"react-select": "^5.10.1",
"react-signature-canvas": "^1.0.7",
"react-syntax-highlighter": "^15.6.1",
"react-timezone-select": "^3.2.8",
@@ -53,12 +53,12 @@
"reactour": "^1.19.4",
"redux": "^5.0.1",
"redux-thunk": "^3.1.0",
"regex-parser": "^2.3.0",
"regex-parser": "^2.3.1",
"serve": "^14.2.4",
"styled-components": "^5.3.0",
"web-vitals": "^4.2.4",
"ws": "^8.18.0",
"xlsx": "^0.18.5"
"ws": "^8.18.1",
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz"
},
"scripts": {
"build": "npm run version && react-scripts build",
@@ -96,26 +96,26 @@
}
},
"devDependencies": {
"@babel/core": "^7.26.8",
"@babel/preset-env": "^7.26.8",
"@babel/core": "^7.26.10",
"@babel/preset-env": "^7.26.9",
"@babel/preset-react": "^7.26.3",
"@babel/runtime-corejs2": "^7.26.7",
"autoprefixer": "^10.4.20",
"babel-loader": "^9.2.1",
"@babel/runtime-corejs2": "^7.27.0",
"autoprefixer": "^10.4.21",
"babel-loader": "^10.0.0",
"commitizen": "^4.3.1",
"concurrently": "^9.1.2",
"css-loader": "^7.1.2",
"daisyui": "^4.12.23",
"dotenv": "^16.4.7",
"dotenv-webpack": "^8.1.0",
"eslint": "^9.20.0",
"eslint-plugin-prettier": "^5.2.3",
"eslint": "^9.23.0",
"eslint-plugin-prettier": "^5.2.5",
"eslint-plugin-react": "^7.37.4",
"lint-staged": "^15.4.3",
"lint-staged": "^15.5.0",
"mini-css-extract-plugin": "^2.9.2",
"postcss": "^8.5.1",
"prettier": "^3.5.0",
"pretty-quick": "^4.0.0",
"postcss": "^8.5.3",
"prettier": "^3.5.3",
"pretty-quick": "^4.1.1",
"tailwindcss": "^3.4.17",
"terser-webpack-plugin": "^5.3.11",
"webpack-cli": "^5.1.4"
+3 -2
View File
@@ -1,5 +1,6 @@
<!DOCTYPE html>
<html lang="en" data-theme="opensigncss">
<head>
<meta charset="utf-8" />
<link rel="icon" href="/favicon.ico" />
@@ -11,8 +12,8 @@
<link rel="manifest" href="/manifest.json" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.0.0/dist/css/bootstrap.min.css"
integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous" />
<link rel="stylesheet" href="https://cdn.opensignlabs.com/fonts.css" />
<title>OpenSign™</title>
<link rel="stylesheet" href="https://cdn.opensignlabs.com/fonts.css" />
<title>...Loading</title>
</head>
<body>
<div id="root" style="touch-action:pan-x pan-y;"></div>
@@ -51,7 +51,6 @@
"docs": "Dokumente",
"session-expired": "Ihre Sitzung ist abgelaufen.",
"public-profile-help": "Dies ist Ihre öffentliche URL. Kopieren oder teilen Sie sie mit dem Unterzeichner, und Sie können alle öffentlich eingestellten Vorlagen sehen.",
"tagline-help": "Dieser Slogan wird in Ihrem öffentlichen OpenSign-Profil angezeigt, z. B. https://opensign.me/alex",
"welcome": "Willkommen zurück!",
"Login-to-your-account": "Melden Sie sich bei Ihrem Konto an",
"password": "Passwort",
@@ -74,12 +73,23 @@
"Sign yourself": "Selbst unterschreiben",
"Request signatures": "Signaturen anfordern",
"Templates": "Vorlagen",
"Analytics": "Analytik",
"Branding": "Markenbildung",
"Mail": "Mail",
"Storage": "Speicher",
"Signing certificate": "Signierzertifikat",
"Teams": "Teams",
"General": "Allgemein",
"Teams-Children": {
"Organizations": "Organisationen",
"OrgAdmins": "OrgAdmins"
},
"Templates-Children": {
"Create template": "Vorlage erstellen",
"Manage templates": "Vorlagen verwalten",
"Embedding": "Einbetten"
},
"OpenSign™ Drive": "OpenSign™ drive",
"OpenSign™ Drive": "{{appName}} Drive",
"Documents": "Dokumente",
"Reports": "Berichte",
"Documents-Children": {
@@ -140,7 +150,10 @@
"Copy Public URL": "Öffentliche URL kopieren",
"extend-expiry-date": "Ablaufdatum verlängern",
"Duplicate Template": "Vorlage duplizieren",
"Duplicate": "Duplikat"
"Duplicate": "Duplikat",
"daily-mail-quota": "Tägliches E-Mail-Kontingent",
"Save as template": "Als Vorlage speichern",
"Fix & resend": "Korrigieren und erneut senden"
},
"report-heading": {
"Sr.No": "Nr.",
@@ -160,7 +173,7 @@
"Name": "Name",
"Status": "Status",
"created-date": "Erstellungsdatum",
"Type": "Typ",
"Type": "Type",
"Logs": "Protokolle",
"Expiry-date": "Ablaufdatum"
},
@@ -198,7 +211,7 @@
"remind-once": "Einmal erinnern alle (Tage)",
"next": "Weiter",
"select-folder": "Ordner auswählen",
"OpenSign-drive": "OpenSign™ Drive",
"OpenSign-drive": "{{appName}} Drive",
"drive-document-status": {
"Completed": "Abgeschlossen",
"Declined": "Abgelehnt",
@@ -206,7 +219,7 @@
"Expired": "Abgelaufen",
"In Progress": "In Bearbeitung"
},
"select-folder-help": "Wenn Sie keinen Ordner auswählen, wird Ihr unterzeichnetes Dokument im Hauptordner des OpenSign-Drives gespeichert.",
"select-folder-help": "Wenn Sie keinen Ordner auswählen, wird Ihr unterzeichnetes Dokument im Hauptordner des {{appName}} Drive gespeichert.",
"no-data": "Keine Daten gefunden",
"save-here": "Hier speichern",
"back": "Zurück",
@@ -236,7 +249,7 @@
"generate-token-alert": "Sind Sie sicher, dass Sie das Token neu generieren möchten? Das alte Token wird ablaufen.",
"yes": "Ja",
"copied": "Kopiert",
"something-went-wrong-mssg": "Etwas ist schiefgelaufen. Bitte versuchen Sie es später erneut.",
"something-went-wrong-mssg": "Etwas ist schiefgelaufen, Das Aktualisieren dieser Seite kann das Problem lösen.",
"token-generated": "Token erfolgreich generiert.",
"webhook": "Webhook",
"update-webhook": "Webhook aktualisieren",
@@ -272,13 +285,14 @@
"make-template-public": "Vorlage öffentlich machen",
"make-template-private": "Vorlage privat machen",
"make-template-public-alert": "Sind Sie sicher, dass Sie diese Vorlage öffentlich machen möchten?",
"make-template-private-alert-non": "Sind Sie sicher, dass Sie diese Vorlage privat machen möchten?",
"make-template-private-alert": "Sind Sie sicher, dass Sie diese Vorlage privat machen möchten? Dadurch wird sie aus Ihrem öffentlichen Profil entfernt.",
"public-role": "Öffentliche Rolle",
"public-url": "Öffentliches Profil",
"embed-template": "Vorlage einbetten",
"public-url-copy": "Hier ist Ihre öffentliche URL: ",
"public-url-copy-mssg": "Kopieren oder teilen Sie diese URL, und Sie können alle öffentlich eingestellten Vorlagen sehen.",
"add-public-url-alert": "Sie können Ihr öffentliches OpenSign-Profil generieren",
"add-public-url-alert": "Sie können Ihr öffentliches {{appName}} Profil generieren",
"share-with-alert": "Sie können eine Vorlage nicht teilen, wenn Rollen bereits Kontakte zugewiesen haben. Entfernen Sie bitte alle Kontaktzuweisungen von den Rollen, bevor Sie die Vorlage teilen.",
"share-with": "Teilen mit",
"team-not-found": "Team nicht gefunden",
@@ -297,7 +311,7 @@
"revoke-document": "Dokument widerrufen",
"revoke-document-alert": "Sind Sie sicher, dass Sie dieses Dokument widerrufen möchten?",
"resend-mail": "E-Mail erneut senden",
"resend-mail-help": "Sie können folgende Variablen verwenden, die durch ihre tatsächlichen Werte ersetzt werden: {{document_title}}, {{sender_name}}, {{sender_mail}}, {{sender_phone}}, {{receiver_name}}, {{receiver_email}}, {{receiver_phone}}, {{expiry_date}}, {{company_name}}, {{signing_url}}.",
"resend-mail-help": "Sie können folgende Variablen verwenden, die durch ihre tatsächlichen Werte ersetzt werden: {{document_title}}, {{sender_name}}, {{sender_mail}}, {{sender_phone}}, {{receiver_name}}, {{receiver_email}}, {{receiver_phone}}, {{expiry_date}}, {{company_name}}, {{signing_url}}, {{note}}.",
"subject": "Betreff",
"body": "Inhalt",
"add-contact": "Kontakt hinzufügen",
@@ -447,6 +461,7 @@
"placeholder-alert-3": "Sind Sie sicher, dass Sie dieses Dokument zur Unterzeichnung senden möchten?",
"placeholder-alert-4": "Sie haben erfolgreich E-Mails an alle Empfänger gesendet!",
"placeholder-mail-alert": "Sie haben erfolgreich eine E-Mail an {{name}} gesendet. Die nachfolgenden Unterzeichner erhalten eine E-Mail, sobald {{name}} das Dokument unterzeichnet.",
"placeholder-mail-alert-you": "Nachfolgende Unterzeichner erhalten E-Mails, sobald Sie das Dokument unterschreiben.",
"placeholder-alert-5": "Möchten Sie die Dokumente jetzt unterschreiben?",
"placeholder-alert-6": "Bitte richten Sie den E-Mail-Adapter ein, um E-Mails zu senden!",
"placeholder-alert-7": "Bitte wählen Sie einen Unterzeichner aus, um Platzhalter hinzuzufügen!",
@@ -493,7 +508,7 @@
"role-not-found": "Rolle nicht gefunden.",
"do-not-access": "Sie haben keinen Zugriff. Bitte kontaktieren Sie den Administrator.",
"add-admin": "Admin hinzufügen",
"opensign-setup": "OpenSign-Einrichtung",
"opensign-setup": "{{appName}} Einrichtung",
"join-discord": "Treten Sie unserem Discord-Server bei",
"admin-already-exist": "Admin existiert bereits.",
"password-update-alert-1": "Passwort erfolgreich aktualisiert.",
@@ -510,7 +525,7 @@
"correct-password": "Bitte korrektes Passwort angeben",
"decrypting-pdf": "PDF wird entschlüsselt, bitte warten...",
"invalid-otp": "Ungültiger OTP",
"user-not-found": "Benutzer nicht gefunden!",
"user-not-found": "Benutzer nicht gefunden",
"enter-otp-alert": "Bitte OTP eingeben!",
"get-verification-code": "Verifizierungscode erhalten",
"get-verification-code-2": "Sie erhalten einen Verifizierungscode per E-Mail",
@@ -550,7 +565,6 @@
"filed-required-correctly": "Bitte füllen Sie die erforderlichen Details korrekt aus.",
"admin-created": "Admin erstellt",
"invalid-masterkey": "Ungültiger Hauptschlüssel",
"opensign-Setup": "OpenSign-Einrichtung",
"master-key": "Hauptschlüssel",
"profile-update-alert": "Profil erfolgreich aktualisiert.",
"date": "Datum",
@@ -575,7 +589,7 @@
"tour-mssg": {
"home-layout-1": "Sie haben sich erfolgreich angemeldet! Werfen wir einen Blick darauf.",
"home-layout-2": "Um Dokumente zum Selbstunterzeichnen hochzuladen oder um Unterschriften anderer anzufordern, wählen Sie einfach die entsprechenden Schaltflächen.",
"home-layout-3": "Sie sind bereit, OpenSign zu verwenden! Wenn Sie Unterstützung benötigen, kontaktieren Sie uns gerne.",
"home-layout-3": "Sie sind bereit, {{appName}} zu verwenden! Wenn Sie Unterstützung benötigen, kontaktieren Sie uns gerne.",
"generate-token": "Jetzt upgraden, um ein Produktions-API-Token zu generieren.",
"opensign-drive-1": "Klicken Sie auf die Breadcrumb-Links, um einfach durch die Ordnerhierarchie zu navigieren und die Dokumente in jedem Ordner anzuzeigen.",
"opensign-drive-2": "Klicken Sie auf die Schaltfläche Hinzufügen, um einen neuen Ordner oder ein neues Dokument zu erstellen.",
@@ -596,6 +610,7 @@
"placeholder-sign-4": "Ziehen Sie ein Feld in das Dokument oder klicken Sie darauf, um es hinzuzufügen.",
"placeholder-sign-5": "Der PDF-Inhaltsbereich zeigt bereits die vorhandenen Platzhalter der Vorlage an. Diese Platzhalter entsprechen der Farbe des Empfängernamens, um sie leicht erkennbar zu machen.",
"placeholder-sign-6": "Mit einem Klick auf 'Weiter' wird das Dokument gespeichert. Im nächsten Schritt können Sie die E-Mails, die an die Empfänger versendet werden sollen, anpassen oder die Signaturlinks kopieren und diese selbst mit den Empfängern teilen.",
"report-1":"Klicken Sie auf die Schaltfläche „Hinzufügen“, um eine neue Vorlage zu erstellen. Vorlagen sind wiederverwendbare Dokumente, mit denen schnell neue Dokumente mit derselben Struktur und unterschiedlichen Unterzeichnern erstellt werden können. Eine HR-Vorlage für die Einarbeitung könnte beispielsweise vordefinierte Rollen wie „Personalleiter“ und „Neuer Mitarbeiter“ enthalten. Bei jeder Verwendung der Vorlage können Sie die Rolle „Neuer Mitarbeiter“ verschiedenen neuen Mitarbeitern zuweisen, während die Rolle „Personalleiter“ unverändert bleibt. So wird ein nahtloser Einarbeitungsprozess für jeden neuen Mitarbeiter ermöglicht.",
"redirect": "Klicken Sie auf die Schaltfläche 'Verwenden', um ein neues Dokument aus einer bestehenden Vorlage zu erstellen.",
"bulksend": "Um schnell mehrere Dokumente mithilfe einer vorhandenen Vorlage zu versenden, indem Sie einfach die E-Mail-Adressen der Empfänger erstellen, klicken Sie auf die Schaltfläche Massenversand",
"option": "Dieses Menü zeigt weitere Optionen wie Bearbeiten und Löschen. Verwenden Sie die Schaltfläche 'Bearbeiten', um Unterzeichnerrollen hinzuzufügen, Felder zu ändern und Ihre Vorlage zu aktualisieren. Änderungen gelten für alle zukünftigen Dokumente, die aus dieser Vorlage erstellt werden, wirken sich jedoch nicht auf vorhandene Dokumente aus. Verwenden Sie die Schaltfläche 'Löschen', um die Vorlage zu entfernen.",
@@ -689,19 +704,19 @@
"download-pdf": "PDF herunterladen",
"pdf-certificate": "PDF + Zertifikat herunterladen",
"document-logs": "Dokumentenprotokolle",
"server-down": "Verbindung zum OpenSign-Server nicht möglich. Wenn Sie OpenSign selbst hosten, stellen Sie bitte sicher, dass alle Schritte in der Dokumentation korrekt befolgt wurden. Wenn Sie OpenSign lokal ausführen, verwenden Sie möglicherweise eine falsche Portnummer.",
"server-down": "Verbindung zum {{appName}} Server nicht möglich. Wenn Sie {{appName}} selbst hosten, stellen Sie bitte sicher, dass alle Schritte in der Dokumentation korrekt befolgt wurden. Wenn Sie {{appName}} lokal ausführen, verwenden Sie möglicherweise eine falsche Portnummer.",
"admin-exists": "Admin existiert bereits. Bitte melden Sie sich mit den Admin-Anmeldedaten an, um Benutzer zu verwalten.",
"enable-tour": "Tour aktivieren",
"istourenabled-help": {
"p1": "Dies aktiviert eine geführte Tour für Unterzeichner, die während des Unterzeichnungsprozesses Anweisungen gibt.",
"p2": "Dies deaktiviert die geführte Tour, wodurch ein schneller, ununterbrochener Unterzeichnungsprozess gewährleistet wird.",
"p3": "Tipp: Wenn Ihre Unterzeichner mit OpenSign vertraut sind und es häufig verwenden, können Sie die geführte Tour deaktivieren, um ein nahtloses Erlebnis zu gewährleisten. Wählen Sie die Option, die am besten zu Ihren Anforderungen passt."
"p3": "Tipp: Wenn Ihre Unterzeichner mit {{appName}} vertraut sind und es häufig verwenden, können Sie die geführte Tour deaktivieren, um ein nahtloses Erlebnis zu gewährleisten. Wählen Sie die Option, die am besten zu Ihren Anforderungen passt."
},
"form-title-1": "Dokumentenfluss-Einstellungen",
"form-title-2": "Sicherheitseinstellungen",
"public-tour-message": "Die Vorlage muss öffentlich sein, bevor Sie einen teilbaren Link generieren können.",
"add-user-template": "Sie müssen eine Rolle hinzufügen, bevor Sie Felder dafür hinzufügen können.",
"pdf-uncompatible": "Dieses PDF ist nicht kompatibel mit OpenSign. Bitte kontaktieren Sie <support@opensignlabs.com>",
"pdf-uncompatible": "Diese PDF-Datei ist nicht kompatibel, bitte kontaktieren Sie {{appName}}",
"text-field-tour": "Felder vom Typ 'Text' müssen im Voraus ausgefüllt werden, bevor das Dokument versendet wird. Wenn Sie möchten, dass die Unterzeichner Eingaben machen, verwenden Sie das Feld 'Texteingabe'.",
"attach-signer-tour": "Sie müssen einen Unterzeichner jeder Rolle zuweisen. Dies können Sie durch Klicken auf dieses Symbol tun. Sobald Sie einen Unterzeichner auswählen, wird er allen Feldern der zugehörigen Rolle zugewiesen, die in derselben Farbe erscheinen.",
"allowed-signature-types": "Erlaubte Signaturtypen",
@@ -741,7 +756,7 @@
"delete-folder-alert": "Sind Sie sicher, dass Sie diesen Ordner löschen möchten?",
"delete-folder-alert-1": "Löschen nicht möglich: Der Ordner muss leer sein.",
"defaultSign-alert": "{{widgetsType}} Widgets sind nicht für die automatische Signatur verfügbar.",
"embed-heading": "OpenSign-Vorlagen einbetten",
"embed-heading": "{{appName}} Vorlagen einbetten",
"public-template-edit-title": "Öffentliche Vorlage bearbeiten",
"public-template-edit-mssg": "Eine öffentliche Vorlage können Sie nicht direkt bearbeiten. Um Änderungen vorzunehmen, wird die Vorlage auf privat gesetzt und Sie müssen sie anschließend wieder öffentlich machen. Möchten Sie fortfahren?",
"agree-p1": "Ich bestätige, dass ich die ",
@@ -750,46 +765,46 @@
"agrre-button": "Zustimmen & Fortfahren",
"term-cond-title": "Allgemeine Geschäftsbedingungen",
"term-cond-h": "OFFENLEGUNG ELEKTRONISCHER AUFZEICHNUNGEN UND SIGNATUREN",
"term-cond-p1": "Diese Offenlegung elektronischer Aufzeichnungen und Signaturen ('Offenlegung') ist eine Vereinbarung zwischen dem Dokumentersteller ('Sender') und dem Unterzeichner ('Ihnen'), bereitgestellt über die OpenSign-Plattform ('Plattform'). Durch das Signieren von Dokumenten über OpenSign stimmen Sie den in dieser Offenlegung beschriebenen Bedingungen zu. Bitte lesen Sie sie sorgfältig durch, bevor Sie fortfahren.",
"term-cond-p1": "Diese Offenlegung elektronischer Aufzeichnungen und Signaturen ('Offenlegung') ist eine Vereinbarung zwischen dem Dokumentersteller ('Sender') und dem Unterzeichner ('Ihnen'), bereitgestellt über die {{appName}} Plattform ('Plattform'). Durch das Signieren von Dokumenten über {{appName}} stimmen Sie den in dieser Offenlegung beschriebenen Bedingungen zu. Bitte lesen Sie sie sorgfältig durch, bevor Sie fortfahren.",
"term-cond-h1": "1. Zweck und Geltungsbereich",
"term-cond-p2": "Diese Offenlegung regelt die Nutzung elektronischer Aufzeichnungen und Signaturen für Dokumente, die vom Sender bereitgestellt und von Ihnen über die OpenSign-Plattform unterzeichnet werden. Mit Ihrer Zustimmung erkennen Sie an, dass:",
"term-cond-p3": "Sie Dokumente elektronisch über OpenSign empfangen und unterzeichnen werden.",
"term-cond-p2": "Diese Offenlegung regelt die Nutzung elektronischer Aufzeichnungen und Signaturen für Dokumente, die vom Sender bereitgestellt und von Ihnen über die {{appName}} Plattform unterzeichnet werden. Mit Ihrer Zustimmung erkennen Sie an, dass:",
"term-cond-p3": "Sie Dokumente elektronisch über {{appName}} empfangen und unterzeichnen werden.",
"term-cond-p4": "Ihre elektronische Signatur rechtlich bindend und einer handschriftlichen Signatur gleichgestellt ist.",
"term-cond-h2": "2. Zustimmung zur Nutzung elektronischer Aufzeichnungen und Signaturen",
"term-cond-p5": "Durch Ihre Zustimmung zu dieser Offenlegung:",
"term-cond-p6": "Erklären Sie sich einverstanden, elektronisch mit dem Sender über OpenSign zu kommunizieren, und verstehen, dass diese Zustimmung gültig ist, bis sie widerrufen wird.",
"term-cond-p7": "Stimmen Sie zu, Dokumente über OpenSign elektronisch zu prüfen, zu unterzeichnen und zurückzusenden.",
"term-cond-p6": "Erklären Sie sich einverstanden, elektronisch mit dem Sender über {{appName}} zu kommunizieren, und verstehen, dass diese Zustimmung gültig ist, bis sie widerrufen wird.",
"term-cond-p7": "Stimmen Sie zu, Dokumente über {{appName}} elektronisch zu prüfen, zu unterzeichnen und zurückzusenden.",
"term-cond-p8": "Diese Zustimmung gilt nur für die spezifischen Dokumente, die vom Sender bereitgestellt werden.",
"term-cond-h3": "3. Recht auf Widerruf der Zustimmung",
"term-cond-p9": "Sie haben das Recht, Ihre Zustimmung zur Nutzung elektronischer Aufzeichnungen und Unterschriften zu widerrufen, indem Sie:",
"term-cond-p10": "Den Absender direkt über die in seiner Kommunikation angegebenen Kontaktdaten kontaktieren.",
"term-cond-p11": "Den Absender vor Abschluss des Unterschriftsprozesses benachrichtigen.",
"term-cond-h4": "4. Hardware- und Softwareanforderungen",
"term-cond-p12": "Um Dokumente mit OpenSign zu unterzeichnen, benötigen Sie:",
"term-cond-p12": "Um Dokumente mit {{appName}} zu unterzeichnen, benötigen Sie:",
"term-cond-p13": "Ein Gerät mit Internetzugang.",
"term-cond-p14": "Einen kompatiblen Webbrowser mit aktiviertem JavaScript.",
"term-cond-p15": "Ein E-Mail-Konto, um Benachrichtigungen und unterzeichnete Dokumente zu erhalten.",
"term-cond-p16": "Wenn sich diese Anforderungen ändern, werden Sie vom Absender oder OpenSign über die Aktualisierungen informiert.",
"term-cond-p16": "Wenn sich diese Anforderungen ändern, werden Sie vom Absender oder {{appName}} über die Aktualisierungen informiert.",
"term-cond-h5": "5. Aufbewahrung von Unterlagen und Anforderung von Papierkopien",
"term-cond-p17": "Sie sind dafür verantwortlich, sicherzustellen, dass Sie auf die elektronisch unterzeichneten Dokumente zugreifen und diese aufbewahren können. OpenSign stellt Tools zur Verfügung, mit denen Sie das unterzeichnete Dokument unmittelbar nach Abschluss des Unterzeichnungsprozesses herunterladen oder ausdrucken können.",
"term-cond-p17": "Sie sind dafür verantwortlich, sicherzustellen, dass Sie auf die elektronisch unterzeichneten Dokumente zugreifen und diese aufbewahren können. {{appName}} stellt Tools zur Verfügung, mit denen Sie das unterzeichnete Dokument unmittelbar nach Abschluss des Unterzeichnungsprozesses herunterladen oder ausdrucken können.",
"term-cond-p18": "Wenn Sie eine Papierkopie eines elektronisch unterzeichneten Dokuments bevorzugen, können Sie diese direkt beim Absender anfordern. Der Absender kann eine angemessene Gebühr für die Bereitstellung von Papierkopien erheben, sofern dies nicht durch geltendes Recht verboten ist.",
"term-cond-h6": "6. Anerkennung und Zustimmung",
"term-cond-p19": "Durch das Fortfahren bestätigen Sie:",
"term-cond-p20": "Dass Sie in der Lage sind, elektronische Dokumente wie in dieser Offenlegung beschrieben zu öffnen und zu überprüfen.",
"term-cond-p21": "Der Nutzung elektronischer Aufzeichnungen und Unterschriften für die angegebenen Transaktionen mit dem Absender zuzustimmen.",
"term-cond-p22": "Zu verstehen, dass OpenSign eine Plattform zur Unterstützung der Transaktion ist und keine Partei der Vereinbarung ist.",
"term-cond-p22": "Zu verstehen, dass {{appName}} eine Plattform zur Unterstützung der Transaktion ist und keine Partei der Vereinbarung ist.",
"term-cond-h7": "7. Rechtliche Wirkung",
"term-cond-p23": "Ihre durch OpenSign vermittelte elektronische Unterschrift:",
"term-cond-p23": "Ihre durch {{appName}} vermittelte elektronische Unterschrift:",
"term-cond-p24": "Entspricht den geltenden Gesetzen über elektronische Unterschriften, einschließlich, aber nicht beschränkt auf den E-SIGN Act in den Vereinigten Staaten, die EU-eIDAS-Verordnung und das indische Information Technology Act.",
"term-cond-p25": "Ist rechtlich bindend zwischen Ihnen und dem Absender für die unterzeichneten Dokumente.",
"term-cond-h8": "8. Plattformrolle und Haftungsbeschränkung",
"term-cond-p26": "OpenSign dient als Plattform zur Unterstützung elektronischer Transaktionen. Es ist nicht verantwortlich für den Inhalt, die Gültigkeit oder die Durchsetzbarkeit der vom Absender gesendeten Dokumente. Alle Streitigkeiten oder Probleme im Zusammenhang mit dem Dokument oder seiner Unterzeichnung müssen direkt zwischen Ihnen und dem Absender gelöst werden.",
"term-cond-p26": "{{appName}} dient als Plattform zur Unterstützung elektronischer Transaktionen. Es ist nicht verantwortlich für den Inhalt, die Gültigkeit oder die Durchsetzbarkeit der vom Absender gesendeten Dokumente. Alle Streitigkeiten oder Probleme im Zusammenhang mit dem Dokument oder seiner Unterzeichnung müssen direkt zwischen Ihnen und dem Absender gelöst werden.",
"term-cond-h9": "9. Beendigung der Vereinbarung",
"term-cond-p27": "Ihre Zustimmung gilt nur für die aktuelle Transaktion, es sei denn, der Absender gibt etwas anderes an. OpenSign behält sich das Recht vor, den Zugriff auf seine Plattform bei Verstößen gegen die Nutzungsbedingungen oder Richtlinien zu beenden.",
"term-cond-p28": "Durch Auswahl von 'Ich stimme zu' bestätigen Sie, dass Sie die Bedingungen dieser Offenlegung über elektronische Aufzeichnungen und Unterschriften verstanden haben und diesen zustimmen sowie der elektronischen Unterzeichnung der vom Absender über OpenSign bereitgestellten Dokumente zustimmen.",
"term-cond-p29": "Wenn Sie Fragen zu dieser Offenlegung haben, kontaktieren Sie den Absender direkt. Für technischen Support mit OpenSign besuchen Sie",
"term-cond-p30": "oder senden Sie eine E-Mail an",
"js-snippet-msg": "Um OpenSign-Vorlagen in Ihre HTML-Websites oder Landingpages zu integrieren, können Sie den folgenden Code verwenden:",
"term-cond-p27": "Ihre Zustimmung gilt nur für die aktuelle Transaktion, es sei denn, der Absender gibt etwas anderes an. {{appName}} behält sich das Recht vor, den Zugriff auf seine Plattform bei Verstößen gegen die Nutzungsbedingungen oder Richtlinien zu beenden.",
"term-cond-p28": "Durch Auswahl von 'Ich stimme zu' bestätigen Sie, dass Sie die Bedingungen dieser Offenlegung über elektronische Aufzeichnungen und Unterschriften verstanden haben und diesen zustimmen sowie der elektronischen Unterzeichnung der vom Absender über {{appName}} bereitgestellten Dokumente zustimmen.",
"term-cond-p29": "Wenn Sie Fragen zu dieser Offenlegung haben, kontaktieren Sie den Absender direkt. Für technischen Support mit {{appName}} besuchen Sie ",
"term-cond-p30": " oder senden Sie eine E-Mail an",
"js-snippet-msg": "Um {{appName}} Vorlagen in Ihre HTML-Websites oder Landingpages zu integrieren, können Sie den folgenden Code verwenden:",
"js-snippet-msg-1": "Die TemplateId erhalten Sie von der",
"js-snippet-msg-2": "Vorlagen verwalten",
"js-snippet-msg-3": "Seite.",
@@ -814,13 +829,13 @@
},
"signature-setting": "Signatur-Einstellungen",
"activation-key": "Aktivierungsschlüssel",
"paid-selfhost-help": "Bitte kontaktieren Sie OpenSign für weitere Informationen unter ",
"remaining-credits-selfhost-help": "Verwenden Sie Premium-Credits für API-Dokumentensignaturen, den Massenversand und die OpenSign-Integration auf Ihrer Website. Sie haben {{allowedcredits}} enthaltene Credits und {{addoncredits}} zusätzlich gekaufte Credits übrig.",
"paid-selfhost-help": "Bitte kontaktieren Sie {{appName}} für weitere Informationen unter ",
"remaining-credits-selfhost-help": "Verwenden Sie Premium-Credits für API-Dokumentensignaturen, den Massenversand und die {{appName}} Integration auf Ihrer Website. Sie haben {{allowedcredits}} enthaltene Credits und {{addoncredits}} zusätzlich gekaufte Credits übrig.",
"signature-type": "Ihre Signatur",
"initial-type": "Ihre Initialen",
"redirect-url": "Weiterleitungs-URL",
"bulk-send": "Massenversand",
"select-timezone": "Wählen Sie Ihre Zeitzone",
"select-timezone": "Zeitzone",
"current-time": "Aktuelle Uhrzeit",
"email-help": "Aus Sicherheitsgründen dürfen Sie die E-Mail-Adresse nicht ändern. Bitte erstellen Sie ein weiteres kostenloses Konto mit der neuen E-Mail-Adresse.",
"doc-sent": "Dokument erfolgreich gesendet.",
@@ -833,5 +848,44 @@
"variables-use": "Zu verwendende Variablen",
"send-to-sign": "hat Sie gebeten zu unterschreiben",
"use-template": "Vorlage verwenden",
"contact-already-exists": "Der Kontakt existiert bereits."
"contact-already-exists": "Der Kontakt existiert bereits.",
"docx-error": "Wir haben derzeit Probleme mit der Verarbeitung von DOCX-Dateien. Bitte laden Sie die PDF-Datei hoch",
"docx-error-contact": "oder kontaktieren Sie uns unter support@opensignlabs.com",
"agreement-note": "Hinweis: Durch Ihre Zustimmung unterzeichnen Sie das Dokument nicht sofort. Sie können das Dokument nur elektronisch einsehen. Sie haben die Möglichkeit, es vollständig zu lesen und anschließend zu entscheiden, ob Sie es unterzeichnen möchten.",
"draft-template-info-p1": "Um Ihre Vorlage öffentlich zu machen, muss sie entweder eine einzelne Rolle enthalten oder, wenn sie mehrere Rollen umfasst, müssen alle zusätzlichen Rollen bereits den Unterzeichnern zugewiesen sein. Die nicht zugewiesene öffentliche Rolle muss leer bleiben und an erster Stelle stehen.",
"visit-below-link": "Besuchen Sie den untenstehenden Link, um mehr zu erfahren -",
"storage-help": "Durch die Aktivierung von BYOC können Sie Ihren eigenen S3-Speicher verbinden, sodass Ihre Dateien vollständig unter Ihrer Kontrolle bleiben und keine externen Kopien gespeichert werden. Wenn Ihnen Datenautonomie wichtig ist, erwägen Sie ein Upgrade auf Teams, um diese Funktion freizuschalten.",
"daily-quota-reached": "Sie haben Ihr tägliches Kontingent erreicht. Für Unterstützung kontaktieren Sie bitte quotas@opensignlabs.com.",
"enabled-signature-type": "Aktivierte Signaturtypen",
"enabled-signature-type-help": "Die Einstellung 'Aktivierte Signaturtypen' bestimmt, welche Signaturoptionen in Ihrer Organisation verfügbar sind. Wenn Sie beispielsweise die Option 'Zeichnen' deaktivieren, wird sie den Mitgliedern Ihrer Organisation im Signatur-Widget nicht angezeigt, während die anderen drei Optionen weiterhin zugänglich bleiben.",
"indexing-public-profile": "Erlaube die Indexierung des öffentlichen Profils durch Suchmaschinen",
"user-created-successfully": "Benutzer erfolgreich erstellt.",
"only-15-reminder-allowed": "Sie können bis zu 15 automatische Erinnerungen festlegen. Wenn zum Beispiel 'TimeToComplete' auf 15 Tage und 'RemindOnceInEvery' auf 1 Tag eingestellt ist, erreichen Sie das maximale Limit von 15 Erinnerungen. Passen Sie Ihre Einstellungen entsprechend an.",
"rate-your-experience": "Wie war Ihre Erfahrung mit {{appName}}?",
"thanks-for-feedback": "Danke für Ihr Feedback 🙏",
"share-your-feedback": "Teilen Sie Ihr Feedback",
"share-your-review": "Teilen Sie Ihre Bewertung",
"date-format": "Datumsformat",
"document-deleted": "Das Dokument wurde gelöscht oder Sie haben keinen Zugriff. Bitte kontaktieren Sie den Absender.",
"save-as-template-?": "Sind Sie sicher, dass Sie dieses Dokument als Vorlage speichern möchten?",
"go-to-manage-templates": "Zu 'Vorlagen verwalten' gehen",
"template-created": "Vorlage erstellt",
"how-would-you-like-to-proceed?": "Wie möchten Sie fortfahren?",
"failed-to-load-refresh-page": "Fehler beim Laden des Dokuments. Bitte versuchen Sie, diese Seite zu aktualisieren.",
"document-has-been-signed": "Das Dokument wurde erfolgreich unterschrieben!",
"document-has-been-signed-by-you": "Das Dokument wurde erfolgreich von Ihnen unterschrieben!",
"participant-completed-signing": "Alle Teilnehmer haben den Signaturprozess abgeschlossen.",
"you-will-receive-email-shortly": "✅ Das war's! Sie erhalten in Kürze eine Bestätigungs-E-Mail.",
"please-provide-templateid": "Bitte geben Sie templateid an",
"this-template-is-not-public": "Dieses template ist nicht öffentlich",
"invalid-templateid": "Ungültige templateid",
"contact-billing-at-opensign": "Um weitere Plätze hinzuzufügen, kontaktieren Sie bitte OpenSign™ unter <1>billing@opensignlabs.com</1> für Unterstützung.",
"title-length-alert": "Der Titel darf höchstens 250 Zeichen lang sein.",
"note-length-alert": "Die Notiz darf höchstens 200 Zeichen lang sein.",
"description-length-alert": "Die Beschreibung darf höchstens 500 Zeichen lang sein.",
"fix-&-resend-document": "Dokument korrigieren und erneut senden",
"do-you-want-recreate-document?": "Dadurch wird ein Entwurf aus diesem Dokument mit allen vorhandenen Feldern erstellt. Sind Sie sicher, dass Sie dieses Dokument neu erstellen möchten?",
"start-editing": "Bearbeitung starten",
"unsaved-changes-discard-them?": "Sie haben ungespeicherte Änderungen. Verwerfen?",
"yes-discard": "Ja, verwerfen"
}
+100 -47
View File
@@ -51,7 +51,6 @@
"docs": "Docs",
"session-expired": "Your session has expired.",
"public-profile-help": "This is your public URL. Copy or share it with the signer, and you will be able to see all your publicly set templates.",
"tagline-help": "This tagline will appear on your OpenSign public profile for ex. https://opensign.me/alex",
"welcome": "Welcome back!",
"Login-to-your-account": "Login to your account",
"password": "Password",
@@ -74,12 +73,23 @@
"Sign yourself": "Sign yourself",
"Request signatures": "Request signatures",
"Templates": "Templates",
"Analytics": "Analytics",
"Branding": "Branding",
"Mail": "Mail",
"Storage": "Storage",
"Signing certificate": "Signing certificate",
"Teams": "Teams",
"General": "General",
"Teams-Children": {
"Organizations": "Organizations",
"OrgAdmins": "OrgAdmins"
},
"Templates-Children": {
"Create template": "Create template",
"Manage templates": "Manage templates",
"Embedding": "Embedding"
},
"OpenSign™ Drive": "OpenSign™ drive",
"OpenSign™ Drive": "{{appName}} Drive",
"Reports": "Reports",
"Documents": "Documents",
"Documents-Children": {
@@ -140,7 +150,10 @@
"Copy Public URL": "Copy public URL",
"extend-expiry-date": "Extend expiry date",
"Duplicate Template": "Duplicate template",
"Duplicate": "Duplicate"
"Duplicate": "Duplicate",
"daily-mail-quota": "Daily Email Quota",
"Save as template": "Save as template",
"Fix & resend": "Fix & Resend"
},
"report-heading": {
"Sr.No": "Sr.No",
@@ -198,7 +211,7 @@
"remind-once": " Remind once in every (Days)",
"next": "Next",
"select-folder": "Select folder",
"OpenSign-drive": "OpenSign™ drive",
"OpenSign-drive": "{{appName}} Drive",
"drive-document-status": {
"Completed": "Completed",
"Declined": "Declined",
@@ -206,7 +219,7 @@
"Expired": "Expired",
"In Progress": "In progress"
},
"select-folder-help": "If you do not select a folder, your signed document will be saved in the Main OpenSign drive folder.",
"select-folder-help": "If you do not select a folder, your signed document will be saved in the Main {{appName}} Drive folder.",
"no-data": "No data found",
"save-here": "Save here",
"back": "Back",
@@ -236,7 +249,7 @@
"generate-token-alert": "Are you sure you want to regenerate token it will expire old token?",
"yes": "Yes",
"copied": "Copied",
"something-went-wrong-mssg": "Something went wrong, please try again later.",
"something-went-wrong-mssg": "Something went wrong, refreshing this page may solve this issue.",
"token-generated": "Token generated successfully.",
"webhook": "Webhook",
"update-webhook": "Update webhook",
@@ -272,13 +285,14 @@
"make-template-public": "Make template public",
"make-template-private": "Make template private",
"make-template-public-alert": "Are you sure you want to make this template public?",
"make-template-private-alert-non": "Are you sure you want to make this template private?",
"make-template-private-alert": "Are you sure you want to make this template private? This will remove it from your public profile.",
"public-role": "Public role",
"public-url": "Public profile",
"embed-template": "Embed template",
"public-url-copy": "Heres your public URL: ",
"public-url-copy": "Here's your public URL: ",
"public-url-copy-mssg": "Copy it or share it with the signer, and you will be able to see all your publicly set templates.",
"add-public-url-alert": "You can generate your OpenSign public profile",
"add-public-url-alert": "You can generate your {{appName}} public profile",
"share-with-alert": "You cannot share a template if any roles already have contacts assigned. Please remove all contact assignments from the roles before sharing the template.",
"share-with": "Share with",
"team-not-found": "Team not found",
@@ -297,12 +311,12 @@
"revoke-document": "Revoke document",
"revoke-document-alert": "Are you sure you want to revoke this document?",
"resend-mail": "Resend mail",
"resend-mail-help": "You can use following variables which will get replaced with their actual values:- {{document_title}}, {{sender_name}}, {{sender_mail}}, {{sender_phone}}, {{receiver_name}}, {{receiver_email}}, {{receiver_phone}}, {{expiry_date}}, {{company_name}}, {{signing_url}}.",
"resend-mail-help": "You can use following variables which will get replaced with their actual values:- {{document_title}}, {{sender_name}}, {{sender_mail}}, {{sender_phone}}, {{receiver_name}}, {{receiver_email}}, {{receiver_phone}}, {{expiry_date}}, {{company_name}}, {{signing_url}}, {{note}}.",
"subject": "Subject",
"body": "Body",
"add-contact": "Add contact",
"edit-contact": "Edit contact",
"add-signer-alert": "Contact already exist! Please select it from Signers dropdown",
"add-signer-alert": "Contact already exist! Please select it from 'Signers' dropdown",
"record-delete-alert": "Record deleted successfully!",
"record-revoke-alert": "Record revoked successfully!",
"mail-sent-alert": "Mail sent successfully.",
@@ -447,6 +461,7 @@
"placeholder-alert-3": " Are you sure you want to send out this document for signatures?",
"placeholder-alert-4": "You have successfully sent mails to all recipients!",
"placeholder-mail-alert": "You have successfully sent email to {{name}}. Subsequent signers will get email(s) once {{name}} signs the document",
"placeholder-mail-alert-you": "Subsequent signers will get email(s) once you signs the document.",
"placeholder-alert-5": "Do you want to sign the document right now?",
"placeholder-alert-6": "Please setup mail adapter to send mail!",
"placeholder-alert-7": "Please select signer for add placeholder!",
@@ -493,7 +508,7 @@
"role-not-found": "Role not found.",
"do-not-access": "You don't have access, please contact the admin.",
"add-admin": "Add admin",
"opensign-setup": "OpenSign setup",
"opensign-setup": "{{appName}} setup",
"join-discord": "Join our discord server",
"admin-already-exist": "Admin already exist.",
"password-update-alert-1": "Password updated successfully.",
@@ -510,7 +525,7 @@
"correct-password": "Please provide correct password",
"decrypting-pdf": " Decrypting pdf please wait...",
"invalid-otp": "Invalid otp",
"user-not-found": "User not found!",
"user-not-found": "User not found",
"enter-otp-alert": "Please enter OTP!",
"get-verification-code": "Get verification code",
"get-verification-code-2": "You will get a verification code via Email",
@@ -550,7 +565,6 @@
"filed-required-correctly": "Please fill required details correctly.",
"admin-created": "Admin created",
"invalid-masterkey": "Invalid masterkey",
"opensign-Setup": "OpenSign setup",
"master-key": " Master key",
"profile-update-alert": "Profile updated successfully.",
"date": "Date",
@@ -574,8 +588,8 @@
},
"tour-mssg": {
"home-layout-1": "You have logged in successfully! Let's take a look.",
"home-layout-2": "To upload documents for self-signing or to request others signatures, simply select the respective buttons.",
"home-layout-3": "You are ready to start using OpenSign! If you need support feel free to contact us.",
"home-layout-2": "To upload documents for self-signing or to request others' signatures, simply select the respective buttons.",
"home-layout-3": "You are ready to start using {{appName}}! If you need support feel free to contact us.",
"generate-token": "Upgrade now to generate production API token.",
"opensign-drive-1": "Click on the breadcrumb links to easily navigate through the folder hierarchy and view the documents within each folder.",
"opensign-drive-2": "Click the add button to create a new folder or document.",
@@ -583,7 +597,7 @@
"opensign-drive-4": "Click on this menu to display the documents in list view.",
"opensign-drive-5": "The document list is displayed according to the selected sorting option. Icons next to each document indicate its current status.",
"opensign-drive-6": "Right-click on a document to see options such as Download, Rename, Move, and Delete. Click on the document to open it.",
"opensign-drive-7": "Right-click on any folder to see options. Choose Rename to change the folders name or click on the folder to navigate through its contents.",
"opensign-drive-7": "Right-click on any folder to see options. Choose 'Rename' to change the folder's name or click on the folder to navigate through its contents.",
"pdf-request-file-1": "List of signers who still need to sign the document .",
"pdf-request-file-2": "Click any of the placeholders appearing on the document to sign. You will then see options to draw your signature, type it, or upload an image .",
"pdf-request-file-3": "Click Decline, or Finish buttons to navigate your document. Use the ellipsis menu for additional options, including the Download button .",
@@ -596,24 +610,24 @@
"placeholder-sign-4": "Drag or click on a field to add it to the document.",
"placeholder-sign-5": "The PDF content area already displays the template's existing placeholders. For your convenience, these placeholders will match the color of the recipient's name, making them easily identifiable.",
"placeholder-sign-6": "Clicking 'Next' will save the document. In the next step you can customize the emails to be sent out to the recipients or copy the signing links and share those with the recipients yourself.",
"report-1": "Click the 'Add' button to create a new template. Templates are reusable documents designed to quickly generate new documents with the same structure and varying signers. For example, an HR template for onboarding could have predefined roles like HR Manager and New Employee. Each time you use the template, you can assign the New Employee role to different incoming staff members, while the HR Manager role remains constant, facilitating a seamless onboarding process for each recruit. ",
"report-1": "Click the 'Add' button to create a new template. Templates are reusable documents designed to quickly generate new documents with the same structure and varying signers. For example, an HR template for onboarding could have predefined roles like 'HR Manager' and 'New Employee'. Each time you use the template, you can assign the 'New Employee' role to different incoming staff members, while the 'HR Manager' role remains constant, facilitating a seamless onboarding process for each recruit. ",
"redirect": "Click the 'Use' button to create a new document from an existing template.",
"bulksend": "To quickly send multiple documents using an existing template by just creating the recipient email addresses, click the 'Bulk Send' button.",
"option": "This menu reveals more options such as Edit & Delete. Use the 'Edit' button to add signer roles, modify fields, and update your template. Changes will apply to all future documents created from this template but wont affect existing documents.Use the Delete button you can delete template. ",
"option": "This menu reveals more options such as Edit & Delete. Use the 'Edit' button to add signer roles, modify fields, and update your template. Changes will apply to all future documents created from this template but won't affect existing documents.Use the Delete button you can delete template. ",
"signyour-self-1": "Select and drag your preferred widgets onto the PDF to customize your document before signing. Choose the perfect spots for each modification to tailor the document to your needs.",
"signyour-self-2": "Drag and drop anywhere in this area. You can resize and move it later.",
"template-placeholder-1": "Clicking 'Add role' button will allow you to add various signer roles. You can attach users to each role in subsequent steps.",
"template-placeholder-2": "Once roles are added, select a role from list to add a place-holder where he is supposed to sign. The placeholder will appear in the same colour as the role name once you drop it on the document.",
"template-placeholder-3": "Drag or click on a field to add it to the document.",
"template-placeholder-4": "Drag the placeholder for a role anywhere on the document.Remember, it will appear in the same colour as the name of the recipient for easy reference.",
"template-placeholder-5": "Clicking 'Next' will store the current template. After saving, youll be prompted to create a new document from this template if you wish.",
"template-placeholder-5": "Clicking 'Next' will store the current template. After saving, you'll be prompted to create a new document from this template if you wish.",
"webhook-1": "Upgrade now to set webhook",
"Need your Signature": "Clicking on this card will take you to the list of documents awaiting your review.",
"Out for signatures": "Clicking on this card will take you to a list of documents awaiting signature.",
"Recent signature requests": "This is a list of documents that are waiting for your signature.",
"Recently sent for signatures": "This is a list of documents you've sent to other parties for signature.",
"Drafts": "This are documents you have started but have not finalized for sending.",
"public-template": "This video demonstrates how to set up your personalized public profile, such as https://opensign.me/your-username. Youll also learn how to customize your tagline and make your templates available for public signing.",
"public-template": "This video demonstrates how to set up your personalized public profile, such as 'https://opensign.me/your-username'. You'll also learn how to customize your tagline and make your templates available for public signing.",
"allowModify-widgets": "You can drag and drop any of these fields onto the document, in addition to the fields already designated for you by the document creator."
},
"enter-email-plaholder": "Add an email address and hit enter",
@@ -647,13 +661,13 @@
"bulk-send-subcription-alert": "Please upgrade to Professional or Team plan to use bulk send.",
"generate-test-token": "Generate test token",
"regenerate-test-token": "Regenerate test token",
"help-test-token": "This token can be used to test the APIs at the https://sandbox.opensignlabs.com/api/v1 endpoint, allowing you to conduct unlimited document signatures. Please note that the sandbox API will sign your documents with self-signed certificates, which may not be recognized as valid by Adobe. Once youve completed your testing, you can upgrade to one of our paid plans to generate a production token.",
"help-test-token": "This token can be used to test the APIs at the https://sandbox.opensignlabs.com/api/v1 endpoint, allowing you to conduct unlimited document signatures. Please note that the sandbox API will sign your documents with self-signed certificates, which may not be recognized as valid by Adobe. Once you've completed your testing, you can upgrade to one of our paid plans to generate a production token.",
"help-api-token": "This token can be used to access the production APIs at the {{origin}}/api/v1 endpoint. It can only be generated on one of our paid plans.",
"reason": "Reason",
"decline-by": "Declined/revoked by",
"document-declined": "Document declined",
"public-template-mssg-1": "To integrate OpenSign into your React or Next.js project, simply run the following command:",
"public-template-mssg-2": "Ensure you have npm or yarn set up in your project. If youre using Yarn, you can replace npm install with yarn add @opensign/react.",
"public-template-mssg-2": "Ensure you have npm or yarn set up in your project. If you're using Yarn, you can replace npm install with yarn add @opensign/react.",
"public-template-mssg-3": "Need more details or examples?",
"public-template-mssg-4": "Visit the",
"public-template-mssg-5": " npm for the latest updates, detailed documentation, and version history.",
@@ -690,19 +704,19 @@
"download-pdf": "Download pdf",
"pdf-certificate": "Download pdf + Certificate",
"document-logs": "Document logs",
"server-down": "Unable to connect to the OpenSign server. If you are self-hosting OpenSign, please ensure that all the steps in the documentation have been followed correctly. If you're running OpenSign locally, you might be accessing it through an incorrect port number.",
"server-down": "Unable to connect to the {{appName}} server. If you are self-hosting {{appName}}, please ensure that all the steps in the documentation have been followed correctly. If you're running {{appName}} locally, you might be accessing it through an incorrect port number.",
"admin-exists": "Admin already exists. Please login to the application using admin credentials in order to manage users.",
"enable-tour": "Enable tour",
"istourenabled-help": {
"p1": "This will enable a guided tour for signers, providing instructions during the signing process.",
"p2": "This will disable the guided tour, ensuring a faster, uninterrupted signing experience.",
"p3": "Tip: If your signers are familiar with OpenSign and frequently use it, you may want to disable the guided tour for a more seamless experience. Choose the option that best fits your document signing needs."
"p3": "Tip: If your signers are familiar with {{appName}} and frequently use it, you may want to disable the guided tour for a more seamless experience. Choose the option that best fits your document signing needs."
},
"form-title-1": "Document flow setting",
"form-title-2": "Security settings",
"public-tour-message": "The template needs to be public before you can generate a shareable link.",
"add-user-template": "You need to add a role before you can add fields for it.",
"pdf-uncompatible": "This pdf is not compatible with opensign, please contact <support@opensignlabs.com>",
"pdf-uncompatible": "This pdf is not compatible, please contact {{appName}}",
"text-field-tour": "Fields of type 'Text' must be filled in advance before the document is sent. If you need the signers to provide input, use the 'Text Input' field instead.",
"attach-signer-tour": "You need to attach a Signer to every role. You can do that by clicking this icon. Once you select a Signer it will be attached to all the fields associated with that role which appear in the same colour.",
"allowed-signature-types": "Allowed signature types",
@@ -742,7 +756,7 @@
"delete-folder-alert": "Are you sure you want to delete this Folder ?",
"delete-folder-alert-1": "Unable to delete: The folder must be empty.",
"defaultSign-alert": "{{widgetsType}} widgets are not available for auto sign.",
"embed-heading": "Embed OpenSign Templates",
"embed-heading": "Embed {{appName}} Templates",
"public-template-edit-title": "Edit Public Template",
"public-template-edit-mssg": "You cannot edit a public template directly. To make changes, the template will be set to private, and you'll need to make it public again afterward. Do you want to continue?",
"agree-p1": "I confirm that I have read and understood the ",
@@ -751,46 +765,46 @@
"agrre-button": " Agree & Continue",
"term-cond-title": "Terms and conditions",
"term-cond-h": "ELECTRONIC RECORD AND SIGNATURE DISCLOSURE",
"term-cond-p1": "This Electronic Record and Signature Disclosure ('Disclosure') is an agreement between the Document Creator ('Sender') and the Signer ('You'), facilitated through the OpenSign platform ('Platform'). By signing documents via OpenSign, you agree to the terms outlined in this Disclosure. Please read it carefully before proceeding.",
"term-cond-p1": "This Electronic Record and Signature Disclosure ('Disclosure') is an agreement between the Document Creator ('Sender') and the Signer ('You'), facilitated through the {{appName}} platform ('Platform'). By signing documents via {{appName}}, you agree to the terms outlined in this Disclosure. Please read it carefully before proceeding.",
"term-cond-h1": "1. Purpose and Scope",
"term-cond-p2": "This Disclosure governs the use of electronic records and signatures for documents sent by the Sender and signed by You through the OpenSign platform. By agreeing, You acknowledge that:",
"term-cond-p3": "You will receive and sign documents electronically via OpenSign.",
"term-cond-p2": "This Disclosure governs the use of electronic records and signatures for documents sent by the Sender and signed by You through the {{appName}} platform. By agreeing, You acknowledge that:",
"term-cond-p3": "You will receive and sign documents electronically via {{appName}}.",
"term-cond-p4": "Your electronic signature is legally binding and equivalent to a handwritten signature.",
"term-cond-h2": "2. Consent to Use Electronic Records and Signatures",
"term-cond-p5": "By agreeing to this Disclosure:",
"term-cond-p6": "You consent to transact electronically with the Sender using OpenSign and understand that this consent is valid until withdrawn.",
"term-cond-p7": "You agree to review, sign, and return documents electronically using OpenSign.",
"term-cond-p6": "You consent to transact electronically with the Sender using {{appName}} and understand that this consent is valid until withdrawn.",
"term-cond-p7": "You agree to review, sign, and return documents electronically using {{appName}}.",
"term-cond-p8": "This consent applies only to the specific document(s) provided by the Sender.",
"term-cond-h3": "3. Right to Withdraw Consent",
"term-cond-p9": "You have the right to withdraw your consent to use electronic records and signatures by:",
"term-cond-p10": "Contacting the Sender directly using the contact information provided in their communication.",
"term-cond-p11": "Notifying the Sender before completing the signing process.",
"term-cond-h4": "4. Hardware and Software Requirements",
"term-cond-p12": "To sign documents using OpenSign, You must have:",
"term-cond-p12": "To sign documents using {{appName}}, You must have:",
"term-cond-p13": "A device with internet access.",
"term-cond-p14": "A compatible web browser with JavaScript enabled.",
"term-cond-p15": "An email account to receive notifications and signed documents.",
"term-cond-p16": "If these requirements change, the Sender or OpenSign will notify You of the updates.",
"term-cond-p16": "If these requirements change, the Sender or {{appName}} will notify You of the updates.",
"term-cond-h5": "5. Retention of Records and Request for Paper Copies",
"term-cond-p17": "You are responsible for ensuring You can access and retain the electronically signed document(s). OpenSign provides tools to download or print the signed document immediately after the signing process is complete.",
"term-cond-p17": "You are responsible for ensuring You can access and retain the electronically signed document(s). {{appName}} provides tools to download or print the signed document immediately after the signing process is complete.",
"term-cond-p18": "If You prefer a paper copy of any electronically signed document, You may request it directly from the Sender. The Sender may charge a reasonable fee for providing paper copies unless prohibited by applicable law.",
"term-cond-h6": "6. Acknowledgment and Agreement",
"term-cond-p19": "By proceeding, You:",
"term-cond-p20": "Confirm that You can access and review electronic documents as described in this Disclosure.",
"term-cond-p21": "Agree to use electronic records and signatures for the specified transaction(s) with the Sender.",
"term-cond-p22": "Understand that OpenSign is a platform facilitating the transaction and is not a party to the agreement.",
"term-cond-p22": "Understand that {{appName}} is a platform facilitating the transaction and is not a party to the agreement.",
"term-cond-h7": "7. Legal Effect",
"term-cond-p23": "Your electronic signature facilitated through OpenSign:",
"term-cond-p24": "Complies with applicable electronic signature laws, including but not limited to the E-SIGN Act in the United States, the EU eIDAS Regulation, and Indias Information Technology Act.",
"term-cond-p23": "Your electronic signature facilitated through {{appName}}:",
"term-cond-p24": "Complies with applicable electronic signature laws, including but not limited to the E-SIGN Act in the United States, the EU eIDAS Regulation, and India's Information Technology Act.",
"term-cond-p25": "Is legally binding between You and the Sender for the signed document(s).",
"term-cond-h8": "8. Platform Role and Limitation of Liability",
"term-cond-p26": "OpenSign serves as a platform to facilitate electronic transactions. It is not responsible for the content, validity, or enforceability of the documents sent by the Sender. Any disputes or issues related to the document or its signing must be resolved directly between You and the Sender.",
"term-cond-p26": "{{appName}} serves as a platform to facilitate electronic transactions. It is not responsible for the content, validity, or enforceability of the documents sent by the Sender. Any disputes or issues related to the document or its signing must be resolved directly between You and the Sender.",
"term-cond-h9": "9. Termination of Agreement",
"term-cond-p27": "Your consent applies only to the current transaction unless otherwise specified by the Sender. OpenSign reserves the right to terminate access to its platform for violations of its Terms of Service or policies.",
"term-cond-p28": "By selecting 'I Agree,' You confirm that You understand and agree to the terms of this Electronic Record and Signature Disclosure and consent to electronically sign the document(s) provided by the Sender through OpenSign.",
"term-cond-p29": "If You have questions about this Disclosure, contact the Sender directly. For technical support with OpenSign, visit",
"term-cond-p30": "or email",
"js-snippet-msg": "To integrate OpenSign Templates into your HTML websites or landing pages, you can use below code:",
"term-cond-p27": "Your consent applies only to the current transaction unless otherwise specified by the Sender. {{appName}} reserves the right to terminate access to its platform for violations of its Terms of Service or policies.",
"term-cond-p28": "By selecting 'I Agree,' You confirm that You understand and agree to the terms of this Electronic Record and Signature Disclosure and consent to electronically sign the document(s) provided by the Sender through {{appName}}.",
"term-cond-p29": "If You have questions about this Disclosure, contact the Sender directly. For technical support with {{appName}}, visit ",
"term-cond-p30": " or email",
"js-snippet-msg": "To integrate {{appName}} Templates into your HTML websites or landing pages, you can use below code:",
"js-snippet-msg-1": "You can get the TemplateId from the",
"js-snippet-msg-2": "Manage Templates",
"js-snippet-msg-3": "page.",
@@ -815,13 +829,13 @@
},
"signature-setting": "Signature settings",
"activation-key": "Activation key",
"paid-selfhost-help": "Please contact to opensign for more info at ",
"remaining-credits-selfhost-help": "Use premium credits for API document signing, bulk sending OpenSign integration on your website. You have {{allowedcredits}} included credits and {{addoncredits}} additional purchased credits remaining.",
"paid-selfhost-help": "Please contact to {{appName}} for more info at ",
"remaining-credits-selfhost-help": "Use premium credits for API document signing, bulk sending {{appName}} integration on your website. You have {{allowedcredits}} included credits and {{addoncredits}} additional purchased credits remaining.",
"signature-type": "Your signature",
"initial-type": "Your initials",
"redirect-url": "Redirect url",
"bulk-send": "Bulk send",
"select-timezone": "Select your Timezone",
"select-timezone": "Timezone",
"current-time": "Current time",
"email-help": "You are not allowed to change email address due to security reasons. Please create another free account using the new email address.",
"doc-sent": "Document sent successfully.",
@@ -834,5 +848,44 @@
"variables-use": "Variables to use",
"send-to-sign": "has requested you to sign",
"use-template": "Use Template",
"contact-already-exists": "contact already exists."
"contact-already-exists": "contact already exists.",
"docx-error": "We are currently experiencing some issues with processing DOCX files. Please upload the PDF file",
"docx-error-contact": "or contact us on support@opensignlabs.com",
"agreement-note": "Note: Agreeing to this does not mean you are signing the document immediately. This only allows you to review the document electronically. You will have the opportunity to read it in full and decide whether to sign it afterward.",
"draft-template-info-p1": "To make your template public, it must either contain a single role, or, if it includes multiple roles, all additional roles must already be assigned to signers. The unassigned public role should remain empty and must be placed in the first position.",
"visit-below-link": "Visit below link to know more -",
"storage-help": "Enabling BYOC lets you connect your own S3 storage so your files remain entirely under your control—no external copies retained. If data autonomy matters to you, consider upgrading to Teams to unlock this feature.",
"daily-quota-reached": "You've reached your daily quota. For assistance, please contact quotas@opensignlabs.com.",
"enabled-signature-type": "Enabled Signature Types",
"enabled-signature-type-help": "The 'Enabled Signature Types' setting determines which signature options are available across your organization. For example, if you disable the 'Draw' option, members of your organization will not see it in the signature widget, while the other three options will remain accessible.",
"indexing-public-profile": "Allow indexing of public profile by search engines",
"user-created-successfully": "user created successfully.",
"only-15-reminder-allowed": "You can set up to 15 automatic reminders. For example, if 'TimeToComplete' is 15 days and 'RemindOnceInEvery' is 1 day, you'll reach the maximum limit of 15 reminders. Adjust your settings accordingly.",
"rate-your-experience": "How was your experience with {{appName}}?",
"thanks-for-feedback": "Thanks for your feedback 🙏",
"share-your-feedback": "Share your feedback",
"share-your-review": "Share your review",
"date-format": "Date format",
"document-deleted": "The document has been deleted or you don't have access. Please contact the sender.",
"save-as-template-?": "Are you sure you want to save this document as template?",
"go-to-manage-templates": "go to 'Manage templates'",
"template-created": "Template Created",
"how-would-you-like-to-proceed?": "How would you like to proceed?",
"failed-to-load-refresh-page": "Failed to load the document. Please try refreshing this page.",
"document-has-been-signed": "The document has been signed successfully!",
"document-has-been-signed-by-you": "The document has been successfully signed by you!",
"participant-completed-signing": "All participants have completed the signing process.",
"you-will-receive-email-shortly": "✅ That's it! You'll receive a confirmation email shortly.",
"please-provide-templateid": "Please provide templateid",
"this-template-is-not-public": "This template is not public",
"invalid-templateid": "Invaldi templateid",
"contact-billing-at-opensign": "To add more seats, please contact OpenSign™ at <1>billing@opensignlabs.com</1> for assistance",
"title-length-alert": "Title must be at most 250 characters long.",
"note-length-alert": "Note must be at most 200 characters long.",
"description-length-alert": "Description must be at most 500 characters long.",
"fix-&-resend-document": "Fix & Resend Document",
"do-you-want-recreate-document?": "This will create a draft from this document with all fields intact. Are you sure you want to recreate this document?",
"start-editing": "Start Editing",
"unsaved-changes-discard-them?": "You have unsaved changes. Discard them?",
"yes-discard": "Yes, Discard"
}
@@ -51,7 +51,6 @@
"docs": "Documentación",
"session-expired": "Tu sesión ha expirado.",
"public-profile-help": "Esta es tu URL pública. Cópiala o compártela con el firmante, y podrás ver todas tus plantillas públicas.",
"tagline-help": "Este eslogan aparecerá en tu perfil público de OpenSign, por ejem. https://opensign.me/alex",
"welcome": "¡Bienvenido de nuevo!",
"Login-to-your-account": "Ingresa a tu cuenta",
"password": "Contraseña",
@@ -74,12 +73,23 @@
"Sign yourself": "Firma tu mismo",
"Request signatures": "Solicitar firmas",
"Templates": "Plantillas",
"Analytics": "Analítica",
"Branding": "Marca",
"Mail": "Correo",
"Storage": "Almacenamiento",
"Signing certificate": "Certificado de firma",
"Teams": "Equipos",
"General": "General",
"Teams-Children": {
"Organizations": "Organizaciones",
"OrgAdmins": "OrgAdmins"
},
"Templates-Children": {
"Create template": "Crear plantilla",
"Manage templates": "Administrar plantillas",
"Embedding": "Incrustar"
},
"OpenSign™ Drive": "OpenSign™ Drive",
"OpenSign™ Drive": "{{appName}} Drive",
"Reports": "Reportes",
"Documents": "Documentos",
"Documents-Children": {
@@ -140,7 +150,10 @@
"Copy Public URL": "Copiar URL pública",
"extend-expiry-date": "Date d'expiration",
"Duplicate Template": "Plantilla duplicada",
"Duplicate": "Duplicada"
"Duplicate": "Duplicada",
"daily-mail-quota": "Cuota diaria de correos electrónicos",
"Save as template": "Guardar como plantilla",
"Fix & resend": "Corregir y reenviar"
},
"report-heading": {
"Sr.No": "Nº",
@@ -198,7 +211,7 @@
"remind-once": " Recordar una vez cada (Días)",
"next": "Siguiente",
"select-folder": "Seleccionar carpeta",
"OpenSign-drive": "OpenSign™ Drive",
"OpenSign-drive": "{{appName}} Drive",
"drive-document-status": {
"Completed": "Completado",
"Declined": "Rechazado",
@@ -206,7 +219,7 @@
"Expired": "Expirado",
"In Progress": "En progreso"
},
"select-folder-help": "Si no seleccionas una carpeta, tu documento firmado se guardará en la carpeta principal de OpenSign Drive.",
"select-folder-help": "Si no seleccionas una carpeta, tu documento firmado se guardará en la carpeta principal de {{appName}} Drive.",
"no-data": "No se encontraron datos",
"save-here": "Guardar aquí",
"back": "Atrás",
@@ -237,7 +250,7 @@
"generate-token-alert": "¿En definitiva quieres regenerar el token? Esto expirará el token antiguo.",
"yes": "Sí",
"copied": "Copiado",
"something-went-wrong-mssg": "Algo salió mal, por favor, intenta de nuevo más tarde.",
"something-went-wrong-mssg": "Un problème est survenu, Actualiser cette page peut résoudre le problème.",
"token-generated": "Token generado exitosamente.",
"webhook": "Webhook",
"update-webhook": "Actualizar webhook",
@@ -273,13 +286,14 @@
"make-template-public": "Convertir la plantilla en pública",
"make-template-private": "Convertir la plantilla en privada",
"make-template-public-alert": "¿En definitiva quieres convertir esta plantilla en pública?",
"make-template-private-alert-non": "¿Está seguro de que desea hacer este plantilla privado?",
"make-template-private-alert": "¿En definitiva quieres convertir esta plantilla en privada? Esto lo removerá de tu perfil público.",
"public-role": "Rol público",
"public-url": "Perfil publico",
"embed-template": "Insertar plantilla",
"public-url-copy": "Aquí está tu URL pública: ",
"public-url-copy-mssg": "Cópiala o compártela con el firmante y podrás ver todas tus plantillas públicas.",
"add-public-url-alert": "Puedes generar tu perfil público OpenSign",
"add-public-url-alert": "Puedes generar tu perfil público {{appName}}",
"share-with-alert": "No puedes compartir una plantilla si alguno de los roles ya tiene contactos asignados. Elimina todas las asignaciones de contactos de los roles antes de compartir la plantilla.",
"share-with": "Compartir con",
"team-not-found": "Equipo no encontrado",
@@ -298,7 +312,7 @@
"revoke-document": "Revocar documento",
"revoke-document-alert": "¿En definitiva quieres revocar este documento?",
"resend-mail": "Reenviar correo",
"resend-mail-help": "Puedes usar las siguientes variables que serán reemplazadas por sus valores reales:- {{document_title}}, {{sender_name}}, {{sender_mail}}, {{sender_phone}}, {{receiver_name}}, {{receiver_email}}, {{receiver_phone}}, {{expiry_date}}, {{company_name}}, {{signing_url}}.",
"resend-mail-help": "Puedes usar las siguientes variables que serán reemplazadas por sus valores reales:- {{document_title}}, {{sender_name}}, {{sender_mail}}, {{sender_phone}}, {{receiver_name}}, {{receiver_email}}, {{receiver_phone}}, {{expiry_date}}, {{company_name}}, {{signing_url}}, {{note}}.",
"subject": "Asunto",
"body": "Cuerpo",
"add-contact": "Agregar contacto",
@@ -447,6 +461,7 @@
"placeholder-alert-3": " ¿En definitiva quieres enviar este documento para ser firmado?",
"placeholder-alert-4": "¡Has enviado exitosamente correos a todos los destinatarios!",
"placeholder-mail-alert": "Has enviado un correo electrónico con éxito a {{name}}. Los siguientes firmantes recibirán un correo electrónico una vez que {{name}} firme el documento.",
"placeholder-mail-alert-you": "Los firmantes posteriores recibirán correos electrónicos una vez que firme el documento.",
"placeholder-alert-5": "¿Quieres firmar documentos ahora mismo?",
"placeholder-alert-6": "¡Por favor, configura el adaptador de correo para enviar correos!",
"placeholder-alert-7": "¡Por favor, selecciona un firmante para agregar un marcador de posición!",
@@ -493,7 +508,7 @@
"role-not-found": "Rol no encontrado.",
"do-not-access": "No tienes acceso, por favor, contacta al administrador.",
"add-admin": "Agregar administrador",
"opensign-setup": "Instalación de OpenSign",
"opensign-setup": "Instalación de {{appName}}",
"join-discord": "Únete a nuestro servidor de Discord",
"admin-already-exist": "El administrador ya existe.",
"password-update-alert-1": "Contraseña actualizada exitosamente.",
@@ -510,7 +525,7 @@
"correct-password": "Por favor, proporciona la contraseña correcta",
"decrypting-pdf": " Desencriptando PDF, por favor, espera...",
"invalid-otp": "OTP inválido",
"user-not-found": "¡Usuario no encontrado!",
"user-not-found": "Usuario no encontrado",
"enter-otp-alert": "¡Por favor, ingresa el OTP!",
"get-verification-code": "Obtener código de verificación",
"get-verification-code-2": "Obtendrás un código de verificación por correo",
@@ -550,7 +565,6 @@
"filed-required-correctly": "Por favor, rellena correctamente los detalles requeridos.",
"admin-created": "Administrador creado",
"invalid-masterkey": "Llave maestra inválida",
"opensign-Setup": "Instalación de OpenSign",
"master-key": " Llave maestra",
"profile-update-alert": "Perfil actualizado exitosamente.",
"date": "Fecha",
@@ -575,7 +589,7 @@
"tour-mssg": {
"home-layout-1": "¡Has iniciado sesión exitosamente! Echemos un vistazo.",
"home-layout-2": "Para subir documentos para firmar tú mismo o para solicitar la firma de otros, simplemente selecciona los botones correspondientes.",
"home-layout-3": "¡Ya puedes empezar a utilizar OpenSign! Si necesitas soporte siéntete libre de contactarnos.",
"home-layout-3": "¡Ya puedes empezar a utilizar {{appName}}! Si necesitas soporte siéntete libre de contactarnos.",
"generate-token": "Mejora ahora para generar un token API de producción.",
"opensign-drive-1": "Haz clic en los enlaces para navegar fácilmente por la estructura jerárquica de carpetas y ver los documentos de cada carpeta.",
"opensign-drive-2": "Haz clic en el botón «Agregar» para crear una nueva carpeta o documento.",
@@ -690,16 +704,16 @@
"download-pdf": "Descargar PDF",
"pdf-certificate": "Descargar PDF + Certificado",
"document-logs": "Registros de documentos",
"server-down": "No se pudo conectar al servidor de OpenSign. Si estás alojando OpenSign por tu cuenta, por favor, asegúrate de que todos los pasos de la documentación han sido seguidos correctamente. Si estás ejecutando OpenSign localmente, puedes estar accediendo a OpenSign por un número de puerto incorrecto.",
"server-down": "No se pudo conectar al servidor de {{appName}}. Si estás alojando {{appName}} por tu cuenta, por favor, asegúrate de que todos los pasos de la documentación han sido seguidos correctamente. Si estás ejecutando {{appName}} localmente, puedes estar accediendo a {{appName}} por un número de puerto incorrecto.",
"admin-exists": "El administrador ya existe. Por favor, inicia sesión en la aplicación usando las credenciales de administrador para poder administrar los usuarios.",
"public-tour-message": "Por favor, convierte la plantilla en pública para copiar la URL pública",
"add-user-template": "Es necesario agregar un rol antes de poder agregarle campos.",
"pdf-uncompatible": "Este pdf no es compatible con opensign, comuníquese con <support@opensignlabs.com>",
"pdf-uncompatible": "Este PDF no es compatible, por favor contacta con {{appName}}",
"enable-tour": "Habilitar recorrido",
"istourenabled-help": {
"p1": "Esto permitirá realizar una visita guiada para los firmantes, brindando instrucciones durante el proceso de firma.",
"p2": "Esto desactivará la visita guiada, lo que garantizará una experiencia de firma más rápida e ininterrumpida.",
"p3": "Consejo: si sus firmantes están familiarizados con OpenSign y lo utilizan con frecuencia, es posible que desee desactivar la visita guiada para disfrutar de una experiencia más fluida. Elija la opción que mejor se adapte a sus necesidades de firma de documentos"
"p3": "Consejo: si sus firmantes están familiarizados con {{appName}} y lo utilizan con frecuencia, es posible que desee desactivar la visita guiada para disfrutar de una experiencia más fluida. Elija la opción que mejor se adapte a sus necesidades de firma de documentos"
},
"form-title-1": "Configuración del flujo de documentos",
"form-title-2": "Configuración de seguridad",
@@ -742,7 +756,7 @@
"delete-folder-alert": "¿Está seguro de que desea eliminar este Carpeta?",
"delete-folder-alert-1": "No se puede eliminar: la carpeta debe estar vacía",
"defaultSign-alert": "Los widgets {{widgetsType}} no están disponibles para la firma automática",
"embed-heading": "Incrustar plantillas de OpenSign",
"embed-heading": "Incrustar plantillas de {{appName}}",
"public-template-edit-title": "Editar plantilla pública",
"public-template-edit-mssg": "No puede editar una plantilla pública directamente. Para realizar cambios, la plantilla se configurará como privada y luego tendrás que volver a hacerla pública. ¿Quieres continuar?",
"agree-p1": "Confirmo que he leído y comprendido el ",
@@ -751,49 +765,49 @@
"agrre-button": "Aceptar y continuar",
"term-cond-title": "Términos y condiciones",
"term-cond-h": "DIVULGACIÓN DE REGISTRO ELECTRÓNICO Y FIRMA",
"term-cond-p1": "Esta Divulgación de Firma y Registro Electrónico ('Divulgación') es un acuerdo entre el Creador del Documento ('Remitente') y el Firmante ('Usted'), facilitado a través de la plataforma OpenSign ('Plataforma'). Al firmar documentos a través de OpenSign, usted acepta los términos descritos en esta Divulgación. Léala detenidamente antes de continuar.",
"term-cond-p1": "Esta Divulgación de Firma y Registro Electrónico ('Divulgación') es un acuerdo entre el Creador del Documento ('Remitente') y el Firmante ('Usted'), facilitado a través de la plataforma {{appName}} ('Plataforma'). Al firmar documentos a través de {{appName}}, usted acepta los términos descritos en esta Divulgación. Léala detenidamente antes de continuar.",
"term-cond-h1": "1. Objet et portée",
"term-cond-p2": "Esta Divulgación rige el uso de registros y firmas electrónicas para los documentos enviados por el Remitente y firmados por Usted a través de la plataforma OpenSign. Al aceptar, Usted reconoce que:",
"term-cond-p3": "Recibirá y firmará documentos electrónicamente a través de OpenSign.",
"term-cond-p2": "Esta Divulgación rige el uso de registros y firmas electrónicas para los documentos enviados por el Remitente y firmados por Usted a través de la plataforma {{appName}}. Al aceptar, Usted reconoce que:",
"term-cond-p3": "Recibirá y firmará documentos electrónicamente a través de {{appName}}.",
"term-cond-p4": "Su firma electrónica es jurídicamente vinculante y equivalente a una firma manuscrita.",
"term-cond-h2": "2. Consentimiento para el uso de registros y firmas electrónicas",
"js-snippet-msg-2": "Administrar plantillas",
"js-snippet-msg-3": "página.",
"term-cond-p5": "Al aceptar esta Divulgación:",
"term-cond-p6": "Usted acepta realizar transacciones electrónicas con el Remitente utilizando OpenSign y comprende que este consentimiento es válido hasta que se retire.",
"term-cond-p7": "Usted acepta revisar, firmar y devolver documentos electrónicamente utilizando OpenSign.",
"term-cond-p6": "Usted acepta realizar transacciones electrónicas con el Remitente utilizando {{appName}} y comprende que este consentimiento es válido hasta que se retire.",
"term-cond-p7": "Usted acepta revisar, firmar y devolver documentos electrónicamente utilizando {{appName}}.",
"term-cond-p8": "Este consentimiento se aplica únicamente a los documentos específicos proporcionados por el Remitente.",
"term-cond-h3": "3. Derecho a retirar el consentimiento",
"term-cond-p9": "Tiene derecho a retirar su consentimiento para el uso de registros y firmas electrónicas mediante:",
"term-cond-p10": "Contactar directamente con el Remitente utilizando la información de contacto proporcionada en su comunicación.",
"term-cond-p11": "Notificar al remitente antes de completar el proceso de firma.",
"term-cond-h4": "4. Requisitos de hardware y software",
"term-cond-p12": "Para firmar documentos usando OpenSign, debe tener:",
"term-cond-p12": "Para firmar documentos usando {{appName}}, debe tener:",
"term-cond-p13": "Un dispositivo con acceso a Internet.",
"term-cond-p14": "Un navegador web compatible con JavaScript habilitado.",
"term-cond-p15": "Una cuenta de correo electrónico para recibir notificaciones y documentos firmados.",
"term-cond-p16": "Si estos requisitos cambian, el Remitente u OpenSign le notificarán las actualizaciones.",
"term-cond-p16": "Si estos requisitos cambian, el Remitente u {{appName}} le notificarán las actualizaciones.",
"term-cond-h5": "5. Conservación de registros y solicitud de copias en papel",
"term-cond-p17": "Usted es responsable de garantizar que pueda acceder y conservar los documentos firmados electrónicamente. OpenSign proporciona herramientas para descargar o imprimir el documento firmado inmediatamente después de que se complete el proceso de firma.",
"term-cond-p17": "Usted es responsable de garantizar que pueda acceder y conservar los documentos firmados electrónicamente. {{appName}} proporciona herramientas para descargar o imprimir el documento firmado inmediatamente después de que se complete el proceso de firma.",
"term-cond-p18": "Si prefiere una copia en papel de cualquier documento firmado electrónicamente, puede solicitarla directamente al Remitente. El Remitente puede cobrar una tarifa razonable por proporcionar copias en papel, a menos que lo prohíba la ley aplicable.",
"term-cond-h6": "6. Reconocimiento y Acuerdo",
"term-cond-p19": "Al continuar, usted:",
"term-cond-p20": "Confirme que puede acceder y revisar documentos electrónicos como se describe en esta Divulgación.",
"term-cond-p21": "Acepta utilizar registros y firmas electrónicas para las transacciones especificadas con el Remitente.",
"term-cond-p22": "Comprenda que OpenSign es una plataforma que facilita la transacción y no es parte del acuerdo.",
"term-cond-p22": "Comprenda que {{appName}} es una plataforma que facilita la transacción y no es parte del acuerdo.",
"term-cond-h7": "7. Efecto jurídico",
"term-cond-p23": "Tu firma electrónica facilitada a través de OpenSign:",
"term-cond-p23": "Tu firma electrónica facilitada a través de {{appName}}:",
"term-cond-p24": "Cumple con las leyes de firma electrónica aplicables, incluidas, entre otras, la Ley E-SIGN de los Estados Unidos, el Reglamento eIDAS de la UE y la Ley de tecnología de la información de la India",
"term-cond-p25": "Es legalmente vinculante entre Usted y el Remitente para los documentos firmados",
"term-cond-h8": "8. Platform Role and Limitation of Liability",
"term-cond-p26": "OpenSign sirve como plataforma para facilitar transacciones electrónicas. No es responsable del contenido, validez o aplicabilidad de los documentos enviados por el Remitente. Cualquier disputa o problema relacionado con el documento o su firma debe resolverse directamente entre Usted y el Remitente.",
"term-cond-p26": "{{appName}} sirve como plataforma para facilitar transacciones electrónicas. No es responsable del contenido, validez o aplicabilidad de los documentos enviados por el Remitente. Cualquier disputa o problema relacionado con el documento o su firma debe resolverse directamente entre Usted y el Remitente.",
"term-cond-h9": "9. Terminación del Acuerdo",
"term-cond-p27": "Su consentimiento se aplica únicamente a la transacción actual a menos que el Remitente especifique lo contrario. OpenSign se reserva el derecho de cancelar el acceso a su plataforma por violaciones de sus Términos de Servicio o políticas.",
"term-cond-p28": "Al seleccionar 'Acepto', usted confirma que comprende y acepta los términos de este Registro electrónico y Divulgación de firma y acepta firmar electrónicamente los documentos proporcionados por el Remitente a través de OpenSign.",
"term-cond-p29": "Si tiene preguntas sobre esta Divulgación, comuníquese directamente con el Remitente. Para obtener soporte técnico con OpenSign, visite",
"term-cond-p30": "o correo electrónico",
"js-snippet-msg": "Para integrar plantillas OpenSign en sus sitios web HTML o páginas de destino, puede utilizar el siguiente código:",
"term-cond-p27": "Su consentimiento se aplica únicamente a la transacción actual a menos que el Remitente especifique lo contrario. {{appName}} se reserva el derecho de cancelar el acceso a su plataforma por violaciones de sus Términos de Servicio o políticas.",
"term-cond-p28": "Al seleccionar 'Acepto', usted confirma que comprende y acepta los términos de este Registro electrónico y Divulgación de firma y acepta firmar electrónicamente los documentos proporcionados por el Remitente a través de {{appName}}.",
"term-cond-p29": "Si tiene preguntas sobre esta Divulgación, comuníquese directamente con el Remitente. Para obtener soporte técnico con {{appName}}, visite ",
"term-cond-p30": " o correo electrónico",
"js-snippet-msg": "Para integrar plantillas {{appName}} en sus sitios web HTML o páginas de destino, puede utilizar el siguiente código:",
"js-snippet-msg-1": "Puede obtener el TemplateId en la página Administrar plantillas",
"js-snippet-msg-2": "Administrar plantillas",
"js-snippet-msg-3": "página.",
"agrrement-alert": "Para continuar, debe consentir la divulgación de registros y firmas electrónicas.",
"webhook-already-exists": "¡La URL ya existe! Pruebe con uno diferente.",
"webhook-must-be-secure": "La URL del webhook debe ser segura y utilizar https://",
@@ -815,13 +829,13 @@
"template-form-description": "Utilice este formulario para crear plantillas reutilizables para sus documentos.",
"change-signer": "Cambiar firmante",
"activation-key": "Clave de activación",
"paid-selfhost-help": "Por favor, póngase en contacto con OpenSign para más información en ",
"remaining-credits-selfhost-help": "Use créditos premium para firmar documentos mediante la API, enviar en masa e integrar OpenSign en su sitio web. Tiene {{allowedcredits}} créditos incluidos y {{addoncredits}} créditos adicionales comprados restantes.",
"paid-selfhost-help": "Por favor, póngase en contacto con {{appName}} para más información en ",
"remaining-credits-selfhost-help": "Use créditos premium para firmar documentos mediante la API, enviar en masa e integrar {{appName}} en su sitio web. Tiene {{allowedcredits}} créditos incluidos y {{addoncredits}} créditos adicionales comprados restantes.",
"signature-type": "Tu firma",
"initial-type": "Tus iniciales",
"redirect-url": "URL de redireccionamiento",
"bulk-send": "envío masivo",
"select-timezone": "Seleccione su zona horaria",
"select-timezone": "Zona horaria",
"current-time": "Hora actual",
"email-help": "No se permite cambiar la dirección de correo electrónico por razones de seguridad. Por favor, cree otra cuenta gratuita utilizando la nueva dirección de correo electrónico.",
"doc-sent": "Documento enviado con éxito.",
@@ -834,5 +848,44 @@
"variables-use": "Variables para usar",
"send-to-sign": "le ha solicitado que firme",
"use-template": "Usar plantilla",
"contact-already-exists": "El contacto ya existe."
"contact-already-exists": "El contacto ya existe.",
"docx-error": "Actualmente estamos experimentando problemas con el procesamiento de archivos DOCX. Por favor, sube el archivo PDF",
"docx-error-contact": "o contáctanos en support@opensignlabs.com",
"agreement-note": "Nota: Aceptar esto no significa que esté firmando el documento de inmediato. Esto solo le permite revisar el documento electrónicamente. Tendrá la oportunidad de leerlo en su totalidad y decidir si desea firmarlo después.",
"draft-template-info-p1": "Para hacer que tu plantilla sea pública, debe contener un único rol o, si incluye múltiples roles, todos los roles adicionales deben estar ya asignados a firmantes. El rol público no asignado debe permanecer vacío y debe estar en la primera posición.",
"visit-below-link": "Visita el siguiente enlace para saber más -",
"storage-help": "Habilitar BYOC te permite conectar tu propio almacenamiento S3 para que tus archivos permanezcan completamente bajo tu control, sin copias externas retenidas. Si la autonomía de los datos es importante para ti, considera actualizar a Teams para desbloquear esta función.",
"daily-quota-reached": "Ha alcanzado su cuota diaria. Para obtener ayuda, comuníquese con quotas@opensignlabs.com.",
"enabled-signature-type": "Tipos de firma habilitados",
"enabled-signature-type-help": "La configuración de 'Tipos de firma habilitados' determina qué opciones de firma están disponibles en su organización. Por ejemplo, si desactiva la opción 'Dibujar', los miembros de su organización no la verán en el widget de firma, mientras que las otras tres opciones seguirán siendo accesibles.",
"indexing-public-profile": "Permitir la indexación del perfil público por los motores de búsqueda",
"user-created-successfully": "Usuario creado con éxito.",
"only-15-reminder-allowed": "Puede configurar hasta 15 recordatorios automáticos. Por ejemplo, si 'TimeToComplete' es de 15 días y 'RemindOnceInEvery' es de 1 día, alcanzará el límite máximo de 15 recordatorios. Ajuste su configuración en consecuencia.",
"rate-your-experience": "¿Cómo fue su experiencia con {{appName}}?",
"thanks-for-feedback": "Gracias por su comentario 🙏",
"share-your-feedback": "Comparta sus comentarios",
"share-your-review": "Comparta su reseña",
"date-format": "Formato de fecha",
"document-deleted": "El documento ha sido eliminado o no tiene acceso. Por favor, contacte al remitente.",
"save-as-template-?": "¿Está seguro de que desea guardar este documento como plantilla?",
"go-to-manage-templates": "Ir a 'Gestionar plantillas'",
"template-created": "Plantilla creada",
"how-would-you-like-to-proceed?": "¿Cómo le gustaría proceder?",
"failed-to-load-refresh-page": "Error al cargar el documento. Intente actualizar esta página.",
"document-has-been-signed": "¡El documento ha sido firmado con éxito!",
"document-has-been-signed-by-you": "¡El documento ha sido firmado con éxito por usted!",
"participant-completed-signing": "Todos los participantes han completado el proceso de firma.",
"you-will-receive-email-shortly": "✅ ¡Eso es todo! Recibirá un correo de confirmación en breve.",
"please-provide-templateid": "Por favor, proporcione templateid",
"this-template-is-not-public": "Esta template no es pública",
"invalid-templateid": "templateid no válida",
"contact-billing-at-opensign": "Para agregar más asientos, comuníquese con OpenSign™ a <1>billing@opensignlabs.com</1> para obtener ayuda.",
"title-length-alert": "El título debe tener como máximo 250 caracteres.",
"note-length-alert": "La nota debe tener como máximo 200 caracteres.",
"description-length-alert": "La descripción debe tener como máximo 500 caracteres.",
"fix-&-resend-document": "Corregir y reenviar el documento",
"do-you-want-recreate-document?": "Esto creará un borrador a partir de este documento con todos los campos intactos. ¿Está seguro de que desea recrear este documento?",
"start-editing": "Comenzar a editar",
"unsaved-changes-discard-them?": "Tienes cambios sin guardar. ¿Deseas descartarlos?",
"yes-discard": "Sí, descartar"
}
@@ -51,7 +51,6 @@
"session-expired": "Votre session a expiré.",
"user": "Utilisateur",
"public-profile-help": "Il s'agit de votre URL publique. Copiez-le ou partagez-le avec le signataire et vous pourrez voir tous vos modèles définis publiquement.",
"tagline-help": "Ce surnom apparaîtra sur votre profil public OpenSign par exemple. https://opensign.me/alex",
"welcome": "Content de te revoir!",
"Login-to-your-account": "Se connecter à son compte",
"password": "Mot de passe",
@@ -74,12 +73,23 @@
"Sign yourself": "signez vous-même",
"Request signatures": "Demander des signatures",
"Templates": "Modèles",
"Analytics": "Analytique",
"Branding": "Image de marque",
"Mail": "Courrier",
"Storage": "Stockage",
"Signing certificate": "Certificat de signature",
"Teams": "Équipes",
"General": "Général",
"Teams-Children": {
"Organizations": "Organisations",
"OrgAdmins": "OrgAdmins"
},
"Templates-Children": {
"Create template": "Créer un modèle",
"Manage templates": "Gérer les modèles",
"Embedding": "Intégration"
},
"OpenSign™ Drive": "Lecteur OpenSign™",
"OpenSign™ Drive": "Lecteur {{appName}}",
"Reports": "Rapports",
"Documents": "Documents",
"Documents-Children": {
@@ -161,7 +171,10 @@
"Copy Public URL": "Copier l'URL publique",
"extend-expiry-date": "Prolonger la date d'expiration",
"Duplicate Template": "dupliquer le modèle",
"Duplicate": "Double"
"Duplicate": "Double",
"daily-mail-quota": "Quota d'e-mails quotidien",
"Save as template": "Enregistrer comme modèle",
"Fix & resend": "Corriger et renvoyer"
},
"report-help": {
"Draft Documents": "Il s'agit de documents que vous avez commencés mais que vous n'avez pas finalisés pour envoi.",
@@ -189,7 +202,7 @@
"send-in-order-help": {
"p1": "Choisissez la manière dont vous souhaitez que les demandes de signature soient envoyées aux signataires du document :",
"p2": "La sélection de cette option enverra initialement la demande de signature au premier signataire. Une fois que le premier signataire a terminé sa partie, le prochain signataire de la séquence recevra la demande. Ce processus se poursuit jusqu'à ce que tous les signataires aient signé le document. Cette méthode garantit que le document est signé dans un ordre spécifique.",
"p3": "La sélection de cette option enverra les liens de signature à tous les signataires simultanément. Chaque signataire peut signer le document à sa convenance, que d'autres signataires aient ou non complété leur signature. Cette méthode est plus rapide mais nimpose aucun ordre de signature entre les participants.",
"p3": "La sélection de cette option enverra les liens de signature à tous les signataires simultanément. Chaque signataire peut signer le document à sa convenance, que d'autres signataires aient ou non complété leur signature. Cette méthode est plus rapide mais n'impose aucun ordre de signature entre les participants.",
"p4": "Sélectionnez l'option qui correspond le mieux aux besoins de votre traitement de documents."
},
"no": "Non",
@@ -197,7 +210,7 @@
"remind-once": "Rappeler une fois tous les (jours)",
"next": "Suivant",
"select-folder": "Sélectionner le dossier",
"OpenSign-drive": "Lecteur OpenSign™",
"OpenSign-drive": "Lecteur {{appName}}",
"drive-document-status": {
"Completed": "Complété",
"Declined": "Refuser",
@@ -205,7 +218,7 @@
"Expired": "Expiré",
"In Progress": "En cours"
},
"select-folder-help": "Si vous ne sélectionnez pas de dossier, votre document signé sera enregistré dans le dossier du lecteur principal OpenSign.",
"select-folder-help": "Si vous ne sélectionnez pas de dossier, votre document signé sera enregistré dans le dossier du lecteur principal {{appName}}.",
"no-data": "Aucune données disponible",
"save-here": "Enregistrer ici",
"back": "Retour",
@@ -215,7 +228,7 @@
"create": "Créer",
"signers": "Signataires",
"signers-help": "Commencez à saisir le nom d'un contact pour voir les signataires suggérés par vos contacts enregistrés ou en ajouter de nouveaux. Organisez l'ordre de signature en ajoutant des signataires dans l'ordre souhaité. Utilisez le bouton « + » pour inclure les signataires et le « x » pour les supprimer. Chaque signataire recevra un e-mail invité à signer le document dans l'ordre indiqué.",
"bcc-help": "Commencez à taper le nom dun contact pour voir les suggestions parmi vos contacts enregistrés ou en ajouter de nouveaux. Utilisez le bouton '+' pour ajouter un utilisateur et le bouton 'x' pour le supprimer. Ladresse e-mail de lutilisateur sélectionné sera ajoutée en Bcc (copie carbone invisible). Chaque utilisateur recevra une notification par e-mail une fois le document terminé.",
"bcc-help": "Commencez à taper le nom d'un contact pour voir les suggestions parmi vos contacts enregistrés ou en ajouter de nouveaux. Utilisez le bouton '+' pour ajouter un utilisateur et le bouton 'x' pour le supprimer. L'adresse e-mail de l'utilisateur sélectionné sera ajoutée en Bcc (copie carbone invisible). Chaque utilisateur recevra une notification par e-mail une fois le document terminé.",
"add-signer": "Ajouter un signataire",
"contact-not-found": "Contact introuvable",
"add-yourself": "Ajoutez-vous",
@@ -272,13 +285,14 @@
"make-template-public": "Rendre le modèle public",
"make-template-private": "Rendre le modèle privé",
"make-template-public-alert": "Êtes-vous sûr de vouloir rendre ce modèle public ?",
"make-template-private-alert-non": "Êtes-vous sûr de vouloir rendre ce modèle privé ?",
"make-template-private-alert": "Êtes-vous sûr de vouloir rendre ce modèle privé ? Cela le supprimera de votre profil public.",
"public-role": "Rôle public",
"public-url": "Profil public",
"embed-template": "Intégrer le modèle",
"public-url-copy": "Voici votre URL publique : ",
"public-url-copy-mssg": "copiez-la ou partagez-le avec le signataire et vous pouvoir voir tous vos paramètres publics modèles.",
"add-public-url-alert": "Vous pouvez générer votre profil public OpenSign",
"add-public-url-alert": "Vous pouvez générer votre profil public {{appName}}",
"share-with-alert": "Vous ne pouvez pas partager un modèle si des contacts sont déjà attribués à des rôles. Veuillez supprimer toutes les attributions de contacts des rôles avant de partager le modèle.",
"share-with": "Partager avec",
"team-not-found": "Équipe introuvable",
@@ -297,7 +311,7 @@
"revoke-document": "Révoquer le document",
"revoke-document-alert": "Êtes-vous sûr de vouloir révoquer ce document ?",
"resend-mail": "Renvoyer le courrier",
"resend-mail-help": "Vous pouvez utiliser les variables suivantes qui seront remplacées par leurs valeurs réelles : - {{document_title}}, {{sender_name}}, {{sender_mail}}, {{sender_phone}}, {{receiver_name}}, {{receiver_email} }, {{receiver_phone}}, {{expiry_date}}, {{company_name}}, {{signing_url}}.",
"resend-mail-help": "Vous pouvez utiliser les variables suivantes qui seront remplacées par leurs valeurs réelles : - {{document_title}}, {{sender_name}}, {{sender_mail}}, {{sender_phone}}, {{receiver_name}}, {{receiver_email} }, {{receiver_phone}}, {{expiry_date}}, {{company_name}}, {{signing_url}}, {{note}}.",
"subject": "Sujet",
"body": "Corps",
"add-contact": "Ajouter le contact",
@@ -441,11 +455,13 @@
"add-recipients": "Ajouter des destinataires",
"loading-mssg": "Cela pourrait prendre du temps",
"send-mail": "Envoyer un mail",
"signature-field-widget": "Au moins un champ de signature doit être ajouté pour chaque utilisateur. Vous n'avez pas ajouté de champs de signature pour {{signersName}}",
"placeholder-alert-1": "Veuillez vous assurer qu'au moins un widget de signature est ajouté pour tous les destinataires.",
"placeholder-alert-2": "Veuillez confirmer que vous avez rempli le champ de texte.",
"placeholder-alert-3": "Etes-vous sûr de vouloir envoyer ce document pour signature ? ",
"placeholder-alert-4": "Vous avez envoyé avec succès des mails à tous les",
"placeholder-mail-alert": "Vous avez envoyé un e-mail avec succès à {{name}}. Les signataires suivants recevront un e-mail une fois que {{name}} aura signé le document.",
"placeholder-mail-alert-you": "Les signataires suivants recevront un e-mail dès que vous signez le document.",
"placeholder-alert-5": "Voulez-vous signer des documents maintenant ?",
"placeholder-alert-6": "Veuillez configurer l'adaptateur de messagerie pour envoyer du courrier !",
"placeholder-alert-7": "Veuillez sélectionner le signataire pour ajouter un espace réservé !",
@@ -492,7 +508,7 @@
"role-not-found": "Rôle introuvable.",
"do-not-access": "Vous n'y avez pas accès, veuillez contacter l'administrateur.",
"add-admin": "Ajouter un administrateur",
"opensign-setup": "Configuration d'OpenSign",
"opensign-setup": "Configuration {{appName}}",
"join-discord": "Rejoignez notre serveur Discord",
"admin-already-exist": "L'administrateur existe déjà.",
"password-update-alert-1": "Mot de passe mis à jour avec succès.",
@@ -509,7 +525,7 @@
"correct-password": "Veuillez fournir un mot de passe correct",
"decrypting-pdf": "Décryptage du pdf, veuillez patienter...",
"invalid-otp": "OTP invalide",
"user-not-found": "Utilisateur non trouvé!",
"user-not-found": "Utilisateur non trouvé",
"enter-otp-alert": "Veuillez saisir OTP !",
"get-verification-code": "Obtenir le code de vérification",
"get-verification-code-2": "Vous recevrez un code de vérification par e-mail",
@@ -549,7 +565,6 @@
"filed-required-correctly": "Veuillez remplir correctement les informations requises.",
"admin-created": "Administrateur créé",
"invalid-masterkey": "Clé principale invalide",
"opensign-Setup": "Configuration d'OpenSign",
"master-key": "La clef maitresse",
"profile-update-alert": "Mise à jour du profil réussie.",
"date": "Date",
@@ -574,7 +589,7 @@
"tour-mssg": {
"home-layout-1": "Vous vous êtes connecté avec succès ! Jetons un coup d'oeil.",
"home-layout-2": "Pour télécharger des documents à auto-signature ou pour demander la signature d'autres personnes, sélectionnez simplement les boutons correspondants.",
"home-layout-3": "Vous êtes prêt à commencer à utiliser OpenSign ! Si vous avez besoin d'aide, n'hésitez pas à nous contacter.",
"home-layout-3": "Vous êtes prêt à commencer à utiliser {{appName}} ! Si vous avez besoin d'aide, n'hésitez pas à nous contacter.",
"generate-token": "Effectuez la mise à niveau maintenant pour générer un jeton API de production.",
"opensign-drive-1": "Cliquez sur les liens du fil d'Ariane pour naviguer facilement dans la hiérarchie des dossiers et afficher les documents dans chaque dossier.",
"opensign-drive-2": "Cliquez sur le bouton Ajouter pour créer un nouveau dossier ou document.",
@@ -646,7 +661,7 @@
"bulk-send-subcription-alert": "Veuillez passer au forfait Professionnel ou Équipe pour utiliser Quicksend.",
"generate-test-token": "Générer jeton de test",
"regenerate-test-token": "Régénérer le jeton de test",
"help-test-token": "Ce jeton peut être utilisé pour tester les API au niveau du point de terminaison https://sandbox.opensignlabs.com/api/v1, vous permettant ainsi d'effectuer un nombre illimité de signatures de documents. Veuillez noter que l'API sandbox signera vos documents avec des certificats auto-signés, qui peuvent ne pas être reconnus comme valides par Adobe. Une fois vos tests terminés, vous pouvez passer à lun de nos forfaits payants pour générer un jeton de production.",
"help-test-token": "Ce jeton peut être utilisé pour tester les API au niveau du point de terminaison https://sandbox.opensignlabs.com/api/v1, vous permettant ainsi d'effectuer un nombre illimité de signatures de documents. Veuillez noter que l'API sandbox signera vos documents avec des certificats auto-signés, qui peuvent ne pas être reconnus comme valides par Adobe. Une fois vos tests terminés, vous pouvez passer à l'un de nos forfaits payants pour générer un jeton de production.",
"help-api-token": "Ce jeton peut être utilisé pour accéder aux API de production au point de terminaison {{origin}}/api/v1. Il ne peut être généré que sur l'un de nos forfaits payants.",
"reason": "Raison",
"decline-by": "Refusé/révoqué par",
@@ -689,19 +704,19 @@
"download-pdf": "Télécharger le PDF",
"pdf-certificate": "Télécharger Pdf + Certificat",
"document-logs": "Journaux de documents",
"server-down": "Impossible de se connecter au serveur OpenSign. Si vous hébergez vous-même OpenSign, veuillez vous assurer que toutes les étapes de la documentation ont été correctement suivies. Si vous exécutez OpenSign localement, vous y accédez peut-être via un numéro de port incorrect.",
"server-down": "Impossible de se connecter au serveur {{appName}}. Si vous hébergez vous-même {{appName}}, veuillez vous assurer que toutes les étapes de la documentation ont été correctement suivies. Si vous exécutez {{appName}} localement, vous y accédez peut-être via un numéro de port incorrect.",
"admin-exists": "L'administrateur existe déjà. Veuillez vous connecter à l'application en utilisant les informations d'identification de l'administrateur afin de gérer les utilisateurs.",
"enable-tour": "Activer la visite",
"istourenabled-otp-help": {
"p1": "Cela permettra une visite guidée pour les signataires, fournissant des instructions pendant le processus de signature.",
"p2": "Cela désactivera la visite guidée, garantissant ainsi une expérience de signature plus rapide et ininterrompue.",
"p3": "Conseil : si vos signataires connaissent OpenSign et l'utilisent fréquemment, vous souhaiterez peut-être désactiver la visite guidée pour une expérience plus fluide. Choisissez l'option qui correspond le mieux à vos besoins en matière de signature de documents."
"p3": "Conseil: si vos signataires connaissent {{appName}} et l'utilisent fréquemment, vous souhaiterez peut-être désactiver la visite guidée pour une expérience plus fluide. Choisissez l'option qui correspond le mieux à vos besoins en matière de signature de documents."
},
"form-title-1": "Paramètre du flux de documents",
"form-title-2": "Paramètre de sécurité",
"public-tour-message": "Le modèle doit être public avant que vous puissiez générer un lien partageable.",
"add-user-template": "Vous devez ajouter un rôle avant de pouvoir lui ajouter des champs. ",
"pdf-uncompatible": "Ce pdf n'est pas compatible avec opensign, veuillez contacter <support@opensignlabs.com>",
"pdf-uncompatible": "PDF n'est pas compatible, veuillez contacter {{appName}}",
"text-field-tour": "Les champs de type 'Texte' doivent être remplis à l'avance avant l'envoi du document. Si vous avez besoin que les signataires fournissent des informations, utilisez plutôt le champ 'Saisie de texte'.",
"attach-signer-tour": "Vous devez associer un signataire à chaque rôle. Vous pouvez le faire en cliquant sur cette icône. Une fois que vous avez sélectionné un signataire, il sera attaché à tous les champs associés à ce rôle qui apparaissent dans la même couleur.",
"allowed-signature-types": "Types de signature autorisés",
@@ -741,7 +756,7 @@
"delete-folder-alert": "Êtes-vous sûr de vouloir supprimer ce Dossier ?",
"delete-folder-alert-1": "Impossible de supprimer : le dossier doit être vide.",
"defaultSign-alert": "Les widgets {{widgetsType}} ne sont pas disponibles pour la signature automatique.",
"embed-heading": "Intégrer des modèles OpenSign",
"embed-heading": "Intégrer des modèles {{appName}}",
"public-template-edit-title": "Modifier le modèle public",
"public-template-edit-mssg": "Vous ne pouvez pas modifier directement un modèle public. Pour apporter des modifications, le modèle sera défini comme privé et vous devrez ensuite le rendre à nouveau public. Voulez-vous continuer ?",
"agree-p1": "Je confirme avoir lu et compris les ",
@@ -750,48 +765,48 @@
"agrre-button": " Accepter et continuer ",
"term-cond-title": "Termes et conditions",
"term-cond-h": "DIVULGATION D'ENREGISTREMENT ÉLECTRONIQUE ET DE SIGNATURE",
"term-cond-p1": "Cette divulgation d'enregistrement électronique et de signature (' Divulgation ') est un accord entre le créateur du document (' Expéditeur ') et le signataire (' Vous '), facilité via la plateforme OpenSign (' Plateforme '). En signant des documents via OpenSign, vous acceptez les conditions décrites dans cette divulgation. Veuillez la lire attentivement avant de continuer.",
"term-cond-p1": "Cette divulgation d'enregistrement électronique et de signature (' Divulgation ') est un accord entre le créateur du document (' Expéditeur ') et le signataire (' Vous '), facilité via la plateforme {{appName}} ( Plateforme ). En signant des documents via {{appName}}, vous acceptez les conditions décrites dans cette divulgation. Veuillez la lire attentivement avant de continuer.",
"term-cond-h1": "1. Objet et portée",
"term-cond-p2": "Cette divulgation régit l'utilisation d'enregistrements et de signatures électroniques pour les documents envoyés par l'expéditeur et signés par vous via la plateforme OpenSign. En acceptant, vous reconnaissez que :",
"term-cond-p3": "Vous recevrez et signerez des documents électroniquement via OpenSign.",
"term-cond-p2": "Cette divulgation régit l'utilisation d'enregistrements et de signatures électroniques pour les documents envoyés par l'expéditeur et signés par vous via la plateforme {{appName}}. En acceptant, vous reconnaissez que :",
"term-cond-p3": "Vous recevrez et signerez des documents électroniquement via {{appName}}.",
"term-cond-p4": "Votre signature électronique est juridiquement contraignante et équivaut à une signature manuscrite.",
"js-snippet-msg-2": "Gérer les modèles",
"js-snippet-msg-3": "page.",
"term-cond-h2": "2. Consentement à l'utilisation d'enregistrements et de signatures électroniques",
"term-cond-p5": "En acceptant cette divulgation :",
"term-cond-p6": "Vous consentez à effectuer des transactions électroniques avec l'expéditeur en utilisant OpenSign et comprenez que ce consentement est valable jusqu'à son retrait.",
"term-cond-p7": "Vous acceptez de consulter, de signer et de renvoyer des documents par voie électronique à l'aide d'OpenSign.",
"term-cond-p5": "En acceptant cette divulgation:",
"term-cond-p6": "Vous consentez à effectuer des transactions électroniques avec l'expéditeur en utilisant {{appName}} et comprenez que ce consentement est valable jusqu'à son retrait.",
"term-cond-p7": "Vous acceptez de consulter, de signer et de renvoyer des documents par voie électronique à l'aide d'{{appName}}.",
"term-cond-p8": "Ce consentement s'applique uniquement au(x) document(s) spécifique(s) fourni(s) par l'Expéditeur.",
"term-cond-h3": "3. Droit de retirer le consentement",
"term-cond-p9": "Vous avez le droit de retirer votre consentement à l'utilisation d'enregistrements et de signatures électroniques en :",
"term-cond-p10": "Contacter directement l'expéditeur en utilisant les coordonnées fournies dans sa communication.",
"term-cond-p11": "Notifier l'expéditeur avant de terminer le processus de signature.",
"term-cond-h4": "4. Configuration matérielle et logicielle requise",
"term-cond-p12": "Pour signer des documents avec OpenSign, vous devez avoir :",
"term-cond-p12": "Pour signer des documents avec {{appName}}, vous devez avoir:",
"term-cond-p13": "Un appareil avec accès à Internet.",
"term-cond-p14": "Un navigateur Web compatible avec JavaScript activé.",
"term-cond-p15": "Un compte email pour recevoir des notifications et des documents signés.",
"term-cond-p16": "Si ces exigences changent, l'expéditeur ou OpenSign vous informera des mises à jour.",
"term-cond-p16": "Si ces exigences changent, l'expéditeur ou {{appName}} vous informera des mises à jour.",
"term-cond-h5": "5. Conservation des dossiers et demande de copies papier",
"term-cond-p17": "Vous êtes responsable de vous assurer que vous pouvez accéder et conserver le(s) document(s) signé(s) électroniquement. OpenSign fournit des outils pour télécharger ou imprimer le document signé immédiatement une fois le processus de signature terminé.",
"term-cond-p17": "Vous êtes responsable de vous assurer que vous pouvez accéder et conserver le(s) document(s) signé(s) électroniquement. {{appName}} fournit des outils pour télécharger ou imprimer le document signé immédiatement une fois le processus de signature terminé.",
"term-cond-p18": "Si vous préférez une copie papier de tout document signé électroniquement, vous pouvez la demander directement à l'expéditeur. L'expéditeur peut facturer des frais raisonnables pour la fourniture de copies papier, sauf interdiction par la loi applicable.",
"term-cond-h6": "6. Reconnaissance et accord",
"term-cond-p19": "En procédant, Vous :",
"term-cond-p20": "Confirmez que vous pouvez accéder et consulter les documents électroniques comme décrit dans cette divulgation.",
"term-cond-p21": "Accepter d'utiliser des enregistrements et des signatures électroniques pour la ou les transactions spécifiées avec l'expéditeur.",
"term-cond-p22": "Comprenez qu'OpenSign est une plateforme facilitant la transaction et n'est pas partie à l'accord.",
"term-cond-p22": "Comprenez qu'{{appName}} est une plateforme facilitant la transaction et n'est pas partie à l'accord.",
"term-cond-h7": "7. Effet juridique",
"term-cond-p23": "Votre signature électronique facilitée grâce à OpenSign :",
"term-cond-p23": "Votre signature électronique facilitée grâce à {{appName}}:",
"term-cond-p24": "Conforme aux lois applicables sur les signatures électroniques, y compris, mais sans s'y limiter, la loi E-SIGN aux États-Unis, le règlement eIDAS de l'UE et la loi indienne sur les technologies de l'information.",
"term-cond-p25": "Est juridiquement contraignant entre vous et l'expéditeur pour le(s) document(s) signé(s).",
"term-cond-h8": "8. Rôle de la plateforme et limitation de responsabilité",
"term-cond-p26": "OpenSign sert de plateforme pour faciliter les transactions électroniques. Il n'est pas responsable du contenu, de la validité ou du caractère exécutoire des documents envoyés par l'expéditeur. Tout litige ou problème lié au document ou à sa signature doit être résolu directement entre vous et l'expéditeur. Expéditeur.",
"term-cond-p26": "{{appName}} sert de plateforme pour faciliter les transactions électroniques. Il n'est pas responsable du contenu, de la validité ou du caractère exécutoire des documents envoyés par l'expéditeur. Tout litige ou problème lié au document ou à sa signature doit être résolu directement entre vous et l'expéditeur. Expéditeur.",
"term-cond-h9": "9. Résiliation du Contrat",
"term-cond-p27": "Votre consentement s'applique uniquement à la transaction en cours, sauf indication contraire de la part de l'expéditeur. OpenSign se réserve le droit de mettre fin à l'accès à sa plateforme en cas de violation de ses conditions d'utilisation ou de ses politiques.",
"term-cond-p28": "En sélectionnant J'accepte, vous confirmez que vous comprenez et acceptez les termes de cette divulgation d'enregistrement électronique et de signature et consentez à signer électroniquement le(s) document(s) fourni(s) par l'expéditeur via OpenSign.",
"term-cond-p29": "Si vous avez des questions sur cette divulgation, contactez directement l'expéditeur. Pour obtenir une assistance technique avec OpenSign, visitez",
"term-cond-p30": "ou par email",
"js-snippet-msg": "Pour intégrer des modèles OpenSign dans vos sites Web ou pages de destination HTML, vous pouvez utiliser le code ci-dessous :",
"term-cond-p27": "Votre consentement s'applique uniquement à la transaction en cours, sauf indication contraire de la part de l'expéditeur. {{appName}} se réserve le droit de mettre fin à l'accès à sa plateforme en cas de violation de ses conditions d'utilisation ou de ses politiques.",
"term-cond-p28": "En sélectionnant J'accepte, vous confirmez que vous comprenez et acceptez les termes de cette divulgation d'enregistrement électronique et de signature et consentez à signer électroniquement le(s) document(s) fourni(s) par l'expéditeur via {{appName}}.",
"term-cond-p29": "Si vous avez des questions sur cette divulgation, contactez directement l'expéditeur. Pour obtenir une assistance technique avec {{appName}}, visitez ",
"term-cond-p30": " ou par email",
"js-snippet-msg": "Pour intégrer des modèles {{appName}} dans vos sites Web ou pages de destination HTML, vous pouvez utiliser le code ci-dessous:",
"js-snippet-msg-1": "Vous pouvez obtenir le TemplateId à partir de la page Gérer les modèles.",
"agrrement-alert": "Pour procéder, vous devez consentir à la divulgation des enregistrements et signatures électroniques.",
"webhook-already-exists": "L'URL existe déjà ! Veuillez en essayer un autre.",
@@ -814,13 +829,13 @@
"requestsign-description": "Utilisez ce formulaire pour demander les signatures des autres et de vous-même.",
"template-form-description": "Utilisez ce formulaire pour créer des modèles réutilisables pour vos documents.",
"activation-key": "Clé d'activation",
"paid-selfhost-help": "Veuillez contacter OpenSign pour plus d'informations à l'adresse ",
"remaining-credits-selfhost-help": "Utilisez des crédits premium pour la signature de documents via l'API, l'envoi en masse et l'intégration d'OpenSign sur votre site web. Vous avez {{allowedcredits}} crédits inclus et {{addoncredits}} crédits supplémentaires achetés restants.",
"paid-selfhost-help": "Veuillez contacter {{appName}} pour plus d'informations à l'adresse ",
"remaining-credits-selfhost-help": "Utilisez des crédits premium pour la signature de documents via l'API, l'envoi en masse et l'intégration d'{{appName}} sur votre site web. Vous avez {{allowedcredits}} crédits inclus et {{addoncredits}} crédits supplémentaires achetés restants.",
"signature-type": "Votre signature",
"initial-type": "Vos initiales",
"redirect-url": "URL de redirection",
"bulk-send": "Envoi groupé",
"select-timezone": "Sélectionnez votre fuseau horaire",
"select-timezone": "Fuseau horaire",
"current-time": "Heure actuelle",
"email-help": "Vous n'êtes pas autorisé à changer d'adresse e-mail pour des raisons de sécurité. Veuillez créer un autre compte gratuit en utilisant la nouvelle adresse e-mail.",
"doc-sent": "Document envoyé avec succès.",
@@ -833,5 +848,44 @@
"variables-use": "Variables à utiliser",
"send-to-sign": "vous a demandé de signer",
"use-template": "Utiliser le modèle",
"contact-already-exists": "Le contact existe déjà."
"contact-already-exists": "Le contact existe déjà.",
"docx-error": "Nous rencontrons actuellement des problèmes avec le traitement des fichiers DOCX. Veuillez télécharger le fichier PDF",
"docx-error-contact": "ou contactez-nous à support@opensignlabs.com",
"agreement-note": "Remarque : Accepter cela ne signifie pas que vous signez immédiatement le document. Cela vous permet uniquement de consulter le document électroniquement. Vous aurez l'opportunité de le lire entièrement et de décider ensuite si vous souhaitez le signer.",
"draft-template-info-p1": "Pour rendre votre modèle public, il doit contenir un seul rôle ou, s'il inclut plusieurs rôles, tous les rôles supplémentaires doivent déjà être attribués aux signataires. Le rôle public non attribué doit rester vide et être placé en première position.",
"visit-below-link": "Visitez le lien ci-dessous pour en savoir plus -",
"storage-help": "Activer BYOC vous permet de connecter votre propre stockage S3 afin que vos fichiers restent entièrement sous votre contrôle, sans copie externe conservée. Si l'autonomie des données est importante pour vous, envisagez de passer à l'offre Teams pour débloquer cette fonctionnalité.",
"daily-quota-reached": "Vous avez atteint votre quota quotidien. Pour obtenir de l'aide, veuillez contacter quotas@opensignlabs.com.",
"enabled-signature-type": "Types de signature activés",
"enabled-signature-type-help": "Le paramètre 'Types de signature activés' détermine quelles options de signature sont disponibles dans votre organisation. Par exemple, si vous désactivez l'option 'Dessiner', les membres de votre organisation ne la verront pas dans le widget de signature, tandis que les trois autres options resteront accessibles.",
"indexing-public-profile": "Autoriser l'indexation du profil public par les moteurs de recherche",
"user-created-successfully": "Utilisateur créé avec succès.",
"only-15-reminder-allowed": "Vous pouvez définir jusqu'à 15 rappels automatiques. Par exemple, si 'TimeToComplete' est de 15 jours et 'RemindOnceInEvery' est de 1 jour, vous atteindrez la limite maximale de 15 rappels. Ajustez vos paramètres en conséquence.",
"rate-your-experience": "Comment s'est passée votre expérience avec {{appName}} ?",
"thanks-for-feedback": "Merci pour votre retour 🙏",
"share-your-feedback": "Partagez votre avis",
"share-your-review": "Partagez votre avis",
"date-format": "Format de date",
"document-deleted": "Le document a été supprimé ou vous n'y avez pas accès. Veuillez contacter l'expéditeur.",
"save-as-template-?": "Êtes-vous sûr de vouloir enregistrer ce document comme modèle ?",
"go-to-manage-templates": "Aller à 'Gérer les modèles'",
"template-created": "Modèle créé",
"how-would-you-like-to-proceed?": "Comment souhaitez-vous procéder ?",
"failed-to-load-refresh-page": "Échec du chargement du document. Veuillez essayer d'actualiser cette page.",
"document-has-been-signed": "Le document a été signé avec succès !",
"document-has-been-signed-by-you": "Le document a été signé avec succès par vous !",
"participant-completed-signing": "Tous les participants ont terminé le processus de signature.",
"you-will-receive-email-shortly": "✅ Voilà, c'est fait ! Vous recevrez un e-mail de confirmation sous peu.",
"please-provide-templateid": "Veuillez fournir templateid",
"this-template-is-not-public": "Ce template n'est pas public",
"invalid-templateid": "templateid invalide",
"contact-billing-at-opensign": "Pour ajouter plus de places, veuillez contacter OpenSign™ à l'adresse <1>billing@opensignlabs.com</1> pour obtenir de l'aide.",
"title-length-alert": "Le titre doit comporter au maximum 250 caractères.",
"note-length-alert": "La note doit comporter au maximum 200 caractères.",
"description-length-alert": "La description doit comporter au maximum 500 caractères",
"fix-&-resend-document": "Corriger et renvoyer le document",
"do-you-want-recreate-document?": "Cela créera un brouillon à partir de ce document avec tous les champs intacts. Êtes-vous sûr de vouloir recréer ce document ?",
"start-editing": "Commencer l'édition",
"unsaved-changes-discard-them?": "Vous avez des modifications non enregistrées. Les supprimer ?",
"yes-discard": "Oui, supprimer"
}
@@ -51,7 +51,6 @@
"docs": "Documenti",
"session-expired": "La tua sessione è scaduta.",
"public-profile-help": "Questo è il tuo URL pubblico. Copialo o condividilo con il firmatario, e potrai visualizzare tutti i tuoi modelli pubblici.",
"tagline-help": "Questo slogan apparirà nel tuo profilo pubblico OpenSign, es. https://opensign.me/alex",
"welcome": "Bentornato!",
"Login-to-your-account": "Accedi al tuo account",
"password": "Password",
@@ -74,12 +73,23 @@
"Sign yourself": "Firma tu stesso",
"Request signatures": "Richiedi firme",
"Templates": "Modelli",
"Analytics": "Analitica",
"Branding": "Marchio",
"Mail": "Posta",
"Storage": "Archiviazione",
"Signing certificate": "Certificato di firma",
"Teams": "Squadre",
"General": "Generale",
"Teams-Children": {
"Organizations": "Organizzazioni",
"OrgAdmins": "OrgAdmins"
},
"Templates-Children": {
"Create template": "Crea modello",
"Manage templates": "Gestisci modelli",
"Embedding": "Incorporazione"
},
"OpenSign™ Drive": "OpenSign™ Drive",
"OpenSign™ Drive": "{{appName}} Drive",
"Reports": "Report",
"Documents": "Documenti",
"Documents-Children": {
@@ -140,7 +150,10 @@
"Copy Public URL": "Copia URL pubblico",
"extend-expiry-date": "Estendi data di scadenza",
"Duplicate Template": "Duplica modello",
"Duplicate": "Duplica"
"Duplicate": "Duplica",
"daily-mail-quota": "Quota e-mail giornaliera",
"Save as template": "Salva come modello",
"Fix & resend": "Correggi e reinvia"
},
"report-heading": {
"Sr.No": "Nr.",
@@ -198,7 +211,7 @@
"remind-once": "Ricorda una volta ogni (giorni)",
"next": "Avanti",
"select-folder": "Seleziona cartella",
"OpenSign-drive": "OpenSign™ Drive",
"OpenSign-drive": "{{appName}} Drive",
"drive-document-status": {
"Completed": "Completato",
"Declined": "Rifiutato",
@@ -206,7 +219,7 @@
"Expired": "Scaduto",
"In Progress": "In corso"
},
"select-folder-help": "Se non selezioni una cartella, il tuo documento firmato verrà salvato nella cartella principale di OpenSign drive.",
"select-folder-help": "Se non selezioni una cartella, il tuo documento firmato verrà salvato nella cartella principale di {{appName}} Drive.",
"no-data": "Nessun dato trovato",
"save-here": "Salva qui",
"back": "Indietro",
@@ -236,7 +249,7 @@
"generate-token-alert": "Sei sicuro di voler rigenerare il token? Questo invaliderà il vecchio token.",
"yes": "Sì",
"copied": "Copiato",
"something-went-wrong-mssg": "Qualcosa è andato storto, riprova più tardi.",
"something-went-wrong-mssg": "Si è verificato un errore, L'aggiornamento della pagina potrebbe risolvere il problema.",
"token-generated": "Token generato con successo.",
"webhook": "Webhook",
"update-webhook": "Aggiorna Webhook",
@@ -272,13 +285,14 @@
"make-template-public": "Rendi il modello pubblico",
"make-template-private": "Rendi il modello privato",
"make-template-public-alert": "Sei sicuro di voler rendere pubblico questo modello?",
"make-template-private-alert-non": "Sei sicuro di voler rendere privato questo modello?",
"make-template-private-alert": "Sei sicuro di voler rendere privato questo modello? Questo lo rimuoverà dal tuo profilo pubblico.",
"public-role": "Ruolo pubblico",
"public-url": "Profilo pubblico",
"embed-template": "Incorpora Modello",
"public-url-copy": "Ecco il tuo URL pubblico: ",
"public-url-copy-mssg": "Copialo o condividilo con il firmatario; potrai vedere tutti i tuoi modelli impostati come pubblici.",
"add-public-url-alert": "Completa la configurazione del tuo profilo pubblico OpenSign. Dopo, potrai rendere i modelli pubblici. Questo processo è necessario solo una volta.",
"add-public-url-alert": "Completa la configurazione del tuo profilo pubblico {{appName}}. Dopo, potrai rendere i modelli pubblici. Questo processo è necessario solo una volta.",
"share-with-alert": "Non puoi condividere un modello se alcuni ruoli hanno già contatti assegnati. Rimuovi tutte le assegnazioni di contatti dai ruoli prima di condividere il modello.",
"share-with": "Condividi con",
"team-not-found": "Team non trovato",
@@ -297,12 +311,12 @@
"revoke-document": "Revoca documento",
"revoke-document-alert": "Sei sicuro di voler revocare questo documento?",
"resend-mail": "Reinvia email",
"resend-mail-help": "Puoi usare le seguenti variabili che verranno sostituite con i loro valori effettivi: {{document_title}}, {{sender_name}}, {{sender_mail}}, {{sender_phone}}, {{receiver_name}}, {{receiver_email}}, {{receiver_phone}}, {{expiry_date}}, {{company_name}}, {{signing_url}}.",
"resend-mail-help": "Puoi usare le seguenti variabili che verranno sostituite con i loro valori effettivi: {{document_title}}, {{sender_name}}, {{sender_mail}}, {{sender_phone}}, {{receiver_name}}, {{receiver_email}}, {{receiver_phone}}, {{expiry_date}}, {{company_name}}, {{signing_url}}, {{note}}.",
"subject": "Oggetto",
"body": "Corpo del messaggio",
"add-contact": "Aggiungi contatto",
"edit-contact": "Modifica contatto",
"add-signer-alert": "Il contatto esiste già! Selezionalo dal menu a tendina Firmatari.",
"add-signer-alert": "Il contatto esiste già! Selezionalo dal menu a tendina 'Firmatari'.",
"record-delete-alert": "Record eliminato con successo!",
"record-revoke-alert": "Record revocato con successo!",
"mail-sent-alert": "Email inviata con successo.",
@@ -441,11 +455,13 @@
"add-recipients": "Aggiungi destinatari",
"loading-mssg": "Questo potrebbe richiedere del tempo",
"send-mail": "Invia Mail",
"signature-field-widget":"È necessario aggiungere almeno un campo firma per ogni utente. Non hai aggiunto campi firma per {{signersName}}",
"placeholder-alert-1": "Assicurati che sia stato aggiunto almeno un widget per la firma per tutti i destinatari.",
"placeholder-alert-2": "Conferma di aver compilato il campo di testo.",
"placeholder-alert-3": "Sei sicuro di voler inviare questo documento per le firme?",
"placeholder-alert-4": "Hai inviato con successo le mail a tutti i destinatari!",
"placeholder-mail-alert": "Hai inviato con successo un'e-mail a {{name}}. I firmatari successivi riceveranno un'e-mail una volta che {{name}} avrà firmato il documento.",
"placeholder-mail-alert-you": "I firmatari successivi riceveranno un'email non appena firmi il documento.",
"placeholder-alert-5": "Vuoi firmare i documenti ora?",
"placeholder-alert-6": "Configura l'adattatore email per inviare mail!",
"placeholder-alert-7": "Seleziona il firmatario per aggiungere un segnaposto!",
@@ -492,7 +508,7 @@
"role-not-found": "Ruolo non trovato.",
"do-not-access": "Non hai accesso, contatta l'amministratore.",
"add-admin": "Aggiungi amministratore",
"opensign-setup": "Configurazione OpenSign",
"opensign-setup": "Configurazione {{appName}}",
"join-discord": "Unisciti al nostro server Discord",
"admin-already-exist": "L'amministratore esiste già.",
"password-update-alert-1": "Password aggiornata con successo.",
@@ -509,7 +525,7 @@
"correct-password": "Fornisci la password corretta",
"decrypting-pdf": "Decrittazione PDF, attendi...",
"invalid-otp": "OTP non valido",
"user-not-found": "Utente non trovato!",
"user-not-found": "Utente non trovato",
"enter-otp-alert": "Inserisci l'OTP!",
"get-verification-code": "Ottieni codice di verifica",
"get-verification-code-2": "Riceverai un codice di verifica tramite Email",
@@ -549,7 +565,6 @@
"filed-required-correctly": "Compila i dettagli richiesti correttamente.",
"admin-created": "Amministratore creato",
"invalid-masterkey": "Masterkey non valida",
"opensign-Setup": "Configurazione OpenSign",
"master-key": "Chiave principale",
"profile-update-alert": "Profilo aggiornato con successo.",
"date": "Data",
@@ -574,7 +589,7 @@
"tour-mssg": {
"home-layout-1": "Accesso effettuato con successo! Diamo un'occhiata.",
"home-layout-2": "Per caricare documenti da firmare personalmente o richiedere le firme di altri, seleziona semplicemente i pulsanti corrispondenti.",
"home-layout-3": "Sei pronto per iniziare a usare OpenSign! Se hai bisogno di supporto, contattaci pure.",
"home-layout-3": "Sei pronto per iniziare a usare {{appName}}! Se hai bisogno di supporto, contattaci pure.",
"generate-token": "Esegui l'upgrade ora per generare un token API di produzione.",
"opensign-drive-1": "Fai clic sui collegamenti breadcrumb per navigare facilmente attraverso la gerarchia delle cartelle e visualizzare i documenti in ciascuna cartella.",
"opensign-drive-2": "Fai clic sul pulsante aggiungi per creare una nuova cartella o documento.",
@@ -595,7 +610,7 @@
"placeholder-sign-4": "Trascina o fai clic su un campo per aggiungerlo al documento.",
"placeholder-sign-5": "L'area del contenuto PDF visualizza già i segnaposti esistenti del modello. Per tua comodità, questi segnaposti corrisponderanno al colore del nome del destinatario, rendendoli facilmente identificabili.",
"placeholder-sign-6": "Facendo clic su 'Invia' il documento verrà salvato. Nel passaggio successivo potrai personalizzare le email da inviare ai destinatari o copiare i link di firma e condividerli direttamente con i destinatari.",
"report-1": "Fai clic sul pulsante 'Aggiungi' per creare un nuovo modello. I modelli sono documenti riutilizzabili progettati per generare rapidamente nuovi documenti con la stessa struttura e firmatari diversi. Ad esempio, un modello HR per l'onboarding potrebbe avere ruoli predefinita come Responsabile HR e Nuovo Dipendente. Ogni volta che usi il modello, puoi assegnare il ruolo Nuovo Dipendente a membri dello staff in arrivo, mentre il ruolo Responsabile HR rimane costante, facilitando un processo di onboarding fluido per ogni nuovo assunto.",
"report-1": "Fai clic sul pulsante 'Aggiungi' per creare un nuovo modello. I modelli sono documenti riutilizzabili progettati per generare rapidamente nuovi documenti con la stessa struttura e firmatari diversi. Ad esempio, un modello HR per l'onboarding potrebbe avere ruoli predefinita come 'Responsabile HR' e 'Nuovo Dipendente'. Ogni volta che usi il modello, puoi assegnare il ruolo 'Nuovo Dipendente' a membri dello staff in arrivo, mentre il ruolo 'Responsabile HR' rimane costante, facilitando un processo di onboarding fluido per ogni nuovo assunto.",
"redirect": "Fai clic sul pulsante 'Usa' per creare un nuovo documento da un modello esistente.",
"bulksend": "Per inviare rapidamente più documenti utilizzando un modello esistente creando semplicemente gli indirizzi e-mail dei destinatari, fai clic sul pulsante 'Invio Multiplo'.",
"option": "Questo menu rivela altre opzioni come Modifica ed Elimina. Usa il pulsante 'Modifica' per aggiungere ruoli di firmatari, modificare i campi e aggiornare il modello. Le modifiche si applicheranno a tutti i futuri documenti creati da questo modello ma non influiranno sui documenti esistenti. Usa il pulsante Elimina per eliminare il modello.",
@@ -612,7 +627,7 @@
"Recent signature requests": "Questo è un elenco di documenti che aspettano la tua firma.",
"Recently sent for signatures": "Questo è un elenco di documenti che hai inviato ad altre parti per la firma.",
"Drafts": "Questi sono documenti che hai iniziato ma non hai finalizzato per l'invio.",
"public-template": "Questo video dimostra come configurare il tuo profilo pubblico personalizzato, come https://opensign.me/tuo-username. Imparerai anche come personalizzare il tuo slogan e rendere i tuoi modelli disponibili per la firma pubblica.",
"public-template": "Questo video dimostra come configurare il tuo profilo pubblico personalizzato, come 'https://opensign.me/tuo-username'. Imparerai anche come personalizzare il tuo slogan e rendere i tuoi modelli disponibili per la firma pubblica.",
"allowModify-widgets": "È possibile trascinare e rilasciare uno qualsiasi di questi campi nel documento, oltre ai campi già designati dal creatore del documento."
},
"enter-email-plaholder": "Aggiungi un indirizzo email e premi invio",
@@ -689,19 +704,19 @@
"download-pdf": "Scarica Pdf",
"pdf-certificate": "Scarica Pdf + Certificato",
"document-logs": "Log del documento",
"server-down": "Impossibile connettersi al server OpenSign. Se stai ospitando OpenSign autonomamente, assicurati che tutti i passaggi nella documentazione siano stati seguiti correttamente. Se stai eseguendo OpenSign localmente, potresti accedervi tramite un numero di porta errato.",
"server-down": "Impossibile connettersi al server {{appName}}. Se stai ospitando {{appName}} autonomamente, assicurati che tutti i passaggi nella documentazione siano stati seguiti correttamente. Se stai eseguendo {{appName}} localmente, potresti accedervi tramite un numero di porta errato.",
"admin-exists": "L'amministratore esiste già. Accedi all'applicazione utilizzando le credenziali di amministratore per gestire gli utenti.",
"enable-tour": "Abilita tour",
"istourenabled-help": {
"p1": "Questo abiliterà un tour guidato per i firmatari, fornendo istruzioni durante il processo di firma.",
"p2": "Questo disabiliterà il tour guidato, garantendo un'esperienza di firma più veloce e senza interruzioni.",
"p3": "Suggerimento: Se i tuoi firmatari sono familiari con OpenSign e lo usano frequentemente, potresti voler disabilitare il tour guidato per un'esperienza più fluida. Scegli l'opzione che meglio si adatta alle tue esigenze di firma dei documenti."
"p3": "Suggerimento: Se i tuoi firmatari sono familiari con {{appName}} e lo usano frequentemente, potresti voler disabilitare il tour guidato per un'esperienza più fluida. Scegli l'opzione che meglio si adatta alle tue esigenze di firma dei documenti."
},
"form-title-1": "Impostazioni del flusso del documento",
"form-title-2": "Impostazioni di sicurezza",
"public-tour-message": "Il modello deve essere pubblico prima di poter generare un link condivisibile.",
"add-user-template": "Devi aggiungere un ruolo prima di poter aggiungere i campi per esso.",
"pdf-uncompatible": "Questo PDF non è compatibile con OpenSign, contatta <support@opensignlabs.com>",
"pdf-uncompatible": "Questo PDF non è compatibile, contatta {{appName}}",
"text-field-tour": "I campi di tipo 'Testo' devono essere compilati in anticipo prima che il documento venga inviato. Se hai bisogno che i firmatari forniscano un input, usa invece il campo 'Testo di input'.",
"attach-signer-tour": "Devi allegare un firmatario a ogni ruolo. Puoi farlo cliccando su questa icona. Una volta selezionato un firmatario, sarà associato a tutti i campi associati a quel ruolo che appariranno dello stesso colore.",
"allowed-signature-types": "Tipi di firma consentiti",
@@ -741,7 +756,7 @@
"delete-folder-alert": "Sei sicuro di voler eliminare questa cartella?",
"delete-folder-alert-1": "Impossibile eliminare: la cartella deve essere vuota.",
"defaultSign-alert": "I widget {{widgetsType}} non sono disponibili per la firma automatica.",
"embed-heading": "Incorpora modelli OpenSign",
"embed-heading": "Incorpora modelli {{appName}}",
"public-template-edit-title": "Modifica Modello Pubblico",
"public-template-edit-mssg": "Non puoi modificare direttamente un modello pubblico. Per apportare modifiche, il modello sarà impostato come privato e dovrai renderlo pubblico di nuovo successivamente. Vuoi continuare?",
"agree-p1": "Confermo di aver letto e compreso il",
@@ -750,46 +765,46 @@
"agrre-button": "Accetta e Continua",
"term-cond-title": "Termini e Condizioni",
"term-cond-h": "DIVULGAZIONE SULLE REGISTRAZIONI E FIRME ELETTRONICHE",
"term-cond-p1": "Questa Divulgazione sulle Registrazioni e Firme Elettroniche ('Divulgazione') è un accordo tra il Creatore del Documento ('Mittente') e il Firmatario ('Tu'), facilitato tramite la piattaforma OpenSign ('Piattaforma'). Firmando documenti tramite OpenSign, accetti i termini descritti in questa Divulgazione. Ti preghiamo di leggerla attentamente prima di procedere.",
"term-cond-p1": "Questa Divulgazione sulle Registrazioni e Firme Elettroniche ('Divulgazione') è un accordo tra il Creatore del Documento ('Mittente') e il Firmatario ('Tu'), facilitato tramite la piattaforma {{appName}} (Piattaforma). Firmando documenti tramite {{appName}}, accetti i termini descritti in questa Divulgazione. Ti preghiamo di leggerla attentamente prima di procedere.",
"term-cond-h1": "1. Scopo e ambito",
"term-cond-p2": "Questa Divulgazione regola l'uso di registrazioni e firme elettroniche per i documenti inviati dal Mittente e firmati da Te tramite la piattaforma OpenSign. Accettando, riconosci che:",
"term-cond-p3": "Riceverai e firmerai documenti elettronicamente tramite OpenSign.",
"term-cond-p2": "Questa Divulgazione regola l'uso di registrazioni e firme elettroniche per i documenti inviati dal Mittente e firmati da Te tramite la piattaforma {{appName}}. Accettando, riconosci che:",
"term-cond-p3": "Riceverai e firmerai documenti elettronicamente tramite {{appName}}.",
"term-cond-p4": "La tua firma elettronica è legalmente vincolante ed equivalente a una firma manoscritta.",
"term-cond-h2": "2. Consenso all'uso di registrazioni e firme elettroniche",
"term-cond-p5": "Accettando questa Divulgazione:",
"term-cond-p6": "Consenti di effettuare transazioni elettroniche con il Mittente utilizzando OpenSign e comprendi che questo consenso è valido fino a revoca.",
"term-cond-p7": "Accetti di rivedere, firmare e restituire documenti elettronicamente utilizzando OpenSign.",
"term-cond-p6": "Consenti di effettuare transazioni elettroniche con il Mittente utilizzando {{appName}} e comprendi che questo consenso è valido fino a revoca.",
"term-cond-p7": "Accetti di rivedere, firmare e restituire documenti elettronicamente utilizzando {{appName}}.",
"term-cond-p8": "Questo consenso si applica solo ai documenti specifici forniti dal Mittente.",
"term-cond-h3": "3. Diritto di revoca del consenso",
"term-cond-p9": "Hai il diritto di ritirare il tuo consenso all'uso di registrazioni elettroniche e firme contattando direttamente il mittente tramite le informazioni di contatto fornite nella loro comunicazione.",
"term-cond-p10": "Contattando il mittente direttamente utilizzando le informazioni di contatto fornite nella loro comunicazione.",
"term-cond-p11": "Notificando il mittente prima di completare il processo di firma.",
"term-cond-h4": "4. Requisiti hardware e software",
"term-cond-p12": "Per firmare documenti utilizzando OpenSign, devi avere:",
"term-cond-p12": "Per firmare documenti utilizzando {{appName}}, devi avere:",
"term-cond-p13": "Un dispositivo con accesso a Internet.",
"term-cond-p14": "Un browser web compatibile con JavaScript abilitato.",
"term-cond-p15": "Un account email per ricevere notifiche e documenti firmati.",
"term-cond-p16": "Se questi requisiti cambiano, il mittente o OpenSign ti notificheranno gli aggiornamenti.",
"term-cond-p16": "Se questi requisiti cambiano, il mittente o {{appName}} ti notificheranno gli aggiornamenti.",
"term-cond-h5": "5. Conservazione dei documenti e richiesta di copie cartacee",
"term-cond-p17": "Sei responsabile di assicurarti di poter accedere e conservare il documento(i) firmato(i) elettronicamente. OpenSign fornisce strumenti per scaricare o stampare il documento firmato subito dopo che il processo di firma è completo.",
"term-cond-p17": "Sei responsabile di assicurarti di poter accedere e conservare il documento(i) firmato(i) elettronicamente. {{appName}} fornisce strumenti per scaricare o stampare il documento firmato subito dopo che il processo di firma è completo.",
"term-cond-p18": "Se preferisci una copia cartacea di qualsiasi documento firmato elettronicamente, puoi richiederla direttamente al mittente. Il mittente può addebitare una tariffa ragionevole per fornire copie cartacee, salvo quanto vietato dalla legge applicabile.",
"term-cond-h6": "6. Riconoscimento e Accordo",
"term-cond-p19": "Procedendo, confermi che:",
"term-cond-p20": "Confermi di poter accedere e rivedere i documenti elettronici come descritto in questa divulgazione.",
"term-cond-p21": "Accetti di utilizzare registrazioni elettroniche e firme per le transazioni specificate con il mittente.",
"term-cond-p22": "Comprendi che OpenSign è una piattaforma che facilita la transazione e non è parte dell'accordo.",
"term-cond-p22": "Comprendi che {{appName}} è una piattaforma che facilita la transazione e non è parte dell'accordo.",
"term-cond-h7": "7. Effetto Legale",
"term-cond-p23": "La tua firma elettronica facilitata tramite OpenSign:",
"term-cond-p23": "La tua firma elettronica facilitata tramite {{appName}}:",
"term-cond-p24": "Rispetta le leggi applicabili sulle firme elettroniche, inclusa, ma non limitata alla Legge E-SIGN negli Stati Uniti, al Regolamento eIDAS dell'UE e alla Legge sull'Information Technology in India.",
"term-cond-p25": "È legalmente vincolante tra te e il mittente per il/i documento(i) firmato(i).",
"term-cond-h8": "8. Ruolo della piattaforma e limitazione di responsabilità",
"term-cond-p26": "OpenSign funge da piattaforma per facilitare le transazioni elettroniche. Non è responsabile del contenuto, della validità o dell'applicabilità dei documenti inviati dal mittente. Eventuali controversie o problemi relativi al documento o alla sua firma devono essere risolti direttamente tra te e il mittente.",
"term-cond-p26": "{{appName}} funge da piattaforma per facilitare le transazioni elettroniche. Non è responsabile del contenuto, della validità o dell'applicabilità dei documenti inviati dal mittente. Eventuali controversie o problemi relativi al documento o alla sua firma devono essere risolti direttamente tra te e il mittente.",
"term-cond-h9": "9. Risoluzione dell'accordo",
"term-cond-p27": "Il tuo consenso si applica solo alla transazione corrente, salvo diverso accordo da parte del mittente. OpenSign si riserva il diritto di terminare l'accesso alla sua piattaforma per violazioni dei suoi Termini di servizio o delle politiche.",
"term-cond-p28": "Selezionando 'Accetto', confermi di aver compreso e accettato i termini di questa divulgazione delle registrazioni elettroniche e delle firme e acconsenti a firmare elettronicamente il/i documento(i) fornito(i) dal mittente tramite OpenSign.",
"term-cond-p29": "Se hai domande su questa divulgazione, contatta direttamente il mittente. Per supporto tecnico con OpenSign, visita",
"term-cond-p30": "o invia una email a",
"js-snippet-msg": "Per integrare i modelli OpenSign nel tuo sito web HTML o nelle tue pagine di destinazione, puoi usare il codice qui sotto:",
"term-cond-p27": "Il tuo consenso si applica solo alla transazione corrente, salvo diverso accordo da parte del mittente. {{appName}} si riserva il diritto di terminare l'accesso alla sua piattaforma per violazioni dei suoi Termini di servizio o delle politiche.",
"term-cond-p28": "Selezionando 'Accetto', confermi di aver compreso e accettato i termini di questa divulgazione delle registrazioni elettroniche e delle firme e acconsenti a firmare elettronicamente il/i documento(i) fornito(i) dal mittente tramite {{appName}}.",
"term-cond-p29": "Se hai domande su questa divulgazione, contatta direttamente il mittente. Per supporto tecnico con {{appName}}, visita ",
"term-cond-p30": " o invia una email a",
"js-snippet-msg": "Per integrare i modelli {{appName}} nel tuo sito web HTML o nelle tue pagine di destinazione, puoi usare il codice qui sotto:",
"js-snippet-msg-1": "Puoi ottenere il TemplateId dalla",
"js-snippet-msg-2": "pagina di gestione modelli",
"js-snippet-msg-3": "pagina.",
@@ -814,13 +829,13 @@
},
"signature-setting": "Impostazioni Firma",
"activation-key": "Chiave di attivazione",
"paid-selfhost-help": "Si prega di contattare OpenSign per ulteriori informazioni all'indirizzo ",
"remaining-credits-selfhost-help": "Utilizza i crediti premium per la firma di documenti tramite API, l'invio in massa e l'integrazione di OpenSign sul tuo sito web. Hai {{allowedcredits}} crediti inclusi e {{addoncredits}} crediti aggiuntivi acquistati rimanenti.",
"paid-selfhost-help": "Si prega di contattare {{appName}} per ulteriori informazioni all'indirizzo ",
"remaining-credits-selfhost-help": "Utilizza i crediti premium per la firma di documenti tramite API, l'invio in massa e l'integrazione di {{appName}} sul tuo sito web. Hai {{allowedcredits}} crediti inclusi e {{addoncredits}} crediti aggiuntivi acquistati rimanenti.",
"signature-type": "La tua firma",
"initial-type": "Le tue iniziali",
"redirect-url": "URL di reindirizzamento",
"bulk-send": "Invio in blocco",
"select-timezone": "Seleziona il tuo fuso orario",
"select-timezone": "Fuso orario",
"current-time": "Ora corrente",
"email-help": "Non ti è permesso cambiare l'indirizzo email per motivi di sicurezza. Ti preghiamo di creare un altro account gratuito utilizzando il nuovo indirizzo email.",
"doc-sent": "Documento inviato con successo.",
@@ -833,5 +848,44 @@
"variables-use": "Variabili da utilizzare",
"send-to-sign": "ha richiesto la tua firma",
"use-template": "Usa modello",
"contact-already-exists": "Il contatto esiste già."
"contact-already-exists": "Il contatto esiste già.",
"docx-error": "Stiamo riscontrando alcuni problemi con l'elaborazione dei file DOCX. Per favore, carica il file PDF",
"docx-error-contact": "o contattaci a support@opensignlabs.com",
"agreement-note": "Nota: Accettare questo non significa che stai firmando immediatamente il documento. Questo ti consente solo di esaminare il documento elettronicamente. Avrai l'opportunità di leggerlo per intero e decidere successivamente se firmarlo.",
"draft-template-info-p1": "Per rendere il tuo modello pubblico, deve contenere un solo ruolo oppure, se include più ruoli, tutti i ruoli aggiuntivi devono essere già assegnati ai firmatari. Il ruolo pubblico non assegnato deve rimanere vuoto e deve essere posizionato per primo.",
"visit-below-link": "Visita il link qui sotto per saperne di più -",
"storage-help": "Abilitare BYOC ti consente di collegare il tuo archivio S3 in modo che i tuoi file rimangano completamente sotto il tuo controllo, senza copie esterne conservate. Se l'autonomia dei dati è importante per te, considera l'upgrade a Teams per sbloccare questa funzionalità.",
"daily-quota-reached": "Hai raggiunto la tua quota giornaliera. Per assistenza, contatta quotas@opensignlabs.com.",
"enabled-signature-type": "Tipi di firma abilitati",
"enabled-signature-type-help": "L'impostazione 'Tipi di firma abilitati' determina quali opzioni di firma sono disponibili nella tua organizzazione. Ad esempio, se disabiliti l'opzione 'Disegna', i membri della tua organizzazione non la vedranno nel widget della firma, mentre le altre tre opzioni resteranno accessibili.",
"indexing-public-profile": "Consenti l'indicizzazione del profilo pubblico dai motori di ricerca",
"user-created-successfully": "Utente creato con successo.",
"only-15-reminder-allowed": "Puoi impostare fino a 15 promemoria automatici. Ad esempio, se 'TimeToComplete' è di 15 giorni e 'RemindOnceInEvery' è di 1 giorno, raggiungerai il limite massimo di 15 promemoria. Regola le tue impostazioni di conseguenza.",
"rate-your-experience": "Com'è stata la sua esperienza con {{appName}}?",
"thanks-for-feedback": "Grazie per il tuo feedback 🙏",
"share-your-feedback": "Condividi il tuo feedback",
"share-your-review": "Condividi la tua recensione",
"date-format": "Formato data",
"document-deleted": "Il documento è stato eliminato o non hai accesso. Si prega di contattare il mittente.",
"save-as-template-?": "Sei sicuro di voler salvare questo documento come modello?",
"go-to-manage-templates": "Vai a 'Gestisci modelli'",
"template-created": "Modello creato",
"how-would-you-like-to-proceed?": "Come desideri procedere?",
"failed-to-load-refresh-page": "Impossibile caricare il documento. Prova ad aggiornare questa pagina.",
"document-has-been-signed": "Il documento è stato firmato con successo!",
"document-has-been-signed-by-you": "Il documento è stato firmato con successo da lei!",
"participant-completed-signing": "Tutti i partecipanti hanno completato il processo di firma.",
"you-will-receive-email-shortly": "✅ È tutto! Riceverà a breve un'e-mail di conferma.",
"please-provide-templateid": "Si prega di fornire templateid",
"this-template-is-not-public": "Questo template non è pubblico",
"invalid-templateid": "templateid non valido",
"contact-billing-at-opensign": " Per aggiungere altri posti, contattare OpenSign™ all'indirizzo <1>billing@opensignlabs.com</1> per assistenza.",
"title-length-alert": "Il titolo può contenere al massimo 250 caratteri.",
"note-length-alert": "La nota può contenere al massimo 200 caratteri.",
"description-length-alert": " La descrizione può contenere al massimo 500 caratteri.",
"fix-&-resend-document": "Correggi e reinvia il documento",
"do-you-want-recreate-document?": "Questo creerà una bozza da questo documento con tutti i campi intatti. Sei sicuro di voler ricreare questo documento?",
"start-editing": "Inizia a modificare",
"unsaved-changes-discard-them?": "Hai modifiche non salvate. Vuoi scartarle?",
"yes-discard": "Sì, scarta"
}
+10 -2
View File
@@ -19,6 +19,7 @@ import Loader from "./primitives/Loader";
import UserList from "./pages/UserList";
import { serverUrl_fn } from "./constant/appinfo";
import DocSuccessPage from "./pages/DocSuccessPage";
import ValidateSession from "./primitives/ValidateSession";
const DebugPdf = lazy(() => import("./pages/DebugPdf"));
const ForgetPassword = lazy(() => import("./pages/ForgetPassword"));
const GuestLogin = lazy(() => import("./pages/GuestLogin"));
@@ -29,6 +30,7 @@ const ManageSign = lazy(() => import("./pages/Managesign"));
const AddAdmin = lazy(() => import("./pages/AddAdmin"));
const UpdateExistUserAdmin = lazy(() => import("./pages/UpdateExistUserAdmin"));
const Preferences = lazy(() => import("./pages/Preferences"));
pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/legacy/build/pdf.worker.min.mjs`;
const AppLoader = () => {
return (
@@ -108,7 +110,13 @@ function App() {
path="/forgetpassword"
element={<LazyPage Page={ForgetPassword} />}
/>
<Route element={<HomeLayout />}>
<Route
element={
<ValidateSession>
<HomeLayout />
</ValidateSession>
}
>
<Route
path="/changepassword"
element={<LazyPage Page={ChangePassword} />}
@@ -121,7 +129,7 @@ function App() {
element={<LazyPage Page={UserProfile} />}
/>
<Route
path="/opensigndrive"
path="/drive"
element={<LazyPage Page={Opensigndrive} />}
/>
<Route
Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

+1 -1
View File
@@ -24,7 +24,7 @@ const AddSigner = (props) => {
if (savedUserDetails && addYourself) {
setName(savedUserDetails.name);
setPhone(savedUserDetails?.phone || "");
setEmail(savedUserDetails.email);
setEmail(savedUserDetails.email?.toLowerCase()?.replace(/\s/g, ""));
}
}, [addYourself]);
+163 -285
View File
@@ -2,13 +2,8 @@ import React, { useEffect, useState } from "react";
import Parse from "parse";
import Title from "./Title";
import Loader from "../primitives/Loader";
import {
copytoData,
usertimezone
} from "../constant/Utils";
import {
emailRegex,
} from "../constant/const";
import { copytoData, usertimezone } from "../constant/Utils";
import { emailRegex } from "../constant/const";
import { useTranslation } from "react-i18next";
function generatePassword(length) {
const characters =
@@ -46,9 +41,9 @@ const AddUser = (props) => {
if (teamRes.length > 0) {
const _teamRes = JSON.parse(JSON.stringify(teamRes));
setTeamList(_teamRes);
const allUserId =
_teamRes.find((x) => x.Name === "All Users")?.objectId || "";
setFormdata((prev) => ({ ...prev, team: allUserId }));
const allUserId =
_teamRes.find((x) => x.Name === "All Users")?.objectId || "";
setFormdata((prev) => ({ ...prev, team: allUserId }));
}
};
const checkUserExist = async () => {
@@ -76,167 +71,57 @@ const AddUser = (props) => {
setIsFormLoader(true);
const res = await checkUserExist();
if (res) {
props.setIsAlert({ type: "danger", msg: t("user-already-exist") });
props.showAlert("danger", t("user-already-exist"));
setIsFormLoader(false);
setTimeout(() => props.setIsAlert({ type: "success", msg: "" }), 1000);
} else {
try {
const extUser = new Parse.Object("contracts_Users");
extUser.set("Name", formdata.name);
if (formdata.phone) {
extUser.set("Phone", formdata.phone);
}
extUser.set("Email", formdata.email);
extUser.set("UserRole", `contracts_${formdata.role}`);
if (formdata?.team) {
extUser.set("TeamIds", [
{
__type: "Pointer",
className: "contracts_Teams",
objectId: formdata.team
}
]);
}
if (localUser && localUser.OrganizationId) {
extUser.set("OrganizationId", {
__type: "Pointer",
className: "contracts_Organizations",
objectId: localUser.OrganizationId.objectId
});
}
if (localUser && localUser.Company) {
extUser.set("Company", localUser.Company);
}
if (localStorage.getItem("TenantId")) {
extUser.set("TenantId", {
__type: "Pointer",
className: "partners_Tenant",
objectId: localStorage.getItem("TenantId")
});
}
if (localStorage.getItem("TenantId")) {
const timezone = usertimezone;
if (timezone) {
extUser.set("Timezone", timezone);
}
try {
const _users = Parse.Object.extend("User");
const _user = new _users();
_user.set("name", formdata.name);
_user.set("username", formdata.email);
_user.set("email", formdata.email);
_user.set("password", formdata.password);
if (formdata.phone) {
_user.set("phone", formdata.phone);
}
const user = await _user.save();
if (user) {
const currentUser = Parse.User.current();
extUser.set(
"CreatedBy",
Parse.User.createWithoutData(currentUser.id)
);
extUser.set("UserId", user);
const acl = new Parse.ACL();
acl.setPublicReadAccess(true);
acl.setPublicWriteAccess(true);
acl.setReadAccess(currentUser.id, true);
acl.setWriteAccess(currentUser.id, true);
extUser.setACL(acl);
const res = await extUser.save();
const parseData = JSON.parse(JSON.stringify(res));
if (props.closePopup) {
props.closePopup();
const params = {
name: formdata.name,
email: formdata.email,
phone: formdata.phone,
password: formdata.password,
role: formdata.role,
team: formdata.team,
timezone: timezone,
tenantId: localStorage.getItem("TenantId"),
organization: {
objectId: localUser?.OrganizationId?.objectId,
company: localUser?.Company
}
if (props.handleUserData) {
if (formdata?.team) {
const team = teamList.find(
(x) => x.objectId === formdata.team
);
parseData.TeamIds = parseData.TeamIds.map((y) =>
y.objectId === team.objectId ? team : y
);
}
props.handleUserData(parseData);
}
setIsFormLoader(false);
setFormdata({
name: "",
email: "",
phone: "",
team: "",
role: ""
});
};
const res = await Parse.Cloud.run("adduser", params);
const parseData = JSON.parse(JSON.stringify(res));
console.log("parseData ", parseData);
if (props.closePopup) {
props.closePopup();
}
if (props.handleUserData) {
if (formdata?.team) {
const team = teamList.find((x) => x.objectId === formdata.team);
parseData.TeamIds = parseData.TeamIds.map((y) =>
y.objectId === team.objectId ? team : y
);
}
props.handleUserData(parseData);
}
setIsFormLoader(false);
setFormdata({
name: "",
email: "",
phone: "",
team: "",
role: ""
});
props.showAlert("success", t("user-created-successfully"));
} catch (err) {
console.log("err ", err);
if (err.code === 202) {
const params = { email: formdata.email };
const userRes = await Parse.Cloud.run("getUserId", params);
const currentUser = Parse.User.current();
extUser.set(
"CreatedBy",
Parse.User.createWithoutData(currentUser.id)
);
extUser.set("UserId", {
__type: "Pointer",
className: "_User",
objectId: userRes.id
});
const acl = new Parse.ACL();
acl.setPublicReadAccess(true);
acl.setPublicWriteAccess(true);
acl.setReadAccess(currentUser.id, true);
acl.setWriteAccess(currentUser.id, true);
extUser.setACL(acl);
const res = await extUser.save();
const parseData = JSON.parse(JSON.stringify(res));
if (props.closePopup) {
props.closePopup();
}
if (props.handleUserData) {
if (formdata?.team) {
const team = teamList.find(
(x) => x.objectId === formdata.team
);
parseData.TeamIds = parseData.TeamIds.map((y) =>
y.objectId === team.objectId ? team : y
);
}
props.handleUserData(parseData);
}
setIsFormLoader(false);
setFormdata({
name: "",
email: "",
phone: "",
team: "",
role: ""
});
}
console.log("err", err);
setIsFormLoader(false);
props.showAlert("danger", t("something-went-wrong-mssg"));
}
} catch (err) {
console.log("err", err);
setIsFormLoader(false);
props.setIsAlert({
type: "danger",
msg: t("something-went-wrong-mssg")
});
} finally {
setTimeout(
() => props.setIsAlert({ type: "success", msg: "" }),
1500
);
} else {
props.showAlert("danger", t("something-went-wrong-mssg"));
}
}
}
@@ -259,8 +144,7 @@ const AddUser = (props) => {
const copytoclipboard = (text) => {
copytoData(text);
props.setIsAlert({ type: "success", msg: t("copied") });
setTimeout(() => props.setIsAlert({ type: "success", msg: "" }), 1500); // Reset copied state after 1.5 seconds
props.showAlert("success", t("copied"));
};
return (
<div className="shadow-md rounded-box my-[1px] p-3 bg-base-100 relative">
@@ -270,126 +154,120 @@ const AddUser = (props) => {
<Loader />
</div>
)}
<div className="w-full mx-auto">
<form onSubmit={handleSubmit}>
<div className="mb-3">
<label
htmlFor="name"
className="block text-xs text-gray-700 font-semibold"
>
{t("name")}
<span className="text-[red] text-[13px]"> *</span>
</label>
<input
type="text"
name="name"
value={formdata.name}
onChange={(e) => handleChange(e)}
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
required
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
/>
</div>
<div className="mb-3">
<label
htmlFor="email"
className="block text-xs text-gray-700 font-semibold"
>
{t("email")}
<span className="text-[red] text-[13px]"> *</span>
</label>
<input
type="email"
name="email"
value={formdata.email}
onChange={(e) => handleChange(e)}
required
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
/>
</div>
<div className="mb-3">
<label className="block text-xs text-gray-700 font-semibold">
{t("password")}
</label>
<div className="flex justify-between items-center op-input op-input-bordered op-input-sm text-base-content w-full h-full text-[13px]">
<div className="break-all">{formdata?.password}</div>
<i
onClick={() => copytoclipboard(formdata?.password)}
className="fa-light fa-copy rounded-full hover:bg-base-300 p-[8px] cursor-pointer "
></i>
</div>
<div className="text-[12px] ml-2 mb-0 text-[red] select-none">
{t("password-generateed")}
</div>
</div>
<div className="mb-3">
<label
htmlFor="phone"
className="block text-xs text-gray-700 font-semibold"
>
{t("phone")}
{/* <span className="text-[red] text-[13px]"> *</span> */}
</label>
<input
type="text"
name="phone"
placeholder={t("phone-optional")}
value={formdata.phone}
onChange={(e) => handleChange(e)}
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
/>
</div>
<div className="mb-3">
<label
htmlFor="phone"
className="block text-xs text-gray-700 font-semibold"
>
{t("Role")}
<span className="text-[red] text-[13px]"> *</span>
</label>
<select
value={formdata.role}
onChange={(e) => handleChange(e)}
name="role"
className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content w-full text-xs"
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
required
>
<option defaultValue={""} value={""}>
{t("Select")}
</option>
{role.length > 0 &&
role.map((x) => (
<option key={x} value={x}>
{x}
</option>
))}
</select>
</div>
<div className="flex items-center mt-3 gap-2 text-white">
<button type="submit" className="op-btn op-btn-primary">
{t("submit")}
</button>
<div
type="button"
onClick={() => handleReset()}
className="op-btn op-btn-secondary"
>
{t("cancel")}
</div>
</div>
</form>
</div>
<div className="w-full mx-auto">
<form onSubmit={handleSubmit}>
<div className="mb-3">
<label
htmlFor="name"
className="block text-xs text-gray-700 font-semibold"
>
{t("name")}
<span className="text-[red] text-[13px]"> *</span>
</label>
<input
type="text"
name="name"
value={formdata.name}
onChange={(e) => handleChange(e)}
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
onInput={(e) => e.target.setCustomValidity("")}
required
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
/>
</div>
<div className="mb-3">
<label
htmlFor="email"
className="block text-xs text-gray-700 font-semibold"
>
{t("email")}
<span className="text-[red] text-[13px]"> *</span>
</label>
<input
type="email"
name="email"
value={formdata.email}
onChange={(e) => handleChange(e)}
required
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
onInput={(e) => e.target.setCustomValidity("")}
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
/>
</div>
<div className="mb-3">
<label className="block text-xs text-gray-700 font-semibold">
{t("password")}
</label>
<div className="flex justify-between items-center op-input op-input-bordered op-input-sm text-base-content w-full h-full text-[13px]">
<div className="break-all">{formdata?.password}</div>
<i
onClick={() => copytoclipboard(formdata?.password)}
className="fa-light fa-copy rounded-full hover:bg-base-300 p-[8px] cursor-pointer "
></i>
</div>
<div className="text-[12px] ml-2 mb-0 text-[red] select-none">
{t("password-generateed")}
</div>
</div>
<div className="mb-3">
<label
htmlFor="phone"
className="block text-xs text-gray-700 font-semibold"
>
{t("phone")}
{/* <span className="text-[red] text-[13px]"> *</span> */}
</label>
<input
type="text"
name="phone"
placeholder={t("phone-optional")}
value={formdata.phone}
onChange={(e) => handleChange(e)}
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
/>
</div>
<div className="mb-3">
<label
htmlFor="phone"
className="block text-xs text-gray-700 font-semibold"
>
{t("Role")}
<span className="text-[red] text-[13px]"> *</span>
</label>
<select
value={formdata.role}
onChange={(e) => handleChange(e)}
name="role"
className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content w-full text-xs"
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
onInput={(e) => e.target.setCustomValidity("")}
required
>
<option defaultValue={""} value={""}>
{t("Select")}
</option>
{role.length > 0 &&
role.map((x) => (
<option key={x} value={x}>
{x}
</option>
))}
</select>
</div>
<div className="flex items-center mt-3 gap-2 text-white">
<button type="submit" className="op-btn op-btn-primary">
{t("submit")}
</button>
<div
type="button"
onClick={() => handleReset()}
className="op-btn op-btn-secondary"
>
{t("cancel")}
</div>
</div>
</form>
</div>
</div>
);
};
+80 -81
View File
@@ -3,9 +3,7 @@ import axios from "axios";
import SuggestionInput from "./shared/fields/SuggestionInput";
import Loader from "../primitives/Loader";
import { useTranslation } from "react-i18next";
import {
emailRegex,
} from "../constant/const";
import { emailRegex } from "../constant/const";
const BulkSendUi = (props) => {
const { t } = useTranslation();
const [forms, setForms] = useState([]);
@@ -22,15 +20,15 @@ const BulkSendUi = (props) => {
//function to check atleast one signature field exist
const signatureExist = async () => {
setIsDisableBulkSend(false);
const getPlaceholder = props?.Placeholders;
const checkIsSignatureExistt = getPlaceholder?.every((placeholderObj) =>
placeholderObj?.placeHolder?.some((holder) =>
holder?.pos?.some((posItem) => posItem?.type === "signature")
)
);
setIsSignatureExist(checkIsSignatureExistt);
setIsLoader(false);
setIsDisableBulkSend(false);
const getPlaceholder = props?.Placeholders;
const checkIsSignatureExistt = getPlaceholder?.every((placeholderObj) =>
placeholderObj?.placeHolder?.some((holder) =>
holder?.pos?.some((posItem) => posItem?.type === "signature")
)
);
setIsSignatureExist(checkIsSignatureExistt);
setIsLoader(false);
};
useEffect(() => {
if (scrollOnNextUpdate && formRef.current) {
@@ -74,7 +72,6 @@ const BulkSendUi = (props) => {
setForms(newForms);
};
const handleRemoveForm = (index) => {
const updatedForms = forms.filter((_, i) => i !== index);
setForms(updatedForms);
@@ -95,82 +92,83 @@ const BulkSendUi = (props) => {
const handleSubmit = async (e) => {
e.preventDefault();
e.stopPropagation();
setIsSubmit(true);
if (validateEmails(forms)) {
// Create a copy of Placeholders array from props.item
let Placeholders = [...props.Placeholders];
// Initialize an empty array to store updated documents
let Documents = [];
// Loop through each form
forms.forEach((form) => {
//checking if user enter email which already exist as a signer then add user in a signers array
let existSigner = [];
form.fields.map((data) => {
if (data.signer) {
existSigner.push(data.signer);
}
});
// Map through the copied Placeholders array to update email values
const updatedPlaceholders = Placeholders.map((placeholder) => {
// Find the field in the current form that matches the placeholder Id
const field = form.fields.find(
(element) => parseInt(element.fieldId) === placeholder.Id
);
// If a matching field is found, update the email value in the placeholder
const signer = field?.signer?.objectId ? field.signer : "";
if (field) {
if (signer) {
return {
...placeholder,
signerObjId: field?.signer?.objectId || "",
signerPtr: signer
};
} else {
return {
...placeholder,
email: field.email,
signerObjId: field?.signer?.objectId || "",
signerPtr: signer
};
}
}
// If no matching field is found, keep the placeholder as is
return placeholder;
});
// Push a new document object with updated Placeholders into the Documents array
if (existSigner?.length > 0) {
Documents.push({
...props.item,
Placeholders: updatedPlaceholders,
Signers: props.item.Signers
? [...props.item.Signers, ...existSigner]
: [...existSigner]
});
} else {
Documents.push({
...props.item,
Placeholders: updatedPlaceholders,
SignatureType: props.signatureType
});
setIsSubmit(true);
if (validateEmails(forms)) {
// Create a copy of Placeholders array from props.item
let Placeholders = [...props.Placeholders];
// Initialize an empty array to store updated documents
let Documents = [];
// Loop through each form
forms.forEach((form) => {
//checking if user enter email which already exist as a signer then add user in a signers array
let existSigner = [];
form.fields.map((data) => {
if (data.signer) {
existSigner.push(data.signer);
}
});
await batchQuery(Documents);
} else {
setIsSubmit(false);
}
// Map through the copied Placeholders array to update email values
const updatedPlaceholders = Placeholders.map((placeholder) => {
// Find the field in the current form that matches the placeholder Id
const field = form.fields.find(
(element) => parseInt(element.fieldId) === placeholder.Id
);
// If a matching field is found, update the email value in the placeholder
const signer = field?.signer?.objectId ? field.signer : "";
if (field) {
if (signer) {
return {
...placeholder,
signerObjId: field?.signer?.objectId || "",
signerPtr: signer
};
} else {
return {
...placeholder,
email: field.email,
signerObjId: field?.signer?.objectId || "",
signerPtr: signer
};
}
}
// If no matching field is found, keep the placeholder as is
return placeholder;
});
// Push a new document object with updated Placeholders into the Documents array
if (existSigner?.length > 0) {
Documents.push({
...props.item,
Placeholders: updatedPlaceholders,
Signers: props.item.Signers
? [...props.item.Signers, ...existSigner]
: [...existSigner]
});
} else {
Documents.push({
...props.item,
Placeholders: updatedPlaceholders,
SignatureType: props.signatureType
});
}
});
await batchQuery(Documents);
} else {
setIsSubmit(false);
}
};
const batchQuery = async (Documents) => {
const token =
{ "X-Parse-Session-Token": localStorage.getItem("accesstoken") };
const token = {
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
};
const functionsUrl = `${localStorage.getItem(
"baseUrl"
)}functions/batchdocuments`;
const headers = {
"Content-Type": "application/json",
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
...token,
...token
};
const params = { Documents: JSON.stringify(Documents) };
try {
@@ -218,7 +216,9 @@ const BulkSendUi = (props) => {
className="flex flex-col"
key={field.fieldId}
>
<label>{field.label}</label>
<label className="block text-xs font-semibold">
{field.label}
</label>
<SuggestionInput
required
type="email"
@@ -275,8 +275,7 @@ const BulkSendUi = (props) => {
)}
</>
) : (
<>
</>
<></>
)}
</>
)}
+1 -1
View File
@@ -4,6 +4,7 @@ import axios from "axios";
import { openInNewTab } from "../constant/Utils";
import { useTranslation } from "react-i18next";
const Footer = () => {
const appName = "OpenSign™";
const { t } = useTranslation();
const [showButton, setShowButton] = useState(false);
const [version, setVersion] = useState("");
@@ -39,7 +40,6 @@ const Footer = () => {
};
}, []);
const appName = "OpenSign™";
const openUrl = () => {
openInNewTab(
"https://github.com/OpenSignLabs/OpenSign/releases/tag/" + version
+56 -43
View File
@@ -10,16 +10,16 @@ import {
saveLanguageInLocal
} from "../constant/Utils";
import { useTranslation } from "react-i18next";
const Header = ({ showSidebar, setIsMenu }) => {
import { appInfo } from "../constant/appinfo";
const Header = ({ showSidebar, setIsMenu, isConsole }) => {
const { t, i18n } = useTranslation();
const navigate = useNavigate();
const { width } = useWindowSize();
const username = localStorage.getItem("username") || "";
const image = localStorage.getItem("profileImg") || dp;
const [isOpen, setIsOpen] = useState(false);
const [applogo, setAppLogo] = useState(
localStorage.getItem("appLogo") || " "
);
const [applogo, setAppLogo] = useState("");
const toggleDropdown = () => {
setIsOpen(!isOpen);
@@ -36,7 +36,8 @@ const Header = ({ showSidebar, setIsMenu }) => {
if (applogo?.logo) {
setAppLogo(applogo?.logo);
} else {
setAppLogo(localStorage.getItem("appLogo") || "");
const logo = localStorage.getItem("appLogo") || appInfo.applogo;
setAppLogo(logo);
}
}
@@ -95,11 +96,13 @@ const Header = ({ showSidebar, setIsMenu }) => {
</div>
<div className="flex-1 ml-2">
<div className="h-[25px] md:h-[40px] w-auto overflow-hidden">
<img
className="object-contain h-full w-auto"
src={applogo}
alt="img"
/>
{applogo && (
<img
className="object-contain h-full w-auto"
src={applogo}
alt="logo"
/>
)}
</div>
</div>
<div id="profile-menu" className="flex-none gap-2">
@@ -121,55 +124,65 @@ const Header = ({ showSidebar, setIsMenu }) => {
{width >= 768 && (
<div
onClick={toggleDropdown}
role="button"
tabIndex="0"
className="cursor-pointer text-base-content text-sm"
>
{username && username}
</div>
)}
<div className="op-dropdown op-dropdown-end" id="profile-menu">
<div
className="op-dropdown op-dropdown-open op-dropdown-end"
id="profile-menu"
>
<div
tabIndex={0}
role="button"
onClick={toggleDropdown}
className="op-btn op-btn-ghost op-btn-xs w-[10px] h-[20px] hover:bg-transparent"
>
<i
tabIndex={0}
role="button"
onClick={toggleDropdown}
className="fa-light fa-angle-down text-base-content"
></i>
<i className="fa-light fa-angle-down text-base-content"></i>
</div>
<ul
tabIndex={0}
className={`mt-3 z-[1] p-2 shadow op-menu op-menu-sm op-dropdown-content text-base-content bg-base-100 rounded-box w-52 ${
className={`mt-3 z-[1] p-2 shadow op-dropdown-open op-menu op-menu-sm op-dropdown-content text-base-content bg-base-100 rounded-box w-52 ${
isOpen ? "" : "hidden"
}`}
>
<li onClick={() => openInNewTab("https://docs.opensignlabs.com")}>
<span>
<i className="fa-light fa-book"></i> {t("docs")}
</span>
</li>
<li
onClick={() => {
setIsOpen(false);
navigate("/profile");
}}
>
<span>
<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>
{!isConsole && (
<>
<li
onClick={() =>
openInNewTab("https://docs.opensignlabs.com")
}
>
<span>
<i className="fa-light fa-book"></i> {t("docs")}
</span>
</li>
<li
onClick={() => {
setIsOpen(false);
navigate("/profile");
}}
>
<span>
<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={closeDropdown}>
<span>
<i className="fa-light fa-arrow-right-from-bracket"></i>{" "}
@@ -1,7 +1,9 @@
import React from "react";
import { Document, Page } from "react-pdf";
import { Stage, Layer, Rect, Text } from "react-konva";
import { useTranslation } from "react-i18next";
const RenderDebugPdf = (props) => {
const { t } = useTranslation();
return (
<div>
<div className="sticky top-0 p-[10px] z-10 bg-white border-[1px] border-[gray] my-[5px]">
@@ -12,10 +14,9 @@ const RenderDebugPdf = (props) => {
onMouseMove={props.handleMouseMoveDiv}
>
<Document
onLoadError={() => {
props.setPdfLoadFail(false);
}}
loading={"Loading Document.."}
onLoadError={() => props.setPdfLoadFail(false)}
loading={t("loading-doc")}
error={<p className="mx-2">{t("failed-to-load-refresh-page")}</p>}
onLoadSuccess={props.pageDetails}
ref={props.pdfRef}
file={props.pdfUrl}
+4 -2
View File
@@ -2,10 +2,12 @@ import React from "react";
import { Helmet } from "react-helmet";
function Title({ title, drive }) {
const appName =
"OpenSign™";
return (
<Helmet>
<title>{drive ? title : `${title} - OpenSign™`}</title>
<meta name="description" content={`${title} - OpenSign™`} />
<title>{drive ? title : `${title} - ${appName}`}</title>
<meta name="description" content={`${title} - ${appName}`} />
<link
rel="icon"
type="image/png"
@@ -346,7 +346,6 @@ const DashboardCard = (props) => {
id={props.Label}
iconColor={"white"}
message={t(`tour-mssg.${props.Label}`)}
// {props?.Data?.tourMessage}
/>
</div>
</div>
@@ -347,12 +347,14 @@ function DriveBody(props) {
}
const signersName = () => {
const getSignersName = signerExist.map((data) => data.Name);
const signerName = getSignersName.join(", ");
const getSignersName =
signerExist?.length > 0 && signerExist?.map((data) => data?.Name || "");
const signerName =
getSignersName?.length > 0 ? getSignersName?.join(", ") : "";
return (
<span className="text-[12px] font-medium w-[90%] break-words">
{signerName}
{signerName && signerName}
</span>
);
};
@@ -626,7 +628,7 @@ function DriveBody(props) {
</tr>
</thead>
<tbody>
{props.pdfData.map((data, ind) => {
{props?.pdfData?.map((data, ind) => {
return (
<React.Fragment key={ind}>
{handleFolderData(data, ind, "table")}
@@ -17,8 +17,8 @@ const AddRoleModal = (props) => {
onChange={(e) => props.setRoleName(e.target.value)}
placeholder={
props.signersdata.length > 0
? "User " + (props.signersdata.length + 1)
: "User 1"
? "Role " + (props.signersdata.length + 1)
: "Role 1"
}
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs mt-1"
/>
@@ -4,6 +4,8 @@ import ModalUi from "../../primitives/ModalUi";
function AgreementContent(props) {
const { t } = useTranslation();
const appName =
"OpenSign™";
const h2Style = "text-base-content font-medium text-lg";
const ulStyle = "list-disc px-4 py-3";
const handleOnclick = () => {
@@ -21,78 +23,94 @@ function AgreementContent(props) {
>
<div className="h-[100%] p-[20px]">
<h2 className={h2Style}>{t("term-cond-h")}</h2>
<span className="mt-2">{t("term-cond-p1")}</span>
<div className="h-[1px] bg-[#9f9f9f] w-full my-[15px]"></div>
<span className="mt-2">
{t("term-cond-p1", { appName: appName })}
</span>
<hr className="bg-[#9f9f9f] w-full my-[15px]" />
<h2 className={h2Style}>{t("term-cond-h1")}</h2>
<span className="mt-2">{t("term-cond-p2")}</span>
<span className="mt-2">
{t("term-cond-p2", { appName: appName })}
</span>
<ul className={ulStyle}>
<li>{t("term-cond-p3")}</li>
<li>{t("term-cond-p3", { appName: appName })}</li>
<li>{t("term-cond-p4")}</li>
</ul>
<div className="h-[1px] bg-[#9f9f9f] w-full my-[15px]"></div>
<hr className="bg-[#9f9f9f] w-full my-[15px]" />
<h2 className={h2Style}>{t("term-cond-h2")}</h2>
<span className="mt-2">{t("term-cond-p5")}</span>
<ul className={ulStyle}>
<li>{t("term-cond-p6")}</li>
<li>{t("term-cond-p7")}</li>
<li>{t("term-cond-p6", { appName: appName })}</li>
<li>{t("term-cond-p7", { appName: appName })}</li>
<li>{t("term-cond-p8")}</li>
</ul>
<div className="h-[1px] bg-[#9f9f9f] w-full my-[15px]"></div>
<hr className="bg-[#9f9f9f] w-full my-[15px]" />
<h2 className={h2Style}>{t("term-cond-h3")}</h2>
<span className="mt-2">{t("term-cond-p9")}</span>
<ul className={ulStyle}>
<li>{t("term-cond-p10")}</li>
<li>{t("term-cond-p11")}</li>
</ul>
<div className="h-[1px] bg-[#9f9f9f] w-full my-[15px]"></div>
<hr className="bg-[#9f9f9f] w-full my-[15px]" />
<h2 className={h2Style}>{t("term-cond-h4")}</h2>
<span className="mt-2">{t("term-cond-p12")}</span>
<span className="mt-2">
{t("term-cond-p12", { appName: appName })}
</span>
<ul className={ulStyle}>
<li>{t("term-cond-p13")}</li>
<li>{t("term-cond-p14")}</li>
<li>{t("term-cond-p15")}</li>
</ul>
<span>{t("term-cond-p16")}</span>
<div className="h-[1px] bg-[#9f9f9f] w-full my-[15px]"></div>
<span>{t("term-cond-p16", { appName: appName })}</span>
<hr className="bg-[#9f9f9f] w-full my-[15px]" />
<h2 className={h2Style}>{t("term-cond-h5")}</h2>
<p>{t("term-cond-p17")}</p>
<p>{t("term-cond-p17", { appName: appName })}</p>
<p className="mt-2">{t("term-cond-p18")}</p>
<div className="h-[1px] bg-[#9f9f9f] w-full my-[15px]"></div>
<hr className="bg-[#9f9f9f] w-full my-[15px]" />
<h2 className={h2Style}>{t("term-cond-h6")}</h2>
<span className="mt-2">{t("term-cond-p19")}</span>
<ul className={ulStyle}>
<li>{t("term-cond-p20")}</li>
<li>{t("term-cond-p21")}</li>
<li>{t("term-cond-p22")}</li>
<li>{t("term-cond-p22", { appName: appName })}</li>
</ul>
<div className="h-[1px] bg-[#9f9f9f] w-full my-[15px]"></div>
<hr className="bg-[#9f9f9f] w-full my-[15px]" />
<h2 className={h2Style}>{t("term-cond-h7")}</h2>
<span className="mt-2">{t("term-cond-p23")}</span>
<span className="mt-2">
{t("term-cond-p23", { appName: appName })}
</span>
<ul className={ulStyle}>
<li>{t("term-cond-p24")}</li>
<li>{t("term-cond-p25")}</li>
</ul>
<div className="h-[1px] bg-[#9f9f9f] w-full my-[15px]"></div>
<hr className="bg-[#9f9f9f] w-full my-[15px]" />
<h2 className={h2Style}>{t("term-cond-h8")}</h2>
<span className="mt-2">{t("term-cond-p26")}</span>
<div className="h-[1px] bg-[#9f9f9f] w-full my-[15px]"></div>
<span className="mt-2">
{t("term-cond-p26", { appName: appName })}
</span>
<hr className="bg-[#9f9f9f] w-full my-[15px]" />
<h2 className={h2Style}>{t("term-cond-h9")}</h2>
<span className="mt-2">{t("term-cond-p27")}</span>
<div className="h-[1px] bg-[#9f9f9f] w-full my-[15px]"></div>
<span className="mt-2 font-medium">{t("term-cond-p28")}</span>
<div className="h-[1px] bg-[#9f9f9f] w-full my-[15px]"></div>
<span className="mt-2">{t("term-cond-p29")}</span>
<a
href="www.opensignlabs.com"
target="_blank"
className="text-blue-700 cursor-pointer"
>
{" "}
www.opensignlabs.com{" "}
</a>
<span>{t("term-cond-p30")}</span>
<span className="font-medium"> support@opensignlabs.com </span>
<div className="h-[1px] bg-[#9f9f9f] w-full my-[15px]"></div>
<span className="mt-2">
{t("term-cond-p27", { appName: appName })}
</span>
<hr className="bg-[#9f9f9f] w-full my-[15px]" />
<span className="mt-2 font-medium">
{t("term-cond-p28", { appName: appName })}
</span>
<hr className="bg-[#9f9f9f] w-full my-[15px]" />
<span className="mt-2">
{t("term-cond-p29", { appName: appName })}
</span>
<a
href="www.opensignlabs.com"
target="_blank"
className="text-blue-700 cursor-pointer"
>
www.opensignlabs.com
</a>
<span>{t("term-cond-p30")}</span>
<span className="font-medium"> support@opensignlabs.com </span>
<hr className="bg-[#9f9f9f] w-full my-[15px]" />
<div className="mt-6 flex justify-start gap-2">
<button
onClick={() => handleOnclick()}
@@ -54,6 +54,9 @@ function AgreementSign(props) {
{t("agrre-button")}
</button>
</div>
<div className="mt-2">
<span className="text-[11px]">{t("agreement-note")}</span>
</div>
</div>
</div>
{isShowAgreeTerms && (
@@ -1,276 +0,0 @@
import React, { useEffect, useState } from "react";
import opensignLogo from "../../assets/images/logo.png";
import {
Page,
Text,
View,
Document,
StyleSheet,
Image
} from "@react-pdf/renderer";
function Certificate({ pdfData }) {
const [isMultiSigners, setIsMultiSigners] = useState();
const [multiSigner, setMultiSigners] = useState([]);
const [isLoad, setIsLoad] = useState(false);
useEffect(() => {
handleSignerData();
// eslint-disable-next-line
}, []);
const handleSignerData = () => {
const checkSigners = pdfData.filter((data) => data.Signers);
if (checkSigners && checkSigners.length > 0) {
setIsMultiSigners(true);
const checkSignSigners =
pdfData[0].AuditTrail &&
pdfData[0].AuditTrail.length > 0 &&
pdfData[0].AuditTrail.filter((data) => data.Activity === "Signed");
setMultiSigners(checkSignSigners);
} else {
setIsMultiSigners(false);
}
setIsLoad(true);
};
const styles = StyleSheet.create({
page: {
borderRadius: "5px",
padding: "10px",
backgroundColor: "white"
},
section1: {
border: "1px solid rgb(177, 174, 174)",
padding: "20px"
},
textStyle: {
fontWeight: "bold",
fontSize: "11px",
marginBottom: "10px"
},
textStyle2: {
fontWeight: "600",
fontSize: "11px",
marginBottom: "10px",
color: "gray"
},
image: {
width: "71px",
height: "17px"
}
});
const generatedDate = () => {
const newDate = new Date();
const utcTime = newDate.toUTCString();
return (
<Text
style={{
color: "gray",
fontSize: "10px"
}}
>
Generated On {utcTime}
</Text>
);
};
const changeCompletedDate = () => {
const completedOn = pdfData[0].updatedAt;
const newDate = new Date(completedOn);
const utcTime = newDate.toUTCString();
return <Text style={styles.textStyle2}>{utcTime}</Text>;
};
const signerName = (data) => {
const getSignerName = pdfData[0].Signers.filter(
(sign) => sign.objectId === data.UserPtr.objectId
);
return (
getSignerName[0] &&
getSignerName.length > 0 && (
<>
<Text style={styles.textStyle}>
Name : &nbsp;
<Text style={styles.textStyle2}>{getSignerName[0].Name}</Text>
</Text>
<Text style={styles.textStyle}>
Email : &nbsp;
<Text style={styles.textStyle2}>{getSignerName[0].Email}</Text>
</Text>
</>
)
);
};
return (
isLoad && (
<Document>
{/** Page defines a single page of content. */}
<Page size="A4" style={styles.page}>
<View style={styles.section1}>
<View
style={{
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
marginBottom: "30px"
}}
>
<Image src={opensignLogo} style={styles.image} />
{generatedDate()}
</View>
<View style={{ justifyContent: "center" }}>
<Text
style={{
textAlign: "center",
fontSize: "20px",
fontWeight: "bold",
color: "#31bceb",
marginBottom: "10px"
}}
>
{" "}
Certificate of Completion
</Text>
<View style={{ border: "1px solid #bdbbbb" }}></View>
<View>
<View>
<Text
style={{
fontSize: "16px",
fontWeight: "bold",
color: "#31bceb",
margin: "10px 0px 10px 0px"
}}
>
Summary
</Text>
</View>
<View style={{ display: "flex", flexDirection: "column" }}>
<Text style={styles.textStyle}>
Document ID : &nbsp;
<Text style={styles.textStyle2}>{pdfData[0].objectId}</Text>
</Text>
<Text style={styles.textStyle}>
Document Name : &nbsp;
<Text style={styles.textStyle2}>{pdfData[0].Name}</Text>
</Text>
<Text style={styles.textStyle}>
Organization : &nbsp;
<Text style={styles.textStyle2}>
{pdfData[0].ExtUserPtr.Company}
</Text>
</Text>
<Text style={styles.textStyle}>
Completed on : &nbsp;{changeCompletedDate()}
</Text>
{multiSigner && multiSigner.length > 0 && (
<Text style={styles.textStyle}>
Signers : &nbsp;
<Text style={styles.textStyle2}>
{multiSigner.length}
</Text>
</Text>
)}
</View>
{isMultiSigners ? (
<View style={{ display: "flex", flexDirection: "column" }}>
<Text
style={{
fontSize: "16px",
fontWeight: "bold",
color: "#31bceb",
margin: "10px 0px 10px 0px"
}}
>
Recipients
</Text>
<View>
{multiSigner &&
multiSigner.map((data, ind) => {
return (
<View
key={ind}
style={{
display: "flex",
flexDirection: "column"
}}
>
<View
style={{
border: "0.4px solid #bdbbbb",
marginBottom: "10px"
}}
></View>
{signerName(data)}
<Text style={styles.textStyle}>
Accessed from : &nbsp;
<Text style={styles.textStyle2}>
{data.ipAddress}
</Text>
</Text>
</View>
);
})}
</View>
</View>
) : (
<View style={{ display: "flex", flexDirection: "column" }}>
<Text
style={{
fontSize: "16px",
fontWeight: "bold",
color: "#31bceb",
margin: "10px 0px 10px 0px"
}}
>
Recipients
</Text>
<Text style={styles.textStyle}>
Signers : &nbsp;<Text style={styles.textStyle2}>1</Text>
</Text>
<Text style={styles.textStyle}>
Name : &nbsp;
<Text style={styles.textStyle2}>
{pdfData[0].ExtUserPtr.Name}
</Text>
</Text>
<Text style={styles.textStyle}>
Email : &nbsp;
<Text style={styles.textStyle2}>
{pdfData[0].ExtUserPtr.Email}
</Text>
</Text>
<Text style={styles.textStyle}>
Accessed from : &nbsp;
<Text style={styles.textStyle2}>
{pdfData[0].AuditTrail &&
pdfData[0].AuditTrail[0].ipAddress}
</Text>
</Text>
<Text style={styles.textStyle}>
Signed on : &nbsp;{changeCompletedDate()}
</Text>
</View>
)}
</View>
</View>
</View>
</Page>
</Document>
)
);
}
export default Certificate;
@@ -30,15 +30,19 @@ function DraftDocument() {
documentData === "Error: Something went wrong!" ||
(documentData.result && documentData.result.error)
) {
setIsLoading({
isLoader: false,
message: "Error: Something went wrong!"
});
if (documentData?.result?.error?.includes("deleted")) {
setIsLoading({
isLoader: false,
message: t("document-deleted")
});
} else {
setIsLoading({
isLoader: false,
message: t("something-went-wrong-mssg")
});
}
} else {
setIsLoading({
isLoader: false,
message: "No data found!"
});
setIsLoading({ isLoader: false, message: t("no-data") });
}
};
+413 -214
View File
@@ -1,18 +1,34 @@
import React, {
useState,
} from "react";
import React, { useState, useRef } from "react";
import {
base64ToArrayBuffer,
convertBase64ToFile,
generatePdfName,
getFileName
} from "../../constant/Utils";
import {
maxDescriptionLength,
maxNoteLength,
maxTitleLength
} from "../../constant/const";
import { useTranslation } from "react-i18next";
import { Tooltip } from "react-tooltip";
import SignersInput from "../shared/fields/SignersInput";
import { PDFDocument } from "pdf-lib";
import ModalUi from "../../primitives/ModalUi";
import { SaveFileSize } from "../../constant/saveFileSize";
const EditTemplate = ({
title,
handleClose,
pdfbase64,
template,
onSuccess,
setPdfArrayBuffer,
setPdfBase64Url
}) => {
const appName = "OpenSign™";
const { t } = useTranslation();
const inputFileRef = useRef(null);
const [formData, setFormData] = useState({
Name: template?.Name || "",
Note: template?.Note || "",
@@ -33,6 +49,31 @@ const EditTemplate = ({
AllowModifications: template?.AllowModifications || false,
TimeToCompleteDays: template?.TimeToCompleteDays || 15
});
const [isUpdate, setIsUpdate] = useState(false);
const [showConfirm, setShowConfirm] = useState(false);
const [uploadPdf, setUploadPdf] = useState({
name: "",
base64: "",
url: ""
});
const handleDrop = (e) => {
e.preventDefault();
const file = e.dataTransfer.files[0];
handleFile(file);
};
const handleFile = (file) => {
if (file && file.type === "application/pdf") {
handleReplaceFileValdition(file);
// You can handle the file here
} else {
alert("Only pdf files are allowed.");
if (inputFileRef.current) inputFileRef.current.value = "";
}
};
const handleDragOver = (e) => {
e.preventDefault();
};
// `isValidURL` is used to check valid webhook url
function isValidURL(value) {
@@ -45,9 +86,63 @@ const EditTemplate = ({
}
const handleStrInput = (e) => {
setIsUpdate(true);
setFormData({ ...formData, [e.target.name]: e.target.value });
};
const getPdfMetadataHash = async (pdfBytes) => {
const pdfDoc = await PDFDocument.load(pdfBytes);
const pages = pdfDoc.getPages();
const metaString = pages
.map((page, index) => {
const { width, height } = page.getSize();
return `${index + 1}:${Math.round(width)}x${Math.round(height)}`;
})
.join("|");
const encoder = new TextEncoder();
const data = encoder.encode(metaString);
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
return Array.from(new Uint8Array(hashBuffer))
.map((b) => b.toString(16).padStart(2, "0"))
.join("");
};
const handleFileInput = async (e) => {
const file = e.target.files[0];
if (!file) return;
handleReplaceFileValdition(file);
};
const handleReplaceFileValdition = async (file) => {
try {
const basePdfBytes = base64ToArrayBuffer(pdfbase64);
const expectedHash = await getPdfMetadataHash(basePdfBytes);
const fileReader = new FileReader();
fileReader.onload = async (event) => {
const uploadedPdfBytes = event.target.result;
const uploadedHash = await getPdfMetadataHash(uploadedPdfBytes);
if (expectedHash === uploadedHash) {
const arrayBuffer = uploadedPdfBytes;
const uint8Array = new Uint8Array(arrayBuffer);
const binaryString = Array.from(uint8Array)
.map((b) => String.fromCharCode(b))
.join("");
const base64 = btoa(binaryString);
const pdfName = generatePdfName(16);
setIsUpdate(true);
setUploadPdf((prev) => ({ ...prev, name: pdfName, base64: base64 }));
// alert("✅ PDFs match (based on page number, width, height)");
} else {
alert("❌ PDF do NOT match based on page number, width, height");
if (inputFileRef.current) inputFileRef.current.value = "";
}
};
fileReader.readAsArrayBuffer(file);
} catch (err) {
alert("Error: " + err.message);
if (inputFileRef.current) inputFileRef.current.value = "";
}
};
// Define a function to handle form submission
const handleSubmit = async (e) => {
e.preventDefault();
@@ -56,12 +151,44 @@ const EditTemplate = ({
alert(t("invalid-redirect-url"));
return;
}
if (formData?.Name?.length > maxTitleLength) {
alert(t("title-length-alert"));
return;
}
if (formData?.Note?.length > maxNoteLength) {
alert(t("note-length-alert"));
return;
}
if (formData?.Description?.length > maxDescriptionLength) {
alert(t("description-length-alert"));
return;
}
let pdfUrl;
if (uploadPdf?.base64) {
pdfUrl = await convertBase64ToFile(uploadPdf.name, uploadPdf.base64);
setUploadPdf((prev) => ({ ...prev, url: pdfUrl }));
const pdfBuffer = base64ToArrayBuffer(uploadPdf.base64);
setPdfArrayBuffer && setPdfArrayBuffer(pdfBuffer);
setPdfBase64Url && setPdfBase64Url(uploadPdf.base64);
const tenantId =
localStorage.getItem("TenantId") ||
template?.ExtUserPtr?.TenantId?.objectId;
const buffer = atob(uploadPdf.base64);
SaveFileSize(buffer.length, pdfUrl, tenantId);
}
const isChecked = formData.SendinOrder === "true" ? true : false;
const isTourEnabled = formData?.IsTourEnabled === "false" ? false : true;
const AutoReminder = formData?.AutomaticReminders || false;
const IsEnableOTP = formData.IsEnableOTP === "true" ? true : false;
const allowModify = formData?.AllowModifications || false;
let reminderDate = {};
const remindOnceInEvery = formData?.RemindOnceInEvery;
const TimeToCompleteDays = parseInt(formData?.TimeToCompleteDays);
const reminderCount = TimeToCompleteDays / remindOnceInEvery;
if (AutoReminder && reminderCount > 15) {
alert(t("only-15-reminder-allowed"));
return;
}
if (AutoReminder) {
const RemindOnceInEvery = parseInt(formData?.RemindOnceInEvery);
const ReminderDate = new Date(template?.createdAt);
@@ -70,6 +197,7 @@ const EditTemplate = ({
}
const data = {
...formData,
...(pdfUrl ? { URL: pdfUrl } : {}),
SendinOrder: isChecked,
IsEnableOTP: IsEnableOTP,
IsTourEnabled: isTourEnabled,
@@ -81,6 +209,7 @@ const EditTemplate = ({
// `handleNotifySignChange` is trigger when user change radio of notify on signatures
const handleNotifySignChange = (value) => {
setIsUpdate(true);
setFormData((obj) => ({ ...obj, NotifyOnSignatures: value }));
};
const handleBcc = (data) => {
@@ -90,234 +219,304 @@ const EditTemplate = ({
Name: item?.label,
Email: item?.email
}));
setIsUpdate(true);
setFormData((prev) => ({ ...prev, Bcc: trimEmail }));
}
};
const handleEditTemplateClose = () => {
if (isUpdate) {
setShowConfirm(true);
} else {
handleClose();
}
};
const discardChanges = () => {
setShowConfirm(false);
handleClose();
};
return (
<div className="max-h-[300px] md:max-h-[400px] overflow-y-scroll p-[10px]">
<div className="text-base-content">
<form onSubmit={handleSubmit}>
<div className="mb-[0.35rem]">
<label htmlFor="name" className="text-[13px]">
{t("report-heading.File")}
</label>
<div className="op-input op-input-bordered op-input-sm focus:outline-none py-2 font-semibold w-full text-xs">
{getFileName(template.URL)}
</div>
<ModalUi
isOpen
title={isUpdate ? `${title} (unsaved)` : title}
handleClose={handleEditTemplateClose}
>
<ModalUi isOpen={showConfirm} showClose={false}>
<div className="p-[20px]">
<p className="text-base font-normal text-base-content py-[5px] md:py-[6px] px-[5px]">
{t("unsaved-changes-discard-them?")}
</p>
<div className="flex items-center mt-2.5 gap-2 md:gap-3 text-white">
<button
className="op-btn op-btn-primary px-6"
onClick={discardChanges}
>
{t("yes-discard")}
</button>
<button
className="op-btn op-btn-secondary px-4 md:px-6"
onClick={() => setShowConfirm(false)}
>
{t("cancel")}
</button>
</div>
<div className="mb-[0.35rem]">
<label htmlFor="name" className="text-[13px]">
{t("Title")}
<span className="text-[13px] text-[red]"> *</span>
</label>
<input
type="text"
name="Name"
value={formData.Name}
onChange={(e) => handleStrInput(e)}
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
onInput={(e) => e.target.setCustomValidity("")}
required
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
/>
</div>
<div className="mb-[0.35rem]">
<label htmlFor="Note" className="text-[13px]">
{t("report-heading.Note")}
</label>
<input
type="text"
name="Note"
id="Note"
value={formData.Note}
onChange={(e) => handleStrInput(e)}
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
/>
</div>
<div className="mb-[0.35rem]">
<label htmlFor="Description" className="text-[13px]">
{t("description")}
</label>
<input
type="text"
name="Description"
id="Description"
value={formData.Description}
onChange={(e) => handleStrInput(e)}
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
/>
</div>
<div className="mb-[0.35rem]">
<label className="text-[13px]">{t("send-in-order")}</label>
<div className="flex flex-col md:flex-row md:gap-4">
<div className="flex items-center gap-[8px] ml-[8px] mb-[5px]">
<input
type="radio"
value={"true"}
className="op-radio op-radio-xs"
name="SendinOrder"
checked={formData.SendinOrder === "true"}
onChange={handleStrInput}
/>
<div className="text-[12px]">{t("yes")}</div>
</div>
</ModalUi>
<div className="max-h-[300px] md:max-h-[400px] overflow-y-scroll p-[10px]">
<div className="text-base-content">
<form onSubmit={handleSubmit}>
<div className="mb-[0.35rem]">
<label htmlFor="name" className="text-[13px]">
{t("report-heading.File")}
</label>
<div
className="border-[1.5px] border-dashed border-gray-300 rounded-lg px-4 py-6 text-center text-gray-500 bg-white cursor-pointer hover:border-base-content transition"
onDrop={handleDrop}
onDragOver={handleDragOver}
onClick={() => inputFileRef?.current?.click()}
>
<label
htmlFor="fileUpload"
className="cursor-pointer text-center mb-0"
>
Browse or drag & drop a new file to replace the existng one
</label>
</div>
<div className="flex items-center gap-[8px] ml-[8px] mb-[5px]">
<input
type="radio"
value={"false"}
name="SendinOrder"
className="op-radio op-radio-xs"
checked={formData.SendinOrder === "false"}
onChange={handleStrInput}
/>
<div className="text-[12px]">{t("no")}</div>
<input
ref={inputFileRef}
type="file"
className="hidden"
accept="application/pdf"
onChange={(e) => handleFileInput(e)}
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
/>
{uploadPdf?.name && (
<div
onClick={() => inputFileRef?.current?.click()}
className="mt-2 cursor-pointer op-input op-input-bordered op-input-sm focus:outline-none py-2 font-semibold w-full text-xs"
>
selected:{" "}
{uploadPdf?.url
? `${uploadPdf?.name}.pdf`
: getFileName(template.URL)}
</div>
)}
</div>
<div className="mb-[0.35rem]">
<label htmlFor="name" className="text-[13px]">
{t("Title")}
<span className="text-[13px] text-[red]"> *</span>
</label>
<input
type="text"
name="Name"
value={formData.Name}
onChange={(e) => handleStrInput(e)}
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
required
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
/>
</div>
<div className="mb-[0.35rem]">
<label htmlFor="Note" className="text-[13px]">
{t("report-heading.Note")}
</label>
<input
type="text"
name="Note"
id="Note"
value={formData.Note}
onChange={(e) => handleStrInput(e)}
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
/>
</div>
<div className="mb-[0.35rem]">
<label htmlFor="Description" className="text-[13px]">
{t("description")}
</label>
<input
type="text"
name="Description"
id="Description"
value={formData.Description}
onChange={(e) => handleStrInput(e)}
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
/>
</div>
<div className="mb-[0.35rem]">
<label className="text-[13px]">{t("send-in-order")}</label>
<div className="flex flex-col md:flex-row md:gap-4">
<div className="flex items-center gap-[8px] ml-[8px] mb-[5px]">
<input
type="radio"
value={"true"}
className="op-radio op-radio-xs"
name="SendinOrder"
checked={formData.SendinOrder === "true"}
onChange={handleStrInput}
/>
<div className="text-[12px]">{t("yes")}</div>
</div>
<div className="flex items-center gap-[8px] ml-[8px] mb-[5px]">
<input
type="radio"
value={"false"}
name="SendinOrder"
className="op-radio op-radio-xs"
checked={formData.SendinOrder === "false"}
onChange={handleStrInput}
/>
<div className="text-[12px]">{t("no")}</div>
</div>
</div>
</div>
</div>
<div className="text-xs mt-3">
<label className="block">
<span>
{t("enable-tour")}
<a data-tooltip-id="istourenabled-tooltip" className="ml-1">
<div className="text-xs mt-3">
<label className="block">
<span>
{t("enable-tour")}
<a data-tooltip-id="istourenabled-tooltip" className="ml-1">
<sup>
<i className="fa-light fa-question rounded-full border-[#33bbff] text-[#33bbff] text-[13px] border-[1px] py-[1.5px] px-[4px]"></i>
</sup>
</a>{" "}
</span>
<Tooltip id="istourenabled-tooltip" className="z-50">
<div className="max-w-[200px] md:max-w-[450px]">
<p className="font-bold">{t("enable-tour")}</p>
<div className="p-[5px]">
<ol className="list-disc">
<li>
<span className="font-bold">{t("yes")}: </span>
<span>{t("istourenabled-help.p1")}</span>
</li>
<li>
<span className="font-bold">{t("no")}: </span>
<span>{t("istourenabled-help.p2")}</span>
</li>
</ol>
</div>
<p>{t("istourenabled-help.p3", { appName: appName })}</p>
</div>
</Tooltip>
</label>
<div className="flex flex-col md:flex-row md:gap-4">
<div className="flex items-center gap-2 ml-2 mb-1">
<input
type="radio"
value={"true"}
className="op-radio op-radio-xs"
name="IsTourEnabled"
checked={formData.IsTourEnabled === "true"}
onChange={handleStrInput}
/>
<div className="text-center">{t("yes")}</div>
</div>
<div className="flex items-center gap-2 ml-2 mb-1">
<input
type="radio"
value={"false"}
name="IsTourEnabled"
className="op-radio op-radio-xs"
checked={formData.IsTourEnabled === "false"}
onChange={handleStrInput}
/>
<div className="text-center">{t("no")}</div>
</div>
</div>
</div>
<div className="text-xs mt-3">
<label>
{t("notify-on-signatures")}
<a data-tooltip-id="nos-tooltip" className="ml-1">
<sup>
<i className="fa-light fa-question rounded-full border-[#33bbff] text-[#33bbff] text-[13px] border-[1px] py-[1.5px] px-[4px]"></i>
</sup>
</a>{" "}
</span>
<Tooltip id="istourenabled-tooltip" className="z-50">
<div className="max-w-[200px] md:max-w-[450px]">
<p className="font-bold">{t("enable-tour")}</p>
<p className="p-[5px]">
<ol className="list-disc">
<li>
<span className="font-bold">{t("yes")}: </span>
<span>{t("istourenabled-help.p1")}</span>
</li>
<li>
<span className="font-bold">{t("no")}: </span>
<span>{t("istourenabled-help.p2")}</span>
</li>
</ol>
</p>
<p>{t("istourenabled-help.p3")}</p>
<Tooltip id="nos-tooltip" className="z-[999]">
<div className="max-w-[200px] md:max-w-[450px] text-[11px]">
<p className="font-bold">{t("notify-on-signatures")}</p>
<p>{t("notify-on-signatures-help.p1")}</p>
<p>{t("notify-on-signatures-help.note")}</p>
</div>
</Tooltip>
</label>
<div className="flex flex-col md:flex-row md:gap-4">
<div className={`flex items-center gap-2 ml-2 mb-1`}>
<input
className="mr-[2px] op-radio op-radio-xs"
type="radio"
onChange={() => handleNotifySignChange(true)}
checked={formData.NotifyOnSignatures === true}
/>
<div className="text-center">{t("yes")}</div>
</div>
<div className={`flex items-center gap-2 ml-2 mb-1`}>
<input
className="mr-[2px] op-radio op-radio-xs"
type="radio"
onChange={() => handleNotifySignChange(false)}
checked={formData.NotifyOnSignatures === false}
/>
<div className="text-center">{t("no")}</div>
</div>
</Tooltip>
</label>
<div className="flex flex-col md:flex-row md:gap-4">
<div className="flex items-center gap-2 ml-2 mb-1">
<input
type="radio"
value={"true"}
className="op-radio op-radio-xs"
name="IsTourEnabled"
checked={formData.IsTourEnabled === "true"}
onChange={handleStrInput}
/>
<div className="text-center">{t("yes")}</div>
</div>
<div className="flex items-center gap-2 ml-2 mb-1">
<input
type="radio"
value={"false"}
name="IsTourEnabled"
className="op-radio op-radio-xs"
checked={formData.IsTourEnabled === "false"}
onChange={handleStrInput}
/>
<div className="text-center">{t("no")}</div>
</div>
</div>
</div>
<div className="text-xs mt-3">
<label>
{t("notify-on-signatures")}
<a data-tooltip-id="nos-tooltip" className="ml-1">
<sup>
<i className="fa-light fa-question rounded-full border-[#33bbff] text-[#33bbff] text-[13px] border-[1px] py-[1.5px] px-[4px]"></i>
</sup>
</a>{" "}
<Tooltip id="nos-tooltip" className="z-[999]">
<div className="max-w-[200px] md:max-w-[450px] text-[11px]">
<p className="font-bold">{t("notify-on-signatures")}</p>
<p>{t("notify-on-signatures-help.p1")}</p>
<p>{t("notify-on-signatures-help.note")}</p>
</div>
</Tooltip>
</label>
<div className="flex flex-col md:flex-row md:gap-4">
<div
className={
`flex items-center gap-2 ml-2 mb-1`
}
>
<input
className="mr-[2px] op-radio op-radio-xs"
type="radio"
onChange={() => handleNotifySignChange(true)}
checked={formData.NotifyOnSignatures === true}
/>
<div className="text-center">{t("yes")}</div>
</div>
<div
className={
`flex items-center gap-2 ml-2 mb-1`
}
>
<input
className="mr-[2px] op-radio op-radio-xs"
type="radio"
onChange={() => handleNotifySignChange(false)}
checked={formData.NotifyOnSignatures === false}
/>
<div className="text-center">{t("no")}</div>
</div>
<div className="text-xs mt-3">
<SignersInput
label={t("Bcc")}
initialData={template?.Bcc}
onChange={handleBcc}
helptextZindex={50}
helpText={t("bcc-help")}
isCaptureAllData
/>
</div>
</div>
<div className="text-xs mt-3">
<SignersInput
label={t("Bcc")}
initialData={template?.Bcc}
onChange={handleBcc}
helptextZindex={50}
helpText={t("bcc-help")}
isCaptureAllData
/>
</div>
<div className="text-xs mt-2">
<label className="block">Redirect Url</label>
<input
name="RedirectUrl"
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
value={formData.RedirectUrl}
onChange={handleStrInput}
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
onInput={(e) => e.target.setCustomValidity("")}
/>
</div>
<div className="text-xs mt-2">
<label className="block">
{t("time-to-complete")}
<span className="text-red-500 text-[13px]">*</span>
</label>
<input
type="number"
name="TimeToCompleteDays"
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
value={formData.TimeToCompleteDays}
onChange={(e) => handleStrInput(e)}
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
onInput={(e) => e.target.setCustomValidity("")}
required
/>
</div>
<div className="mt-[1rem] flex justify-start">
<button type="submit" className="op-btn op-btn-primary">
{t("submit")}
</button>
</div>
</form>
<div className="text-xs mt-2">
<label className="block">Redirect Url</label>
<input
name="RedirectUrl"
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
value={formData.RedirectUrl}
onChange={handleStrInput}
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
/>
</div>
<div className="text-xs mt-2">
<label className="block">
{t("time-to-complete")}
<span className="text-red-500 text-[13px]">*</span>
</label>
<input
type="number"
name="TimeToCompleteDays"
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
value={formData.TimeToCompleteDays}
onChange={(e) => handleStrInput(e)}
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
required
/>
</div>
<div className="mt-[1rem] flex justify-start">
<button type="submit" className="op-btn op-btn-primary">
{t("submit")}
</button>
</div>
</form>
</div>
</div>
</div>
</ModalUi>
);
};
@@ -1,20 +1,17 @@
import React, { useState } from "react";
import axios from "axios";
import { handleToPrint } from "../../constant/Utils";
import { themeColor, emailRegex } from "../../constant/const";
import { emailRegex } from "../../constant/const";
import Loader from "../../primitives/Loader";
import ModalUi from "../../primitives/ModalUi";
import { useTranslation } from "react-i18next";
import Parse from "parse";
function EmailComponent({
isEmail,
pdfUrl,
setIsEmail,
setSuccessEmail,
pdfDetails,
sender,
setIsAlert,
extUserId,
setIsDownloadModal
}) {
const { t } = useTranslation();
@@ -24,81 +21,14 @@ function EmailComponent({
const [emailErr, setEmailErr] = useState(false);
const [isDownloading, setIsDownloading] = useState("");
const isAndroid = /Android/i.test(navigator.userAgent);
//function for send email
const sendEmail = async () => {
const pdfName = pdfDetails[0]?.Name;
setIsLoading(true);
let sendMail;
const docId = pdfDetails?.[0]?.objectId || "";
let presignedUrl = pdfUrl;
try {
const axiosRes = await axios.post(
`${localStorage.getItem("baseUrl")}/functions/getsignedurl`,
{
url: pdfUrl,
docId: docId,
},
{
headers: {
"content-type": "Application/json",
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
}
}
);
presignedUrl = axiosRes.data.result;
} catch (err) {
console.log("err in getsignedurl", err);
}
for (let i = 0; i < emailList.length; i++) {
try {
const imgPng =
"https://qikinnovation.ams3.digitaloceanspaces.com/logo.png";
let url = `${localStorage.getItem("baseUrl")}functions/sendmailv3`;
const headers = {
"Content-Type": "application/json",
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
sessionToken: localStorage.getItem("accesstoken")
};
const openSignUrl = "https://www.opensignlabs.com/contact-us";
const themeBGcolor = themeColor;
let params = {
extUserId: extUserId,
pdfName: pdfName,
url: presignedUrl,
recipient: emailList[i],
subject: `${sender.name} has signed the doc - ${pdfName}`,
replyto:
pdfDetails?.[0]?.ExtUserPtr?.Email ||
"",
from:
sender.email,
html:
"<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body> <div style='background-color:#f5f5f5;padding:20px'> <div style='box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;background-color:white;'> <div><img src=" +
imgPng +
" height='50' style='padding:20px,width:170px,height:40px'/> </div><div style='padding:2px;font-family:system-ui; background-color:" +
themeBGcolor +
";'> <p style='font-size:20px;font-weight:400;color:white;padding-left:20px',> Document Copy</p></div><div><p style='padding:20px;font-family:system-ui;font-size:14px'>A copy of the document <strong>" +
pdfName +
" </strong>is attached to this email. Kindly download the document from the attachment.</p></div> </div><div><p>This is an automated email from OpenSign™. For any queries regarding this email, please contact the sender " +
sender.email +
" directly. If you think this email is inappropriate or spam, you may file a complaint with OpenSign™ <a href= " +
openSignUrl +
" target=_blank>here</a> </p></div></div></body></html>"
};
sendMail = await axios.post(url, params, { headers: headers });
} catch (error) {
console.log("error", error);
setIsLoading(false);
setIsEmail(false);
setIsAlert({
isShow: true,
alertMessage: t("something-went-wrong-mssg")
});
}
}
if (sendMail?.data?.result?.status === "success") {
const params = { docId: pdfDetails?.[0]?.objectId, recipients: emailList };
const sendmail = await Parse.Cloud.run("forwarddoc", params);
console.log("sendmail ", sendmail);
if (sendmail?.status === "success") {
setSuccessEmail(true);
setIsEmail(false);
setTimeout(() => {
@@ -177,7 +107,7 @@ function EmailComponent({
</div>
)}
<div className="flex justify-between items-center py-[10px] px-[20px] border-b-[1px] border-base-content">
<span className="text-base-content font-semibold">
<span className="text-base-content font-bold text-sm md:text-lg">
{t("successfully-signed")}
</span>
<div className="flex flex-row">
@@ -186,14 +116,14 @@ function EmailComponent({
onClick={(e) =>
handleToPrint(e, setIsDownloading, pdfDetails)
}
className="op-btn op-btn-neutral op-btn-sm text-[15px]"
className="op-btn op-btn-neutral op-btn-sm text-xs md:text-[15px]"
>
<i className="fa-light fa-print" aria-hidden="true"></i>
{t("print")}
</button>
)}
<button
className="op-btn op-btn-primary op-btn-sm text-[15px] ml-2"
className="op-btn op-btn-primary op-btn-sm text-xs md:text-[15px] ml-2"
onClick={() => {
handleClose();
setIsDownloadModal(true);
@@ -209,12 +139,12 @@ function EmailComponent({
{t("email-mssg")}
</p>
{emailList.length > 0 ? (
<div className="p-0 border-[1.5px] op-border-primary rounded w-full text-[15px]">
<div className="p-0 border-[1px] op-border-primary w-full rounded-md text-[15px] overflow-hidden">
<div className="flex flex-row flex-wrap">
{emailList.map((data, ind) => {
return (
<div
className="flex flex-row items-center op-bg-primary m-[4px] rounded-md py-[5px] px-[10px]"
className="flex flex-row items-center op-bg-primary mx-[2px] mt-[2px] rounded-md py-[5px] px-[10px]"
key={ind}
>
<span className="text-base-100 text-[13px]">
@@ -234,7 +164,7 @@ function EmailComponent({
<input
type="email"
value={emailValue}
className="p-[10px] pb-[20px] rounded w-full text-[15px] bg-transparent outline-none"
className="p-[10px] rounded-md w-full text-[15px] bg-transparent outline-none"
onChange={handleEmailValue}
onKeyDown={handleEnterPress}
onBlur={() => emailValue && handleEnterPress("add")}
@@ -251,7 +181,7 @@ function EmailComponent({
<input
type="email"
value={emailValue}
className="p-[10px] pb-[20px] rounded w-full text-[15px] outline-none bg-transparent border-[1.5px] op-border-primary"
className="p-[10px] pb-[20px] rounded-md w-full text-[15px] outline-none bg-transparent border-[1px] op-border-primary"
onChange={handleEmailValue}
onKeyDown={handleEnterPress}
placeholder={t("enter-email-plaholder")}
@@ -269,7 +199,7 @@ function EmailComponent({
{t("email-error-1")}
</p>
)}
<button
{/* <button
className={`${
emailValue ? "cursor-pointer" : "cursor-default"
} op-btn op-btn-primary op-btn-sm m-2 shadow-md`}
@@ -277,26 +207,27 @@ function EmailComponent({
>
<i className="fa-light fa-plus" aria-hidden="true"></i>
</button>
<div className="bg-[#e3e2e1] mt-[10px] p-[5px] rounded">
<span className="font-bold">{t("report-heading.Note")}: </span>
<span className="text-[15px]">{t("email-error-2")}</span>
</div>
<hr className="w-full my-[15px] bg-base-content" />
<button
type="button"
className="op-btn op-btn-secondary"
onClick={() => emailList.length > 0 && sendEmail()}
>
{t("send")}
</button>
<button
type="button"
className="op-btn op-btn-ghost ml-2"
onClick={() => handleClose()}
>
{t("close")}
</button>
<hr className="w-full my-[15px] bg-base-content" /> */}
<div className="mt-2">
<button
type="button"
className="op-btn op-btn-secondary"
onClick={() => emailList.length > 0 && sendEmail()}
>
{t("send")}
</button>
<button
type="button"
className="op-btn op-btn-ghost ml-2"
onClick={() => handleClose()}
>
{t("close")}
</button>
</div>
</div>
</ModalUi>
)}
@@ -1,305 +0,0 @@
import React, { useState } from "react";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { tomorrow } from "react-syntax-highlighter/dist/esm/styles/prism";
import { copytoData } from "../../constant/Utils";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router";
function EmbedTab(props) {
const { t } = useTranslation();
const navigate = useNavigate();
const tabName = [
{ title: "React/Next.js", icon: "fa-brands fa-react", color: "#61dafb" },
{ title: "JavaScript", icon: "fa-brands fa-js", color: "#ffd43b" },
{ title: "Angular", icon: "fa-brands fa-angular", color: "#ff5733" }
];
const [activeTab, setActiveTab] = useState(0);
// State to track if the code has been copied
const [isCopied, setIsCopied] = useState(false);
const reactCode = [
{
id: 0,
title: "Installation",
codeString: `npm install @opensign/react`
},
{
id: 1,
title: "Usage",
codeString: `
import React from "react";
import Opensign from "@opensign/react";
export default function App() {
return (
<div className="app">
<Opensign
onLoad={() => console.log("success")}
onLoadError={(error) => console.log(error)}
templateId= "${props.templateId ? props.templateId : "#templateId"}"
/>
</div>
);
}
`
}
];
const angularCode = [
{
id: 0,
title: "Installation",
codeString: `npm install @opensign/angular`
},
{
id: 1,
title: "Usage",
codeString: `
import { Component } from '@angular/core';
import { OpensignComponent } from "@opensign/angular"
@Component({
selector:'app-root',
standalone: true,
imports: [OpensignComponent],
template:\`<opensign templateId="${props.templateId ? props.templateId : "#templateId"}"
(onLoad)="handleLoad()"
(onLoadError)="handleError($event)"
></opensign>\`,
})
export class AppComponent {
handleLoad() {
console.log("success");
}
handleError(error: string) {
console.log(error);
}
}
`
}
];
const jsCodeString = `
<script
src= "${window.location.origin}/static/js/public-template.bundle.js"
id="opensign-script"
templateId=${props.templateId ? props.templateId : "#templateId"}
></script>
`;
const handleCopy = (code, ind) => {
copytoData(code);
setIsCopied({ ...isCopied, [ind]: true });
setTimeout(() => setIsCopied(false), 3000);
};
return (
<div className={`${props.templateId && "border-t-[1px] mt-4"}`}>
{props.templateId && (
<h3 className="text-base-content font-bold text-lg pt-[15px] pb-[5px]">
{t("embed-template")}
</h3>
)}
<div className="flex justify-center items-center mt-2">
<div role="tablist" className="op-tabs op-tabs-bordered">
{tabName.map((tabData, ind) => (
<div
onClick={() => setActiveTab(ind)}
key={ind}
role="tab"
className={`${
activeTab === ind ? "op-tab-active" : ""
} op-tab flex items-center pb-10 md:pb-0`}
>
<i
className={`${tabData.icon}`}
style={{ color: tabData.color }}
></i>
<span className="ml-1 text-[10px] font-medium md:font-normal md:text-[15px]">
{tabData.title}
</span>
</div>
))}
</div>
</div>
<div>
{activeTab === 0 ? (
<div className="mt-4">
{reactCode.map((data, ind) => {
return (
<div key={ind}>
<p className="font-medium text-[18px]">
{t(`${data.title}`)}
</p>
{ind === 0 && (
<p className="text-[15px] mt-2">
{t("public-template-mssg-1")}
</p>
)}
<div className="relative p-1">
<div
onClick={() => handleCopy(data.codeString, ind)}
className="absolute top-[20px] right-[20px] cursor-pointer"
>
<i className="fa-light fa-copy text-white mr-[2px]" />
<span className=" text-white">
{isCopied[ind] ? t("copied-code") : t("copy-code")}
</span>
</div>
<SyntaxHighlighter
customStyle={{
borderRadius: "15px"
}}
language="javascript"
style={tomorrow}
>
{data.codeString}
</SyntaxHighlighter>
</div>
</div>
);
})}
{props.isEmbedPage && (
<p className="text-[15px] my-2">
{t("js-snippet-msg-1")}
<span
className="text-blue-600 cursor-pointer px-1"
onClick={() => navigate("/report/6TeaPr321t")}
>
{t("js-snippet-msg-2")}
</span>
<span>{t("js-snippet-msg-3")}</span>
</p>
)}
<p className="font-medium mt-3 text-[15px]">
{t("public-template-mssg-3")}
</p>
<p className="my-[6px]">
{" "}
{t("public-template-mssg-4")}
<a
href="https://www.npmjs.com/package/@opensign/react"
target="_blank"
rel="noreferrer"
className="cursor-pointer text-blue-700 "
>
{" "}
OpenSign React package{" "}
</a>
{t("public-template-mssg-5")}
</p>
</div>
) : activeTab === 1 ? (
<div className="mt-4">
<div>
<p className="font-medium text-[18px]">{t(`Usage`)}</p>
<p className="text-[15px] my-2">{t("js-snippet-msg")}</p>
<div className="relative p-1">
<div
onClick={() => handleCopy(jsCodeString, 0)}
className="absolute top-[20px] right-[20px] cursor-pointer"
>
<i className="fa-light fa-copy text-white mr-[2px]" />
<span className=" text-white">
{isCopied[0] ? t("copied-code") : t("copy-code")}
</span>
</div>
<SyntaxHighlighter
customStyle={{
borderRadius: "15px"
}}
language="javascript"
style={tomorrow}
>
{jsCodeString}
</SyntaxHighlighter>
</div>
{props.isEmbedPage && (
<p className="text-[15px] my-2">
{t("js-snippet-msg-1")}
<span
className="text-blue-600 cursor-pointer px-1"
onClick={() => navigate("/report/6TeaPr321t")}
>
{t("js-snippet-msg-2")}
</span>
<span>{t("js-snippet-msg-3")}</span>
</p>
)}
</div>
</div>
) : (
activeTab === 2 && (
<div className="mt-4">
{angularCode.map((data, ind) => {
return (
<div key={ind}>
<p className="font-medium text-[18px]">
{t(`${data.title}`)}
</p>
{ind === 0 && (
<p className="text-[15px] mt-2">
{t("angular-npm-mssg-1")}
</p>
)}
<div className="relative p-1">
<div
onClick={() => handleCopy(data.codeString, ind)}
className="absolute top-[20px] right-[20px] cursor-pointer"
>
<i className="fa-light fa-copy text-white mr-[2px]" />
<span className=" text-white">
{isCopied[ind] ? t("copied-code") : t("copy-code")}
</span>
</div>
<SyntaxHighlighter
customStyle={{
borderRadius: "15px"
}}
language="javascript"
style={tomorrow}
>
{data.codeString}
</SyntaxHighlighter>
</div>
</div>
);
})}
{props.isEmbedPage && (
<p className="text-[15px] my-2">
{t("js-snippet-msg-1")}
<span
className="text-blue-600 cursor-pointer px-1"
onClick={() => navigate("/report/6TeaPr321t")}
>
{t("js-snippet-msg-2")}
</span>
<span>{t("js-snippet-msg-3")}</span>
</p>
)}
<p className="font-medium mt-3 text-[15px]">
{t("public-template-mssg-3")}
</p>
<p className="my-[6px]">
{" "}
{t("public-template-mssg-4")}
<a
href="https://www.npmjs.com/package/@opensign/angular"
target="_blank"
rel="noreferrer"
className="cursor-pointer text-blue-700 "
>
{" "}
OpenSign Angular package{" "}
</a>
{t("public-template-mssg-5")}
</p>
</div>
)
)}
</div>
</div>
);
}
export default EmbedTab;
@@ -14,6 +14,7 @@ import ModalUi from "../../primitives/ModalUi";
import Loader from "../../primitives/Loader";
import { useTranslation } from "react-i18next";
import { PDFDocument } from "pdf-lib";
import { maxFileSize } from "../../constant/const";
function Header(props) {
const { t } = useTranslation();
@@ -50,6 +51,13 @@ function Header(props) {
}
};
// `removeFile` is used to remove file if exists
const removeFile = (e) => {
if (e) {
e.target.value = "";
}
};
const handleFileUpload = async (e) => {
const file = e.target.files[0];
if (!file) {
@@ -60,6 +68,13 @@ function Header(props) {
alert("Only PDF files are allowed.");
return;
}
const mb = Math.round(file?.size / Math.pow(1024, 2));
if (mb > maxFileSize) {
alert(`${t("file-alert-1")} ${maxFileSize} MB`);
removeFile(e);
return;
}
try {
const uploadedPdfBytes = await file.arrayBuffer();
const uploadedPdfDoc = await PDFDocument.load(uploadedPdfBytes, {
@@ -7,6 +7,7 @@ import {
} from "../../constant/Utils";
import ModalUi from "../../primitives/ModalUi";
import { PDFDocument } from "pdf-lib";
import { maxFileSize } from "../../constant/const";
function PdfZoom(props) {
const { t } = useTranslation();
@@ -41,6 +42,14 @@ function PdfZoom(props) {
console.log("error in delete pdf page", e);
}
};
// `removeFile` is used to remove file if exists
const removeFile = (e) => {
if (e) {
e.target.value = "";
}
};
const handleFileUpload = async (e) => {
const file = e.target.files[0];
if (!file) {
@@ -51,6 +60,12 @@ function PdfZoom(props) {
alert("Only PDF files are allowed.");
return;
}
const mb = Math.round(file?.size / Math.pow(1024, 2));
if (mb > maxFileSize) {
alert(`${t("file-alert-1")} ${maxFileSize} MB`);
removeFile(e);
return;
}
try {
const uploadedPdfBytes = await file.arrayBuffer();
const uploadedPdfDoc = await PDFDocument.load(uploadedPdfBytes, {
+278 -259
View File
@@ -25,6 +25,8 @@ const selectFormat = (data) => {
switch (data) {
case "L":
return "MM/dd/yyyy";
case "MM/DD/YYYY":
return "MM/dd/yyyy";
case "DD-MM-YYYY":
return "dd-MM-yyyy";
case "DD/MM/YYYY":
@@ -41,6 +43,8 @@ const selectFormat = (data) => {
return "MM.dd.yyyy";
case "MMM DD, YYYY":
return "MMM dd, yyyy";
case "MMMM DD, YYYY":
return "MMMM dd, yyyy";
case "DD MMMM, YYYY":
return "dd MMMM, yyyy";
default:
@@ -74,25 +78,24 @@ const changeDateToMomentFormat = (format) => {
return "L";
}
};
//function to get default date
const getDefaultdate = (selectedDate, format = "dd-MM-yyyy") => {
let date;
if (format && format === "dd-MM-yyyy") {
const newdate = selectedDate
? selectedDate
: moment(new Date()).format(changeDateToMomentFormat(format));
const [day, month, year] = newdate.split("-");
date = new Date(`${year}-${month}-${day}`);
} else {
date = new Date(selectedDate);
}
const value = date;
return value;
};
//function to get default format
const getDefaultFormat = (dateFormat) => dateFormat || "MM/dd/yyyy";
//function to convert formated date to new Date() format
const getDefaultDate = (dateStr, format) => {
//get valid date format for moment to convert formated date to new Date() format
const formats = changeDateToMomentFormat(format);
const parsedDate = moment(dateStr, formats);
let date;
if (parsedDate.isValid()) {
date = new Date(parsedDate.toISOString());
return date;
} else {
date = new Date();
return date;
}
};
function Placeholder(props) {
//'isTouchDevice' is used to detect whether a device has a touchscreen or is mouse-based
const isTouchDevice = navigator.maxTouchPoints > 0;
@@ -104,31 +107,16 @@ function Placeholder(props) {
const holdTimeout = useRef(null);
const startTime = useRef(null); // Track when the user starts holdings
const [isDisableDragging, setIsDisableDragging] = useState(true);
const [selectDate, setSelectDate] = useState({
date:
props.pos.type === "date"
? moment(
getDefaultdate(
props?.pos?.options?.response,
props.pos?.options?.validation?.format
).getTime()
).format(
changeDateToMomentFormat(props.pos?.options?.validation?.format)
)
: "",
format:
props.pos.type === "date"
? getDefaultFormat(props.pos?.options?.validation?.format)
: ""
});
const [selectDate, setSelectDate] = useState({});
const [dateFormat, setDateFormat] = useState([]);
const [clickonWidget, setClickonWidget] = useState({});
const [startDate, setStartDate] = useState(
props.pos.type === "date" &&
getDefaultdate(
props?.pos?.options?.response,
props.pos?.options?.validation?.format
)
props?.pos?.options?.response
? getDefaultDate(
props?.pos?.options?.response,
props.pos?.options?.validation?.format
)
: new Date()
);
const [getCheckboxRenderWidth, setGetCheckboxRenderWidth] = useState({
width: null,
@@ -167,7 +155,6 @@ function Placeholder(props) {
return () => clearTimeout(timer);
}, [props.pos]);
useEffect(() => {
const onOutsideClick = () => {
if (!isDraggingEnabled) {
@@ -456,7 +443,9 @@ function Placeholder(props) {
//checking widget's type and open widget copy modal for required widgets
if (
["signature", textWidget, "stamp", "initials"].includes(props.pos.type)
["signature", textInputWidget, textWidget, "stamp", "initials"].includes(
props.pos.type
)
) {
props.setIsPageCopy(true);
props.setSignKey(props.pos.key);
@@ -479,7 +468,7 @@ function Placeholder(props) {
const isDateChange = true;
const dateObj = {
date: startDate,
format: selectDate.format
format: getDefaultFormat(props.pos?.options?.validation?.format)
};
handleSaveDate(dateObj, isDateChange); //function to save date and format in local array
}
@@ -488,19 +477,18 @@ function Placeholder(props) {
//function to save date and format on local array onchange date and onclick format
const handleSaveDate = (data, isDateChange) => {
let updateDate = data.date;
//check if date change by user
if (isDateChange) {
//`changeDateToMomentFormat` is used to convert date as per required to moment package
updateDate = moment(data.date).format(
changeDateToMomentFormat(data.format)
let date;
if (data?.format === "dd-MM-yyyy") {
date = isDateChange
? moment(updateDate).format(changeDateToMomentFormat(data.format))
: updateDate;
} else {
//using moment package is used to change date as per the format provided in selectDate obj e.g. - MM/dd/yyyy -> 03/12/2024
const newDate = new Date(updateDate);
date = moment(newDate.getTime()).format(
changeDateToMomentFormat(data?.format)
);
}
//using moment package is used to change date as per the format provided in selectDate obj e.g. - MM/dd/yyyy -> 03/12/2024
//`getDefaultdate` is used to convert update date in new Date() format
const date = moment(
getDefaultdate(updateDate, data?.format).getTime()
).format(changeDateToMomentFormat(data?.format));
//`onChangeInput` is used to save data related to date in a placeholder field
onChangeInput(
date,
@@ -785,7 +773,8 @@ function Placeholder(props) {
//enable dragging functionality only if isAlllowModify true on tab that widget and hold 1sec
if (
props.isAlllowModify &&
props?.assignedWidgetId.includes(props.pos.key)
props?.assignedWidgetId.includes(props.pos.key) &&
props.data?.signerObjId === props.signerObjId
) {
//if 'isTouchDevice' then handle dragging functionality conditionaly
if (isTouchDevice) {
@@ -797,7 +786,8 @@ function Placeholder(props) {
} else if (
//condition when 'isAlllowModify' is true and user add new widgets then handle dragging functionality like signyourself flow
props.isAlllowModify &&
!props?.assignedWidgetId.includes(props.pos.key)
!props?.assignedWidgetId.includes(props.pos.key) &&
props.data?.signerObjId === props.signerObjId
) {
return !isDraggingEnabled;
} else {
@@ -808,8 +798,10 @@ function Placeholder(props) {
else if (props.isPlaceholder && ![textWidget].includes(props.pos.type)) {
return false;
} //dragging depend on 'isDraggingEnabled' variable in self sign and signyourself flow
else {
else if (isTouchDevice) {
return !isDraggingEnabled;
} else {
return false;
}
};
//function to handle widget background color
@@ -868,183 +860,93 @@ function Placeholder(props) {
}
}, 1000); // Hold for 1 second before vibrating
};
const fontSize = calculateFont(props.pos.options?.fontSize);
const fontColor = props.pos.options?.fontColor || "black";
return (
<>
<Rnd
id={props.pos.key}
data-tut={props.pos.key === props.unSignedWidgetId ? "IsSigned" : ""}
key={props.pos.key}
lockAspectRatio={
!props.isFreeResize &&
![
textWidget,
"email",
"name",
"company",
"job title",
textInputWidget
].includes(props.pos.type) &&
(props.pos.Width
? props.pos.Width / props.pos.Height
: defaultWidthHeight(props.pos.type).width /
defaultWidthHeight(props.pos.type).height)
}
enableResizing={{
top: false,
right: false,
bottom: false,
left: false,
topRight: false,
bottomRight:
props.data && props.isNeedSign
? props.data?.signerObjId === props.signerObjId &&
props.pos.type !== "checkbox" &&
props.pos.type !== radioButtonWidget
? true
: false
: props.pos.type !== radioButtonWidget &&
props.pos.type !== "checkbox" &&
props.pos.key === props.selectWidgetId &&
true,
bottomLeft: false,
topLeft: false
}}
bounds="parent"
className="signYourselfBlock"
style={{
border: "1px solid #007bff",
borderRadius: "2px",
cursor: getCursor(),
zIndex:
props.pos.type === "date"
? props.pos.key === props.selectWidgetId
? 99 + 1
: 99
: props?.pos?.zIndex
? props.pos.zIndex
: "5",
opacity:
props.isNeedSign && props.data?.Id !== props?.uniqueId && "0.4",
background: handleBackground()
}}
onDrag={() => {
setDraggingEnabled(true);
props.handleTabDrag && props.handleTabDrag(props.pos.key);
}}
size={{
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)
}}
minHeight={calculateFont(props.pos.options?.fontSize, true)}
maxHeight="auto"
onResizeStart={() => {
setDraggingEnabled(true);
props.setIsResize && props.setIsResize(true);
}}
onResizeStop={(e, direction, ref) => {
setTimeout(() => {
props.setIsResize && props.setIsResize(false);
}, 50);
props.handleSignYourselfImageResize &&
props.handleSignYourselfImageResize(
ref,
props.pos.key,
props.xyPosition,
props.setXyPosition,
props.index,
containerScale,
props.scale,
props.data && props.data.Id,
props.isResize
);
}}
disableDragging={handleDragging()}
onDragStop={(event, dragElement) => {
setIsDisableDragging(true);
props.handleStop &&
props.handleStop(
event,
dragElement,
props.data?.Id,
props.pos?.key
);
}}
position={{
x: xPos(props.pos, props.isSignYourself),
y: yPos(props.pos, props.isSignYourself)
}}
onResize={(e, direction, ref) => {
setPlaceholderBorder({
w: ref.offsetWidth / (props.scale * containerScale),
h: ref.offsetHeight / (props.scale * containerScale)
});
}}
// onClick={() =>
// !props.isResize && !isMobile && handleOnClickPlaceholder()
// }
>
{props.pos.key === props.selectWidgetId &&
((props.isShowBorder &&
![radioButtonWidget, "checkbox"].includes(props.pos.type)) ||
(props?.isAlllowModify &&
!props?.assignedWidgetId.includes(props.pos.key))) ? (
<BorderResize
right={-12}
top={-11}
pos={props.pos}
posHeight={props.posHeight}
isSignYourself={props.isSignYourself || props.isSelfSign}
/>
) : props.data && props.isNeedSign && props.pos.type !== "checkbox" ? (
props.data?.signerObjId === props.signerObjId &&
![radioButtonWidget, "checkbox"].includes(props.pos.type) ? (
<BorderResize
posHeight={props.posHeight}
isSignYourself={props.isSignYourself || props.isSelfSign}
pos={props.pos}
/>
) : (
<></>
)
) : (
![radioButtonWidget, "checkbox"].includes(props.pos.type) &&
props.pos.key === props.selectWidgetId && <BorderResize />
)}
{/* 1- Show a border if props.pos.key === props.selectWidgetId, indicating the current user's selected widget.
2- If props.isShowBorder is true, display borders for all widgets.
3- Use the combination of props?.isAlllowModify and !props?.assignedWidgetId.includes(props.pos.key) to determine when to show borders:
1- When isAlllowModify is true, show borders.
2- Do not display border for widgets already assigned (props.assignedWidgetId.includes(props.pos.key) is true).
*/}
{props.pos.key === props.selectWidgetId &&
(props.isShowBorder ||
!isDisableDragging ||
(props?.isAlllowModify &&
!props?.assignedWidgetId.includes(props.pos.key))) && (
<PlaceholderBorder
setDraggingEnabled={setDraggingEnabled}
pos={props.pos}
isPlaceholder={props.isPlaceholder}
getCheckboxRenderWidth={getCheckboxRenderWidth}
scale={props.scale}
containerScale={containerScale}
placeholderBorder={placeholderBorder}
/>
)}
<div
className="flex items-stretch justify-center"
{/* Check if a text widget (prefill type) exists. Once the user enters a value and clicks outside or the widget becomes non-selectable, it should appear as plain text (just like embedded text in a document). When the user clicks on the text again, it should become editable. */}
{props.pos?.options?.response &&
props.pos.key !== props.selectWidgetId &&
props.pos.type === textWidget ? (
<span
onClick={() => {
props.setSelectWidgetId && props.setSelectWidgetId(props.pos.key);
}}
style={{
fontFamily: "Arial, sans-serif",
position: "absolute",
left: xPos(props.pos, props.isSignYourself),
top: yPos(props.pos, props.isSignYourself),
fontSize: fontSize,
color: fontColor,
zIndex: 99
}}
>
{props.pos?.options?.response}
</span>
) : (
<Rnd
id={props.pos.key}
data-tut={props.pos.key === props.unSignedWidgetId ? "IsSigned" : ""}
key={props.pos.key}
lockAspectRatio={
!props.isFreeResize &&
![
textWidget,
"email",
"name",
"company",
"job title",
textInputWidget
].includes(props.pos.type) &&
(props.pos.Width
? props.pos.Width / props.pos.Height
: defaultWidthHeight(props.pos.type).width /
defaultWidthHeight(props.pos.type).height)
}
enableResizing={{
top: false,
right: false,
bottom: false,
left: false,
topRight: false,
bottomRight:
props.data && props.isNeedSign
? props.data?.signerObjId === props.signerObjId &&
props.pos.type !== "checkbox" &&
props.pos.type !== radioButtonWidget
? true
: false
: props.pos.type !== radioButtonWidget &&
props.pos.type !== "checkbox" &&
props.pos.key === props.selectWidgetId &&
true,
bottomLeft: false,
topLeft: false
}}
bounds="parent"
className="signYourselfBlock"
style={{
border: "1px solid #007bff",
borderRadius: "2px",
cursor: getCursor(),
zIndex:
props.pos.type === "date"
? props.pos.key === props.selectWidgetId
? 99 + 1
: 99
: props?.pos?.zIndex
? props.pos.zIndex
: "5",
opacity:
props.isNeedSign && props.data?.Id !== props?.uniqueId && "0.4",
background: handleBackground()
}}
onDrag={() => {
setDraggingEnabled(true);
props.handleTabDrag && props.handleTabDrag(props.pos.key);
}}
size={{
width:
props.pos.type === radioButtonWidget ||
props.pos.type === "checkbox"
@@ -1054,40 +956,157 @@ function Placeholder(props) {
props.pos.type === radioButtonWidget ||
props.pos.type === "checkbox"
? "auto"
: props.posHeight(props.pos, props.isSignYourself),
zIndex: "10"
: props.posHeight(props.pos, props.isSignYourself)
}}
onTouchEnd={() => handleTouchEnd()}
onClick={() => handleOnClickPlaceholder()}
onTouchStart={() => handleTouchStart()}
minHeight={calculateFont(props.pos.options?.fontSize, true)}
maxHeight="auto"
onResizeStart={() => {
setDraggingEnabled(true);
props.setIsResize && props.setIsResize(true);
}}
onResizeStop={(e, direction, ref) => {
setTimeout(() => {
props.setIsResize && props.setIsResize(false);
}, 50);
props.handleSignYourselfImageResize &&
props.handleSignYourselfImageResize(
ref,
props.pos.key,
props.xyPosition,
props.setXyPosition,
props.index,
containerScale,
props.scale,
props.data && props.data.Id,
props.isResize
);
}}
disableDragging={handleDragging()}
onDragStop={(event, dragElement) => {
setIsDisableDragging(true);
props.handleStop &&
props.handleStop(
event,
dragElement,
props.data?.Id,
props.pos?.key
);
}}
position={{
x: xPos(props.pos, props.isSignYourself),
y: yPos(props.pos, props.isSignYourself)
}}
onResize={(e, direction, ref) => {
setPlaceholderBorder({
w: ref.offsetWidth / (props.scale * containerScale),
h: ref.offsetHeight / (props.scale * containerScale)
});
}}
// onClick={() =>
// !props.isResize && !isMobile && handleOnClickPlaceholder()
// }
>
{props.pos.key === props.selectWidgetId && <PlaceholderIcon />}
<PlaceholderType
pos={props.pos}
xyPosition={props.xyPosition}
index={props.index}
setXyPosition={props.setXyPosition}
data={props.data}
setSignKey={props.setSignKey}
isShowDropdown={props?.isShowDropdown}
isPlaceholder={props.isPlaceholder}
isSignYourself={props.isSignYourself}
isSelfSign={props.isSelfSign}
signerObjId={props.signerObjId}
handleUserName={props.handleUserName}
pdfDetails={props?.pdfDetails && props?.pdfDetails[0]}
isNeedSign={props.isNeedSign}
setSelectDate={setSelectDate}
selectDate={selectDate}
setValidateAlert={props.setValidateAlert}
setStartDate={setStartDate}
startDate={startDate}
handleSaveDate={handleSaveDate}
xPos={props.xPos}
calculateFont={calculateFont}
/>
</div>
</Rnd>
{props.pos.key === props.selectWidgetId &&
((props.isShowBorder &&
![radioButtonWidget, "checkbox"].includes(props.pos.type)) ||
(props?.isAlllowModify &&
!props?.assignedWidgetId.includes(props.pos.key))) ? (
<BorderResize
right={-12}
top={-11}
pos={props.pos}
posHeight={props.posHeight}
isSignYourself={props.isSignYourself || props.isSelfSign}
/>
) : props.data &&
props.isNeedSign &&
props.pos.type !== "checkbox" ? (
props.data?.signerObjId === props.signerObjId &&
![radioButtonWidget, "checkbox"].includes(props.pos.type) ? (
<BorderResize
posHeight={props.posHeight}
isSignYourself={props.isSignYourself || props.isSelfSign}
pos={props.pos}
/>
) : (
<></>
)
) : (
![radioButtonWidget, "checkbox"].includes(props.pos.type) &&
props.pos.key === props.selectWidgetId && <BorderResize />
)}
{/* 1- Show a border if props.pos.key === props.selectWidgetId, indicating the current user's selected widget.
2- If props.isShowBorder is true, display borders for all widgets.
3- Use the combination of props?.isAlllowModify and !props?.assignedWidgetId.includes(props.pos.key) to determine when to show borders:
1- When isAlllowModify is true, show borders.
2- Do not display border for widgets already assigned (props.assignedWidgetId.includes(props.pos.key) is true).
*/}
{props.pos.key === props.selectWidgetId &&
(props.isShowBorder ||
!isDisableDragging ||
(props?.isAlllowModify &&
!props?.assignedWidgetId.includes(props.pos.key))) && (
<PlaceholderBorder
setDraggingEnabled={setDraggingEnabled}
pos={props.pos}
isPlaceholder={props.isPlaceholder}
getCheckboxRenderWidth={getCheckboxRenderWidth}
scale={props.scale}
containerScale={containerScale}
placeholderBorder={placeholderBorder}
/>
)}
<div
className="flex items-stretch justify-center"
style={{
left: xPos(props.pos, props.isSignYourself),
top: yPos(props.pos, props.isSignYourself),
width:
props.pos.type === radioButtonWidget ||
props.pos.type === "checkbox"
? "auto"
: props.posWidth(props.pos, props.isSignYourself),
height:
props.pos.type === radioButtonWidget ||
props.pos.type === "checkbox"
? "auto"
: props.posHeight(props.pos, props.isSignYourself),
zIndex: "10"
}}
onTouchEnd={() => handleTouchEnd()}
onClick={() => handleOnClickPlaceholder()}
onTouchStart={() => handleTouchStart()}
>
{props.pos.key === props.selectWidgetId && <PlaceholderIcon />}
<PlaceholderType
pos={props.pos}
xyPosition={props.xyPosition}
index={props.index}
setXyPosition={props.setXyPosition}
data={props.data}
setSignKey={props.setSignKey}
isShowDropdown={props?.isShowDropdown}
isPlaceholder={props.isPlaceholder}
isSignYourself={props.isSignYourself}
isSelfSign={props.isSelfSign}
signerObjId={props.signerObjId}
calculateFontsize={props.calculateFontsize}
pdfDetails={props?.pdfDetails && props?.pdfDetails[0]}
isNeedSign={props.isNeedSign}
setSelectDate={setSelectDate}
selectDate={selectDate}
setValidateAlert={props.setValidateAlert}
setStartDate={setStartDate}
startDate={startDate}
handleSaveDate={handleSaveDate}
xPos={props.xPos}
calculateFont={calculateFont}
/>
</div>
</Rnd>
)}
<ModalUi isOpen={isDateModal} title={t("widget-info")} showClose={false}>
<div className="h-[100%] p-[20px]">
<div className="flex flex-row items-center">
@@ -24,12 +24,13 @@ const widgetCls =
function PlaceholderType(props) {
const { t } = useTranslation();
const type = props?.pos?.type;
const widgetTypeTraslation = t(`widgets-name.${props?.pos?.type}`);
const widgetTypeTranslation = t(`widgets-name.${props?.pos?.type}`);
const [selectOption, setSelectOption] = useState("");
const [validatePlaceholder, setValidatePlaceholder] = useState("");
const inputRef = useRef(null);
const [textValue, setTextValue] = useState();
const [selectedCheckbox, setSelectedCheckbox] = useState([]);
const [hint, setHint] = useState("");
const years = range(1950, getYear(new Date()) + 16, 1);
const fontSize = props.calculateFont(props.pos.options?.fontSize);
const fontColor = props.pos.options?.fontColor || "black";
@@ -150,6 +151,11 @@ function PlaceholderType(props) {
if (defaultData) {
setTextValue(defaultData);
}
if (props.pos?.options?.hint) {
setHint(props.pos?.options.hint);
} else {
setHint(props.pos?.type);
}
} else if ([textInputWidget].includes(props.pos?.type)) {
const defaultData = props.pos?.options?.defaultValue;
if (defaultData) {
@@ -158,7 +164,6 @@ function PlaceholderType(props) {
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [props.pos?.options?.defaultValue]);
const ExampleCustomInput = forwardRef(({ value, onClick }, ref) => (
<div
style={{
@@ -327,13 +332,20 @@ function PlaceholderType(props) {
/>
) : (
<div className={widgetCls}>
{props?.handleUserName &&
props?.handleUserName(
props?.data?.Id,
props?.data?.Role,
widgetTypeTraslation,
props.pos
)}
{props.pos.type && (
<div
style={{
fontSize: props.pos
? props.calculateFontsize(props.pos)
: "11px"
}}
className="font-medium"
>
{props.isNeedSign
? props.pos?.options?.hint || widgetTypeTranslation
: widgetTypeTranslation}
</div>
)}
</div>
);
case "stamp":
@@ -346,13 +358,20 @@ function PlaceholderType(props) {
/>
) : (
<div className={widgetCls}>
{props?.handleUserName &&
props?.handleUserName(
props?.data?.Id,
props?.data?.Role,
widgetTypeTraslation,
props.pos
)}
{props.pos.type && (
<div
style={{
fontSize: props.pos
? props.calculateFontsize(props.pos)
: "11px"
}}
className="font-medium"
>
{props.isNeedSign
? props.pos?.options?.hint || widgetTypeTranslation
: widgetTypeTranslation}
</div>
)}
</div>
);
case "checkbox":
@@ -456,7 +475,7 @@ function PlaceholderType(props) {
/>
) : (
<div style={textWidgetStyle} className="select-none-cls">
<span>{textValue || widgetTypeTraslation}</span>
<span>{textValue || widgetTypeTranslation}</span>
</div>
);
case "dropdown":
@@ -513,10 +532,14 @@ function PlaceholderType(props) {
})}
</select>
) : (
<div style={textWidgetStyle} className="select-none-cls">
<div
style={textWidgetStyle}
className="select-none-cls flex justify-between items-center"
>
{props.pos?.options?.name
? props.pos.options.name
: widgetTypeTraslation}
: widgetTypeTranslation}
<i className="fa-light fa-circle-chevron-down mr-1 "></i>
</div>
);
case "initials":
@@ -529,13 +552,20 @@ function PlaceholderType(props) {
/>
) : (
<div className={widgetCls}>
{props?.handleUserName &&
props?.handleUserName(
props?.data?.Id,
props?.data?.Role,
widgetTypeTraslation,
props.pos
)}
{props.pos.type && (
<div
style={{
fontSize: props.pos
? props.calculateFontsize(props.pos)
: "11px"
}}
className="font-medium text-center"
>
{props.isNeedSign
? props.pos?.options?.hint || widgetTypeTranslation
: widgetTypeTranslation}
</div>
)}
</div>
);
case "name":
@@ -544,7 +574,7 @@ function PlaceholderType(props) {
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
<textarea
ref={inputRef}
placeholder={t("widgets-name.name")}
placeholder={hint || widgetTypeTranslation}
rows={1}
onKeyDown={handleEnterPress}
value={textValue}
@@ -567,7 +597,7 @@ function PlaceholderType(props) {
/>
) : (
<div className="flex h-full select-none-cls" style={textWidgetStyle}>
<span>{widgetTypeTraslation}</span>
<span>{widgetTypeTranslation}</span>
</div>
);
case "company":
@@ -576,7 +606,7 @@ function PlaceholderType(props) {
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
<textarea
ref={inputRef}
placeholder={t("widgets-name.company")}
placeholder={hint || widgetTypeTranslation}
rows={1}
onKeyDown={handleEnterPress}
value={textValue}
@@ -598,7 +628,7 @@ function PlaceholderType(props) {
/>
) : (
<div style={textWidgetStyle} className="select-none-cls">
<span>{widgetTypeTraslation}</span>
<span>{widgetTypeTranslation}</span>
</div>
);
case "job title":
@@ -607,7 +637,7 @@ function PlaceholderType(props) {
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
<textarea
ref={inputRef}
placeholder={t("widgets-name.job title")}
placeholder={hint || widgetTypeTranslation}
rows={1}
onKeyDown={handleEnterPress}
value={textValue}
@@ -629,7 +659,7 @@ function PlaceholderType(props) {
/>
) : (
<div style={textWidgetStyle} className="select-none-cls">
<span>{widgetTypeTraslation}</span>
<span>{widgetTypeTranslation}</span>
</div>
);
case "date":
@@ -672,12 +702,7 @@ function PlaceholderType(props) {
onBlur={handleInputBlur}
closeOnScroll={true}
className={`${selectWidgetCls} outline-[#007bff]`}
selected={
props?.startDate
? props?.startDate
: props.pos.options?.response &&
new Date(props.pos.options.response)
}
selected={props?.startDate}
onChange={(date) => handleOnDateChange(date)}
popperPlacement="top-end"
customInput={<ExampleCustomInput />}
@@ -713,13 +738,20 @@ function PlaceholderType(props) {
/>
) : (
<div className={widgetCls}>
{props?.handleUserName &&
props?.handleUserName(
props?.data?.Id,
props?.data?.Role,
widgetTypeTraslation,
props.pos
)}
{props.pos.type && (
<div
style={{
fontSize: props.pos
? props.calculateFontsize(props.pos)
: "11px"
}}
className="font-medium text-center"
>
{props.isNeedSign
? props.pos?.options?.hint || widgetTypeTranslation
: widgetTypeTranslation}
</div>
)}
</div>
);
case "email":
@@ -728,7 +760,7 @@ function PlaceholderType(props) {
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
<textarea
ref={inputRef}
placeholder={t("widgets-name.email")}
placeholder={hint || widgetTypeTranslation}
rows={1}
onKeyDown={(e) => {
// Prevent new line on Enter key press
@@ -760,7 +792,7 @@ function PlaceholderType(props) {
/>
) : (
<div style={textWidgetStyle} className="select-none-cls">
<span>{widgetTypeTraslation}</span>
<span>{widgetTypeTranslation}</span>
</div>
);
case radioButtonWidget:
@@ -849,13 +881,20 @@ function PlaceholderType(props) {
) : (
<div className={widgetCls}>
{props.pos.isStamp ? <div>stamp</div> : <div>signature</div>}
{props?.handleUserName &&
props?.handleUserName(
props?.data?.Id,
props?.data?.Role,
null,
props.pos
)}
{props.pos.type && (
<div
style={{
fontSize: props.pos
? props.calculateFontsize(props.pos)
: "11px"
}}
className="font-medium"
>
{props.isNeedSign
? props.pos?.options?.hint || widgetTypeTranslation
: widgetTypeTranslation}
</div>
)}
</div>
);
}
@@ -4,6 +4,7 @@ import { Document, Page } from "react-pdf";
import { useSelector } from "react-redux";
import { PDFDocument } from "pdf-lib";
import { base64ToArrayBuffer } from "../../constant/Utils";
import { maxFileSize } from "../../constant/const";
function RenderAllPdfPage(props) {
const { t } = useTranslation();
@@ -62,6 +63,12 @@ function RenderAllPdfPage(props) {
};
const pdfDataBase64 = `data:application/pdf;base64,${props?.pdfBase64Url}`;
// `removeFile` is used to remove file if exists
const removeFile = (e) => {
if (e) {
e.target.value = "";
}
};
// `handleFileUpload` is trigger when user click on add pages btn and is used to merge multiple pdf
const handleFileUpload = async (e) => {
const file = e.target.files[0];
@@ -73,6 +80,12 @@ function RenderAllPdfPage(props) {
alert("Only PDF files are allowed.");
return;
}
const mb = Math.round(file?.size / Math.pow(1024, 2));
if (mb > maxFileSize) {
alert(`${t("file-alert-1")} ${maxFileSize} MB`);
removeFile(e);
return;
}
try {
const uploadedPdfBytes = await file.arrayBuffer();
const uploadedPdfDoc = await PDFDocument.load(uploadedPdfBytes, {
@@ -110,6 +123,7 @@ function RenderAllPdfPage(props) {
autoSignScroll hide-scrollbar max-h-[100vh] `}
>
<Document
error=""
loading={t("loading-doc")}
onLoadSuccess={onDocumentLoad}
file={pdfDataBase64}
+19 -114
View File
@@ -89,35 +89,6 @@ function RenderPdf(props) {
sign?.Id === data?.Id || sign?.objectId === data?.signerObjId
)
: [];
const handleAllUserName = (Id, Role, type, pos) => {
return (
<React.Fragment>
<div className="text-black text-[8px] font-bold">
{
props.pdfDetails[0].Signers?.find(
(signer) => signer.objectId === data.signerObjId
)?.Name
}
</div>
{type && (
<div
style={{ fontSize: pos ? calculateFontsize(pos) : "11px" }}
className="font-bold"
>
{type}
</div>
)}
{Role && (
<div
style={{ fontSize: pos ? calculateFontsize(pos) : "8px" }}
className="text-black font-medium"
>
{`(${Role})`}
</div>
)}
</React.Fragment>
);
};
return (
checkSign.length === 0 &&
data?.placeHolder?.map((placeData, key) => {
@@ -148,7 +119,6 @@ function RenderPdf(props) {
isSignYourself={false}
posWidth={posWidth}
posHeight={posHeight}
handleUserName={handleAllUserName}
isDragging={props.isDragging}
pdfDetails={props.pdfDetails}
setIsInitial={props.setIsInitial}
@@ -185,6 +155,7 @@ function RenderPdf(props) {
fontColor={props.fontColor}
setFontColor={props.setFontColor}
setRequestSignTour={props.setRequestSignTour}
calculateFontsize={calculateFontsize}
/>
</React.Fragment>
)
@@ -206,73 +177,6 @@ function RenderPdf(props) {
return `${width / 10}px`;
}
};
//function for render placeholder block over pdf document
const handleUserName = (Id, Role, type, pos) => {
if (Id) {
const checkSign = props.signersdata.find((sign) => sign.Id === Id);
if (checkSign?.Name) {
return (
<>
<div
style={{
fontSize: pos ? calculateFontsize(pos) : "8px"
}}
className="text-black font-medium"
>
{checkSign?.Name}
</div>
{type && (
<div
style={{
fontSize: pos ? calculateFontsize(pos) : "11px"
}}
className=" font-bold"
>
{type}
</div>
)}
{Role && (
<div
style={{
fontSize: pos ? calculateFontsize(pos) : "8px"
}}
className="text-black text-[8px] font-medium"
>
{`(${Role})`}
</div>
)}
</>
);
} else {
return (
<>
{type && (
<div
style={{ fontSize: pos ? calculateFontsize(pos) : "11px" }}
className=" font-bold"
>
{type}
</div>
)}
<div
style={{ fontSize: pos ? calculateFontsize(pos) : "8px" }}
className="text-black font-medium"
>
{Role}
</div>
</>
);
}
} else {
return (
<>
{type && <div className="text-[11px] font-bold">{type}</div>}
<div className="text-black text-[8px] font-medium">{Role}</div>
</>
);
}
};
const pdfDataBase64 = `data:application/pdf;base64,${props.pdfBase64Url}`;
//calculate render height of pdf in mobile view
const handlePageLoadSuccess = (page) => {
@@ -361,7 +265,6 @@ function RenderPdf(props) {
handleLinkUser={
props.handleLinkUser
}
handleUserName={handleUserName}
isSignYourself={false}
posWidth={posWidth}
posHeight={posHeight}
@@ -403,6 +306,9 @@ function RenderPdf(props) {
props.unSignedWidgetId
}
isFreeResize={true}
calculateFontsize={
calculateFontsize
}
/>
</React.Fragment>
);
@@ -448,7 +354,6 @@ function RenderPdf(props) {
setWidgetType={props.setWidgetType}
setSelectWidgetId={props.setSelectWidgetId}
selectWidgetId={props.selectWidgetId}
handleUserName={handleUserName}
setIsCheckbox={props.setIsCheckbox}
setValidateAlert={props.setValidateAlert}
setCurrWidgetsDetails={
@@ -468,6 +373,7 @@ function RenderPdf(props) {
setIsResize={props.setIsResize}
isFreeResize={false}
isOpenSignPad={true}
calculateFontsize={calculateFontsize}
/>
)
);
@@ -475,17 +381,15 @@ function RenderPdf(props) {
</React.Fragment>
);
}))}
{/* Mobile */}
<Document
error={<p className="mx-2">{t("failed-to-load-refresh-page")}</p>}
onLoadError={() => props.setPdfLoad(false)}
loading={t("loading-doc")}
onLoadSuccess={props.pageDetails}
// ref={pdfRef}'
onClick={() => {
if (props.setSelectWidgetId) {
props.setSelectWidgetId("");
}
}}
onClick={() =>
props.setSelectWidgetId && props.setSelectWidgetId("")
}
file={pdfDataBase64}
>
<Page
@@ -573,7 +477,6 @@ function RenderPdf(props) {
handleLinkUser={
props.handleLinkUser
}
handleUserName={handleUserName}
isSignYourself={false}
posWidth={posWidth}
posHeight={posHeight}
@@ -615,6 +518,9 @@ function RenderPdf(props) {
props.unSignedWidgetId
}
isFreeResize={true}
calculateFontsize={
calculateFontsize
}
/>
</React.Fragment>
);
@@ -665,7 +571,6 @@ function RenderPdf(props) {
setWidgetType={props.setWidgetType}
setSelectWidgetId={props.setSelectWidgetId}
selectWidgetId={props.selectWidgetId}
handleUserName={handleUserName}
setIsCheckbox={props.setIsCheckbox}
setValidateAlert={props.setValidateAlert}
setCurrWidgetsDetails={
@@ -686,6 +591,7 @@ function RenderPdf(props) {
setIsResize={props.setIsResize}
isFreeResize={false}
isOpenSignPad={true}
calculateFontsize={calculateFontsize}
/>
</React.Fragment>
);
@@ -693,17 +599,16 @@ function RenderPdf(props) {
</React.Fragment>
);
}))}
{/* large device */}
{/* this component for render pdf document is in middle of the component */}
<Document
error={<p className="mx-2">{t("failed-to-load-refresh-page")}</p>}
onLoadError={() => props.setPdfLoad(false)}
loading={t("loading-doc")}
onLoadSuccess={props.pageDetails}
onClick={() => {
if (props.setSelectWidgetId) {
props.setSelectWidgetId("");
}
}}
onClick={() =>
props.setSelectWidgetId && props.setSelectWidgetId("")
}
file={pdfDataBase64}
>
<Page
@@ -14,53 +14,53 @@ function TextFontSetting(props) {
handleClose={() => props.setIsTextSetting(false)}
>
<div className="h-full p-[20px]">
<div className="flex items-center">
<span>{t("font-size")} :</span>
<select
className="ml-[7px] op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs"
value={
props.fontSize ||
props.currWidgetsDetails?.options?.fontSize ||
12
}
onChange={(e) => {
props.setFontSize(parseInt(e.target.value));
}}
>
{fontsizeArr.map((size, ind) => {
return (
<option className="text-[13px]" value={size} key={ind}>
<div className="flex flex-col md:flex-row md:items-center gap-3">
{/* Font Size Selector */}
<div className="flex items-center gap-2">
<span className="whitespace-nowrap">{t("font-size")}:</span>
<select
className="ml-[7px] w-[60%] op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs"
value={
props.fontSize ||
props.currWidgetsDetails?.options?.fontSize ||
12
}
onChange={(e) => props.setFontSize(parseInt(e.target.value))}
>
{fontsizeArr.map((size, ind) => (
<option key={ind} className="text-[13px]" value={size}>
{size}
</option>
);
})}
</select>
<div className="flex flex-row gap-1 items-center ml-4">
<span>{t("color")}: </span>
))}
</select>
</div>
{/* Font Color Selector */}
<div className="flex items-center">
<span className="whitespace-nowrap">{t("color")}:</span>
<select
className="ml-[33px] md:ml-4 w-[65%] md:w-[full] op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs"
value={
props.fontColor ||
props.currWidgetsDetails?.options?.fontColor ||
"black"
}
onChange={(e) => props.setFontColor(e.target.value)}
className="ml-[7px] op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs"
>
{fontColorArr.map((color, ind) => {
return (
<option value={color} key={ind}>
{t(`color-type.${color}`)}
</option>
);
})}
{fontColorArr.map((color, ind) => (
<option key={ind} value={color}>
{t(`color-type.${color}`)}
</option>
))}
</select>
{/* Color Preview Box */}
<span
className="w-5 h-5 ml-2 rounded border border-gray-300"
style={{
background:
backgroundColor:
props.fontColor ||
props.currWidgetsDetails?.options?.fontColor
props.currWidgetsDetails?.options?.fontColor ||
"black"
}}
className="w-5 h-[19px] ml-1"
></span>
</div>
</div>
@@ -31,7 +31,11 @@ const WidgetNameModal = (props) => {
name: props.defaultdata?.options?.name || "",
defaultValue: props.defaultdata?.options?.defaultValue || "",
status: props.defaultdata?.options?.status || "required",
hint: props.defaultdata?.options?.hint || "",
hint:
props.defaultdata?.options?.hint ||
(props.defaultdata?.type === textInputWidget
? "Enter text"
: `Enter ${props.defaultdata?.options?.name}`),
textvalidate:
props.defaultdata?.options?.validation?.type === "regex"
? props.defaultdata?.options?.validation?.pattern
@@ -113,7 +117,6 @@ const WidgetNameModal = (props) => {
}
}
const handleCheckboxChange = (index) => {
// Update the state with the modified array
setSignatureType((prev) =>
@@ -272,12 +275,13 @@ const WidgetNameModal = (props) => {
</div>
</div>
)}
{props.defaultdata?.type === textInputWidget && (
{props.defaultdata?.type !== textWidget && (
<div className="mb-[0.75rem]">
<label htmlFor="hint" className="text-[13px]">
{t("hint")}
</label>
<input
maxLength={40}
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
name="hint"
value={formdata.hint}
@@ -293,24 +297,26 @@ const WidgetNameModal = (props) => {
"job title",
"email"
].includes(props.defaultdata?.type) && (
<div className="flex items-center mb-[0.75rem]">
<span>{t("font-size")}:</span>
<select
className="ml-[7px] op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs"
value={
props.fontSize || props.defaultdata?.options?.fontSize || 12
}
onChange={(e) => props.setFontSize(parseInt(e.target.value))}
>
{fontsizeArr.map((size, ind) => {
return (
<option className="text-[13px]" value={size} key={ind}>
{size}
</option>
);
})}
</select>
<div className="flex flex-row gap-1 items-center ml-4">
<div className="flex flex-col md:flex-row md:items-center gap-3 mb-3">
<div className="flex items-center gap-2 ">
<span className="whitespace-nowrap">{t("font-size")}: </span>
<select
className="ml-[7px] w-[60%] op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs"
value={
props.fontSize || props.defaultdata?.options?.fontSize || 12
}
onChange={(e) => props.setFontSize(parseInt(e.target.value))}
>
{fontsizeArr.map((size, ind) => {
return (
<option className="text-[13px]" value={size} key={ind}>
{size}
</option>
);
})}
</select>
</div>
<div className="flex items-center">
<span>{t("color")}: </span>
<select
value={
@@ -319,7 +325,7 @@ const WidgetNameModal = (props) => {
"black"
}
onChange={(e) => props.setFontColor(e.target.value)}
className="ml-[7px] op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs"
className="ml-[33px] md:ml-4 w-[65%] md:w-[full] op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs"
>
{fontColorArr.map((color, ind) => {
return (
@@ -0,0 +1,79 @@
import React, { useState } from "react";
import { formatDateTime } from "../../../constant/Utils";
import { useTranslation } from "react-i18next";
const DateFormatSelector = (props) => {
const { t } = useTranslation();
const date = new Date();
const [selectedFormat, setSelectedFormat] = useState(props.dateFormat);
const [is12Hour, setIs12Hour] = useState(props?.is12HourTime);
const dateFormats = [
"MM/DD/YYYY",
"MMMM DD, YYYY",
"DD MMMM, YYYY",
"DD-MM-YYYY",
"DD MMM, YYYY",
"YYYY-MM-DD",
"MM-DD-YYYY",
"MM.DD.YYYY",
"MMM DD, YYYY"
];
// Handle format change
const handleFormatChange = (event) => {
setSelectedFormat(event.target.value);
props.setDateFormat && props.setDateFormat(event.target.value);
};
const handleHrInput = () => {
setIs12Hour(!is12Hour);
props.setIs12HourTime && props.setIs12HourTime(!is12Hour);
};
return (
<div className="max-w-[400px] pr-[20px]">
<label className="text-[14px] mb-[0.7rem] font-medium">
{t("date-format")}
</label>
<select
className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content w-full h-full text-[11px]"
value={selectedFormat}
onChange={handleFormatChange}
>
{dateFormats.map((format) => (
<option key={format} value={format}>
{format}
</option>
))}
</select>
<div className="flex flex-row gap-4 mt-[0.75rem] text-[12px]">
<div className="flex items-center gap-2 ml-2">
<input
type="radio"
value={true}
className="op-radio op-radio-xs"
checked={is12Hour}
onChange={handleHrInput}
/>
<div className="text-center">12 hr</div>
</div>
<div className="flex items-center gap-2 ml-2">
<input
type="radio"
value={false}
className="op-radio op-radio-xs"
checked={!is12Hour}
onChange={handleHrInput}
/>
<div className="text-center">24 hr</div>
</div>
</div>
<p className="mt-[12px] ml-[10px] text-[13px] font-medium">
<strong>
{formatDateTime(date, selectedFormat, props?.timezone, is12Hour)}
</strong>
</p>
</div>
);
};
export default DateFormatSelector;
@@ -6,6 +6,9 @@ import { useTranslation } from "react-i18next";
const FolderModal = (props) => {
const { t } = useTranslation();
const appName =
"OpenSign™";
const drivename = appName === "OpenSign™" ? "OpenSign™" : "";
const [clickFolder, setClickFolder] = useState("");
const [folderList, setFolderList] = useState([]);
const [tabList, setTabList] = useState([]);
@@ -79,7 +82,7 @@ const FolderModal = (props) => {
// `handleSubmit` is used to pass folderPtr to parent component
const handleSubmit = () => {
let url = "OpenSign™ Drive";
let url = drivename + " Drive";
tabList.forEach((t) => {
url = url + " / " + t.Name;
});
@@ -157,10 +160,10 @@ const FolderModal = (props) => {
<div className="pt-1 text-[#ac4848] text-[14px] font-[500]">
<span
className="cursor-pointer"
title="OpenSign™ Drive"
title={`${drivename} Drive`}
onClick={(e) => removeTabListItem(e)}
>
{t("OpenSign-drive")} /{" "}
{t("OpenSign-drive", { appName: drivename })} /{" "}
</span>
{tabList &&
tabList.map((tab, i) => (
@@ -6,6 +6,9 @@ import Tooltip from "../../../primitives/Tooltip";
import { useTranslation } from "react-i18next";
const SelectFolder = ({ required, onSuccess, folderCls, isReset }) => {
const appName =
"OpenSign™";
const drivename = appName === "OpenSign™" ? "OpenSign™" : "";
const { t } = useTranslation();
const [isOpen, SetIsOpen] = useState(false);
const [clickFolder, setClickFolder] = useState("");
@@ -99,7 +102,7 @@ const SelectFolder = ({ required, onSuccess, folderCls, isReset }) => {
// `handleSubmit` is used to pass folderPtr to parent component
const handleSubmit = () => {
let url = "OpenSign™ Drive";
let url = drivename + " Drive";
tabList.forEach((t) => {
url = url + " / " + t.Name;
});
@@ -194,7 +197,7 @@ const SelectFolder = ({ required, onSuccess, folderCls, isReset }) => {
<p>
{selectFolder && selectFolder.Name
? selectFolder.Name
: t("OpenSign-drive")}
: t("OpenSign-drive", { appName: drivename })}
</p>
<div className="text-black text-sm">
<i
@@ -210,7 +213,7 @@ const SelectFolder = ({ required, onSuccess, folderCls, isReset }) => {
</div>
</div>
<div className="absolute top-2 right-1 cursor-pointer">
<Tooltip message={t("select-folder-help")} />
<Tooltip message={t("select-folder-help", { appName: drivename })} />
</div>
</div>
<ModalUi
@@ -222,10 +225,10 @@ const SelectFolder = ({ required, onSuccess, folderCls, isReset }) => {
<div className="pt-1 text-[#ac4848] text-[14px] font-[500]">
<span
className="cursor-pointer"
title="OpenSign™ Drive"
title={`${drivename} Drive`}
onClick={(e) => removeTabListItem(e)}
>
{t("OpenSign-drive")} /{" "}
{t("OpenSign-drive", { appName: drivename })} /{" "}
</span>
{tabList &&
tabList.map((tab, i) => (
@@ -1,10 +1,10 @@
import React, { useState, useEffect } from "react";
import AsyncSelect from "react-select/async";
import AddSigner from "../../AddSigner";
import Parse from "parse";
import AddContact from "../../../primitives/AddContact";
import Tooltip from "../../../primitives/Tooltip";
import { createPortal } from "react-dom";
import { useTranslation } from "react-i18next";
import { findContact } from "../../../constant/Utils";
function arrayMove(array, from, to) {
array = array.slice();
array.splice(to < 0 ? array.length + to : to, 0, array.splice(from, 1)[0]);
@@ -103,17 +103,21 @@ const SignersInput = (props) => {
// `handleNewDetails` is used to set just save from quick form to selected option in dropdown
const handleNewDetails = (data) => {
setState([...state, data]);
const user = {
value: data["objectId"],
label: data["Name"],
email: data?.Email
};
setState([...state, user]);
if (selected.length > 0) {
setSelected([...selected, data]);
setSelected([...selected, user]);
} else {
setSelected([data]);
setSelected([user]);
}
};
const loadOptions = async (inputValue) => {
try {
const params = { search: inputValue };
const contactRes = await Parse.Cloud.run("getsigners", params);
const contactRes = await findContact(inputValue);
if (contactRes) {
const res = JSON.parse(JSON.stringify(contactRes));
//compareArrays is a function where compare between two array (total signersList and dcument signers list)
@@ -148,7 +152,9 @@ const SignersInput = (props) => {
{props.label ? props.label : t("signers")}
{props.required && <span className="text-red-500 text-[13px]">*</span>}
<span
className={`z-[${props?.helptextZindex ? props.helptextZindex : 30}] absolute ml-1 text-xs`}
className={`z-[${
props?.helptextZindex ? props.helptextZindex : 30
}] absolute ml-1 text-xs`}
>
<Tooltip
id={`${props.label ? props.label : "signers"}-tooltip`}
@@ -209,9 +215,8 @@ const SignersInput = (props) => {
</button>
{isModal && (
<AddSigner
valueKey={"objectId"}
displayKey={"Name"}
<AddContact
isDisableTitle
details={handleNewDetails}
closePopup={handleModalCloseClick}
/>
@@ -7,27 +7,10 @@ const TimezoneSelector = (props) => {
// Intl.DateTimeFormat().resolvedOptions().timeZone // Default to the user's local timezone
const onChangeTimezone = (timezone) => {
setSelectedTimezone(timezone);
setSelectedTimezone(timezone?.value);
props.setTimezone && props.setTimezone(timezone?.value);
};
// Format date and time for the selected timezone
const formatDate = (date, timezone) => {
return timezone
? new Intl.DateTimeFormat("en-US", {
weekday: "short",
year: "numeric",
month: "short",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
timeZone: timezone,
hour12: false
}).format(date)
: new Date(date).toUTCString();
};
return (
<>
<div className="max-w-[400px] pr-[20px]">
@@ -54,17 +37,6 @@ const TimezoneSelector = (props) => {
}}
/>
</div>
<div className="mt-[20px] text-[13px]">
<p>
{t("current-time")}:{" "}
<strong>
{formatDate(
new Date(),
selectedTimezone?.value || selectedTimezone
)}
</strong>{" "}
</p>
</div>
</>
);
};
+9 -3
View File
@@ -3,6 +3,8 @@ import { useTranslation } from "react-i18next";
import { NavLink } from "react-router";
const Menu = ({ item, isOpen, closeSidebar }) => {
const appName = "OpenSign™";
const drivename = appName === "OpenSign™" ? "OpenSign™" : "";
const { t } = useTranslation();
return (
<li key={item.title} role="none" className="my-0.5">
@@ -15,14 +17,18 @@ const Menu = ({ item, isOpen, closeSidebar }) => {
className={({ isActive }) =>
`${
isActive ? " bg-base-300 text-base-content" : ""
} flex items-center text-left p-3 lg:p-4 text-base-content hover:text-base-content focus:bg-base-300 hover:bg-base-300 hover:no-underline focus:outline-none`
} flex items-center justify-start text-left p-3 lg:p-4 text-base-content hover:text-base-content focus:bg-base-300 hover:bg-base-300 hover:no-underline focus:outline-none`
}
onClick={closeSidebar}
tabIndex={isOpen ? 0 : -1}
role="menuitem"
>
<i className={`${item.icon} text-[18px]`} aria-hidden="true"></i>
<span className="ml-3 lg:ml-4">{t(`sidebar.${item.title}`)}</span>
<span className="w-[20px] h-[20px] flex justify-center">
<i className={`${item.icon} text-[18px]`} aria-hidden="true"></i>
</span>
<span className="ml-3 lg:ml-4">
{t(`sidebar.${item.title}`, { appName: drivename })}
</span>
</NavLink>
</li>
);
@@ -1,7 +1,7 @@
import React, { useState, useEffect } from "react";
import Menu from "./Menu";
import Submenu from "./SubMenu";
import SocialMedia from "./SocialMedia";
import SocialMedia from "../SocialMedia";
import dp from "../../assets/images/dp.png";
import sidebarList, { subSetting } from "../../json/menuJson";
import { useNavigate } from "react-router";
@@ -140,9 +140,9 @@ const Sidebar = ({ isOpen, closeSidebar }) => {
)}
</ul>
</nav>
<footer className="mt-4 flex justify-center items-center text-[25px] text-base-content gap-3">
<SocialMedia />
</footer>
<footer className="mt-4 flex justify-center items-center text-[25px] text-base-content gap-3">
<SocialMedia />
</footer>
</aside>
);
};
@@ -3,20 +3,26 @@ import { useTranslation } from "react-i18next";
import { NavLink } from "react-router";
const Submenu = ({ item, closeSidebar, toggleSubmenu, submenuOpen }) => {
const appName = "OpenSign™";
const drivename = appName === "OpenSign™" ? "OpenSign™" : "";
const { t } = useTranslation();
const { title, icon, children } = item;
return (
<li role="none" className="my-0.5">
<button
onClick={() => toggleSubmenu(item.title)}
className="flex items-center text-left p-3 lg:p-4 text-base-content hover:text-base-content focus:bg-base-300 hover:bg-base-300 hover:no-underline focus:outline-none "
className="flex items-center justify-start text-left p-3 lg:p-4 text-base-content hover:text-base-content focus:bg-base-300 hover:bg-base-300 hover:no-underline focus:outline-none "
aria-expanded={submenuOpen}
aria-haspopup="true"
aria-controls={`submenu-${title}`}
>
<i className={`${icon} text-[18px]`}></i>
<span className="w-[20px] h-[20px] flex justify-center">
<i className={`${icon} text-[18px]`}></i>
</span>
<div className="flex justify-between items-center w-full">
<span className="ml-3 lg:ml-4">{t(`sidebar.${item.title}`)}</span>
<span className="ml-3 lg:ml-4 text-start">
{t(`sidebar.${item.title}`, { appName })}
</span>
<i
className={`${
submenuOpen[item.title]
@@ -40,18 +46,22 @@ const Submenu = ({ item, closeSidebar, toggleSubmenu, submenuOpen }) => {
className={({ isActive }) =>
`${
isActive ? "bg-base-300 text-base-content" : ""
} flex items-center text-left pl-6 md:pl-8 py-2 text-sm cursor-pointer text-base-content hover:text-base-content focus:bg-base-300 hover:bg-base-300 hover:no-underline focus:outline-none`
} flex items-center justify-start text-left pl-6 md:pl-8 py-2 text-sm cursor-pointer text-base-content hover:text-base-content focus:bg-base-300 hover:bg-base-300 hover:no-underline focus:outline-none`
}
onClick={closeSidebar}
role="menuitem"
tabIndex={submenuOpen ? 0 : -1}
>
<i
className={`${childItem.icon} text-[18px]`}
aria-hidden="true"
></i>
<span className="w-[15px] h-[15px] flex justify-center">
<i
className={`${childItem.icon} text-[18px]`}
aria-hidden="true"
></i>
</span>
<span className="ml-3 lg:ml-4">
{t(`sidebar.${item.title}-Children.${childItem.title}`)}
{t(`sidebar.${item.title}-Children.${childItem.title}`, {
appName: drivename
})}
</span>
</NavLink>
</li>
+189 -125
View File
@@ -7,6 +7,8 @@ import { appInfo } from "./appinfo";
import { saveAs } from "file-saver";
import printModule from "print-js";
import fontkit from "@pdf-lib/fontkit";
import { themeColor } from "./const";
import { format, toZonedTime } from "date-fns-tz";
export const fontsizeArr = [2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28];
export const fontColorArr = ["red", "black", "blue", "yellow"];
@@ -17,6 +19,7 @@ export const isTabAndMobile = window.innerWidth < 1023;
export const textInputWidget = "text input";
export const textWidget = "text";
export const radioButtonWidget = "radio button";
export const fileasbytes = async (filepath) => {
const response = await fetch(filepath); // Adjust the path accordingly
const arrayBuffer = await response.arrayBuffer();
@@ -132,14 +135,12 @@ export const pdfNewWidthFun = (divRef) => {
};
//`contractUsers` function is used to get contract_User details
export const contractUsers = async (
) => {
export const contractUsers = async () => {
try {
const url = `${localStorage.getItem("baseUrl")}functions/getUserDetails`;
const parseAppId = localStorage.getItem("parseAppId");
const accesstoken = localStorage.getItem("accesstoken");
const token =
{ "X-Parse-Session-Token": accesstoken };
const token = { "X-Parse-Session-Token": accesstoken };
const headers = {
headers: {
"Content-Type": "application/json",
@@ -232,17 +233,47 @@ export const widgets = [
{ type: "email", icon: "fa-light fa-envelope", iconSize: "20px" }
];
export const getDate = () => {
export const getDate = (dateformat) => {
const format = dateformat || "MM/DD/YYYY";
const date = new Date();
const milliseconds = date.getTime();
const newDate = moment(milliseconds).format("MM/DD/YYYY");
const newDate = moment(milliseconds).format(format);
return newDate;
};
export const addWidgetOptions = (type) => {
const defaultOpt = {
name: type,
status: "required"
};
export const selectFormat = (data) => {
switch (data) {
case "L":
return "MM/dd/yyyy";
case "MM/DD/YYYY":
return "MM/dd/yyyy";
case "DD-MM-YYYY":
return "dd-MM-yyyy";
case "DD/MM/YYYY":
return "dd/MM/yyyy";
case "LL":
return "MMMM dd, yyyy";
case "DD MMM, YYYY":
return "dd MMM, yyyy";
case "YYYY-MM-DD":
return "yyyy-MM-dd";
case "MM-DD-YYYY":
return "MM-dd-yyyy";
case "MM.DD.YYYY":
return "MM.dd.yyyy";
case "MMM DD, YYYY":
return "MMM dd, yyyy";
case "MMMM DD, YYYY":
return "MMMM dd, yyyy";
case "DD MMMM, YYYY":
return "dd MMMM, yyyy";
default:
return "MM/dd/yyyy";
}
};
export const addWidgetOptions = (type, signer) => {
const defaultOpt = { name: type, status: "required" };
switch (type) {
case "signature":
return defaultOpt;
@@ -263,12 +294,16 @@ export const addWidgetOptions = (type) => {
return { ...defaultOpt };
case "job title":
return { ...defaultOpt };
case "date":
case "date": {
const dateFormat = signer?.DateFormat
? selectFormat(signer?.DateFormat)
: "MM/dd/yyyy";
return {
...defaultOpt,
response: getDate(),
validation: { format: "MM/dd/yyyy", type: "date-format" }
response: getDate(signer?.DateFormat),
validation: { format: dateFormat, type: "date-format" }
};
}
case "image":
return defaultOpt;
case "email":
@@ -289,7 +324,7 @@ export const addWidgetOptions = (type) => {
}
};
export const addWidgetSelfsignOptions = (type, getWidgetValue) => {
export const addWidgetSelfsignOptions = (type, getWidgetValue, owner) => {
switch (type) {
case "signature":
return { name: "signature" };
@@ -319,12 +354,16 @@ export const addWidgetSelfsignOptions = (type, getWidgetValue) => {
defaultValue: getWidgetValue(type),
validation: { type: "text", pattern: "" }
};
case "date":
case "date": {
const dateFormat = owner?.DateFormat
? selectFormat(owner?.DateFormat)
: "MM/dd/yyyy";
return {
name: "date",
response: getDate(),
validation: { format: "MM/dd/yyyy", type: "date-format" }
response: getDate(owner?.DateFormat),
validation: { format: dateFormat, type: "date-format" }
};
}
case "image":
return { name: "image" };
case "email":
@@ -491,7 +530,7 @@ export const signPdfFun = async (
documentId,
signerObjectId,
objectId,
widgets,
widgets
) => {
let isCustomCompletionMail = false;
try {
@@ -500,10 +539,7 @@ export const signPdfFun = async (
if (tenantDetails && tenantDetails === "user does not exist!") {
return { status: "error", message: "User does not exist." };
} else {
if (
tenantDetails?.CompletionBody &&
tenantDetails?.CompletionSubject
) {
if (tenantDetails?.CompletionBody && tenantDetails?.CompletionSubject) {
isCustomCompletionMail = true;
}
}
@@ -607,6 +643,15 @@ export const createDocument = async (
const RedirectUrl = Doc?.RedirectUrl
? { RedirectUrl: Doc?.RedirectUrl }
: {};
const TemplateId = Doc?.objectId
? {
TemplateId: {
__type: "Pointer",
className: "contracts_Template",
objectId: Doc?.objectId
}
}
: {};
const data = {
Name: Doc.Name,
URL: pdfUrl,
@@ -636,9 +681,16 @@ export const createDocument = async (
...SignatureType,
...NotifyOnSignatures,
...Bcc,
...RedirectUrl
...RedirectUrl,
...TemplateId
};
const remindOnceInEvery = Doc?.RemindOnceInEvery;
const TimeToCompleteDays = parseInt(Doc?.TimeToCompleteDays);
const reminderCount = TimeToCompleteDays / remindOnceInEvery;
const AutomaticReminders = Doc.autoreminder;
if (AutomaticReminders && reminderCount > 15) {
return { status: "error", id: "only-15-reminder-allowed" };
}
try {
const res = await axios.post(
`${localStorage.getItem("baseUrl")}classes/contracts_Document`,
@@ -656,7 +708,7 @@ export const createDocument = async (
}
} catch (err) {
console.log("axois err ", err);
return { status: "error", id: "Something Went Wrong!" };
return { status: "error", id: "something-went-wrong-mssg" };
}
}
};
@@ -929,10 +981,7 @@ export const calculateInitialWidthHeight = (widgetData) => {
const height = span.offsetHeight;
document.body.removeChild(span);
return {
getWidth: width,
getHeight: height
};
return { getWidth: width, getHeight: height };
};
export const addInitialData = (signerPos, setXyPosition, value, userId) => {
function widgetDataValue(type) {
@@ -963,9 +1012,7 @@ export const addInitialData = (signerPos, setXyPosition, value, userId) => {
)
};
} else {
return {
...item
};
return item;
}
} else if (item.pos && item.pos.length > 0) {
// If there is no nested array, add the new field
@@ -981,19 +1028,11 @@ export const addInitialData = (signerPos, setXyPosition, value, userId) => {
...item,
options: {
...item.options,
defaultValue: widgetData
defaultValue: item?.options?.defaultValue || widgetData
}
// Width:
// calculateInitialWidthHeight(item.type, widgetData).getWidth ||
// item?.Width,
// Height:
// calculateInitialWidthHeight(item.type, widgetData).getHeight ||
// item?.Height
};
} else {
return {
...item
};
return item;
}
}
});
@@ -1001,6 +1040,7 @@ export const addInitialData = (signerPos, setXyPosition, value, userId) => {
//function for embed document id
export const embedDocId = async (pdfDoc, documentId, allPages) => {
const appName = "OpenSign™";
// `fontBytes` is used to embed custom font in pdf
const fontBytes = await fileasbytes(
"https://cdn.opensignlabs.com/webfonts/times.ttf"
@@ -1009,7 +1049,7 @@ export const embedDocId = async (pdfDoc, documentId, allPages) => {
const font = await pdfDoc.embedFont(fontBytes, { subset: true });
for (let i = 0; i < allPages; i++) {
const fontSize = 10;
const textContent = documentId && `OpenSign™ DocumentId: ${documentId} `;
const textContent = documentId && `${appName} DocumentId: ${documentId} `;
const pages = pdfDoc.getPages();
const page = pages[i];
try {
@@ -1862,12 +1902,9 @@ export const contactBook = async (objectId) => {
};
//function for getting document details from contract_Documents class
export const contractDocument = async (
documentId,
) => {
export const contractDocument = async (documentId) => {
const data = { docId: documentId };
const token =
{ sessionToken: localStorage.getItem("accesstoken") };
const token = { sessionToken: localStorage.getItem("accesstoken") };
const documentDeatils = await axios
.post(`${localStorage.getItem("baseUrl")}functions/getDocument`, data, {
headers: {
@@ -2051,35 +2088,31 @@ export const getFileName = (fileUrl) => {
//fetch tenant app logo from `partners_Tenant` class by domain name
export const getAppLogo = async () => {
const domain = window.location.host;
try {
const tenant = await Parse.Cloud.run("getlogobydomain", {
domain: domain
});
if (tenant) {
return { logo: tenant?.logo, user: tenant?.user };
}
} catch (err) {
console.log("err in getlogo ", err);
if (err?.message?.includes("valid JSON")) {
return { logo: appInfo.applogo, user: "exist", error: "invalid_json" };
} else {
return { logo: appInfo.applogo, user: "exist" };
}
const domain = window.location.host;
try {
const tenant = await Parse.Cloud.run("getlogobydomain", {
domain: domain
});
if (tenant) {
localStorage.setItem("appname", "OpenSign™");
return { logo: tenant?.logo, user: tenant?.user };
}
} catch (err) {
console.log("err in getlogo ", err);
if (err?.message?.includes("valid JSON")) {
return { logo: appInfo.applogo, user: "exist", error: "invalid_json" };
} else {
return { logo: appInfo.applogo, user: "exist" };
}
}
};
export const getTenantDetails = async (
objectId,
contactId
) => {
export const getTenantDetails = async (objectId, contactId) => {
try {
const url = `${localStorage.getItem("baseUrl")}functions/gettenant`;
const parseAppId = localStorage.getItem("parseAppId");
const accesstoken = localStorage.getItem("accesstoken");
const token =
{ "X-Parse-Session-Token": accesstoken };
const data =
{ userId: objectId, contactId: contactId };
const token = { "X-Parse-Session-Token": accesstoken };
const data = { userId: objectId, contactId: contactId };
const res = await axios.post(url, data, {
headers: {
"Content-Type": "application/json",
@@ -2159,31 +2192,30 @@ export const handleSendOTP = async (email) => {
"Content-Type": "application/json",
"X-Parse-Application-Id": localStorage.getItem("parseAppId")
};
const body = { email: email };
const body = {
email: email
};
await axios.post(url, body, { headers: headers });
} catch (error) {
alert(error.message);
}
};
export const fetchUrl = async (url, pdfName) => {
const appName = "OpenSign™";
try {
const response = await fetch(url);
if (!response.ok) {
alert("something went wrong, please try again later.");
alert("something went wrong, refreshing this page may solve this issue.");
throw new Error("Network response was not ok");
}
const blob = await response.blob();
saveAs(blob, `${sanitizeFileName(pdfName)}_signed_by_OpenSign™.pdf`);
saveAs(blob, `${sanitizeFileName(pdfName)}_signed_by_${appName}.pdf`);
} catch (error) {
alert("something went wrong, please try again later.");
alert("something went wrong, refreshing this page may solve this issue.");
console.error("Error downloading the file:", error);
}
};
export const getSignedUrl = async (
pdfUrl,
docId,
templateId
) => {
export const getSignedUrl = async (pdfUrl, docId, templateId) => {
//use only axios here due to public template sign
const axiosRes = await axios.post(
`${localStorage.getItem("baseUrl")}/functions/getsignedurl`,
@@ -2244,16 +2276,13 @@ export const handleDownloadPdf = async (
setIsDownloading && setIsDownloading("pdf");
const docId = pdfDetails?.[0]?.objectId || "";
try {
const url = await getSignedUrl(
pdfUrl,
docId,
);
const url = await getSignedUrl(pdfUrl, docId);
await fetchUrl(url, pdfName);
setIsDownloading && setIsDownloading("");
} catch (err) {
console.log("err in getsignedurl", err);
setIsDownloading("");
alert("something went wrong, please try again later.");
alert("something went wrong, refreshing this page may solve this issue.");
}
}
};
@@ -2277,7 +2306,7 @@ export const handleToPrint = async (event, setIsDownloading, pdfDetails) => {
`${localStorage.getItem("baseUrl")}/functions/getsignedurl`,
{
url: pdfUrl,
docId: docId,
docId: docId
},
{
headers: {
@@ -2311,7 +2340,7 @@ export const handleToPrint = async (event, setIsDownloading, pdfDetails) => {
} catch (err) {
setIsDownloading("");
console.log("err in getsignedurl", err);
alert("something went wrong, please try again later.");
alert("something went wrong, refreshing this page may solve this issue.");
}
};
@@ -2321,6 +2350,7 @@ export const handleDownloadCertificate = async (
setIsDownloading,
isZip
) => {
const appName = "OpenSign™";
if (pdfDetails?.length > 0 && pdfDetails[0]?.CertificateUrl) {
try {
await fetch(pdfDetails[0] && pdfDetails[0]?.CertificateUrl);
@@ -2328,7 +2358,7 @@ export const handleDownloadCertificate = async (
if (isZip) {
return certificateUrl;
} else {
saveAs(certificateUrl, `Certificate_signed_by_OpenSign™.pdf`);
saveAs(certificateUrl, `Certificate_signed_by_${appName}.pdf`);
}
} catch (err) {
console.log("err in download in certificate", err);
@@ -2357,7 +2387,7 @@ export const handleDownloadCertificate = async (
setIsDownloading("");
return certificateUrl;
} else {
saveAs(certificateUrl, `Certificate_signed_by_OpenSign™.pdf`);
saveAs(certificateUrl, `Certificate_signed_by_${appName}.pdf`);
setIsDownloading("");
}
} else {
@@ -2382,7 +2412,7 @@ export const handleDownloadCertificate = async (
// Convert the response into a Blob
const certificateBlob = await fetchCertificate.blob();
setIsDownloading("");
saveAs(certificateBlob, `Certificate_signed_by_OpenSign™.pdf`);
saveAs(certificateBlob, `Certificate_signed_by_${appName}.pdf`);
}
} catch (err) {
console.log("err in download in certificate", err);
@@ -2396,7 +2426,7 @@ export const handleDownloadCertificate = async (
} catch (err) {
setIsDownloading("certificate_err");
console.log("err in download in certificate", err);
alert("something went wrong, please try again later.");
alert("something went wrong, refreshing this page may solve this issue.");
}
}
};
@@ -2404,14 +2434,13 @@ export const handleDownloadCertificate = async (
export function escapeRegExp(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // Escape special characters
}
export async function findContact(
value,
) {
export async function findContact(value) {
try {
const baseURL = localStorage.getItem("baseUrl");
const url = `${baseURL}functions/getsigners`;
const token =
{ "X-Parse-Session-Token": localStorage.getItem("accesstoken") };
const token = {
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
};
const headers = {
"Content-Type": "application/json",
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
@@ -2623,23 +2652,20 @@ export function base64ToArrayBuffer(base64) {
return bytes.buffer;
}
export const convertBase64ToFile = async (
pdfName,
pdfBase64,
) => {
export const convertBase64ToFile = async (pdfName, pdfBase64) => {
const fileName = sanitizeFileName(pdfName) + ".pdf";
try {
const pdfFile = new Parse.File(fileName, { base64: pdfBase64 });
// Save the Parse File if needed
const pdfData = await pdfFile.save();
const pdfUrl = pdfData.url();
const fileRes = await getSecureUrl(pdfUrl);
if (fileRes?.url) {
return fileRes.url;
}
} catch (e) {
console.log("error in convertbase64tofile", e);
try {
const pdfFile = new Parse.File(fileName, { base64: pdfBase64 });
// Save the Parse File if needed
const pdfData = await pdfFile.save();
const pdfUrl = pdfData.url();
const fileRes = await getSecureUrl(pdfUrl);
if (fileRes?.url) {
return fileRes.url;
}
} catch (e) {
console.log("error in convertbase64tofile", e);
}
};
export const onClickZoomIn = (scale, zoomPercent, setScale, setZoomPercent) => {
setScale(scale + 0.1 * scale);
@@ -2857,20 +2883,11 @@ export function generatePdfName(length) {
// Format date and time for the selected timezone
export const formatTimeInTimezone = (date, timezone) => {
return timezone
? new Intl.DateTimeFormat("en-US", {
weekday: "short",
year: "numeric",
month: "short",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
timeZone: timezone,
timeZoneName: "short",
hour12: false
}).format(date)
const nyDate = timezone && toZonedTime(date, timezone);
const generatedDate = timezone
? format(nyDate, "EEE, dd MMM yyyy HH:mm:ss zzz", { timeZone: timezone })
: new Date(date).toUTCString();
return generatedDate;
};
// `usertimezone` is used to get timezone of current user
@@ -2936,3 +2953,50 @@ export const flattenPdf = async (pdfFile) => {
const flatPdf = await pdfDoc.save({ useObjectStreams: false });
return flatPdf;
};
export const mailTemplate = (param) => {
const appName = "OpenSign™";
const logo = `<div style='padding:10px'><img src='https://qikinnovation.ams3.digitaloceanspaces.com/logo.png' height='50' /></div>`;
const opurl = ` <a href='https://www.opensignlabs.com' target=_blank>here</a>.</p></div></div></body></html>`;
const subject = `${param.senderName} has requested you to sign "${param.title}"`;
const body =
"<html><head><meta http-equiv='Content-Type' content='text/html;charset=UTF-8' /></head><body><div style='background-color:#f5f5f5;padding:20px'><div style='background:white;padding-bottom:20px'>" +
logo +
`<div style='padding:2px;font-family:system-ui;background-color:${themeColor}'><p style='font-size:20px;font-weight:400;color:white;padding-left:20px'>Digital Signature Request</p></div><div><p style='padding:20px;font-size:14px;margin-bottom:10px'>` +
param.senderName +
" has requested you to review and sign <strong>" +
param.title +
"</strong>.</p><div style='padding: 5px 0px 5px 25px;display:flex;flex-direction:row;justify-content:space-around'><table><tr><td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Sender</td><td></td><td style='color:#626363;font-weight:bold'>" +
param.senderMail +
"</td></tr><tr><td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Organization</td><td></td><td style='color:#626363;font-weight:bold'> " +
param.organization +
"</td></tr><tr><td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Expires on</td><td></td><td style='color:#626363;font-weight:bold'>" +
param.localExpireDate +
"</td></tr><tr><td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Note</td><td></td><td style='color:#626363;font-weight:bold'>" +
param.note +
"</td></tr><tr><td></td><td></td></tr></table></div> <div style='margin-left:70px'><a target=_blank href=" +
param.sigingUrl +
"><button style='padding:12px;background-color:#d46b0f;color:white;border:0px;font-weight:bold;margin-top:30px'>Sign here</button></a></div><div style='display:flex;justify-content:center;margin-top:10px'></div></div></div><div><p> This is an automated email from " +
appName +
". For any queries regarding this email, please contact the sender " +
param.senderMail +
" directly. If you think this email is inappropriate or spam, you may file a complaint with " +
appName +
opurl;
return { subject, body };
};
export function formatDateTime(date, dateFormat, timeZone, is12Hour) {
const zonedDate = toZonedTime(date, timeZone); // Convert date to the given timezone
const timeFormat = is12Hour ? "hh:mm:ss a" : "HH:mm:ss";
return dateFormat
? format(
zonedDate,
`${selectFormat(dateFormat)}, ${timeFormat} 'GMT' XXX`,
{ timeZone }
)
: formatTimeInTimezone(date, timeZone);
}
+3 -5
View File
@@ -1,11 +1,9 @@
import logo from "../assets/images/logo.png";
export function serverUrl_fn() {
let baseUrl;
baseUrl = process.env.REACT_APP_SERVERURL
? process.env.REACT_APP_SERVERURL
: window.location.origin + "/app";
let baseUrl = process.env.REACT_APP_SERVERURL
? process.env.REACT_APP_SERVERURL
: window.location.origin + "/api/app";
return baseUrl;
}
export const appInfo = {
+4
View File
@@ -4,3 +4,7 @@ export const documentCls = "contracts_Document";
export const themeColor = "#47a3ad";
export const iconColor = "#686968";
export const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
export const maxFileSize = 10; // 10MB
export const maxTitleLength = 250; // 250 characters
export const maxNoteLength = 200; // 200 characters
export const maxDescriptionLength = 500; // 500 characters
-8
View File
@@ -14,7 +14,6 @@ import {
Preview
} from "react-dnd-multi-backend";
import DragElement from "./components/pdf/DragElement";
import TagManager from "react-gtm-module";
import Parse from "parse";
import "./polyfills";
import { serverUrl_fn } from "./constant/appinfo";
@@ -56,13 +55,6 @@ const generatePreview = (props) => {
);
};
if (process.env.REACT_APP_GTM) {
const tagManagerArgs = {
gtmId: process.env.REACT_APP_GTM
};
TagManager.initialize(tagManagerArgs);
}
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<CookiesProvider defaultSetOptions={{ path: "/" }}>
+108 -1
View File
@@ -1,4 +1,3 @@
export default function reportJson(id) {
// console.log("json ", json);
const head = ["Title", "Note", "Folder", "File", "Owner", "Signers"];
@@ -29,6 +28,25 @@ export default function reportJson(id) {
btnIcon: "fa-light fa-trash",
redirectUrl: "",
action: "delete"
},
{
btnId: "22534",
hoverLabel: "option",
btnColor: "",
restrictBtn: true,
textColor: "black",
btnIcon: "fa-light fa-ellipsis-vertical fa-lg",
action: "option",
subaction: [
{
btnId: "1630",
btnLabel: "Save as template",
hoverLabel: "Save as template",
btnIcon: "fa-light fa-envelope",
redirectUrl: "",
action: "saveastemplate"
}
]
}
],
helpMsg:
@@ -107,6 +125,14 @@ export default function reportJson(id) {
redirectUrl: "",
action: "revoke"
},
{
btnId: "0630",
btnLabel: "Save as template",
hoverLabel: "Save as template",
btnIcon: "fa-light fa-envelope",
redirectUrl: "",
action: "saveastemplate"
},
{
btnId: "1488",
btnLabel: "Delete",
@@ -143,6 +169,25 @@ export default function reportJson(id) {
restrictBtn: true,
redirectUrl: "",
action: "delete"
},
{
btnId: "33534",
hoverLabel: "option",
btnColor: "",
restrictBtn: true,
textColor: "black",
btnIcon: "fa-light fa-ellipsis-vertical fa-lg",
action: "option",
subaction: [
{
btnId: "0930",
btnLabel: "Save as template",
hoverLabel: "Save as template",
btnIcon: "fa-light fa-envelope",
redirectUrl: "",
action: "saveastemplate"
}
]
}
],
helpMsg:
@@ -169,6 +214,33 @@ export default function reportJson(id) {
btnIcon: "fa-light fa-trash",
redirectUrl: "",
action: "delete"
},
{
btnId: "44534",
hoverLabel: "option",
btnColor: "",
textColor: "black",
btnIcon: "fa-light fa-ellipsis-vertical fa-lg",
action: "option",
subaction: [
{
btnId: "0940",
btnLabel: "Save as template",
hoverLabel: "Save as template",
restrictBtn: true,
btnIcon: "fa-light fa-envelope",
redirectUrl: "",
action: "saveastemplate"
},
{
btnId: "8440",
btnLabel: "Fix & resend",
hoverLabel: "Fix & resend",
btnIcon: "fa-light fa-paper-plane",
redirectUrl: "",
action: "recreatedocument"
}
]
}
],
helpMsg:
@@ -212,6 +284,14 @@ export default function reportJson(id) {
btnIcon: "fa-light fa-hourglass-end",
redirectUrl: "",
action: "extendexpiry"
},
{
btnId: "5530",
btnLabel: "Save as template",
hoverLabel: "Save as template",
btnIcon: "fa-light fa-envelope",
redirectUrl: "",
action: "saveastemplate"
}
]
}
@@ -273,6 +353,14 @@ export default function reportJson(id) {
redirectUrl: "",
action: "revoke"
},
{
btnId: "7730",
btnLabel: "Save as template",
hoverLabel: "Save as template",
btnIcon: "fa-light fa-envelope",
redirectUrl: "",
action: "saveastemplate"
},
{
btnId: "2000",
btnLabel: "Delete",
@@ -323,6 +411,25 @@ export default function reportJson(id) {
btnIcon: "fa-light fa-trash",
redirectUrl: "",
action: "delete"
},
{
btnId: "55534",
hoverLabel: "option",
btnColor: "",
restrictBtn: true,
textColor: "black",
btnIcon: "fa-light fa-ellipsis-vertical fa-lg",
action: "option",
subaction: [
{
btnId: "6630",
btnLabel: "Save as template",
hoverLabel: "Save as template",
btnIcon: "fa-light fa-envelope",
redirectUrl: "",
action: "saveastemplate"
}
]
}
]
};
+1 -1
View File
@@ -74,7 +74,7 @@ const sidebarList = [
target: "_self",
pageType: "",
description: "",
objectId: "opensigndrive"
objectId: "drive"
},
{
icon: "fa-light fa-address-card",
+5 -4
View File
@@ -15,6 +15,7 @@ import { showHeader } from "../redux/reducers/showHeader";
import { useTranslation } from "react-i18next";
const HomeLayout = () => {
const appName = "OpenSign™";
const { t, i18n } = useTranslation();
const navigate = useNavigate();
const location = useLocation();
@@ -52,8 +53,8 @@ const HomeLayout = () => {
});
if (user) {
localStorage.setItem("profileImg", user.get("ProfilePic") || "");
setIsUserValid(true);
setIsLoader(false);
setIsUserValid(true);
setIsLoader(false);
} else {
setIsUserValid(false);
}
@@ -70,7 +71,7 @@ const HomeLayout = () => {
//function to use save data in cookies storage
const saveCookies = () => {
const main_Domain = window.location.origin;
const domainName = window.location.hostname; //app.opensignlabs.com
const domainName = window.location.hostname;
// Find the index of the first dot in the string
const indexOfFirstDot = domainName.indexOf(".");
// Remove the first dot and get the substring starting from the next character
@@ -149,7 +150,7 @@ const HomeLayout = () => {
...resArr,
{
selector: '[data-tut="reactourLast"]',
content: t("tour-mssg.home-layout-3"),
content: t("tour-mssg.home-layout-3", { appName }),
position: "top"
// style: { backgroundColor: "#abd4d2" },
}
+7 -6
View File
@@ -16,6 +16,7 @@ import { useTranslation } from "react-i18next";
import { emailRegex } from "../constant/const";
const AddAdmin = () => {
const appName = "OpenSign™";
const { t, i18n } = useTranslation();
const navigate = useNavigate();
const dispatch = useDispatch();
@@ -48,7 +49,7 @@ const AddAdmin = () => {
try {
const app = await getAppLogo();
if (app?.error === "invalid_json") {
setErrMsg(t("server-down"));
setErrMsg(t("server-down", { appName: appName }));
} else if (app?.user === "exist") {
setErrMsg(t("admin-exists"));
}
@@ -94,7 +95,7 @@ const AddAdmin = () => {
setState({ loading: true });
const userDetails = {
name: name,
email: email,
email: email?.toLowerCase()?.replace(/\s/g, ""),
phone: phone,
company: company,
jobTitle: jobTitle
@@ -104,10 +105,10 @@ const AddAdmin = () => {
event.preventDefault();
const user = new Parse.User();
user.set("name", name);
user.set("email", email);
user.set("email", email?.toLowerCase()?.replace(/\s/g, ""));
user.set("password", password);
user.set("phone", phone);
user.set("username", email);
user.set("username", email?.toLowerCase()?.replace(/\s/g, ""));
const userRes = await user.save();
if (userRes) {
const params = {
@@ -115,7 +116,7 @@ const AddAdmin = () => {
jobTitle: jobTitle,
company: company,
name: name,
email: email,
email: email?.toLowerCase()?.replace(/\s/g, ""),
phone: phone,
role: "contracts_Admin",
timezone: usertimezone
@@ -280,7 +281,7 @@ const AddAdmin = () => {
<form onSubmit={handleSubmit}>
<div className="w-full my-4 op-card bg-base-100 shadow-md outline outline-1 outline-slate-300/50">
<h2 className="text-[30px] text-center mt-3 font-medium">
{t("opensign-setup")}
{t("opensign-setup", { appName })}
</h2>
<NavLink
to="https://discord.com/invite/xe9TDuyAyj"
+83 -44
View File
@@ -1,5 +1,6 @@
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import Confetti from "react-confetti"; // Import the confetti library
import {
getBase64FromUrl,
handleDownloadCertificate,
@@ -9,19 +10,26 @@ import {
import ModalUi from "../primitives/ModalUi";
import Loader from "../primitives/Loader";
import DownloadPdfZip from "../primitives/DownloadPdfZip";
import Title from "../components/Title";
import CheckCircle from "../primitives/CheckCircle";
const DocSuccessPage = () => {
const { t } = useTranslation();
const signed = window.location?.search?.includes("docid");
const sent = window.location?.search?.includes("message");
const [isDownloading, setIsDownloading] = useState(false);
const [isDownloadModal, setIsDownloadModal] = useState(false);
const [pdfDetails, setPdfDetails] = useState([]);
const [pdfBase64Url, setPdfBase64Url] = useState("");
const { t } = useTranslation();
const [showConfetti, setShowConfetti] = useState(true); // State to control confetti
useEffect(() => {
initialsetup();
// Stop confetti after 5 seconds
const timer = setTimeout(() => setShowConfetti(false), 5000);
return () => clearTimeout(timer);
}, []);
const initialsetup = async () => {
const search = window.location.search.split("?")[1];
if (search) {
@@ -43,6 +51,7 @@ const DocSuccessPage = () => {
}
}
};
const handleDownload = () => {
if (pdfDetails?.[0]?.IsCompleted) {
setIsDownloadModal(true);
@@ -50,49 +59,79 @@ const DocSuccessPage = () => {
handleDownloadPdf(pdfDetails, setIsDownloading, pdfBase64Url);
}
};
return (
<div className="flex h-screen justify-center items-center text-sm md:text-base">
<>
<Title title="Success" />
{/* Confetti Effect */}
{showConfetti && (
<Confetti width={window.innerWidth} height={window.innerHeight} />
)}
{sent ? (
<div>{t("doc-sent")}</div>
<div className="min-h-screen flex flex-col items-center justify-center p-3 md:p-8 text-center">
<div className="max-w-lg md:max-w-2xl bg-white rounded-lg shadow-lg p-3 md:p-10">
{t("doc-sent")}
</div>
</div>
) : signed ? (
<div className="text-center">
<p>
{pdfDetails?.[0]?.IsCompleted
? t("document-signed-alert-4")
: t("document-signed-alert")}
</p>
<div className="m-2">
<button
onClick={(e) => handleToPrint(e, setIsDownloading, pdfDetails)}
type="button"
className="font-[500] text-[13px] mr-[5px] op-btn op-btn-neutral"
>
<i className="fa-light fa-print" aria-hidden="true"></i>
<span className="hidden lg:block">{t("print")}</span>
</button>
{pdfDetails?.[0]?.IsCompleted && (
<button
type="button"
onClick={() =>
handleDownloadCertificate(pdfDetails, setIsDownloading)
}
className="font-[500] text-[13px] mr-[5px] op-btn op-btn-secondary"
>
<i
className="fa-light fa-award mx-[3px] lg:mx-0"
aria-hidden="true"
></i>
<span className="hidden lg:block">{t("certificate")}</span>
</button>
)}
<button
type="button"
className="font-[500] text-[13px] mr-[5px] op-btn op-btn-primary"
onClick={() => handleDownload()}
>
<i className="fa-light fa-download" aria-hidden="true"></i>
<span className="hidden lg:block">{t("download")}</span>
</button>
<>
<div className="min-h-screen flex flex-col items-center justify-center p-3 md:p-8 text-center">
<div className="max-w-lg md:max-w-2xl bg-white rounded-lg shadow-lg p-3 md:p-10">
<div className="flex flex-col items-center space-y-4 ">
<CheckCircle className="text-green-500 w-12 h-12 md:w-14 md:h-14" />
<h1 className="text-xl md:text-2xl font-semibold text-gray-800">
{pdfDetails?.[0]?.IsCompleted
? t("document-has-been-signed")
: t("document-has-been-signed-by-you")}
</h1>
{pdfDetails?.[0]?.IsCompleted && (
<p className="text-sm md:text-base text-gray-600">
{t("participant-completed-signing")}
</p>
)}
</div>
{/* Action Buttons */}
<div className="mt-6 flex flex-wrap justify-center gap-2">
<button
type="button"
className="font-medium text-sm md:text-[13px] md:px-4 py-2 op-btn op-btn-primary"
onClick={() => handleDownload()}
>
<i className="fa-light fa-download" aria-hidden="true"></i>
<span>{t("download")}</span>
</button>
{pdfDetails?.[0]?.IsCompleted && (
<button
type="button"
onClick={() =>
handleDownloadCertificate(pdfDetails, setIsDownloading)
}
className="font-medium text-sm md:text-[13px] md:px-4 py-2 op-btn op-btn-secondary"
>
<i
className="fa-light fa-award mx-[3px] md:mx-0"
aria-hidden="true"
></i>
<span>{t("certificate")}</span>
</button>
)}
<button
onClick={(e) =>
handleToPrint(e, setIsDownloading, pdfDetails)
}
type="button"
className="font-medium text-sm md:text-[13px] px-4 py-2 op-btn op-btn-neutral"
>
<i className="fa-light fa-print" aria-hidden="true"></i>
<span>{t("print")}</span>
</button>
</div>
{/* Footer Message */}
<p className="mt-4 md:mt-6 text-xs md:text-sm text-gray-500">
{t("you-will-receive-email-shortly")}
</p>
</div>
</div>
{isDownloading === "pdf" && (
<div className="fixed z-[1000] inset-0 flex justify-center items-center bg-black bg-opacity-30">
@@ -112,7 +151,7 @@ const DocSuccessPage = () => {
}
handleClose={() => setIsDownloading("")}
>
<div className="p-3 md:p-5 text-[13px] md:text-base text-center text-base-content">
<div className="p-3 md:p-5 text-sm md:text-base text-center text-base-content">
{isDownloading === "certificate" ? (
<p>{t("generate-certificate-alert")}</p>
) : (
@@ -127,11 +166,11 @@ const DocSuccessPage = () => {
isDocId={true}
pdfBase64={pdfBase64Url}
/>
</div>
</>
) : (
<></>
)}
</div>
</>
);
};
+19 -14
View File
@@ -7,17 +7,17 @@ import Alert from "../primitives/Alert";
import { appInfo } from "../constant/appinfo";
import { useDispatch } from "react-redux";
import { fetchAppInfo } from "../redux/reducers/infoReducer";
import {
emailRegex,
} from "../constant/const";
import { emailRegex } from "../constant/const";
import { useTranslation } from "react-i18next";
import Loader from "../primitives/Loader";
function ForgotPassword() {
const { t } = useTranslation();
const dispatch = useDispatch();
const navigate = useNavigate();
const [state, setState] = useState({ email: "", password: "", hideNav: "" });
const [sentStatus, setSentStatus] = useState("");
const [toast, setToast] = useState({ type: "", message: "" });
const [isLoading, setIsLoading] = useState(false);
const [image, setImage] = useState();
const handleChange = (event) => {
@@ -40,18 +40,23 @@ function ForgotPassword() {
if (!emailRegex.test(state.email)) {
alert("Please enter a valid email address.");
} else {
setIsLoading(true);
localStorage.setItem("appLogo", appInfo.applogo);
localStorage.setItem("userSettings", JSON.stringify(appInfo.settings));
if (state.email) {
const username = state.email;
try {
await Parse.User.requestPasswordReset(username);
setSentStatus("success");
setToast({ type: "success", message: t("reset-password-alert-1") });
} catch (err) {
console.log("err ", err.code);
setSentStatus("failed");
setToast({
type: "danger",
message: err.message || t("reset-password-alert-2")
});
} finally {
setTimeout(() => setSentStatus(""), 1000);
setIsLoading(false);
setTimeout(() => setToast({ type: "", message: "" }), 1000);
}
}
}
@@ -71,17 +76,17 @@ function ForgotPassword() {
} catch (err) {
console.log("err while logging out ", err);
}
setImage(appInfo?.applogo || undefined);
setImage(appInfo?.applogo || undefined);
};
return (
<div>
<Title title="Forgot password page" />
{sentStatus === "success" && (
<Alert type="success">{t("reset-password-alert-1")}</Alert>
)}
{sentStatus === "failed" && (
<Alert type={"danger"}>{t("reset-password-alert-2")}</Alert>
{isLoading && (
<div className="fixed w-full h-full flex justify-center items-center bg-black bg-opacity-30 z-50">
<Loader />
</div>
)}
<Title title="Forgot password" />
{toast?.message && <Alert type={toast.type}>{toast.message}</Alert>}
<div className="md:p-10 lg:p-16">
<div className="md:p-4 lg:p-10 p-4 bg-base-100 text-base-content op-card">
<div className="w-[250px] h-[66px] inline-block overflow-hidden">
+241 -223
View File
@@ -18,6 +18,12 @@ import {
} from "../constant/Utils";
import { PDFDocument } from "pdf-lib";
import axios from "axios";
import {
maxFileSize,
maxDescriptionLength,
maxNoteLength,
maxTitleLength
} from "../constant/const";
import ModalUi from "../primitives/ModalUi";
import { Tooltip } from "react-tooltip";
import Loader from "../primitives/Loader";
@@ -36,8 +42,8 @@ function Form() {
}
const Forms = (props) => {
const appName = "OpenSign™";
const { t } = useTranslation();
const maxFileSize = 20;
const abortController = new AbortController();
const inputFileRef = useRef(null);
const navigate = useNavigate();
@@ -79,6 +85,15 @@ const Forms = (props) => {
const extUserData =
localStorage.getItem("Extand_Class") &&
JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
const sendinorder =
extUserData?.SendinOrder !== undefined && extUserData?.SendinOrder === false
? "false"
: "true";
const istourenabled =
extUserData?.IsTourEnabled !== undefined &&
extUserData?.IsTourEnabled === false
? "false"
: "true";
useEffect(() => {
handleReset();
return () => abortController.abort();
@@ -90,7 +105,12 @@ const Forms = (props) => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const initializeValues = async () => {
setFormData((obj) => ({ ...obj, NotifyOnSignatures: true }));
setFormData((obj) => ({
...obj,
NotifyOnSignatures: true,
SendinOrder: sendinorder,
IsTourEnabled: istourenabled
}));
};
function getFileAsArrayBuffer(file) {
@@ -128,17 +148,81 @@ const Forms = (props) => {
const name = generatePdfName(16);
const pdfName = `${name?.split(".")[0]}.pdf`;
setfileload(true);
try {
const res = await getFileAsArrayBuffer(files[0]);
const flatPdf = await flattenPdf(res);
const parseFile = new Parse.File(
pdfName,
[...flatPdf],
"application/pdf"
);
try {
const res = await getFileAsArrayBuffer(files[0]);
const flatPdf = await flattenPdf(res);
const parseFile = new Parse.File(
pdfName,
[...flatPdf],
"application/pdf"
);
try {
const response = await parseFile.save({
progress: (progressValue, loaded, total, { type }) => {
if (type === "upload" && progressValue !== null) {
const percentCompleted = Math.round(
(loaded * 100) / total
);
setpercentage(percentCompleted);
}
}
});
// The response object will contain information about the uploaded file
// You can access the URL of the uploaded file using response.url()
if (response.url()) {
const fileRes = await getSecureUrl(response.url());
if (fileRes.url) {
setFileUpload(fileRes.url);
setfileload(false);
const tenantId = localStorage.getItem("TenantId");
const title = generateTitleFromFilename(files?.[0]?.name);
setFormData((obj) => ({ ...obj, Name: title }));
SaveFileSize(size, fileRes.url, tenantId);
return fileRes.url;
} else {
removeFile(e);
}
} else {
removeFile(e);
}
} catch (error) {
removeFile(e);
console.error("Error uploading file:", error);
}
} catch (err) {
if (err?.message?.includes("is encrypted")) {
try {
const response = await parseFile.save({
setIsDecrypting(true);
const size = files?.[0].size;
const name = generatePdfName(16);
const url = "https://ai.nxglabs.in/decryptpdf"; //
let formData = new FormData();
formData.append("file", files[0]);
formData.append("password", "");
const config = {
headers: { "content-type": "multipart/form-data" },
responseType: "blob"
};
const response = await axios.post(url, formData, config);
const pdfBlob = new Blob([response.data], {
type: "application/pdf"
});
const pdfFile = new File([pdfBlob], name, {
type: "application/pdf"
});
setIsDecrypting(false);
setfileload(true);
const res = await getFileAsArrayBuffer(pdfFile);
const flatPdf = await flattenPdf(res);
// Upload the file to Parse Server
const parseFile = new Parse.File(
name,
[...flatPdf],
"application/pdf"
);
await parseFile.save({
progress: (progressValue, loaded, total, { type }) => {
if (type === "upload" && progressValue !== null) {
const percentCompleted = Math.round(
@@ -148,16 +232,16 @@ const Forms = (props) => {
}
}
});
// The response object will contain information about the uploaded file
// You can access the URL of the uploaded file using response.url()
if (response.url()) {
const fileRes = await getSecureUrl(response.url());
// Retrieve the URL of the uploaded file
if (parseFile.url()) {
const fileRes = await getSecureUrl(parseFile.url());
if (fileRes.url) {
setFileUpload(fileRes.url);
setfileload(false);
const tenantId = localStorage.getItem("TenantId");
removeFile();
const title = generateTitleFromFilename(files?.[0]?.name);
setFormData((obj) => ({ ...obj, Name: title }));
const tenantId = localStorage.getItem("TenantId");
SaveFileSize(size, fileRes.url, tenantId);
return fileRes.url;
} else {
@@ -166,95 +250,22 @@ const Forms = (props) => {
} else {
removeFile(e);
}
} catch (error) {
removeFile(e);
console.error("Error uploading file:", error);
}
} catch (err) {
if (err?.message?.includes("is encrypted")) {
try {
await Parse.Cloud.run("encryptedpdf", {
email: Parse.User.current().getEmail()
});
} catch (err) {
console.log("err in sending posthog encryptedpdf", err);
}
try {
setIsDecrypting(true);
const size = files?.[0].size;
const name = generatePdfName(16);
const url = "https://ai.nxglabs.in/decryptpdf"; //
let formData = new FormData();
formData.append("file", files[0]);
formData.append("password", "");
const config = {
headers: { "content-type": "multipart/form-data" },
responseType: "blob"
};
const response = await axios.post(url, formData, config);
const pdfBlob = new Blob([response.data], {
type: "application/pdf"
});
const pdfFile = new File([pdfBlob], name, {
type: "application/pdf"
});
} catch (err) {
removeFile();
if (err?.response?.status === 401) {
setIsPassword(true);
} else {
console.log("Error uploading file: ", err?.response);
setIsDecrypting(false);
setfileload(true);
const res = await getFileAsArrayBuffer(pdfFile);
const flatPdf = await flattenPdf(res);
// Upload the file to Parse Server
const parseFile = new Parse.File(
name,
[...flatPdf],
"application/pdf"
);
await parseFile.save({
progress: (progressValue, loaded, total, { type }) => {
if (type === "upload" && progressValue !== null) {
const percentCompleted = Math.round(
(loaded * 100) / total
);
setpercentage(percentCompleted);
}
}
});
// Retrieve the URL of the uploaded file
if (parseFile.url()) {
const fileRes = await getSecureUrl(parseFile.url());
if (fileRes.url) {
setFileUpload(fileRes.url);
removeFile();
const title = generateTitleFromFilename(
files?.[0]?.name
);
setFormData((obj) => ({ ...obj, Name: title }));
const tenantId = localStorage.getItem("TenantId");
SaveFileSize(size, fileRes.url, tenantId);
return fileRes.url;
} else {
removeFile(e);
}
} else {
removeFile(e);
}
} catch (err) {
removeFile();
if (err?.response?.status === 401) {
setIsPassword(true);
} else {
console.log("Error uploading file: ", err?.response);
setIsDecrypting(false);
e.target.value = "";
}
e.target.value = "";
}
} else {
console.log("err ", err);
setFileUpload("");
removeFile(e);
}
} else {
console.log("err ", err);
setFileUpload("");
removeFile(e);
}
}
} else {
const isImage = files?.[0]?.type.includes("image/");
if (isImage) {
@@ -279,50 +290,50 @@ const Forms = (props) => {
});
const size = files?.[0]?.size;
const name = generatePdfName(16);
const getFile = await pdfDoc.save({
useObjectStreams: false
});
setfileload(true);
const pdfName = `${name?.split(".")[0]}.pdf`;
const parseFile = new Parse.File(
pdfName,
[...getFile],
"application/pdf"
);
const getFile = await pdfDoc.save({
useObjectStreams: false
});
setfileload(true);
const pdfName = `${name?.split(".")[0]}.pdf`;
const parseFile = new Parse.File(
pdfName,
[...getFile],
"application/pdf"
);
try {
const response = await parseFile.save({
progress: (progressValue, loaded, total, { type }) => {
if (type === "upload" && progressValue !== null) {
const percentCompleted = Math.round(
(loaded * 100) / total
);
setpercentage(percentCompleted);
}
}
});
// The response object will contain information about the uploaded file
// You can access the URL of the uploaded file using response.url()
if (response.url()) {
const fileRes = await getSecureUrl(response.url());
if (fileRes.url) {
setFileUpload(fileRes.url);
setfileload(false);
const tenantId = localStorage.getItem("TenantId");
const title = generateTitleFromFilename(files?.[0]?.name);
setFormData((obj) => ({ ...obj, Name: title }));
SaveFileSize(size, fileRes.url, tenantId);
return fileRes.url;
} else {
removeFile(e);
try {
const response = await parseFile.save({
progress: (progressValue, loaded, total, { type }) => {
if (type === "upload" && progressValue !== null) {
const percentCompleted = Math.round(
(loaded * 100) / total
);
setpercentage(percentCompleted);
}
}
});
// The response object will contain information about the uploaded file
// You can access the URL of the uploaded file using response.url()
if (response.url()) {
const fileRes = await getSecureUrl(response.url());
if (fileRes.url) {
setFileUpload(fileRes.url);
setfileload(false);
const tenantId = localStorage.getItem("TenantId");
const title = generateTitleFromFilename(files?.[0]?.name);
setFormData((obj) => ({ ...obj, Name: title }));
SaveFileSize(size, fileRes.url, tenantId);
return fileRes.url;
} else {
removeFile(e);
}
} catch (error) {
} else {
removeFile(e);
console.error("Error uploading file:", error);
}
} catch (error) {
removeFile(e);
console.error("Error uploading file:", error);
}
}
}
}
@@ -348,6 +359,18 @@ const Forms = (props) => {
e.preventDefault();
e.stopPropagation();
if (fileupload) {
if (formData?.Name?.length > maxTitleLength) {
alert(t("title-length-alert"));
return;
}
if (formData?.Note?.length > maxNoteLength) {
alert(t("note-length-alert"));
return;
}
if (formData?.Description?.length > maxDescriptionLength) {
alert(t("description-length-alert"));
return;
}
if (formData.RedirectUrl && !isValidURL(formData?.RedirectUrl)) {
alert(t("invalid-redirect-url"));
return;
@@ -372,19 +395,24 @@ const Forms = (props) => {
const isChecked = formData.SendinOrder === "false" ? false : true;
const isTourEnabled =
formData?.IsTourEnabled === "false" ? false : true;
const remindOnceInEvery = parseInt(formData.remindOnceInEvery);
const TimeToCompleteDays = parseInt(formData?.TimeToCompleteDays);
const AutomaticReminders = formData.autoreminder;
const reminderCount = TimeToCompleteDays / remindOnceInEvery;
if (AutomaticReminders && reminderCount > 15) {
alert(t("only-15-reminder-allowed"));
return;
}
object.set("SendinOrder", isChecked);
object.set("AutomaticReminders", formData.autoreminder);
object.set("RemindOnceInEvery", parseInt(formData.remindOnceInEvery));
object.set("AutomaticReminders", AutomaticReminders);
object.set("RemindOnceInEvery", remindOnceInEvery);
object.set("IsTourEnabled", isTourEnabled);
object.set(
"TimeToCompleteDays",
parseInt(formData?.TimeToCompleteDays)
);
object.set("AllowModifications", false);
object.set("IsEnableOTP", false);
if (formData.NotifyOnSignatures !== undefined) {
object.set("NotifyOnSignatures", formData.NotifyOnSignatures);
}
object.set("TimeToCompleteDays", TimeToCompleteDays);
object.set("AllowModifications", false);
object.set("IsEnableOTP", false);
if (formData.NotifyOnSignatures !== undefined) {
object.set("NotifyOnSignatures", formData.NotifyOnSignatures);
}
if (formData?.RedirectUrl) {
object.set("RedirectUrl", formData.RedirectUrl);
}
@@ -423,10 +451,9 @@ const Forms = (props) => {
setSigners([]);
setBcc([]);
setFolder({ ObjectId: "", Name: "" });
const notifySign =
extUserData?.NotifyOnSignatures
? extUserData?.NotifyOnSignatures
: true;
const notifySign = extUserData?.NotifyOnSignatures
? extUserData?.NotifyOnSignatures
: true;
setFormData({
Name: "",
Description: "",
@@ -435,29 +462,34 @@ const Forms = (props) => {
? "Note to myself"
: "Please review and sign this document",
TimeToCompleteDays: 15,
SendinOrder: "true",
SendinOrder: sendinorder,
password: "",
file: "",
NotifyOnSignatures: notifySign,
remindOnceInEvery: 5,
autoreminder: false,
IsEnableOTP: "false",
IsTourEnabled: "true",
IsTourEnabled: istourenabled,
RedirectUrl: "",
AllowModifications: false
});
setFileUpload("");
setpercentage(0);
setIsAlert((obj) => ({
...obj,
type: "success",
message: `${props.msgVar} created successfully!`
}));
navigate(`/${props?.redirectRoute}/${res.id}`);
}
} catch (err) {
console.log("err ", err);
setIsAlert({ type: "danger", message: t("something-went-wrong-mssg") });
if (err.message === "only 15 reminder allowed") {
setIsAlert({
type: "danger",
message: t("only-15-reminder-allowed")
});
} else {
setIsAlert({
type: "danger",
message: t("something-went-wrong-mssg")
});
}
} finally {
setTimeout(() => setIsAlert({ type: "success", message: "" }), 1000);
setIsSubmit(false);
@@ -497,10 +529,9 @@ const Forms = (props) => {
setSigners([]);
setBcc([]);
setFolder({ ObjectId: "", Name: "" });
const notifySign =
extUserData?.NotifyOnSignatures
? extUserData?.NotifyOnSignatures
: true;
const notifySign = extUserData?.NotifyOnSignatures
? extUserData?.NotifyOnSignatures
: true;
let obj = {
Name: "",
Description: "",
@@ -509,13 +540,13 @@ const Forms = (props) => {
? "Note to myself"
: "Please review and sign this document",
TimeToCompleteDays: 15,
SendinOrder: "true",
SendinOrder: sendinorder,
password: "",
file: "",
remindOnceInEvery: 5,
autoreminder: false,
IsEnableOTP: "false",
IsTourEnabled: "true",
IsTourEnabled: istourenabled,
NotifyOnSignatures: notifySign,
RedirectUrl: "",
AllowModifications: false
@@ -554,36 +585,28 @@ const Forms = (props) => {
type: "application/pdf"
});
setIsDecrypting(false);
const res = await getFileAsArrayBuffer(pdfFile);
const flatPdf = await flattenPdf(res);
const parseFile = new Parse.File(name, [...flatPdf], "application/pdf");
await parseFile.save({
progress: (progressValue, loaded, total, { type }) => {
if (type === "upload" && progressValue !== null) {
const percentCompleted = Math.round((loaded * 100) / total);
setpercentage(percentCompleted);
}
}
});
// Retrieve the URL of the uploaded file
if (parseFile.url()) {
const fileRes = await getSecureUrl(parseFile.url());
if (fileRes.url) {
setFileUpload(fileRes.url);
removeFile();
const title = generateTitleFromFilename(formData?.file?.name);
setFormData((obj) => ({ ...obj, password: "", Name: title }));
const tenantId = localStorage.getItem("TenantId");
SaveFileSize(size, fileRes.url, tenantId);
return fileRes.url;
} else {
removeFile();
setFormData((prev) => ({ ...prev, password: "" }));
setIsDecrypting(false);
if (inputFileRef.current) {
inputFileRef.current.value = ""; // Set file input value to empty string
}
const res = await getFileAsArrayBuffer(pdfFile);
const flatPdf = await flattenPdf(res);
const parseFile = new Parse.File(name, [...flatPdf], "application/pdf");
await parseFile.save({
progress: (progressValue, loaded, total, { type }) => {
if (type === "upload" && progressValue !== null) {
const percentCompleted = Math.round((loaded * 100) / total);
setpercentage(percentCompleted);
}
}
});
// Retrieve the URL of the uploaded file
if (parseFile.url()) {
const fileRes = await getSecureUrl(parseFile.url());
if (fileRes.url) {
setFileUpload(fileRes.url);
removeFile();
const title = generateTitleFromFilename(formData?.file?.name);
setFormData((obj) => ({ ...obj, password: "", Name: title }));
const tenantId = localStorage.getItem("TenantId");
SaveFileSize(size, fileRes.url, tenantId);
return fileRes.url;
} else {
removeFile();
setFormData((prev) => ({ ...prev, password: "" }));
@@ -592,6 +615,14 @@ const Forms = (props) => {
inputFileRef.current.value = ""; // Set file input value to empty string
}
}
} else {
removeFile();
setFormData((prev) => ({ ...prev, password: "" }));
setIsDecrypting(false);
if (inputFileRef.current) {
inputFileRef.current.value = ""; // Set file input value to empty string
}
}
} catch (err) {
removeFile();
if (err?.response?.status === 401) {
@@ -713,9 +744,7 @@ const Forms = (props) => {
)}
<div className="text-xs">
<label className="block">
{`${`${t("report-heading.File")} (${t("file-type")}`}${
")"
}`}
{`${`${t("report-heading.File")} (${t("file-type")}`}${")"}`}
<span className="text-red-500 text-[13px]">*</span>
</label>
{fileupload.length > 0 ? (
@@ -739,9 +768,7 @@ const Forms = (props) => {
className="op-file-input op-file-input-bordered op-file-input-sm focus:outline-none hover:border-base-content w-full text-xs"
onChange={(e) => handleFileInput(e)}
ref={inputFileRef}
accept={
"application/pdf,image/png,image/jpeg"
}
accept={"application/pdf,image/png,image/jpeg"}
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
@@ -842,7 +869,7 @@ const Forms = (props) => {
<div className="max-w-[200px] md:max-w-[450px]">
<p className="font-bold">{t("send-in-order")}</p>
<p>{t("send-in-order-help.p1")}</p>
<p className="p-[5px]">
<div className="p-[5px]">
<ol className="list-disc">
<li>
<span className="font-bold">
@@ -857,7 +884,7 @@ const Forms = (props) => {
<span>{t("send-in-order-help.p3")}</span>
</li>
</ol>
</p>
</div>
<p>{t("send-in-order-help.p4")}</p>
</div>
</Tooltip>
@@ -939,7 +966,7 @@ const Forms = (props) => {
{t("send-in-order")}
</p>
<p>{t("send-in-order-help.p1")}</p>
<p className="p-[5px]">
<div className="p-[5px]">
<ol className="list-disc">
<li>
<span className="font-bold">
@@ -958,7 +985,7 @@ const Forms = (props) => {
</span>
</li>
</ol>
</p>
</div>
<p>{t("send-in-order-help.p4")}</p>
</div>
</Tooltip>
@@ -1031,10 +1058,7 @@ const Forms = (props) => {
{isAdvanceOpt && (
<div
style={{
height:
props.title === "New Template"
? "100px"
: "280px"
height: props.title === "New Template" ? "100px" : "280px"
}}
className="w-[1px] bg-gray-300 m-auto hidden md:inline-block"
></div>
@@ -1079,7 +1103,7 @@ const Forms = (props) => {
<Tooltip id="istourenabled-tooltip" className="z-[999]">
<div className="max-w-[200px] md:max-w-[450px]">
<p className="font-bold">{t("enable-tour")}</p>
<p className="p-[5px]">
<div className="p-[5px]">
<ol className="list-disc">
<li>
<span className="font-bold">
@@ -1092,8 +1116,10 @@ const Forms = (props) => {
<span>{t("istourenabled-help.p2")}</span>
</li>
</ol>
</div>
<p>
{t("istourenabled-help.p3", { appName: appName })}
</p>
<p>{t("istourenabled-help.p3")}</p>
</div>
</Tooltip>
</label>
@@ -1141,11 +1167,7 @@ const Forms = (props) => {
</Tooltip>
</label>
<div className="flex flex-col md:flex-row md:gap-4">
<div
className={
`flex items-center gap-2 ml-2 mb-1`
}
>
<div className={`flex items-center gap-2 ml-2 mb-1`}>
<input
className="mr-[2px] op-radio op-radio-xs"
type="radio"
@@ -1154,11 +1176,7 @@ const Forms = (props) => {
/>
<div className="text-center">{t("yes")}</div>
</div>
<div
className={
`flex items-center gap-2 ml-2 mb-1`
}
>
<div className={`flex items-center gap-2 ml-2 mb-1`}>
<input
className="mr-[2px] op-radio op-radio-xs"
type="radio"
+45 -22
View File
@@ -1,25 +1,23 @@
import React, { useState, useEffect } from "react";
import { useNavigate, useParams } from "react-router";
import axios from "axios";
import {
emailRegex,
} from "../constant/const";
import {
contractUsers,
saveLanguageInLocal
} from "../constant/Utils";
import { emailRegex } from "../constant/const";
import { contractUsers, saveLanguageInLocal } from "../constant/Utils";
import logo from "../assets/images/logo.png";
import { appInfo } from "../constant/appinfo";
import Parse from "parse";
import { useTranslation } from "react-i18next";
import SelectLanguage from "../components/pdf/SelectLanguage";
import LoaderWithMsg from "../primitives/LoaderWithMsg";
import Title from "../components/Title";
function GuestLogin() {
const { t, i18n } = useTranslation();
const { id, userMail, contactBookId, base64url } = useParams();
const navigate = useNavigate();
const [email, setEmail] = useState(userMail);
const [email, setEmail] = useState(
userMail?.toLowerCase()?.replace(/\s/g, "")
);
const [OTP, setOTP] = useState("");
const [EnterOTP, setEnterOtp] = useState(false);
const [loading, setLoading] = useState(false);
@@ -62,9 +60,10 @@ function GuestLogin() {
//function generate serverUrl and parseAppId from url and save it in local storage
const handleServerUrl = async () => {
setAppLogo(logo);
setAppLogo(logo);
localStorage.clear();
localStorage.clear(); // Clears everything
localStorage.setItem("appname", "OpenSign™");
//save isGuestSigner true in local to handle login flow header in mobile view
localStorage.setItem("isGuestSigner", true);
saveLanguageInLocal(i18n);
@@ -79,12 +78,18 @@ function GuestLogin() {
//split url in array from '/'
const checkSplit = decodebase64.split("/");
setDocumentId(checkSplit[0]);
setContact((prev) => ({ ...prev, email: checkSplit[1] }));
setEmail(checkSplit[1]);
setContact((prev) => ({
...prev,
email: checkSplit[1]?.toLowerCase()?.replace(/\s/g, "")
}));
setEmail(checkSplit[1]?.toLowerCase()?.replace(/\s/g, ""));
const contactId = checkSplit?.[2];
setSendmail(checkSplit[3]);
if (!contactId) {
const params = { email: checkSplit[1], docId: checkSplit[0] };
const params = {
email: checkSplit[1]?.toLowerCase()?.replace(/\s/g, ""),
docId: checkSplit[0]
};
try {
const linkContactRes = await Parse.Cloud.run(
"linkcontacttodoc",
@@ -106,9 +111,12 @@ function GuestLogin() {
//send email OTP function
const SendOtp = async () => {
setLoading(true);
setEmail(email);
setEmail(email?.toLowerCase()?.replace(/\s/g, ""));
try {
const params = { email: email.toString(), docId: documentId };
const params = {
email: email?.toLowerCase()?.replace(/\s/g, "")?.toString(),
docId: documentId
};
const Otp = await Parse.Cloud.run("SendOTPMailV1", params);
if (Otp) {
setLoading(false);
@@ -139,7 +147,10 @@ function GuestLogin() {
"Content-Type": "application/json",
"X-Parse-Application-Id": parseId
};
let body = { email: email, otp: OTP };
let body = {
email: email?.toLowerCase()?.replace(/\s/g, ""),
otp: OTP
};
let user = await axios.post(url, body, { headers: headers });
if (user.data.result === "Invalid Otp") {
alert(t("invalid-otp"));
@@ -184,7 +195,7 @@ function GuestLogin() {
};
const handleUserData = async (e) => {
e.preventDefault();
if (!emailRegex.test(contact.email)) {
if (!emailRegex.test(contact.email?.toLowerCase()?.replace(/\s/g, ""))) {
alert("Please enter a valid email address.");
} else {
const params = { ...contact, docId: documentId };
@@ -211,18 +222,30 @@ function GuestLogin() {
}
};
const handleInputChange = (e) => {
setContact((prev) => ({ ...prev, [e.target.name]: e.target.value }));
if (e.target.name === "email") {
setContact((prev) => ({
...prev,
[e.target.name]: e.target.value?.toLowerCase()?.replace(/\s/g, "")
}));
} else {
setContact((prev) => ({ ...prev, [e.target.name]: e.target.value }));
}
};
return (
<div>
<Title title="Request Sign" />
{isLoading.isLoad ? (
<LoaderWithMsg isLoading={isLoading} />
) : (
<div className="p-14 h">
<div className="m-1 md:m-2 p-[30px] text-base-content bg-base-100 op-card shadow-md">
<div className="md:w-[250px] md:h-[66px] inline-block overflow-hidden mt-2 mb-11">
<div className="pb-1 md:pb-4 pt-10 md:px-10 lg:px-16">
<div className="md:p-4 lg:p-10 p-4 text-base-content bg-base-100 op-card shadow-md">
<div className="w-[250px] h-[66px] inline-block overflow-hidden mb-6">
{appLogo && (
<img src={appLogo} className="object-contain" alt="logo" />
<img
src={appLogo}
className="object-contain h-full"
alt="logo"
/>
)}
</div>
{contactId ? (
+61 -148
View File
@@ -7,9 +7,7 @@ import { NavLink, useNavigate, useLocation } from "react-router";
import login_img from "../assets/images/login_img.svg";
import { useWindowSize } from "../hook/useWindowSize";
import ModalUi from "../primitives/ModalUi";
import {
emailRegex,
} from "../constant/const";
import { emailRegex } from "../constant/const";
import Alert from "../primitives/Alert";
import { appInfo } from "../constant/appinfo";
import { fetchAppInfo } from "../redux/reducers/infoReducer";
@@ -24,6 +22,7 @@ import { useTranslation } from "react-i18next";
import SelectLanguage from "../components/pdf/SelectLanguage";
function Login() {
const appName = "OpenSign™";
const { t, i18n } = useTranslation();
const navigate = useNavigate();
const location = useLocation();
@@ -41,7 +40,7 @@ function Login() {
baseUrl: localStorage.getItem("baseUrl"),
parseAppId: localStorage.getItem("parseAppId"),
loading: false,
thirdpartyLoader: false,
thirdpartyLoader: false
});
const [userDetails, setUserDetails] = useState({
Company: "",
@@ -55,13 +54,15 @@ function Login() {
// eslint-disable-next-line
}, []);
const showToast = (type, msg) => {
setState({ ...state, loading: false, alertType: type, alertMsg: msg });
setTimeout(() => setState({ ...state, alertMsg: "" }), 2000);
};
const checkUserExt = async () => {
const app = await getAppLogo();
if (app?.error === "invalid_json") {
setErrMsg(t("server-down"));
} else if (
app?.user === "not_exist"
) {
setErrMsg(t("server-down", { appName: appName }));
} else if (app?.user === "not_exist") {
navigate("/addadmin");
}
if (app?.logo) {
@@ -151,68 +152,33 @@ function Login() {
localStorage.setItem("PageLanding", menu.pageId);
localStorage.setItem("defaultmenuid", menu.menuId);
localStorage.setItem("pageType", menu.pageType);
setState({ ...state, loading: false });
// Redirect to the appropriate URL after successful login
navigate(redirectUrl);
setState({ ...state, loading: false });
// Redirect to the appropriate URL after successful login
navigate(redirectUrl);
} else {
setState({ ...state, loading: false });
setIsModal(true);
}
} else {
setState({
...state,
loading: false,
alertType: "danger",
alertMsg:
"You don't have access, please contact the admin."
});
showToast("danger", t("do-not-access-contact-admin"));
logOutUser();
}
} else {
setState({ ...state, loading: false });
setState({
...state,
loading: false,
alertType: "danger",
alertMsg: "User not found."
});
logOutUser();
showToast("danger", t("user-not-found"));
logOutUser();
}
})
.catch((error) => {
setState({
...state,
loading: false,
alertType: "danger",
alertMsg: `Something went wrong.`
});
setTimeout(() => setState({ ...state, alertMsg: "" }), 2000);
showToast("danger", t("something-went-wrong-mssg"));
console.error("Error while fetching Follow", error);
});
} catch (error) {
setState({
...state,
loading: false,
alertType: "danger",
alertMsg: `${error.message}`
});
console.log(error);
setTimeout(() => setState({ ...state, alertMsg: "" }), 2000);
showToast("danger", `${error.message}`);
}
}
} catch (error) {
setState({
...state,
loading: false,
alertType: "danger",
alertMsg: "Invalid username/password or region"
});
console.error("Error while logging in user", error);
} finally {
setTimeout(
() => setState((prev) => ({ ...prev, alertMsg: "" })),
2000
);
showToast("danger", "Invalid username/password or region");
}
}
}
@@ -280,55 +246,31 @@ function Login() {
localStorage.setItem("PageLanding", menu.pageId);
localStorage.setItem("defaultmenuid", menu.menuId);
localStorage.setItem("pageType", menu.pageType);
navigate(redirectUrl);
navigate(redirectUrl);
} else {
setState({
...state,
loading: false,
alertType: "danger",
alertMsg: "Role not found."
});
showToast("danger", t("role-not-found"));
logOutUser();
}
} else {
setState({
...state,
loading: false,
alertType: "danger",
alertMsg: "You don't have access, please contact the admin."
});
showToast("danger", t("do-not-access-contact-admin"));
logOutUser();
}
} else {
setState({
...state,
alertType: "danger",
alertMsg: "User not found."
});
showToast("danger", t("user-not-found"));
logOutUser();
}
})
.catch((err) => {
console.error("err in fetching extUser", err);
setState({
...state,
alertType: "danger",
alertMsg: `${err.message}`
});
showToast("danger", `${err.message}`);
const payload = { sessionToken: sessionToken };
handleSubmitbtn(payload);
});
} catch (error) {
setState({
...state,
alertType: "danger",
alertMsg: `${error.message}`
});
showToast("danger", `${error.message}`);
console.log(error);
} finally {
setThirdpartyLoader(false);
setState({ ...state, loading: false });
setTimeout(() => setState({ ...state, alertMsg: "" }), 2000);
}
}
};
@@ -378,40 +320,24 @@ function Login() {
localStorage.setItem("PageLanding", menu.pageId);
localStorage.setItem("defaultmenuid", menu.menuId);
localStorage.setItem("pageType", menu.pageType);
// Redirect to the appropriate URL after successful login
navigate(redirectUrl);
// Redirect to the appropriate URL after successful login
navigate(redirectUrl);
} else {
setState({ ...state, loading: false });
logOutUser();
}
} else {
setState({
...state,
loading: false,
alertType: "danger",
alertMsg: "You don't have access, please contact the admin."
});
showToast("danger", t("do-not-access-contact-admin"));
logOutUser();
}
} else {
setState({
...state,
alertType: "danger",
alertMsg: "User not found."
});
showToast("danger", t("user-not-found"));
logOutUser();
}
});
} catch (error) {
setState({
...state,
alertType: "danger",
alertMsg: "Something went wrong, please try again later."
});
showToast("danger", t("something-went-wrong-mssg"));
console.log("err", error);
} finally {
setState({ ...state, loading: false });
setTimeout(() => setState({ ...state, alertMsg: "" }), 2000);
}
};
@@ -423,8 +349,6 @@ function Login() {
e.preventDefault();
if (userDetails.Destination && userDetails.Company) {
setThirdpartyLoader(true);
// console.log("handelSubmit", userDetails);
// const payload = await Parse.User.logIn(state.email, state.password);
const payload = { sessionToken: localStorage.getItem("accesstoken") };
const userInformation = JSON.parse(
localStorage.getItem("UserInformation")
@@ -464,13 +388,7 @@ function Login() {
alert(t("server-error"));
}
} else {
setState({
...state,
loading: false,
alertType: "warning",
alertMsg: "Please fill required details."
});
setTimeout(() => setState((prev) => ({ ...prev, alertMsg: "" })), 2000);
showToast("warning", t("fill-required-details!"));
}
};
@@ -499,14 +417,13 @@ function Login() {
localStorage.setItem("parseAppId", appid);
};
return errMsg ? (
<div className="h-screen flex justify-center text-center items-center p-4 text-gray-500 text-base">
{errMsg}
</div>
) : (
<div>
<Title title={"Login Page"} />
<Title title="Login" />
{state.loading && (
<div
aria-live="assertive"
@@ -520,7 +437,7 @@ function Login() {
<div
aria-labelledby="loginHeading"
role="region"
className="pb-1 md:pb-4 pt-10 md:px-10 lg:px-16"
className="pb-1 md:pb-4 pt-10 md:px-10 lg:px-16 h-screen"
>
<div className="md:p-4 lg:p-10 p-4 bg-base-100 text-base-content op-card">
<div className="w-[250px] h-[66px] inline-block overflow-hidden">
@@ -559,39 +476,35 @@ function Login() {
onInput={(e) => e.target.setCustomValidity("")}
/>
<hr className="my-1 border-none" />
<label className="block text-xs" htmlFor="password">
{t("password")}
</label>
<div className="relative">
<input
id="password"
type={
state.passwordVisible ? "text" : "password"
}
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
name="password"
value={state.password}
autoComplete="current-password"
onChange={handleChange}
onInvalid={(e) =>
e.target.setCustomValidity(
t("input-required")
)
}
onInput={(e) => e.target.setCustomValidity("")}
required
/>
<span
className="absolute cursor-pointer top-[50%] right-[10px] -translate-y-[50%] text-base-content"
onClick={togglePasswordVisibility}
>
{state.passwordVisible ? (
<i className="fa-light fa-eye-slash text-xs pb-1" /> // Close eye icon
) : (
<i className="fa-light fa-eye text-xs pb-1 " /> // Open eye icon
)}
</span>
</div>
<label className="block text-xs" htmlFor="password">
{t("password")}
</label>
<div className="relative">
<input
id="password"
type={state.passwordVisible ? "text" : "password"}
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
name="password"
value={state.password}
autoComplete="current-password"
onChange={handleChange}
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
required
/>
<span
className="absolute cursor-pointer top-[50%] right-[10px] -translate-y-[50%] text-base-content"
onClick={togglePasswordVisibility}
>
{state.passwordVisible ? (
<i className="fa-light fa-eye-slash text-xs pb-1" /> // Close eye icon
) : (
<i className="fa-light fa-eye text-xs pb-1 " /> // Open eye icon
)}
</span>
</div>
<div className="relative mt-1">
<NavLink
+2
View File
@@ -13,6 +13,7 @@ import Alert from "../primitives/Alert";
import Loader from "../primitives/Loader";
import { useTranslation } from "react-i18next";
import sanitizeFileName from "../primitives/sanitizeFileName";
import Title from "../components/Title";
const ManageSign = () => {
const { t } = useTranslation();
const [penColor, setPenColor] = useState("blue");
@@ -297,6 +298,7 @@ const ManageSign = () => {
};
return (
<div className="relative h-full bg-base-100 text-base-content flex shadow-md rounded-box overflow-auto">
<Title title="My signature" />
{isLoader && (
<div className="absolute bg-black bg-opacity-30 z-50 w-full h-full flex justify-center items-center">
<Loader />
+19 -10
View File
@@ -1,8 +1,6 @@
import React, { useEffect, useState, useRef } from "react";
import "../styles/opensigndrive.css";
import {
iconColor,
} from "../constant/const";
import { iconColor } from "../constant/const";
import { getDrive } from "../constant/Utils";
import { useNavigate } from "react-router";
import Title from "../components/Title";
@@ -13,6 +11,7 @@ import Tour from "reactour";
import axios from "axios";
import Loader from "../primitives/Loader";
import { useTranslation } from "react-i18next";
const DriveBody = React.lazy(
() => import("../components/opensigndrive/DriveBody")
);
@@ -26,6 +25,8 @@ const AppLoader = () => {
);
};
function Opensigndrive() {
const appName = "OpenSign™";
const drivename = appName === "OpenSign™" ? "OpenSign™" : "";
const { t } = useTranslation();
const navigate = useNavigate();
const scrollRef = useRef(null);
@@ -73,8 +74,7 @@ function Opensigndrive() {
const jsonCurrentUser = JSON.parse(currentUser);
useEffect(() => {
getPdfDocumentList();
getDetails();
// eslint-disable-next-line
}, [docId]);
@@ -124,6 +124,9 @@ function Opensigndrive() {
style: { fontSize: "13px" }
}
];
const getDetails = async () => {
getPdfDocumentList();
};
//function for get all pdf document list
const getPdfDocumentList = async (disbaleLoading) => {
setLoading(true);
@@ -136,7 +139,7 @@ function Opensigndrive() {
try {
const driveDetails = await getDrive(docId, skip, limit);
if (driveDetails && driveDetails === "Error: Something went wrong!") {
setHandleError("Error: Something went wrong!");
setHandleError(t("something-went-wrong-mssg"));
} else if (driveDetails && driveDetails.length > 0) {
const addMoreTour = [
{
@@ -195,7 +198,9 @@ function Opensigndrive() {
setTourData(tourConfigs);
}
if (!docId) {
setFolderName([{ name: t("OpenSign-drive"), objectId: "" }]);
setFolderName([
{ name: t("OpenSign-drive", { appName: drivename }), objectId: "" }
]);
}
} catch (e) {
setIsAlert({
@@ -514,7 +519,7 @@ function Opensigndrive() {
}
return (
<div className="bg-base-100 text-base-content rounded-box w-full shadow-md">
<Title title={"OpenSign™ Drive"} drive={true} />
<Title title={`${drivename} Drive`} drive={true} />
<ModalUi
isOpen={isAlert.isShow}
title={t("alert")}
@@ -625,7 +630,9 @@ function Opensigndrive() {
<div className="flex flex-row items-center">
<div
id="folder-menu"
className={`${isOptions ? "dropdown show dropDownStyle" : "dropdown"} hidden md:block`}
className={`${
isOptions ? "dropdown show dropDownStyle" : "dropdown"
} hidden md:block`}
onClick={() => setIsOptions(!isOptions)}
>
<div className="sort" data-tut="reactourSecond">
@@ -763,7 +770,9 @@ function Opensigndrive() {
</div>
<div
id="folder-menu"
className={`${isOptions ? "dropdown show dropDownStyle" : "dropdown"} md:hidden`}
className={`${
isOptions ? "dropdown show dropDownStyle" : "dropdown"
} md:hidden`}
onClick={() => setIsOptions(!isOptions)}
>
<div
File diff suppressed because it is too large Load Diff
+164 -146
View File
@@ -3,9 +3,7 @@ import axios from "axios";
import Parse from "parse";
import "../styles/signature.css";
import { PDFDocument } from "pdf-lib";
import {
themeColor
} from "../constant/const";
import { maxTitleLength } from "../constant/const";
import { DndProvider } from "react-dnd";
import { HTML5Backend } from "react-dnd-html5-backend";
import { useDrop } from "react-dnd";
@@ -43,7 +41,8 @@ import {
signatureTypes,
handleSignatureType,
getBase64FromUrl,
generatePdfName
generatePdfName,
mailTemplate
} from "../constant/Utils";
import RenderPdf from "../components/pdf/RenderPdf";
import { useNavigate } from "react-router";
@@ -70,6 +69,8 @@ import AddContact from "../primitives/AddContact";
function PlaceHolderSign() {
const { t } = useTranslation();
const copyUrlRef = useRef(null);
const appName = "OpenSign™";
const editorRef = useRef();
const { state } = useLocation();
const navigate = useNavigate();
@@ -150,7 +151,6 @@ function PlaceHolderSign() {
status: false,
message: ""
});
const [extUserId, setExtUserId] = useState("");
const [isCustomize, setIsCustomize] = useState(false);
const [zoomPercent, setZoomPercent] = useState(0);
const [scale, setScale] = useState(1);
@@ -164,13 +164,14 @@ function PlaceHolderSign() {
const [userList, setUserList] = useState([]);
const [isAttchSignerModal, setIsAttchSignerModal] = useState(false);
const [isNewContact, setIsNewContact] = useState({ status: false, id: "" });
const [owner, setOwner] = useState({});
const [docTitle, setDocTitle] = useState("");
const isMobile = window.innerWidth < 767;
const [, drop] = useDrop({
accept: "BOX",
drop: (item, monitor) => addPositionOfSignature(item, monitor),
collect: (monitor) => ({ isOver: !!monitor.isOver() })
});
const documentId = docId;
useEffect(() => {
if (documentId) {
@@ -188,7 +189,7 @@ function PlaceHolderSign() {
);
if (user) {
try {
const defaultRequestBody = `<p>Hi {{receiver_name}},</p><br><p>We hope this email finds you well. {{sender_name}}&nbsp;has requested you to review and sign&nbsp;{{document_title}}.</p><p>Your signature is crucial to proceed with the next steps as it signifies your agreement and authorization.</p><br><p>{{signing_url}}</p><br><p>If you have any questions or need further clarification regarding the document or the signing process, please contact the sender.</p><br><p>Thanks</p><p> Team OpenSign™</p><br>`;
const defaultRequestBody = `<p>Hi {{receiver_name}},</p><br><p>We hope this email finds you well. {{sender_name}}&nbsp;has requested you to review and sign&nbsp;{{document_title}}.</p><p>Your signature is crucial to proceed with the next steps as it signifies your agreement and authorization.</p><br><p>{{signing_url}}</p><br><p>If you have any questions or need further clarification regarding the document or the signing process, please contact the sender.</p><br><p>Thanks</p><p> Team ${appName}</p><br>`;
const defaultSubject = `{{sender_name}} has requested you to sign {{document_title}}`;
setDefaultBody(defaultRequestBody);
setDefaultSubject(defaultSubject);
@@ -244,14 +245,18 @@ function PlaceHolderSign() {
//getting document details
const documentData = await contractDocument(documentId);
if (documentData && documentData.length > 0) {
setDocTitle(documentData?.[0]?.Name);
if (documentData[0]?.Placeholders?.length > 0) {
const signerNotExist = documentData[0]?.Placeholders.some(
(data) => !data.signerObjId
(data) => !data.signerObjId && data.Role !== "prefill"
);
//condition to check any role does not attach signer
if (signerNotExist) {
const filterPrefill = documentData[0]?.Placeholders?.filter(
(x) => x.Role !== "prefill"
);
let users = [];
documentData[0]?.Placeholders?.forEach((element) => {
filterPrefill?.forEach((element) => {
let label = "";
const signerData = documentData[0]?.Signers.find(
(x) => element.signerObjId && element.signerObjId === x.objectId
@@ -272,6 +277,7 @@ function PlaceHolderSign() {
setForms(users);
}
}
const url = documentData[0] && documentData[0]?.URL;
//convert document url in array buffer format to use embed widgets in pdf using pdf-lib
const arrayBuffer = await convertPdfArrayBuffer(url);
@@ -282,7 +288,7 @@ function PlaceHolderSign() {
setPdfArrayBuffer(arrayBuffer);
setPdfBase64Url(base64Pdf);
}
setExtUserId(documentData[0]?.ExtUserPtr?.objectId);
setOwner(documentData?.[0]?.ExtUserPtr);
const alreadyPlaceholder = documentData[0]?.SignedUrl;
// Check if document is sent for signing
if (alreadyPlaceholder) {
@@ -453,7 +459,11 @@ function PlaceHolderSign() {
documentData === "Error: Something went wrong!" ||
(documentData.result && documentData.result.error)
) {
setHandleError(t("something-went-wrong-mssg"));
if (documentData?.result?.error?.includes("deleted")) {
setHandleError(t("document-deleted"));
} else {
setHandleError(t("something-went-wrong-mssg"));
}
setIsLoading({ isLoad: false });
} else {
setHandleError(t("no-data-avaliable"));
@@ -521,7 +531,7 @@ function PlaceHolderSign() {
scale: containerScale,
zIndex: posZIndex,
type: dragTypeValue,
options: addWidgetOptions(dragTypeValue),
options: addWidgetOptions(dragTypeValue, owner),
Width: widgetWidth / (containerScale * scale),
Height: widgetHeight / (containerScale * scale)
};
@@ -552,7 +562,7 @@ function PlaceHolderSign() {
scale: containerScale,
zIndex: posZIndex,
type: dragTypeValue,
options: addWidgetOptions(dragTypeValue),
options: addWidgetOptions(dragTypeValue, owner),
Width: widgetWidth / (containerScale * scale),
Height: widgetHeight / (containerScale * scale)
};
@@ -859,11 +869,7 @@ function PlaceHolderSign() {
scale
);
const pdfName = generatePdfName(16);
const pdfUrl = await convertBase64ToFile(
pdfName,
pdfBase64,
"",
);
const pdfUrl = await convertBase64ToFile(pdfName, pdfBase64, "");
const tenantId = localStorage.getItem("TenantId");
const buffer = atob(pdfBase64);
SaveFileSize(buffer.length, pdfUrl, tenantId);
@@ -875,11 +881,7 @@ function PlaceHolderSign() {
} else if (pdfBase64Url) {
try {
const pdfName = generatePdfName(16);
const pdfUrl = await convertBase64ToFile(
pdfName,
pdfBase64Url,
"",
);
const pdfUrl = await convertBase64ToFile(pdfName, pdfBase64Url, "");
return pdfUrl;
} catch (err) {
console.log("error to convertBase64ToFile in placeholder flow", err);
@@ -985,11 +987,7 @@ function PlaceHolderSign() {
let pdfUrl;
if (isUploadPdf) {
const pdfName = generatePdfName(16);
pdfUrl = await convertBase64ToFile(
pdfName,
pdfBase64Url,
"",
);
pdfUrl = await convertBase64ToFile(pdfName, pdfBase64Url, "");
}
try {
const docCls = new Parse.Object("contracts_Document");
@@ -1015,6 +1013,7 @@ function PlaceHolderSign() {
const saveDocumentDetails = async () => {
setIsUiLoading(true);
let signerMail = signersdata.slice();
// For "Send in order", only consider the first signer
if (pdfDetails?.[0]?.SendinOrder && pdfDetails?.[0]?.SendinOrder === true) {
signerMail.splice(1);
}
@@ -1027,7 +1026,7 @@ function PlaceHolderSign() {
objectId: x.objectId
};
});
const addExtraDays = pdfDetails[0]?.TimeToCompleteDays
const addExtraDays = pdfDetails?.[0]?.TimeToCompleteDays
? pdfDetails[0].TimeToCompleteDays
: 15;
const currentUser = signersdata.find((x) => x.Email === currentId);
@@ -1042,22 +1041,22 @@ function PlaceHolderSign() {
} else {
setIsCurrUser(currentUser?.objectId ? true : false);
}
let updateExpiryDate, data;
updateExpiryDate = new Date();
// Compute expiry date with extra days
let updateExpiryDate = new Date();
updateExpiryDate.setDate(updateExpiryDate.getDate() + addExtraDays);
//filter label widgets after add label widgets data on pdf
// Filter out prefill roles
const filterPrefill = signerPos.filter((data) => data.Role !== "prefill");
try {
data = {
const data = {
Name: docTitle || pdfDetails?.[0]?.Name,
Placeholders: filterPrefill,
SignedUrl: pdfUrl,
Signers: signers,
SentToOthers: true,
SignatureType: pdfDetails?.[0]?.SignatureType
SignatureType: pdfDetails?.[0]?.SignatureType,
ExpiryDate: { iso: updateExpiryDate, __type: "Date" }
};
if (updateExpiryDate) {
data["ExpiryDate"] = { iso: updateExpiryDate, __type: "Date" };
}
await axios.put(
`${localStorage.getItem(
"baseUrl"
@@ -1076,6 +1075,11 @@ function PlaceHolderSign() {
setIsUiLoading(false);
setSignerPos([]);
setIsSendAlert({ mssg: "confirm", alert: true });
if (docTitle) {
const updatedPdfDetails = [...pdfDetails];
updatedPdfDetails[0].Name = docTitle;
setPdfDetails(updatedPdfDetails);
}
} catch (e) {
console.log("error", e);
alert(t("something-went-wrong-mssg"));
@@ -1087,6 +1091,9 @@ function PlaceHolderSign() {
const copytoclipboard = (text) => {
copytoData(text);
if (copyUrlRef.current) {
copyUrlRef.current.textContent = text; // Update text safely
}
setCopied(true);
setTimeout(() => setCopied(false), 1500); // Reset copied state after 1.5 seconds
};
@@ -1155,8 +1162,6 @@ function PlaceHolderSign() {
for (let i = 0; i < signerMail.length; i++) {
try {
const imgPng =
"https://qikinnovation.ams3.digitaloceanspaces.com/logo.png";
let url = `${localStorage.getItem("baseUrl")}functions/sendmailv3`;
const headers = {
"Content-Type": "application/json",
@@ -1170,20 +1175,14 @@ function PlaceHolderSign() {
`${pdfDetails?.[0].objectId}/${signerMail[i].Email}/${objectId}`
);
let signPdf = `${hostUrl}/login/${encodeBase64}`;
const openSignUrl = "https://www.opensignlabs.com/";
const orgName = pdfDetails[0]?.ExtUserPtr.Company
? pdfDetails[0].ExtUserPtr.Company
: "";
const themeBGcolor = themeColor;
const senderName = `${pdfDetails?.[0].ExtUserPtr.Name}`;
const senderName = pdfDetails?.[0].ExtUserPtr.Name;
const documentName = `${pdfDetails?.[0].Name}`;
let replaceVar;
if (
requestBody &&
requestSubject &&
isCustomize
) {
if (requestBody && requestSubject && isCustomize) {
const replacedRequestBody = requestBody.replace(/"/g, "'");
htmlReqBody =
"<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body>" +
@@ -1192,10 +1191,9 @@ function PlaceHolderSign() {
const variables = {
document_title: documentName,
sender_name:
senderName,
sender_mail:
senderEmail,
note: pdfDetails?.[0]?.Note,
sender_name: senderName,
sender_mail: senderEmail,
sender_phone: senderPhone || "",
receiver_name: signerMail[i]?.Name || "",
receiver_email: signerMail[i].Email,
@@ -1209,10 +1207,7 @@ function PlaceHolderSign() {
htmlReqBody,
variables
);
} else if (
tenantMailTemplate?.body &&
tenantMailTemplate?.subject
) {
} else if (tenantMailTemplate?.body && tenantMailTemplate?.subject) {
const mailBody = tenantMailTemplate?.body;
const mailSubject = tenantMailTemplate?.subject;
const replacedRequestBody = mailBody.replace(/"/g, "'");
@@ -1222,10 +1217,9 @@ function PlaceHolderSign() {
"</body> </html>";
const variables = {
document_title: documentName,
sender_name:
senderName,
sender_mail:
senderEmail,
note: pdfDetails?.[0]?.Note,
sender_name: senderName,
sender_mail: senderEmail,
sender_phone: senderPhone || "",
receiver_name: signerMail[i]?.Name || "",
receiver_email: signerMail[i].Email,
@@ -1236,40 +1230,26 @@ function PlaceHolderSign() {
};
replaceVar = replaceMailVaribles(mailSubject, htmlReqBody, variables);
}
const mailparam = {
senderName: senderName,
note: pdfDetails?.[0]?.Note || "",
senderMail: senderEmail,
title: documentName,
organization: orgName,
localExpireDate: localExpireDate,
sigingUrl: signPdf
};
let params = {
extUserId: extUserId,
extUserId: owner?.objectId,
recipient: signerMail[i].Email,
subject: replaceVar?.subject
? replaceVar?.subject
: `${senderName} has requested you to sign "${documentName}"`,
replyto:
senderEmail ||
"",
from:
senderEmail,
: mailTemplate(mailparam).subject,
replyto: senderEmail,
from: senderEmail,
html: replaceVar?.body
? replaceVar?.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='box-shadow:rgba(0, 0, 0, 0.1) 0px 4px 12px;background:white;padding-bottom:20px;'><div style='padding:10px 10px 0 10px'><img src=" +
imgPng +
" height='50' style='padding:20px,width:170px,height:40px' /></div><div style='padding:2px;font-family:system-ui;background-color:" +
themeBGcolor +
";'><p style='font-size:20px;font-weight:400;color:white;padding-left:20px;' > Digital Signature Request</p></div><div><p style='padding:20px;font-family:system-ui;font-size:14px;margin-bottom:10px;'> " +
pdfDetails?.[0].ExtUserPtr.Name +
" has requested you to review and sign <strong> " +
pdfDetails?.[0].Name +
"</strong>.</p><div style='padding: 5px 0px 5px 25px;display:flex;flex-direction:row;justify-content:space-around;'><table><tr><td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Sender</td><td></td><td style='color:#626363;font-weight:bold;'>" +
senderEmail +
"</td></tr><tr><td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Organization</td><td></td><td style='color:#626363;font-weight:bold'> " +
orgName +
"</td></tr><tr><td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Expire on</td><td></td><td style='color:#626363;font-weight:bold'>" +
localExpireDate +
"</td></tr><tr><td></td><td></td></tr></table></div> <div style='margin-left:70px'><a target=_blank href=" +
signPdf +
"><button style='padding: 12px 12px 12px 12px;background-color:#d46b0f;color:white;border:0px;box-shadow: rgba(0, 0, 0, 0.05) 0px 6px 24px 0px,rgba(0, 0, 0, 0.08) 0px 0px 0px 1px;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 OpenSign™. For any queries regarding this email, please contact the sender " +
senderEmail +
" directly.If you think this email is inappropriate or spam, you may file a complaint with OpenSign™ <a href= " +
openSignUrl +
" target=_blank>here</a>.</p></div></div></body></html>"
: mailTemplate(mailparam).body
};
sendMail = await axios.post(url, params, { headers: headers });
@@ -1281,20 +1261,13 @@ function PlaceHolderSign() {
setMailStatus("success");
try {
let data;
if (
requestBody &&
requestSubject &&
isCustomize
) {
if (requestBody && requestSubject && isCustomize) {
data = {
RequestBody: htmlReqBody,
RequestSubject: requestSubject,
SendMail: true
};
} else if (
tenantMailTemplate?.body &&
tenantMailTemplate?.subject
) {
} else if (tenantMailTemplate?.body && tenantMailTemplate?.subject) {
data = {
RequestBody: tenantMailTemplate?.body,
RequestSubject: tenantMailTemplate?.subject,
@@ -1573,8 +1546,7 @@ function PlaceHolderSign() {
status: defaultdata?.status || "required",
hint: defaultdata?.hint || "",
defaultValue: defaultdata?.defaultValue || "",
validation:
{},
validation: {},
fontSize:
fontSize || currWidgetsDetails?.options?.fontSize || 12,
fontColor:
@@ -1584,6 +1556,15 @@ function PlaceHolderSign() {
isReadOnly: defaultdata?.isReadOnly || false
}
};
} else if (["signature"].includes(position.type)) {
return {
...position,
options: {
...position.options,
name: defaultdata.name,
hint: defaultdata?.hint || ""
}
};
} else {
return {
...position,
@@ -1592,6 +1573,7 @@ function PlaceHolderSign() {
name: defaultdata.name,
status: defaultdata.status,
defaultValue: defaultdata.defaultValue,
hint: defaultdata?.hint || "",
fontSize:
fontSize || currWidgetsDetails?.options?.fontSize || 12,
fontColor:
@@ -1871,6 +1853,10 @@ function PlaceHolderSign() {
setPdfBase64Url(urlDetails.base64);
};
const handleSendDoc = () => {
if (docTitle?.length > maxTitleLength) {
alert(t("title-length-alert"));
return;
}
setIsAttchSignerModal(false);
setCheckTourStatus(true);
alertSendEmail();
@@ -1927,10 +1913,16 @@ function PlaceHolderSign() {
}
};
const handleDisable = () => {
const isAllSigner = signerPos.some((x) => !x.signerObjId);
const isAllSigner = signerPos.some(
(x) => !x.signerObjId && x.Role !== "prefill"
);
return isAllSigner;
};
const handleCloseAttachSigner = () => {
if (docTitle?.length > maxTitleLength) {
alert(t("title-length-alert"));
return;
}
setIsAttchSignerModal(false);
};
return (
@@ -2037,30 +2029,26 @@ function PlaceHolderSign() {
{!isCustomize && (
<span>{t("placeholder-alert-3")}</span>
)}
{
isCustomize && (
<>
<EmailBody
editorRef={editorRef}
requestBody={requestBody}
requestSubject={requestSubject}
handleOnchangeRequest={
handleOnchangeRequest
}
setRequestSubject={setRequestSubject}
/>
<div
className="flex justify-end items-center gap-1 mt-2 op-link op-link-primary"
onClick={() => {
setRequestBody(defaultBody);
setRequestSubject(defaultSubject);
}}
>
<span>{t("reset-to-default")}</span>
</div>
</>
)
}
{isCustomize && (
<>
<EmailBody
editorRef={editorRef}
requestBody={requestBody}
requestSubject={requestSubject}
handleOnchangeRequest={handleOnchangeRequest}
setRequestSubject={setRequestSubject}
/>
<div
className="flex justify-end items-center gap-1 mt-2 op-link op-link-primary"
onClick={() => {
setRequestBody(defaultBody);
setRequestSubject(defaultSubject);
}}
>
<span>{t("reset-to-default")}</span>
</div>
</>
)}
<div className="flex flex-row items-center gap-2 md:gap-6 mt-2">
<div className="flex flex-row gap-2">
<button
@@ -2078,16 +2066,14 @@ function PlaceHolderSign() {
</button>
)}
</div>
{
!isCustomize && (
<span
className="op-link op-link-accent text-sm"
onClick={() => setIsCustomize(!isCustomize)}
>
{t("cutomize-email")}
</span>
)
}
{!isCustomize && (
<span
className="op-link op-link-accent text-sm"
onClick={() => setIsCustomize(!isCustomize)}
>
{t("cutomize-email")}
</span>
)}
</div>
</>
)}
@@ -2099,6 +2085,11 @@ function PlaceHolderSign() {
<span className="h-[1px] w-[20%] bg-[#ccc]"></span>
</div>
<div className="my-3">{handleShareList()}</div>
<p
id="copyUrl"
ref={copyUrlRef}
className="hidden"
></p>
</>
)}
</div>
@@ -2125,9 +2116,11 @@ function PlaceHolderSign() {
<LottieWithLoader />
{pdfDetails[0].SendinOrder ? (
<p>
{t("placeholder-mail-alert", {
name: pdfDetails[0]?.Signers[0]?.Name
})}
{isCurrUser
? t("placeholder-mail-alert-you")
: t("placeholder-mail-alert", {
name: signersdata[0]?.Name
})}
</p>
) : (
<p>{t("placeholder-alert-4")}</p>
@@ -2140,7 +2133,11 @@ function PlaceHolderSign() {
</div>
) : (
<div className="mb-[10px]">
<p>{t("placeholder-alert-6")}</p>
{mailStatus === "dailyquotareached" ? (
<p>{t("daily-quota-reached")}</p>
) : (
<p>{t("placeholder-alert-6")}</p>
)}
{isCurrUser && (
<p className="mt-1">{t("placeholder-alert-5")}</p>
)}
@@ -2200,25 +2197,46 @@ function PlaceHolderSign() {
</ModalUi>
<ModalUi
isOpen={isAttchSignerModal}
title={t("add-signer")}
title={t("create-document")}
handleClose={() => handleCloseAttachSigner()}
>
<div className="h-[100%] p-[20px]">
<div className="h-[100%] px-[20px] py-[10px]">
<div>
<label
htmlFor="doctitle"
className="block text-xs font-semibold"
>
Title
</label>
<input
type="text"
name="doctitle"
value={docTitle}
onChange={(e) => setDocTitle(e.target.value)}
required
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-[11px] py-[18px]"
/>
</div>
{pdfDetails[0].Placeholders?.some(
(x) => !x.signerObjId
) && (
<>
{/* grid grid-cols-1 md:grid-cols-2 */}
<div className="min-h-max max-h-[250px] overflow-y-auto">
<div className="p-3 op-card border-[1px] border-gray-400 mt-3 mx-4 mb-4 bg-base-200 text-base-content flex flex-col gap-2 relative">
<div className="py-2 text-base-content flex flex-col gap-2 relative">
{forms?.map((field, id) => {
return (
<div
className="flex flex-col"
key={field?.value}
>
<label>{field?.role}</label>
<div className="flex justify-between items-center gap-2">
<label className="block text-xs font-semibold">
{field?.role}
</label>
<div className="flex justify-between items-center gap-1">
<div className="flex-1">
<AsyncSelect
cacheOptions
@@ -2260,7 +2278,7 @@ function PlaceHolderSign() {
onClick={(e) =>
handleCreateNew(e, field.value)
}
className="op-btn op-btn-accent op-btn-outline op-btn-sm "
className="op-btn op-btn-accent op-btn-outline op-btn-sm"
>
<i className="fa-light fa-plus"></i>
</button>
@@ -2270,8 +2288,8 @@ function PlaceHolderSign() {
})}
</div>
</div>
<div className="w-full h-[1px] bg-[#9f9f9f] my-[15px]"></div>
<div className="flex mx-4 mb-4 gap-3">
<div className="w-full h-[0.5px] bg-[#9f9f9f] mt-[8px] mb-[15px]"></div>
<div className="flex mx-2 mb-2 gap-3">
<button
disabled={handleDisable()}
onClick={() => handleSendDoc()}
+328 -90
View File
@@ -20,8 +20,10 @@ import EditorToolbar, {
module2,
formats
} from "../components/pdf/EditorToolbar";
import DateFormatSelector from "../components/shared/fields/DateFormatSelector";
const Preferences = () => {
const appName = "OpenSign™";
const { t } = useTranslation();
const editorRef = useRef();
const editorRefCom = useRef();
@@ -44,6 +46,10 @@ const Preferences = () => {
const [activeTab, setactiveTab] = useState(0);
const generaltab = { title: t("general"), icon: "fa-light fa-gears" };
const [tab, setTab] = useState([generaltab]);
const [sendinOrder, setSendinOrder] = useState(true);
const [isTourEnabled, setIsTourEnabled] = useState(false);
const [dateFormat, setDateFormat] = useState("MM/DD/YYYY");
const [is12HourTime, setIs12HourTime] = useState(false);
useEffect(() => {
fetchSignType();
@@ -88,10 +94,26 @@ const Preferences = () => {
const SignatureType = _getUser?.SignatureType || signatureTypes;
setSignatureType(SignatureType);
}
const sendinorder =
_getUser?.SendinOrder !== undefined ? _getUser?.SendinOrder : true;
setSendinOrder(sendinorder);
const istourenabled =
_getUser?.IsTourEnabled !== undefined
? _getUser?.IsTourEnabled
: true;
setIsTourEnabled(istourenabled);
const DateFormat =
_getUser?.DateFormat !== undefined
? _getUser?.DateFormat
: "MM/DD/YYYY";
setDateFormat(DateFormat);
const is12Hr =
_getUser?.Is12HourTime !== undefined ? _getUser?.Is12HourTime : false;
setIs12HourTime(is12Hr);
}
} catch (err) {
console.log("err while getting user details", err);
setErrMsg("Something went wrong");
setErrMsg(t("something-went-wrong-mssg"));
} finally {
setIsTopLoader(false);
}
@@ -140,14 +162,25 @@ const Preferences = () => {
params = { ...params, NotifyOnSignatures: isNotifyOnSignatures };
}
try {
params = {
...params,
SendinOrder: sendinOrder,
IsTourEnabled: isTourEnabled,
DateFormat: dateFormat,
Is12HourTime: is12HourTime
};
const updateRes = await Parse.Cloud.run("updatepreferences", params);
if (updateRes) {
setIsAlert({ type: "success", msg: "Saved successfully." });
setIsAlert({ type: "success", msg: t("saved-successfully") });
let extUser =
localStorage.getItem("Extand_Class") &&
JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
if (extUser && extUser?.objectId) {
extUser.NotifyOnSignatures = isNotifyOnSignatures;
extUser.SendinOrder = sendinOrder;
extUser.IsTourEnabled = isTourEnabled;
extUser.DateFormat = dateFormat;
extUser.Is12HourTime = is12HourTime;
const _extUser = JSON.parse(JSON.stringify(extUser));
localStorage.setItem("Extand_Class", JSON.stringify([_extUser]));
}
@@ -173,7 +206,7 @@ const Preferences = () => {
setIsLoader(true);
const updateRes = tenantRes;
setTenantId(updateRes?.objectId);
const defaultRequestBody = `<p>Hi {{receiver_name}},</p><br><p>We hope this email finds you well. {{sender_name}}&nbsp;has requested you to review and sign&nbsp;{{document_title}}.</p><p>Your signature is crucial to proceed with the next steps as it signifies your agreement and authorization.</p><br><p>{{signing_url}}</p><br><p>If you have any questions or need further clarification regarding the document or the signing process, please contact the sender.</p><br><p>Thanks</p><p> Team OpenSign™</p><br>`;
const defaultRequestBody = `<p>Hi {{receiver_name}},</p><br><p>We hope this email finds you well. {{sender_name}}&nbsp;has requested you to review and sign&nbsp;{{document_title}}.</p><p>Your signature is crucial to proceed with the next steps as it signifies your agreement and authorization.</p><br><p>{{signing_url}}</p><br><p>If you have any questions or need further clarification regarding the document or the signing process, please contact the sender.</p><br><p>Thanks</p><p> Team ${appName}</p><br>`;
if (updateRes?.RequestBody) {
setRequestBody(updateRes?.RequestBody);
setRequestSubject(updateRes?.RequestSubject);
@@ -187,7 +220,7 @@ const Preferences = () => {
body: defaultRequestBody,
subject: `{{sender_name}} has requested you to sign {{document_title}}`
});
const defaultCompletionBody = `<p>Hi {{sender_name}},</p><br><p>All parties have successfully signed the document {{document_title}}. Kindly download the document from the attachment.</p><br><p>Thanks</p><p> Team OpenSign™</p><br>`;
const defaultCompletionBody = `<p>Hi {{sender_name}},</p><br><p>All parties have successfully signed the document {{document_title}}. Kindly download the document from the attachment.</p><br><p>Thanks</p><p> Team ${appName}</p><br>`;
if (updateRes?.CompletionBody) {
SetCompletionBody(updateRes?.CompletionBody);
setCompletionSubject(updateRes?.CompletionSubject);
@@ -211,21 +244,23 @@ const Preferences = () => {
setIsLoader(true);
const replacedHtmlBody = completionBody.replace(/"/g, "'");
const htmlBody = `<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body>${replacedHtmlBody}</body></html>`;
const tenantQuery = new Parse.Query("partners_Tenant");
const updateTenantObj = await tenantQuery.get(tenantId);
updateTenantObj.set("CompletionBody", htmlBody);
updateTenantObj.set("CompletionSubject", completionsubject);
const res = await updateTenantObj.save();
if (res) {
const updateRes = JSON.parse(JSON.stringify(res));
const updateTenant = await Parse.Cloud.run("updatetenant", {
tenantId: tenantId,
details: {
CompletionBody: htmlBody,
CompletionSubject: completionsubject
}
});
if (updateTenant) {
const updateRes = JSON.parse(JSON.stringify(updateTenant));
SetCompletionBody(updateRes?.CompletionBody);
setCompletionSubject(updateRes?.CompletionSubject);
setIsAlert({ type: "success", msg: "Saved successfully." });
setIsAlert({ type: "success", msg: t("saved-successfully") });
setTimeout(() => setIsAlert({ type: "", msg: "" }), 1500);
}
} catch (err) {
console.log("Err", err);
setIsAlert({ type: "danger", msg: "Something went wrong." });
setIsAlert({ type: "danger", msg: t("something-went-wrong-mssg") });
setTimeout(() => setIsAlert({ type: "", msg: "" }), 1500);
} finally {
setIsLoader(false);
@@ -238,21 +273,23 @@ const Preferences = () => {
setIsLoader(true);
const replacedHtmlBody = requestBody.replace(/"/g, "'");
const htmlBody = `<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body>${replacedHtmlBody}</body></html>`;
const tenantQuery = new Parse.Query("partners_Tenant");
const updateTenantObj = await tenantQuery.get(tenantId);
updateTenantObj.set("RequestBody", htmlBody);
updateTenantObj.set("RequestSubject", requestSubject);
const res = await updateTenantObj.save();
if (res) {
const updateRes = JSON.parse(JSON.stringify(res));
const updateTenant = await Parse.Cloud.run("updatetenant", {
tenantId: tenantId,
details: {
RequestBody: htmlBody,
RequestSubject: requestSubject
}
});
if (updateTenant) {
const updateRes = JSON.parse(JSON.stringify(updateTenant));
setRequestBody(updateRes?.RequestBody);
setRequestSubject(updateRes?.RequestSubject);
setIsAlert({ type: "success", msg: "Saved successfully." });
setIsAlert({ type: "success", msg: t("saved-successfully") });
setTimeout(() => setIsAlert({ type: "", msg: "" }), 1500);
}
} catch (err) {
console.log("Err", err);
setIsAlert({ type: "danger", msg: "Something went wrong." });
setIsAlert({ type: "danger", msg: t("something-went-wrong-mssg") });
setTimeout(() => setIsAlert({ type: "", msg: "" }), 1500);
} finally {
setIsLoader(false);
@@ -283,6 +320,9 @@ const Preferences = () => {
}
};
const handleTourInput = () => setIsTourEnabled(!isTourEnabled);
const handleSendinOrderInput = () => setSendinOrder(!sendinOrder);
return (
<React.Fragment>
<Title title={t("Preferences")} />
@@ -300,14 +340,14 @@ const Preferences = () => {
) : (
<div className="relative bg-base-100 text-base-content flex flex-col justify-center shadow-md rounded-box mb-3">
{isLoader && (
<div className="flex justify-center items-center absolute w-full h-full rounded-box bg-black/30">
<div className="flex z-[100] justify-center items-center absolute w-full h-full rounded-box bg-black/30">
<Loader />
</div>
)}
<h1 className="ml-4 mt-3 mb-2 font-semibold">
OpenSign {t("Preferences")}{" "}
<h1 className="ml-4 mt-3 text-lg mb-2 font-semibold text-base-content">
{appName} {t("Preferences")}{" "}
<span>
<Tooltip message={`OpenSign™ ${t("Preferences")}`} />
<Tooltip message={`${appName} ${t("Preferences")}`} />
</span>
</h1>
<div className="flex justify-center items-center mt-2">
@@ -320,7 +360,11 @@ const Preferences = () => {
onClick={() => setactiveTab(ind)}
key={ind}
role="tab"
className={`${activeTab === ind ? "op-tab-active" : ""} op-tab text-xs md:text-base pb-2 md:pb-0 }`}
className={`${
activeTab === ind ? "op-tab-active" : ""
} op-tab text-xs md:text-base pb-2 md:pb-0 transition-all `}
aria-selected={activeTab === ind}
aria-controls={`panel-${tabData.title}`}
>
<i className={tabData.icon}></i>
<span className="ml-1 md:ml-2">{tabData.title}</span>
@@ -328,14 +372,21 @@ const Preferences = () => {
))}
</div>
</div>
<div className="flex justify-center md:justify-start mt-3 md:mt-4 break-all">
<div
id="panel-general"
className="px-6 pt-4 pb-6"
aria-labelledby="tab-general"
role="tabpanel"
>
{activeTab === 0 ? (
<>
<div className="ml-4 mt-1 mb-2 flex flex-col">
<div className="mb-[0.75rem]">
<div className="grid grid-cols-1 md:grid-cols-12 md:gap-x-8">
{/* Left Column - Signature Settings */}
<div className="md:col-span-5 flex flex-col">
{/* Signature Types Section */}
<div className="mb-6">
<label
htmlFor="signaturetype"
className="text-[14px] mb-[0.7rem] font-medium"
htmlFor="signaturetype"
>
{t("allowed-signature-types")}
<a
@@ -350,12 +401,12 @@ const Preferences = () => {
id="signtypes-tooltip"
className="z-[999]"
>
<div className="max-w-[200px] md:max-w-[450px] text-[11px]">
<div className="max-w-[200px] md:max-w-[450px]">
<p className="font-bold">
{t("allowed-signature-types")}
</p>
<p>{t("allowed-signature-types-help.p1")}</p>
<p className="p-[5px] ml-2">
<div className="p-[5px] ml-2">
<ol className="list-disc">
<li>
<span className="font-bold">Draw: </span>
@@ -382,34 +433,38 @@ const Preferences = () => {
</span>
</li>
</ol>
</p>
</div>
</div>
</ReactTooltip>
</label>
<div className=" ml-[7px] flex flex-col md:flex-row gap-[10px] mb-[0.7rem]">
<div className="flex flex-col md:flex-row gap-3 mb-2">
{signatureType.map((type, i) => (
<div
key={i}
className="flex flex-row gap-[5px] items-center"
className="flex flex-row gap-2 items-center"
>
<input
className="mr-[2px] op-checkbox op-checkbox-xs"
className="op-checkbox op-checkbox-xs"
type="checkbox"
id={`signature-type-${type.name}`}
name="signaturetype"
onChange={() => handleCheckboxChange(i)}
checked={type.enabled}
/>
<div
className="text-[13px] font-medium hover:underline underline-offset-2 cursor-default capitalize"
title={`Enabling this allow signers to ${type.name} signature`}
<label
htmlFor={`signature-type-${type.name}`}
className="text-sm font-medium text-base-content hover:underline underline-offset-2 cursor-pointer capitalize mb-0"
title={`Enabling this allows signers to ${type.name} signature`}
>
{type?.name === "typed" ? "type" : type?.name}
</div>
</label>
</div>
))}
</div>
</div>
<div className="mb-[0.75rem]">
{/* Notify on Signatures Section */}
<div className="mb-6">
<label className="text-[14px] mb-[0.7rem] font-medium">
{t("notify-on-signatures")}
<a data-tooltip-id="nos-tooltip" className="ml-1">
@@ -418,7 +473,7 @@ const Preferences = () => {
</sup>
</a>
<ReactTooltip id="nos-tooltip" className="z-[999]">
<div className="max-w-[200px] md:max-w-[450px] text-[11px]">
<div className="max-w-[200px] md:max-w-[450px]">
<p className="font-bold">
{t("notify-on-signatures")}
</p>
@@ -427,59 +482,230 @@ const Preferences = () => {
</div>
</ReactTooltip>
</label>
<div className="flex flex-col md:flex-row md:gap-4">
<div
className={
"flex items-center gap-2 ml-2 mb-1"
}
>
<div className="flex flex-row gap-6">
<div className={"flex items-center gap-2"}>
<input
className="mr-[2px] op-radio op-radio-xs"
id="notify-yes"
className="op-radio op-radio-xs"
type="radio"
onChange={() => handleNotifySignChange(true)}
checked={isNotifyOnSignatures === true}
/>
<div className="text-[13px] font-medium cursor-default capitalize">
{t("yes")}
</div>
</div>
<div className="flex flex-col md:flex-row md:gap-4">
<div
className={
"flex items-center gap-2 ml-2 mb-1"
}
<label
htmlFor="notify-yes"
className="text-sm text-base-content cursor-pointer mb-0"
>
<input
className="mr-[2px] op-radio op-radio-xs"
type="radio"
onChange={() => handleNotifySignChange(false)}
checked={isNotifyOnSignatures === false}
/>
<div className="text-[13px] font-medium cursor-default capitalize">
{t("no")}
</div>
</div>
{t("yes")}
</label>
</div>
<div className={"flex items-center gap-2"}>
<input
id="notify-no"
className="op-radio op-radio-xs"
type="radio"
onChange={() => handleNotifySignChange(false)}
checked={isNotifyOnSignatures === false}
/>
<label
htmlFor="notify-no"
className="text-sm text-base-content cursor-pointer mb-0"
>
{t("no")}
</label>
</div>
</div>
</div>
<div className="mb-[0.75rem]">
{/* Send in Order Section */}
<div className="mb-6">
<label className="text-[14px] mb-[0.7rem] font-medium">
{t("send-in-order")}
<a
data-tooltip-id="sendInOrder-tooltip"
className="ml-1"
>
<sup>
<i className="fa-light fa-question rounded-full border-[#33bbff] text-[#33bbff] text-[13px] border-[1px] py-[1.5px] px-[4px]"></i>
</sup>
</a>
<ReactTooltip
id="sendInOrder-tooltip"
className="z-[999]"
>
<div className="max-w-[200px] md:max-w-[450px]">
<p className="font-bold">{t("send-in-order")}</p>
<p>{t("send-in-order-help.p1")}</p>
<div className="p-[5px]">
<ol className="list-disc">
<li>
<span className="font-bold">
{t("yes")}:{" "}
</span>
<span>{t("send-in-order-help.p2")}</span>
</li>
<li>
<span className="font-bold">
{t("no")}:{" "}
</span>
<span>{t("send-in-order-help.p3")}</span>
</li>
</ol>
</div>
<p>{t("send-in-order-help.p4")}</p>
</div>
</ReactTooltip>
</label>
<div className="flex flex-row gap-6">
<div className="flex items-center gap-2">
<input
id="order-yes"
type="radio"
value={true}
className="op-radio op-radio-xs"
name="SendinOrder"
checked={sendinOrder}
onChange={handleSendinOrderInput}
/>
<label
htmlFor="order-yes"
className="text-sm text-base-content cursor-pointer mb-0"
>
{t("yes")}
</label>
</div>
<div className="flex items-center gap-2">
<input
id="order-no"
type="radio"
value={false}
name="SendinOrder"
className="op-radio op-radio-xs"
checked={!sendinOrder}
onChange={handleSendinOrderInput}
/>
<label
htmlFor="order-no"
className="text-sm text-base-content cursor-pointer mb-0"
>
{t("no")}
</label>
</div>
</div>
</div>
{/* Enable Tour Section */}
<div className="mb-6">
<label className="text-[14px] mb-[0.7rem] font-medium">
{t("enable-tour")}
<a
data-tooltip-id="istourenabled-tooltip"
className="ml-1"
>
<sup>
<i className="fa-light fa-question rounded-full border-[#33bbff] text-[#33bbff] text-[13px] border-[1px] py-[1.5px] px-[4px]"></i>
</sup>
</a>
<ReactTooltip
id="istourenabled-tooltip"
className="z-[999]"
>
<div className="max-w-[200px] md:max-w-[450px]">
<p className="font-bold">{t("enable-tour")}</p>
<div className="p-[5px]">
<ol className="list-disc">
<li>
<span className="font-bold">
{t("yes")}:{" "}
</span>
<span>{t("istourenabled-help.p1")}</span>
</li>
<li>
<span className="font-bold">
{t("no")}:{" "}
</span>
<span>{t("istourenabled-help.p2")}</span>
</li>
</ol>
</div>
<p>
{t("istourenabled-help.p3", {
appName: appName
})}
</p>
</div>
</ReactTooltip>
</label>
<div className="flex flex-row gap-6">
<div className="flex items-center gap-2">
<input
id="tour-yes"
type="radio"
value={true}
className="op-radio op-radio-xs"
name="IsTourEnabled"
checked={isTourEnabled}
onChange={handleTourInput}
/>
<label
htmlFor="tour-yes"
className="text-sm text-base-content cursor-pointer mb-0"
>
{t("yes")}
</label>
</div>
<div className="flex items-center gap-2">
<input
id="tour-no"
type="radio"
value={false}
name="IsTourEnabled"
className="op-radio op-radio-xs"
checked={!isTourEnabled}
onChange={handleTourInput}
/>
<label
htmlFor="tour-no"
className="text-sm text-base-content cursor-pointer mb-0"
>
{t("no")}
</label>
</div>
</div>
</div>
</div>
{/* Right Column - Timezone & Date Settings */}
<div className="md:col-span-7 flex flex-col">
<div className="mb-6">
<TimezoneSelector
timezone={timezone}
setTimezone={setTimezone}
/>
</div>
<div className="mb-[0.75rem]">
<button
className="op-btn op-btn-primary"
onClick={handleSave}
>
{t("save")}
</button>
<div className="mb-6">
<DateFormatSelector
timezone={timezone}
dateFormat={dateFormat}
is12HourTime={is12HourTime}
setIs12HourTime={setIs12HourTime}
setDateFormat={setDateFormat}
/>
</div>
</div>
</>
{/* Save Button - Full Width */}
<div className="md:col-span-12 flex justify-start mt-2">
<button
className="op-btn op-btn-primary w-[110px]"
onClick={handleSave}
>
{t("save")}
</button>
</div>
</div>
) : (
<div className="flex flex-col mx-4 mb-4">
<div className="flex flex-col mb-4">
<div className="flex flex-col">
<h1 className="text-[14px] mb-[0.7rem] font-medium">
{t("request-email")}
@@ -491,10 +717,12 @@ const Preferences = () => {
>
<div className="text-lg font-normal">
<label className="text-sm">
{t("subject")}
{t("subject")}{" "}
<Tooltip
id={"request-sub-tooltip"}
message={`${t("variables-use")}: {{sender_name}} {{document_title}}`}
message={`${t(
"variables-use"
)}: {{sender_name}} {{document_title}}`}
/>
</label>
<input
@@ -503,16 +731,20 @@ const Preferences = () => {
onChange={(e) =>
setRequestSubject(e.target.value)
}
placeholder={`{{sender_name}} ${t("send-to-sign")} {{document_title}}`}
placeholder={`{{sender_name}} ${t(
"send-to-sign"
)} {{document_title}}`}
className="w-full op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content text-xs"
/>
</div>
<div className="text-lg font-normal py-2">
<label className="text-sm mt-3">
{t("body")}
{t("body")}{" "}
<Tooltip
id={"request-body-tooltip"}
message={`${t("variables-use")}: {{sender_name}} {{document_title}}`}
message={`${t(
"variables-use"
)}: {{sender_name}} {{document_title}}`}
/>
</label>
<EditorToolbar containerId="toolbar1" />
@@ -555,10 +787,12 @@ const Preferences = () => {
>
<div className="text-lg font-normal">
<label className="text-sm">
{t("subject")}
{t("subject")}{" "}
<Tooltip
id={"complete-sub-tooltip"}
message={`${t("variables-use")}:{{sender_name}} {{document_title}}`}
message={`${t(
"variables-use"
)}:{{sender_name}} {{document_title}}`}
/>
</label>
<input
@@ -567,16 +801,20 @@ const Preferences = () => {
onChange={(e) =>
setCompletionSubject(e.target.value)
}
placeholder={`{{sender_name}} ${t("send-to-sign")} {{document_title}}`}
placeholder={`{{sender_name}} ${t(
"send-to-sign"
)} {{document_title}}`}
className="w-full op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content text-xs"
/>
</div>
<div className="text-lg font-normal py-2">
<label className="text-sm mt-3">
{t("body")}
{t("body")}{" "}
<Tooltip
id={"complete-body-tooltip"}
message={`${t("variables-use")}:{{sender_name}} {{document_title}} {{signing_url}}`}
message={`${t(
"variables-use"
)}:{{sender_name}} {{document_title}} {{signing_url}}`}
/>
</label>
<EditorToolbar containerId="toolbar2" />
+21 -73
View File
@@ -40,7 +40,8 @@ import {
convertBase64ToFile,
generatePdfName,
handleRemoveWidgets,
compressedFileSize
compressedFileSize,
addWidgetSelfsignOptions
} from "../constant/Utils";
import { useParams } from "react-router";
import Tour from "reactour";
@@ -66,6 +67,7 @@ import LoaderWithMsg from "../primitives/LoaderWithMsg";
function SignYourSelf() {
const { t } = useTranslation();
const { docId } = useParams();
const appName = "OpenSign™";
const divRef = useRef(null);
const nodeRef = useRef(null);
const imageRef = useRef(null);
@@ -122,7 +124,6 @@ function SignYourSelf() {
const [pdfLoad, setPdfLoad] = useState(false);
const [isAlert, setIsAlert] = useState({ isShow: false, alertMessage: "" });
const [isDontShow, setIsDontShow] = useState(false);
const [extUserId, setExtUserId] = useState("");
const [isCompleted, setIsCompleted] = useState(false);
const [isCelebration, setIsCelebration] = useState(false);
const [pdfArrayBuffer, setPdfArrayBuffer] = useState("");
@@ -144,6 +145,7 @@ function SignYourSelf() {
isVisible: false,
signId: ""
});
const [owner, setOwner] = useState({});
const [, drop] = useDrop({
accept: "BOX",
drop: (item, monitor) => addPositionOfSignature(item, monitor),
@@ -204,8 +206,8 @@ function SignYourSelf() {
const documentData = await contractDocument(documentId);
if (documentData && documentData.length > 0) {
setOwner(documentData?.[0]?.ExtUserPtr);
setPdfDetails(documentData);
setExtUserId(documentData[0]?.ExtUserPtr?.objectId);
const placeholders =
documentData[0]?.Placeholders?.length > 0
? documentData[0]?.Placeholders
@@ -250,7 +252,11 @@ function SignYourSelf() {
documentData === "Error: Something went wrong!" ||
(documentData.result && documentData.result.error)
) {
setHandleError(t("something-went-wrong-mssg"));
if (documentData?.result?.error?.includes("deleted")) {
setHandleError(t("document-deleted"));
} else {
setHandleError(t("something-went-wrong-mssg"));
}
setIsLoading({ isLoad: false });
} else {
setHandleError(t("no-data-avaliable"));
@@ -322,7 +328,7 @@ function SignYourSelf() {
}
} catch (err) {
console.log("Error: error in getDocumentDetails", err);
setHandleError("Error: Something went wrong!");
setHandleError(t("something-went-wrong-mssg"));
setIsLoading({ isLoad: false });
}
};
@@ -345,54 +351,6 @@ function SignYourSelf() {
}
};
const addWidgetOptions = (type) => {
switch (type) {
case "signature":
return { name: "signature" };
case "stamp":
return { name: "stamp" };
case "checkbox":
return { name: "checkbox" };
case textWidget:
return { name: "text" };
case "initials":
return { name: "initials" };
case "name":
return {
name: "name",
defaultValue: getWidgetValue(type),
validation: { type: "text", pattern: "" }
};
case "company":
return {
name: "company",
defaultValue: getWidgetValue(type),
validation: { type: "text", pattern: "" }
};
case "job title":
return {
name: "job title",
defaultValue: getWidgetValue(type),
validation: { type: "text", pattern: "" }
};
case "date":
return {
name: "date",
response: getDate(),
validation: { format: "MM/dd/yyyy", type: "date-format" }
};
case "image":
return { name: "image" };
case "email":
return {
name: "email",
defaultValue: getWidgetValue(type),
validation: { type: "email", pattern: "" }
};
default:
return {};
}
};
//function for setting position after drop signature button over pdf
const addPositionOfSignature = (item, monitor) => {
setCurrWidgetsDetails({});
@@ -417,7 +375,7 @@ function SignYourSelf() {
// `getBoundingClientRect()` is used to get accurate measurement height of the div
const divHeight = divRef.current.getBoundingClientRect().height;
const getWidth = widgetTypeExist
? calculateInitialWidthHeight(dragTypeValue, widgetValue).getWidth
? calculateInitialWidthHeight(widgetValue).getWidth
: defaultWidthHeight(dragTypeValue).width;
const getHeight = defaultWidthHeight(dragTypeValue).height;
@@ -431,7 +389,7 @@ function SignYourSelf() {
scale: containerScale,
Width: getWidth,
Height: getHeight,
options: addWidgetOptions(dragTypeValue)
options: addWidgetSelfsignOptions(dragTypeValue, getWidgetValue, owner)
};
dropData.push(dropObj);
} else {
@@ -460,7 +418,7 @@ function SignYourSelf() {
type: dragTypeValue,
Width: getWidth / (containerScale * scale),
Height: getHeight / (containerScale * scale),
options: addWidgetOptions(dragTypeValue),
options: addWidgetSelfsignOptions(dragTypeValue, getWidgetValue, owner),
scale: containerScale
};
dropData.push(dropObj);
@@ -562,11 +520,7 @@ function SignYourSelf() {
let pdfUrl;
if (isUploadPdf) {
const pdfName = generatePdfName(16);
pdfUrl = await convertBase64ToFile(
pdfName,
pdfBase64Url,
"",
);
pdfUrl = await convertBase64ToFile(pdfName, pdfBase64Url, "");
}
const widgetsType = ["signature", "stamp", "image", "initials"];
let updatedXYPosition;
@@ -705,7 +659,7 @@ function SignYourSelf() {
setIsAlert({
header: t("error"),
isShow: true,
alertMessage: t("pdf-uncompatible")
alertMessage: t("pdf-uncompatible", { appName: appName })
});
}
} catch (err) {
@@ -752,10 +706,7 @@ function SignYourSelf() {
if (tenantDetails && tenantDetails === "user does not exist!") {
alert(t("user-not-exist"));
} else {
if (
tenantDetails?.CompletionBody &&
tenantDetails?.CompletionSubject
) {
if (tenantDetails?.CompletionBody && tenantDetails?.CompletionSubject) {
isCustomCompletionMail = true;
}
}
@@ -791,7 +742,7 @@ function SignYourSelf() {
pdfFile: base64Url,
docId: documentId,
isCustomCompletionMail: isCustomCompletionMail,
signature: suffixbase64,
signature: suffixbase64
};
const resSignPdf = await Parse.Cloud.run("signPdf", params);
if (resSignPdf) {
@@ -1197,7 +1148,7 @@ function SignYourSelf() {
};
return (
<DndProvider backend={HTML5Backend}>
<Title title={"signyourself"} />
<Title title={"Signyourself"} />
{isLoading.isLoad ? (
<LoaderWithMsg isLoading={isLoading} />
) : handleError ? (
@@ -1261,7 +1212,7 @@ function SignYourSelf() {
setPdfBase64Url={setPdfBase64Url}
setIsUploadPdf={setIsUploadPdf}
pdfArrayBuffer={pdfArrayBuffer}
isMergePdfBtn={true}
isMergePdfBtn={!pdfDetails?.[0]?.IsCompleted}
/>
<div className=" w-full md:w-[57%] flex mr-4">
<PdfZoom
@@ -1369,13 +1320,10 @@ function SignYourSelf() {
{/*render email component to send email after finish signature on document */}
<EmailComponent
isEmail={isEmail}
pdfUrl={pdfUrl}
setIsEmail={setIsEmail}
pdfDetails={pdfDetails}
setSuccessEmail={setSuccessEmail}
sender={jsonSender}
pdfDetails={pdfDetails}
setIsAlert={setIsAlert}
extUserId={extUserId}
setIsDownloadModal={setIsDownloadModal}
/>
{/* pdf header which contain funish back button */}
+261 -89
View File
@@ -11,6 +11,8 @@ import Tour from "reactour";
import SignerListPlace from "../components/pdf/SignerListPlace";
import Header from "../components/pdf/PdfHeader";
import WidgetNameModal from "../components/pdf/WidgetNameModal";
import { PDFDocument } from "pdf-lib";
import { SaveFileSize } from "../constant/saveFileSize";
import {
pdfNewWidthFun,
contractUsers,
@@ -34,7 +36,9 @@ import {
handleSignatureType,
getBase64FromUrl,
convertPdfArrayBuffer,
generatePdfName
generatePdfName,
textWidget,
multiSignEmbed
} from "../constant/Utils";
import RenderPdf from "../components/pdf/RenderPdf";
import "../styles/AddUser.css";
@@ -132,6 +136,10 @@ const TemplatePlaceholder = () => {
const [scale, setScale] = useState(1);
const [signatureType, setSignatureType] = useState([]);
const [pdfArrayBuffer, setPdfArrayBuffer] = useState("");
const [updatedPdfUrl, setUpdatedPdfUrl] = useState("");
const [tempSignerId, setTempSignerId] = useState("");
const [unSignedWidgetId, setUnSignedWidgetId] = useState("");
const [owner, setOwner] = useState({});
useEffect(() => {
fetchTemplate();
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -158,7 +166,6 @@ const TemplatePlaceholder = () => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [divRef.current, isHeader]);
//function to fetch tenant Details
const fetchTenantDetails = async () => {
const user = JSON.parse(
@@ -208,6 +215,7 @@ const TemplatePlaceholder = () => {
: [];
if (documentData && documentData.length > 0) {
setOwner(documentData?.[0]?.ExtUserPtr);
const url = documentData[0] && documentData[0]?.URL;
if (url) {
const arrayBuffer = await convertPdfArrayBuffer(url);
@@ -238,9 +246,22 @@ const TemplatePlaceholder = () => {
documentData[0].Placeholders &&
documentData[0].Placeholders.length > 0
) {
setSignerPos(documentData[0].Placeholders);
const placeholder = documentData[0]?.Placeholders.filter(
(data) => data.Role !== "prefill"
);
setSignerPos(placeholder);
signersdata.forEach((x) => {
if (x.objectId) {
const obj = {
__type: "Pointer",
className: "contracts_Contactbook",
objectId: x.objectId
};
signers.push(obj);
}
});
let signers = [...documentData[0].Signers];
let updatedSigners = documentData[0].Placeholders.map((x) => {
let updatedSigners = placeholder.map((x) => {
let matchingSigner = signers.find(
(y) => x.signerObjId && x.signerObjId === y.objectId
);
@@ -263,22 +284,25 @@ const TemplatePlaceholder = () => {
const updatedSigners = documentData[0].Signers.map((x, index) => ({
...x,
Id: randomId(),
Role: "User " + (index + 1)
Role: "Role " + (index + 1)
}));
setSignersData(updatedSigners);
setUniqueId(updatedSigners[0].Id);
setBlockColor(updatedSigners[0].blockColor);
}
} else {
setRoleName("User 1");
setRoleName("Role 1");
if (
documentData[0].Placeholders &&
documentData[0].Placeholders.length > 0
) {
let updatedSigners = documentData[0].Placeholders.map((x) => {
const placeholder = documentData[0]?.Placeholders.filter(
(data) => data.Role !== "prefill"
);
let updatedSigners = placeholder.map((x) => {
return { Role: x.Role, Id: x.Id, blockColor: x.blockColor };
});
setSignerPos(documentData[0].Placeholders);
setSignerPos(placeholder);
setUniqueId(updatedSigners[0].Id);
setSignersData(updatedSigners);
setIsSelectId(0);
@@ -353,7 +377,8 @@ const TemplatePlaceholder = () => {
const widgetHeight =
defaultWidthHeight(dragTypeValue).height * containerScale;
let dropData = [],
currentPagePosition;
currentPagePosition,
filterSignerPos;
let placeHolder;
if (item === "onclick") {
// `getBoundingClientRect()` is used to get accurate measurement height of the div
@@ -368,7 +393,7 @@ const TemplatePlaceholder = () => {
scale: containerScale,
zIndex: posZIndex,
type: dragTypeValue,
options: addWidgetOptions(dragTypeValue),
options: addWidgetOptions(dragTypeValue, owner),
Width: widgetWidth / (containerScale * scale),
Height: widgetHeight / (containerScale * scale)
};
@@ -398,7 +423,7 @@ const TemplatePlaceholder = () => {
// isMobile: isMobile,
zIndex: posZIndex,
type: item.text,
options: addWidgetOptions(dragTypeValue),
options: addWidgetOptions(dragTypeValue, owner),
Width: widgetWidth / (containerScale * scale),
Height: widgetHeight / (containerScale * scale)
};
@@ -406,7 +431,11 @@ const TemplatePlaceholder = () => {
dropData.push(dropObj);
placeHolder = { pageNumber: pageNumber, pos: dropData };
}
let filterSignerPos = signerPos.find((data) => data.Id === uniqueId);
if (dragTypeValue === textWidget) {
filterSignerPos = signerPos.find((data) => data.Role === "prefill");
} else {
filterSignerPos = signerPos.find((data) => data.Id === uniqueId);
}
const getPlaceHolder = filterSignerPos?.placeHolder;
if (getPlaceHolder) {
//checking exist placeholder on same page
@@ -423,20 +452,53 @@ const TemplatePlaceholder = () => {
const newSignPos = getPos.concat(dropData);
let xyPos = { pageNumber: pageNumber, pos: newSignPos };
updatePlace.push(xyPos);
const updatesignerPos = signerPos.map((x) =>
x.Id === uniqueId ? { ...x, placeHolder: updatePlace } : x
);
let updatesignerPos;
if (dragTypeValue === textWidget) {
updatesignerPos = signerPos.map((x) =>
x.Role === "prefill" ? { ...x, placeHolder: updatePlace } : x
);
} else {
updatesignerPos = signerPos.map((x) =>
x.Id === uniqueId ? { ...x, placeHolder: updatePlace } : x
);
}
setSignerPos(updatesignerPos);
} else {
//else condition to add placeholder widgets on multiple page first time
const updatesignerPos = signerPos.map((x) =>
x.Id === uniqueId && x?.placeHolder
? { ...x, placeHolder: [...x.placeHolder, placeHolder] }
: x.Id === uniqueId
? { ...x, placeHolder: [placeHolder] }
: x
);
setSignerPos(updatesignerPos);
//if condition when widget type is prefill label text widget
if (dragTypeValue === textWidget) {
//check text widgets data (prefill) already exist then and want to add text widget on new page
//create new page entry with old data and update placeholder
if (filterSignerPos) {
const addPrefillData =
filterSignerPos && filterSignerPos?.placeHolder;
addPrefillData.push(placeHolder);
const updatePrefillPos = signerPos.map((x) =>
x.Role === "prefill" ? { ...x, placeHolder: addPrefillData } : x
);
setSignerPos(updatePrefillPos);
} //else condition if user do not have any text widget data
else {
const prefillTextWidget = {
signerPtr: {},
signerObjId: "",
blockColor: "#f58f8c",
placeHolder: [placeHolder],
Role: "prefill",
Id: key
};
setSignerPos((prev) => [...prev, prefillTextWidget]);
}
} else {
//else condition to add placeholder widgets on multiple page first time
const updatesignerPos = signerPos.map((x) =>
x.Id === uniqueId && x?.placeHolder
? { ...x, placeHolder: [...x.placeHolder, placeHolder] }
: x.Id === uniqueId
? { ...x, placeHolder: [placeHolder] }
: x
);
setSignerPos(updatesignerPos);
}
}
if (dragTypeValue === "dropdown") {
@@ -446,9 +508,14 @@ const TemplatePlaceholder = () => {
} else if (dragTypeValue === radioButtonWidget) {
setIsRadio(true);
} else if (
[textInputWidget, "name", "company", "job title", "email"].includes(
dragTypeValue
)
[
textInputWidget,
textWidget,
"name",
"company",
"job title",
"email"
].includes(dragTypeValue)
) {
setFontSize(12);
setFontColor("black");
@@ -509,20 +576,22 @@ const TemplatePlaceholder = () => {
containerWH
);
if (keyValue >= 0) {
const filterSignerPos = updateSignPos.filter(
(data) => data.Id === signId
);
let filterSignerPos;
if (signId) {
filterSignerPos = updateSignPos.filter((data) => data.Id === signId);
} else {
filterSignerPos = updateSignPos.filter(
(data) => data.Role === "prefill"
);
}
if (filterSignerPos.length > 0) {
const getPlaceHolder = filterSignerPos[0].placeHolder;
const getPageNumer = getPlaceHolder.filter(
(data) => data.pageNumber === pageNumber
);
if (getPageNumer.length > 0) {
const getXYdata = getPageNumer[0].pos;
const getPosData = getXYdata;
const addSignPos = getPosData.map((url) => {
if (url.key === keyValue) {
@@ -542,12 +611,17 @@ const TemplatePlaceholder = () => {
return obj;
});
const newUpdateSigner = updateSignPos.map((obj) => {
if (obj.Id === signId) {
return { ...obj, placeHolder: newUpdateSignPos };
if (signId) {
if (obj.Id === signId) {
return { ...obj, placeHolder: newUpdateSignPos };
}
} else {
if (obj.Role === "prefill") {
return { ...obj, placeHolder: newUpdateSignPos };
}
}
return obj;
});
setSignerPos(newUpdateSigner);
}
}
@@ -654,10 +728,32 @@ const TemplatePlaceholder = () => {
};
const alertSendEmail = async () => {
const isPlaceholderExist = signerPos.every((data) => data.placeHolder);
if (isPlaceholderExist) {
const getPrefill = signerPos?.find((data) => data.Role === "prefill");
const prefillPlaceholder = getPrefill?.placeHolder;
let isLabel = false;
let unfilledTextWidgetId = "";
//condition is used to check text widget data is empty or have response
if (getPrefill && prefillPlaceholder) {
prefillPlaceholder.map((data) => {
if (!isLabel) {
const unfilledTextWidgets = data.pos.find(
(position) => !position.options.response
);
if (unfilledTextWidgets) {
isLabel = true;
unfilledTextWidgetId = unfilledTextWidgets.key;
}
}
});
}
if (getPrefill && isLabel) {
setUnSignedWidgetId(unfilledTextWidgetId);
} else if (isPlaceholderExist) {
handleSaveTemplate();
} else {
const signerList = signerPos.filter((data) => !data.placeHolder);
const signerList = signerPos.filter(
(data) => !data.placeHolder && data.Role !== "prefill"
);
const getSigner = signerList.map((x) => {
return signersdata.find((y) => y.Id === x.Id).Role;
});
@@ -673,7 +769,7 @@ const TemplatePlaceholder = () => {
}, 2000);
return () => clearTimeout(timer);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [signerPos, signatureType, pdfBase64Url]);
}, [signerPos, signersdata, signatureType, pdfBase64Url]);
// `autosavedetails` is used to save template details after every 2 sec when changes are happern in placeholder like drag-drop widgets, remove signers
const autosavedetails = async () => {
@@ -681,11 +777,7 @@ const TemplatePlaceholder = () => {
let pdfUrl;
if (isUploadPdf) {
const pdfName = generatePdfName(16);
pdfUrl = await convertBase64ToFile(
pdfName,
pdfBase64Url,
"",
);
pdfUrl = await convertBase64ToFile(pdfName, pdfBase64Url, "");
}
if (signersdata?.length > 0) {
signersdata.forEach((x) => {
@@ -703,7 +795,10 @@ const TemplatePlaceholder = () => {
const templateCls = new Parse.Object("contracts_Template");
templateCls.id = templateId;
if (signerPos?.length > 0) {
templateCls.set("Placeholders", signerPos);
const removePrefill = signerPos.filter((x) => x.Role !== "prefill");
if (removePrefill.length > 0) {
templateCls.set("Placeholders", removePrefill);
}
}
templateCls.set("Signers", signers);
templateCls.set("SignatureType", signatureType);
@@ -731,11 +826,60 @@ const TemplatePlaceholder = () => {
}
};
//embed prefill label widget data
const embedPrefilllData = async () => {
const prefillExist = signerPos.filter((data) => data.Role === "prefill");
if (prefillExist && prefillExist.length > 0) {
const placeholder = prefillExist[0].placeHolder;
const existingPdfBytes = pdfArrayBuffer;
const pdfDoc = await PDFDocument.load(existingPdfBytes, {
ignoreEncryption: true
});
const isSignYourSelfFlow = false;
try {
const pdfBase64 = await multiSignEmbed(
placeholder,
pdfDoc,
isSignYourSelfFlow,
scale
);
const pdfName = generatePdfName(16);
const pdfUrl = await convertBase64ToFile(pdfName, pdfBase64, "");
const tenantId = localStorage.getItem("TenantId");
const buffer = atob(pdfBase64);
SaveFileSize(buffer.length, pdfUrl, tenantId);
return pdfUrl;
} catch (err) {
console.log("error to convertBase64ToFile in placeholder flow", err);
alert(err?.message);
}
} else if (pdfBase64Url) {
try {
const pdfName = generatePdfName(16);
const pdfUrl = await convertBase64ToFile(pdfName, pdfBase64Url, "");
return pdfUrl;
} catch (err) {
console.log("error to convertBase64ToFile in placeholder flow", err);
alert(err?.message);
}
} else {
return pdfDetails[0].URL;
}
};
const handleSaveTemplate = async () => {
if (signersdata?.length) {
const remindOnceInEvery = parseInt(pdfDetails[0]?.RemindOnceInEvery);
const TimeToCompleteDays = parseInt(pdfDetails[0]?.TimeToCompleteDays);
const AutomaticReminders = pdfDetails[0]?.AutomaticReminders;
const reminderCount = TimeToCompleteDays / remindOnceInEvery;
if (AutomaticReminders && reminderCount > 15) {
alert(t("only-15-reminder-allowed"));
return;
}
setIsLoading({ isLoad: true, message: t("loading-mssg") });
setIsSendAlert(false);
let signers = [];
let signers = [],
pdfUrl;
if (signersdata?.length > 0) {
signersdata.forEach((x) => {
if (x.objectId) {
@@ -748,15 +892,10 @@ const TemplatePlaceholder = () => {
}
});
}
let pdfUrl = pdfDetails[0]?.URL;
if (pdfBase64Url) {
try {
const pdfName = generatePdfName(16);
pdfUrl = await convertBase64ToFile(
pdfName,
pdfBase64Url,
"",
);
pdfUrl = await embedPrefilllData();
setUpdatedPdfUrl(pdfUrl);
} catch (e) {
console.log("error to convertBase64ToFile in placeholder flow", e);
}
@@ -774,8 +913,9 @@ const TemplatePlaceholder = () => {
const RedirectUrl = pdfDetails[0]?.RedirectUrl
? { RedirectUrl: pdfDetails[0]?.RedirectUrl }
: {};
const removePrefill = signerPos.filter((x) => x?.Role !== "prefill");
const data = {
Placeholders: signerPos,
Placeholders: removePrefill,
Signers: signers,
Name: pdfDetails[0]?.Name || "",
Note: pdfDetails[0]?.Note || "",
@@ -927,25 +1067,13 @@ const TemplatePlaceholder = () => {
const handleCreateDocModal = async () => {
setIsCreateDocModal(false);
setIsCreateDoc(true);
let pdfUrl = pdfDetails[0]?.URL;
if (pdfBase64Url) {
try {
const pdfName = generatePdfName(16);
pdfUrl = await convertBase64ToFile(
pdfName,
pdfBase64Url,
"",
);
} catch (e) {
console.log("error to convertBase64ToFile in placeholder flow", e);
}
}
const removePrefill = signerPos.filter((x) => x.Role !== "prefill");
// handle create document
const res = await createDocument(
pdfDetails,
signerPos,
removePrefill,
signersdata,
pdfUrl
updatedPdfUrl
);
if (res.status === "success") {
navigate(`/placeHolderSign/${res.id}`, {
@@ -953,7 +1081,7 @@ const TemplatePlaceholder = () => {
});
setIsCreateDoc(false);
} else {
setHandleError(t("something-went-wrong-mssg"));
setHandleError(t(res.id));
setIsCreateDoc(false);
}
};
@@ -971,7 +1099,7 @@ const TemplatePlaceholder = () => {
const Id = randomId();
const index = signersdata.length;
const obj = {
Role: roleName || "User " + count,
Role: roleName || "Role " + count,
Id: Id,
blockColor: color[index]
};
@@ -980,7 +1108,7 @@ const TemplatePlaceholder = () => {
signerPtr: {},
signerObjId: "",
blockColor: color[index],
Role: roleName || "User " + count,
Role: roleName || "Role " + count,
Id: Id
};
@@ -1120,6 +1248,7 @@ const TemplatePlaceholder = () => {
? { RedirectUrl: updateTemplate?.[0]?.RedirectUrl }
: {};
const data = {
...(updateTemplate?.[0]?.URL ? { URL: updateTemplate?.[0]?.URL } : {}),
Name: updateTemplate?.[0]?.Name || "",
Note: updateTemplate?.[0]?.Note || "",
Description: updateTemplate?.[0]?.Description || "",
@@ -1328,8 +1457,7 @@ const TemplatePlaceholder = () => {
status: defaultdata?.status || "required",
hint: defaultdata?.hint || "",
defaultValue: defaultdata?.defaultValue || "",
validation:
{},
validation: {},
fontSize:
fontSize || currWidgetsDetails?.options?.fontSize || 12,
fontColor:
@@ -1338,6 +1466,15 @@ const TemplatePlaceholder = () => {
"black"
}
};
} else if (["signature"].includes(position.type)) {
return {
...position,
options: {
...position.options,
name: defaultdata.name,
hint: defaultdata?.hint || ""
}
};
} else {
return {
...position,
@@ -1346,6 +1483,7 @@ const TemplatePlaceholder = () => {
name: defaultdata.name,
status: defaultdata.status,
defaultValue: defaultdata.defaultValue,
hint: defaultdata?.hint || "",
fontSize:
fontSize || currWidgetsDetails?.options?.fontSize || 12,
fontColor:
@@ -1386,6 +1524,13 @@ const TemplatePlaceholder = () => {
setShowDropdown(false);
setIsRadio(false);
setIsCheckbox(false);
//condition for text widget type after set all values for text widget
//change setUniqueId which is set in tempsignerId
//because textwidget do not have signer user so for selected signers we have to do
if (currWidgetsDetails.type === textWidget) {
setUniqueId(tempSignerId);
setTempSignerId("");
}
};
const clickOnZoomIn = () => {
@@ -1433,7 +1578,14 @@ const TemplatePlaceholder = () => {
style: { fontSize: "13px" }
}
];
const textFieldTour = [
{
selector: '[data-tut="IsSigned"]',
content: t("text-field-tour"),
position: "top",
style: { fontSize: "13px" }
}
];
return (
<>
<Title title={"Template"} />
@@ -1468,13 +1620,25 @@ const TemplatePlaceholder = () => {
closeWithMask={false}
/>
)}
<Tour
onRequestClose={() => setIsSendAlert(false)}
steps={signatureWidgetTour}
isOpen={isSendAlert}
rounded={5}
closeWithMask={false}
/>
{isSendAlert && (
<Tour
onRequestClose={() => setIsSendAlert(false)}
steps={signatureWidgetTour}
isOpen={true}
rounded={5}
closeWithMask={false}
/> // this is the tour for add signature widget for all role
)}
{unSignedWidgetId && (
<Tour
onRequestClose={() => setUnSignedWidgetId("")}
steps={textFieldTour}
isOpen={true}
rounded={5}
closeWithMask={false}
/>
)}
{/* this component used to render all pdf pages in left side */}
<RenderAllPdfPage
allPages={allPages}
@@ -1614,6 +1778,10 @@ const TemplatePlaceholder = () => {
pageNumber={pageNumber}
signKey={signKey}
Id={uniqueId}
widgetType={widgetType}
setUniqueId={setUniqueId}
tempSignerId={tempSignerId}
setTempSignerId={setTempSignerId}
/>
{/* pdf header which contain funish back button */}
<Header
@@ -1691,6 +1859,9 @@ const TemplatePlaceholder = () => {
setFontColor={setFontColor}
isResize={isResize}
divRef={divRef}
setTempSignerId={setTempSignerId}
uniqueId={uniqueId}
unSignedWidgetId={unSignedWidgetId}
/>
)}
</div>
@@ -1721,7 +1892,6 @@ const TemplatePlaceholder = () => {
handleOnBlur={handleOnBlur}
title={t("roles")}
initial={true}
isTemplateFlow={true}
sendInOrder={pdfDetails[0].SendinOrder}
setSignersData={setSignersData}
blockColor={blockColor}
@@ -1729,6 +1899,7 @@ const TemplatePlaceholder = () => {
setSignerPos={setSignerPos}
uniqueId={uniqueId}
setSelectWidgetId={setSelectWidgetId}
isTemplateFlow={true}
/>
</div>
) : (
@@ -1792,16 +1963,17 @@ const TemplatePlaceholder = () => {
/>
)}
</div>
<ModalUi
title={t("edit-template")}
isOpen={isEditTemplate}
handleClose={handleEditTemplateModal}
>
{isEditTemplate && (
<EditTemplate
title={t("edit-template")}
handleClose={handleEditTemplateModal}
pdfbase64={pdfBase64Url}
template={pdfDetails?.[0]}
onSuccess={handleEditTemplateForm}
setPdfArrayBuffer={setPdfArrayBuffer}
setPdfBase64Url={setPdfBase64Url}
/>
</ModalUi>
)}
<WidgetNameModal
signatureType={signatureType}
widgetName={widgetName}
@@ -7,6 +7,7 @@ import Title from "../components/Title";
import { useTranslation } from "react-i18next";
import { emailRegex } from "../constant/const";
const UpdateExistUserAdmin = () => {
const appName = "OpenSign™";
const { t } = useTranslation();
const navigate = useNavigate();
const [formdata, setFormdata] = useState({ email: "", masterkey: "" });
@@ -28,7 +29,7 @@ const UpdateExistUserAdmin = () => {
}
} catch (err) {
console.log("Err in checkadminexist", err);
setErrMsg("Something went wrong.");
setErrMsg(t("something-went-wrong-mssg"));
} finally {
setLoader(false);
}
@@ -93,7 +94,7 @@ const UpdateExistUserAdmin = () => {
</div>
)}
<h2 className="text-[30px] text-center mt-3 font-medium">
{t("opensign-Setup")}
{t("opensign-setup", { appName })}
</h2>
<NavLink
to="https://discord.com/invite/xe9TDuyAyj"
+188 -197
View File
@@ -10,7 +10,6 @@ import AddUser from "../components/AddUser";
import Title from "../components/Title";
import { useTranslation } from "react-i18next";
const heading = ["Sr.No", "Name", "Email", "Phone", "Role", "Team", "Active"];
// const actions = [];
const UserList = () => {
const { t } = useTranslation();
const [userList, setUserList] = useState([]);
@@ -114,9 +113,8 @@ const UserList = () => {
setUserList(_userRes);
} catch (err) {
console.log("Err in fetch userlist", err);
setIsAlert({ type: "danger", msg: t("something-went-wrong-mssg") });
showAlert("danger", t("something-went-wrong-mssg"));
} finally {
setTimeout(() => setIsAlert({ type: "success", msg: "" }), 1500);
setIsLoader(false);
}
}
@@ -173,17 +171,15 @@ const UserList = () => {
extUser.id = user.objectId;
extUser.set("IsDisabled", !IsDisabled);
await extUser.save();
setIsAlert({
type: !IsDisabled === true ? "danger" : "success",
msg:
!IsDisabled === true ? t("user-deactivated") : t("user-activated")
});
showAlert(
!IsDisabled === true ? "danger" : "success",
!IsDisabled === true ? t("user-deactivated") : t("user-activated")
);
} catch (err) {
setIsAlert({ type: "danger", msg: t("something-went-wrong-mssg") });
showAlert("danger", t("something-went-wrong-mssg"));
console.log("err in disable team", err);
} finally {
setIsActLoader({});
setTimeout(() => setIsAlert({ type: "success", msg: "" }), 1500);
}
}
};
@@ -191,6 +187,11 @@ const UserList = () => {
setIsActiveModal({ [user.objectId]: true });
};
// `showAlert` handle show/hide alert
const showAlert = (type, msg) => {
setIsAlert({ type, msg });
setTimeout(() => setIsAlert({ type: "success", msg: "" }), 1500);
};
return (
<div className="relative">
<Title title={isAdmin ? "Users" : "Page not found"} />
@@ -205,197 +206,187 @@ const UserList = () => {
</div>
)}
{
!isLoader && (
<>
{isAdmin ? (
<div className="p-2 w-full bg-base-100 text-base-content op-card shadow-lg">
{isAlert.msg && (
<Alert type={isAlert.type}>{isAlert.msg}</Alert>
)}
<div className="flex flex-row items-center justify-between my-2 mx-3 text-[20px] md:text-[23px]">
<div className="font-light">
{t("report-name.Users")}{" "}
<span className="text-xs md:text-[13px] font-normal">
<Tooltip message={t("users-from-teams")} />
</span>
</div>
<div className="flex flex-row gap-2 items-center">
<div
className="cursor-pointer"
onClick={() => handleModal("form")}
>
<i className="fa-light fa-square-plus text-accent text-[30px] md:text-[40px]"></i>
</div>
</div>
</div>
<div className="overflow-x-auto w-full">
<table className="op-table border-collapse w-full">
<thead className="text-[14px]">
<tr className="border-y-[1px]">
{heading?.map((item, index) => (
<th key={index} className="px-4 py-2">
{t(`report-heading.${item}`)}
</th>
))}
</tr>
</thead>
<tbody className="text-[12px]">
{userList?.length > 0 && (
<>
{currentList.map((item, index) => (
<tr className="border-y-[1px]" key={index}>
{heading.includes("Sr.No") && (
<th className="px-4 py-2">
{startIndex + index + 1}
</th>
)}
<td className="px-4 py-2 font-semibold">
{item?.Name}{" "}
</td>
<td className="px-4 py-2 ">
{item?.Email || "-"}
</td>
<td className="px-4 py-2">
{item?.Phone || "-"}
</td>
<td className="px-4 py-2">
{item?.UserRole?.split("_").pop() || "-"}
</td>
<td className="px-4 py-2">
{formatRow(item.TeamIds)}
</td>
{item.UserRole !== "contracts_Admin" && (
<td className="px-4 py-2 font-semibold">
<label className="cursor-pointer relative block items-center mb-0">
<input
type="checkbox"
className="op-toggle transition-all op-toggle-secondary"
checked={item?.IsDisabled !== true}
onChange={() => handleToggleBtn(item)}
/>
</label>
{isActiveModal[item.objectId] && (
<ModalUi
isOpen
title={t("user-status")}
handleClose={handleClose}
>
<div className="m-[20px]">
<div className="text-lg font-normal text-black">
{t("are-you-sure")}{" "}
{item?.IsDisabled
? t("activate")
: t("deactivate")}{" "}
{t("this-user")}?
</div>
<hr className="bg-[#ccc] mt-4 " />
<div className="flex items-center mt-3 gap-2 text-white">
<button
onClick={() =>
handleToggleSubmit(item)
}
className="op-btn op-btn-primary"
>
{t("yes")}
</button>
<button
onClick={handleClose}
className="op-btn op-btn-secondary"
>
{t("no")}
</button>
</div>
</div>
</ModalUi>
)}
</td>
)}
</tr>
))}
</>
)}
</tbody>
</table>
</div>
<div className="flex flex-row justify-between items-center text-xs font-medium">
<div className="op-join flex flex-wrap items-center p-2">
{userList.length > recordperPage && (
<button
onClick={() => paginateBack()}
className="op-join-item op-btn op-btn-sm"
>
{t("prev")}
</button>
)}
{pageNumbers.map((x, i) => (
<button
key={i}
onClick={() => setCurrentPage(x)}
disabled={x === "..."}
className={`${
x === currentPage ? "op-btn-active" : ""
} op-join-item op-btn op-btn-sm`}
>
{x}
</button>
))}
{userList.length > recordperPage && (
<button
onClick={() => paginateFront()}
className="op-join-item op-btn op-btn-sm"
>
{t("next")}
</button>
)}
</div>
</div>
{userList?.length <= 0 && (
<div
className={`${
isDashboard ? "h-[317px]" : ""
} flex flex-col items-center justify-center w-ful bg-base-100 text-base-content rounded-xl py-4`}
>
<div className="w-[60px] h-[60px] overflow-hidden">
<img
className="w-full h-full object-contain"
src={pad}
alt="img"
/>
</div>
<div className="text-sm font-semibold">
{t("no-data-avaliable")}
</div>
</div>
)}
<ModalUi
isOpen={isModal.form}
title={formHeader}
handleClose={() => handleModal("form")}
>
<AddUser
setIsAlert={setIsAlert}
handleUserData={handleUserData}
closePopup={() => handleModal("form")}
setFormHeader={setFormHeader}
/>
</ModalUi>
{!isLoader && (
<>
{isAdmin ? (
<div className="p-2 w-full bg-base-100 text-base-content op-card shadow-lg">
{isAlert.msg && <Alert type={isAlert.type}>{isAlert.msg}</Alert>}
<div className="flex flex-row items-center justify-between my-2 mx-3 text-[20px] md:text-[23px]">
<div className="font-light">
{t("report-name.Users")}{" "}
<span className="text-xs md:text-[13px] font-normal">
<Tooltip message={t("users-from-teams")} />
</span>
</div>
) : (
<div className="flex items-center justify-center h-screen w-full bg-base-100 text-base-content rounded-box">
<div className="text-center">
<h1 className="text-[60px] lg:text-[120px] font-semibold">
404
</h1>
<p className="text-[30px] lg:text-[50px]">
{t("page-not-found")}
</p>
<div className="flex flex-row gap-2 items-center">
<div
className="cursor-pointer"
onClick={() => handleModal("form")}
>
<i className="fa-light fa-square-plus text-accent text-[30px] md:text-[40px]"></i>
</div>
</div>
</div>
<div className="w-full overflow-x-auto">
<table className="op-table border-collapse w-full mb-[50px]">
<thead className="text-[14px]">
<tr className="border-y-[1px]">
{heading?.map((item, index) => (
<th key={index} className="px-4 py-2">
{t(`report-heading.${item}`)}
</th>
))}
</tr>
</thead>
{userList?.length > 0 && (
<tbody className="text-[12px]">
{currentList.map((item, index) => (
<tr className="border-y-[1px]" key={index}>
{heading.includes("Sr.No") && (
<th className="px-4 py-2">
{startIndex + index + 1}
</th>
)}
<td className="px-4 py-2 font-semibold">
{item?.Name}{" "}
</td>
<td className="px-4 py-2 ">{item?.Email || "-"}</td>
<td className="px-4 py-2">{item?.Phone || "-"}</td>
<td className="px-4 py-2">
{item?.UserRole?.split("_").pop() || "-"}
</td>
<td className="px-4 py-2">
{formatRow(item.TeamIds)}
</td>
{item.UserRole !== "contracts_Admin" ? (
<td className="px-4 py-2 font-semibold">
<label className="cursor-pointer relative block items-center mb-0">
<input
type="checkbox"
className="op-toggle transition-all op-toggle-secondary"
checked={item?.IsDisabled !== true}
onChange={() => handleToggleBtn(item)}
/>
</label>
{isActiveModal[item.objectId] && (
<ModalUi
isOpen
title={t("user-status")}
handleClose={handleClose}
>
<div className="m-[20px]">
<div className="text-lg font-normal text-black">
{t("are-you-sure")}{" "}
{item?.IsDisabled
? t("activate")
: t("deactivate")}{" "}
{t("this-user")}?
</div>
<hr className="bg-[#ccc] mt-4 " />
<div className="flex items-center mt-3 gap-2 text-white">
<button
onClick={() => handleToggleSubmit(item)}
className="op-btn op-btn-primary"
>
{t("yes")}
</button>
<button
onClick={handleClose}
className="op-btn op-btn-secondary"
>
{t("no")}
</button>
</div>
</div>
</ModalUi>
)}
</td>
) : (
<td className="px-4 py-2 font-semibold"></td>
)}
</tr>
))}
</tbody>
)}
</table>
</div>
<div className="flex flex-row justify-between items-center text-xs font-medium">
<div className="op-join flex flex-wrap items-center p-2">
{userList.length > recordperPage && (
<button
onClick={() => paginateBack()}
className="op-join-item op-btn op-btn-sm"
>
{t("prev")}
</button>
)}
{pageNumbers.map((x, i) => (
<button
key={i}
onClick={() => setCurrentPage(x)}
disabled={x === "..."}
className={`${
x === currentPage ? "op-btn-active" : ""
} op-join-item op-btn op-btn-sm`}
>
{x}
</button>
))}
{userList.length > recordperPage && (
<button
onClick={() => paginateFront()}
className="op-join-item op-btn op-btn-sm"
>
{t("next")}
</button>
)}
</div>
</div>
{userList?.length <= 0 && (
<div
className={`${
isDashboard ? "h-[317px]" : ""
} flex flex-col items-center justify-center w-ful bg-base-100 text-base-content rounded-xl py-4`}
>
<div className="w-[60px] h-[60px] overflow-hidden">
<img
className="w-full h-full object-contain"
src={pad}
alt="img"
/>
</div>
<div className="text-sm font-semibold">
{t("no-data-avaliable")}
</div>
</div>
)}
</>
)
}
<ModalUi
isOpen={isModal.form}
title={formHeader}
handleClose={() => handleModal("form")}
>
<AddUser
showAlert={showAlert}
handleUserData={handleUserData}
closePopup={() => handleModal("form")}
setFormHeader={setFormHeader}
/>
</ModalUi>
</div>
) : (
<div className="flex items-center justify-center h-screen w-full bg-base-100 text-base-content rounded-box">
<div className="text-center">
<h1 className="text-[60px] lg:text-[120px] font-semibold">
404
</h1>
<p className="text-[30px] lg:text-[50px]">
{t("page-not-found")}
</p>
</div>
</div>
)}
</>
)}
</div>
);
};
+6 -12
View File
@@ -1,7 +1,4 @@
import React, {
useState,
useEffect,
} from "react";
import React, { useState, useEffect } from "react";
import { Navigate, useNavigate } from "react-router";
import Parse from "parse";
import { SaveFileSize } from "../constant/saveFileSize";
@@ -10,10 +7,7 @@ import Title from "../components/Title";
import sanitizeFileName from "../primitives/sanitizeFileName";
import axios from "axios";
import Tooltip from "../primitives/Tooltip";
import {
getSecureUrl,
handleSendOTP,
} from "../constant/Utils";
import { getSecureUrl, handleSendOTP } from "../constant/Utils";
import ModalUi from "../primitives/ModalUi";
import Loader from "../primitives/Loader";
import { useTranslation } from "react-i18next";
@@ -121,13 +115,13 @@ function UserProfile() {
const updateExtUser = async (obj) => {
try {
const extData = JSON.parse(localStorage.getItem("Extand_Class"));
const ExtUserId = extData[0].objectId;
const ExtUserId = extData?.[0]?.objectId;
const body = {
Phone: obj?.Phone || "",
Name: obj.Name,
JobTitle: jobTitle,
Company: company,
Language: obj?.language || "",
Language: obj?.language || ""
};
await axios.put(
@@ -235,7 +229,7 @@ function UserProfile() {
const handleResend = async (e) => {
e.preventDefault();
setOtpLoader(true);
await handleSendOTP();
await handleSendOTP(Parse.User.current().getEmail());
setOtpLoader(false);
alert(t("otp-sent-alert"));
};
@@ -412,7 +406,7 @@ function UserProfile() {
<button
type="button"
onClick={(e) => {
editmode ? handleSubmit(e) : setEditMode(true);
editmode ? handleSubmit(e) : setEditMode(true);
}}
className="op-btn op-btn-primary w-[100px]"
>
+10 -10
View File
@@ -33,8 +33,9 @@ const AddContact = (props) => {
try {
const baseURL = localStorage.getItem("baseUrl");
const url = `${baseURL}functions/isuserincontactbook`;
const token =
{ "X-Parse-Session-Token": localStorage.getItem("accesstoken") };
const token = {
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
};
const headers = {
"Content-Type": "application/json",
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
@@ -63,18 +64,15 @@ const AddContact = (props) => {
)
);
const userId = user?.objectId || "";
const tenantDetails = await getTenantDetails(
userId,
);
const tenantDetails = await getTenantDetails(userId);
const tenantId = tenantDetails?.objectId || "";
if (tenantId) {
try {
const baseURL = localStorage.getItem("baseUrl");
const url = `${baseURL}functions/savecontact`;
const token =
{
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
};
const token = {
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
};
const data = { name, email, phone, tenantId };
const headers = {
"Content-Type": "application/json",
@@ -137,7 +135,9 @@ const AddContact = (props) => {
</div>
)}
<div className="w-full mx-auto p-[8px]">
<div className="text-[14px] font-[700]">{t("add-contact")}</div>
{!props?.isDisableTitle && (
<div className="text-[14px] font-[700]">{t("add-contact")}</div>
)}
{isUserExist && (
<div className="mb-[0.75rem] flex items-center mt-1">
<input
+1 -1
View File
@@ -11,7 +11,7 @@ const Alert = ({ children, type, className }) => {
case "danger":
return "op-alert-error";
case "warning":
return "op-alert-warning";
return "op-alert-warning text-black";
default:
return "";
}
@@ -0,0 +1,34 @@
import React from "react";
const CheckCircle = ({ size = 56, color = "text-green-500" }) => {
return (
<div className={`flex items-center justify-center ${color}`}>
<svg
xmlns="http://www.w3.org/2000/svg"
className={`w-${size / 4} h-${size / 4}`}
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth={2}
>
<circle
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="2"
fill="none"
/>
<path
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M9 12l2 2l4-4"
/>
</svg>
</div>
);
};
export default CheckCircle;
+14 -13
View File
@@ -12,6 +12,8 @@ import JSZip from "jszip";
import { saveAs } from "file-saver";
function DownloadPdfZip(props) {
const appName =
"OpenSign™";
const { t } = useTranslation();
const [selectType, setSelectType] = useState(1);
const [isDownloading, setIsDownloading] = useState(false);
@@ -63,19 +65,18 @@ function DownloadPdfZip(props) {
throw new Error(`Failed to fetch certificate PDF: ${certificateUrl}`);
}
const pdf2Blob = await pdf2Response.blob();
// Add files to ZIP
zip.file(
`${sanitizeFileName(pdfName)}_signed_by_OpenSign™.pdf`,
pdf1Blob
);
zip.file("Certificate_signed_by_OpenSign™.pdf", pdf2Blob);
// Generate the ZIP and trigger download
const zipBlob = await zip.generateAsync({ type: "blob" });
saveAs(
zipBlob,
`${sanitizeFileName(pdfName)}_signed_by_OpenSign™.zip`
);
// Add files to ZIP
zip.file(
`${sanitizeFileName(pdfName)}_signed_by_${appName}.pdf`,
pdf1Blob
);
zip.file(`Certificate_signed_by_${appName}.pdf`, pdf2Blob);
// Generate the ZIP and trigger download
const zipBlob = await zip.generateAsync({ type: "blob" });
saveAs(
zipBlob,
`${sanitizeFileName(pdfName)}_signed_by_${appName}.zip`
);
setSelectType(1);
props.setIsDownloadModal(false);
setIsDownloading("");
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -45,7 +45,7 @@ const Tooltip = ({
</a>
<ReactTooltip
id={id ? id : "my-tooltip"}
className={`${maxWidth ? maxWidth : "max-w-[200px]"} z-[200]`}
className={`${maxWidth ? maxWidth : "max-w-[200px]"} z-[200]`}
/>
</>
);
@@ -0,0 +1,35 @@
import React from "react";
import { useTranslation } from "react-i18next";
import ModalUi from "./ModalUi";
import { useNavigate } from "react-router";
import Parse from "parse";
const ValidateSession = ({ children }) => {
const { t } = useTranslation();
const navigate = useNavigate();
const handleLoginBtn = async () => {
try {
await Parse?.User?.logOut();
} catch (err) {
console.log("err ", err);
} finally {
localStorage.removeItem("accesstoken");
navigate("/", { replace: true });
}
};
return localStorage.getItem("accesstoken") ? (
children
) : (
<ModalUi showHeader={false} isOpen={true} showClose={false}>
<div className="flex flex-col justify-center items-center py-4 md:py-5 gap-5">
<p className="text-xl font-medium">Your session has expired.</p>
<button onClick={handleLoginBtn} className="op-btn op-btn-neutral">
{t("login")}
</button>
</div>
</ModalUi>
);
};
export default ValidateSession;
@@ -1,7 +1,7 @@
function sanitizeFileName(fileName) {
// Remove spaces and invalid characters
const file = fileName.replace(/[^a-zA-Z0-9._-]/g, "");
const removedot = file.replace(/\.(?=.*\.)/g, "");
return removedot.replace(/[^a-zA-Z0-9._-]/g, "");
const file = fileName?.replace(/[^a-zA-Z0-9._-]/g, "");
const removedot = file?.replace(/\.(?=.*\.)/g, "");
return removedot?.replace(/[^a-zA-Z0-9._-]/g, "");
}
export default sanitizeFileName;
+5 -1
View File
@@ -1,5 +1,9 @@
# Use an official Node runtime as the base image
FROM node:20
FROM node:22.14.0
# install java
RUN wget https://downloads.opensign.me/jdk-23_linux-x64_bin.deb \
&& dpkg -i jdk-23_linux-x64_bin.deb
# Set the working directory inside the container
WORKDIR /usr/src/app
+129 -135
View File
@@ -2,15 +2,16 @@ import dotenv from 'dotenv';
import { format, toZonedTime } from 'date-fns-tz';
import { getSignedLocalUrl } from './cloud/parsefunction/getSignedUrl.js';
import { PDFDocument } from 'pdf-lib';
import crypto from 'node:crypto';
import axios from 'axios';
dotenv.config();
export const cloudServerUrl = 'http://localhost:8080/app';
export const appName = process.env.APP_NAME || 'OpenSign™';
export function customAPIurl() {
const url = new URL(cloudServerUrl);
return url.pathname === '/api/app' ? url.origin + '/api' : url.origin;
}
export const appName = 'OpenSign™';
export const MAX_NAME_LENGTH = 250;
export const MAX_NOTE_LENGTH = 200;
export const MAX_DESCRIPTION_LENGTH = 500;
export const color = [
'#93a3db',
'#e6c3db',
@@ -53,7 +54,7 @@ export const saveFileUsage = async (size, fileUrl, userId) => {
className: '_User',
objectId: userId,
});
const tenant = await tenantQuery.first();
const tenant = await tenantQuery.first({ useMasterKey: true });
if (tenant) {
const tenantPtr = { __type: 'Pointer', className: 'partners_Tenant', objectId: tenant.id };
try {
@@ -105,20 +106,29 @@ export const updateMailCount = async (extUserId, plan, monthchange) => {
try {
const contractUser = await query.first({ useMasterKey: true });
if (contractUser) {
const _extRes = JSON.parse(JSON.stringify(contractUser));
let updateDate = new Date();
if (_extRes?.LastEmailCountReset?.iso) {
updateDate = new Date(_extRes?.LastEmailCountReset?.iso);
const newDate = new Date();
// Update the month while keeping the same day and year
updateDate.setMonth(newDate.getMonth());
updateDate.setFullYear(newDate.getFullYear());
}
contractUser.increment('EmailCount', 1);
if (plan === 'freeplan') {
if (monthchange) {
contractUser.set('LastEmailCountReset', new Date());
contractUser.set('LastEmailCountReset', updateDate);
contractUser.set('MonthlyFreeEmails', 1);
} else {
if (contractUser?.get('MonthlyFreeEmails')) {
contractUser.increment('MonthlyFreeEmails', 1);
if (contractUser?.get('LastEmailCountReset')) {
contractUser.set('LastEmailCountReset', new Date());
contractUser.set('LastEmailCountReset', updateDate);
}
} else {
contractUser.set('MonthlyFreeEmails', 1);
contractUser.set('LastEmailCountReset', new Date());
contractUser.set('LastEmailCountReset', updateDate);
}
}
}
@@ -129,131 +139,6 @@ export const updateMailCount = async (extUserId, plan, monthchange) => {
}
};
export function formatWidgetOptions(type, options) {
const colorsArr = ['red', 'black', 'blue', 'yellow'];
const fontSizes = [2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28];
const status = options?.required === true ? 'required' : 'optional' || 'required';
const defaultValue = options?.default || '';
const values = options?.values || [];
const color = options?.color ? options.color : 'black';
const fontColor = colorsArr.includes(color) ? color : 'black';
const size = options?.fontsize ? parseInt(options.fontsize) : 12;
const fontSize = fontSizes.includes(size) ? size : 12;
switch (type) {
case 'signature':
return { name: 'signature', status: 'required' };
case 'stamp':
return { status: status, name: 'stamp' };
case 'initials':
return { status: status, name: options.name || 'initials' };
case 'image':
return { status: status, name: options.name || 'image' };
case 'email':
return {
status: status,
name: options.name || 'email',
validation: { type: 'email' },
fontColor: fontColor,
fontSize: fontSize,
};
case 'name':
return {
status: status,
name: options.name || 'name',
fontColor: fontColor,
fontSize: fontSize,
};
case 'job title':
return {
status: status,
name: options.name || 'job title',
fontColor: fontColor,
fontSize: fontSize,
};
case 'company':
return {
status: status,
name: options.name || 'company',
fontColor: fontColor,
fontSize: fontSize,
};
case 'date': {
let today = new Date();
let dd = String(today.getDate()).padStart(2, '0');
let mm = String(today.getMonth() + 1).padStart(2, '0'); // January is 0!
let yyyy = today.getFullYear();
today = dd + '-' + mm + '-' + yyyy;
let dateFormat = options?.format;
dateFormat = dateFormat.replace(/m/g, 'M');
return {
status: status,
name: options.name || 'date',
response: defaultValue || today,
validation: { format: dateFormat || 'dd-MM-yyyy', type: 'date-format' },
fontColor: fontColor,
fontSize: fontSize,
};
}
case 'textbox':
return {
status: status,
name: 'textbox',
defaultValue: defaultValue,
hint: options.hint,
validation: { type: 'regex', pattern: options?.regularexpression || '/^[a-zA-Z0-9s]+$/' },
fontColor: fontColor,
fontSize: fontSize,
isReadOnly: options?.readonly || false,
};
case 'checkbox': {
const arr = options?.values;
let selectedvalues = [];
for (const obj of options.selectedvalues) {
const index = arr.indexOf(obj);
selectedvalues.push(index);
}
return {
status: status,
name: options.name || 'checkbox',
values: values,
isReadOnly: options?.readonly || false,
isHideLabel: options?.hidelabel || false,
validation: {
minRequiredCount: options?.validation?.minselections || 0,
maxRequiredCount: options?.validation?.maxselections || 0,
},
defaultValue: selectedvalues || [],
fontColor: fontColor,
fontSize: fontSize,
};
}
case 'radio button': {
return {
status: status,
name: options.name || 'radio',
values: values,
isReadOnly: options?.readonly || false,
isHideLabel: options?.hidelabel || false,
defaultValue: defaultValue,
fontColor: fontColor,
fontSize: fontSize,
};
}
case 'dropdown':
return {
status: status,
name: options.name || 'dropdown',
values: values,
defaultValue: defaultValue,
fontColor: fontColor,
fontSize: fontSize,
isReadOnly: options?.readonly || false,
};
default:
break;
}
}
export function sanitizeFileName(fileName) {
// Remove spaces and invalid characters
const file = fileName.replace(/[^a-zA-Z0-9._-]/g, '');
@@ -265,7 +150,7 @@ export const useLocal = process.env.USE_LOCAL ? process.env.USE_LOCAL.toLowerCas
export const smtpsecure = process.env.SMTP_PORT && process.env.SMTP_PORT !== '465' ? false : true;
export const smtpenable =
process.env.SMTP_ENABLE && process.env.SMTP_ENABLE.toLowerCase() === 'true' ? true : false;
export const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
// `generateId` is used to unique Id for fileAdapter
export function generateId(length) {
@@ -342,3 +227,112 @@ export const flattenPdf = async pdfFile => {
throw new Error('error in pdf');
}
};
export const mailTemplate = param => {
const themeColor = '#47a3ad';
const subject = `${param.senderName} has requested you to sign "${param.title}"`;
const AppName = appName;
const logo = `<img src='https://qikinnovation.ams3.digitaloceanspaces.com/logo.png' height='50' />`;
const opurl = ` <a href='www.opensignlabs.com' target=_blank>here</a>`;
const body =
"<html><head><meta http-equiv='Content-Type' content='text/html;charset=UTF-8' /></head><body><div style='background-color:#f5f5f5;padding:20px'><div style='background:white;padding-bottom:20px'><div style='padding:10px'>" +
logo +
`</div><div style='padding:2px;font-family:system-ui;background-color:${themeColor}'><p style='font-size:20px;font-weight:400;color:white;padding-left:20px'>Digital Signature Request</p></div><div><p style='padding:20px;font-size:14px;margin-bottom:10px'>` +
param.senderName +
' has requested you to review and sign <strong>' +
param.title +
"</strong>.</p><div style='padding: 5px 0px 5px 25px;display:flex;flex-direction:row;justify-content:space-around'><table><tr><td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Sender</td><td></td><td style='color:#626363;font-weight:bold'>" +
param.senderMail +
"</td></tr><tr><td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Organization</td><td></td><td style='color:#626363;font-weight:bold'> " +
param.organization +
"</td></tr><tr><td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Expires on</td><td></td><td style='color:#626363;font-weight:bold'>" +
param.localExpireDate +
"</td></tr><tr><td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Note</td><td></td><td style='color:#626363;font-weight:bold'>" +
param.note +
"</td></tr><tr><td></td><td></td></tr></table></div> <div style='margin-left:70px'><a target=_blank href=" +
param.sigingUrl +
"><button style='padding:12px;background-color:#d46b0f;color:white;border:0px;font-weight:bold;margin-top:30px'>Sign here</button></a></div><div style='display:flex;justify-content:center;margin-top:10px'></div></div></div><div><p> This is an automated email from " +
AppName +
'. For any queries regarding this email, please contact the sender ' +
param.senderMail +
` directly. If you think this email is inappropriate or spam, you may file a complaint with ${AppName}${opurl}.</p></div></div></body></html>`;
return { subject, body };
};
export const selectFormat = data => {
switch (data) {
case 'L':
return 'MM/dd/yyyy';
case 'MM/DD/YYYY':
return 'MM/dd/yyyy';
case 'DD-MM-YYYY':
return 'dd-MM-yyyy';
case 'DD/MM/YYYY':
return 'dd/MM/yyyy';
case 'LL':
return 'MMMM dd, yyyy';
case 'DD MMM, YYYY':
return 'dd MMM, yyyy';
case 'YYYY-MM-DD':
return 'yyyy-MM-dd';
case 'MM-DD-YYYY':
return 'MM-dd-yyyy';
case 'MM.DD.YYYY':
return 'MM.dd.yyyy';
case 'MMM DD, YYYY':
return 'MMM dd, yyyy';
case 'MMMM DD, YYYY':
return 'MMMM dd, yyyy';
case 'DD MMMM, YYYY':
return 'dd MMMM, yyyy';
default:
return 'MM/dd/yyyy';
}
};
export function formatDateTime(date, dateFormat, timeZone, is12Hour) {
const zonedDate = toZonedTime(date, timeZone); // Convert date to the given timezone
const timeFormat = is12Hour ? 'hh:mm:ss a' : 'HH:mm:ss';
return dateFormat
? format(zonedDate, `${selectFormat(dateFormat)}, ${timeFormat} 'GMT' XXX`, { timeZone })
: formatTimeInTimezone(date, timeZone);
}
// Utility: Convert base64 to buffer
export const base64ToBuffer = base64 => Buffer.from(base64, 'base64');
// Utility: Generate SHA-256 hash from PDF page metadata
const getPdfMetadataHash = async pdfBytes => {
const pdfDoc = await PDFDocument.load(pdfBytes);
const metaString = pdfDoc
.getPages()
.map((page, index) => {
const { width, height } = page.getSize();
return `${index + 1}:${Math.round(width)}x${Math.round(height)}`;
})
.join('|');
return crypto.createHash('sha256').update(metaString).digest('hex');
};
// Utility: Validate if uploaded file matches original template PDF
export const handleReplaceFileValidation = async (baseFileUrl, newFileBase64) => {
try {
const { data } = await axios.get(baseFileUrl, { responseType: 'arraybuffer' });
const basePdfBytes = Buffer.from(data);
const uploadedPdfBytes = base64ToBuffer(newFileBase64);
const baseHash = await getPdfMetadataHash(basePdfBytes);
const uploadedHash = await getPdfMetadataHash(uploadedPdfBytes);
if (baseHash === uploadedHash) {
return { base64: newFileBase64 };
}
return { error: 'PDFs do NOT match based on page number, width, and height' };
} catch (err) {
console.error('Validation Error:', err.message);
return { error: err.message };
}
};
+5 -1
View File
@@ -11,7 +11,11 @@ export const SSOAuth = {
Authorization: `Bearer ${authData.access_token}`,
},
});
if (response.data && response.data.id && response.data.email === authData.id) {
if (
response.data &&
response.data.id &&
response.data.email?.toLowerCase()?.replace(/\s/g, '') === authData.id
) {
return;
}
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'SSO auth is invalid for this user.');
+10 -6
View File
@@ -1,4 +1,3 @@
import PDF from './parsefunction/pdf/PDF.js';
import sendmailv3 from './parsefunction/sendMailv3.js';
import usersignup from './parsefunction/usersignup.js';
@@ -13,7 +12,6 @@ import getDrive from './parsefunction/getDrive.js';
import getReport from './parsefunction/getReport.js';
import TemplateAfterSave from './parsefunction/TemplateAfterSave.js';
import GetTemplate from './parsefunction/GetTemplate.js';
import callWebhook from './parsefunction/callWebhook.js';
import DocumentBeforesave from './parsefunction/DocumentBeforesave.js';
import TemplateBeforeSave from './parsefunction/TemplateBeforesave.js';
import DocumentBeforeFind from './parsefunction/DocumentAfterFind.js';
@@ -22,7 +20,6 @@ import UserAfterFind from './parsefunction/UserAfterFInd.js';
import SignatureAfterFind from './parsefunction/SignatureAfterFind.js';
import TenantAterFind from './parsefunction/TenantAfterFind.js';
import VerifyEmail from './parsefunction/VerifyEmail.js';
import encryptedpdf from './parsefunction/encryptedPdf.js';
import { getSignedUrl } from './parsefunction/getSignedUrl.js';
import createBatchDocs from './parsefunction/createBatchDocs.js';
import linkContactToDoc from './parsefunction/linkContactToDoc.js';
@@ -51,7 +48,11 @@ import generateCertificatebydocId from './parsefunction/generateCertificatebydoc
import fileUpload from './parsefunction/fileUpload.js';
import getUserListByOrg from './parsefunction/getUserListByOrg.js';
import editContact from './parsefunction/editContact.js';
import forwardDoc from './parsefunction/ForwardDoc.js';
import saveAsTemplate from './parsefunction/saveAsTemplate.js';
import updateTenant from './parsefunction/updateTenant.js';
import recreateDocument from './parsefunction/recreateDocument.js';
import addUser from './parsefunction/addUser.js';
// This afterSave function triggers after an object is added or updated in the specified class, allowing for post-processing logic.
Parse.Cloud.afterSave('contracts_Document', DocumentAftersave);
@@ -82,9 +83,7 @@ Parse.Cloud.define('getDocument', getDocument);
Parse.Cloud.define('getDrive', getDrive);
Parse.Cloud.define('getReport', getReport);
Parse.Cloud.define('getTemplate', GetTemplate);
Parse.Cloud.define('callwebhook', callWebhook);
Parse.Cloud.define('verifyemail', VerifyEmail);
Parse.Cloud.define('encryptedpdf', encryptedpdf);
Parse.Cloud.define('getsignedurl', getSignedUrl);
Parse.Cloud.define('batchdocuments', createBatchDocs);
Parse.Cloud.define('linkcontacttodoc', linkContactToDoc);
@@ -112,3 +111,8 @@ Parse.Cloud.define('generatecertificate', generateCertificatebydocId);
Parse.Cloud.define('fileupload', fileUpload);
Parse.Cloud.define('getuserlistbyorg', getUserListByOrg);
Parse.Cloud.define('editcontact', editContact);
Parse.Cloud.define('forwarddoc', forwardDoc);
Parse.Cloud.define('saveastemplate', saveAsTemplate);
Parse.Cloud.define('updatetenant', updateTenant);
Parse.Cloud.define('recreatedoc', recreateDocument);
Parse.Cloud.define('adduser', addUser);
@@ -61,7 +61,7 @@ async function addTeamAndOrg(extUser) {
async function saveUser(userDetails) {
const userQuery = new Parse.Query(Parse.User);
userQuery.equalTo('username', userDetails.email);
userQuery.equalTo('username', userDetails.email?.toLowerCase()?.replace(/\s/g, ''));
const userRes = await userQuery.first({ useMasterKey: true });
if (userRes) {
@@ -83,9 +83,9 @@ async function saveUser(userDetails) {
return { id: login.objectId, sessionToken: login.sessionToken };
} else {
const user = new Parse.User();
user.set('username', userDetails.email);
user.set('username', userDetails.email?.toLowerCase()?.replace(/\s/g, ''));
user.set('password', userDetails.password);
user.set('email', userDetails.email);
user.set('email', userDetails.email?.toLowerCase()?.replace(/\s/g, ''));
if (userDetails?.phone) {
user.set('phone', userDetails.phone);
}
@@ -111,7 +111,6 @@ export default async function AddAdmin(request) {
if (extUser) {
return { message: 'User already exist' };
} else {
// console.log("role ", role);
const partnerQuery = new Parse.Object('partners_Tenant');
partnerQuery.set('UserId', {
__type: 'Pointer',
@@ -123,7 +122,7 @@ export default async function AddAdmin(request) {
partnerQuery.set('ContactNumber', userDetails.phone);
}
partnerQuery.set('TenantName', userDetails.company);
partnerQuery.set('EmailAddress', userDetails.email);
partnerQuery.set('EmailAddress', userDetails.email?.toLowerCase()?.replace(/\s/g, ''));
partnerQuery.set('IsActive', true);
partnerQuery.set('CreatedBy', {
__type: 'Pointer',
@@ -154,7 +153,7 @@ export default async function AddAdmin(request) {
objectId: user.id,
});
newObj.set('UserRole', userDetails.role);
newObj.set('Email', userDetails.email);
newObj.set('Email', userDetails.email?.toLowerCase()?.replace(/\s/g, ''));
newObj.set('Name', userDetails.name);
if (userDetails?.phone) {
newObj.set('Phone', userDetails?.phone);
@@ -177,7 +176,7 @@ export default async function AddAdmin(request) {
const extUser = {
objectId: extRes.id,
Name: userDetails.name,
Email: userDetails.email,
Email: userDetails.email?.toLowerCase()?.replace(/\s/g, ''),
Phone: userDetails?.phone ? userDetails.phone : '',
TenantId: { objectId: tenantRes.id },
UserId: { objectId: user.id },
@@ -1,4 +1,31 @@
import { MAX_DESCRIPTION_LENGTH, MAX_NAME_LENGTH, MAX_NOTE_LENGTH } from '../../Utils.js';
async function DocumentBeforesave(request) {
if (!request.original) {
const validations = [
{ field: 'Name', max: MAX_NAME_LENGTH },
{ field: 'Note', max: MAX_NOTE_LENGTH },
{ field: 'Description', max: MAX_DESCRIPTION_LENGTH },
];
for (const { field, max } of validations) {
const value = request?.object?.get(field);
if (value && value.length > max) {
throw new Parse.Error(
Parse.Error.VALIDATION_ERROR,
`The "${field}" field must be at most ${max} characters long.`
);
}
}
const TimeToCompleteDays = request?.object?.get('TimeToCompleteDays') || 15;
const RemindOnceInEvery = request?.object?.get('RemindOnceInEvery') || 5;
const AutoReminder = request?.object?.get('AutomaticReminders') || false;
const reminderCount = TimeToCompleteDays / RemindOnceInEvery;
if (AutoReminder && reminderCount > 15) {
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'only 15 reminder allowed');
}
}
try {
// below code is used to update document when user sent document or self signed
const document = request.object;
@@ -27,7 +54,6 @@ async function DocumentBeforesave(request) {
}
if (document?.get('Signers') && document.get('Signers').length > 0) {
document.set('DocSentAt', new Date());
document.save(null, { useMasterKey: true });
}
}
} catch (err) {
@@ -0,0 +1,82 @@
import axios from 'axios';
import { appName, cloudServerUrl } from '../../Utils.js';
export default async function forwardDoc(request) {
try {
if (!request.user) {
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'unauthorized.');
}
const { docId, recipients } = request.params;
const isReceipents = recipients?.length > 0 && recipients?.length <= 10;
if (docId && isReceipents) {
const userPtr = { __type: 'Pointer', className: '_User', objectId: request.user.id };
const docQuery = new Parse.Query('contracts_Document');
docQuery
.equalTo('objectId', docId)
.equalTo('CreatedBy', userPtr)
.notEqualTo('IsArchive', true)
.notEqualTo('IsDeclined', true)
.include('Signers')
.include('ExtUserPtr')
.include('Placeholders.signerPtr')
.include('ExtUserPtr.TenantId');
const docRes = await docQuery.first({ useMasterKey: true });
if (!docRes) {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Document not found.');
}
const _docRes = docRes?.toJSON();
const docName = _docRes.Name;
const fileAdapterId = _docRes?.FileAdapterId || '';
const extUserId = _docRes?.ExtUserPtr?.objectId;
const TenantAppName = appName;
const from = _docRes?.ExtUserPtr?.Email;
const replyTo = _docRes?.ExtUserPtr?.Email;
const senderName = _docRes?.ExtUserPtr?.Name;
try {
let mailRes;
for (let i = 0; i < recipients.length; i++) {
const logo = `<img src='https://qikinnovation.ams3.digitaloceanspaces.com/logo.png' height='50' style='padding:20px'/>`;
const opurl = ` <a href='www.opensignlabs.com' target=_blank>here</a>`;
const themeColor = '#47a3ad';
let params = {
extUserId: extUserId,
pdfName: docName,
url: _docRes?.SignedUrl || '',
recipient: recipients[i],
subject: `${senderName} has signed the doc - ${docName}`,
replyto: replyTo || '',
from: from,
html:
`<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8'/></head><body><div style='background-color:#f5f5f5;padding:20px'><div style='background-color:white'><div>` +
`${logo}</div><div style='padding:2px;font-family:system-ui;background-color:${themeColor}'><p style='font-size:20px;font-weight:400;color:white;padding-left:20px'>Document Copy</p></div><div>` +
`<p style='padding:20px;font-family:system-ui;font-size:14px'>A copy of the document <strong>${docName}</strong> is attached to this email. Kindly download the document from the attachment.</p>` +
`</div></div><div><p>This is an automated email from ${TenantAppName}. For any queries regarding this email, please contact the sender ${replyTo} directly. ` +
`If you think this email is inappropriate or spam, you may file a complaint with ${TenantAppName}${opurl}.</p></div></div></body></html>`,
};
mailRes = await axios.post(`${cloudServerUrl}/functions/sendmailv3`, params, {
headers: {
'Content-Type': 'application/json',
'X-Parse-Application-Id': process.env.APP_ID,
'X-Parse-Master-Key': process.env.MASTER_KEY,
},
});
}
return mailRes.data?.result;
} catch (error) {
const msg =
error?.response?.data?.error ||
error?.response?.data ||
error?.message ||
'Something went wrong.';
throw new Parse.Error(400, msg);
}
} else {
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'please provide parameters.');
}
} catch (err) {
console.log('Err in forwardDoc', err);
throw err;
}
}
@@ -1,20 +1,26 @@
import { appName } from '../../Utils.js';
// `GetLogoByDomain` is used to get logo by domain as well as check any tenant exist or not in db
export default async function GetLogoByDomain(request) {
const domain = request.params.domain;
try {
const tenantCreditsQuery = new Parse.Query('partners_Tenant');
tenantCreditsQuery.equalTo('Domain', domain);
const res = await tenantCreditsQuery.first();
const res = await tenantCreditsQuery.first({ useMasterKey: true });
if (res) {
const updateRes = JSON.parse(JSON.stringify(res));
return { logo: updateRes?.Logo, user: 'exist' };
return {
logo: updateRes?.Logo,
appname: appName,
user: 'exist',
};
} else {
const tenantCreditsQuery = new Parse.Query('partners_Tenant');
const tenantRes = await tenantCreditsQuery.first();
const tenantRes = await tenantCreditsQuery.first({ useMasterKey: true });
if (tenantRes) {
return { logo: '', user: 'exist' };
return { logo: '', appname: appName, user: 'exist' };
} else {
return { logo: '', user: 'not_exist' };
return { logo: '', appname: appName, user: 'not_exist' };
}
}
} catch (err) {
@@ -1,112 +1,82 @@
import axios from 'axios';
import {
cloudServerUrl,
} from '../../Utils.js';
import { cloudServerUrl } from '../../Utils.js';
export default async function GetTemplate(request) {
const serverUrl = cloudServerUrl; //process.env.SERVER_URL;
const templateId = request.params.templateId;
const ispublic = request.params.ispublic;
const sessiontoken = request.headers?.sessiontoken;
try {
if (!ispublic) {
let userEmail;
if (sessiontoken) {
const userRes = await axios.get(serverUrl + '/users/me', {
headers: {
'X-Parse-Application-Id': process.env.APP_ID,
'X-Parse-Session-Token': sessiontoken,
},
});
userEmail = userRes.data && userRes.data.email;
}
if (templateId && userEmail) {
try {
let template = new Parse.Query('contracts_Template');
template.equalTo('objectId', templateId);
template.include('ExtUserPtr');
template.include('Signers');
template.include('CreatedBy');
template.include('ExtUserPtr.TenantId');
template.include('Bcc');
const extUserQuery = new Parse.Query('contracts_Users');
extUserQuery.equalTo('Email', userEmail);
extUserQuery.include('TeamIds');
const extUser = await extUserQuery.first({ useMasterKey: true });
if (extUser) {
const _extUser = JSON.parse(JSON.stringify(extUser));
if (_extUser?.TeamIds && _extUser.TeamIds?.length > 0) {
let teamsArr = [];
_extUser?.TeamIds?.forEach(x => (teamsArr = [...teamsArr, ...x.Ancestors]));
// Create the first query
const sharedWithQuery = new Parse.Query('contracts_Template');
sharedWithQuery.containedIn('SharedWith', teamsArr);
// Create the second query
const createdByQuery = new Parse.Query('contracts_Template');
createdByQuery.equalTo('ExtUserPtr', {
__type: 'Pointer',
className: 'contracts_Users',
objectId: extUser.id,
});
template = Parse.Query.or(sharedWithQuery, createdByQuery);
template.equalTo('objectId', templateId);
template.include('ExtUserPtr');
template.include('Signers');
template.include('CreatedBy');
template.include('ExtUserPtr.TenantId');
template.include('Placeholders.signerPtr');
template.include('Bcc');
}
}
const res = await template.first({ useMasterKey: true });
if (res) {
const templateRes = JSON.parse(JSON.stringify(res));
delete templateRes?.ExtUserPtr?.TenantId?.FileAdapters;
delete templateRes?.ExtUserPtr?.TenantId?.PfxFile;
return templateRes;
} else {
return { error: "You don't have access of this document!" };
}
} catch (err) {
console.log('err', err);
return err;
}
} else {
return { error: "You don't have access of this document!" };
}
} else if (templateId && ispublic) {
let userEmail;
if (sessiontoken) {
const userRes = await axios.get(serverUrl + '/users/me', {
headers: {
'X-Parse-Application-Id': process.env.APP_ID,
'X-Parse-Session-Token': sessiontoken,
},
});
userEmail = userRes.data && userRes.data.email;
}
if (templateId && userEmail) {
try {
const template = new Parse.Query('contracts_Template');
let template = new Parse.Query('contracts_Template');
template.equalTo('objectId', templateId);
template.include('ExtUserPtr');
template.include('Signers');
template.include('CreatedBy');
template.include('ExtUserPtr.TenantId');
template.include('Bcc');
const extUserQuery = new Parse.Query('contracts_Users');
extUserQuery.equalTo('Email', userEmail);
extUserQuery.include('TeamIds');
const extUser = await extUserQuery.first({ useMasterKey: true });
if (extUser) {
const _extUser = JSON.parse(JSON.stringify(extUser));
if (_extUser?.TeamIds && _extUser.TeamIds?.length > 0) {
let teamsArr = [];
_extUser?.TeamIds?.forEach(x => (teamsArr = [...teamsArr, ...x.Ancestors]));
// Create the first query
const sharedWithQuery = new Parse.Query('contracts_Template');
sharedWithQuery.containedIn('SharedWith', teamsArr);
// Create the second query
const createdByQuery = new Parse.Query('contracts_Template');
createdByQuery.equalTo('ExtUserPtr', {
__type: 'Pointer',
className: 'contracts_Users',
objectId: extUser.id,
});
template = Parse.Query.or(sharedWithQuery, createdByQuery);
template.equalTo('objectId', templateId);
template.include('ExtUserPtr');
template.include('Signers');
template.include('CreatedBy');
template.include('ExtUserPtr.TenantId');
template.include('Placeholders.signerPtr');
template.include('Bcc');
}
}
const res = await template.first({ useMasterKey: true });
// console.log("res ", res)
if (res) {
const templateRes = JSON.parse(JSON.stringify(res));
delete templateRes?.ExtUserPtr?.TenantId?.FileAdapters;
delete templateRes?.ExtUserPtr?.TenantId?.PfxFile;
return templateRes;
} else {
return { error: "You don't have access of this document!" };
return { error: "You don't have access of this template!" };
}
} catch (err) {
console.log('err', err);
return err;
}
} else {
return { error: 'Please pass required parameters!' };
return { error: "You don't have access of this template!" };
}
} catch (err) {
console.log('err', err);
if (err?.response?.data?.code === 209 || err.code == 209) {
return { error: 'Invalid session token' };
} else {
return { error: "You don't have access of this document!" };
return { error: "You don't have access of this template!" };
}
}
}
@@ -1,10 +1,10 @@
import axios from 'axios';
export default async function Newsletter(request) {
const name = request.params.name;
const email = request.params.email;
const email = request.params?.email?.toLowerCase()?.replace(/\s/g, '');
const domain = request.params.domain;
try {
const envAppId = process.env.REACT_APP_APPID || 'legadranaxn';
const envAppId = process.env.REACT_APP_APPID || 'opensign';
const headers = { 'Content-Type': 'application/json', 'X-Parse-Application-Id': envAppId };
const envProdServer = process.env.REACT_APP_SERVERURL || 'https://app.opensignlabs.com/api/app';
const newsletter = await axios.post(
@@ -11,7 +11,7 @@ async function getDocument(docId) {
query.include('Placeholders');
query.notEqualTo('IsArchive', true);
const res = await query.first({ useMasterKey: true });
const _res = res.toJSON();
const _res = res?.toJSON();
return _res?.ExtUserPtr?.objectId;
} catch (err) {
console.log('err ', err);
@@ -19,24 +19,24 @@ async function getDocument(docId) {
}
async function sendMailOTPv1(request) {
try {
//--for elearning app side
let code = Math.floor(1000 + Math.random() * 9000);
let email = request.params.email;
var TenantId = request.params.TenantId ? request.params.TenantId : undefined;
let TenantId = request.params.TenantId ? request.params.TenantId : undefined;
const AppName = appName;
if (email) {
const recipient = request.params.email;
const mailsender = smtpenable ? process.env.SMTP_USER_EMAIL : process.env.MAILGUN_SENDER;
try {
await Parse.Cloud.sendEmail({
from: appName + ' <' + mailsender + '>',
sender: AppName + ' <' + mailsender + '>',
recipient: recipient,
subject: `Your ${appName} OTP`,
text: 'This email is a test.',
subject: `Your ${AppName} OTP`,
text: 'otp email',
html:
`<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body><div style='background-color:#f5f5f5;padding:20px'><div style='box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;background-color:white;'><div style='background-color:red;padding:2px;font-family:system-ui; background-color:#47a3ad;'> <p style='font-size:20px;font-weight:400;color:white;padding-left:20px',>OTP Verification</p></div><div style='padding:20px'><p style='font-family:system-ui;font-size:14px'>Your OTP for ${appName} verification is:</p><p style=' text-decoration: none; font-weight: bolder; color:blue;font-size:45px;margin:20px'>` +
`<html><head><meta http-equiv='Content-Type' content='text/html;charset=UTF-8' /></head><body><div style='background-color:#f5f5f5;padding:20px'><div style='background-color:white;'><div style='background-color:red;padding:2px;font-family:system-ui;background-color:#47a3ad;'><p style='font-size:20px;font-weight:400;color:white;padding-left:20px;'>OTP Verification</p></div><div style='padding:20px;'><p style='font-family:system-ui;font-size:14px;'>Your OTP for ${AppName} verification is:</p><p style='text-decoration:none;font-weight:bolder;color:blue;font-size:45px;margin:20px;'>` +
code +
'</p></div> </div> </div></body></html>',
'</p></div></div></div></body></html>',
});
console.log('OTP sent', code);
if (request.params?.docId) {
@@ -1,4 +1,31 @@
import { MAX_DESCRIPTION_LENGTH, MAX_NAME_LENGTH, MAX_NOTE_LENGTH } from '../../Utils.js';
async function TemplateBeforeSave(request) {
if (!request.original) {
const validations = [
{ field: 'Name', max: MAX_NAME_LENGTH },
{ field: 'Note', max: MAX_NOTE_LENGTH },
{ field: 'Description', max: MAX_DESCRIPTION_LENGTH },
];
for (const { field, max } of validations) {
const value = request.object?.get(field);
if (value && value.length > max) {
throw new Parse.Error(
Parse.Error.VALIDATION_ERROR,
`The "${field}" field must be at most ${max} characters long.`
);
}
}
const TimeToCompleteDays = request.object.get('TimeToCompleteDays') || 15;
const RemindOnceInEvery = request?.object?.get('RemindOnceInEvery') || 5;
const AutoReminder = request?.object?.get('AutomaticReminders') || false;
const reminderCount = TimeToCompleteDays / RemindOnceInEvery;
if (AutoReminder && reminderCount > 15) {
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'only 15 reminder allowed');
}
}
try {
if (!request.original) {
// below code is used to update template when user sent template or self signed
@@ -0,0 +1,106 @@
export default async function addUser(request) {
const { phone, name, password, organization, team, tenantId, timezone, role } = request.params;
const email = request.params?.email?.toLowerCase()?.replace(/\s/g, '');
if (!request.user) {
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Invalid session token.');
}
const currentUser = { __type: 'Pointer', className: '_User', objectId: request.user.id };
if (name && email && password && organization && team && role && tenantId) {
try {
const extUser = new Parse.Object('contracts_Users');
extUser.set('Name', name);
if (phone) {
extUser.set('Phone', phone);
}
extUser.set('Email', email);
extUser.set('UserRole', `contracts_${role}`);
if (team) {
extUser.set('TeamIds', [
{
__type: 'Pointer',
className: 'contracts_Teams',
objectId: team,
},
]);
}
if (organization.objectId) {
extUser.set('OrganizationId', {
__type: 'Pointer',
className: 'contracts_Organizations',
objectId: organization.objectId,
});
}
if (organization.company) {
extUser.set('Company', organization.company);
}
if (tenantId) {
extUser.set('TenantId', {
__type: 'Pointer',
className: 'partners_Tenant',
objectId: tenantId,
});
}
if (timezone) {
extUser.set('Timezone', timezone);
}
try {
const _users = Parse.Object.extend('User');
const _user = new _users();
_user.set('name', name);
_user.set('username', email);
_user.set('email', email);
_user.set('password', password);
if (phone) {
_user.set('phone', phone);
}
const user = await _user.save();
if (user) {
extUser.set('CreatedBy', currentUser);
extUser.set('UserId', user);
const acl = new Parse.ACL();
acl.setPublicReadAccess(true);
acl.setPublicWriteAccess(true);
acl.setReadAccess(request.user.id, true);
acl.setWriteAccess(request.user.id, true);
extUser.setACL(acl);
const extUserRes = await extUser.save();
const parseData = JSON.parse(JSON.stringify(extUserRes));
return parseData;
}
} catch (err) {
console.log('err ', err);
if (err.code === 202) {
const userQuery = new Parse.Query(Parse.User);
userQuery.equalTo('email', email);
const userRes = await userQuery.first({ useMasterKey: true });
userRes.setPassword(password);
await userRes.save(null, { useMasterKey: true });
extUser.set('CreatedBy', currentUser);
extUser.set('UserId', { __type: 'Pointer', className: '_User', objectId: userRes.id });
const acl = new Parse.ACL();
acl.setPublicReadAccess(true);
acl.setPublicWriteAccess(true);
acl.setReadAccess(request.user.id, true);
acl.setWriteAccess(request.user.id, true);
extUser.setACL(acl);
const res = await extUser.save();
const parseData = JSON.parse(JSON.stringify(res));
return parseData;
} else {
throw new Parse.Error(400, err?.message || 'something went wrong');
}
}
} catch (err) {
console.log('err', err);
throw new Parse.Error(400, err?.message || 'something went wrong');
}
} else {
throw new Parse.Error(400, 'Please provide all required fields.');
}
}
@@ -1,108 +0,0 @@
import axios from 'axios';
import { cloudServerUrl } from '../../Utils.js';
export default async function callWebhook(request) {
const event = request.params.event;
const body = request.params.body;
const docId = body.objectId;
const contactId = request.params.contactId;
const serverUrl = cloudServerUrl; //process.env.SERVER_URL;
const appId = process.env.APP_ID;
try {
const docQuery = new Parse.Query('contracts_Document');
docQuery.include('ExtUserPtr.TenantId');
const docRes = await docQuery.get(docId, { useMasterKey: true });
const isEnableOTP = docRes?.get('IsEnableOTP') || false;
let userId;
if (isEnableOTP) {
const userRes = await axios.get(serverUrl + '/users/me', {
headers: {
'X-Parse-Application-Id': appId,
'X-Parse-Session-Token': request.headers['sessiontoken'],
},
});
userId = userRes.data && userRes.data.objectId;
}
if (!isEnableOTP || userId) {
if (event === 'viewed' && contactId) {
if (docRes) {
const _docRes = docRes.toJSON();
const userPtr = {
__type: 'Pointer',
className: 'contracts_Contactbook',
objectId: contactId,
};
const date = new Date().toISOString();
const obj = {
UserPtr: userPtr,
SignedUrl: _docRes.SignedUrl,
Activity: 'Viewed',
ipAddress: request.headers['x-real-ip'],
ViewedOn: date,
};
const isUserExist = _docRes?.AuditTrail?.some(
x => x.UserPtr.objectId === contactId && x?.ViewedOn
);
if (!isUserExist) {
const updateDoc = new Parse.Object('contracts_Document');
updateDoc.id = docRes.id;
if (_docRes?.AuditTrail && _docRes?.AuditTrail?.length > 0) {
updateDoc.set('AuditTrail', [..._docRes?.AuditTrail, obj]);
} else {
updateDoc.set('AuditTrail', [obj]);
}
await updateDoc.save(null, { useMasterKey: true });
}
}
}
const extendcls = new Parse.Query('contracts_Users');
extendcls.equalTo('objectId', docRes.get('ExtUserPtr')?.id);
// extendcls.equalTo('UserId', { __type: 'Pointer', className: '_User', objectId: userId });
const resExt = await extendcls.first({ useMasterKey: true });
if (resExt) {
const extUser = JSON.parse(JSON.stringify(resExt));
if (extUser?.Webhook) {
const params = { event: event, ...body };
await axios
.post(extUser?.Webhook, params, {
headers: { 'Content-Type': 'application/json' },
})
.then(res => {
try {
const webhook = new Parse.Object('contracts_Webhook');
webhook.set('Log', res?.status);
webhook.set('UserId', {
__type: 'Pointer',
className: '_User',
objectId: userId,
});
webhook.save(null, { useMasterKey: true });
} catch (err) {
console.log('err save in contracts_Webhook', err.message);
}
})
.catch(err => {
console.log('Err send data to webhook', err.message);
try {
const webhook = new Parse.Object('contracts_Webhook');
webhook.set('Log', err?.status);
webhook.set('UserId', {
__type: 'Pointer',
className: '_User',
objectId: userId,
});
webhook.save(null, { useMasterKey: true });
} catch (err) {
console.log('err save in contracts_Webhook', err.message);
}
});
}
return { message: 'webhook called!' };
}
} else {
return { message: 'User not found!' };
}
} catch (err) {
console.log('Err in callwebhook', err);
return { message: 'Something went wrong!' };
}
}
@@ -21,7 +21,7 @@ export default async function createBatchContact(req) {
TenantId: { __type: 'Pointer', className: 'partners_Tenant', objectId: x.TenantId },
CreatedBy: { __type: 'Pointer', className: '_User', objectId: req.user.id },
Name: x.Name,
Email: x.Email,
Email: x.Email?.toLowerCase()?.replace(/\s/g, ''),
IsDeleted: false,
IsImported: true,
...(x?.Phone ? { Phone: `${x?.Phone}` } : {}),
@@ -1,14 +1,8 @@
import axios from 'axios';
import {
cloudServerUrl,
replaceMailVaribles,
} from '../../Utils.js';
import { cloudServerUrl, replaceMailVaribles } from '../../Utils.js';
const serverUrl = cloudServerUrl; //process.env.SERVER_URL;
const appId = process.env.APP_ID;
async function deductcount(
docsCount,
extUserId,
) {
async function deductcount(docsCount, extUserId) {
try {
const extCls = new Parse.Object('contracts_Users');
extCls.id = extUserId;
@@ -18,9 +12,9 @@ async function deductcount(
console.log('Err in deduct in quick send', err);
}
}
async function sendMail(document) {
async function sendMail(document, publicUrl) {
//sessionToken
const baseUrl = new URL(process.env.PUBLIC_URL);
const baseUrl = new URL(publicUrl); //process.env.PUBLIC_URL
// console.log("pdfDetails", pdfDetails);
const timeToCompleteDays = document?.TimeToCompleteDays || 15;
@@ -32,8 +26,9 @@ async function sendMail(document) {
month: 'long',
year: 'numeric',
});
const sender = document.ExtUserPtr.Email;
let signerMail = document.Placeholders;
const senderName = document.ExtUserPtr.Name;
const senderEmail = document.ExtUserPtr.Email;
if (document.SendinOrder) {
signerMail = signerMail.slice();
@@ -41,7 +36,6 @@ async function sendMail(document) {
}
for (let i = 0; i < signerMail.length; i++) {
try {
const imgPng = 'https://qikinnovation.ams3.digitaloceanspaces.com/logo.png';
let url = `${serverUrl}/functions/sendmailv3`;
const headers = { 'Content-Type': 'application/json', 'X-Parse-Application-Id': appId };
const objectId = signerMail[i]?.signerObjId;
@@ -55,9 +49,7 @@ async function sendMail(document) {
encodeBase64 = btoa(`${document.objectId}/${signerMail[i].email}`);
}
let signPdf = `${hostUrl}/login/${encodeBase64}`;
const openSignUrl = 'https://www.opensignlabs.com/';
const orgName = document.ExtUserPtr.Company ? document.ExtUserPtr.Company : '';
const themeBGcolor = '#47a3ad';
const senderObj = document?.ExtUserPtr;
const mailBody = document?.ExtUserPtr?.TenantId?.RequestBody || '';
const mailSubject = document?.ExtUserPtr?.TenantId?.RequestSubject || '';
@@ -70,10 +62,9 @@ async function sendMail(document) {
'</body></html>';
const variables = {
document_title: document?.Name,
sender_name:
senderObj?.Name,
sender_mail:
senderObj?.Email,
note: document?.Note || '',
sender_name: senderName,
sender_mail: senderEmail,
sender_phone: senderObj?.Phone || '',
receiver_name: existSigner?.Name || '',
receiver_email: existSigner?.Email || signerMail[i].email,
@@ -84,41 +75,22 @@ async function sendMail(document) {
};
replaceVar = replaceMailVaribles(mailSubject, htmlReqBody, variables);
}
const mailparam = {
note: document?.Note || '',
senderName: senderName,
senderMail: senderEmail,
title: document.Name,
organization: orgName,
localExpireDate: localExpireDate,
sigingUrl: signPdf,
};
let params = {
extUserId: document.ExtUserPtr.objectId,
recipient: objectId ? existSigner?.Email : signerMail[i].email,
subject: replaceVar?.subject
? replaceVar?.subject
: `${document.ExtUserPtr.Name} has requested you to sign "${document.Name}"`,
from:
sender,
replyto:
sender ||
'',
html: replaceVar?.body
? replaceVar?.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='box-shadow:rgba(0, 0, 0, 0.1) 0px 4px 12px;background:white;padding-bottom:20px;'><div style='padding:10px 10px 0 10px'><img src=" +
imgPng +
" height='50' style='padding:20px;width:170px;height:40px;' /></div><div style='padding:2px;font-family:system-ui;background-color:" +
themeBGcolor +
";'><p style='font-size:20px;font-weight:400;color:white;padding-left:20px;' > Digital Signature Request</p></div><div><p style='padding:20px;font-family:system-ui;font-size:14px;margin-bottom:10px;'> " +
document.ExtUserPtr.Name +
' has requested you to review and sign <strong> ' +
document.Name +
"</strong>.</p><div style='padding: 5px 0px 5px 25px;display:flex;flex-direction:row;justify-content:space-around;'><table><tr><td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Sender</td><td></td><td style='color:#626363;font-weight:bold'>" +
sender +
"</td></tr><tr><td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Organization</td><td></td><td style='color:#626363;font-weight:bold'> " +
orgName +
"</td></tr><tr><td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Expires on</td><td></td><td style='color:#626363;font-weight:bold'>" +
localExpireDate +
"</td></tr><tr><td></td><td></td></tr></table></div><div style='margin-left:70px;'><a target=_blank href=" +
signPdf +
"><button style='padding:12px 12px 12px 12px;background-color:#d46b0f;color:white;border:0px;box-shadow:rgba(0, 0, 0, 0.05) 0px 6px 24px 0px,rgba(0, 0, 0, 0.08) 0px 0px 0px 1px;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 OpenSign™. For any queries regarding this email, please contact the sender " +
sender +
' directly.If you think this email is inappropriate or spam, you may file a complaint with OpenSign™ <a href=' +
openSignUrl +
' target=_blank>here</a>.</p></div></div></body></html>',
subject: replaceVar?.subject ? replaceVar?.subject : mailTemplate(mailparam).subject,
from: document.ExtUserPtr.Email,
replyto: senderEmail || '',
html: replaceVar?.body ? replaceVar?.body : mailTemplate(mailparam).body,
};
const sendMail = await axios.post(url, params, { headers: headers });
// if (sendMail.data.result.status === 'success') {
@@ -129,13 +101,7 @@ async function sendMail(document) {
}
}
}
async function batchQuery(
userId,
Documents,
Ip,
parseConfig,
type
) {
async function batchQuery(userId, Documents, Ip, parseConfig, type, publicUrl) {
const extCls = new Parse.Query('contracts_Users');
extCls.equalTo('UserId', {
__type: 'Pointer',
@@ -216,26 +182,35 @@ async function batchQuery(
...(x?.RedirectUrl ? { RedirectUrl: x?.RedirectUrl } : {}),
...(mailBody ? { RequestBody: mailBody } : {}),
...(mailSubject ? { RequestSubject: mailSubject } : {}),
...(x?.objectId
? {
TemplateId: {
__type: 'Pointer',
className: 'contracts_Template',
objectId: x?.objectId,
},
}
: {}),
},
};
});
// console.log('requests ', requests);
if (requests?.length > 0) {
const newrequests = [requests?.[0]];
const response = await axios.post('batch', { requests: newrequests }, parseConfig);
// Handle the batch query response
// console.log('Batch query response:', response.data);
if (response.data && response.data.length > 0) {
const document = Documents?.[0];
const updateDocuments = {
...document,
objectId: response.data[0]?.success?.objectId,
createdAt: response.data[0]?.success?.createdAt,
};
deductcount(response.data.length, resExt.id);
sendMail(updateDocuments); //sessionToken
return 'success';
}
if (requests?.length > 0) {
const newrequests = [requests?.[0]];
const response = await axios.post('batch', { requests: newrequests }, parseConfig);
// Handle the batch query response
// console.log('Batch query response:', response.data);
if (response.data && response.data.length > 0) {
const document = Documents?.[0];
const updateDocuments = {
...document,
objectId: response.data[0]?.success?.objectId,
createdAt: response.data[0]?.success?.createdAt,
};
deductcount(response.data.length, resExt.id);
sendMail(updateDocuments, publicUrl); //sessionToken
return 'success';
}
}
} catch (error) {
const code = error?.response?.data?.code || error?.response?.status || error?.code || 400;
@@ -257,6 +232,8 @@ export default async function createBatchDocs(request) {
const type = request.headers?.type || 'quicksend';
const Documents = JSON.parse(strDocuments);
const Ip = request?.headers?.['x-real-ip'] || '';
// Access the host from the headers
const publicUrl = request.headers.public_url;
const parseConfig = {
baseURL: serverUrl,
headers: {
@@ -267,9 +244,8 @@ export default async function createBatchDocs(request) {
};
try {
if (request?.user) {
return await batchQuery(request.user.id, Documents, Ip, parseConfig, '', type);
}
else {
return await batchQuery(request.user.id, Documents, Ip, parseConfig, '', type, publicUrl);
} else {
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User is not authenticated.');
}
} catch (err) {
@@ -15,7 +15,7 @@ export default async function editContact(request) {
const query = new Parse.Query('contracts_Contactbook');
query.equalTo('CreatedBy', createdBy);
query.notEqualTo('IsDeleted', true);
query.equalTo('Email', email);
query.equalTo('Email', email?.toLowerCase()?.replace(/\s/g, ''));
const isContactExist = await query.first({ useMasterKey: true });
if (isContactExist) {
throw new Parse.Error(Parse.Error.DUPLICATE_VALUE, 'Contact already exists.');
@@ -25,7 +25,7 @@ export default async function editContact(request) {
if (phone) {
contactQuery.set('Phone', phone);
}
contactQuery.set('Email', email);
contactQuery.set('Email', email?.toLowerCase()?.replace(/\s/g, ''));
contactQuery.set('UserRole', 'contracts_Guest');
contactQuery.set('IsDeleted', false);
contactQuery.set('TenantId', {
@@ -37,9 +37,9 @@ export default async function editContact(request) {
const _users = Parse.Object.extend('User');
const _user = new _users();
_user.set('name', name);
_user.set('username', email);
_user.set('email', email);
_user.set('password', email);
_user.set('username', email?.toLowerCase()?.replace(/\s/g, ''));
_user.set('email', email?.toLowerCase()?.replace(/\s/g, ''));
_user.set('password', email?.toLowerCase()?.replace(/\s/g, ''));
if (phone) {
_user.set('phone', phone);
}
@@ -1,14 +0,0 @@
import { PostHog } from 'posthog-node';
const ph_project_api_key = process.env.PH_PROJECT_API_KEY;
const client = ph_project_api_key ? new PostHog(ph_project_api_key) : '';
export default async function encryptedpdf(request) {
const email = request.params.email;
if (client) {
client?.capture({
distinctId: email,
event: 'encrypted_pdf_error',
properties: { response_code: 200 },
});
}
return { message: 'success' };
}
@@ -8,25 +8,22 @@ import dotenv from 'dotenv';
import GenerateCertificate from './pdf/GenerateCertificate.js';
import { getSecureUrl } from '../../Utils.js';
dotenv.config();
const eSignName = 'opensign';
const eSignName = 'OpenSign';
const eSigncontact = 'hello@opensignlabs.com';
// `uploadFile` is used to create url in from pdfFile
async function uploadFile(
pdfName,
filepath,
) {
async function uploadFile(pdfName, filepath) {
try {
const filedata = fs.readFileSync(filepath);
let fileUrl;
const file = new Parse.File(pdfName, [...filedata], 'application/pdf');
await file.save({ useMasterKey: true });
const fileRes = getSecureUrl(file.url());
fileUrl = fileRes.url;
const file = new Parse.File(pdfName, [...filedata], 'application/pdf');
await file.save({ useMasterKey: true });
const fileRes = getSecureUrl(file.url());
fileUrl = fileRes.url;
return { imageUrl: fileUrl };
} catch (err) {
console.log('Err ', err);
// `unlinkCertificate` is used to remove exported signed pdf file from exports folder
// `unlinkCertificate` is used to remove exported certificate file from exports folder
unlinkCertificate(filepath);
}
}
@@ -71,7 +68,7 @@ export default async function generateCertificatebydocId(req) {
// `pdflibAddPlaceholder` is used to add code of only digitial sign in certificate
pdflibAddPlaceholder({
pdfDoc: certificatePdf,
reason: 'Digitally signed by OpenSign.',
reason: `Digitally signed by ${eSignName}.`,
location: 'n/a',
name: eSignName,
contactInfo: eSigncontact,
@@ -86,10 +83,7 @@ export default async function generateCertificatebydocId(req) {
//below is used to save signed certificate in exports folder
fs.writeFileSync(certificatePath, signedCertificate);
const file = await uploadFile(
'certificate.pdf',
certificatePath,
);
const file = await uploadFile('certificate.pdf', certificatePath);
const updateDoc = new Parse.Object('contracts_Document');
updateDoc.id = doc.objectId;
updateDoc.set('CertificateUrl', file.imageUrl);
@@ -1,7 +1,5 @@
import axios from 'axios';
import {
cloudServerUrl,
} from '../../Utils.js';
import { cloudServerUrl } from '../../Utils.js';
export default async function getDocument(request) {
const serverUrl = cloudServerUrl; //process.env.SERVER_URL;
const docId = request.params.docId;
@@ -47,13 +45,12 @@ export default async function getDocument(request) {
console.log('err user in not authenticated', err);
return { error: "You don't have access of this document!" };
}
}
else {
} else {
return { error: "You don't have access of this document!" };
}
}
} else {
return { error: "You don't have access of this document!" };
return { error: "document deleted or you don't have access." };
}
} catch (err) {
console.log('err', err);

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