Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7204183542 | ||
|
|
5561673fdc | ||
|
|
0df44062d6 | ||
|
|
8983440763 | ||
|
|
5a28c0ed06 | ||
|
|
cbca22c308 | ||
|
|
b0e0b38e8f | ||
|
|
49c23fb52b | ||
|
|
da4b604710 | ||
|
|
82d518b3c9 | ||
|
|
dcbde2b661 | ||
|
|
27b0b426ad | ||
|
|
8788afaa8e | ||
|
|
428aa65b38 | ||
|
|
295d942427 | ||
|
|
c4e28e6e6e | ||
|
|
b19fb29b82 | ||
|
|
3d223ba343 | ||
|
|
98754c776f | ||
|
|
1550e3a3d0 | ||
|
|
3500883205 | ||
|
|
d888897b62 | ||
|
|
8f2433fbea | ||
|
|
b671dbac19 | ||
|
|
79cdbc9c8c | ||
|
|
e585b7c943 | ||
|
|
5336e2cb7c | ||
|
|
72401291f8 | ||
|
|
7b34314b9e | ||
|
|
b6f1cceeca | ||
|
|
c422cdac06 | ||
|
|
ab08d2615d | ||
|
|
2b0c7488d9 | ||
|
|
6927540741 | ||
|
|
1ceeee36ac | ||
|
|
c4779c14c1 | ||
|
|
e48869cb2f | ||
|
|
81d3969d1f | ||
|
|
5819103f57 | ||
|
|
6653b7ae5a | ||
|
|
b50c1e70ee | ||
|
|
365c2a217e | ||
|
|
b1b9b20e97 | ||
|
|
75c0cdb5f9 | ||
|
|
740ccca1f6 | ||
|
|
f3b79cb71f | ||
|
|
f95ea0b9e9 | ||
|
|
647a9ce5b4 | ||
|
|
3f3ca668f5 | ||
|
|
2fd6d1a37a | ||
|
|
307137f926 | ||
|
|
2394880dfc | ||
|
|
414cdcd4b5 | ||
|
|
1fa6b25890 | ||
|
|
ff2fd44b19 | ||
|
|
ad466fb066 | ||
|
|
0f179fed67 | ||
|
|
3ded6c35a1 | ||
|
|
64293db3a1 | ||
|
|
18a4c8523a | ||
|
|
f7b39409a8 | ||
|
|
6283524d2f | ||
|
|
216288fd09 | ||
|
|
3b1d8a48b7 | ||
|
|
ec68770ff0 | ||
|
|
f5d2027e3a | ||
|
|
8af0010f55 | ||
|
|
d7b7ad6285 | ||
|
|
fca45ced38 | ||
|
|
ac8ab70246 | ||
|
|
8373679f27 | ||
|
|
0a23aabbe4 | ||
|
|
4a85599703 | ||
|
|
3e9b84de72 | ||
|
|
4313d8d0e4 | ||
|
|
3088fdd163 | ||
|
|
8c86fe27ec | ||
|
|
01e09998e5 | ||
|
|
55fdb52bf3 | ||
|
|
08f46a393b | ||
|
|
9345da543a | ||
|
|
208891e10d | ||
|
|
db0fa61b2b | ||
|
|
221f098de6 | ||
|
|
9658761ede | ||
|
|
6aad832e37 | ||
|
|
e4b6be51d9 | ||
|
|
a3b2c797fb | ||
|
|
22d27abb76 | ||
|
|
45e0679ca7 | ||
|
|
2b2aa20deb | ||
|
|
8d6ed3d2b5 | ||
|
|
6064f66cdf | ||
|
|
0ba29eb799 | ||
|
|
d0dd571478 | ||
|
|
ddb63d81a7 | ||
|
|
f78451d3f9 | ||
|
|
28630e2d23 | ||
|
|
c5022eaee6 | ||
|
|
1b7f07a2c4 | ||
|
|
2484c67406 | ||
|
|
abfc3f32f2 | ||
|
|
5c06ff4242 | ||
|
|
1899acfffc | ||
|
|
796ff058c8 | ||
|
|
c88cb5357c | ||
|
|
a6a0325d4b | ||
|
|
b18d17c44d | ||
|
|
a73779e74b | ||
|
|
5b943c1965 | ||
|
|
f73844d730 | ||
|
|
aaadab540a | ||
|
|
83bc466df4 | ||
|
|
d85ed954f1 | ||
|
|
369deb6e26 | ||
|
|
295d1af910 | ||
|
|
080d7c08dc | ||
|
|
c877f9fc5d | ||
|
|
99c7a58d62 | ||
|
|
97642bf857 | ||
|
|
2f67ac7b0a | ||
|
|
23aecb45d9 | ||
|
|
92b507e621 | ||
|
|
1f6c2c7d38 | ||
|
|
c1be197346 | ||
|
|
38f8561d18 | ||
|
|
10e6ab58a5 | ||
|
|
24a95e8852 | ||
|
|
39fc2daa19 | ||
|
|
c38f59642f | ||
|
|
e9d0af7abb | ||
|
|
b39d845cd6 | ||
|
|
031673f925 | ||
|
|
e299891174 | ||
|
|
b55aff20d3 | ||
|
|
a9ccb1a3e5 | ||
|
|
f3d34b1d2c | ||
|
|
3f56aa0860 | ||
|
|
4eeacbaaf1 | ||
|
|
096a9e5736 | ||
|
|
4650660a26 | ||
|
|
9e6309644a | ||
|
|
14477a17ee | ||
|
|
7d3d618ff3 | ||
|
|
284cd277ea | ||
|
|
df61d752c8 | ||
|
|
9def956929 | ||
|
|
53aa5a26aa | ||
|
|
86cb5ccf78 | ||
|
|
c8e91e5205 | ||
|
|
87cfb643f9 | ||
|
|
2f7bd75608 | ||
|
|
930bac71a0 | ||
|
|
fcad7dc020 | ||
|
|
bada731d16 | ||
|
|
fd5e86edf2 | ||
|
|
1a886261ef | ||
|
|
657ab13a04 |
@@ -8,13 +8,11 @@ PUBLIC_URL=https://localhost:3001
|
||||
GENERATE_SOURCEMAP=false
|
||||
# Set it to the URL from where APIs will be accessible, for local development it should be localhost:3000/api/app (use your local port number instead)
|
||||
# REACT_APP_SERVERURL=http://localhost:8080/app
|
||||
# A 12 character long random app identifier. The value of this should be same as APP_ID which is a variable used by backend API.
|
||||
# (DEPRECATED) This should not be changed if provided; it should be 'opensign'.
|
||||
REACT_APP_APPID=opensign
|
||||
|
||||
|
||||
# Backend ExpressJS config ****************************************************************************************************************************************************************************************
|
||||
# A 12 character long random app identifier. The value of this should be same as REACT_APP_APPID which is a variable used by Frontend React App.
|
||||
APP_ID=opensign
|
||||
# Name of the app. It will be visible in the verification emails sent out.
|
||||
appName=open_sign_server
|
||||
# A 12 character long random secret key that allows access to all the data. It is used in Parse dashboard config to view all the data in the database.
|
||||
@@ -111,3 +109,6 @@ CRUxFgQUDYlgGVxSxuOknhQc256x3++7BDwwMTAhMAkGBSsOAwIaBQAEFFjASdYl
|
||||
|
||||
# Provide Pass pharse of above PFX or p12 document
|
||||
PASS_PHRASE=opensign
|
||||
|
||||
# (DEPRECATED) This should not be changed if provided; it should be 'opensign'.
|
||||
APP_ID=opensign
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
name: ci
|
||||
|
||||
# 👇 add this block
|
||||
permissions:
|
||||
contents: read # allow checkout & metadata-action to read repo
|
||||
id-token: write # needed by docker/metadata-action v4
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
- 'staging'
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{$HOST_URL} {
|
||||
reverse_proxy client:3000
|
||||
handle_path /app/* {
|
||||
handle_path /api/* {
|
||||
reverse_proxy server:8080
|
||||
rewrite * /app{uri}
|
||||
rewrite * {uri}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,11 +73,27 @@ Welcome to OpenSign, the premier open source docusign alternative - document e-s
|
||||
|
||||
---
|
||||
|
||||
### Installation
|
||||
### Deploy
|
||||
|
||||
Note: The default MongoDB instance used in deployment is not persistant and will be cleared on every restart. To retain your data, configure and supply your own MongoDB connection URL.
|
||||
|
||||
#### DigitalOcean
|
||||
[](https://cloud.digitalocean.com/apps/new?repo=https://github.com/OpenSignLabs/Deploy-OpenSign-to-Digital-Ocean/tree/main&refcode=30db1c901ab0)
|
||||
|
||||
#### Docker
|
||||
The simplest way to install OpenSign on your own server is using official docker images by running the following command -
|
||||
|
||||
**Command for linux/MacOS**
|
||||
```
|
||||
export HOST_URL=https://opensign.yourdomain.com && curl --remote-name-all https://raw.githubusercontent.com/OpenSignLabs/OpenSign/docker_beta/docker-compose.yml https://raw.githubusercontent.com/OpenSignLabs/OpenSign/docker_beta/Caddyfile https://raw.githubusercontent.com/OpenSignLabs/OpenSign/docker_beta/.env.local_dev && mv .env.local_dev .env.prod && docker compose up --force-recreate
|
||||
export HOST_URL=https://opensign.yourdomain.com && curl --remote-name-all https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/docker-compose.yml https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/Caddyfile https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/.env.local_dev && mv .env.local_dev .env.prod && docker compose up --force-recreate
|
||||
```
|
||||
**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 -
|
||||
|
||||
@@ -158,3 +174,5 @@ We would like to thank all our contributors and users for their support and feed
|
||||
|
||||
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
||||
|
||||
This project is tested with BrowserStack.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Use an official Node runtime as the base image
|
||||
FROM node:18
|
||||
FROM node:22.14.0
|
||||
|
||||
# Set the working directory inside the container
|
||||
WORKDIR /usr/src/app
|
||||
@@ -13,15 +13,24 @@ 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
|
||||
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
|
||||
|
||||
# Define environment variables if needed
|
||||
# ENV NODE_ENV production
|
||||
ENTRYPOINT ["./entrypoint.sh"]
|
||||
|
||||
# Run the application
|
||||
CMD ["npm", "start"]
|
||||
|
||||
@@ -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.
|
||||
@@ -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
|
||||
};
|
||||
@@ -1,20 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" data-theme="opensigncss">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
|
||||
<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"
|
||||
integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous" />
|
||||
<link rel="stylesheet" href="https://cdn.opensignlabs.com/fonts.css" />
|
||||
<title>OpenSign™</title>
|
||||
<link rel="stylesheet" href="https://cdn.opensignlabs.com/fonts.css" />
|
||||
<title>...Loading</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<div id="root" style="touch-action:pan-x pan-y;"></div>
|
||||
<script type="module" src="/src/index.jsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
@@ -1,73 +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.9.3",
|
||||
"@imgly/background-removal": "^1.6.0",
|
||||
"@lottiefiles/dotlottie-react": "^0.14.0",
|
||||
"@pdf-lib/fontkit": "^1.1.1",
|
||||
"@radix-ui/themes": "^3.1.6",
|
||||
"@react-pdf/renderer": "^4.0.0",
|
||||
"@reduxjs/toolkit": "^2.3.0",
|
||||
"axios": "^1.7.7",
|
||||
"@radix-ui/themes": "^3.2.1",
|
||||
"@reduxjs/toolkit": "^2.8.2",
|
||||
"axios": "^1.9.0",
|
||||
"date-fns-tz": "^3.2.0",
|
||||
"file-saver": "^2.0.5",
|
||||
"i18next": "^23.16.5",
|
||||
"i18next-browser-languagedetector": "^8.0.0",
|
||||
"i18next-http-backend": "^2.6.2",
|
||||
"i18next": "^23.16.8",
|
||||
"i18next-browser-languagedetector": "^8.1.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": "^5.3.0",
|
||||
"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.5",
|
||||
"react-confetti": "^6.1.0",
|
||||
"react-cookie": "^7.2.2",
|
||||
"react-datepicker": "^7.5.0",
|
||||
"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-datepicker": "^8.4.0",
|
||||
"react-dnd": "^16.0.1",
|
||||
"react-dnd-html5-backend": "^16.0.1",
|
||||
"react-dnd-multi-backend": "^8.0.3",
|
||||
"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.1.1",
|
||||
"react-i18next": "^15.5.2",
|
||||
"react-konva": "^18.2.10",
|
||||
"react-pdf": "^9.1.1",
|
||||
"react-quill-new": "^3.3.3",
|
||||
"react-redux": "^9.1.2",
|
||||
"react-rnd": "^10.4.13",
|
||||
"react-router-dom": "^6.28.0",
|
||||
"react-scripts": "5.0.1",
|
||||
"react-pdf": "^9.2.1",
|
||||
"react-quill-new": "^3.4.6",
|
||||
"react-redux": "^9.2.0",
|
||||
"react-rnd": "^10.5.2",
|
||||
"react-router": "^7.6.1",
|
||||
"react-scrollbars-custom": "^4.1.1",
|
||||
"react-select": "^5.8.3",
|
||||
"react-signature-canvas": "^1.0.6",
|
||||
"react-syntax-highlighter": "^15.6.1",
|
||||
"react-tooltip": "^5.28.0",
|
||||
"react-web-share": "^2.0.2",
|
||||
"react-select": "^5.10.1",
|
||||
"react-signature-canvas": "^1.1.0-alpha.2",
|
||||
"react-timezone-select": "^3.2.8",
|
||||
"react-tooltip": "^5.28.1",
|
||||
"reactour": "^1.19.4",
|
||||
"redux": "^5.0.1",
|
||||
"redux-thunk": "^3.1.0",
|
||||
"regex-parser": "^2.3.0",
|
||||
"regex-parser": "^2.3.1",
|
||||
"serve": "^14.2.4",
|
||||
"styled-components": "^5.3.0",
|
||||
"web-vitals": "^4.2.4",
|
||||
"ws": "^8.18.0"
|
||||
"styled-components": "^5.3.11",
|
||||
"web-vitals": "^5.0.2",
|
||||
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz"
|
||||
},
|
||||
"scripts": {
|
||||
"build-webpack": "webpack --config webpack.config.js",
|
||||
"build": "npm run version && npm run build-webpack && react-scripts build",
|
||||
"build-watch": "webpack --config webpack.config.js --watch",
|
||||
"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 && npm run build-webpack && 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"
|
||||
},
|
||||
@@ -95,36 +94,40 @@
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.26.0",
|
||||
"@babel/preset-env": "^7.26.0",
|
||||
"@babel/preset-react": "^7.25.9",
|
||||
"@babel/runtime-corejs2": "^7.26.0",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"babel-loader": "^9.2.1",
|
||||
"clean-webpack-plugin": "^4.0.0",
|
||||
"@babel/core": "^7.27.4",
|
||||
"@babel/preset-env": "^7.27.2",
|
||||
"@babel/preset-react": "^7.27.1",
|
||||
"@babel/runtime-corejs2": "^7.27.4",
|
||||
"@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.5.1",
|
||||
"@vitejs/plugin-react-swc": "^3.10.1",
|
||||
"@vitest/ui": "^3.2.0",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"babel-loader": "^10.0.0",
|
||||
"commitizen": "^4.3.1",
|
||||
"concurrently": "^9.1.0",
|
||||
"concurrently": "^9.1.2",
|
||||
"css-loader": "^7.1.2",
|
||||
"daisyui": "^4.12.14",
|
||||
"dotenv": "^16.4.5",
|
||||
"dotenv-webpack": "^8.1.0",
|
||||
"eslint": "^9.14.0",
|
||||
"eslint-plugin-prettier": "^5.2.1",
|
||||
"eslint-plugin-react": "^7.37.2",
|
||||
"lint-staged": "^15.2.10",
|
||||
"mini-css-extract-plugin": "^2.9.2",
|
||||
"postcss": "^8.4.49",
|
||||
"prettier": "^3.3.3",
|
||||
"pretty-quick": "^4.0.0",
|
||||
"tailwindcss": "^3.4.14",
|
||||
"terser-webpack-plugin": "^5.3.10",
|
||||
"webpack-cli": "^5.1.4"
|
||||
},
|
||||
"overrides": {
|
||||
"nth-check": "$nth-check",
|
||||
"ws": "$ws"
|
||||
"daisyui": "^4.12.24",
|
||||
"dotenv": "^16.5.0",
|
||||
"eslint": "^9.28.0",
|
||||
"eslint-plugin-prettier": "^5.4.1",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"jsdom": "^26.1.0",
|
||||
"lint-staged": "^16.1.0",
|
||||
"postcss": "^8.5.4",
|
||||
"prettier": "^3.5.3",
|
||||
"pretty-quick": "^4.2.2",
|
||||
"rollup-plugin-node-polyfills": "^0.2.1",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"vite": "^6.3.5",
|
||||
"vite-plugin-svgr": "^4.3.0",
|
||||
"vite-tsconfig-paths": "^5.1.4",
|
||||
"vitest": "^3.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "18 || 20 || 22"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
@@ -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",
|
||||
@@ -39,8 +41,10 @@
|
||||
"save": "Guardar",
|
||||
"cancel": "Cancelar",
|
||||
"upgrade-now": "Mejorar ahora",
|
||||
"contact-now": "Contactar ahora",
|
||||
"upgrade-to": "Mejorar a",
|
||||
"plan": "Plan",
|
||||
"subscription-renew-warning": "Su suscripción vencerá en {{remainingDays}} días. Por favor, renueve su suscripción.",
|
||||
"subscribe-card-teamplan": "¡Libera todo el poder de la colaboración! Crea organizaciones, equipos y jerarquías ilimitadas. Comparte plantillas sin problemas entre equipos y asigna funciones de usuario personalizadas. ¡Mejora tu flujo de trabajo hoy mismo!",
|
||||
"subscribe-card-plan": "Desbloquea funciones premium desde solo {{premiumPrice}}/mes. Disfruta de un rendimiento mejorado y solo {{addonPrice}} por crédito adicional después de tus créditos premium incluidos.",
|
||||
"user-name-limit-char": "Para tener un nombre de usuario menor a 8 caracteres por favor suscríbete",
|
||||
@@ -51,7 +55,6 @@
|
||||
"docs": "Documentación",
|
||||
"session-expired": "Tu sesión ha expirado.",
|
||||
"public-profile-help": "Esta es tu URL pública. Cópiala o compártela con el firmante, y podrás ver todas tus plantillas públicas.",
|
||||
"tagline-help": "Este eslogan aparecerá en tu perfil público de OpenSign, por ejem. https://opensign.me/alex",
|
||||
"welcome": "¡Bienvenido de nuevo!",
|
||||
"Login-to-your-account": "Ingresa a tu cuenta",
|
||||
"password": "Contraseña",
|
||||
@@ -74,21 +77,34 @@
|
||||
"Sign yourself": "Firma tu mismo",
|
||||
"Request signatures": "Solicitar firmas",
|
||||
"Templates": "Plantillas",
|
||||
"Analytics": "Analítica",
|
||||
"Branding": "Marca",
|
||||
"Mail": "Correo",
|
||||
"Storage": "Almacenamiento",
|
||||
"Signing certificate": "Certificado de firma",
|
||||
"Teams": "Equipos",
|
||||
"General": "General",
|
||||
"Teams-Children": {
|
||||
"Organizations": "Organizaciones",
|
||||
"OrgAdmins": "OrgAdmins"
|
||||
},
|
||||
"Templates-Children": {
|
||||
"Create template": "Crear plantilla",
|
||||
"Manage templates": "Administrar plantillas"
|
||||
"Manage templates": "Administrar plantillas",
|
||||
"Embedding": "Incrustar"
|
||||
},
|
||||
"OpenSign™ Drive": "OpenSign™ Drive",
|
||||
"OpenSign™ Drive": "{{appName}} Drive",
|
||||
"Reports": "Reportes",
|
||||
"Reports-Children": {
|
||||
"Documents": "Documentos",
|
||||
"Documents-Children": {
|
||||
"Need your sign": "Necesitan tu firma",
|
||||
"In Progress": "En progreso",
|
||||
"Completed": "Completado",
|
||||
"Drafts": "Borradores",
|
||||
"Declined": "Rechazados",
|
||||
"Expired": "Expirados",
|
||||
"Contactbook": "Agenda de contactos"
|
||||
"Expired": "Expirados"
|
||||
},
|
||||
"Contactbook": "Agenda de contactos",
|
||||
"Settings": "Ajustes",
|
||||
"Settings-Children": {
|
||||
"My Signature": "Mi firma",
|
||||
@@ -123,19 +139,27 @@
|
||||
"sign": "firmar",
|
||||
"Sign": "FIRMAR",
|
||||
"Resend": "Reenviar",
|
||||
"Rename": "Renombrar",
|
||||
"Revoke": "Revocar",
|
||||
"Delete": "Eliminar",
|
||||
"Use": "Usar",
|
||||
"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",
|
||||
"Embed": "Incrustar",
|
||||
"Copy TemplateId": "Copiar ID de plantilla",
|
||||
"Copy Public URL": "Copiar URL pública",
|
||||
"extend-expiry-date": "Ampliar fecha de caducidad"
|
||||
"extend-expiry-date": "Date d'expiration",
|
||||
"Duplicate Template": "Plantilla duplicada",
|
||||
"Duplicate": "Duplicada",
|
||||
"daily-mail-quota": "Cuota diaria de correos electrónicos",
|
||||
"Save as template": "Guardar como plantilla",
|
||||
"Fix & resend": "Corregir y reenviar",
|
||||
"Kiosk Mode": "Modo Kiosco"
|
||||
},
|
||||
"report-heading": {
|
||||
"Sr.No": "Nº",
|
||||
@@ -193,7 +217,7 @@
|
||||
"remind-once": " Recordar una vez cada (Días)",
|
||||
"next": "Siguiente",
|
||||
"select-folder": "Seleccionar carpeta",
|
||||
"OpenSign-drive": "OpenSign™ Drive",
|
||||
"OpenSign-drive": "{{appName}} Drive",
|
||||
"drive-document-status": {
|
||||
"Completed": "Completado",
|
||||
"Declined": "Rechazado",
|
||||
@@ -201,7 +225,7 @@
|
||||
"Expired": "Expirado",
|
||||
"In Progress": "En progreso"
|
||||
},
|
||||
"select-folder-help": "Si no seleccionas una carpeta, tu documento firmado se guardará en la carpeta principal de OpenSign Drive.",
|
||||
"select-folder-help": "Si no seleccionas una carpeta, tu documento firmado se guardará en la carpeta principal de {{appName}} Drive.",
|
||||
"no-data": "No se encontraron datos",
|
||||
"save-here": "Guardar aquí",
|
||||
"back": "Atrás",
|
||||
@@ -211,12 +235,14 @@
|
||||
"create": "Crear",
|
||||
"signers": "Firmantes",
|
||||
"signers-help": "Empieza a escribir el nombre de un contacto para ver los firmantes sugeridos entre tus contactos guardados o añade otros nuevos. Organiza el orden de firma añadiendo firmantes en la secuencia deseada. Utiliza el botón «+» para añadir nuevos firmantes y la «X» para quitar todos. Cada firmante recibirá una solicitud por correo electrónico para que firme el documento según el orden indicado.",
|
||||
"bcc-help": "Empieza a escribir el nombre de un contacto para ver sugerencias de tus contactos guardados o agregar nuevos. Usa el botón '+' para añadir un usuario y el botón 'x' para eliminarlo. El correo electrónico del usuario seleccionado se añadirá como Bcc (copia oculta). Cada usuario recibirá una notificación por correo electrónico una vez que el documento esté completado.",
|
||||
"add-signer": "Añadir firmante",
|
||||
"contact-not-found": "Contacto no encontrado",
|
||||
"add-yourself": "Añadirte a ti mismo",
|
||||
"submit": "Enviar",
|
||||
"reset": "Volver a empezar",
|
||||
"my-signature": "Mi firma",
|
||||
"initial-alert": "Mi inicial no encontrada",
|
||||
"signature": "Firma",
|
||||
"upload-image": " Subir imagen",
|
||||
"clear": "Limpiar",
|
||||
@@ -225,12 +251,13 @@
|
||||
"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.",
|
||||
"yes": "Sí",
|
||||
"copied": "Copiado",
|
||||
"something-went-wrong-mssg": "Algo salió mal, por favor, intenta de nuevo más tarde.",
|
||||
"something-went-wrong-mssg": "Un problème est survenu, Actualiser cette page peut résoudre le problème.",
|
||||
"token-generated": "Token generado exitosamente.",
|
||||
"webhook": "Webhook",
|
||||
"update-webhook": "Actualizar webhook",
|
||||
@@ -266,13 +293,14 @@
|
||||
"make-template-public": "Convertir la plantilla en pública",
|
||||
"make-template-private": "Convertir la plantilla en privada",
|
||||
"make-template-public-alert": "¿En definitiva quieres convertir esta plantilla en pública?",
|
||||
"make-template-private-alert-non": "¿Está seguro de que desea hacer este plantilla privado?",
|
||||
"make-template-private-alert": "¿En definitiva quieres convertir esta plantilla en privada? Esto lo removerá de tu perfil público.",
|
||||
"public-role": "Rol público",
|
||||
"public-url": "Perfil publico",
|
||||
"embed-template": "Insertar plantilla",
|
||||
"public-url-copy": "Aquí está tu URL pública: ",
|
||||
"public-url-copy-mssg": "Cópiala o compártela con el firmante y podrás ver todas tus plantillas públicas.",
|
||||
"add-public-url-alert": "Por favor, completa la configuración de tu perfil público OpenSign. Una vez terminado, podrás crear plantillas públicas. Este proceso solo necesita ser completado una vez.",
|
||||
"add-public-url-alert": "Puedes generar tu perfil público {{appName}}",
|
||||
"share-with-alert": "No puedes compartir una plantilla si alguno de los roles ya tiene contactos asignados. Elimina todas las asignaciones de contactos de los roles antes de compartir la plantilla.",
|
||||
"share-with": "Compartir con",
|
||||
"team-not-found": "Equipo no encontrado",
|
||||
@@ -284,17 +312,18 @@
|
||||
"send": "Enviar",
|
||||
"quick-send-alert-1": "Todos los roles de este documento están actualmente vinculados a contactos. Para enviar rápidamente copias de esta plantilla a varios firmantes, por favor, asegúrate de que al menos un rol no esté vinculado a ningún contacto.",
|
||||
"quick-send-alert-2": "Por favor, asegúrate de que hay al menos un widget de firma añadido para cada destinatario.",
|
||||
"quick-send-alert-3": "Por favor, añade al menos un rol a esta plantilla para poder hacer un «envío rápido» de copias a varios firmantes.",
|
||||
"quick-send-alert-3": "Veuillez ajouter au moins un rôle à ce template.",
|
||||
"quick-send-alert-4": "El envío rápido ha alcanzado el límite.",
|
||||
"copy-link": "Copiar enlace",
|
||||
"copy": "Copiar",
|
||||
"revoke-document": "Revocar documento",
|
||||
"revoke-document-alert": "¿En definitiva quieres revocar este documento?",
|
||||
"resend-mail": "Reenviar correo",
|
||||
"resend-mail-help": "Puedes usar las siguientes variables que serán reemplazadas por sus valores reales:- {{document_title}}, {{sender_name}}, {{sender_mail}}, {{sender_phone}}, {{receiver_name}}, {{receiver_email}}, {{receiver_phone}}, {{expiry_date}}, {{company_name}}, {{signing_url}}.",
|
||||
"resend-mail-help": "Puedes usar las siguientes variables que serán reemplazadas por sus valores reales:- {{document_title}}, {{sender_name}}, {{sender_mail}}, {{sender_phone}}, {{receiver_name}}, {{receiver_email}}, {{receiver_phone}}, {{expiry_date}}, {{company_name}}, {{signing_url}}, {{note}}.",
|
||||
"subject": "Asunto",
|
||||
"body": "Cuerpo",
|
||||
"add-contact": "Agregar contacto",
|
||||
"edit-contact": "Editar contacto",
|
||||
"add-signer-alert": "¡El contacto ya existe! Selecciónelo en el menú desplegable 'Firmantes'",
|
||||
"record-delete-alert": "¡Entrada eliminada exitosamente!",
|
||||
"record-revoke-alert": "¡Entrada revocada exitosamente!",
|
||||
@@ -320,7 +349,7 @@
|
||||
"verify-email-1": "Verificar correo",
|
||||
"resend": "Reenviar",
|
||||
"contact-details": "Detalles del contacto",
|
||||
"verify-email": "¡Por favor, verifica tu correo!",
|
||||
"verify-email": "Por favor verifique su correo electrónico para continuar. Este es un paso único para confirmar el correo electrónico asociado a su cuenta de OpenSign.",
|
||||
"send-otp": " Enviar OTP",
|
||||
"otp-placeholder": "Ingresa el código de verificación enviado por correo",
|
||||
"loading-doc": "Cargando documento...",
|
||||
@@ -334,6 +363,7 @@
|
||||
"date": "fecha",
|
||||
"text": "texto",
|
||||
"text input": "entrada de texto",
|
||||
"cells": "células",
|
||||
"checkbox": "casilla",
|
||||
"dropdown": "desplegable",
|
||||
"radio button": "botón de radio",
|
||||
@@ -388,10 +418,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",
|
||||
@@ -404,10 +436,9 @@
|
||||
"encrypted-pdf-alert": "Actualmente los archivos PDF encriptados no están soportados.",
|
||||
"encrypted-pdf-alert-1": "La contraseña del archivo PFX es inválida.",
|
||||
"user-not-exist": "El usuario no existe",
|
||||
"template-signature-field": "Por favor, agrega al menos un campo de firma para todos los roles.",
|
||||
"template-role-alert": "Por favor, primero agrega roles",
|
||||
"create-document": "Crear documento",
|
||||
"template-created-alert": "¿Quieres crear un documento usando la plantilla que acabas de crear?",
|
||||
"template-created-alert": "¿Qué te gustaría hacer con la plantilla que acabas de crear?",
|
||||
"signers-alert": "alerta de firmantes",
|
||||
"template-creation-alert-1": "¡Por favor, selecciona un firmante para agregar un marcador de posición!",
|
||||
"ok": "OK",
|
||||
@@ -439,6 +470,8 @@
|
||||
"placeholder-alert-2": "Por favor, confirma que hayas rellenado el campo de texto.",
|
||||
"placeholder-alert-3": " ¿En definitiva quieres enviar este documento para ser firmado?",
|
||||
"placeholder-alert-4": "¡Has enviado exitosamente correos a todos los destinatarios!",
|
||||
"placeholder-mail-alert": "Has enviado un correo electrónico con éxito a {{name}}. Los siguientes firmantes recibirán un correo electrónico una vez que {{name}} firme el documento.",
|
||||
"placeholder-mail-alert-you": "Los firmantes posteriores recibirán correos electrónicos una vez que firme el documento.",
|
||||
"placeholder-alert-5": "¿Quieres firmar documentos ahora mismo?",
|
||||
"placeholder-alert-6": "¡Por favor, configura el adaptador de correo para enviar correos!",
|
||||
"placeholder-alert-7": "¡Por favor, selecciona un firmante para agregar un marcador de posición!",
|
||||
@@ -453,6 +486,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",
|
||||
@@ -469,12 +503,13 @@
|
||||
"sign-up": "Registrarse",
|
||||
"folder-already-exist!": "¡la carpeta ya existe!",
|
||||
"please-select-position!": "¡por favor, selecciona una posición!",
|
||||
"default-sign-alert": "¿En definitiva quieres firmar automáticamente en todos los lugares solicitados?",
|
||||
"default-sign-alert": "¿Está seguro de que desea iniciar sesión automáticamente en todos los {{widgetsType}} widgets solicitados?",
|
||||
"Your-Signature": "Tu firma",
|
||||
"auto-sign-all": "Firmar todo automáticamente",
|
||||
"generating-certificate": "Generando certificado",
|
||||
"pdf-download": "Descargar PDF",
|
||||
"generate-certificate-alert": " Tu certificado de finalización se está generando. Por favor, espera un momento. Si la descarga no inicia pronto, presiona el botón otra vez.",
|
||||
"generate-certificate-err": "Estamos experimentando un problema al generar el certificado. Por favor, inténtelo de nuevo más tarde.",
|
||||
"folder-already-exist": "¡La carpeta ya existe!",
|
||||
"folder-created-successfully": "¡Carpeta creada exitosamente!",
|
||||
"fill-folder-name": "Por favor, rellena el nombre de la carpeta",
|
||||
@@ -484,7 +519,7 @@
|
||||
"role-not-found": "Rol no encontrado.",
|
||||
"do-not-access": "No tienes acceso, por favor, contacta al administrador.",
|
||||
"add-admin": "Agregar administrador",
|
||||
"opensign-setup": "Instalación de OpenSign",
|
||||
"opensign-setup": "Instalación de {{appName}}",
|
||||
"join-discord": "Únete a nuestro servidor de Discord",
|
||||
"admin-already-exist": "El administrador ya existe.",
|
||||
"password-update-alert-1": "Contraseña actualizada exitosamente.",
|
||||
@@ -501,7 +536,7 @@
|
||||
"correct-password": "Por favor, proporciona la contraseña correcta",
|
||||
"decrypting-pdf": " Desencriptando PDF, por favor, espera...",
|
||||
"invalid-otp": "OTP inválido",
|
||||
"user-not-found": "¡Usuario no encontrado!",
|
||||
"user-not-found": "Usuario no encontrado",
|
||||
"enter-otp-alert": "¡Por favor, ingresa el OTP!",
|
||||
"get-verification-code": "Obtener código de verificación",
|
||||
"get-verification-code-2": "Obtendrás un código de verificación por correo",
|
||||
@@ -510,6 +545,7 @@
|
||||
"provide-email": "Por favor, proporciona un correo.",
|
||||
"additional-info": "Información adicional",
|
||||
"signature-saved-alert": "Firma guardada exitosamente.",
|
||||
"saved-successfully": "Guardado con éxito.",
|
||||
"fill-field": "Por favor, rellena este campo",
|
||||
"error-template": "Error: ¡Plantilla no encontrada!",
|
||||
"error": "Error",
|
||||
@@ -540,7 +576,6 @@
|
||||
"filed-required-correctly": "Por favor, rellena correctamente los detalles requeridos.",
|
||||
"admin-created": "Administrador creado",
|
||||
"invalid-masterkey": "Llave maestra inválida",
|
||||
"opensign-Setup": "Instalación de OpenSign",
|
||||
"master-key": " Llave maestra",
|
||||
"profile-update-alert": "Perfil actualizado exitosamente.",
|
||||
"date": "Fecha",
|
||||
@@ -565,7 +600,7 @@
|
||||
"tour-mssg": {
|
||||
"home-layout-1": "¡Has iniciado sesión exitosamente! Echemos un vistazo.",
|
||||
"home-layout-2": "Para subir documentos para firmar tú mismo o para solicitar la firma de otros, simplemente selecciona los botones correspondientes.",
|
||||
"home-layout-3": "¡Ya puedes empezar a utilizar OpenSign! Si necesitas soporte siéntete libre de contactarnos.",
|
||||
"home-layout-3": "¡Ya puedes empezar a utilizar {{appName}}! Si necesitas soporte siéntete libre de contactarnos.",
|
||||
"generate-token": "Mejora ahora para generar un token API de producción.",
|
||||
"opensign-drive-1": "Haz clic en los enlaces para navegar fácilmente por la estructura jerárquica de carpetas y ver los documentos de cada carpeta.",
|
||||
"opensign-drive-2": "Haz clic en el botón «Agregar» para crear una nueva carpeta o documento.",
|
||||
@@ -576,18 +611,19 @@
|
||||
"opensign-drive-7": "Haz clic con el botón derecho en cualquier carpeta para ver las opciones. Elige «Renombrar» para cambiar el nombre de la carpeta o haz clic en ella para navegar por su contenido.",
|
||||
"pdf-request-file-1": "Lista de firmantes que todavía tienen que firmar este documento.",
|
||||
"pdf-request-file-2": "Haz clic en cualquiera de los marcadores de posición que aparecen en el documento para firmar. A continuación, verás opciones para dibujar tu firma, escribirla o subir una imagen .",
|
||||
"pdf-request-file-3": "Haz clic en los botones «Atrás», «Rechazar» o «Finalizar» para navegar por el documento. Utiliza el menú de tres puntos para ver otras opciones, como el botón «Descargar».",
|
||||
"pdf-request-file-3": "Haga clic en los botones Rechazar o Finalizar para navegar por el documento. Utilice el menú de puntos suspensivos para ver opciones adicionales, incluido el botón Descargar.",
|
||||
"pdf-request-file-4": "Lista de firmantes que ya han firmado el documento.",
|
||||
"pdf-request-file-5": "Puedes hacer clic en «Firmar todo automáticamente» para firmar de manera automática en todos los lugares que deban ser firmados por ti. Asegúrate de revisar correctamente el documento antes de hacer clic en ese botón.",
|
||||
"pdf-request-file-6": "Por favor, complete los campos en la página {{pagenumbers}}, todos resaltados con el mismo color para facilitar la identificación.",
|
||||
"placeholder-sign-1": "Selecciona un destinatario de esta lista para agregar un marcador de posición dónde este destinatario tendrá que firmar. El marcador de posición aparecerá con el mismo color que el nombre del destinatario una vez lo sueltes en el documento.",
|
||||
"placeholder-sign-2": "Hacer clic en el botón «Agregar destinatarios» te permitirá añadir más firmantes.",
|
||||
"placeholder-sign-3": "Haz clic en este botón para agregar más destinatarios/firmantes al documento.",
|
||||
"placeholder-sign-4": "Arrastra o haz clic en un capo para agregarlo al documento.",
|
||||
"placeholder-sign-5": "El área de contenido del PDF ya muestra los marcadores de posición existentes en la plantilla. Para tu comodidad, estos marcadores de posición coincidirán con el color del nombre del destinatario, haciéndolos fácilmente identificables.",
|
||||
"placeholder-sign-6": "Hacer clic en «Enviar» guardará el documento. En el siguiente paso podrás personalizar los correos que serán enviados a los destinatarios o copiar los enlaces para firmar y compartirlos con los destinatarios por tu cuenta.",
|
||||
"placeholder-sign-6": "Al hacer clic en 'Siguiente' se guardará el documento. En el siguiente paso, puede personalizar los correos electrónicos que se enviarán a los destinatarios o copiar los enlaces de firma y compartirlos usted mismo con los destinatarios.",
|
||||
"report-1": "Haz clic en el botón «Agregar» para crear una nueva plantilla. Las plantillas son documentos reusables diseñados para generar rápidamente nuevos documentos con la misma estructura pero diferentes firmantes. Por ejemplo, una plantilla de RR.HH. para el proceso de incorporación podría tener roles predefinidos como «Director de RR.HH.» y «Nuevo empleado». Cada vez que utilices la plantilla, puedes asignar el rol de «Nuevo empleado» a los distintos miembros del personal que se incorporen, mientras que el rol de « Director de RR.HH.» permanece constante, lo que facilita un proceso de incorporación fluido para cada persona contratada.",
|
||||
"redirect": "Haz clic en el botón «Usar» para crear un nuevo documento desde una plantilla existente.",
|
||||
"bulksend": "Para crear y enviar rápidamente múltiples documentos usando una plantilla existente, haz clic en el botón «Envío rápido».",
|
||||
"bulksend": "Para enviar rápidamente múltiples documentos utilizando una plantilla existente creando simplemente las direcciones de correo electrónico de los destinatarios, haz clic en el botón 'Envío Masivo'.",
|
||||
"option": "Este menú revela más opciones como «Editar» y «Eliminar». Usa el botón «Editar» para agregar roles, modificar campos y actualizar tu plantilla. Los cambios aplicarán para todos los documentos futuros que sean creados usando esta plantilla pero no afectarán a los documentos ya existentes. Con el botón «Eliminar» puedes eliminar la plantilla.",
|
||||
"signyour-self-1": "Para personalizar el documento antes de que sea firmado, selecciona y arrastra al documento los widgets que prefieras. Escoge el espacio perfecto para cada modificación para adaptar el documento a tus necesidades.",
|
||||
"signyour-self-2": "Arrastra y suelta en cualquier lugar de esta área. Después podrás cambiar el tamaño y moverlo.",
|
||||
@@ -602,7 +638,8 @@
|
||||
"Recent signature requests": "Esta es una lista de documentos que esperan por tu firma.",
|
||||
"Recently sent for signatures": "Esta es una lista de documentos que has enviado a otras partes para que sea firmado.",
|
||||
"Drafts": "Estos son documentos que has empezado pero que todavía no finalizas para su envío.",
|
||||
"public-template": "Este video muestra cómo puedes configurar tu perfil público personalizado, como «https://opensign.me/tu-nombre-de-usuario». También aprenderás cómo personalizar tu eslogan y cómo hacer tus plantillas disponibles para su firmado público."
|
||||
"public-template": "Este video muestra cómo puedes configurar tu perfil público personalizado, como «https://opensign.me/tu-nombre-de-usuario». También aprenderás cómo personalizar tu eslogan y cómo hacer tus plantillas disponibles para su firmado público.",
|
||||
"allowModify-widgets": "Puede arrastrar y soltar cualquiera de estos campos en el documento, además de los campos ya designados para usted por el creador del documento"
|
||||
},
|
||||
"enter-email-plaholder": "Agrega una dirección de correo y presiona «Enter»",
|
||||
"success-email-alert": "¡Correo enviado exitosamente!",
|
||||
@@ -643,9 +680,11 @@
|
||||
"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.",
|
||||
"here": "aquí",
|
||||
"copy-code": "COPIAR",
|
||||
"copied-code": "COPIADO",
|
||||
"Installation": "Instalación",
|
||||
@@ -653,11 +692,11 @@
|
||||
"insufficient-credits": "Créditos de firma insuficientes",
|
||||
"insufficient-credits-mssg": "El propietario de este documento actualmente carece de los créditos OpenSign necesarios para que puedas firmarlo. Por favor, contacta al propietario si necesitas más detalles.",
|
||||
"angular-npm-mssg-1": "Para integrar OpenSign a tu proyecto Angular, simplemente ejecuta los siguientes comandos:",
|
||||
"quota-mail-info-head": "Límite mensual de solicitudes de firma por correo",
|
||||
"quota-mail-info": "Puedes enviar hasta 15 solicitudes de firma por correo cada mes. Mejora ahora para enviar solicitudes de firma sin límite.",
|
||||
"quota-mail-info-head": "Límite mensual de correos electrónicos",
|
||||
"quota-mail-info": "Para mantener la calidad del servicio y prevenir el spam, OpenSign permite hasta 15 correos electrónicos por mes en el plan gratuito. Mejora tu plan ahora para envíos ilimitados de correos electrónicos.",
|
||||
"quota-mail-reset": "Los créditos de tu correo electrónico de solicitud de firma se restablecerán el",
|
||||
"quota-mail": "Has alcanzado tu límite de 15 solicitudes de firma por correo para este mes. Mejora ahora para continuar enviando correos.",
|
||||
"quota-mail-tip": "Consejo: Todavía puedes solciitar la firma de <1>documentos ilimitados</1> compartiendo manualmente los enlaces de solicitud de firma.",
|
||||
"quota-mail-tip": "Consejo: Aún puedes firmar <1>documentos ilimitados</1> compartiendo manualmente el enlace de solicitud de firma.",
|
||||
"quota-mail-head": "Cuota alcanzada",
|
||||
"unauthorized-modal": "No tienes permisos para realizar esta acción, por favor, contacta a {{adminEmail}}.",
|
||||
"sent-this-month": "Enviados este mes",
|
||||
@@ -676,25 +715,25 @@
|
||||
"download-pdf": "Descargar PDF",
|
||||
"pdf-certificate": "Descargar PDF + Certificado",
|
||||
"document-logs": "Registros de documentos",
|
||||
"server-down": "No se pudo conectar al servidor de OpenSign. Si estás alojando OpenSign por tu cuenta, por favor, asegúrate de que todos los pasos de la documentación han sido seguidos correctamente. Si estás ejecutando OpenSign localmente, puedes estar accediendo a OpenSign por un número de puerto incorrecto.",
|
||||
"server-down": "No se pudo conectar al servidor de {{appName}}. Si estás alojando {{appName}} por tu cuenta, por favor, asegúrate de que todos los pasos de la documentación han sido seguidos correctamente. Si estás ejecutando {{appName}} localmente, puedes estar accediendo a {{appName}} por un número de puerto incorrecto.",
|
||||
"admin-exists": "El administrador ya existe. Por favor, inicia sesión en la aplicación usando las credenciales de administrador para poder administrar los usuarios.",
|
||||
"public-tour-message": "Por favor, convierte la plantilla en pública para copiar la URL pública",
|
||||
"add-user-template": "Es necesario agregar un rol antes de poder agregarle campos.",
|
||||
"pdf-uncompatible": "Este pdf no es compatible con opensign, comuníquese con <support@opensignlabs.com>",
|
||||
"pdf-uncompatible": "Este PDF no es compatible, por favor contacta con {{appName}}",
|
||||
"enable-tour": "Habilitar recorrido",
|
||||
"istourenabled-help":{
|
||||
"istourenabled-help": {
|
||||
"p1": "Esto permitirá realizar una visita guiada para los firmantes, brindando instrucciones durante el proceso de firma.",
|
||||
"p2": "Esto desactivará la visita guiada, lo que garantizará una experiencia de firma más rápida e ininterrumpida.",
|
||||
"p3": "Consejo: si sus firmantes están familiarizados con OpenSign y lo utilizan con frecuencia, es posible que desee desactivar la visita guiada para disfrutar de una experiencia más fluida. Elija la opción que mejor se adapte a sus necesidades de firma de documentos"
|
||||
},
|
||||
"p3": "Consejo: si sus firmantes están familiarizados con {{appName}} y lo utilizan con frecuencia, es posible que desee desactivar la visita guiada para disfrutar de una experiencia más fluida. Elija la opción que mejor se adapte a sus necesidades de firma de documentos"
|
||||
},
|
||||
"form-title-1": "Configuración del flujo de documentos",
|
||||
"form-title-2": "Configuración de seguridad",
|
||||
"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'",
|
||||
"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.",
|
||||
"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'",
|
||||
"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.",
|
||||
"expect-default-one-signature-type": "Habilite un tipo de firma adicional aparte del predeterminado.",
|
||||
"add-signer-note": "Nota - Este contacto se agregará a su lista de contactos",
|
||||
"add-signer-note": "Nota - Este contacto se guardará en sus Contactos",
|
||||
"allowed-signature-types-help": {
|
||||
"p1": "Esta preferencia de firma controla qué opciones de firma están disponibles para sus firmantes. Solo los tipos de firma que seleccione aparecerán en el widget de firma cuando se firme un documento. Por ejemplo, si elige sólo la opción 'Draw' en las preferencias, su firmante verá sólo la opción 'Draw' en el widget de firma, mientras que las otras tres opciones no estarán disponibles.",
|
||||
"l1": "Cree una firma manuscrita directamente en la pantalla usando su dedo o un lápiz.",
|
||||
@@ -705,13 +744,294 @@
|
||||
"notify-on-signatures": "Notificar sobre firmas",
|
||||
"notify-on-signatures-help": {
|
||||
"p1": "El creador del documento recibirá una notificación por correo electrónico cada vez que un firmante firme el documento.",
|
||||
"note": "Nota: No se enviará un correo electrónico de notificación al último firmante, ya que el creador del documento recibirá un correo electrónico de finalización."
|
||||
"note": "Nota: Independientemente de esta configuración, siempre se envía un correo electrónico de finalización con el documento firmado y el certificado de finalización adjunto a todos los firmantes y al propietario del documento."
|
||||
},
|
||||
"expiry-date": "Date d'expiration",
|
||||
"expiry-date-updated": "La fecha de vencimiento se ha extendido con éxito hasta el {{newexpirydate}}",
|
||||
"expiry-date-error": "Proporcione una nueva fecha de vencimiento que sea posterior a la actual",
|
||||
"duplicate-template-alert": "¿Está seguro de que desea crear un duplicado de esta plantilla?",
|
||||
"duplicate-template-created": "Plantilla duplicada creada correctamente.",
|
||||
"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"
|
||||
"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)",
|
||||
"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.",
|
||||
"invalid-data": "Datos no válidos",
|
||||
"delete-folder-alert": "¿Está seguro de que desea eliminar este Carpeta?",
|
||||
"delete-folder-alert-1": "No se puede eliminar: la carpeta debe estar vacía",
|
||||
"defaultSign-alert": "Los widgets {{widgetsType}} no están disponibles para la firma automática",
|
||||
"embed-heading": "Incrustar plantillas de {{appName}}",
|
||||
"public-template-edit-title": "Editar plantilla pública",
|
||||
"public-template-edit-mssg": "No puede editar una plantilla pública directamente. Para realizar cambios, la plantilla se configurará como privada y luego tendrás que volver a hacerla pública. ¿Quieres continuar?",
|
||||
"agree-p1": "Confirmo que he leído y comprendido el ",
|
||||
"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",
|
||||
"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.",
|
||||
"term-cond-h1": "1. Objet et portée",
|
||||
"term-cond-p2": "Esta Divulgación rige el uso de registros y firmas electrónicas para los documentos enviados por el Remitente y firmados por Usted a través de la plataforma {{appName}}. Al aceptar, Usted reconoce que:",
|
||||
"term-cond-p3": "Recibirá y firmará documentos electrónicamente a través de {{appName}}.",
|
||||
"term-cond-p4": "Su firma electrónica es jurídicamente vinculante y equivalente a una firma manuscrita.",
|
||||
"term-cond-h2": "2. Consentimiento para el uso de registros y firmas electrónicas",
|
||||
"term-cond-p5": "Al aceptar esta Divulgación:",
|
||||
"term-cond-p6": "Usted acepta realizar transacciones electrónicas con el Remitente utilizando {{appName}} y comprende que este consentimiento es válido hasta que se retire.",
|
||||
"term-cond-p7": "Usted acepta revisar, firmar y devolver documentos electrónicamente utilizando {{appName}}.",
|
||||
"term-cond-p8": "Este consentimiento se aplica únicamente a los documentos específicos proporcionados por el Remitente.",
|
||||
"term-cond-h3": "3. Derecho a retirar el consentimiento",
|
||||
"term-cond-p9": "Tiene derecho a retirar su consentimiento para el uso de registros y firmas electrónicas mediante:",
|
||||
"term-cond-p10": "Contactar directamente con el Remitente utilizando la información de contacto proporcionada en su comunicación.",
|
||||
"term-cond-p11": "Notificar al remitente antes de completar el proceso de firma.",
|
||||
"term-cond-h4": "4. Requisitos de hardware y software",
|
||||
"term-cond-p12": "Para firmar documentos usando {{appName}}, debe tener:",
|
||||
"term-cond-p13": "Un dispositivo con acceso a Internet.",
|
||||
"term-cond-p14": "Un navegador web compatible con JavaScript habilitado.",
|
||||
"term-cond-p15": "Una cuenta de correo electrónico para recibir notificaciones y documentos firmados.",
|
||||
"term-cond-p16": "Si estos requisitos cambian, el Remitente u {{appName}} le notificarán las actualizaciones.",
|
||||
"term-cond-h5": "5. Conservación de registros y solicitud de copias en papel",
|
||||
"term-cond-p17": "Usted es responsable de garantizar que pueda acceder y conservar los documentos firmados electrónicamente. {{appName}} proporciona herramientas para descargar o imprimir el documento firmado inmediatamente después de que se complete el proceso de firma.",
|
||||
"term-cond-p18": "Si prefiere una copia en papel de cualquier documento firmado electrónicamente, puede solicitarla directamente al Remitente. El Remitente puede cobrar una tarifa razonable por proporcionar copias en papel, a menos que lo prohíba la ley aplicable.",
|
||||
"term-cond-h6": "6. Reconocimiento y Acuerdo",
|
||||
"term-cond-p19": "Al continuar, usted:",
|
||||
"term-cond-p20": "Confirme que puede acceder y revisar documentos electrónicos como se describe en esta Divulgación.",
|
||||
"term-cond-p21": "Acepta utilizar registros y firmas electrónicas para las transacciones especificadas con el Remitente.",
|
||||
"term-cond-p22": "Comprenda que {{appName}} es una plataforma que facilita la transacción y no es parte del acuerdo.",
|
||||
"term-cond-h7": "7. Efecto jurídico",
|
||||
"term-cond-p23": "Tu firma electrónica facilitada a través de {{appName}}:",
|
||||
"term-cond-p24": "Cumple con las leyes de firma electrónica aplicables, incluidas, entre otras, la Ley E-SIGN de los Estados Unidos, el Reglamento eIDAS de la UE y la Ley de tecnología de la información de la India",
|
||||
"term-cond-p25": "Es legalmente vinculante entre Usted y el Remitente para los documentos firmados",
|
||||
"term-cond-h8": "8. Platform Role and Limitation of Liability",
|
||||
"term-cond-p26": "{{appName}} sirve como plataforma para facilitar transacciones electrónicas. No es responsable del contenido, validez o aplicabilidad de los documentos enviados por el Remitente. Cualquier disputa o problema relacionado con el documento o su firma debe resolverse directamente entre Usted y el Remitente.",
|
||||
"term-cond-h9": "9. Terminación del Acuerdo",
|
||||
"term-cond-p27": "Su consentimiento se aplica únicamente a la transacción actual a menos que el Remitente especifique lo contrario. {{appName}} se reserva el derecho de cancelar el acceso a su plataforma por violaciones de sus Términos de Servicio o políticas.",
|
||||
"term-cond-p28": "Al seleccionar 'Acepto', usted confirma que comprende y acepta los términos de este Registro electrónico y Divulgación de firma y acepta firmar electrónicamente los documentos proporcionados por el Remitente a través de {{appName}}.",
|
||||
"term-cond-p29": "Si tiene preguntas sobre esta Divulgación, comuníquese directamente con el Remitente. Para obtener soporte técnico con {{appName}}, visite ",
|
||||
"term-cond-p30": " o correo electrónico",
|
||||
"js-snippet-msg": "Para integrar plantillas {{appName}} en sus sitios web HTML o páginas de destino, puede utilizar el siguiente código:",
|
||||
"js-snippet-msg-1": "Puede obtener el TemplateId en la página Administrar plantillas",
|
||||
"js-snippet-msg-2": "Administrar plantillas",
|
||||
"js-snippet-msg-3": "página.",
|
||||
"agrrement-alert": "Para continuar, debe consentir la divulgación de registros y firmas electrónicas.",
|
||||
"webhook-already-exists": "¡La URL ya existe! Pruebe con uno diferente.",
|
||||
"webhook-must-be-secure": "La URL del webhook debe ser segura y utilizar https://",
|
||||
"provide-test-token": "Por favor genere un token de prueba de API",
|
||||
"invalid-redirect-url": "Proporcione una URL de redireccionamiento válida.",
|
||||
"auto-sign-mssg": "firmar automáticamente todo",
|
||||
"already-exist-signer": "El firmante seleccionado ya está presente en la lista de destinatarios",
|
||||
"allow-modify": "Permitir modificaciones",
|
||||
"allow-modify-help": {
|
||||
"p1": "¿Desea permitir modificaciones al documento durante el proceso de firma?",
|
||||
"p2": "Seleccionar esta opción permite a los firmantes agregar elementos adicionales, como firmas, iniciales, sellos o texto, sobre los widgets existentes.",
|
||||
"p3": "Seleccionar esta opción restringe a los firmantes de realizar cualquier adición al documento durante el proceso de firma."
|
||||
},
|
||||
"signature-setting": "Configuración de firmas",
|
||||
"signyour-self-button": "Utilice este formulario para crear plantillas reutilizables para sus documentos.",
|
||||
"requestsign-button": "Utilice esta opción para solicitar firmas de otras personas y de usted mismo en conjunto.",
|
||||
"signyour-self-description": "Utilice este formulario para firmar el documento usted mismo sin agregar otros.",
|
||||
"requestsign-description": "Utilice este formulario para solicitar firmas de otras personas y de usted mismo en conjunto.",
|
||||
"template-form-description": "Utilice este formulario para crear plantillas reutilizables para sus documentos.",
|
||||
"change-signer": "Cambiar firmante",
|
||||
"activation-key": "Clave de activación",
|
||||
"paid-selfhost-help": "Por favor, póngase en contacto con {{appName}} para más información en ",
|
||||
"remaining-credits-selfhost-help": "Use créditos premium para firmar documentos mediante la API, enviar en masa e integrar {{appName}} en su sitio web. Tiene {{allowedcredits}} créditos incluidos y {{addoncredits}} créditos adicionales comprados restantes.",
|
||||
"signature-type": "Tu firma",
|
||||
"initial-type": "Tus iniciales",
|
||||
"redirect-url": "URL de redireccionamiento",
|
||||
"bulk-send": "envío masivo",
|
||||
"select-timezone": "Zona horaria",
|
||||
"current-time": "Hora actual",
|
||||
"email-help": "No se permite cambiar la dirección de correo electrónico por razones de seguridad. Por favor, cree otra cuenta gratuita utilizando la nueva dirección de correo electrónico.",
|
||||
"doc-sent": "Documento enviado con éxito.",
|
||||
"no-signer": "Eliminar",
|
||||
"and": "y",
|
||||
"make-public": "Hacer público",
|
||||
"general": "General",
|
||||
"request-email": "Correo de solicitud",
|
||||
"completion-email": "Correo de finalización",
|
||||
"variables-use": "Variables para usar",
|
||||
"send-to-sign": "le ha solicitado que firme",
|
||||
"use-template": "Usar plantilla",
|
||||
"contact-already-exists": "El contacto ya existe.",
|
||||
"docx-error": "Actualmente estamos experimentando problemas con el procesamiento de archivos DOCX. Por favor, sube el archivo PDF",
|
||||
"docx-error-contact": "o contáctanos en support@opensignlabs.com",
|
||||
"agreement-note": "Nota: Aceptar esto no significa que esté firmando el documento de inmediato. Esto solo le permite revisar el documento electrónicamente. Tendrá la oportunidad de leerlo en su totalidad y decidir si desea firmarlo después.",
|
||||
"draft-template-info-p1": "Para hacer que tu plantilla sea pública, debe contener un único rol o, si incluye múltiples roles, todos los roles adicionales deben estar ya asignados a firmantes. El rol público no asignado debe permanecer vacío y debe estar en la primera posición.",
|
||||
"visit-below-link": "Visita el siguiente enlace para saber más -",
|
||||
"storage-help": "Habilitar BYOC te permite conectar tu propio almacenamiento S3 para que tus archivos permanezcan completamente bajo tu control, sin copias externas retenidas. Si la autonomía de los datos es importante para ti, considera actualizar a Teams para desbloquear esta función.",
|
||||
"daily-quota-reached": "Ha alcanzado su cuota diaria. Para obtener ayuda, comuníquese con quotas@opensignlabs.com.",
|
||||
"enabled-signature-type": "Tipos de firma habilitados",
|
||||
"enabled-signature-type-help": "La configuración de 'Tipos de firma habilitados' determina qué opciones de firma están disponibles en su organización. Por ejemplo, si desactiva la opción 'Dibujar', los miembros de su organización no la verán en el widget de firma, mientras que las otras tres opciones seguirán siendo accesibles.",
|
||||
"indexing-public-profile": "Permitir la indexación del perfil público por los motores de búsqueda",
|
||||
"user-created-successfully": "Usuario creado con éxito.",
|
||||
"only-15-reminder-allowed": "Puede configurar hasta 15 recordatorios automáticos. Por ejemplo, si 'TimeToComplete' es de 15 días y 'RemindOnceInEvery' es de 1 día, alcanzará el límite máximo de 15 recordatorios. Ajuste su configuración en consecuencia.",
|
||||
"rate-your-experience": "¿Cómo fue su experiencia con {{appName}}?",
|
||||
"thanks-for-feedback": "Gracias por su comentario 🙏",
|
||||
"share-your-feedback": "Comparta sus comentarios",
|
||||
"share-your-review": "Comparta su reseña",
|
||||
"date-format": "Formato de fecha",
|
||||
"document-deleted": "El documento ha sido eliminado o no tiene acceso. Por favor, contacte al remitente.",
|
||||
"save-as-template-?": "¿Está seguro de que desea guardar este documento como plantilla?",
|
||||
"go-to-manage-templates": "Ir a 'Gestionar plantillas'",
|
||||
"template-created": "Plantilla creada",
|
||||
"how-would-you-like-to-proceed?": "¿Cómo le gustaría proceder?",
|
||||
"failed-to-load-refresh-page": "Error al cargar el documento. Intente actualizar esta página.",
|
||||
"document-has-been-signed": "¡El documento ha sido firmado con éxito!",
|
||||
"document-has-been-signed-by-you": "¡El documento ha sido firmado con éxito por usted!",
|
||||
"participant-completed-signing": "Todos los participantes han completado el proceso de firma.",
|
||||
"you-will-receive-email-shortly": "✅ ¡Eso es todo! Recibirá un correo de confirmación en breve.",
|
||||
"please-provide-templateid": "Por favor, proporcione templateid",
|
||||
"this-template-is-not-public": "Esta template no es pública",
|
||||
"invalid-templateid": "templateid no válida",
|
||||
"contact-billing-at-opensign": "Para agregar más asientos, comuníquese con OpenSign™ a <1>billing@opensignlabs.com</1> para obtener ayuda.",
|
||||
"title-length-alert": "El título debe tener como máximo 250 caracteres.",
|
||||
"note-length-alert": "La nota debe tener como máximo 200 caracteres.",
|
||||
"description-length-alert": "La descripción debe tener como máximo 500 caracteres.",
|
||||
"fix-&-resend-document": "Corregir y reenviar el documento",
|
||||
"do-you-want-recreate-document?": "Esto creará un borrador a partir de este documento con todos los campos intactos. ¿Está seguro de que desea recrear este documento?",
|
||||
"start-editing": "Comenzar a editar",
|
||||
"unsaved-changes-discard-them?": "Tienes cambios sin guardar. ¿Deseas descartarlos?",
|
||||
"yes-discard": "Sí, descartar",
|
||||
"LTV-enabled-signatures": "Firmas con LTV habilitado",
|
||||
"BETA": "BETA",
|
||||
"two-factor-authentication": "Autenticación de dos factores",
|
||||
"2fa-help-text": "La autenticación de dos factores agrega una capa adicional de seguridad a su cuenta al requerir más que solo una contraseña para iniciar sesión.",
|
||||
"2fa-help-bullet1": "Mejora la seguridad al requerir tanto su contraseña como un código de verificación.",
|
||||
"2fa-help-bullet2": "El código de verificación es generado por una aplicación de autenticación en su dispositivo.",
|
||||
"2fa-help-bullet3": "Protege su cuenta incluso si su contraseña se ve comprometida.",
|
||||
"setup-2fa": "Configurar 2FA",
|
||||
"setup-2fa-again": "Configurar 2FA nuevamente",
|
||||
"2fa-setup-intro": "Proteja su cuenta con la autenticación de dos factores. Cuando esté habilitada, deberá ingresar un código de su aplicación de autenticación cada vez que inicie sesión.",
|
||||
"scan-qr-code": "Escanear código QR",
|
||||
"scan-qr-instructions": "Use una aplicación de autenticación como Google Authenticator, Microsoft Authenticator o Authy para escanear este código QR.",
|
||||
"manual-setup-instructions": "¿No puede escanear el código? Puede configurar manualmente su aplicación de autenticación utilizando esta clave secreta:",
|
||||
"secret-key": "Clave secreta",
|
||||
"copied-to-clipboard": "Copiado al portapapeles",
|
||||
"copy-to-clipboard": "Copiar al portapapeles",
|
||||
"recovery-codes": "Códigos de recuperación",
|
||||
"recovery-codes-instructions": "Guarde estos códigos de recuperación en un lugar seguro. Si pierde el acceso a su aplicación de autenticación, puede usar uno de estos códigos de un solo uso para iniciar sesión.",
|
||||
"download-recovery-codes": "Descargar códigos de recuperación",
|
||||
"verification-code": "Código de verificación",
|
||||
"enter-code-from-authenticator-app": "Ingrese el código de 6 dígitos de su aplicación de autenticación",
|
||||
"verification-code-required": "Se requiere el código de verificación",
|
||||
"verification-code-invalid": "Código de verificación inválido. Por favor, inténtelo de nuevo.",
|
||||
"2fa-enabled": "2FA activado",
|
||||
"2fa-enabled-successfully": "Autenticación de dos factores habilitada con éxito",
|
||||
"2fa-setup-complete": "¡Configuración completa!",
|
||||
"2fa-setup-complete-instructions": "Su cuenta ahora está protegida con autenticación de dos factores. Deberá ingresar un código de verificación cada vez que inicie sesión.",
|
||||
"two-factor-verification": "Verificación de dos factores",
|
||||
"enter-verification-code-instructions": "Ingrese el código de verificación de 6 dígitos de su aplicación de autenticación para continuar.",
|
||||
"recovery-code": "Código de recuperación",
|
||||
"enter-recovery-code-help": "Ingrese uno de sus códigos de recuperación",
|
||||
"recovery-code-required": "Se requiere el código de recuperación",
|
||||
"use-verification-code-instead": "Usar código de verificación en su lugar",
|
||||
"use-recovery-code-instead": "Usar código de recuperación en su lugar",
|
||||
"regenerate-2fa-remove-existing": "¿Está seguro de que desea regenerar la autenticación de dos factores? Esta acción eliminará su configuración de autenticación actual.",
|
||||
"use-passkey": "Iniciar sesión con passkey",
|
||||
"security-section": "Seguridad",
|
||||
"passkey-authentication": "Autenticación con passkey",
|
||||
"passkey-not-supported": "Tu navegador o dispositivo no admite la autenticación con passkey",
|
||||
"passkey-description": "Las passkeys proporcionan una alternativa más segura y resistente al phishing que las contraseñas. Puedes usar tu huella digital, reconocimiento facial o PIN del dispositivo para iniciar sesión de forma segura.",
|
||||
"passkey-tooltip": "Las passkeys son una alternativa más simple y segura a las contraseñas. Usan datos biométricos como huellas digitales o reconocimiento facial ya almacenados en tu dispositivo.",
|
||||
"security-section-help": "Administra las opciones de seguridad, incluidas las passkeys y métodos de autenticación para proteger tu cuenta.",
|
||||
"passkey-register": "Registrar passkey",
|
||||
"passkey-register-another": "Registrar otra passkey",
|
||||
"passkey-registered": "Passkey registrada",
|
||||
"passkey-registering": "Registrando passkey...",
|
||||
"passkey-registered-success": "¡Passkey registrada con éxito!",
|
||||
"passkey-registration-failed": "Fallo al registrar la passkey",
|
||||
"passkey-auth-failed": "Falló la autenticación con passkey",
|
||||
"passkey-missing-user-info": "Se requiere información del usuario",
|
||||
"passkeys-list": "Tus passkeys",
|
||||
"default-passkey": "Tu passkey",
|
||||
"passkey-rename": "Renombrar",
|
||||
"passkey-delete": "Eliminar",
|
||||
"passkey-rename-title": "Renombrar passkey",
|
||||
"passkey-delete-title": "Eliminar passkey",
|
||||
"passkey-delete-confirm": "¿Estás seguro de que deseas eliminar la passkey \"{{name}}\"?",
|
||||
"passkey-name": "Nombre de la passkey",
|
||||
"passkey-name-placeholder": "Introduce un nombre descriptivo para esta passkey",
|
||||
"passkey-renamed-success": "Passkey renombrada con éxito",
|
||||
"passkey-deleted-success": "Passkey eliminada con éxito",
|
||||
"passkey-rename-failed": "No se pudo renombrar la passkey",
|
||||
"passkey-delete-failed": "No se pudo eliminar la passkey",
|
||||
"processing": "Procesando...",
|
||||
"today": "Hoy",
|
||||
"yesterday": "Ayer",
|
||||
"days-ago": "Hace {{days}} días",
|
||||
"verify-with-passkey": "Verificar con passkey",
|
||||
"verify-with-otp": "Verificar con OTP",
|
||||
"verify-identity": "Verificar identidad",
|
||||
"verify-account": "Verifique su identidad",
|
||||
"verification": "Verificación",
|
||||
"passkey-verification-failed": "La verificación con passkey ha fallado",
|
||||
"security-auth-help": {
|
||||
"p1": "Administre la configuración de seguridad de su cuenta para mantener sus datos seguros. OpenSign admite métodos de autenticación avanzados para mejorar la protección de la cuenta.",
|
||||
"2fa-auth-help": "Agregue una capa adicional de seguridad activando 2FA. Esto requiere ingresar un código de verificación desde una aplicación autenticadora después de su contraseña.",
|
||||
"passkey-auth-help": "Use claves de acceso para iniciar sesión sin contraseña con verificación biométrica o basada en el dispositivo, proporcionando una seguridad sólida y comodidad."
|
||||
},
|
||||
"signer-already-present": "Firmante ya presente",
|
||||
"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",
|
||||
"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…"
|
||||
}
|
||||
|
||||
@@ -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)",
|
||||
@@ -39,10 +41,12 @@
|
||||
"save": "Sauvegarder",
|
||||
"cancel": "Annuler",
|
||||
"upgrade-now": "Mettre à jour maintenant",
|
||||
"contact-now": "Contacter maintenant",
|
||||
"upgrade-to": "Mettre à niveau vers",
|
||||
"pro": "PRO",
|
||||
"plan": "Offre",
|
||||
"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 !",
|
||||
"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",
|
||||
"tour-content": "Ne plus afficher",
|
||||
@@ -51,7 +55,6 @@
|
||||
"session-expired": "Votre session a expiré.",
|
||||
"user": "Utilisateur",
|
||||
"public-profile-help": "Il s'agit de votre URL publique. Copiez-le ou partagez-le avec le signataire et vous pourrez voir tous vos modèles définis publiquement.",
|
||||
"tagline-help": "Ce surnom apparaîtra sur votre profil public OpenSign par exemple. https://opensign.me/alex",
|
||||
"welcome": "Content de te revoir!",
|
||||
"Login-to-your-account": "Se connecter à son compte",
|
||||
"password": "Mot de passe",
|
||||
@@ -74,21 +77,34 @@
|
||||
"Sign yourself": "signez vous-même",
|
||||
"Request signatures": "Demander des signatures",
|
||||
"Templates": "Modèles",
|
||||
"Analytics": "Analytique",
|
||||
"Branding": "Image de marque",
|
||||
"Mail": "Courrier",
|
||||
"Storage": "Stockage",
|
||||
"Signing certificate": "Certificat de signature",
|
||||
"Teams": "Équipes",
|
||||
"General": "Général",
|
||||
"Teams-Children": {
|
||||
"Organizations": "Organisations",
|
||||
"OrgAdmins": "OrgAdmins"
|
||||
},
|
||||
"Templates-Children": {
|
||||
"Create template": "Créer un modèle",
|
||||
"Manage templates": "Gérer les modèles"
|
||||
"Manage templates": "Gérer les modèles",
|
||||
"Embedding": "Intégration"
|
||||
},
|
||||
"OpenSign™ Drive": "Lecteur OpenSign™",
|
||||
"OpenSign™ Drive": "Lecteur {{appName}}",
|
||||
"Reports": "Rapports",
|
||||
"Reports-Children": {
|
||||
"Documents": "Documents",
|
||||
"Documents-Children": {
|
||||
"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"
|
||||
"Expired": "Expiré"
|
||||
},
|
||||
"Contactbook": "Carnet de contacts",
|
||||
"Settings": "Paramètres",
|
||||
"Settings-Children": {
|
||||
"My Signature": "Ma signature",
|
||||
@@ -144,19 +160,27 @@
|
||||
"btnLabel": {
|
||||
"sign": "Signer",
|
||||
"Resend": "Relancer",
|
||||
"Rename": "Renommer",
|
||||
"Revoke": "Révoquer",
|
||||
"Delete": "Supprimer",
|
||||
"Use": "Utiliser",
|
||||
"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",
|
||||
"Embed": "Intégrer",
|
||||
"Copy TemplateId": "Copier l'ID du modèle",
|
||||
"Copy Public URL":"Copier l'URL publique",
|
||||
"extend-expiry-date": "Prolonger la date d'expiration"
|
||||
"Copy Public URL": "Copier l'URL publique",
|
||||
"extend-expiry-date": "Prolonger la date d'expiration",
|
||||
"Duplicate Template": "dupliquer le modèle",
|
||||
"Duplicate": "Double",
|
||||
"daily-mail-quota": "Quota d'e-mails quotidien",
|
||||
"Save as template": "Enregistrer comme modèle",
|
||||
"Fix & resend": "Corriger et renvoyer",
|
||||
"Kiosk Mode": "Mode Kiosque"
|
||||
},
|
||||
"report-help": {
|
||||
"Draft Documents": "Il s'agit de documents que vous avez commencés mais que vous n'avez pas finalisés pour envoi.",
|
||||
@@ -184,7 +208,7 @@
|
||||
"send-in-order-help": {
|
||||
"p1": "Choisissez la manière dont vous souhaitez que les demandes de signature soient envoyées aux signataires du document :",
|
||||
"p2": "La sélection de cette option enverra initialement la demande de signature au premier signataire. Une fois que le premier signataire a terminé sa partie, le prochain signataire de la séquence recevra la demande. Ce processus se poursuit jusqu'à ce que tous les signataires aient signé le document. Cette méthode garantit que le document est signé dans un ordre spécifique.",
|
||||
"p3": "La sélection de cette option enverra les liens de signature à tous les signataires simultanément. Chaque signataire peut signer le document à sa convenance, que d'autres signataires aient ou non complété leur signature. Cette méthode est plus rapide mais n’impose aucun ordre de signature entre les participants.",
|
||||
"p3": "La sélection de cette option enverra les liens de signature à tous les signataires simultanément. Chaque signataire peut signer le document à sa convenance, que d'autres signataires aient ou non complété leur signature. Cette méthode est plus rapide mais n'impose aucun ordre de signature entre les participants.",
|
||||
"p4": "Sélectionnez l'option qui correspond le mieux aux besoins de votre traitement de documents."
|
||||
},
|
||||
"no": "Non",
|
||||
@@ -192,7 +216,7 @@
|
||||
"remind-once": "Rappeler une fois tous les (jours)",
|
||||
"next": "Suivant",
|
||||
"select-folder": "Sélectionner le dossier",
|
||||
"OpenSign-drive": "Lecteur OpenSign™",
|
||||
"OpenSign-drive": "Lecteur {{appName}}",
|
||||
"drive-document-status": {
|
||||
"Completed": "Complété",
|
||||
"Declined": "Refuser",
|
||||
@@ -200,7 +224,7 @@
|
||||
"Expired": "Expiré",
|
||||
"In Progress": "En cours"
|
||||
},
|
||||
"select-folder-help": "Si vous ne sélectionnez pas de dossier, votre document signé sera enregistré dans le dossier du lecteur principal OpenSign.",
|
||||
"select-folder-help": "Si vous ne sélectionnez pas de dossier, votre document signé sera enregistré dans le dossier du lecteur principal {{appName}}.",
|
||||
"no-data": "Aucune données disponible",
|
||||
"save-here": "Enregistrer ici",
|
||||
"back": "Retour",
|
||||
@@ -210,12 +234,14 @@
|
||||
"create": "Créer",
|
||||
"signers": "Signataires",
|
||||
"signers-help": "Commencez à saisir le nom d'un contact pour voir les signataires suggérés par vos contacts enregistrés ou en ajouter de nouveaux. Organisez l'ordre de signature en ajoutant des signataires dans l'ordre souhaité. Utilisez le bouton « + » pour inclure les signataires et le « x » pour les supprimer. Chaque signataire recevra un e-mail invité à signer le document dans l'ordre indiqué.",
|
||||
"bcc-help": "Commencez à taper le nom d'un contact pour voir les suggestions parmi vos contacts enregistrés ou en ajouter de nouveaux. Utilisez le bouton '+' pour ajouter un utilisateur et le bouton 'x' pour le supprimer. L'adresse e-mail de l'utilisateur sélectionné sera ajoutée en Bcc (copie carbone invisible). Chaque utilisateur recevra une notification par e-mail une fois le document terminé.",
|
||||
"add-signer": "Ajouter un signataire",
|
||||
"contact-not-found": "Contact introuvable",
|
||||
"add-yourself": "Ajoutez-vous",
|
||||
"submit": "Soumettre",
|
||||
"reset": "Réinitialiser",
|
||||
"my-signature": "Ma signature",
|
||||
"initial-alert": "Mon initiale introuvable",
|
||||
"signature": "Signature",
|
||||
"upload-image": "Télécharger une image",
|
||||
"clear": "Vider",
|
||||
@@ -224,6 +250,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?",
|
||||
@@ -260,18 +287,19 @@
|
||||
"user-deactivated": "Utilisateur désactivé.",
|
||||
"user-activated": "Activé par l'utilisateur.",
|
||||
"public": "Public",
|
||||
"download": "Téléchargement",
|
||||
"download": "Télécharger",
|
||||
"view": "voir",
|
||||
"make-template-public": "Rendre le modèle public",
|
||||
"make-template-private": "Rendre le modèle privé",
|
||||
"make-template-public-alert": "Êtes-vous sûr de vouloir rendre ce modèle public ?",
|
||||
"make-template-private-alert-non": "Êtes-vous sûr de vouloir rendre ce modèle privé ?",
|
||||
"make-template-private-alert": "Êtes-vous sûr de vouloir rendre ce modèle privé ? Cela le supprimera de votre profil public.",
|
||||
"public-role": "Rôle public",
|
||||
"public-url": "Profil public",
|
||||
"embed-template": "Intégrer le modèle",
|
||||
"public-url-copy": "Voici votre URL publique : ",
|
||||
"public-url-copy-mssg": "copiez-la ou partagez-le avec le signataire et vous pouvoir voir tous vos paramètres publics modèles.",
|
||||
"add-public-url-alert": "Veuillez terminer la configuration de votre profil public OpenSign. Une fois cela fait, vous pourrez rendre les modèles publics. Ce processus ne doit être effectué qu’une seule fois.",
|
||||
"add-public-url-alert": "Vous pouvez générer votre profil public {{appName}}",
|
||||
"share-with-alert": "Vous ne pouvez pas partager un modèle si des contacts sont déjà attribués à des rôles. Veuillez supprimer toutes les attributions de contacts des rôles avant de partager le modèle.",
|
||||
"share-with": "Partager avec",
|
||||
"team-not-found": "Équipe introuvable",
|
||||
@@ -283,17 +311,18 @@
|
||||
"send": "envoyer",
|
||||
"quick-send-alert-1": "Tous les rôles dans ce document sont actuellement liés à des contacts. Pour envoyer rapidement des copies de ce modèle à plusieurs signataires, veuillez vous assurer qu'au moins un rôle n'est lié à aucun contact.",
|
||||
"quick-send-alert-2": "Veuillez vous assurer qu'au moins un widget de signature est ajouté pour tous les destinataires.",
|
||||
"quick-send-alert-3": "Veuillez ajouter au moins un rôle à ce modèle afin d'en « envoyer rapidement » des copies à plusieurs signataires.",
|
||||
"quick-send-alert-3": "Veuillez ajouter au moins un rôle à ce template.",
|
||||
"quick-send-alert-4": "L'envoi rapide a atteint la limite.",
|
||||
"copy-link": "Copier le lien",
|
||||
"copy": "Copier",
|
||||
"revoke-document": "Révoquer le document",
|
||||
"revoke-document-alert": "Êtes-vous sûr de vouloir révoquer ce document ?",
|
||||
"resend-mail": "Renvoyer le courrier",
|
||||
"resend-mail-help": "Vous pouvez utiliser les variables suivantes qui seront remplacées par leurs valeurs réelles : - {{document_title}}, {{sender_name}}, {{sender_mail}}, {{sender_phone}}, {{receiver_name}}, {{receiver_email} }, {{receiver_phone}}, {{expiry_date}}, {{company_name}}, {{signing_url}}.",
|
||||
"resend-mail-help": "Vous pouvez utiliser les variables suivantes qui seront remplacées par leurs valeurs réelles : - {{document_title}}, {{sender_name}}, {{sender_mail}}, {{sender_phone}}, {{receiver_name}}, {{receiver_email} }, {{receiver_phone}}, {{expiry_date}}, {{company_name}}, {{signing_url}}, {{note}}.",
|
||||
"subject": "Sujet",
|
||||
"body": "Corps",
|
||||
"add-contact": "Ajouter le contact",
|
||||
"edit-contact": "Modifier le contact",
|
||||
"add-signer-alert": "Le contact existe déjà ! Veuillez le sélectionner dans la liste déroulante 'Signataires'",
|
||||
"record-delete-alert": "Enregistrement supprimé avec succès !",
|
||||
"record-revoke-alert": " Enregistrement révoqué avec succès ! ",
|
||||
@@ -319,7 +348,7 @@
|
||||
"verify-email-1": "Vérifier l'e-mail",
|
||||
"resend": "Renvoyer",
|
||||
"contact-details": "Coordonnées",
|
||||
"verify-email": "Veuillez vérifier votre email!",
|
||||
"verify-email": "Veuillez vérifier votre e-mail pour continuer. C'est une étape unique pour confirmer l'adresse e-mail associée à votre compte OpenSign.",
|
||||
"send-otp": "envoyer un code à usage unique",
|
||||
"otp-placeholder": "Entrez le code de vérification reçu par e-mail",
|
||||
"loading-doc": "Chargement du document..",
|
||||
@@ -333,6 +362,7 @@
|
||||
"date": "date",
|
||||
"text": "texte",
|
||||
"text input": "saisie de texte",
|
||||
"cells": "cellules",
|
||||
"checkbox": "case à cocher",
|
||||
"dropdown": "dérouler",
|
||||
"radio button": "bouton radio",
|
||||
@@ -387,10 +417,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",
|
||||
@@ -403,10 +435,9 @@
|
||||
"encrypted-pdf-alert": "Les fichiers PDF actuellement cryptés ne sont pas pris en charge.",
|
||||
"encrypted-pdf-alert-1": "Le mot de passe du fichier PFX n'est pas valide.",
|
||||
"user-not-exist": "L'utilisateur n'existe pas",
|
||||
"template-signature-field": "Veuillez ajouter au moins un champ de signature pour tous les rôles.",
|
||||
"template-role-alert": "Veuillez d'abord ajouter des rôles",
|
||||
"create-document": "Créer un document",
|
||||
"template-created-alert": "Voulez-vous créer un document en utilisant le modèle que vous vient de créer ?",
|
||||
"template-created-alert": "Que voulez-vous faire avec le modèle que vous venez de créer ?",
|
||||
"signers-alert": "alerte des signataires",
|
||||
"template-creation-alert-1": "Veuillez sélectionner le signataire pour ajouter un espace réservé !",
|
||||
"ok": "D'accord",
|
||||
@@ -434,10 +465,13 @@
|
||||
"add-recipients": "Ajouter des destinataires",
|
||||
"loading-mssg": "Cela pourrait prendre du temps",
|
||||
"send-mail": "Envoyer un mail",
|
||||
"signature-field-widget": "Au moins un champ de signature doit être ajouté pour chaque utilisateur. Vous n'avez pas ajouté de champs de signature pour {{signersName}}",
|
||||
"placeholder-alert-1": "Veuillez vous assurer qu'au moins un widget de signature est ajouté pour tous les destinataires.",
|
||||
"placeholder-alert-2": "Veuillez confirmer que vous avez rempli le champ de texte.",
|
||||
"placeholder-alert-3": "Etes-vous sûr de vouloir envoyer ce document pour signature ? ",
|
||||
"placeholder-alert-4": "Vous avez envoyé avec succès des mails à tous les",
|
||||
"placeholder-mail-alert": "Vous avez envoyé un e-mail avec succès à {{name}}. Les signataires suivants recevront un e-mail une fois que {{name}} aura signé le document.",
|
||||
"placeholder-mail-alert-you": "Les signataires suivants recevront un e-mail dès que vous signez le document.",
|
||||
"placeholder-alert-5": "Voulez-vous signer des documents maintenant ?",
|
||||
"placeholder-alert-6": "Veuillez configurer l'adaptateur de messagerie pour envoyer du courrier !",
|
||||
"placeholder-alert-7": "Veuillez sélectionner le signataire pour ajouter un espace réservé !",
|
||||
@@ -452,6 +486,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é",
|
||||
@@ -468,12 +503,13 @@
|
||||
"sign-up": "S'inscrire",
|
||||
"folder-already-exist!": "le dossier existe déjà !",
|
||||
"please-select-position!": "Veuillez sélectionner un poste !",
|
||||
"default-sign-alert": "Êtes-vous sûr de vouloir vous connecter automatiquement à tous les emplacements demandés ? ",
|
||||
"default-sign-alert": "Êtes-vous sûr de vouloir vous connecter automatiquement à tous les widgets {{widgetsType}} ?",
|
||||
"Your-Signature": "Votre signature",
|
||||
"auto-sign-all": "Tout signer automatiquement",
|
||||
"generating-certificate": "Génération du certificat",
|
||||
"pdf-download": "Téléchargement PDF",
|
||||
"pdf-download": "Télécharger PDF",
|
||||
"generate-certificate-alert": " Votre certificat d'achèvement est en cours de génération. Veuillez patienter quelques instants. Si le téléchargement ne démarre pas sous peu, cliquez à nouveau sur le bouton. ",
|
||||
"generate-certificate-err": "Nous rencontrons actuellement un problème lors de la génération du certificat. Veuillez réessayer plus tard.",
|
||||
"folder-already-exist": "Le dossier existe déjà !",
|
||||
"folder-created-successfully": "Dossier créé avec succès !",
|
||||
"fill-folder-name": "Veuillez indiquer le nom du dossier",
|
||||
@@ -483,7 +519,7 @@
|
||||
"role-not-found": "Rôle introuvable.",
|
||||
"do-not-access": "Vous n'y avez pas accès, veuillez contacter l'administrateur.",
|
||||
"add-admin": "Ajouter un administrateur",
|
||||
"opensign-setup": "Configuration d'OpenSign",
|
||||
"opensign-setup": "Configuration {{appName}}",
|
||||
"join-discord": "Rejoignez notre serveur Discord",
|
||||
"admin-already-exist": "L'administrateur existe déjà.",
|
||||
"password-update-alert-1": "Mot de passe mis à jour avec succès.",
|
||||
@@ -500,7 +536,7 @@
|
||||
"correct-password": "Veuillez fournir un mot de passe correct",
|
||||
"decrypting-pdf": "Décryptage du pdf, veuillez patienter...",
|
||||
"invalid-otp": "OTP invalide",
|
||||
"user-not-found": "Utilisateur non trouvé!",
|
||||
"user-not-found": "Utilisateur non trouvé",
|
||||
"enter-otp-alert": "Veuillez saisir OTP !",
|
||||
"get-verification-code": "Obtenir le code de vérification",
|
||||
"get-verification-code-2": "Vous recevrez un code de vérification par e-mail",
|
||||
@@ -509,6 +545,7 @@
|
||||
"provide-email": "Veuillez fournir un e-mail.",
|
||||
"additional-info": "Information additionnelle",
|
||||
"signature-saved-alert": "Signature enregistrée avec succès.",
|
||||
"saved-successfully": "Enregistré avec succès.",
|
||||
"fill-field": "Veuillez remplir ce champ",
|
||||
"error-template": "Erreur : modèle introuvable ! ",
|
||||
"error": "Erreur",
|
||||
@@ -539,7 +576,6 @@
|
||||
"filed-required-correctly": "Veuillez remplir correctement les informations requises.",
|
||||
"admin-created": "Administrateur créé",
|
||||
"invalid-masterkey": "Clé principale invalide",
|
||||
"opensign-Setup": "Configuration d'OpenSign",
|
||||
"master-key": "La clef maitresse",
|
||||
"profile-update-alert": "Mise à jour du profil réussie.",
|
||||
"date": "Date",
|
||||
@@ -564,7 +600,7 @@
|
||||
"tour-mssg": {
|
||||
"home-layout-1": "Vous vous êtes connecté avec succès ! Jetons un coup d'oeil.",
|
||||
"home-layout-2": "Pour télécharger des documents à auto-signature ou pour demander la signature d'autres personnes, sélectionnez simplement les boutons correspondants.",
|
||||
"home-layout-3": "Vous êtes prêt à commencer à utiliser OpenSign ! Si vous avez besoin d'aide, n'hésitez pas à nous contacter.",
|
||||
"home-layout-3": "Vous êtes prêt à commencer à utiliser {{appName}} ! Si vous avez besoin d'aide, n'hésitez pas à nous contacter.",
|
||||
"generate-token": "Effectuez la mise à niveau maintenant pour générer un jeton API de production.",
|
||||
"opensign-drive-1": "Cliquez sur les liens du fil d'Ariane pour naviguer facilement dans la hiérarchie des dossiers et afficher les documents dans chaque dossier.",
|
||||
"opensign-drive-2": "Cliquez sur le bouton Ajouter pour créer un nouveau dossier ou document.",
|
||||
@@ -575,18 +611,19 @@
|
||||
"opensign-drive-7": "Faites un clic droit sur n'importe quel dossier pour voir les options. Choisissez 'Renommer' pour changer le nom du dossier ou cliquez sur le dossier pour naviguer dans son contenu.",
|
||||
"pdf-request-file-1": "Liste des signataires qui doivent encore signer le document.",
|
||||
"pdf-request-file-2": "Cliquez sur l'un des espaces réservés apparaissant sur le document pour signer. Vous verrez alors des options pour dessiner votre signature, la saisir ou télécharger une image.",
|
||||
"pdf-request-file-3": "Cliquez sur les boutons Précédent, Refuser ou Terminer pour parcourir votre document. Utilisez le menu points de suspension pour des options supplémentaires, notamment le bouton Télécharger.",
|
||||
"pdf-request-file-3": "Cliquez sur les boutons Refuser ou Terminer pour parcourir votre document. Utilisez le menu à points de suspension pour des options supplémentaires, notamment le bouton Télécharger.",
|
||||
"pdf-request-file-4": "Liste des signataires ayant déjà signé le document.",
|
||||
"pdf-request-file-5": "Vous pouvez cliquer sur 'Signer automatiquement tout' pour signer automatiquement à tous les emplacements que vous êtes censé signer. Assurez-vous de lire correctement le document avant de cliquer sur ce bouton.",
|
||||
"pdf-request-file-6": "Veuillez remplir les champs à la page {{pagenumbers}}, tous mis en évidence avec la même couleur pour une identification facile.",
|
||||
"placeholder-sign-1": "Sélectionnez un destinataire dans cette liste pour ajouter un espace réservé à l'endroit où il est censé signer. L'espace réservé apparaîtra de la même couleur que le nom du destinataire une fois que vous le déposerez sur le document.",
|
||||
"placeholder-sign-2": "Cliquer sur le bouton 'Ajouter des destinataires' vous permettra d'ajouter plus de signataires.",
|
||||
"placeholder-sign-3": "Cliquez sur ce bouton pour ajouter plus de destinataires/signataires au document",
|
||||
"placeholder-sign-4": "Faites glisser ou cliquez sur un champ pour l'ajouter au document.",
|
||||
"placeholder-sign-5": "La zone de contenu PDF affiche déjà les espaces réservés existants du modèle. Pour votre commodité, ces espaces réservés correspondront à la couleur du nom du destinataire, ce qui les rendra facilement identifiables.",
|
||||
"placeholder-sign-6": "Cliquer sur 'Envoyer' enregistrera le document. À l'étape suivante, vous pourrez personnaliser les e-mails à envoyer aux destinataires ou copier les liens de signature et les partager vous-même avec les destinataires.",
|
||||
"placeholder-sign-6": "En cliquant sur 'Suivant', le document sera enregistré. À l'étape suivante, vous pouvez personnaliser les e-mails à envoyer aux destinataires ou copier les liens de signature et les partager vous-même avec les destinataires.",
|
||||
"report-1": "Cliquez sur le bouton 'Ajouter' pour créer un nouveau modèle. Les modèles sont des documents réutilisables conçus pour générer rapidement de nouveaux documents avec la même structure et différents signataires. Par exemple, un modèle RH pour l'intégration peut avoir des rôles prédéfinis tels que 'Responsable RH' et ' Nouvel employé'. Chaque fois que vous utilisez le modèle, vous pouvez attribuer le rôle 'Nouvel employé' à différents membres du personnel entrants, tandis que le rôle 'Responsable RH' reste constant, facilitant ainsi un processus d'intégration fluide pour chaque recrue",
|
||||
"redirect": "Cliquez sur le bouton 'Utiliser' pour créer un nouveau document à partir d'un modèle existant.",
|
||||
"bulksend": "Pour créer et envoyer rapidement plusieurs documents à l'aide d'un modèle existant, cliquez sur le bouton 'Envoi rapide'.",
|
||||
"bulksend": "Pour envoyer rapidement plusieurs documents en utilisant un modèle existant en créant simplement les adresses e-mail des destinataires, cliquez sur le bouton 'Envoi en masse'.",
|
||||
"option": "Ce menu révèle plus d'options telles que Modifier et Supprimer. Utilisez le bouton « Modifier » pour ajouter des rôles de signataire, modifier les champs et mettre à jour votre modèle. Les modifications s'appliqueront à tous les futurs documents créés à partir de ce modèle mais n'affecteront pas les documents existants. Utilisez le bouton Supprimer, vous pouvez supprimer le modèle ",
|
||||
"signyour-self-1": "Sélectionnez et faites glisser vos widgets préférés sur le PDF pour personnaliser votre document avant de le signer. Choisissez les endroits parfaits pour chaque modification afin d'adapter le document à vos besoins.",
|
||||
"signyour-self-2": "Faites glisser et déposer n'importe où dans cette zone. Vous pourrez la redimensionner et la déplacer plus tard.",
|
||||
@@ -601,7 +638,8 @@
|
||||
"Recent signature requests": "Voici une liste de documents qui attendent votre signature.",
|
||||
"Recently sent for signatures": "Il s'agit d'une liste de documents que vous avez envoyés à d'autres parties pour signature.",
|
||||
"Drafts": "Il s'agit de documents que vous avez commencés mais que vous n'avez pas finalisés pour envoi.",
|
||||
"public-template": "Cette vidéo montre comment configurer votre profil public personnalisé, tel que 'https://opensign.me/your-username'. Vous apprendrez également comment personnaliser votre slogan et rendre vos modèles disponibles pour la signature publique."
|
||||
"public-template": "Cette vidéo montre comment configurer votre profil public personnalisé, tel que 'https://opensign.me/your-username'. Vous apprendrez également comment personnaliser votre slogan et rendre vos modèles disponibles pour la signature publique.",
|
||||
"allowModify-widgets": "Vous pouvez faire glisser et déposer n'importe lequel de ces champs sur le document, en plus des champs déjà désignés pour vous par le créateur du document."
|
||||
},
|
||||
"enter-email-plaholder": "Ajoutez une adresse email et appuyez sur Entrée",
|
||||
"success-email-alert": "E-mail envoyé avec succès!",
|
||||
@@ -621,47 +659,49 @@
|
||||
"Add-seats": "Ajouter des sièges",
|
||||
"format": "format",
|
||||
"select-date-format": "Sélectionnez un format de date",
|
||||
"quantity-of-credits":"Quantité de crédits de prime",
|
||||
"remaining-credits":"Crédits premium disponibles :",
|
||||
"additional-credits":"Veuillez acheter des crédits premium",
|
||||
"remaining-credits-help":"Utilisez des crédits premium pour la signature de documents API, l'envoi groupé ou l'intégration d'OpenSign sur votre site Web. Il vous reste {{allowedcredits}} crédits inclus et {{addoncredits}} crédits achetés supplémentaires.",
|
||||
"quantity-of-credits": "Quantité de crédits de prime",
|
||||
"remaining-credits": "Crédits premium disponibles :",
|
||||
"additional-credits": "Veuillez acheter des crédits premium",
|
||||
"remaining-credits-help": "Utilisez des crédits premium pour la signature de documents API, l'envoi groupé ou l'intégration d'OpenSign sur votre site Web. Il vous reste {{allowedcredits}} crédits inclus et {{addoncredits}} crédits achetés supplémentaires.",
|
||||
"quota-err-quicksend": "Quota atteint, vous n'avez pas assez de crédits.",
|
||||
"buy-credits": "Acheter des crédits premium",
|
||||
"rotate-right" :"Faire pivoter à droite",
|
||||
"rotate-left" :"Faire pivoter à gauche",
|
||||
"rotate-alert-mssg" :"Tous les widgets de cette page seront perdus. Êtes-vous sûr de vouloir continuer ?",
|
||||
"templateid":"ID de modèle",
|
||||
"bulk-send-subcription-alert":"Veuillez passer au forfait Professionnel ou Équipe pour utiliser Quicksend.",
|
||||
"rotate-right": "Faire pivoter à droite",
|
||||
"rotate-left": "Faire pivoter à gauche",
|
||||
"rotate-alert-mssg": "Tous les widgets de cette page seront perdus. Êtes-vous sûr de vouloir continuer ?",
|
||||
"templateid": "ID de modèle",
|
||||
"bulk-send-subcription-alert": "Veuillez passer au forfait Professionnel ou Équipe pour utiliser Quicksend.",
|
||||
"generate-test-token": "Générer jeton de test",
|
||||
"regenerate-test-token":"Régénérer le jeton de test",
|
||||
"help-test-token":"Ce jeton peut être utilisé pour tester les API au niveau du point de terminaison https://sandbox.opensignlabs.com/api/v1, vous permettant ainsi d'effectuer un nombre illimité de signatures de documents. Veuillez noter que l'API sandbox signera vos documents avec des certificats auto-signés, qui peuvent ne pas être reconnus comme valides par Adobe. Une fois vos tests terminés, vous pouvez passer à l’un de nos forfaits payants pour générer un jeton de production.",
|
||||
"help-api-token":"Ce jeton peut être utilisé pour accéder aux API de production au point de terminaison {{origin}}/api/v1. Il ne peut être généré que sur l'un de nos forfaits payants.",
|
||||
"reason" :"Raison",
|
||||
"decline-by" :"Refusé/révoqué par",
|
||||
"document-declined":"document refusé",
|
||||
"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-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.",
|
||||
"copy-code":"COPIE",
|
||||
"copied-code":"COPIÉ",
|
||||
"Installation":"Installation",
|
||||
"Usage" :"Usage",
|
||||
"insufficient-credits":"Crédits de signature insuffisants",
|
||||
"insufficient-credits-mssg" :"Le propriétaire de ce document ne dispose actuellement pas des crédits OpenSign nécessaires pour que vous puissiez le signer. Veuillez contacter le propriétaire si vous avez besoin de plus de détails.",
|
||||
"angular-npm-mssg-1" :"Pour intégrer OpenSign dans votre projet Angular, exécutez simplement la commande suivante :",
|
||||
"quota-mail-info-head":"Limite mensuelle d'e-mails de signatures de demandes",
|
||||
"quota-mail-info": "Vous pouvez envoyer jusqu'à 15 e-mails de demande de signature chaque mois. Mettez à niveau maintenant pour envoyer directement des demandes de signature illimitées.",
|
||||
"regenerate-test-token": "Régénérer le jeton de test",
|
||||
"help-test-token": "Ce jeton peut être utilisé pour tester les API au niveau du point de terminaison https://sandbox.opensignlabs.com/api/v1, vous permettant ainsi d'effectuer un nombre illimité de signatures de documents. Veuillez noter que l'API sandbox signera vos documents avec des certificats auto-signés, qui peuvent ne pas être reconnus comme valides par Adobe. Une fois vos tests terminés, vous pouvez passer à l'un de nos forfaits payants pour générer un jeton de production.",
|
||||
"help-api-token": "Ce jeton peut être utilisé pour accéder aux API de production au point de terminaison {{origin}}/api/v1. Il ne peut être généré que sur l'un de nos forfaits payants.",
|
||||
"reason": "Raison",
|
||||
"decline-by": "Refusé/révoqué par",
|
||||
"document-declined": "document refusé",
|
||||
"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-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.",
|
||||
"here": "ici",
|
||||
"copy-code": "COPIE",
|
||||
"copied-code": "COPIÉ",
|
||||
"Installation": "Installation",
|
||||
"Usage": "Usage",
|
||||
"insufficient-credits": "Crédits de signature insuffisants",
|
||||
"insufficient-credits-mssg": "Le propriétaire de ce document ne dispose actuellement pas des crédits OpenSign nécessaires pour que vous puissiez le signer. Veuillez contacter le propriétaire si vous avez besoin de plus de détails.",
|
||||
"angular-npm-mssg-1": "Pour intégrer OpenSign dans votre projet Angular, exécutez simplement la commande suivante :",
|
||||
"quota-mail-info-head": "Limite mensuelle d'e-mails",
|
||||
"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 les liens 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",
|
||||
"available-seats":"Disponible sièges",
|
||||
"buy-users":"Acheter plus d'utilisateurs",
|
||||
"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-head": "Quota atteint",
|
||||
"unauthorized-modal": "Vous n'êtes pas autorisé à effectuer cette action, veuillez contacter {{adminEmail}}.",
|
||||
"sent-this-month": "envoyé ce mois-ci",
|
||||
"available-seats": "Disponible sièges",
|
||||
"buy-users": "Acheter plus d'utilisateurs",
|
||||
"isenable-otp": "Activer la vérification OTP",
|
||||
"isenable-otp-help": {
|
||||
"p1": "Souhaitez-vous activer le processus de vérification à l'aide d'un mot de passe à usage unique (OTP)?",
|
||||
@@ -669,31 +709,31 @@
|
||||
"p3": "La sélection de cette option désactivera la vérification OTP, permettant aux utilisateurs de signer le document directement sans étapes supplémentaires.",
|
||||
"p4": "Veuillez choisir l'option qui correspond le mieux à vos exigences en matière de signature de documents."
|
||||
},
|
||||
"advanced-options":"Options avancées",
|
||||
"advanced-options": "Options avancées",
|
||||
"hide-advanced-options": "Masquer les options avancées",
|
||||
"download-files" :"Télécharger des fichiers",
|
||||
"download-pdf" :"Télécharger le PDF",
|
||||
"pdf-certificate" :"Télécharger Pdf + Certificat",
|
||||
"document-logs":"Journaux de documents",
|
||||
"server-down": "Impossible de se connecter au serveur OpenSign. Si vous hébergez vous-même OpenSign, veuillez vous assurer que toutes les étapes de la documentation ont été correctement suivies. Si vous exécutez OpenSign localement, vous y accédez peut-être via un numéro de port incorrect.",
|
||||
"download-files": "Télécharger des fichiers",
|
||||
"download-pdf": "Télécharger le PDF",
|
||||
"pdf-certificate": "Télécharger Pdf + Certificat",
|
||||
"document-logs": "Journaux de documents",
|
||||
"server-down": "Impossible de se connecter au serveur {{appName}}. Si vous hébergez vous-même {{appName}}, veuillez vous assurer que toutes les étapes de la documentation ont été correctement suivies. Si vous exécutez {{appName}} localement, vous y accédez peut-être via un numéro de port incorrect.",
|
||||
"admin-exists": "L'administrateur existe déjà. Veuillez vous connecter à l'application en utilisant les informations d'identification de l'administrateur afin de gérer les utilisateurs.",
|
||||
"enable-tour": "Activer la visite",
|
||||
"istourenabled-otp-help":{
|
||||
"istourenabled-otp-help": {
|
||||
"p1": "Cela permettra une visite guidée pour les signataires, fournissant des instructions pendant le processus de signature.",
|
||||
"p2": "Cela désactivera la visite guidée, garantissant ainsi une expérience de signature plus rapide et ininterrompue.",
|
||||
"p3": "Conseil : si vos signataires connaissent OpenSign et l'utilisent fréquemment, vous souhaiterez peut-être désactiver la visite guidée pour une expérience plus fluide. Choisissez l'option qui correspond le mieux à vos besoins en matière de signature de documents."
|
||||
"p3": "Conseil: si vos signataires connaissent {{appName}} et l'utilisent fréquemment, vous souhaiterez peut-être désactiver la visite guidée pour une expérience plus fluide. Choisissez l'option qui correspond le mieux à vos besoins en matière de signature de documents."
|
||||
},
|
||||
"form-title-1": "Paramètre du flux de documents",
|
||||
"form-title-2": "Paramètre de sécurité",
|
||||
"form-title-1": "Paramètre du flux de documents",
|
||||
"form-title-2": "Paramètre de sécurité",
|
||||
"public-tour-message": "Le modèle doit être public avant que vous puissiez générer un lien partageable.",
|
||||
"add-user-template":"Vous devez ajouter un rôle avant de pouvoir lui ajouter des champs. ",
|
||||
"pdf-uncompatible":"Ce pdf n'est pas compatible avec opensign, veuillez contacter <support@opensignlabs.com>",
|
||||
"text-field-tour" :"Les champs de type 'Texte' doivent être remplis à l'avance avant l'envoi du document. Si vous avez besoin que les signataires fournissent des informations, utilisez plutôt le champ 'Saisie de texte'.",
|
||||
"attach-signer-tour" :"Vous devez associer un signataire à chaque rôle. Vous pouvez le faire en cliquant sur cette icône. Une fois que vous avez sélectionné un signataire, il sera attaché à tous les champs associés à ce rôle qui apparaissent dans la même couleur.",
|
||||
"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'.",
|
||||
"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é.",
|
||||
"expect-default-one-signature-type": "Veuillez activer un type de signature supplémentaire en plus de celui par défaut.",
|
||||
"add-signer-note": "Remarque - Ce contact sera ajouté à votre liste de contacts",
|
||||
"add-signer-note": "Remarque - Ce contact sera enregistré dans vos contacts",
|
||||
"allowed-signature-types-help": {
|
||||
"p1": "Cette préférence de signature contrôle les options de signature disponibles pour vos signataires. Seuls les types de signature que vous sélectionnez apparaîtront dans le widget de signature lorsqu'un document est signé. Par exemple, si vous choisissez uniquement l'option 'Draw' dans les préférences, votre signataire ne verra que l'option 'Draw' dans le widget de signature, tandis que les trois autres options ne seront pas disponibles.",
|
||||
"l1": "Créez une signature manuscrite directement sur l'écran à l'aide de votre doigt ou d'un stylet.",
|
||||
@@ -704,13 +744,294 @@
|
||||
"notify-on-signatures": "Notifier les signatures",
|
||||
"notify-on-signatures-help": {
|
||||
"p1": "Le créateur du document recevra une notification par e-mail chaque fois qu'un signataire signera le document.",
|
||||
"note": "Remarque : L'e-mail de notification ne sera pas envoyé pour le dernier signataire, car le créateur du document recevra à la place un e-mail de fin."
|
||||
"note": "Remarque: Quel que soit ce paramètre, un e-mail d'achèvement contenant le document signé et le certificat d'achèvement en pièce jointe est toujours envoyé à tous les signataires et au propriétaire du document."
|
||||
},
|
||||
"expiry-date": "Fecha de caducidad",
|
||||
"expiry-date-updated": "La fecha de vencimiento se ha extendido con éxito hasta el {{newexpirydate}}",
|
||||
"expiry-date-updated": "La date d'expiration a été prolongée avec succès jusqu'au {{newexpirydate}}",
|
||||
"expiry-date-error": "Veuillez fournir une nouvelle date d'expiration postérieure à la date actuelle",
|
||||
"delete-page": "supprimer la page",
|
||||
"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"
|
||||
"duplicate-template-alert": "Êtes-vous sûr de vouloir créer une copie de ce modèle ?",
|
||||
"duplicate-template-created": "Modèle en double créé avec succès.",
|
||||
"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)",
|
||||
"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.",
|
||||
"invalid-data": "Données invalides",
|
||||
"delete-folder-alert": "Êtes-vous sûr de vouloir supprimer ce Dossier ?",
|
||||
"delete-folder-alert-1": "Impossible de supprimer : le dossier doit être vide.",
|
||||
"defaultSign-alert": "Les widgets {{widgetsType}} ne sont pas disponibles pour la signature automatique.",
|
||||
"embed-heading": "Intégrer des modèles {{appName}}",
|
||||
"public-template-edit-title": "Modifier le modèle public",
|
||||
"public-template-edit-mssg": "Vous ne pouvez pas modifier directement un modèle public. Pour apporter des modifications, le modèle sera défini comme privé et vous devrez ensuite le rendre à nouveau public. Voulez-vous continuer ?",
|
||||
"agree-p1": "Je confirme avoir lu et compris les ",
|
||||
"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 ",
|
||||
"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.",
|
||||
"term-cond-h1": "1. Objet et portée",
|
||||
"term-cond-p2": "Cette divulgation régit l'utilisation d'enregistrements et de signatures électroniques pour les documents envoyés par l'expéditeur et signés par vous via la plateforme {{appName}}. En acceptant, vous reconnaissez que :",
|
||||
"term-cond-p3": "Vous recevrez et signerez des documents électroniquement via {{appName}}.",
|
||||
"term-cond-p4": "Votre signature électronique est juridiquement contraignante et équivaut à une signature manuscrite.",
|
||||
"js-snippet-msg-2": "Gérer les modèles",
|
||||
"js-snippet-msg-3": "page.",
|
||||
"term-cond-h2": "2. Consentement à l'utilisation d'enregistrements et de signatures électroniques",
|
||||
"term-cond-p5": "En acceptant cette divulgation:",
|
||||
"term-cond-p6": "Vous consentez à effectuer des transactions électroniques avec l'expéditeur en utilisant {{appName}} et comprenez que ce consentement est valable jusqu'à son retrait.",
|
||||
"term-cond-p7": "Vous acceptez de consulter, de signer et de renvoyer des documents par voie électronique à l'aide d'{{appName}}.",
|
||||
"term-cond-p8": "Ce consentement s'applique uniquement au(x) document(s) spécifique(s) fourni(s) par l'Expéditeur.",
|
||||
"term-cond-h3": "3. Droit de retirer le consentement",
|
||||
"term-cond-p9": "Vous avez le droit de retirer votre consentement à l'utilisation d'enregistrements et de signatures électroniques en :",
|
||||
"term-cond-p10": "Contacter directement l'expéditeur en utilisant les coordonnées fournies dans sa communication.",
|
||||
"term-cond-p11": "Notifier l'expéditeur avant de terminer le processus de signature.",
|
||||
"term-cond-h4": "4. Configuration matérielle et logicielle requise",
|
||||
"term-cond-p12": "Pour signer des documents avec {{appName}}, vous devez avoir:",
|
||||
"term-cond-p13": "Un appareil avec accès à Internet.",
|
||||
"term-cond-p14": "Un navigateur Web compatible avec JavaScript activé.",
|
||||
"term-cond-p15": "Un compte email pour recevoir des notifications et des documents signés.",
|
||||
"term-cond-p16": "Si ces exigences changent, l'expéditeur ou {{appName}} vous informera des mises à jour.",
|
||||
"term-cond-h5": "5. Conservation des dossiers et demande de copies papier",
|
||||
"term-cond-p17": "Vous êtes responsable de vous assurer que vous pouvez accéder et conserver le(s) document(s) signé(s) électroniquement. {{appName}} fournit des outils pour télécharger ou imprimer le document signé immédiatement une fois le processus de signature terminé.",
|
||||
"term-cond-p18": "Si vous préférez une copie papier de tout document signé électroniquement, vous pouvez la demander directement à l'expéditeur. L'expéditeur peut facturer des frais raisonnables pour la fourniture de copies papier, sauf interdiction par la loi applicable.",
|
||||
"term-cond-h6": "6. Reconnaissance et accord",
|
||||
"term-cond-p19": "En procédant, Vous :",
|
||||
"term-cond-p20": "Confirmez que vous pouvez accéder et consulter les documents électroniques comme décrit dans cette divulgation.",
|
||||
"term-cond-p21": "Accepter d'utiliser des enregistrements et des signatures électroniques pour la ou les transactions spécifiées avec l'expéditeur.",
|
||||
"term-cond-p22": "Comprenez qu'{{appName}} est une plateforme facilitant la transaction et n'est pas partie à l'accord.",
|
||||
"term-cond-h7": "7. Effet juridique",
|
||||
"term-cond-p23": "Votre signature électronique facilitée grâce à {{appName}}:",
|
||||
"term-cond-p24": "Conforme aux lois applicables sur les signatures électroniques, y compris, mais sans s'y limiter, la loi E-SIGN aux États-Unis, le règlement eIDAS de l'UE et la loi indienne sur les technologies de l'information.",
|
||||
"term-cond-p25": "Est juridiquement contraignant entre vous et l'expéditeur pour le(s) document(s) signé(s).",
|
||||
"term-cond-h8": "8. Rôle de la plateforme et limitation de responsabilité",
|
||||
"term-cond-p26": "{{appName}} sert de plateforme pour faciliter les transactions électroniques. Il n'est pas responsable du contenu, de la validité ou du caractère exécutoire des documents envoyés par l'expéditeur. Tout litige ou problème lié au document ou à sa signature doit être résolu directement entre vous et l'expéditeur. Expéditeur.",
|
||||
"term-cond-h9": "9. Résiliation du Contrat",
|
||||
"term-cond-p27": "Votre consentement s'applique uniquement à la transaction en cours, sauf indication contraire de la part de l'expéditeur. {{appName}} se réserve le droit de mettre fin à l'accès à sa plateforme en cas de violation de ses conditions d'utilisation ou de ses politiques.",
|
||||
"term-cond-p28": "En sélectionnant J'accepte, vous confirmez que vous comprenez et acceptez les termes de cette divulgation d'enregistrement électronique et de signature et consentez à signer électroniquement le(s) document(s) fourni(s) par l'expéditeur via {{appName}}.",
|
||||
"term-cond-p29": "Si vous avez des questions sur cette divulgation, contactez directement l'expéditeur. Pour obtenir une assistance technique avec {{appName}}, visitez ",
|
||||
"term-cond-p30": " ou par email",
|
||||
"js-snippet-msg": "Pour intégrer des modèles {{appName}} dans vos sites Web ou pages de destination HTML, vous pouvez utiliser le code ci-dessous:",
|
||||
"js-snippet-msg-1": "Vous pouvez obtenir le TemplateId à partir de la page Gérer les modèles.",
|
||||
"agrrement-alert": "Pour procéder, vous devez consentir à la divulgation des enregistrements et signatures électroniques.",
|
||||
"webhook-already-exists": "L'URL existe déjà ! Veuillez en essayer un autre.",
|
||||
"webhook-must-be-secure": "L'URL du webhook doit être sécurisée et utiliser https://",
|
||||
"provide-test-token": "Veuillez générer un jeton de test API",
|
||||
"invalid-redirect-url": "Veuillez fournir une URL de redirection valide.",
|
||||
"auto-sign-mssg": "Tout signer automatiquement",
|
||||
"already-exist-signer": "Le signataire sélectionné est déjà présent dans la liste des destinataires",
|
||||
"allow-modify": "Autoriser les modifications",
|
||||
"allow-modify-help": {
|
||||
"p1": "Souhaitez-vous autoriser les modifications du document pendant le processus de signature ?",
|
||||
"p2": "Sélectionner cette option permet aux signataires d'ajouter des éléments supplémentaires, tels que des signatures, des initiales, des tampons ou du texte, par-dessus les widgets existants.",
|
||||
"p3": "Sélectionner cette option empêche les signataires d'apporter des ajouts au document pendant le processus de signature."
|
||||
},
|
||||
"signature-setting": "Paramètres de signature",
|
||||
"change-signer": "Changer de signataire",
|
||||
"signyour-self-button": "Utilisez cette option pour signer le document vous-même sans en ajouter d'autres.",
|
||||
"requestsign-button": "Utilisez cette option pour demander les signatures des autres et de vous-même.",
|
||||
"signyour-self-description": "Utilisez ce formulaire pour signer le document vous-même sans en ajouter d'autres.",
|
||||
"requestsign-description": "Utilisez ce formulaire pour demander les signatures des autres et de vous-même.",
|
||||
"template-form-description": "Utilisez ce formulaire pour créer des modèles réutilisables pour vos documents.",
|
||||
"activation-key": "Clé d'activation",
|
||||
"paid-selfhost-help": "Veuillez contacter {{appName}} pour plus d'informations à l'adresse ",
|
||||
"remaining-credits-selfhost-help": "Utilisez des crédits premium pour la signature de documents via l'API, l'envoi en masse et l'intégration d'{{appName}} sur votre site web. Vous avez {{allowedcredits}} crédits inclus et {{addoncredits}} crédits supplémentaires achetés restants.",
|
||||
"signature-type": "Votre signature",
|
||||
"initial-type": "Vos initiales",
|
||||
"redirect-url": "URL de redirection",
|
||||
"bulk-send": "Envoi groupé",
|
||||
"select-timezone": "Fuseau horaire",
|
||||
"current-time": "Heure actuelle",
|
||||
"email-help": "Vous n'êtes pas autorisé à changer d'adresse e-mail pour des raisons de sécurité. Veuillez créer un autre compte gratuit en utilisant la nouvelle adresse e-mail.",
|
||||
"doc-sent": "Document envoyé avec succès.",
|
||||
"no-signer": "Supprimer",
|
||||
"and": "et",
|
||||
"make-public": "Rendre public",
|
||||
"general": "Général",
|
||||
"request-email": "E-mail de demande",
|
||||
"completion-email": "E-mail de finalisation",
|
||||
"variables-use": "Variables à utiliser",
|
||||
"send-to-sign": "vous a demandé de signer",
|
||||
"use-template": "Utiliser le modèle",
|
||||
"contact-already-exists": "Le contact existe déjà.",
|
||||
"docx-error": "Nous rencontrons actuellement des problèmes avec le traitement des fichiers DOCX. Veuillez télécharger le fichier PDF",
|
||||
"docx-error-contact": "ou contactez-nous à support@opensignlabs.com",
|
||||
"agreement-note": "Remarque : Accepter cela ne signifie pas que vous signez immédiatement le document. Cela vous permet uniquement de consulter le document électroniquement. Vous aurez l'opportunité de le lire entièrement et de décider ensuite si vous souhaitez le signer.",
|
||||
"draft-template-info-p1": "Pour rendre votre modèle public, il doit contenir un seul rôle ou, s'il inclut plusieurs rôles, tous les rôles supplémentaires doivent déjà être attribués aux signataires. Le rôle public non attribué doit rester vide et être placé en première position.",
|
||||
"visit-below-link": "Visitez le lien ci-dessous pour en savoir plus -",
|
||||
"storage-help": "Activer BYOC vous permet de connecter votre propre stockage S3 afin que vos fichiers restent entièrement sous votre contrôle, sans copie externe conservée. Si l'autonomie des données est importante pour vous, envisagez de passer à l'offre Teams pour débloquer cette fonctionnalité.",
|
||||
"daily-quota-reached": "Vous avez atteint votre quota quotidien. Pour obtenir de l'aide, veuillez contacter quotas@opensignlabs.com.",
|
||||
"enabled-signature-type": "Types de signature activés",
|
||||
"enabled-signature-type-help": "Le paramètre 'Types de signature activés' détermine quelles options de signature sont disponibles dans votre organisation. Par exemple, si vous désactivez l'option 'Dessiner', les membres de votre organisation ne la verront pas dans le widget de signature, tandis que les trois autres options resteront accessibles.",
|
||||
"indexing-public-profile": "Autoriser l'indexation du profil public par les moteurs de recherche",
|
||||
"user-created-successfully": "Utilisateur créé avec succès.",
|
||||
"only-15-reminder-allowed": "Vous pouvez définir jusqu'à 15 rappels automatiques. Par exemple, si 'TimeToComplete' est de 15 jours et 'RemindOnceInEvery' est de 1 jour, vous atteindrez la limite maximale de 15 rappels. Ajustez vos paramètres en conséquence.",
|
||||
"rate-your-experience": "Comment s'est passée votre expérience avec {{appName}} ?",
|
||||
"thanks-for-feedback": "Merci pour votre retour 🙏",
|
||||
"share-your-feedback": "Partagez votre avis",
|
||||
"share-your-review": "Partagez votre avis",
|
||||
"date-format": "Format de date",
|
||||
"document-deleted": "Le document a été supprimé ou vous n'y avez pas accès. Veuillez contacter l'expéditeur.",
|
||||
"save-as-template-?": "Êtes-vous sûr de vouloir enregistrer ce document comme modèle ?",
|
||||
"go-to-manage-templates": "Aller à 'Gérer les modèles'",
|
||||
"template-created": "Modèle créé",
|
||||
"how-would-you-like-to-proceed?": "Comment souhaitez-vous procéder ?",
|
||||
"failed-to-load-refresh-page": "Échec du chargement du document. Veuillez essayer d'actualiser cette page.",
|
||||
"document-has-been-signed": "Le document a été signé avec succès !",
|
||||
"document-has-been-signed-by-you": "Le document a été signé avec succès par vous !",
|
||||
"participant-completed-signing": "Tous les participants ont terminé le processus de signature.",
|
||||
"you-will-receive-email-shortly": "✅ Voilà, c'est fait ! Vous recevrez un e-mail de confirmation sous peu.",
|
||||
"please-provide-templateid": "Veuillez fournir templateid",
|
||||
"this-template-is-not-public": "Ce template n'est pas public",
|
||||
"invalid-templateid": "templateid invalide",
|
||||
"contact-billing-at-opensign": "Pour ajouter plus de places, veuillez contacter OpenSign™ à l'adresse <1>billing@opensignlabs.com</1> pour obtenir de l'aide.",
|
||||
"title-length-alert": "Le titre doit comporter au maximum 250 caractères.",
|
||||
"note-length-alert": "La note doit comporter au maximum 200 caractères.",
|
||||
"description-length-alert": "La description doit comporter au maximum 500 caractères",
|
||||
"fix-&-resend-document": "Corriger et renvoyer le document",
|
||||
"do-you-want-recreate-document?": "Cela créera un brouillon à partir de ce document avec tous les champs intacts. Êtes-vous sûr de vouloir recréer ce document ?",
|
||||
"start-editing": "Commencer l'édition",
|
||||
"unsaved-changes-discard-them?": "Vous avez des modifications non enregistrées. Les supprimer ?",
|
||||
"yes-discard": "Oui, supprimer",
|
||||
"LTV-enabled-signatures": "Signatures avec LTV activée",
|
||||
"BETA": "BETA",
|
||||
"two-factor-authentication": "Authentification à deux facteurs",
|
||||
"2fa-help-text": "L'authentification à deux facteurs ajoute une couche de sécurité supplémentaire à votre compte en exigeant plus qu'un simple mot de passe pour se connecter.",
|
||||
"2fa-help-bullet1": "Renforce la sécurité en demandant à la fois votre mot de passe et un code de vérification.",
|
||||
"2fa-help-bullet2": "Le code de vérification est généré par une application d'authentification sur votre appareil.",
|
||||
"2fa-help-bullet3": "Protège votre compte même si votre mot de passe est compromis.",
|
||||
"setup-2fa": "Configurer 2FA",
|
||||
"setup-2fa-again": "Reconfigurer 2FA",
|
||||
"2fa-setup-intro": "Protégez votre compte avec l'authentification à deux facteurs. Une fois activée, vous devrez saisir un code provenant de votre application d'authentification à chaque connexion.",
|
||||
"scan-qr-code": "Scanner le code QR",
|
||||
"scan-qr-instructions": "Utilisez une application d'authentification comme Google Authenticator, Microsoft Authenticator ou Authy pour scanner ce code QR.",
|
||||
"manual-setup-instructions": "Vous ne pouvez pas scanner le code ? Configurez manuellement votre application d'authentification avec cette clé secrète :",
|
||||
"secret-key": "Clé secrète",
|
||||
"copied-to-clipboard": "Copié dans le presse-papiers",
|
||||
"copy-to-clipboard": "Copier dans le presse-papiers",
|
||||
"recovery-codes": "Codes de récupération",
|
||||
"recovery-codes-instructions": "Enregistrez ces codes de récupération dans un endroit sûr. Si vous perdez l'accès à votre application d'authentification, vous pourrez utiliser l'un de ces codes à usage unique pour vous connecter.",
|
||||
"download-recovery-codes": "Télécharger les codes de récupération",
|
||||
"verification-code": "Code de vérification",
|
||||
"enter-code-from-authenticator-app": "Saisissez le code à 6 chiffres de votre application d'authentification",
|
||||
"verification-code-required": "Le code de vérification est requis",
|
||||
"verification-code-invalid": "Code de vérification invalide. Veuillez réessayer.",
|
||||
"2fa-enabled": "2FA activé",
|
||||
"2fa-enabled-successfully": "Authentification à deux facteurs activée avec succès",
|
||||
"2fa-setup-complete": "Configuration terminée !",
|
||||
"2fa-setup-complete-instructions": "Votre compte est maintenant protégé par l'authentification à deux facteurs. Vous devrez entrer un code de vérification à chaque connexion.",
|
||||
"two-factor-verification": "Vérification en deux étapes",
|
||||
"enter-verification-code-instructions": "Saisissez le code de vérification à 6 chiffres de votre application d'authentification pour continuer.",
|
||||
"recovery-code": "Code de récupération",
|
||||
"enter-recovery-code-help": "Saisissez l'un de vos codes de récupération",
|
||||
"recovery-code-required": "Le code de récupération est requis",
|
||||
"use-verification-code-instead": "Utiliser le code de vérification à la place",
|
||||
"use-recovery-code-instead": "Utiliser le code de récupération à la place",
|
||||
"regenerate-2fa-remove-existing": "Êtes-vous sûr de vouloir régénérer l'authentification à deux facteurs ? Cette action supprimera vos paramètres d'authentification existants.",
|
||||
"use-passkey": "Se connecter avec une clé d'accès",
|
||||
"security-section": "Sécurité",
|
||||
"passkey-authentication": "Authentification par clé d'accès",
|
||||
"passkey-not-supported": "Votre navigateur ou appareil ne prend pas en charge l'authentification par clé d'accès",
|
||||
"passkey-description": "Les clés d'accès offrent une alternative plus sécurisée et résistante au phishing que les mots de passe. Vous pouvez utiliser votre empreinte digitale, reconnaissance faciale ou code PIN de l'appareil pour vous connecter en toute sécurité.",
|
||||
"passkey-tooltip": "Les clés d'accès sont une alternative plus simple et plus sécurisée aux mots de passe. Elles utilisent des données biométriques comme les empreintes digitales ou la reconnaissance faciale déjà stockées sur votre appareil.",
|
||||
"security-section-help": "Gérez les options de sécurité, y compris les clés d'accès et les méthodes d'authentification pour protéger votre compte.",
|
||||
"passkey-register": "Enregistrer une clé d'accès",
|
||||
"passkey-register-another": "Enregistrer une autre clé d'accès",
|
||||
"passkey-registered": "Clé d'accès enregistrée",
|
||||
"passkey-registering": "Enregistrement de la clé d'accès...",
|
||||
"passkey-registered-success": "Clé d'accès enregistrée avec succès !",
|
||||
"passkey-registration-failed": "Échec de l'enregistrement de la clé d'accès",
|
||||
"passkey-auth-failed": "Échec de l'authentification avec la clé d'accès",
|
||||
"passkey-missing-user-info": "Informations utilisateur requises",
|
||||
"passkeys-list": "Vos clés d'accès",
|
||||
"default-passkey": "Votre clé d'accès",
|
||||
"passkey-rename": "Renommer",
|
||||
"passkey-delete": "Supprimer",
|
||||
"passkey-rename-title": "Renommer la clé d'accès",
|
||||
"passkey-delete-title": "Supprimer la clé d'accès",
|
||||
"passkey-delete-confirm": "Êtes-vous sûr de vouloir supprimer la clé d'accès « {{name}} » ?",
|
||||
"passkey-name": "Nom de la clé d'accès",
|
||||
"passkey-name-placeholder": "Entrez un nom descriptif pour cette clé d'accès",
|
||||
"passkey-renamed-success": "Clé d'accès renommée avec succès",
|
||||
"passkey-deleted-success": "Clé d'accès supprimée avec succès",
|
||||
"passkey-rename-failed": "Échec du renommage de la clé d'accès",
|
||||
"passkey-delete-failed": "Échec de la suppression de la clé d'accès",
|
||||
"processing": "Traitement en cours...",
|
||||
"today": "Aujourd'hui",
|
||||
"yesterday": "Hier",
|
||||
"days-ago": "Il y a {{days}} jours",
|
||||
"verify-with-passkey": "Vérifier avec une passkey",
|
||||
"verify-with-otp": "Vérifier avec OTP",
|
||||
"verify-identity": "Vérifier l'identité",
|
||||
"verify-account": "Vérifiez votre identité",
|
||||
"verification": "Vérification",
|
||||
"passkey-verification-failed": "La vérification par passkey a échoué",
|
||||
"security-auth-help": {
|
||||
"p1": "Gérez les paramètres de sécurité de votre compte pour protéger vos données. OpenSign prend en charge des méthodes d'authentification avancées pour renforcer la protection de votre compte.",
|
||||
"2fa-auth-help": "Ajoutez une couche de sécurité supplémentaire en activant 2FA. Cela vous demandera de saisir un code de vérification provenant d’une application d’authentification après votre mot de passe.",
|
||||
"passkey-auth-help": "Utilisez des passkeys pour une connexion sans mot de passe grâce à une vérification biométrique ou basée sur l’appareil, offrant à la fois une sécurité renforcée et une grande commodité."
|
||||
},
|
||||
"signer-already-present": "Signataire déjà présent",
|
||||
"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",
|
||||
"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…"
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 95 KiB |
@@ -0,0 +1 @@
|
||||
import "@testing-library/jest-dom";
|
||||
@@ -1,7 +1,6 @@
|
||||
import React, { useState, useEffect, lazy } from "react";
|
||||
import { Routes, Route, BrowserRouter } from "react-router-dom";
|
||||
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";
|
||||
@@ -15,28 +14,23 @@ import DraftDocument from "./components/pdf/DraftDocument";
|
||||
import PlaceHolderSign from "./pages/PlaceHolderSign";
|
||||
import PdfRequestFiles from "./pages/PdfRequestFiles";
|
||||
import LazyPage from "./primitives/LazyPage";
|
||||
import { isEnableSubscription } from "./constant/const";
|
||||
import SSOVerify from "./pages/SSOVerify";
|
||||
import Loader from "./primitives/Loader";
|
||||
import TeamList from "./pages/TeamList";
|
||||
import UserList from "./pages/UserList";
|
||||
import { serverUrl_fn } from "./constant/appinfo";
|
||||
import DocSuccessPage from "./pages/DocSuccessPage";
|
||||
import ValidateSession from "./primitives/ValidateSession";
|
||||
const DebugPdf = lazy(() => import("./pages/DebugPdf"));
|
||||
const ForgetPassword = lazy(() => import("./pages/ForgetPassword"));
|
||||
const GuestLogin = lazy(() => import("./pages/GuestLogin"));
|
||||
const Pgsignup = lazy(() => import("./pages/Pgsignup"));
|
||||
const Subscriptions = lazy(() => import("./pages/PlanSubscriptions"));
|
||||
const ChangePassword = lazy(() => import("./pages/ChangePassword"));
|
||||
const UserProfile = lazy(() => import("./pages/UserProfile"));
|
||||
const Signup = lazy(() => import("./pages/Signup"));
|
||||
const Opensigndrive = lazy(() => import("./pages/Opensigndrive"));
|
||||
const ManageSign = lazy(() => import("./pages/Managesign"));
|
||||
const GenerateToken = lazy(() => import("./pages/GenerateToken"));
|
||||
const Webhook = lazy(() => import("./pages/Webhook"));
|
||||
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 (
|
||||
@@ -73,12 +67,7 @@ function App() {
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
<Route element={<ValidateRoute />}>
|
||||
<Route exact path="/" element={<Login />} />
|
||||
{isEnableSubscription && (
|
||||
<Route path="/signup" element={<LazyPage Page={Signup} />} />
|
||||
)}
|
||||
{!isEnableSubscription && (
|
||||
<>
|
||||
<Route exact path="/" element={<LazyPage Page={Login} />} />
|
||||
<Route
|
||||
path="/addadmin"
|
||||
element={<LazyPage Page={AddAdmin} />}
|
||||
@@ -87,8 +76,6 @@ function App() {
|
||||
path="/upgrade-2.1"
|
||||
element={<LazyPage Page={UpdateExistUserAdmin} />}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Route>
|
||||
<Route element={<Validate />}>
|
||||
<Route
|
||||
@@ -119,27 +106,21 @@ function App() {
|
||||
element={<LazyPage Page={GuestLogin} />}
|
||||
/>
|
||||
<Route path="/debugpdf" element={<LazyPage Page={DebugPdf} />} />
|
||||
<Route
|
||||
path="/forgetpassword"
|
||||
element={<LazyPage Page={ForgetPassword} />}
|
||||
/>
|
||||
{isEnableSubscription && (
|
||||
<>
|
||||
<Route
|
||||
path="/pgsignup"
|
||||
element={<LazyPage Page={Pgsignup} />}
|
||||
/>
|
||||
<Route
|
||||
path="/subscription"
|
||||
element={<LazyPage Page={Subscriptions} />}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<Route element={<HomeLayout />}>
|
||||
<Route
|
||||
path="/changepassword"
|
||||
element={<LazyPage Page={ChangePassword} />}
|
||||
path="/forgetpassword"
|
||||
element={<LazyPage Page={ForgetPassword} />}
|
||||
/>
|
||||
<Route
|
||||
element={
|
||||
<ValidateSession>
|
||||
<HomeLayout />
|
||||
</ValidateSession>
|
||||
}
|
||||
>
|
||||
<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 />} />
|
||||
@@ -148,7 +129,7 @@ function App() {
|
||||
element={<LazyPage Page={UserProfile} />}
|
||||
/>
|
||||
<Route
|
||||
path="/opensigndrive"
|
||||
path="/drive"
|
||||
element={<LazyPage Page={Opensigndrive} />}
|
||||
/>
|
||||
<Route
|
||||
@@ -161,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
|
||||
@@ -175,7 +156,6 @@ function App() {
|
||||
path="/pdfRequestFiles/:docId"
|
||||
element={<PdfRequestFiles />}
|
||||
/>
|
||||
|
||||
{/* recipient signature route with no rowlevel data using docId from url */}
|
||||
<Route
|
||||
path="/recipientSignPdf/:docId/:contactBookId"
|
||||
@@ -185,26 +165,17 @@ function App() {
|
||||
path="/recipientSignPdf/:docId"
|
||||
element={<PdfRequestFiles />}
|
||||
/>
|
||||
{isEnableSubscription && (
|
||||
<>
|
||||
<Route path="/teams" element={<TeamList />} />
|
||||
<Route
|
||||
path="/generatetoken"
|
||||
element={<LazyPage Page={GenerateToken} />}
|
||||
/>
|
||||
<Route
|
||||
path="/webhook"
|
||||
element={<LazyPage Page={Webhook} />}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<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} />}
|
||||
/>
|
||||
</Route>
|
||||
<Route path="/sso" element={<SSOVerify />} />
|
||||
<Route path="/success" element={<DocSuccessPage />} />
|
||||
<Route path="*" element={<PageNotFound />} />
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
|
Before Width: | Height: | Size: 805 B |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 15 KiB |
@@ -1,184 +1,184 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="691.365" height="586.346" viewBox="0 0 691.365 586.346">
|
||||
<defs>
|
||||
<linearGradient id="linear-gradient" x1="0.5" y1="1" x2="0.5" y2="-0.001" gradientUnits="objectBoundingBox">
|
||||
<stop offset="0" stop-color="gray" stop-opacity="0.251"/>
|
||||
<stop offset="0.54" stop-color="gray" stop-opacity="0.122"/>
|
||||
<stop offset="1" stop-color="gray" stop-opacity="0.102"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g id="Group_644" data-name="Group 644" transform="translate(-588.635 -80)">
|
||||
<g id="Group_642" data-name="Group 642" transform="translate(22538.635 6999)">
|
||||
<path id="Path_472" data-name="Path 472" d="M482.764,637.675c51.612-29.22,67.336-29.693,124.954-17.169,81.646,17.758,88.43-49.93,108.306-186.933,33.946-233.98-108.306-295.737-108.306-295.737s-94.6-8.1-150.536-38.963C377.726,55.027,286.9,49.443,207.046,86.542c-51.069,23.7-93.125,56.714-100.31,102.034-20.09,126.746,12.9,196.58-48.819,237.954S48.944,581.5,96.263,577.564c34.968-2.906,80.842,52.415,130.687,60.111,17.382,2.684,88.7-13.043,99.578,0C341.327,655.4,464.763,647.87,482.764,637.675Z" transform="translate(-21979.895 -6980.982)" fill="#00ddf1" opacity="0.1"/>
|
||||
<g id="Group_641" data-name="Group 641" transform="translate(-21545.08 -6816.88)">
|
||||
<rect id="Rectangle_263" data-name="Rectangle 263" width="217.867" height="15.073" transform="translate(0 39.052)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_264" data-name="Rectangle 264" width="217.867" height="15.073" transform="translate(0 104.137)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_265" data-name="Rectangle 265" width="217.867" height="15.073" transform="translate(0 171.964)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_266" data-name="Rectangle 266" width="13.702" height="39.052" transform="translate(19.183)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_267" data-name="Rectangle 267" width="13.702" height="2.74" transform="translate(19.183 6.166)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_268" data-name="Rectangle 268" width="13.702" height="2.74" transform="translate(19.183 27.405)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_269" data-name="Rectangle 269" width="13.702" height="39.052" transform="translate(39.737)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_270" data-name="Rectangle 270" width="13.702" height="2.74" transform="translate(39.737 6.166)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_271" data-name="Rectangle 271" width="13.702" height="2.74" transform="translate(39.737 27.405)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_272" data-name="Rectangle 272" width="13.702" height="39.052" transform="translate(60.29)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_273" data-name="Rectangle 273" width="13.702" height="2.74" transform="translate(60.29 6.166)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_274" data-name="Rectangle 274" width="13.702" height="2.74" transform="translate(60.29 27.405)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_275" data-name="Rectangle 275" width="13.702" height="39.052" transform="translate(80.844)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_276" data-name="Rectangle 276" width="13.702" height="2.74" transform="translate(80.844 6.166)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_277" data-name="Rectangle 277" width="13.702" height="2.74" transform="translate(80.844 27.405)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_278" data-name="Rectangle 278" width="13.702" height="39.052" transform="translate(101.397)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_279" data-name="Rectangle 279" width="13.702" height="2.74" transform="translate(101.397 6.166)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_280" data-name="Rectangle 280" width="13.702" height="2.74" transform="translate(101.397 27.405)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_281" data-name="Rectangle 281" width="13.702" height="39.052" transform="translate(121.95)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_282" data-name="Rectangle 282" width="13.702" height="2.74" transform="translate(121.95 6.166)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_283" data-name="Rectangle 283" width="13.702" height="2.74" transform="translate(121.95 27.405)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_284" data-name="Rectangle 284" width="13.702" height="39.052" transform="translate(142.504)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_285" data-name="Rectangle 285" width="13.702" height="2.74" transform="translate(142.504 6.166)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_286" data-name="Rectangle 286" width="13.702" height="2.74" transform="translate(142.504 27.405)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_287" data-name="Rectangle 287" width="13.702" height="39.052" transform="matrix(0.894, -0.448, 0.448, 0.894, 135.847, 138.025)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_288" data-name="Rectangle 288" width="13.702" height="2.74" transform="matrix(0.894, -0.448, 0.448, 0.894, 138.614, 143.541)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_289" data-name="Rectangle 289" width="13.702" height="2.74" transform="matrix(0.894, -0.448, 0.448, 0.894, 148.128, 162.529)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_290" data-name="Rectangle 290" width="13.702" height="39.052" transform="translate(19.183 65.771)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_291" data-name="Rectangle 291" width="13.702" height="2.74" transform="translate(19.183 71.937)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_292" data-name="Rectangle 292" width="13.702" height="2.74" transform="translate(19.183 93.176)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_293" data-name="Rectangle 293" width="13.702" height="39.052" transform="translate(39.737 65.771)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_294" data-name="Rectangle 294" width="13.702" height="2.74" transform="translate(39.737 71.937)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_295" data-name="Rectangle 295" width="13.702" height="2.74" transform="translate(39.737 93.176)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_296" data-name="Rectangle 296" width="13.702" height="39.052" transform="translate(60.29 65.771)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_297" data-name="Rectangle 297" width="13.702" height="2.74" transform="translate(60.29 71.937)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_298" data-name="Rectangle 298" width="13.702" height="2.74" transform="translate(60.29 93.176)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_299" data-name="Rectangle 299" width="13.702" height="39.052" transform="translate(145.93 65.771)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_300" data-name="Rectangle 300" width="13.702" height="2.74" transform="translate(145.93 71.937)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_301" data-name="Rectangle 301" width="13.702" height="2.74" transform="translate(145.93 93.176)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_302" data-name="Rectangle 302" width="13.702" height="39.052" transform="translate(166.483 65.771)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_303" data-name="Rectangle 303" width="13.702" height="2.74" transform="translate(166.483 71.937)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_304" data-name="Rectangle 304" width="13.702" height="2.74" transform="translate(166.483 93.176)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_305" data-name="Rectangle 305" width="13.702" height="39.052" transform="translate(187.036 65.771)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_306" data-name="Rectangle 306" width="13.702" height="2.74" transform="translate(187.036 71.937)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_307" data-name="Rectangle 307" width="13.702" height="2.74" transform="translate(187.036 93.176)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_308" data-name="Rectangle 308" width="13.702" height="39.052" transform="translate(19.183 132.912)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_309" data-name="Rectangle 309" width="13.702" height="2.74" transform="translate(19.183 139.078)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_310" data-name="Rectangle 310" width="13.702" height="2.74" transform="translate(19.183 160.317)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_311" data-name="Rectangle 311" width="13.702" height="39.052" transform="translate(39.737 132.912)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_312" data-name="Rectangle 312" width="13.702" height="2.74" transform="translate(39.737 139.078)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_313" data-name="Rectangle 313" width="13.702" height="2.74" transform="translate(39.737 160.317)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_314" data-name="Rectangle 314" width="13.702" height="39.052" transform="translate(60.29 132.912)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_315" data-name="Rectangle 315" width="13.702" height="2.74" transform="translate(60.29 139.078)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_316" data-name="Rectangle 316" width="13.702" height="2.74" transform="translate(60.29 160.317)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_317" data-name="Rectangle 317" width="13.702" height="39.052" transform="translate(80.844 132.912)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_318" data-name="Rectangle 318" width="13.702" height="2.74" transform="translate(80.844 139.078)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_319" data-name="Rectangle 319" width="13.702" height="2.74" transform="translate(80.844 160.317)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_320" data-name="Rectangle 320" width="13.702" height="39.052" transform="translate(101.397 132.912)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_321" data-name="Rectangle 321" width="13.702" height="2.74" transform="translate(101.397 139.078)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_322" data-name="Rectangle 322" width="13.702" height="2.74" transform="translate(101.397 160.317)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_323" data-name="Rectangle 323" width="13.702" height="39.052" transform="translate(121.95 132.912)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_324" data-name="Rectangle 324" width="13.702" height="2.74" transform="translate(121.95 139.078)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_325" data-name="Rectangle 325" width="13.702" height="2.74" transform="translate(121.95 160.317)" fill="#00ddf1" opacity="0.1"/>
|
||||
<path id="Path_473" data-name="Path 473" d="M797.862,159.09v35.571h-13.7V156.48Q791.039,157.693,797.862,159.09Z" transform="translate(-621.103 -155.61)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_326" data-name="Rectangle 326" width="13.702" height="2.74" transform="translate(163.057 6.166)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_327" data-name="Rectangle 327" width="13.702" height="2.74" transform="translate(163.057 27.405)" fill="#00ddf1" opacity="0.1"/>
|
||||
<path id="Path_474" data-name="Path 474" d="M827.862,165.719v30.83h-13.7V162.43c1.781.391,3.556.8,5.33,1.22Q823.7,164.636,827.862,165.719Z" transform="translate(-630.549 -157.483)" fill="#00ddf1" opacity="0.1"/>
|
||||
<path id="Path_475" data-name="Path 475" d="M827.862,166.285v.685h-13.7v-2.74h5.33Q823.7,165.2,827.862,166.285Z" transform="translate(-630.549 -158.05)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_328" data-name="Rectangle 328" width="13.702" height="2.74" transform="translate(183.611 27.405)" fill="#00ddf1" opacity="0.1"/>
|
||||
<path id="Path_476" data-name="Path 476" d="M691.358,287.276s-4.152-1.411-6.166,2.254a6.68,6.68,0,0,0,.089,6.426,9.4,9.4,0,0,0,9.146,5.179c4.35-.233,5.8-3.549,6.166-6.639a6.851,6.851,0,0,0-9.057-7.3Z" transform="translate(-589.693 -196.649)" fill="#00ddf1"/>
|
||||
<path id="Path_477" data-name="Path 477" d="M686.2,296.239a6.68,6.68,0,0,1-.089-6.426c2-3.659,6.166-2.254,6.166-2.254l.212-.075a6.762,6.762,0,0,1,4.885.11,6.762,6.762,0,0,0-5.762-.548l-.212.075s-4.152-1.411-6.166,2.254a6.68,6.68,0,0,0,.089,6.426,9.982,9.982,0,0,0,4.7,4.446A10.277,10.277,0,0,1,686.2,296.239Z" transform="translate(-589.706 -196.595)" fill="#00ddf1" opacity="0.1"/>
|
||||
<path id="Path_478" data-name="Path 478" d="M691.42,290.06s1.542,1.213,5.282,0" transform="translate(-591.9 -197.672)" fill="#00ddf1" opacity="0.2"/>
|
||||
<path id="Path_479" data-name="Path 479" d="M690.93,277.21s2.638,7.153,2.418,9.023l2.09-.219s-3.521-6.269-2.638-8.8Z" transform="translate(-591.746 -193.626)" fill="#00ddf1"/>
|
||||
<path id="Path_480" data-name="Path 480" d="M690.93,277.21s2.638,7.153,2.418,9.023l2.09-.219s-3.521-6.269-2.638-8.8Z" transform="translate(-591.746 -193.626)" fill="#00ddf1" opacity="0.2"/>
|
||||
<path id="Path_481" data-name="Path 481" d="M702.92,277.6s-4.159-2.124-6.015-.37-2.144,6.241-2.144,6.241S701.536,284.391,702.92,277.6Z" transform="translate(-592.952 -193.414)" fill="#00ddf1"/>
|
||||
<path id="Path_482" data-name="Path 482" d="M702.92,277.6s-4.159-2.124-6.015-.37-2.144,6.241-2.144,6.241S701.536,284.391,702.92,277.6Z" transform="translate(-592.952 -193.414)" fill="#00ddf1" opacity="0.1"/>
|
||||
<path id="Path_483" data-name="Path 483" d="M694.75,283.958s1.089-5.207,8.16-5.878" transform="translate(-592.949 -193.9)" fill="#00ddf1"/>
|
||||
<path id="Path_484" data-name="Path 484" d="M819.358,386.276s-4.152-1.411-6.166,2.254a6.68,6.68,0,0,0,.089,6.426,9.4,9.4,0,0,0,9.146,5.18c4.35-.233,5.8-3.549,6.166-6.639a6.851,6.851,0,0,0-9.057-7.3Z" transform="translate(-629.999 -227.823)" fill="#00ddf1"/>
|
||||
<path id="Path_485" data-name="Path 485" d="M814.2,395.239a6.68,6.68,0,0,1-.089-6.426c2-3.659,6.166-2.254,6.166-2.254l.212-.075a6.762,6.762,0,0,1,4.885.11,6.762,6.762,0,0,0-5.762-.548l-.212.075s-4.152-1.411-6.166,2.254a6.68,6.68,0,0,0,.089,6.426,9.982,9.982,0,0,0,4.7,4.446A10.276,10.276,0,0,1,814.2,395.239Z" transform="translate(-630.011 -227.769)" fill="#00ddf1" opacity="0.1"/>
|
||||
<path id="Path_486" data-name="Path 486" d="M819.42,389.06s1.542,1.213,5.282,0" transform="translate(-632.206 -228.846)" fill="#00ddf1" opacity="0.2"/>
|
||||
<path id="Path_487" data-name="Path 487" d="M818.93,376.21s2.638,7.153,2.419,9.023l2.09-.219s-3.522-6.269-2.638-8.8Z" transform="translate(-632.051 -224.799)" fill="#00ddf1"/>
|
||||
<path id="Path_488" data-name="Path 488" d="M818.93,376.21s2.638,7.153,2.419,9.023l2.09-.219s-3.522-6.269-2.638-8.8Z" transform="translate(-632.051 -224.799)" fill="#00ddf1" opacity="0.2"/>
|
||||
<path id="Path_489" data-name="Path 489" d="M830.92,376.6s-4.159-2.124-6.015-.37-2.144,6.241-2.144,6.241S829.536,383.391,830.92,376.6Z" transform="translate(-633.257 -224.588)" fill="#00ddf1"/>
|
||||
<path id="Path_490" data-name="Path 490" d="M830.92,376.6s-4.159-2.124-6.015-.37-2.144,6.241-2.144,6.241S829.536,383.391,830.92,376.6Z" transform="translate(-633.257 -224.588)" fill="#00ddf1" opacity="0.1"/>
|
||||
<path id="Path_491" data-name="Path 491" d="M822.75,382.958s1.089-5.207,8.16-5.878" transform="translate(-633.254 -225.073)" fill="#00ddf1"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="Group_643" data-name="Group 643" transform="translate(7 -63)">
|
||||
<g id="Group_639" data-name="Group 639" transform="translate(21233 8372)">
|
||||
<ellipse id="Ellipse_73" data-name="Ellipse 73" cx="49.851" cy="8.007" rx="49.851" ry="8.007" transform="translate(-20118.443 -7772.555)" fill="#00abf6" opacity="0.1"/>
|
||||
<rect id="Rectangle_182" data-name="Rectangle 182" width="3.208" height="72.224" transform="translate(-20070.199 -7917.934)" fill="#535461"/>
|
||||
<path id="Path_268" data-name="Path 268" d="M1025.906,587.251l-.2,3.254-.287,4.6-.118,1.913-.281,4.6-.123,1.913-.286,4.6-3.233,52.22a8.851,8.851,0,0,1-8.83,8.268H995.731a8.841,8.841,0,0,1-8.82-8.3l-3.244-52.225-.281-4.6-.117-1.913-.292-4.6-.117-1.913-.281-4.6-.2-3.254a4.6,4.6,0,0,1,4.574-4.865h34.391a4.6,4.6,0,0,1,4.569,4.9Z" transform="translate(-21072.734 -8433.181)" fill="#565661"/>
|
||||
<path id="Path_269" data-name="Path 269" d="M990.949,517.36l-.287,4.589H948.106l-.286-4.589Z" transform="translate(-21037.977 -8360.036)" fill="#9d9cb5"/>
|
||||
<path id="Path_270" data-name="Path 270" d="M990.93,530.08l-.281,4.594H948.9l-.286-4.594Z" transform="translate(-21038.363 -8366.248)" fill="#9d9cb5"/>
|
||||
<path id="Path_271" data-name="Path 271" d="M990.912,542.8l-.287,4.589H949.686L949.4,542.8Z" transform="translate(-21038.748 -8372.46)" fill="#9d9cb5"/>
|
||||
<path id="Path_272" data-name="Path 272" d="M933.875,490.464c18.684,13.517,26.466,34.656,26.466,34.656s-22.511-.773-41.2-14.289-26.46-34.651-26.46-34.651S915.2,476.953,933.875,490.464Z" transform="translate(-21028.934 -8381.328)" fill="#00abf6"/>
|
||||
<path id="Path_273" data-name="Path 273" d="M892.68,476.18s23.365,12.969,30.865,23.708,36.8,25.233,36.8,25.233" transform="translate(-21028.934 -8381.328)" fill="none" stroke="#535461" stroke-miterlimit="10" stroke-width="2"/>
|
||||
<path id="Path_274" data-name="Path 274" d="M975.393,443.457c10.171,7.352,14.407,18.863,14.407,18.863s-12.279-.425-22.424-7.776S953,435.68,953,435.68,965.227,436.1,975.393,443.457Z" transform="translate(-21058.393 -8361.549)" fill="#00abf6"/>
|
||||
<path id="Path_275" data-name="Path 275" d="M953,435.68s12.719,7.06,16.8,12.9,20.03,13.737,20.03,13.737" transform="translate(-21058.393 -8361.549)" fill="none" stroke="#535461" stroke-miterlimit="10" stroke-width="2"/>
|
||||
<path id="Path_276" data-name="Path 276" d="M1040.3,470.483c-11.68,12.836-14.151,29.612-14.151,29.612s16.469-4.042,28.139-16.883,14.151-29.612,14.151-29.612S1051.981,457.647,1040.3,470.483Z" transform="translate(-21094.119 -8370.301)" fill="#00abf6"/>
|
||||
<path id="Path_277" data-name="Path 277" d="M1068.45,453.61s-15.215,13.159-19.083,22.224S1026.15,500.1,1026.15,500.1" transform="translate(-21094.119 -8370.306)" fill="none" stroke="#535461" stroke-miterlimit="10" stroke-width="2"/>
|
||||
</g>
|
||||
<g id="Group_638" data-name="Group 638" transform="translate(-40 36)">
|
||||
<rect id="Rectangle_254" data-name="Rectangle 254" width="12.343" height="37.459" transform="translate(741.459 392.675)" fill="#3f3d56"/>
|
||||
<rect id="Rectangle_255" data-name="Rectangle 255" width="12.343" height="39.629" transform="translate(1073.647 390.726)" fill="#3f3d56"/>
|
||||
<path id="Path_418" data-name="Path 418" d="M597.889,523.665v8.446l-98.7.909-1.052.02-37.511.344-.9.013-11.4.1h-.338l-69.5.643L264.408,535.2h-.1l-29.007.273-.312.007L106.1,536.658v-9.745l30.306-18.19,60.808-.507,1.3-.007.429-.007,1.3-.019,29.2-.24,1.306-.007.416-.007,1.3-.006,9.362-.078.247-.006,23.1-.195h.188l180.214-1.52,4.411-.032h.442l3.365-.033,24.459-.2,1.3-.02,1.2-.013h.624l7.607-.058,1.507-.02,2.02-.013,6.334-.058,10.05-.084,1.3-.013,2.261-.013h.422l48.536-.416Z" transform="translate(566.82 43)" fill="#65617d"/>
|
||||
<path id="Path_419" data-name="Path 419" d="M597.889,523.665v8.446l-98.7.909-1.052.02-37.511.344-.9.013-11.4.1h-.338l-69.5.643L264.408,535.2h-.1l-29.007.273-.312.007L106.1,536.658v-9.745l30.306-18.19,60.808-.507,1.3-.007.429-.007,1.3-.019,29.2-.24,1.306-.007.416-.007,1.3-.006,9.362-.078.247-.006,23.1-.195h.188l180.214-1.52,4.411-.032h.442l3.365-.033,24.459-.2,1.3-.02,1.2-.013h.624l7.607-.058,1.507-.02,2.02-.013,6.334-.058,10.05-.084,1.3-.013,2.261-.013h.422l48.536-.416Z" transform="translate(566.82 43)" opacity="0.2"/>
|
||||
<path id="Path_420" data-name="Path 420" d="M358.914,656.57v15.91a3.982,3.982,0,0,1-2.274,3.6,3.9,3.9,0,0,1-1.624.39l-22.673.481a3.9,3.9,0,0,1-1.754-.37,3.976,3.976,0,0,1-2.319-3.619V656.57Z" transform="translate(437.441 -122.381)" fill="#3f3d56"/>
|
||||
<path id="Path_421" data-name="Path 421" d="M358.914,656.57v15.91a3.982,3.982,0,0,1-2.274,3.6,3.9,3.9,0,0,1-1.624.39l-22.673.481a3.9,3.9,0,0,1-1.754-.37,3.976,3.976,0,0,1-2.319-3.619V656.57Z" transform="translate(437.441 -122.381)" opacity="0.1"/>
|
||||
<path id="Path_422" data-name="Path 422" d="M360.914,656.57v15.91a3.982,3.982,0,0,1-2.274,3.6,3.9,3.9,0,0,1-1.624.39l-22.673.481a3.9,3.9,0,0,1-1.754-.37,3.976,3.976,0,0,1-2.319-3.619V656.57Z" transform="translate(436.74 -122.381)" fill="#3f3d56"/>
|
||||
<rect id="Rectangle_256" data-name="Rectangle 256" width="30.644" height="20.464" transform="translate(1046.362 534.189)" fill="#3f3d56"/>
|
||||
<rect id="Rectangle_257" data-name="Rectangle 257" width="30.644" height="20.464" transform="translate(1046.362 534.189)" opacity="0.1"/>
|
||||
<rect id="Rectangle_258" data-name="Rectangle 258" width="30.644" height="20.464" transform="translate(1045.062 534.189)" fill="#3f3d56"/>
|
||||
<path id="Path_423" data-name="Path 423" d="M223.943,265.82v3.723l-12.343-.318v-3.4Z" transform="translate(529.859 126.848)" opacity="0.1"/>
|
||||
<path id="Path_424" data-name="Path 424" d="M358.741,271.246,343.909,377.789a2.794,2.794,0,0,1-2.839,2.41L222.314,377.12l-2.254-.058a2.794,2.794,0,0,1-2.7-3.164l16.651-111.533a2.794,2.794,0,0,1,2.949-2.358l119.186,8.069a2.787,2.787,0,0,1,2.6,3.17Z" transform="translate(476.307 16.556)" fill="#65617d"/>
|
||||
<path id="Path_425" data-name="Path 425" d="M169.158,99.16,156.6,197.472l109.142,3.463,13.422-94.85Z" transform="translate(549.128 185.237)" fill="#00ddf1"/>
|
||||
<rect id="Rectangle_259" data-name="Rectangle 259" width="132.965" height="112.605" rx="5.33" transform="translate(853.849 282.877)" fill="#65617d"/>
|
||||
<rect id="Rectangle_260" data-name="Rectangle 260" width="120.836" height="93.765" transform="translate(860.125 290.458)" fill="#00ddf1"/>
|
||||
<path id="Path_426" data-name="Path 426" d="M735.273,262.82v2.462l-12.343.253V262.82Z" transform="translate(350.717 127.899)" opacity="0.1"/>
|
||||
<path id="Path_427" data-name="Path 427" d="M833.626,371a2,2,0,0,1-1.949,2.131l-121.615,2.514a2,2,0,0,1-2.033-1.813L697.575,265.156a2,2,0,0,1,1.9-2.189l120.693-6,1.3-.065a1.994,1.994,0,0,1,2.092,1.819Z" transform="translate(308.06 17.643)" fill="#65617d"/>
|
||||
<path id="Path_428" data-name="Path 428" d="M628.93,100.238,739.157,94.82l9.31,98.748-109.356,2.6Z" transform="translate(383.65 186.757)" fill="#00ddf1"/>
|
||||
<path id="Path_429" data-name="Path 429" d="M899.741,371a2,2,0,0,1-1.949,2.131L886.28,256.963l1.3-.065a2,2,0,0,1,2.092,1.819Z" transform="translate(241.945 17.643)" opacity="0.1"/>
|
||||
<rect id="Rectangle_261" data-name="Rectangle 261" width="12.343" height="11.096" transform="translate(741.459 419.038)" opacity="0.1"/>
|
||||
<path id="Path_431" data-name="Path 431" d="M281.574,483.65v3.229l-30.644.819V483.65Z" transform="translate(516.08 50.533)" opacity="0.1"/>
|
||||
<path id="Path_432" data-name="Path 432" d="M390.8,483.886V593.158a2.6,2.6,0,0,1-2.5,2.566l-124.506,3.313h-.033a2.6,2.6,0,0,1-2.566-2.566V483.886a2.6,2.6,0,0,1,2.566-2.566H388.23A2.6,2.6,0,0,1,390.8,483.886Z" transform="translate(460.942 -60.983)" fill="#65617d"/>
|
||||
<path id="Path_434" data-name="Path 434" d="M194.6,319.15H309.914v96.311L194.6,418.06Z" transform="translate(535.815 108.164)" fill="#d4dfec"/>
|
||||
<rect id="Rectangle_262" data-name="Rectangle 262" width="12.343" height="11.317" transform="translate(1073.647 419.038)" opacity="0.1"/>
|
||||
<path id="Path_435" data-name="Path 435" d="M709.574,483.65v4.164L678.93,487V483.65Z" transform="translate(366.132 50.533)" opacity="0.1"/>
|
||||
<path id="Path_436" data-name="Path 436" d="M797.782,483.886V596.471a2.6,2.6,0,0,1-2.6,2.566h0l-124.532-3.313a2.6,2.6,0,0,1-2.5-2.566V483.886a2.6,2.6,0,0,1,2.566-2.566H795.19a2.6,2.6,0,0,1,2.592,2.566Z" transform="translate(318.365 -60.983)" fill="#65617d"/>
|
||||
<path id="Path_437" data-name="Path 437" d="M866.289,483.886V596.471a2.6,2.6,0,0,1-2.6,2.566h0V481.32h0a2.6,2.6,0,0,1,2.6,2.566Z" transform="translate(249.859 -60.983)" opacity="0.1"/>
|
||||
<path id="Path_438" data-name="Path 438" d="M713.414,319.15H598.1v96.311l115.314,2.6Z" transform="translate(394.451 108.164)" fill="#d4dfec"/>
|
||||
<path id="Path_439" data-name="Path 439" d="M593.3,483.886V593.158a2.6,2.6,0,0,1-2.5,2.566l-124.506,3.313h0a2.6,2.6,0,0,1-2.6-2.566V483.886a2.6,2.6,0,0,1,2.566-2.566H590.73A2.6,2.6,0,0,1,593.3,483.886Z" transform="translate(389.997 -60.983)" fill="#65617d"/>
|
||||
<path id="Path_440" data-name="Path 440" d="M397.1,319.15H512.414v96.311L397.1,418.06Z" transform="translate(464.87 108.164)" fill="#d4dfec"/>
|
||||
<path id="Path_441" data-name="Path 441" d="M597.889,533.65V542.1l-98.7.909-1.052.02-37.511.344-.9.013-11.4.1h-.338l-69.5.643-114.086,1.059h-.1l-29.007.273-.312.007L106.1,546.643V536.9l131.562-.864h.312l26.629-.182h.123l183.9-1.215h.188l.11-.006h.305l3.112-.019h.948l48.393-.325,2.858-.013.637-.007Z" transform="translate(566.82 33.015)" opacity="0.1"/>
|
||||
<circle id="Ellipse_99" data-name="Ellipse 99" cx="33.347" cy="33.347" r="33.347" transform="translate(887.196 363.44)" fill="#fbbebe"/>
|
||||
<path id="Path_442" data-name="Path 442" d="M383.74,592.973a.806.806,0,0,1,.065-.227c.65-2.332,1.806-6.276,2.67-9.427.11-.39.214-.773.312-1.137.955-3.554,2.813-11.876,3.846-17.066.364-1.826.65-3.248.708-3.963.13-1.1,2.085-5.795,4.3-11.194,1.553-3.794,3.248-7.939,4.489-11.434a39.876,39.876,0,0,0,1.9-6.4,3.313,3.313,0,0,0,0-1.384,9.543,9.543,0,0,1-.312-2.274h0c-.149-4.028,1.052-10.394,2.91-13.065,2.274-3.248,3.9-15.371,3.9-15.371s3.365.487,3.9-2.293a4,4,0,0,0,.065-.91,4.815,4.815,0,0,0-.058-.721c-.578-4.054,9.563-8.368,15.267-9.3a12.769,12.769,0,0,1,1.949-.182,17.694,17.694,0,0,0,4.06-.935q.909-.279,1.949-.65c2.391-.806,5.249-1.871,8.283-3.04,11.408-4.424,25.22-10.394,25.22-10.394.1-.461.208-.9.331-1.338a18,18,0,0,1,10.622-12.3h0v-1.3c0-2.241-.1-4.548-.195-6.847,0-.448-.039-.9-.065-1.345v-.734c0-.455-.046-.91-.071-1.358l-.117-.091a10.66,10.66,0,0,1-2.534-2.729,9.916,9.916,0,0,1-.923-1.949c-.812-2.2-1-4.658-1.546-6.984l-.117-.481a54.579,54.579,0,0,0-2.6-6.873c-1.436-3.521-2.54-7.146-3.638-10.817q-1-3.333-2.02-6.678l-1.026-3.4c-.195-.65-.39-1.3-.539-1.949h0a8.627,8.627,0,0,1-.169-.91h0a6.5,6.5,0,0,1,.156-2.995,20.854,20.854,0,0,1,.858-1.949,5.113,5.113,0,0,0,.325-.9h0a1.947,1.947,0,0,0,.052-.208,3.667,3.667,0,0,0,.071-.7,11.993,11.993,0,0,0-.338-2.527.045.045,0,0,1,0-.032,1.661,1.661,0,0,0-.032-.175,16.564,16.564,0,0,1-.234-1.949,18.483,18.483,0,0,1,2.254-10.115l.123-.234a16.383,16.383,0,0,1,1.2-1.806h0a9.122,9.122,0,0,1,.65-.793,2.594,2.594,0,0,1-3.606-.936,2.475,2.475,0,0,1-.149-.292c-.033-.078-.059-.162-.085-.247-.455-1.592.338-3.287,1.377-4.58a12.012,12.012,0,0,1,3.729-3.053h0a2.51,2.51,0,0,1,.26-.136,11.125,11.125,0,0,1,1.715-.689h0a9.781,9.781,0,0,1,1.059-.286l-2.41-.221-.974-.091A9.414,9.414,0,0,1,479,361.527h.7a7.625,7.625,0,0,1,1.02.084A2.885,2.885,0,0,1,479,358.8a9.362,9.362,0,0,0,5.97,1.988h.247a19.494,19.494,0,0,1-.221-2.124h0a21.545,21.545,0,0,1,0-2.9,5.483,5.483,0,0,0,4.4,1.468l-1.351-1.884a6.165,6.165,0,0,0,4.262,1.332l.312-.033a4.547,4.547,0,0,0,.773-.149h.058c-.123-.156-.234-.318-.344-.487l-.039-.078a6.282,6.282,0,0,1-.65-1.358,4.284,4.284,0,0,1-.156-.565h0a5.684,5.684,0,0,1,.292-3.541c2.1,1.754,4.4,4.151,7.146,2.339s2.839-5.139,7.146-4.8a6.626,6.626,0,0,1,5.464,3.976,4.385,4.385,0,0,1,4.983-1.137,4.45,4.45,0,0,1,2.521,4.476c1.786-.448,3.71-.89,5.425-.214s2.82,2.865,1.845,4.327a.931.931,0,0,1-.091.13c-.8,1.033-2.566,1.6-2.378,2.9l.273.195.845.611q4.97,3.586,9.68,7.536c1.611,1.351,3.326,2.975,3.437,5.087a5.617,5.617,0,0,1-.156,1.54h0c-.1.507-.247,1.013-.357,1.52a12.676,12.676,0,0,0-.221,3.93h0c.045.383.1.76.175,1.143.994,5.022,5.009,9.167,5.405,14.292v.364a11.546,11.546,0,0,1-.045,1.644c-.3,3.118-1.741,6.113-2.91,9.1-.072.182-.143.364-.208.546a90.5,90.5,0,0,0-4.548,18.294,65.14,65.14,0,0,1-1.3,6.555,17.365,17.365,0,0,1-3.307,6.776,10.536,10.536,0,0,1-2.183,1.949,12.132,12.132,0,0,1-1.722.916v.039c.071.4.13.793.2,1.189.039.208.071.416.11.65h0a.089.089,0,0,0,0,.039c.546,3.073,1.169,6.081,1.826,8.939l.331,1.423c8.186,2.956,9.154,9.011,9.212,11.33a6.259,6.259,0,0,1,0,.929s12.343.325,17.216,3.9a13.21,13.21,0,0,0,2.93,1.494,53.917,53.917,0,0,0,6.009,1.877c1.046.273,2.137.539,3.248.8,1.949.448,4.015.884,6.009,1.3,5.321,1.072,10.33,1.949,12.87,2.6,6.061,1.533,19.925,14.825,19.925,14.825s1.624,13.409,3.248,15.364,4.548,8.829,4.548,8.829l10.745,19.106,4.548,8.036h2.923a4.97,4.97,0,0,1,2.085,1.364c.773.689,1.631,1.546,2.384,2.326l1.247,1.332.78.864s4.723.117,6,3.852c3.846-.11,8.569.143,11.876,1.624a9.187,9.187,0,0,1,1.3.7,5.639,5.639,0,0,1,2.683,3.677,5.422,5.422,0,0,1,.117.935c0,.37,0,.741,0,1.1-.234,4.918-3.437,9.011-6.38,11.765l.208.37s-.1.234-.286.65c-.052.123-.117.26-.188.416h0c-.383.819-.994,2.046-1.793,3.482a41.136,41.136,0,0,1-6.217,8.556l-.188.195-37.511.344-.11-.13-7.276-8.679-3.619-4.314-.11-.136-.1,1.033-.351,3.443-.845,8.322-.045.416v.11l-.1.961.071-.961h-.188l-69.513.65-114.092,1.26v.546h-.091v-.546l-29.072.312Z" transform="translate(418.007 -14.269)" fill="url(#linear-gradient)"/>
|
||||
<path id="Path_443" data-name="Path 443" d="M586.185,511.522s-64.641,7.8-58.469,0c2.235-2.82,2.852-11.174,2.729-20.691-.039-2.891-.143-5.886-.292-8.868-.715-14.292-2.436-28.26-2.436-28.26s56.52-26.636,50.023-5.522c-2.6,8.53-1.748,20.51.227,31.755.578,3.281,1.247,6.5,1.949,9.55a223.892,223.892,0,0,0,6.269,22.036Z" transform="translate(367.711 -46.521)" fill="#fbbebe"/>
|
||||
<path id="Path_444" data-name="Path 444" d="M586.185,537.354s-64.641,7.8-58.469,0c2.235-2.82,2.852-11.174,2.729-20.692,9.855-4.21,22.394-1.072,22.394-1.072,13.2-2.631,21.666-2.118,27.078-.273A223.888,223.888,0,0,0,586.185,537.354Z" transform="translate(367.711 -72.353)" opacity="0.1"/>
|
||||
<path id="Path_445" data-name="Path 445" d="M413.346,651.973v-.546l114.092-1.241,69.513-.65h.195l-.071.961.1-.961h.123v-.11l.89-8.738.338-3.307.1-1.033.091-.864,4.015-13.389,4.015-13.37,3.378-11.258,5.158-17.2s-2.923-6.821-4.548-8.77-3.248-15.267-3.248-15.267-13.864-13.208-19.925-14.728c-2.54-.65-7.555-1.514-12.883-2.6-1.994-.4-4.041-.832-6.009-1.3-1.111-.253-2.2-.52-3.248-.786a57.086,57.086,0,0,1-6.009-1.858,13.448,13.448,0,0,1-2.924-1.488c-4.775-3.5-16.742-3.9-17.2-3.9h0a.269.269,0,0,1,0-.045v-.058a6.66,6.66,0,0,0,0-.819,5.208,5.208,0,0,0-.059-.91.82.82,0,0,0-.045-.325c-.032-.234-.071-.481-.13-.741a3.226,3.226,0,0,0-.078-.344,11.181,11.181,0,0,0-.812-2.267c-.045-.1-.1-.214-.156-.318a11.861,11.861,0,0,0-1.1-1.709c-.084-.11-.175-.227-.273-.338a12.721,12.721,0,0,0-1.767-1.715c-.175-.136-.357-.279-.546-.409a3.026,3.026,0,0,0-.331-.234l-.578-.377-.539-.312c-.474-.26-.974-.513-1.52-.754-.169-.078-.351-.149-.533-.221s-.468-.182-.708-.273h-.039l-.487-.195c-.37-.123-.747-.234-1.143-.344l-.6-.156-.65-.149-.65-.136c-.65-.13-1.345-.247-2.072-.338l-.741-.091-.76-.078-.786-.065-1.117-.065-1.3-.045a81.017,81.017,0,0,0-17.054,1.814,46.946,46.946,0,0,0-11.855-1.093h-.565l-.65.039h-.416l-.838.078a6.224,6.224,0,0,0-.845.1c-.279.039-.572.071-.851.117l-.429.071c-.422.071-.851.149-1.3.24q-.214.039-.429.1c-.565.123-1.13.273-1.689.442-.279.078-.559.169-.838.266-.143.039-.279.091-.416.143l-.416.149-.409.162-.487.195c-.247.1-.487.214-.728.331-.136.058-.273.123-.4.195s-.266.13-.39.2a18.03,18.03,0,0,0-4.034,2.846l-.331.318-.325.331c-.11.11-.214.227-.318.344a15.473,15.473,0,0,0-1.182,1.462c-.1.136-.2.273-.3.416a17.335,17.335,0,0,0-1.117,1.9c-.084.156-.162.318-.24.474a21.243,21.243,0,0,0-1.3,3.411q-.182.65-.331,1.3s-13.753,6.068-25.161,10.46c-3.034,1.169-5.892,2.228-8.283,3.027-.7.234-1.345.448-1.949.65a17.347,17.347,0,0,1-4.06.935,12.778,12.778,0,0,0-1.949.182c-5.243.851-14.227,4.548-15.208,8.257a2.4,2.4,0,0,0-.065.91v.071c.65,4.548-3.9,3.9-3.9,3.9s-1.624,12.019-3.9,15.267c-1.644,2.339-2.768,7.555-2.91,11.492h0a12.741,12.741,0,0,0,.286,3.658v.091c.26.871-.364,3.248-1.423,6.3-1.15,3.346-2.826,7.5-4.437,11.427-2.43,5.925-4.716,11.3-4.859,12.486-.058.526-.227,1.468-.461,2.683-.948,4.95-3.053,14.377-4.093,18.19-.773,2.845-1.9,6.743-2.67,9.42a.91.91,0,0,1-.065.227l29.124-.286v.546h.091Z" transform="translate(417.856 -73.042)" fill="#00ddf1"/>
|
||||
<path id="Path_446" data-name="Path 446" d="M445.44,607.57s37.03,19.165,50.348,16.891Z" transform="translate(396.391 -105.214)" opacity="0.1"/>
|
||||
<path id="Path_447" data-name="Path 447" d="M561.44,595.57s36.056,11.694,48.074,13.318S561.44,595.57,561.44,595.57Z" transform="translate(355.751 -101.01)" opacity="0.1"/>
|
||||
<path id="Path_448" data-name="Path 448" d="M585.443,530.686c-18.84,3.469-54.311,2.722-69.643,2.2,5.62-20.139,33.022-13.292,33.022-13.292C582.74,512.821,585.339,526.866,585.443,530.686Z" transform="translate(371.74 -73.754)" opacity="0.1"/>
|
||||
<path id="Path_449" data-name="Path 449" d="M585.443,528.686c-18.84,3.469-54.311,2.722-69.643,2.2,5.62-20.139,33.022-13.292,33.022-13.292C582.74,510.821,585.339,524.866,585.443,528.686Z" transform="translate(371.74 -73.053)" fill="#00ddf1"/>
|
||||
<path id="Path_450" data-name="Path 450" d="M776.94,672.008s22.192-3.411,22.628,6.334S788.25,694.9,788.25,694.9Z" transform="translate(280.251 -127.587)" fill="#fbbebe"/>
|
||||
<path id="Path_451" data-name="Path 451" d="M578.17,479.936a30.558,30.558,0,0,1-2.923,1.13c-7,2.417-14.065,4.853-21.439,5.736s-15.111.1-21.543-3.573a18.685,18.685,0,0,1-1.949-1.267c-.715-14.292-2.436-28.26-2.436-28.26s56.52-26.636,50.023-5.522C575.318,456.711,576.195,468.69,578.17,479.936Z" transform="translate(367.508 -46.521)" opacity="0.1"/>
|
||||
<path id="Path_452" data-name="Path 452" d="M536.9,360.29a6.153,6.153,0,0,1-5.405-1.137l1.351,1.878a5.5,5.5,0,0,1-4.4-1.462,22.146,22.146,0,0,0,.208,5.035,9.433,9.433,0,0,1-6.211-1.949,2.872,2.872,0,0,0,1.715,2.8,9.316,9.316,0,0,0-10.135,6.958l3.391.312a12,12,0,0,0-6.782,4.138c-1.039,1.3-1.832,2.962-1.377,4.548s2.651,2.6,3.846,1.468a18.151,18.151,0,0,0-3.963,14.806,9.4,9.4,0,0,1,.3,3.417,28.993,28.993,0,0,1-1.228,3.047c-.65,1.9-.032,3.956.546,5.847l3.047,10.018c1.1,3.619,2.2,7.244,3.645,10.745a53.328,53.328,0,0,1,2.6,6.828c.819,3.144.851,6.588,2.6,9.336a12.948,12.948,0,0,0,4.665,4.112c6.425,3.677,14.188,4.457,21.542,3.573s14.409-3.32,21.406-5.736a20.141,20.141,0,0,0,4.444-1.949c4.84-3.17,5.918-9.654,6.821-15.364a87.242,87.242,0,0,1,4.736-18.71c1.416-3.567,3.248-7.185,2.936-11.005-.4-5.074-4.411-9.186-5.405-14.175a12.344,12.344,0,0,1,.045-5.048,11.473,11.473,0,0,0,.513-3.053c-.11-2.092-1.826-3.7-3.437-5.048q-5.243-4.379-10.823-8.277c-.188-1.3,1.579-1.858,2.378-2.884,1.13-1.449-.039-3.748-1.754-4.424s-3.638-.234-5.425.214a4.281,4.281,0,0,0-7.5-3.32,6.627,6.627,0,0,0-5.47-3.95c-4.307-.338-4.392,2.956-7.146,4.768s-5.028-.578-7.146-2.319A6.029,6.029,0,0,0,536.9,360.29Z" transform="translate(374.5 -15.625)" fill="#3f3d56"/>
|
||||
<path id="Path_453" data-name="Path 453" d="M768.171,669.128s-.578,1.377-1.644,3.372a44.83,44.83,0,0,1-5.847,8.556l-.175.188h-1.052l-37.511.344h-.9l-.11-.13-7.276-8.634-2.741-3.255-.11-.136-.234-.273-.13-.156-.169-.2-5.334-6.328,1.949-8.121,3.32-13.682.084-.331.65-2.6.461-1.949,1.546-6.354,5.3-21.861,9.42,4.872,9.933,17.541,5.334,9.42h2.923c.552,0,1.819,1.065,3.092,2.293.455.435.91.9,1.3,1.3,1.156,1.2,2.072,2.222,2.072,2.222s4.736.1,6.009,3.833a4.537,4.537,0,0,1,.162.552,26.191,26.191,0,0,0,1.644,4.262c.838,1.826,1.891,3.9,2.956,5.97,1.981,3.8,4,7.419,4.736,8.731l.091.162Z" transform="translate(305.476 -105.217)" fill="#00ddf1"/>
|
||||
<path id="Path_454" data-name="Path 454" d="M615.681,653.093l-.065.929v.065l-.11,1.494-.864,12.025L613.6,682.093l-.3,4.08-.65,8.731v.11l-69.513.65L429.061,696.9v.546h-.091v-.728l.292-9.329.559-18.229L430.3,653.7l.045-1.54a6.841,6.841,0,0,1,7.419-6.607c3.066.26,7.049.578,11.811.916l1.949.143c27.5,1.91,78.322,4.379,129.606,1.3,1.078-.059,2.163-.123,3.248-.2,7.952-.5,15.9-1.143,23.764-1.949a6.841,6.841,0,0,1,7.536,7.3Z" transform="translate(402.161 -118.514)" opacity="0.1"/>
|
||||
<path id="Path_455" data-name="Path 455" d="M429.08,698.857v-.546l114.092-1.241,69.513-.65h.195v-.11l.65-8.731.279-3.9,1.065-14.689.838-11.629v-.286a6.893,6.893,0,0,0-.071-1.6.2.2,0,0,0,0-.065,6.751,6.751,0,0,0-1.3-3.014,6.847,6.847,0,0,0-6.12-2.6c-7.867.806-15.813,1.449-23.764,1.949-1.085.078-2.17.143-3.248.2-51.284,3.047-102.106.578-129.606-1.3l-1.949-.143c-4.762-.338-8.744-.65-11.811-.916a6.847,6.847,0,0,0-7.3,5.542,7.146,7.146,0,0,0-.117,1.065l-.442,14.4-.6,18.236-.286,9.329v.728Z" transform="translate(402.122 -119.926)" fill="#3f3d56"/>
|
||||
<path id="Path_456" data-name="Path 456" d="M615.925,661.692l-.8,9.959L613.937,686.5l-.3,3.716-.65,8.322-.039.416v.11h0l-.1.961.071-.961h-.188l-69.513.65L429.11,700.944v-.182l.318-9.329.65-18.229.416-12.116a4.132,4.132,0,0,1,1.988-3.411,4.061,4.061,0,0,1,2.456-.585c1.364.11,3.04.24,5,.383,3.177.234,7.094.507,11.655.793,8.445.533,19.041,1.1,31.183,1.572,33.782,1.3,79.388,1.735,122.707-2.059,1.949-.169,3.963-.357,5.931-.546a4.066,4.066,0,0,1,2.261.429,3.346,3.346,0,0,1,.559.351,3.989,3.989,0,0,1,1.3,1.6,2.315,2.315,0,0,1,.162.4A4.08,4.08,0,0,1,615.925,661.692Z" transform="translate(402.112 -122.559)" opacity="0.1"/>
|
||||
<path id="Path_457" data-name="Path 457" d="M360.914,677.87v2.072a3.982,3.982,0,0,1-2.274,3.6l-26.051.5a3.976,3.976,0,0,1-2.319-3.619v-1.949Z" transform="translate(436.74 -129.843)" opacity="0.1"/>
|
||||
<path id="Path_458" data-name="Path 458" d="M280.294,512.532l-33.944.65V507.55l33.944-.65Z" transform="translate(517.685 42.387)" fill="#3f3d56"/>
|
||||
<path id="Path_459" data-name="Path 459" d="M280.294,512.532l-33.944.65V507.55l33.944-.65Z" transform="translate(517.685 42.387)" opacity="0.1"/>
|
||||
<path id="Path_460" data-name="Path 460" d="M282.294,512.532l-33.944.65V507.55l33.944-.65Z" transform="translate(516.984 42.387)" fill="#3f3d56"/>
|
||||
<path id="Path_461" data-name="Path 461" d="M552.228,360.039A6.334,6.334,0,0,1,551,357.22a6.165,6.165,0,0,0,.273,2.988A5.847,5.847,0,0,0,552.228,360.039Z" transform="translate(359.434 -17.505)"/>
|
||||
<path id="Path_462" data-name="Path 462" d="M540.1,366.9a22.83,22.83,0,0,1-.24-2.4c0,.8,0,1.6,0,2.41Z" transform="translate(363.311 -20.056)"/>
|
||||
<path id="Path_463" data-name="Path 463" d="M621.706,398.627a11.912,11.912,0,0,1,.234-1.722,11.7,11.7,0,0,0,.52-3.047,4.008,4.008,0,0,0-.1-.689c-.11.6-.286,1.2-.416,1.787a12.433,12.433,0,0,0-.234,3.671Z" transform="translate(334.652 -30.1)"/>
|
||||
<path id="Path_464" data-name="Path 464" d="M600.922,368.894l.708.513a12.8,12.8,0,0,0,1.67-1.442,2.384,2.384,0,0,0,.247-2.365,1.95,1.95,0,0,1-.247.416C602.5,367.042,600.734,367.607,600.922,368.894Z" transform="translate(341.923 -20.441)"/>
|
||||
<path id="Path_465" data-name="Path 465" d="M518.455,380.29a8,8,0,0,0-.565,1.481,11.8,11.8,0,0,1,3.248-1.228Z" transform="translate(371.008 -25.587)"/>
|
||||
<path id="Path_466" data-name="Path 466" d="M531.888,368.979a9.637,9.637,0,0,1-1.221-.8,2.758,2.758,0,0,0,.058.767,7.7,7.7,0,0,1,1.163.033Z" transform="translate(366.534 -21.345)"/>
|
||||
<path id="Path_467" data-name="Path 467" d="M587.579,428.33c-.266,3.151-1.735,6.185-2.923,9.173a87.2,87.2,0,0,0-4.736,18.717c-.9,5.71-1.949,12.194-6.821,15.358a19.488,19.488,0,0,1-4.437,1.949c-7,2.423-14.065,4.853-21.439,5.736s-15.111.11-21.543-3.567a12.993,12.993,0,0,1-4.664-4.112c-1.735-2.748-1.767-6.191-2.6-9.336a53.424,53.424,0,0,0-2.6-6.834c-1.436-3.5-2.54-7.146-3.638-10.739q-1.527-5.009-3.047-10.024a18.716,18.716,0,0,1-.741-3.086,11.644,11.644,0,0,0,.741,5.035q1.52,5.022,3.047,10.024c1.1,3.619,2.2,7.244,3.638,10.739a53.417,53.417,0,0,1,2.6,6.834c.819,3.144.851,6.587,2.6,9.336a12.993,12.993,0,0,0,4.664,4.112c6.432,3.677,14.188,4.45,21.543,3.567s14.409-3.313,21.439-5.736a19.487,19.487,0,0,0,4.437-1.949c4.84-3.164,5.918-9.647,6.821-15.358a87.2,87.2,0,0,1,4.736-18.717c1.416-3.56,3.248-7.179,2.936-11.005C587.592,428.408,587.579,428.369,587.579,428.33Z" transform="translate(374.359 -42.418)"/>
|
||||
<path id="Path_468" data-name="Path 468" d="M510.318,421.419v-.071a9.1,9.1,0,0,0-.3-3.411,17.272,17.272,0,0,1-.253-2.157,17.769,17.769,0,0,0,.253,4.106C510.123,420.393,510.24,420.906,510.318,421.419Z" transform="translate(373.864 -38.021)"/>
|
||||
<path id="Path_469" data-name="Path 469" d="M512.452,397.929a18.02,18.02,0,0,1,1.7-2.384c-1.189,1.137-3.385.123-3.846-1.455a3.788,3.788,0,0,0,0,1.949,2.852,2.852,0,0,0,2.15,1.89Z" transform="translate(373.712 -30.422)"/>
|
||||
<path id="Path_470" data-name="Path 470" d="M762.946,656.57s2.059,7.471,0,13.968S762.946,656.57,762.946,656.57Z" transform="translate(285.475 -122.381)" opacity="0.1"/>
|
||||
<path id="Path_471" data-name="Path 471" d="M776,668.3s.864,9.485-1.085,11.168S776,668.3,776,668.3Z" transform="translate(281.189 -126.49)" opacity="0.1"/>
|
||||
<line id="Line_88" data-name="Line 88" y1="0.65" x2="66.011" transform="translate(733.267 442.068)" fill="none" stroke="#00ddf1" stroke-miterlimit="10" stroke-width="2"/>
|
||||
<line id="Line_89" data-name="Line 89" y1="0.643" x2="66.011" transform="translate(733.267 450.176)" fill="none" stroke="#00ddf1" stroke-miterlimit="10" stroke-width="2"/>
|
||||
<line id="Line_90" data-name="Line 90" y1="0.65" x2="66.011" transform="translate(733.267 458.277)" fill="none" stroke="#00ddf1" stroke-miterlimit="10" stroke-width="2"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="691.365" height="586.346" viewBox="0 0 691.365 586.346">
|
||||
<defs>
|
||||
<linearGradient id="linear-gradient" x1="0.5" y1="1" x2="0.5" y2="-0.001" gradientUnits="objectBoundingBox">
|
||||
<stop offset="0" stop-color="gray" stop-opacity="0.251"/>
|
||||
<stop offset="0.54" stop-color="gray" stop-opacity="0.122"/>
|
||||
<stop offset="1" stop-color="gray" stop-opacity="0.102"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g id="Group_644" data-name="Group 644" transform="translate(-588.635 -80)">
|
||||
<g id="Group_642" data-name="Group 642" transform="translate(22538.635 6999)">
|
||||
<path id="Path_472" data-name="Path 472" d="M482.764,637.675c51.612-29.22,67.336-29.693,124.954-17.169,81.646,17.758,88.43-49.93,108.306-186.933,33.946-233.98-108.306-295.737-108.306-295.737s-94.6-8.1-150.536-38.963C377.726,55.027,286.9,49.443,207.046,86.542c-51.069,23.7-93.125,56.714-100.31,102.034-20.09,126.746,12.9,196.58-48.819,237.954S48.944,581.5,96.263,577.564c34.968-2.906,80.842,52.415,130.687,60.111,17.382,2.684,88.7-13.043,99.578,0C341.327,655.4,464.763,647.87,482.764,637.675Z" transform="translate(-21979.895 -6980.982)" fill="#00ddf1" opacity="0.1"/>
|
||||
<g id="Group_641" data-name="Group 641" transform="translate(-21545.08 -6816.88)">
|
||||
<rect id="Rectangle_263" data-name="Rectangle 263" width="217.867" height="15.073" transform="translate(0 39.052)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_264" data-name="Rectangle 264" width="217.867" height="15.073" transform="translate(0 104.137)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_265" data-name="Rectangle 265" width="217.867" height="15.073" transform="translate(0 171.964)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_266" data-name="Rectangle 266" width="13.702" height="39.052" transform="translate(19.183)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_267" data-name="Rectangle 267" width="13.702" height="2.74" transform="translate(19.183 6.166)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_268" data-name="Rectangle 268" width="13.702" height="2.74" transform="translate(19.183 27.405)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_269" data-name="Rectangle 269" width="13.702" height="39.052" transform="translate(39.737)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_270" data-name="Rectangle 270" width="13.702" height="2.74" transform="translate(39.737 6.166)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_271" data-name="Rectangle 271" width="13.702" height="2.74" transform="translate(39.737 27.405)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_272" data-name="Rectangle 272" width="13.702" height="39.052" transform="translate(60.29)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_273" data-name="Rectangle 273" width="13.702" height="2.74" transform="translate(60.29 6.166)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_274" data-name="Rectangle 274" width="13.702" height="2.74" transform="translate(60.29 27.405)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_275" data-name="Rectangle 275" width="13.702" height="39.052" transform="translate(80.844)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_276" data-name="Rectangle 276" width="13.702" height="2.74" transform="translate(80.844 6.166)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_277" data-name="Rectangle 277" width="13.702" height="2.74" transform="translate(80.844 27.405)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_278" data-name="Rectangle 278" width="13.702" height="39.052" transform="translate(101.397)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_279" data-name="Rectangle 279" width="13.702" height="2.74" transform="translate(101.397 6.166)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_280" data-name="Rectangle 280" width="13.702" height="2.74" transform="translate(101.397 27.405)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_281" data-name="Rectangle 281" width="13.702" height="39.052" transform="translate(121.95)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_282" data-name="Rectangle 282" width="13.702" height="2.74" transform="translate(121.95 6.166)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_283" data-name="Rectangle 283" width="13.702" height="2.74" transform="translate(121.95 27.405)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_284" data-name="Rectangle 284" width="13.702" height="39.052" transform="translate(142.504)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_285" data-name="Rectangle 285" width="13.702" height="2.74" transform="translate(142.504 6.166)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_286" data-name="Rectangle 286" width="13.702" height="2.74" transform="translate(142.504 27.405)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_287" data-name="Rectangle 287" width="13.702" height="39.052" transform="matrix(0.894, -0.448, 0.448, 0.894, 135.847, 138.025)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_288" data-name="Rectangle 288" width="13.702" height="2.74" transform="matrix(0.894, -0.448, 0.448, 0.894, 138.614, 143.541)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_289" data-name="Rectangle 289" width="13.702" height="2.74" transform="matrix(0.894, -0.448, 0.448, 0.894, 148.128, 162.529)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_290" data-name="Rectangle 290" width="13.702" height="39.052" transform="translate(19.183 65.771)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_291" data-name="Rectangle 291" width="13.702" height="2.74" transform="translate(19.183 71.937)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_292" data-name="Rectangle 292" width="13.702" height="2.74" transform="translate(19.183 93.176)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_293" data-name="Rectangle 293" width="13.702" height="39.052" transform="translate(39.737 65.771)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_294" data-name="Rectangle 294" width="13.702" height="2.74" transform="translate(39.737 71.937)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_295" data-name="Rectangle 295" width="13.702" height="2.74" transform="translate(39.737 93.176)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_296" data-name="Rectangle 296" width="13.702" height="39.052" transform="translate(60.29 65.771)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_297" data-name="Rectangle 297" width="13.702" height="2.74" transform="translate(60.29 71.937)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_298" data-name="Rectangle 298" width="13.702" height="2.74" transform="translate(60.29 93.176)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_299" data-name="Rectangle 299" width="13.702" height="39.052" transform="translate(145.93 65.771)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_300" data-name="Rectangle 300" width="13.702" height="2.74" transform="translate(145.93 71.937)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_301" data-name="Rectangle 301" width="13.702" height="2.74" transform="translate(145.93 93.176)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_302" data-name="Rectangle 302" width="13.702" height="39.052" transform="translate(166.483 65.771)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_303" data-name="Rectangle 303" width="13.702" height="2.74" transform="translate(166.483 71.937)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_304" data-name="Rectangle 304" width="13.702" height="2.74" transform="translate(166.483 93.176)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_305" data-name="Rectangle 305" width="13.702" height="39.052" transform="translate(187.036 65.771)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_306" data-name="Rectangle 306" width="13.702" height="2.74" transform="translate(187.036 71.937)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_307" data-name="Rectangle 307" width="13.702" height="2.74" transform="translate(187.036 93.176)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_308" data-name="Rectangle 308" width="13.702" height="39.052" transform="translate(19.183 132.912)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_309" data-name="Rectangle 309" width="13.702" height="2.74" transform="translate(19.183 139.078)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_310" data-name="Rectangle 310" width="13.702" height="2.74" transform="translate(19.183 160.317)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_311" data-name="Rectangle 311" width="13.702" height="39.052" transform="translate(39.737 132.912)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_312" data-name="Rectangle 312" width="13.702" height="2.74" transform="translate(39.737 139.078)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_313" data-name="Rectangle 313" width="13.702" height="2.74" transform="translate(39.737 160.317)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_314" data-name="Rectangle 314" width="13.702" height="39.052" transform="translate(60.29 132.912)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_315" data-name="Rectangle 315" width="13.702" height="2.74" transform="translate(60.29 139.078)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_316" data-name="Rectangle 316" width="13.702" height="2.74" transform="translate(60.29 160.317)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_317" data-name="Rectangle 317" width="13.702" height="39.052" transform="translate(80.844 132.912)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_318" data-name="Rectangle 318" width="13.702" height="2.74" transform="translate(80.844 139.078)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_319" data-name="Rectangle 319" width="13.702" height="2.74" transform="translate(80.844 160.317)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_320" data-name="Rectangle 320" width="13.702" height="39.052" transform="translate(101.397 132.912)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_321" data-name="Rectangle 321" width="13.702" height="2.74" transform="translate(101.397 139.078)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_322" data-name="Rectangle 322" width="13.702" height="2.74" transform="translate(101.397 160.317)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_323" data-name="Rectangle 323" width="13.702" height="39.052" transform="translate(121.95 132.912)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_324" data-name="Rectangle 324" width="13.702" height="2.74" transform="translate(121.95 139.078)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_325" data-name="Rectangle 325" width="13.702" height="2.74" transform="translate(121.95 160.317)" fill="#00ddf1" opacity="0.1"/>
|
||||
<path id="Path_473" data-name="Path 473" d="M797.862,159.09v35.571h-13.7V156.48Q791.039,157.693,797.862,159.09Z" transform="translate(-621.103 -155.61)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_326" data-name="Rectangle 326" width="13.702" height="2.74" transform="translate(163.057 6.166)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_327" data-name="Rectangle 327" width="13.702" height="2.74" transform="translate(163.057 27.405)" fill="#00ddf1" opacity="0.1"/>
|
||||
<path id="Path_474" data-name="Path 474" d="M827.862,165.719v30.83h-13.7V162.43c1.781.391,3.556.8,5.33,1.22Q823.7,164.636,827.862,165.719Z" transform="translate(-630.549 -157.483)" fill="#00ddf1" opacity="0.1"/>
|
||||
<path id="Path_475" data-name="Path 475" d="M827.862,166.285v.685h-13.7v-2.74h5.33Q823.7,165.2,827.862,166.285Z" transform="translate(-630.549 -158.05)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_328" data-name="Rectangle 328" width="13.702" height="2.74" transform="translate(183.611 27.405)" fill="#00ddf1" opacity="0.1"/>
|
||||
<path id="Path_476" data-name="Path 476" d="M691.358,287.276s-4.152-1.411-6.166,2.254a6.68,6.68,0,0,0,.089,6.426,9.4,9.4,0,0,0,9.146,5.179c4.35-.233,5.8-3.549,6.166-6.639a6.851,6.851,0,0,0-9.057-7.3Z" transform="translate(-589.693 -196.649)" fill="#00ddf1"/>
|
||||
<path id="Path_477" data-name="Path 477" d="M686.2,296.239a6.68,6.68,0,0,1-.089-6.426c2-3.659,6.166-2.254,6.166-2.254l.212-.075a6.762,6.762,0,0,1,4.885.11,6.762,6.762,0,0,0-5.762-.548l-.212.075s-4.152-1.411-6.166,2.254a6.68,6.68,0,0,0,.089,6.426,9.982,9.982,0,0,0,4.7,4.446A10.277,10.277,0,0,1,686.2,296.239Z" transform="translate(-589.706 -196.595)" fill="#00ddf1" opacity="0.1"/>
|
||||
<path id="Path_478" data-name="Path 478" d="M691.42,290.06s1.542,1.213,5.282,0" transform="translate(-591.9 -197.672)" fill="#00ddf1" opacity="0.2"/>
|
||||
<path id="Path_479" data-name="Path 479" d="M690.93,277.21s2.638,7.153,2.418,9.023l2.09-.219s-3.521-6.269-2.638-8.8Z" transform="translate(-591.746 -193.626)" fill="#00ddf1"/>
|
||||
<path id="Path_480" data-name="Path 480" d="M690.93,277.21s2.638,7.153,2.418,9.023l2.09-.219s-3.521-6.269-2.638-8.8Z" transform="translate(-591.746 -193.626)" fill="#00ddf1" opacity="0.2"/>
|
||||
<path id="Path_481" data-name="Path 481" d="M702.92,277.6s-4.159-2.124-6.015-.37-2.144,6.241-2.144,6.241S701.536,284.391,702.92,277.6Z" transform="translate(-592.952 -193.414)" fill="#00ddf1"/>
|
||||
<path id="Path_482" data-name="Path 482" d="M702.92,277.6s-4.159-2.124-6.015-.37-2.144,6.241-2.144,6.241S701.536,284.391,702.92,277.6Z" transform="translate(-592.952 -193.414)" fill="#00ddf1" opacity="0.1"/>
|
||||
<path id="Path_483" data-name="Path 483" d="M694.75,283.958s1.089-5.207,8.16-5.878" transform="translate(-592.949 -193.9)" fill="#00ddf1"/>
|
||||
<path id="Path_484" data-name="Path 484" d="M819.358,386.276s-4.152-1.411-6.166,2.254a6.68,6.68,0,0,0,.089,6.426,9.4,9.4,0,0,0,9.146,5.18c4.35-.233,5.8-3.549,6.166-6.639a6.851,6.851,0,0,0-9.057-7.3Z" transform="translate(-629.999 -227.823)" fill="#00ddf1"/>
|
||||
<path id="Path_485" data-name="Path 485" d="M814.2,395.239a6.68,6.68,0,0,1-.089-6.426c2-3.659,6.166-2.254,6.166-2.254l.212-.075a6.762,6.762,0,0,1,4.885.11,6.762,6.762,0,0,0-5.762-.548l-.212.075s-4.152-1.411-6.166,2.254a6.68,6.68,0,0,0,.089,6.426,9.982,9.982,0,0,0,4.7,4.446A10.276,10.276,0,0,1,814.2,395.239Z" transform="translate(-630.011 -227.769)" fill="#00ddf1" opacity="0.1"/>
|
||||
<path id="Path_486" data-name="Path 486" d="M819.42,389.06s1.542,1.213,5.282,0" transform="translate(-632.206 -228.846)" fill="#00ddf1" opacity="0.2"/>
|
||||
<path id="Path_487" data-name="Path 487" d="M818.93,376.21s2.638,7.153,2.419,9.023l2.09-.219s-3.522-6.269-2.638-8.8Z" transform="translate(-632.051 -224.799)" fill="#00ddf1"/>
|
||||
<path id="Path_488" data-name="Path 488" d="M818.93,376.21s2.638,7.153,2.419,9.023l2.09-.219s-3.522-6.269-2.638-8.8Z" transform="translate(-632.051 -224.799)" fill="#00ddf1" opacity="0.2"/>
|
||||
<path id="Path_489" data-name="Path 489" d="M830.92,376.6s-4.159-2.124-6.015-.37-2.144,6.241-2.144,6.241S829.536,383.391,830.92,376.6Z" transform="translate(-633.257 -224.588)" fill="#00ddf1"/>
|
||||
<path id="Path_490" data-name="Path 490" d="M830.92,376.6s-4.159-2.124-6.015-.37-2.144,6.241-2.144,6.241S829.536,383.391,830.92,376.6Z" transform="translate(-633.257 -224.588)" fill="#00ddf1" opacity="0.1"/>
|
||||
<path id="Path_491" data-name="Path 491" d="M822.75,382.958s1.089-5.207,8.16-5.878" transform="translate(-633.254 -225.073)" fill="#00ddf1"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="Group_643" data-name="Group 643" transform="translate(7 -63)">
|
||||
<g id="Group_639" data-name="Group 639" transform="translate(21233 8372)">
|
||||
<ellipse id="Ellipse_73" data-name="Ellipse 73" cx="49.851" cy="8.007" rx="49.851" ry="8.007" transform="translate(-20118.443 -7772.555)" fill="#00abf6" opacity="0.1"/>
|
||||
<rect id="Rectangle_182" data-name="Rectangle 182" width="3.208" height="72.224" transform="translate(-20070.199 -7917.934)" fill="#535461"/>
|
||||
<path id="Path_268" data-name="Path 268" d="M1025.906,587.251l-.2,3.254-.287,4.6-.118,1.913-.281,4.6-.123,1.913-.286,4.6-3.233,52.22a8.851,8.851,0,0,1-8.83,8.268H995.731a8.841,8.841,0,0,1-8.82-8.3l-3.244-52.225-.281-4.6-.117-1.913-.292-4.6-.117-1.913-.281-4.6-.2-3.254a4.6,4.6,0,0,1,4.574-4.865h34.391a4.6,4.6,0,0,1,4.569,4.9Z" transform="translate(-21072.734 -8433.181)" fill="#565661"/>
|
||||
<path id="Path_269" data-name="Path 269" d="M990.949,517.36l-.287,4.589H948.106l-.286-4.589Z" transform="translate(-21037.977 -8360.036)" fill="#9d9cb5"/>
|
||||
<path id="Path_270" data-name="Path 270" d="M990.93,530.08l-.281,4.594H948.9l-.286-4.594Z" transform="translate(-21038.363 -8366.248)" fill="#9d9cb5"/>
|
||||
<path id="Path_271" data-name="Path 271" d="M990.912,542.8l-.287,4.589H949.686L949.4,542.8Z" transform="translate(-21038.748 -8372.46)" fill="#9d9cb5"/>
|
||||
<path id="Path_272" data-name="Path 272" d="M933.875,490.464c18.684,13.517,26.466,34.656,26.466,34.656s-22.511-.773-41.2-14.289-26.46-34.651-26.46-34.651S915.2,476.953,933.875,490.464Z" transform="translate(-21028.934 -8381.328)" fill="#00abf6"/>
|
||||
<path id="Path_273" data-name="Path 273" d="M892.68,476.18s23.365,12.969,30.865,23.708,36.8,25.233,36.8,25.233" transform="translate(-21028.934 -8381.328)" fill="none" stroke="#535461" stroke-miterlimit="10" stroke-width="2"/>
|
||||
<path id="Path_274" data-name="Path 274" d="M975.393,443.457c10.171,7.352,14.407,18.863,14.407,18.863s-12.279-.425-22.424-7.776S953,435.68,953,435.68,965.227,436.1,975.393,443.457Z" transform="translate(-21058.393 -8361.549)" fill="#00abf6"/>
|
||||
<path id="Path_275" data-name="Path 275" d="M953,435.68s12.719,7.06,16.8,12.9,20.03,13.737,20.03,13.737" transform="translate(-21058.393 -8361.549)" fill="none" stroke="#535461" stroke-miterlimit="10" stroke-width="2"/>
|
||||
<path id="Path_276" data-name="Path 276" d="M1040.3,470.483c-11.68,12.836-14.151,29.612-14.151,29.612s16.469-4.042,28.139-16.883,14.151-29.612,14.151-29.612S1051.981,457.647,1040.3,470.483Z" transform="translate(-21094.119 -8370.301)" fill="#00abf6"/>
|
||||
<path id="Path_277" data-name="Path 277" d="M1068.45,453.61s-15.215,13.159-19.083,22.224S1026.15,500.1,1026.15,500.1" transform="translate(-21094.119 -8370.306)" fill="none" stroke="#535461" stroke-miterlimit="10" stroke-width="2"/>
|
||||
</g>
|
||||
<g id="Group_638" data-name="Group 638" transform="translate(-40 36)">
|
||||
<rect id="Rectangle_254" data-name="Rectangle 254" width="12.343" height="37.459" transform="translate(741.459 392.675)" fill="#3f3d56"/>
|
||||
<rect id="Rectangle_255" data-name="Rectangle 255" width="12.343" height="39.629" transform="translate(1073.647 390.726)" fill="#3f3d56"/>
|
||||
<path id="Path_418" data-name="Path 418" d="M597.889,523.665v8.446l-98.7.909-1.052.02-37.511.344-.9.013-11.4.1h-.338l-69.5.643L264.408,535.2h-.1l-29.007.273-.312.007L106.1,536.658v-9.745l30.306-18.19,60.808-.507,1.3-.007.429-.007,1.3-.019,29.2-.24,1.306-.007.416-.007,1.3-.006,9.362-.078.247-.006,23.1-.195h.188l180.214-1.52,4.411-.032h.442l3.365-.033,24.459-.2,1.3-.02,1.2-.013h.624l7.607-.058,1.507-.02,2.02-.013,6.334-.058,10.05-.084,1.3-.013,2.261-.013h.422l48.536-.416Z" transform="translate(566.82 43)" fill="#65617d"/>
|
||||
<path id="Path_419" data-name="Path 419" d="M597.889,523.665v8.446l-98.7.909-1.052.02-37.511.344-.9.013-11.4.1h-.338l-69.5.643L264.408,535.2h-.1l-29.007.273-.312.007L106.1,536.658v-9.745l30.306-18.19,60.808-.507,1.3-.007.429-.007,1.3-.019,29.2-.24,1.306-.007.416-.007,1.3-.006,9.362-.078.247-.006,23.1-.195h.188l180.214-1.52,4.411-.032h.442l3.365-.033,24.459-.2,1.3-.02,1.2-.013h.624l7.607-.058,1.507-.02,2.02-.013,6.334-.058,10.05-.084,1.3-.013,2.261-.013h.422l48.536-.416Z" transform="translate(566.82 43)" opacity="0.2"/>
|
||||
<path id="Path_420" data-name="Path 420" d="M358.914,656.57v15.91a3.982,3.982,0,0,1-2.274,3.6,3.9,3.9,0,0,1-1.624.39l-22.673.481a3.9,3.9,0,0,1-1.754-.37,3.976,3.976,0,0,1-2.319-3.619V656.57Z" transform="translate(437.441 -122.381)" fill="#3f3d56"/>
|
||||
<path id="Path_421" data-name="Path 421" d="M358.914,656.57v15.91a3.982,3.982,0,0,1-2.274,3.6,3.9,3.9,0,0,1-1.624.39l-22.673.481a3.9,3.9,0,0,1-1.754-.37,3.976,3.976,0,0,1-2.319-3.619V656.57Z" transform="translate(437.441 -122.381)" opacity="0.1"/>
|
||||
<path id="Path_422" data-name="Path 422" d="M360.914,656.57v15.91a3.982,3.982,0,0,1-2.274,3.6,3.9,3.9,0,0,1-1.624.39l-22.673.481a3.9,3.9,0,0,1-1.754-.37,3.976,3.976,0,0,1-2.319-3.619V656.57Z" transform="translate(436.74 -122.381)" fill="#3f3d56"/>
|
||||
<rect id="Rectangle_256" data-name="Rectangle 256" width="30.644" height="20.464" transform="translate(1046.362 534.189)" fill="#3f3d56"/>
|
||||
<rect id="Rectangle_257" data-name="Rectangle 257" width="30.644" height="20.464" transform="translate(1046.362 534.189)" opacity="0.1"/>
|
||||
<rect id="Rectangle_258" data-name="Rectangle 258" width="30.644" height="20.464" transform="translate(1045.062 534.189)" fill="#3f3d56"/>
|
||||
<path id="Path_423" data-name="Path 423" d="M223.943,265.82v3.723l-12.343-.318v-3.4Z" transform="translate(529.859 126.848)" opacity="0.1"/>
|
||||
<path id="Path_424" data-name="Path 424" d="M358.741,271.246,343.909,377.789a2.794,2.794,0,0,1-2.839,2.41L222.314,377.12l-2.254-.058a2.794,2.794,0,0,1-2.7-3.164l16.651-111.533a2.794,2.794,0,0,1,2.949-2.358l119.186,8.069a2.787,2.787,0,0,1,2.6,3.17Z" transform="translate(476.307 16.556)" fill="#65617d"/>
|
||||
<path id="Path_425" data-name="Path 425" d="M169.158,99.16,156.6,197.472l109.142,3.463,13.422-94.85Z" transform="translate(549.128 185.237)" fill="#00ddf1"/>
|
||||
<rect id="Rectangle_259" data-name="Rectangle 259" width="132.965" height="112.605" rx="5.33" transform="translate(853.849 282.877)" fill="#65617d"/>
|
||||
<rect id="Rectangle_260" data-name="Rectangle 260" width="120.836" height="93.765" transform="translate(860.125 290.458)" fill="#00ddf1"/>
|
||||
<path id="Path_426" data-name="Path 426" d="M735.273,262.82v2.462l-12.343.253V262.82Z" transform="translate(350.717 127.899)" opacity="0.1"/>
|
||||
<path id="Path_427" data-name="Path 427" d="M833.626,371a2,2,0,0,1-1.949,2.131l-121.615,2.514a2,2,0,0,1-2.033-1.813L697.575,265.156a2,2,0,0,1,1.9-2.189l120.693-6,1.3-.065a1.994,1.994,0,0,1,2.092,1.819Z" transform="translate(308.06 17.643)" fill="#65617d"/>
|
||||
<path id="Path_428" data-name="Path 428" d="M628.93,100.238,739.157,94.82l9.31,98.748-109.356,2.6Z" transform="translate(383.65 186.757)" fill="#00ddf1"/>
|
||||
<path id="Path_429" data-name="Path 429" d="M899.741,371a2,2,0,0,1-1.949,2.131L886.28,256.963l1.3-.065a2,2,0,0,1,2.092,1.819Z" transform="translate(241.945 17.643)" opacity="0.1"/>
|
||||
<rect id="Rectangle_261" data-name="Rectangle 261" width="12.343" height="11.096" transform="translate(741.459 419.038)" opacity="0.1"/>
|
||||
<path id="Path_431" data-name="Path 431" d="M281.574,483.65v3.229l-30.644.819V483.65Z" transform="translate(516.08 50.533)" opacity="0.1"/>
|
||||
<path id="Path_432" data-name="Path 432" d="M390.8,483.886V593.158a2.6,2.6,0,0,1-2.5,2.566l-124.506,3.313h-.033a2.6,2.6,0,0,1-2.566-2.566V483.886a2.6,2.6,0,0,1,2.566-2.566H388.23A2.6,2.6,0,0,1,390.8,483.886Z" transform="translate(460.942 -60.983)" fill="#65617d"/>
|
||||
<path id="Path_434" data-name="Path 434" d="M194.6,319.15H309.914v96.311L194.6,418.06Z" transform="translate(535.815 108.164)" fill="#d4dfec"/>
|
||||
<rect id="Rectangle_262" data-name="Rectangle 262" width="12.343" height="11.317" transform="translate(1073.647 419.038)" opacity="0.1"/>
|
||||
<path id="Path_435" data-name="Path 435" d="M709.574,483.65v4.164L678.93,487V483.65Z" transform="translate(366.132 50.533)" opacity="0.1"/>
|
||||
<path id="Path_436" data-name="Path 436" d="M797.782,483.886V596.471a2.6,2.6,0,0,1-2.6,2.566h0l-124.532-3.313a2.6,2.6,0,0,1-2.5-2.566V483.886a2.6,2.6,0,0,1,2.566-2.566H795.19a2.6,2.6,0,0,1,2.592,2.566Z" transform="translate(318.365 -60.983)" fill="#65617d"/>
|
||||
<path id="Path_437" data-name="Path 437" d="M866.289,483.886V596.471a2.6,2.6,0,0,1-2.6,2.566h0V481.32h0a2.6,2.6,0,0,1,2.6,2.566Z" transform="translate(249.859 -60.983)" opacity="0.1"/>
|
||||
<path id="Path_438" data-name="Path 438" d="M713.414,319.15H598.1v96.311l115.314,2.6Z" transform="translate(394.451 108.164)" fill="#d4dfec"/>
|
||||
<path id="Path_439" data-name="Path 439" d="M593.3,483.886V593.158a2.6,2.6,0,0,1-2.5,2.566l-124.506,3.313h0a2.6,2.6,0,0,1-2.6-2.566V483.886a2.6,2.6,0,0,1,2.566-2.566H590.73A2.6,2.6,0,0,1,593.3,483.886Z" transform="translate(389.997 -60.983)" fill="#65617d"/>
|
||||
<path id="Path_440" data-name="Path 440" d="M397.1,319.15H512.414v96.311L397.1,418.06Z" transform="translate(464.87 108.164)" fill="#d4dfec"/>
|
||||
<path id="Path_441" data-name="Path 441" d="M597.889,533.65V542.1l-98.7.909-1.052.02-37.511.344-.9.013-11.4.1h-.338l-69.5.643-114.086,1.059h-.1l-29.007.273-.312.007L106.1,546.643V536.9l131.562-.864h.312l26.629-.182h.123l183.9-1.215h.188l.11-.006h.305l3.112-.019h.948l48.393-.325,2.858-.013.637-.007Z" transform="translate(566.82 33.015)" opacity="0.1"/>
|
||||
<circle id="Ellipse_99" data-name="Ellipse 99" cx="33.347" cy="33.347" r="33.347" transform="translate(887.196 363.44)" fill="#fbbebe"/>
|
||||
<path id="Path_442" data-name="Path 442" d="M383.74,592.973a.806.806,0,0,1,.065-.227c.65-2.332,1.806-6.276,2.67-9.427.11-.39.214-.773.312-1.137.955-3.554,2.813-11.876,3.846-17.066.364-1.826.65-3.248.708-3.963.13-1.1,2.085-5.795,4.3-11.194,1.553-3.794,3.248-7.939,4.489-11.434a39.876,39.876,0,0,0,1.9-6.4,3.313,3.313,0,0,0,0-1.384,9.543,9.543,0,0,1-.312-2.274h0c-.149-4.028,1.052-10.394,2.91-13.065,2.274-3.248,3.9-15.371,3.9-15.371s3.365.487,3.9-2.293a4,4,0,0,0,.065-.91,4.815,4.815,0,0,0-.058-.721c-.578-4.054,9.563-8.368,15.267-9.3a12.769,12.769,0,0,1,1.949-.182,17.694,17.694,0,0,0,4.06-.935q.909-.279,1.949-.65c2.391-.806,5.249-1.871,8.283-3.04,11.408-4.424,25.22-10.394,25.22-10.394.1-.461.208-.9.331-1.338a18,18,0,0,1,10.622-12.3h0v-1.3c0-2.241-.1-4.548-.195-6.847,0-.448-.039-.9-.065-1.345v-.734c0-.455-.046-.91-.071-1.358l-.117-.091a10.66,10.66,0,0,1-2.534-2.729,9.916,9.916,0,0,1-.923-1.949c-.812-2.2-1-4.658-1.546-6.984l-.117-.481a54.579,54.579,0,0,0-2.6-6.873c-1.436-3.521-2.54-7.146-3.638-10.817q-1-3.333-2.02-6.678l-1.026-3.4c-.195-.65-.39-1.3-.539-1.949h0a8.627,8.627,0,0,1-.169-.91h0a6.5,6.5,0,0,1,.156-2.995,20.854,20.854,0,0,1,.858-1.949,5.113,5.113,0,0,0,.325-.9h0a1.947,1.947,0,0,0,.052-.208,3.667,3.667,0,0,0,.071-.7,11.993,11.993,0,0,0-.338-2.527.045.045,0,0,1,0-.032,1.661,1.661,0,0,0-.032-.175,16.564,16.564,0,0,1-.234-1.949,18.483,18.483,0,0,1,2.254-10.115l.123-.234a16.383,16.383,0,0,1,1.2-1.806h0a9.122,9.122,0,0,1,.65-.793,2.594,2.594,0,0,1-3.606-.936,2.475,2.475,0,0,1-.149-.292c-.033-.078-.059-.162-.085-.247-.455-1.592.338-3.287,1.377-4.58a12.012,12.012,0,0,1,3.729-3.053h0a2.51,2.51,0,0,1,.26-.136,11.125,11.125,0,0,1,1.715-.689h0a9.781,9.781,0,0,1,1.059-.286l-2.41-.221-.974-.091A9.414,9.414,0,0,1,479,361.527h.7a7.625,7.625,0,0,1,1.02.084A2.885,2.885,0,0,1,479,358.8a9.362,9.362,0,0,0,5.97,1.988h.247a19.494,19.494,0,0,1-.221-2.124h0a21.545,21.545,0,0,1,0-2.9,5.483,5.483,0,0,0,4.4,1.468l-1.351-1.884a6.165,6.165,0,0,0,4.262,1.332l.312-.033a4.547,4.547,0,0,0,.773-.149h.058c-.123-.156-.234-.318-.344-.487l-.039-.078a6.282,6.282,0,0,1-.65-1.358,4.284,4.284,0,0,1-.156-.565h0a5.684,5.684,0,0,1,.292-3.541c2.1,1.754,4.4,4.151,7.146,2.339s2.839-5.139,7.146-4.8a6.626,6.626,0,0,1,5.464,3.976,4.385,4.385,0,0,1,4.983-1.137,4.45,4.45,0,0,1,2.521,4.476c1.786-.448,3.71-.89,5.425-.214s2.82,2.865,1.845,4.327a.931.931,0,0,1-.091.13c-.8,1.033-2.566,1.6-2.378,2.9l.273.195.845.611q4.97,3.586,9.68,7.536c1.611,1.351,3.326,2.975,3.437,5.087a5.617,5.617,0,0,1-.156,1.54h0c-.1.507-.247,1.013-.357,1.52a12.676,12.676,0,0,0-.221,3.93h0c.045.383.1.76.175,1.143.994,5.022,5.009,9.167,5.405,14.292v.364a11.546,11.546,0,0,1-.045,1.644c-.3,3.118-1.741,6.113-2.91,9.1-.072.182-.143.364-.208.546a90.5,90.5,0,0,0-4.548,18.294,65.14,65.14,0,0,1-1.3,6.555,17.365,17.365,0,0,1-3.307,6.776,10.536,10.536,0,0,1-2.183,1.949,12.132,12.132,0,0,1-1.722.916v.039c.071.4.13.793.2,1.189.039.208.071.416.11.65h0a.089.089,0,0,0,0,.039c.546,3.073,1.169,6.081,1.826,8.939l.331,1.423c8.186,2.956,9.154,9.011,9.212,11.33a6.259,6.259,0,0,1,0,.929s12.343.325,17.216,3.9a13.21,13.21,0,0,0,2.93,1.494,53.917,53.917,0,0,0,6.009,1.877c1.046.273,2.137.539,3.248.8,1.949.448,4.015.884,6.009,1.3,5.321,1.072,10.33,1.949,12.87,2.6,6.061,1.533,19.925,14.825,19.925,14.825s1.624,13.409,3.248,15.364,4.548,8.829,4.548,8.829l10.745,19.106,4.548,8.036h2.923a4.97,4.97,0,0,1,2.085,1.364c.773.689,1.631,1.546,2.384,2.326l1.247,1.332.78.864s4.723.117,6,3.852c3.846-.11,8.569.143,11.876,1.624a9.187,9.187,0,0,1,1.3.7,5.639,5.639,0,0,1,2.683,3.677,5.422,5.422,0,0,1,.117.935c0,.37,0,.741,0,1.1-.234,4.918-3.437,9.011-6.38,11.765l.208.37s-.1.234-.286.65c-.052.123-.117.26-.188.416h0c-.383.819-.994,2.046-1.793,3.482a41.136,41.136,0,0,1-6.217,8.556l-.188.195-37.511.344-.11-.13-7.276-8.679-3.619-4.314-.11-.136-.1,1.033-.351,3.443-.845,8.322-.045.416v.11l-.1.961.071-.961h-.188l-69.513.65-114.092,1.26v.546h-.091v-.546l-29.072.312Z" transform="translate(418.007 -14.269)" fill="url(#linear-gradient)"/>
|
||||
<path id="Path_443" data-name="Path 443" d="M586.185,511.522s-64.641,7.8-58.469,0c2.235-2.82,2.852-11.174,2.729-20.691-.039-2.891-.143-5.886-.292-8.868-.715-14.292-2.436-28.26-2.436-28.26s56.52-26.636,50.023-5.522c-2.6,8.53-1.748,20.51.227,31.755.578,3.281,1.247,6.5,1.949,9.55a223.892,223.892,0,0,0,6.269,22.036Z" transform="translate(367.711 -46.521)" fill="#fbbebe"/>
|
||||
<path id="Path_444" data-name="Path 444" d="M586.185,537.354s-64.641,7.8-58.469,0c2.235-2.82,2.852-11.174,2.729-20.692,9.855-4.21,22.394-1.072,22.394-1.072,13.2-2.631,21.666-2.118,27.078-.273A223.888,223.888,0,0,0,586.185,537.354Z" transform="translate(367.711 -72.353)" opacity="0.1"/>
|
||||
<path id="Path_445" data-name="Path 445" d="M413.346,651.973v-.546l114.092-1.241,69.513-.65h.195l-.071.961.1-.961h.123v-.11l.89-8.738.338-3.307.1-1.033.091-.864,4.015-13.389,4.015-13.37,3.378-11.258,5.158-17.2s-2.923-6.821-4.548-8.77-3.248-15.267-3.248-15.267-13.864-13.208-19.925-14.728c-2.54-.65-7.555-1.514-12.883-2.6-1.994-.4-4.041-.832-6.009-1.3-1.111-.253-2.2-.52-3.248-.786a57.086,57.086,0,0,1-6.009-1.858,13.448,13.448,0,0,1-2.924-1.488c-4.775-3.5-16.742-3.9-17.2-3.9h0a.269.269,0,0,1,0-.045v-.058a6.66,6.66,0,0,0,0-.819,5.208,5.208,0,0,0-.059-.91.82.82,0,0,0-.045-.325c-.032-.234-.071-.481-.13-.741a3.226,3.226,0,0,0-.078-.344,11.181,11.181,0,0,0-.812-2.267c-.045-.1-.1-.214-.156-.318a11.861,11.861,0,0,0-1.1-1.709c-.084-.11-.175-.227-.273-.338a12.721,12.721,0,0,0-1.767-1.715c-.175-.136-.357-.279-.546-.409a3.026,3.026,0,0,0-.331-.234l-.578-.377-.539-.312c-.474-.26-.974-.513-1.52-.754-.169-.078-.351-.149-.533-.221s-.468-.182-.708-.273h-.039l-.487-.195c-.37-.123-.747-.234-1.143-.344l-.6-.156-.65-.149-.65-.136c-.65-.13-1.345-.247-2.072-.338l-.741-.091-.76-.078-.786-.065-1.117-.065-1.3-.045a81.017,81.017,0,0,0-17.054,1.814,46.946,46.946,0,0,0-11.855-1.093h-.565l-.65.039h-.416l-.838.078a6.224,6.224,0,0,0-.845.1c-.279.039-.572.071-.851.117l-.429.071c-.422.071-.851.149-1.3.24q-.214.039-.429.1c-.565.123-1.13.273-1.689.442-.279.078-.559.169-.838.266-.143.039-.279.091-.416.143l-.416.149-.409.162-.487.195c-.247.1-.487.214-.728.331-.136.058-.273.123-.4.195s-.266.13-.39.2a18.03,18.03,0,0,0-4.034,2.846l-.331.318-.325.331c-.11.11-.214.227-.318.344a15.473,15.473,0,0,0-1.182,1.462c-.1.136-.2.273-.3.416a17.335,17.335,0,0,0-1.117,1.9c-.084.156-.162.318-.24.474a21.243,21.243,0,0,0-1.3,3.411q-.182.65-.331,1.3s-13.753,6.068-25.161,10.46c-3.034,1.169-5.892,2.228-8.283,3.027-.7.234-1.345.448-1.949.65a17.347,17.347,0,0,1-4.06.935,12.778,12.778,0,0,0-1.949.182c-5.243.851-14.227,4.548-15.208,8.257a2.4,2.4,0,0,0-.065.91v.071c.65,4.548-3.9,3.9-3.9,3.9s-1.624,12.019-3.9,15.267c-1.644,2.339-2.768,7.555-2.91,11.492h0a12.741,12.741,0,0,0,.286,3.658v.091c.26.871-.364,3.248-1.423,6.3-1.15,3.346-2.826,7.5-4.437,11.427-2.43,5.925-4.716,11.3-4.859,12.486-.058.526-.227,1.468-.461,2.683-.948,4.95-3.053,14.377-4.093,18.19-.773,2.845-1.9,6.743-2.67,9.42a.91.91,0,0,1-.065.227l29.124-.286v.546h.091Z" transform="translate(417.856 -73.042)" fill="#00ddf1"/>
|
||||
<path id="Path_446" data-name="Path 446" d="M445.44,607.57s37.03,19.165,50.348,16.891Z" transform="translate(396.391 -105.214)" opacity="0.1"/>
|
||||
<path id="Path_447" data-name="Path 447" d="M561.44,595.57s36.056,11.694,48.074,13.318S561.44,595.57,561.44,595.57Z" transform="translate(355.751 -101.01)" opacity="0.1"/>
|
||||
<path id="Path_448" data-name="Path 448" d="M585.443,530.686c-18.84,3.469-54.311,2.722-69.643,2.2,5.62-20.139,33.022-13.292,33.022-13.292C582.74,512.821,585.339,526.866,585.443,530.686Z" transform="translate(371.74 -73.754)" opacity="0.1"/>
|
||||
<path id="Path_449" data-name="Path 449" d="M585.443,528.686c-18.84,3.469-54.311,2.722-69.643,2.2,5.62-20.139,33.022-13.292,33.022-13.292C582.74,510.821,585.339,524.866,585.443,528.686Z" transform="translate(371.74 -73.053)" fill="#00ddf1"/>
|
||||
<path id="Path_450" data-name="Path 450" d="M776.94,672.008s22.192-3.411,22.628,6.334S788.25,694.9,788.25,694.9Z" transform="translate(280.251 -127.587)" fill="#fbbebe"/>
|
||||
<path id="Path_451" data-name="Path 451" d="M578.17,479.936a30.558,30.558,0,0,1-2.923,1.13c-7,2.417-14.065,4.853-21.439,5.736s-15.111.1-21.543-3.573a18.685,18.685,0,0,1-1.949-1.267c-.715-14.292-2.436-28.26-2.436-28.26s56.52-26.636,50.023-5.522C575.318,456.711,576.195,468.69,578.17,479.936Z" transform="translate(367.508 -46.521)" opacity="0.1"/>
|
||||
<path id="Path_452" data-name="Path 452" d="M536.9,360.29a6.153,6.153,0,0,1-5.405-1.137l1.351,1.878a5.5,5.5,0,0,1-4.4-1.462,22.146,22.146,0,0,0,.208,5.035,9.433,9.433,0,0,1-6.211-1.949,2.872,2.872,0,0,0,1.715,2.8,9.316,9.316,0,0,0-10.135,6.958l3.391.312a12,12,0,0,0-6.782,4.138c-1.039,1.3-1.832,2.962-1.377,4.548s2.651,2.6,3.846,1.468a18.151,18.151,0,0,0-3.963,14.806,9.4,9.4,0,0,1,.3,3.417,28.993,28.993,0,0,1-1.228,3.047c-.65,1.9-.032,3.956.546,5.847l3.047,10.018c1.1,3.619,2.2,7.244,3.645,10.745a53.328,53.328,0,0,1,2.6,6.828c.819,3.144.851,6.588,2.6,9.336a12.948,12.948,0,0,0,4.665,4.112c6.425,3.677,14.188,4.457,21.542,3.573s14.409-3.32,21.406-5.736a20.141,20.141,0,0,0,4.444-1.949c4.84-3.17,5.918-9.654,6.821-15.364a87.242,87.242,0,0,1,4.736-18.71c1.416-3.567,3.248-7.185,2.936-11.005-.4-5.074-4.411-9.186-5.405-14.175a12.344,12.344,0,0,1,.045-5.048,11.473,11.473,0,0,0,.513-3.053c-.11-2.092-1.826-3.7-3.437-5.048q-5.243-4.379-10.823-8.277c-.188-1.3,1.579-1.858,2.378-2.884,1.13-1.449-.039-3.748-1.754-4.424s-3.638-.234-5.425.214a4.281,4.281,0,0,0-7.5-3.32,6.627,6.627,0,0,0-5.47-3.95c-4.307-.338-4.392,2.956-7.146,4.768s-5.028-.578-7.146-2.319A6.029,6.029,0,0,0,536.9,360.29Z" transform="translate(374.5 -15.625)" fill="#3f3d56"/>
|
||||
<path id="Path_453" data-name="Path 453" d="M768.171,669.128s-.578,1.377-1.644,3.372a44.83,44.83,0,0,1-5.847,8.556l-.175.188h-1.052l-37.511.344h-.9l-.11-.13-7.276-8.634-2.741-3.255-.11-.136-.234-.273-.13-.156-.169-.2-5.334-6.328,1.949-8.121,3.32-13.682.084-.331.65-2.6.461-1.949,1.546-6.354,5.3-21.861,9.42,4.872,9.933,17.541,5.334,9.42h2.923c.552,0,1.819,1.065,3.092,2.293.455.435.91.9,1.3,1.3,1.156,1.2,2.072,2.222,2.072,2.222s4.736.1,6.009,3.833a4.537,4.537,0,0,1,.162.552,26.191,26.191,0,0,0,1.644,4.262c.838,1.826,1.891,3.9,2.956,5.97,1.981,3.8,4,7.419,4.736,8.731l.091.162Z" transform="translate(305.476 -105.217)" fill="#00ddf1"/>
|
||||
<path id="Path_454" data-name="Path 454" d="M615.681,653.093l-.065.929v.065l-.11,1.494-.864,12.025L613.6,682.093l-.3,4.08-.65,8.731v.11l-69.513.65L429.061,696.9v.546h-.091v-.728l.292-9.329.559-18.229L430.3,653.7l.045-1.54a6.841,6.841,0,0,1,7.419-6.607c3.066.26,7.049.578,11.811.916l1.949.143c27.5,1.91,78.322,4.379,129.606,1.3,1.078-.059,2.163-.123,3.248-.2,7.952-.5,15.9-1.143,23.764-1.949a6.841,6.841,0,0,1,7.536,7.3Z" transform="translate(402.161 -118.514)" opacity="0.1"/>
|
||||
<path id="Path_455" data-name="Path 455" d="M429.08,698.857v-.546l114.092-1.241,69.513-.65h.195v-.11l.65-8.731.279-3.9,1.065-14.689.838-11.629v-.286a6.893,6.893,0,0,0-.071-1.6.2.2,0,0,0,0-.065,6.751,6.751,0,0,0-1.3-3.014,6.847,6.847,0,0,0-6.12-2.6c-7.867.806-15.813,1.449-23.764,1.949-1.085.078-2.17.143-3.248.2-51.284,3.047-102.106.578-129.606-1.3l-1.949-.143c-4.762-.338-8.744-.65-11.811-.916a6.847,6.847,0,0,0-7.3,5.542,7.146,7.146,0,0,0-.117,1.065l-.442,14.4-.6,18.236-.286,9.329v.728Z" transform="translate(402.122 -119.926)" fill="#3f3d56"/>
|
||||
<path id="Path_456" data-name="Path 456" d="M615.925,661.692l-.8,9.959L613.937,686.5l-.3,3.716-.65,8.322-.039.416v.11h0l-.1.961.071-.961h-.188l-69.513.65L429.11,700.944v-.182l.318-9.329.65-18.229.416-12.116a4.132,4.132,0,0,1,1.988-3.411,4.061,4.061,0,0,1,2.456-.585c1.364.11,3.04.24,5,.383,3.177.234,7.094.507,11.655.793,8.445.533,19.041,1.1,31.183,1.572,33.782,1.3,79.388,1.735,122.707-2.059,1.949-.169,3.963-.357,5.931-.546a4.066,4.066,0,0,1,2.261.429,3.346,3.346,0,0,1,.559.351,3.989,3.989,0,0,1,1.3,1.6,2.315,2.315,0,0,1,.162.4A4.08,4.08,0,0,1,615.925,661.692Z" transform="translate(402.112 -122.559)" opacity="0.1"/>
|
||||
<path id="Path_457" data-name="Path 457" d="M360.914,677.87v2.072a3.982,3.982,0,0,1-2.274,3.6l-26.051.5a3.976,3.976,0,0,1-2.319-3.619v-1.949Z" transform="translate(436.74 -129.843)" opacity="0.1"/>
|
||||
<path id="Path_458" data-name="Path 458" d="M280.294,512.532l-33.944.65V507.55l33.944-.65Z" transform="translate(517.685 42.387)" fill="#3f3d56"/>
|
||||
<path id="Path_459" data-name="Path 459" d="M280.294,512.532l-33.944.65V507.55l33.944-.65Z" transform="translate(517.685 42.387)" opacity="0.1"/>
|
||||
<path id="Path_460" data-name="Path 460" d="M282.294,512.532l-33.944.65V507.55l33.944-.65Z" transform="translate(516.984 42.387)" fill="#3f3d56"/>
|
||||
<path id="Path_461" data-name="Path 461" d="M552.228,360.039A6.334,6.334,0,0,1,551,357.22a6.165,6.165,0,0,0,.273,2.988A5.847,5.847,0,0,0,552.228,360.039Z" transform="translate(359.434 -17.505)"/>
|
||||
<path id="Path_462" data-name="Path 462" d="M540.1,366.9a22.83,22.83,0,0,1-.24-2.4c0,.8,0,1.6,0,2.41Z" transform="translate(363.311 -20.056)"/>
|
||||
<path id="Path_463" data-name="Path 463" d="M621.706,398.627a11.912,11.912,0,0,1,.234-1.722,11.7,11.7,0,0,0,.52-3.047,4.008,4.008,0,0,0-.1-.689c-.11.6-.286,1.2-.416,1.787a12.433,12.433,0,0,0-.234,3.671Z" transform="translate(334.652 -30.1)"/>
|
||||
<path id="Path_464" data-name="Path 464" d="M600.922,368.894l.708.513a12.8,12.8,0,0,0,1.67-1.442,2.384,2.384,0,0,0,.247-2.365,1.95,1.95,0,0,1-.247.416C602.5,367.042,600.734,367.607,600.922,368.894Z" transform="translate(341.923 -20.441)"/>
|
||||
<path id="Path_465" data-name="Path 465" d="M518.455,380.29a8,8,0,0,0-.565,1.481,11.8,11.8,0,0,1,3.248-1.228Z" transform="translate(371.008 -25.587)"/>
|
||||
<path id="Path_466" data-name="Path 466" d="M531.888,368.979a9.637,9.637,0,0,1-1.221-.8,2.758,2.758,0,0,0,.058.767,7.7,7.7,0,0,1,1.163.033Z" transform="translate(366.534 -21.345)"/>
|
||||
<path id="Path_467" data-name="Path 467" d="M587.579,428.33c-.266,3.151-1.735,6.185-2.923,9.173a87.2,87.2,0,0,0-4.736,18.717c-.9,5.71-1.949,12.194-6.821,15.358a19.488,19.488,0,0,1-4.437,1.949c-7,2.423-14.065,4.853-21.439,5.736s-15.111.11-21.543-3.567a12.993,12.993,0,0,1-4.664-4.112c-1.735-2.748-1.767-6.191-2.6-9.336a53.424,53.424,0,0,0-2.6-6.834c-1.436-3.5-2.54-7.146-3.638-10.739q-1.527-5.009-3.047-10.024a18.716,18.716,0,0,1-.741-3.086,11.644,11.644,0,0,0,.741,5.035q1.52,5.022,3.047,10.024c1.1,3.619,2.2,7.244,3.638,10.739a53.417,53.417,0,0,1,2.6,6.834c.819,3.144.851,6.587,2.6,9.336a12.993,12.993,0,0,0,4.664,4.112c6.432,3.677,14.188,4.45,21.543,3.567s14.409-3.313,21.439-5.736a19.487,19.487,0,0,0,4.437-1.949c4.84-3.164,5.918-9.647,6.821-15.358a87.2,87.2,0,0,1,4.736-18.717c1.416-3.56,3.248-7.179,2.936-11.005C587.592,428.408,587.579,428.369,587.579,428.33Z" transform="translate(374.359 -42.418)"/>
|
||||
<path id="Path_468" data-name="Path 468" d="M510.318,421.419v-.071a9.1,9.1,0,0,0-.3-3.411,17.272,17.272,0,0,1-.253-2.157,17.769,17.769,0,0,0,.253,4.106C510.123,420.393,510.24,420.906,510.318,421.419Z" transform="translate(373.864 -38.021)"/>
|
||||
<path id="Path_469" data-name="Path 469" d="M512.452,397.929a18.02,18.02,0,0,1,1.7-2.384c-1.189,1.137-3.385.123-3.846-1.455a3.788,3.788,0,0,0,0,1.949,2.852,2.852,0,0,0,2.15,1.89Z" transform="translate(373.712 -30.422)"/>
|
||||
<path id="Path_470" data-name="Path 470" d="M762.946,656.57s2.059,7.471,0,13.968S762.946,656.57,762.946,656.57Z" transform="translate(285.475 -122.381)" opacity="0.1"/>
|
||||
<path id="Path_471" data-name="Path 471" d="M776,668.3s.864,9.485-1.085,11.168S776,668.3,776,668.3Z" transform="translate(281.189 -126.49)" opacity="0.1"/>
|
||||
<line id="Line_88" data-name="Line 88" y1="0.65" x2="66.011" transform="translate(733.267 442.068)" fill="none" stroke="#00ddf1" stroke-miterlimit="10" stroke-width="2"/>
|
||||
<line id="Line_89" data-name="Line 89" y1="0.643" x2="66.011" transform="translate(733.267 450.176)" fill="none" stroke="#00ddf1" stroke-miterlimit="10" stroke-width="2"/>
|
||||
<line id="Line_90" data-name="Line 90" y1="0.65" x2="66.011" transform="translate(733.267 458.277)" fill="none" stroke="#00ddf1" stroke-miterlimit="10" stroke-width="2"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 23 KiB |
@@ -24,7 +24,7 @@ const AddSigner = (props) => {
|
||||
if (savedUserDetails && addYourself) {
|
||||
setName(savedUserDetails.name);
|
||||
setPhone(savedUserDetails?.phone || "");
|
||||
setEmail(savedUserDetails.email);
|
||||
setEmail(savedUserDetails.email?.toLowerCase()?.replace(/\s/g, ""));
|
||||
}
|
||||
}, [addYourself]);
|
||||
|
||||
@@ -109,7 +109,8 @@ const AddSigner = (props) => {
|
||||
if (props.details) {
|
||||
props.details({
|
||||
value: parseData[props.valueKey],
|
||||
label: parseData[props.displayKey]
|
||||
label: parseData[props.displayKey],
|
||||
email: parseData?.Email
|
||||
});
|
||||
}
|
||||
if (props.closePopup) {
|
||||
@@ -155,7 +156,8 @@ const AddSigner = (props) => {
|
||||
if (props.details) {
|
||||
props.details({
|
||||
value: parseData[props.valueKey],
|
||||
label: parseData[props.displayKey]
|
||||
label: parseData[props.displayKey],
|
||||
email: parseData?.Email
|
||||
});
|
||||
}
|
||||
if (props.closePopup) {
|
||||
@@ -211,7 +213,7 @@ const AddSigner = (props) => {
|
||||
</div>
|
||||
)}
|
||||
<div className="w-full mx-auto p-2 text-base-content">
|
||||
<p className="mb-3">{t("add-signer-note")}</p>
|
||||
<p className="mb-3 text-xs text-gray-500">{t("add-signer-note")}</p>
|
||||
{isUserExist && (
|
||||
<div className="mb-3 flex items-center">
|
||||
<input
|
||||
@@ -1,198 +0,0 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Parse from "parse";
|
||||
import Title from "./Title";
|
||||
import Loader from "../primitives/Loader";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const AddTeam = (props) => {
|
||||
const { t } = useTranslation();
|
||||
const [formdata, setFormdata] = useState({
|
||||
name: "",
|
||||
team: ""
|
||||
});
|
||||
const [isLoader, setIsLoader] = useState(false);
|
||||
const [teamList, setTeamList] = useState([]);
|
||||
useEffect(() => {
|
||||
getTeamList();
|
||||
}, []);
|
||||
|
||||
const getTeamList = async () => {
|
||||
setIsLoader(true);
|
||||
try {
|
||||
const teams = await Parse.Cloud.run("getteams", { active: true });
|
||||
const teamRes = JSON.parse(JSON.stringify(teams));
|
||||
if (teamRes.length > 0) {
|
||||
const _teamRes = JSON.parse(JSON.stringify(teamRes));
|
||||
const allUsersteam = _teamRes.find((x) => x.Name === "All Users");
|
||||
if (allUsersteam) {
|
||||
setFormdata({ team: allUsersteam.objectId });
|
||||
}
|
||||
setTeamList(_teamRes);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("Err in fetch top level teamList", err);
|
||||
} finally {
|
||||
setIsLoader(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDropdown = (e) => {
|
||||
setFormdata((prev) => ({ ...prev, [e.target.name]: e.target.value }));
|
||||
};
|
||||
// Define a function to handle form submission
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
// Extracting values except for the 'name' key
|
||||
let updatedAncestors = [];
|
||||
if (formdata.team) {
|
||||
const Ancestors = teamList.find(
|
||||
(x) => x.objectId === formdata.team
|
||||
)?.Ancestors;
|
||||
if (Ancestors && Ancestors.length > 0) {
|
||||
updatedAncestors = Ancestors.map((x) => ({
|
||||
__type: "Pointer",
|
||||
className: "contracts_Teams",
|
||||
objectId: x.objectId
|
||||
}));
|
||||
} else {
|
||||
const AllUser = teamList.find((x) => x.objectId === "All Users");
|
||||
updatedAncestors = [
|
||||
AllUser,
|
||||
{
|
||||
__type: "Pointer",
|
||||
className: "contracts_Teams",
|
||||
objectId: formdata.team
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
try {
|
||||
setIsLoader(true);
|
||||
let data = { Name: formdata.name };
|
||||
if (updatedAncestors.length > 0) {
|
||||
const ParentId = updatedAncestors[updatedAncestors.length - 1];
|
||||
data["ParentId"] = ParentId?.objectId;
|
||||
data["Ancestors"] = updatedAncestors;
|
||||
}
|
||||
const newTeamRes = await Parse.Cloud.run("addteam", data);
|
||||
// console.log("teamRes ", newTeamRes);
|
||||
if (updatedAncestors.length > 0) {
|
||||
const ParentId = teamList.find((x) => x.objectId === formdata.team);
|
||||
props.handleTeamInfo({
|
||||
objectId: newTeamRes.id,
|
||||
Name: formdata.name,
|
||||
ParentId: ParentId,
|
||||
Ancestors: updatedAncestors,
|
||||
IsActive: true
|
||||
});
|
||||
} else {
|
||||
props.handleTeamInfo({
|
||||
objectId: newTeamRes.id,
|
||||
Name: formdata.name,
|
||||
ParentId: "",
|
||||
Ancestors: "",
|
||||
IsActive: true
|
||||
});
|
||||
}
|
||||
if (props.closePopup) {
|
||||
props.closePopup();
|
||||
}
|
||||
setFormdata({ name: "", team: { name: "", objectId: "" } });
|
||||
props.setIsAlert({ type: "success", msg: "Team created successfully." });
|
||||
} catch (err) {
|
||||
console.log("err in save team", err);
|
||||
if (err.code === 137) {
|
||||
props.setIsAlert({ type: "danger", msg: "Teams already exists." });
|
||||
} else if (err.code === 102) {
|
||||
props.setIsAlert({ type: "warning", msg: "Provide team name." });
|
||||
} else {
|
||||
props.setIsAlert({ type: "danger", msg: "Something went wrong." });
|
||||
}
|
||||
} finally {
|
||||
setTimeout(() => props.setIsAlert({ type: "success", msg: "" }), 1500);
|
||||
setIsLoader(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Define a function to handle the "add yourself" checkbox
|
||||
const handleReset = () => {
|
||||
setFormdata({
|
||||
name: "",
|
||||
team: { name: "", objectId: "" }
|
||||
});
|
||||
};
|
||||
const handleChange = (e) => {
|
||||
setFormdata((prev) => ({ ...prev, [e.target.name]: e.target.value }));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="shadow-md rounded-box my-[1px] p-3 bg-base-100 relative">
|
||||
<Title title="Add Team" />
|
||||
{isLoader && (
|
||||
<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}>
|
||||
{/* <h1 className="text-[20px] font-semibold mb-4">Add User</h1> */}
|
||||
<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="phone"
|
||||
className="block text-xs text-gray-700 font-semibold"
|
||||
>
|
||||
{t("report-heading.Parent Team")}
|
||||
</label>
|
||||
<select
|
||||
value={formdata.team}
|
||||
onChange={(e) => handleDropdown(e)}
|
||||
name="team"
|
||||
className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
>
|
||||
{teamList.length > 0 &&
|
||||
teamList.map((x) => (
|
||||
<option key={x.objectId} value={x.objectId}>
|
||||
{x.Name}
|
||||
</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("reset")}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddTeam;
|
||||
@@ -1,577 +0,0 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Parse from "parse";
|
||||
import Title from "./Title";
|
||||
import Loader from "../primitives/Loader";
|
||||
import { copytoData, fetchSubscriptionInfo } from "../constant/Utils";
|
||||
import { emailRegex, isEnableSubscription } 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 [amount, setAmount] = useState({ quantity: 1, price: 0 });
|
||||
const [planInfo, setPlanInfo] = useState({
|
||||
priceperUser: 0,
|
||||
price: 0,
|
||||
totalAllowedUser: 0,
|
||||
adminId: ""
|
||||
});
|
||||
const [isFormLoader, setIsFormLoader] = useState(false);
|
||||
const [isLoader, setIsLoader] = useState(false);
|
||||
const [teamList, setTeamList] = useState([]);
|
||||
const [allowedUser, setAllowedUser] = useState(0);
|
||||
const [err, setErr] = useState("");
|
||||
const role = ["OrgAdmin", "Editor", "User"];
|
||||
const extUser =
|
||||
localStorage.getItem("Extand_Class") &&
|
||||
JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
|
||||
useEffect(() => {
|
||||
getTeamList();
|
||||
// eslint-disable-next-line
|
||||
}, []);
|
||||
|
||||
const getTeamList = async () => {
|
||||
if (isEnableSubscription) {
|
||||
try {
|
||||
setIsLoader(true);
|
||||
const resSub = await fetchSubscriptionInfo();
|
||||
if (!resSub.error) {
|
||||
setPlanInfo((prev) => ({
|
||||
...prev,
|
||||
priceperUser: resSub.price,
|
||||
totalAllowedUser: resSub.totalAllowedUser,
|
||||
adminId: resSub.adminId
|
||||
}));
|
||||
setAmount((prev) => ({ ...prev, price: resSub.price }));
|
||||
const res = await Parse.Cloud.run("allowedusers");
|
||||
if (props.setFormHeader) {
|
||||
if (res > 0) {
|
||||
props.setFormHeader(t("add-user"));
|
||||
} else {
|
||||
props.setFormHeader(
|
||||
resSub.adminId !== extUser?.objectId
|
||||
? "Unauthorized"
|
||||
: t("Add-seats")
|
||||
);
|
||||
}
|
||||
}
|
||||
setAllowedUser(res);
|
||||
} else {
|
||||
setAllowedUser(0);
|
||||
}
|
||||
} catch (err) {
|
||||
setAllowedUser(0);
|
||||
console.log("Err in alloweduser", err);
|
||||
setErr(err.message);
|
||||
} finally {
|
||||
setIsLoader(false);
|
||||
}
|
||||
}
|
||||
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);
|
||||
if (!isEnableSubscription) {
|
||||
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.setIsAlert({ type: "danger", msg: t("user-already-exist") });
|
||||
setIsFormLoader(false);
|
||||
setTimeout(() => props.setIsAlert({ type: "success", msg: "" }), 1000);
|
||||
} else {
|
||||
try {
|
||||
const extUser = new Parse.Object("contracts_Users");
|
||||
extUser.set("Name", formdata.name);
|
||||
if (formdata.phone) {
|
||||
extUser.set("Phone", formdata.phone);
|
||||
}
|
||||
extUser.set("Email", formdata.email);
|
||||
extUser.set("UserRole", `contracts_${formdata.role}`);
|
||||
if (formdata?.team) {
|
||||
extUser.set("TeamIds", [
|
||||
{
|
||||
__type: "Pointer",
|
||||
className: "contracts_Teams",
|
||||
objectId: formdata.team
|
||||
}
|
||||
]);
|
||||
}
|
||||
if (localUser && localUser.OrganizationId) {
|
||||
extUser.set("OrganizationId", {
|
||||
__type: "Pointer",
|
||||
className: "contracts_Organizations",
|
||||
objectId: localUser.OrganizationId.objectId
|
||||
});
|
||||
}
|
||||
if (localUser && localUser.Company) {
|
||||
extUser.set("Company", localUser.Company);
|
||||
}
|
||||
|
||||
if (localStorage.getItem("TenantId")) {
|
||||
extUser.set("TenantId", {
|
||||
__type: "Pointer",
|
||||
className: "partners_Tenant",
|
||||
objectId: localStorage.getItem("TenantId")
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const _users = Parse.Object.extend("User");
|
||||
const _user = new _users();
|
||||
_user.set("name", formdata.name);
|
||||
_user.set("username", formdata.email);
|
||||
_user.set("email", formdata.email);
|
||||
_user.set("password", formdata.password);
|
||||
if (formdata.phone) {
|
||||
_user.set("phone", formdata.phone);
|
||||
}
|
||||
|
||||
const user = await _user.save();
|
||||
if (user) {
|
||||
const currentUser = Parse.User.current();
|
||||
extUser.set(
|
||||
"CreatedBy",
|
||||
Parse.User.createWithoutData(currentUser.id)
|
||||
);
|
||||
|
||||
extUser.set("UserId", user);
|
||||
const acl = new Parse.ACL();
|
||||
acl.setPublicReadAccess(true);
|
||||
acl.setPublicWriteAccess(true);
|
||||
acl.setReadAccess(currentUser.id, true);
|
||||
acl.setWriteAccess(currentUser.id, true);
|
||||
|
||||
extUser.setACL(acl);
|
||||
|
||||
const res = await extUser.save();
|
||||
|
||||
const parseData = JSON.parse(JSON.stringify(res));
|
||||
|
||||
if (props.closePopup) {
|
||||
props.closePopup();
|
||||
}
|
||||
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: ""
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("err ", err);
|
||||
if (err.code === 202) {
|
||||
const params = { email: formdata.email };
|
||||
const userRes = await Parse.Cloud.run("getUserId", params);
|
||||
const currentUser = Parse.User.current();
|
||||
extUser.set(
|
||||
"CreatedBy",
|
||||
Parse.User.createWithoutData(currentUser.id)
|
||||
);
|
||||
|
||||
extUser.set("UserId", {
|
||||
__type: "Pointer",
|
||||
className: "_User",
|
||||
objectId: userRes.id
|
||||
});
|
||||
const acl = new Parse.ACL();
|
||||
acl.setPublicReadAccess(true);
|
||||
acl.setPublicWriteAccess(true);
|
||||
acl.setReadAccess(currentUser.id, true);
|
||||
acl.setWriteAccess(currentUser.id, true);
|
||||
|
||||
extUser.setACL(acl);
|
||||
const res = await extUser.save();
|
||||
|
||||
const parseData = JSON.parse(JSON.stringify(res));
|
||||
if (props.closePopup) {
|
||||
props.closePopup();
|
||||
}
|
||||
if (props.handleUserData) {
|
||||
if (formdata?.team) {
|
||||
const team = teamList.find(
|
||||
(x) => x.objectId === formdata.team
|
||||
);
|
||||
parseData.TeamIds = parseData.TeamIds.map((y) =>
|
||||
y.objectId === team.objectId ? team : y
|
||||
);
|
||||
}
|
||||
props.handleUserData(parseData);
|
||||
}
|
||||
setIsFormLoader(false);
|
||||
setFormdata({
|
||||
name: "",
|
||||
email: "",
|
||||
phone: "",
|
||||
team: "",
|
||||
role: ""
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("err", err);
|
||||
setIsFormLoader(false);
|
||||
props.setIsAlert({
|
||||
type: "danger",
|
||||
msg: t("something-went-wrong-mssg")
|
||||
});
|
||||
} finally {
|
||||
setTimeout(
|
||||
() => props.setIsAlert({ type: "success", msg: "" }),
|
||||
1500
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 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.setIsAlert({ type: "success", msg: t("copied") });
|
||||
setTimeout(() => props.setIsAlert({ type: "success", msg: "" }), 1500); // Reset copied state after 1.5 seconds
|
||||
};
|
||||
const handlePricePerUser = (e) => {
|
||||
const quantity = e.target.value;
|
||||
const price = e.target?.value > 0 ? planInfo.priceperUser * quantity : 0;
|
||||
setAmount((prev) => ({ ...prev, quantity: quantity, price: price }));
|
||||
};
|
||||
const handleAddOnSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setIsFormLoader(true);
|
||||
try {
|
||||
const resAddon = await Parse.Cloud.run("buyaddonusers", {
|
||||
users: amount.quantity
|
||||
});
|
||||
if (resAddon) {
|
||||
const _resAddon = JSON.parse(JSON.stringify(resAddon));
|
||||
if (_resAddon.status === "success") {
|
||||
setAllowedUser(amount.quantity);
|
||||
setPlanInfo((obj) => ({ ...obj, totalAllowedUser: _resAddon.addon }));
|
||||
}
|
||||
if (props.handleBuyUsers) {
|
||||
props.handleBuyUsers(amount.quantity, _resAddon.addon);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("Err in buy addon", err);
|
||||
props.setIsAlert({ type: "danger", msg: t("something-went-wrong-mssg") });
|
||||
} finally {
|
||||
setTimeout(() => props.setIsAlert({ type: "success", msg: "" }), 2000);
|
||||
setIsFormLoader(false);
|
||||
}
|
||||
};
|
||||
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>
|
||||
)}
|
||||
{!isLoader ? (
|
||||
<>
|
||||
{err ? (
|
||||
<div className="flex justify-center items-center h-[70px] text-[18px]">
|
||||
{err}
|
||||
</div>
|
||||
) : (
|
||||
<div className="w-full mx-auto">
|
||||
{!isEnableSubscription || allowedUser > 0 ? (
|
||||
<form onSubmit={handleSubmit}>
|
||||
{isEnableSubscription && (
|
||||
<p className="flex justify-center mb-1">
|
||||
{t("remaing-users")}{" "}
|
||||
<span
|
||||
className={`${
|
||||
allowedUser < 2 ? "op-text-accent" : "op-text-primary"
|
||||
} font-medium ml-1`}
|
||||
>
|
||||
{allowedUser} {t("of")} {planInfo.totalAllowedUser}
|
||||
</span>
|
||||
</p>
|
||||
)}
|
||||
<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>
|
||||
{/* added hint for phone number being optional */}
|
||||
<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>
|
||||
{isEnableSubscription && (
|
||||
<div className="mb-3">
|
||||
<label
|
||||
htmlFor="phone"
|
||||
className="block text-xs text-gray-700 font-semibold"
|
||||
>
|
||||
{t("team")}
|
||||
<span className="text-[red] text-[13px]"> *</span>
|
||||
</label>
|
||||
<select
|
||||
name="team"
|
||||
value={formdata.team}
|
||||
onChange={(e) => handleChange(e)}
|
||||
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>
|
||||
{teamList.length > 0 &&
|
||||
teamList.map((x) => (
|
||||
<option key={x.objectId} value={x.objectId}>
|
||||
{x.Name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</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>
|
||||
) : (
|
||||
<>
|
||||
{planInfo.adminId !== extUser?.objectId ? (
|
||||
<div className="mb-3 mt-1 flex justify-center text-center items-center font-medium break-all">
|
||||
{t("unauthorized-modal", {
|
||||
adminEmail: extUser?.TenantId?.EmailAddress
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<form onSubmit={handleAddOnSubmit}>
|
||||
<p className="flex justify-center text-center mx-2 mb-3 text-base op-text-accent font-medium">
|
||||
{t("additional-users")}
|
||||
</p>
|
||||
<div className="mb-3 flex justify-between">
|
||||
<label
|
||||
htmlFor="quantity"
|
||||
className="block text-xs text-gray-700 font-semibold"
|
||||
>
|
||||
{t("Quantity-of-users")}
|
||||
<span className="text-[red] text-[13px]"> *</span>
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
name="quantity"
|
||||
value={amount.quantity}
|
||||
onChange={(e) => handlePricePerUser(e)}
|
||||
className="w-1/4 op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content text-xs"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-3 flex justify-between">
|
||||
<label className="block text-xs text-gray-700 font-semibold">
|
||||
{t("Price")} (1 * {planInfo.priceperUser})
|
||||
</label>
|
||||
<div className="w-1/4 flex justify-center items-center text-sm">
|
||||
USD {amount.price}
|
||||
</div>
|
||||
</div>
|
||||
<hr className="text-base-content mb-3" />
|
||||
<button className="op-btn op-btn-primary w-full">
|
||||
{t("Proceed")}
|
||||
</button>
|
||||
</form>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<div className="w-full h-[200px] inset-0 flex justify-center items-center z-50">
|
||||
<Loader />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddUser;
|
||||
@@ -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;
|
||||
@@ -1,544 +0,0 @@
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import axios from "axios";
|
||||
import SuggestionInput from "./shared/fields/SuggestionInput";
|
||||
import Loader from "../primitives/Loader";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Parse from "parse";
|
||||
import ModalUi from "../primitives/ModalUi";
|
||||
import { emailRegex, isEnableSubscription } from "../constant/const";
|
||||
import { fetchSubscription } from "../constant/Utils";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
const BulkSendUi = (props) => {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const [forms, setForms] = useState([]);
|
||||
const [formId, setFormId] = useState(2);
|
||||
const formRef = useRef(null);
|
||||
const [scrollOnNextUpdate, setScrollOnNextUpdate] = useState(false);
|
||||
const [isSubmit, setIsSubmit] = useState(false);
|
||||
const [allowedForm, setAllowedForm] = useState(0);
|
||||
const [isSignatureExist, setIsSignatureExist] = useState();
|
||||
const [isBulkAvailable, setIsBulkAvailable] = useState(false);
|
||||
const quantityList = [500, 1000, 5000, 50000];
|
||||
const [amount, setAmount] = useState({
|
||||
price: (75.0).toFixed(2),
|
||||
quantity: 500,
|
||||
priceperbulksend: 0.15,
|
||||
totalcredits: 0
|
||||
});
|
||||
const [isQuotaReached, setIsQuotaReached] = useState(false);
|
||||
const [isLoader, setIsLoader] = useState(false);
|
||||
const [isFreePlan, setIsFreePlan] = useState(false);
|
||||
const [admin, setAdmin] = useState({
|
||||
objectId: "",
|
||||
email: "",
|
||||
isAdmin: true
|
||||
});
|
||||
const allowedSigners = 50;
|
||||
useEffect(() => {
|
||||
signatureExist();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
//function to check atleast one signature field exist
|
||||
const signatureExist = async () => {
|
||||
if (isEnableSubscription) {
|
||||
setIsLoader(true);
|
||||
try {
|
||||
const subscription = await fetchSubscription();
|
||||
const extUser =
|
||||
localStorage.getItem("Extand_Class") &&
|
||||
JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
|
||||
if (
|
||||
subscription?.adminId &&
|
||||
extUser?.objectId === subscription?.adminId
|
||||
) {
|
||||
setAdmin((obj) => ({
|
||||
...obj,
|
||||
objectId: subscription?.adminId,
|
||||
isAdmin: true
|
||||
}));
|
||||
} else {
|
||||
setAdmin((obj) => ({
|
||||
...obj,
|
||||
isAdmin: false,
|
||||
email: extUser?.TenantId?.EmailAddress
|
||||
}));
|
||||
}
|
||||
setAdmin((obj) => ({ ...obj, objectId: subscription?.adminId }));
|
||||
if (subscription?.plan === "freeplan") {
|
||||
setIsFreePlan(true);
|
||||
}
|
||||
const token = props.jwttoken
|
||||
? { jwttoken: props.jwttoken }
|
||||
: { "X-Parse-Session-Token": localStorage.getItem("accesstoken") };
|
||||
const axiosres = await axios.post(
|
||||
`${localStorage.getItem("baseUrl")}functions/allowedcredits`,
|
||||
{},
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
|
||||
...token
|
||||
}
|
||||
}
|
||||
);
|
||||
const resCredits = axiosres.data && axiosres.data.result;
|
||||
if (resCredits) {
|
||||
const allowedcredits = resCredits?.allowedcredits || 0;
|
||||
const addoncredits = resCredits?.addoncredits || 0;
|
||||
const totalcredits = allowedcredits + addoncredits;
|
||||
if (totalcredits > 0) {
|
||||
if (subscription?.plan !== "freeplan") {
|
||||
setIsBulkAvailable(true);
|
||||
}
|
||||
}
|
||||
setAmount((obj) => ({ ...obj, totalcredits: totalcredits }));
|
||||
}
|
||||
const getPlaceholder = props?.Placeholders;
|
||||
const checkIsSignatureExistt = getPlaceholder?.every((placeholderObj) =>
|
||||
placeholderObj?.placeHolder?.some((holder) =>
|
||||
holder?.pos?.some((posItem) => posItem?.type === "signature")
|
||||
)
|
||||
);
|
||||
setIsSignatureExist(checkIsSignatureExistt);
|
||||
setIsLoader(false);
|
||||
} catch (err) {
|
||||
setIsLoader(false);
|
||||
alert(t("something-went-wrong-mssg"));
|
||||
console.log("Err", err);
|
||||
}
|
||||
} else {
|
||||
setIsBulkAvailable(true);
|
||||
setAdmin((obj) => ({ ...obj, isAdmin: true }));
|
||||
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) {
|
||||
formRef.current.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "end",
|
||||
inline: "nearest"
|
||||
});
|
||||
setScrollOnNextUpdate(false);
|
||||
}
|
||||
}, [forms, scrollOnNextUpdate]);
|
||||
|
||||
useEffect(() => {
|
||||
(() => {
|
||||
if (props?.Placeholders?.length > 0) {
|
||||
let users = [];
|
||||
props?.Placeholders?.forEach((element) => {
|
||||
if (!element.signerObjId) {
|
||||
users = [
|
||||
...users,
|
||||
{
|
||||
fieldId: element.Id,
|
||||
email: "",
|
||||
label: element.Role,
|
||||
signer: {}
|
||||
}
|
||||
];
|
||||
}
|
||||
});
|
||||
setForms((prevForms) => [...prevForms, { Id: 1, fields: users }]);
|
||||
const totalForms = Math.floor(allowedSigners / users?.length);
|
||||
setAllowedForm(totalForms);
|
||||
}
|
||||
})();
|
||||
// eslint-disable-next-line
|
||||
}, []);
|
||||
const handleInputChange = (index, signer, fieldIndex) => {
|
||||
const newForms = [...forms];
|
||||
newForms[index].fields[fieldIndex].email = signer?.Email
|
||||
? signer?.Email
|
||||
: signer || "";
|
||||
newForms[index].fields[fieldIndex].signer = signer?.objectId ? signer : "";
|
||||
setForms(newForms);
|
||||
};
|
||||
|
||||
const handleAddForm = (e) => {
|
||||
e.preventDefault();
|
||||
// Check if the quick send limit has been reached
|
||||
if (isEnableSubscription && forms.length >= amount.totalcredits) {
|
||||
setIsQuotaReached(true);
|
||||
} else {
|
||||
if (forms?.length < allowedForm) {
|
||||
if (props?.Placeholders.length > 0) {
|
||||
let newForm = [];
|
||||
props?.Placeholders?.forEach((element) => {
|
||||
if (!element.signerObjId) {
|
||||
newForm = [
|
||||
...newForm,
|
||||
{
|
||||
fieldId: element.Id,
|
||||
email: "",
|
||||
label: element.Role,
|
||||
signer: {}
|
||||
}
|
||||
];
|
||||
}
|
||||
});
|
||||
setForms([...forms, { Id: formId, fields: newForm }]);
|
||||
}
|
||||
setFormId(formId + 1);
|
||||
setScrollOnNextUpdate(true);
|
||||
} else {
|
||||
// If the limit has been reached, throw an error with the appropriate message
|
||||
alert(t("quick-send-alert-4"));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveForm = (index) => {
|
||||
const updatedForms = forms.filter((_, i) => i !== index);
|
||||
setForms(updatedForms);
|
||||
};
|
||||
|
||||
function validateEmails(data) {
|
||||
for (const item of data) {
|
||||
for (const field of item.fields) {
|
||||
if (!emailRegex.test(field.email)) {
|
||||
alert(`Invalid email found: ${field.email}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setIsSubmit(true);
|
||||
if (validateEmails(forms)) {
|
||||
// Create a copy of Placeholders array from props.item
|
||||
let Placeholders = [...props.Placeholders];
|
||||
// Initialize an empty array to store updated documents
|
||||
let Documents = [];
|
||||
// Loop through each form
|
||||
forms.forEach((form) => {
|
||||
//checking if user enter email which already exist as a signer then add user in a signers array
|
||||
let existSigner = [];
|
||||
form.fields.map((data) => {
|
||||
if (data.signer) {
|
||||
existSigner.push(data.signer);
|
||||
}
|
||||
});
|
||||
// Map through the copied Placeholders array to update email values
|
||||
const updatedPlaceholders = Placeholders.map((placeholder) => {
|
||||
// Find the field in the current form that matches the placeholder Id
|
||||
const field = form.fields.find(
|
||||
(element) => parseInt(element.fieldId) === placeholder.Id
|
||||
);
|
||||
// If a matching field is found, update the email value in the placeholder
|
||||
const signer = field?.signer?.objectId ? field.signer : "";
|
||||
if (field) {
|
||||
if (signer) {
|
||||
return {
|
||||
...placeholder,
|
||||
signerObjId: field?.signer?.objectId || "",
|
||||
signerPtr: signer
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
...placeholder,
|
||||
email: field.email,
|
||||
signerObjId: field?.signer?.objectId || "",
|
||||
signerPtr: signer
|
||||
};
|
||||
}
|
||||
}
|
||||
// If no matching field is found, keep the placeholder as is
|
||||
return placeholder;
|
||||
});
|
||||
|
||||
// Push a new document object with updated Placeholders into the Documents array
|
||||
if (existSigner?.length > 0) {
|
||||
Documents.push({
|
||||
...props.item,
|
||||
Placeholders: updatedPlaceholders,
|
||||
Signers: props.item.Signers
|
||||
? [...props.item.Signers, ...existSigner]
|
||||
: [...existSigner]
|
||||
});
|
||||
} else {
|
||||
Documents.push({
|
||||
...props.item,
|
||||
Placeholders: updatedPlaceholders,
|
||||
SignatureType: props.signatureType
|
||||
});
|
||||
}
|
||||
});
|
||||
await batchQuery(Documents);
|
||||
} else {
|
||||
setIsSubmit(false);
|
||||
}
|
||||
};
|
||||
|
||||
const batchQuery = async (Documents) => {
|
||||
const token = props.jwttoken
|
||||
? { jwttoken: props.jwttoken }
|
||||
: { "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
|
||||
};
|
||||
const params = { Documents: JSON.stringify(Documents) };
|
||||
try {
|
||||
const res = await axios.post(functionsUrl, params, { headers: headers });
|
||||
// console.log("res ", res);
|
||||
if (res.data && res.data.result) {
|
||||
props.handleClose("success", Documents?.length);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("Err ", err);
|
||||
props.handleClose("error", 0);
|
||||
} finally {
|
||||
setIsSubmit(false);
|
||||
}
|
||||
};
|
||||
const handleAddOnQuickSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setIsSubmit(true);
|
||||
try {
|
||||
const resAddon = await Parse.Cloud.run("buycredits", {
|
||||
credits: amount.quantity
|
||||
});
|
||||
if (resAddon) {
|
||||
const _resAddon = JSON.parse(JSON.stringify(resAddon));
|
||||
if (_resAddon.status === "success") {
|
||||
setIsBulkAvailable(true);
|
||||
handleCloseQuotaReached();
|
||||
setAmount((obj) => ({
|
||||
...obj,
|
||||
quantity: 500,
|
||||
priceperbulksend: 0.15,
|
||||
price: (75.0).toFixed(2),
|
||||
totalcredits: obj?.totalcredits + _resAddon.addon
|
||||
}));
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("Err in buy addon", err);
|
||||
alert(t("something-went-wrong-mssg"));
|
||||
} finally {
|
||||
setIsSubmit(false);
|
||||
}
|
||||
};
|
||||
const handlePricePerQuick = async (e) => {
|
||||
const quantity = e.target?.value;
|
||||
const price =
|
||||
quantity > 0
|
||||
? (Math.round(quantity * amount.priceperbulksend * 100) / 100).toFixed(
|
||||
2
|
||||
)
|
||||
: 500 * amount.priceperbulksend;
|
||||
setAmount((prev) => ({ ...prev, quantity: quantity, price: price }));
|
||||
};
|
||||
const handleCloseQuotaReached = () => {
|
||||
setIsQuotaReached(false);
|
||||
};
|
||||
const handleNavigation = () => {
|
||||
navigate("/subscription");
|
||||
};
|
||||
return (
|
||||
<>
|
||||
{isLoader ? (
|
||||
<div className="w-full h-[100px] flex justify-center items-center z-[999]">
|
||||
<Loader />
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{isSubmit && (
|
||||
<div className="absolute z-[999] h-full w-full flex justify-center items-center bg-black bg-opacity-30">
|
||||
<Loader />
|
||||
</div>
|
||||
)}
|
||||
{isBulkAvailable ? (
|
||||
<>
|
||||
{props.Placeholders?.length > 0 ? (
|
||||
isSignatureExist ? (
|
||||
<>
|
||||
{props.Placeholders?.some((x) => !x.signerObjId) ? (
|
||||
<div>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="min-h-max max-h-[250px] overflow-y-auto">
|
||||
{forms?.map((form, index) => (
|
||||
<div
|
||||
key={form.Id}
|
||||
className="p-3 op-card border-[1px] border-gray-400 mt-3 mx-4 mb-4 bg-base-200 text-base-content grid grid-cols-1 md:grid-cols-2 gap-2 relative"
|
||||
>
|
||||
{form?.fields?.map((field, fieldIndex) => (
|
||||
<div
|
||||
className="flex flex-col"
|
||||
key={field.fieldId}
|
||||
>
|
||||
<label>{field.label}</label>
|
||||
<SuggestionInput
|
||||
required
|
||||
type="email"
|
||||
value={field.value}
|
||||
index={fieldIndex}
|
||||
onChange={(signer) =>
|
||||
handleInputChange(
|
||||
index,
|
||||
signer,
|
||||
fieldIndex
|
||||
)
|
||||
}
|
||||
jwttoken={props?.jwttoken}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
{forms?.length > 1 && (
|
||||
<button
|
||||
onClick={() => handleRemoveForm(index)}
|
||||
className="absolute right-3 top-1 text-[red] border-[1px] border-[red] rounded-lg w-[1.7rem] h-[1.7rem]"
|
||||
>
|
||||
<i className="fa-light fa-trash"></i>
|
||||
</button>
|
||||
)}
|
||||
<div ref={formRef}></div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex flex-col mx-4 mb-4 gap-3">
|
||||
{isEnableSubscription && (
|
||||
<button
|
||||
onClick={handleAddForm}
|
||||
className="op-btn op-btn-primary focus:outline-none"
|
||||
>
|
||||
<i className="fa-light fa-plus"></i>{" "}
|
||||
<span>{t("add-new")}</span>
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
type="submit"
|
||||
className="op-btn op-btn-accent focus:outline-none"
|
||||
>
|
||||
<i className="fa-light fa-paper-plane"></i>{" "}
|
||||
<span>{t("send")}</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<ModalUi
|
||||
isOpen={isQuotaReached}
|
||||
handleClose={() => handleCloseQuotaReached()}
|
||||
>
|
||||
<div className="p-4 flex justify-center items-center flex-col gap-y-3">
|
||||
<p className="text-center text-base-content">
|
||||
{t("quota-err-quicksend")}
|
||||
</p>
|
||||
<button
|
||||
onClick={() => setIsBulkAvailable(false)}
|
||||
className=" op-btn op-btn-primary w-[200px]"
|
||||
>
|
||||
{t("buy-credits")}
|
||||
</button>
|
||||
</div>
|
||||
</ModalUi>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-black p-3 bg-white w-full text-sm md:text-base flex justify-center items-center">
|
||||
{t("quick-send-alert-1")}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<div className="text-black p-3 bg-white w-full text-sm md:text-base flex justify-center items-center">
|
||||
{t("quick-send-alert-2")}
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
<div className="text-black p-3 bg-white w-full text-sm md:text-base flex justify-center items-center">
|
||||
{t("quick-send-alert-3")}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{isFreePlan ? (
|
||||
<div className="w-full h-[130px] flex flex-col justify-center items-center text-center p-4">
|
||||
<p className="text-base font-medium mb-2.5">
|
||||
{t("bulk-send-subcription-alert")}
|
||||
</p>
|
||||
<button
|
||||
onClick={() => handleNavigation()}
|
||||
className="op-btn op-btn-primary"
|
||||
>
|
||||
{t("upgrade-now")}
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{admin?.isAdmin ? (
|
||||
<form onSubmit={handleAddOnQuickSubmit} className="p-3">
|
||||
<p className="flex justify-center text-center mx-2 mb-3 text-base op-text-accent font-medium">
|
||||
{t("additional-credits")}
|
||||
</p>
|
||||
<div className="mb-3 flex justify-between">
|
||||
<label
|
||||
htmlFor="quantity"
|
||||
className="block text-xs text-gray-700 font-semibold"
|
||||
>
|
||||
{t("quantity-of-credits")}
|
||||
<span className="text-[red] text-[13px]">*</span>
|
||||
</label>
|
||||
<select
|
||||
value={amount.quantity}
|
||||
onChange={(e) => handlePricePerQuick(e)}
|
||||
name="quantity"
|
||||
className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content w-1/4 text-xs"
|
||||
required
|
||||
>
|
||||
{quantityList.length > 0 &&
|
||||
quantityList.map((x) => (
|
||||
<option key={x} value={x}>
|
||||
{x}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="mb-3 flex justify-between">
|
||||
<label className="block text-xs text-gray-700 font-semibold">
|
||||
{t("Price")} (1 * {amount.priceperbulksend})
|
||||
</label>
|
||||
<div className="w-1/4 flex justify-center items-center text-sm">
|
||||
USD {amount.price}
|
||||
</div>
|
||||
</div>
|
||||
<hr className="text-base-content mb-3" />
|
||||
<button className="op-btn op-btn-primary w-full mt-2">
|
||||
{t("Proceed")}
|
||||
</button>
|
||||
</form>
|
||||
) : (
|
||||
<div className="mx-8 mt-4 mb-8 flex justify-center text-center items-center font-medium break-all">
|
||||
{t("unauthorized-modal", { adminEmail: admin?.email })}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default BulkSendUi;
|
||||
@@ -0,0 +1,289 @@
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
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";
|
||||
const BulkSendUi = (props) => {
|
||||
const { t } = useTranslation();
|
||||
const [forms, setForms] = useState([]);
|
||||
const formRef = useRef(null);
|
||||
const [scrollOnNextUpdate, setScrollOnNextUpdate] = useState(false);
|
||||
const [isSubmit, setIsSubmit] = useState(false);
|
||||
const [isSignatureExist, setIsSignatureExist] = useState();
|
||||
const [isDisableBulkSend, setIsDisableBulkSend] = useState(false);
|
||||
const [isLoader, setIsLoader] = useState(false);
|
||||
useEffect(() => {
|
||||
signatureExist();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
//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);
|
||||
};
|
||||
useEffect(() => {
|
||||
if (scrollOnNextUpdate && formRef.current) {
|
||||
formRef.current.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "end",
|
||||
inline: "nearest"
|
||||
});
|
||||
setScrollOnNextUpdate(false);
|
||||
}
|
||||
}, [forms, scrollOnNextUpdate]);
|
||||
|
||||
useEffect(() => {
|
||||
(() => {
|
||||
if (props?.Placeholders?.length > 0) {
|
||||
let users = [];
|
||||
props?.Placeholders?.forEach((element) => {
|
||||
if (!element.signerObjId) {
|
||||
users = [
|
||||
...users,
|
||||
{
|
||||
fieldId: element.Id,
|
||||
email: "",
|
||||
label: element.Role,
|
||||
signer: {}
|
||||
}
|
||||
];
|
||||
}
|
||||
});
|
||||
setForms((prevForms) => [...prevForms, { Id: 1, fields: users }]);
|
||||
}
|
||||
})();
|
||||
// eslint-disable-next-line
|
||||
}, []);
|
||||
const handleInputChange = (index, signer, fieldIndex) => {
|
||||
const newForms = [...forms];
|
||||
newForms[index].fields[fieldIndex].email = signer?.Email
|
||||
? signer?.Email
|
||||
: signer || "";
|
||||
newForms[index].fields[fieldIndex].signer = signer?.objectId ? signer : "";
|
||||
setForms(newForms);
|
||||
};
|
||||
|
||||
|
||||
const handleRemoveForm = (index) => {
|
||||
const updatedForms = forms.filter((_, i) => i !== index);
|
||||
setForms(updatedForms);
|
||||
};
|
||||
|
||||
function validateEmails(data) {
|
||||
for (const item of data) {
|
||||
for (const field of item.fields) {
|
||||
if (!emailRegex.test(field.email)) {
|
||||
alert(`Invalid email found: ${field.email}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setIsSubmit(true);
|
||||
if (validateEmails(forms)) {
|
||||
// Create a copy of Placeholders array from props.item
|
||||
let Placeholders = [...props.Placeholders];
|
||||
// Initialize an empty array to store updated documents
|
||||
let Documents = [];
|
||||
// Loop through each form
|
||||
forms.forEach((form) => {
|
||||
//checking if user enter email which already exist as a signer then add user in a signers array
|
||||
let existSigner = [];
|
||||
form.fields.map((data) => {
|
||||
if (data.signer) {
|
||||
existSigner.push(data.signer);
|
||||
}
|
||||
});
|
||||
// Map through the copied Placeholders array to update email values
|
||||
const updatedPlaceholders = Placeholders.map((placeholder) => {
|
||||
// Find the field in the current form that matches the placeholder Id
|
||||
const field = form.fields.find(
|
||||
(element) => parseInt(element.fieldId) === placeholder.Id
|
||||
);
|
||||
// If a matching field is found, update the email value in the placeholder
|
||||
const signer = field?.signer?.objectId ? field.signer : "";
|
||||
if (field) {
|
||||
if (signer) {
|
||||
return {
|
||||
...placeholder,
|
||||
signerObjId: field?.signer?.objectId || "",
|
||||
signerPtr: signer
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
...placeholder,
|
||||
email: field.email,
|
||||
signerObjId: field?.signer?.objectId || "",
|
||||
signerPtr: signer
|
||||
};
|
||||
}
|
||||
}
|
||||
// If no matching field is found, keep the placeholder as is
|
||||
return placeholder;
|
||||
});
|
||||
|
||||
// Push a new document object with updated Placeholders into the Documents array
|
||||
if (existSigner?.length > 0) {
|
||||
Documents.push({
|
||||
...props.item,
|
||||
Placeholders: updatedPlaceholders,
|
||||
Signers: props.item.Signers
|
||||
? [...props.item.Signers, ...existSigner]
|
||||
: [...existSigner]
|
||||
});
|
||||
} else {
|
||||
Documents.push({
|
||||
...props.item,
|
||||
Placeholders: updatedPlaceholders,
|
||||
SignatureType: props.signatureType
|
||||
});
|
||||
}
|
||||
});
|
||||
await batchQuery(Documents);
|
||||
} else {
|
||||
setIsSubmit(false);
|
||||
}
|
||||
};
|
||||
|
||||
const batchQuery = async (Documents) => {
|
||||
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,
|
||||
};
|
||||
const params = { Documents: JSON.stringify(Documents) };
|
||||
try {
|
||||
const res = await axios.post(functionsUrl, params, { headers: headers });
|
||||
// console.log("res ", res);
|
||||
if (res.data && res.data.result) {
|
||||
props.handleClose("success", Documents?.length);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("Err ", err);
|
||||
props.handleClose("error", 0);
|
||||
} finally {
|
||||
setIsSubmit(false);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<>
|
||||
{isLoader ? (
|
||||
<div className="w-full h-[100px] flex justify-center items-center z-[999]">
|
||||
<Loader />
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{isSubmit && (
|
||||
<div className="absolute z-[999] h-full w-full flex justify-center items-center bg-black bg-opacity-30">
|
||||
<Loader />
|
||||
</div>
|
||||
)}
|
||||
{!isDisableBulkSend ? (
|
||||
<>
|
||||
{props.Placeholders?.length > 0 ? (
|
||||
isSignatureExist ? (
|
||||
<>
|
||||
{props.Placeholders?.some((x) => !x.signerObjId) ? (
|
||||
<>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="min-h-max max-h-[250px] overflow-y-auto">
|
||||
{forms?.map((form, index) => (
|
||||
<div
|
||||
key={form.Id}
|
||||
className="p-3 op-card border-[1px] border-gray-400 mt-3 mx-4 mb-4 bg-base-200 text-base-content grid grid-cols-1 md:grid-cols-2 gap-2 relative"
|
||||
>
|
||||
{form?.fields?.map((field, fieldIndex) => (
|
||||
<div
|
||||
className="flex flex-col"
|
||||
key={field.fieldId}
|
||||
>
|
||||
<label className="block text-xs font-semibold">
|
||||
{field.label}
|
||||
</label>
|
||||
<SuggestionInput
|
||||
required
|
||||
type="email"
|
||||
value={field.value}
|
||||
index={fieldIndex}
|
||||
onChange={(signer) =>
|
||||
handleInputChange(
|
||||
index,
|
||||
signer,
|
||||
fieldIndex
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
{forms?.length > 1 && (
|
||||
<button
|
||||
onClick={() => handleRemoveForm(index)}
|
||||
className="absolute right-3 top-1 text-[red] border-[1px] border-[red] rounded-lg w-[1.7rem] h-[1.7rem]"
|
||||
>
|
||||
<i className="fa-light fa-trash"></i>
|
||||
</button>
|
||||
)}
|
||||
<div ref={formRef}></div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex flex-col mx-4 mb-4 gap-3">
|
||||
<button
|
||||
type="submit"
|
||||
className="op-btn op-btn-accent focus:outline-none"
|
||||
>
|
||||
<i className="fa-light fa-paper-plane"></i>{" "}
|
||||
<span>{t("send")}</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</>
|
||||
) : (
|
||||
<div className="text-black p-3 bg-white w-full text-sm md:text-base flex justify-center items-center">
|
||||
{t("quick-send-alert-1")}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<div className="text-black p-3 bg-white w-full text-sm md:text-base flex justify-center items-center">
|
||||
{t("quick-send-alert-2")}
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
<div className="text-black p-3 bg-white w-full text-sm md:text-base flex justify-center items-center">
|
||||
{t("quick-send-alert-3")}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default BulkSendUi;
|
||||
@@ -0,0 +1,126 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Loader from "../primitives/Loader";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Parse from "parse";
|
||||
const EditContactForm = (props) => {
|
||||
const { t } = useTranslation();
|
||||
const [isLoader, setIsLoader] = useState(false);
|
||||
const [formData, setFormData] = useState({ Name: "", Email: "", Phone: "" });
|
||||
useEffect(() => {
|
||||
if (props.contact?.Email) {
|
||||
setFormData((prev) => ({ ...prev, ...props.contact }));
|
||||
}
|
||||
}, [props.contact]);
|
||||
const handleChange = (e) => {
|
||||
if (e.target.name === "Email") {
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
[e.target.name]: e.target.value?.toLowerCase()?.replace(/\s/g, "")
|
||||
}));
|
||||
} else {
|
||||
setFormData((prev) => ({ ...prev, [e.target.name]: e.target.value }));
|
||||
}
|
||||
};
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (props.handleEditContact) {
|
||||
try {
|
||||
setIsLoader(true);
|
||||
const params = {
|
||||
contactId: props.contact.objectId,
|
||||
name: formData.Name,
|
||||
email: formData.Email,
|
||||
phone: formData.Phone,
|
||||
tenantId: localStorage.getItem("TenantId")
|
||||
};
|
||||
const res = await Parse.Cloud.run("editcontact", params);
|
||||
const updateContact = {
|
||||
...res,
|
||||
Name: formData.Name,
|
||||
Email: formData.Email,
|
||||
Phone: formData.Phone
|
||||
};
|
||||
props.handleEditContact(updateContact);
|
||||
} catch (err) {
|
||||
console.log("err in edit contact ", err);
|
||||
if (err.code === 137) {
|
||||
alert(t("contact-already-exists"));
|
||||
} else {
|
||||
alert(t("something-went-wrong-mssg"));
|
||||
}
|
||||
} finally {
|
||||
setIsLoader(false);
|
||||
props.handleClose && props.handleClose();
|
||||
}
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div className="h-full p-[20px]">
|
||||
{isLoader && (
|
||||
<div className="fixed inset-0 flex justify-center items-center bg-black bg-opacity-30">
|
||||
<Loader />
|
||||
</div>
|
||||
)}
|
||||
<div className="w-full mx-auto p-2 text-base-content">
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="mb-3">
|
||||
<label htmlFor="name" className="block text-xs font-semibold">
|
||||
{t("name")}
|
||||
<span className="text-[red] text-[13px]"> *</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="name"
|
||||
name="Name"
|
||||
value={formData.Name}
|
||||
onChange={(e) => handleChange(e)}
|
||||
onInput={(e) => e.target.setCustomValidity("")}
|
||||
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<label htmlFor="email" className="block text-xs font-semibold">
|
||||
{t("email")}
|
||||
<span className="text-[red] text-[13px]"> *</span>
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
name="Email"
|
||||
value={formData.Email}
|
||||
onChange={(e) => handleChange(e)}
|
||||
onInput={(e) => e.target.setCustomValidity("")}
|
||||
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<label htmlFor="phone" className="block text-xs font-semibold">
|
||||
{t("phone")}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="phone"
|
||||
name="Phone"
|
||||
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"
|
||||
placeholder={t("phone-optional")}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-4 flex gap-x-2 justify-start">
|
||||
<button type="submit" className="op-btn op-btn-primary">
|
||||
{t("submit")}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditContactForm;
|
||||
@@ -4,6 +4,7 @@ import axios from "axios";
|
||||
import { openInNewTab } from "../constant/Utils";
|
||||
import { useTranslation } from "react-i18next";
|
||||
const Footer = () => {
|
||||
const appName = "OpenSign™";
|
||||
const { t } = useTranslation();
|
||||
const [showButton, setShowButton] = useState(false);
|
||||
const [version, setVersion] = useState("");
|
||||
@@ -39,7 +40,6 @@ const Footer = () => {
|
||||
};
|
||||
}, []);
|
||||
|
||||
const appName = "OpenSign™";
|
||||
const openUrl = () => {
|
||||
openInNewTab(
|
||||
"https://github.com/OpenSignLabs/OpenSign/releases/tag/" + version
|
||||
@@ -1,321 +0,0 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import dp from "../assets/images/dp.png";
|
||||
import FullScreenButton from "./FullScreenButton";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import Parse from "parse";
|
||||
import { useWindowSize } from "../hook/useWindowSize";
|
||||
import {
|
||||
checkIsSubscribed,
|
||||
formatDate,
|
||||
getAppLogo,
|
||||
openInNewTab,
|
||||
saveLanguageInLocal
|
||||
} from "../constant/Utils";
|
||||
import { isEnableSubscription, isStaging } from "../constant/const";
|
||||
import { validplan } from "../json/plansArr";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import ModalUi from "../primitives/ModalUi";
|
||||
import QuotaCard from "../primitives/QuotaCard";
|
||||
|
||||
const Header = ({ showSidebar, setIsMenu }) => {
|
||||
const { t, i18n } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const { width } = useWindowSize();
|
||||
const current_notification_version = "1"; // Update this with each new feature release
|
||||
// Get the dismissed version from local storage
|
||||
const dismissedVersion = localStorage.getItem("notificationDismissedVersion");
|
||||
const username = localStorage.getItem("username") || "";
|
||||
const image = localStorage.getItem("profileImg") || dp;
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [isSubscribe, setIsSubscribe] = useState(true);
|
||||
const [isTeam, setIsTeam] = useState({ plan: "", isValid: false });
|
||||
const [applogo, setAppLogo] = useState(
|
||||
localStorage.getItem("appLogo") || " "
|
||||
);
|
||||
const [emailUsed, setEmailUsed] = useState(0);
|
||||
const [isModal, setIsModal] = useState(false);
|
||||
const [emailResetDate, setEmailResetDate] = useState("");
|
||||
const [showNotification, setShowNotification] = useState(
|
||||
(!dismissedVersion || dismissedVersion !== current_notification_version) &&
|
||||
true
|
||||
);
|
||||
const toggleDropdown = () => {
|
||||
setIsOpen(!isOpen);
|
||||
if (width <= 768) {
|
||||
setIsMenu(false);
|
||||
}
|
||||
};
|
||||
useEffect(() => {
|
||||
checkSubscription();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
async function checkSubscription() {
|
||||
if (isEnableSubscription) {
|
||||
const subscribe = await checkIsSubscribed();
|
||||
if (subscribe.isValid) {
|
||||
const applogo = await getAppLogo();
|
||||
if (applogo?.logo) {
|
||||
setAppLogo(applogo?.logo);
|
||||
} else {
|
||||
setAppLogo(localStorage.getItem("appLogo") || "");
|
||||
}
|
||||
}
|
||||
setIsTeam(subscribe);
|
||||
setIsSubscribe(subscribe.isValid);
|
||||
try {
|
||||
const extUser = await Parse.Cloud.run("getUserDetails");
|
||||
const MonthlyFreeEmails = extUser?.get("MonthlyFreeEmails") || 0;
|
||||
setEmailUsed(MonthlyFreeEmails);
|
||||
if (extUser) {
|
||||
const _extUser = JSON.parse(JSON.stringify(extUser));
|
||||
if (_extUser?.LastEmailCountReset?.iso) {
|
||||
const resetDate = formatDate(_extUser?.LastEmailCountReset?.iso);
|
||||
setEmailResetDate(resetDate);
|
||||
}
|
||||
localStorage.setItem("Extand_Class", JSON.stringify([_extUser]));
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("err in while fetching monthlyfreeEmails", err);
|
||||
}
|
||||
} else {
|
||||
const applogo = await getAppLogo();
|
||||
if (applogo?.logo) {
|
||||
setAppLogo(applogo?.logo);
|
||||
} else {
|
||||
setAppLogo(localStorage.getItem("appLogo") || "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const closeDropdown = async () => {
|
||||
setIsOpen(false);
|
||||
try {
|
||||
await Parse.User.logOut();
|
||||
} catch (err) {
|
||||
console.log("Err while logging out", err);
|
||||
}
|
||||
let appdata = localStorage.getItem("userSettings");
|
||||
let applogo = localStorage.getItem("appLogo");
|
||||
let defaultmenuid = localStorage.getItem("defaultmenuid");
|
||||
let PageLanding = localStorage.getItem("PageLanding");
|
||||
let baseUrl = localStorage.getItem("baseUrl");
|
||||
let appid = localStorage.getItem("parseAppId");
|
||||
|
||||
localStorage.clear();
|
||||
saveLanguageInLocal(i18n);
|
||||
localStorage.setItem("appLogo", applogo);
|
||||
localStorage.setItem("defaultmenuid", defaultmenuid);
|
||||
localStorage.setItem("PageLanding", PageLanding);
|
||||
localStorage.setItem("userSettings", appdata);
|
||||
localStorage.setItem("baseUrl", baseUrl);
|
||||
localStorage.setItem("parseAppId", appid);
|
||||
|
||||
navigate("/");
|
||||
};
|
||||
|
||||
//handle to close profile drop down menu onclick screen
|
||||
useEffect(() => {
|
||||
const closeMenuOnOutsideClick = (e) => {
|
||||
if (isOpen && !e.target.closest("#profile-menu")) {
|
||||
setIsOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("click", closeMenuOnOutsideClick);
|
||||
|
||||
return () => {
|
||||
// Cleanup the event listener when the component unmounts
|
||||
document.removeEventListener("click", closeMenuOnOutsideClick);
|
||||
};
|
||||
}, [isOpen]);
|
||||
const handleConsoleRedirect = () => {
|
||||
if (isStaging) {
|
||||
window.open(" https://staging-console.opensignlabs.com/");
|
||||
} else {
|
||||
window.open("https://console.opensignlabs.com/");
|
||||
}
|
||||
};
|
||||
const handleNavigation = () => {
|
||||
navigate("/subscription");
|
||||
};
|
||||
const handleMailUsed = () => {
|
||||
setIsModal(!isModal);
|
||||
};
|
||||
// Handle dismissing the notification
|
||||
const dismissNotification = () => {
|
||||
// Store the current version as dismissed in local storage
|
||||
localStorage.setItem(
|
||||
"notificationDismissedVersion",
|
||||
current_notification_version
|
||||
);
|
||||
setShowNotification(false);
|
||||
};
|
||||
const handleSetupNowNavigation = () => {
|
||||
const url = isStaging
|
||||
? "https://staging-console.opensignlabs.com/fileadapter"
|
||||
: "https://console.opensignlabs.com/fileadapter";
|
||||
|
||||
openInNewTab(url);
|
||||
};
|
||||
return (
|
||||
<div>
|
||||
{isEnableSubscription && showNotification && (
|
||||
<div className="flex justify-between items-center shadow py-1 bg-[#CAE4FA] p-1">
|
||||
<div className="text-center text-[14px] ml-auto">
|
||||
{t("header-news")} —
|
||||
<span
|
||||
className="cursor-pointer font-medium underline text-blue-800"
|
||||
onClick={() => handleSetupNowNavigation()}
|
||||
>
|
||||
{" " + t("header-news-btn") + "."}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className="ml-auto mr-1 cursor-pointer"
|
||||
onClick={() => dismissNotification()}
|
||||
>
|
||||
<i className="fa-light fa-xmark"></i>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="op-navbar bg-base-100 shadow">
|
||||
<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"
|
||||
onClick={showSidebar}
|
||||
>
|
||||
<i className="fa-light fa-bars text-xl text-base-content"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex-1 ml-2">
|
||||
<div className="h-[25px] md:h-[40px] w-auto overflow-hidden">
|
||||
<img
|
||||
className="object-contain h-full w-auto"
|
||||
src={applogo}
|
||||
alt="img"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div id="profile-menu" className="flex-none gap-2">
|
||||
{!isSubscribe && (
|
||||
<div>
|
||||
<button
|
||||
className="text-xs md:text-sm shadow op-btn op-btn-outline op-btn-sm md:op-btn-md op-btn-accent"
|
||||
onClick={() => handleNavigation()}
|
||||
>
|
||||
{t("upgrade-now")}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{isTeam.isValid && !validplan[isTeam.plan] && (
|
||||
<div className="w-[35px] h-[35px] bg-white rounded-full ring-[1px] ring-offset-2 ring-[#002862] text-[#002862] overflow-hidden font-semibold flex items-center justify-center">
|
||||
{t("pro")}
|
||||
</div>
|
||||
)}
|
||||
{validplan[isTeam.plan] && (
|
||||
<div className="w-[35px] h-[35px] bg-white rounded-full ring-[1px] text-[13px] ring-offset-2 ring-[#002862] text-[#002862] overflow-hidden font-semibold flex items-center justify-center">
|
||||
{t("team")}
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<FullScreenButton />
|
||||
</div>
|
||||
{width >= 768 && (
|
||||
<div
|
||||
onClick={toggleDropdown}
|
||||
className="cursor-pointer w-[35px] h-[35px] rounded-full ring-[1px] ring-offset-2 ring-gray-400 overflow-hidden"
|
||||
>
|
||||
<img
|
||||
className="w-[35px] h-[35px] object-contain"
|
||||
src={image}
|
||||
alt="img"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{width >= 768 && (
|
||||
<div
|
||||
onClick={toggleDropdown}
|
||||
className="cursor-pointer text-base-content text-sm"
|
||||
>
|
||||
{username && username}
|
||||
</div>
|
||||
)}
|
||||
<div className="op-dropdown op-dropdown-end" id="profile-menu">
|
||||
<div
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
className="op-btn op-btn-ghost op-btn-xs w-[10px] h-[20px] hover:bg-transparent"
|
||||
>
|
||||
<i
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
onClick={toggleDropdown}
|
||||
className="fa-light fa-angle-down text-base-content"
|
||||
></i>
|
||||
</div>
|
||||
<ul
|
||||
tabIndex={0}
|
||||
className="mt-3 z-[1] p-2 shadow op-menu op-menu-sm op-dropdown-content text-base-content bg-base-100 rounded-box w-52"
|
||||
>
|
||||
<li onClick={() => openInNewTab("https://docs.opensignlabs.com")}>
|
||||
<span>
|
||||
<i className="fa-light fa-book"></i> {t("docs")}
|
||||
</span>
|
||||
</li>
|
||||
<li
|
||||
onClick={() => {
|
||||
setIsOpen(false);
|
||||
navigate("/profile");
|
||||
}}
|
||||
>
|
||||
<span>
|
||||
<i className="fa-light fa-user"></i> {t("profile")}
|
||||
</span>
|
||||
</li>
|
||||
<li
|
||||
onClick={() => {
|
||||
setIsOpen(false);
|
||||
navigate("/changepassword");
|
||||
}}
|
||||
>
|
||||
<span>
|
||||
<i className="fa-light fa-lock"></i> {t("change-password")}
|
||||
</span>
|
||||
</li>
|
||||
{isEnableSubscription && (
|
||||
<li onClick={() => handleConsoleRedirect()}>
|
||||
<span>
|
||||
<i className="fa-light fa-id-card"></i> Console
|
||||
</span>
|
||||
</li>
|
||||
)}
|
||||
{isEnableSubscription && isTeam?.plan === "freeplan" && (
|
||||
<li className="cursor-pointer" onClick={handleMailUsed}>
|
||||
<span>
|
||||
<i className="fa-light fa-envelope"></i>
|
||||
{emailUsed}/15 {t("sent-this-month")}
|
||||
</span>
|
||||
</li>
|
||||
)}
|
||||
<li onClick={closeDropdown}>
|
||||
<span>
|
||||
<i className="fa-light fa-arrow-right-from-bracket"></i>{" "}
|
||||
{t("log-out")}
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ModalUi isOpen={isModal}>
|
||||
<QuotaCard
|
||||
isPaidInfo={true}
|
||||
handlClose={handleMailUsed}
|
||||
emailResetDate={emailResetDate}
|
||||
/>
|
||||
</ModalUi>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Header;
|
||||
@@ -0,0 +1,252 @@
|
||||
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";
|
||||
import {
|
||||
getAppLogo,
|
||||
openInNewTab,
|
||||
saveLanguageInLocal
|
||||
} from "../constant/Utils";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { appInfo } from "../constant/appinfo";
|
||||
|
||||
const Header = ({ showSidebar, setIsMenu, isConsole }) => {
|
||||
const { t, i18n } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const { width } = useWindowSize();
|
||||
const username = localStorage.getItem("username") || "";
|
||||
const image = localStorage.getItem("profileImg") || dp;
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [applogo, setAppLogo] = useState("");
|
||||
|
||||
const toggleDropdown = () => {
|
||||
setIsOpen(!isOpen);
|
||||
if (width <= 768) {
|
||||
setIsMenu(false);
|
||||
}
|
||||
};
|
||||
useEffect(() => {
|
||||
initializeHead();
|
||||
// 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 closeDropdown = async () => {
|
||||
setIsOpen(false);
|
||||
try {
|
||||
await Parse.User.logOut();
|
||||
} catch (err) {
|
||||
console.log("Err while logging out", err);
|
||||
}
|
||||
let appdata = localStorage.getItem("userSettings");
|
||||
let applogo = localStorage.getItem("appLogo");
|
||||
let defaultmenuid = localStorage.getItem("defaultmenuid");
|
||||
let PageLanding = localStorage.getItem("PageLanding");
|
||||
let baseUrl = localStorage.getItem("baseUrl");
|
||||
let appid = localStorage.getItem("parseAppId");
|
||||
|
||||
localStorage.clear();
|
||||
saveLanguageInLocal(i18n);
|
||||
localStorage.setItem("appLogo", applogo);
|
||||
localStorage.setItem("defaultmenuid", defaultmenuid);
|
||||
localStorage.setItem("PageLanding", PageLanding);
|
||||
localStorage.setItem("userSettings", appdata);
|
||||
localStorage.setItem("baseUrl", baseUrl);
|
||||
localStorage.setItem("parseAppId", appid);
|
||||
|
||||
navigate("/");
|
||||
};
|
||||
|
||||
//handle to close profile drop down menu onclick screen
|
||||
useEffect(() => {
|
||||
const closeMenuOnOutsideClick = (e) => {
|
||||
if (isOpen && !e.target.closest("#profile-menu")) {
|
||||
setIsOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("click", closeMenuOnOutsideClick);
|
||||
|
||||
return () => {
|
||||
// Cleanup the event listener when the component unmounts
|
||||
document.removeEventListener("click", closeMenuOnOutsideClick);
|
||||
};
|
||||
}, [isOpen]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const updateLogoForTheme = () => {
|
||||
const isDarkMode =
|
||||
document.documentElement.getAttribute("data-theme") === "opensigndark";
|
||||
const logo = isDarkMode
|
||||
? "/static/js/assets/images/logo-dark.png" // Path to the dark mode logo
|
||||
: appInfo.applogo; // Use current logo for light mode
|
||||
if (applogo !== logo) {
|
||||
setAppLogo(logo);
|
||||
}
|
||||
};
|
||||
|
||||
// Set the logo immediately based on the current theme
|
||||
updateLogoForTheme();
|
||||
|
||||
const observer = new MutationObserver(() => {
|
||||
updateLogoForTheme();
|
||||
});
|
||||
|
||||
observer.observe(document.documentElement, {
|
||||
attributes: true,
|
||||
attributeFilter: ["data-theme"]
|
||||
});
|
||||
|
||||
return () => observer.disconnect();
|
||||
}, [applogo]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="op-navbar bg-base-100 shadow">
|
||||
<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"
|
||||
onClick={showSidebar}
|
||||
>
|
||||
<i className="fa-light fa-bars text-xl text-base-content"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex-1 ml-2">
|
||||
<div className="h-[25px] md:h-[40px] w-auto overflow-hidden">
|
||||
{applogo && (
|
||||
<img
|
||||
className="object-contain h-full w-auto"
|
||||
src={applogo}
|
||||
alt="logo"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div id="profile-menu" className="flex-none gap-2">
|
||||
<div>
|
||||
<FullScreenButton />
|
||||
</div>
|
||||
{width >= 768 && (
|
||||
<div
|
||||
onClick={toggleDropdown}
|
||||
className="cursor-pointer w-[35px] h-[35px] rounded-full ring-[1px] ring-offset-2 ring-gray-400 overflow-hidden"
|
||||
>
|
||||
<img
|
||||
className="w-[35px] h-[35px] object-contain"
|
||||
src={image}
|
||||
alt="img"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{width >= 768 && (
|
||||
<div
|
||||
onClick={toggleDropdown}
|
||||
role="button"
|
||||
tabIndex="0"
|
||||
className="cursor-pointer text-base-content text-sm"
|
||||
>
|
||||
{username && username}
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className="op-dropdown op-dropdown-open op-dropdown-end"
|
||||
id="profile-menu"
|
||||
>
|
||||
<div
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
onClick={toggleDropdown}
|
||||
className="op-btn op-btn-ghost op-btn-xs w-[10px] h-[20px] hover:bg-transparent"
|
||||
>
|
||||
<i className="fa-light fa-angle-down text-base-content"></i>
|
||||
</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-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={() => {
|
||||
setIsOpen(false);
|
||||
navigate("/profile");
|
||||
}}
|
||||
>
|
||||
<span>
|
||||
<i className="fa-light fa-user"></i> {t("profile")}
|
||||
</span>
|
||||
</li>
|
||||
<li
|
||||
onClick={() => {
|
||||
setIsOpen(false);
|
||||
navigate("/changepassword");
|
||||
}}
|
||||
>
|
||||
<span>
|
||||
<i className="fa-light fa-lock"></i>{" "}
|
||||
{t("change-password")}
|
||||
</span>
|
||||
</li>
|
||||
<li
|
||||
onClick={() => {
|
||||
setIsOpen(false);
|
||||
navigate("/verify-document");
|
||||
}}
|
||||
>
|
||||
<span>
|
||||
<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>
|
||||
</>
|
||||
)}
|
||||
<li onClick={closeDropdown}>
|
||||
<span>
|
||||
<i className="fa-light fa-arrow-right-from-bracket"></i>{" "}
|
||||
{t("log-out")}
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Header;
|
||||
@@ -1,220 +0,0 @@
|
||||
import React, { useState } from "react";
|
||||
import Parse from "parse";
|
||||
import Loader from "../primitives/Loader";
|
||||
import { useTranslation } from "react-i18next";
|
||||
// import FacebookLogin from "react-facebook-login";
|
||||
|
||||
const LoginFacebook = ({
|
||||
FBCred,
|
||||
thirdpartyLoginfn,
|
||||
thirdpartyLoader,
|
||||
setThirdpartyLoader
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [isModal, setIsModal] = useState(false);
|
||||
const [userDetails, setUserDetails] = useState({
|
||||
Phone: "",
|
||||
Company: "",
|
||||
Name: "",
|
||||
Email: ""
|
||||
});
|
||||
const [fBDetails, setFBDetails] = useState({
|
||||
Id: "",
|
||||
AccessToken: "",
|
||||
Name: "",
|
||||
Email: ""
|
||||
});
|
||||
const responseFacebook = async (response) => {
|
||||
// console.log("response ", response);
|
||||
if (response.userID) {
|
||||
setThirdpartyLoader(true);
|
||||
const details = {
|
||||
Email: response.email,
|
||||
Name: response.name
|
||||
};
|
||||
setUserDetails({ ...userDetails, ...details });
|
||||
const fDetails = {
|
||||
Id: response.userID,
|
||||
AccessToken: response.accessToken,
|
||||
Name: response.name,
|
||||
Email: response.email
|
||||
};
|
||||
setFBDetails({ ...fBDetails, ...fDetails });
|
||||
const res = await checkExtUser(fDetails);
|
||||
}
|
||||
};
|
||||
|
||||
const checkExtUser = async (details) => {
|
||||
// const extUser = new Parse.Query("contracts_Users");
|
||||
// extUser.equalTo("Email", details.Email);
|
||||
// const extRes = await extUser.first();
|
||||
const params = { email: details.Email };
|
||||
const extRes = await Parse.Cloud.run("getUserDetails", params);
|
||||
// console.log("extRes ", extRes);
|
||||
if (extRes) {
|
||||
const params = { ...details, Phone: extRes?.get("Phone") || "" };
|
||||
const payload = await Parse.Cloud.run("facebooksign", params);
|
||||
// console.log("payload ", payload);
|
||||
if (payload && payload.sessiontoken) {
|
||||
const billingDate =
|
||||
extRes.get("Next_billing_date") && extRes.get("Next_billing_date");
|
||||
// console.log("billingDate expired", billingDate > new Date());
|
||||
const LocalUserDetails = {
|
||||
name: details.Name,
|
||||
email: details.Email,
|
||||
phone: extRes?.get("Phone") || "",
|
||||
company: extRes.get("Company")
|
||||
};
|
||||
localStorage.setItem("userDetails", JSON.stringify(LocalUserDetails));
|
||||
thirdpartyLoginfn(payload.sessiontoken, billingDate);
|
||||
}
|
||||
return { msg: "exist" };
|
||||
} else {
|
||||
setIsModal(true);
|
||||
setThirdpartyLoader(false);
|
||||
return { msg: "notexist" };
|
||||
}
|
||||
};
|
||||
const handleSubmitbtn = async () => {
|
||||
if (userDetails.Phone && userDetails.Company) {
|
||||
setThirdpartyLoader(true);
|
||||
const params = { ...fBDetails, Phone: userDetails?.Phone || "" };
|
||||
const payload = await Parse.Cloud.run("facebooksign", params);
|
||||
// console.log("payload ", payload);
|
||||
|
||||
if (payload && payload.sessiontoken) {
|
||||
const params = {
|
||||
userDetails: {
|
||||
name: userDetails.Name,
|
||||
email: userDetails.Email,
|
||||
// "passsword":userDetails.Phone,
|
||||
phone: userDetails?.Phone || "",
|
||||
role: "contracts_User",
|
||||
company: userDetails.Company
|
||||
}
|
||||
};
|
||||
const userSignUp = await Parse.Cloud.run("usersignup", params);
|
||||
// console.log("userSignUp ", userSignUp);
|
||||
if (userSignUp && userSignUp.sessionToken) {
|
||||
const LocalUserDetails = {
|
||||
name: userDetails.Name,
|
||||
email: userDetails.Email,
|
||||
phone: userDetails?.Phone || "",
|
||||
company: userDetails.Company
|
||||
};
|
||||
localStorage.setItem("userDetails", JSON.stringify(LocalUserDetails));
|
||||
thirdpartyLoginfn(userSignUp.sessionToken);
|
||||
} else {
|
||||
alert(userSignUp.message);
|
||||
}
|
||||
} else {
|
||||
alert(t("server-error"));
|
||||
}
|
||||
} else {
|
||||
alert(t("fill-required-details!"));
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div className="relative">
|
||||
{thirdpartyLoader && (
|
||||
<div className="fixed flex justify-center items-center inset-0 bg-black bg-opacity-25 z-20 ">
|
||||
<Loader />
|
||||
</div>
|
||||
)}
|
||||
{/* <FacebookLogin
|
||||
appId={FBCred}
|
||||
fields="name,email,picture"
|
||||
callback={responseFacebook}
|
||||
cssClass="btn btn-info btn-md btn-fb"
|
||||
icon="fa fa-facebook-f"
|
||||
/> */}
|
||||
{isModal && (
|
||||
<div
|
||||
className="modal fade show block z-[1]"
|
||||
id="exampleModal"
|
||||
tabIndex="-1"
|
||||
role="dialog"
|
||||
>
|
||||
<div className="modal-dialog" role="document">
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<h5 className="modal-title">{t("sign-up-form")}</h5>
|
||||
<span>
|
||||
<span></span>
|
||||
</span>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
<form>
|
||||
<div className="form-group">
|
||||
<label htmlFor="Phone" className="col-form-label">
|
||||
{t("phone")}{" "}
|
||||
<span className="text-[red] text-[13px]">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="tel"
|
||||
className="form-control"
|
||||
id="Phone"
|
||||
value={userDetails.Phone}
|
||||
onChange={(e) =>
|
||||
setUserDetails({
|
||||
...userDetails,
|
||||
Phone: e.target.value
|
||||
})
|
||||
}
|
||||
onInvalid={(e) =>
|
||||
e.target.setCustomValidity(t("input-required"))
|
||||
}
|
||||
onInput={(e) => e.target.setCustomValidity("")}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label htmlFor="Company" className="col-form-label">
|
||||
{t("company")}{" "}
|
||||
<span className="text-[red] text-[13px]">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
id="Company"
|
||||
value={userDetails.Company}
|
||||
onChange={(e) =>
|
||||
setUserDetails({
|
||||
...userDetails,
|
||||
Company: e.target.value
|
||||
})
|
||||
}
|
||||
onInvalid={(e) =>
|
||||
e.target.setCustomValidity(t("input-required"))
|
||||
}
|
||||
onInput={(e) => e.target.setCustomValidity("")}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleSubmitbtn()}
|
||||
className="btn btn-info mr-[10px]"
|
||||
>
|
||||
{t("sign-up")}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-secondary w-[90px]"
|
||||
onClick={() => setIsModal(false)}
|
||||
>
|
||||
{t("cancel")}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LoginFacebook;
|
||||
@@ -1,280 +0,0 @@
|
||||
import React, { useState, useRef } from "react";
|
||||
import Parse from "parse";
|
||||
import { jwtDecode } from "jwt-decode";
|
||||
import { useScript } from "../hook/useScript";
|
||||
import ModalUi from "../primitives/ModalUi";
|
||||
import Loader from "../primitives/Loader";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { saveLanguageInLocal } from "../constant/Utils";
|
||||
/*
|
||||
* `GoogleSignInBtn`as it's name indicates it render google sign in button
|
||||
* and in this `useScript` in which we have created for generate google sign button
|
||||
* and when user click on sign in with google it will be verify on server side
|
||||
* and then generate token
|
||||
*/
|
||||
const GoogleSignInBtn = ({
|
||||
GoogleCred,
|
||||
thirdpartyLoginfn,
|
||||
thirdpartyLoader,
|
||||
setThirdpartyLoader
|
||||
}) => {
|
||||
const { t, i18n } = useTranslation();
|
||||
const [isModal, setIsModal] = useState(false);
|
||||
const googleBtn = useRef();
|
||||
const [userDetails, setUserDetails] = useState({
|
||||
Phone: "",
|
||||
Company: "",
|
||||
Name: "",
|
||||
Email: "",
|
||||
Destination: ""
|
||||
});
|
||||
const [googleDetails, setGoogleDetails] = useState({
|
||||
Id: "",
|
||||
TokenId: "",
|
||||
Gmail: "",
|
||||
Name: ""
|
||||
});
|
||||
useScript("https://accounts.google.com/gsi/client", () => {
|
||||
window.google.accounts.id.initialize({
|
||||
client_id: GoogleCred,
|
||||
callback: responseGoogle,
|
||||
auto_select: false
|
||||
});
|
||||
window.google.accounts.id.renderButton(googleBtn.current, {
|
||||
theme: "outline",
|
||||
size: "large",
|
||||
width: "187px"
|
||||
});
|
||||
});
|
||||
const clearStorage = async () => {
|
||||
try {
|
||||
await Parse.User.logOut();
|
||||
} catch (err) {
|
||||
console.log("Err while logging out", err);
|
||||
}
|
||||
let baseUrl = localStorage.getItem("baseUrl");
|
||||
let appid = localStorage.getItem("parseAppId");
|
||||
let applogo = localStorage.getItem("appLogo");
|
||||
let defaultmenuid = localStorage.getItem("defaultmenuid");
|
||||
let PageLanding = localStorage.getItem("PageLanding");
|
||||
let userSettings = localStorage.getItem("userSettings");
|
||||
|
||||
localStorage.clear();
|
||||
saveLanguageInLocal(i18n);
|
||||
|
||||
localStorage.setItem("baseUrl", baseUrl);
|
||||
localStorage.setItem("parseAppId", appid);
|
||||
localStorage.setItem("appLogo", applogo);
|
||||
localStorage.setItem("defaultmenuid", defaultmenuid);
|
||||
localStorage.setItem("PageLanding", PageLanding);
|
||||
localStorage.setItem("userSettings", userSettings);
|
||||
localStorage.setItem("baseUrl", baseUrl);
|
||||
localStorage.setItem("parseAppId", appid);
|
||||
};
|
||||
const responseGoogle = async (response) => {
|
||||
clearStorage();
|
||||
setThirdpartyLoader(true);
|
||||
// console.log("response ", response);
|
||||
if (response.credential) {
|
||||
const data = jwtDecode(response.credential);
|
||||
// console.log("data ", data);
|
||||
if (data.sub && data.email) {
|
||||
const details = { Email: data.email, Name: data.name };
|
||||
setUserDetails({ ...userDetails, ...details });
|
||||
const Gdetails = {
|
||||
Id: data.sub,
|
||||
TokenId: response.credential,
|
||||
Gmail: data.email,
|
||||
Name: data.name
|
||||
};
|
||||
setGoogleDetails({ ...googleDetails, ...Gdetails });
|
||||
await checkExtUser(Gdetails);
|
||||
}
|
||||
}
|
||||
};
|
||||
const checkExtUser = async (details) => {
|
||||
const params = { email: details.Gmail };
|
||||
const extRes = await Parse.Cloud.run("getUserDetails", params);
|
||||
if (extRes) {
|
||||
// const params = { ...details, Phone: extRes?.get("Phone") || "" };
|
||||
const params = { ...details, extUserId: extRes.objectId };
|
||||
const payload = await Parse.Cloud.run("googlesign", params);
|
||||
if (payload && payload.sessiontoken) {
|
||||
const LocalUserDetails = {
|
||||
name: details.Name,
|
||||
email: details.Gmail,
|
||||
phone: payload?.phone || "",
|
||||
company: payload.company
|
||||
};
|
||||
localStorage.setItem("userDetails", JSON.stringify(LocalUserDetails));
|
||||
thirdpartyLoginfn(payload.sessiontoken);
|
||||
}
|
||||
return { msg: "exist" };
|
||||
} else {
|
||||
setIsModal(true);
|
||||
setThirdpartyLoader(false);
|
||||
return { msg: "notexist" };
|
||||
}
|
||||
};
|
||||
const handleSubmitbtn = async () => {
|
||||
if (userDetails.Phone && userDetails.Company) {
|
||||
setThirdpartyLoader(true);
|
||||
// e.preventDefault()
|
||||
// console.log("handelSubmit", userDetails);
|
||||
const params = { ...googleDetails, Phone: userDetails?.Phone };
|
||||
const payload = await Parse.Cloud.run("googlesign", params);
|
||||
|
||||
// console.log("payload ", payload);
|
||||
if (payload && payload.sessiontoken) {
|
||||
const params = {
|
||||
userDetails: {
|
||||
name: userDetails.Name,
|
||||
email: userDetails.Email,
|
||||
// "passsword":userDetails.Phone,
|
||||
phone: userDetails?.Phone || "",
|
||||
role: "contracts_User",
|
||||
company: userDetails.Company,
|
||||
jobTitle: userDetails.Destination
|
||||
}
|
||||
};
|
||||
const userSignUp = await Parse.Cloud.run("usersignup", params);
|
||||
// console.log("userSignUp ", userSignUp);
|
||||
if (userSignUp && userSignUp.sessionToken) {
|
||||
const LocalUserDetails = {
|
||||
name: userDetails.Name,
|
||||
email: userDetails.Email,
|
||||
phone: userDetails?.Phone || "",
|
||||
company: userDetails.Company
|
||||
// jobTitle: userDetails.JobTitle
|
||||
};
|
||||
localStorage.setItem("userDetails", JSON.stringify(LocalUserDetails));
|
||||
thirdpartyLoginfn(userSignUp.sessionToken);
|
||||
} else {
|
||||
alert(userSignUp.message);
|
||||
}
|
||||
} else if (
|
||||
payload &&
|
||||
payload.message.replace(/ /g, "_") === "Internal_server_err"
|
||||
) {
|
||||
alert(t("server-error"));
|
||||
}
|
||||
} else {
|
||||
alert(t("fill-required-details!"));
|
||||
}
|
||||
};
|
||||
const handleCloseModal = async () => {
|
||||
setIsModal(false);
|
||||
try {
|
||||
await Parse.User.logOut();
|
||||
} catch (err) {
|
||||
console.log("Err while logging out", err);
|
||||
}
|
||||
let appdata = localStorage.getItem("userSettings");
|
||||
let applogo = localStorage.getItem("appLogo");
|
||||
let defaultmenuid = localStorage.getItem("defaultmenuid");
|
||||
let PageLanding = localStorage.getItem("PageLanding");
|
||||
let baseUrl = localStorage.getItem("baseUrl");
|
||||
let appid = localStorage.getItem("parseAppId");
|
||||
|
||||
localStorage.clear();
|
||||
saveLanguageInLocal(i18n);
|
||||
localStorage.setItem("appLogo", applogo);
|
||||
localStorage.setItem("defaultmenuid", defaultmenuid);
|
||||
localStorage.setItem("PageLanding", PageLanding);
|
||||
localStorage.setItem("userSettings", appdata);
|
||||
localStorage.setItem("baseUrl", baseUrl);
|
||||
localStorage.setItem("parseAppId", appid);
|
||||
};
|
||||
return (
|
||||
<div className="relative">
|
||||
{thirdpartyLoader && (
|
||||
<div className="fixed flex justify-center items-center inset-0 bg-black bg-opacity-25 z-20 ">
|
||||
<Loader />
|
||||
</div>
|
||||
)}
|
||||
<div ref={googleBtn} className="text-sm"></div>
|
||||
<ModalUi showClose={false} isOpen={isModal} title={t("sign-up-form")}>
|
||||
<form className="px-4 py-3 text-base-content">
|
||||
<div className="mb-3">
|
||||
<label htmlFor="Phone" className="block text-xs font-semibold">
|
||||
{t("phone")} <span className="text-[13px] text-[red]">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="tel"
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
id="Phone"
|
||||
value={userDetails.Phone}
|
||||
onChange={(e) =>
|
||||
setUserDetails({
|
||||
...userDetails,
|
||||
Phone: e.target.value
|
||||
})
|
||||
}
|
||||
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
|
||||
onInput={(e) => e.target.setCustomValidity("")}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<label htmlFor="Company" className="block text-xs font-semibold">
|
||||
{t("company")} <span className="text-[13px] text-[red]">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
id="Company"
|
||||
value={userDetails.Company}
|
||||
onChange={(e) =>
|
||||
setUserDetails({
|
||||
...userDetails,
|
||||
Company: e.target.value
|
||||
})
|
||||
}
|
||||
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
|
||||
onInput={(e) => e.target.setCustomValidity("")}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<label htmlFor="JobTitle" className="block text-xs font-semibold">
|
||||
{t("job-title")} <span className="text-[13px] text-[red]">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
id="JobTitle"
|
||||
value={userDetails.Destination}
|
||||
onChange={(e) =>
|
||||
setUserDetails({
|
||||
...userDetails,
|
||||
Destination: e.target.value
|
||||
})
|
||||
}
|
||||
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
|
||||
onInput={(e) => e.target.setCustomValidity("")}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-row gap-2">
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-primary"
|
||||
onClick={() => handleSubmitbtn()}
|
||||
>
|
||||
{t("sign-up")}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-ghost"
|
||||
onClick={handleCloseModal}
|
||||
>
|
||||
{t("cancel")}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</ModalUi>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default GoogleSignInBtn;
|
||||
@@ -1,7 +1,9 @@
|
||||
import React from "react";
|
||||
import { Document, Page } from "react-pdf";
|
||||
import { Stage, Layer, Rect, Text } from "react-konva";
|
||||
import { useTranslation } from "react-i18next";
|
||||
const RenderDebugPdf = (props) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div>
|
||||
<div className="sticky top-0 p-[10px] z-10 bg-white border-[1px] border-[gray] my-[5px]">
|
||||
@@ -12,10 +14,9 @@ const RenderDebugPdf = (props) => {
|
||||
onMouseMove={props.handleMouseMoveDiv}
|
||||
>
|
||||
<Document
|
||||
onLoadError={() => {
|
||||
props.setPdfLoadFail(false);
|
||||
}}
|
||||
loading={"Loading Document.."}
|
||||
onLoadError={() => props.setPdfLoadFail(false)}
|
||||
loading={t("loading-doc")}
|
||||
error={<p className="mx-2">{t("failed-to-load-refresh-page")}</p>}
|
||||
onLoadSuccess={props.pageDetails}
|
||||
ref={props.pdfRef}
|
||||
file={props.pdfUrl}
|
||||
@@ -10,11 +10,17 @@ function RotateAlert(props) {
|
||||
title={t("Rotation-alert")}
|
||||
handleClose={() => props.setShowRotateAlert({ status: false, degree: 0 })}
|
||||
>
|
||||
{" "}
|
||||
<div className="p-[20px] h-full">
|
||||
<p>{t("rotate-alert-mssg")}</p>
|
||||
<div className="h-[1px] w-full my-[15px] bg-[#9f9f9f]"></div>
|
||||
<div className="flex gap-1">
|
||||
<button
|
||||
onClick={() => props.handleRemoveWidgets()}
|
||||
type="button"
|
||||
className="op-btn op-btn-primary"
|
||||
>
|
||||
{t("yes")}
|
||||
</button>
|
||||
<button
|
||||
onClick={() =>
|
||||
props.setShowRotateAlert({ status: false, degree: 0 })
|
||||
@@ -24,13 +30,6 @@ function RotateAlert(props) {
|
||||
>
|
||||
{t("no")}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => props.handleRemoveWidgets()}
|
||||
type="button"
|
||||
className="op-btn op-btn-primary"
|
||||
>
|
||||
{t("yes")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</ModalUi>
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { NavLink } from "react-router-dom";
|
||||
import { NavLink } from "react-router";
|
||||
|
||||
const SocialMedia = () => {
|
||||
const { t } = useTranslation();
|
||||
@@ -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,11 +1,12 @@
|
||||
import React from "react";
|
||||
import { Helmet } from "react-helmet";
|
||||
|
||||
function Title({ title, drive }) {
|
||||
const appName =
|
||||
"OpenSign™";
|
||||
return (
|
||||
<Helmet>
|
||||
<title>{drive ? title : `${title} - OpenSign™`}</title>
|
||||
<meta name="description" content={`${title} - OpenSign™`} />
|
||||
<title>{drive ? title : `${title} - ${appName}`}</title>
|
||||
<meta name="description" content={`${title} - ${appName}`} />
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useNavigate } from "react-router";
|
||||
import { openInNewTab } from "../../constant/Utils";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
@@ -29,16 +29,28 @@ 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">
|
||||
<span className="rounded-full bg-base-content bg-opacity-20 w-[60px] h-[60px] self-start flex justify-center items-center">
|
||||
<i
|
||||
className={`${
|
||||
props.Icon ? props.Icon : "fa-light fa-info"
|
||||
} text-[25px] lg:text-[30px]`}
|
||||
></i>
|
||||
</span>
|
||||
<div className=" text-lg ml-3 text-base-content">
|
||||
<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
|
||||
className={`${
|
||||
props.Icon ? props.Icon : "fa-light fa-info"
|
||||
} text-[25px] lg:text-[30px]`}
|
||||
></i>
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-lg ml-3">
|
||||
{t(`sidebar.${props.Label}`)}
|
||||
{props.Label === "Sign yourself" && (
|
||||
<div className="text-gray-500 text-xs mt-1">
|
||||
{t("signyour-self-button")}
|
||||
</div>
|
||||
)}
|
||||
{props.Label === "Request signatures" && (
|
||||
<div className="text-gray-500 text-xs mt-1">
|
||||
{t("requestsign-button")}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -2,7 +2,7 @@ import React, { useState, useEffect } from "react";
|
||||
import axios from "axios";
|
||||
import Parse from "parse";
|
||||
import getReplacedHashQuery from "../../constant/getReplacedHashQuery";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useNavigate } from "react-router";
|
||||
import Tooltip from "../../primitives/Tooltip";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
@@ -133,7 +133,7 @@ const DashboardCard = (props) => {
|
||||
skip: 0,
|
||||
limit: 200
|
||||
};
|
||||
const url = `${parseBaseUrl}/functions/getReport`;
|
||||
const url = `${parseBaseUrl}functions/getReport`;
|
||||
await axios
|
||||
.post(url, params, {
|
||||
headers: {
|
||||
@@ -346,7 +346,6 @@ const DashboardCard = (props) => {
|
||||
id={props.Label}
|
||||
iconColor={"white"}
|
||||
message={t(`tour-mssg.${props.Label}`)}
|
||||
// {props?.Data?.tourMessage}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -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, 200);
|
||||
getReportData(props.Record.reportId, List.length, 20, searchTerm);
|
||||
}
|
||||
// eslint-disable-next-line
|
||||
}, [isNextRecord]);
|
||||
|
||||
const getReportData = async (id, skipUserRecord = 0, limit = 200) => {
|
||||
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) {
|
||||
@@ -56,8 +119,13 @@ function DashboardReport(props) {
|
||||
sessiontoken: localStorage.getItem("accesstoken")
|
||||
};
|
||||
try {
|
||||
const params = { reportId: id, skip: skipUserRecord, limit: limit };
|
||||
const url = `${localStorage.getItem("baseUrl")}/functions/getReport`;
|
||||
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,
|
||||
signal: abortController.signal // is used to cancel fetch query
|
||||
@@ -94,7 +162,7 @@ function DashboardReport(props) {
|
||||
prevRecord.length > 0 ? [...prevRecord, ...arr] : arr
|
||||
);
|
||||
} else {
|
||||
if (res.data.result.length === docPerPage) {
|
||||
if (res.data.result.length >= docPerPage) {
|
||||
setIsMoreDocs(true);
|
||||
} else {
|
||||
setIsMoreDocs(false);
|
||||
@@ -139,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,14 +1,15 @@
|
||||
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 { useNavigate } from "react-router-dom";
|
||||
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";
|
||||
import { handleDownloadPdf } from "../../constant/Utils";
|
||||
import { handleDownloadPdf, isMobile } from "../../constant/Utils";
|
||||
import Parse from "parse";
|
||||
|
||||
function DriveBody(props) {
|
||||
const { t } = useTranslation();
|
||||
@@ -17,7 +18,7 @@ function DriveBody(props) {
|
||||
const inputRef = useRef(null);
|
||||
const [isOpenMoveModal, setIsOpenMoveModal] = useState(false);
|
||||
const [selectDoc, setSelectDoc] = useState();
|
||||
const [isDeleteDoc, setIsDeleteDoc] = useState(false);
|
||||
const [isDeleteDoc, setIsDeleteDoc] = useState({});
|
||||
const contextMenu = [
|
||||
{ type: "Download", icon: "fa-light fa-arrow-down" },
|
||||
{ type: "Rename", icon: "fa-light fa-font" },
|
||||
@@ -137,7 +138,7 @@ function DriveBody(props) {
|
||||
}
|
||||
};
|
||||
|
||||
const handleMenuItemClick = async (selectType, data) => {
|
||||
const handleMenuItemClick = async (selectType, data, deleteType) => {
|
||||
switch (selectType) {
|
||||
case "Download": {
|
||||
await handleDownloadPdf([data]);
|
||||
@@ -149,7 +150,7 @@ function DriveBody(props) {
|
||||
break;
|
||||
}
|
||||
case "Delete": {
|
||||
setIsDeleteDoc(true);
|
||||
setIsDeleteDoc({ status: true, deleteType });
|
||||
setSelectDoc(data);
|
||||
break;
|
||||
}
|
||||
@@ -163,7 +164,7 @@ function DriveBody(props) {
|
||||
};
|
||||
//function for delete document
|
||||
const handleDeleteDocument = async (docData) => {
|
||||
setIsDeleteDoc(false);
|
||||
setIsDeleteDoc({});
|
||||
const docId = docData.objectId;
|
||||
const data = {
|
||||
IsArchive: true
|
||||
@@ -269,6 +270,50 @@ function DriveBody(props) {
|
||||
}
|
||||
};
|
||||
|
||||
const checkFolderEmpty = async (docData) => {
|
||||
let isEmptyFolder = true;
|
||||
const query = new Parse.Query("contracts_Document");
|
||||
query.equalTo("Folder", {
|
||||
__type: "Pointer",
|
||||
className: "contracts_Document",
|
||||
objectId: docData.objectId
|
||||
});
|
||||
query.notEqualTo("IsArchive", true);
|
||||
const res = await query.find();
|
||||
const jsonRes = JSON.parse(JSON.stringify(res));
|
||||
if (jsonRes && jsonRes.length > 0) {
|
||||
isEmptyFolder = false;
|
||||
return isEmptyFolder;
|
||||
} else {
|
||||
return isEmptyFolder;
|
||||
}
|
||||
};
|
||||
const handleDeleteFolder = async (docData) => {
|
||||
setIsDeleteDoc({});
|
||||
const isEmptyFolder = await checkFolderEmpty(docData);
|
||||
if (isEmptyFolder) {
|
||||
const docId = docData?.objectId;
|
||||
try {
|
||||
const updateQuery = new Parse.Query("contracts_Document");
|
||||
const updateObj = await updateQuery.get(docId);
|
||||
updateObj.set("IsArchive", true);
|
||||
const res = await updateObj.save();
|
||||
if (res) {
|
||||
const updatedData = props.pdfData.filter((x) => x.objectId !== docId);
|
||||
props.setPdfData(updatedData);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("Err ", err);
|
||||
props.setIsAlert({
|
||||
isShow: true,
|
||||
alertMessage: t("something-went-wrong-mssg")
|
||||
});
|
||||
}
|
||||
} else {
|
||||
alert(t("delete-folder-alert-1"));
|
||||
}
|
||||
};
|
||||
|
||||
//component to handle type of document and render according to type
|
||||
const handleFolderData = (data, ind, listType) => {
|
||||
let createddate, status, isDecline, signerExist, isComplete;
|
||||
@@ -302,15 +347,18 @@ function DriveBody(props) {
|
||||
}
|
||||
|
||||
const signersName = () => {
|
||||
const getSignersName = signerExist.map((data) => data.Name);
|
||||
const signerName = getSignersName.join(", ");
|
||||
const getSignersName =
|
||||
signerExist?.length > 0 && signerExist?.map((data) => data?.Name || "");
|
||||
const signerName =
|
||||
getSignersName?.length > 0 ? getSignersName?.join(", ") : "";
|
||||
|
||||
return (
|
||||
<span className="text-[12px] font-medium w-[90%] break-words">
|
||||
{signerName}
|
||||
{signerName && signerName}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
return listType === "table" ? (
|
||||
data.Type === "Folder" ? (
|
||||
<tr onClick={() => handleOnclikFolder(data)}>
|
||||
@@ -359,7 +407,7 @@ function DriveBody(props) {
|
||||
) : listType === "list" && data.Type === "Folder" ? (
|
||||
<div key={ind} className="relative w-[100px] h-[100px] mx-2 my-3">
|
||||
<ContextMenu.Root>
|
||||
<ContextMenu.Trigger className="flex flex-col justify-center items-center">
|
||||
<ContextMenu.Trigger className="flex flex-col justify-center items-center select-none-cls">
|
||||
{/* folder */}
|
||||
<div
|
||||
data-tut={props.dataTutSeventh}
|
||||
@@ -395,7 +443,7 @@ function DriveBody(props) {
|
||||
className="op-input op-input-bordered op-input-xs w-[100px] focus:outline-none hover:border-base-content text-[10px]"
|
||||
/>
|
||||
) : (
|
||||
<span className="fileName">{data.Name}</span>
|
||||
<span className="fileName select-none-cls">{data.Name}</span>
|
||||
)}
|
||||
</div>
|
||||
</ContextMenu.Trigger>
|
||||
@@ -413,13 +461,20 @@ function DriveBody(props) {
|
||||
<i className="fa-light fa-font mr-[8px]"></i>
|
||||
<span>Rename</span>
|
||||
</ContextMenu.Item>
|
||||
<ContextMenu.Item
|
||||
onClick={() => handleMenuItemClick("Delete", data, data.Type)}
|
||||
className="ContextMenuItem"
|
||||
>
|
||||
<i className="fa-light fa-trash mr-[8px]"></i>
|
||||
<span>Delete</span>
|
||||
</ContextMenu.Item>
|
||||
</ContextMenu.Content>
|
||||
</ContextMenu.Portal>
|
||||
</ContextMenu.Root>
|
||||
</div>
|
||||
) : (
|
||||
<HoverCard.Root
|
||||
open={rename ? false : undefined}
|
||||
open={rename || isMobile ? false : undefined}
|
||||
openDelay={0}
|
||||
closeDelay={100}
|
||||
>
|
||||
@@ -427,7 +482,10 @@ function DriveBody(props) {
|
||||
<div>
|
||||
<ContextMenu.Root>
|
||||
<div className="relative w-[100px] h-[100px] mx-2 my-3">
|
||||
<ContextMenu.Trigger className="flex flex-col justify-center items-center">
|
||||
<ContextMenu.Trigger
|
||||
asChild
|
||||
className="flex flex-col justify-center items-center select-none-cls"
|
||||
>
|
||||
{/* pdf */}
|
||||
<div
|
||||
data-tut={props.dataTutSixth}
|
||||
@@ -456,14 +514,16 @@ function DriveBody(props) {
|
||||
}
|
||||
}}
|
||||
onBlur={() => handledRenameDoc(data)}
|
||||
onKeyDown={(e) => handleEnterPress(e, data)}
|
||||
onKeyDown={(e) => handleEnterPress(e, data, data.Type)}
|
||||
ref={inputRef}
|
||||
defaultValue={renameValue}
|
||||
onChange={(e) => setRenameValue(e.target.value)}
|
||||
className="op-input op-input-bordered op-input-xs w-[100px] focus:outline-none hover:border-base-content text-[10px]"
|
||||
/>
|
||||
) : (
|
||||
<span className="fileName">{data.Name}</span>
|
||||
<span className="fileName select-none-cls">
|
||||
{data.Name}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</ContextMenu.Trigger>
|
||||
@@ -568,7 +628,7 @@ function DriveBody(props) {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{props.pdfData.map((data, ind) => {
|
||||
{props?.pdfData?.map((data, ind) => {
|
||||
return (
|
||||
<React.Fragment key={ind}>
|
||||
{handleFolderData(data, ind, "table")}
|
||||
@@ -596,22 +656,33 @@ function DriveBody(props) {
|
||||
/>
|
||||
)}
|
||||
<ModalUi
|
||||
isOpen={isDeleteDoc}
|
||||
isOpen={isDeleteDoc.status}
|
||||
title={t("delete-document")}
|
||||
handleClose={() => setIsDeleteDoc(false)}
|
||||
handleClose={() => setIsDeleteDoc({})}
|
||||
>
|
||||
<div className="h-full p-[20px] text-base-content">
|
||||
<p>{t("delete-document-alert")}</p>
|
||||
{isDeleteDoc.deleteType ? (
|
||||
<p>{t("delete-folder-alert")}</p>
|
||||
) : (
|
||||
<p>{t("delete-document-alert")}</p>
|
||||
)}
|
||||
|
||||
<div className="h-[1px] w-full bg-[#9f9f9f] my-[15px]"></div>
|
||||
<button
|
||||
onClick={() => handleDeleteDocument(selectDoc)}
|
||||
onClick={() => {
|
||||
if (isDeleteDoc.deleteType) {
|
||||
handleDeleteFolder(selectDoc);
|
||||
} else {
|
||||
handleDeleteDocument(selectDoc);
|
||||
}
|
||||
}}
|
||||
type="button"
|
||||
className="op-btn op-btn-primary mr-2"
|
||||
>
|
||||
{t("yes")}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setIsDeleteDoc(false)}
|
||||
onClick={() => setIsDeleteDoc({})}
|
||||
type="button"
|
||||
className="op-btn op-btn-neutral"
|
||||
>
|
||||
@@ -10,15 +10,15 @@ 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}
|
||||
onChange={(e) => props.setRoleName(e.target.value)}
|
||||
placeholder={
|
||||
props.signersdata.length > 0
|
||||
? "User " + (props.signersdata.length + 1)
|
||||
: "User 1"
|
||||
? "Role " + (props.signersdata.length + 1)
|
||||
: "Role 1"
|
||||
}
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs mt-1"
|
||||
/>
|
||||
@@ -0,0 +1,134 @@
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import ModalUi from "../../primitives/ModalUi";
|
||||
|
||||
function AgreementContent(props) {
|
||||
const { t } = useTranslation();
|
||||
const appName =
|
||||
"OpenSign™";
|
||||
const h2Style = "text-base-content font-medium text-lg";
|
||||
const ulStyle = "list-disc px-4 py-3";
|
||||
const handleOnclick = () => {
|
||||
props.setIsAgreeTour(false);
|
||||
props.setIsAgree(true);
|
||||
props.setIsShowAgreeTerms(false);
|
||||
props.showFirstWidget();
|
||||
};
|
||||
return (
|
||||
<div>
|
||||
<ModalUi
|
||||
isOpen={true}
|
||||
title={t("term-cond-title")}
|
||||
handleClose={() => props.setIsShowAgreeTerms(false)}
|
||||
>
|
||||
<div className="h-[100%] p-[20px]">
|
||||
<h2 className={h2Style}>{t("term-cond-h")}</h2>
|
||||
<span className="mt-2">
|
||||
{t("term-cond-p1", { appName: appName })}
|
||||
</span>
|
||||
<hr className="bg-[#9f9f9f] w-full my-[15px]" />
|
||||
<h2 className={h2Style}>{t("term-cond-h1")}</h2>
|
||||
<span className="mt-2">
|
||||
{t("term-cond-p2", { appName: appName })}
|
||||
</span>
|
||||
<ul className={ulStyle}>
|
||||
<li>{t("term-cond-p3", { appName: appName })}</li>
|
||||
<li>{t("term-cond-p4")}</li>
|
||||
</ul>
|
||||
<hr className="bg-[#9f9f9f] w-full my-[15px]" />
|
||||
<h2 className={h2Style}>{t("term-cond-h2")}</h2>
|
||||
<span className="mt-2">{t("term-cond-p5")}</span>
|
||||
<ul className={ulStyle}>
|
||||
<li>{t("term-cond-p6", { appName: appName })}</li>
|
||||
<li>{t("term-cond-p7", { appName: appName })}</li>
|
||||
<li>{t("term-cond-p8")}</li>
|
||||
</ul>
|
||||
<hr className="bg-[#9f9f9f] w-full my-[15px]" />
|
||||
<h2 className={h2Style}>{t("term-cond-h3")}</h2>
|
||||
<span className="mt-2">{t("term-cond-p9")}</span>
|
||||
<ul className={ulStyle}>
|
||||
<li>{t("term-cond-p10")}</li>
|
||||
<li>{t("term-cond-p11")}</li>
|
||||
</ul>
|
||||
<hr className="bg-[#9f9f9f] w-full my-[15px]" />
|
||||
<h2 className={h2Style}>{t("term-cond-h4")}</h2>
|
||||
<span className="mt-2">
|
||||
{t("term-cond-p12", { appName: appName })}
|
||||
</span>
|
||||
<ul className={ulStyle}>
|
||||
<li>{t("term-cond-p13")}</li>
|
||||
<li>{t("term-cond-p14")}</li>
|
||||
<li>{t("term-cond-p15")}</li>
|
||||
</ul>
|
||||
<span>{t("term-cond-p16", { appName: appName })}</span>
|
||||
<hr className="bg-[#9f9f9f] w-full my-[15px]" />
|
||||
<h2 className={h2Style}>{t("term-cond-h5")}</h2>
|
||||
<p>{t("term-cond-p17", { appName: appName })}</p>
|
||||
<p className="mt-2">{t("term-cond-p18")}</p>
|
||||
<hr className="bg-[#9f9f9f] w-full my-[15px]" />
|
||||
<h2 className={h2Style}>{t("term-cond-h6")}</h2>
|
||||
<span className="mt-2">{t("term-cond-p19")}</span>
|
||||
<ul className={ulStyle}>
|
||||
<li>{t("term-cond-p20")}</li>
|
||||
<li>{t("term-cond-p21")}</li>
|
||||
<li>{t("term-cond-p22", { appName: appName })}</li>
|
||||
</ul>
|
||||
<hr className="bg-[#9f9f9f] w-full my-[15px]" />
|
||||
<h2 className={h2Style}>{t("term-cond-h7")}</h2>
|
||||
<span className="mt-2">
|
||||
{t("term-cond-p23", { appName: appName })}
|
||||
</span>
|
||||
<ul className={ulStyle}>
|
||||
<li>{t("term-cond-p24")}</li>
|
||||
<li>{t("term-cond-p25")}</li>
|
||||
</ul>
|
||||
<hr className="bg-[#9f9f9f] w-full my-[15px]" />
|
||||
<h2 className={h2Style}>{t("term-cond-h8")}</h2>
|
||||
<span className="mt-2">
|
||||
{t("term-cond-p26", { appName: appName })}
|
||||
</span>
|
||||
<hr className="bg-[#9f9f9f] w-full my-[15px]" />
|
||||
<h2 className={h2Style}>{t("term-cond-h9")}</h2>
|
||||
<span className="mt-2">
|
||||
{t("term-cond-p27", { appName: appName })}
|
||||
</span>
|
||||
<hr className="bg-[#9f9f9f] w-full my-[15px]" />
|
||||
<span className="mt-2 font-medium">
|
||||
{t("term-cond-p28", { appName: appName })}
|
||||
</span>
|
||||
<hr className="bg-[#9f9f9f] w-full my-[15px]" />
|
||||
<span className="mt-2">
|
||||
{t("term-cond-p29", { appName: appName })}
|
||||
</span>
|
||||
<a
|
||||
href="www.opensignlabs.com"
|
||||
target="_blank"
|
||||
className="text-blue-700 cursor-pointer"
|
||||
>
|
||||
www.opensignlabs.com
|
||||
</a>
|
||||
|
||||
<span>{t("term-cond-p30")}</span>
|
||||
<span className="font-medium"> support@opensignlabs.com </span>
|
||||
<hr className="bg-[#9f9f9f] w-full my-[15px]" />
|
||||
<div className="mt-6 flex justify-start gap-2">
|
||||
<button
|
||||
onClick={() => handleOnclick()}
|
||||
className="op-btn op-btn-primary"
|
||||
>
|
||||
{t("agrre-button")}
|
||||
</button>
|
||||
<button
|
||||
className="op-btn op-btn-ghost"
|
||||
onClick={() => props.setIsShowAgreeTerms(false)}
|
||||
>
|
||||
{t("close")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</ModalUi>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default AgreementContent;
|
||||
@@ -0,0 +1,87 @@
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import AgreementContent from "./AgreementContent";
|
||||
|
||||
function AgreementSign(props) {
|
||||
const { t } = useTranslation();
|
||||
const [isChecked, setIsChecked] = useState(false);
|
||||
const [isShowAgreeTerms, setIsShowAgreeTerms] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<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">
|
||||
<label className="inline-flex justify-center items-center cursor-pointer mb-0">
|
||||
{/* 1) This div becomes the “fake” checkbox */}
|
||||
<div
|
||||
data-tut="IsAgree"
|
||||
className={`w-6 h-6 border-2 mr-3 rounded-full flex text-center items-center justify-center ${isChecked ? "op-border-primary" : "border-red-500"}`}
|
||||
>
|
||||
{isChecked ? (
|
||||
<span className="op-text-primary text-sm font-bold">✓</span>
|
||||
) : (
|
||||
<span className="text-red-500 text-sm font-bold">X</span>
|
||||
)}
|
||||
</div>
|
||||
{/* 2) Visually hide the native checkbox but keep it in the DOM */}
|
||||
<input
|
||||
className="sr-only"
|
||||
type="checkbox"
|
||||
checked={isChecked}
|
||||
onChange={(e) => {
|
||||
setIsChecked(e.target.checked);
|
||||
if (e.target.checked) {
|
||||
props.setIsAgreeTour(false);
|
||||
}
|
||||
props.showFirstWidget();
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
<div className="text-[11px] md:text-base text-base-content">
|
||||
<span>{t("agree-p1")}</span>
|
||||
<span
|
||||
className="font-bold text-blue-600 cursor-pointer"
|
||||
onClick={() => {
|
||||
setIsShowAgreeTerms(true);
|
||||
props.setIsAgreeTour(false);
|
||||
}}
|
||||
>
|
||||
{t("agree-p2")}
|
||||
</span>
|
||||
<span> {t("agree-p3")}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex ml-[35px] mt-3">
|
||||
<button
|
||||
onClick={() => {
|
||||
if (isChecked) {
|
||||
props.setIsAgreeTour(false);
|
||||
props.setIsAgree(true);
|
||||
} else {
|
||||
props.setIsAgreeTour(true);
|
||||
}
|
||||
}}
|
||||
className="op-btn op-btn-primary op-btn-sm w-full md:w-auto"
|
||||
>
|
||||
{t("agrre-button")}
|
||||
</button>
|
||||
</div>
|
||||
<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}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default AgreementSign;
|
||||
@@ -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,276 +0,0 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import opensignLogo from "../../assets/images/logo.png";
|
||||
import {
|
||||
Page,
|
||||
Text,
|
||||
View,
|
||||
Document,
|
||||
StyleSheet,
|
||||
Image
|
||||
} from "@react-pdf/renderer";
|
||||
|
||||
function Certificate({ pdfData }) {
|
||||
const [isMultiSigners, setIsMultiSigners] = useState();
|
||||
const [multiSigner, setMultiSigners] = useState([]);
|
||||
const [isLoad, setIsLoad] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
handleSignerData();
|
||||
// eslint-disable-next-line
|
||||
}, []);
|
||||
|
||||
const handleSignerData = () => {
|
||||
const checkSigners = pdfData.filter((data) => data.Signers);
|
||||
if (checkSigners && checkSigners.length > 0) {
|
||||
setIsMultiSigners(true);
|
||||
|
||||
const checkSignSigners =
|
||||
pdfData[0].AuditTrail &&
|
||||
pdfData[0].AuditTrail.length > 0 &&
|
||||
pdfData[0].AuditTrail.filter((data) => data.Activity === "Signed");
|
||||
|
||||
setMultiSigners(checkSignSigners);
|
||||
} else {
|
||||
setIsMultiSigners(false);
|
||||
}
|
||||
setIsLoad(true);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
page: {
|
||||
borderRadius: "5px",
|
||||
padding: "10px",
|
||||
backgroundColor: "white"
|
||||
},
|
||||
section1: {
|
||||
border: "1px solid rgb(177, 174, 174)",
|
||||
padding: "20px"
|
||||
},
|
||||
textStyle: {
|
||||
fontWeight: "bold",
|
||||
fontSize: "11px",
|
||||
marginBottom: "10px"
|
||||
},
|
||||
textStyle2: {
|
||||
fontWeight: "600",
|
||||
fontSize: "11px",
|
||||
marginBottom: "10px",
|
||||
color: "gray"
|
||||
},
|
||||
image: {
|
||||
width: "71px",
|
||||
height: "17px"
|
||||
}
|
||||
});
|
||||
|
||||
const generatedDate = () => {
|
||||
const newDate = new Date();
|
||||
const utcTime = newDate.toUTCString();
|
||||
|
||||
return (
|
||||
<Text
|
||||
style={{
|
||||
color: "gray",
|
||||
fontSize: "10px"
|
||||
}}
|
||||
>
|
||||
Generated On {utcTime}
|
||||
</Text>
|
||||
);
|
||||
};
|
||||
const changeCompletedDate = () => {
|
||||
const completedOn = pdfData[0].updatedAt;
|
||||
const newDate = new Date(completedOn);
|
||||
const utcTime = newDate.toUTCString();
|
||||
|
||||
return <Text style={styles.textStyle2}>{utcTime}</Text>;
|
||||
};
|
||||
|
||||
const signerName = (data) => {
|
||||
const getSignerName = pdfData[0].Signers.filter(
|
||||
(sign) => sign.objectId === data.UserPtr.objectId
|
||||
);
|
||||
|
||||
return (
|
||||
getSignerName[0] &&
|
||||
getSignerName.length > 0 && (
|
||||
<>
|
||||
<Text style={styles.textStyle}>
|
||||
Name :
|
||||
<Text style={styles.textStyle2}>{getSignerName[0].Name}</Text>
|
||||
</Text>
|
||||
<Text style={styles.textStyle}>
|
||||
Email :
|
||||
<Text style={styles.textStyle2}>{getSignerName[0].Email}</Text>
|
||||
</Text>
|
||||
</>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
isLoad && (
|
||||
<Document>
|
||||
{/** Page defines a single page of content. */}
|
||||
<Page size="A4" style={styles.page}>
|
||||
<View style={styles.section1}>
|
||||
<View
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
marginBottom: "30px"
|
||||
}}
|
||||
>
|
||||
<Image src={opensignLogo} style={styles.image} />
|
||||
{generatedDate()}
|
||||
</View>
|
||||
|
||||
<View style={{ justifyContent: "center" }}>
|
||||
<Text
|
||||
style={{
|
||||
textAlign: "center",
|
||||
fontSize: "20px",
|
||||
fontWeight: "bold",
|
||||
color: "#31bceb",
|
||||
marginBottom: "10px"
|
||||
}}
|
||||
>
|
||||
{" "}
|
||||
Certificate of Completion
|
||||
</Text>
|
||||
<View style={{ border: "1px solid #bdbbbb" }}></View>
|
||||
<View>
|
||||
<View>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: "16px",
|
||||
fontWeight: "bold",
|
||||
color: "#31bceb",
|
||||
margin: "10px 0px 10px 0px"
|
||||
}}
|
||||
>
|
||||
Summary
|
||||
</Text>
|
||||
</View>
|
||||
<View style={{ display: "flex", flexDirection: "column" }}>
|
||||
<Text style={styles.textStyle}>
|
||||
Document ID :
|
||||
<Text style={styles.textStyle2}>{pdfData[0].objectId}</Text>
|
||||
</Text>
|
||||
<Text style={styles.textStyle}>
|
||||
Document Name :
|
||||
<Text style={styles.textStyle2}>{pdfData[0].Name}</Text>
|
||||
</Text>
|
||||
<Text style={styles.textStyle}>
|
||||
Organization :
|
||||
<Text style={styles.textStyle2}>
|
||||
{pdfData[0].ExtUserPtr.Company}
|
||||
</Text>
|
||||
</Text>
|
||||
<Text style={styles.textStyle}>
|
||||
Completed on : {changeCompletedDate()}
|
||||
</Text>
|
||||
{multiSigner && multiSigner.length > 0 && (
|
||||
<Text style={styles.textStyle}>
|
||||
Signers :
|
||||
<Text style={styles.textStyle2}>
|
||||
{multiSigner.length}
|
||||
</Text>
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
{isMultiSigners ? (
|
||||
<View style={{ display: "flex", flexDirection: "column" }}>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: "16px",
|
||||
fontWeight: "bold",
|
||||
color: "#31bceb",
|
||||
margin: "10px 0px 10px 0px"
|
||||
}}
|
||||
>
|
||||
Recipients
|
||||
</Text>
|
||||
|
||||
<View>
|
||||
{multiSigner &&
|
||||
multiSigner.map((data, ind) => {
|
||||
return (
|
||||
<View
|
||||
key={ind}
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column"
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
border: "0.4px solid #bdbbbb",
|
||||
marginBottom: "10px"
|
||||
}}
|
||||
></View>
|
||||
{signerName(data)}
|
||||
|
||||
<Text style={styles.textStyle}>
|
||||
Accessed from :
|
||||
<Text style={styles.textStyle2}>
|
||||
{data.ipAddress}
|
||||
</Text>
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
</View>
|
||||
) : (
|
||||
<View style={{ display: "flex", flexDirection: "column" }}>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: "16px",
|
||||
fontWeight: "bold",
|
||||
color: "#31bceb",
|
||||
margin: "10px 0px 10px 0px"
|
||||
}}
|
||||
>
|
||||
Recipients
|
||||
</Text>
|
||||
<Text style={styles.textStyle}>
|
||||
Signers : <Text style={styles.textStyle2}>1</Text>
|
||||
</Text>
|
||||
<Text style={styles.textStyle}>
|
||||
Name :
|
||||
<Text style={styles.textStyle2}>
|
||||
{pdfData[0].ExtUserPtr.Name}
|
||||
</Text>
|
||||
</Text>
|
||||
<Text style={styles.textStyle}>
|
||||
Email :
|
||||
<Text style={styles.textStyle2}>
|
||||
{pdfData[0].ExtUserPtr.Email}
|
||||
</Text>
|
||||
</Text>
|
||||
<Text style={styles.textStyle}>
|
||||
Accessed from :
|
||||
<Text style={styles.textStyle2}>
|
||||
{pdfData[0].AuditTrail &&
|
||||
pdfData[0].AuditTrail[0].ipAddress}
|
||||
</Text>
|
||||
</Text>
|
||||
|
||||
<Text style={styles.textStyle}>
|
||||
Signed on : {changeCompletedDate()}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</Page>
|
||||
</Document>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export default Certificate;
|
||||
@@ -1,54 +0,0 @@
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
function DefaultSignature(props) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const confirmToaddDefaultSign = () => {
|
||||
if (props?.xyPostion.length > 0) {
|
||||
props?.setDefaultSignAlert({
|
||||
isShow: true,
|
||||
alertMessage: t("default-sign-alert")
|
||||
});
|
||||
} else {
|
||||
props?.setDefaultSignAlert({
|
||||
isShow: true,
|
||||
alertMessage: t("please-select-position!")
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div data-tut="reactourThird">
|
||||
<div className="mx-2 pr-2 pt-2 pb-1 text-[15px] text-base-content font-semibold border-b-[1px] border-base-300">
|
||||
{t("signature")}
|
||||
</div>
|
||||
<div className="flex flex-col items-center mt-[10px] font-semibold relative">
|
||||
<p className="text-base-content">{t("Your-Signature")}</p>
|
||||
<div className="op-card shadow-md h-[111px] w-[90%] p-2">
|
||||
<img
|
||||
alt="signature"
|
||||
className="w-full h-full object-contain"
|
||||
src={props?.defaultSignImg}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-primary op-btn-sm mt-[10px]"
|
||||
onClick={() => confirmToaddDefaultSign()}
|
||||
disabled={!props?.isDefault}
|
||||
>
|
||||
{t("auto-sign-all")}
|
||||
</button>
|
||||
{!props.isDefault && (
|
||||
<div className="absolute bg-black/70 text-white w-full h-full flex items-center justify-center text-[11px] cursor-default">
|
||||
<span className="-rotate-45">
|
||||
Document creator has disabled this option
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default DefaultSignature;
|
||||
@@ -0,0 +1,122 @@
|
||||
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 })
|
||||
});
|
||||
}
|
||||
} else {
|
||||
props?.setDefaultSignAlert({
|
||||
isShow: true,
|
||||
alertMessage: t("please-select-position!")
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div data-tut="reactourThird">
|
||||
<div className="mx-2 pr-2 pt-2 pb-1 text-[15px] text-base-content font-semibold border-b-[1px] border-base-300">
|
||||
<p className="text-base-content">{t("signature")}</p>
|
||||
</div>
|
||||
<div className="flex justify-center items-center mt-2">
|
||||
<div role="tablist" className="op-tabs op-tabs-bordered">
|
||||
{tabName.map((tabData, ind) => (
|
||||
<div
|
||||
onClick={() => setActiveTab(ind)}
|
||||
key={ind}
|
||||
role="tab"
|
||||
className={`${
|
||||
activeTab === ind ? "op-tab-active" : ""
|
||||
} op-tab flex items-center pb-10 md:pb-0`}
|
||||
>
|
||||
<span className="ml-1 text-[7px] font-medium md:font-normal md:text-[12px]">
|
||||
{t(`${tabData}`)}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col items-center mt-[10px] font-semibold relative">
|
||||
<div className="op-card shadow-md h-[111px] w-[90%] p-2">
|
||||
{activeTab === 0 ? (
|
||||
<img
|
||||
alt="signature"
|
||||
className="w-full h-full object-contain"
|
||||
src={defaultSignImg}
|
||||
/>
|
||||
) : (
|
||||
activeTab === 1 &&
|
||||
(myInitial ? (
|
||||
<img
|
||||
alt="signature"
|
||||
className="w-full h-full object-contain"
|
||||
src={myInitial}
|
||||
/>
|
||||
) : (
|
||||
<div className="flex justify-center items-center h-full">
|
||||
<span>{t("initial-alert")}</span>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-primary op-btn-sm mt-[10px]"
|
||||
onClick={() =>
|
||||
confirmToaddDefaultSign(
|
||||
activeTab === 0 ? "signature" : activeTab === 1 && "initials"
|
||||
)
|
||||
}
|
||||
disabled={
|
||||
activeTab === 0 && !props?.isDefault
|
||||
? true
|
||||
: activeTab === 1 && !myInitial
|
||||
? true
|
||||
: false
|
||||
}
|
||||
>
|
||||
{t("auto-sign-all")}
|
||||
</button>
|
||||
{!props.isDefault && (
|
||||
<div className="absolute bg-black/70 text-white w-full h-full flex items-center justify-center text-[11px] cursor-default">
|
||||
<span className="-rotate-45">
|
||||
Document creator has disabled this option
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default DefaultSignature;
|
||||
@@ -2,7 +2,7 @@ import React, { useEffect, useState } from "react";
|
||||
import LoaderWithMsg from "../../primitives/LoaderWithMsg";
|
||||
import { contractDocument } from "../../constant/Utils";
|
||||
import HandleError from "../../primitives/HandleError";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import { useLocation, useNavigate } from "react-router";
|
||||
import { useTranslation } from "react-i18next";
|
||||
function useQuery() {
|
||||
return new URLSearchParams(useLocation().search);
|
||||
@@ -30,15 +30,19 @@ function DraftDocument() {
|
||||
documentData === "Error: Something went wrong!" ||
|
||||
(documentData.result && documentData.result.error)
|
||||
) {
|
||||
setIsLoading({
|
||||
isLoader: false,
|
||||
message: "Error: Something went wrong!"
|
||||
});
|
||||
if (documentData?.result?.error?.includes("deleted")) {
|
||||
setIsLoading({
|
||||
isLoader: false,
|
||||
message: t("document-deleted")
|
||||
});
|
||||
} else {
|
||||
setIsLoading({
|
||||
isLoader: false,
|
||||
message: t("something-went-wrong-mssg")
|
||||
});
|
||||
}
|
||||
} else {
|
||||
setIsLoading({
|
||||
isLoader: false,
|
||||
message: "No data found!"
|
||||
});
|
||||
setIsLoading({ isLoader: false, message: t("no-data") });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,42 +1,42 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { isEnableSubscription } from "../../constant/const";
|
||||
import ModalUi from "../../primitives/ModalUi";
|
||||
import { radioButtonWidget } from "../../constant/Utils";
|
||||
import Upgrade from "../../primitives/Upgrade";
|
||||
import { useTranslation } from "react-i18next";
|
||||
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);
|
||||
@@ -51,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();
|
||||
@@ -106,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 it’s a dropdown and it’s 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,
|
||||
@@ -116,28 +141,12 @@ 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();
|
||||
};
|
||||
|
||||
const handleSetMinMax = (e) => {
|
||||
const minValue = e.target.value;
|
||||
if (minValue > dropdownOptionList.length) {
|
||||
return "";
|
||||
} else {
|
||||
return minValue;
|
||||
}
|
||||
};
|
||||
|
||||
const handleDefaultCheck = (index) => {
|
||||
const getDefaultCheck = defaultCheckbox.includes(index);
|
||||
@@ -147,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">
|
||||
@@ -158,30 +166,30 @@ 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"
|
||||
>
|
||||
{props.type === "checkbox" && !props.isSignYourself && (
|
||||
{props.type === "checkbox" && props.isShowAdvanceFeature && (
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={handleDefaultCheck(index)}
|
||||
@@ -224,65 +232,6 @@ function DropdownWidgetOption(props) {
|
||||
onClick={handleAddInput}
|
||||
className="fa-light fa-square-plus text-[25px] ml-[10px] op-text-primary cursor-pointer"
|
||||
></i>
|
||||
{isEnableSubscription && (
|
||||
<div>
|
||||
{props.type === "checkbox" && !props.isSignYourself && (
|
||||
<>
|
||||
<label
|
||||
className={`${
|
||||
!props.isSubscribe ? "text-[gray]" : ""
|
||||
} text-[13px] font-semibold`}
|
||||
>
|
||||
{t("minimun-check")}
|
||||
</label>
|
||||
{!props.isSubscribe && isEnableSubscription && (
|
||||
<Upgrade />
|
||||
)}
|
||||
<input
|
||||
onInvalid={(e) =>
|
||||
e.target.setCustomValidity(t("input-required"))
|
||||
}
|
||||
onInput={(e) => e.target.setCustomValidity("")}
|
||||
required
|
||||
defaultValue={0}
|
||||
value={minCount}
|
||||
disabled={props.isSubscribe ? false : true}
|
||||
onChange={(e) => {
|
||||
const count = handleSetMinMax(e);
|
||||
setMinCount(count);
|
||||
}}
|
||||
className={`${
|
||||
props.isSubscribe ? "" : "pointer-events-none"
|
||||
} op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs`}
|
||||
/>
|
||||
<label
|
||||
className={`${
|
||||
!props.isSubscribe ? "text-[gray]" : ""
|
||||
} text-[13px] font-semibold`}
|
||||
>
|
||||
{t("maximum-check")}
|
||||
</label>
|
||||
<input
|
||||
onInvalid={(e) =>
|
||||
e.target.setCustomValidity(t("input-required"))
|
||||
}
|
||||
onInput={(e) => e.target.setCustomValidity("")}
|
||||
required
|
||||
defaultValue={0}
|
||||
value={maxCount}
|
||||
disabled={props.isSubscribe ? false : true}
|
||||
onChange={(e) => {
|
||||
const count = handleSetMinMax(e);
|
||||
setMaxCount(count);
|
||||
}}
|
||||
className={`${
|
||||
props.isSubscribe ? "" : "pointer-events-none"
|
||||
} op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs`}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{["dropdown", radioButtonWidget].includes(props.type) && (
|
||||
<>
|
||||
@@ -309,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>
|
||||
@@ -351,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 ||
|
||||
@@ -381,9 +328,11 @@ function DropdownWidgetOption(props) {
|
||||
></span>
|
||||
</div>
|
||||
</div>
|
||||
{["checkbox", radioButtonWidget].includes(props.type) && (
|
||||
{["checkbox", radioButtonWidget, "dropdown"].includes(
|
||||
props.type
|
||||
) && (
|
||||
<div className="flex flex-row gap-5 my-2 items-center text-center">
|
||||
{!props.isSignYourself && (
|
||||
{props.isShowAdvanceFeature && (
|
||||
<div className="flex items-center">
|
||||
<input
|
||||
id="isreadonly"
|
||||
@@ -392,31 +341,66 @@ 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>
|
||||
)}
|
||||
<div className="flex items-center">
|
||||
<input
|
||||
id="ishidelabel"
|
||||
type="checkbox"
|
||||
checked={isHideLabel}
|
||||
className="op-checkbox op-checkbox-sm"
|
||||
onChange={(e) => setIsHideLabel(e.target.checked)}
|
||||
/>
|
||||
{props.type !== "dropdown" && (
|
||||
<div className="flex items-center">
|
||||
<input
|
||||
id="ishidelabel"
|
||||
type="checkbox"
|
||||
checked={isHideLabel}
|
||||
className="op-checkbox op-checkbox-sm"
|
||||
onChange={(e) => setIsHideLabel(e.target.checked)}
|
||||
/>
|
||||
|
||||
<label className="ml-1 mb-0" htmlFor="ishidelabel">
|
||||
{t("hide-labels")}
|
||||
</label>
|
||||
</div>
|
||||
<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 capitalize">
|
||||
{data}
|
||||
</label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={`${
|
||||
props.type === "checkbox" && !props.isSignYourself
|
||||
props.type === "checkbox" && props.isShowAdvanceFeature
|
||||
? "mb-[15px]"
|
||||
: "my-[15px]"
|
||||
} w-full h-[1px] bg-[#9f9f9f]`}
|
||||
@@ -1,388 +0,0 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { checkIsSubscribed, getFileName } from "../../constant/Utils";
|
||||
import Upgrade from "../../primitives/Upgrade";
|
||||
import { isEnableSubscription } from "../../constant/const";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Tooltip } from "react-tooltip";
|
||||
|
||||
const EditTemplate = ({ template, onSuccess, jwttoken }) => {
|
||||
const { t } = useTranslation();
|
||||
const [formData, setFormData] = useState({
|
||||
Name: template?.Name || "",
|
||||
Note: template?.Note || "",
|
||||
Description: template?.Description || "",
|
||||
SendinOrder: template?.SendinOrder ? `${template?.SendinOrder}` : "false",
|
||||
AutomaticReminders: template?.AutomaticReminders || false,
|
||||
RemindOnceInEvery: template?.RemindOnceInEvery || 5,
|
||||
IsEnableOTP: template?.IsEnableOTP ? `${template?.IsEnableOTP}` : "false",
|
||||
IsTourEnabled: template?.IsTourEnabled
|
||||
? `${template?.IsTourEnabled}`
|
||||
: "false",
|
||||
NotifyOnSignatures:
|
||||
template?.NotifyOnSignatures !== undefined
|
||||
? template?.NotifyOnSignatures
|
||||
: false
|
||||
});
|
||||
const [isSubscribe, setIsSubscribe] = useState(false);
|
||||
useEffect(() => {
|
||||
fetchSubscription();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
const fetchSubscription = async () => {
|
||||
if (isEnableSubscription) {
|
||||
const subscribe = await checkIsSubscribed(jwttoken);
|
||||
setIsSubscribe(subscribe.isValid);
|
||||
}
|
||||
};
|
||||
const handleStrInput = (e) => {
|
||||
setFormData({ ...formData, [e.target.name]: e.target.value });
|
||||
};
|
||||
|
||||
// Define a function to handle form submission
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const isChecked = formData.SendinOrder === "true" ? true : false;
|
||||
const isTourEnabled = formData?.IsTourEnabled === "false" ? false : true;
|
||||
const AutoReminder = formData?.AutomaticReminders || false;
|
||||
const IsEnableOTP = formData.IsEnableOTP === "true" ? true : false;
|
||||
let reminderDate = {};
|
||||
if (AutoReminder) {
|
||||
const RemindOnceInEvery = parseInt(formData?.RemindOnceInEvery);
|
||||
const ReminderDate = new Date(template?.createdAt);
|
||||
ReminderDate.setDate(ReminderDate.getDate() + RemindOnceInEvery);
|
||||
reminderDate = { NextReminderDate: ReminderDate };
|
||||
}
|
||||
const data = {
|
||||
...formData,
|
||||
SendinOrder: isChecked,
|
||||
IsEnableOTP: IsEnableOTP,
|
||||
IsTourEnabled: isTourEnabled,
|
||||
...reminderDate
|
||||
};
|
||||
onSuccess(data);
|
||||
};
|
||||
const handleAutoReminder = () => {
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
AutomaticReminders: !formData.AutomaticReminders
|
||||
}));
|
||||
};
|
||||
|
||||
// `handleNotifySignChange` is trigger when user change radio of notify on signatures
|
||||
const handleNotifySignChange = (value) => {
|
||||
setFormData((obj) => ({ ...obj, NotifyOnSignatures: value }));
|
||||
};
|
||||
return (
|
||||
<div className="max-h-[300px] md:max-h-[400px] overflow-y-scroll p-[10px]">
|
||||
<div className="text-base-content">
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="mb-[0.35rem]">
|
||||
<label htmlFor="name" className="text-[13px]">
|
||||
{t("report-heading.File")}
|
||||
</label>
|
||||
<div className="op-input op-input-bordered op-input-sm focus:outline-none py-2 font-semibold w-full text-xs">
|
||||
{getFileName(template.URL)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-[0.35rem]">
|
||||
<label htmlFor="name" className="text-[13px]">
|
||||
{t("Title")}
|
||||
<span className="text-[13px] text-[red]"> *</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="Name"
|
||||
value={formData.Name}
|
||||
onChange={(e) => handleStrInput(e)}
|
||||
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
|
||||
onInput={(e) => e.target.setCustomValidity("")}
|
||||
required
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-[0.35rem]">
|
||||
<label htmlFor="Note" className="text-[13px]">
|
||||
{t("report-heading.Note")}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="Note"
|
||||
id="Note"
|
||||
value={formData.Note}
|
||||
onChange={(e) => handleStrInput(e)}
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-[0.35rem]">
|
||||
<label htmlFor="Description" className="text-[13px]">
|
||||
{t("description")}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="Description"
|
||||
id="Description"
|
||||
value={formData.Description}
|
||||
onChange={(e) => handleStrInput(e)}
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-[0.35rem]">
|
||||
<label className="text-[13px]">{t("send-in-order")}</label>
|
||||
<div className="flex flex-col md:flex-row md:gap-4">
|
||||
<div className="flex items-center gap-[8px] ml-[8px] mb-[5px]">
|
||||
<input
|
||||
type="radio"
|
||||
value={"true"}
|
||||
className="op-radio op-radio-xs"
|
||||
name="SendinOrder"
|
||||
checked={formData.SendinOrder === "true"}
|
||||
onChange={handleStrInput}
|
||||
/>
|
||||
<div className="text-[12px]">{t("yes")}</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-[8px] ml-[8px] mb-[5px]">
|
||||
<input
|
||||
type="radio"
|
||||
value={"false"}
|
||||
name="SendinOrder"
|
||||
className="op-radio op-radio-xs"
|
||||
checked={formData.SendinOrder === "false"}
|
||||
onChange={handleStrInput}
|
||||
/>
|
||||
<div className="text-[12px]">{t("no")}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{isEnableSubscription && (
|
||||
<div className="text-xs mt-2">
|
||||
<span
|
||||
className={
|
||||
isSubscribe ? "font-semibold" : "font-semibold text-gray-300"
|
||||
}
|
||||
>
|
||||
{t("auto-reminder")}
|
||||
{!isSubscribe && isEnableSubscription && <Upgrade />}
|
||||
</span>
|
||||
<label
|
||||
className={`${
|
||||
isSubscribe
|
||||
? "cursor-pointer"
|
||||
: "pointer-events-none opacity-50"
|
||||
} relative block items-center mb-0`}
|
||||
>
|
||||
<input
|
||||
checked={formData.AutomaticReminders}
|
||||
onChange={handleAutoReminder}
|
||||
type="checkbox"
|
||||
className="op-toggle transition-all checked:[--tglbg:#3368ff] checked:bg-white mt-2"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
{isSubscribe && formData?.AutomaticReminders === true && (
|
||||
<div className="text-xs mt-2">
|
||||
<label className="block">
|
||||
{t("remind-once")}
|
||||
<span className="text-red-500 text-[13px]">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
value={formData.RemindOnceInEvery}
|
||||
name="RemindOnceInEvery"
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
onChange={handleStrInput}
|
||||
onInvalid={(e) =>
|
||||
e.target.setCustomValidity(t("input-required"))
|
||||
}
|
||||
onInput={(e) => e.target.setCustomValidity("")}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{isEnableSubscription && (
|
||||
<div className="text-xs mt-2">
|
||||
<label className="block">
|
||||
<span className={isSubscribe ? "" : " text-gray-300"}>
|
||||
{t("isenable-otp")}{" "}
|
||||
<a data-tooltip-id="isenableotp-tooltip" className="ml-1">
|
||||
<sup>
|
||||
<i className="fa-light fa-question rounded-full border-[#33bbff] text-[#33bbff] text-[13px] border-[1px] py-[1.5px] px-[4px]"></i>
|
||||
</sup>
|
||||
</a>{" "}
|
||||
{!isSubscribe && isEnableSubscription && <Upgrade />}
|
||||
</span>
|
||||
<Tooltip id="isenableotp-tooltip" className="z-50">
|
||||
<div className="max-w-[200px] md:max-w-[450px]">
|
||||
<p className="font-bold">{t("isenable-otp")}</p>
|
||||
<p>{t("isenable-otp-help.p1")}</p>
|
||||
<p className="p-[5px]">
|
||||
<ol className="list-disc">
|
||||
<li>
|
||||
<span className="font-bold">{t("yes")}: </span>
|
||||
<span>{t("isenable-otp-help.p2")}</span>
|
||||
</li>
|
||||
<li>
|
||||
<span className="font-bold">{t("no")}: </span>
|
||||
<span>{t("isenable-otp-help.p3")}</span>
|
||||
</li>
|
||||
</ol>
|
||||
</p>
|
||||
<p>{t("isenable-otp-help.p4")}</p>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</label>
|
||||
<div className="flex flex-col md:flex-row md:gap-4">
|
||||
<div
|
||||
className={`${
|
||||
isSubscribe ? "" : "pointer-events-none opacity-50"
|
||||
} flex items-center gap-2 ml-2 mb-1`}
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
value={"true"}
|
||||
className="op-radio op-radio-xs"
|
||||
name="IsEnableOTP"
|
||||
checked={formData.IsEnableOTP === "true"}
|
||||
onChange={handleStrInput}
|
||||
/>
|
||||
<div className="text-center">{t("yes")}</div>
|
||||
</div>
|
||||
<div
|
||||
className={`${
|
||||
isSubscribe ? "" : "pointer-events-none opacity-50"
|
||||
} flex items-center gap-2 ml-2 mb-1 `}
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
value={"false"}
|
||||
name="IsEnableOTP"
|
||||
className="op-radio op-radio-xs"
|
||||
checked={formData.IsEnableOTP === "false"}
|
||||
onChange={handleStrInput}
|
||||
/>
|
||||
<div className="text-center">{t("no")}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="text-xs mt-2">
|
||||
<label className="block">
|
||||
<span>
|
||||
{t("enable-tour")}
|
||||
<a data-tooltip-id="istourenabled-tooltip" className="ml-1">
|
||||
<sup>
|
||||
<i className="fa-light fa-question rounded-full border-[#33bbff] text-[#33bbff] text-[13px] border-[1px] py-[1.5px] px-[4px]"></i>
|
||||
</sup>
|
||||
</a>{" "}
|
||||
</span>
|
||||
<Tooltip id="istourenabled-tooltip" className="z-50">
|
||||
<div className="max-w-[200px] md:max-w-[450px]">
|
||||
<p className="font-bold">{t("enable-tour")}</p>
|
||||
<p className="p-[5px]">
|
||||
<ol className="list-disc">
|
||||
<li>
|
||||
<span className="font-bold">{t("yes")}: </span>
|
||||
<span>{t("istourenabled-help.p1")}</span>
|
||||
</li>
|
||||
<li>
|
||||
<span className="font-bold">{t("no")}: </span>
|
||||
<span>{t("istourenabled-help.p2")}</span>
|
||||
</li>
|
||||
</ol>
|
||||
</p>
|
||||
<p>{t("istourenabled-help.p3")}</p>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</label>
|
||||
<div className="flex flex-col md:flex-row md:gap-4">
|
||||
<div className="flex items-center gap-2 ml-2 mb-1">
|
||||
<input
|
||||
type="radio"
|
||||
value={"true"}
|
||||
className="op-radio op-radio-xs"
|
||||
name="IsTourEnabled"
|
||||
checked={formData.IsTourEnabled === "true"}
|
||||
onChange={handleStrInput}
|
||||
/>
|
||||
<div className="text-center">{t("yes")}</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 ml-2 mb-1">
|
||||
<input
|
||||
type="radio"
|
||||
value={"false"}
|
||||
name="IsTourEnabled"
|
||||
className="op-radio op-radio-xs"
|
||||
checked={formData.IsTourEnabled === "false"}
|
||||
onChange={handleStrInput}
|
||||
/>
|
||||
<div className="text-center">{t("no")}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-xs mt-3">
|
||||
<label
|
||||
className={`${
|
||||
isSubscribe || !isEnableSubscription ? "" : "text-gray-300"
|
||||
} block`}
|
||||
>
|
||||
{t("notify-on-signatures")}
|
||||
<a data-tooltip-id="nos-tooltip" className="ml-1">
|
||||
<sup>
|
||||
<i className="fa-light fa-question rounded-full border-[#33bbff] text-[#33bbff] text-[13px] border-[1px] py-[1.5px] px-[4px]"></i>
|
||||
</sup>
|
||||
</a>
|
||||
{!isSubscribe && isEnableSubscription && <Upgrade />}
|
||||
<Tooltip id="nos-tooltip" className="z-[999]">
|
||||
<div className="max-w-[200px] md:max-w-[450px] text-[11px]">
|
||||
<p className="font-bold">{t("notify-on-signatures")}</p>
|
||||
<p>{t("notify-on-signatures-help.p1")}</p>
|
||||
<p>{t("notify-on-signatures-help.note")}</p>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</label>
|
||||
<div className="flex flex-col md:flex-row md:gap-4">
|
||||
<div
|
||||
className={`${
|
||||
isSubscribe || !isEnableSubscription
|
||||
? ""
|
||||
: "pointer-events-none opacity-50"
|
||||
} flex items-center gap-2 ml-2 mb-1`}
|
||||
>
|
||||
<input
|
||||
className="mr-[2px] op-radio op-radio-xs"
|
||||
type="radio"
|
||||
onChange={() => handleNotifySignChange(true)}
|
||||
checked={formData.NotifyOnSignatures === true}
|
||||
/>
|
||||
<div className="text-center">{t("yes")}</div>
|
||||
</div>
|
||||
<div
|
||||
className={`${
|
||||
isSubscribe || !isEnableSubscription
|
||||
? ""
|
||||
: "pointer-events-none opacity-50"
|
||||
} flex items-center gap-2 ml-2 mb-1`}
|
||||
>
|
||||
<input
|
||||
className="mr-[2px] op-radio op-radio-xs"
|
||||
type="radio"
|
||||
onChange={() => handleNotifySignChange(false)}
|
||||
checked={formData.NotifyOnSignatures === false}
|
||||
/>
|
||||
<div className="text-center">{t("no")}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-[1rem] flex justify-start">
|
||||
<button type="submit" className="op-btn op-btn-primary">
|
||||
{t("submit")}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditTemplate;
|
||||
@@ -0,0 +1,538 @@
|
||||
import {
|
||||
useState,
|
||||
useRef,
|
||||
} from "react";
|
||||
import {
|
||||
base64ToArrayBuffer,
|
||||
convertBase64ToFile,
|
||||
generatePdfName,
|
||||
getFileName
|
||||
} from "../../constant/Utils";
|
||||
import {
|
||||
maxDescriptionLength,
|
||||
maxNoteLength,
|
||||
maxTitleLength
|
||||
} from "../../constant/const";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Tooltip } from "react-tooltip";
|
||||
import SignersInput from "../shared/fields/SignersInput";
|
||||
import { PDFDocument } from "pdf-lib";
|
||||
import ModalUi from "../../primitives/ModalUi";
|
||||
import { SaveFileSize } from "../../constant/saveFileSize";
|
||||
|
||||
const EditTemplate = ({
|
||||
title,
|
||||
handleClose,
|
||||
pdfbase64,
|
||||
template,
|
||||
onSuccess,
|
||||
setPdfArrayBuffer,
|
||||
setPdfBase64Url,
|
||||
}) => {
|
||||
const appName =
|
||||
"OpenSign™";
|
||||
const { t } = useTranslation();
|
||||
const inputFileRef = useRef(null);
|
||||
const [formData, setFormData] = useState({
|
||||
Name: template?.Name || "",
|
||||
Note: template?.Note || "",
|
||||
Description: template?.Description || "",
|
||||
SendinOrder: template?.SendinOrder ? `${template?.SendinOrder}` : "false",
|
||||
AutomaticReminders: template?.AutomaticReminders || false,
|
||||
RemindOnceInEvery: template?.RemindOnceInEvery || 5,
|
||||
IsEnableOTP: template?.IsEnableOTP ? `${template?.IsEnableOTP}` : "false",
|
||||
IsTourEnabled: template?.IsTourEnabled
|
||||
? `${template?.IsTourEnabled}`
|
||||
: "false",
|
||||
NotifyOnSignatures:
|
||||
template?.NotifyOnSignatures !== undefined
|
||||
? template?.NotifyOnSignatures
|
||||
: false,
|
||||
Bcc: template?.Bcc,
|
||||
RedirectUrl: template?.RedirectUrl || "",
|
||||
AllowModifications: template?.AllowModifications || false,
|
||||
TimeToCompleteDays: template?.TimeToCompleteDays || 15
|
||||
});
|
||||
const [isUpdate, setIsUpdate] = useState(false);
|
||||
const [showConfirm, setShowConfirm] = useState(false);
|
||||
const [uploadPdf, setUploadPdf] = useState({
|
||||
name: "",
|
||||
base64: "",
|
||||
url: ""
|
||||
});
|
||||
const handleDrop = (e) => {
|
||||
e.preventDefault();
|
||||
const file = e.dataTransfer.files[0];
|
||||
handleFile(file);
|
||||
};
|
||||
|
||||
const handleFile = (file) => {
|
||||
if (file && file.type === "application/pdf") {
|
||||
handleReplaceFileValdition(file);
|
||||
// You can handle the file here
|
||||
} else {
|
||||
alert("Only pdf files are allowed.");
|
||||
if (inputFileRef.current) inputFileRef.current.value = "";
|
||||
}
|
||||
};
|
||||
const handleDragOver = (e) => {
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
// `isValidURL` is used to check valid webhook url
|
||||
function isValidURL(value) {
|
||||
try {
|
||||
const url = new URL(value);
|
||||
return url.protocol === "https:" || url.protocol === "http:";
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const handleStrInput = (e) => {
|
||||
setIsUpdate(true);
|
||||
setFormData({ ...formData, [e.target.name]: e.target.value });
|
||||
};
|
||||
const getPdfMetadataHash = async (pdfBytes) => {
|
||||
const pdfDoc = await PDFDocument.load(pdfBytes);
|
||||
const pages = pdfDoc.getPages();
|
||||
const metaString = pages
|
||||
.map((page, index) => {
|
||||
const { width, height } = page.getSize();
|
||||
return `${index + 1}:${Math.round(width)}x${Math.round(height)}`;
|
||||
})
|
||||
.join("|");
|
||||
const encoder = new TextEncoder();
|
||||
const data = encoder.encode(metaString);
|
||||
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
||||
return Array.from(new Uint8Array(hashBuffer))
|
||||
.map((b) => b.toString(16).padStart(2, "0"))
|
||||
.join("");
|
||||
};
|
||||
|
||||
const handleFileInput = async (e) => {
|
||||
const file = e.target.files[0];
|
||||
if (!file) return;
|
||||
handleReplaceFileValdition(file);
|
||||
};
|
||||
const handleReplaceFileValdition = async (file) => {
|
||||
try {
|
||||
const basePdfBytes = base64ToArrayBuffer(pdfbase64);
|
||||
const expectedHash = await getPdfMetadataHash(basePdfBytes);
|
||||
const fileReader = new FileReader();
|
||||
fileReader.onload = async (event) => {
|
||||
const uploadedPdfBytes = event.target.result;
|
||||
const uploadedHash = await getPdfMetadataHash(uploadedPdfBytes);
|
||||
|
||||
if (expectedHash === uploadedHash) {
|
||||
const arrayBuffer = uploadedPdfBytes;
|
||||
const uint8Array = new Uint8Array(arrayBuffer);
|
||||
const binaryString = Array.from(uint8Array)
|
||||
.map((b) => String.fromCharCode(b))
|
||||
.join("");
|
||||
const base64 = btoa(binaryString);
|
||||
const pdfName = generatePdfName(16);
|
||||
setIsUpdate(true);
|
||||
setUploadPdf((prev) => ({ ...prev, name: pdfName, base64: base64 }));
|
||||
// alert("✅ PDFs match (based on page number, width, height)");
|
||||
} else {
|
||||
alert("❌ PDF do NOT match based on page number, width, height");
|
||||
if (inputFileRef.current) inputFileRef.current.value = "";
|
||||
}
|
||||
};
|
||||
|
||||
fileReader.readAsArrayBuffer(file);
|
||||
} catch (err) {
|
||||
alert("Error: " + err.message);
|
||||
if (inputFileRef.current) inputFileRef.current.value = "";
|
||||
}
|
||||
};
|
||||
// Define a function to handle form submission
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (formData.RedirectUrl && !isValidURL(formData?.RedirectUrl)) {
|
||||
alert(t("invalid-redirect-url"));
|
||||
return;
|
||||
}
|
||||
if (formData?.Name?.length > maxTitleLength) {
|
||||
alert(t("title-length-alert"));
|
||||
return;
|
||||
}
|
||||
if (formData?.Note?.length > maxNoteLength) {
|
||||
alert(t("note-length-alert"));
|
||||
return;
|
||||
}
|
||||
if (formData?.Description?.length > maxDescriptionLength) {
|
||||
alert(t("description-length-alert"));
|
||||
return;
|
||||
}
|
||||
let pdfUrl;
|
||||
if (uploadPdf?.base64) {
|
||||
pdfUrl = await convertBase64ToFile(
|
||||
uploadPdf.name,
|
||||
uploadPdf.base64,
|
||||
);
|
||||
setUploadPdf((prev) => ({ ...prev, url: pdfUrl }));
|
||||
const pdfBuffer = base64ToArrayBuffer(uploadPdf.base64);
|
||||
setPdfArrayBuffer && setPdfArrayBuffer(pdfBuffer);
|
||||
setPdfBase64Url && setPdfBase64Url(uploadPdf.base64);
|
||||
const tenantId =
|
||||
localStorage.getItem("TenantId") ||
|
||||
template?.ExtUserPtr?.TenantId?.objectId;
|
||||
const buffer = atob(uploadPdf.base64);
|
||||
SaveFileSize(buffer.length, pdfUrl, tenantId);
|
||||
}
|
||||
const isChecked = formData.SendinOrder === "true" ? true : false;
|
||||
const isTourEnabled = formData?.IsTourEnabled === "false" ? false : true;
|
||||
const AutoReminder = formData?.AutomaticReminders || false;
|
||||
const IsEnableOTP = formData.IsEnableOTP === "true" ? true : false;
|
||||
const allowModify = formData?.AllowModifications || false;
|
||||
let reminderDate = {};
|
||||
const remindOnceInEvery = formData?.RemindOnceInEvery;
|
||||
const TimeToCompleteDays = parseInt(formData?.TimeToCompleteDays);
|
||||
const reminderCount = TimeToCompleteDays / remindOnceInEvery;
|
||||
if (AutoReminder && reminderCount > 15) {
|
||||
alert(t("only-15-reminder-allowed"));
|
||||
return;
|
||||
}
|
||||
if (AutoReminder) {
|
||||
const RemindOnceInEvery = parseInt(formData?.RemindOnceInEvery);
|
||||
const ReminderDate = new Date(template?.createdAt);
|
||||
ReminderDate.setDate(ReminderDate.getDate() + RemindOnceInEvery);
|
||||
reminderDate = { NextReminderDate: ReminderDate };
|
||||
}
|
||||
const data = {
|
||||
...formData,
|
||||
...(pdfUrl ? { URL: pdfUrl } : {}),
|
||||
SendinOrder: isChecked,
|
||||
IsEnableOTP: IsEnableOTP,
|
||||
IsTourEnabled: isTourEnabled,
|
||||
AllowModifications: allowModify,
|
||||
...reminderDate
|
||||
};
|
||||
onSuccess(data);
|
||||
};
|
||||
|
||||
// `handleNotifySignChange` is trigger when user change radio of notify on signatures
|
||||
const handleNotifySignChange = (value) => {
|
||||
setIsUpdate(true);
|
||||
setFormData((obj) => ({ ...obj, NotifyOnSignatures: value }));
|
||||
};
|
||||
const handleBcc = (data) => {
|
||||
if (data && data.length > 0) {
|
||||
const trimEmail = data.map((item) => ({
|
||||
objectId: item?.value,
|
||||
Name: item?.label,
|
||||
Email: item?.email
|
||||
}));
|
||||
setIsUpdate(true);
|
||||
setFormData((prev) => ({ ...prev, Bcc: trimEmail }));
|
||||
}
|
||||
};
|
||||
|
||||
const handleEditTemplateClose = () => {
|
||||
if (isUpdate) {
|
||||
setShowConfirm(true);
|
||||
} else {
|
||||
handleClose();
|
||||
}
|
||||
};
|
||||
const discardChanges = () => {
|
||||
setShowConfirm(false);
|
||||
handleClose();
|
||||
};
|
||||
return (
|
||||
<ModalUi
|
||||
isOpen
|
||||
title={isUpdate ? `${title} (unsaved)` : title}
|
||||
handleClose={handleEditTemplateClose}
|
||||
>
|
||||
<ModalUi isOpen={showConfirm} showClose={false}>
|
||||
<div className="p-[20px]">
|
||||
<p className="text-base font-normal text-base-content py-[5px] md:py-[6px] px-[5px]">
|
||||
{t("unsaved-changes-discard-them?")}
|
||||
</p>
|
||||
<div className="flex items-center mt-2.5 gap-2 md:gap-3 text-white">
|
||||
<button
|
||||
className="op-btn op-btn-primary px-6"
|
||||
onClick={discardChanges}
|
||||
>
|
||||
{t("yes-discard")}
|
||||
</button>
|
||||
<button
|
||||
className="op-btn op-btn-secondary px-4 md:px-6"
|
||||
onClick={() => setShowConfirm(false)}
|
||||
>
|
||||
{t("cancel")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</ModalUi>
|
||||
<div className="max-h-[300px] md:max-h-[400px] overflow-y-scroll p-[10px]">
|
||||
<div className="text-base-content">
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="mb-[0.35rem]">
|
||||
<label htmlFor="name" className="text-[13px]">
|
||||
{t("report-heading.File")}
|
||||
</label>
|
||||
<div
|
||||
className="border-[1.5px] border-dashed border-gray-300 rounded-lg px-4 py-6 text-center text-gray-500 bg-white cursor-pointer hover:border-base-content transition"
|
||||
onDrop={handleDrop}
|
||||
onDragOver={handleDragOver}
|
||||
onClick={() => inputFileRef?.current?.click()}
|
||||
>
|
||||
<label
|
||||
htmlFor="fileUpload"
|
||||
className="cursor-pointer text-center mb-0"
|
||||
>
|
||||
Browse or drag & drop a new file to replace the existng one
|
||||
</label>
|
||||
</div>
|
||||
<input
|
||||
ref={inputFileRef}
|
||||
type="file"
|
||||
className="hidden"
|
||||
accept="application/pdf"
|
||||
onChange={(e) => handleFileInput(e)}
|
||||
onInvalid={(e) =>
|
||||
e.target.setCustomValidity(t("input-required"))
|
||||
}
|
||||
onInput={(e) => e.target.setCustomValidity("")}
|
||||
/>
|
||||
{uploadPdf?.name && (
|
||||
<div
|
||||
onClick={() => inputFileRef?.current?.click()}
|
||||
className="mt-2 cursor-pointer op-input op-input-bordered op-input-sm focus:outline-none py-2 font-semibold w-full text-xs"
|
||||
>
|
||||
selected:{" "}
|
||||
{uploadPdf?.url
|
||||
? `${uploadPdf?.name}.pdf`
|
||||
: getFileName(template.URL)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="mb-[0.35rem]">
|
||||
<label htmlFor="name" className="text-[13px]">
|
||||
{t("Title")}
|
||||
<span className="text-[13px] text-[red]"> *</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="Name"
|
||||
value={formData.Name}
|
||||
onChange={(e) => handleStrInput(e)}
|
||||
onInvalid={(e) =>
|
||||
e.target.setCustomValidity(t("input-required"))
|
||||
}
|
||||
onInput={(e) => e.target.setCustomValidity("")}
|
||||
required
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-[0.35rem]">
|
||||
<label htmlFor="Note" className="text-[13px]">
|
||||
{t("report-heading.Note")}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="Note"
|
||||
id="Note"
|
||||
value={formData.Note}
|
||||
onChange={(e) => handleStrInput(e)}
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-[0.35rem]">
|
||||
<label htmlFor="Description" className="text-[13px]">
|
||||
{t("description")}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="Description"
|
||||
id="Description"
|
||||
value={formData.Description}
|
||||
onChange={(e) => handleStrInput(e)}
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-[0.35rem]">
|
||||
<label className="text-[13px]">{t("send-in-order")}</label>
|
||||
<div className="flex flex-col md:flex-row md:gap-4">
|
||||
<div className="flex items-center gap-[8px] ml-[8px] mb-[5px]">
|
||||
<input
|
||||
type="radio"
|
||||
value={"true"}
|
||||
className="op-radio op-radio-xs"
|
||||
name="SendinOrder"
|
||||
checked={formData.SendinOrder === "true"}
|
||||
onChange={handleStrInput}
|
||||
/>
|
||||
<div className="text-[12px]">{t("yes")}</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-[8px] ml-[8px] mb-[5px]">
|
||||
<input
|
||||
type="radio"
|
||||
value={"false"}
|
||||
name="SendinOrder"
|
||||
className="op-radio op-radio-xs"
|
||||
checked={formData.SendinOrder === "false"}
|
||||
onChange={handleStrInput}
|
||||
/>
|
||||
<div className="text-[12px]">{t("no")}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-xs mt-3">
|
||||
<label className="block">
|
||||
<span>
|
||||
{t("enable-tour")}
|
||||
<a data-tooltip-id="istourenabled-tooltip" className="ml-1">
|
||||
<sup>
|
||||
<i className="fa-light fa-question rounded-full border-[#33bbff] text-[#33bbff] text-[13px] border-[1px] py-[1.5px] px-[4px]"></i>
|
||||
</sup>
|
||||
</a>{" "}
|
||||
</span>
|
||||
<Tooltip id="istourenabled-tooltip" className="z-50">
|
||||
<div className="max-w-[200px] md:max-w-[450px]">
|
||||
<p className="font-bold">{t("enable-tour")}</p>
|
||||
<div className="p-[5px]">
|
||||
<ol className="list-disc">
|
||||
<li>
|
||||
<span className="font-bold">{t("yes")}: </span>
|
||||
<span>{t("istourenabled-help.p1")}</span>
|
||||
</li>
|
||||
<li>
|
||||
<span className="font-bold">{t("no")}: </span>
|
||||
<span>{t("istourenabled-help.p2")}</span>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
<p>{t("istourenabled-help.p3", { appName: appName })}</p>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</label>
|
||||
<div className="flex flex-col md:flex-row md:gap-4">
|
||||
<div className="flex items-center gap-2 ml-2 mb-1">
|
||||
<input
|
||||
type="radio"
|
||||
value={"true"}
|
||||
className="op-radio op-radio-xs"
|
||||
name="IsTourEnabled"
|
||||
checked={formData.IsTourEnabled === "true"}
|
||||
onChange={handleStrInput}
|
||||
/>
|
||||
<div className="text-center">{t("yes")}</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 ml-2 mb-1">
|
||||
<input
|
||||
type="radio"
|
||||
value={"false"}
|
||||
name="IsTourEnabled"
|
||||
className="op-radio op-radio-xs"
|
||||
checked={formData.IsTourEnabled === "false"}
|
||||
onChange={handleStrInput}
|
||||
/>
|
||||
<div className="text-center">{t("no")}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-xs mt-3">
|
||||
<label>
|
||||
{t("notify-on-signatures")}
|
||||
<a data-tooltip-id="nos-tooltip" className="ml-1">
|
||||
<sup>
|
||||
<i className="fa-light fa-question rounded-full border-[#33bbff] text-[#33bbff] text-[13px] border-[1px] py-[1.5px] px-[4px]"></i>
|
||||
</sup>
|
||||
</a>{" "}
|
||||
<Tooltip id="nos-tooltip" className="z-[999]">
|
||||
<div className="max-w-[200px] md:max-w-[450px] text-[11px]">
|
||||
<p className="font-bold">{t("notify-on-signatures")}</p>
|
||||
<p>{t("notify-on-signatures-help.p1")}</p>
|
||||
<p>{t("notify-on-signatures-help.note")}</p>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</label>
|
||||
<div className="flex flex-col md:flex-row md:gap-4">
|
||||
<div
|
||||
className={
|
||||
`flex items-center gap-2 ml-2 mb-1`
|
||||
}
|
||||
>
|
||||
<input
|
||||
className="mr-[2px] op-radio op-radio-xs"
|
||||
type="radio"
|
||||
onChange={() => handleNotifySignChange(true)}
|
||||
checked={formData.NotifyOnSignatures === true}
|
||||
/>
|
||||
<div className="text-center">{t("yes")}</div>
|
||||
</div>
|
||||
<div
|
||||
className={
|
||||
`flex items-center gap-2 ml-2 mb-1`
|
||||
}
|
||||
>
|
||||
<input
|
||||
className="mr-[2px] op-radio op-radio-xs"
|
||||
type="radio"
|
||||
onChange={() => handleNotifySignChange(false)}
|
||||
checked={formData.NotifyOnSignatures === false}
|
||||
/>
|
||||
<div className="text-center">{t("no")}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-xs mt-3">
|
||||
<SignersInput
|
||||
label={t("Bcc")}
|
||||
initialData={template?.Bcc}
|
||||
onChange={handleBcc}
|
||||
helptextZindex={50}
|
||||
helpText={t("bcc-help")}
|
||||
isCaptureAllData
|
||||
/>
|
||||
</div>
|
||||
<div className="text-xs mt-2">
|
||||
<label className="block">Redirect Url</label>
|
||||
<input
|
||||
name="RedirectUrl"
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
value={formData.RedirectUrl}
|
||||
onChange={handleStrInput}
|
||||
onInvalid={(e) =>
|
||||
e.target.setCustomValidity(t("input-required"))
|
||||
}
|
||||
onInput={(e) => e.target.setCustomValidity("")}
|
||||
/>
|
||||
</div>
|
||||
<div className="text-xs mt-2">
|
||||
<label className="block">
|
||||
{t("time-to-complete")}
|
||||
<span className="text-red-500 text-[13px]">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
name="TimeToCompleteDays"
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
value={formData.TimeToCompleteDays}
|
||||
onChange={(e) => handleStrInput(e)}
|
||||
onInvalid={(e) =>
|
||||
e.target.setCustomValidity(t("input-required"))
|
||||
}
|
||||
onInput={(e) => e.target.setCustomValidity("")}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-[1rem] flex justify-start">
|
||||
<button type="submit" className="op-btn op-btn-primary">
|
||||
{t("submit")}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</ModalUi>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditTemplate;
|
||||
@@ -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
|
||||
@@ -54,17 +59,24 @@ Quill.register(Font, true);
|
||||
export const module1 = {
|
||||
toolbar: {
|
||||
container: "#toolbar1",
|
||||
handlers: {
|
||||
undo: undoChange,
|
||||
redo: redoChange
|
||||
}
|
||||
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
|
||||
export const module2 = {
|
||||
toolbar: {
|
||||
container: "#toolbar2",
|
||||
handlers: { undo: undoChange, redo: redoChange }
|
||||
},
|
||||
history: { delay: 500, maxStack: 100, userOnly: true },
|
||||
// Enable the "<>" button registered above
|
||||
htmlEditButton: {}
|
||||
};
|
||||
|
||||
// Formats objects for setting up the Quill editor
|
||||
export const formats = [
|
||||
"header",
|
||||
@@ -1,21 +1,21 @@
|
||||
import React, { useState } from "react";
|
||||
import axios from "axios";
|
||||
import { handleToPrint } from "../../constant/Utils";
|
||||
import { themeColor, 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";
|
||||
import Parse from "parse";
|
||||
|
||||
function EmailComponent({
|
||||
isEmail,
|
||||
pdfUrl,
|
||||
setIsEmail,
|
||||
setSuccessEmail,
|
||||
pdfDetails,
|
||||
sender,
|
||||
setIsAlert,
|
||||
extUserId,
|
||||
activeMailAdapter,
|
||||
setIsDownloadModal
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
@@ -25,78 +25,14 @@ function EmailComponent({
|
||||
const [emailErr, setEmailErr] = useState(false);
|
||||
const [isDownloading, setIsDownloading] = useState("");
|
||||
const isAndroid = /Android/i.test(navigator.userAgent);
|
||||
|
||||
//function for send email
|
||||
const sendEmail = async () => {
|
||||
const pdfName = pdfDetails[0]?.Name;
|
||||
setIsLoading(true);
|
||||
let sendMail;
|
||||
const docId = pdfDetails?.[0]?.objectId || "";
|
||||
const FileAdapterId = pdfDetails?.[0]?.FileAdapterId
|
||||
? pdfDetails?.[0]?.FileAdapterId
|
||||
: "";
|
||||
let presignedUrl = pdfUrl;
|
||||
try {
|
||||
const axiosRes = await axios.post(
|
||||
`${localStorage.getItem("baseUrl")}/functions/getsignedurl`,
|
||||
{ url: pdfUrl, docId: docId, fileAdapterId: FileAdapterId },
|
||||
{
|
||||
headers: {
|
||||
"content-type": "Application/json",
|
||||
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
|
||||
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
|
||||
}
|
||||
}
|
||||
);
|
||||
presignedUrl = axiosRes.data.result;
|
||||
} catch (err) {
|
||||
console.log("err in getsignedurl", err);
|
||||
}
|
||||
for (let i = 0; i < emailList.length; i++) {
|
||||
try {
|
||||
const imgPng =
|
||||
"https://qikinnovation.ams3.digitaloceanspaces.com/logo.png";
|
||||
|
||||
let url = `${localStorage.getItem("baseUrl")}functions/sendmailv3`;
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
|
||||
sessionToken: localStorage.getItem("accesstoken")
|
||||
};
|
||||
const openSignUrl = "https://www.opensignlabs.com/contact-us";
|
||||
const themeBGcolor = themeColor;
|
||||
let params = {
|
||||
mailProvider: activeMailAdapter,
|
||||
extUserId: extUserId,
|
||||
pdfName: pdfName,
|
||||
url: presignedUrl,
|
||||
recipient: emailList[i],
|
||||
subject: `${sender.name} has signed the doc - ${pdfName}`,
|
||||
from: sender.email,
|
||||
html:
|
||||
"<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body> <div style='background-color:#f5f5f5;padding:20px'> <div style='box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;background-color:white;'> <div><img src=" +
|
||||
imgPng +
|
||||
" height='50' style='padding:20px,width:170px,height:40px'/> </div><div style='padding:2px;font-family:system-ui; background-color:" +
|
||||
themeBGcolor +
|
||||
";'> <p style='font-size:20px;font-weight:400;color:white;padding-left:20px',> Document Copy</p></div><div><p style='padding:20px;font-family:system-ui;font-size:14px'>A copy of the document <strong>" +
|
||||
pdfName +
|
||||
" </strong>is attached to this email. Kindly download the document from the attachment.</p></div> </div><div><p>This is an automated email from OpenSign™. For any queries regarding this email, please contact the sender " +
|
||||
sender.email +
|
||||
" directly. If you think this email is inappropriate or spam, you may file a complaint with OpenSign™ <a href= " +
|
||||
openSignUrl +
|
||||
" target=_blank>here</a> </p></div></div></body></html>"
|
||||
};
|
||||
sendMail = await axios.post(url, params, { headers: headers });
|
||||
} catch (error) {
|
||||
console.log("error", error);
|
||||
setIsLoading(false);
|
||||
setIsEmail(false);
|
||||
setIsAlert({
|
||||
isShow: true,
|
||||
alertMessage: t("something-went-wrong-mssg")
|
||||
});
|
||||
}
|
||||
}
|
||||
if (sendMail?.data?.result?.status === "success") {
|
||||
const params = { docId: pdfDetails?.[0]?.objectId, recipients: emailList };
|
||||
const sendmail = await Parse.Cloud.run("forwarddoc", params);
|
||||
console.log("sendmail ", sendmail);
|
||||
if (sendmail?.status === "success") {
|
||||
setSuccessEmail(true);
|
||||
setIsEmail(false);
|
||||
setTimeout(() => {
|
||||
@@ -105,7 +41,8 @@ function EmailComponent({
|
||||
setEmailList([]);
|
||||
}, 1500);
|
||||
setIsLoading(false);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
setIsLoading(false);
|
||||
setIsEmail(false);
|
||||
setIsAlert({
|
||||
@@ -175,23 +112,23 @@ function EmailComponent({
|
||||
</div>
|
||||
)}
|
||||
<div className="flex justify-between items-center py-[10px] px-[20px] border-b-[1px] border-base-content">
|
||||
<span className="text-base-content font-semibold">
|
||||
<span className="text-base-content font-bold text-sm md:text-lg">
|
||||
{t("successfully-signed")}
|
||||
</span>
|
||||
<div className="flex flex-row">
|
||||
{!isAndroid && (
|
||||
<button
|
||||
onClick={(e) =>
|
||||
handleToPrint(e, pdfUrl, setIsDownloading, pdfDetails)
|
||||
handleToPrint(e, setIsDownloading, pdfDetails)
|
||||
}
|
||||
className="op-btn op-btn-neutral op-btn-sm text-[15px]"
|
||||
className="op-btn op-btn-neutral op-btn-sm text-xs md:text-[15px]"
|
||||
>
|
||||
<i className="fa-light fa-print" aria-hidden="true"></i>
|
||||
{t("print")}
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
className="op-btn op-btn-primary op-btn-sm text-[15px] ml-2"
|
||||
className="op-btn op-btn-primary op-btn-sm text-xs md:text-[15px] ml-2"
|
||||
onClick={() => {
|
||||
handleClose();
|
||||
setIsDownloadModal(true);
|
||||
@@ -207,12 +144,12 @@ function EmailComponent({
|
||||
{t("email-mssg")}
|
||||
</p>
|
||||
{emailList.length > 0 ? (
|
||||
<div className="p-0 border-[1.5px] op-border-primary rounded w-full text-[15px]">
|
||||
<div className="p-0 border-[1px] op-border-primary w-full rounded-md text-[15px] overflow-hidden">
|
||||
<div className="flex flex-row flex-wrap">
|
||||
{emailList.map((data, ind) => {
|
||||
return (
|
||||
<div
|
||||
className="flex flex-row items-center op-bg-primary m-[4px] rounded-md py-[5px] px-[10px]"
|
||||
className="flex flex-row items-center op-bg-primary mx-[2px] mt-[2px] rounded-md py-[5px] px-[10px]"
|
||||
key={ind}
|
||||
>
|
||||
<span className="text-base-100 text-[13px]">
|
||||
@@ -232,7 +169,7 @@ function EmailComponent({
|
||||
<input
|
||||
type="email"
|
||||
value={emailValue}
|
||||
className="p-[10px] pb-[20px] rounded w-full text-[15px] bg-transparent outline-none"
|
||||
className="p-[10px] rounded-md w-full text-[15px] bg-transparent outline-none"
|
||||
onChange={handleEmailValue}
|
||||
onKeyDown={handleEnterPress}
|
||||
onBlur={() => emailValue && handleEnterPress("add")}
|
||||
@@ -249,7 +186,7 @@ function EmailComponent({
|
||||
<input
|
||||
type="email"
|
||||
value={emailValue}
|
||||
className="p-[10px] pb-[20px] rounded w-full text-[15px] outline-none bg-transparent border-[1.5px] op-border-primary"
|
||||
className="p-[10px] pb-[20px] 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")}
|
||||
@@ -267,34 +204,22 @@ 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 className="mt-2">
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-secondary"
|
||||
onClick={() => emailList.length > 0 && sendEmail()}
|
||||
>
|
||||
{t("send")}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-ghost text-base-content ml-2"
|
||||
onClick={() => handleClose()}
|
||||
>
|
||||
{t("close")}
|
||||
</button>
|
||||
</div>
|
||||
<hr className="w-full my-[15px] bg-base-content" />
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-secondary"
|
||||
onClick={() => emailList.length > 0 && sendEmail()}
|
||||
>
|
||||
{t("send")}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-ghost ml-2"
|
||||
onClick={() => handleClose()}
|
||||
>
|
||||
{t("close")}
|
||||
</button>
|
||||
</div>
|
||||
</ModalUi>
|
||||
)}
|
||||
@@ -1,267 +0,0 @@
|
||||
import React, { useState } from "react";
|
||||
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
|
||||
import { tomorrow } from "react-syntax-highlighter/dist/esm/styles/prism";
|
||||
import { copytoData } from "../../constant/Utils";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
function EmbedTab(props) {
|
||||
const { t } = useTranslation();
|
||||
const tabName = [
|
||||
{ title: "React/Next.js", icon: "fa-brands fa-react", color: "#61dafb" },
|
||||
{ title: "JavaScript", icon: "fa-brands fa-js", color: "#ffd43b" },
|
||||
{ title: "Angular", icon: "fa-brands fa-angular", color: "#ff5733" }
|
||||
];
|
||||
const [activeTab, setActiveTab] = useState(0);
|
||||
// State to track if the code has been copied
|
||||
const [isCopied, setIsCopied] = useState(false);
|
||||
const reactCode = [
|
||||
{
|
||||
id: 0,
|
||||
title: "Installation",
|
||||
codeString: `npm install @opensign/react`
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
title: "Usage",
|
||||
codeString: `
|
||||
import React from "react";
|
||||
import Opensign from "@opensign/react";
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<div className="app">
|
||||
<Opensign
|
||||
onLoad={() => console.log("success")}
|
||||
onLoadError={(error) => console.log(error)}
|
||||
templateId= "${props.templateId}"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
`
|
||||
}
|
||||
];
|
||||
|
||||
const angularCode = [
|
||||
{
|
||||
id: 0,
|
||||
title: "Installation",
|
||||
codeString: `npm install @opensign/angular`
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
title: "Usage",
|
||||
codeString: `
|
||||
import { Component } from '@angular/core';
|
||||
import { OpensignComponent } from "@opensign/angular"
|
||||
|
||||
@Component({
|
||||
selector:'app-root',
|
||||
standalone: true,
|
||||
imports: [OpensignComponent],
|
||||
template:\`<opensign templateId="${props.templateId}"
|
||||
(onLoad)="handleLoad()"
|
||||
(onLoadError)="handleError($event)"
|
||||
></opensign>\`,
|
||||
})
|
||||
export class AppComponent {
|
||||
handleLoad() {
|
||||
console.log("success");
|
||||
}
|
||||
handleError(error: string) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
`
|
||||
}
|
||||
];
|
||||
const jsCodeString = `
|
||||
<script
|
||||
src= "https://app.opensignlabs.com/static/js/public-template.bundle.js"
|
||||
id="opensign-script"
|
||||
templateId=${props.templateId}
|
||||
></script>
|
||||
`;
|
||||
|
||||
const handleCopy = (code, ind) => {
|
||||
copytoData(code);
|
||||
setIsCopied({ ...isCopied, [ind]: true });
|
||||
setTimeout(() => setIsCopied(false), 3000);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mt-4 border-t-[1px]">
|
||||
<h3 className="text-base-content font-bold text-lg pt-[15px] pb-[5px]">
|
||||
{t("embed-template")}
|
||||
</h3>
|
||||
<div className="flex justify-center items-center mt-2">
|
||||
<div role="tablist" className="op-tabs op-tabs-bordered">
|
||||
{tabName.map((tabData, ind) => (
|
||||
<div
|
||||
onClick={() => setActiveTab(ind)}
|
||||
key={ind}
|
||||
role="tab"
|
||||
className={`${
|
||||
activeTab === ind ? "op-tab-active" : ""
|
||||
} op-tab flex items-center pb-10 md:pb-0`}
|
||||
>
|
||||
<i
|
||||
className={`${tabData.icon}`}
|
||||
style={{ color: tabData.color }}
|
||||
></i>
|
||||
<span className="ml-1 text-[10px] font-medium md:font-normal md:text-[15px]">
|
||||
{tabData.title}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
{activeTab === 0 ? (
|
||||
<div className="mt-4">
|
||||
{reactCode.map((data, ind) => {
|
||||
return (
|
||||
<div key={ind}>
|
||||
<p className="font-medium text-[18px]">
|
||||
{t(`${data.title}`)}
|
||||
</p>
|
||||
{ind === 0 && (
|
||||
<p className="text-[13px] mt-2">
|
||||
{t("public-template-mssg-1")}
|
||||
</p>
|
||||
)}
|
||||
<div className="relative p-1">
|
||||
<div
|
||||
onClick={() => handleCopy(data.codeString, ind)}
|
||||
className="absolute top-[20px] right-[20px] cursor-pointer"
|
||||
>
|
||||
<i className="fa-light fa-copy text-white mr-[2px]" />
|
||||
<span className=" text-white">
|
||||
{isCopied[ind] ? t("copied-code") : t("copy-code")}
|
||||
</span>
|
||||
</div>
|
||||
<SyntaxHighlighter
|
||||
customStyle={{
|
||||
borderRadius: "15px"
|
||||
}}
|
||||
language="javascript"
|
||||
style={tomorrow}
|
||||
>
|
||||
{data.codeString}
|
||||
</SyntaxHighlighter>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
<p className="font-medium text-[15px]">
|
||||
{t("public-template-mssg-3")}
|
||||
</p>
|
||||
<p className="my-[6px]">
|
||||
{" "}
|
||||
{t("public-template-mssg-4")}
|
||||
<a
|
||||
href="https://www.npmjs.com/package/@opensign/react"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="cursor-pointer text-blue-700 "
|
||||
>
|
||||
{" "}
|
||||
OpenSign React package{" "}
|
||||
</a>
|
||||
{t("public-template-mssg-5")}
|
||||
</p>
|
||||
</div>
|
||||
) : activeTab === 1 ? (
|
||||
<div className="mt-4">
|
||||
<div>
|
||||
{/* <p className="font-medium text-[18px]">{t(`${data.title}`)}</p> */}
|
||||
|
||||
<div className="relative p-1">
|
||||
<div
|
||||
onClick={() => handleCopy(jsCodeString, 0)}
|
||||
className="absolute top-[20px] right-[20px] cursor-pointer"
|
||||
>
|
||||
<i className="fa-light fa-copy text-white mr-[2px]" />
|
||||
<span className=" text-white">
|
||||
{isCopied[0] ? t("copied-code") : t("copy-code")}
|
||||
</span>
|
||||
</div>
|
||||
<SyntaxHighlighter
|
||||
customStyle={{
|
||||
borderRadius: "15px"
|
||||
}}
|
||||
language="javascript"
|
||||
style={tomorrow}
|
||||
>
|
||||
{jsCodeString}
|
||||
</SyntaxHighlighter>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
activeTab === 2 && (
|
||||
<div className="mt-4">
|
||||
{angularCode.map((data, ind) => {
|
||||
return (
|
||||
<div key={ind}>
|
||||
<p className="font-medium text-[18px]">
|
||||
{t(`${data.title}`)}
|
||||
</p>
|
||||
{ind === 0 && (
|
||||
<p className="text-[13px] mt-2">
|
||||
{t("angular-npm-mssg-1")}
|
||||
</p>
|
||||
)}
|
||||
<div className="relative p-1">
|
||||
<div
|
||||
onClick={() => handleCopy(data.codeString, ind)}
|
||||
className="absolute top-[20px] right-[20px] cursor-pointer"
|
||||
>
|
||||
<i className="fa-light fa-copy text-white mr-[2px]" />
|
||||
<span className=" text-white">
|
||||
{isCopied[ind] ? t("copied-code") : t("copy-code")}
|
||||
</span>
|
||||
</div>
|
||||
<SyntaxHighlighter
|
||||
customStyle={{
|
||||
borderRadius: "15px"
|
||||
}}
|
||||
language="javascript"
|
||||
style={tomorrow}
|
||||
>
|
||||
{data.codeString}
|
||||
</SyntaxHighlighter>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
<p className="font-medium text-[15px]">
|
||||
{t("public-template-mssg-3")}
|
||||
</p>
|
||||
<p className="my-[6px]">
|
||||
{" "}
|
||||
{t("public-template-mssg-4")}
|
||||
<a
|
||||
href="https://www.npmjs.com/package/@opensign/angular"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="cursor-pointer text-blue-700 "
|
||||
>
|
||||
{" "}
|
||||
OpenSign Angular package{" "}
|
||||
</a>
|
||||
{t("public-template-mssg-5")}
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default EmbedTab;
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -1,95 +1,178 @@
|
||||
import React, { useState } from "react";
|
||||
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";
|
||||
|
||||
function Header({
|
||||
isPdfRequestFiles,
|
||||
isPlaceholder,
|
||||
setIsDecline,
|
||||
pageNumber,
|
||||
allPages,
|
||||
changePage,
|
||||
pdfUrl,
|
||||
embedWidgetsData,
|
||||
pdfDetails,
|
||||
signerPos,
|
||||
signersdata,
|
||||
isMailSend,
|
||||
alertSendEmail,
|
||||
isCompleted,
|
||||
isShowHeader,
|
||||
decline,
|
||||
currentSigner,
|
||||
alreadySign,
|
||||
isSignYourself,
|
||||
setIsEmail,
|
||||
completeBtnTitle,
|
||||
setIsEditTemplate,
|
||||
isPublicTemplate,
|
||||
clickOnZoomIn,
|
||||
clickOnZoomOut,
|
||||
handleRotationFun,
|
||||
isDisableRotate,
|
||||
templateId,
|
||||
setIsDownloadModal,
|
||||
disabledBackBtn,
|
||||
setPdfArrayBuffer,
|
||||
setPdfBase64Url,
|
||||
pdfArrayBuffer,
|
||||
setIsUploadPdf,
|
||||
setSignerPos,
|
||||
userId
|
||||
}) {
|
||||
function Header(props) {
|
||||
const { t } = useTranslation();
|
||||
const filterPrefill =
|
||||
signerPos && signerPos?.filter((data) => data.Role !== "prefill");
|
||||
props?.signerPos &&
|
||||
props?.signerPos?.filter((data) => data.Role !== "prefill");
|
||||
const isMobile = window.innerWidth < 767;
|
||||
const [isDownloading, setIsDownloading] = useState("");
|
||||
const isGuestSigner = localStorage.getItem("isGuestSigner");
|
||||
const [isDeletePage, setIsDeletePage] = useState(false);
|
||||
const enabledBackBtn = disabledBackBtn === true ? false : true;
|
||||
|
||||
const [isReorderModal, setIsReorderModal] = useState(false);
|
||||
const mergePdfInputRef = useRef(null);
|
||||
const enabledBackBtn = props?.disabledBackBtn === true ? false : true;
|
||||
//function for show decline alert
|
||||
const handleDeclinePdfAlert = async () => {
|
||||
const currentDecline = { currnt: "Sure", isDeclined: true };
|
||||
setIsDecline(currentDecline);
|
||||
if (props?.handleDecline) {
|
||||
props.handleDecline();
|
||||
} else {
|
||||
const currentDecline = { currnt: "Sure", isDeclined: true };
|
||||
props?.setIsDecline(currentDecline);
|
||||
}
|
||||
};
|
||||
const handleDetelePage = async () => {
|
||||
setIsUploadPdf(true);
|
||||
const pdfupdatedData = await deletePdfPage(pdfArrayBuffer, pageNumber);
|
||||
props?.setIsUploadPdf && props?.setIsUploadPdf(true);
|
||||
const pdfupdatedData = await deletePdfPage(
|
||||
props?.pdfArrayBuffer,
|
||||
props?.pageNumber
|
||||
);
|
||||
if (pdfupdatedData?.totalPages === 1) {
|
||||
alert(t("delete-alert"));
|
||||
} else {
|
||||
setPdfBase64Url(pdfupdatedData.base64);
|
||||
setPdfArrayBuffer(pdfupdatedData.arrayBuffer);
|
||||
props?.setPdfBase64Url(pdfupdatedData.base64);
|
||||
props?.setPdfArrayBuffer(pdfupdatedData.arrayBuffer);
|
||||
setIsDeletePage(false);
|
||||
handleRemoveWidgets(setSignerPos, signerPos, pageNumber, userId);
|
||||
handleRemoveWidgets(
|
||||
props?.setSignerPos,
|
||||
props?.signerPos,
|
||||
props?.pageNumber
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// `removeFile` is used to remove file if exists
|
||||
const removeFile = (e) => {
|
||||
if (e) {
|
||||
e.target.value = "";
|
||||
}
|
||||
};
|
||||
|
||||
const handleFileUpload = async (e) => {
|
||||
const file = e.target.files[0];
|
||||
if (!file) {
|
||||
alert("Please upload a valid PDF file.");
|
||||
return;
|
||||
}
|
||||
if (!file.type.includes("pdf")) {
|
||||
alert("Only PDF files are allowed.");
|
||||
return;
|
||||
}
|
||||
|
||||
const mb = Math.round(file?.size / Math.pow(1024, 2));
|
||||
if (mb > maxFileSize) {
|
||||
alert(`${t("file-alert-1")} ${maxFileSize} MB`);
|
||||
removeFile(e);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
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
|
||||
});
|
||||
const basePdfDoc = await PDFDocument.load(props.pdfArrayBuffer);
|
||||
|
||||
// Copy pages from the uploaded PDF to the base PDF
|
||||
const uploadedPdfPages = await basePdfDoc.copyPages(
|
||||
uploadedPdfDoc,
|
||||
uploadedPdfDoc.getPageIndices()
|
||||
);
|
||||
uploadedPdfPages.forEach((page) => basePdfDoc.addPage(page));
|
||||
// Save the updated PDF
|
||||
const pdfBase64 = await basePdfDoc.saveAsBase64({
|
||||
useObjectStreams: false
|
||||
});
|
||||
const pdfBuffer = base64ToArrayBuffer(pdfBase64);
|
||||
props.setPdfArrayBuffer(pdfBuffer);
|
||||
props.setPdfBase64Url(pdfBase64);
|
||||
props.setIsUploadPdf && props.setIsUploadPdf(true);
|
||||
mergePdfInputRef.current.value = "";
|
||||
} catch (error) {
|
||||
mergePdfInputRef.current.value = "";
|
||||
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 && isShowHeader ? (
|
||||
{isMobile && props?.isShowHeader ? (
|
||||
<div
|
||||
id="navbar"
|
||||
className={isGuestSigner ? "stickySignerHead" : "stickyHead"}
|
||||
className="stickyHead"
|
||||
style={{
|
||||
width: isGuestSigner
|
||||
? window.innerWidth
|
||||
: window.innerWidth - 30 + "px"
|
||||
width: window.innerWidth + "px"
|
||||
}}
|
||||
>
|
||||
<div className="flex justify-between items-center py-[5px] px-[10px] ">
|
||||
<div className="flex justify-between items-center py-[5px] pl-[10px] ">
|
||||
<div onClick={() => window.history.go(-2)}>
|
||||
<i
|
||||
className="fa-light fa-arrow-left text-base-content"
|
||||
@@ -97,14 +180,14 @@ function Header({
|
||||
></i>
|
||||
</div>
|
||||
<PrevNext
|
||||
pageNumber={pageNumber}
|
||||
allPages={allPages}
|
||||
changePage={changePage}
|
||||
pageNumber={props?.pageNumber}
|
||||
allPages={props?.allPages}
|
||||
changePage={props?.changePage}
|
||||
/>
|
||||
{isCompleted || alreadySign ? (
|
||||
{props?.isCompleted || props?.alreadySign ? (
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger asChild>
|
||||
<div className="op-link op-link-primary no-underline text-[16px] font-semibold pr-[3px] pl-[5px]">
|
||||
<div className="op-link op-link-primary no-underline text-[16px] font-semibold px-3">
|
||||
<i
|
||||
className="fa-light fa-ellipsis-v"
|
||||
aria-hidden="true"
|
||||
@@ -119,10 +202,14 @@ function Header({
|
||||
<DropdownMenu.Item
|
||||
className="DropdownMenuItem"
|
||||
onClick={() => {
|
||||
if (isCompleted) {
|
||||
setIsDownloadModal(true);
|
||||
if (props?.isCompleted) {
|
||||
props?.setIsDownloadModal(true);
|
||||
} else {
|
||||
handleDownloadPdf(pdfDetails, setIsDownloading);
|
||||
handleDownloadPdf(
|
||||
props?.pdfDetails,
|
||||
setIsDownloading,
|
||||
props.pdfBase64
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
@@ -134,12 +221,12 @@ function Header({
|
||||
{t("download")}
|
||||
</div>
|
||||
</DropdownMenu.Item>
|
||||
{isCompleted && (
|
||||
{props?.isCompleted && (
|
||||
<DropdownMenu.Item
|
||||
className="DropdownMenuItem"
|
||||
onClick={() =>
|
||||
handleDownloadCertificate(
|
||||
pdfDetails,
|
||||
props?.pdfDetails,
|
||||
setIsDownloading
|
||||
)
|
||||
}
|
||||
@@ -153,10 +240,10 @@ function Header({
|
||||
</div>
|
||||
</DropdownMenu.Item>
|
||||
)}
|
||||
{isSignYourself && (
|
||||
{props?.isSignYourself && (
|
||||
<DropdownMenu.Item
|
||||
className="DropdownMenuItem"
|
||||
onClick={() => setIsEmail(true)}
|
||||
onClick={() => props?.setIsEmail(true)}
|
||||
>
|
||||
<div className="flex flex-row">
|
||||
<i
|
||||
@@ -170,7 +257,7 @@ function Header({
|
||||
<DropdownMenu.Item
|
||||
className="DropdownMenuItem"
|
||||
onClick={(e) =>
|
||||
handleToPrint(e, pdfUrl, setIsDownloading, pdfDetails)
|
||||
handleToPrint(e, setIsDownloading, props?.pdfDetails)
|
||||
}
|
||||
>
|
||||
<div className="flex flex-row">
|
||||
@@ -189,9 +276,9 @@ function Header({
|
||||
{/* current signer is checking user send request and check status of pdf sign than if current
|
||||
user exist than show finish button else no
|
||||
*/}
|
||||
{currentSigner && (
|
||||
{props?.currentSigner && (
|
||||
<div className="flex items-center" data-tut="reactourFifth">
|
||||
{decline && (
|
||||
{props?.decline && (
|
||||
<div
|
||||
onClick={() => handleDeclinePdfAlert()}
|
||||
className="text-[red] border-none font-[650] text-[14px] mr-2"
|
||||
@@ -199,32 +286,41 @@ function Header({
|
||||
{t("decline")}
|
||||
</div>
|
||||
)}
|
||||
{isPlaceholder ? (
|
||||
{props?.isPlaceholder ? (
|
||||
<div
|
||||
onClick={() => {
|
||||
if (!isMailSend) {
|
||||
alertSendEmail();
|
||||
if (!props?.isMailSend) {
|
||||
props?.alertSendEmail();
|
||||
}
|
||||
}}
|
||||
className={`${
|
||||
isMailSend ? "" : "op-link-primary"
|
||||
props?.isMailSend ? "" : "op-link-primary"
|
||||
} op-link no-underline font-[650] text-[14px]`}
|
||||
data-tut="headerArea"
|
||||
>
|
||||
{completeBtnTitle ? completeBtnTitle : t("send")}
|
||||
{props?.completeBtnTitle
|
||||
? props?.completeBtnTitle
|
||||
: t("send")}
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
data-tut="reactourThird"
|
||||
onClick={() => embedWidgetsData()}
|
||||
onClick={() => props?.embedWidgetsData()}
|
||||
className="border-none font-[650] text-[14px] op-link op-link-primary no-underline"
|
||||
>
|
||||
{t("finish")}
|
||||
</div>
|
||||
)}
|
||||
<input
|
||||
type="file"
|
||||
className="hidden"
|
||||
accept="application/pdf"
|
||||
ref={mergePdfInputRef}
|
||||
onChange={handleFileUpload}
|
||||
/>
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger asChild>
|
||||
<div className="font-[650] text-[18px] px-2 ml-[4px] text-base-content no-underline">
|
||||
<div className="font-[650] text-[18px] px-3 text-base-content no-underline">
|
||||
<i
|
||||
className="fa-light fa-ellipsis-v"
|
||||
aria-hidden="true"
|
||||
@@ -236,10 +332,28 @@ function Header({
|
||||
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={() =>
|
||||
handleDownloadPdf(pdfDetails, setIsDownloading)
|
||||
handleDownloadPdf(
|
||||
props?.pdfDetails,
|
||||
setIsDownloading,
|
||||
props.pdfBase64
|
||||
)
|
||||
}
|
||||
>
|
||||
<div className="flex flex-row">
|
||||
@@ -252,11 +366,45 @@ function Header({
|
||||
</span>
|
||||
</div>
|
||||
</DropdownMenu.Item>
|
||||
{!isDisableRotate && (
|
||||
{!props?.isDisablePdfEditTools && (
|
||||
<>
|
||||
<DropdownMenu.Item
|
||||
className="DropdownMenuItem"
|
||||
onClick={() => handleRotationFun(90)}
|
||||
onClick={() => mergePdfInputRef.current.click()}
|
||||
>
|
||||
<div className="flex flex-row">
|
||||
<i className="fa-light fa-plus text-gray-500 2xl:text-[30px] mr-[3px]"></i>
|
||||
<span className="font-[500]">
|
||||
{t("add-pages")}
|
||||
</span>
|
||||
</div>
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item
|
||||
className="DropdownMenuItem"
|
||||
onClick={() => setIsDeletePage(true)}
|
||||
>
|
||||
<div className="flex flex-row">
|
||||
<i className="fa-light fa-trash text-gray-500 2xl:text-[30px] mr-[3px]"></i>
|
||||
<span className="font-[500]">
|
||||
{t("delete-page")}
|
||||
</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"
|
||||
onClick={() => props?.handleRotationFun(90)}
|
||||
>
|
||||
<div className="flex flex-row">
|
||||
<i className="fa-light fa-rotate-right text-gray-500 2xl:text-[30px] mr-[3px]"></i>
|
||||
@@ -267,7 +415,7 @@ function Header({
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item
|
||||
className="DropdownMenuItem"
|
||||
onClick={() => handleRotationFun(-90)}
|
||||
onClick={() => props?.handleRotationFun(-90)}
|
||||
>
|
||||
<div className="flex flex-row">
|
||||
<i className="fa-light fa-rotate-left text-gray-500 2xl:text-[30px] mr-[3px]"></i>
|
||||
@@ -276,23 +424,12 @@ function Header({
|
||||
</span>
|
||||
</div>
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item
|
||||
className="DropdownMenuItem"
|
||||
onClick={() => setIsDeletePage(true)}
|
||||
>
|
||||
<div className="flex flex-row">
|
||||
<i className="fa-light fa-trash text-gray-500 2xl:text-[30px] mr-[3px]"></i>
|
||||
<span className="font-[500]">
|
||||
{t("delete-page")}
|
||||
</span>
|
||||
</div>
|
||||
</DropdownMenu.Item>
|
||||
</>
|
||||
)}
|
||||
|
||||
<DropdownMenu.Item
|
||||
className="DropdownMenuItem"
|
||||
onClick={() => clickOnZoomIn()}
|
||||
onClick={() => props?.clickOnZoomIn()}
|
||||
>
|
||||
<div className="flex flex-row">
|
||||
<i className="fa-light fa-magnifying-glass-plus text-gray-500 2xl:text-[30px] mr-[3px]"></i>
|
||||
@@ -301,7 +438,7 @@ function Header({
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item
|
||||
className="DropdownMenuItem"
|
||||
onClick={() => clickOnZoomOut()}
|
||||
onClick={() => props?.clickOnZoomOut()}
|
||||
>
|
||||
<div className="flex flex-row">
|
||||
<i className="fa-light fa-magnifying-glass-minus text-gray-500 2xl:text-[30px] mr-[3px]"></i>
|
||||
@@ -315,11 +452,11 @@ function Header({
|
||||
</DropdownMenu.Root>
|
||||
</div>
|
||||
)}
|
||||
{isPublicTemplate && (
|
||||
{props?.isPublicTemplate && (
|
||||
<div
|
||||
data-tut="reactourThird"
|
||||
onClick={() => embedWidgetsData()}
|
||||
className="border-none font-[650] text-[14px] op-link op-link-primary no-underline"
|
||||
onClick={() => props?.embedWidgetsData()}
|
||||
className="border-none font-[650] text-[14px] pr-2 op-link op-link-primary no-underline"
|
||||
>
|
||||
{t("sign-now")}
|
||||
</div>
|
||||
@@ -331,25 +468,27 @@ function Header({
|
||||
) : (
|
||||
<div className="flex flex-wrap justify-between items-center w-full gap-y-1 ml-1">
|
||||
<PrevNext
|
||||
pageNumber={pageNumber}
|
||||
allPages={allPages}
|
||||
changePage={changePage}
|
||||
pageNumber={props?.pageNumber}
|
||||
allPages={props?.allPages}
|
||||
changePage={props?.changePage}
|
||||
/>
|
||||
{isPlaceholder ? (
|
||||
{props?.isPlaceholder ? (
|
||||
<>
|
||||
<div className="flex mx-[100px] lg:mx-0 order-last lg:order-none">
|
||||
{!isMailSend &&
|
||||
signersdata.length > 0 &&
|
||||
signersdata.length !== filterPrefill.length && (
|
||||
{!props?.isMailSend &&
|
||||
props?.signersdata.length > 0 &&
|
||||
props?.signersdata.length !== filterPrefill.length && (
|
||||
<div>
|
||||
{filterPrefill.length === 0 ? (
|
||||
<span className="text-[13px] text-[#f5405e]">
|
||||
{t("add")} {signersdata.length - filterPrefill.length}{" "}
|
||||
{t("add")}{" "}
|
||||
{props?.signersdata.length - filterPrefill.length}{" "}
|
||||
{t("recipients")} {t("widgets-name.signature")}
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-[13px] text-[#f5405e]">
|
||||
{t("add")} {signersdata.length - filterPrefill.length}{" "}
|
||||
{t("add")}{" "}
|
||||
{props?.signersdata.length - filterPrefill.length}{" "}
|
||||
{t("more")}
|
||||
{t("recipients")} {t("widgets-name.signature")}
|
||||
</span>
|
||||
@@ -358,9 +497,9 @@ function Header({
|
||||
)}
|
||||
</div>
|
||||
<div className="flex">
|
||||
{setIsEditTemplate && (
|
||||
{props?.setIsEditTemplate && (
|
||||
<button
|
||||
onClick={() => setIsEditTemplate(true)}
|
||||
onClick={() => props?.setIsEditTemplate(true)}
|
||||
className="outline-none border-none text-center mr-[3px]"
|
||||
>
|
||||
<i className="fa-light fa-gear fa-lg"></i>
|
||||
@@ -376,25 +515,25 @@ function Header({
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
disabled={isMailSend && true}
|
||||
disabled={props?.isMailSend && true}
|
||||
data-tut="headerArea"
|
||||
className="op-btn op-btn-primary op-btn-sm mr-[3px]"
|
||||
onClick={() => alertSendEmail()}
|
||||
onClick={() => props?.alertSendEmail()}
|
||||
>
|
||||
{completeBtnTitle
|
||||
? completeBtnTitle
|
||||
: isMailSend
|
||||
{props?.completeBtnTitle
|
||||
? props?.completeBtnTitle
|
||||
: props?.isMailSend
|
||||
? t("sent")
|
||||
: t("send")}
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
) : isPdfRequestFiles ? (
|
||||
alreadySign ? (
|
||||
) : props?.isPdfRequestFiles || props?.isSelfSign ? (
|
||||
props?.alreadySign || (props?.isSelfSign && props?.isCompleted) ? (
|
||||
<div className="flex flex-row">
|
||||
<button
|
||||
onClick={(e) =>
|
||||
handleToPrint(e, pdfUrl, setIsDownloading, pdfDetails)
|
||||
handleToPrint(e, setIsDownloading, props?.pdfDetails)
|
||||
}
|
||||
type="button"
|
||||
className="op-btn op-btn-neutral op-btn-sm mr-[3px] shadow"
|
||||
@@ -405,11 +544,14 @@ function Header({
|
||||
></i>
|
||||
<span className="hidden lg:block">{t("print")}</span>
|
||||
</button>
|
||||
{isCompleted && (
|
||||
{props?.isCompleted && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
handleDownloadCertificate(pdfDetails, setIsDownloading)
|
||||
handleDownloadCertificate(
|
||||
props?.pdfDetails,
|
||||
setIsDownloading
|
||||
)
|
||||
}
|
||||
className="op-btn op-btn-secondary op-btn-sm mr-[3px] shadow"
|
||||
>
|
||||
@@ -424,10 +566,14 @@ function Header({
|
||||
type="button"
|
||||
className="op-btn op-btn-primary op-btn-sm mr-[3px] shadow"
|
||||
onClick={() => {
|
||||
if (isCompleted) {
|
||||
setIsDownloadModal(true);
|
||||
if (props?.isCompleted) {
|
||||
props?.setIsDownloadModal(true);
|
||||
} else {
|
||||
handleDownloadPdf(pdfDetails, setIsDownloading);
|
||||
handleDownloadPdf(
|
||||
props?.pdfDetails,
|
||||
setIsDownloading,
|
||||
props.pdfBase64
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
@@ -440,21 +586,16 @@ function Header({
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex" data-tut="reactourFifth">
|
||||
{!templateId && (
|
||||
<button
|
||||
onClick={() => window.history.go(-2)}
|
||||
type="button"
|
||||
className="op-btn op-btn-ghost op-btn-sm mr-[3px]"
|
||||
>
|
||||
{t("back")}
|
||||
</button>
|
||||
)}
|
||||
{currentSigner && (
|
||||
{props?.currentSigner && (
|
||||
<>
|
||||
{templateId && (
|
||||
{props?.templateId && (
|
||||
<button
|
||||
onClick={() =>
|
||||
handleDownloadPdf(pdfDetails, setIsDownloading)
|
||||
handleDownloadPdf(
|
||||
props?.pdfDetails,
|
||||
setIsDownloading,
|
||||
props.pdfBase64
|
||||
)
|
||||
}
|
||||
type="button"
|
||||
className="op-btn op-btn-ghost op-btn-sm mr-[3px]"
|
||||
@@ -462,42 +603,51 @@ function Header({
|
||||
<span className="hidden lg:block">{t("download")}</span>
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
className="op-btn op-btn-secondary op-btn-sm mr-[3px] shadow"
|
||||
onClick={() => handleDeclinePdfAlert()}
|
||||
>
|
||||
{t("decline")}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-primary op-btn-sm mr-[3px] shadow"
|
||||
onClick={() => embedWidgetsData()}
|
||||
>
|
||||
{t("finish")}
|
||||
</button>
|
||||
{!templateId && (
|
||||
{!props?.isSelfSign && (
|
||||
<button
|
||||
className="op-btn op-btn-secondary op-btn-sm mr-[3px] shadow"
|
||||
onClick={() => handleDeclinePdfAlert()}
|
||||
>
|
||||
{t("decline")}
|
||||
</button>
|
||||
)}
|
||||
{!props?.templateId && (
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-neutral op-btn-sm mr-[3px] shadow"
|
||||
className="op-btn op-btn-ghost op-btn-sm mr-[3px]"
|
||||
onClick={() =>
|
||||
handleDownloadPdf(pdfDetails, setIsDownloading)
|
||||
handleDownloadPdf(
|
||||
props?.pdfDetails,
|
||||
setIsDownloading,
|
||||
props.pdfBase64
|
||||
)
|
||||
}
|
||||
>
|
||||
<i className="fa-light fa-arrow-down font-semibold lg:hidden"></i>
|
||||
<span className="hidden lg:block">{t("download")}</span>
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-primary op-btn-sm mr-[3px] shadow"
|
||||
onClick={() => props?.embedWidgetsData()}
|
||||
>
|
||||
{t("finish")}
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
) : isCompleted ? (
|
||||
) : props?.isCompleted ? (
|
||||
<div className="flex flex-row">
|
||||
{isCompleted && (
|
||||
{props?.isCompleted && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
handleDownloadCertificate(pdfDetails, setIsDownloading)
|
||||
handleDownloadCertificate(
|
||||
props?.pdfDetails,
|
||||
setIsDownloading
|
||||
)
|
||||
}
|
||||
className="op-btn op-btn-secondary op-btn-sm gap-0 font-medium text-[12px] mr-[3px] shadow"
|
||||
>
|
||||
@@ -509,7 +659,7 @@ function Header({
|
||||
)}
|
||||
<button
|
||||
onClick={(e) =>
|
||||
handleToPrint(e, pdfUrl, setIsDownloading, pdfDetails)
|
||||
handleToPrint(e, setIsDownloading, props?.pdfDetails)
|
||||
}
|
||||
type="button"
|
||||
className="op-btn op-btn-neutral op-btn-sm gap-0 font-medium text-[12px] mr-[3px] shadow"
|
||||
@@ -520,9 +670,7 @@ function Header({
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-primary op-btn-sm gap-0 font-medium text-[12px] mr-[3px] shadow"
|
||||
onClick={() => {
|
||||
setIsDownloadModal(true);
|
||||
}}
|
||||
onClick={() => props?.setIsDownloadModal(true)}
|
||||
>
|
||||
<i className="fa-light fa-download" aria-hidden="true"></i>
|
||||
<span className="hidden lg:block ml-1">{t("download")}</span>
|
||||
@@ -530,18 +678,18 @@ function Header({
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-info op-btn-sm gap-0 font-medium text-[12px] mr-[3px] shadow"
|
||||
onClick={() => setIsEmail(true)}
|
||||
onClick={() => props?.setIsEmail(true)}
|
||||
>
|
||||
<i className="fa-light fa-envelope" aria-hidden="true"></i>
|
||||
<span className="hidden lg:block ml-1">{t("mail")}</span>
|
||||
</button>
|
||||
</div>
|
||||
) : isPublicTemplate ? (
|
||||
) : props?.isPublicTemplate ? (
|
||||
<div className="flex">
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-primary op-btn-sm shadow"
|
||||
onClick={() => embedWidgetsData()}
|
||||
onClick={() => props?.embedWidgetsData()}
|
||||
>
|
||||
{t("sign-now")}
|
||||
</button>
|
||||
@@ -558,7 +706,7 @@ function Header({
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-primary op-btn-sm mr-[3px]"
|
||||
onClick={() => embedWidgetsData()}
|
||||
onClick={() => props?.embedWidgetsData()}
|
||||
>
|
||||
{t("finish")}
|
||||
</button>
|
||||
@@ -572,28 +720,32 @@ function Header({
|
||||
</div>
|
||||
)}
|
||||
<ModalUi
|
||||
isOpen={isDownloading === "certificate"}
|
||||
isOpen={
|
||||
isDownloading === "certificate" || isDownloading === "certificate_err"
|
||||
}
|
||||
title={
|
||||
isDownloading === "certificate"
|
||||
isDownloading === "certificate" || isDownloading === "certificate_err"
|
||||
? t("generating-certificate")
|
||||
: t("pdf-download")
|
||||
}
|
||||
handleClose={() => setIsDownloading("")}
|
||||
>
|
||||
<div className="p-3 md:p-5 text-[13px] md:text-base text-center text-base-content">
|
||||
{isDownloading === "certificate"}{" "}
|
||||
<p>{t("generate-certificate-alert")}</p>
|
||||
{isDownloading === "certificate" ? (
|
||||
<p>{t("generate-certificate-alert")}</p>
|
||||
) : (
|
||||
<p>{t("generate-certificate-err")}</p>
|
||||
)}
|
||||
</div>
|
||||
</ModalUi>
|
||||
<ModalUi
|
||||
isOpen={isDeletePage}
|
||||
title={t("validation-alert")}
|
||||
title={t("delete-page")}
|
||||
handleClose={() => setIsDeletePage(false)}
|
||||
>
|
||||
<div className="h-[100%] p-[20px]">
|
||||
<p>{t("delete-alert-2")}</p>
|
||||
<p className="pt-3 font-medium">{t("delete-note")}</p>
|
||||
{/* <p>{t("deletepage-alert")}</p> */}
|
||||
<p className="font-medium">{t("delete-alert-2")}</p>
|
||||
<p className="pt-3">{t("delete-note")}</p>
|
||||
<div className="h-[1px] bg-[#9f9f9f] w-full my-[15px]"></div>
|
||||
<button
|
||||
onClick={() => handleDetelePage()}
|
||||
@@ -611,6 +763,12 @@ function Header({
|
||||
</button>
|
||||
</div>
|
||||
</ModalUi>
|
||||
<PageReorderModal
|
||||
isOpen={isReorderModal}
|
||||
handleClose={() => setIsReorderModal(false)}
|
||||
totalPages={props.allPages}
|
||||
onSave={handleReorderSave}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { deletePdfPage, handleRemoveWidgets } from "../../constant/Utils";
|
||||
import ModalUi from "../../primitives/ModalUi";
|
||||
|
||||
function PdfZoom(props) {
|
||||
const { t } = useTranslation();
|
||||
const [isDeletePage, setIsDeletePage] = useState(false);
|
||||
const handleDetelePage = async () => {
|
||||
props.setIsUploadPdf(true);
|
||||
try {
|
||||
const pdfupdatedData = await deletePdfPage(
|
||||
props.pdfArrayBuffer,
|
||||
props.pageNumber
|
||||
);
|
||||
if (pdfupdatedData?.totalPages === 1) {
|
||||
alert(t("delete-alert"));
|
||||
} else {
|
||||
props.setPdfBase64Url(pdfupdatedData.base64);
|
||||
props.setPdfArrayBuffer(pdfupdatedData.arrayBuffer);
|
||||
setIsDeletePage(false);
|
||||
handleRemoveWidgets(
|
||||
props.setSignerPos,
|
||||
props.signerPos,
|
||||
props.pageNumber,
|
||||
props.userId
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("error in delete pdf page", e);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<span className="hidden md:flex flex-col gap-1 text-center md:w-[5%] mt-[43px] 2xl:mt-[63px]">
|
||||
<span
|
||||
className="bg-gray-50 px-[4px] 2xl:px-[15px] 2xl:py-[10px] cursor-pointer"
|
||||
onClick={() => props.clickOnZoomIn()}
|
||||
title={t("zoom-in")}
|
||||
>
|
||||
<i className="fa-light fa-magnifying-glass-plus text-gray-500 2xl:text-[30px]"></i>
|
||||
</span>
|
||||
{!props.isDisableDelete && (
|
||||
<span
|
||||
className="bg-gray-50 px-[4px] 2xl:px-[15px] 2xl:py-[10px] cursor-pointer"
|
||||
onClick={() => setIsDeletePage(true)}
|
||||
title={t("delete-page")}
|
||||
>
|
||||
<i className="fa-light fa-trash text-gray-500 2xl:text-[30px]"></i>
|
||||
</span>
|
||||
)}
|
||||
|
||||
{!props?.isDisableRotate && (
|
||||
<>
|
||||
<span
|
||||
className="bg-gray-50 px-[4px] 2xl:px-[15px] 2xl:py-[10px] cursor-pointer"
|
||||
onClick={() => props.handleRotationFun(90)}
|
||||
title={t("rotate-right")}
|
||||
>
|
||||
<i className="fa-light fa-rotate-right text-gray-500 2xl:text-[30px]"></i>
|
||||
</span>
|
||||
<span
|
||||
className="bg-gray-50 px-[4px] 2xl:px-[15px] 2xl:py-[10px] cursor-pointer"
|
||||
title={t("rotate-left")}
|
||||
onClick={() => props.handleRotationFun(-90)}
|
||||
>
|
||||
<i className="fa-light fa-rotate-left text-gray-500 2xl:text-[30px]"></i>
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
<span
|
||||
className="bg-gray-50 px-[4px] 2xl:px-[15px] 2xl:py-[10px]"
|
||||
onClick={() => props.clickOnZoomOut()}
|
||||
style={{
|
||||
cursor: props.zoomPercent > 0 ? "pointer" : "default"
|
||||
}}
|
||||
title={t("zoom-out")}
|
||||
>
|
||||
<i className="fa-light fa-magnifying-glass-minus text-gray-500 2xl:text-[30px]"></i>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<ModalUi
|
||||
isOpen={isDeletePage}
|
||||
title={t("validation-alert")}
|
||||
handleClose={() => setIsDeletePage(false)}
|
||||
>
|
||||
<div className="h-[100%] p-[20px]">
|
||||
<p>Are you sure want to delete this page ?</p>
|
||||
<p className="pt-3 font-medium">
|
||||
Note - once delete this page you can not undo
|
||||
</p>
|
||||
{/* <p>{t("deletepage-alert")}</p> */}
|
||||
<div className="h-[1px] bg-[#9f9f9f] w-full my-[15px]"></div>
|
||||
<button
|
||||
onClick={() => handleDetelePage()}
|
||||
type="button"
|
||||
className="op-btn op-btn-primary"
|
||||
>
|
||||
{t("yes")}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setIsDeletePage(false)}
|
||||
type="button"
|
||||
className="op-btn op-btn-ghost"
|
||||
>
|
||||
{t("no")}
|
||||
</button>
|
||||
</div>
|
||||
</ModalUi>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default PdfZoom;
|
||||
@@ -0,0 +1,261 @@
|
||||
import { useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
base64ToArrayBuffer,
|
||||
decryptPdf,
|
||||
deletePdfPage,
|
||||
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 {
|
||||
const pdfupdatedData = await deletePdfPage(
|
||||
props.pdfArrayBuffer,
|
||||
props.pageNumber
|
||||
);
|
||||
if (pdfupdatedData?.totalPages === 1) {
|
||||
alert(t("delete-alert"));
|
||||
} else {
|
||||
props.setPdfBase64Url(pdfupdatedData.base64);
|
||||
props.setPdfArrayBuffer(pdfupdatedData.arrayBuffer);
|
||||
setIsDeletePage(false);
|
||||
handleRemoveWidgets(
|
||||
props.setSignerPos,
|
||||
props.signerPos,
|
||||
props.pageNumber
|
||||
);
|
||||
props.setAllPages(pdfupdatedData.remainingPages || 1);
|
||||
if (props.allPages === props.pageNumber) {
|
||||
props.setPageNumber(props.pageNumber - 1);
|
||||
} else if (props.allPages > 2) {
|
||||
props.setPageNumber(props.pageNumber);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("error in delete pdf page", e);
|
||||
}
|
||||
};
|
||||
|
||||
// `removeFile` is used to remove file if exists
|
||||
const removeFile = (e) => {
|
||||
if (e) {
|
||||
e.target.value = "";
|
||||
}
|
||||
};
|
||||
|
||||
const handleFileUpload = async (e) => {
|
||||
const file = e.target.files[0];
|
||||
if (!file) {
|
||||
alert("Please upload a valid PDF file.");
|
||||
return;
|
||||
}
|
||||
if (!file.type.includes("pdf")) {
|
||||
alert("Only PDF files are allowed.");
|
||||
return;
|
||||
}
|
||||
const mb = Math.round(file?.size / Math.pow(1024, 2));
|
||||
if (mb > maxFileSize) {
|
||||
alert(`${t("file-alert-1")} ${maxFileSize} MB`);
|
||||
removeFile(e);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
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
|
||||
});
|
||||
const basePdfDoc = await PDFDocument.load(props.pdfArrayBuffer);
|
||||
|
||||
// Copy pages from the uploaded PDF to the base PDF
|
||||
const uploadedPdfPages = await basePdfDoc.copyPages(
|
||||
uploadedPdfDoc,
|
||||
uploadedPdfDoc.getPageIndices()
|
||||
);
|
||||
uploadedPdfPages.forEach((page) => basePdfDoc.addPage(page));
|
||||
// Save the updated PDF
|
||||
const pdfBase64 = await basePdfDoc.saveAsBase64({
|
||||
useObjectStreams: false
|
||||
});
|
||||
const pdfBuffer = base64ToArrayBuffer(pdfBase64);
|
||||
props.setPdfArrayBuffer(pdfBuffer);
|
||||
props.setPdfBase64Url(pdfBase64);
|
||||
props.setIsUploadPdf && props.setIsUploadPdf(true);
|
||||
mergePdfInputRef.current.value = "";
|
||||
} catch (error) {
|
||||
mergePdfInputRef.current.value = "";
|
||||
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 (
|
||||
<>
|
||||
<span className="hidden md:flex flex-col gap-1 text-center md:w-[5%] mt-[42px]">
|
||||
{!props.isDisableEditTools && (
|
||||
<>
|
||||
<span
|
||||
className="bg-gray-50 px-[4px] 2xl:py-[10px] cursor-pointer"
|
||||
onClick={() => mergePdfInputRef.current.click()}
|
||||
title={t("add-pages")}
|
||||
>
|
||||
<input
|
||||
type="file"
|
||||
className="hidden"
|
||||
accept="application/pdf"
|
||||
ref={mergePdfInputRef}
|
||||
onChange={handleFileUpload}
|
||||
/>
|
||||
<i className="fa-light fa-plus text-gray-500 2xl:text-[25px]"></i>
|
||||
</span>
|
||||
<span
|
||||
className="bg-gray-50 px-[4px] 2xl:py-[10px] cursor-pointer"
|
||||
onClick={() => setIsDeletePage(true)}
|
||||
title={t("delete-page")}
|
||||
>
|
||||
<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
|
||||
className="bg-gray-50 px-[4px] 2xl:py-[10px] cursor-pointer"
|
||||
onClick={() => props.clickOnZoomIn()}
|
||||
title={t("zoom-in")}
|
||||
>
|
||||
<i className="fa-light fa-magnifying-glass-plus text-gray-500 2xl:text-[25px]"></i>
|
||||
</span>
|
||||
|
||||
{!props.isDisableEditTools && (
|
||||
<>
|
||||
<span
|
||||
className="bg-gray-50 px-[4px] 2xl:py-[10px] cursor-pointer"
|
||||
onClick={() => props.handleRotationFun(90)}
|
||||
title={t("rotate-right")}
|
||||
>
|
||||
<i className="fa-light fa-rotate-right text-gray-500 2xl:text-[25px]"></i>
|
||||
</span>
|
||||
<span
|
||||
className="bg-gray-50 px-[4px] 2xl:py-[10px] cursor-pointer"
|
||||
title={t("rotate-left")}
|
||||
onClick={() => props.handleRotationFun(-90)}
|
||||
>
|
||||
<i className="fa-light fa-rotate-left text-gray-500 2xl:text-[25px]"></i>
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
<span
|
||||
className="bg-gray-50 px-[4px] 2xl:py-[10px]"
|
||||
onClick={() => props.clickOnZoomOut()}
|
||||
style={{
|
||||
cursor: props.zoomPercent > 0 ? "pointer" : "default"
|
||||
}}
|
||||
title={t("zoom-out")}
|
||||
>
|
||||
<i className="fa-light fa-magnifying-glass-minus text-gray-500 2xl:text-[30px]"></i>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<ModalUi
|
||||
isOpen={isDeletePage}
|
||||
title={t("delete-page")}
|
||||
handleClose={() => setIsDeletePage(false)}
|
||||
>
|
||||
<div className="h-[100%] p-[20px]">
|
||||
<p className="font-medium">{t("delete-alert-2")}</p>
|
||||
<p className="pt-3">{t("delete-note")}</p>
|
||||
<div className="h-[1px] bg-[#9f9f9f] w-full my-[15px]"></div>
|
||||
<button
|
||||
onClick={() => handleDetelePage()}
|
||||
type="button"
|
||||
className="op-btn op-btn-primary"
|
||||
>
|
||||
{t("yes")}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setIsDeletePage(false)}
|
||||
type="button"
|
||||
className="op-btn op-btn-ghost ml-1"
|
||||
>
|
||||
{t("no")}
|
||||
</button>
|
||||
</div>
|
||||
</ModalUi>
|
||||
<PageReorderModal
|
||||
isOpen={isReorderModal}
|
||||
handleClose={() => setIsReorderModal(false)}
|
||||
totalPages={props.allPages}
|
||||
onSave={handleReorderSave}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default PdfZoom;
|
||||
@@ -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(true)}
|
||||
onTouchEnd={() =>
|
||||
props.pos.type === textWidget && props?.setDraggingEnabled(true)
|
||||
}
|
||||
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;
|
||||
@@ -19,56 +19,89 @@ function PlaceholderCopy(props) {
|
||||
//function for get copy placeholder position
|
||||
const getCopyPlaceholderPosition = (
|
||||
type,
|
||||
currentPlaceholder,
|
||||
newPlaceholder,
|
||||
newPageNumber,
|
||||
existPlaceholderPosition
|
||||
) => {
|
||||
//remove existing page position when used option copy but first or copy but last
|
||||
const filterPosition =
|
||||
existPlaceholderPosition &&
|
||||
existPlaceholderPosition.filter(
|
||||
(data) => data.xPosition !== currentPlaceholder.xPosition
|
||||
(data) => data.xPosition !== newPlaceholder.xPosition
|
||||
);
|
||||
//first option - copy all page but not first
|
||||
//copy all page placeholder at requested position except first page
|
||||
if (newPageNumber === 1 && type === 3) {
|
||||
if (filterPosition && filterPosition.length > 0) {
|
||||
//when user choose this option and placed widget on first page then do not remove first page widget
|
||||
//it should be copy all page included first page on same postion
|
||||
if (props.pageNumber === 1) {
|
||||
return {
|
||||
pageNumber: newPageNumber,
|
||||
pos: [...existPlaceholderPosition]
|
||||
};
|
||||
} //else when user copy widget and choose this option and not at first page then copy all page but not at first
|
||||
else if (filterPosition && filterPosition.length > 0) {
|
||||
return {
|
||||
pageNumber: newPageNumber,
|
||||
pos: [...filterPosition]
|
||||
};
|
||||
}
|
||||
}
|
||||
//second option - copy all page but not last
|
||||
//copy all page placeholder at requested position except last page
|
||||
else if (newPageNumber === props.allPages && type === 2) {
|
||||
if (existPlaceholderPosition) {
|
||||
//when user choose this option and placed widget on last page then do not remove last page widget
|
||||
//it should be copy all page included last page on same postion
|
||||
if (props.pageNumber === props.allPages) {
|
||||
return {
|
||||
pageNumber: newPageNumber,
|
||||
pos: [...existPlaceholderPosition]
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
pageNumber: newPageNumber,
|
||||
pos: [...filterPosition]
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
//this optin - copy all page with exiting widgets on same page
|
||||
//copy all page placeholder at requested position with existing placeholder position
|
||||
else if (existPlaceholderPosition) {
|
||||
if (newPageNumber === props.pageNumber) {
|
||||
return {
|
||||
pageNumber: props.pageNumber,
|
||||
pos: [...existPlaceholderPosition]
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
pageNumber: newPageNumber,
|
||||
pos: [...filterPosition]
|
||||
pos: [...existPlaceholderPosition, newPlaceholder]
|
||||
};
|
||||
}
|
||||
}
|
||||
//copy all page placeholder at requested position with existing placeholder position
|
||||
else if (existPlaceholderPosition) {
|
||||
return {
|
||||
pageNumber: newPageNumber,
|
||||
pos: [...filterPosition, currentPlaceholder]
|
||||
};
|
||||
}
|
||||
//copy all page placeholder at requested position
|
||||
else {
|
||||
return {
|
||||
pageNumber: newPageNumber,
|
||||
pos: [currentPlaceholder]
|
||||
};
|
||||
if (newPageNumber === props.pageNumber) {
|
||||
return {
|
||||
pageNumber: props.pageNumber,
|
||||
pos: [...existPlaceholderPosition]
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
pageNumber: newPageNumber,
|
||||
pos: [newPlaceholder]
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//function for copy placeholder as per select copy type
|
||||
const copyPlaceholder = (type) => {
|
||||
let newPlaceholderPosition = [];
|
||||
let newPageNumber = 1;
|
||||
const signerPosition = props.xyPostion;
|
||||
const signerPosition = props.xyPosition;
|
||||
const signerId = props.signerObjId ? props.signerObjId : props.Id;
|
||||
|
||||
//handle placeholder array and copy for multiple signers placeholder at requested location
|
||||
if (signerId) {
|
||||
//get current signers data
|
||||
@@ -90,10 +123,9 @@ function PlaceholderCopy(props) {
|
||||
const currentPlaceholder = placeholderPosition[0].pos.find(
|
||||
(position) => position.key === props.signKey
|
||||
);
|
||||
|
||||
for (let i = 0; i < props.allPages; i++) {
|
||||
const newId = randomId();
|
||||
currentPlaceholder.key = newId;
|
||||
const newPlaceholder = { ...currentPlaceholder, key: newId };
|
||||
//get exist placeholder position for particular page
|
||||
const existPlaceholder = filterSignerPosition[0].placeHolder.filter(
|
||||
(data) => data.pageNumber === newPageNumber
|
||||
@@ -104,11 +136,11 @@ function PlaceholderCopy(props) {
|
||||
//function for get copy to requested location of placeholder position
|
||||
const getPlaceholderObj = getCopyPlaceholderPosition(
|
||||
type,
|
||||
currentPlaceholder,
|
||||
newPlaceholder,
|
||||
newPageNumber,
|
||||
existPlaceholderPosition
|
||||
);
|
||||
if (getPlaceholderObj) {
|
||||
if (getPlaceholderObj?.pos) {
|
||||
newPlaceholderPosition.push(getPlaceholderObj);
|
||||
}
|
||||
newPageNumber++;
|
||||
@@ -138,13 +170,13 @@ function PlaceholderCopy(props) {
|
||||
|
||||
const signersData = signerPosition;
|
||||
signersData.splice(0, signerPosition.length, ...updatedSignerPlaceholder);
|
||||
props.setXyPostion(signersData);
|
||||
props.setXyPosition(signersData);
|
||||
}
|
||||
//handle signyourself array and copy for single signers placeholder at requested location
|
||||
else {
|
||||
const xyPostion = props.xyPostion;
|
||||
const xyPosition = props.xyPosition;
|
||||
|
||||
const placeholderPosition = xyPostion.filter(
|
||||
const placeholderPosition = xyPosition.filter(
|
||||
(data) => data.pageNumber === props.pageNumber
|
||||
);
|
||||
//get current placeholder position data which user want to copy
|
||||
@@ -154,18 +186,18 @@ function PlaceholderCopy(props) {
|
||||
|
||||
for (let i = 0; i < props.allPages; i++) {
|
||||
//get exist placeholder position for particular page
|
||||
const existPlaceholder = xyPostion.filter(
|
||||
const existPlaceholder = xyPosition.filter(
|
||||
(data) => data.pageNumber === newPageNumber
|
||||
);
|
||||
const existPlaceholderPosition =
|
||||
existPlaceholder[0] && existPlaceholder[0].pos;
|
||||
|
||||
const newId = randomId();
|
||||
currentPlaceholder.key = newId;
|
||||
const newPlaceholder = { ...currentPlaceholder, key: newId };
|
||||
//function for get copy to requested location of placeholder position
|
||||
const getPlaceholderObj = getCopyPlaceholderPosition(
|
||||
type,
|
||||
currentPlaceholder,
|
||||
newPlaceholder,
|
||||
newPageNumber,
|
||||
existPlaceholderPosition
|
||||
);
|
||||
@@ -175,14 +207,14 @@ function PlaceholderCopy(props) {
|
||||
|
||||
newPageNumber++;
|
||||
}
|
||||
props.setXyPostion(newPlaceholderPosition);
|
||||
props.setXyPosition(newPlaceholderPosition);
|
||||
}
|
||||
};
|
||||
|
||||
//function for getting selected type placeholder copy
|
||||
const handleApplyCopy = () => {
|
||||
if (selectCopyType === 4) {
|
||||
const signerPosition = props.xyPostion;
|
||||
const signerPosition = props.xyPosition;
|
||||
let currentXYposition;
|
||||
const signerId = props.signerObjId ? props.signerObjId : props.Id;
|
||||
if (signerId) {
|
||||
@@ -200,16 +232,16 @@ function PlaceholderCopy(props) {
|
||||
handleCopyNextToWidget(
|
||||
currentXYposition,
|
||||
props.widgetType,
|
||||
props.xyPostion,
|
||||
props.xyPosition,
|
||||
props.pageNumber,
|
||||
props.setXyPostion,
|
||||
props.setXyPosition,
|
||||
props?.Id
|
||||
);
|
||||
} else {
|
||||
const getIndex = props?.xyPostion.findIndex(
|
||||
const getIndex = props?.xyPosition.findIndex(
|
||||
(data) => data.pageNumber === props.pageNumber
|
||||
);
|
||||
const placeholderPosition = props?.xyPostion[getIndex];
|
||||
const placeholderPosition = props?.xyPosition[getIndex];
|
||||
//get current placeholder position data which user want to copy
|
||||
currentXYposition = placeholderPosition.pos.find(
|
||||
(pos) => pos.key === props.signKey
|
||||
@@ -218,9 +250,9 @@ function PlaceholderCopy(props) {
|
||||
handleCopyNextToWidget(
|
||||
currentXYposition,
|
||||
props.widgetType,
|
||||
props.xyPostion,
|
||||
props.xyPosition,
|
||||
getIndex,
|
||||
props.setXyPostion
|
||||
props.setXyPosition
|
||||
);
|
||||
}
|
||||
} else {
|
||||
@@ -229,7 +261,7 @@ function PlaceholderCopy(props) {
|
||||
};
|
||||
const handleUniqueId = () => {
|
||||
const signerId = props.signerObjId ? props.signerObjId : props.Id;
|
||||
if (signerId && props.widgetType === textWidget) {
|
||||
if (signerId && props.widgetType === textWidget && props.setTempSignerId) {
|
||||
props.setUniqueId(props?.tempSignerId);
|
||||
props.setTempSignerId("");
|
||||
}
|
||||
@@ -260,7 +292,7 @@ function PlaceholderCopy(props) {
|
||||
);
|
||||
})}
|
||||
|
||||
<div className="flex bg-[#9f9f9f] w-full my-[15px]"></div>
|
||||
<div className="flex flex-row bg-[#9f9f9f] w-full my-[15px]"></div>
|
||||
<button
|
||||
onClick={() => {
|
||||
handleApplyCopy();
|
||||
@@ -274,7 +306,7 @@ function PlaceholderCopy(props) {
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-ghost"
|
||||
className="op-btn op-btn-ghost ml-2"
|
||||
onClick={() => handleUniqueId()}
|
||||
>
|
||||
{t("cancel")}
|
||||
@@ -1,879 +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";
|
||||
function PlaceholderType(props) {
|
||||
const { t } = useTranslation();
|
||||
const type = props?.pos?.type;
|
||||
const widgetTypeTraslation = 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 years = range(1990, 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 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 = () => {
|
||||
props.setDraggingEnabled(true);
|
||||
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);
|
||||
}
|
||||
}
|
||||
// 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.xyPostion,
|
||||
null,
|
||||
props.setXyPostion,
|
||||
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.xyPostion,
|
||||
props.index,
|
||||
props.setXyPostion,
|
||||
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.xyPostion,
|
||||
props.index,
|
||||
props.setXyPostion,
|
||||
props.data && props.data.Id,
|
||||
false,
|
||||
null,
|
||||
isDefaultEmpty,
|
||||
isRadio
|
||||
);
|
||||
};
|
||||
//function to set onchange date
|
||||
const handleOnDateChange = (date) => {
|
||||
props.setStartDate(date);
|
||||
const isDateChange = true;
|
||||
const dateObj = {
|
||||
date: date,
|
||||
format: props.selectDate.format
|
||||
};
|
||||
props.handleSaveDate(dateObj, isDateChange);
|
||||
};
|
||||
//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.xyPostion,
|
||||
props.index,
|
||||
props.setXyPostion,
|
||||
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 "
|
||||
/>
|
||||
) : (
|
||||
<div className="text-[11px] w-full h-full text-black flex flex-col justify-center items-center">
|
||||
{props?.handleUserName &&
|
||||
props?.handleUserName(
|
||||
props?.data?.Id,
|
||||
props?.data?.Role,
|
||||
widgetTypeTraslation
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
case "stamp":
|
||||
return props.pos.SignUrl ? (
|
||||
<img
|
||||
alt="stamp"
|
||||
draggable="false"
|
||||
src={props.pos.SignUrl}
|
||||
className="w-full h-full"
|
||||
/>
|
||||
) : (
|
||||
<div className="text-[11px] w-full h-full text-black flex flex-col justify-center items-center">
|
||||
{props?.handleUserName &&
|
||||
props?.handleUserName(
|
||||
props?.data?.Id,
|
||||
props?.data?.Role,
|
||||
widgetTypeTraslation
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
case "checkbox":
|
||||
return (
|
||||
<div style={{ zIndex: props.isSignYourself && "99" }}>
|
||||
{props.pos.options?.values?.map((data, ind) => {
|
||||
return (
|
||||
<div key={ind} className="flex items-center text-center gap-0.5">
|
||||
<input
|
||||
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
|
||||
style={{
|
||||
fontSize: fontSize,
|
||||
color: fontColor
|
||||
}}
|
||||
className="text-xs mb-0 text-center"
|
||||
>
|
||||
{data}
|
||||
</label>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
case textInputWidget:
|
||||
return props.isSignYourself ||
|
||||
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
|
||||
<textarea
|
||||
ref={inputRef}
|
||||
placeholder={validatePlaceholder || "please enter value"}
|
||||
rows={1}
|
||||
onKeyDown={handleEnterPress}
|
||||
value={textValue}
|
||||
onBlur={handleInputBlur}
|
||||
onChange={(e) => {
|
||||
setTextValue(e.target.value);
|
||||
onChangeInput(
|
||||
e.target.value,
|
||||
props.pos.key,
|
||||
props.xyPostion,
|
||||
props.index,
|
||||
props.setXyPostion,
|
||||
props.data && props.data?.Id,
|
||||
false
|
||||
);
|
||||
}}
|
||||
className={textWidgetCls}
|
||||
style={{
|
||||
fontSize: fontSize,
|
||||
color: fontColor
|
||||
}}
|
||||
cols="50"
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
fontSize: fontSize,
|
||||
color: fontColor,
|
||||
overflow: "hidden"
|
||||
}}
|
||||
>
|
||||
<span>{widgetTypeTraslation}</span>
|
||||
</div>
|
||||
);
|
||||
case "dropdown":
|
||||
return props.data?.signerObjId === props.signerObjId ? (
|
||||
<select
|
||||
style={{
|
||||
fontSize: fontSize,
|
||||
color: fontColor
|
||||
}}
|
||||
className={`${selectWidgetCls} text-[12px] bg-inherit`}
|
||||
id="myDropdown"
|
||||
value={selectOption}
|
||||
onChange={(e) => {
|
||||
setSelectOption(e.target.value);
|
||||
onChangeInput(
|
||||
e.target.value,
|
||||
props.pos.key,
|
||||
props.xyPostion,
|
||||
props.index,
|
||||
props.setXyPostion,
|
||||
props.data && props.data?.Id,
|
||||
false
|
||||
);
|
||||
}}
|
||||
>
|
||||
{/* 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={{
|
||||
fontSize: fontSize,
|
||||
color: fontColor
|
||||
}}
|
||||
className=" overflow-hidden"
|
||||
>
|
||||
{props.pos?.options?.name
|
||||
? props.pos.options.name
|
||||
: widgetTypeTraslation}
|
||||
</div>
|
||||
);
|
||||
case "initials":
|
||||
return props.pos.SignUrl ? (
|
||||
<img
|
||||
alt="initials"
|
||||
draggable="false"
|
||||
src={props.pos.SignUrl}
|
||||
className="w-full h-full"
|
||||
/>
|
||||
) : (
|
||||
<div className="text-[11px] w-full h-full text-black flex flex-col justify-center items-center">
|
||||
{props?.handleUserName &&
|
||||
props?.handleUserName(
|
||||
props?.data?.Id,
|
||||
props?.data?.Role,
|
||||
widgetTypeTraslation
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
case "name":
|
||||
return props.isSignYourself ||
|
||||
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
|
||||
<textarea
|
||||
ref={inputRef}
|
||||
placeholder={"name"}
|
||||
rows={1}
|
||||
onKeyDown={handleEnterPress}
|
||||
value={textValue}
|
||||
onChange={(e) => {
|
||||
const isDefault = false;
|
||||
handleTextValid(e);
|
||||
onChangeInput(
|
||||
e.target.value,
|
||||
props.pos.key,
|
||||
props.xyPostion,
|
||||
props.index,
|
||||
props.setXyPostion,
|
||||
props.data && props.data?.Id,
|
||||
isDefault
|
||||
);
|
||||
}}
|
||||
className={textWidgetCls}
|
||||
style={{
|
||||
fontSize: fontSize,
|
||||
color: fontColor
|
||||
}}
|
||||
cols="50"
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
fontSize: fontSize,
|
||||
color: fontColor,
|
||||
fontFamily: "Arial, sans-serif",
|
||||
overflow: "hidden"
|
||||
}}
|
||||
>
|
||||
<span>{widgetTypeTraslation}</span>
|
||||
</div>
|
||||
);
|
||||
case "company":
|
||||
return props.isSignYourself ||
|
||||
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
|
||||
<textarea
|
||||
ref={inputRef}
|
||||
placeholder="Enter text"
|
||||
rows={1}
|
||||
onKeyDown={handleEnterPress}
|
||||
value={textValue}
|
||||
onChange={(e) => {
|
||||
handleTextValid(e);
|
||||
onChangeInput(
|
||||
e.target.value,
|
||||
props.pos.key,
|
||||
props.xyPostion,
|
||||
props.index,
|
||||
props.setXyPostion,
|
||||
props.data && props.data?.Id,
|
||||
false
|
||||
);
|
||||
}}
|
||||
className={textWidgetCls}
|
||||
style={{
|
||||
fontSize: fontSize,
|
||||
color: fontColor
|
||||
}}
|
||||
cols="50"
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
fontSize: fontSize,
|
||||
color: fontColor,
|
||||
overflow: "hidden",
|
||||
fontFamily: "Arial, sans-serif"
|
||||
}}
|
||||
>
|
||||
<span>{widgetTypeTraslation}</span>
|
||||
</div>
|
||||
);
|
||||
case "job title":
|
||||
return props.isSignYourself ||
|
||||
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
|
||||
<textarea
|
||||
ref={inputRef}
|
||||
placeholder={"job title"}
|
||||
rows={1}
|
||||
onKeyDown={handleEnterPress}
|
||||
value={textValue}
|
||||
onChange={(e) => {
|
||||
handleTextValid(e);
|
||||
onChangeInput(
|
||||
e.target.value,
|
||||
props.pos.key,
|
||||
props.xyPostion,
|
||||
props.index,
|
||||
props.setXyPostion,
|
||||
props.data && props.data?.Id,
|
||||
false
|
||||
);
|
||||
}}
|
||||
className={textWidgetCls}
|
||||
style={{
|
||||
fontSize: fontSize,
|
||||
color: fontColor
|
||||
}}
|
||||
cols="50"
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
fontSize: fontSize,
|
||||
color: fontColor,
|
||||
fontFamily: "Arial, sans-serif",
|
||||
overflow: "hidden"
|
||||
}}
|
||||
>
|
||||
<span>{widgetTypeTraslation}</span>
|
||||
</div>
|
||||
);
|
||||
case "date":
|
||||
return props.isSignYourself ||
|
||||
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
|
||||
<div>
|
||||
<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
|
||||
? props?.startDate
|
||||
: props.pos.options?.response &&
|
||||
new Date(props.pos.options.response)
|
||||
}
|
||||
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>
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
fontSize: fontSize,
|
||||
color: fontColor,
|
||||
fontFamily: "Arial, sans-serif"
|
||||
}}
|
||||
className="items-center 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"
|
||||
/>
|
||||
) : (
|
||||
<div className="text-[11px] w-full h-full text-black flex flex-col justify-center items-center">
|
||||
{props?.handleUserName &&
|
||||
props?.handleUserName(
|
||||
props?.data?.Id,
|
||||
props?.data?.Role,
|
||||
widgetTypeTraslation
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
case "email":
|
||||
return props.isSignYourself ||
|
||||
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
|
||||
<textarea
|
||||
ref={inputRef}
|
||||
placeholder={"enter email"}
|
||||
rows={1}
|
||||
onKeyDown={handleEnterPress}
|
||||
value={textValue}
|
||||
onBlur={handleInputBlur}
|
||||
onChange={(e) => {
|
||||
handleTextValid(e);
|
||||
onChangeInput(
|
||||
e.target.value,
|
||||
props.pos.key,
|
||||
props.xyPostion,
|
||||
props.index,
|
||||
props.setXyPostion,
|
||||
props.data && props.data?.Id,
|
||||
false
|
||||
);
|
||||
}}
|
||||
className={textWidgetCls}
|
||||
style={{
|
||||
fontSize: fontSize,
|
||||
color: fontColor,
|
||||
fontFamily: "Arial, sans-serif"
|
||||
}}
|
||||
cols="50"
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
fontSize: fontSize,
|
||||
color: fontColor,
|
||||
fontFamily: "Arial, sans-serif",
|
||||
overflow: "hidden"
|
||||
}}
|
||||
>
|
||||
<span>{widgetTypeTraslation}</span>
|
||||
</div>
|
||||
);
|
||||
case radioButtonWidget:
|
||||
return (
|
||||
<div>
|
||||
{props.pos.options?.values.map((data, ind) => {
|
||||
return (
|
||||
<div key={ind} className="flex items-center text-center gap-0.5">
|
||||
<input
|
||||
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
|
||||
style={{
|
||||
fontSize: fontSize,
|
||||
color: fontColor
|
||||
}}
|
||||
className="text-xs mb-0"
|
||||
>
|
||||
{data}
|
||||
</label>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
case textWidget:
|
||||
return (
|
||||
<textarea
|
||||
placeholder="Enter text"
|
||||
rows={1}
|
||||
onKeyDown={handleEnterPress}
|
||||
value={textValue}
|
||||
onBlur={handleInputBlur}
|
||||
onChange={(e) => {
|
||||
setTextValue(e.target.value);
|
||||
onChangeInput(
|
||||
e.target.value,
|
||||
props.pos.key,
|
||||
props.xyPostion,
|
||||
props.index,
|
||||
props.setXyPostion,
|
||||
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="text-[11px] w-full h-full text-black flex flex-col justify-center items-center">
|
||||
{props.pos.isStamp ? <div>stamp</div> : <div>signature</div>}
|
||||
{props?.handleUserName &&
|
||||
props?.handleUserName(props?.data?.Id, props?.data?.Role)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default PlaceholderType;
|
||||
@@ -0,0 +1,534 @@
|
||||
import React, { useEffect, useState, forwardRef, useRef } 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 inputRef = useRef(null);
|
||||
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="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"
|
||||
/>
|
||||
) : (
|
||||
<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-[5px]" : ""}`
|
||||
}`; // 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
|
||||
ref={inputRef}
|
||||
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="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
|
||||
ref={inputRef}
|
||||
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
|
||||
ref={inputRef}
|
||||
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
|
||||
ref={inputRef}
|
||||
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"
|
||||
/>
|
||||
) : (
|
||||
<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
|
||||
ref={inputRef}
|
||||
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-2" : ""}`
|
||||
: `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;
|
||||
@@ -23,7 +23,7 @@ function PrevNext({ pageNumber, allPages, changePage }) {
|
||||
<i className="fa-light fa-backward" aria-hidden="true"></i>
|
||||
</span>
|
||||
</button>
|
||||
<span className="text-xs text-base-content font-medium mx-2 2xl:text-[30px]">
|
||||
<span className="text-xs text-base-content font-medium mx-2 2xl:text-[20px]">
|
||||
{pageNumber || (allPages ? 1 : "--")} {t("of")} {allPages || "--"}
|
||||
</span>
|
||||
<button
|
||||
@@ -31,8 +31,8 @@ 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
|
||||
const handleChangeSequence = (e, ind, isUp, isDown) => {
|
||||
//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;
|
||||
let index = ind;
|
||||
@@ -66,8 +66,7 @@ const RecipientList = (props) => {
|
||||
props?.setIsSelectId(index);
|
||||
props?.setUniqueId(remainingItems[index]?.Id);
|
||||
props?.setRoleName(remainingItems[index]?.Role);
|
||||
props?.setBlockColor(remainingItems[index]?.blockColor);
|
||||
|
||||
props?.setBlockColor(obj.blockColor);
|
||||
//change order of placeholder's list using sorting method
|
||||
//`remainingItems` is correct order of signers after change order
|
||||
const changeOrderSignerList = props?.signerPos.sort((a, b) => {
|
||||
@@ -97,7 +96,9 @@ const RecipientList = (props) => {
|
||||
props.sendInOrder && !isMobile && handleDragOver(e)
|
||||
}
|
||||
onDrop={(e) =>
|
||||
props.sendInOrder && !isMobile && handleChangeSequence(e, ind)
|
||||
props.sendInOrder &&
|
||||
!isMobile &&
|
||||
handleChangeSequence(e, ind, null, null, obj)
|
||||
}
|
||||
data-tut="recipientArea"
|
||||
onMouseEnter={() => setIsHover(ind)}
|
||||
@@ -196,7 +197,7 @@ const RecipientList = (props) => {
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<span className="text-[13px] 2xl:text-[25px]">
|
||||
<span className="text-[13px] 2xl:text-[17px]">
|
||||
{obj.Role}
|
||||
</span>
|
||||
)}
|
||||
@@ -222,7 +223,7 @@ const RecipientList = (props) => {
|
||||
onClick={(e) => {
|
||||
if (ind !== 0) {
|
||||
e.stopPropagation();
|
||||
handleChangeSequence(e, ind, "up");
|
||||
handleChangeSequence(e, ind, "up", null, obj);
|
||||
}
|
||||
}}
|
||||
className={ind === 0 ? "text-[gray]" : "text-black"}
|
||||
@@ -233,7 +234,7 @@ const RecipientList = (props) => {
|
||||
onClick={(e) => {
|
||||
if (ind !== props.signersdata.length - 1) {
|
||||
e.stopPropagation();
|
||||
handleChangeSequence(e, ind, null, "down");
|
||||
handleChangeSequence(e, ind, null, "down", obj);
|
||||
}
|
||||
}}
|
||||
className={
|
||||
@@ -259,7 +260,7 @@ const RecipientList = (props) => {
|
||||
: "text-base-content"
|
||||
} cursor-pointer`}
|
||||
>
|
||||
<i className="fa-light fa-trash-can 2xl:text-[30px]"></i>
|
||||
<i className="fa-light fa-trash-can 2xl:text-[22px]"></i>
|
||||
</div>
|
||||
)}
|
||||
<hr />
|
||||
@@ -1,118 +0,0 @@
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Document, Page } from "react-pdf";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
function RenderAllPdfPage({
|
||||
signPdfUrl,
|
||||
allPages,
|
||||
setAllPages,
|
||||
setPageNumber,
|
||||
setSignBtnPosition,
|
||||
pageNumber,
|
||||
signerPos,
|
||||
signerObjectId,
|
||||
containerWH,
|
||||
pdfBase64Url
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [signPageNumber, setSignPageNumber] = useState([]);
|
||||
const [bookmarkColor, setBookmarkColor] = useState("");
|
||||
//set all number of pages after load pdf
|
||||
function onDocumentLoad({ numPages }) {
|
||||
setAllPages(numPages);
|
||||
//check if signerPos array exist then save page number exist in signerPos array to show bookmark icon
|
||||
if (signerPos) {
|
||||
const checkUser = signerPos.filter(
|
||||
(data) => data.signerObjId === signerObjectId
|
||||
);
|
||||
setBookmarkColor(checkUser[0]?.blockColor);
|
||||
let pageNumberArr = [];
|
||||
if (checkUser?.length > 0) {
|
||||
checkUser[0]?.placeHolder?.map((data) => {
|
||||
pageNumberArr.push(data?.pageNumber);
|
||||
});
|
||||
|
||||
setSignPageNumber(pageNumberArr);
|
||||
}
|
||||
}
|
||||
}
|
||||
const pageContainer = useRef();
|
||||
const isHeader = useSelector((state) => state.showHeader);
|
||||
const [pageWidth, setPageWidth] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
const updateSize = () => {
|
||||
if (pageContainer.current) {
|
||||
setPageWidth(pageContainer.current.offsetWidth);
|
||||
}
|
||||
};
|
||||
|
||||
// Use setTimeout to wait for the transition to complete
|
||||
const timer = setTimeout(updateSize, 100); // match the transition duration
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, [isHeader, pageContainer, containerWH]);
|
||||
//'function `addSignatureBookmark` is used to display the page where the user's signature is located.
|
||||
const addSignatureBookmark = (index) => {
|
||||
const ispageNumber = signPageNumber.includes(index + 1);
|
||||
return (
|
||||
ispageNumber && (
|
||||
<div className="absolute z-20 top-[1px] -right-[13px] -translate-x-1/2 -translate-y-1/2">
|
||||
<i
|
||||
style={{ color: bookmarkColor || "red" }}
|
||||
className="fa-solid fa-bookmark"
|
||||
></i>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
};
|
||||
const pdfDataBase64 = `data:application/pdf;base64,${pdfBase64Url}`;
|
||||
return (
|
||||
<div ref={pageContainer} className="hidden w-[20%] bg-base-100 md:block">
|
||||
<div className="mx-2 pr-2 pt-2 pb-1 text-[15px] text-base-content font-semibold border-b-[1px] border-base-300">
|
||||
{t("pages")}
|
||||
</div>
|
||||
<div
|
||||
className={`flex h-[90%] flex-col items-center m-2
|
||||
autoSignScroll hide-scrollbar max-h-[100vh] `}
|
||||
>
|
||||
<Document
|
||||
loading={t("loading-doc")}
|
||||
onLoadSuccess={onDocumentLoad}
|
||||
file={(pdfBase64Url && pdfDataBase64) || signPdfUrl}
|
||||
>
|
||||
{Array.from(new Array(allPages), (el, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`${
|
||||
pageNumber - 1 === index ? "border-[red]" : "border-[#878787]"
|
||||
} border-2 m-[10px] flex justify-center items-center relative`}
|
||||
onClick={() => {
|
||||
setPageNumber(index + 1);
|
||||
if (setSignBtnPosition) {
|
||||
setSignBtnPosition([]);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{signerPos && addSignatureBookmark(index)}
|
||||
|
||||
{/* <div className="relative z-[1] overflow-hidden"> */}
|
||||
<Page
|
||||
key={`page_${index + 1}`}
|
||||
pageNumber={index + 1}
|
||||
width={pageWidth - 60}
|
||||
scale={1}
|
||||
renderAnnotationLayer={false}
|
||||
renderTextLayer={false}
|
||||
/>
|
||||
{/* </div> */}
|
||||
</div>
|
||||
))}
|
||||
</Document>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default RenderAllPdfPage;
|
||||
@@ -0,0 +1,222 @@
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Document, Page } from "react-pdf";
|
||||
import { useSelector } from "react-redux";
|
||||
import { PDFDocument } from "pdf-lib";
|
||||
import {
|
||||
base64ToArrayBuffer,
|
||||
decryptPdf,
|
||||
flattenPdf,
|
||||
getFileAsArrayBuffer
|
||||
} from "../../constant/Utils";
|
||||
import { maxFileSize } from "../../constant/const";
|
||||
|
||||
function RenderAllPdfPage(props) {
|
||||
const { t } = useTranslation();
|
||||
const pageContainer = useRef();
|
||||
const mergePdfInputRef = useRef(null);
|
||||
const [signPageNumber, setSignPageNumber] = useState([]);
|
||||
const [bookmarkColor, setBookmarkColor] = useState("");
|
||||
const isHeader = useSelector((state) => state.showHeader);
|
||||
const [pageWidth, setPageWidth] = useState("");
|
||||
|
||||
//set all number of pages after load pdf
|
||||
function onDocumentLoad({ numPages }) {
|
||||
props?.setAllPages(numPages);
|
||||
//check if signerPos array exist then save page number exist in signerPos array to show bookmark icon
|
||||
if (props?.signerPos) {
|
||||
const checkUser = props?.signerPos.filter(
|
||||
(data) => data.Id === props?.id
|
||||
);
|
||||
setBookmarkColor(checkUser[0]?.blockColor);
|
||||
let pageNumberArr = [];
|
||||
if (checkUser?.length > 0) {
|
||||
checkUser[0]?.placeHolder?.map((data) => {
|
||||
pageNumberArr.push(data?.pageNumber);
|
||||
});
|
||||
|
||||
setSignPageNumber(pageNumberArr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const updateSize = () => {
|
||||
if (pageContainer.current) {
|
||||
setPageWidth(pageContainer.current.offsetWidth);
|
||||
}
|
||||
};
|
||||
|
||||
// Use setTimeout to wait for the transition to complete
|
||||
const timer = setTimeout(updateSize, 100); // match the transition duration
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, [isHeader, pageContainer, props?.containerWH]);
|
||||
//'function `addSignatureBookmark` is used to display the page where the user's signature is located.
|
||||
const addSignatureBookmark = (index) => {
|
||||
const ispageNumber = signPageNumber.includes(index + 1);
|
||||
return (
|
||||
ispageNumber && (
|
||||
<div className="absolute z-20 top-[1px] -right-[13px] -translate-x-1/2 -translate-y-1/2">
|
||||
<i
|
||||
style={{ color: bookmarkColor || "red" }}
|
||||
className="fa-solid fa-bookmark"
|
||||
></i>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
};
|
||||
const pdfDataBase64 = `data:application/pdf;base64,${props?.pdfBase64Url}`;
|
||||
|
||||
// `removeFile` is used to remove file if exists
|
||||
const removeFile = (e) => {
|
||||
if (e) {
|
||||
e.target.value = "";
|
||||
}
|
||||
};
|
||||
// `handleFileUpload` is trigger when user click on add pages btn and is used to merge multiple pdf
|
||||
const handleFileUpload = async (e) => {
|
||||
const file = e.target.files[0];
|
||||
if (!file) {
|
||||
alert("Please upload a valid PDF file.");
|
||||
return;
|
||||
}
|
||||
if (!file.type.includes("pdf")) {
|
||||
alert("Only PDF files are allowed.");
|
||||
return;
|
||||
}
|
||||
const mb = Math.round(file?.size / Math.pow(1024, 2));
|
||||
if (mb > maxFileSize) {
|
||||
alert(`${t("file-alert-1")} ${maxFileSize} MB`);
|
||||
removeFile(e);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
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
|
||||
});
|
||||
const basePdfDoc = await PDFDocument.load(props.pdfArrayBuffer);
|
||||
|
||||
// Copy pages from the uploaded PDF to the base PDF
|
||||
const uploadedPdfPages = await basePdfDoc.copyPages(
|
||||
uploadedPdfDoc,
|
||||
uploadedPdfDoc.getPageIndices()
|
||||
);
|
||||
uploadedPdfPages.forEach((page) => basePdfDoc.addPage(page));
|
||||
// Save the updated PDF
|
||||
const pdfBase64 = await basePdfDoc.saveAsBase64({
|
||||
useObjectStreams: false
|
||||
});
|
||||
const pdfBuffer = base64ToArrayBuffer(pdfBase64);
|
||||
props.setPdfArrayBuffer(pdfBuffer);
|
||||
props.setPdfBase64Url(pdfBase64);
|
||||
props.setIsUploadPdf && props.setIsUploadPdf(true);
|
||||
mergePdfInputRef.current.value = "";
|
||||
} catch (error) {
|
||||
mergePdfInputRef.current.value = "";
|
||||
console.error("Error merging PDF:", error);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div ref={pageContainer} className="hidden w-[20%] bg-base-100 md:block">
|
||||
<div className="mx-2 pr-2 pt-2 pb-1 text-[15px] text-base-content font-semibold border-b-[1px] border-base-300">
|
||||
{t("pages")}
|
||||
</div>
|
||||
<div
|
||||
className={`flex h-[90%] flex-col items-center m-2
|
||||
autoSignScroll hide-scrollbar max-h-[100vh] `}
|
||||
>
|
||||
<Document
|
||||
error=""
|
||||
loading={t("loading-doc")}
|
||||
onLoadSuccess={onDocumentLoad}
|
||||
file={pdfDataBase64}
|
||||
>
|
||||
{Array.from(new Array(props?.allPages), (el, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`${
|
||||
props?.pageNumber - 1 === index
|
||||
? "border-[red]"
|
||||
: "border-[#878787]"
|
||||
} border-2 m-[10px] flex justify-center items-center relative`}
|
||||
onClick={() => {
|
||||
props?.setPageNumber(index + 1);
|
||||
if (props?.setSignBtnPosition) {
|
||||
props?.setSignBtnPosition([]);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{props?.signerPos && addSignatureBookmark(index)}
|
||||
<Page
|
||||
key={`page_${index + 1}`}
|
||||
pageNumber={index + 1}
|
||||
width={pageWidth - 60}
|
||||
scale={1}
|
||||
renderAnnotationLayer={false}
|
||||
renderTextLayer={false}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</Document>
|
||||
{props?.isMergePdfBtn && (
|
||||
<button
|
||||
className="mb-2 bg-base-100 px-2 py-2 ring-[0.5px] ring-base-content rounded-box flex gap-1 justify-center items-center"
|
||||
onClick={() => mergePdfInputRef.current.click()}
|
||||
title={t("add-pages")}
|
||||
>
|
||||
<input
|
||||
type="file"
|
||||
className="hidden"
|
||||
accept="application/pdf"
|
||||
ref={mergePdfInputRef}
|
||||
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>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default RenderAllPdfPage;
|
||||
@@ -1,669 +0,0 @@
|
||||
import React from "react";
|
||||
import RSC from "react-scrollbars-custom";
|
||||
import { Document, Page } from "react-pdf";
|
||||
import {
|
||||
defaultWidthHeight,
|
||||
getContainerScale,
|
||||
handleImageResize,
|
||||
handleSignYourselfImageResize
|
||||
} from "../../constant/Utils";
|
||||
import Placeholder from "./Placeholder";
|
||||
import Alert from "../../primitives/Alert";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
function RenderPdf({
|
||||
pageNumber,
|
||||
drop,
|
||||
signerPos,
|
||||
successEmail,
|
||||
handleTabDrag,
|
||||
handleStop,
|
||||
isDragging,
|
||||
setIsSignPad,
|
||||
setIsStamp,
|
||||
handleDeleteSign,
|
||||
setSignKey,
|
||||
pdfDetails,
|
||||
xyPostion,
|
||||
pageDetails,
|
||||
pdfRequest,
|
||||
signerObjectId,
|
||||
signedSigners,
|
||||
pdfLoad,
|
||||
setPdfLoad,
|
||||
placeholder,
|
||||
setSignerPos,
|
||||
setXyPostion,
|
||||
index,
|
||||
containerWH,
|
||||
setIsResize,
|
||||
handleLinkUser,
|
||||
setUniqueId,
|
||||
signersdata,
|
||||
setIsPageCopy,
|
||||
setShowDropdown,
|
||||
setIsInitial,
|
||||
setIsValidate,
|
||||
setWidgetType,
|
||||
setValidateAlert,
|
||||
setIsRadio,
|
||||
setCurrWidgetsDetails,
|
||||
setSelectWidgetId,
|
||||
selectWidgetId,
|
||||
unSignedWidgetId,
|
||||
setIsCheckbox,
|
||||
handleNameModal,
|
||||
handleTextSettingModal,
|
||||
setTempSignerId,
|
||||
uniqueId,
|
||||
pdfOriginalWH,
|
||||
scale,
|
||||
setIsSelectId,
|
||||
ispublicTemplate,
|
||||
handleUserDetails,
|
||||
pdfBase64Url,
|
||||
fontSize,
|
||||
setFontSize,
|
||||
fontColor,
|
||||
setFontColor,
|
||||
isResize
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const isMobile = window.innerWidth < 767;
|
||||
|
||||
//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(
|
||||
pdfOriginalWH,
|
||||
pageNumber,
|
||||
containerWH
|
||||
);
|
||||
const defaultWidth = defaultWidthHeight(pos.type).width;
|
||||
const posWidth = pos.Width ? pos.Width : defaultWidth;
|
||||
if (signYourself) {
|
||||
return posWidth * scale * containerScale;
|
||||
} else {
|
||||
if (pos.isMobile && pos.scale) {
|
||||
if (pos.IsResize) {
|
||||
if (scale > 1) {
|
||||
return posWidth * pos.scale * containerScale * scale;
|
||||
} else {
|
||||
return posWidth * containerScale;
|
||||
}
|
||||
} else {
|
||||
if (scale > 1) {
|
||||
return posWidth * pos.scale * containerScale * scale;
|
||||
} else {
|
||||
return posWidth * pos.scale * containerScale;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return posWidth * scale * containerScale;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const posHeight = (pos, signYourself) => {
|
||||
const containerScale = getContainerScale(
|
||||
pdfOriginalWH,
|
||||
pageNumber,
|
||||
containerWH
|
||||
);
|
||||
const posHeight = pos.Height || defaultWidthHeight(pos.type).height;
|
||||
if (signYourself) {
|
||||
return posHeight * scale * containerScale;
|
||||
} else {
|
||||
if (pos.isMobile && pos.scale) {
|
||||
if (pos.IsResize) {
|
||||
if (scale > 1) {
|
||||
return posHeight * pos.scale * containerScale * scale;
|
||||
} else {
|
||||
return posHeight * containerScale;
|
||||
}
|
||||
} else {
|
||||
if (scale > 1) {
|
||||
return posHeight * pos.scale * containerScale * scale;
|
||||
} else {
|
||||
return posHeight * pos.scale * containerScale;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return posHeight * 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 = signedSigners
|
||||
? signedSigners?.filter(
|
||||
(sign) =>
|
||||
sign?.Id === data?.Id || sign?.objectId === data?.signerObjId
|
||||
)
|
||||
: [];
|
||||
const handleAllUserName = (Id, Role, type) => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className="text-black text-[8px] font-bold">
|
||||
{
|
||||
pdfDetails[0].Signers?.find(
|
||||
(signer) => signer.objectId === data.signerObjId
|
||||
)?.Name
|
||||
}
|
||||
</div>
|
||||
{type && <div className="text-[11px] font-bold">{type}</div>}
|
||||
{Role && (
|
||||
<div className="text-black text-[8px] font-medium">
|
||||
{`(${Role})`}
|
||||
</div>
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
return (
|
||||
checkSign.length === 0 &&
|
||||
data.placeHolder.map((placeData, key) => {
|
||||
return (
|
||||
<React.Fragment key={key}>
|
||||
{placeData.pageNumber === pageNumber &&
|
||||
placeData.pos.map((pos) => {
|
||||
return (
|
||||
pos && (
|
||||
<React.Fragment key={pos.key}>
|
||||
<Placeholder
|
||||
pos={pos}
|
||||
setSignKey={setSignKey}
|
||||
setIsSignPad={setIsSignPad}
|
||||
setIsStamp={setIsStamp}
|
||||
handleSignYourselfImageResize={handleImageResize}
|
||||
index={pageNumber}
|
||||
xyPostion={signerPos}
|
||||
setXyPostion={setSignerPos}
|
||||
data={data}
|
||||
setIsResize={setIsResize}
|
||||
isShowBorder={false}
|
||||
signerObjId={signerObjectId}
|
||||
isShowDropdown={true}
|
||||
isNeedSign={true}
|
||||
isSignYourself={false}
|
||||
posWidth={posWidth}
|
||||
posHeight={posHeight}
|
||||
handleUserName={handleAllUserName}
|
||||
isDragging={false}
|
||||
pdfDetails={pdfDetails}
|
||||
setIsInitial={setIsInitial}
|
||||
setValidateAlert={setValidateAlert}
|
||||
unSignedWidgetId={unSignedWidgetId}
|
||||
setSelectWidgetId={setSelectWidgetId}
|
||||
selectWidgetId={selectWidgetId}
|
||||
setCurrWidgetsDetails={setCurrWidgetsDetails}
|
||||
uniqueId={uniqueId}
|
||||
scale={scale}
|
||||
containerWH={containerWH}
|
||||
pdfOriginalWH={pdfOriginalWH}
|
||||
pageNumber={pageNumber}
|
||||
ispublicTemplate={ispublicTemplate}
|
||||
handleUserDetails={handleUserDetails}
|
||||
isResize={isResize}
|
||||
/>
|
||||
</React.Fragment>
|
||||
)
|
||||
);
|
||||
})}
|
||||
</React.Fragment>
|
||||
);
|
||||
})
|
||||
);
|
||||
};
|
||||
//function for render placeholder block over pdf document
|
||||
|
||||
const handleUserName = (Id, Role, type) => {
|
||||
if (Id) {
|
||||
const checkSign = signersdata.find((sign) => sign.Id === Id);
|
||||
if (checkSign?.Name) {
|
||||
return (
|
||||
<>
|
||||
<div className="text-black text-[8px] font-medium">
|
||||
{checkSign?.Name}
|
||||
</div>
|
||||
{type && <div className="text-[11px] font-bold">{type}</div>}
|
||||
{Role && (
|
||||
<div className="text-black text-[8px] font-medium">
|
||||
{`(${Role})`}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<>
|
||||
{type && <div className="text-[11px] font-bold">{type}</div>}
|
||||
<div className="text-black text-[8px] font-medium">{Role}</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return (
|
||||
<>
|
||||
{type && <div className="text-[11px] font-bold">{type}</div>}
|
||||
<div className="text-black text-[8px] font-medium">{Role}</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
};
|
||||
const pdfDataBase64 = `data:application/pdf;base64,${pdfBase64Url}`;
|
||||
return (
|
||||
<>
|
||||
{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",
|
||||
height: window.innerHeight - 200,
|
||||
zIndex: 0
|
||||
}}
|
||||
className="h-full"
|
||||
noScrollY={false}
|
||||
noScrollX={scale === 1 ? true : false}
|
||||
>
|
||||
<div
|
||||
data-tut="reactourForth"
|
||||
className={`${
|
||||
isGuestSigner ? "30px" : ""
|
||||
} border-[0.1px] border-[#ebe8e8] overflow-x-auto`}
|
||||
style={{
|
||||
width: containerWH?.width && containerWH?.width * scale
|
||||
}}
|
||||
ref={drop}
|
||||
id="container"
|
||||
>
|
||||
{containerWH?.width &&
|
||||
pdfOriginalWH.length > 0 &&
|
||||
(pdfRequest
|
||||
? signerPos.map((data, key) => {
|
||||
return (
|
||||
<React.Fragment key={key}>
|
||||
{checkSignedSigners(data)}
|
||||
</React.Fragment>
|
||||
);
|
||||
})
|
||||
: placeholder // placeholder mobile
|
||||
? signerPos?.map((data, ind) => {
|
||||
return (
|
||||
<React.Fragment key={ind}>
|
||||
{data?.placeHolder &&
|
||||
data?.placeHolder.map((placeData, index) => {
|
||||
return (
|
||||
<React.Fragment key={index}>
|
||||
{placeData.pageNumber === pageNumber &&
|
||||
placeData.pos.map((pos) => {
|
||||
return (
|
||||
<React.Fragment key={pos.key}>
|
||||
<Placeholder
|
||||
pos={pos}
|
||||
setIsPageCopy={setIsPageCopy}
|
||||
setSignKey={setSignKey}
|
||||
handleDeleteSign={handleDeleteSign}
|
||||
setIsStamp={setIsStamp}
|
||||
handleTabDrag={handleTabDrag}
|
||||
handleStop={handleStop}
|
||||
handleSignYourselfImageResize={
|
||||
handleImageResize
|
||||
}
|
||||
index={pageNumber}
|
||||
xyPostion={signerPos}
|
||||
setXyPostion={setSignerPos}
|
||||
data={data}
|
||||
setIsResize={setIsResize}
|
||||
setShowDropdown={setShowDropdown}
|
||||
isShowBorder={true}
|
||||
isPlaceholder={true}
|
||||
setUniqueId={setUniqueId}
|
||||
handleLinkUser={handleLinkUser}
|
||||
handleUserName={handleUserName}
|
||||
isSignYourself={false}
|
||||
posWidth={posWidth}
|
||||
posHeight={posHeight}
|
||||
isDragging={isDragging}
|
||||
setIsValidate={setIsValidate}
|
||||
setWidgetType={setWidgetType}
|
||||
setIsRadio={setIsRadio}
|
||||
setIsCheckbox={setIsCheckbox}
|
||||
setCurrWidgetsDetails={
|
||||
setCurrWidgetsDetails
|
||||
}
|
||||
setSelectWidgetId={
|
||||
setSelectWidgetId
|
||||
}
|
||||
selectWidgetId={selectWidgetId}
|
||||
handleNameModal={handleNameModal}
|
||||
setTempSignerId={setTempSignerId}
|
||||
uniqueId={uniqueId}
|
||||
handleTextSettingModal={
|
||||
handleTextSettingModal
|
||||
}
|
||||
scale={scale}
|
||||
containerWH={containerWH}
|
||||
pdfOriginalWH={pdfOriginalWH}
|
||||
pageNumber={pageNumber}
|
||||
setIsSelectId={setIsSelectId}
|
||||
fontSize={fontSize}
|
||||
setFontSize={setFontSize}
|
||||
fontColor={fontColor}
|
||||
setFontColor={setFontColor}
|
||||
isResize={isResize}
|
||||
unSignedWidgetId={unSignedWidgetId}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</React.Fragment>
|
||||
);
|
||||
})
|
||||
: !pdfDetails?.[0]?.IsCompleted &&
|
||||
xyPostion.map((data, ind) => {
|
||||
return (
|
||||
<React.Fragment key={ind}>
|
||||
{data.pageNumber === pageNumber &&
|
||||
data.pos.map((pos, id) => {
|
||||
return (
|
||||
pos && (
|
||||
<Placeholder
|
||||
key={id}
|
||||
pos={pos}
|
||||
setIsPageCopy={setIsPageCopy}
|
||||
setSignKey={setSignKey}
|
||||
handleDeleteSign={handleDeleteSign}
|
||||
setIsStamp={setIsStamp}
|
||||
handleTabDrag={handleTabDrag}
|
||||
handleStop={handleStop}
|
||||
handleSignYourselfImageResize={
|
||||
handleSignYourselfImageResize
|
||||
}
|
||||
index={index}
|
||||
xyPostion={xyPostion}
|
||||
setXyPostion={setXyPostion}
|
||||
containerWH={containerWH}
|
||||
setIsSignPad={setIsSignPad}
|
||||
isShowBorder={true}
|
||||
isSignYourself={true}
|
||||
posWidth={posWidth}
|
||||
posHeight={posHeight}
|
||||
pdfDetails={pdfDetails[0]}
|
||||
isDragging={isDragging}
|
||||
setIsInitial={setIsInitial}
|
||||
setWidgetType={setWidgetType}
|
||||
setSelectWidgetId={setSelectWidgetId}
|
||||
selectWidgetId={selectWidgetId}
|
||||
handleUserName={handleUserName}
|
||||
setIsCheckbox={setIsCheckbox}
|
||||
setValidateAlert={setValidateAlert}
|
||||
setCurrWidgetsDetails={
|
||||
setCurrWidgetsDetails
|
||||
}
|
||||
handleTextSettingModal={
|
||||
handleTextSettingModal
|
||||
}
|
||||
scale={scale}
|
||||
pdfOriginalWH={pdfOriginalWH}
|
||||
pageNumber={pageNumber}
|
||||
fontSize={fontSize}
|
||||
setFontSize={setFontSize}
|
||||
fontColor={fontColor}
|
||||
setFontColor={setFontColor}
|
||||
isResize={isResize}
|
||||
setIsResize={setIsResize}
|
||||
/>
|
||||
)
|
||||
);
|
||||
})}
|
||||
</React.Fragment>
|
||||
);
|
||||
}))}
|
||||
|
||||
<Document
|
||||
onLoadError={() => setPdfLoad(false)}
|
||||
loading={t("loading-doc")}
|
||||
onLoadSuccess={pageDetails}
|
||||
// ref={pdfRef}'
|
||||
onClick={() => {
|
||||
if (setSelectWidgetId) {
|
||||
setSelectWidgetId("");
|
||||
}
|
||||
}}
|
||||
file={
|
||||
(pdfBase64Url && pdfDataBase64) ||
|
||||
pdfDetails[0]?.SignedUrl ||
|
||||
pdfDetails[0].URL
|
||||
}
|
||||
>
|
||||
<Page
|
||||
scale={scale || 1}
|
||||
key={index}
|
||||
pageNumber={pageNumber}
|
||||
width={containerWH.width}
|
||||
renderAnnotationLayer={false}
|
||||
renderTextLayer={false}
|
||||
onGetAnnotationsError={(error) => {
|
||||
console.log("annotation error", error);
|
||||
}}
|
||||
/>
|
||||
</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={scale === 1 ? true : false}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
width: containerWH?.width && containerWH?.width * scale
|
||||
}}
|
||||
ref={drop}
|
||||
id="container"
|
||||
>
|
||||
{pdfLoad &&
|
||||
containerWH?.width &&
|
||||
pdfOriginalWH.length > 0 &&
|
||||
(pdfRequest //pdf request sign flow
|
||||
? signerPos?.map((data, key) => {
|
||||
return (
|
||||
<React.Fragment key={key}>
|
||||
{checkSignedSigners(data)}
|
||||
</React.Fragment>
|
||||
);
|
||||
})
|
||||
: placeholder //placeholder and template flow
|
||||
? signerPos.map((data, ind) => {
|
||||
return (
|
||||
<React.Fragment key={ind}>
|
||||
{data?.placeHolder &&
|
||||
data?.placeHolder.map((placeData, index) => {
|
||||
return (
|
||||
<React.Fragment key={index}>
|
||||
{placeData.pageNumber === pageNumber &&
|
||||
placeData.pos.map((pos) => {
|
||||
return (
|
||||
<React.Fragment key={pos.key}>
|
||||
<Placeholder
|
||||
pos={pos}
|
||||
setIsPageCopy={setIsPageCopy}
|
||||
setSignKey={setSignKey}
|
||||
handleDeleteSign={handleDeleteSign}
|
||||
setIsStamp={setIsStamp}
|
||||
handleTabDrag={handleTabDrag}
|
||||
handleStop={handleStop}
|
||||
handleSignYourselfImageResize={
|
||||
handleImageResize
|
||||
}
|
||||
index={pageNumber}
|
||||
xyPostion={signerPos}
|
||||
setXyPostion={setSignerPos}
|
||||
data={data}
|
||||
setIsResize={setIsResize}
|
||||
setShowDropdown={setShowDropdown}
|
||||
isShowBorder={true}
|
||||
isPlaceholder={true}
|
||||
setUniqueId={setUniqueId}
|
||||
handleLinkUser={handleLinkUser}
|
||||
handleUserName={handleUserName}
|
||||
isSignYourself={false}
|
||||
posWidth={posWidth}
|
||||
posHeight={posHeight}
|
||||
isDragging={isDragging}
|
||||
setIsValidate={setIsValidate}
|
||||
setWidgetType={setWidgetType}
|
||||
setIsRadio={setIsRadio}
|
||||
setIsCheckbox={setIsCheckbox}
|
||||
setCurrWidgetsDetails={
|
||||
setCurrWidgetsDetails
|
||||
}
|
||||
setSelectWidgetId={
|
||||
setSelectWidgetId
|
||||
}
|
||||
selectWidgetId={selectWidgetId}
|
||||
handleNameModal={handleNameModal}
|
||||
setTempSignerId={setTempSignerId}
|
||||
uniqueId={uniqueId}
|
||||
handleTextSettingModal={
|
||||
handleTextSettingModal
|
||||
}
|
||||
scale={scale}
|
||||
containerWH={containerWH}
|
||||
pdfOriginalWH={pdfOriginalWH}
|
||||
pageNumber={pageNumber}
|
||||
setIsSelectId={setIsSelectId}
|
||||
fontSize={fontSize}
|
||||
setFontSize={setFontSize}
|
||||
fontColor={fontColor}
|
||||
setFontColor={setFontColor}
|
||||
isResize={isResize}
|
||||
unSignedWidgetId={unSignedWidgetId}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</React.Fragment>
|
||||
);
|
||||
})
|
||||
: !pdfDetails?.[0]?.IsCompleted &&
|
||||
xyPostion?.map((data, ind) => {
|
||||
// signyourself flow
|
||||
return (
|
||||
<React.Fragment key={ind}>
|
||||
{data.pageNumber === pageNumber &&
|
||||
data.pos.map((pos) => {
|
||||
return (
|
||||
<React.Fragment key={pos.key}>
|
||||
<Placeholder
|
||||
pos={pos}
|
||||
setIsPageCopy={setIsPageCopy}
|
||||
setSignKey={setSignKey}
|
||||
handleDeleteSign={handleDeleteSign}
|
||||
setIsStamp={setIsStamp}
|
||||
handleTabDrag={handleTabDrag}
|
||||
handleStop={(event, dragElement) =>
|
||||
handleStop(event, dragElement, pos.type)
|
||||
}
|
||||
handleSignYourselfImageResize={
|
||||
handleSignYourselfImageResize
|
||||
}
|
||||
index={index}
|
||||
xyPostion={xyPostion}
|
||||
setXyPostion={setXyPostion}
|
||||
setIsSignPad={setIsSignPad}
|
||||
isShowBorder={true}
|
||||
isSignYourself={true}
|
||||
posWidth={posWidth}
|
||||
posHeight={posHeight}
|
||||
pdfDetails={pdfDetails[0]}
|
||||
isDragging={isDragging}
|
||||
setIsInitial={setIsInitial}
|
||||
setWidgetType={setWidgetType}
|
||||
setSelectWidgetId={setSelectWidgetId}
|
||||
selectWidgetId={selectWidgetId}
|
||||
handleUserName={handleUserName}
|
||||
setIsCheckbox={setIsCheckbox}
|
||||
setValidateAlert={setValidateAlert}
|
||||
setCurrWidgetsDetails={
|
||||
setCurrWidgetsDetails
|
||||
}
|
||||
handleTextSettingModal={
|
||||
handleTextSettingModal
|
||||
}
|
||||
scale={scale}
|
||||
containerWH={containerWH}
|
||||
pdfOriginalWH={pdfOriginalWH}
|
||||
pageNumber={pageNumber}
|
||||
fontSize={fontSize}
|
||||
setFontSize={setFontSize}
|
||||
fontColor={fontColor}
|
||||
setFontColor={setFontColor}
|
||||
isResize={isResize}
|
||||
setIsResize={setIsResize}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</React.Fragment>
|
||||
);
|
||||
}))}
|
||||
|
||||
{/* this component for render pdf document is in middle of the component */}
|
||||
<Document
|
||||
onLoadError={() => setPdfLoad(false)}
|
||||
loading={t("loading-doc")}
|
||||
onLoadSuccess={pageDetails}
|
||||
onClick={() => {
|
||||
if (setSelectWidgetId) {
|
||||
setSelectWidgetId("");
|
||||
}
|
||||
}}
|
||||
file={
|
||||
(pdfBase64Url && pdfDataBase64) ||
|
||||
pdfDetails[0]?.SignedUrl ||
|
||||
pdfDetails[0].URL
|
||||
}
|
||||
>
|
||||
<Page
|
||||
key={index}
|
||||
width={containerWH.width}
|
||||
scale={scale || 1}
|
||||
className={"-z-[1]"} // when user zoom-in in tablet widgets move backward that's why pass -z-[1]
|
||||
pageNumber={pageNumber}
|
||||
renderAnnotationLayer={false}
|
||||
renderTextLayer={false}
|
||||
onGetAnnotationsError={(error) => {
|
||||
console.log("annotation error", error);
|
||||
}}
|
||||
/>
|
||||
</Document>
|
||||
</div>
|
||||
</RSC>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default RenderPdf;
|
||||
@@ -0,0 +1,398 @@
|
||||
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 (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}
|
||||
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}
|
||||
setIsAgreeTour={props.setIsAgreeTour}
|
||||
isAgree={props.isAgree}
|
||||
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`
|
||||
: ""
|
||||
}
|
||||
style={{
|
||||
width:
|
||||
props.containerWH?.width && props.containerWH?.width * props.scale
|
||||
}}
|
||||
ref={props.drop}
|
||||
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}
|
||||
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}
|
||||
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>
|
||||
</div>
|
||||
</RSC>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default RenderPdf;
|
||||
@@ -5,9 +5,12 @@ import { useTranslation } from "react-i18next";
|
||||
function SelectLanguage(props) {
|
||||
const { i18n } = useTranslation();
|
||||
const languages = [
|
||||
{ value: "en", text: "English" },
|
||||
{ value: "es", text: "Española" },
|
||||
{ value: "fr", text: "Français" }
|
||||
{ value: "en", text: "English" }, //english
|
||||
{ value: "es", text: "Española" }, //spanish
|
||||
{ value: "fr", text: "Français" }, //french
|
||||
{ value: "it", text: "Italiano" }, //italian
|
||||
{ value: "de", text: "Deutsch" }, //german
|
||||
{ value: "hi", text: "हिन्दी" } //hindi
|
||||
];
|
||||
const defaultLanguage = i18next.language || "en";
|
||||
const [lang, setLang] = useState(defaultLanguage);
|
||||
@@ -21,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) => {
|
||||
@@ -1,680 +0,0 @@
|
||||
import React, { useRef, useState, useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import SignatureCanvas from "react-signature-canvas";
|
||||
|
||||
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 [signValue, setSignValue] = useState("");
|
||||
const [textWidth, setTextWidth] = useState(0);
|
||||
const [textHeight, setTextHeight] = useState(0);
|
||||
const [signatureType, setSignatureType] = useState("");
|
||||
const [isSignTypes, setIsSignTypes] = useState(true);
|
||||
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 senderUser =
|
||||
localStorage.getItem(
|
||||
`Parse/${localStorage.getItem("parseAppId")}/currentUser`
|
||||
) &&
|
||||
localStorage.getItem(
|
||||
`Parse/${localStorage.getItem("parseAppId")}/currentUser`
|
||||
);
|
||||
const jsonSender = 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 index = signtypes?.findIndex((x) => x.enabled === true);
|
||||
if (index !== -1) {
|
||||
setIsSignTypes(true);
|
||||
const tab = props?.signatureTypes[index].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") {
|
||||
setIsTab("mysignature");
|
||||
} 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 = () => {
|
||||
props?.setCurrWidgetsDetails({});
|
||||
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);
|
||||
};
|
||||
//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={() => {
|
||||
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
|
||||
);
|
||||
} else {
|
||||
setIsSignImg("");
|
||||
canvasRef.current.clear();
|
||||
props?.onSaveSign(signatureType);
|
||||
}
|
||||
}
|
||||
setPenColor("blue");
|
||||
} else {
|
||||
setIsSignImg("");
|
||||
props?.onSaveImage(signatureType);
|
||||
}
|
||||
props?.setIsSignPad(false);
|
||||
props?.setIsInitial(false);
|
||||
props?.setIsImageSelect(false);
|
||||
setIsDefaultSign(false);
|
||||
props?.setImage();
|
||||
handleTab();
|
||||
setSignValue("");
|
||||
props?.setIsStamp(false);
|
||||
}}
|
||||
type="button"
|
||||
className={`${
|
||||
isSignImg || props?.image || isDefaultSign || textWidth
|
||||
? ""
|
||||
: "pointer-events-none"
|
||||
} op-btn op-btn-primary shadow-lg`}
|
||||
disabled={
|
||||
(isTab === "draw" && isSignImg) ||
|
||||
(isTab === "image" && props?.image) ||
|
||||
(isTab === "mysignature" && isDefaultSign) ||
|
||||
(isTab === "type" && signValue)
|
||||
? false
|
||||
: props?.image
|
||||
? false
|
||||
: true
|
||||
}
|
||||
>
|
||||
{t("save")}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
//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) {
|
||||
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;
|
||||
setSignValue(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 = signValue
|
||||
? signValue?.trim()
|
||||
: currentUserName?.trim();
|
||||
const firstCharacter = trimmedName?.charAt(0);
|
||||
const userName = props?.isInitial ? firstCharacter : signValue;
|
||||
setSignValue(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-start">
|
||||
{allColor.map((data, key) => {
|
||||
return (
|
||||
<i
|
||||
style={{
|
||||
margin: "5px",
|
||||
color: data,
|
||||
borderBottom:
|
||||
key === 0 && penColor === "blue"
|
||||
? "2px solid blue"
|
||||
: key === 1 && penColor === "red"
|
||||
? "2px solid red"
|
||||
: key === 2 && penColor === "black"
|
||||
? "2px solid black"
|
||||
: "2px solid white"
|
||||
}}
|
||||
onClick={() => {
|
||||
props?.convertToImg &&
|
||||
props?.convertToImg(fontSelect, signValue, data);
|
||||
if (key === 0) {
|
||||
setPenColor("blue");
|
||||
} else if (key === 1) {
|
||||
setPenColor("red");
|
||||
} else if (key === 2) {
|
||||
setPenColor("black");
|
||||
}
|
||||
}}
|
||||
key={key}
|
||||
className="fa-light fa-pen-nib"
|
||||
width={20}
|
||||
height={20}
|
||||
></i>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// `handleCancelBtn` function trigger when user click on cross button
|
||||
const handleCancelBtn = () => {
|
||||
setPenColor("blue");
|
||||
props?.setIsSignPad(false);
|
||||
props?.setIsInitial(false);
|
||||
props?.setIsImageSelect(false);
|
||||
setIsDefaultSign(false);
|
||||
props?.setImage();
|
||||
handleTab();
|
||||
setSignValue("");
|
||||
props?.setIsStamp(false);
|
||||
};
|
||||
return (
|
||||
<div>
|
||||
{props?.isSignPad && (
|
||||
<div className="op-modal op-modal-open">
|
||||
<div className="op-modal-box px-[13px] pt-2 pb-0">
|
||||
{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>
|
||||
) : (
|
||||
<>
|
||||
{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>
|
||||
)}
|
||||
{!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>
|
||||
)
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="text-[1.5rem] cursor-pointer"
|
||||
onClick={handleCancelBtn}
|
||||
>
|
||||
×
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-[20px] h-full">
|
||||
{isDefaultSign ? (
|
||||
<>
|
||||
<div className="flex justify-center">
|
||||
<div
|
||||
style={{
|
||||
border: "1.3px solid #007bff",
|
||||
borderRadius: "2px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
marginBottom: 6,
|
||||
cursor: "pointer"
|
||||
}}
|
||||
className={
|
||||
props?.isInitial
|
||||
? "intialSignatureCanvas"
|
||||
: "signatureCanvas"
|
||||
}
|
||||
>
|
||||
<img
|
||||
alt="stamp img"
|
||||
className="w-full h-full object-contain bg-white"
|
||||
draggable="false"
|
||||
src={
|
||||
props?.isInitial
|
||||
? props?.myInitial
|
||||
: props?.defaultSign
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-end">
|
||||
<SaveBtn />
|
||||
</div>
|
||||
</>
|
||||
) : props?.isImageSelect || props?.isStamp ? (
|
||||
!props?.image ? (
|
||||
<div className="flex justify-center">
|
||||
<div
|
||||
style={{
|
||||
border: "1.3px solid #007bff",
|
||||
borderRadius: "2px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
marginBottom: 6,
|
||||
cursor: "pointer"
|
||||
}}
|
||||
className={
|
||||
props?.isInitial
|
||||
? "intialSignatureCanvas"
|
||||
: "signatureCanvas"
|
||||
}
|
||||
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
|
||||
style={{
|
||||
border: "1.3px solid #007bff",
|
||||
borderRadius: "2px",
|
||||
marginBottom: 6,
|
||||
overflow: "hidden"
|
||||
}}
|
||||
className={
|
||||
props?.isInitial
|
||||
? "intialSignatureCanvas"
|
||||
: "signatureCanvas"
|
||||
}
|
||||
>
|
||||
<img
|
||||
alt="print img"
|
||||
ref={props?.imageRef}
|
||||
src={props?.image.src}
|
||||
draggable="false"
|
||||
className=" object-contain h-full w-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<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="Your signature"
|
||||
value={signValue}
|
||||
onChange={(e) => {
|
||||
setSignValue(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, signValue);
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
padding: "5px 10px 5px 10px",
|
||||
fontSize: "20px",
|
||||
color: penColor
|
||||
}}
|
||||
>
|
||||
{signValue ? signValue : "Your signature"}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row justify-between mt-[10px]">
|
||||
<PenColorComponent convertToImg={convertToImg} />
|
||||
<SaveBtn />
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="flex justify-center">
|
||||
<SignatureCanvas
|
||||
ref={canvasRef}
|
||||
penColor={penColor}
|
||||
canvasProps={{
|
||||
className: props?.isInitial
|
||||
? "intialSignatureCanvas"
|
||||
: "signatureCanvas",
|
||||
style: {
|
||||
border: "1.6px solid #007bff",
|
||||
borderRadius: "2px"
|
||||
}
|
||||
}}
|
||||
// backgroundColor="rgb(255, 255, 255)"
|
||||
onEnd={() =>
|
||||
handleSignatureChange(
|
||||
canvasRef.current?.toDataURL()
|
||||
)
|
||||
}
|
||||
dotSize={1}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-row justify-between mt-[10px]">
|
||||
<PenColorComponent />
|
||||
<SaveBtn />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="">
|
||||
<div className="relative flex flex-row items-center justify-between">
|
||||
<div className="text-base-content font-bold text-lg">
|
||||
Signature
|
||||
</div>
|
||||
<div
|
||||
className="text-[1.5rem] cursor-pointer"
|
||||
onClick={handleCancelBtn}
|
||||
>
|
||||
×
|
||||
</div>
|
||||
</div>
|
||||
<div className="mx-3 mb-6 mt-3">
|
||||
<p>{t("at-least-one-signature-type")}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default SignPad;
|
||||
@@ -1,31 +1,41 @@
|
||||
import React from "react";
|
||||
import "../../styles/signature.css";
|
||||
import { useTranslation } from "react-i18next";
|
||||
function Signedby({ pdfDetails }) {
|
||||
function Signedby(props) {
|
||||
const { t } = useTranslation();
|
||||
const getFirstLetter = (name) => {
|
||||
const getFirstLetter = (pdfData) => {
|
||||
const name = props.isSelfSign
|
||||
? (pdfData?.Signers && pdfData?.Signers[0]?.Name) || "User"
|
||||
: pdfData.ExtUserPtr?.Name;
|
||||
const firstLetter = name.charAt(0);
|
||||
return firstLetter;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="hidden md:block w-full h-full bg-base-100">
|
||||
<div className="mx-2 pr-2 pt-2 pb-1 text-[15px] text-base-content font-semibold border-b-[1px] border-base-300">
|
||||
{t("signed-by")}
|
||||
{props.isSelfSign ? t("user") : t("signed-by")}
|
||||
</div>
|
||||
<div className="mt-[2px] bg-base-100">
|
||||
<div className="bg-[#93a3db] rounded-xl mx-1 flex flex-row items-center py-[10px]">
|
||||
<div className="bg-[#576081] flex w-[30px] h-[30px] rounded-full justify-center items-center mx-1">
|
||||
<span className="text-[12px] text-center font-bold text-white uppercase">
|
||||
{getFirstLetter(pdfDetails.ExtUserPtr.Name)}
|
||||
{getFirstLetter(props.pdfDetails)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<span className="text-[12px] font-bold text-[#424242] w-[100px] whitespace-nowrap overflow-hidden text-ellipsis">
|
||||
{pdfDetails.ExtUserPtr.Name}
|
||||
{props.isSelfSign
|
||||
? (props.pdfDetails?.Signers &&
|
||||
props.pdfDetails?.Signers[0]?.Name) ||
|
||||
"User"
|
||||
: props.pdfDetails.ExtUserPtr.Name || "User"}
|
||||
</span>
|
||||
<span className="text-[10px] font-medium text-[#424242] w-[100px] whitespace-nowrap overflow-hidden text-ellipsis">
|
||||
{pdfDetails.ExtUserPtr.Email}
|
||||
{props.isSelfSign
|
||||
? (props.pdfDetails?.Signers &&
|
||||
props.pdfDetails?.Signers[0]?.Email) ||
|
||||
""
|
||||
: props.pdfDetails.ExtUserPtr.Email || ""}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -19,7 +19,7 @@ function SignerListPlace(props) {
|
||||
</sup>
|
||||
</a>
|
||||
<Tooltip id="my-tooltip" className="z-[100]">
|
||||
<div className="max-w-[450px] 2xl:max-w-[500px] 2xl:text-[20px] p-[1px]">
|
||||
<div className="max-w-[450px] 2xl:max-w-[500px] p-[1px]">
|
||||
<p className="font-bold pb-[1px]">{t("role-help.p1")}</p>
|
||||
<p>{t("role-help.p2")} </p>
|
||||
<p className="font-bold">{t("role-help.p3")}</p>
|
||||
@@ -1,81 +0,0 @@
|
||||
import React from "react";
|
||||
import ModalUi from "../../primitives/ModalUi";
|
||||
import { fontColorArr, fontsizeArr } from "../../constant/Utils";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
function TextFontSetting(props) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<ModalUi
|
||||
headerColor={"#dc3545"}
|
||||
isOpen={props.isTextSetting}
|
||||
reduceWidth={"max-w-[350px]"}
|
||||
title={t("text-field")}
|
||||
handleClose={() => props.setIsTextSetting(false)}
|
||||
>
|
||||
<div className="h-full p-[20px]">
|
||||
<div className="flex items-center">
|
||||
<span>{t("font-size")} :</span>
|
||||
<select
|
||||
className="ml-[7px] op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs"
|
||||
value={
|
||||
props.fontSize ||
|
||||
props.currWidgetsDetails?.options?.fontSize ||
|
||||
12
|
||||
}
|
||||
onChange={(e) => {
|
||||
props.setFontSize(parseInt(e.target.value));
|
||||
}}
|
||||
>
|
||||
{fontsizeArr.map((size, ind) => {
|
||||
return (
|
||||
<option className="text-[13px]" value={size} key={ind}>
|
||||
{size}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</select>
|
||||
<div className="flex flex-row gap-1 items-center ml-4">
|
||||
<span>{t("color")}: </span>
|
||||
<select
|
||||
value={
|
||||
props.fontColor ||
|
||||
props.currWidgetsDetails?.options?.fontColor ||
|
||||
"black"
|
||||
}
|
||||
onChange={(e) => props.setFontColor(e.target.value)}
|
||||
className="ml-[7px] op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs"
|
||||
>
|
||||
{fontColorArr.map((color, ind) => {
|
||||
return (
|
||||
<option value={color} key={ind}>
|
||||
{t(`color-type.${color}`)}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</select>
|
||||
<span
|
||||
style={{
|
||||
background:
|
||||
props.fontColor ||
|
||||
props.currWidgetsDetails?.options?.fontColor
|
||||
}}
|
||||
className="w-5 h-[19px] ml-1"
|
||||
></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="h-[1px] bg-[#9f9f9f] w-full mt-[15px] mb-2"></div>
|
||||
<button
|
||||
onClick={() => props.handleSaveFontSize()}
|
||||
type="button"
|
||||
className="op-btn op-btn-primary mt-2"
|
||||
>
|
||||
{t("save")}
|
||||
</button>
|
||||
</div>
|
||||
</ModalUi>
|
||||
);
|
||||
}
|
||||
|
||||
export default TextFontSetting;
|
||||
@@ -0,0 +1,81 @@
|
||||
import React from "react";
|
||||
import ModalUi from "../../primitives/ModalUi";
|
||||
import { fontColorArr, fontsizeArr } from "../../constant/Utils";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
function TextFontSetting(props) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<ModalUi
|
||||
headerColor={"#dc3545"}
|
||||
isOpen={props.isTextSetting}
|
||||
reduceWidth={"max-w-[350px]"}
|
||||
title={t("text-field")}
|
||||
handleClose={() => props.setIsTextSetting(false)}
|
||||
>
|
||||
<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">
|
||||
<span className="whitespace-nowrap">{t("font-size")}:</span>
|
||||
<select
|
||||
className="ml-[7px] w-[60%] op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs"
|
||||
value={
|
||||
props.fontSize ||
|
||||
props.currWidgetsDetails?.options?.fontSize ||
|
||||
12
|
||||
}
|
||||
onChange={(e) => props.setFontSize(parseInt(e.target.value))}
|
||||
>
|
||||
{fontsizeArr.map((size, ind) => (
|
||||
<option key={ind} className="text-[13px]" value={size}>
|
||||
{size}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
{/* Font Color Selector */}
|
||||
<div className="flex items-center">
|
||||
<span className="whitespace-nowrap">{t("color")}:</span>
|
||||
<select
|
||||
className="ml-[33px] md:ml-4 w-[65%] md:w-[full] op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs"
|
||||
value={
|
||||
props.fontColor ||
|
||||
props.currWidgetsDetails?.options?.fontColor ||
|
||||
"black"
|
||||
}
|
||||
onChange={(e) => props.setFontColor(e.target.value)}
|
||||
>
|
||||
{fontColorArr.map((color, ind) => (
|
||||
<option key={ind} value={color}>
|
||||
{t(`color-type.${color}`)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
{/* Color Preview Box */}
|
||||
<span
|
||||
className="w-5 h-5 ml-2 rounded border border-gray-300"
|
||||
style={{
|
||||
backgroundColor:
|
||||
props.fontColor ||
|
||||
props.currWidgetsDetails?.options?.fontColor ||
|
||||
"black"
|
||||
}}
|
||||
></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="h-[1px] bg-[#9f9f9f] w-full mt-[15px] mb-2"></div>
|
||||
<button
|
||||
onClick={() => props.handleSaveFontSize()}
|
||||
type="button"
|
||||
className="op-btn op-btn-primary mt-2"
|
||||
>
|
||||
{t("save")}
|
||||
</button>
|
||||
</div>
|
||||
</ModalUi>
|
||||
);
|
||||
}
|
||||
|
||||
export default TextFontSetting;
|
||||
@@ -5,107 +5,79 @@ import { useDrag } from "react-dnd";
|
||||
import WidgetList from "./WidgetList";
|
||||
import {
|
||||
color,
|
||||
isMobile,
|
||||
radioButtonWidget,
|
||||
textInputWidget,
|
||||
cellsWidget,
|
||||
textWidget,
|
||||
widgets
|
||||
} from "../../constant/Utils";
|
||||
import { useTranslation } from "react-i18next";
|
||||
function WidgetComponent({
|
||||
dragSignature,
|
||||
signRef,
|
||||
handleDivClick,
|
||||
handleMouseLeave,
|
||||
dragStamp,
|
||||
isSignYourself,
|
||||
addPositionOfSignature,
|
||||
signersdata,
|
||||
isSelectListId,
|
||||
setIsSelectId,
|
||||
isSigners,
|
||||
dataTut,
|
||||
isMailSend,
|
||||
handleAddSigner,
|
||||
setUniqueId,
|
||||
setRoleName,
|
||||
handleDeleteUser,
|
||||
signerPos,
|
||||
handleRoleChange,
|
||||
handleOnBlur,
|
||||
title,
|
||||
setSignersData,
|
||||
sendInOrder,
|
||||
isTemplateFlow,
|
||||
setBlockColor,
|
||||
setIsAddSigner,
|
||||
uniqueId
|
||||
}) {
|
||||
function WidgetComponent(props) {
|
||||
const { t } = useTranslation();
|
||||
const signRef = useRef(null);
|
||||
const userInformation = localStorage.getItem("UserInformation");
|
||||
const [isSignersModal, setIsSignersModal] = useState(false);
|
||||
const [, signature] = useDrag({
|
||||
type: "BOX",
|
||||
item: { type: "BOX", id: 1, text: "signature" }
|
||||
});
|
||||
const [, stamp] = useDrag({
|
||||
type: "BOX",
|
||||
item: { type: "BOX", id: 2, text: "stamp" }
|
||||
});
|
||||
const [, dropdown] = useDrag({
|
||||
type: "BOX",
|
||||
item: { type: "BOX", id: 5, text: "dropdown" },
|
||||
collect: (monitor) => ({
|
||||
isDragDropdown: !!monitor.isDragging()
|
||||
})
|
||||
item: { type: "BOX", id: 5, text: "dropdown" }
|
||||
});
|
||||
const [, checkbox] = useDrag({
|
||||
type: "BOX",
|
||||
item: { type: "BOX", id: 6, text: "checkbox" },
|
||||
collect: (monitor) => ({ isDragCheck: !!monitor.isDragging() })
|
||||
item: { type: "BOX", id: 6, text: "checkbox" }
|
||||
});
|
||||
const [, textInput] = useDrag({
|
||||
type: "BOX",
|
||||
item: { type: "BOX", id: 7, text: textInputWidget },
|
||||
collect: (monitor) => ({ isDragTextInput: !!monitor.isDragging() })
|
||||
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" },
|
||||
collect: (monitor) => ({ isDragInitial: !!monitor.isDragging() })
|
||||
item: { type: "BOX", id: 8, text: "initials" }
|
||||
});
|
||||
const [, name] = useDrag({
|
||||
type: "BOX",
|
||||
item: { type: "BOX", id: 9, text: "name" },
|
||||
collect: (monitor) => ({ isDragName: !!monitor.isDragging() })
|
||||
item: { type: "BOX", id: 9, text: "name" }
|
||||
});
|
||||
const [, company] = useDrag({
|
||||
type: "BOX",
|
||||
item: { type: "BOX", id: 10, text: "company" },
|
||||
collect: (monitor) => ({ isDragCompany: !!monitor.isDragging() })
|
||||
item: { type: "BOX", id: 10, text: "company" }
|
||||
});
|
||||
const [, jobTitle] = useDrag({
|
||||
type: "BOX",
|
||||
item: { type: "BOX", id: 11, text: "job title" },
|
||||
collect: (monitor) => ({ isDragJobtitle: !!monitor.isDragging() })
|
||||
item: { type: "BOX", id: 11, text: "job title" }
|
||||
});
|
||||
const [, date] = useDrag({
|
||||
type: "BOX",
|
||||
item: { type: "BOX", id: 12, text: "date" },
|
||||
collect: (monitor) => ({ isDragDate: !!monitor.isDragging() })
|
||||
item: { type: "BOX", id: 12, text: "date" }
|
||||
});
|
||||
const [, image] = useDrag({
|
||||
type: "BOX",
|
||||
item: { type: "BOX", id: 13, text: "image" },
|
||||
collect: (monitor) => ({ isDragImage: !!monitor.isDragging() })
|
||||
item: { type: "BOX", id: 13, text: "image" }
|
||||
});
|
||||
const [, email] = useDrag({
|
||||
type: "BOX",
|
||||
item: { type: "BOX", id: 14, text: "email" },
|
||||
collect: (monitor) => ({ isDragEmail: !!monitor.isDragging() })
|
||||
item: { type: "BOX", id: 14, text: "email" }
|
||||
});
|
||||
const [, radioButton] = useDrag({
|
||||
type: "BOX",
|
||||
item: { type: "BOX", id: 15, text: radioButtonWidget },
|
||||
collect: (monitor) => ({ isDragRadiotton: !!monitor.isDragging() })
|
||||
item: { type: "BOX", id: 15, text: radioButtonWidget }
|
||||
});
|
||||
const [, text] = useDrag({
|
||||
type: "BOX",
|
||||
item: { type: "BOX", id: 16, text: textWidget },
|
||||
collect: (monitor) => ({ isDragText: !!monitor.isDragging() })
|
||||
item: { type: "BOX", id: 16, text: textWidget }
|
||||
});
|
||||
const isMobile = window.innerWidth < 767;
|
||||
const scrollContainerRef = useRef(null);
|
||||
const [widget, setWidget] = useState([]);
|
||||
const handleModal = () => {
|
||||
setIsSignersModal(!isSignersModal);
|
||||
@@ -113,8 +85,8 @@ function WidgetComponent({
|
||||
|
||||
useEffect(() => {
|
||||
const widgetRef = [
|
||||
dragSignature,
|
||||
dragStamp,
|
||||
signature,
|
||||
stamp,
|
||||
initials,
|
||||
name,
|
||||
jobTitle,
|
||||
@@ -122,6 +94,7 @@ function WidgetComponent({
|
||||
date,
|
||||
text,
|
||||
textInput,
|
||||
cells,
|
||||
checkbox,
|
||||
dropdown,
|
||||
radioButton,
|
||||
@@ -137,37 +110,69 @@ function WidgetComponent({
|
||||
// eslint-disable-next-line
|
||||
}, []);
|
||||
|
||||
const modifiedWidgets = widget.filter(
|
||||
(data) =>
|
||||
![
|
||||
"dropdown",
|
||||
radioButtonWidget,
|
||||
textInputWidget,
|
||||
"date",
|
||||
"image",
|
||||
"checkbox"
|
||||
].includes(data.type)
|
||||
);
|
||||
const unlogedInUserWidgets = widget.filter(
|
||||
(data) =>
|
||||
![
|
||||
"dropdown",
|
||||
radioButtonWidget,
|
||||
textInputWidget,
|
||||
"date",
|
||||
"image",
|
||||
"checkbox",
|
||||
"name",
|
||||
"email",
|
||||
"job title",
|
||||
"company"
|
||||
].includes(data.type)
|
||||
);
|
||||
const filterWidgets = widget.filter(
|
||||
(data) =>
|
||||
data.type !== "dropdown" &&
|
||||
data.type !== radioButtonWidget &&
|
||||
data.type !== textInputWidget
|
||||
!["dropdown", radioButtonWidget, textInputWidget].includes(
|
||||
data.type
|
||||
)
|
||||
);
|
||||
const textWidgetData = widget.filter((data) => data.type !== textWidget);
|
||||
const updateWidgets = isSignYourself
|
||||
const updateWidgets = props.isSignYourself
|
||||
? filterWidgets
|
||||
: isTemplateFlow
|
||||
: props.isTemplateFlow
|
||||
? textWidgetData
|
||||
: widget;
|
||||
: props.isAlllowModify
|
||||
? userInformation
|
||||
? modifiedWidgets
|
||||
: unlogedInUserWidgets
|
||||
: widget;
|
||||
|
||||
const handleSelectRecipient = () => {
|
||||
if (
|
||||
signersdata[isSelectListId]?.Email ||
|
||||
signersdata[isSelectListId]?.Role
|
||||
props.signersdata[props.isSelectListId]?.Email ||
|
||||
props.signersdata[props.isSelectListId]?.Role
|
||||
) {
|
||||
const userData =
|
||||
signersdata[isSelectListId]?.Name || signersdata[isSelectListId]?.Role;
|
||||
props.signersdata[props.isSelectListId]?.Name ||
|
||||
props.signersdata[props.isSelectListId]?.Role;
|
||||
const name =
|
||||
userData?.length > 20 ? `${userData.slice(0, 20)}...` : userData;
|
||||
return name;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{isMobile ? (
|
||||
!isMailSend && (
|
||||
!props.isMailSend && (
|
||||
<div id="navbar" className="fixed z-[99] bottom-0 right-0 w-full">
|
||||
{isSigners && (
|
||||
{props.isSigners && (
|
||||
<div className="w-full mb-[5px] flex justify-center items-center gap-1">
|
||||
<div className="w-full ml-[5px]" onClick={() => handleModal()}>
|
||||
<select
|
||||
@@ -175,8 +180,8 @@ function WidgetComponent({
|
||||
className="w-full op-select op-select-bordered pointer-events-none"
|
||||
value={handleSelectRecipient()}
|
||||
style={{
|
||||
backgroundColor: isSelectListId
|
||||
? color[isSelectListId % color.length]
|
||||
backgroundColor: props.blockColor
|
||||
? props.blockColor
|
||||
: color[0]
|
||||
}}
|
||||
>
|
||||
@@ -187,19 +192,19 @@ function WidgetComponent({
|
||||
</div>
|
||||
|
||||
<div className="w-[18%]">
|
||||
{handleAddSigner ? (
|
||||
{props.handleAddSigner ? (
|
||||
<button
|
||||
data-tut="reactourAddbtn"
|
||||
onClick={() => handleAddSigner()}
|
||||
onClick={() => props.handleAddSigner()}
|
||||
className="op-btn op-btn-accent"
|
||||
>
|
||||
<i className="fa-light fa-plus "></i>
|
||||
</button>
|
||||
) : (
|
||||
setIsAddSigner && (
|
||||
props.setIsAddSigner && (
|
||||
<button
|
||||
data-tut="addRecipient"
|
||||
onClick={() => setIsAddSigner(true)}
|
||||
onClick={() => props.setIsAddSigner(true)}
|
||||
className="op-btn op-btn-accent"
|
||||
>
|
||||
<i className="fa-light fa-plus"></i>
|
||||
@@ -212,18 +217,16 @@ function WidgetComponent({
|
||||
|
||||
<div
|
||||
data-tut="addWidgets"
|
||||
ref={scrollContainerRef}
|
||||
className="bg-base-100 border-[2px] border-t-primary"
|
||||
>
|
||||
<div className="flex whitespace-nowrap overflow-x-scroll pt-[10px] pb-[5px] pr-[5px]">
|
||||
<WidgetList
|
||||
updateWidgets={updateWidgets}
|
||||
handleDivClick={handleDivClick}
|
||||
handleMouseLeave={handleMouseLeave}
|
||||
handleDivClick={props.handleDivClick}
|
||||
handleMouseLeave={props.handleMouseLeave}
|
||||
signRef={signRef}
|
||||
marginLeft={5}
|
||||
addPositionOfSignature={addPositionOfSignature}
|
||||
isMobile={isMobile}
|
||||
addPositionOfSignature={props.addPositionOfSignature}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -231,56 +234,55 @@ function WidgetComponent({
|
||||
)
|
||||
) : (
|
||||
<div
|
||||
data-tut={dataTut}
|
||||
data-tut={props.dataTut}
|
||||
className={`${
|
||||
isMailSend ? "bg-opacity-50 pointer-events-none" : ""
|
||||
props.isMailSend ? "bg-opacity-50 pointer-events-none" : ""
|
||||
} hidden md:block h-full bg-base-100`}
|
||||
>
|
||||
<div className="mx-2 pr-2 pt-2 pb-1 text-[15px] text-base-content font-semibold border-b-[1px] border-base-300">
|
||||
<span>{t("fields")}</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="p-[15px] flex flex-col pt-4 2xl:m-5"
|
||||
data-tut="addWidgets"
|
||||
>
|
||||
<div className="p-[15px] flex flex-col pt-4" data-tut="addWidgets">
|
||||
<WidgetList
|
||||
updateWidgets={updateWidgets}
|
||||
handleDivClick={handleDivClick}
|
||||
handleMouseLeave={handleMouseLeave}
|
||||
handleDivClick={props.handleDivClick}
|
||||
handleMouseLeave={props.handleMouseLeave}
|
||||
signRef={signRef}
|
||||
addPositionOfSignature={props.addPositionOfSignature}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{isSignersModal && (
|
||||
<ModalUi
|
||||
title={title ? title : t("recipients")}
|
||||
title={props.title ? props.title : t("recipients")}
|
||||
isOpen={isSignersModal}
|
||||
handleClose={handleModal}
|
||||
>
|
||||
{signersdata.length > 0 ? (
|
||||
{props.signersdata.length > 0 ? (
|
||||
<div className="max-h-[600px] overflow-auto pb-1">
|
||||
<RecipientList
|
||||
signerPos={signerPos}
|
||||
signersdata={signersdata}
|
||||
isSelectListId={isSelectListId}
|
||||
setIsSelectId={setIsSelectId}
|
||||
setUniqueId={setUniqueId}
|
||||
setRoleName={setRoleName}
|
||||
handleDeleteUser={handleDeleteUser}
|
||||
handleRoleChange={handleRoleChange}
|
||||
handleOnBlur={handleOnBlur}
|
||||
signerPos={props.signerPos}
|
||||
signersdata={props.signersdata}
|
||||
isSelectListId={props.isSelectListId}
|
||||
setIsSelectId={props.setIsSelectId}
|
||||
setUniqueId={props.setUniqueId}
|
||||
setRoleName={props.setRoleName}
|
||||
handleDeleteUser={props.handleDeleteUser}
|
||||
handleRoleChange={props.handleRoleChange}
|
||||
handleOnBlur={props.handleOnBlur}
|
||||
handleModal={handleModal}
|
||||
sendInOrder={sendInOrder}
|
||||
setSignersData={setSignersData}
|
||||
setBlockColor={setBlockColor}
|
||||
uniqueId={uniqueId}
|
||||
sendInOrder={props.sendInOrder}
|
||||
setSignersData={props.setSignersData}
|
||||
setBlockColor={props.setBlockColor}
|
||||
uniqueId={props.uniqueId}
|
||||
setSignerPos={props.setSignerPos}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className=" p-[20px] text-[15px] font-medium text-center">
|
||||
{t("please-add")} {title ? title : t("recipients")}
|
||||
{t("please-add")} {props.title ? props.title : t("recipients")}
|
||||
</div>
|
||||
)}
|
||||
</ModalUi>
|
||||