Compare commits

...
Author SHA1 Message Date
prafull-opensignlabs e857724502 Merge pull request #1137 from nxglabs/signers_completed_main
fix: not able update public username and save sign, initials not working
2025-08-14 06:53:43 +00:00
prafull-opensignlabsandGitHub 086d8ee5be Merge pull request #1889 from OpenSignLabs/updates-16770567266
fix: managesign report alignment and loader issue
2025-08-06 13:06:40 +05:30
prafull-opensignlabs 04543e826a Merge pull request #1105 from nxglabs/raktima-main-patch-2
fix: managesign report alignment and loader issue
2025-08-06 07:35:09 +00:00
prafull-opensignlabsandGitHub 04f6e38778 Merge pull request #1881 from OpenSignLabs/updates-16679049560
refactor: remove form-name from form title
2025-08-01 21:03:59 +05:30
prafull-opensignlabs 1b5c85fd5f Merge pull request #1085 from nxglabs/signers_completed_main
refactor: remove form-name from form title
2025-08-01 15:33:06 +00:00
prafull-opensignlabsandGitHub 123425d7d1 Merge pull request #1880 from OpenSignLabs/updates-16678047946
feat: allow preferences menu for all user
2025-08-01 20:18:07 +05:30
prafull-opensignlabs a5b9d8178c Merge pull request #1084 from nxglabs/signers_completed_main
feat: allow preferences menu for all user and add smtp setting in it
2025-08-01 14:46:44 +00:00
prafull-opensignlabsandGitHub aefd2ff933 Merge pull request #1868 from OpenSignLabs/updates-16625254839
refactor: correct grammar and normalise font size of signer email in report
2025-07-30 19:54:36 +05:30
nxglabs fe93be8be7 Merge pull request #1070 from nxglabs/signers_completed_main 2025-07-30 14:22:07 +00:00
prafull-opensignlabsandGitHub 369e3660fc Merge pull request #1865 from OpenSignLabs/updates-16598173522
fix: add dynamic truncate for email in list of signers
2025-07-29 19:29:57 +05:30
nxglabs 378c0f057b Merge pull request #1066 from nxglabs/signers_completed_main 2025-07-29 13:54:44 +00:00
prafull-opensignlabsandGitHub 4f3c034cec Merge pull request #1864 from OpenSignLabs/updates-16597572999
Fix: show signers list in completed documents
2025-07-29 19:04:08 +05:30
nxglabs e7d3453744 Merge pull request #1064 from nxglabs/signers_completed_main 2025-07-29 13:30:45 +00:00
prafull-opensignlabsandGitHub 47cc0ef580 Merge pull request #1858 from OpenSignLabs/updates-16525889391
remove checkbox from sign agreement screen
2025-07-25 21:18:33 +05:30
prafull-opensignlabs 04f71be6a2 Merge pull request #1044 from nxglabs/raktima-patch-main-2
fix: remove agreement checkbox and remove agreement checkbox tour
2025-07-25 15:38:38 +00:00
prafull-opensignlabsandGitHub 07603f4bc2 Merge pull request #1856 from OpenSignLabs/imgbot
[ImgBot] Optimize images
2025-07-24 14:40:19 +05:30
ImgBotApp ea16180551 [ImgBot] Optimize images
*Total -- 487.98kb -> 238.96kb (51.03%)

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

Signed-off-by: ImgBotApp <ImgBotHelp@gmail.com>
2025-07-24 08:28:02 +00:00
prafull-opensignlabsandGitHub 75ad566d54 Merge pull request #1841 from OpenSignLabs/updates-16144605887
v2.26.1
2025-07-08 19:01:19 +05:30
nxglabs fda9fb534b Merge pull request #963 from nxglabs/staging
v2.26.1
2025-07-08 13:27:37 +00:00
prafull-opensignlabsandGitHub cb3e31ebbf Merge pull request #1838 from OpenSignLabs/updates-16124490027
dark-theme logo
2025-07-07 23:43:31 +05:30
nxglabs 400525debb Merge pull request #960 from nxglabs/fix_customlogo
fix: custom applogo not visible in header
2025-07-07 18:07:24 +00:00
prafull-opensignlabsandGitHub 1cc017bb05 Merge pull request #1829 from OpenSignLabs/updates-16070658635
fix: quicksend screen get black out while sending document
2025-07-04 15:06:16 +05:30
prafull-opensignlabs 531dd11185 Merge pull request #942 from nxglabs/staging
fix: quicksend screen get black out while sending document
2025-07-04 09:32:50 +00:00
prafull-opensignlabsandGitHub 1e80754750 Merge pull request #1827 from OpenSignLabs/updates-16068582405
revert: css of drive
2025-07-04 13:14:27 +05:30
prafull-opensignlabs 0d286938c7 Merge pull request #940 from nxglabs/staging
revert: css of drive
2025-07-04 07:43:32 +00:00
prafull-opensignlabsandGitHub 360c13df25 Merge pull request #1825 from OpenSignLabs/updates-16055306436
v2.26.0
2025-07-04 10:35:33 +05:30
nxglabs 71b9bd6367 Merge pull request #937 from nxglabs/tenant_mailtemplate
fix: if user not provide default value use it from template in createdocument from template API
2025-07-03 16:04:35 +00:00
prafull-opensignlabsandGitHub 4a5b35e147 Merge pull request #1818 from OpenSignLabs/updates-15966812948
fix: handle undefine appId
2025-06-30 13:09:02 +05:30
prafull-opensignlabs 7930a8f439 Merge pull request #917 from nxglabs/handle_appid
fix: handle undefine appId
2025-06-30 07:37:32 +00:00
prafull-opensignlabsandGitHub 7204183542 Merge pull request #1804 from OpenSignLabs/updates-15754544632
fix: resolve issues
2025-06-19 15:03:08 +05:30
nxglabs 5561673fdc Merge pull request #866 from nxglabs/fix_main_issues 2025-06-19 09:31:17 +00:00
prafull-opensignlabsandGitHub 0df44062d6 Merge pull request #1803 from OpenSignLabs/updates-15736278136
fix: correct baseapi of docxtopdf and decryptpdf
2025-06-18 20:25:53 +05:30
nxglabs 8983440763 Merge pull request #864 from nxglabs/staging 2025-06-18 14:54:13 +00:00
prafull-opensignlabsandGitHub 5a28c0ed06 Merge pull request #1802 from OpenSignLabs/updates-15735030679
v2.25.0
2025-06-18 19:43:43 +05:30
nxglabs cbca22c308 Merge pull request #861 from nxglabs/staging
fix: handle password protected pdf
2025-06-18 14:03:05 +00:00
prafull-opensignlabsandGitHub b0e0b38e8f Merge pull request #1789 from OpenSignLabs/updates-15483575034
v2.24.1
2025-06-06 11:17:12 +05:30
prafull-opensignlabs 49c23fb52b Merge pull request #753 from OpenSignLabs/staging
v2.24.1
2025-06-06 05:33:37 +00:00
prafull-opensignlabsandGitHub da4b604710 Merge pull request #1785 from OpenSignLabs/updates-15425322075
handle crashing pattern for text input widgets
2025-06-04 00:17:30 +05:30
prafull-opensignlabs 82d518b3c9 Merge pull request #739 from OpenSignLabs/staging
disable signing request email when send_email flag is false
2025-06-03 18:45:49 +00:00
prafull-opensignlabsandGitHub dcbde2b661 Merge pull request #1784 from OpenSignLabs/updates-15419310132
v2.23.1
2025-06-03 19:28:35 +05:30
prafull-opensignlabs 27b0b426ad v2.23.1
Enable widget placement on click in desktop view
Disable signature types not working
Change icon of agreement sign checkbox
Update dependencies
2025-06-03 13:55:57 +00:00
prafull-opensignlabsandGitHub 8788afaa8e Merge pull request #1766 from OpenSignLabs/updates-15350472621
fix: add verify-document route
2025-05-30 21:19:35 +05:30
prafull-opensignlabs 428aa65b38 Merge pull request #710 from OpenSignLabs/staging
fix: add verify-document route
2025-05-30 15:45:27 +00:00
AmolandGitHub 295d942427 feat: add deploy to digitalocean button 2025-05-30 20:33:43 +05:30
prafull-opensignlabsandGitHub c4e28e6e6e Merge pull request #1764 from OpenSignLabs/updates-15349515719
v2.23.0
2025-05-30 20:26:41 +05:30
prafull-opensignlabs b19fb29b82 Merge pull request #708 from OpenSignLabs/staging
refactor: unnecessary code
2025-05-30 14:54:53 +00:00
prafull-opensignlabsandGitHub 3d223ba343 Merge pull request #1756 from OpenSignLabs/updates-15275255429
v2.22.0
2025-05-27 18:13:12 +05:30
prafull-opensignlabs 98754c776f Merge pull request #675 from OpenSignLabs/staging
v2.22.0
2025-05-27 12:28:40 +00:00
prafull-opensignlabsandGitHub 1550e3a3d0 Merge pull request #1748 from OpenSignLabs/updates-15156338467
v2.21.2
2025-05-21 13:09:14 +05:30
prafull-opensignlabs 3500883205 v2.21.2
fix: handle subscription error in publicsign
2025-05-21 07:37:15 +00:00
prafull-opensignlabsandGitHub d888897b62 Update README.md 2025-05-20 20:10:44 +05:30
prafull-opensignlabsandGitHub 8f2433fbea Merge pull request #1737 from OpenSignLabs/updates-15043583319
v2.21.1
2025-05-15 16:48:53 +05:30
prafull-opensignlabs b671dbac19 v2.21.1
fix: handle undefined placeholders condition
2025-05-15 11:17:30 +00:00
prafull-opensignlabsandGitHub 79cdbc9c8c Merge pull request #1735 from OpenSignLabs/updates-15024699698
fix: resolve error cannot read undefined props
2025-05-14 20:55:08 +05:30
prafull-opensignlabs e585b7c943 fix: resolve error cannot read undefined props 2025-05-14 15:24:27 +00:00
prafull-opensignlabsandGitHub 5336e2cb7c Merge pull request #1734 from OpenSignLabs/prafull-opensignlabs-patch-7
refactor: remove unnecessary file
2025-05-14 20:26:36 +05:30
prafull-opensignlabsandGitHub 72401291f8 Delete apps/OpenSign/vite.public-template.config.js 2025-05-14 20:24:54 +05:30
prafull-opensignlabsandGitHub 7b34314b9e Merge pull request #1733 from OpenSignLabs/updates-15023728155
fix: correct build commands for selfhosting
2025-05-14 20:13:14 +05:30
prafull-opensignlabs b6f1cceeca Merge pull request #612 from OpenSignLabs/staging
fix: correct build commands for selfhosting
2025-05-14 14:42:30 +00:00
prafull-opensignlabsandGitHub c422cdac06 Merge pull request #1732 from OpenSignLabs/updates-15023371589
fix: edit template option not visible in mobile view
2025-05-14 20:00:02 +05:30
prafull-opensignlabsandGitHub ab08d2615d Update package.json 2025-05-14 19:59:01 +05:30
prafull-opensignlabs 2b0c7488d9 Merge pull request #610 from OpenSignLabs/staging
fix: edit template option not visible in mobile view
2025-05-14 14:27:57 +00:00
prafull-opensignlabsandGitHub 6927540741 Merge pull request #1731 from OpenSignLabs/updates-15022725718
v2.21.0
2025-05-14 19:33:19 +05:30
prafull-opensignlabs 1ceeee36ac v2.21.0
Merge pull request #601 from OpenSignLabs/staging
2025-05-14 14:00:50 +00:00
prafull-opensignlabsandGitHub c4779c14c1 feat: add all three docker command 2025-05-14 19:11:18 +05:30
prafull-opensignlabsandGitHub e48869cb2f feat: add deploy to digitalocean button 2025-05-14 18:36:47 +05:30
prafull-opensignlabsandGitHub 81d3969d1f Merge pull request #1719 from OpenSignLabs/updates-14856876951
v2.20.1
2025-05-06 15:35:42 +05:30
prafull-opensignlabs 5819103f57 v2.20.1 2025-05-06 10:01:20 +00:00
185 changed files with 24847 additions and 28132 deletions
+17 -1
View File
@@ -73,12 +73,28 @@ Welcome to OpenSign, the premier open source docusign alternative - document e-s
---
### Installation
### Deploy
Note: The default MongoDB instance used in deployment is not persistant and will be cleared on every restart. To retain your data, configure and supply your own MongoDB connection URL.
#### DigitalOcean
[![Deploy on DigitalOcean](https://www.deploytodo.com/do-btn-blue.svg)](https://cloud.digitalocean.com/apps/new?repo=https://github.com/OpenSignLabs/Deploy-OpenSign-to-Digital-Ocean/tree/main&refcode=30db1c901ab0)
#### Docker
The simplest way to install OpenSign on your own server is using official docker images by running the following command -
**Command for linux/MacOS**
```
export HOST_URL=https://opensign.yourdomain.com && curl --remote-name-all https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/docker-compose.yml https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/Caddyfile https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/.env.local_dev && mv .env.local_dev .env.prod && docker compose up --force-recreate
```
**Command for Windows (Powershell)**
```
$env:HOST_URL="https://opensign.yourdomain.com"; Invoke-WebRequest -Uri https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/docker-compose.yml -OutFile docker-compose.yml; Invoke-WebRequest -Uri https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/Caddyfile -OutFile Caddyfile; Invoke-WebRequest -Uri https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/.env.local_dev -OutFile .env.local_dev; Rename-Item -Path .env.local_dev -NewName .env.prod; docker compose up --force-recreate
```
**Command for Windows (CMD/Terminal)**
```
set HOST_URL=https://opensign.yourdomain.com && curl -O https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/docker-compose.yml && curl -O https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/Caddyfile && curl -O https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/.env.local_dev && rename .env.local_dev .env.prod && docker compose up --force-recreate
```
Make sure that you have `Docker` and `git` installed before you run this command -
Please refer to the [Installation Guide](https://docs.opensignlabs.com/docs/self-host/docker/run-locally/) for detailed instructions on how to install OpenSign on your system.
-25
View File
@@ -1,25 +0,0 @@
# Use an official Node runtime as the base image
FROM node:18
# Set the working directory inside the container
WORKDIR /usr/src/app
# Copy package.json and package-lock.json first to leverage Docker cache
COPY ./package*.json ./
# Install application dependencies
RUN npm install
# Copy the current directory contents into the container
COPY ./ .
COPY ./.husky .
# Make port 3000 available to the world outside this container
EXPOSE 3000
# Define environment variables if needed
# ENV NODE_ENV production
# Run the application
ENTRYPOINT npm run start
+9
View File
@@ -13,6 +13,10 @@ RUN npm install
# Copy the current directory contents into the container
COPY apps/OpenSign/ .
COPY apps/OpenSign/.husky .
COPY apps/OpenSign/entrypoint.sh .
# make the entrypoint.sh file executable
RUN chmod +x entrypoint.sh
# Define environment variables if needed
ENV NODE_ENV=production
@@ -20,8 +24,13 @@ ENV GENERATE_SOURCEMAP=false
# build
RUN npm run build
# Inject env.js loader into index.html
RUN sed -i '/<head>/a\<script src="/env.js"></script>' build/index.html
# Make port 3000 available to the world outside this container
EXPOSE 3000
ENTRYPOINT ["./entrypoint.sh"]
# Run the application
CMD ["npm", "start"]
+213
View File
@@ -0,0 +1,213 @@
# Tailwind Dark Mode Usage Guide
## Overview
This guide shows how to use the new Tailwind utilities for better dark mode visibility in OpenSign.
## Button Styling
### VS Code-style Disabled Buttons
```jsx
// Option 1: Direct VS Code styling
<button className="op-btn op-btn-primary op-btn-vscode-disabled" disabled>
Disabled Button
</button>
// Option 2: Themed disabled styling
<button className="op-btn btn-themed-disabled">
Themed Button
</button>
// Option 3: Conditional styling
<button
className={`op-btn op-btn-primary ${isDisabled ? 'op-btn-vscode-disabled' : ''}`}
disabled={isDisabled}
>
Dynamic Button
</button>
```
## Icon Styling
### Theme-aware Icons
```jsx
// Better visibility in dark mode
<i className="fa-light fa-folder icon-improved"></i>
// Muted but still visible
<i className="fa-light fa-plus icon-muted"></i>
// Disabled state
<i className="fa-light fa-trash icon-disabled"></i>
```
### CSS Variable Approach
```jsx
// Using CSS variables
<i className="fa-light fa-search icon-themed"></i>
<i className="fa-light fa-settings icon-themed-muted"></i>
// Inline styles with CSS variables
<i
className="fa-light fa-plus"
style={{ color: 'var(--icon-color)' }}
/>
```
### Legacy JavaScript Function (Still Supported)
```jsx
// Existing approach - still works
<i
className="fa-light fa-plus"
style={{ color: getThemeIconColor() }}
/>
```
## Text Styling
### Improved Gray Text
```jsx
// These automatically improve in dark mode
<span className="text-gray-500">More visible in dark mode</span>
<span className="text-gray-400">Muted but readable</span>
<span className="text-gray-600">Clear text</span>
```
## Complete Examples
### Toolbar with Better Visibility
```jsx
const Toolbar = () => (
<div className="flex space-x-2 p-2">
<button className="p-2 hover:bg-gray-200 rounded">
<i className="fa-light fa-plus icon-improved"></i>
</button>
<button className="p-2 hover:bg-gray-200 rounded" disabled>
<i className="fa-light fa-trash icon-disabled"></i>
</button>
<button className="p-2 hover:bg-gray-200 rounded">
<i className="fa-light fa-edit icon-improved"></i>
</button>
</div>
);
```
### Form with Disabled States
```jsx
const Form = ({ isSubmitting }) => (
<form>
<input className="op-input" />
<button
className={`op-btn op-btn-primary ${isSubmitting ? 'op-btn-vscode-disabled' : ''}`}
disabled={isSubmitting}
>
{isSubmitting ? 'Submitting...' : 'Submit'}
</button>
</form>
);
```
## React-Tour and Tooltip Dark Mode Support
### React-Tour Modals
The react-tour modals now automatically support dark mode with VS Code-inspired styling:
```jsx
// These components automatically get dark mode styling
<Tour
onRequestClose={closeTour}
steps={tourConfig}
isOpen={isOpen}
rounded={5}
/>
```
### ReactTooltip Components
All ReactTooltip instances now support dark mode:
```jsx
// Automatically styled for dark mode
<ReactTooltip id="my-tooltip" className="z-[999]">
<div className="max-w-[200px]">
<p>Tooltip content</p>
</div>
</ReactTooltip>
```
### HoverCard Balloon UI
The balloon tooltips in OpenSign Drive now properly support dark mode:
```jsx
// These automatically get dark styling in dark mode
<HoverCard>
<HoverCardContent>
Document information
</HoverCardContent>
</HoverCard>
```
## Dark Mode Features Added
### 1. **React-Tour Modal Styling**
- Background: `#1F2937` (VS Code modal background)
- Text: `#E5E7EB` (soft white for readability)
- Borders: `#374151` (subtle borders)
- Buttons: VS Code-style primary/secondary buttons
### 2. **ReactTooltip Styling**
- Background: `#1F2937` with proper contrast
- Border: `#374151` for definition
- Box shadow: Enhanced for dark backgrounds
- Text: `#E5E7EB` for optimal readability
### 3. **HoverCard Balloon UI**
- Background: `#1F2937` (matches VS Code)
- Text: `#E5E7EB` for readability
- Arrow: Automatically matches background color
- Enhanced shadows for dark backgrounds
### 4. **React-Datepicker Support**
- Calendar background: `#1F2937`
- Selected dates: VS Code blue (`#007ACC`)
- Hover states: Proper contrast ratios
- Navigation arrows: Themed appropriately
## CSS Classes Reference
| Class | Purpose | Dark Mode Color |
|-------|---------|----------------|
| `icon-improved` | Better icon visibility | `#CCCCCC` |
| `icon-muted` | Muted but visible icons | `#999999` |
| `icon-disabled` | Disabled icon state | `#858585` |
| `op-btn-vscode-disabled` | VS Code disabled button | Background: `#3C3C3C` |
| `btn-themed-disabled` | Themed disabled button | Uses CSS variables |
| `icon-themed` | Variable-based icon color | `var(--icon-color)` |
| `.reactour__helper` | `#1F2937` background |
| `.react-tooltip` | `#1F2937` background |
| `.HoverCardContent` | `#1F2937` background |
| `.react-datepicker` | `#1F2937` background |
## Migration Guide
### From JavaScript Function to Tailwind
```jsx
// Before
<i style={{ color: getThemeIconColor() }} className="fa-light fa-plus" />
// After
<i className="fa-light fa-plus icon-improved" />
```
### From Hardcoded Colors to Theme-aware
```jsx
// Before
<i className="fa-light fa-plus text-gray-500" />
// After (automatic improvement)
<i className="fa-light fa-plus text-gray-500" />
// OR explicitly
<i className="fa-light fa-plus icon-improved" />
```
## Migration Notes
All existing tooltip and tour components will automatically inherit the new dark mode styling when the theme is set to `opensigndark`. No code changes required for existing implementations.
+29
View File
@@ -0,0 +1,29 @@
#!/bin/sh
ENV_FILE=./build/env.js
DOTENV_FILE=./.env.prod # ✅ use .env.prod
echo "Generating runtime env file at $ENV_FILE..."
echo "window.RUNTIME_ENV = {" > $ENV_FILE
# List of keys to include
RUNTIME_KEYS="REACT_APP_SERVERURL"
for key in $RUNTIME_KEYS; do
# First check docker env (-e), fallback to .env file
value=$(printenv "$key")
if [ -z "$value" ] && [ -f "$DOTENV_FILE" ]; then
# fallback: read from .env
value=$(grep "^$key=" "$DOTENV_FILE" | cut -d '=' -f2- | tr -d '\r\n' | sed 's/"/\\"/g')
else
value=$(echo "$value" | sed 's/"/\\"/g')
fi
echo " $key: \"$value\"," >> $ENV_FILE
done
echo "};" >> $ENV_FILE
exec "$@"
@@ -0,0 +1,92 @@
/**
* Tailwind Dark Mode Usage Examples for OpenSign
*
* This file demonstrates how to use the new Tailwind utilities
* for better dark mode visibility of buttons and icons.
*/
// Example 1: VS Code-style disabled buttons
const DisabledButtonExamples = () => {
return (
<div>
{/* Option A: Using the VS Code disabled style */}
<button className="op-btn op-btn-primary op-btn-vscode-disabled" disabled>
VS Code Style Disabled Button
</button>
{/* Option B: Using themed disabled style */}
<button className="op-btn btn-themed-disabled">
Themed Disabled Button
</button>
{/* Option C: Conditional styling */}
<button
className={`op-btn op-btn-primary ${
isDisabled ? "op-btn-vscode-disabled" : ""
}`}
disabled={isDisabled}
>
Conditional Button
</button>
</div>
);
};
// Example 2: Icon visibility improvements
const IconExamples = () => {
return (
<div>
{/* Theme-aware icons with better visibility */}
<i className="fa-light fa-folder icon-improved"></i>
<i className="fa-light fa-plus icon-muted"></i>
<i className="fa-light fa-trash icon-disabled"></i>
{/* Using CSS variables */}
<i className="fa-light fa-search icon-themed"></i>
<i className="fa-light fa-settings icon-themed-muted"></i>
{/* Gray text that automatically improves in dark mode */}
<span className="text-gray-500">
This text is now more visible in dark mode
</span>
<span className="text-gray-400">Muted but still readable</span>
</div>
);
};
// Example 3: Using CSS variables in inline styles
const InlineStyleExamples = () => {
return (
<div>
{/* Using CSS variables directly */}
<i className="fa-light fa-plus" style={{ color: "var(--icon-color)" }} />
{/* Using the existing JavaScript function */}
<i className="fa-light fa-minus" style={{ color: getThemeIconColor() }} />
</div>
);
};
// Example 4: Toolbar with improved icons
const ToolbarExample = () => {
return (
<div className="flex space-x-2 p-2">
<button className="p-2 hover:bg-gray-200 rounded">
<i className="fa-light fa-plus icon-improved"></i>
</button>
<button className="p-2 hover:bg-gray-200 rounded" disabled>
<i className="fa-light fa-trash icon-disabled"></i>
</button>
<button className="p-2 hover:bg-gray-200 rounded">
<i className="fa-light fa-edit icon-improved"></i>
</button>
</div>
);
};
export {
DisabledButtonExamples,
IconExamples,
InlineStyleExamples,
ToolbarExample
};
@@ -0,0 +1,94 @@
/**
* Tailwind Dark Mode Usage Examples for OpenSign
*
* This file demonstrates how to use the new Tailwind utilities
* for better dark mode visibility of buttons and icons.
*/
// Example 1: VS Code-style disabled buttons
const DisabledButtonExamples = () => {
return (
<div>
{/* Option A: Using the VS Code disabled style */}
<button className="op-btn op-btn-primary op-btn-vscode-disabled" disabled>
VS Code Style Disabled Button
</button>
{/* Option B: Using themed disabled style */}
<button className="op-btn btn-themed-disabled">
Themed Disabled Button
</button>
{/* Option C: Conditional styling */}
<button
className={`op-btn op-btn-primary ${isDisabled ? 'op-btn-vscode-disabled' : ''}`}
disabled={isDisabled}
>
Conditional Button
</button>
</div>
);
};
// Example 2: Icon visibility improvements
const IconExamples = () => {
return (
<div>
{/* Theme-aware icons with better visibility */}
<i className="fa-light fa-folder icon-improved"></i>
<i className="fa-light fa-plus icon-muted"></i>
<i className="fa-light fa-trash icon-disabled"></i>
{/* Using CSS variables */}
<i className="fa-light fa-search icon-themed"></i>
<i className="fa-light fa-settings icon-themed-muted"></i>
{/* Gray text that automatically improves in dark mode */}
<span className="text-gray-500">This text is now more visible in dark mode</span>
<span className="text-gray-400">Muted but still readable</span>
</div>
);
};
// Example 3: Using CSS variables in inline styles
const InlineStyleExamples = () => {
return (
<div>
{/* Using CSS variables directly */}
<i
className="fa-light fa-plus"
style={{ color: 'var(--icon-color)' }}
/>
{/* Using the existing JavaScript function */}
<i
className="fa-light fa-minus"
style={{ color: getThemeIconColor() }}
/>
</div>
);
};
// Example 4: Toolbar with improved icons
const ToolbarExample = () => {
return (
<div className="flex space-x-2 p-2">
<button className="p-2 hover:bg-gray-200 rounded">
<i className="fa-light fa-plus icon-improved"></i>
</button>
<button className="p-2 hover:bg-gray-200 rounded" disabled>
<i className="fa-light fa-trash icon-disabled"></i>
</button>
<button className="p-2 hover:bg-gray-200 rounded">
<i className="fa-light fa-edit icon-improved"></i>
</button>
</div>
);
};
export {
DisabledButtonExamples,
IconExamples,
InlineStyleExamples,
ToolbarExample
};
@@ -3,11 +3,11 @@
<head>
<meta charset="utf-8" />
<link rel="icon" href="/favicon.ico" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="The fastest way to sign PDFs & request signatures from others" />
<!-- <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> -->
<!-- <link rel="apple-touch-icon" href="/logo192.png" /> -->
<link rel="manifest" href="/manifest.json" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.0.0/dist/css/bootstrap.min.css"
@@ -17,5 +17,6 @@
</head>
<body>
<div id="root" style="touch-action:pan-x pan-y;"></div>
<script type="module" src="/src/index.jsx"></script>
</body>
</html>
</html>
+7502 -17065
View File
File diff suppressed because it is too large Load Diff
+54 -51
View File
@@ -1,74 +1,72 @@
{
"name": "open_sign",
"version": "0.1.0",
"version": "2.21.1",
"private": true,
"dependencies": {
"@formkit/auto-animate": "^0.8.2",
"@lottiefiles/dotlottie-react": "^0.13.4",
"@imgly/background-removal": "^1.6.0",
"@lottiefiles/dotlottie-react": "^0.14.2",
"@pdf-lib/fontkit": "^1.1.1",
"@radix-ui/themes": "^3.1.6",
"@reduxjs/toolkit": "^2.7.0",
"axios": "^1.9.0",
"css-minimizer-webpack-plugin": "^7.0.2",
"@radix-ui/themes": "^3.2.1",
"@reduxjs/toolkit": "^2.8.2",
"axios": "^1.10.0",
"date-fns-tz": "^3.2.0",
"file-saver": "^2.0.5",
"i18next": "^23.16.8",
"i18next-browser-languagedetector": "^8.0.5",
"i18next": "^25.3.0",
"i18next-browser-languagedetector": "^8.2.0",
"i18next-http-backend": "^3.0.2",
"jszip": "^3.10.1",
"jwt-decode": "^4.0.0",
"moment": "^2.30.1",
"nth-check": "^2.1.1",
"parse": "^6.1.1",
"pdf-lib": "^1.17.1",
"pkijs": "^3.0.8",
"print-js": "^1.6.0",
"radix-ui": "^1.0.1",
"react": "^18.2.0",
"react-bootstrap": "^2.10.9",
"prismjs": "^1.30.0",
"quill-html-edit-button": "^3.0.0",
"radix-ui": "^1.4.2",
"react": "^18.3.1",
"react-bootstrap": "^2.10.10",
"react-confetti": "^6.4.0",
"react-cookie": "^7.2.2",
"react-datepicker": "^7.6.0",
"react-datepicker": "^8.4.0",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
"react-dnd-multi-backend": "^9.0.0",
"react-dnd-touch-backend": "^16.0.1",
"react-dom": "^18.2.0",
"react-dom": "^18.3.1",
"react-gtm-module": "^2.0.11",
"react-helmet": "^6.1.0",
"react-i18next": "^15.5.0",
"react-i18next": "^15.5.3",
"react-konva": "^18.2.10",
"react-pdf": "^9.2.1",
"react-quill-new": "^3.4.6",
"react-redux": "^9.2.0",
"react-rnd": "^10.5.2",
"react-router": "^7.5.2",
"react-scripts": "^5.0.1",
"react-router": "^7.6.3",
"react-scrollbars-custom": "^4.1.1",
"react-select": "^5.10.1",
"react-signature-canvas": "^1.0.7",
"react-syntax-highlighter": "^15.6.1",
"react-signature-canvas": "^1.1.0-alpha.2",
"react-timezone-select": "^3.2.8",
"react-tooltip": "^5.28.1",
"react-web-share": "^2.0.2",
"react-tooltip": "^5.29.1",
"reactour": "^1.19.4",
"redux": "^5.0.1",
"redux-thunk": "^3.1.0",
"regex-parser": "^2.3.1",
"serve": "^14.2.4",
"styled-components": "^5.3.0",
"web-vitals": "^4.2.4",
"ws": "^8.18.1",
"styled-components": "^5.3.11",
"web-vitals": "^5.0.3",
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz"
},
"scripts": {
"build": "npm run version && react-scripts build",
"start-dev": "react-scripts start",
"build": "npm run version && NODE_OPTIONS=\"--max-old-space-size=8192\" vite build",
"start-dev": "vite",
"dev": "vite",
"preview": "vite preview",
"start": "serve -s build",
"version": "curl -s https://api.github.com/repos/opensignlabs/opensign/releases/latest | grep '\"tag_name\":' | awk -F '\"' '{print $4}' > ./public/version.txt",
"version-win": "powershell -Command \"Invoke-RestMethod -Uri 'https://api.github.com/repos/opensignlabs/opensign/releases/latest' | Select-Object -ExpandProperty tag_name | Out-File -FilePath ./public/version.txt\"",
"build-win": "npm run version-win && react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"build-win": "npm run version-win && vite build",
"test": "vitest run",
"test:watch": "vitest",
"release": "standard-version",
"commit": "cz"
},
@@ -96,33 +94,38 @@
}
},
"devDependencies": {
"@babel/core": "^7.26.10",
"@babel/preset-env": "^7.26.9",
"@babel/preset-react": "^7.26.3",
"@babel/runtime-corejs2": "^7.27.0",
"@babel/core": "^7.27.7",
"@babel/preset-env": "^7.27.2",
"@babel/preset-react": "^7.27.1",
"@babel/runtime-corejs2": "^7.27.6",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^14.6.1",
"@types/react": "^18.3.23",
"@vitejs/plugin-react": "^4.6.0",
"@vitejs/plugin-react-swc": "^3.10.2",
"@vitest/ui": "^3.2.4",
"autoprefixer": "^10.4.21",
"babel-loader": "^10.0.0",
"commitizen": "^4.3.1",
"concurrently": "^9.1.2",
"concurrently": "^9.2.0",
"css-loader": "^7.1.2",
"daisyui": "^4.12.24",
"dotenv": "^16.5.0",
"dotenv-webpack": "^8.1.0",
"eslint": "^8.57.1",
"eslint-plugin-prettier": "^5.2.6",
"dotenv": "^16.6.1",
"eslint": "^9.30.0",
"eslint-plugin-prettier": "^5.5.1",
"eslint-plugin-react": "^7.37.5",
"lint-staged": "^15.5.1",
"mini-css-extract-plugin": "^2.9.2",
"postcss": "^8.5.3",
"prettier": "^3.5.3",
"pretty-quick": "^4.1.1",
"jsdom": "^26.1.0",
"lint-staged": "^16.1.2",
"postcss": "^8.5.6",
"prettier": "^3.6.2",
"pretty-quick": "^4.2.2",
"rollup-plugin-node-polyfills": "^0.2.1",
"tailwindcss": "^3.4.17",
"terser-webpack-plugin": "^5.3.14",
"webpack-cli": "^5.1.4"
},
"overrides": {
"nth-check": "$nth-check",
"ws": "$ws"
"vite": "^6.3.5",
"vite-plugin-svgr": "^4.3.0",
"vite-tsconfig-paths": "^5.1.4",
"vitest": "^3.2.4"
},
"engines": {
"node": "18 || 20 || 22"
+182 -12
View File
@@ -1,9 +1,11 @@
{
"header-news": "Neue Funktion: Benutzer des Teams-Plans können jetzt ihre eigenen AWS S3-Buckets für die Dateispeicherung integrieren",
"header-news-btn": "Jetzt einrichten",
"sandbox-news": "Dies ist eine Sandbox-Umgebung. Bitte nicht für produktive Zwecke verwenden.",
"create-account": "Konto erstellen",
"login": "Anmelden",
"language": "Sprache",
"dark-mode": "Dunkelmodus",
"name": "Name",
"phone": "Telefon",
"phone-optional": "optional",
@@ -24,6 +26,8 @@
"Name": "Name",
"Date": "Datum"
},
"folder": "Ordner",
"pdf": "Pdf",
"context-menu": {
"Download": "Herunterladen",
"Rename": "Umbenennen",
@@ -39,8 +43,14 @@
"save": "Speichern",
"cancel": "Abbrechen",
"upgrade-now": "Jetzt upgraden",
"contact-now": "Jetzt kontaktieren",
"upgrade-to": "Upgrade zu",
"plan": "Plan",
"connect": "Verbinden",
"connect-to-g-drive": "Mit Google Drive verbinden",
"reconnect-to-g-drive": "Erneut mit Google Drive verbinden",
"gdrive-info-connect": "Wenn Google Drive verbunden ist, wird das abgeschlossene Dokument im Ordner {{appName}} auf Google Drive gespeichert.",
"subscription-renew-warning": "Ihr Abonnement läuft in {{remainingDays}} Tagen ab. Bitte verlängern Sie Ihr Abonnement.",
"subscribe-card-teamplan": "Entfesseln Sie die volle Kraft der Zusammenarbeit! Erstellen Sie unbegrenzt Organisationen, Teams und Hierarchien. Teilen Sie Vorlagen nahtlos zwischen Teams und weisen Sie benutzerdefinierte Benutzerrollen zu. Optimieren Sie Ihren Workflow noch heute!",
"subscribe-card-plan": "Entsperren Sie Premium-Funktionen ab nur {{premiumPrice}}/Monat. Genießen Sie eine verbesserte Leistung und zahlen Sie nur {{addonPrice}} pro zusätzlichem Credit nach den enthaltenen Premium-Credits.",
"user-name-limit-char": "Um einen Benutzernamen mit weniger als 8 Zeichen zu haben, abonnieren Sie bitte.",
@@ -54,7 +64,7 @@
"welcome": "Willkommen zurück!",
"Login-to-your-account": "Melden Sie sich bei Ihrem Konto an",
"password": "Passwort",
"forgot-password": "Passwort vergessen?",
"forgot-password": "Passwort vergessen",
"loading": "Wird geladen...",
"of": "von",
"sign-SSO": "Mit SSO anmelden",
@@ -142,6 +152,7 @@
"Quick send": "Schnell senden",
"Edit": "Bearbeiten",
"Share with team": "Mit Team teilen",
"Share with user": "Mit Kollegen teilen",
"Share": "Teilen",
"View": "Ansehen",
"option": "Option",
@@ -176,7 +187,9 @@
"created-date": "Erstellungsdatum",
"Type": "Type",
"Logs": "Protokolle",
"Expiry-date": "Ablaufdatum"
"Expiry-date": "Ablaufdatum",
"Company": "Unternehmen",
"JobTitle": "Berufsbezeichnung"
},
"report-help": {
"Draft Documents": "Dies sind Dokumente, die Sie begonnen, aber noch nicht zum Versenden fertiggestellt haben.",
@@ -188,16 +201,15 @@
"Contactbook": "Dies ist eine Liste von Kontakten/Unterzeichnern, die Sie hinzugefügt haben. Diese erscheinen als Vorschläge, wenn Sie neue Unterzeichner hinzufügen möchten.",
"Templates": "Dies ist eine Liste von Vorlagen, die Ihnen zur Verfügung stehen, um Dokumente zu erstellen. Sie können die Schaltfläche 'Verwenden' anklicken, um ein neues Dokument mit einer Vorlage zu erstellen, das Dokument zu ändern und Unterzeichner im nächsten Schritt hinzuzufügen."
},
"form-name": {
"Sign Yourself": "Selbst unterschreiben",
"Request Signatures": "Signaturen anfordern",
"New Template": "Neue Vorlage"
},
"Sign Yourself": "Selbst unterschreiben",
"Request Signatures": "Signaturen anfordern",
"New Template": "Neue Vorlage",
"file-type": "pdf, png, jpg, jpeg",
"docx": "docx",
"file-selected": "Datei ausgewählt",
"template-title": "Vorlagentitel",
"document-title": "Dokumenttitel",
"title": "Titel",
"description": "Beschreibung",
"time-to-complete": "Bearbeitungszeit (Tage)",
"send-in-order": "In der Reihenfolge senden",
@@ -245,6 +257,7 @@
"API": "API",
"api-token": "API-Token",
"regenerate-token": "Live-Token neu generieren",
"remove-background": "Hintergrund entfernen",
"generate-token": "Live-Token generieren",
"view-docs": "Dokumentation ansehen",
"generate-token-alert": "Sind Sie sicher, dass Sie das Token neu generieren möchten? Das alte Token wird ablaufen.",
@@ -356,6 +369,7 @@
"date": "Datum",
"text": "Text",
"text input": "Texteingabe",
"cells": "Zellen",
"checkbox": "Checkbox",
"dropdown": "Dropdown",
"radio button": "Radiobutton",
@@ -372,6 +386,7 @@
"certificate": "Zertifikat",
"decline": "Ablehnen",
"finish": "Fertigstellen",
"done": "Fertig",
"mail": "E-Mail",
"sign-now": "Jetzt unterzeichnen",
"successfully-signed": "Erfolgreich unterzeichnet!",
@@ -381,6 +396,10 @@
"Email-verified-alert-1": "E-Mail wurde verifiziert.",
"Email-verified-alert-2": "E-Mail wurde bereits verifiziert.",
"upload-stamp-image": "Stempelbild hochladen",
"draw-signature": "Unterschrift zeichnen",
"draw-initials": "Initialen zeichnen",
"enter-text": "Text eingeben",
"enter-widgettype": "{{widgetType}} eingeben",
"draw": "Zeichnen",
"type": "Schreiben",
"color-type": {
@@ -410,10 +429,12 @@
"options": "Optionen",
"minimun-check": "Minimale Anzahl",
"maximum-check": "Maximale Anzahl",
"cell-count": "Zellzahl",
"default-value": "Standardwert",
"select": "Auswählen",
"read-only": "Nur lesen",
"read-only": "Ist schreibgeschützt",
"hide-labels": "Labels ausblenden",
"layout": "Layout",
"checkbox": "Checkbox",
"alert": "Warnung",
"zoom-in": "Vergrößern",
@@ -478,6 +499,7 @@
"document-alert": "Dokument-Warnung",
"owner-subscription-expired": "Das Abonnement des Besitzers ist abgelaufen.",
"subscription-expired": "Abonnement abgelaufen",
"owner-doesnt-have-paid-plan": "Der Inhaber hat keinen kostenpflichtigen Plan.",
"alert-message": "Warnmeldung",
"document-decline": "Dokument ablehnen",
"decline-alert-1": "Sind Sie sicher, dass Sie dieses Dokument ablehnen möchten?",
@@ -670,7 +692,7 @@
"public-template-mssg-1": "Um OpenSign in Ihr React- oder Next.js-Projekt zu integrieren, führen Sie einfach den folgenden Befehl aus:",
"public-template-mssg-2": "Stellen Sie sicher, dass npm oder yarn in Ihrem Projekt eingerichtet ist. Wenn Sie Yarn verwenden, können Sie npm install durch yarn add @opensign/react ersetzen.",
"public-template-mssg-3": "Benötigen Sie weitere Details oder Beispiele?",
"public-template-mssg-4": "Besuchen Sie die",
"public-template-mssg-4": "Besuchen Sie die ",
"public-template-mssg-5": " npm für die neuesten Updates, detaillierte Dokumentationen und Versionshistorie.",
"public-template-mssg-6": "Bevor Sie diesen Code-Schnipsel verwenden können, müssen Sie diese Vorlage öffentlich machen.",
"public-template-mssg-7": "Bevor Sie einen öffentlichen Link generieren können, müssen Sie diese Vorlage öffentlich machen.",
@@ -718,7 +740,7 @@
"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": "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'.",
"text-field-tour": "Felder zum Vorabfüllen müssen vor dem Senden des Dokuments ausgefüllt werden. Wenn Sie Eingaben von den Unterzeichnern benötigen, verwenden Sie stattdessen die Felder für Unterzeichner.",
"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",
"at-least-one-signature-type": "Mindestens ein Signaturtyp sollte aktiviert sein.",
@@ -744,12 +766,15 @@
"delete-page": "Seite löschen",
"merge-pdf": "PDFs zusammenführen",
"add-pages": "Seiten hinzufügen",
"reorder-pages": "Seiten neu anordnen",
"delete-alert": "Einzelne Seite kann nicht gelöscht werden.",
"delete-alert-2": "Sind Sie sicher, dass Sie diese Seite löschen möchten?",
"delete-note": "Hinweis: Sobald Sie diese Seite löschen, kann dies nicht rückgängig gemacht werden.",
"Rotation-alert": "Seite drehen",
"bulk-import": "Massenimport",
"contacts-file": "Kontaktdatei (xlsx, csv)",
"import-guideline": "Laden Sie eine CSV- oder Excel-Datei mit den Spalten Name, Email und optional Phone hoch. Es werden nur die ersten 100 Kontakte importiert.",
"download-sample": "Beispieldatei herunterladen",
"100-records-only": "Derzeit können Sie nur bis zu 100 Datensätze importieren.",
"csv-excel-support-only": "Laden Sie eine Datei in einem der folgenden Formate hoch: CSV, XLSX oder XLS.",
"contact-imported": "{{imported}} Kontakte wurden importiert. {{failed}} Kontakte konnten nicht importiert werden.",
@@ -763,7 +788,7 @@
"agree-p1": "Ich bestätige, dass ich die ",
"agree-p2": "Offenlegung elektronischer Aufzeichnungen und Signaturen",
"agree-p3": "gelesen und verstanden habe und der Verwendung elektronischer Aufzeichnungen und Signaturen zustimme.",
"agrre-button": "Zustimmen & Fortfahren",
"agrre-button": "Ich bestätige und stimme zu, fortzufahren",
"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 {{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.",
@@ -971,5 +996,150 @@
"kiosk-sign": "Kiosk-Unterschrift",
"dont-have-access-to-template": "Das template wurde gelöscht oder Sie haben keinen Zugriff. Bitte kontaktieren Sie den Absender.",
"kiosk-info": "Kiosk Modus ermöglicht es Ihnen, persönliche Unterschriften schnell und effizient zu erfassen. Ideal für Messen, Veranstaltungen oder Laufkundschaft, bei denen alle Unterzeichner physisch anwesend sind. ",
"learn-more": "Mehr erfahren"
"learn-more": "Mehr erfahren",
"finish-mssg": "Sind Sie sicher, dass Sie das Dokument abschließen möchten?",
"review": "Überprüfen",
"next-field": "Nächstes Feld",
"required-mssg": "{{leftRequiredWidget}} von {{totalWidget}} Feldern übrig",
"verify-document": "Dokument verifizieren",
"verify-document-signature": "Dokumentensignatur überprüfen",
"select-pdf-document": "PDF-Dokument auswählen",
"selected-file": "Ausgewählte Datei",
"verify-signature": "Signatur überprüfen",
"verification-status": "Überprüfungsstatus",
"verification-in-progress": "Überprüfung läuft...",
"verification-results-will-appear-here": "Überprüfungsergebnisse werden hier angezeigt",
"please-select-pdf": "Bitte wählen Sie eine gültige PDF-Datei aus",
"please-select-file-to-verify": "Bitte wählen Sie eine Datei zur Überprüfung aus",
"no-signature-found": "Keine Signatur im Dokument gefunden",
"error-verifying-pdf": "Fehler beim Überprüfen der PDF",
"signature-valid-basic": "Signatur ist gültig",
"signature-invalid-basic": "Signatur ist ungültig",
"all-signatures-verified-convincing": "Dokument überprüft: Alle Signaturen wurden erfolgreich validiert.",
"some-signatures-invalid-basic": "Einige Signaturen sind ungültig",
"no-signatures-processed": "Keine Signaturen verarbeitet",
"unnamed-signature-field": "Unbenanntes Signaturfeld",
"error-processing-signature": "Fehler beim Verarbeiten der Signatur",
"signer-info-not-available": "Signaturinformationen nicht verfügbar",
"cert-validity-not-checked": "Gültigkeit des Zertifikats nicht geprüft",
"valid": "Gültig",
"expired-or-not-yet-valid": "Abgelaufen oder noch nicht gültig",
"valid-from": "Gültig von",
"to": "bis",
"signer": "Unterzeichner",
"issuer": "Aussteller",
"not-available": "Nicht verfügbar",
"not-performed": "Nicht durchgeführt",
"missing-acrofield-dict": "Fehlendes Acrofield-Wörterbuch",
"signature-dictionary-not-found-or-invalid": "Signaturwörterbuch nicht gefunden oder ungültig",
"missing-or-invalid-byterange": "Fehlender oder ungültiger ByteRange",
"missing-or-invalid-contents": "Fehlender oder ungültiger Inhalt",
"missing-signature-contents": "Fehlender Signaturinhalt",
"invalid-signature-hex-format": "Ungültiges Signatur-Hex-Format",
"unsupported-signature-format-not-signeddata": "Nicht unterstütztes Signaturformat - nicht SignedData",
"signer-certificate-not-found": "Unterzeichnerzertifikat nicht gefunden",
"no-certificates-in-signature": "Keine Zertifikate in der Signatur",
"no-signer-info-in-pkcs7": "Keine Signaturinformationen in PKCS#7",
"could-not-parse-signer-info": "Signaturinformationen konnten nicht analysiert werden",
"not-calculated": "Nicht berechnet",
"not-found-in-signature": "Nicht in Signatur gefunden",
"readonly-error": "Das schreibgeschützte {{widgetName}}-Widget muss einen Standardwert haben oder kann optional gemacht werden.",
"choose-one":"Wählen Sie eine aus",
"search-templates": "Vorlagen durchsuchen…",
"search-documents": "Dokumente suchen…",
"search-contacts": "Kontakte durchsuchen…",
"edit-draft": "Entwurf bearbeiten",
"add-role-alert": "Bitte fügen Sie mindestens eine Rolle hinzu",
"invalid-email-found": "Ungültige E-Mail gefunden: {{email}}",
"duplicate-email-found": "Doppelte E-Mail gefunden: {{email}}",
"vertical": "Vertikal",
"horizontal": "Horizontal",
"billing": "Abrechnung",
"console": "Konsole",
"prefill-widget": "Vorausgefüllte Widgets",
"action-prohibited": "Diese Aktion ist für Ihre E-Mail-Domain nicht erlaubt. Bitte wenden Sie sich an Ihren Administrator, um Hilfe zu erhalten.",
"must-have-at-least-one-vacant-role": "Mindestens eine Rolle muss unzugewiesen sein, bevor Sie ein template auf 'public' setzen.",
"remove-duplicate": "Bitte doppelte Option entfernen",
"prefill-bulk-error": "Der Massenversand ist nicht erlaubt, wenn Prefill-Widgets hinzugefügt wurden. Bitte entfernen Sie die Prefill-Widgets, um fortzufahren.",
"session-expired-title": "Sitzung abgelaufen",
"access-denied": "Zugriff verweigert",
"upgrade": "Upgrade",
"do-not-access-app": "Sie haben keinen Zugriff auf diese Anwendung.",
"dont-have-access": "Sie haben keinen Zugriff.",
"valid-email-alert": "Bitte geben Sie eine gültige E-Mail-Adresse ein.",
"otp-not-validate": "OTP ist ungültig.",
"domain-not-allowed": "Diese Domain ist nicht erlaubt",
"atleast-one-recipient-alert": "Bitte fügen Sie mindestens einen Empfänger hinzu!",
"incorrect-password-or-decryption-failed": "Falsches Passwort oder Entschlüsselung fehlgeschlagen.",
"incorrect-password-for-file": "Falsches Passwort für Datei: {{file}}",
"error-uploading-pdf": "Fehler beim Hochladen der PDF.",
"provide-password": "Bitte geben Sie das Passwort an.",
"only-pdf-allowed": "Nur PDF-Dateien sind erlaubt.",
"invalid-username-password-region": "Ungültiger Benutzername/Passwort oder Region.",
"pfx-extension-alert": "Bitte laden Sie eine Datei mit der Endung .pfx hoch.",
"email-already-exist": "E-Mail existiert bereits",
"branding": "Markenbildung",
"branding-help": "Branding ermöglicht White-Labeling für Ihre App",
"custom-sub-domain": "Benutzerdefinierte Subdomain",
"app-name": "App Name",
"provide-domain-name": "Geben Sie Ihren Domainnamen an",
"provide-app-name": "Geben Sie Ihren App-Namen an",
"logo": "Logo",
"upload-app-logo": "Laden Sie Ihr App-Logo hoch",
"prefill-unfilled-widget": "Die folgenden Pflichtfelder dürfen nicht leer sein: {{emptyWidget}}. Bitte füllen Sie diese aus, um fortzufahren.",
"Dashboard": "Armaturenbrett",
"Analytics": "Analytik",
"Templates": "Vorlagen",
"Need your sign": "Benötigt Ihre Unterschrift",
"In Progress": "In Bearbeitung",
"Completed": "Abgeschlossen",
"Drafts": "Entwürfe",
"Declined": "Abgelehnt",
"Expired": "Abgelaufen",
"Contactbook": "Kontaktbuch",
"My Signature": "Meine Unterschrift",
"API Token": "API-Token",
"Webhook": "Webhook",
"Preferences": "Einstellungen",
"Teams": "Teams",
"Users": "Benutzer",
"Drive": "Drive",
"Branding": "Markenbildung",
"Mail": "Mail",
"Storage": "Speicher",
"Signing certificate": "Signierzertifikat",
"General": "Allgemein",
"Organizations": "Organisationen",
"OrgAdmins": "OrgAdmins",
"Debug Pdf": "PDF debuggen",
"New Document": "Neues Dokument",
"subscription": "Abonnement",
"Draft document": "Dokumententwurf",
"Draft template": "Vorlagenentwurf",
"Public sign": "Öffentliche Signatur",
"Signup": "Registrieren",
"delete-contact": "Kontakt löschen",
"total-records-found": "Gesamtanzahl gefundener Einträge: {{count}}",
"Invalid-records-found": "Ungültige Einträge gefunden: {{records}}",
"previous": "Zurück",
"page-n-of-n": "Seite {{currentPage}} von {{totalPages}}",
"import": "Importieren",
"search": "Suchen",
"viewed-on": "Angesehen am: {{ViewedOn}}",
"signed-on": "Unterschrieben am: {{SignedOn}}",
"hide": "Ausblenden",
"show-more": "Mehr anzeigen",
"browse-or-drag-to-replace-existing-file": "Durchsuchen oder per Drag & Drop eine neue Datei ziehen, um die vorhandene zu ersetzen",
"optional-details": "Optionale Angaben",
"mail-adapter-subscription-alert": "Bitte upgraden Sie auf den Professional- oder Team-Plan, um den Mail-Adapter einzurichten.",
"connect-to-mail": "Mit Gmail verbinden",
"custom-smtp": "Benutzerdefiniertes SMTP",
"default-smtp": "{{appName}} Standard-SMTP",
"host": "Host",
"port": "Port",
"sender-email": "Absender-E-Mail",
"username": "Benutzername",
"use-default-mail-adapter": "Möchten Sie wirklich die Standard-Mailserver von {{appName}} verwenden, um Ihre Signaturanfragen zu versenden? Wir empfehlen, Ihre eigenen Gmail- oder SMTP-Server zu verwenden, um die Zustellbarkeit zu verbessern.",
"verification-code-sent-registered-email": "Ein Bestätigungscode wurde an Ihre registrierte E-Mail-Adresse <1>{{useremail}}</1> gesendet. Bitte geben Sie den Code unten ein, um Ihre Einstellungen zu bestätigen.",
"smpt-credentials": "SMTP-Zugangsdaten"
}
+187 -16
View File
@@ -1,9 +1,11 @@
{
"header-news": "New feature: Protect your account with Two-Factor Authentication (2FA) and enjoy the future of login with Passkeys — no passwords needed.",
"header-news-btn": "Setup now",
"sandbox-news": "This is a sandbox environment. Please do not use it for production purposes.",
"create-account": "Create account",
"login": "Login",
"language": "Language",
"dark-mode": "Dark mode",
"name": "Name",
"phone": "Phone",
"phone-optional": "optional",
@@ -24,6 +26,8 @@
"Name": "Name",
"Date": "Date"
},
"folder": "Folder",
"pdf": "Pdf",
"context-menu": {
"Download": "Download",
"Rename": "Rename",
@@ -39,8 +43,14 @@
"save": "Save",
"cancel": "Cancel",
"upgrade-now": "Upgrade now",
"contact-now": "Contact now",
"upgrade-to": "Upgrade to",
"plan": "Plan",
"connect": "connect",
"connect-to-g-drive": "Connect to Google Drive",
"reconnect-to-g-drive": "Reconnect to Google Drive",
"gdrive-info-connect": "When Google Drive is connected, the completed document will be saved in the {{appName}} folder on Google Drive.",
"subscription-renew-warning": "Your subscription will expire in {{remainingDays}} days. Please renew your subscription.",
"subscribe-card-teamplan": "Unlock the full power of collaboration! Create unlimited organizations, teams, and hierarchies. Share templates seamlessly across teams and assign custom user roles. Elevate your workflow today!",
"subscribe-card-plan": "Unlock premium features starting at just {{premiumPrice}}/month. Enjoy enhanced performance and only {{addonPrice}} per additional credit after your included premium credits.",
"user-name-limit-char": "To have a username less than 8 character please subscribe",
@@ -54,7 +64,7 @@
"welcome": "Welcome back!",
"Login-to-your-account": "Login to your account",
"password": "Password",
"forgot-password": "Forgot password?",
"forgot-password": "Forgot password",
"loading": "Loading...",
"of": "of",
"sign-SSO": "Sign in with SSO",
@@ -142,6 +152,7 @@
"Quick send": "Quick send",
"Edit": "Edit",
"Share with team": "Share with team",
"Share with user": "Share with colleague",
"Share": "Share",
"View": "View",
"option": "Option",
@@ -176,7 +187,9 @@
"created-date": "Created date",
"Type": "Type",
"Logs": "Logs",
"Expiry-date": "Expiry date"
"Expiry-date": "Expiry date",
"Company": "Company",
"JobTitle": "Job title"
},
"report-help": {
"Draft Documents": "These are documents you have started but have not finalized for sending.",
@@ -188,16 +201,15 @@
"Contactbook": "This is a list of contacts/signers added by you. These will appear as suggestions when you try to add signers to a new document.",
"Templates": "This is a list of templates that are available to you for creating documents. You can click the 'use' button to create a new document using a template, modify the document & add signers in the next step."
},
"form-name": {
"Sign Yourself": "Sign yourself",
"Request Signatures": "Request signatures",
"New Template": "New template"
},
"Sign Yourself": "Sign yourself",
"Request Signatures": "Request signatures",
"New Template": "New template",
"file-type": "pdf, png, jpg, jpeg",
"docx": "docx",
"file-selected": "file selected",
"file-selected": "file(s) selected",
"template-title": "Template title",
"document-title": "Document title",
"title": "Title",
"description": "Description",
"time-to-complete": "Time to complete (Days)",
"send-in-order": "Send in order",
@@ -245,6 +257,7 @@
"API": "API",
"api-token": "API token",
"regenerate-token": "Regenerate live token",
"remove-background": "Remove Background",
"generate-token": "Generate live token",
"view-docs": "View docs",
"generate-token-alert": "Are you sure you want to regenerate token it will expire old token?",
@@ -356,6 +369,7 @@
"date": "date",
"text": "text",
"text input": "text input",
"cells": "cells",
"checkbox": "checkbox",
"dropdown": "dropdown",
"radio button": "radio button",
@@ -372,6 +386,7 @@
"certificate": "Certificate",
"decline": "Decline",
"finish": "Finish",
"done": "Done",
"mail": "Mail",
"sign-now": "Sign now",
"successfully-signed": "Successfully signed!",
@@ -381,6 +396,10 @@
"Email-verified-alert-1": "Email is verified.",
"Email-verified-alert-2": "Email is already verified.",
"upload-stamp-image": "Upload stamp image",
"draw-signature": "Draw signature",
"draw-initials": "Draw initials",
"enter-text": "Enter text",
"enter-widgettype": "Enter {{widgetType}}",
"draw": "Draw",
"type": "Type",
"color-type": {
@@ -401,6 +420,7 @@
"reset-password-alert-3": "Reset Your Password",
"faild-animation": "Failed to load animation",
"apply": "Apply",
"select-columns": "Select columns",
"copy-type": {
"All pages": "All pages",
"All pages but last": "All pages but last",
@@ -410,10 +430,12 @@
"options": "Options",
"minimun-check": "Minimun check",
"maximum-check": "Maximum check",
"cell-count": "Cell count",
"default-value": "Default value",
"select": "Select",
"read-only": "Is read only",
"read-only": "read only",
"hide-labels": "Hide labels",
"layout": "Layout",
"checkbox": "Checkbox",
"alert": "Alert",
"zoom-in": "Zoom in",
@@ -462,7 +484,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-mail-alert-you": "Subsequent signers will get email(s) once you sign 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!",
@@ -478,6 +500,7 @@
"document-alert": "Document alert",
"owner-subscription-expired": "Owner's subscription has expired.",
"subscription-expired": "Subscription Expired",
"owner-doesnt-have-paid-plan": "Owner doesn't have paid plan.",
"alert-message": "Alert message",
"document-decline": "Document decline",
"decline-alert-1": "Are you sure want to decline this document ?",
@@ -520,7 +543,7 @@
"new-password": "New password",
"confirm-password": "Confirm password",
"file-alert-1": "The selected file size is too large. Please select a file less than",
"file-alert-2": "Please select file.",
"file-alert-2": "Please select file(s).",
"file-alert-3": "Please wait while the document is being uploaded.",
"enter-pdf-password": "Enter Pdf password",
"correct-password": "Please provide correct password",
@@ -670,7 +693,7 @@
"public-template-mssg-1": "To integrate OpenSign into your React or Next.js project, simply run the following command:",
"public-template-mssg-2": "Ensure you have npm or yarn set up in your project. If you're using Yarn, you can replace npm install with yarn add @opensign/react.",
"public-template-mssg-3": "Need more details or examples?",
"public-template-mssg-4": "Visit the",
"public-template-mssg-4": "Visit the ",
"public-template-mssg-5": " npm for the latest updates, detailed documentation, and version history.",
"public-template-mssg-6": "Before you can use this code snippet, you must make this template public.",
"public-template-mssg-7": "Before you can generate a public link you must make this template public.",
@@ -718,7 +741,7 @@
"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, 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.",
"text-field-tour": "'Prefill' fields must be filled in advance before the document is sent. If you need the signers to provide input, use signers fields 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",
"at-least-one-signature-type": "At least one signature type should be enabled.",
@@ -744,12 +767,15 @@
"delete-page": "Delete page",
"merge-pdf": "Merge pdf",
"add-pages": "Add pages",
"reorder-pages": "Reorder pages",
"delete-alert": "Can not delete single page",
"delete-alert-2": "Are you sure you want to delete this page?",
"delete-note": "Note: Once you delete this page, you cannot undo.",
"Rotation-alert": "Rotate page",
"bulk-import": "Bulk import",
"contacts-file": "Contacts file (xlsx, csv)",
"import-guideline": "Upload a CSV or Excel file with columns Name, Email and optional Phone. Only the first 100 records will be imported.",
"download-sample": "Download sample file",
"100-records-only": "Currently you can only import up to 100 records.",
"csv-excel-support-only": "Upload a file in one of the following formats: CSV, XLSX or XLS.",
"contact-imported": "{{imported}} contacts were imported. {{failed}} contacts failed to import.",
@@ -763,7 +789,7 @@
"agree-p1": "I confirm that I have read and understood the ",
"agree-p2": "Electronic Record and Signature Disclosure",
"agree-p3": "and consent to use electronic records and signatures.",
"agrre-button": " Agree & Continue",
"agrre-button": "I confirm & agree to 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 {{appName}} platform ('Platform'). By signing documents via {{appName}}, you agree to the terms outlined in this Disclosure. Please read it carefully before proceeding.",
@@ -879,7 +905,7 @@
"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",
"invalid-templateid": "Invalid 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.",
@@ -971,5 +997,150 @@
"kiosk-sign": "Kiosk Sign",
"dont-have-access-to-template": "The template has been deleted or you don't have access. Please contact the sender.",
"kiosk-info": "Kiosk Mode lets you collect in-person signatures quickly and efficiently. Ideal for trade shows, events, or walk-in scenarios where all signers are physically present. ",
"learn-more": "Learn more"
"learn-more": "Learn more",
"finish-mssg":" Are you sure you want to finish the document ?",
"review":"Review",
"next-field":"Next Field",
"required-mssg":"{{leftRequiredWidget}} of {{totalWidget}} fields left",
"verify-document": "Verify document",
"verify-document-signature": "Verify Document Signature",
"select-pdf-document": "Select PDF Document",
"selected-file": "Selected file",
"verify-signature": "Verify Signature",
"verification-status": "Verification Status",
"verification-in-progress": "Verification in progress...",
"verification-results-will-appear-here": "Verification results will appear here",
"please-select-pdf": "Please select a valid PDF file",
"please-select-file-to-verify": "Please select a file to verify",
"no-signature-found": "No signature found in the document",
"error-verifying-pdf": "Error verifying PDF",
"signature-valid-basic": "Signature is valid",
"signature-invalid-basic": "Signature is invalid",
"all-signatures-verified-convincing": "Document Verified: All signatures have been successfully validated.",
"some-signatures-invalid-basic": "Some signatures are invalid",
"no-signatures-processed": "No signatures were processed",
"unnamed-signature-field": "Unnamed Signature Field",
"error-processing-signature": "Error processing signature",
"signer-info-not-available": "Signer information not available",
"cert-validity-not-checked": "Certificate validity not checked",
"valid": "Valid",
"expired-or-not-yet-valid": "Expired or not yet valid",
"valid-from": "Valid from",
"to": "to",
"signer": "Signer",
"issuer": "Issuer",
"not-available": "Not available",
"not-performed": "Not performed",
"missing-acrofield-dict": "Missing acrofield dictionary",
"signature-dictionary-not-found-or-invalid": "Signature dictionary not found or invalid",
"missing-or-invalid-byterange": "Missing or invalid ByteRange",
"missing-or-invalid-contents": "Missing or invalid Contents",
"missing-signature-contents": "Missing signature contents",
"invalid-signature-hex-format": "Invalid signature hex format",
"unsupported-signature-format-not-signeddata": "Unsupported signature format - not SignedData",
"signer-certificate-not-found": "Signer certificate not found",
"no-certificates-in-signature": "No certificates in signature",
"no-signer-info-in-pkcs7": "No signer info in PKCS#7",
"could-not-parse-signer-info": "Could not parse signer info",
"not-calculated": "Not calculated",
"not-found-in-signature": "Not found in signature",
"readonly-error": "Read-only {{widgetName}} widget must have a default value or you can make it optional.",
"choose-one":"Choose One",
"search-templates": "Search templates…",
"search-documents": "Search documents…",
"search-contacts": "Search contacts…",
"add-role-alert":"Please add at least one role",
"edit-draft":"Edit draft",
"invalid-email-found": "Invalid email found: {{email}}",
"duplicate-email-found": "Duplicate email found: {{email}}",
"vertical": "Vertical",
"horizontal": "Horizontal",
"billing": "Billing",
"console": "Console",
"prefill-widget":"Prefill Widgets",
"action-prohibited": "This action isn't allowed for your email domain. Please contact your administrator for assistance.",
"must-have-at-least-one-vacant-role": "You must have at least one role unassigned before setting a template to 'public'.",
"remove-duplicate": "Please remove duplicate option",
"prefill-bulk-error":"Bulk send is not allowed when prefill widgets are added. Please remove the prefill widgets to proceed.",
"session-expired-title": "Session Expired",
"access-denied": "Access denied",
"upgrade": "Upgrade",
"do-not-access-app": "You don't have access to this application.",
"dont-have-access": "You don't have access.",
"valid-email-alert": "Please enter a valid email address.",
"otp-not-validate": "OTP is not valid.",
"domain-not-allowed": "This domain is not allowed",
"atleast-one-recipient-alert": "Please add at least one recipient!",
"incorrect-password-or-decryption-failed": "Incorrect password or decryption failed.",
"incorrect-password-for-file": "Incorrect password for file: {{file}}",
"error-uploading-pdf": "Error while uploading PDF.",
"provide-password": "Please provide password.",
"only-pdf-allowed": "Only PDF files are allowed.",
"invalid-username-password-region": "Invalid username/password or region.",
"pfx-extension-alert": "Please upload a file with a .pfx extension.",
"email-already-exist": "Email already exists",
"branding": "Branding",
"branding-help": "Branding provides white labelling to your app",
"custom-sub-domain": "Custom sub-domain",
"app-name": "App Name",
"provide-domain-name": "provide your domain name",
"provide-app-name": "provide your app name",
"logo":"Logo",
"upload-app-logo": "upload your app logo",
"prefill-unfilled-widget" :"The following required field(s) cannot be left empty: {{emptyWidget}} Please fill them out to proceed.",
"Dashboard": "Dashboard",
"Analytics": "Analytics",
"Templates": "Templates",
"Need your sign": "Need your sign",
"In Progress": "In progress",
"Completed": "Completed",
"Drafts": "Drafts",
"Declined": "Declined",
"Expired": "Expired",
"Contactbook": "Contactbook",
"My Signature": "My signature",
"API Token": "API token",
"Webhook": "Webhook",
"Preferences": "Preferences",
"Teams": "Teams",
"Users": "Users",
"Drive": "Drive",
"Branding": "Branding",
"Mail": "Mail",
"Storage": "Storage",
"Signing certificate": "Signing certificate",
"General": "General",
"Organizations": "Organizations",
"OrgAdmins": "OrgAdmins",
"Debug Pdf": "Debug Pdf",
"New Document": "New Document",
"subscription": "subscription",
"Draft document": "Draft document",
"Draft template": "Draft template",
"Public sign": "Public sign",
"Signup": "Signup",
"delete-contact": "Delete Contact",
"total-records-found": "Total records found: {{count}}",
"Invalid-records-found": "Invalid records found: {{records}}",
"previous": "Previous",
"page-n-of-n": "Page {{currentPage}} of {{totalPages}}",
"import": "Import",
"search": "Search",
"viewed-on": "Viewed on: {{ViewedOn}}",
"signed-on": "Signed on: {{SignedOn}}",
"hide": "Hide",
"show-more": "Show More",
"browse-or-drag-to-replace-existing-file": "Browse or drag & drop a new file to replace the existing one",
"optional-details": "Optional details",
"mail-adapter-subscription-alert": "Please upgrade to Professional or Team plan to setup custom SMTP.",
"connect-to-mail": "Connect to Gmail",
"custom-smtp": "Custom SMTP",
"default-smtp": "{{appName}} default SMTP",
"host":"Host",
"port":"Port",
"sender-email": "Sender Email",
"username": "Username",
"use-default-mail-adapter": "Are you sure you want to use {{appName}}'s default mail servers to send your signature request emails? We recommend using your own Gmail or SMTP servers for improved inbox deliverability.",
"verification-code-sent-registered-email": "A verification code has been sent to your registered email <1>{{useremail}}</1> confirm your settings. Please enter the code below.",
"smpt-credentials": "SMTP Credentials"
}
+183 -12
View File
@@ -1,9 +1,11 @@
{
"header-news": "Nueva característica: los usuarios del plan Teams ahora pueden integrar sus propios depósitos de AWS S3 para el almacenamiento de archivos",
"header-news-btn": "Configurar ahora",
"sandbox-news": "Este es un entorno sandbox. Por favor, no lo utilice con fines de producción.",
"create-account": "Crear cuenta",
"login": "Iniciar sesión",
"language": "Idioma",
"dark-mode": "Modo oscuro",
"name": "Nombre",
"phone": "Teléfono",
"phone-optional": "opcional",
@@ -24,6 +26,8 @@
"Name": "Nombre",
"Date": "Fecha"
},
"folder": "Carpeta",
"pdf": "Pdf",
"context-menu": {
"Download": "Descargar",
"Rename": "Renombrar",
@@ -39,8 +43,14 @@
"save": "Guardar",
"cancel": "Cancelar",
"upgrade-now": "Mejorar ahora",
"contact-now": "Contactar ahora",
"upgrade-to": "Mejorar a",
"plan": "Plan",
"connect": "Conectar",
"connect-to-g-drive": "Conectar con Google Drive",
"reconnect-to-g-drive": "Volver a conectar con Google Drive",
"gdrive-info-connect": "Cuando Google Drive está conectado, el documento completado se guardará en la carpeta {{appName}} de Google Drive.",
"subscription-renew-warning": "Su suscripción vencerá en {{remainingDays}} días. Por favor, renueve su suscripción.",
"subscribe-card-teamplan": "¡Libera todo el poder de la colaboración! Crea organizaciones, equipos y jerarquías ilimitadas. Comparte plantillas sin problemas entre equipos y asigna funciones de usuario personalizadas. ¡Mejora tu flujo de trabajo hoy mismo!",
"subscribe-card-plan": "Desbloquea funciones premium desde solo {{premiumPrice}}/mes. Disfruta de un rendimiento mejorado y solo {{addonPrice}} por crédito adicional después de tus créditos premium incluidos.",
"user-name-limit-char": "Para tener un nombre de usuario menor a 8 caracteres por favor suscríbete",
@@ -54,7 +64,7 @@
"welcome": "¡Bienvenido de nuevo!",
"Login-to-your-account": "Ingresa a tu cuenta",
"password": "Contraseña",
"forgot-password": "¿Olvidaste la contraseña?",
"forgot-password": "¿Olvidaste la contraseña",
"loading": "Cargando...",
"of": "de",
"sign-SSO": "Ingresar con SSO",
@@ -142,6 +152,7 @@
"Quick send": "Envío rápido",
"Edit": "Editar",
"Share with team": "Compartir con el equipo",
"Share with user": "Compartir con un colega",
"Share": "Compartir",
"View": "Ver",
"option": "Opción",
@@ -176,7 +187,9 @@
"created-date": "Fecha de creación",
"Type": "Tipo",
"Logs": "Registros",
"Expiry-date": "Date d'expiration"
"Expiry-date": "Date d'expiration",
"Company": "Compañía",
"JobTitle": "Título del puesto"
},
"report-help": {
"Draft Documents": "Estos son documentos que has iniciado pero no has finalizado para su envío.",
@@ -188,16 +201,15 @@
"Contactbook": "Esta es una lista de contactos/firmantes añadidos por ti. Aparecerán como sugerencias cuando intentes añadir firmantes a un nuevo documento.",
"Templates": "Esta es una lista de plantillas que están a tu disposición para crear documentos. Puedes hacer clic en el botón «usar» para crear un nuevo documento utilizando una plantilla, modifica el documento y añade firmantes en el siguiente paso."
},
"form-name": {
"Sign Yourself": "Firmar",
"Request Signatures": "Solicitar firmas",
"New Template": "Nueva plantilla"
},
"Sign Yourself": "Firmar",
"Request Signatures": "Solicitar firmas",
"New Template": "Nueva plantilla",
"file-type": "pdf, png, jpg, jpeg",
"docx": "docx",
"file-selected": "archivo seleccionado",
"template-title": "Título de la plantilla",
"document-title": "Título del documento",
"title": "Título",
"description": "Descripción",
"time-to-complete": "Plazo de finalización (días)",
"send-in-order": "Enviar en orden",
@@ -246,6 +258,7 @@
"API": "API",
"api-token": "Token API",
"regenerate-token": "Regenerar token activo",
"remove-background": "Eliminar fondo",
"generate-token": "Generar token activo",
"view-docs": "Ver documentación",
"generate-token-alert": "¿En definitiva quieres regenerar el token? Esto expirará el token antiguo.",
@@ -357,6 +370,7 @@
"date": "fecha",
"text": "texto",
"text input": "entrada de texto",
"cells": "células",
"checkbox": "casilla",
"dropdown": "desplegable",
"radio button": "botón de radio",
@@ -373,6 +387,7 @@
"certificate": "Certificado",
"decline": "Rechazar",
"finish": "Finalizar",
"done": "Hecho",
"mail": "Correo",
"sign-now": "Firmar ahora",
"successfully-signed": "¡Firmado exitosamente!",
@@ -382,6 +397,10 @@
"Email-verified-alert-1": "El correo está verificado.",
"Email-verified-alert-2": "El correo ya ha sido verificado.",
"upload-stamp-image": "Subir imagen del sello",
"draw-signature": "Dibujar firma",
"draw-initials": "Dibujar iniciales",
"enter-text": "Introducir texto",
"enter-widgettype": "Introduzca {{widgetType}}",
"draw": "Dibujar",
"type": "Escribir",
"color-type": {
@@ -402,6 +421,7 @@
"reset-password-alert-3": "Restablece tu contraseña",
"faild-animation": "Error al cargar la animación",
"apply": "Aplicar",
"select-columns": "Seleccionar columnas",
"copy-type": {
"All pages": "Todas las páginas",
"All pages but last": "Todas las páginas menos la última",
@@ -411,10 +431,12 @@
"options": "Opciones",
"minimun-check": "Chequeo mínimo",
"maximum-check": "Chequeo máximo",
"cell-count": "recuento de células",
"default-value": "Valor por defecto",
"select": "Seleccionar",
"read-only": "Es de solo lectura",
"hide-labels": "Esconder etiquetas",
"layout": "Diseño",
"checkbox": "Casilla",
"alert": "Alerta",
"zoom-in": "Acercar",
@@ -462,7 +484,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-mail-alert-you": "Los firmantes siguientes recibirán un correo electrónico una vez que usted 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!",
@@ -477,6 +499,7 @@
"mail-not-delivered": "correo no entregado",
"document-alert": "Alerta de documento",
"owner-subscription-expired": "La suscripción del propietario ha expirado.",
"owner-doesnt-have-paid-plan": "El propietario no tiene un plan de pago.",
"subscription-expired": "Suscripción expirada",
"alert-message": "Mensaje de alerta",
"document-decline": "Rechazar documento",
@@ -670,7 +693,7 @@
"public-template-mssg-1": "Para integrar OpenSign a tu proyecto React o Next.js, simplemente ejecuta los siguientes comandos:",
"public-template-mssg-2": "Asegúrate de tener «npm» o «yarn» configurado en tu proyecto. Si estás usando «yarn», puedes reemplazar «npm install» con «yarn add @opensign/react».",
"public-template-mssg-3": "¿Necesitas más detalles o ejemplos?",
"public-template-mssg-4": "Visita la",
"public-template-mssg-4": "Visita la ",
"public-template-mssg-5": " «npm» para las últimas actualizaciones, documentación detallada e historial de versiones.",
"public-template-mssg-6": "Antes de que puedas usar este fragmento de código, debes convertir esta plantilla en pública.",
"public-template-mssg-7": "Antes de poder generar un enlace público, debes hacer que esta plantilla sea pública.",
@@ -718,7 +741,7 @@
},
"form-title-1": "Configuración del flujo de documentos",
"form-title-2": "Configuración de seguridad",
"text-field-tour": "Los campos de tipo 'Texto' deben completarse con anticipación antes de enviar el documento. Si necesita que los firmantes proporcionen información, utilice el campo 'Entrada de texto'",
"text-field-tour": "Los campos de 'Rellenado previo' deben completarse antes de enviar el documento. Si necesita que los firmantes proporcionen información, utilice los campos de firmantes.",
"attach-signer-tour": "Debe adjuntar un firmante a cada función. Puede hacerlo haciendo clic en este icono. Una vez que seleccione un Firmante, se adjuntará a todos los campos asociados con ese rol que aparecen en el mismo color.",
"allowed-signature-types": "Tipos de firma permitidos",
"at-least-one-signature-type": "Se debe habilitar al menos un tipo de firma.",
@@ -744,12 +767,15 @@
"delete-page": "eliminar página",
"merge-pdf": "fusionar pdf",
"add-pages": "Agregar páginas",
"reorder-pages": "Reordenar páginas",
"delete-alert": "No se puede eliminar una sola página",
"delete-alert-2": "¿Está seguro de que desea eliminar esta página?",
"delete-note": "Nota: una vez que elimines esta página, no podrás deshacerla",
"Rotation-alert": "Girar página",
"bulk-import": "Importación masiva",
"contacts-file": "Archivo de contactos (xlsx, csv)",
"import-guideline": "Sube un archivo CSV o Excel con las columnas Name, Email y opcionalmente Phone. Solo se importarán los primeros 100 contactos.",
"download-sample": "Descargar archivo de ejemplo",
"100-records-only": "Currently you can only import up to 100 records.",
"csv-excel-support-only": "Cargue un archivo en uno de los siguientes formatos: CSV, XLSX o XLS.",
"contact-imported": "Se importaron {{imported}} contactos. {{failed}} contactos no pudieron importarse.",
@@ -763,7 +789,7 @@
"agree-p1": "Confirmo que he leído y comprendido el ",
"agree-p2": "Registro Electrónico y Divulgación de Firma",
"agree-p3": "y consentimiento para utilizar registros y firmas electrónicas.",
"agrre-button": "Aceptar y continuar",
"agrre-button": "Confirmo y acepto 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 {{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.",
@@ -971,5 +997,150 @@
"kiosk-sign": "Firma en quiosco",
"dont-have-access-to-template": "El template ha sido eliminado o no tiene acceso. Por favor, contacte al remitente.",
"kiosk-info": "El Modo Kiosco le permite recopilar firmas en persona de forma rápida y eficiente. Ideal para ferias, eventos o situaciones con personas que firman en el lugar. ",
"learn-more": "Más información"
"learn-more": "Más información",
"finish-mssg": "¿Está seguro de que desea finalizar el documento?",
"review": "Revisar",
"next-field": "Siguiente campo",
"required-mssg": "{{leftRequiredWidget}} de {{totalWidget}} campos restantes",
"verify-document": "Verificar documento",
"verify-document-signature": "Verificar firma del documento",
"select-pdf-document": "Seleccionar documento PDF",
"selected-file": "Archivo seleccionado",
"verify-signature": "Verificar firma",
"verification-status": "Estado de verificación",
"verification-in-progress": "Verificación en curso...",
"verification-results-will-appear-here": "Los resultados de la verificación aparecerán aquí",
"please-select-pdf": "Por favor, seleccione un archivo PDF válido",
"please-select-file-to-verify": "Por favor, seleccione un archivo para verificar",
"no-signature-found": "No se encontró ninguna firma en el documento",
"error-verifying-pdf": "Error al verificar el PDF",
"signature-valid-basic": "La firma es válida",
"signature-invalid-basic": "La firma no es válida",
"all-signatures-verified-convincing": "Documento verificado: Todas las firmas han sido validadas exitosamente.",
"some-signatures-invalid-basic": "Algunas firmas no son válidas",
"no-signatures-processed": "No se procesaron firmas",
"unnamed-signature-field": "Campo de firma sin nombre",
"error-processing-signature": "Error al procesar la firma",
"signer-info-not-available": "Información del firmante no disponible",
"cert-validity-not-checked": "Validez del certificado no verificada",
"valid": "Válido",
"expired-or-not-yet-valid": "Caducado o aún no válido",
"valid-from": "Válido desde",
"to": "hasta",
"signer": "Firmante",
"issuer": "Emisor",
"not-available": "No disponible",
"not-performed": "No realizado",
"missing-acrofield-dict": "Falta el diccionario Acrofield",
"signature-dictionary-not-found-or-invalid": "Diccionario de firmas no encontrado o inválido",
"missing-or-invalid-byterange": "ByteRange faltante o inválido",
"missing-or-invalid-contents": "Contenido faltante o inválido",
"missing-signature-contents": "Falta el contenido de la firma",
"invalid-signature-hex-format": "Formato hexadecimal de firma inválido",
"unsupported-signature-format-not-signeddata": "Formato de firma no compatible - no SignedData",
"signer-certificate-not-found": "Certificado del firmante no encontrado",
"no-certificates-in-signature": "No hay certificados en la firma",
"no-signer-info-in-pkcs7": "No hay información del firmante en PKCS#7",
"could-not-parse-signer-info": "No se pudo analizar la información del firmante",
"not-calculated": "No calculado",
"not-found-in-signature": "No encontrado en la firma",
"readonly-error": "El widget de solo lectura {{widgetName}} debe tener un valor predeterminado o puede hacerlo opcional.",
"choose-one":"Elige uno",
"search-templates": "Buscar plantillas…",
"search-documents": "Buscar documentos…",
"search-contacts": "Buscar contactos…",
"invalid-email-found": "Correo electrónico no válido encontrado: {{email}}",
"duplicate-email-found": "Correo electrónico duplicado encontrado: {{email}}",
"vertical": "Vertical",
"add-role-alert": "Por favor, agregue al menos un rol",
"edit-draft": "Editar borrador",
"horizontal": "Horizontal",
"billing": "Facturación",
"console": "Consola",
"prefill-widget": "Widgets de Relleno Previo",
"action-prohibited": "Esta acción no está permitida para su dominio de correo electrónico. Por favor, contacte con su administrador para obtener ayuda.",
"must-have-at-least-one-vacant-role": "Debe dejar al menos un rol sin asignar antes de establecer un template como 'public'.",
"remove-duplicate": "Por favor, elimina la opción duplicada",
"prefill-bulk-error": "El envío masivo no está permitido cuando se han agregado widgets de pre-rellenado. Por favor, elimine los widgets de pre-rellenado para continuar.",
"session-expired-title": "Sesión expirada",
"access-denied": "Acceso denegado",
"upgrade": "Actualizar",
"do-not-access-app": "No tienes acceso a esta aplicación.",
"dont-have-access": "No tienes acceso.",
"valid-email-alert": "Por favor ingresa una dirección de correo electrónico válida.",
"otp-not-validate": "OTP no es válido.",
"domain-not-allowed": "Este dominio no está permitido",
"atleast-one-recipient-alert": "¡Por favor agrega al menos un destinatario!",
"incorrect-password-or-decryption-failed": "Contraseña incorrecta o fallo de descifrado.",
"incorrect-password-for-file": "Contraseña incorrecta para el archivo: {{file}}",
"error-uploading-pdf": "Error al subir PDF.",
"provide-password": "Por favor proporciona la contraseña.",
"only-pdf-allowed": "Solo se permiten archivos PDF.",
"invalid-username-password-region": "Usuario/contraseña o región inválidos.",
"pfx-extension-alert": "Por favor sube un archivo con extensión .pfx.",
"email-already-exist": "El correo electrónico ya existe",
"branding": "Marca",
"branding-help": "El branding permite el white labelling de su aplicación",
"custom-sub-domain": "Subdominio personalizado",
"app-name": "Nombre de la aplicación",
"provide-domain-name": "Proporcione su nombre de dominio",
"provide-app-name": "Proporcione el nombre de su aplicación",
"logo": "Logo",
"upload-app-logo": "Suba el logotipo de su aplicación",
"prefill-unfilled-widget": "Los siguientes campos obligatorios no pueden quedar vacíos: {{emptyWidget}}. Por favor, complételos para continuar.",
"Dashboard": "Panel",
"Analytics": "Analítica",
"Templates": "Plantillas",
"Need your sign": "Necesitan tu firma",
"In Progress": "En progreso",
"Completed": "Completado",
"Drafts": "Borradores",
"Declined": "Rechazados",
"Expired": "Expirados",
"Contactbook": "Agenda de contactos",
"My Signature": "Mi firma",
"API Token": "Token API",
"Webhook": "Webhook",
"Preferences": "Preferencias",
"Teams": "Equipos",
"Users": "Usuarios",
"Drive": "Drive",
"Branding": "Marca",
"Mail": "Correo",
"Storage": "Almacenamiento",
"Signing certificate": "Certificado de firma",
"General": "General",
"Organizations": "Organizaciones",
"OrgAdmins": "OrgAdmins",
"Debug Pdf": "Depurar PDF",
"New Document": "Nuevo documento",
"subscription": "Suscripción",
"Draft document": "Borrador de documento",
"Draft template": "Borrador de plantilla",
"Public sign": "Firma pública",
"Signup": "Registrarse",
"delete-contact": "Eliminar contacto",
"total-records-found": "Total de registros encontrados: {{count}}",
"Invalid-records-found": "Registros inválidos encontrados: {{records}}",
"previous": "Anterior",
"page-n-of-n": "Página {{currentPage}} de {{totalPages}}",
"import": "Importar",
"search": "Buscar",
"viewed-on": "Visto el: {{ViewedOn}}",
"signed-on": "Firmado el: {{SignedOn}}",
"hide": "Ocultar",
"show-more": "Mostrar más",
"browse-or-drag-to-replace-existing-file": "Busque o arrastre y suelte un nuevo archivo para reemplazar el existente",
"optional-details": "Detalles opcionales",
"mail-adapter-subscription-alert": "Bitte upgraden Sie auf den Professional- oder Team-Plan, um ein benutzerdefiniertes SMTP einzurichten.",
"connect-to-mail": "Conectar con Gmail",
"custom-smtp": "SMTP personalizado",
"default-smtp": "SMTP predeterminado de {{appName}}",
"host": "Host",
"port": "Puerto",
"sender-email": "Correo del remitente",
"username": "Nombre de usuario",
"use-default-mail-adapter": "¿Está seguro de que desea usar los servidores de correo predeterminados de {{appName}} para enviar solicitudes de firma? Recomendamos usar su propio servidor de Gmail o SMTP para una mejor entregabilidad.",
"verification-code-sent-registered-email": "Se ha enviado un código de verificación a su correo registrado <1>{{useremail}}</1>. Ingrese el código a continuación para confirmar su configuración.",
"smpt-credentials": "Credenciales SMTP"
}
+183 -13
View File
@@ -1,9 +1,11 @@
{
"header-news": "Nouvelle fonctionnalité : les utilisateurs du forfait Teams peuvent désormais intégrer leurs propres compartiments AWS S3 pour le stockage de fichiers",
"header-news-btn": "Configurer maintenant",
"sandbox-news": "Ceci est un environnement sandbox. Veuillez ne pas l'utiliser à des fins de production.",
"create-account": "Créer un compte",
"login": "Se Connecter",
"language": "Langue",
"dark-mode": "Mode sombre",
"name": "Nom et Prénom",
"phone": "Téléphone",
"phone-optional": "(facultatif)",
@@ -24,6 +26,8 @@
"Name": "Nom et Prénom",
"Date": "Date"
},
"folder": "Dossier",
"pdf": "Pdf",
"context-menu": {
"Download": "Télécharger",
"Rename": "Renommer",
@@ -39,9 +43,15 @@
"save": "Sauvegarder",
"cancel": "Annuler",
"upgrade-now": "Mettre à jour maintenant",
"contact-now": "Contacter maintenant",
"upgrade-to": "Mettre à niveau vers",
"pro": "PRO",
"plan": "Offre",
"connect": "Connecter",
"connect-to-g-drive": "Se connecter à Google Drive",
"reconnect-to-g-drive": "Se reconnecter à Google Drive",
"gdrive-info-connect": "Lorsque Google Drive est connecté, le document complété sera enregistré dans le dossier {{appName}} sur Google Drive.",
"subscription-renew-warning": "Votre abonnement expirera dans {{remainingDays}} jours. Veuillez renouveler votre abonnement.",
"subscribe-card-teamplan": "Libérez toute la puissance de la collaboration ! Créez un nombre illimité d'organisations, d'équipes et de hiérarchies. Partagez des modèles de manière transparente entre les équipes et attribuez des rôles d'utilisateur personnalisés. Améliorez votre flux de travail dès aujourd'hui !",
"subscribe-card-plan": "Débloquez des fonctionnalités premium à partir de seulement {{premiumPrice}}/mois. Bénéficiez de performances améliorées et de seulement {{addonPrice}} par crédit supplémentaire après vos crédits premium inclus.",
"user-name-limit-char": "Pour avoir un nom d'utilisateur de moins de 8 caractères s'il vous plaît s'abonner",
@@ -54,7 +64,7 @@
"welcome": "Content de te revoir!",
"Login-to-your-account": "Se connecter à son compte",
"password": "Mot de passe",
"forgot-password": "Mot de passe oublié?",
"forgot-password": "Mot de passe oublié",
"loading": "Chargement...",
"of": "de",
"sign-SSO": "Connectez-vous avec SSO",
@@ -151,7 +161,9 @@
"created-date": "Date de création",
"Type": "Saisir",
"Logs": "Journaux",
"Expiry-date": "Fecha de caducidad"
"Expiry-date": "Fecha de caducidad",
"Company": "Organisation",
"JobTitle": "Votre Fonction"
},
"btnLabel": {
"sign": "Signer",
@@ -163,6 +175,7 @@
"Quick send": "Envoi rapide",
"Edit": "Modifier",
"Share with team": "Partager avec l'équipe",
"Share with user": "Partager avec un collègue",
"Share": "Partager",
"View": "Voir",
"option": "Option",
@@ -187,16 +200,15 @@
"Contactbook": "Il s'agit d'une liste de contacts/signataires que vous avez ajoutés. Ceux-ci apparaîtront sous forme de suggestions lorsque vous tenterez d'ajouter des signataires à un nouveau document.",
"Templates": "Il s'agit d'une liste de modèles à votre disposition pour créer des documents. Vous pouvez cliquer sur le bouton 'Utiliser' pour créer un nouveau document à l'aide d'un modèle, modifier le document et ajouter des signataires à l'étape suivante."
},
"form-name": {
"Sign Yourself": "signez vous-même",
"Request Signatures": "Demander des signatures",
"New Template": "Nouveau modèle"
},
"Sign Yourself": "signez vous-même",
"Request Signatures": "Demander des signatures",
"New Template": "Nouveau modèle",
"file-type": "pdf, png, jpg, jpeg",
"docx": "docx",
"file-selected": "fichier sélectionné",
"template-title": "Titre du modèle",
"document-title": "Titre du document",
"title": "Titre",
"description": "Description",
"time-to-complete": "Temps de réalisation (jours)",
"send-in-order": "Envoyer dans l'ordre ?",
@@ -245,6 +257,7 @@
"API": "API",
"api-token": "Jeton API",
"regenerate-token": "Régénérer en direct jeton",
"remove-background": "Supprimer l'arrière-plan",
"generate-token": "Générer en direct jeton",
"view-docs": "Afficher les documents",
"generate-token-alert": "Êtes-vous sûr de vouloir régénérer le jeton, votre ancien jeton sera supprimer?",
@@ -356,6 +369,7 @@
"date": "date",
"text": "texte",
"text input": "saisie de texte",
"cells": "cellules",
"checkbox": "case à cocher",
"dropdown": "dérouler",
"radio button": "bouton radio",
@@ -372,6 +386,7 @@
"certificate": "Certificat",
"decline": "refusé",
"finish": "terminé",
"done": "Terminé",
"mail": "Mail",
"sign-now": "Signez maintenant",
"successfully-signed": "Signé avec succès !",
@@ -381,6 +396,10 @@
"Email-verified-alert-1": "L'e-mail est vérifié.",
"Email-verified-alert-2": "L'e-mail est déjà vérifié.",
"upload-stamp-image": "Télécharger l'image du tampon",
"draw-signature": "Dessiner la signature",
"draw-initials": "Dessiner les initiales",
"enter-text": "Saisir du texte",
"enter-widgettype": "Saisir {{widgetType}}",
"draw": "Dessiner",
"type": "Taper",
"color-type": {
@@ -410,10 +429,12 @@
"options": "Possibilités",
"minimun-check": "Vérification minimale",
"maximum-check": "Contrôle maximum",
"cell-count": "numération cellulaire",
"default-value": "Valeur par défaut",
"select": "Sélectionner",
"read-only": "Est en lecture seule",
"hide-labels": "Masquer les étiquettes",
"layout": "Disposition",
"checkbox": "Case à cocher",
"alert": "Alerte",
"zoom-in": "Agrandir",
@@ -462,7 +483,7 @@
"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 s que vous signez le document.",
"placeholder-mail-alert-you": "Les signataires suivants recevront un e-mail une fois que vous aurez signé 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é !",
@@ -477,6 +498,7 @@
"mail-not-delivered": "Courrier non distribué",
"document-alert": "Alerte document",
"owner-subscription-expired": "L'abonnement du propriétaire a expiré.",
"owner-doesnt-have-paid-plan": "Le propriétaire n'a pas de plan payant.",
"subscription-expired": "Abonnement expiré",
"alert-message": "Message d'alerte",
"document-decline": "Document-refusé",
@@ -670,7 +692,7 @@
"public-template-mssg-1": "Pour intégrer OpenSign dans votre projet React ou Next.js, exécutez simplement la commande suivante :",
"public-template-mssg-2": "Assurez-vous que npm ou Yarn est configuré dans votre projet. Si vous utilisez Yarn, vous pouvez remplacer npm install par Yarn Add @opensign/react.",
"public-template-mssg-3": "Besoin de plus de détails ou d'exemples ?",
"public-template-mssg-4": "Visitez le",
"public-template-mssg-4": "Visitez le ",
"public-template-mssg-5": "npm pour les dernières mises à jour, une documentation détaillée et l'historique des versions.",
"public-template-mssg-6": "Avant de pouvoir utiliser cet extrait de code, vous devez rendre ce modèle public.",
"public-template-mssg-7": "Avant de pouvoir générer un lien public, vous devez rendre ce modèle public.",
@@ -686,7 +708,7 @@
"quota-mail-info": "Pour maintenir la qualité du service et prévenir le spam, OpenSign permet jusqu'à 15 e-mails par mois avec le plan gratuit. Passez à l'offre supérieure pour un envoi illimité d'e-mails.",
"quota-mail-reset": "Les crédits de votre email de demande de signature seront réinitialisés le",
"quota-mail": "Vous avez atteint votre limite de 15 e-mails de demande de signature pour ce mois. Mettez à niveau maintenant pour continuer à envoyer des e-mails directement.",
"quota-mail-tip-tip": "Astuce: Vous pouvez toujours signer un nombre <1>illimité de documents</1> en partageant manuellement le lien de demande de signature.",
"quota-mail-tip": "Astuce: Vous pouvez toujours signer un nombre <1>illimité de documents</1> en partageant manuellement le lien de demande de signature.",
"quota-mail-head": "Quota atteint",
"unauthorized-modal": "Vous n'êtes pas autorisé à effectuer cette action, veuillez contacter {{adminEmail}}.",
"sent-this-month": "envoyé ce mois-ci",
@@ -718,7 +740,7 @@
"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": "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'.",
"text-field-tour": "Les champs 'Pré-remplissage' doivent être remplis avant l'envoi du document. Si vous avez besoin de l'intervention des signataires, utilisez plutôt les champs réservés aux signataires.",
"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",
"at-least-one-signature-type": "Au moins un type de signature doit être activé.",
@@ -744,12 +766,15 @@
"delete-page": "supprimer la page",
"merge-pdf": "Fusionner le pdf",
"add-pages": "Ajouter des pages",
"reorder-pages": "Réorganiser les pages",
"delete-alert": "Impossible de supprimer une seule page",
"delete-alert-2": "Etes-vous sûr de vouloir supprimer cette page ?",
"delete-note": "Remarque : une fois cette page supprimée, vous ne pouvez plus l'annuler",
"Rotation-alert": "Faire pivoter la page",
"bulk-import": "Importation en masse",
"contacts-file": "Fichier de contacts (xlsx, csv)",
"import-guideline": "Téléchargez un fichier CSV ou Excel avec les colonnes Name, Email et éventuellement Phone. Seuls les 100 premiers contacts seront importés.",
"download-sample": "Télécharger un fichier d'exemple",
"100-records-only": "Actuellement, vous ne pouvez importer que 100 enregistrements.",
"csv-excel-support-only": "Veuillez télécharger un fichier dans l'un des formats suivants : CSV, XLSX ou XLS.",
"contact-imported": "{{imported}} contacts ont été importés. {{failed}} contacts n'ont pas pu être importés.",
@@ -763,7 +788,7 @@
"agree-p1": "Je confirme avoir lu et compris les ",
"agree-p2": "Divulgation des enregistrements électroniques et des signatures",
"agree-p3": "et consentez à l'utilisation d'enregistrements et de signatures électroniques.",
"agrre-button": " Accepter et continuer ",
"agrre-button": "Je confirme et j'accepte de 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 {{appName}} ( Plateforme ). En signant des documents via {{appName}}, vous acceptez les conditions décrites dans cette divulgation. Veuillez la lire attentivement avant de continuer.",
@@ -971,5 +996,150 @@
"kiosk-sign": "Signature sur kiosque",
"dont-have-access-to-template": "Le template a été supprimé ou vous n'y avez pas accès. Veuillez contacter l'expéditeur.",
"kiosk-info": "Le Mode Kiosque vous permet de recueillir des signatures en personne rapidement et efficacement. Idéal pour les salons, événements ou situations où tous les signataires sont physiquement présents. ",
"learn-more": "En savoir plus"
"learn-more": "En savoir plus",
"finish-mssg": "Êtes-vous sûr de vouloir terminer le document ?",
"review": "Revoir",
"next-field": "Champ suivant",
"required-mssg":"{{leftRequiredWidget}} champs sur {{totalWidget}} restants",
"verify-document": "Vérifier le document",
"verify-document-signature": "Vérifier la signature du document",
"select-pdf-document": "Sélectionner le document PDF",
"selected-file": "Fichier sélectionné",
"verify-signature": "Vérifier la signature",
"verification-status": "État de la vérification",
"verification-in-progress": "Vérification en cours...",
"verification-results-will-appear-here": "Les résultats de la vérification apparaîtront ici",
"please-select-pdf": "Veuillez sélectionner un fichier PDF valide",
"please-select-file-to-verify": "Veuillez sélectionner un fichier à vérifier",
"no-signature-found": "Aucune signature trouvée dans le document",
"error-verifying-pdf": "Erreur lors de la vérification du PDF",
"signature-valid-basic": "La signature est valide",
"signature-invalid-basic": "La signature est invalide",
"all-signatures-verified-convincing": "Document vérifié : Toutes les signatures ont été validées avec succès.",
"some-signatures-invalid-basic": "Certaines signatures sont invalides",
"no-signatures-processed": "Aucune signature traitée",
"unnamed-signature-field": "Champ de signature sans nom",
"error-processing-signature": "Erreur lors du traitement de la signature",
"signer-info-not-available": "Informations sur le signataire non disponibles",
"cert-validity-not-checked": "Validité du certificat non vérifiée",
"valid": "Valide",
"expired-or-not-yet-valid": "Expiré ou pas encore valide",
"valid-from": "Valide du",
"to": "au",
"signer": "Signataire",
"issuer": "Émetteur",
"not-available": "Non disponible",
"not-performed": "Non effectué",
"missing-acrofield-dict": "Dictionnaire Acrofield manquant",
"signature-dictionary-not-found-or-invalid": "Dictionnaire de signatures introuvable ou invalide",
"missing-or-invalid-byterange": "ByteRange manquant ou invalide",
"missing-or-invalid-contents": "Contenu manquant ou invalide",
"missing-signature-contents": "Contenu de la signature manquant",
"invalid-signature-hex-format": "Format hexadécimal de signature invalide",
"unsupported-signature-format-not-signeddata": "Format de signature non pris en charge - pas SignedData",
"signer-certificate-not-found": "Certificat du signataire introuvable",
"no-certificates-in-signature": "Aucun certificat dans la signature",
"no-signer-info-in-pkcs7": "Aucune information sur le signataire dans PKCS#7",
"could-not-parse-signer-info": "Impossible d'analyser les informations sur le signataire",
"not-calculated": "Non calculé",
"not-found-in-signature": "Introuvable dans la signature",
"readonly-error": "Le widget en lecture seule {{widgetName}} doit avoir une valeur par défaut ou vous pouvez le rendre facultatif.",
"choose-one":"Choisissez-en un",
"search-templates": "Rechercher des modèles…",
"search-documents": "Rechercher des documents…",
"search-contacts": "Rechercher des contacts…",
"add-role-alert": "Veuillez ajouter au moins un rôle",
"edit-draft": "Modifier le brouillon",
"invalid-email-found": "Adresse e-mail invalide trouvée : {{email}}",
"duplicate-email-found": "Adresse e-mail en double trouvée : {{email}}",
"vertical": "Vertical",
"horizontal": "Horizontal",
"billing": "Facturation",
"console": "Console",
"prefill-widget": "Widgets de Préremplissage",
"action-prohibited": "Cette action n'est pas autorisée pour votre domaine de messagerie. Veuillez contacter votre administrateur pour obtenir de l'aide.",
"must-have-at-least-one-vacant-role": "Vous devez laisser au moins un rôle non attribué avant de définir un template comme 'public'.",
"remove-duplicate": "Veuillez supprimer l'option en double",
"prefill-bulk-error": "L'envoi en masse n'est pas autorisé lorsque des widgets de pré-remplissage sont ajoutés. Veuillez supprimer les widgets de pré-remplissage pour continuer.",
"session-expired-title": "Session expirée",
"access-denied": "Accès refusé",
"upgrade": "Mettre à jour",
"do-not-access-app": "Vous n'avez pas accès à cette application.",
"dont-have-access": "Vous n'avez pas accès.",
"valid-email-alert": "Veuillez saisir une adresse e-mail valide.",
"otp-not-validate": "OTP non valide.",
"domain-not-allowed": "Ce domaine n'est pas autorisé",
"atleast-one-recipient-alert": "Veuillez ajouter au moins un destinataire !",
"incorrect-password-or-decryption-failed": "Mot de passe incorrect ou échec du déchiffrement.",
"incorrect-password-for-file": "Mot de passe incorrect pour le fichier : {{file}}",
"error-uploading-pdf": "Erreur lors du téléchargement du PDF.",
"provide-password": "Veuillez fournir le mot de passe.",
"only-pdf-allowed": "Seuls les fichiers PDF sont autorisés.",
"invalid-username-password-region": "Nom d'utilisateur/mot de passe ou région invalide.",
"pfx-extension-alert": "Veuillez télécharger un fichier avec l'extension .pfx.",
"email-already-exist": "L'e-mail existe déjà",
"branding": "Image de marque",
"branding-help": "Le branding permet le white labelling de votre application",
"custom-sub-domain": "Sous-domaine personnalisé",
"app-name": "Nom de l'application",
"provide-domain-name": "Fournissez votre nom de domaine",
"provide-app-name": "Fournissez le nom de votre application",
"logo": "Logo",
"upload-app-logo": "Téléchargez le logo de votre application",
"prefill-unfilled-widget": "Les champs obligatoires suivants ne peuvent pas être vides : {{emptyWidget}}. Veuillez les remplir pour continuer.",
"Dashboard": "Tableau de bord",
"Analytics": "Analytique",
"Templates": "Modèles",
"Need your sign": "Besoin de votre signature",
"In Progress": "En cours",
"Completed": "Complété",
"Drafts": "Brouillons",
"Declined": "Signature refusé",
"Expired": "Expiré",
"Contactbook": "Carnet de contacts",
"My Signature": "Ma signature",
"API Token": "Jeton API",
"Webhook": "Webhook",
"Preferences": "Préférences",
"Teams": "Équipe",
"Users": "Utilisateurs",
"Drive": "Lecteur",
"Branding": "Image de marque",
"Mail": "Courrier",
"Storage": "Stockage",
"Signing certificate": "Certificat de signature",
"General": "Général",
"Organizations": "Organisations",
"OrgAdmins": "OrgAdmins",
"Debug Pdf": "Déboguer le PDF",
"New Document": "Nouveau document",
"subscription": "Abonnement",
"Draft document": "Brouillon de document",
"Draft template": "Brouillon de modèle",
"Public sign": "Signature publique",
"Signup": "Inscription",
"delete-contact": "Supprimer le contact",
"total-records-found": "Nombre total d'enregistrements trouvés : {{count}}",
"Invalid-records-found": "Enregistrements invalides trouvés : {{records}}",
"previous": "Précédent",
"page-n-of-n": "Page {{currentPage}} sur {{totalPages}}",
"import": "Importer",
"search": "Rechercher",
"viewed-on": "Consulté le : {{ViewedOn}}",
"signed-on": "Signé le : {{SignedOn}}",
"hide": "Masquer",
"show-more": "Afficher plus",
"browse-or-drag-to-replace-existing-file": "Parcourez ou faites glisser un nouveau fichier pour remplacer l'existant",
"optional-details": "Détails facultatifs",
"mail-adapter-subscription-alert": "Veuillez passer au plan Professionnel ou Équipe pour configurer un SMTP personnalisé.",
"connect-to-mail": "Se connecter à Gmail",
"custom-smtp": "SMTP personnalisé",
"default-smtp": "SMTP par défaut de {{appName}}",
"host": "Hôte",
"port": "Port",
"sender-email": "Email de l'expéditeur",
"username": "Nom d'utilisateur",
"use-default-mail-adapter": "Voulez-vous vraiment utiliser les serveurs de messagerie par défaut de {{appName}} pour envoyer vos demandes de signature ? Nous vous recommandons d'utiliser votre propre serveur Gmail ou SMTP pour une meilleure délivrabilité.",
"verification-code-sent-registered-email": "Un code de vérification a été envoyé à votre email enregistré <1>{{useremail}}</1>. Veuillez entrer le code ci-dessous pour confirmer vos paramètres.",
"smpt-credentials": "Identifiants SMTP"
}
File diff suppressed because it is too large Load Diff
+182 -13
View File
@@ -1,9 +1,11 @@
{
"header-news": "Nuova funzionalità: Gli utenti del piano Teams possono ora integrare i propri bucket AWS S3 per l'archiviazione dei file",
"header-news-btn": "Configura Ora",
"sandbox-news": "Questo è un ambiente sandbox. Si prega di non utilizzarlo per scopi di produzione.",
"create-account": "Crea Account",
"login": "Accedi",
"language": "Lingua",
"dark-mode": "Modalità scura",
"name": "Nome",
"phone": "Telefono",
"phone-optional": "facoltativo",
@@ -24,6 +26,8 @@
"Name": "Nome",
"Date": "Data"
},
"folder": "Cartella",
"pdf": "Pdf",
"context-menu": {
"Download": "Scarica",
"Rename": "Rinomina",
@@ -39,8 +43,14 @@
"save": "Salva",
"cancel": "Annulla",
"upgrade-now": "Aggiorna ora",
"contact-now": "Contatta ora",
"upgrade-to": "Aggiorna a",
"plan": "Piano",
"connect": "Connetti",
"connect-to-g-drive": "Connetti a Google Drive",
"reconnect-to-g-drive": "Riconnetti a Google Drive",
"gdrive-info-connect": "Quando Google Drive è connesso, il documento completato verrà salvato nella cartella {{appName}} su Google Drive.",
"subscription-renew-warning": "Il tuo abbonamento scadrà tra {{remainingDays}} giorni. Ti preghiamo di rinnovarlo.",
"subscribe-card-teamplan": "Sblocca tutto il potenziale della collaborazione! Crea organizzazioni, team e gerarchie illimitati. Condividi modelli senza problemi tra i team e assegna ruoli personalizzati agli utenti. Migliora il tuo flusso di lavoro oggi stesso!",
"subscribe-card-plan": "Sblocca le funzionalità premium a partire da soli {{premiumPrice}}/mese. Approfitta di prestazioni migliorate e paga solo {{addonPrice}} per ogni credito aggiuntivo dopo quelli inclusi.",
"user-name-limit-char": "Per un nome utente con meno di 8 caratteri, abbonati",
@@ -54,7 +64,7 @@
"welcome": "Bentornato!",
"Login-to-your-account": "Accedi al tuo account",
"password": "Password",
"forgot-password": "Password dimenticata?",
"forgot-password": "Password dimenticata",
"loading": "Caricamento...",
"of": "di",
"sign-SSO": "Accedi con SSO",
@@ -142,6 +152,7 @@
"Quick send": "Invio rapido",
"Edit": "Modifica",
"Share with team": "Condividi con il team",
"Share with user": "Condividi con un collega",
"Share": "Condividi",
"View": "Visualizza",
"option": "Opzione",
@@ -176,7 +187,9 @@
"created-date": "Data di creazione",
"Type": "Tipo",
"Logs": "Log",
"Expiry-date": "Data di scadenza"
"Expiry-date": "Data di scadenza",
"Company": "Azienda",
"JobTitle": "Titolo professionale"
},
"report-help": {
"Draft Documents": "Questi sono documenti che hai iniziato ma non hai ancora finalizzato per l'invio.",
@@ -188,16 +201,15 @@
"Contactbook": "Questa è una lista di contatti/firmatari aggiunti da te. Appariranno come suggerimenti quando provi ad aggiungere firmatari a un nuovo documento.",
"Templates": "Questa è una lista di modelli disponibili per creare documenti. Puoi fare clic sul pulsante 'Usa' per creare un nuovo documento utilizzando un modello, modificare il documento e aggiungere firmatari nel passaggio successivo."
},
"form-name": {
"Sign Yourself": "Firma tu stesso",
"Request Signatures": "Richiedi firme",
"New Template": "Nuovo modello"
},
"Sign Yourself": "Firma tu stesso",
"Request Signatures": "Richiedi firme",
"New Template": "Nuovo modello",
"file-type": "pdf, png, jpg, jpeg",
"docx": "docx",
"file-selected": "file selezionato",
"template-title": "Titolo del modello",
"document-title": "Titolo del documento",
"title": "Titolo",
"description": "Descrizione",
"time-to-complete": "Tempo per completare (giorni)",
"send-in-order": "Invia in ordine",
@@ -245,6 +257,7 @@
"API": "API",
"api-token": "Token API",
"regenerate-token": "Rigenera token live",
"remove-background": "Rimuovi sfondo",
"generate-token": "Genera token live",
"view-docs": "Visualizza documenti",
"generate-token-alert": "Sei sicuro di voler rigenerare il token? Questo invaliderà il vecchio token.",
@@ -356,6 +369,7 @@
"date": "data",
"text": "testo",
"text input": "campo di testo",
"cells": "cellule",
"checkbox": "casella di controllo",
"dropdown": "menu a tendina",
"radio button": "pulsante di opzione",
@@ -372,6 +386,7 @@
"certificate": "Certificato",
"decline": "Rifiuta",
"finish": "Completa",
"done": "Fatto",
"mail": "Email",
"sign-now": "Firma ora",
"successfully-signed": "Firmato con successo!",
@@ -381,6 +396,10 @@
"Email-verified-alert-1": "Email verificata.",
"Email-verified-alert-2": "Email già verificata.",
"upload-stamp-image": "Carica immagine timbro",
"draw-signature": "Disegna la firma",
"draw-initials": "Disegna le iniziali",
"enter-text": "Inserisci testo",
"enter-widgettype": "Inserisci {{widgetType}}",
"draw": "Disegno",
"type": "Digitata",
"color-type": {
@@ -410,10 +429,12 @@
"options": "Opzioni",
"minimun-check": "Controllo minimo",
"maximum-check": "Controllo massimo",
"cell-count": "conteggio delle cellule",
"default-value": "Valore predefinita",
"select": "Seleziona",
"read-only": "È solo lettura",
"read-only": di sola lettura",
"hide-labels": "Nascondi etichette",
"layout": "Layout",
"checkbox": "Casella di controllo",
"alert": "Avviso",
"zoom-in": "Ingrandisci",
@@ -462,7 +483,7 @@
"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-mail-alert-you": "I firmatari successivi riceveranno un'email dopo che avrai firmato 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!",
@@ -477,6 +498,7 @@
"mail-not-delivered": "Mail non consegnata",
"document-alert": "Avviso Documento",
"owner-subscription-expired": "L'abbonamento del proprietario è scaduto.",
"owner-doesnt-have-paid-plan": "Il proprietario non ha un piano a pagamento.",
"subscription-expired": "Abbonamento Scaduto",
"alert-message": "Messaggio di avviso",
"document-decline": "Documento rifiutato",
@@ -670,7 +692,7 @@
"public-template-mssg-1": "Per integrare OpenSign nel tuo progetto React o Next.js, esegui semplicemente il seguente comando:",
"public-template-mssg-2": "Assicurati di avere npm o yarn configurato nel tuo progetto. Se usi Yarn, puoi sostituire npm install con yarn add @opensign/react.",
"public-template-mssg-3": "Hai bisogno di maggiori dettagli o esempi?",
"public-template-mssg-4": "Visita il",
"public-template-mssg-4": "Visita il ",
"public-template-mssg-5": " npm per gli aggiornamenti più recenti, documentazione dettagliata e cronologia delle versioni.",
"public-template-mssg-6": "Prima di poter utilizzare questo frammento di codice, devi rendere questo modello pubblico.",
"public-template-mssg-7": "Prima di poter generare un link pubblico, devi rendere questo modello pubblico.",
@@ -718,7 +740,7 @@
"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, 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'.",
"text-field-tour": "I campi 'Prefill' devono essere compilati in anticipo prima dell'invio del documento. Se hai bisogno che i firmatari forniscano input, usa invece i campi dei firmatari.",
"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",
"at-least-one-signature-type": "Almeno un tipo di firma deve essere abilitato.",
@@ -744,12 +766,15 @@
"delete-page": "Elimina pagina",
"merge-pdf": "Unisci PDF",
"add-pages": "Aggiungi pagine",
"reorder-pages": "Riordina pagine",
"delete-alert": "Non è possibile eliminare una singola pagina",
"delete-alert-2": "Sei sicuro di voler eliminare questa pagina?",
"delete-note": "Nota: Una volta eliminata questa pagina, non potrai annullare l'operazione.",
"Rotation-alert": "Ruota pagina",
"bulk-import": "Importazione massiva",
"contacts-file": "File contatti (xlsx, csv)",
"import-guideline": "Carica un file CSV o Excel con le colonne Name, Email e opzionale Phone. Verranno importati solo i primi 100 contatti.",
"download-sample": "Scarica file di esempio",
"100-records-only": "Attualmente puoi importare solo fino a 100 record.",
"csv-excel-support-only": "Carica un file nei seguenti formati: CSV, XLSX o XLS.",
"contact-imported": "{{imported}} contatti importati. {{failed}} contatti non sono stati importati.",
@@ -763,7 +788,7 @@
"agree-p1": "Confermo di aver letto e compreso il",
"agree-p2": "Divulgazione sulle registrazioni e firme elettroniche",
"agree-p3": "e acconsento all'uso di registrazioni e firme elettroniche.",
"agrre-button": "Accetta e Continua",
"agrre-button": "Confermo e accetto di continuare",
"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 {{appName}} (Piattaforma). Firmando documenti tramite {{appName}}, accetti i termini descritti in questa Divulgazione. Ti preghiamo di leggerla attentamente prima di procedere.",
@@ -971,5 +996,149 @@
"kiosk-sign": "Firma su chiosco",
"dont-have-access-to-template": "Il template è stato eliminato o non hai accesso. Si prega di contattare il mittente.",
"kiosk-info": "La Modalità Kiosk consente di raccogliere firme in presenza in modo rapido ed efficiente. Ideale per fiere, eventi o situazioni con firmatari fisicamente presenti. ",
"learn-more": "Scopri di più"
"learn-more": "Scopri di più",
"finish-mssg": "Sei sicuro di voler completare il documento?",
"review": "Rivedere",
"next-field": "Campo successivo",
"required-mssg":"{{leftRequiredWidget}} di {{totalWidget}} campi rimanenti",
"verify-document": "Verifica documento",
"verify-document-signature": "Verifica firma documento",
"select-pdf-document": "Seleziona documento PDF",
"selected-file": "File selezionato",
"verify-signature": "Verifica firma",
"verification-status": "Stato verifica",
"verification-in-progress": "Verifica in corso...",
"verification-results-will-appear-here": "I risultati della verifica appariranno qui",
"please-select-pdf": "Seleziona un file PDF valido",
"please-select-file-to-verify": "Seleziona un file da verificare",
"no-signature-found": "Nessuna firma trovata nel documento",
"error-verifying-pdf": "Errore durante la verifica del PDF",
"signature-valid-basic": "La firma è valida",
"signature-invalid-basic": "La firma non è valida",
"all-signatures-verified-convincing": "Documento Verificato: Tutte le firme sono state validate con successo.",
"some-signatures-invalid-basic": "Alcune firme non sono valide",
"no-signatures-processed": "Nessuna firma elaborata",
"unnamed-signature-field": "Campo firma senza nome",
"error-processing-signature": "Errore durante l'elaborazione della firma",
"signer-info-not-available": "Informazioni firmatario non disponibili",
"cert-validity-not-checked": "Validità certificato non verificata",
"valid": "Valido",
"expired-or-not-yet-valid": "Scaduto o non ancora valido",
"valid-from": "Valido dal",
"to": "al",
"signer": "Firmatario",
"issuer": "Emittente",
"not-available": "Non disponibile",
"not-performed": "Non eseguito",
"missing-acrofield-dict": "Dizionario Acrofield mancante",
"signature-dictionary-not-found-or-invalid": "Dizionario firme non trovato o non valido",
"missing-or-invalid-byterange": "ByteRange mancante o non valido",
"missing-or-invalid-contents": "Contenuto mancante o non valido",
"missing-signature-contents": "Contenuto firma mancante",
"invalid-signature-hex-format": "Formato esadecimale firma non valido",
"unsupported-signature-format-not-signeddata": "Formato firma non supportato - non SignedData",
"signer-certificate-not-found": "Certificato firmatario non trovato",
"no-certificates-in-signature": "Nessun certificato nella firma",
"no-signer-info-in-pkcs7": "Nessuna informazione firmatario in PKCS#7",
"could-not-parse-signer-info": "Impossibile analizzare le informazioni del firmatario",
"not-calculated": "Non calcolato",
"not-found-in-signature": "Non trovato nella firma",
"readonly-error": "Il widget {{widgetName}} di sola lettura deve avere un valore predefinito oppure può essere reso facoltativo.",
"choose-one":"Scegline uno",
"search-templates": "Cerca modelli…",
"search-documents": "Cerca documenti…",
"search-contacts": "Cerca contatti…",
"add-role-alert": "Si prega di aggiungere almeno un ruolo",
"edit-draft": "Modifica bozza",
"invalid-email-found": "Email non valida trovata: {{email}}",
"duplicate-email-found": "Email duplicata trovata: {{email}}",
"vertical": "Verticale",
"horizontal": "Orizzontale",
"billing": "Fatturazione",
"console": "Console",
"prefill-widget": "Widget Precompilati",
"action-prohibited": "Questa azione non è consentita per il tuo dominio email. Contatta il tuo amministratore per ricevere assistenza.",
"must-have-at-least-one-vacant-role": "Devi lasciare almeno un ruolo non assegnato prima di impostare un template su 'public'.",
"remove-duplicate": "Si prega di rimuovere l'opzione duplicata",
"prefill-bulk-error": "L'invio in blocco non è consentito quando sono stati aggiunti i widget di precompilazione. Si prega di rimuovere i widget di precompilazione per procedere.",
"session-expired-title": "Sessione scaduta",
"access-denied": "Accesso negato",
"upgrade": "Aggiorna",
"do-not-access-app": "Non hai accesso a questa applicazione.",
"dont-have-access": "Non hai accesso.",
"valid-email-alert": "Inserisci un indirizzo email valido.",
"otp-not-validate": "OTP non valido.",
"domain-not-allowed": "Questo dominio non è consentito",
"atleast-one-recipient-alert": "Aggiungi almeno un destinatario!",
"incorrect-password-or-decryption-failed": "Password errata o decrittazione non riuscita.",
"incorrect-password-for-file": "Password errata per il file: {{file}}",
"error-uploading-pdf": "Errore durante il caricamento del PDF.",
"provide-password": "Fornisci la password.",
"only-pdf-allowed": "Sono consentiti solo file PDF.",
"invalid-username-password-region": "Nome utente/password o regione non valida.",
"pfx-extension-alert": "Carica un file con estensione .pfx.",
"email-already-exist": "L'email esiste già",
"branding": "Marchio",
"branding-help": "Il branding consente il white labelling della tua app",
"custom-sub-domain": "Sottodominio personalizzato",
"app-name": "Nome dell'app",
"provide-domain-name": "Fornisci il tuo nome di dominio",
"provide-app-name": "Fornisci il nome della tua app",
"logo": "Logo",
"upload-app-logo": "Carica il logo della tua app",
"prefill-unfilled-widget": "I seguenti campi obbligatori non possono essere lasciati vuoti: {{emptyWidget}}. Per favore compilali per procedere.",
"Dashboard": "Dashboard",
"Analytics": "Analitica",
"Templates": "Modelli",
"Need your sign": "Necessita della tua firma",
"In Progress": "In corso",
"Completed": "Completati",
"Drafts": "Bozze",
"Declined": "Rifiutati",
"Expired": "Scaduti",
"Contactbook": "Rubrica",
"My Signature": "La mia firma",
"API Token": "Token API",
"Webhook": "Webhook",
"Preferences": "Preferenze",
"Teams": "Team",
"Users": "Utenti",
"Drive": "Drive",
"Branding": "Marchio",
"Mail": "Posta",
"Storage": "Archiviazione",
"Signing certificate": "Certificato di firma",
"General": "Generale",
"Organizations": "Organizzazioni",
"OrgAdmins": "OrgAdmins",
"Debug Pdf": "Debug PDF",
"New Document": "Nuovo documento",
"subscription": "Abbonamento",
"Draft document": "Bozza di documento",
"Draft template": "Bozza di modello",
"Public sign": "Firma pubblica",
"Signup": "Registrazione",
"delete-contact": "Elimina contatto",
"total-records-found": "Totale record trovati: {{count}}",
"Invalid-records-found": "Record non validi trovati: {{records}}",
"previous": "Precedente",
"page-n-of-n": "Pagina {{currentPage}} di {{totalPages}}",
"import": "Importa",
"search": "Cerca",
"viewed-on": "Visualizzato il: {{ViewedOn}}",
"signed-on": "Firmato il: {{SignedOn}}",
"hide": "Nascondi",
"show-more": "Mostra di più",
"browse-or-drag-to-replace-existing-file": "Sfoglia o trascina un nuovo file per sostituire quello esistente",
"optional-details": "Dettagli facoltativi",
"mail-adapter-subscription-alert": "Esegui l'upgrade al piano Professional o Team per configurare un SMTP personalizzato.", "connect-to-mail": "Connetti a Gmail",
"custom-smtp": "SMTP personalizzato",
"default-smtp": "SMTP predefinito di {{appName}}",
"host": "Host",
"port": "Porta",
"sender-email": "Email del mittente",
"username": "Nome utente",
"use-default-mail-adapter": "Sei sicuro di voler usare i server di posta predefiniti di {{appName}} per inviare le richieste di firma? Ti consigliamo di usare i tuoi server Gmail o SMTP per una migliore consegna in posta in arrivo.",
"verification-code-sent-registered-email": "Un codice di verifica è stato inviato alla tua email registrata <1>{{useremail}}</1>. Inserisci il codice qui sotto per confermare le impostazioni.",
"smpt-credentials": "Credenziali SMTP"
}
+4
View File
@@ -0,0 +1,4 @@
Name,Email,Phone
John Doe,john@example.com,1234567890
Jane Smith,jane@example.com,9876543210
Foo Bar,foo@example.com,5555555555
1 Name Email Phone
2 John Doe john@example.com 1234567890
3 Jane Smith jane@example.com 9876543210
4 Foo Bar foo@example.com 5555555555
Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.
+1
View File
@@ -0,0 +1 @@
import "@testing-library/jest-dom";
@@ -1,7 +1,6 @@
import React, { useState, useEffect, lazy } from "react";
import { useState, useEffect, lazy } from "react";
import { Routes, Route, BrowserRouter } from "react-router";
import { pdfjs } from "react-pdf";
import Login from "./pages/Login";
import Form from "./pages/Form";
import Report from "./pages/Report";
import Dashboard from "./pages/Dashboard";
@@ -30,7 +29,8 @@ const ManageSign = lazy(() => import("./pages/Managesign"));
const AddAdmin = lazy(() => import("./pages/AddAdmin"));
const UpdateExistUserAdmin = lazy(() => import("./pages/UpdateExistUserAdmin"));
const Preferences = lazy(() => import("./pages/Preferences"));
const Login = lazy(() => import("./pages/Login"));
const VerifyDocument = lazy(() => import("./pages/VerifyDocument"));
pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/legacy/build/pdf.worker.min.mjs`;
const AppLoader = () => {
return (
@@ -67,7 +67,7 @@ function App() {
<BrowserRouter>
<Routes>
<Route element={<ValidateRoute />}>
<Route exact path="/" element={<Login />} />
<Route exact path="/" element={<LazyPage Page={Login} />} />
<Route
path="/addadmin"
element={<LazyPage Page={AddAdmin} />}
@@ -106,10 +106,10 @@ function App() {
element={<LazyPage Page={GuestLogin} />}
/>
<Route path="/debugpdf" element={<LazyPage Page={DebugPdf} />} />
<Route
path="/forgetpassword"
element={<LazyPage Page={ForgetPassword} />}
/>
<Route
path="/forgetpassword"
element={<LazyPage Page={ForgetPassword} />}
/>
<Route
element={
<ValidateSession>
@@ -117,10 +117,10 @@ function App() {
</ValidateSession>
}
>
<Route
path="/changepassword"
element={<LazyPage Page={ChangePassword} />}
/>
<Route
path="/changepassword"
element={<LazyPage Page={ChangePassword} />}
/>
<Route path="/form/:id" element={<Form />} />
<Route path="/report/:id" element={<Report />} />
<Route path="/dashboard/:id" element={<Dashboard />} />
@@ -142,7 +142,7 @@ function App() {
/>
{/* signyouself route with no rowlevel data using docId from url */}
<Route path="/signaturePdf/:docId" element={<SignYourSelf />} />
{/* draft document route to handle and navigate route page accordiing to document status */}
{/* draft document route to handle and navigate route page according to document status */}
<Route path="/draftDocument" element={<DraftDocument />} />
{/* recipient placeholder set route with no rowlevel data using docId from url*/}
<Route
@@ -165,7 +165,11 @@ function App() {
path="/recipientSignPdf/:docId"
element={<PdfRequestFiles />}
/>
<Route path="/users" element={<UserList />} />
<Route path="/users" element={<UserList />} />
<Route
path="/verify-document"
element={<LazyPage Page={VerifyDocument} />}
/>
<Route
path="/preferences"
element={<LazyPage Page={Preferences} />}
-275
View File
@@ -1,275 +0,0 @@
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 { useTranslation } from "react-i18next";
function generatePassword(length) {
const characters =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let result = "";
const charactersLength = characters.length;
for (let i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}
const AddUser = (props) => {
const { t } = useTranslation();
const [formdata, setFormdata] = useState({
name: "",
phone: "",
email: "",
team: "",
password: "",
role: ""
});
const [isFormLoader, setIsFormLoader] = useState(false);
const [teamList, setTeamList] = useState([]);
const role = ["OrgAdmin", "Editor", "User"];
useEffect(() => {
getTeamList();
// eslint-disable-next-line
}, []);
const getTeamList = async () => {
setFormdata((prev) => ({ ...prev, password: generatePassword(12) }));
const teamRes = await Parse.Cloud.run("getteams", { active: true });
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 checkUserExist = async () => {
try {
const res = await Parse.Cloud.run("getUserDetails", {
email: formdata.email
});
if (res) {
return true;
} else {
return false;
}
} catch (err) {
console.log("err", err);
}
};
// Define a function to handle form submission
const handleSubmit = async (e) => {
e.preventDefault();
e.stopPropagation();
if (!emailRegex.test(formdata.email)) {
alert("Please enter a valid email address.");
} else {
const localUser = JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
setIsFormLoader(true);
const res = await checkUserExist();
if (res) {
props.showAlert("danger", t("user-already-exist"));
setIsFormLoader(false);
} else {
if (localStorage.getItem("TenantId")) {
const timezone = usertimezone;
try {
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
}
};
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);
setIsFormLoader(false);
props.showAlert("danger", t("something-went-wrong-mssg"));
}
} else {
props.showAlert("danger", t("something-went-wrong-mssg"));
}
}
}
};
// Define a function to handle the "add yourself" checkbox
const handleReset = () => {
setFormdata({ name: "", email: "", phone: "", team: "", role: "" });
if (props.closePopup) {
props.closePopup();
}
};
const handleChange = (event) => {
let { name, value } = event.target;
if (name === "email") {
value = value?.toLowerCase()?.replace(/\s/g, "");
}
setFormdata((prev) => ({ ...prev, [name]: value }));
};
const copytoclipboard = (text) => {
copytoData(text);
props.showAlert("success", t("copied"));
};
return (
<div className="shadow-md rounded-box my-[1px] p-3 bg-base-100 relative">
<Title title={t("add-user")} />
{isFormLoader && (
<div className="absolute w-full h-full inset-0 flex justify-center items-center bg-base-content/30 z-50">
<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>
);
};
export default AddUser;
+288
View File
@@ -0,0 +1,288 @@
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 {
useTranslation
} from "react-i18next";
function generatePassword(length) {
const characters =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let result = "";
const charactersLength = characters.length;
for (let i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}
const AddUser = (props) => {
const { t } = useTranslation();
const [formdata, setFormdata] = useState({
name: "",
phone: "",
email: "",
team: "",
password: "",
role: ""
});
const [isFormLoader, setIsFormLoader] = useState(false);
const [teamList, setTeamList] = useState([]);
const role = ["OrgAdmin", "Editor", "User"];
useEffect(() => {
getTeamList();
// eslint-disable-next-line
}, []);
const getTeamList = async () => {
setFormdata((prev) => ({ ...prev, password: generatePassword(12) }));
const teamRes = await Parse.Cloud.run("getteams", { active: true });
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 checkUserExist = async () => {
try {
const res = await Parse.Cloud.run("getUserDetails", {
email: formdata.email
});
if (res) {
return true;
} else {
return false;
}
} catch (err) {
console.log("err", err);
}
};
// Define a function to handle form submission
const handleSubmit = async (e) => {
e.preventDefault();
e.stopPropagation();
if (!emailRegex.test(formdata.email)) {
alert("Please enter a valid email address.");
} else {
const localUser = JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
setIsFormLoader(true);
const res = await checkUserExist();
if (res) {
props.showAlert("danger", t("user-already-exist"));
setIsFormLoader(false);
} else {
if (localStorage.getItem("TenantId")) {
const timezone = usertimezone;
try {
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
},
};
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);
setIsFormLoader(false);
props.showAlert("danger", t("something-went-wrong-mssg"));
}
} else {
props.showAlert("danger", t("something-went-wrong-mssg"));
}
}
}
};
// Define a function to handle the "add yourself" checkbox
const handleReset = () => {
setFormdata({ name: "", email: "", phone: "", team: "", role: "" });
if (props.closePopup) {
props.closePopup();
}
};
const handleChange = (event) => {
let { name, value } = event.target;
if (name === "email") {
value = value?.toLowerCase()?.replace(/\s/g, "");
}
setFormdata((prev) => ({ ...prev, [name]: value }));
};
const copytoclipboard = (text) => {
copytoData(text);
props.showAlert("success", t("copied"));
};
return (
<div className="shadow-md rounded-box my-[1px] p-3 bg-base-100 relative">
<Title title={t("add-user")} />
{isFormLoader && (
<div className="absolute w-full h-full inset-0 flex justify-center items-center bg-base-content/30 z-50">
<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>
);
};
export default AddUser;
@@ -3,7 +3,9 @@ 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([]);
@@ -13,22 +15,24 @@ const BulkSendUi = (props) => {
const [isSignatureExist, setIsSignatureExist] = useState();
const [isDisableBulkSend, setIsDisableBulkSend] = useState(false);
const [isLoader, setIsLoader] = useState(false);
const [signers, setSigners] = useState([]);
const [emails, setEmails] = useState([]);
useEffect(() => {
signatureExist();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
//function to check atleast one signature field exist
//function to check at least 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) {
@@ -45,7 +49,14 @@ const BulkSendUi = (props) => {
(() => {
if (props?.Placeholders?.length > 0) {
let users = [];
let emails = [];
props?.Placeholders?.forEach((element) => {
const signerEmail = element?.email || element?.signerPtr?.Email;
// only add when there's a non-empty signerEmail
if (signerEmail) {
emails = [...emails, signerEmail];
}
if (!element.signerObjId) {
users = [
...users,
@@ -58,7 +69,10 @@ const BulkSendUi = (props) => {
];
}
});
setEmails(emails);
setForms((prevForms) => [...prevForms, { Id: 1, fields: users }]);
const signer = props.item?.Signers?.filter((x) => x?.objectId);
setSigners(signer);
}
})();
// eslint-disable-next-line
@@ -72,6 +86,7 @@ const BulkSendUi = (props) => {
setForms(newForms);
};
const handleRemoveForm = (index) => {
const updatedForms = forms.filter((_, i) => i !== index);
setForms(updatedForms);
@@ -79,10 +94,16 @@ const BulkSendUi = (props) => {
function validateEmails(data) {
for (const item of data) {
let email = "";
for (const field of item.fields) {
if (!emailRegex.test(field.email)) {
alert(`Invalid email found: ${field.email}`);
alert(t("invalid-email-found", { email: field.email }));
return false;
} else if (email === field.email || emails?.includes(field.email)) {
alert(t("duplicate-email-found", { email: field.email }));
return false;
} else {
email = field.email;
}
}
}
@@ -92,83 +113,81 @@ 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
};
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);
}
}
// If no matching field is found, keep the placeholder as is
return placeholder;
});
});
// 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);
}
// Push a new document object with updated Placeholders into the Documents array
if (existSigner?.length > 0) {
Documents.push({
...props.item,
Placeholders: updatedPlaceholders,
Signers: signers ? [...signers, ...existSigner] : [...existSigner]
});
} else {
Documents.push({
...props.item,
Placeholders: updatedPlaceholders,
SignatureType: props.signatureType,
Signers: signers
});
}
});
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 {
@@ -275,7 +294,8 @@ const BulkSendUi = (props) => {
)}
</>
) : (
<></>
<>
</>
)}
</>
)}
@@ -1,6 +1,7 @@
import React, { useState, useEffect } from "react";
import { useState, useEffect } from "react";
import dp from "../assets/images/dp.png";
import FullScreenButton from "./FullScreenButton";
import ThemeToggle from "./ThemeToggle";
import { useNavigate } from "react-router";
import Parse from "parse";
import { useWindowSize } from "../hook/useWindowSize";
@@ -18,8 +19,13 @@ const Header = ({ showSidebar, setIsMenu, isConsole }) => {
const { width } = useWindowSize();
const username = localStorage.getItem("username") || "";
const image = localStorage.getItem("profileImg") || dp;
const isAdmin =
localStorage.getItem("Extand_Class") &&
JSON.parse(localStorage.getItem("Extand_Class"))?.[0]?.UserRole ===
"contracts_Admin";
const [isOpen, setIsOpen] = useState(false);
const [applogo, setAppLogo] = useState("");
const [isDarkTheme, setIsDarkTheme] = useState();
const toggleDropdown = () => {
setIsOpen(!isOpen);
@@ -32,14 +38,15 @@ const Header = ({ showSidebar, setIsMenu, isConsole }) => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
async function initializeHead() {
const applogo = await getAppLogo();
if (applogo?.logo) {
setAppLogo(applogo?.logo);
} else {
const logo = localStorage.getItem("appLogo") || appInfo.applogo;
setAppLogo(logo);
}
const applogo = await getAppLogo();
if (applogo?.logo) {
setAppLogo(applogo?.logo);
} else {
const logo = localStorage.getItem("appLogo") || appInfo.applogo;
setAppLogo(logo);
}
}
const closeDropdown = async () => {
@@ -84,9 +91,30 @@ const Header = ({ showSidebar, setIsMenu, isConsole }) => {
};
}, [isOpen]);
useEffect(() => {
const updateThemeStatus = () => {
const isDarkTheme =
document.documentElement.getAttribute("data-theme") === "opensigndark";
setIsDarkTheme(isDarkTheme);
};
updateThemeStatus();
const observer = new MutationObserver(() => {
updateThemeStatus();
});
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ["data-theme"]
});
return () => observer.disconnect();
}, []);
return (
<div>
<div className="op-navbar bg-base-100 shadow">
<div className="op-navbar bg-base-100 shadow touch-none">
<div className="flex-none">
<button
className="op-btn op-btn-square op-btn-ghost focus:outline-none hover:bg-transparent op-btn-sm no-animation"
@@ -100,7 +128,11 @@ const Header = ({ showSidebar, setIsMenu, isConsole }) => {
{applogo && (
<img
className="object-contain h-full w-auto"
src={applogo}
src={
isDarkTheme
? "/static/js/assets/images/logo-dark.png"
: applogo
}
alt="logo"
/>
)}
@@ -146,21 +178,21 @@ const Header = ({ showSidebar, setIsMenu, isConsole }) => {
</div>
<ul
tabIndex={0}
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 ${
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-56 ${
isOpen ? "" : "hidden"
}`}
>
{!isConsole && (
<>
<li
onClick={() =>
openInNewTab("https://docs.opensignlabs.com")
}
>
<span>
<i className="fa-light fa-book"></i> {t("docs")}
</span>
</li>
<li
onClick={() =>
openInNewTab("https://docs.opensignlabs.com")
}
>
<span>
<i className="fa-light fa-book"></i> {t("docs")}
</span>
</li>
<li
onClick={() => {
setIsOpen(false);
@@ -171,15 +203,36 @@ const Header = ({ showSidebar, setIsMenu, isConsole }) => {
<i className="fa-light fa-user"></i> {t("profile")}
</span>
</li>
<li
onClick={() => {
setIsOpen(false);
navigate("/changepassword");
}}
>
<span>
<i className="fa-light fa-lock"></i>{" "}
{t("change-password")}
</span>
</li>
<li
onClick={() => {
setIsOpen(false);
navigate("/changepassword");
navigate("/verify-document");
}}
>
<span>
<i className="fa-light fa-lock"></i>{" "}
{t("change-password")}
<i className="fa-light fa-check-square"></i>{" "}
{t("verify-document")}
</span>
</li>
<li>
<span>
<i className="fa-light fa-moon"></i>
{t("dark-mode")}
<span className="text-[10px] font-semibold bg-base-300 text-base-content px-1 rounded-md">
BETA
</span>
<ThemeToggle />
</span>
</li>
</>
@@ -0,0 +1,41 @@
import { useEffect, useState } from "react";
const ThemeToggle = () => {
const [isDark, setIsDark] = useState(false);
useEffect(() => {
const storedTheme = localStorage.getItem("theme");
if (storedTheme === "dark") {
setIsDark(true);
document.documentElement.setAttribute("data-theme", "opensigndark");
} else {
document.documentElement.setAttribute("data-theme", "opensigncss");
}
}, []);
const handleChange = () => {
const newTheme = !isDark;
setIsDark(newTheme);
if (newTheme) {
document.documentElement.setAttribute("data-theme", "opensigndark");
localStorage.setItem("theme", "dark");
} else {
document.documentElement.setAttribute("data-theme", "opensigncss");
localStorage.setItem("theme", "light");
}
};
return (
<>
<input
id="dark-mode-toggle"
type="checkbox"
className="op-toggle checked:[--tglbg:#3368ff] transition-all checked:bg-white"
checked={isDark}
onChange={handleChange}
/>
</>
);
};
export default ThemeToggle;
@@ -1,4 +1,3 @@
import React from "react";
import { Helmet } from "react-helmet";
function Title({ title, drive }) {
@@ -29,7 +29,7 @@ const DashboardButton = (props) => {
: "cursor-default"
} w-full shadow-md px-3 py-2 op-card bg-base-100`}
>
<div className="flex flex-row items-center">
<div className="flex flex-row items-center text-base-content">
<div className="flex flex-row items-center">
<span className="rounded-full bg-base-content bg-opacity-20 w-[60px] h-[60px] self-start flex justify-center items-center">
<i
@@ -39,7 +39,7 @@ const DashboardButton = (props) => {
></i>
</span>
</div>
<div className="text-lg ml-3 text-base-content">
<div className="text-lg ml-3">
{t(`sidebar.${props.Label}`)}
{props.Label === "Sign yourself" && (
<div className="text-gray-500 text-xs mt-1">
@@ -148,7 +148,7 @@ const DashboardCard = (props) => {
);
let arr = [];
for (const obj of listData) {
const isSigner = obj.Signers.some(
const isSigner = obj.Signers?.some(
(item) => item.UserId.objectId === currentUser.id
);
if (isSigner) {
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from "react";
import React, { useEffect, useState, useRef } from "react";
import Parse from "parse";
import ReportTable from "../../primitives/GetReportDisplay";
import reportJson from "../../json/ReportJson";
@@ -17,10 +17,16 @@ function DashboardReport(props) {
const [isMoreDocs, setIsMoreDocs] = useState(true);
const abortController = new AbortController();
const docPerPage = 5;
const [searchTerm, setSearchTerm] = useState("");
const [mobileSearchOpen, setMobileSearchOpen] = useState(false);
const [isSearchResult, setIsSearchResult] = useState(false);
const debounceTimer = useRef(null);
useEffect(() => {
setReportName("");
getReportData(props.Record.reportId);
setSearchTerm("");
setMobileSearchOpen(false);
getReportData(props.Record.reportId, 0, 20, "");
// Function returned from useEffect is called on unmount
return () => {
@@ -36,12 +42,69 @@ function DashboardReport(props) {
// below useEffect call when isNextRecord state is true and fetch next record
useEffect(() => {
if (isNextRecord) {
getReportData(props.Record.reportId, List.length, 20);
getReportData(props.Record.reportId, List.length, 20, searchTerm);
}
// eslint-disable-next-line
}, [isNextRecord]);
const getReportData = async (id, skipUserRecord = 0, limit = 20) => {
const handleSearchChange = async (e) => {
const term = e.target.value.toLowerCase();
setSearchTerm(term);
if (debounceTimer.current) {
clearTimeout(debounceTimer.current);
}
debounceTimer.current = setTimeout(async () => {
try {
const headers = {
"Content-Type": "application/json",
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
sessiontoken: localStorage.getItem("accesstoken")
};
const url = `${localStorage.getItem("baseUrl")}functions/getReport`;
const res = await axios.post(
url,
{
reportId: props.Record.reportId,
searchTerm: term,
skip: 0,
limit: docPerPage
},
{ headers }
);
const data = res.data?.result || [];
if (!data.error) {
setList(data);
setIsMoreDocs(data.length >= docPerPage);
setIsNextRecord(false);
setIsSearchResult(true);
}
} catch (err) {
console.error("Search error:", err);
}
}, 300);
setIsSearchResult(false);
};
const handleSearchPaste = (e) => {
setTimeout(() => {
handleSearchChange({ target: { value: e.target.value } });
}, 0);
};
useEffect(() => {
return () => {
if (debounceTimer.current) {
clearTimeout(debounceTimer.current);
}
};
}, []);
const getReportData = async (
id,
skipUserRecord = 0,
limit = 20,
term = searchTerm
) => {
setIsLoader(true);
const json = reportJson(id);
if (json) {
@@ -59,6 +122,9 @@ function DashboardReport(props) {
const skipRecord = id === "5Go51Q7T8r" ? 0 : skipUserRecord;
const limitRecord = id === "5Go51Q7T8r" ? 200 : limit;
const params = { reportId: id, skip: skipRecord, limit: limitRecord };
if (term) {
params.searchTerm = term;
}
const url = `${localStorage.getItem("baseUrl")}functions/getReport`;
const res = await axios.post(url, params, {
headers: headers,
@@ -68,7 +134,7 @@ function DashboardReport(props) {
const listData = res.data?.result.filter((x) => x.Signers.length > 0);
let arr = [];
for (const obj of listData) {
const isSigner = obj.Signers.some(
const isSigner = obj.Signers?.some(
(item) => item.UserId.objectId === currentUser
);
if (isSigner) {
@@ -141,11 +207,17 @@ function DashboardReport(props) {
setIsNextRecord={setIsNextRecord}
isMoreDocs={isMoreDocs}
docPerPage={docPerPage}
mobileSearchOpen={mobileSearchOpen}
setMobileSearchOpen={setMobileSearchOpen}
searchTerm={searchTerm}
handleSearchChange={handleSearchChange}
handleSearchPaste={handleSearchPaste}
isSearchResult={isSearchResult}
/>
) : (
<div className="flex items-center justify-center h-[100px] w-full bg-white rounded">
<div className="text-center">
<p className="text-xl text-black">{t("report-not-found")}</p>
<p className="text-xl text-base-content">{t("report-not-found")}</p>
</div>
</div>
)}
@@ -1,10 +1,10 @@
import React, { useState, useEffect, useRef } from "react";
import "../../styles/opensigndrive.css";
import axios from "axios";
import * as ContextMenu from "@radix-ui/react-context-menu";
import { ContextMenu } from "radix-ui";
import { useNavigate } from "react-router";
import Table from "react-bootstrap/Table";
import * as HoverCard from "@radix-ui/react-hover-card";
import { HoverCard } from "radix-ui";
import ModalUi from "../../primitives/ModalUi";
import FolderModal from "../shared/fields/FolderModal";
import { useTranslation } from "react-i18next";
@@ -405,7 +405,7 @@ function DriveBody(props) {
</tr>
)
) : listType === "list" && data.Type === "Folder" ? (
<div key={ind} className="relative w-[100px] h-[100px] mx-2 my-3">
<div className="relative w-[100px] h-[100px] mx-2 my-3">
<ContextMenu.Root>
<ContextMenu.Trigger className="flex flex-col justify-center items-center select-none-cls">
{/* folder */}
@@ -447,7 +447,6 @@ function DriveBody(props) {
)}
</div>
</ContextMenu.Trigger>
<ContextMenu.Portal>
<ContextMenu.Content
className="ContextMenuContent"
@@ -641,7 +640,7 @@ function DriveBody(props) {
) : (
<div className="flex flex-row flex-wrap items-center mt-1 pb-[20px] mx-[5px]">
{props.pdfData.map((data, ind) => {
return <div key={ind}>{handleFolderData(data, ind, "list")}</div>;
return <React.Fragment key={ind}>{handleFolderData(data, ind, "list")}</React.Fragment>;
})}
</div>
)}
@@ -10,7 +10,7 @@ const AddRoleModal = (props) => {
isOpen={props.isModalRole}
handleClose={props.handleCloseRoleModal}
>
<div className="h-full py-[10px] px-[20px]">
<div className="text-base-content h-full py-[10px] px-[20px]">
<form className="flex flex-col" onSubmit={props.handleAddRole}>
<input
value={props.roleName}
@@ -9,7 +9,6 @@ function AgreementContent(props) {
const h2Style = "text-base-content font-medium text-lg";
const ulStyle = "list-disc px-4 py-3";
const handleOnclick = () => {
props.setIsAgreeTour(false);
props.setIsAgree(true);
props.setIsShowAgreeTerms(false);
props.showFirstWidget();
@@ -4,7 +4,6 @@ import AgreementContent from "./AgreementContent";
function AgreementSign(props) {
const { t } = useTranslation();
const [isChecked, setIsChecked] = useState(false);
const [isShowAgreeTerms, setIsShowAgreeTerms] = useState(false);
return (
@@ -12,26 +11,12 @@ function AgreementSign(props) {
<div className="op-modal op-modal-open absolute z-[448]">
<div className="w-[95%] md:w-[60%] lg:w-[40%] op-modal-box overflow-y-auto hide-scrollbar text-sm p-4">
<div className="flex flex-row items-center">
<input
data-tut="IsAgree"
className="mr-3 op-checkbox op-checkbox-m"
type="checkbox"
value={isChecked}
onChange={(e) => {
setIsChecked(e.target.checked);
if (e.target.checked) {
props.setIsAgreeTour(false);
}
props.showFirstWidget();
}}
/>
<div className="text-[11px] md:text-base">
<div className="text-[11px] md:text-base text-base-content">
<span>{t("agree-p1")}</span>
<span
className="font-bold text-blue-600 cursor-pointer"
onClick={() => {
setIsShowAgreeTerms(true);
props.setIsAgreeTour(false);
}}
>
{t("agree-p2")}
@@ -39,29 +24,24 @@ function AgreementSign(props) {
<span> {t("agree-p3")}</span>
</div>
</div>
<div className="flex ml-[35px] mt-3">
<div className="flex mt-3">
<button
onClick={() => {
if (isChecked) {
props.setIsAgreeTour(false);
props.setIsAgree(true);
} else {
props.setIsAgreeTour(true);
}
props.setIsAgree(true);
props.showFirstWidget()
}}
className="op-btn op-btn-primary op-btn-sm w-full md:w-auto"
>
{t("agrre-button")}
</button>
</div>
<div className="mt-2">
<div className="mt-2 text-base-content">
<span className="text-[11px]">{t("agreement-note")}</span>
</div>
</div>
</div>
{isShowAgreeTerms && (
<AgreementContent
setIsAgreeTour={props.setIsAgreeTour}
setIsAgree={props.setIsAgree}
setIsShowAgreeTerms={setIsShowAgreeTerms}
showFirstWidget={props.showFirstWidget}
@@ -12,12 +12,9 @@ function BorderResize(props) {
return (
<div
style={{
width: getHeight() || "14px",
height: getHeight() || "14px"
}}
style={{ width: getHeight() || "14px", height: getHeight() || "14px" }}
className={`${props.right ? `-right-[12px]` : "-right-[2px]"} ${
props.top ? `-bottom-[12px]` : "-bottom-[2px] "
props.top ? `-bottom-[12px]` : "-bottom-[2px]"
} absolute inline-block hover:cursor-sw-resize border-r-[3px] border-b-[3px] border-[#188ae2]`}
></div>
);
@@ -0,0 +1,104 @@
import React, { useState, useEffect } from "react";
import ModalUi from "../../primitives/ModalUi";
import { fontsizeArr, fontColorArr } from "../../constant/Utils";
import { useTranslation } from "react-i18next";
export default function CellsSettingModal({
isOpen,
handleClose,
defaultData,
handleSave
}) {
const { t } = useTranslation();
const [name, setName] = useState("");
const [cellCount, setCellCount] = useState(5);
const [fontSize, setFontSize] = useState(12);
const [fontColor, setFontColor] = useState("black");
useEffect(() => {
if (defaultData) {
setName(defaultData.options?.name || "Cells");
setCellCount(defaultData.options?.cellCount || 5);
setFontSize(defaultData.options?.fontSize || 12);
setFontColor(defaultData.options?.fontColor || "black");
}
}, [defaultData]);
const onSubmit = (e) => {
e.preventDefault();
handleSave &&
handleSave({
name,
cellCount: parseInt(cellCount, 10),
fontSize,
fontColor
});
};
return (
<ModalUi isOpen={isOpen} handleClose={handleClose} title={t("widget-info")}>
<form onSubmit={onSubmit} className="p-[20px] text-base-content flex flex-col gap-3">
<div>
<label htmlFor="name" className="text-[13px]">
{t("name")} <span className="text-[red]">*</span>
</label>
<input
required
name="name"
className="op-input op-input-bordered op-input-sm w-full text-xs focus:outline-none hover:border-base-content"
value={name}
onChange={(e) => setName(e.target.value)}
/>
</div>
<div>
<label htmlFor="cellCount" className="text-[13px]">
{t("cell-count")}
</label>
<input
type="number"
min="1"
name="cellCount"
className="op-input op-input-bordered op-input-sm w-full text-xs focus:outline-none hover:border-base-content"
value={cellCount}
onChange={(e) => setCellCount(e.target.value)}
/>
</div>
<div className="flex flex-col md:flex-row md:items-center gap-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={fontSize}
onChange={(e) => setFontSize(parseInt(e.target.value))}
>
{fontsizeArr.map((size, ind) => (
<option className="text-[13px]" value={size} key={ind}>
{size}
</option>
))}
</select>
</div>
<div className="flex items-center">
<span>{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={fontColor}
onChange={(e) => setFontColor(e.target.value)}
>
{fontColorArr.map((color, ind) => (
<option value={color} key={ind}>
{t(`color-type.${color}`)}
</option>
))}
</select>
<span className="w-5 h-[19px] ml-1" style={{ background: fontColor }} />
</div>
</div>
<div className="h-[1px] w-full bg-[#b7b3b3] my-[16px]"></div>
<button type="submit" className="op-btn op-btn-primary">
{t("save")}
</button>
</form>
</ModalUi>
);
}
@@ -0,0 +1,143 @@
import { useState, useEffect, useRef } from "react";
const Cell = ({
isEnabled,
count,
h,
value,
editable,
onChange,
onKeyDown,
onBlur,
inputRef,
index,
fontSize,
fontColor,
hint
}) => (
<div
className={`${isEnabled ? "bg-white border-gray-400" : "select-none-cls pointer-events-none border-gray-500"} flex items-center justify-center border-[1px]`}
style={{ flex: `0 0 ${100 / count}%`, height: h }}
>
<input
disabled={!isEnabled}
maxLength={1}
value={value}
readOnly={!editable}
ref={inputRef}
onChange={editable ? (e) => onChange && onChange(e, index) : undefined}
onKeyDown={editable ? (e) => onKeyDown && onKeyDown(e, index) : undefined}
// trigger validation when leaving a cell
onBlur={editable ? (e) => onBlur && onBlur(e, index) : undefined}
className={`${isEnabled ? "placeholder-gray-300" : "placeholder-gray-500"} w-full text-center focus:outline-none bg-transparent`}
placeholder={hint}
style={{ fontFamily: "Arial, sans-serif", fontSize, color: fontColor }}
/>
</div>
);
export default function CellsWidget({
isEnabled,
count = 8,
height = 40,
value = "",
editable = false,
onChange,
onKeyDown,
onBlur,
onCellCountChange,
inputRefs,
resizable = false,
fontSize = "12px",
fontColor = "black",
hint = ""
}) {
const [cellCount, setCellCount] = useState(count);
// keep internal state in sync with prop updates
useEffect(() => setCellCount(count), [count]);
const startX = useRef(0);
const startCount = useRef(cellCount);
const capture = (downEv, onMove, onUp = () => {}) => {
const id = downEv.pointerId;
const move = (ev) => id === ev.pointerId && onMove(ev);
const up = (ev) => {
if (id !== ev.pointerId) return;
onUp(ev);
downEv.target.releasePointerCapture(id);
window.removeEventListener("pointermove", move);
window.removeEventListener("pointerup", up);
};
window.addEventListener("pointermove", move);
window.addEventListener("pointerup", up);
downEv.target.setPointerCapture(id);
};
const onTopHandlePointerDown = (ev) => {
// Prevent triggering the widget drag logic
ev.stopPropagation();
ev.preventDefault();
startX.current = ev.clientX;
startCount.current = cellCount;
capture(ev, (moveEv) => {
const dx = moveEv.clientX - startX.current;
const delta = Math.floor(-dx / 5);
let newCount = Math.max(1, startCount.current + delta);
if (newCount !== cellCount) {
setCellCount(newCount);
onCellCountChange?.(newCount);
}
});
};
const cells = Array.from({ length: cellCount }).map((_, i) => value[i] || "");
const hints = Array.from({ length: cellCount }).map((_, i) => hint[i] || "");
return (
<div
className="relative flex w-full h-full overflow-visible"
style={{ height }}
>
{resizable && (
<div
className="cell-size-handle absolute left-1/2 -translate-x-1/2 -bottom-4 rotate-180 cursor-ew-resize touch-none"
onPointerDown={onTopHandlePointerDown}
>
<svg
className="w-4 h-4 text-blue-600"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path
fillRule="evenodd"
d="m9.69 18.933.003.001C9.89 19.02 10 19 10 19s.11.02.308-.066l.002-.001.006-.003.018-.008a5.741 5.741 0 0 0 .281-.14c.186-.096.446-.24.757-.433.62-.384 1.445-.966 2.274-1.765C15.302 14.988 17 12.493 17 9A7 7 0 1 0 3 9c0 3.492 1.698 5.988 3.355 7.584a13.731 13.731 0 0 0 2.273 1.765 11.842 11.842 0 0 0 .976.544l.062.029.018.008.006.003ZM10 11.25a2.25 2.25 0 1 0 0-4.5 2.25 2.25 0 0 0 0 4.5Z"
clipRule="evenodd"
/>
</svg>
</div>
)}
{cells.map((val, i) => (
<Cell
key={i}
isEnabled={isEnabled}
count={cellCount}
h={height}
value={val}
editable={editable}
onChange={onChange}
onKeyDown={onKeyDown}
onBlur={onBlur}
inputRef={inputRefs ? (el) => (inputRefs.current[i] = el) : undefined}
index={i}
fontSize={fontSize}
fontColor={fontColor}
hint={hints[i]}
/>
))}
</div>
);
}
@@ -1,42 +1,41 @@
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { useSelector } from "react-redux";
function DefaultSignature(props) {
const { t } = useTranslation();
const defaultSignImg = useSelector((state) => state.widget.defaultSignImg);
const myInitial = useSelector((state) => state.widget.myInitial);
const tabName = ["my-signature", "my-initials"];
const [activeTab, setActiveTab] = useState(0);
const confirmToaddDefaultSign = (type) => {
if (!props.isAgree) {
props.setIsAgreeTour(true);
} else {
if (props?.xyPosition.length > 0) {
//check signature or initial widgets exist or not for auto signing
const getCurrentSignerXY = props?.xyPosition.filter(
(data) => data.Id === props.uniqueId
);
const checkIsSignInitialExist = getCurrentSignerXY?.every(
(placeholderObj) =>
placeholderObj?.placeHolder?.some((placeholder) =>
placeholder?.pos?.some((posItem) => posItem?.type === type)
)
);
if (checkIsSignInitialExist) {
props?.setDefaultSignAlert({
isShow: true,
alertMessage: t("default-sign-alert", { widgetsType: type }),
type: type
});
} else {
props?.setDefaultSignAlert({
isShow: true,
alertMessage: t("defaultSign-alert", { widgetsType: type })
});
}
if (props?.xyPosition.length > 0) {
//check signature or initial widgets exist or not for auto signing
const getCurrentSignerXY = props?.xyPosition.filter(
(data) => data.Id === props.uniqueId
);
const checkIsSignInitialExist = getCurrentSignerXY?.every(
(placeholderObj) =>
placeholderObj?.placeHolder?.some((placeholder) =>
placeholder?.pos?.some((posItem) => posItem?.type === type)
)
);
if (checkIsSignInitialExist) {
props?.setDefaultSignAlert({
isShow: true,
alertMessage: t("default-sign-alert", { widgetsType: type }),
type: type
});
} else {
props?.setDefaultSignAlert({
isShow: true,
alertMessage: t("please-select-position!")
alertMessage: t("defaultSign-alert", { widgetsType: type })
});
}
} else {
props?.setDefaultSignAlert({
isShow: true,
alertMessage: t("please-select-position!")
});
}
};
@@ -69,15 +68,15 @@ function DefaultSignature(props) {
<img
alt="signature"
className="w-full h-full object-contain"
src={props?.defaultSignImg}
src={defaultSignImg}
/>
) : (
activeTab === 1 &&
(props?.myInitial ? (
(myInitial ? (
<img
alt="signature"
className="w-full h-full object-contain"
src={props?.myInitial}
src={myInitial}
/>
) : (
<div className="flex justify-center items-center h-full">
@@ -97,7 +96,7 @@ function DefaultSignature(props) {
disabled={
activeTab === 0 && !props?.isDefault
? true
: activeTab === 1 && !props.myInitial
: activeTab === 1 && !myInitial
? true
: false
}
@@ -7,34 +7,36 @@ import { fontColorArr, fontsizeArr } from "../../constant/Utils";
function DropdownWidgetOption(props) {
const { t } = useTranslation();
const [dropdownOptionList, setDropdownOptionList] = useState([
"option-1",
"option-2"
"Option-1",
"Option-2"
]);
const [minCount, setMinCount] = useState(0);
const [maxCount, setMaxCount] = useState(0);
const [dropdownName, setDropdownName] = useState(props.type);
const [dropdownName, setDropdownName] = useState();
const [isReadOnly, setIsReadOnly] = useState(false);
const [isHideLabel, setIsHideLabel] = useState(false);
const [status, setStatus] = useState("required");
const [defaultValue, setDefaultValue] = useState("");
const statusArr = ["required", "optional"];
const [defaultCheckbox, setDefaultCheckbox] = useState([]);
const [layout, setLayout] = useState("vertical");
const statusArr = ["required", "optional"];
const layoutArr = ["vertical", "horizontal"];
const resetState = () => {
setDropdownOptionList(["option-1", "option-2"]);
setDropdownName(props.type);
setDropdownOptionList(["Option-1", "Option-2"]);
setDropdownName(props.currWidgetsDetails?.options?.name || props.type);
setIsReadOnly(false);
setIsHideLabel(false);
setMinCount(0);
setMaxCount(0);
setDefaultCheckbox([]);
setDefaultValue("");
setLayout("vertical");
};
useEffect(() => {
if (
props.currWidgetsDetails?.options?.name &&
props.currWidgetsDetails?.options?.values
props.currWidgetsDetails?.options?.values?.length > 0
) {
setDropdownName(props.currWidgetsDetails?.options?.name);
setDropdownOptionList(props.currWidgetsDetails?.options?.values);
@@ -49,6 +51,7 @@ function DropdownWidgetOption(props) {
setStatus(props.currWidgetsDetails?.options?.status || "required");
setDefaultValue(props.currWidgetsDetails?.options?.defaultValue || "");
setDefaultCheckbox(props.currWidgetsDetails?.options?.defaultValue || []);
setLayout(props.currWidgetsDetails?.options?.layout || "vertical");
} else {
setStatus("required");
resetState();
@@ -104,6 +107,30 @@ function DropdownWidgetOption(props) {
? defaultCheckbox
: defaultValue;
const isDropdownOrRadio =
props?.type === "dropdown" || props?.type === radioButtonWidget;
const readOnlyWithoutValue =
isReadOnly && !defaultValue && status !== "optional";
const isCheckbox = props?.type === "checkbox";
const WidgetLayout = ["checkbox", radioButtonWidget].includes(props.type)
? layout
: null;
// If its a dropdown and its read-only without a value (nor marked optional), stop here.
if (isDropdownOrRadio && readOnlyWithoutValue) {
alert(t("readonly-error", { widgetName: props?.type }));
return;
} else if (
isCheckbox &&
isReadOnly &&
minCount > 0 &&
defaultCheckbox?.length === 0
) {
alert(t("readonly-error", { widgetName: props?.type }));
return;
}
// Otherwise (either not a dropdown, or a valid dropdown), do the save + reset exactly once.
props.handleSaveWidgetsOptions(
dropdownName,
dropdownOptionList,
@@ -114,18 +141,10 @@ function DropdownWidgetOption(props) {
null,
status,
defaultData,
isHideLabel
isHideLabel,
WidgetLayout
);
// props.setShowDropdown(false);
setDropdownOptionList(["option-1", "option-2"]);
setDropdownName(props.type);
// props.setCurrWidgetsDetails({});
setIsReadOnly(false);
setIsHideLabel(false);
setMinCount(0);
setMaxCount(0);
setDefaultCheckbox([]);
setDefaultValue("");
resetState();
};
@@ -137,7 +156,6 @@ function DropdownWidgetOption(props) {
return false;
}
};
return (
<ModalUi isOpen={props.showDropdown} title={props.title} showClose={false}>
<div className="h-full p-[15px] text-base-content">
@@ -148,25 +166,25 @@ function DropdownWidgetOption(props) {
}}
>
<div>
<label className="text-[13px] font-semibold">
<label htmlFor="title" className="text-[13px] font-semibold">
{t("name")}
<span className="text-[red] text-[13px]"> *</span>
</label>
<input
id="title"
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
onInput={(e) => e.target.setCustomValidity("")}
required
defaultValue={dropdownName}
value={dropdownName}
onChange={(e) => setDropdownName(e.target.value)}
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
required
/>
<label className="text-[13px] font-semibold mt-[5px]">
{t("options")}
</label>
<div className="flex flex-col">
{dropdownOptionList.map((option, index) => (
{dropdownOptionList?.map((option, index) => (
<div
key={index}
className="flex flex-row mb-[5px] items-center"
@@ -240,28 +258,26 @@ function DropdownWidgetOption(props) {
</select>
</>
)}
{props.type !== "checkbox" && props.type !== radioButtonWidget && (
<>
<div className="flex flex-row gap-[10px] mt-[0.5rem]">
{statusArr.map((data, ind) => {
return (
<div
key={ind}
className="flex flex-row gap-[5px] items-center"
>
<input
className="op-radio op-radio-xs my-1"
type="radio"
name="status"
onChange={() => setStatus(data.toLowerCase())}
checked={status.toLowerCase() === data.toLowerCase()}
/>
<div className="text-[13px] font-500">{data}</div>
</div>
);
})}
</div>
</>
{props.type !== "checkbox" && (
<div className="flex flex-row gap-[10px] mt-[0.5rem]">
{statusArr.map((data, ind) => (
<div
key={ind}
className="flex flex-row gap-[5px] items-center"
>
<input
className="op-radio op-radio-xs my-1"
type="radio"
name="status"
onChange={() => setStatus(data.toLowerCase())}
checked={status.toLowerCase() === data.toLowerCase()}
/>
<div className="text-[13px] font-500 capitalize">
{data}
</div>
</div>
))}
</div>
)}
<div className="flex items-center mt-3 mb-3">
<span>{t("font-size")} :</span>
@@ -282,8 +298,8 @@ function DropdownWidgetOption(props) {
);
})}
</select>
<div className="flex flex-row gap-1 items-center ml-4 ">
<span>{t("color")} : </span>
<div className="flex flex-row gap-1 items-center ml-4">
<span className="capitalize">{t("color")} : </span>
<select
value={
props.fontColor ||
@@ -325,7 +341,10 @@ function DropdownWidgetOption(props) {
className="op-checkbox op-checkbox-sm"
onChange={(e) => setIsReadOnly(e.target.checked)}
/>
<label className="ml-1 mb-0" htmlFor="isreadonly">
<label
className="ml-2 mb-0 capitalize"
htmlFor="isreadonly"
>
{t("read-only")}
</label>
</div>
@@ -340,15 +359,45 @@ function DropdownWidgetOption(props) {
onChange={(e) => setIsHideLabel(e.target.checked)}
/>
<label className="ml-1 mb-0" htmlFor="ishidelabel">
<label
className="ml-2 mb-0 capitalize"
htmlFor="ishidelabel"
>
{t("hide-labels")}
</label>
</div>
)}
</div>
)}
{["checkbox", radioButtonWidget].includes(props.type) && (
<>
<div className="text-[13px] font-semibold mt-[5px] capitalize">
{t("layout")}
</div>
<div
className={`${props.type === "checkbox" ? "mb-[10px]" : ""} flex flex-row gap-[10px] mt-[0.5rem]`}
>
{layoutArr.map((data, ind) => (
<div
key={ind}
className="flex flex-row gap-[5px] items-center"
>
<input
className="op-radio op-radio-xs my-1"
type="radio"
name="layout"
checked={layout.toLowerCase() === data.toLowerCase()}
onChange={() => setLayout(data.toLowerCase())}
/>
<label className="text-[13px] font-500 mb-0">
{t(data)}
</label>
</div>
))}
</div>
</>
)}
</div>
<div
className={`${
props.type === "checkbox" && props.isShowAdvanceFeature
@@ -1,4 +1,7 @@
import React, { useState, useRef } from "react";
import {
useState,
useRef,
} from "react";
import {
base64ToArrayBuffer,
convertBase64ToFile,
@@ -24,9 +27,10 @@ const EditTemplate = ({
template,
onSuccess,
setPdfArrayBuffer,
setPdfBase64Url
setPdfBase64Url,
}) => {
const appName = "OpenSign™";
const appName =
"OpenSign™";
const { t } = useTranslation();
const inputFileRef = useRef(null);
const [formData, setFormData] = useState({
@@ -165,7 +169,10 @@ const EditTemplate = ({
}
let pdfUrl;
if (uploadPdf?.base64) {
pdfUrl = await convertBase64ToFile(uploadPdf.name, uploadPdf.base64);
pdfUrl = await convertBase64ToFile(
uploadPdf.name,
uploadPdf.base64,
);
setUploadPdf((prev) => ({ ...prev, url: pdfUrl }));
const pdfBuffer = base64ToArrayBuffer(uploadPdf.base64);
setPdfArrayBuffer && setPdfArrayBuffer(pdfBuffer);
@@ -279,7 +286,7 @@ const EditTemplate = ({
htmlFor="fileUpload"
className="cursor-pointer text-center mb-0"
>
Browse or drag & drop a new file to replace the existng one
{t("browse-or-drag-to-replace-existing-file")}
</label>
</div>
<input
@@ -447,7 +454,11 @@ const EditTemplate = ({
</Tooltip>
</label>
<div className="flex flex-col md:flex-row md:gap-4">
<div className={`flex items-center gap-2 ml-2 mb-1`}>
<div
className={
`flex items-center gap-2 ml-2 mb-1`
}
>
<input
className="mr-[2px] op-radio op-radio-xs"
type="radio"
@@ -456,7 +467,11 @@ const EditTemplate = ({
/>
<div className="text-center">{t("yes")}</div>
</div>
<div className={`flex items-center gap-2 ml-2 mb-1`}>
<div
className={
`flex items-center gap-2 ml-2 mb-1`
}
>
<input
className="mr-[2px] op-radio op-radio-xs"
type="radio"
@@ -1,5 +1,10 @@
import React from "react";
import { Quill } from "react-quill-new";
// Use a third-party plugin so editors can toggle raw HTML view.
import htmlEditButton from "quill-html-edit-button";
// Register the module which adds a "<>" button for HTML editing.
Quill.register("modules/htmlEditButton", htmlEditButton);
// Custom Undo button icon component for Quill editor. You can import it directly
// from 'quill/assets/icons/undo.svg' but I found that a number of loaders do not
@@ -56,7 +61,9 @@ export const module1 = {
container: "#toolbar1",
handlers: { undo: undoChange, redo: redoChange }
},
history: { delay: 500, maxStack: 100, userOnly: true }
history: { delay: 500, maxStack: 100, userOnly: true },
// Enable the "<>" button registered above
htmlEditButton: {}
};
// Modules object for setting up the Quill editor
@@ -65,7 +72,9 @@ export const module2 = {
container: "#toolbar2",
handlers: { undo: undoChange, redo: redoChange }
},
history: { delay: 500, maxStack: 100, userOnly: true }
history: { delay: 500, maxStack: 100, userOnly: true },
// Enable the "<>" button registered above
htmlEditButton: {}
};
// Formats objects for setting up the Quill editor
@@ -1,6 +1,10 @@
import React, { useState } from "react";
import { handleToPrint } from "../../constant/Utils";
import { emailRegex } from "../../constant/const";
import {
handleToPrint,
} from "../../constant/Utils";
import {
emailRegex,
} from "../../constant/const";
import Loader from "../../primitives/Loader";
import ModalUi from "../../primitives/ModalUi";
import { useTranslation } from "react-i18next";
@@ -37,7 +41,8 @@ function EmailComponent({
setEmailList([]);
}, 1500);
setIsLoading(false);
} else {
}
else {
setIsLoading(false);
setIsEmail(false);
setIsAlert({
@@ -181,7 +186,7 @@ function EmailComponent({
<input
type="email"
value={emailValue}
className="p-[10px] pb-[20px] rounded-md w-full text-[15px] outline-none bg-transparent border-[1px] op-border-primary"
className="p-[10px] pb-[20px] text-base-content rounded-md w-full text-[15px] outline-none bg-transparent border-[1px] op-border-primary"
onChange={handleEmailValue}
onKeyDown={handleEnterPress}
placeholder={t("enter-email-plaholder")}
@@ -199,19 +204,6 @@ function EmailComponent({
{t("email-error-1")}
</p>
)}
{/* <button
className={`${
emailValue ? "cursor-pointer" : "cursor-default"
} op-btn op-btn-primary op-btn-sm m-2 shadow-md`}
onClick={() => emailValue && handleEnterPress("add")}
>
<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" /> */}
<div className="mt-2">
<button
type="button"
@@ -222,7 +214,7 @@ function EmailComponent({
</button>
<button
type="button"
className="op-btn op-btn-ghost ml-2"
className="op-btn op-btn-ghost text-base-content ml-2"
onClick={() => handleClose()}
>
{t("close")}
@@ -0,0 +1,94 @@
import React, { useEffect, useState, useRef } from "react";
import ModalUi from "../../primitives/ModalUi";
import { useTranslation } from "react-i18next";
export default function PageReorderModal({
isOpen,
handleClose,
totalPages = 0,
onSave
}) {
const { t } = useTranslation();
const [order, setOrder] = useState([]);
// Keeps track of the page order relative to the original PDF
const orderRef = useRef([]);
// Captures the order when the modal opens
const initialOrderRef = useRef([]);
// Initialize orderRef when total pages change (e.g. after upload)
useEffect(() => {
if (orderRef.current.length !== totalPages) {
orderRef.current = Array.from({ length: totalPages }, (_, i) => i + 1);
}
}, [totalPages]);
// When modal opens, display the last saved order and store it as initial
useEffect(() => {
if (isOpen) {
setOrder(orderRef.current);
initialOrderRef.current = [...orderRef.current];
}
}, [isOpen]);
const move = (index, dir) => {
const swapIndex = index + dir;
if (swapIndex < 0 || swapIndex >= order.length) return;
const newOrder = [...order];
[newOrder[index], newOrder[swapIndex]] = [newOrder[swapIndex], newOrder[index]];
setOrder(newOrder);
};
const handleSave = () => {
const saveOrder = order.map((num) =>
initialOrderRef.current.indexOf(num) + 1
);
// Persist the new display order for next time
orderRef.current = [...order];
onSave && onSave(saveOrder);
};
const isUnchanged =
order.length === initialOrderRef.current.length &&
order.every((n, i) => n === initialOrderRef.current[i]);
return (
<ModalUi isOpen={isOpen} handleClose={handleClose} title={t("reorder-pages")}>
<div className="p-[20px] flex flex-col gap-2 text-base-content">
{order.map((num, i) => (
<div key={num} className="flex items-center justify-between">
<span>
{t("page")} {num}
</span>
<div className="flex gap-1">
<button
className="op-btn op-btn-xs op-btn-ghost"
disabled={i === 0}
onClick={() => move(i, -1)}
>
<i className="fa-light fa-arrow-up"></i>
</button>
<button
className="op-btn op-btn-xs op-btn-ghost"
disabled={i === order.length - 1}
onClick={() => move(i, 1)}
>
<i className="fa-light fa-arrow-down"></i>
</button>
</div>
</div>
))}
<div className="h-[1px] bg-[#9f9f9f] w-full my-[15px]"></div>
<button onClick={handleSave} type="button" className="op-btn op-btn-primary" disabled={isUnchanged}>
{t("save")}
</button>
<button
onClick={handleClose}
type="button"
className="op-btn op-btn-ghost ml-1"
>
{t("close")}
</button>
</div>
</ModalUi>
);
}
@@ -2,16 +2,21 @@ import React, { useRef, useState } from "react";
import PrevNext from "./PrevNext";
import {
base64ToArrayBuffer,
decryptPdf,
deletePdfPage,
flattenPdf,
getFileAsArrayBuffer,
handleDownloadCertificate,
handleDownloadPdf,
handleRemoveWidgets,
handleToPrint
handleToPrint,
reorderPdfPages
} from "../../constant/Utils";
import "../../styles/signature.css";
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
import { DropdownMenu } from "radix-ui";
import ModalUi from "../../primitives/ModalUi";
import Loader from "../../primitives/Loader";
import PageReorderModal from "./PageReorderModal";
import { useTranslation } from "react-i18next";
import { PDFDocument } from "pdf-lib";
import { maxFileSize } from "../../constant/const";
@@ -24,6 +29,7 @@ function Header(props) {
const isMobile = window.innerWidth < 767;
const [isDownloading, setIsDownloading] = useState("");
const [isDeletePage, setIsDeletePage] = useState(false);
const [isReorderModal, setIsReorderModal] = useState(false);
const mergePdfInputRef = useRef(null);
const enabledBackBtn = props?.disabledBackBtn === true ? false : true;
//function for show decline alert
@@ -80,7 +86,42 @@ function Header(props) {
return;
}
try {
const uploadedPdfBytes = await file.arrayBuffer();
let uploadedPdfBytes = await file.arrayBuffer();
try {
uploadedPdfBytes = await flattenPdf(uploadedPdfBytes);
} catch (err) {
if (err?.message?.includes("is encrypted")) {
try {
const pdfFile = await decryptPdf(file, "");
const pdfArrayBuffer = await getFileAsArrayBuffer(pdfFile);
uploadedPdfBytes = await flattenPdf(pdfArrayBuffer);
} catch (err) {
if (err?.response?.status === 401) {
const password = prompt(
`PDF "${file.name}" is password-protected. Enter password:`
);
if (password) {
try {
const pdfFile = await decryptPdf(file, password);
const pdfArrayBuffer = await getFileAsArrayBuffer(pdfFile);
uploadedPdfBytes = await flattenPdf(pdfArrayBuffer);
// Upload the file to Parse Server
} catch (err) {
console.error("Incorrect password or decryption failed", err);
alert("Incorrect password or decryption failed.");
}
} else {
alert("Please provided Password.");
}
} else {
console.log("Err ", err);
alert("error while uploading pdf.");
}
}
} else {
alert("error while uploading pdf.");
}
}
const uploadedPdfDoc = await PDFDocument.load(uploadedPdfBytes, {
ignoreEncryption: true
});
@@ -106,12 +147,27 @@ function Header(props) {
console.error("Error merging PDF:", error);
}
};
const handleReorderSave = async (order) => {
try {
const pdfupdatedData = await reorderPdfPages(props.pdfArrayBuffer, order);
if (pdfupdatedData) {
props.setPdfArrayBuffer(pdfupdatedData.arrayBuffer);
props.setPdfBase64Url(pdfupdatedData.base64);
props.setAllPages(pdfupdatedData.totalPages);
props.setPageNumber(1);
}
} catch (e) {
console.log("error in reorder pdf pages", e);
}
setIsReorderModal(false);
};
return (
<div className="flex py-[5px]">
{isMobile && props?.isShowHeader ? (
<div
id="navbar"
className="stickyHead"
className="stickyHead touch-none"
style={{
width: window.innerWidth + "px"
}}
@@ -276,6 +332,20 @@ function Header(props) {
className="bg-white shadow-md rounded-md px-3 py-2"
sideOffset={5}
>
{props?.setIsEditTemplate && (
<DropdownMenu.Item
className="DropdownMenuItem"
onClick={() => props?.setIsEditTemplate(true)}
>
<div className="flex flex-row">
<i
className="fa-light fa-gear mr-[3px]"
aria-hidden="true"
></i>
<span className="font-[500]">{t("Edit")}</span>
</div>
</DropdownMenu.Item>
)}
<DropdownMenu.Item
className="DropdownMenuItem"
onClick={() =>
@@ -320,6 +390,17 @@ function Header(props) {
</span>
</div>
</DropdownMenu.Item>
<DropdownMenu.Item
className="DropdownMenuItem"
onClick={() => setIsReorderModal(true)}
>
<div className="flex flex-row">
<i className="fa-light fa-list-ol text-gray-500 2xl:text-[30px] mr-[3px]"></i>
<span className="font-[500]">
{t("reorder-pages")}
</span>
</div>
</DropdownMenu.Item>
<DropdownMenu.Item
className="DropdownMenuItem"
@@ -682,6 +763,12 @@ function Header(props) {
</button>
</div>
</ModalUi>
<PageReorderModal
isOpen={isReorderModal}
handleClose={() => setIsReorderModal(false)}
totalPages={props.allPages}
onSave={handleReorderSave}
/>
</div>
);
}
@@ -1,18 +1,24 @@
import React, { useRef, useState } from "react";
import { useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import {
base64ToArrayBuffer,
decryptPdf,
deletePdfPage,
handleRemoveWidgets
flattenPdf,
getFileAsArrayBuffer,
handleRemoveWidgets,
reorderPdfPages
} from "../../constant/Utils";
import ModalUi from "../../primitives/ModalUi";
import { PDFDocument } from "pdf-lib";
import { maxFileSize } from "../../constant/const";
import PageReorderModal from "./PageReorderModal";
function PdfZoom(props) {
const { t } = useTranslation();
const mergePdfInputRef = useRef(null);
const [isDeletePage, setIsDeletePage] = useState(false);
const [isReorderModal, setIsReorderModal] = useState(false);
const handleDetelePage = async () => {
props.setIsUploadPdf && props.setIsUploadPdf(true);
try {
@@ -67,7 +73,42 @@ function PdfZoom(props) {
return;
}
try {
const uploadedPdfBytes = await file.arrayBuffer();
let uploadedPdfBytes = await file.arrayBuffer();
try {
uploadedPdfBytes = await flattenPdf(uploadedPdfBytes);
} catch (err) {
if (err?.message?.includes("is encrypted")) {
try {
const pdfFile = await decryptPdf(file, "");
const pdfArrayBuffer = await getFileAsArrayBuffer(pdfFile);
uploadedPdfBytes = await flattenPdf(pdfArrayBuffer);
} catch (err) {
if (err?.response?.status === 401) {
const password = prompt(
`PDF "${file.name}" is password-protected. Enter password:`
);
if (password) {
try {
const pdfFile = await decryptPdf(file, password);
const pdfArrayBuffer = await getFileAsArrayBuffer(pdfFile);
uploadedPdfBytes = await flattenPdf(pdfArrayBuffer);
// Upload the file to Parse Server
} catch (err) {
console.error("Incorrect password or decryption failed", err);
alert("Incorrect password or decryption failed.");
}
} else {
alert("Please provided Password.");
}
} else {
console.log("Err ", err);
alert("error while uploading pdf.");
}
}
} else {
alert("error while uploading pdf.");
}
}
const uploadedPdfDoc = await PDFDocument.load(uploadedPdfBytes, {
ignoreEncryption: true
});
@@ -94,6 +135,21 @@ function PdfZoom(props) {
}
};
const handleReorderSave = async (order) => {
try {
const pdfupdatedData = await reorderPdfPages(props.pdfArrayBuffer, order);
if (pdfupdatedData) {
props.setPdfArrayBuffer(pdfupdatedData.arrayBuffer);
props.setPdfBase64Url(pdfupdatedData.base64);
props.setAllPages(pdfupdatedData.totalPages);
props.setPageNumber(1);
}
} catch (e) {
console.log("error in reorder pdf pages", e);
}
setIsReorderModal(false);
};
return (
<>
<span className="hidden md:flex flex-col gap-1 text-center md:w-[5%] mt-[42px]">
@@ -120,6 +176,13 @@ function PdfZoom(props) {
>
<i className="fa-light fa-trash text-gray-500 2xl:text-[25px]"></i>
</span>
<span
className="bg-gray-50 px-[4px] 2xl:py-[10px] cursor-pointer"
onClick={() => setIsReorderModal(true)}
title={t("reorder-pages")}
>
<i className="fa-light fa-list-ol text-gray-500 2xl:text-[25px]"></i>
</span>
</>
)}
<span
@@ -185,6 +248,12 @@ function PdfZoom(props) {
</button>
</div>
</ModalUi>
<PageReorderModal
isOpen={isReorderModal}
handleClose={() => setIsReorderModal(false)}
totalPages={props.allPages}
onSave={handleReorderSave}
/>
</>
);
}
@@ -1,60 +0,0 @@
import React from "react";
import { themeColor } from "../../constant/const";
import {
defaultWidthHeight,
isMobile,
radioButtonWidget,
resizeBorderExtraWidth,
textWidget
} from "../../constant/Utils";
function PlaceholderBorder(props) {
const getResizeBorderExtraWidth = resizeBorderExtraWidth();
const defaultWidth = defaultWidthHeight(props.pos.type).width;
const defaultHeight = defaultWidthHeight(props.pos.type).height;
const width = () => {
const getWidth =
props.placeholderBorder.w || props.pos.Width || defaultWidth;
return (
getWidth * props.scale * props.containerScale + getResizeBorderExtraWidth
);
};
const height = () => {
const getHeight =
props.placeholderBorder.h || props.pos.Height || defaultHeight;
return (
getHeight * props.scale * props.containerScale + getResizeBorderExtraWidth
);
};
const handleMinWidth = () => {
if (props.pos.type === "checkbox" || props.pos.type === radioButtonWidget) {
return props.getCheckboxRenderWidth.width + getResizeBorderExtraWidth;
} else {
return width();
}
};
const handleMinHeight = () => {
if (props.pos.type === "checkbox" || props.pos.type === radioButtonWidget) {
return props.getCheckboxRenderWidth.height + getResizeBorderExtraWidth;
} else {
return height();
}
};
return (
<div
onMouseEnter={() => !isMobile && props?.setDraggingEnabled(false)}
onTouchEnd={() =>
props.pos.type === textWidget && props?.setDraggingEnabled(false)
}
className="absolute inline-block w-[14px] h-[14px] border-[0.2px] overflow-hidden border-dashed"
style={{
borderColor: themeColor,
minWidth: handleMinWidth() || 0,
minHeight: handleMinHeight() || 0
}}
></div>
);
}
export default PlaceholderBorder;
@@ -1,903 +0,0 @@
import React, { useEffect, useState, forwardRef, useRef } from "react";
import {
getMonth,
getYear,
onChangeHeightOfTextArea,
onChangeInput,
radioButtonWidget,
range,
textInputWidget,
textWidget
} from "../../constant/Utils";
import DatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css";
import "../../styles/signature.css";
import RegexParser from "regex-parser";
import { emailRegex } from "../../constant/const";
import { useTranslation } from "react-i18next";
const textWidgetCls =
"w-full h-full md:min-w-full md:min-h-full z-[999] text-[12px] rounded-[2px] border-[1px] border-[#007bff] overflow-hidden resize-none outline-none text-base-content item-center whitespace-pre-wrap bg-white";
const selectWidgetCls =
"w-full h-full absolute left-0 top-0 border-[1px] border-[#007bff] rounded-[2px] focus:outline-none text-base-content";
const widgetCls =
"select-none-cls overflow-hidden w-full h-full text-black flex flex-col justify-center items-center";
function PlaceholderType(props) {
const { t } = useTranslation();
const type = 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";
const months = [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December"
];
const textWidgetStyle = {
fontSize: fontSize,
color: fontColor,
fontFamily: "Arial, sans-serif",
overflow: "hidden",
textAlign: "start",
width: "100%",
display: "flex",
height: "100%"
};
const validateExpression = (regexValidation) => {
if (textValue && regexValidation) {
let regexObject = regexValidation;
if (props.pos?.options?.validation?.type === "regex") {
regexObject = RegexParser(regexValidation);
}
// new RegExp(regexValidation);
let isValidate = regexObject.test(textValue);
if (!isValidate) {
props?.setValidateAlert(true);
inputRef.current.focus();
}
}
};
const handleInputBlur = () => {
const validateType = props.pos?.options?.validation?.type;
let regexValidation;
if (validateType && validateType !== "text") {
switch (validateType) {
case "email":
regexValidation = emailRegex;
validateExpression(regexValidation);
break;
case "number":
regexValidation = /^[0-9\s]*$/;
validateExpression(regexValidation);
break;
default:
regexValidation = props.pos?.options?.validation?.pattern || "";
validateExpression(regexValidation);
}
}
};
const handleTextValid = (e) => {
const textInput = e.target.value;
setTextValue(textInput);
};
function checkRegularExpress(validateType) {
switch (validateType) {
case "email":
setValidatePlaceholder("demo@gmail.com");
break;
case "number":
setValidatePlaceholder("12345");
break;
case "text":
setValidatePlaceholder("please enter text");
break;
default:
setValidatePlaceholder("please enter value");
}
}
useEffect(() => {
if (type && type === "checkbox" && props.isNeedSign) {
const isDefaultValue = props.pos.options?.defaultValue;
if (isDefaultValue) {
setSelectedCheckbox(isDefaultValue);
}
} else if (props.pos?.options?.hint) {
setValidatePlaceholder(props.pos?.options.hint);
} else if (props.pos?.options?.validation?.type) {
checkRegularExpress(props.pos?.options?.validation?.type);
}
setTextValue(
props.pos?.options?.response
? props.pos?.options?.response
: props.pos?.options?.defaultValue
? props.pos?.options?.defaultValue
: ""
);
setSelectOption(
props.pos?.options?.response
? props.pos?.options?.response
: props.pos?.options?.defaultValue
? props.pos?.options?.defaultValue
: ""
);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
if (
["name", "email", "job title", "company"].includes(props.pos?.type) &&
props.isNeedSign &&
props.data?.signerObjId === props?.signerObjId
) {
const defaultData = props.pos?.options?.defaultValue;
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) {
setTextValue(defaultData);
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [props.pos?.options?.defaultValue]);
const ExampleCustomInput = forwardRef(({ value, onClick }, ref) => (
<div
style={{
fontSize: fontSize,
color: fontColor,
fontFamily: "Arial, sans-serif"
}}
className={`${selectWidgetCls} overflow-hidden`}
onClick={onClick}
ref={ref}
>
{value}
<i className="fa-light fa-calendar ml-[5px]"></i>
</div>
));
ExampleCustomInput.displayName = "ExampleCustomInput";
useEffect(() => {
if (
["name", "email", "job title", "company"].includes(type) &&
props.isNeedSign &&
props.data?.signerObjId === props.signerObjId
) {
const isDefault = true;
const senderUser = localStorage.getItem("Extand_Class");
const jsonSender = JSON.parse(senderUser);
onChangeInput(
jsonSender && jsonSender[0],
null,
props.xyPosition,
null,
props.setXyPosition,
props.data.Id,
isDefault
);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [type]);
//function for show checked checkbox
const selectCheckbox = (ind) => {
const res = props.pos.options?.response;
const defaultCheck = props.pos.options?.defaultValue;
if (res && res?.length > 0) {
const isSelectIndex = res.indexOf(ind);
if (isSelectIndex > -1) {
return true;
} else {
return false;
}
// }
} else if (defaultCheck) {
const isSelectIndex = defaultCheck.indexOf(ind);
if (isSelectIndex > -1) {
return true;
} else {
return false;
}
} else {
return false;
}
};
const handleRadioCheck = (data) => {
const defaultData = props.pos.options?.defaultValue;
if (textValue === data) {
return true;
} else if (defaultData === data) {
return true;
} else {
return false;
}
};
//function for set checked and unchecked value of checkbox
const handleCheckboxValue = (isChecked, ind) => {
let updateSelectedCheckbox = [],
checkedList;
let isDefaultValue, isDefaultEmpty;
if (type === "checkbox") {
updateSelectedCheckbox = selectedCheckbox ? selectedCheckbox : [];
if (isChecked) {
updateSelectedCheckbox.push(ind);
setSelectedCheckbox(updateSelectedCheckbox);
} else {
checkedList = selectedCheckbox.filter((data) => data !== ind);
setSelectedCheckbox(checkedList);
}
if (props.isNeedSign) {
isDefaultValue = props.pos.options?.defaultValue;
}
if (isDefaultValue && isDefaultValue.length > 0) {
isDefaultEmpty = true;
}
onChangeInput(
checkedList ? checkedList : updateSelectedCheckbox,
props.pos.key,
props.xyPosition,
props.index,
props.setXyPosition,
props.data && props.data.Id,
false,
null,
isDefaultEmpty
);
}
};
//function to handle select radio widget and set value seletced by user
const handleCheckRadio = (isChecked, data) => {
let isDefaultValue,
isDefaultEmpty,
isRadio = true;
if (props.isNeedSign) {
isDefaultValue = props.pos.options?.defaultValue;
}
if (isDefaultValue) {
isDefaultEmpty = true;
}
if (isChecked) {
setTextValue(data);
} else {
setTextValue("");
}
onChangeInput(
data,
props.pos.key,
props.xyPosition,
props.index,
props.setXyPosition,
props.data && props.data.Id,
false,
null,
isDefaultEmpty,
isRadio
);
};
//function to set onchange date
const handleOnDateChange = (date) => {
props.setStartDate(date);
};
//handle height on enter press in text area
const handleEnterPress = (e) => {
const height = 18;
if (e.key === "Enter") {
//function to save height of text area
onChangeHeightOfTextArea(
height,
props.pos.type,
props.pos.key,
props.xyPosition,
props.index,
props.setXyPosition,
props.data && props.data?.Id
);
}
};
switch (type) {
case "signature":
return props.pos.SignUrl ? (
<img
alt="signature"
draggable="false"
src={props.pos.SignUrl}
className="w-full h-full select-none-cls "
/>
) : (
<div className={widgetCls}>
{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":
return props.pos.SignUrl ? (
<img
alt="stamp"
draggable="false"
src={props.pos.SignUrl}
className="w-full h-full select-none-cls"
/>
) : (
<div className={widgetCls}>
{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":
return (
<div style={{ zIndex: props.isSignYourself && "99" }}>
{props.pos.options?.values?.map((data, ind) => {
return (
<div
key={ind}
className="select-none-cls flex items-center text-center gap-0.5"
>
<input
id={`checkbox-${props.pos.key + ind}`}
style={{ width: fontSize, height: fontSize }}
className={`${
ind === 0 ? "mt-0" : "mt-[5px]"
} flex justify-center op-checkbox rounded-[1px] `}
onBlur={handleInputBlur}
disabled={
props.isNeedSign &&
(props.pos.options?.isReadOnly ||
props.data?.signerObjId !== props.signerObjId)
}
type="checkbox"
checked={selectCheckbox(ind)}
onChange={(e) => {
if (e.target.checked) {
if (!props.isPlaceholder) {
const maxRequired =
props.pos.options?.validation?.maxRequiredCount;
const maxCountInt =
maxRequired && parseInt(maxRequired);
if (maxCountInt > 0) {
if (
selectedCheckbox &&
selectedCheckbox?.length <= maxCountInt - 1
) {
handleCheckboxValue(e.target.checked, ind);
}
} else {
handleCheckboxValue(e.target.checked, ind);
}
}
} else {
handleCheckboxValue(e.target.checked, ind);
}
}}
/>
{!props.pos.options?.isHideLabel && (
<label
htmlFor={`checkbox-${props.pos.key + ind}`}
style={{ fontSize: fontSize, color: fontColor }}
className="text-xs mb-0 text-center"
>
{data}
</label>
)}
</div>
);
})}
</div>
);
case textInputWidget:
return props.isSignYourself ||
(props.isSelfSign && props.data?.signerObjId === props.signerObjId) ||
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
<textarea
ref={inputRef}
placeholder={validatePlaceholder || t("widgets-name.text")}
rows={1}
onKeyDown={handleEnterPress}
value={textValue}
onBlur={handleInputBlur}
onChange={(e) => {
setTextValue(e.target.value);
onChangeInput(
e.target.value,
props.pos.key,
props.xyPosition,
props.index,
props.setXyPosition,
props.data && props.data?.Id,
false
);
}}
className={`${
props.isNeedSign &&
(props.pos.options?.isReadOnly ||
props.data?.signerObjId !== props.signerObjId)
? " bg-black/68 select-none "
: "" + textWidgetCls
}`}
style={{ fontSize: fontSize, color: fontColor }}
disabled={
props.isNeedSign &&
(props.pos.options?.isReadOnly ||
props.data?.signerObjId !== props.signerObjId)
}
cols="50"
/>
) : (
<div style={textWidgetStyle} className="select-none-cls">
<span>{textValue || widgetTypeTranslation}</span>
</div>
);
case "dropdown":
return props.data?.signerObjId === props.signerObjId ? (
<select
style={{ fontSize: fontSize, color: fontColor }}
className={`${
props.isNeedSign &&
(props.pos.options?.isReadOnly ||
props.data?.signerObjId !== props.signerObjId)
? " disabled:bg-inherit select-none "
: "" + `${selectWidgetCls} text-[12px] bg-inherit`
}`}
id="myDropdown"
value={selectOption}
onChange={(e) => {
setSelectOption(e.target.value);
onChangeInput(
e.target.value,
props.pos.key,
props.xyPosition,
props.index,
props.setXyPosition,
props.data && props.data?.Id,
false
);
}}
disabled={
props.isNeedSign &&
(props.pos.options?.isReadOnly ||
props.data?.signerObjId !== props.signerObjId)
}
>
{/* Default/Title option */}
<option
style={{ fontSize: fontSize, color: fontColor }}
value=""
disabled
hidden
>
{props?.pos?.options?.name}
</option>
{props.pos?.options?.values?.map((data, ind) => {
return (
<option
style={{ fontSize: fontSize, color: fontColor }}
key={ind}
value={data}
>
{data}
</option>
);
})}
</select>
) : (
<div
style={textWidgetStyle}
className="select-none-cls flex justify-between items-center"
>
{props.pos?.options?.name
? props.pos.options.name
: widgetTypeTranslation}
<i className="fa-light fa-circle-chevron-down mr-1 "></i>
</div>
);
case "initials":
return props.pos.SignUrl ? (
<img
alt="initials"
draggable="false"
src={props.pos.SignUrl}
className="w-full h-full select-none-cls"
/>
) : (
<div className={widgetCls}>
{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":
return props.isSignYourself ||
(props.isSelfSign && props.data?.signerObjId === props.signerObjId) ||
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
<textarea
ref={inputRef}
placeholder={hint || widgetTypeTranslation}
rows={1}
onKeyDown={handleEnterPress}
value={textValue}
onChange={(e) => {
const isDefault = false;
handleTextValid(e);
onChangeInput(
e.target.value,
props.pos.key,
props.xyPosition,
props.index,
props.setXyPosition,
props.data && props.data?.Id,
isDefault
);
}}
className={textWidgetCls}
style={{ fontSize: fontSize, color: fontColor }}
cols="50"
/>
) : (
<div className="flex h-full select-none-cls" style={textWidgetStyle}>
<span>{widgetTypeTranslation}</span>
</div>
);
case "company":
return props.isSignYourself ||
(props.isSelfSign && props.data?.signerObjId === props.signerObjId) ||
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
<textarea
ref={inputRef}
placeholder={hint || widgetTypeTranslation}
rows={1}
onKeyDown={handleEnterPress}
value={textValue}
onChange={(e) => {
handleTextValid(e);
onChangeInput(
e.target.value,
props.pos.key,
props.xyPosition,
props.index,
props.setXyPosition,
props.data && props.data?.Id,
false
);
}}
className={textWidgetCls}
style={{ fontSize: fontSize, color: fontColor }}
cols="50"
/>
) : (
<div style={textWidgetStyle} className="select-none-cls">
<span>{widgetTypeTranslation}</span>
</div>
);
case "job title":
return props.isSignYourself ||
(props.isSelfSign && props.data?.signerObjId === props.signerObjId) ||
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
<textarea
ref={inputRef}
placeholder={hint || widgetTypeTranslation}
rows={1}
onKeyDown={handleEnterPress}
value={textValue}
onChange={(e) => {
handleTextValid(e);
onChangeInput(
e.target.value,
props.pos.key,
props.xyPosition,
props.index,
props.setXyPosition,
props.data && props.data?.Id,
false
);
}}
className={textWidgetCls}
style={{ fontSize: fontSize, color: fontColor }}
cols="50"
/>
) : (
<div style={textWidgetStyle} className="select-none-cls">
<span>{widgetTypeTranslation}</span>
</div>
);
case "date":
return props.isSignYourself ||
(props.isSelfSign && props.data?.signerObjId === props.signerObjId) ||
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
<DatePicker
renderCustomHeader={({ date, changeYear, changeMonth }) => (
<div className="flex justify-start ml-2 ">
<select
className="bg-transparent outline-none"
value={months[getMonth(date)]}
onChange={({ target: { value } }) =>
changeMonth(months.indexOf(value))
}
>
{months.map((option) => (
<option key={option} value={option}>
{option}
</option>
))}
</select>
<select
className="bg-transparent outline-none"
value={getYear(date)}
onChange={({ target: { value } }) => changeYear(value)}
>
{years.map((option) => (
<option key={option} value={option}>
{option}
</option>
))}
</select>
</div>
)}
disabled={
props.isPlaceholder ||
(props.isNeedSign && props.data?.signerObjId !== props.signerObjId)
}
onBlur={handleInputBlur}
closeOnScroll={true}
className={`${selectWidgetCls} outline-[#007bff]`}
selected={props?.startDate}
onChange={(date) => handleOnDateChange(date)}
popperPlacement="top-end"
customInput={<ExampleCustomInput />}
dateFormat={
props.selectDate
? props.selectDate?.format
: props.pos?.options?.validation?.format
? props.pos?.options?.validation?.format
: "MM/dd/yyyy"
}
/>
) : (
<div
style={textWidgetStyle}
className="select-none-cls overflow-hidden"
>
<span>
{props.selectDate
? props.selectDate?.format
: props.pos?.options?.validation?.format
? props.pos?.options?.validation?.format
: "MM/dd/yyyy"}
</span>
</div>
);
case "image":
return props.pos.SignUrl ? (
<img
alt="image"
draggable="false"
src={props.pos.SignUrl}
className="w-full h-full select-none-cls"
/>
) : (
<div className={widgetCls}>
{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":
return props.isSignYourself ||
(props.isSelfSign && props.data?.signerObjId === props.signerObjId) ||
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
<textarea
ref={inputRef}
placeholder={hint || widgetTypeTranslation}
rows={1}
onKeyDown={(e) => {
// Prevent new line on Enter key press
if (e.key === "Enter") {
e.preventDefault();
}
}}
value={textValue}
onBlur={handleInputBlur}
onChange={(e) => {
handleTextValid(e);
onChangeInput(
e.target.value,
props.pos.key,
props.xyPosition,
props.index,
props.setXyPosition,
props.data && props.data?.Id,
false
);
}}
className={textWidgetCls}
style={{
fontSize: fontSize,
color: fontColor,
fontFamily: "Arial, sans-serif"
}}
cols="1"
/>
) : (
<div style={textWidgetStyle} className="select-none-cls">
<span>{widgetTypeTranslation}</span>
</div>
);
case radioButtonWidget:
return (
<div>
{props.pos.options?.values.map((data, ind) => {
return (
<div
key={ind}
className="select-none-cls flex items-center text-center gap-0.5"
>
<input
id={`radio-${props.pos.key + ind}`}
style={{
width: fontSize,
height: fontSize,
marginTop: ind > 0 ? "10px" : "0px"
}}
className={`flex justify-center op-radio`}
type="radio"
disabled={
props.isNeedSign &&
(props.pos.options?.isReadOnly ||
props.data?.signerObjId !== props.signerObjId)
}
checked={handleRadioCheck(data)}
onChange={(e) => {
if (!props.isPlaceholder) {
handleCheckRadio(e.target.checked, data);
}
}}
/>
{!props.pos.options?.isHideLabel && (
<label
htmlFor={`radio-${props.pos.key + ind}`}
style={{ fontSize: fontSize, color: fontColor }}
className="text-xs mb-0"
>
{data}
</label>
)}
</div>
);
})}
</div>
);
case textWidget:
return (
<textarea
placeholder={t("widgets-name.text")}
rows={1}
onKeyDown={handleEnterPress}
value={textValue}
onBlur={handleInputBlur}
onChange={(e) => {
setTextValue(e.target.value);
onChangeInput(
e.target.value,
props.pos.key,
props.xyPosition,
props.index,
props.setXyPosition,
props.data && props.data?.Id,
false
);
}}
className={textWidgetCls}
style={{
fontFamily: "Arial, sans-serif",
fontSize: fontSize,
color: fontColor
}}
cols="50"
/>
);
default:
return props.pos.SignUrl ? (
<div className="pointer-events-none">
<img
alt="image"
draggable="false"
src={props.pos.SignUrl}
className="w-full h-full "
/>
</div>
) : (
<div className={widgetCls}>
{props.pos.isStamp ? <div>stamp</div> : <div>signature</div>}
{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>
);
}
}
export default PlaceholderType;
@@ -0,0 +1,528 @@
import React, { useEffect, useState, forwardRef } from "react";
import {
getMonth,
getYear,
radioButtonWidget,
textInputWidget,
cellsWidget,
textWidget,
months,
years,
selectCheckbox,
checkRegularExpress
} from "../../constant/Utils";
import DatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css";
import "../../styles/signature.css";
import { useTranslation } from "react-i18next";
import CellsWidget from "./CellsWidget";
const textWidgetCls =
"w-full h-full md:min-w-full md:min-h-full z-[999] text-[12px] rounded-[2px] border-[1px] border-[#007bff] overflow-hidden resize-none outline-none text-base-content item-center whitespace-pre-wrap";
const selectWidgetCls =
"w-full h-full absolute left-0 top-0 border-[1px] border-[#007bff] rounded-[2px] focus:outline-none text-base-content";
const widgetCls =
"select-none-cls overflow-hidden w-full h-full text-black flex flex-col justify-center items-center";
function PlaceholderType(props) {
const { t } = useTranslation();
const type = props?.pos?.type;
const iswidgetEnable =
props.isSignYourself ||
((props.isSelfSign || props.isNeedSign) &&
props.data?.signerObjId === props.signerObjId);
const isReadOnly =
props.pos.options?.isReadOnly ||
props.data?.signerObjId !== props.signerObjId;
// prefer the latest response value over any default value
const widgetData =
props.pos?.options?.response ?? props.pos?.options?.defaultValue ?? "";
const widgetTypeTranslation = t(`widgets-name.${props?.pos?.type}`);
const [widgetValue, setwidgetValue] = useState();
const [selectedCheckbox, setSelectedCheckbox] = useState([]);
const [hint, setHint] = useState("");
const fontSize = props.calculateFont(props.pos.options?.fontSize);
const fontColor = props.pos.options?.fontColor || "black";
const textWidgetStyle = {
fontSize: fontSize,
color: fontColor,
fontFamily: "Arial, sans-serif",
overflow: "hidden",
textAlign: "start",
width: "100%",
display: "flex",
height: "100%"
};
useEffect(() => {
if (type !== "date") {
if (type && type === "checkbox") {
setSelectedCheckbox(
props?.pos?.options?.response ||
props?.pos?.options?.defaultValue ||
[]
);
} else {
// keep displayed value in sync with the stored response
setwidgetValue(widgetData);
}
if (props.pos?.options?.hint) {
setHint(props.pos?.options.hint);
} else if (props.pos?.options?.validation?.type) {
checkRegularExpress(props.pos?.options?.validation?.type, setHint);
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [props.pos]);
const ExampleCustomInput = forwardRef(({ value, onClick }, ref) => (
<div
style={{
fontSize: fontSize,
color: fontColor,
fontFamily: "Arial, sans-serif"
}}
className={`${isReadOnly ? `select-none` : ``} ${selectWidgetCls} overflow-hidden`}
disabled={isReadOnly}
onClick={onClick}
ref={ref}
>
{value}
<i className="fa-light fa-calendar ml-[5px]"></i>
</div>
));
ExampleCustomInput.displayName = "ExampleCustomInput";
const handleRadioCheck = (data) => {
const defaultData = props.pos.options?.defaultValue;
if (widgetValue === data) {
return true;
} else if (defaultData === data) {
return true;
} else {
return false;
}
};
switch (type) {
case "signature":
return props.pos.SignUrl ? (
<img
alt="signature"
draggable="false"
src={props.pos.SignUrl}
className={`${props.pos.signatureType !== "type" ? "object-contain" : ""} w-full h-full select-none-cls`}
/>
) : (
<div className={widgetCls}>
{props.pos.type && (
<div
style={{
fontSize: props.pos
? props.calculateFontsize(props.pos)
: "11px"
}}
className="font-medium"
>
{hint || widgetTypeTranslation}
</div>
)}
</div>
);
case "stamp":
return props.pos.SignUrl ? (
<img
alt="stamp"
draggable="false"
src={props.pos.SignUrl}
className="w-full h-full select-none-cls object-contain"
/>
) : (
<div className={widgetCls}>
{props.pos.type && (
<div
style={{
fontSize: props.pos
? props.calculateFontsize(props.pos)
: "11px"
}}
className="font-medium"
>
{hint || widgetTypeTranslation}
</div>
)}
</div>
);
case "checkbox":
const checkBoxLayout = props.pos.options?.layout || "vertical";
const isMultipleCheckbox =
props.pos.options?.values?.length > 0 ? true : false;
const checkBoxWrapperClass = `flex items-start ${
checkBoxLayout === "horizontal"
? `flex-row flex-wrap ${isMultipleCheckbox ? "gap-x-2" : ""}`
: `flex-col ${isMultipleCheckbox ? "gap-y-[3px]" : ""}`
}`; // Using gap-y-1 for consistency, adjust if needed
return (
<div
className={checkBoxWrapperClass}
style={{ zIndex: props.isSignYourself && "99" }}
>
{props.pos.options?.values?.map((data, ind) => (
<div key={ind} className="select-none-cls pointer-events-none">
<label
htmlFor={`checkbox-${props.pos.key + ind}`}
style={{ fontSize: fontSize, color: fontColor }}
className={`mb-0 flex items-center gap-1`}
>
<input
id={`checkbox-${props.pos.key + ind}`}
style={{ width: fontSize, height: fontSize }}
className="op-checkbox rounded-[1px]"
disabled={props.isNeedSign && isReadOnly}
type="checkbox"
readOnly
checked={!!selectCheckbox(ind, selectedCheckbox)}
/>
{!props.pos.options?.isHideLabel && (
<span className="leading-none">{data}</span>
)}
</label>
</div>
))}
</div>
);
case textInputWidget:
return props.isSignYourself || iswidgetEnable ? (
<textarea
placeholder={hint || t("widgets-name.text")}
rows={1}
value={widgetValue}
className={`${textWidgetCls} ${isReadOnly ? "select-none" : ""}`}
style={{
fontSize: fontSize,
color: fontColor,
background: isReadOnly ? props.data?.blockColor : "white",
pointerEvents: "none"
}}
readOnly
disabled={props.isNeedSign && isReadOnly}
cols="50"
/>
) : (
<div style={textWidgetStyle} className="select-none-cls">
<span>{hint || widgetTypeTranslation}</span>
</div>
);
case cellsWidget: {
const count = props.pos.options?.cellCount || 5;
const cells = (widgetValue || "").split("");
const height = "100%";
const fontSize = props.calculateFont(props.pos.options?.fontSize);
const fontColor = props.pos.options?.fontColor || "black";
const handleCellResize = (newCount) => {
if (props.setCellCount) props.setCellCount(props.pos.key, newCount);
};
const isEditable =
props.isPlaceholder || props.isSignYourself || props.isSelfSign;
return (
<CellsWidget
isEnabled={iswidgetEnable}
count={count}
height={height}
value={cells.join("")}
editable={isEditable}
resizable={isEditable}
fontSize={fontSize}
fontColor={fontColor}
hint={hint}
onCellCountChange={handleCellResize}
/>
);
}
case "dropdown":
return (
<div
style={textWidgetStyle}
className="select-none-cls flex justify-between items-center"
>
{widgetData || t("choose-one")}
<i className="fa-light fa-circle-chevron-down mr-1 "></i>
</div>
);
case "initials":
return props.pos.SignUrl ? (
<img
alt="initials"
draggable="false"
src={props.pos.SignUrl}
className={`${props.pos.signatureType !== "type" ? "object-contain" : ""} w-full h-full select-none-cls`}
/>
) : (
<div className={widgetCls}>
{props.pos.type && (
<div
style={{
fontSize: props.pos
? props.calculateFontsize(props.pos)
: "11px"
}}
className="font-medium text-center"
>
{hint || widgetTypeTranslation}
</div>
)}
</div>
);
case "name":
return iswidgetEnable ? (
<textarea
readOnly
placeholder={hint || widgetTypeTranslation}
rows={1}
value={widgetValue}
className={`${textWidgetCls} ${isReadOnly ? "select-none" : ""}`}
style={{
fontSize: fontSize,
color: fontColor,
background: isReadOnly ? props.data?.blockColor : "white",
pointerEvents: "none"
}}
cols="50"
disabled={props.isNeedSign && isReadOnly}
/>
) : (
<div className="flex h-full select-none-cls" style={textWidgetStyle}>
<span> {props.pos?.options?.hint || widgetTypeTranslation}</span>
</div>
);
case "company":
return iswidgetEnable ? (
<textarea
readOnly
placeholder={hint || widgetTypeTranslation}
rows={1}
value={widgetValue}
className={`${textWidgetCls} ${isReadOnly ? "select-none" : ""}`}
style={{
fontSize: fontSize,
color: fontColor,
background: isReadOnly ? props.data?.blockColor : "white",
pointerEvents: "none"
}}
cols="50"
disabled={props.isNeedSign && isReadOnly}
/>
) : (
<div style={textWidgetStyle} className="select-none-cls">
<span>{hint || widgetTypeTranslation}</span>
</div>
);
case "job title":
return iswidgetEnable ? (
<textarea
readOnly
placeholder={hint || widgetTypeTranslation}
rows={1}
value={widgetValue}
className={`${textWidgetCls} ${isReadOnly ? "select-none" : ""}`}
style={{
fontSize: fontSize,
color: fontColor,
background: isReadOnly ? props.data?.blockColor : "white",
pointerEvents: "none"
}}
cols="50"
disabled={props.isNeedSign && isReadOnly}
/>
) : (
<div style={textWidgetStyle} className="select-none-cls">
<span>{hint || widgetTypeTranslation}</span>
</div>
);
case "date":
return iswidgetEnable ? (
<DatePicker
renderCustomHeader={({ date, changeYear, changeMonth }) => (
<div className="flex justify-start ml-2 ">
<select
className="bg-transparent outline-none"
value={months[getMonth(date)]}
onChange={({ target: { value } }) =>
changeMonth(months.indexOf(value))
}
>
{months.map((option) => (
<option key={option} value={option}>
{option}
</option>
))}
</select>
<select
className="bg-transparent outline-none"
value={getYear(date)}
onChange={({ target: { value } }) => changeYear(value)}
>
{years.map((option) => (
<option key={option} value={option}>
{option}
</option>
))}
</select>
</div>
)}
disabled={true}
closeOnScroll={true}
className={`${selectWidgetCls} outline-[#007bff]`}
selected={props?.startDate}
popperPlacement="top-end"
customInput={<ExampleCustomInput />}
dateFormat={
props.selectDate
? props.selectDate?.format
: props.pos?.options?.validation?.format
? props.pos?.options?.validation?.format
: "MM/dd/yyyy"
}
/>
) : (
<div
style={textWidgetStyle}
className="select-none-cls overflow-hidden"
>
<span>
{props.selectDate
? props.selectDate?.format
: props.pos?.options?.validation?.format
? props.pos?.options?.validation?.format
: "MM/dd/yyyy"}
</span>
</div>
);
case "image":
return props.pos.SignUrl ? (
<img
alt="image"
draggable="false"
src={props.pos.SignUrl}
className="w-full h-full select-none-cls object-contain"
/>
) : (
<div className={widgetCls}>
{props.pos.type && (
<div
style={{
fontSize: props.pos
? props.calculateFontsize(props.pos)
: "11px"
}}
className="font-medium text-center"
>
{hint || widgetTypeTranslation}
</div>
)}
</div>
);
case "email":
return iswidgetEnable ? (
<textarea
readOnly
placeholder={hint || widgetTypeTranslation}
rows={1}
value={widgetValue}
className={`${textWidgetCls} ${isReadOnly ? "select-none" : ""}`}
style={{
fontSize: fontSize,
color: fontColor,
background: isReadOnly ? props.data?.blockColor : "white",
pointerEvents: "none"
}}
cols="1"
disabled={props.isNeedSign && isReadOnly}
/>
) : (
<div style={textWidgetStyle} className="select-none-cls">
<span>{hint || widgetTypeTranslation}</span>
</div>
);
case radioButtonWidget:
const radioLayout = props.pos.options?.layout || "vertical";
const isOnlyOneBtn = props.pos.options?.values?.length > 0 ? true : false;
const radioWrapperClass = `flex items-start ${
radioLayout === "horizontal"
? `flex-row flex-wrap ${isOnlyOneBtn ? "gap-x-[10px]" : ""}`
: `flex-col ${isOnlyOneBtn ? "gap-y-[5px]" : ""}`
}`; // Using gap-y-1 for consistency, adjust if needed
return (
<div className={radioWrapperClass}>
{props.pos.options?.values.map((data, ind) => (
<div key={ind} className="select-none-cls pointer-events-none">
<label
htmlFor={`radio-${props.pos.key + ind}`}
style={{ fontSize: fontSize, color: fontColor }}
className="text-xs mb-0 flex items-center gap-1"
>
<input
readOnly
id={`radio-${props.pos.key + ind}`}
style={{ width: fontSize, height: fontSize, lineHeight: 2 }}
className={`op-radio rounded-full border-black appearance-none bg-white inline-block align-middle relative ${
handleRadioCheck(data) ? "checked-radio" : ""
}`}
type="radio"
disabled={props.isNeedSign && isReadOnly}
checked={handleRadioCheck(data)}
/>
{!props.pos.options?.isHideLabel && (
<span className="leading-none">{data}</span>
)}
</label>
</div>
))}
</div>
);
case textWidget:
return (
<textarea
readOnly
placeholder={t("widgets-name.text")}
rows={1}
value={widgetValue}
className={textWidgetCls}
style={{
fontFamily: "Arial, sans-serif",
fontSize: fontSize,
color: fontColor,
background: "white"
}}
cols="50"
/>
);
default:
return props.pos.SignUrl ? (
<div className="pointer-events-none">
<img
alt="image"
draggable="false"
src={props.pos.SignUrl}
className="w-full h-full "
/>
</div>
) : (
<div className={widgetCls}>
{props.pos.isStamp ? <div>stamp</div> : <div>signature</div>}
{props.pos.type && (
<div
style={{
fontSize: props.pos
? props.calculateFontsize(props.pos)
: "11px"
}}
className="font-medium"
>
{hint || widgetTypeTranslation}
</div>
)}
</div>
);
}
}
export default PlaceholderType;
@@ -31,7 +31,7 @@ const RecipientList = (props) => {
e.preventDefault();
};
//handle draggable element drop and also used in mobile view on up and down key to chnage sequence of recipient's list
//handle draggable element drop and also used in mobile view on up and down key to change sequence of recipient's list
const handleChangeSequence = (e, ind, isUp, isDown, obj) => {
e.preventDefault();
let draggedItemId;
@@ -82,7 +82,7 @@ const RecipientList = (props) => {
};
return (
<>
{props.signersdata.length > 0 &&
{props.signersdata?.length > 0 &&
props.signersdata.map((obj, ind) => {
return (
<div
@@ -129,9 +129,9 @@ const RecipientList = (props) => {
}
}}
>
<div className="flex flex-row items-center w-full">
<div className="flex flex-row items-center w-full overflow-hidden pr-2">
<div
className="flex w-[30px] h-[30px] rounded-full items-center justify-center mr-2"
className="flex flex-shrink-0 w-[30px] h-[30px] rounded-full items-center justify-center mr-2"
style={{
background: obj?.blockColor
? darkenColor(obj?.blockColor, 0.4)
@@ -153,7 +153,7 @@ const RecipientList = (props) => {
<div
className={`${
obj.Name ? "flex-col" : "flex-row"
} flex items-center`}
} flex overflow-hidden flex-grow-0`}
>
{obj.Name ? (
<span
@@ -162,7 +162,7 @@ const RecipientList = (props) => {
props.isSelectListId === ind
? "text-[#424242]"
: "text-base-content"
} text-[12px] font-bold w-[100px] whitespace-nowrap overflow-hidden text-ellipsis`}
} text-[12px] font-bold truncate whitespace-nowrap`}
>
{obj.Name}
</span>
@@ -173,7 +173,7 @@ const RecipientList = (props) => {
props.isSelectListId === ind
? "text-[#424242]"
: "text-base-content"
} text-[12px] font-bold w-[100px] whitespace-nowrap overflow-hidden text-ellipsis cursor-pointer`}
} text-[12px] font-bold truncate whitespace-nowrap cursor-pointer`}
onClick={() => {
setIsEdit({ [obj.Id]: true });
props.setRoleName(obj.Role);
@@ -182,7 +182,7 @@ const RecipientList = (props) => {
{isEdit?.[obj.Id] && props.handleRoleChange ? (
<input
ref={inputRef}
className="bg-transparent p-[3px]"
className="bg-transparent p-[3px] w-full"
value={obj.Role}
onChange={(e) => props.handleRoleChange(e, obj.Id)}
onBlur={() => {
@@ -210,7 +210,7 @@ const RecipientList = (props) => {
props.isSelectListId === ind
? "text-[#424242]"
: "text-base-content"
} text-[10px] font-medium w-[100px] whitespace-nowrap overflow-hidden text-ellipsis`}
} text-[10px] font-medium truncate whitespace-nowrap`}
>
{obj?.Role || obj?.Email}
</span>
@@ -3,7 +3,12 @@ import { useTranslation } from "react-i18next";
import { Document, Page } from "react-pdf";
import { useSelector } from "react-redux";
import { PDFDocument } from "pdf-lib";
import { base64ToArrayBuffer } from "../../constant/Utils";
import {
base64ToArrayBuffer,
decryptPdf,
flattenPdf,
getFileAsArrayBuffer
} from "../../constant/Utils";
import { maxFileSize } from "../../constant/const";
function RenderAllPdfPage(props) {
@@ -87,7 +92,42 @@ function RenderAllPdfPage(props) {
return;
}
try {
const uploadedPdfBytes = await file.arrayBuffer();
let uploadedPdfBytes = await file.arrayBuffer();
try {
uploadedPdfBytes = await flattenPdf(uploadedPdfBytes);
} catch (err) {
if (err?.message?.includes("is encrypted")) {
try {
const pdfFile = await decryptPdf(file, "");
const pdfArrayBuffer = await getFileAsArrayBuffer(pdfFile);
uploadedPdfBytes = await flattenPdf(pdfArrayBuffer);
} catch (err) {
if (err?.response?.status === 401) {
const password = prompt(
`PDF "${file.name}" is password-protected. Enter password:`
);
if (password) {
try {
const pdfFile = await decryptPdf(file, password);
const pdfArrayBuffer = await getFileAsArrayBuffer(pdfFile);
uploadedPdfBytes = await flattenPdf(pdfArrayBuffer);
// Upload the file to Parse Server
} catch (err) {
console.error("Incorrect password or decryption failed", err);
alert("Incorrect password or decryption failed.");
}
} else {
alert("Please provided Password.");
}
} else {
console.log("Err ", err);
alert("error while uploading pdf.");
}
}
} else {
alert("error while uploading pdf.");
}
}
const uploadedPdfDoc = await PDFDocument.load(uploadedPdfBytes, {
ignoreEncryption: true
});
@@ -169,8 +209,8 @@ function RenderAllPdfPage(props) {
onChange={handleFileUpload}
/>
<i className="fa-light fa-plus text-gray-500"></i>
<span className="text-xs lg:text-sm text-base-content ">
Add pages
<span className="text-xs lg:text-sm text-base-content">
{t("add-pages")}
</span>
</button>
)}
@@ -1,635 +0,0 @@
import React, { useState } from "react";
import RSC from "react-scrollbars-custom";
import { Document, Page } from "react-pdf";
import {
defaultWidthHeight,
getContainerScale,
handleImageResize,
handleSignYourselfImageResize,
isMobile
} from "../../constant/Utils";
import Placeholder from "./Placeholder";
import Alert from "../../primitives/Alert";
import { useTranslation } from "react-i18next";
function RenderPdf(props) {
const { t } = useTranslation();
const [scaledHeight, setScaledHeight] = useState();
//check isGuestSigner is present in local if yes than handle login flow header in mobile view
const isGuestSigner = localStorage.getItem("isGuestSigner");
// handle signature block width and height according to screen
const posWidth = (pos, signYourself) => {
const containerScale = getContainerScale(
props.pdfOriginalWH,
props.pageNumber,
props.containerWH
);
const defaultWidth = defaultWidthHeight(pos.type).width;
const posWidth = pos.Width ? pos.Width : defaultWidth;
if (signYourself) {
return posWidth * props.scale * containerScale;
} else {
if (pos.isMobile && pos.scale) {
if (pos.IsResize) {
if (props.scale > 1) {
return posWidth * pos.scale * containerScale * props.scale;
} else {
return posWidth * containerScale;
}
} else {
if (props.scale > 1) {
return posWidth * pos.scale * containerScale * props.scale;
} else {
return posWidth * pos.scale * containerScale;
}
}
} else {
return posWidth * props.scale * containerScale;
}
}
};
const posHeight = (pos, signYourself) => {
const containerScale = getContainerScale(
props.pdfOriginalWH,
props.pageNumber,
props.containerWH
);
const posHeight = pos.Height || defaultWidthHeight(pos.type).height;
if (signYourself) {
return posHeight * props.scale * containerScale;
} else {
if (pos.isMobile && pos.scale) {
if (pos.IsResize) {
if (props.scale > 1) {
return posHeight * pos.scale * containerScale * props.scale;
} else {
return posHeight * containerScale;
}
} else {
if (props.scale > 1) {
return posHeight * pos.scale * containerScale * props.scale;
} else {
return posHeight * pos.scale * containerScale;
}
}
} else {
return posHeight * props.scale * containerScale;
}
}
};
//function for render placeholder block over pdf document
const checkSignedSigners = (data) => {
let checkSign = [];
//condition to handle quick send flow and using normal request sign flow
checkSign = props.signedSigners
? props.signedSigners?.filter(
(sign) =>
sign?.Id === data?.Id || sign?.objectId === data?.signerObjId
)
: [];
return (
checkSign.length === 0 &&
data?.placeHolder?.map((placeData, key) => {
return (
<React.Fragment key={key}>
{placeData.pageNumber === props.pageNumber &&
placeData.pos.map((pos) => {
return (
pos && (
<React.Fragment key={pos.key}>
<Placeholder
pos={pos}
setSignKey={props.setSignKey}
setIsSignPad={props.setIsSignPad}
setIsStamp={props.setIsStamp}
handleSignYourselfImageResize={handleImageResize}
index={props.pageNumber}
xyPosition={props.signerPos}
setXyPosition={props.setSignerPos}
data={data}
setIsResize={props.setIsResize}
isShowBorder={props.isSelfSign}
isAlllowModify={props.isAlllowModify}
signerObjId={props.signerObjectId}
isShowDropdown={true}
isNeedSign={props.pdfRequest}
isSelfSign={true}
isSignYourself={false}
posWidth={posWidth}
posHeight={posHeight}
isDragging={props.isDragging}
pdfDetails={props.pdfDetails}
setIsInitial={props.setIsInitial}
setValidateAlert={props.setValidateAlert}
unSignedWidgetId={props.unSignedWidgetId}
setSelectWidgetId={props.setSelectWidgetId}
selectWidgetId={props.selectWidgetId}
setCurrWidgetsDetails={props.setCurrWidgetsDetails}
uniqueId={props.uniqueId}
scale={props.scale}
containerWH={props.containerWH}
pdfOriginalWH={props.pdfOriginalWH}
pageNumber={props.pageNumber}
ispublicTemplate={props.ispublicTemplate}
handleUserDetails={props.handleUserDetails}
isResize={props.isResize}
setIsAgreeTour={props.setIsAgreeTour}
isAgree={props.isAgree}
handleTabDrag={props.handleTabDrag}
handleStop={props.handleStop}
setWidgetType={props.setWidgetType}
setUniqueId={props.setUniqueId}
setIsSelectId={props.setIsSelectId}
handleDeleteSign={props.handleDeleteSign}
setIsPageCopy={props.setIsPageCopy}
handleTextSettingModal={props.handleTextSettingModal}
setIsCheckbox={props.setIsCheckbox}
isFreeResize={false}
isOpenSignPad={true}
assignedWidgetId={props.assignedWidgetId}
isApplyAll={true}
setFontSize={props.setFontSize}
fontSize={props.fontSize}
fontColor={props.fontColor}
setFontColor={props.setFontColor}
setRequestSignTour={props.setRequestSignTour}
calculateFontsize={calculateFontsize}
/>
</React.Fragment>
)
);
})}
</React.Fragment>
);
})
);
};
const calculateFontsize = (pos) => {
const width = posWidth(pos);
const height = posHeight(pos);
if (height === width || height < width) {
return `${height / 5}px`;
} else if (width < height) {
return `${width / 10}px`;
}
};
const pdfDataBase64 = `data:application/pdf;base64,${props.pdfBase64Url}`;
//calculate render height of pdf in mobile view
const handlePageLoadSuccess = (page) => {
const containerWidth = props.divRef.current.offsetWidth; // Get container width
const viewport = page.getViewport({ scale: 1 });
const scale = containerWidth / viewport.width; // Scale to fit container width
const scaleHeight = viewport.height * scale;
setScaledHeight(scaleHeight);
};
return (
<>
{props.successEmail && (
<Alert type={"success"}>{t("success-email-alert")}</Alert>
)}
{isMobile ? (
<RSC
style={{
position: "relative",
boxShadow: "rgba(17, 12, 46, 0.15) 0px 48px 100px 0px",
//49 is height of header
height: isGuestSigner ? window.innerHeight - 49 : scaledHeight,
zIndex: 0
}}
noScrollY={props.scale === 1 ? true : false}
noScrollX={props.scale === 1 ? true : false}
>
<div
data-tut="reactourForth"
className={`${
isGuestSigner ? "30px" : ""
} border-[0.1px] border-[#ebe8e8] overflow-x-auto`}
style={{
width:
props.containerWH?.width &&
props.containerWH?.width * props.scale
}}
ref={props.drop}
id="container"
>
{props.containerWH?.width &&
props.pdfOriginalWH.length > 0 &&
(props.pdfRequest || props.isSelfSign
? props.signerPos?.map((data, key) => {
return (
<React.Fragment key={key}>
{checkSignedSigners(data)}
</React.Fragment>
);
})
: props.placeholder // placeholder mobile
? props.signerPos?.map((data, ind) => {
return (
<React.Fragment key={ind}>
{data?.placeHolder &&
data?.placeHolder.map((placeData, index) => {
return (
<React.Fragment key={index}>
{placeData.pageNumber === props.pageNumber &&
placeData.pos.map((pos) => {
return (
<React.Fragment key={pos.key}>
<Placeholder
pos={pos}
setIsPageCopy={props.setIsPageCopy}
setSignKey={props.setSignKey}
handleDeleteSign={
props.handleDeleteSign
}
setIsStamp={props.setIsStamp}
handleTabDrag={props.handleTabDrag}
handleStop={props.handleStop}
handleSignYourselfImageResize={
handleImageResize
}
index={props.pageNumber}
xyPosition={props.signerPos}
setXyPosition={props.setSignerPos}
data={data}
setIsResize={props.setIsResize}
setShowDropdown={
props.setShowDropdown
}
isShowBorder={true}
isPlaceholder={true}
setUniqueId={props.setUniqueId}
handleLinkUser={
props.handleLinkUser
}
isSignYourself={false}
posWidth={posWidth}
posHeight={posHeight}
isDragging={props.isDragging}
setIsValidate={props.setIsValidate}
setWidgetType={props.setWidgetType}
setIsRadio={props.setIsRadio}
setIsCheckbox={props.setIsCheckbox}
setCurrWidgetsDetails={
props.setCurrWidgetsDetails
}
setSelectWidgetId={
props.setSelectWidgetId
}
selectWidgetId={
props.selectWidgetId
}
handleNameModal={
props.handleNameModal
}
setTempSignerId={
props.setTempSignerId
}
uniqueId={props.uniqueId}
handleTextSettingModal={
props.handleTextSettingModal
}
scale={props.scale}
containerWH={props.containerWH}
pdfOriginalWH={props.pdfOriginalWH}
pageNumber={props.pageNumber}
setIsSelectId={props.setIsSelectId}
fontSize={props.fontSize}
setFontSize={props.setFontSize}
fontColor={props.fontColor}
setFontColor={props.setFontColor}
isResize={props.isResize}
unSignedWidgetId={
props.unSignedWidgetId
}
isFreeResize={true}
calculateFontsize={
calculateFontsize
}
/>
</React.Fragment>
);
})}
</React.Fragment>
);
})}
</React.Fragment>
);
})
: !props.pdfDetails?.[0]?.IsCompleted &&
props.xyPosition?.map((data, ind) => {
return (
<React.Fragment key={ind}>
{data.pageNumber === props.pageNumber &&
data.pos.map((pos, id) => {
return (
pos && (
<Placeholder
key={id}
pos={pos}
setIsPageCopy={props.setIsPageCopy}
setSignKey={props.setSignKey}
handleDeleteSign={props.handleDeleteSign}
setIsStamp={props.setIsStamp}
handleTabDrag={props.handleTabDrag}
handleStop={props.handleStop}
handleSignYourselfImageResize={
handleSignYourselfImageResize
}
index={props.index}
xyPosition={props.xyPosition}
setXyPosition={props.setXyPosition}
containerWH={props.containerWH}
setIsSignPad={props.setIsSignPad}
isShowBorder={true}
isSignYourself={true}
posWidth={posWidth}
posHeight={posHeight}
pdfDetails={props.pdfDetails[0]}
isDragging={props.isDragging}
setIsInitial={props.setIsInitial}
setWidgetType={props.setWidgetType}
setSelectWidgetId={props.setSelectWidgetId}
selectWidgetId={props.selectWidgetId}
setIsCheckbox={props.setIsCheckbox}
setValidateAlert={props.setValidateAlert}
setCurrWidgetsDetails={
props.setCurrWidgetsDetails
}
handleTextSettingModal={
props.handleTextSettingModal
}
scale={props.scale}
pdfOriginalWH={props.pdfOriginalWH}
pageNumber={props.pageNumber}
fontSize={props.fontSize}
setFontSize={props.setFontSize}
fontColor={props.fontColor}
setFontColor={props.setFontColor}
isResize={props.isResize}
setIsResize={props.setIsResize}
isFreeResize={false}
isOpenSignPad={true}
calculateFontsize={calculateFontsize}
/>
)
);
})}
</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}
onClick={() =>
props.setSelectWidgetId && props.setSelectWidgetId("")
}
file={pdfDataBase64}
>
<Page
onLoadSuccess={handlePageLoadSuccess}
scale={props.scale || 1}
key={props.index}
pageNumber={props.pageNumber}
width={props.containerWH.width}
renderAnnotationLayer={false}
renderTextLayer={false}
onGetAnnotationsError={(error) => {
console.log("annotation error", error);
}}
className="select-none touch-callout-none"
/>
</Document>
</div>
</RSC>
) : (
<RSC
style={{
position: "relative",
boxShadow: "rgba(17, 12, 46, 0.15) 0px 48px 100px 0px",
height: window.innerHeight + "px",
zIndex: 0
}}
noScrollY={false}
noScrollX={props.scale === 1 ? true : false}
>
<div
style={{
width:
props.containerWH?.width &&
props.containerWH?.width * props.scale
}}
ref={props.drop}
id="container"
>
{props.pdfLoad &&
props.containerWH?.width &&
props.pdfOriginalWH.length > 0 &&
(props.pdfRequest || props.isSelfSign //pdf request sign flow
? props.signerPos?.map((data, key) => {
return (
<React.Fragment key={key}>
{checkSignedSigners(data)}
</React.Fragment>
);
})
: props.placeholder //placeholder and template flow
? props.signerPos.map((data, ind) => {
return (
<React.Fragment key={ind}>
{data?.placeHolder &&
data?.placeHolder.map((placeData, index) => {
return (
<React.Fragment key={index}>
{placeData.pageNumber === props.pageNumber &&
placeData.pos.map((pos) => {
return (
<React.Fragment key={pos.key}>
<Placeholder
pos={pos}
setIsPageCopy={props.setIsPageCopy}
setSignKey={props.setSignKey}
handleDeleteSign={
props.handleDeleteSign
}
setIsStamp={props.setIsStamp}
handleTabDrag={props.handleTabDrag}
handleStop={props.handleStop}
handleSignYourselfImageResize={
handleImageResize
}
index={props.pageNumber}
xyPosition={props.signerPos}
setXyPosition={props.setSignerPos}
data={data}
setIsResize={props.setIsResize}
setShowDropdown={
props.setShowDropdown
}
isShowBorder={true}
isPlaceholder={true}
setUniqueId={props.setUniqueId}
handleLinkUser={
props.handleLinkUser
}
isSignYourself={false}
posWidth={posWidth}
posHeight={posHeight}
isDragging={props.isDragging}
setIsValidate={props.setIsValidate}
setWidgetType={props.setWidgetType}
setIsRadio={props.setIsRadio}
setIsCheckbox={props.setIsCheckbox}
setCurrWidgetsDetails={
props.setCurrWidgetsDetails
}
setSelectWidgetId={
props.setSelectWidgetId
}
selectWidgetId={
props.selectWidgetId
}
handleNameModal={
props.handleNameModal
}
setTempSignerId={
props.setTempSignerId
}
uniqueId={props.uniqueId}
handleTextSettingModal={
props.handleTextSettingModal
}
scale={props.scale}
containerWH={props.containerWH}
pdfOriginalWH={props.pdfOriginalWH}
pageNumber={props.pageNumber}
setIsSelectId={props.setIsSelectId}
fontSize={props.fontSize}
setFontSize={props.setFontSize}
fontColor={props.fontColor}
setFontColor={props.setFontColor}
isResize={props.isResize}
unSignedWidgetId={
props.unSignedWidgetId
}
isFreeResize={true}
calculateFontsize={
calculateFontsize
}
/>
</React.Fragment>
);
})}
</React.Fragment>
);
})}
</React.Fragment>
);
})
: !props.pdfDetails?.[0]?.IsCompleted &&
props.xyPosition?.map((data, ind) => {
// signyourself flow
return (
<React.Fragment key={ind}>
{data.pageNumber === props.pageNumber &&
data.pos.map((pos) => {
return (
<React.Fragment key={pos.key}>
<Placeholder
pos={pos}
setIsPageCopy={props.setIsPageCopy}
setSignKey={props.setSignKey}
handleDeleteSign={props.handleDeleteSign}
setIsStamp={props.setIsStamp}
handleTabDrag={props.handleTabDrag}
handleStop={(event, dragElement) =>
props.handleStop(
event,
dragElement,
pos.type
)
}
handleSignYourselfImageResize={
handleSignYourselfImageResize
}
index={props.index}
xyPosition={props.xyPosition}
setXyPosition={props.setXyPosition}
setIsSignPad={props.setIsSignPad}
isShowBorder={true}
isSignYourself={true}
posWidth={posWidth}
posHeight={posHeight}
pdfDetails={props.pdfDetails[0]}
isDragging={props.isDragging}
setIsInitial={props.setIsInitial}
setWidgetType={props.setWidgetType}
setSelectWidgetId={props.setSelectWidgetId}
selectWidgetId={props.selectWidgetId}
setIsCheckbox={props.setIsCheckbox}
setValidateAlert={props.setValidateAlert}
setCurrWidgetsDetails={
props.setCurrWidgetsDetails
}
handleTextSettingModal={
props.handleTextSettingModal
}
scale={props.scale}
containerWH={props.containerWH}
pdfOriginalWH={props.pdfOriginalWH}
pageNumber={props.pageNumber}
fontSize={props.fontSize}
setFontSize={props.setFontSize}
fontColor={props.fontColor}
setFontColor={props.setFontColor}
isResize={props.isResize}
setIsResize={props.setIsResize}
isFreeResize={false}
isOpenSignPad={true}
calculateFontsize={calculateFontsize}
/>
</React.Fragment>
);
})}
</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={() =>
props.setSelectWidgetId && props.setSelectWidgetId("")
}
file={pdfDataBase64}
>
<Page
key={props.index}
width={props.containerWH.width}
scale={props.scale || 1}
className={"-z-[1]"} // when user zoom-in in tablet widgets move backward that's why pass -z-[1]
pageNumber={props.pageNumber}
renderAnnotationLayer={false}
renderTextLayer={false}
onGetAnnotationsError={(error) => {
console.log("annotation error", error);
}}
/>
</Document>
</div>
</RSC>
)}
</>
);
}
export default RenderPdf;
@@ -0,0 +1,458 @@
import React, { useState, useRef } from "react";
import RSC from "react-scrollbars-custom";
import { Document, Page } from "react-pdf";
import {
defaultWidthHeight,
getContainerScale,
handleImageResize,
handleSignYourselfImageResize,
isMobile
} from "../../constant/Utils";
import Placeholder from "./Placeholder";
import Alert from "../../primitives/Alert";
import { useTranslation } from "react-i18next";
import usePdfPinchZoom from "../../hook/usePdfPinchZoom";
function RenderPdf(props) {
const { t } = useTranslation();
const [scaledHeight, setScaledHeight] = useState();
const [guideline, setGuideline] = useState({
show: false,
x1: 0,
x2: 0,
y1: 0,
y2: 0
});
//check isGuestSigner is present in local if yes than handle login flow header in mobile view
const isGuestSigner = localStorage.getItem("isGuestSigner");
const pdfContainerRef = useRef(null);
// enable pinch to zoom only on actual pdf wrapper
usePdfPinchZoom(
pdfContainerRef,
props.scale,
props.setScale,
props.setZoomPercent
);
const handleGuideline = (isShow, x = 0, y = 0, width = 0, height = 0) => {
if (isShow) {
setGuideline({
show: true,
x1: x,
x2: x + width,
y1: y,
y2: y + height
});
} else {
setGuideline({ show: false, x1: 0, x2: 0, y1: 0, y2: 0 });
}
};
// handle signature block width and height according to screen
const posWidth = (pos, signYourself) => {
const containerScale = getContainerScale(
props.pdfOriginalWH,
props.pageNumber,
props.containerWH
);
const defaultWidth = defaultWidthHeight(pos.type).width;
const posWidth = pos.Width ? pos.Width : defaultWidth;
if (signYourself) {
return posWidth * props.scale * containerScale;
} else {
if (pos.isMobile && pos.scale) {
if (pos.IsResize) {
if (props.scale > 1) {
return posWidth * pos.scale * containerScale * props.scale;
} else {
return posWidth * containerScale;
}
} else {
if (props.scale > 1) {
return posWidth * pos.scale * containerScale * props.scale;
} else {
return posWidth * pos.scale * containerScale;
}
}
} else {
return posWidth * props.scale * containerScale;
}
}
};
const posHeight = (pos, signYourself) => {
const containerScale = getContainerScale(
props.pdfOriginalWH,
props.pageNumber,
props.containerWH
);
const posHeight = pos.Height || defaultWidthHeight(pos.type).height;
if (signYourself) {
return posHeight * props.scale * containerScale;
} else {
if (pos.isMobile && pos.scale) {
if (pos.IsResize) {
if (props.scale > 1) {
return posHeight * pos.scale * containerScale * props.scale;
} else {
return posHeight * containerScale;
}
} else {
if (props.scale > 1) {
return posHeight * pos.scale * containerScale * props.scale;
} else {
return posHeight * pos.scale * containerScale;
}
}
} else {
return posHeight * props.scale * containerScale;
}
}
};
// function for render placeholder block over pdf document (all signing flow)
const checkSignedSigners = (data) => {
let checkSign = [];
//condition to handle quick send flow and using normal request sign flow
checkSign = props.signedSigners
? props.signedSigners?.filter(
(sign) =>
sign?.Id === data?.Id || sign?.objectId === data?.signerObjId
)
: [];
return (
checkSign.length === 0 &&
data?.placeHolder?.map((placeData, key) => (
<React.Fragment key={key}>
{placeData.pageNumber === props.pageNumber &&
placeData.pos.map(
(pos, ind) =>
pos && (
<React.Fragment key={ind}>
<Placeholder
pos={pos}
handleSignYourselfImageResize={handleImageResize}
index={props.pageNumber}
xyPosition={props.signerPos}
setXyPosition={props.setSignerPos}
data={data}
setIsResize={props.setIsResize}
isShowBorder={props.isSelfSign}
isAlllowModify={props.isAlllowModify}
signerObjId={props.signerObjectId}
isShowDropdown={true}
isNeedSign={props.pdfRequest}
isSelfSign={true}
isSignYourself={false}
posWidth={posWidth}
posHeight={posHeight}
showGuidelines={handleGuideline}
isDragging={props.isDragging}
pdfDetails={props.pdfDetails}
unSignedWidgetId={props.unSignedWidgetId}
setCurrWidgetsDetails={props.setCurrWidgetsDetails}
uniqueId={props.uniqueId}
scale={props.scale}
containerWH={props.containerWH}
pdfOriginalWH={props.pdfOriginalWH}
pageNumber={props.pageNumber}
ispublicTemplate={props.ispublicTemplate}
handleUserDetails={props.handleUserDetails}
isResize={props.isResize}
handleTabDrag={props.handleTabDrag}
handleStop={props.handleStop}
setUniqueId={props.setUniqueId}
setIsSelectId={props.setIsSelectId}
handleDeleteSign={props.handleDeleteSign}
setIsPageCopy={props.setIsPageCopy}
handleTextSettingModal={props.handleTextSettingModal}
handleCellSettingModal={props.handleCellSettingModal}
setIsCheckbox={props.setIsCheckbox}
isFreeResize={props.isSelfSign ? true : false}
isOpenSignPad={true}
assignedWidgetId={props.assignedWidgetId}
isApplyAll={true}
setCellCount={props.setCellCount}
setFontSize={props.setFontSize}
fontSize={props.fontSize}
fontColor={props.fontColor}
setFontColor={props.setFontColor}
setRequestSignTour={props.setRequestSignTour}
calculateFontsize={calculateFontsize}
currWidgetsDetails={props?.currWidgetsDetails}
setTempSignerId={props.setTempSignerId}
/>
</React.Fragment>
)
)}
</React.Fragment>
))
);
};
const calculateFontsize = (pos) => {
const width = posWidth(pos);
const height = posHeight(pos);
if (height === width || height < width) {
return `${height / 5}px`;
} else if (width < height) {
return `${width / 10}px`;
}
};
const pdfDataBase64 = `data:application/pdf;base64,${props.pdfBase64Url}`;
// calculate render height of pdf in mobile view
const handlePageLoadSuccess = (page) => {
if (isMobile) {
const containerWidth = props.divRef.current.offsetWidth; // Get container width
const viewport = page.getViewport({ scale: 1 });
const scale = containerWidth / viewport.width; // Scale to fit container width
const scaleHeight = viewport.height * scale;
setScaledHeight(scaleHeight);
}
};
return (
<>
{props.successEmail && (
<Alert type={"success"}>{t("success-email-alert")}</Alert>
)}
<RSC
style={{
position: "relative",
boxShadow: "rgba(17, 12, 46, 0.15) 0px 48px 100px 0px",
height: isMobile
? isGuestSigner
? window.innerHeight - 49 // 49 is height of header
: scaledHeight
: `${window.innerHeight}px`,
zIndex: 0
}}
noScrollY={isMobile ? props.scale === 1 : false}
noScrollX={props.scale === 1}
>
<div
data-tut={isMobile ? "reactourForth" : undefined}
className={
isMobile
? `${isGuestSigner ? "30px" : ""} border-[0.1px] border-[#ebe8e8] overflow-x-auto relative`
: "relative"
}
style={{
width:
props.containerWH?.width && props.containerWH?.width * props.scale
}}
ref={(node) => {
pdfContainerRef.current = node;
props.drop && props.drop(node);
}}
id="container"
>
{props.pdfLoad !== false &&
props.containerWH?.width &&
props.pdfOriginalWH.length > 0 && (
<>
{props.pdfRequest || props.isSelfSign
? // request sign, guest sign,
props.signerPos?.map((data, key) => (
<React.Fragment key={key}>
{checkSignedSigners(data)}
</React.Fragment>
))
: props.placeholder // placeholdersign document, draft document, create template, draft template
? props.signerPos?.map((data, ind) => (
<React.Fragment key={ind}>
{data?.placeHolder &&
data?.placeHolder.map((placeData, index) => (
<React.Fragment key={index}>
{placeData.pageNumber === props.pageNumber &&
placeData.pos.map((pos) => (
<React.Fragment key={pos.key}>
<Placeholder
pos={pos}
setIsPageCopy={props.setIsPageCopy}
handleDeleteSign={
props.handleDeleteSign
}
handleTabDrag={props.handleTabDrag}
handleStop={props.handleStop}
handleSignYourselfImageResize={
handleImageResize
}
index={props.pageNumber}
xyPosition={props.signerPos}
setXyPosition={props.setSignerPos}
data={data}
setIsResize={props.setIsResize}
setShowDropdown={props.setShowDropdown}
isShowBorder={true}
isPlaceholder={true}
setUniqueId={props.setUniqueId}
handleLinkUser={props.handleLinkUser}
isSignYourself={false}
posWidth={posWidth}
posHeight={posHeight}
showGuidelines={handleGuideline}
isDragging={props.isDragging}
setIsValidate={props.setIsValidate}
setIsRadio={props.setIsRadio}
setIsCheckbox={props.setIsCheckbox}
setCurrWidgetsDetails={
props.setCurrWidgetsDetails
}
handleNameModal={props.handleNameModal}
setTempSignerId={props.setTempSignerId}
uniqueId={props.uniqueId}
handleTextSettingModal={
props.handleTextSettingModal
}
handleCellSettingModal={
props.handleCellSettingModal
}
scale={props.scale}
containerWH={props.containerWH}
pdfOriginalWH={props.pdfOriginalWH}
pageNumber={props.pageNumber}
setIsSelectId={props.setIsSelectId}
fontSize={props.fontSize}
setFontSize={props.setFontSize}
setCellCount={props.setCellCount}
fontColor={props.fontColor}
setFontColor={props.setFontColor}
isResize={props.isResize}
unSignedWidgetId={
props.unSignedWidgetId
}
isFreeResize={true}
calculateFontsize={calculateFontsize}
currWidgetsDetails={
props?.currWidgetsDetails
}
/>
</React.Fragment>
))}
</React.Fragment>
))}
</React.Fragment>
))
: !props.pdfDetails?.[0]?.IsCompleted && // signyourself flow
props.xyPosition?.map((data, ind) => (
<React.Fragment key={ind}>
{data.pageNumber === props.pageNumber &&
data.pos.map(
(pos, id) =>
pos && (
<Placeholder
key={id}
pos={pos}
setIsPageCopy={props.setIsPageCopy}
handleDeleteSign={props.handleDeleteSign}
handleTabDrag={props.handleTabDrag}
handleStop={props.handleStop}
handleSignYourselfImageResize={
handleSignYourselfImageResize
}
index={props.index}
xyPosition={props.xyPosition}
setXyPosition={props.setXyPosition}
containerWH={props.containerWH}
isShowBorder={true}
isSignYourself={true}
posWidth={posWidth}
posHeight={posHeight}
showGuidelines={handleGuideline}
pdfDetails={props.pdfDetails[0]}
isDragging={props.isDragging}
setIsCheckbox={props.setIsCheckbox}
setCurrWidgetsDetails={
props.setCurrWidgetsDetails
}
handleTextSettingModal={
props.handleTextSettingModal
}
handleCellSettingModal={
props.handleCellSettingModal
}
scale={props.scale}
pdfOriginalWH={props.pdfOriginalWH}
pageNumber={props.pageNumber}
fontSize={props.fontSize}
setFontSize={props.setFontSize}
fontColor={props.fontColor}
setFontColor={props.setFontColor}
isResize={props.isResize}
setIsResize={props.setIsResize}
isFreeResize={true}
isOpenSignPad={true}
calculateFontsize={calculateFontsize}
currWidgetsDetails={
props?.currWidgetsDetails
}
/>
)
)}
</React.Fragment>
))}
</>
)}
<Document
error={<p className="mx-2">{t("failed-to-load-refresh-page")}</p>}
onLoadError={(e) => {
console.log("PDF load error", e);
props.setPdfLoad(false);
}}
loading={t("loading-doc")}
onLoadSuccess={(pdf) => {
props.setPdfLoad(true);
props.pageDetails(pdf);
}}
onClick={() =>
props.setCurrWidgetsDetails && props.setCurrWidgetsDetails({})
}
file={pdfDataBase64}
>
<Page
key={props.index}
onLoadSuccess={handlePageLoadSuccess}
width={props.containerWH.width}
scale={props.scale || 1}
className={isMobile ? "select-none touch-callout-none" : "-z-[1]"}
pageNumber={props.pageNumber}
renderAnnotationLayer={false}
renderTextLayer={false}
onGetAnnotationsError={(error) => {
console.log("annotation error", error);
}}
/>
</Document>
{guideline.show && (
<>
{/* top guide */}
<div
className="absolute pointer-events-none z-[1000] left-0 w-full border-t-[1px] border-dashed border-[#3b82f6]"
style={{ top: guideline.y1 }}
/>
{/* bottom guide */}
<div
className="absolute pointer-events-none z-[1000] left-0 w-full border-t-[1px] border-dashed border-[#3b82f6]"
style={{ top: guideline.y2 }}
/>
{/* left guide */}
<div
className="absolute pointer-events-none z-[1000] top-0 h-full border-l-[1px] border-dashed border-[#3b82f6]"
style={{ left: guideline.x1 }}
/>
{/* right guide */}
<div
className="absolute pointer-events-none z-[1000] top-0 h-full border-l-[1px] border-dashed border-[#3b82f6]"
style={{ left: guideline.x2 }}
/>
</>
)}
</div>
</RSC>
</>
);
}
export default RenderPdf;
@@ -9,7 +9,8 @@ function SelectLanguage(props) {
{ value: "es", text: "Española" }, //spanish
{ value: "fr", text: "Français" }, //french
{ value: "it", text: "Italiano" }, //italian
{ value: "de", text: "Deutsch" } //german
{ value: "de", text: "Deutsch" }, //german
{ value: "hi", text: "हिन्दी" } //hindi
];
const defaultLanguage = i18next.language || "en";
const [lang, setLang] = useState(defaultLanguage);
@@ -23,14 +24,14 @@ function SelectLanguage(props) {
<div
className={`${
!props.isProfile && " mt-[9px] pb-2 md:pb-0 "
} flex justify-center items-center `}
} flex justify-center items-center text-base-content`}
>
<select
value={lang}
onChange={handleChangeLang}
className={`${
!props.isProfile ? " md:w-[15%] w-[50%]" : "w-[180px]"
} op-select op-select-bordered bg-white op-select-sm `}
} op-select op-select-bordered op-select-sm `}
>
<option disabled>select</option>
{languages.map((item) => {
-882
View File
@@ -1,882 +0,0 @@
import React, { useRef, useState, useEffect } from "react";
import { useTranslation } from "react-i18next";
import SignatureCanvas from "react-signature-canvas";
import Parse from "parse";
import {
generateTitleFromFilename,
getBase64FromUrl,
getSecureUrl
} from "../../constant/Utils";
import sanitizeFileName from "../../primitives/sanitizeFileName";
import { SaveFileSize } from "../../constant/saveFileSize";
import Loader from "../../primitives/Loader";
function SignPad(props) {
const { t } = useTranslation();
const [penColor, setPenColor] = useState("blue");
const allColor = ["blue", "red", "black"];
const canvasRef = useRef(null);
const [isDefaultSign, setIsDefaultSign] = useState(false);
const [isTab, setIsTab] = useState("");
const [isSignImg, setIsSignImg] = useState("");
const [textWidth, setTextWidth] = useState(0);
const [textHeight, setTextHeight] = useState(0);
const [signatureType, setSignatureType] = useState("");
const [isSignTypes, setIsSignTypes] = useState(true);
const [typedSignature, setTypedSignature] = useState("");
const fontOptions = [
{ value: "Fasthand" },
{ value: "Dancing Script" },
{ value: "Cedarville Cursive" },
{ value: "Delicious Handrawn" }
// Add more font options as needed
];
const [fontSelect, setFontSelect] = useState(fontOptions[0].value);
const [isSavedSign, setIsSavedSign] = useState(false);
const [isLoader, setIsLoader] = useState(false);
const accesstoken = localStorage.getItem("accesstoken") || "";
const senderUser = localStorage.getItem(
`Parse/${localStorage.getItem("parseAppId")}/currentUser`
);
const jsonSender = senderUser && JSON.parse(senderUser);
const currentUserName = jsonSender && jsonSender?.name;
useEffect(() => {
handleTab();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [props.signatureTypes]);
function handleTab() {
const signtypes = props?.signatureTypes || [];
const defaultIndex = signtypes?.findIndex(
(x) =>
x.name === "default" &&
x.enabled === true &&
props.defaultSign &&
(props?.currWidgetsDetails?.type || props.widgetType) !== "image" &&
(props?.currWidgetsDetails?.type || props.widgetType) !== "stamp"
);
const getIndex =
defaultIndex !== -1 // Check if the default index exists
? defaultIndex // If found, use it
: signtypes?.findIndex((x) => x.enabled === true);
if (getIndex !== -1) {
setIsSignTypes(true);
const tab = props?.signatureTypes[getIndex].name;
if (tab === "draw") {
setIsTab("draw");
setSignatureType("draw");
} else if (tab === "upload") {
props?.setIsImageSelect(true);
setIsTab("uploadImage");
} else if (tab === "typed") {
setIsTab("type");
} else if (tab === "default") {
if (
(props?.isInitial && props?.myInitial) ||
(!props?.isInitial && props?.defaultSign)
) {
setIsDefaultSign(true);
setIsTab("mysignature");
} else {
setIsTab("draw");
}
} else {
setIsTab(true);
}
} else {
setIsSignTypes(false);
}
}
function isTabEnabled(tabName) {
const isEnabled = props?.signatureTypes.find(
(x) => x.name === tabName
)?.enabled;
return isEnabled;
}
//function for clear signature image
const handleClear = () => {
if (isTab === "draw") {
if (canvasRef.current) {
canvasRef.current.clear();
} else if (props?.isStamp) {
props?.setImage("");
}
setIsSignImg("");
} else if (isTab === "uploadImage") {
props?.setImage("");
}
};
//function for set signature url
const handleSignatureChange = (data) => {
props?.setSignature(data);
setIsSignImg(data);
};
function base64StringtoFile(base64String, filename) {
let arr = base64String.split(","),
// type of uploaded image
mime = arr[0].match(/:(.*?);/)[1],
// decode base64
bstr = atob(arr[1]),
n = bstr.length,
u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
const ext = mime.split("/").pop();
const name = `${filename}.${ext}`;
return new File([u8arr], name, { type: mime });
}
const uploadFile = async (file) => {
try {
const parseFile = new Parse.File(file.name, file);
const response = await parseFile.save();
if (response?.url()) {
const fileRes = await getSecureUrl(response.url());
if (fileRes.url) {
const tenantId = localStorage.getItem("TenantId");
SaveFileSize(file.size, fileRes.url, tenantId);
return fileRes?.url;
} else {
alert(`${t("something-went-wrong-mssg")}`);
return false;
}
} else {
alert(`${t("something-went-wrong-mssg")}`);
return false;
}
} catch (err) {
console.log("sign upload err", err);
alert(`${err.message}`);
return false;
}
};
// `handlesavesign` is used to save signaute, initials, stamp as a default
const handleSaveSign = async () => {
if (isSignImg || props?.image?.src) {
setIsLoader(true);
try {
const User = Parse?.User?.current();
const sanitizename = generateTitleFromFilename(User?.get("name"));
const replaceSpace = sanitizeFileName(sanitizename);
let file;
if (isSignImg) {
file = base64StringtoFile(isSignImg, `${replaceSpace}__sign`);
} else {
file = base64StringtoFile(props?.image?.src, `${replaceSpace}__sign`);
}
const imageUrl = await uploadFile(file);
const userId = {
__type: "Pointer",
className: "_User",
objectId: User?.id
};
if (imageUrl) {
// below code is used to save or update default signaute, initials, stamp
try {
const signCls = new Parse.Object("contracts_Signature");
if (props?.saveSignCheckbox?.signId) {
signCls.id = props.saveSignCheckbox.signId;
}
if (
props.currWidgetsDetails?.type === "initials" ||
props?.widgetType === "initials"
) {
signCls.set("Initials", imageUrl);
} else if (
props.currWidgetsDetails?.type === "signature" ||
props?.widgetType === "signature"
) {
signCls.set("ImageURL", imageUrl);
}
signCls.set("UserId", userId);
const signRes = await signCls.save();
if (signRes) {
props.saveSignCheckbox.signId;
props.setSaveSignCheckbox((prev) => ({
...prev,
signId: signRes?.id
}));
const _signRes = JSON.parse(JSON.stringify(signRes));
if (
props.currWidgetsDetails?.type === "signature" ||
props?.widgetType === "signature"
) {
const defaultSign = await getBase64FromUrl(
_signRes?.ImageURL,
true
);
props.setDefaultSignImg(defaultSign);
} else if (
props.currWidgetsDetails?.type === "initials" ||
props?.widgetType === "initials"
) {
const defaultInitials = await getBase64FromUrl(
_signRes?.Initials,
true
);
props.setMyInitial(defaultInitials);
}
alert(t("saved-successfully"));
}
return signRes;
} catch (err) {
console.log(err);
alert(`${err.message}`);
} finally {
setIsLoader(false);
}
}
} catch (err) {
console.log("Err while saving signature", err);
}
}
};
const handleSaveBtn = async () => {
if (accesstoken && isSavedSign) {
await handleSaveSign();
resetToDefault();
} else {
resetToDefault();
}
};
const resetToDefault = () => {
props?.setCurrWidgetsDetails({});
if (!props?.image) {
if (isTab === "mysignature") {
setIsSignImg("");
if (props?.isInitial) {
props?.onSaveSign(signatureType, "initials");
} else {
props?.onSaveSign(null, "default");
}
} else {
if (isTab === "type") {
setIsSignImg("");
props?.onSaveSign(
null,
false,
!props?.isInitial && textWidth > 150 ? 150 : textWidth,
!props?.isInitial && textHeight > 35 ? 35 : textHeight,
typedSignature
);
} else {
setIsSignImg("");
canvasRef.current.clear();
props?.onSaveSign(signatureType);
}
}
setPenColor("blue");
} else {
setIsSignImg("");
props?.onSaveImage(signatureType);
}
props?.setIsSignPad(false);
props?.setIsInitial && props?.setIsInitial(false);
props?.setIsImageSelect(false);
setIsDefaultSign(false);
props?.setImage();
handleTab();
props?.setIsStamp(false);
};
//save button component
const SaveBtn = () => {
return (
<div>
{(isTab === "draw" || isTab === "uploadImage") && (
<button
type="button"
className="op-btn op-btn-ghost mr-1 mt-[2px]"
onClick={() => handleClear()}
>
{t("clear")}
</button>
)}
<button
onClick={() => handleSaveBtn()}
type="button"
className={`${
isSignImg ||
props?.image ||
isDefaultSign ||
textWidth ||
props.isAutoSign
? ""
: "pointer-events-none"
} op-btn op-btn-primary shadow-lg`}
disabled={
(isTab === "draw" && isSignImg) ||
(isTab === "image" && props?.image) ||
(isTab === "mysignature" && isDefaultSign) ||
(isTab === "type" && typedSignature) ||
props.isAutoSign
? false
: props?.image
? false
: true
}
>
{t("save")}
</button>
</div>
);
};
const autoSignAll = () => {
return (
<label className="cursor-pointer flex items-center mb-[6px] text-center text-[11px] md:text-base">
<input
className="mr-2 md:mr-3 op-checkbox op-checkbox-xs md:op-checkbox-sm"
type="checkbox"
value={props.isAutoSign}
onChange={(e) => {
props.setIsAutoSign(e.target.checked);
}}
/>
{t("auto-sign-mssg")}
</label>
);
};
//useEffect for set already draw or save signature url/text url of signature text type and draw type for initial type and signature type widgets
useEffect(() => {
if (props?.currWidgetsDetails && canvasRef.current && props.isSignPad) {
const isWidgetType = props?.currWidgetsDetails?.type;
const signatureType = props?.currWidgetsDetails?.signatureType;
const url = props?.currWidgetsDetails?.SignUrl;
//checking widget type and draw type signature url
if (props?.isInitial) {
if (isWidgetType === "initials" && signatureType === "draw" && url) {
canvasRef.current.fromDataURL(url);
}
} else if (
isWidgetType === "signature" &&
signatureType === "draw" &&
url
) {
canvasRef.current.fromDataURL(url);
}
const trimmedName = currentUserName && currentUserName?.trim();
const firstCharacter = trimmedName?.charAt(0);
const userName = props?.isInitial ? firstCharacter : currentUserName;
const signatureValue = props?.currWidgetsDetails?.typeSignature;
setTypedSignature(signatureValue || userName || "");
setFontSelect("Fasthand");
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [props.isSignPad]);
useEffect(() => {
const loadFont = async () => {
try {
await document.fonts.load(`20px ${fontSelect}`);
const selectFontSTyle = fontOptions.find(
(font) => font.value === fontSelect
);
setFontSelect(selectFontSTyle?.value || fontOptions[0].value);
} catch (error) {
console.error("Error loading font:", error);
}
};
loadFont();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [fontSelect]);
useEffect(() => {
// Load the default signature after the component mounts
if (canvasRef.current) {
canvasRef.current.fromDataURL(isSignImg);
}
if (isTab === "type") {
const trimmedName = typedSignature
? typedSignature?.trim()
: currentUserName?.trim();
const firstCharacter = trimmedName?.charAt(0);
const userName = props?.isInitial ? firstCharacter : typedSignature;
const signatureValue = props?.currWidgetsDetails?.typeSignature;
setTypedSignature(signatureValue || userName || "");
convertToImg(fontSelect, userName);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isTab]);
//function for convert input text value in image
const convertToImg = async (fontStyle, text, color) => {
//get text content to convert in image
const textContent = text;
const fontfamily = fontStyle
? fontStyle
: fontSelect
? fontSelect
: "Fasthand";
const fontSizeValue = "40px";
//creating span for getting text content width
const span = document.createElement("span");
span.textContent = textContent;
span.style.font = `${fontSizeValue} ${fontfamily}`; // here put your text size and font family
span.style.color = color ? color : penColor;
span.style.display = "hidden";
document.body.appendChild(span); // Replace 'container' with the ID of the container element
//create canvas to render text in canvas and convert in image
const canvasElement = document.createElement("canvas");
// Draw the text content on the canvas
const ctx = canvasElement.getContext("2d");
const pixelRatio = window.devicePixelRatio || 1;
const addExtraWidth = props?.isInitial ? 10 : 50;
const width = span.offsetWidth + addExtraWidth;
const height = span.offsetHeight;
setTextWidth(width);
setTextHeight(height);
const font = span.style["font"];
// Set the canvas dimensions to match the span
canvasElement.width = width * pixelRatio;
canvasElement.height = height * pixelRatio;
// You can customize text styles if needed
ctx.font = font;
ctx.fillStyle = color ? color : penColor; // Set the text color
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.scale(pixelRatio, pixelRatio);
// Draw the content of the span onto the canvas
ctx.fillText(span.textContent, width / 2, height / 2); // Adjust the x,y-coordinate as needed
//remove span tag
document.body.removeChild(span);
// Convert the canvas to image data
const dataUrl = canvasElement.toDataURL("image/png");
props?.setSignature(dataUrl);
};
const PenColorComponent = (props) => {
return (
<div className="flex flex-row items-center m-[5px] gap-2">
{allColor.map((data, key) => {
return (
<i
key={key}
onClick={() => {
props?.convertToImg &&
props?.convertToImg(fontSelect, typedSignature, data);
setPenColor(allColor[key]);
}}
className={`border-b-[2px] ${
key === 0 && penColor === "blue"
? "border-blue-600"
: key === 1 && penColor === "red"
? "border-red-500"
: key === 2 && penColor === "black"
? "border-black"
: "border-white"
} text-[${data}] text-[16px] fa-light fa-pen-nib`}
></i>
);
})}
</div>
);
};
// `handleCancelBtn` function trigger when user click on cross button
const handleCancelBtn = () => {
setPenColor("blue");
props?.setIsSignPad(false);
props?.setIsInitial && props?.setIsInitial(false);
props?.setIsImageSelect(false);
setIsDefaultSign(false);
props?.setImage();
handleTab();
props?.setIsStamp(false);
};
const savesigncheckbox = (
<label className="cursor-pointer flex items-center mb-0 text-center text-[11px] md:text-base">
<input
className="mr-2 md:mr-3 op-checkbox op-checkbox-xs md:op-checkbox-sm"
type="checkbox"
checked={isSavedSign}
onChange={(e) => setIsSavedSign(e.target.checked)}
/>
Save {props?.currWidgetsDetails?.type || props?.widgetType}
</label>
);
return (
<div>
{props?.isSignPad && (
<div className="op-modal op-modal-open">
<div className="op-modal-box px-[13px] pt-2 pb-0">
{isLoader && (
<div className="absolute w-full h-full inset-0 flex justify-center items-center bg-base-content/30 z-50">
<Loader />
</div>
)}
{isSignTypes ? (
<>
<div className="flex justify-between text-base-content items-center">
<div className="text-[1.2rem]">
<div className="flex flex-row justify-between mt-[3px]">
<div className="flex flex-row justify-between gap-[5px] md:gap-[8px] text-[11px] md:text-base">
{props?.isStamp ? (
<span className="text-base-content font-bold text-lg">
{props?.widgetType === "image" ||
props?.currWidgetsDetails?.type === "image"
? t("upload-image")
: t("upload-stamp-image")}
</span>
) : (
<>
{!props?.isInitial &&
props?.defaultSign &&
isTabEnabled("default") ? (
<div>
<span
onClick={() => {
setIsDefaultSign(true);
props?.setIsImageSelect(true);
setIsTab("mysignature");
setSignatureType("");
props?.setImage();
}}
className={`${
isTab === "mysignature"
? "op-link-primary"
: "no-underline"
} op-link underline-offset-8 ml-[2px]`}
>
{t("my-signature")}
</span>
</div>
) : (
props?.isInitial &&
props?.myInitial &&
isTabEnabled("default") && (
<div>
<span
onClick={() => {
setIsDefaultSign(true);
props?.setIsImageSelect(true);
setIsTab("mysignature");
setSignatureType("");
props?.setImage();
}}
className={`${
isTab === "mysignature"
? "op-link-primary"
: "no-underline"
} op-link underline-offset-8 ml-[2px]`}
>
{t("my-initials")}
</span>
</div>
)
)}
{isTabEnabled("draw") && (
<div>
<span
onClick={() => {
setIsDefaultSign(false);
props?.setIsImageSelect(false);
setIsTab("draw");
props?.setImage();
if (isSignImg) {
props?.setSignature(isSignImg);
}
}}
className={`${
isTab === "draw"
? "op-link-primary"
: "no-underline"
} op-link underline-offset-8 ml-[2px]`}
>
{t("draw")}
</span>
</div>
)}
{isTabEnabled("upload") && (
<div>
<span
onClick={() => {
setIsDefaultSign(false);
props?.setIsImageSelect(true);
setIsTab("uploadImage");
setSignatureType("");
}}
className={`${
isTab === "uploadImage"
? "op-link-primary"
: "no-underline"
} op-link underline-offset-8 ml-[2px]`}
>
{t("upload-image")}
</span>
</div>
)}
{isTabEnabled("typed") && (
<div>
<span
onClick={() => {
setIsDefaultSign(false);
props?.setIsImageSelect(false);
setIsTab("type");
setSignatureType("");
props?.setImage();
}}
className={`${
isTab === "type"
? "op-link-primary"
: "no-underline"
} op-link underline-offset-8 ml-[2px]`}
>
{t("type")}
</span>
</div>
)}
</>
)}
</div>
</div>
</div>
<div
className="text-[1.5rem] cursor-pointer"
onClick={handleCancelBtn}
>
&times;
</div>
</div>
<div className="p-[20px] h-full">
{isDefaultSign ? (
<>
{!props?.isInitial &&
props?.defaultSign &&
isTabEnabled("default") && (
<>
<div className="flex justify-center">
<div
className={`${
props?.isInitial
? "intialSignatureCanvas"
: "signatureCanvas"
} bg-white border-[1.3px] border-[#007bff] flex flex-col justify-center items-center mb-[6px] cursor-pointer`}
>
<img
alt="stamp img"
className="w-full h-full object-contain bg-white"
draggable="false"
src={
props?.isInitial
? props?.myInitial
: props?.defaultSign
}
/>
</div>
</div>
{props.setIsAutoSign && autoSignAll()}
<div className="flex justify-end">
<SaveBtn />
</div>
</>
)}
{props?.isInitial &&
props?.myInitial &&
isTabEnabled("default") && (
<>
<div className="flex justify-center">
<div
className={`${
props?.isInitial
? "intialSignatureCanvas"
: "signatureCanvas"
} bg-white border-[1.3px] border-[#007bff] flex flex-col justify-center items-center mb-[6px] cursor-pointer`}
>
<img
alt="stamp img"
className="w-full h-full object-contain bg-white"
draggable="false"
src={
props?.isInitial
? props?.myInitial
: props?.defaultSign
}
/>
</div>
</div>
{props.setIsAutoSign && autoSignAll()}
<div className="flex justify-end">
<SaveBtn />
</div>
</>
)}
</>
) : props?.isImageSelect || props?.isStamp ? (
!props?.image ? (
<div className="flex justify-center">
<div
className={`${
props?.isInitial
? "intialSignatureCanvas"
: "signatureCanvas"
} bg-white border-[1.3px] border-[#007bff] flex flex-col justify-center items-center mb-[6px] cursor-pointer`}
onClick={() => props?.imageRef.current.click()}
>
<input
type="file"
onChange={props?.onImageChange}
className="filetype"
accept="image/png,image/jpeg"
ref={props?.imageRef}
hidden
/>
<i className="fa-light fa-cloud-upload-alt uploadImgLogo"></i>
<div className="text-[10px]">{t("upload")}</div>
</div>
</div>
) : (
<>
<div className="flex justify-center">
<div
className={`${
props?.isInitial
? "intialSignatureCanvas"
: "signatureCanvas"
} bg-white border-[1.3px] border-[#007bff] mb-[6px] overflow-hidden`}
>
<img
alt="print img"
ref={props?.imageRef}
src={props?.image.src}
draggable="false"
className="object-contain h-full w-full"
/>
</div>
</div>
{props.setIsAutoSign && autoSignAll()}
<div className="flex justify-end">
<SaveBtn />
</div>
</>
)
) : isTab === "type" ? (
<div>
<div className="flex justify-between items-center">
<span className="mr-[5px] text-[12px]">
{props?.isInitial
? t("initial-teb")
: t("signature-tab")}
:
</span>
<input
maxLength={props?.isInitial ? 3 : 30}
style={{ fontFamily: fontSelect, color: penColor }}
type="text"
className="ml-1 op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-[20px]"
placeholder={
props?.isInitial
? t("initial-type")
: t("signature-type")
}
value={typedSignature}
onChange={(e) => {
setTypedSignature(e.target.value);
convertToImg(fontSelect, e.target.value);
}}
/>
</div>
<div className="border-[1px] border-[#d6d3d3] mt-[10px] ml-[5px]">
{fontOptions.map((font, ind) => {
return (
<div
key={ind}
style={{
cursor: "pointer",
fontFamily: font.value,
backgroundColor:
fontSelect === font.value &&
"rgb(206 225 247)"
}}
onClick={() => {
setFontSelect(font.value);
convertToImg(font.value, typedSignature);
}}
>
<div
className="py-[5px] px-[10px] text-[20px]"
style={{ color: penColor }}
>
{typedSignature
? typedSignature
: "Your signature"}
</div>
</div>
);
})}
</div>
<div className="flex flex-col justify-between mt-[10px]">
{props.setIsAutoSign && autoSignAll()}
<div className="flex flex-row justify-between mt-[10px]">
<PenColorComponent convertToImg={convertToImg} />
<SaveBtn />
</div>
</div>
</div>
) : (
<>
<div className="flex justify-center">
<SignatureCanvas
ref={canvasRef}
penColor={penColor}
canvasProps={{
className: `${
props?.isInitial
? "intialSignatureCanvas"
: "signatureCanvas"
} border-[1.3px] border-[#007bff]`
}}
onEnd={() =>
handleSignatureChange(
canvasRef.current?.toDataURL()
)
}
dotSize={1}
/>
</div>
<div className="flex flex-col justify-between mt-[10px]">
{props.setIsAutoSign && autoSignAll()}
{accesstoken &&
props?.saveSignCheckbox?.isVisible &&
savesigncheckbox}
<div className="flex flex-row justify-between mt-[10px]">
<PenColorComponent />
<SaveBtn />
</div>
</div>
</>
)}
</div>
</>
) : (
<div>
<div className="relative flex flex-row items-center justify-between">
<div className="text-base-content font-bold text-lg">
Signature
</div>
<div
className="text-[1.5rem] cursor-pointer"
onClick={handleCancelBtn}
>
&times;
</div>
</div>
<div className="mx-3 mb-6 mt-3">
<p>{t("at-least-one-signature-type")}</p>
</div>
</div>
)}
</div>
</div>
)}
</div>
);
}
export default SignPad;
@@ -27,12 +27,12 @@ function SignerListComponent(props) {
return (
<div
className="rounded-xl mx-1 flex flex-row items-center py-[10px] mt-1"
className="rounded-xl mx-1 flex flex-row flex-grow-0 items-center py-[10px] mt-1"
style={{ background: checkSignerBackColor(props.obj) }}
>
<div
style={{ background: checkUserNameColor(props.obj) }}
className="flex w-[30px] h-[30px] rounded-full justify-center items-center mx-1"
className="flex flex-shrink-0 w-[30px] h-[30px] rounded-full justify-center items-center mx-1"
>
<span className="text-[12px] text-center font-bold text-black uppercase">
{getFirstLetter(
@@ -40,11 +40,11 @@ function SignerListComponent(props) {
)}
</span>
</div>
<div className="flex flex-col">
<span className="text-[12px] font-bold text-[#424242] w-[100px] whitespace-nowrap overflow-hidden text-ellipsis">
<div className="flex flex-grow-0 flex-col overflow-hidden pr-2">
<span className="text-[12px] font-bold truncate whitespace-nowrap">
{props.obj?.Name || props?.obj?.Role}
</span>
<span className="text-[10px] font-medium text-[#424242] w-[100px] whitespace-nowrap overflow-hidden text-ellipsis">
<span className="text-[10px] font-medium truncate whitespace-nowrap">
{props.obj?.Email || props.obj?.email}
</span>
</div>
@@ -13,7 +13,7 @@ function TextFontSetting(props) {
title={t("text-field")}
handleClose={() => props.setIsTextSetting(false)}
>
<div className="h-full p-[20px]">
<div className="h-full p-[20px] text-base-content">
<div className="flex flex-col md:flex-row md:items-center gap-3">
{/* Font Size Selector */}
<div className="flex items-center gap-2">
@@ -8,6 +8,7 @@ import {
isMobile,
radioButtonWidget,
textInputWidget,
cellsWidget,
textWidget,
widgets
} from "../../constant/Utils";
@@ -37,6 +38,10 @@ function WidgetComponent(props) {
type: "BOX",
item: { type: "BOX", id: 7, text: textInputWidget }
});
const [, cells] = useDrag({
type: "BOX",
item: { type: "BOX", id: 17, text: cellsWidget }
});
const [, initials] = useDrag({
type: "BOX",
item: { type: "BOX", id: 8, text: "initials" }
@@ -89,6 +94,7 @@ function WidgetComponent(props) {
date,
text,
textInput,
cells,
checkbox,
dropdown,
radioButton,
@@ -132,7 +138,9 @@ function WidgetComponent(props) {
);
const filterWidgets = widget.filter(
(data) =>
!["dropdown", radioButtonWidget, textInputWidget].includes(data.type)
!["dropdown", radioButtonWidget, textInputWidget].includes(
data.type
)
);
const textWidgetData = widget.filter((data) => data.type !== textWidget);
const updateWidgets = props.isSignYourself
@@ -241,6 +249,7 @@ function WidgetComponent(props) {
handleDivClick={props.handleDivClick}
handleMouseLeave={props.handleMouseLeave}
signRef={signRef}
addPositionOfSignature={props.addPositionOfSignature}
/>
</div>
</div>
@@ -5,6 +5,7 @@ import RegexParser from "regex-parser";
import {
signatureTypes,
textInputWidget,
cellsWidget,
textWidget
} from "../../constant/Utils";
import { fontColorArr, fontsizeArr } from "../../constant/Utils";
@@ -19,33 +20,47 @@ const WidgetNameModal = (props) => {
status: "required",
hint: "",
textvalidate: "",
isReadOnly: false
isReadOnly: false,
cellCount: 5
});
const [isValid, setIsValid] = useState(true);
const statusArr = ["Required", "Optional"];
const [signatureType, setSignatureType] = useState([]);
const handleHint = () => {
const type = props.defaultdata?.type;
if (type === "signature") {
return "Draw signature";
} else if (type === "stamp" || type === "image") {
return `Upload ${type}`;
} else if (type === "initials") {
return "Draw initial";
} else if (type === textInputWidget) {
return "Enter text";
} else {
return `Enter ${type}`;
}
};
useEffect(() => {
if (props.defaultdata) {
setFormdata({
name: props.defaultdata?.options?.name || "",
defaultValue: props.defaultdata?.options?.defaultValue || "",
status: props.defaultdata?.options?.status || "required",
hint:
props.defaultdata?.options?.hint ||
(props.defaultdata?.type === textInputWidget
? "Enter text"
: `Enter ${props.defaultdata?.options?.name}`),
hint: props.defaultdata?.options?.hint || handleHint(),
textvalidate:
props.defaultdata?.options?.validation?.type === "regex"
? props.defaultdata?.options?.validation?.pattern
: props.defaultdata?.options?.validation?.type || "",
isReadOnly: props.defaultdata?.options?.isReadOnly || false
isReadOnly: props.defaultdata?.options?.isReadOnly || false,
cellCount: props.defaultdata?.options?.cellCount || 5
});
} else {
setFormdata({
...formdata,
name: props.defaultdata?.options?.name || ""
name: props.defaultdata?.options?.name || "",
cellCount: props.defaultdata?.options?.cellCount || 5
});
}
@@ -72,6 +87,20 @@ const WidgetNameModal = (props) => {
props.handleData(data, props.defaultdata?.type);
}
} else {
const isTextInput = [textInputWidget, cellsWidget].includes(
props.defaultdata?.type
);
const { isReadOnly, defaultValue, status } = formdata;
// If its a textinput widget, enforce that read-only fields have
// either a defaultValue or an "optional" status.
if (isTextInput) {
const readOnlyWithoutValue =
isReadOnly && !defaultValue && status !== "optional";
if (readOnlyWithoutValue) {
alert(t("readonly-error", { widgetName: props.defaultdata?.type }));
return;
}
}
props.handleData(formdata);
}
setFormdata({
@@ -80,7 +109,8 @@ const WidgetNameModal = (props) => {
defaultValue: "",
status: "required",
hint: "",
textvalidate: ""
textvalidate: "",
cellCount: 5
});
setSignatureType(signTypes);
}
@@ -93,6 +123,23 @@ const WidgetNameModal = (props) => {
}
};
const handleChangeValidateInput = (e) => {
if (e) {
if (e.target.value === "ssn") {
setFormdata({
...formdata,
[e.target.name]: e.target.value,
hint: "xxx-xx-xxxx",
cellCount: 11
});
} else {
setFormdata({ ...formdata, [e.target.name]: e.target.value });
}
} else {
setFormdata({ ...formdata, textvalidate: "" });
}
};
const handledefaultChange = (e) => {
if (formdata.textvalidate) {
const regexObject = RegexParser(handleValidation(formdata.textvalidate));
@@ -101,7 +148,11 @@ const WidgetNameModal = (props) => {
} else {
setIsValid(true);
}
setFormdata({ ...formdata, [e.target.name]: e.target.value });
const val =
props.defaultdata?.type === cellsWidget
? e.target.value.slice(0, formdata.cellCount)
: e.target.value;
setFormdata({ ...formdata, [e.target.name]: val });
};
function handleValidation(type) {
@@ -111,12 +162,16 @@ const WidgetNameModal = (props) => {
case "number":
return "/^\\d+$/";
case "text":
return "/^[a-zA-Zs]+$/";
//allow space in text regex
return "/^[a-zA-Z ]+$/";
case "ssn":
return "/^(?!000|666|9\\d{2})\\d{3}-(?!00)\\d{2}-(?!0000)\\d{4}$/";
default:
return type;
}
}
const handleCheckboxChange = (index) => {
// Update the state with the modified array
setSignatureType((prev) =>
@@ -138,7 +193,7 @@ const WidgetNameModal = (props) => {
<form
onSubmit={handleSubmit}
className={`${
props.defaultdata?.type === textInputWidget
[textInputWidget, cellsWidget].includes(props.defaultdata?.type)
? "pt-0"
: ["signature", "initials"].includes(props.defaultdata?.type)
? "pt-2"
@@ -162,7 +217,21 @@ const WidgetNameModal = (props) => {
/>
</div>
)}
{props.defaultdata?.type === textInputWidget && (
{props.defaultdata?.type === cellsWidget && (
<div className="mb-[0.75rem] text-[13px]">
<label htmlFor="cellCount">{t("cell-count")}</label>
<input
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
type="number"
min="1"
name="cellCount"
value={formdata.cellCount}
onChange={(e) => handleChange(e)}
required
/>
</div>
)}
{[textInputWidget, cellsWidget].includes(props.defaultdata?.type) && (
<>
<div className="mb-[0.75rem]">
<label htmlFor="name" className="text-[13px]">
@@ -174,6 +243,11 @@ const WidgetNameModal = (props) => {
value={formdata.defaultValue}
onChange={(e) => handledefaultChange(e)}
autoComplete="off"
maxLength={
props.defaultdata?.type === cellsWidget
? formdata.cellCount
: undefined
}
onBlur={() => {
if (isValid === false) {
setFormdata({ ...formdata, defaultValue: "" });
@@ -225,7 +299,9 @@ const WidgetNameModal = (props) => {
);
})}
</div>
{[textInputWidget].includes(props.defaultdata?.type) && (
{[textInputWidget, cellsWidget].includes(
props.defaultdata?.type
) && (
<div className="flex items-center">
<input
id="isReadOnly"
@@ -240,7 +316,10 @@ const WidgetNameModal = (props) => {
}))
}
/>
<label className="ml-1 mb-0" htmlFor="isreadonly">
<label
className="ml-1.5 mb-0 capitalize text-[13px]"
htmlFor="isreadonly"
>
{t("read-only")}
</label>
</div>
@@ -292,6 +371,7 @@ const WidgetNameModal = (props) => {
{[
textInputWidget,
textWidget,
cellsWidget,
"name",
"company",
"job title",
File diff suppressed because it is too large Load Diff
@@ -6,7 +6,8 @@ import { useTranslation } from "react-i18next";
const FolderModal = (props) => {
const { t } = useTranslation();
const appName = "OpenSign™";
const appName =
"OpenSign™";
const drivename = appName === "OpenSign™" ? "OpenSign™" : "";
const [clickFolder, setClickFolder] = useState("");
const [folderList, setFolderList] = useState([]);
@@ -6,7 +6,8 @@ import Tooltip from "../../../primitives/Tooltip";
import { useTranslation } from "react-i18next";
const SelectFolder = ({ required, onSuccess, folderCls, isReset }) => {
const appName = "OpenSign™";
const appName =
"OpenSign™";
const drivename = appName === "OpenSign™" ? "OpenSign™" : "";
const { t } = useTranslation();
const [isOpen, SetIsOpen] = useState(false);
@@ -194,7 +195,7 @@ const SelectFolder = ({ required, onSuccess, folderCls, isReset }) => {
? selectFolder.Name
: t("OpenSign-drive", { appName: drivename })}
</p>
<div className="text-black text-sm">
<div className="text-sm">
<i
className="fa-light fa-pencil cursor-pointer"
title={t("select-folder")}
@@ -2,9 +2,19 @@ import React, { useEffect, useState } from "react";
import AsyncSelect from "react-select/async";
import { useTranslation } from "react-i18next";
import axios from "axios";
import { handleUnlinkSigner } from "../../../constant/Utils";
const SelectSigners = (props) => {
const { t } = useTranslation();
const {
signerPos,
setSignerPos,
signersData,
setSignersData,
uniqueId,
isRemove,
handleAddUser
} = props;
const [userList, setUserList] = useState([]);
const [selected, setSelected] = useState();
const [userData, setUserData] = useState({});
@@ -30,7 +40,7 @@ const SelectSigners = (props) => {
//checking if user select no signer option from dropdown
if (item) {
//checking selected signer is already assign to the document or not
const alreadyAssign = props.signersData.some(
const alreadyAssign = signersData.some(
(item2) => item2.objectId === item.value
);
if (alreadyAssign) {
@@ -49,7 +59,7 @@ const SelectSigners = (props) => {
};
const handleAdd = () => {
if (userData && userData.objectId) {
props.details(userData);
handleAddUser(userData);
if (props.closePopup) {
props.closePopup();
}
@@ -60,7 +70,13 @@ const SelectSigners = (props) => {
};
//function to use remove signer from assigned widgets in create template flow
const handleRemove = () => {
props.handleUnlinkSigner();
handleUnlinkSigner(
signerPos,
setSignerPos,
signersData,
setSignersData,
uniqueId
);
if (props.closePopup) {
props.closePopup();
}
@@ -81,7 +97,7 @@ const SelectSigners = (props) => {
const contactRes = axiosRes?.data?.result || [];
if (contactRes) {
const res = JSON.parse(JSON.stringify(contactRes));
//compareArrays is a function where compare between two array (total signersList and dcument signers list)
//compareArrays is a function where compare between two array (total signersList and document signers list)
//and filter signers from total signer's list which already present in document's signers list
// const compareArrays = (res, signerObj) => {
// return res.filter(
@@ -161,7 +177,7 @@ const SelectSigners = (props) => {
<button className="op-btn op-btn-primary" onClick={() => handleAdd()}>
{t("submit")}
</button>
{props.isExistSigner && props.handleUnlinkSigner && (
{props.isExistSigner && isRemove && (
<button
className="op-btn op-btn-accent op-btn-outline"
onClick={() => handleRemove()}
@@ -117,10 +117,12 @@ const SignersInput = (props) => {
};
const loadOptions = async (inputValue) => {
try {
const contactRes = await findContact(inputValue);
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)
//compareArrays is a function where compare between two array (total signersList and document signers list)
//and filter signers from total signer's list which already present in document's signers list
const compareArrays = (res, signerObj) => {
return res.filter(
@@ -152,9 +154,7 @@ 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`}
@@ -3,7 +3,8 @@ import { useTranslation } from "react-i18next";
import { NavLink } from "react-router";
const Menu = ({ item, isOpen, closeSidebar }) => {
const appName = "OpenSign™";
const appName =
"OpenSign™";
const drivename = appName === "OpenSign™" ? "OpenSign™" : "";
const { t } = useTranslation();
return (
@@ -17,16 +18,16 @@ const Menu = ({ item, isOpen, closeSidebar }) => {
className={({ isActive }) =>
`${
isActive ? " bg-base-300 text-base-content" : ""
} flex items-center justify-start text-left p-3 lg:p-4 text-base-content hover:text-base-content focus:bg-base-300 hover:bg-base-300 hover:no-underline focus:outline-none`
} flex gap-x-5 items-center justify-start text-left p-3 text-base-content hover:text-base-content focus:bg-base-300 hover:bg-base-300 hover:no-underline focus:outline-none`
}
onClick={closeSidebar}
tabIndex={isOpen ? 0 : -1}
role="menuitem"
>
<span className="w-[20px] h-[20px] flex justify-center">
<i className={`${item.icon} text-[18px]`} aria-hidden="true"></i>
<i className={`${item.icon} text-[20px]`} aria-hidden="true"></i>
</span>
<span className="ml-3 lg:ml-4">
<span className="flex items-center mb-0.5">
{t(`sidebar.${item.title}`, { appName: drivename })}
</span>
</NavLink>
@@ -1,4 +1,4 @@
import React, { useState, useEffect } from "react";
import { useState, useEffect } from "react";
import Menu from "./Menu";
import Submenu from "./SubMenu";
import SocialMedia from "../SocialMedia";
@@ -27,39 +27,18 @@ const Sidebar = ({ isOpen, closeSidebar }) => {
if (localStorage.getItem("defaultmenuid")) {
const Extand_Class = localStorage.getItem("Extand_Class");
const extClass = Extand_Class && JSON.parse(Extand_Class);
// console.log("extClass ", extClass);
let userRole = "contracts_User";
if (extClass && extClass.length > 0) {
userRole = extClass[0].UserRole;
}
if (
userRole === "contracts_Admin" ||
userRole === "contracts_OrgAdmin"
) {
const newSidebarList = sidebarList.map((item) => {
if (item.title === "Settings") {
// Make a shallow copy of the item
const newItem = { ...item };
const arr = newItem.children.slice(0, 1);
newItem.children = [...arr, ...subSetting];
return newItem;
}
return item;
});
setmenuList(newSidebarList);
} else {
const newSidebarList = sidebarList.map((item) => {
if (item.title === "Settings") {
// Make a shallow copy of the item
const newItem = { ...item };
const arr = newItem.children.slice(0, 1);
newItem.children = arr;
return newItem;
}
return item;
});
setmenuList(newSidebarList);
}
const userRole = extClass?.[0]?.UserRole || "contracts_User";
const isAdmin =
userRole === "contracts_Admin" || userRole === "contracts_OrgAdmin";
const newSidebarList = sidebarList.map((item) => {
if (item.title !== "Settings") return item;
const newItem = { ...item };
const baseChildren = isAdmin ? subSetting : subSetting?.slice(0, 1);
const mysignature = newItem.children.slice(0, 1);
newItem.children = [...mysignature, ...baseChildren];
return newItem;
});
setmenuList(newSidebarList);
}
} catch (e) {
console.error("Problem", e);
@@ -3,7 +3,8 @@ import { useTranslation } from "react-i18next";
import { NavLink } from "react-router";
const Submenu = ({ item, closeSidebar, toggleSubmenu, submenuOpen }) => {
const appName = "OpenSign™";
const appName =
"OpenSign™";
const drivename = appName === "OpenSign™" ? "OpenSign™" : "";
const { t } = useTranslation();
const { title, icon, children } = item;
@@ -11,16 +12,16 @@ const Submenu = ({ item, closeSidebar, toggleSubmenu, submenuOpen }) => {
<li role="none" className="my-0.5">
<button
onClick={() => toggleSubmenu(item.title)}
className="flex items-center justify-start text-left p-3 lg:p-4 text-base-content hover:text-base-content focus:bg-base-300 hover:bg-base-300 hover:no-underline focus:outline-none "
className="flex gap-x-5 items-center justify-start text-left p-3 text-base-content hover:text-base-content focus:bg-base-300 hover:bg-base-300 hover:no-underline focus:outline-none"
aria-expanded={submenuOpen}
aria-haspopup="true"
aria-controls={`submenu-${title}`}
>
<span className="w-[20px] h-[20px] flex justify-center">
<i className={`${icon} text-[18px]`}></i>
<i className={`${icon} text-[20px]`}></i>
</span>
<div className="flex justify-between items-center w-full">
<span className="ml-3 lg:ml-4 text-start">
<span className="flex items-center mb-0.5">
{t(`sidebar.${item.title}`, { appName })}
</span>
<i
@@ -45,20 +46,20 @@ const Submenu = ({ item, closeSidebar, toggleSubmenu, submenuOpen }) => {
}
className={({ isActive }) =>
`${
isActive ? "bg-base-300 text-base-content" : ""
} flex items-center justify-start text-left pl-6 md:pl-8 py-2 text-sm cursor-pointer text-base-content hover:text-base-content focus:bg-base-300 hover:bg-base-300 hover:no-underline focus:outline-none`
isActive ? "bg-base-300 text-base-content " : ""
} pl-4 flex items-center gap-x-5 py-2 text-sm cursor-pointer text-base-content hover:text-base-content focus:bg-base-300 hover:bg-base-300 hover:no-underline focus:outline-none`
}
onClick={closeSidebar}
role="menuitem"
tabIndex={submenuOpen ? 0 : -1}
>
<span className="w-[15px] h-[15px] flex justify-center">
<span className="w-[18px] h-[18px] flex justify-center">
<i
className={`${childItem.icon} text-[18px]`}
aria-hidden="true"
></i>
</span>
<span className="ml-3 lg:ml-4">
<span className="mb-0.5">
{t(`sidebar.${item.title}-Children.${childItem.title}`, {
appName: drivename
})}
File diff suppressed because it is too large Load Diff
+6 -6
View File
@@ -1,9 +1,12 @@
import logo from "../assets/images/logo.png";
import { getEnv } from "./Utils";
export function serverUrl_fn() {
let baseUrl = process.env.REACT_APP_SERVERURL
? process.env.REACT_APP_SERVERURL
: window.location.origin + "/api/app";
const env = getEnv();
const serverurl = env?.REACT_APP_SERVERURL
? env.REACT_APP_SERVERURL // env.REACT_APP_SERVERURL is used for prod
: process.env.REACT_APP_SERVERURL; // process.env.REACT_APP_SERVERURL is used for dev (locally)
let baseUrl = serverurl ? serverurl : window.location.origin + "/api/app";
return baseUrl;
}
export const appInfo = {
@@ -11,9 +14,6 @@ export const appInfo = {
appId: process.env.REACT_APP_APPID ? process.env.REACT_APP_APPID : "opensign",
baseUrl: serverUrl_fn(),
defaultRole: "contracts_User",
fbAppId: process.env.REACT_APP_FBAPPID
? `${process.env.REACT_APP_FBAPPID}`
: "",
fev_Icon:
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAAXNSR0IArs4c6QAAALlJREFUaEPtmN0NwjAMBpNxYDKYiM1Yp90g93CKStH1NbIdfz921Dker2Pc+Js1cDF7MXAxASMGYkAigBI6vh9ZwoXP53uZoAYcvhwdA3mAVbI2aSb+9zFKU4IURB6j/HoPUIEa2G3iGIAhQQDlAUIoE2diabIklISS0NoFPebo77RF6OenEF3QntOm128he0GKrwHyACFoz2MgBqSGkpAEcHs47oHtN5AFakACqMNjQEMoE8SABFCHn4HE2zGHSLeEAAAAAElFTkSuQmCC",
googleClietId: process.env.REACT_APP_GOOGLECLIENTID
+5
View File
@@ -3,6 +3,11 @@ export const templateCls = "contracts_Template";
export const documentCls = "contracts_Document";
export const themeColor = "#47a3ad";
export const iconColor = "#686968";
// Dynamic icon color function for better dark mode visibility
export const getThemeIconColor = () => {
const theme = document.documentElement.getAttribute("data-theme");
return theme === "opensigndark" ? "#CCCCCC" : "#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
-4
View File
@@ -1,4 +0,0 @@
export default function linkURL(url) {
let newURL = (url && decodeURIComponent(url)) || "";
return newURL.substring(newURL.indexOf(".com/") + 5, newURL.indexOf("_"));
}
+38
View File
@@ -0,0 +1,38 @@
import { useState, useEffect } from 'react';
type Size = { width: number; height: number; };
/**
* useSize
* @param ref React ref object pointing to a DOM element
* @returns current size of that element: { width, height }
*/
export function useElSize(ref: React.RefObject<HTMLElement>): Size {
const [size, setSize] = useState<Size>({ width: 0, height: 0 });
useEffect(() => {
const el = ref.current;
if (!el) return;
// Initialize size
setSize({
width: el.offsetWidth,
height: el.offsetHeight,
});
// Watch for resize
const observer = new ResizeObserver(entries => {
for (let entry of entries) {
const { width, height } = entry.contentRect;
setSize({ width, height });
}
});
observer.observe(el);
return () => {
observer.disconnect();
};
}, [ref]);
return size;
}
+110
View File
@@ -0,0 +1,110 @@
import { useEffect, useRef } from "react";
export default function usePdfPinchZoom(ref, scale, setScale, setZoomPercent) {
const initialDistanceRef = useRef(null);
const startScaleRef = useRef(scale);
const currentScaleRef = useRef(scale);
const pinchScaleRef = useRef(null); // stores relative scale during a gesture
const frameRef = useRef(null);
// keep a reference to the latest scale value
useEffect(() => {
const element = ref.current;
currentScaleRef.current = scale;
// once React commits the new scale after a pinch we clear the transform
if (element && !pinchScaleRef.current) {
element.style.transform = "";
}
}, [scale, ref]);
useEffect(() => {
const element = ref.current;
if (!element) return;
const getDistance = (touches) => {
const [touch1, touch2] = touches;
const dx = touch1.clientX - touch2.clientX;
const dy = touch1.clientY - touch2.clientY;
return Math.hypot(dx, dy);
};
const handleTouchStart = (e) => {
if (e.touches.length === 2) {
initialDistanceRef.current = getDistance(e.touches);
startScaleRef.current = currentScaleRef.current;
element.style.transformOrigin = "0 0";
}
};
const handleTouchMove = (e) => {
if (e.touches.length === 2 && initialDistanceRef.current) {
e.preventDefault();
const newDistance = getDistance(e.touches);
const pinchScale = newDistance / initialDistanceRef.current;
const newScale = Math.max(1, startScaleRef.current * pinchScale);
// store relative scale so it can be applied on top of current width
pinchScaleRef.current = newScale / startScaleRef.current;
if (!frameRef.current) {
frameRef.current = requestAnimationFrame(() => {
element.style.transform = `scale(${pinchScaleRef.current})`;
frameRef.current = null;
});
}
}
};
const handleTouchEnd = () => {
if (pinchScaleRef.current) {
let relativeScale = pinchScaleRef.current;
const inlineTransform = element.style.transform;
if (inlineTransform && inlineTransform.startsWith("scale(")) {
const value = parseFloat(inlineTransform.slice(6, -1));
if (!isNaN(value)) {
relativeScale = value;
}
}
const finalScale = Math.max(1, startScaleRef.current * relativeScale);
const percent = parseFloat(((finalScale - 1) * 100).toFixed(2));
const unchanged =
Math.abs(finalScale - currentScaleRef.current) < 0.001;
pinchScaleRef.current = null;
if (frameRef.current) {
cancelAnimationFrame(frameRef.current);
frameRef.current = null;
}
if (unchanged) {
// if scale didn't actually change, reset transform right away
element.style.transform = "";
} else {
// keep transform applied until state update reflects the new scale
element.style.transform = `scale(${relativeScale})`;
}
setScale(finalScale);
setZoomPercent(percent);
// ensure refs reflect the latest value before next gesture starts
currentScaleRef.current = finalScale;
startScaleRef.current = finalScale;
}
initialDistanceRef.current = null;
};
element.addEventListener("touchstart", handleTouchStart, {
passive: false
});
element.addEventListener("touchmove", handleTouchMove, { passive: false });
element.addEventListener("touchend", handleTouchEnd);
element.addEventListener("touchcancel", handleTouchEnd);
return () => {
element.removeEventListener("touchstart", handleTouchStart);
element.removeEventListener("touchmove", handleTouchMove);
element.removeEventListener("touchend", handleTouchEnd);
element.removeEventListener("touchcancel", handleTouchEnd);
};
}, [ref, setScale, setZoomPercent]);
}
+1 -2
View File
@@ -1,6 +1,6 @@
import { useEffect } from "react";
/**`useScript` hook is generated scripte for google sign in button */
/**`useScript` hook is generated script for google sign in button */
export const useScript = (url, onload) => {
useEffect(() => {
const script = document.createElement("script");
@@ -15,4 +15,3 @@ export const useScript = (url, onload) => {
};
}, [url, onload]);
};
+1 -1
View File
@@ -25,7 +25,7 @@ i18n
interpolation: {
escapeValue: false // Not needed for react as it escapes by default
},
whitelist: ["en", "es", "fr", "it", "de"] // List of allowed languages
whitelist: ["en", "es", "fr", "it", "de", "hi"] // List of allowed languages
});
export default i18n;
+163
View File
@@ -17,6 +17,9 @@ body {
scrollbar-width: none;
/* Firefox */
}
.react-datepicker-popper {
z-index: 9999 !important;
}
@media screen and (max-width: 766px) {
.reactour__close {
@@ -95,4 +98,164 @@ body {
*::-webkit-scrollbar-thumb {
background-color: gray;
border-radius: 10px;
}
/* Note: Dark mode styling is now handled via Tailwind utilities in tailwind.config.js */
/* You can use classes like: icon-improved, icon-muted, icon-disabled, op-btn-vscode-disabled */
/* React-tour and ReactTooltip dark mode styling */
[data-theme="opensigndark"] {
/* React-tour modal styling */
.reactour__helper {
background-color: #1F2937 !important;
color: #E5E7EB !important;
border: 1px solid #374151 !important;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.5) !important;
}
.reactour__close {
color: #E5E7EB !important;
background-color: #374151 !important;
border: 1px solid #4B5563 !important;
}
.reactour__close:hover {
background-color: #4B5563 !important;
color: #FFFFFF !important;
}
/* React-tour navigation buttons */
.reactour__controls {
background-color: #1F2937 !important;
border-top: 1px solid #374151 !important;
}
.reactour__controls button {
background-color: #374151 !important;
color: #E5E7EB !important;
border: 1px solid #4B5563 !important;
}
.reactour__controls button:hover {
background-color: #4B5563 !important;
color: #FFFFFF !important;
}
.reactour__controls button:disabled {
background-color: #3C3C3C !important;
color: #858585 !important;
border-color: #565656 !important;
cursor: not-allowed !important;
}
/* ReactTooltip styling */
.react-tooltip {
background-color: #1F2937 !important;
color: #E5E7EB !important;
border: 1px solid #374151 !important;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4) !important;
}
.react-tooltip.type-dark {
background-color: #1F2937 !important;
color: #E5E7EB !important;
}
.react-tooltip.place-top:after,
.react-tooltip.place-bottom:after,
.react-tooltip.place-left:after,
.react-tooltip.place-right:after {
border-color: #1F2937 !important;
}
/* Tour content improvements */
.reactour__badge {
background-color: #007ACC !important;
color: #FFFFFF !important;
}
.reactour__helper h1,
.reactour__helper h2,
.reactour__helper h3,
.reactour__helper h4,
.reactour__helper h5,
.reactour__helper h6 {
color: #FFFFFF !important;
}
.reactour__helper p {
color: #E5E7EB !important;
}
/* VS Code-style tour buttons */
.reactour__controls .op-btn {
background-color: #007ACC !important;
color: #FFFFFF !important;
border: 1px solid #007ACC !important;
}
.reactour__controls .op-btn:hover {
background-color: #0086D1 !important;
border-color: #0086D1 !important;
}
.reactour__controls .op-btn-secondary {
background-color: #374151 !important;
color: #E5E7EB !important;
border: 1px solid #4B5563 !important;
}
.reactour__controls .op-btn-secondary:hover {
background-color: #4B5563 !important;
color: #FFFFFF !important;
}
/* React-datepicker dark mode styling */
.react-datepicker {
background-color: #1F2937 !important;
border: 1px solid #374151 !important;
color: #E5E7EB !important;
}
.react-datepicker__header {
background-color: #374151 !important;
border-bottom: 1px solid #4B5563 !important;
color: #E5E7EB !important;
}
.react-datepicker__current-month,
.react-datepicker__day-name {
color: #E5E7EB !important;
}
.react-datepicker__day {
color: #E5E7EB !important;
}
.react-datepicker__day:hover {
background-color: #4B5563 !important;
color: #FFFFFF !important;
}
.react-datepicker__day--selected {
background-color: #007ACC !important;
color: #FFFFFF !important;
}
.react-datepicker__day--keyboard-selected {
background-color: #374151 !important;
color: #E5E7EB !important;
}
.react-datepicker__day--outside-month {
color: #6B7280 !important;
}
.react-datepicker__navigation {
color: #E5E7EB !important;
}
.react-datepicker__navigation:hover {
background-color: #4B5563 !important;
}
}
@@ -1,10 +1,10 @@
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import "./styles/dark-theme-improvements.css";
import App from "./App";
import { Provider } from "react-redux";
import { store } from "./redux/store";
import { CookiesProvider } from "react-cookie";
import { HTML5Backend } from "react-dnd-html5-backend";
import { TouchBackend } from "react-dnd-touch-backend";
import {
@@ -13,19 +13,23 @@ import {
MouseTransition,
Preview
} from "react-dnd-multi-backend";
import DragElement from "./components/pdf/DragElement";
import DragElement from "./components/pdf/DragElement.jsx";
import Parse from "parse";
import "./polyfills";
import { serverUrl_fn } from "./constant/appinfo";
import "./i18n";
const appId = process.env.REACT_APP_APPID
? process.env.REACT_APP_APPID
: "opensign";
const appId =
import.meta.env.VITE_APPID || process.env.REACT_APP_APPID || "opensign";
const serverUrl = serverUrl_fn();
Parse.initialize(appId);
Parse.serverURL = serverUrl;
const savedTheme = localStorage.getItem("theme");
if (savedTheme === "dark") {
document.documentElement.setAttribute("data-theme", "opensigndark");
}
const HTML5toTouch = {
backends: [
{
@@ -55,14 +59,13 @@ const generatePreview = (props) => {
);
};
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<CookiesProvider defaultSetOptions={{ path: "/" }}>
<Provider store={store}>
<DndProvider options={HTML5toTouch}>
<Preview>{generatePreview}</Preview>
<App />
</DndProvider>
</Provider>
</CookiesProvider>
<Provider store={store}>
<DndProvider options={HTML5toTouch}>
<Preview>{generatePreview}</Preview>
<App />
</DndProvider>
</Provider>
);
+10 -2
View File
@@ -186,6 +186,14 @@ export default function reportJson(id) {
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"
}
]
}
@@ -533,9 +541,9 @@ export default function reportJson(id) {
btnId: "1873",
btnLabel: "Share with team",
hoverLabel: "Share with team",
btnIcon: "fa-light fa-share-nodes",
btnIcon: "fa-light fa-user-group",
redirectUrl: "",
action: "sharewith"
action: "sharewithteam"
});
return newItem;
}
+1 -1
View File
@@ -36,7 +36,7 @@ const dashboardJson = [
queryType: "",
class: "contracts_Document",
query:
'where={"Type":{"#*ne":"Folder"},"Signers":{"#*exists":true,"#*ne":[]},"Placeholders":{"#*ne":null},"SignedUrl":{"#*ne":null},"IsCompleted":{"#*ne":true},"IsDeclined":{"#*ne":true},"IsArchive":{"#*ne":true},"CreatedBy":{"__type":"Pointer","className":"_User","objectId":"#UserId.objectId#"},"ExpiryDate":{"#*gt":{"__type":"#Date#","iso":"#today#"}}}&count=1',
'where={"Type":null,"Signers":{"#*exists":true},"Placeholders":{"#*exists":true},"SignedUrl":{"#*exists":true},"IsCompleted":false,"IsDeclined":false,"IsArchive":null,"CreatedBy":{"__type":"Pointer","className":"_User","objectId":"#UserId.objectId#"},"ExpiryDate":{"#*gt":{"__type":"#Date#","iso":"#today#"}}}&keys=Name,ExpiryDate,SignedUrl,Signers&count=1',
key: "count",
Redirect_type: "Report",
Redirect_id: "1MwEuxLEkF",

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