mirror of
https://github.com/OpenSignLabs/OpenSign.git
synced 2026-09-03 08:18:50 +02:00
Compare commits
47
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e857724502 | ||
|
|
086d8ee5be | ||
|
|
04543e826a | ||
|
|
04f6e38778 | ||
|
|
1b5c85fd5f | ||
|
|
123425d7d1 | ||
|
|
a5b9d8178c | ||
|
|
aefd2ff933 | ||
|
|
fe93be8be7 | ||
|
|
369e3660fc | ||
|
|
378c0f057b | ||
|
|
4f3c034cec | ||
|
|
e7d3453744 | ||
|
|
47cc0ef580 | ||
|
|
04f71be6a2 | ||
|
|
07603f4bc2 | ||
|
|
ea16180551 | ||
|
|
75ad566d54 | ||
|
|
fda9fb534b | ||
|
|
cb3e31ebbf | ||
|
|
400525debb | ||
|
|
1cc017bb05 | ||
|
|
531dd11185 | ||
|
|
1e80754750 | ||
|
|
0d286938c7 | ||
|
|
360c13df25 | ||
|
|
71b9bd6367 | ||
|
|
4a5b35e147 | ||
|
|
7930a8f439 | ||
|
|
7204183542 | ||
|
|
5561673fdc | ||
|
|
0df44062d6 | ||
|
|
8983440763 | ||
|
|
5a28c0ed06 | ||
|
|
cbca22c308 | ||
|
|
b0e0b38e8f | ||
|
|
49c23fb52b | ||
|
|
da4b604710 | ||
|
|
82d518b3c9 | ||
|
|
dcbde2b661 | ||
|
|
27b0b426ad | ||
|
|
8788afaa8e | ||
|
|
428aa65b38 | ||
|
|
295d942427 | ||
|
|
d888897b62 | ||
|
|
c4779c14c1 | ||
|
|
e48869cb2f |
@@ -73,12 +73,28 @@ Welcome to OpenSign, the premier open source docusign alternative - document e-s
|
||||
|
||||
---
|
||||
|
||||
### Installation
|
||||
### Deploy
|
||||
|
||||
Note: The default MongoDB instance used in deployment is not persistant and will be cleared on every restart. To retain your data, configure and supply your own MongoDB connection URL.
|
||||
|
||||
#### DigitalOcean
|
||||
[](https://cloud.digitalocean.com/apps/new?repo=https://github.com/OpenSignLabs/Deploy-OpenSign-to-Digital-Ocean/tree/main&refcode=30db1c901ab0)
|
||||
|
||||
#### Docker
|
||||
The simplest way to install OpenSign on your own server is using official docker images by running the following command -
|
||||
|
||||
**Command for linux/MacOS**
|
||||
```
|
||||
export HOST_URL=https://opensign.yourdomain.com && curl --remote-name-all https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/docker-compose.yml https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/Caddyfile https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/.env.local_dev && mv .env.local_dev .env.prod && docker compose up --force-recreate
|
||||
```
|
||||
**Command for Windows (Powershell)**
|
||||
```
|
||||
$env:HOST_URL="https://opensign.yourdomain.com"; Invoke-WebRequest -Uri https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/docker-compose.yml -OutFile docker-compose.yml; Invoke-WebRequest -Uri https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/Caddyfile -OutFile Caddyfile; Invoke-WebRequest -Uri https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/.env.local_dev -OutFile .env.local_dev; Rename-Item -Path .env.local_dev -NewName .env.prod; docker compose up --force-recreate
|
||||
```
|
||||
**Command for Windows (CMD/Terminal)**
|
||||
```
|
||||
set HOST_URL=https://opensign.yourdomain.com && curl -O https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/docker-compose.yml && curl -O https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/Caddyfile && curl -O https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/.env.local_dev && rename .env.local_dev .env.prod && docker compose up --force-recreate
|
||||
```
|
||||
Make sure that you have `Docker` and `git` installed before you run this command -
|
||||
|
||||
Please refer to the [Installation Guide](https://docs.opensignlabs.com/docs/self-host/docker/run-locally/) for detailed instructions on how to install OpenSign on your system.
|
||||
|
||||
@@ -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,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
|
||||
};
|
||||
Generated
+1117
-326
File diff suppressed because it is too large
Load Diff
+28
-25
@@ -4,30 +4,31 @@
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@formkit/auto-animate": "^0.8.2",
|
||||
"@lottiefiles/dotlottie-react": "^0.13.5",
|
||||
"@imgly/background-removal": "^1.6.0",
|
||||
"@lottiefiles/dotlottie-react": "^0.14.2",
|
||||
"@pdf-lib/fontkit": "^1.1.1",
|
||||
"@radix-ui/themes": "^3.2.1",
|
||||
"@reduxjs/toolkit": "^2.8.2",
|
||||
"@imgly/background-removal": "^1.6.0",
|
||||
"axios": "^1.9.0",
|
||||
"axios": "^1.10.0",
|
||||
"date-fns-tz": "^3.2.0",
|
||||
"file-saver": "^2.0.5",
|
||||
"i18next": "^23.16.8",
|
||||
"i18next-browser-languagedetector": "^8.1.0",
|
||||
"i18next": "^25.3.0",
|
||||
"i18next-browser-languagedetector": "^8.2.0",
|
||||
"i18next-http-backend": "^3.0.2",
|
||||
"jszip": "^3.10.1",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"moment": "^2.30.1",
|
||||
"parse": "^6.1.1",
|
||||
"pkijs": "^3.0.8",
|
||||
"pdf-lib": "^1.17.1",
|
||||
"pkijs": "^3.0.8",
|
||||
"print-js": "^1.6.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.3.0",
|
||||
"react-datepicker": "^8.4.0",
|
||||
"react-dnd": "^16.0.1",
|
||||
"react-dnd-html5-backend": "^16.0.1",
|
||||
"react-dnd-multi-backend": "^9.0.0",
|
||||
@@ -35,24 +36,24 @@
|
||||
"react-dom": "^18.3.1",
|
||||
"react-gtm-module": "^2.0.11",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-i18next": "^15.5.1",
|
||||
"react-i18next": "^15.5.3",
|
||||
"react-konva": "^18.2.10",
|
||||
"react-pdf": "^9.2.1",
|
||||
"react-quill-new": "^3.4.6",
|
||||
"react-redux": "^9.2.0",
|
||||
"react-rnd": "^10.5.2",
|
||||
"react-router": "^7.6.0",
|
||||
"react-router": "^7.6.3",
|
||||
"react-scrollbars-custom": "^4.1.1",
|
||||
"react-select": "^5.10.1",
|
||||
"react-signature-canvas": "^1.1.0-alpha.2",
|
||||
"react-timezone-select": "^3.2.8",
|
||||
"react-tooltip": "^5.28.1",
|
||||
"react-tooltip": "^5.29.1",
|
||||
"reactour": "^1.19.4",
|
||||
"redux": "^5.0.1",
|
||||
"regex-parser": "^2.3.1",
|
||||
"serve": "^14.2.4",
|
||||
"styled-components": "^5.3.11",
|
||||
"web-vitals": "^5.0.1",
|
||||
"web-vitals": "^5.0.3",
|
||||
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz"
|
||||
},
|
||||
"scripts": {
|
||||
@@ -93,36 +94,38 @@
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.27.1",
|
||||
"@babel/core": "^7.27.7",
|
||||
"@babel/preset-env": "^7.27.2",
|
||||
"@babel/preset-react": "^7.27.1",
|
||||
"@babel/runtime-corejs2": "^7.27.1",
|
||||
"@babel/runtime-corejs2": "^7.27.6",
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
"@testing-library/react": "^16.3.0",
|
||||
"@testing-library/user-event": "^14.6.1",
|
||||
"@types/react": "^18.3.22",
|
||||
"@vitejs/plugin-react": "^4.4.1",
|
||||
"@vitejs/plugin-react-swc": "^3.9.0",
|
||||
"@types/react": "^18.3.23",
|
||||
"@vitejs/plugin-react": "^4.6.0",
|
||||
"@vitejs/plugin-react-swc": "^3.10.2",
|
||||
"@vitest/ui": "^3.2.4",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"babel-loader": "^10.0.0",
|
||||
"commitizen": "^4.3.1",
|
||||
"concurrently": "^9.1.2",
|
||||
"concurrently": "^9.2.0",
|
||||
"css-loader": "^7.1.2",
|
||||
"daisyui": "^4.12.24",
|
||||
"dotenv": "^16.5.0",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint-plugin-prettier": "^5.4.0",
|
||||
"dotenv": "^16.6.1",
|
||||
"eslint": "^9.30.0",
|
||||
"eslint-plugin-prettier": "^5.5.1",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"lint-staged": "^16.0.0",
|
||||
"postcss": "^8.5.3",
|
||||
"prettier": "^3.5.3",
|
||||
"pretty-quick": "^4.1.1",
|
||||
"jsdom": "^26.1.0",
|
||||
"lint-staged": "^16.1.2",
|
||||
"postcss": "^8.5.6",
|
||||
"prettier": "^3.6.2",
|
||||
"pretty-quick": "^4.2.2",
|
||||
"rollup-plugin-node-polyfills": "^0.2.1",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"vite": "^6.3.5",
|
||||
"vite-plugin-svgr": "^4.3.0",
|
||||
"vite-tsconfig-paths": "^5.1.4",
|
||||
"vitest": "^3.1.4"
|
||||
"vitest": "^3.2.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": "18 || 20 || 22"
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"create-account": "Konto erstellen",
|
||||
"login": "Anmelden",
|
||||
"language": "Sprache",
|
||||
"dark-mode": "Dunkelmodus",
|
||||
"name": "Name",
|
||||
"phone": "Telefon",
|
||||
"phone-optional": "optional",
|
||||
@@ -25,6 +26,8 @@
|
||||
"Name": "Name",
|
||||
"Date": "Datum"
|
||||
},
|
||||
"folder": "Ordner",
|
||||
"pdf": "Pdf",
|
||||
"context-menu": {
|
||||
"Download": "Herunterladen",
|
||||
"Rename": "Umbenennen",
|
||||
@@ -43,6 +46,10 @@
|
||||
"contact-now": "Jetzt kontaktieren",
|
||||
"upgrade-to": "Upgrade zu",
|
||||
"plan": "Plan",
|
||||
"connect": "Verbinden",
|
||||
"connect-to-g-drive": "Mit Google Drive verbinden",
|
||||
"reconnect-to-g-drive": "Erneut mit Google Drive verbinden",
|
||||
"gdrive-info-connect": "Wenn Google Drive verbunden ist, wird das abgeschlossene Dokument im Ordner {{appName}} auf Google Drive gespeichert.",
|
||||
"subscription-renew-warning": "Ihr Abonnement läuft in {{remainingDays}} Tagen ab. Bitte verlängern Sie Ihr Abonnement.",
|
||||
"subscribe-card-teamplan": "Entfesseln Sie die volle Kraft der Zusammenarbeit! Erstellen Sie unbegrenzt Organisationen, Teams und Hierarchien. Teilen Sie Vorlagen nahtlos zwischen Teams und weisen Sie benutzerdefinierte Benutzerrollen zu. Optimieren Sie Ihren Workflow noch heute!",
|
||||
"subscribe-card-plan": "Entsperren Sie Premium-Funktionen ab nur {{premiumPrice}}/Monat. Genießen Sie eine verbesserte Leistung und zahlen Sie nur {{addonPrice}} pro zusätzlichem Credit nach den enthaltenen Premium-Credits.",
|
||||
@@ -57,7 +64,7 @@
|
||||
"welcome": "Willkommen zurück!",
|
||||
"Login-to-your-account": "Melden Sie sich bei Ihrem Konto an",
|
||||
"password": "Passwort",
|
||||
"forgot-password": "Passwort vergessen?",
|
||||
"forgot-password": "Passwort vergessen",
|
||||
"loading": "Wird geladen...",
|
||||
"of": "von",
|
||||
"sign-SSO": "Mit SSO anmelden",
|
||||
@@ -180,7 +187,9 @@
|
||||
"created-date": "Erstellungsdatum",
|
||||
"Type": "Type",
|
||||
"Logs": "Protokolle",
|
||||
"Expiry-date": "Ablaufdatum"
|
||||
"Expiry-date": "Ablaufdatum",
|
||||
"Company": "Unternehmen",
|
||||
"JobTitle": "Berufsbezeichnung"
|
||||
},
|
||||
"report-help": {
|
||||
"Draft Documents": "Dies sind Dokumente, die Sie begonnen, aber noch nicht zum Versenden fertiggestellt haben.",
|
||||
@@ -192,16 +201,15 @@
|
||||
"Contactbook": "Dies ist eine Liste von Kontakten/Unterzeichnern, die Sie hinzugefügt haben. Diese erscheinen als Vorschläge, wenn Sie neue Unterzeichner hinzufügen möchten.",
|
||||
"Templates": "Dies ist eine Liste von Vorlagen, die Ihnen zur Verfügung stehen, um Dokumente zu erstellen. Sie können die Schaltfläche 'Verwenden' anklicken, um ein neues Dokument mit einer Vorlage zu erstellen, das Dokument zu ändern und Unterzeichner im nächsten Schritt hinzuzufügen."
|
||||
},
|
||||
"form-name": {
|
||||
"Sign Yourself": "Selbst unterschreiben",
|
||||
"Request Signatures": "Signaturen anfordern",
|
||||
"New Template": "Neue Vorlage"
|
||||
},
|
||||
"Sign Yourself": "Selbst unterschreiben",
|
||||
"Request Signatures": "Signaturen anfordern",
|
||||
"New Template": "Neue Vorlage",
|
||||
"file-type": "pdf, png, jpg, jpeg",
|
||||
"docx": "docx",
|
||||
"file-selected": "Datei ausgewählt",
|
||||
"template-title": "Vorlagentitel",
|
||||
"document-title": "Dokumenttitel",
|
||||
"title": "Titel",
|
||||
"description": "Beschreibung",
|
||||
"time-to-complete": "Bearbeitungszeit (Tage)",
|
||||
"send-in-order": "In der Reihenfolge senden",
|
||||
@@ -361,6 +369,7 @@
|
||||
"date": "Datum",
|
||||
"text": "Text",
|
||||
"text input": "Texteingabe",
|
||||
"cells": "Zellen",
|
||||
"checkbox": "Checkbox",
|
||||
"dropdown": "Dropdown",
|
||||
"radio button": "Radiobutton",
|
||||
@@ -377,6 +386,7 @@
|
||||
"certificate": "Zertifikat",
|
||||
"decline": "Ablehnen",
|
||||
"finish": "Fertigstellen",
|
||||
"done": "Fertig",
|
||||
"mail": "E-Mail",
|
||||
"sign-now": "Jetzt unterzeichnen",
|
||||
"successfully-signed": "Erfolgreich unterzeichnet!",
|
||||
@@ -386,6 +396,10 @@
|
||||
"Email-verified-alert-1": "E-Mail wurde verifiziert.",
|
||||
"Email-verified-alert-2": "E-Mail wurde bereits verifiziert.",
|
||||
"upload-stamp-image": "Stempelbild hochladen",
|
||||
"draw-signature": "Unterschrift zeichnen",
|
||||
"draw-initials": "Initialen zeichnen",
|
||||
"enter-text": "Text eingeben",
|
||||
"enter-widgettype": "{{widgetType}} eingeben",
|
||||
"draw": "Zeichnen",
|
||||
"type": "Schreiben",
|
||||
"color-type": {
|
||||
@@ -415,10 +429,12 @@
|
||||
"options": "Optionen",
|
||||
"minimun-check": "Minimale Anzahl",
|
||||
"maximum-check": "Maximale Anzahl",
|
||||
"cell-count": "Zellzahl",
|
||||
"default-value": "Standardwert",
|
||||
"select": "Auswählen",
|
||||
"read-only": "Nur lesen",
|
||||
"read-only": "Ist schreibgeschützt",
|
||||
"hide-labels": "Labels ausblenden",
|
||||
"layout": "Layout",
|
||||
"checkbox": "Checkbox",
|
||||
"alert": "Warnung",
|
||||
"zoom-in": "Vergrößern",
|
||||
@@ -724,7 +740,7 @@
|
||||
"public-tour-message": "Die Vorlage muss öffentlich sein, bevor Sie einen teilbaren Link generieren können.",
|
||||
"add-user-template": "Sie müssen eine Rolle hinzufügen, bevor Sie Felder dafür hinzufügen können.",
|
||||
"pdf-uncompatible": "Diese PDF-Datei ist nicht kompatibel, bitte kontaktieren Sie {{appName}}",
|
||||
"text-field-tour": "Felder vom Typ 'Text' müssen im Voraus ausgefüllt werden, bevor das Dokument versendet wird. Wenn Sie möchten, dass die Unterzeichner Eingaben machen, verwenden Sie das Feld 'Texteingabe'.",
|
||||
"text-field-tour": "Felder zum Vorabfüllen müssen vor dem Senden des Dokuments ausgefüllt werden. Wenn Sie Eingaben von den Unterzeichnern benötigen, verwenden Sie stattdessen die Felder für Unterzeichner.",
|
||||
"attach-signer-tour": "Sie müssen einen Unterzeichner jeder Rolle zuweisen. Dies können Sie durch Klicken auf dieses Symbol tun. Sobald Sie einen Unterzeichner auswählen, wird er allen Feldern der zugehörigen Rolle zugewiesen, die in derselben Farbe erscheinen.",
|
||||
"allowed-signature-types": "Erlaubte Signaturtypen",
|
||||
"at-least-one-signature-type": "Mindestens ein Signaturtyp sollte aktiviert sein.",
|
||||
@@ -750,12 +766,15 @@
|
||||
"delete-page": "Seite löschen",
|
||||
"merge-pdf": "PDFs zusammenführen",
|
||||
"add-pages": "Seiten hinzufügen",
|
||||
"reorder-pages": "Seiten neu anordnen",
|
||||
"delete-alert": "Einzelne Seite kann nicht gelöscht werden.",
|
||||
"delete-alert-2": "Sind Sie sicher, dass Sie diese Seite löschen möchten?",
|
||||
"delete-note": "Hinweis: Sobald Sie diese Seite löschen, kann dies nicht rückgängig gemacht werden.",
|
||||
"Rotation-alert": "Seite drehen",
|
||||
"bulk-import": "Massenimport",
|
||||
"contacts-file": "Kontaktdatei (xlsx, csv)",
|
||||
"import-guideline": "Laden Sie eine CSV- oder Excel-Datei mit den Spalten Name, Email und optional Phone hoch. Es werden nur die ersten 100 Kontakte importiert.",
|
||||
"download-sample": "Beispieldatei herunterladen",
|
||||
"100-records-only": "Derzeit können Sie nur bis zu 100 Datensätze importieren.",
|
||||
"csv-excel-support-only": "Laden Sie eine Datei in einem der folgenden Formate hoch: CSV, XLSX oder XLS.",
|
||||
"contact-imported": "{{imported}} Kontakte wurden importiert. {{failed}} Kontakte konnten nicht importiert werden.",
|
||||
@@ -769,7 +788,7 @@
|
||||
"agree-p1": "Ich bestätige, dass ich die ",
|
||||
"agree-p2": "Offenlegung elektronischer Aufzeichnungen und Signaturen",
|
||||
"agree-p3": "gelesen und verstanden habe und der Verwendung elektronischer Aufzeichnungen und Signaturen zustimme.",
|
||||
"agrre-button": "Zustimmen & Fortfahren",
|
||||
"agrre-button": "Ich bestätige und stimme zu, fortzufahren",
|
||||
"term-cond-title": "Allgemeine Geschäftsbedingungen",
|
||||
"term-cond-h": "OFFENLEGUNG ELEKTRONISCHER AUFZEICHNUNGEN UND SIGNATUREN",
|
||||
"term-cond-p1": "Diese Offenlegung elektronischer Aufzeichnungen und Signaturen ('Offenlegung') ist eine Vereinbarung zwischen dem Dokumentersteller ('Sender') und dem Unterzeichner ('Ihnen'), bereitgestellt über die {{appName}} Plattform ('Plattform'). Durch das Signieren von Dokumenten über {{appName}} stimmen Sie den in dieser Offenlegung beschriebenen Bedingungen zu. Bitte lesen Sie sie sorgfältig durch, bevor Sie fortfahren.",
|
||||
@@ -982,6 +1001,7 @@
|
||||
"review": "Überprüfen",
|
||||
"next-field": "Nächstes Feld",
|
||||
"required-mssg": "{{leftRequiredWidget}} von {{totalWidget}} Feldern übrig",
|
||||
"verify-document": "Dokument verifizieren",
|
||||
"verify-document-signature": "Dokumentensignatur überprüfen",
|
||||
"select-pdf-document": "PDF-Dokument auswählen",
|
||||
"selected-file": "Ausgewählte Datei",
|
||||
@@ -1022,5 +1042,104 @@
|
||||
"no-signer-info-in-pkcs7": "Keine Signaturinformationen in PKCS#7",
|
||||
"could-not-parse-signer-info": "Signaturinformationen konnten nicht analysiert werden",
|
||||
"not-calculated": "Nicht berechnet",
|
||||
"not-found-in-signature": "Nicht in Signatur gefunden"
|
||||
"not-found-in-signature": "Nicht in Signatur gefunden",
|
||||
"readonly-error": "Das schreibgeschützte {{widgetName}}-Widget muss einen Standardwert haben oder kann optional gemacht werden.",
|
||||
"choose-one":"Wählen Sie eine aus",
|
||||
"search-templates": "Vorlagen durchsuchen…",
|
||||
"search-documents": "Dokumente suchen…",
|
||||
"search-contacts": "Kontakte durchsuchen…",
|
||||
"edit-draft": "Entwurf bearbeiten",
|
||||
"add-role-alert": "Bitte fügen Sie mindestens eine Rolle hinzu",
|
||||
"invalid-email-found": "Ungültige E-Mail gefunden: {{email}}",
|
||||
"duplicate-email-found": "Doppelte E-Mail gefunden: {{email}}",
|
||||
"vertical": "Vertikal",
|
||||
"horizontal": "Horizontal",
|
||||
"billing": "Abrechnung",
|
||||
"console": "Konsole",
|
||||
"prefill-widget": "Vorausgefüllte Widgets",
|
||||
"action-prohibited": "Diese Aktion ist für Ihre E-Mail-Domain nicht erlaubt. Bitte wenden Sie sich an Ihren Administrator, um Hilfe zu erhalten.",
|
||||
"must-have-at-least-one-vacant-role": "Mindestens eine Rolle muss unzugewiesen sein, bevor Sie ein template auf 'public' setzen.",
|
||||
"remove-duplicate": "Bitte doppelte Option entfernen",
|
||||
"prefill-bulk-error": "Der Massenversand ist nicht erlaubt, wenn Prefill-Widgets hinzugefügt wurden. Bitte entfernen Sie die Prefill-Widgets, um fortzufahren.",
|
||||
"session-expired-title": "Sitzung abgelaufen",
|
||||
"access-denied": "Zugriff verweigert",
|
||||
"upgrade": "Upgrade",
|
||||
"do-not-access-app": "Sie haben keinen Zugriff auf diese Anwendung.",
|
||||
"dont-have-access": "Sie haben keinen Zugriff.",
|
||||
"valid-email-alert": "Bitte geben Sie eine gültige E-Mail-Adresse ein.",
|
||||
"otp-not-validate": "OTP ist ungültig.",
|
||||
"domain-not-allowed": "Diese Domain ist nicht erlaubt",
|
||||
"atleast-one-recipient-alert": "Bitte fügen Sie mindestens einen Empfänger hinzu!",
|
||||
"incorrect-password-or-decryption-failed": "Falsches Passwort oder Entschlüsselung fehlgeschlagen.",
|
||||
"incorrect-password-for-file": "Falsches Passwort für Datei: {{file}}",
|
||||
"error-uploading-pdf": "Fehler beim Hochladen der PDF.",
|
||||
"provide-password": "Bitte geben Sie das Passwort an.",
|
||||
"only-pdf-allowed": "Nur PDF-Dateien sind erlaubt.",
|
||||
"invalid-username-password-region": "Ungültiger Benutzername/Passwort oder Region.",
|
||||
"pfx-extension-alert": "Bitte laden Sie eine Datei mit der Endung .pfx hoch.",
|
||||
"email-already-exist": "E-Mail existiert bereits",
|
||||
"branding": "Markenbildung",
|
||||
"branding-help": "Branding ermöglicht White-Labeling für Ihre App",
|
||||
"custom-sub-domain": "Benutzerdefinierte Subdomain",
|
||||
"app-name": "App Name",
|
||||
"provide-domain-name": "Geben Sie Ihren Domainnamen an",
|
||||
"provide-app-name": "Geben Sie Ihren App-Namen an",
|
||||
"logo": "Logo",
|
||||
"upload-app-logo": "Laden Sie Ihr App-Logo hoch",
|
||||
"prefill-unfilled-widget": "Die folgenden Pflichtfelder dürfen nicht leer sein: {{emptyWidget}}. Bitte füllen Sie diese aus, um fortzufahren.",
|
||||
"Dashboard": "Armaturenbrett",
|
||||
"Analytics": "Analytik",
|
||||
"Templates": "Vorlagen",
|
||||
"Need your sign": "Benötigt Ihre Unterschrift",
|
||||
"In Progress": "In Bearbeitung",
|
||||
"Completed": "Abgeschlossen",
|
||||
"Drafts": "Entwürfe",
|
||||
"Declined": "Abgelehnt",
|
||||
"Expired": "Abgelaufen",
|
||||
"Contactbook": "Kontaktbuch",
|
||||
"My Signature": "Meine Unterschrift",
|
||||
"API Token": "API-Token",
|
||||
"Webhook": "Webhook",
|
||||
"Preferences": "Einstellungen",
|
||||
"Teams": "Teams",
|
||||
"Users": "Benutzer",
|
||||
"Drive": "Drive",
|
||||
"Branding": "Markenbildung",
|
||||
"Mail": "Mail",
|
||||
"Storage": "Speicher",
|
||||
"Signing certificate": "Signierzertifikat",
|
||||
"General": "Allgemein",
|
||||
"Organizations": "Organisationen",
|
||||
"OrgAdmins": "OrgAdmins",
|
||||
"Debug Pdf": "PDF debuggen",
|
||||
"New Document": "Neues Dokument",
|
||||
"subscription": "Abonnement",
|
||||
"Draft document": "Dokumententwurf",
|
||||
"Draft template": "Vorlagenentwurf",
|
||||
"Public sign": "Öffentliche Signatur",
|
||||
"Signup": "Registrieren",
|
||||
"delete-contact": "Kontakt löschen",
|
||||
"total-records-found": "Gesamtanzahl gefundener Einträge: {{count}}",
|
||||
"Invalid-records-found": "Ungültige Einträge gefunden: {{records}}",
|
||||
"previous": "Zurück",
|
||||
"page-n-of-n": "Seite {{currentPage}} von {{totalPages}}",
|
||||
"import": "Importieren",
|
||||
"search": "Suchen",
|
||||
"viewed-on": "Angesehen am: {{ViewedOn}}",
|
||||
"signed-on": "Unterschrieben am: {{SignedOn}}",
|
||||
"hide": "Ausblenden",
|
||||
"show-more": "Mehr anzeigen",
|
||||
"browse-or-drag-to-replace-existing-file": "Durchsuchen oder per Drag & Drop eine neue Datei ziehen, um die vorhandene zu ersetzen",
|
||||
"optional-details": "Optionale Angaben",
|
||||
"mail-adapter-subscription-alert": "Bitte upgraden Sie auf den Professional- oder Team-Plan, um den Mail-Adapter einzurichten.",
|
||||
"connect-to-mail": "Mit Gmail verbinden",
|
||||
"custom-smtp": "Benutzerdefiniertes SMTP",
|
||||
"default-smtp": "{{appName}} Standard-SMTP",
|
||||
"host": "Host",
|
||||
"port": "Port",
|
||||
"sender-email": "Absender-E-Mail",
|
||||
"username": "Benutzername",
|
||||
"use-default-mail-adapter": "Möchten Sie wirklich die Standard-Mailserver von {{appName}} verwenden, um Ihre Signaturanfragen zu versenden? Wir empfehlen, Ihre eigenen Gmail- oder SMTP-Server zu verwenden, um die Zustellbarkeit zu verbessern.",
|
||||
"verification-code-sent-registered-email": "Ein Bestätigungscode wurde an Ihre registrierte E-Mail-Adresse <1>{{useremail}}</1> gesendet. Bitte geben Sie den Code unten ein, um Ihre Einstellungen zu bestätigen.",
|
||||
"smpt-credentials": "SMTP-Zugangsdaten"
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"create-account": "Create account",
|
||||
"login": "Login",
|
||||
"language": "Language",
|
||||
"dark-mode": "Dark mode",
|
||||
"name": "Name",
|
||||
"phone": "Phone",
|
||||
"phone-optional": "optional",
|
||||
@@ -25,6 +26,8 @@
|
||||
"Name": "Name",
|
||||
"Date": "Date"
|
||||
},
|
||||
"folder": "Folder",
|
||||
"pdf": "Pdf",
|
||||
"context-menu": {
|
||||
"Download": "Download",
|
||||
"Rename": "Rename",
|
||||
@@ -43,6 +46,10 @@
|
||||
"contact-now": "Contact now",
|
||||
"upgrade-to": "Upgrade to",
|
||||
"plan": "Plan",
|
||||
"connect": "connect",
|
||||
"connect-to-g-drive": "Connect to Google Drive",
|
||||
"reconnect-to-g-drive": "Reconnect to Google Drive",
|
||||
"gdrive-info-connect": "When Google Drive is connected, the completed document will be saved in the {{appName}} folder on Google Drive.",
|
||||
"subscription-renew-warning": "Your subscription will expire in {{remainingDays}} days. Please renew your subscription.",
|
||||
"subscribe-card-teamplan": "Unlock the full power of collaboration! Create unlimited organizations, teams, and hierarchies. Share templates seamlessly across teams and assign custom user roles. Elevate your workflow today!",
|
||||
"subscribe-card-plan": "Unlock premium features starting at just {{premiumPrice}}/month. Enjoy enhanced performance and only {{addonPrice}} per additional credit after your included premium credits.",
|
||||
@@ -57,7 +64,7 @@
|
||||
"welcome": "Welcome back!",
|
||||
"Login-to-your-account": "Login to your account",
|
||||
"password": "Password",
|
||||
"forgot-password": "Forgot password?",
|
||||
"forgot-password": "Forgot password",
|
||||
"loading": "Loading...",
|
||||
"of": "of",
|
||||
"sign-SSO": "Sign in with SSO",
|
||||
@@ -180,7 +187,9 @@
|
||||
"created-date": "Created date",
|
||||
"Type": "Type",
|
||||
"Logs": "Logs",
|
||||
"Expiry-date": "Expiry date"
|
||||
"Expiry-date": "Expiry date",
|
||||
"Company": "Company",
|
||||
"JobTitle": "Job title"
|
||||
},
|
||||
"report-help": {
|
||||
"Draft Documents": "These are documents you have started but have not finalized for sending.",
|
||||
@@ -192,16 +201,15 @@
|
||||
"Contactbook": "This is a list of contacts/signers added by you. These will appear as suggestions when you try to add signers to a new document.",
|
||||
"Templates": "This is a list of templates that are available to you for creating documents. You can click the 'use' button to create a new document using a template, modify the document & add signers in the next step."
|
||||
},
|
||||
"form-name": {
|
||||
"Sign Yourself": "Sign yourself",
|
||||
"Request Signatures": "Request signatures",
|
||||
"New Template": "New template"
|
||||
},
|
||||
"Sign Yourself": "Sign yourself",
|
||||
"Request Signatures": "Request signatures",
|
||||
"New Template": "New template",
|
||||
"file-type": "pdf, png, jpg, jpeg",
|
||||
"docx": "docx",
|
||||
"file-selected": "file selected",
|
||||
"file-selected": "file(s) selected",
|
||||
"template-title": "Template title",
|
||||
"document-title": "Document title",
|
||||
"title": "Title",
|
||||
"description": "Description",
|
||||
"time-to-complete": "Time to complete (Days)",
|
||||
"send-in-order": "Send in order",
|
||||
@@ -361,6 +369,7 @@
|
||||
"date": "date",
|
||||
"text": "text",
|
||||
"text input": "text input",
|
||||
"cells": "cells",
|
||||
"checkbox": "checkbox",
|
||||
"dropdown": "dropdown",
|
||||
"radio button": "radio button",
|
||||
@@ -377,6 +386,7 @@
|
||||
"certificate": "Certificate",
|
||||
"decline": "Decline",
|
||||
"finish": "Finish",
|
||||
"done": "Done",
|
||||
"mail": "Mail",
|
||||
"sign-now": "Sign now",
|
||||
"successfully-signed": "Successfully signed!",
|
||||
@@ -386,6 +396,10 @@
|
||||
"Email-verified-alert-1": "Email is verified.",
|
||||
"Email-verified-alert-2": "Email is already verified.",
|
||||
"upload-stamp-image": "Upload stamp image",
|
||||
"draw-signature": "Draw signature",
|
||||
"draw-initials": "Draw initials",
|
||||
"enter-text": "Enter text",
|
||||
"enter-widgettype": "Enter {{widgetType}}",
|
||||
"draw": "Draw",
|
||||
"type": "Type",
|
||||
"color-type": {
|
||||
@@ -406,6 +420,7 @@
|
||||
"reset-password-alert-3": "Reset Your Password",
|
||||
"faild-animation": "Failed to load animation",
|
||||
"apply": "Apply",
|
||||
"select-columns": "Select columns",
|
||||
"copy-type": {
|
||||
"All pages": "All pages",
|
||||
"All pages but last": "All pages but last",
|
||||
@@ -415,10 +430,12 @@
|
||||
"options": "Options",
|
||||
"minimun-check": "Minimun check",
|
||||
"maximum-check": "Maximum check",
|
||||
"cell-count": "Cell count",
|
||||
"default-value": "Default value",
|
||||
"select": "Select",
|
||||
"read-only": "Is read only",
|
||||
"read-only": "read only",
|
||||
"hide-labels": "Hide labels",
|
||||
"layout": "Layout",
|
||||
"checkbox": "Checkbox",
|
||||
"alert": "Alert",
|
||||
"zoom-in": "Zoom in",
|
||||
@@ -467,7 +484,7 @@
|
||||
"placeholder-alert-3": " Are you sure you want to send out this document for signatures?",
|
||||
"placeholder-alert-4": "You have successfully sent mails to all recipients!",
|
||||
"placeholder-mail-alert": "You have successfully sent email to {{name}}. Subsequent signers will get email(s) once {{name}} signs the document",
|
||||
"placeholder-mail-alert-you": "Subsequent signers will get email(s) once you signs the document.",
|
||||
"placeholder-mail-alert-you": "Subsequent signers will get email(s) once you sign the document.",
|
||||
"placeholder-alert-5": "Do you want to sign the document right now?",
|
||||
"placeholder-alert-6": "Please setup mail adapter to send mail!",
|
||||
"placeholder-alert-7": "Please select signer for add placeholder!",
|
||||
@@ -526,7 +543,7 @@
|
||||
"new-password": "New password",
|
||||
"confirm-password": "Confirm password",
|
||||
"file-alert-1": "The selected file size is too large. Please select a file less than",
|
||||
"file-alert-2": "Please select file.",
|
||||
"file-alert-2": "Please select file(s).",
|
||||
"file-alert-3": "Please wait while the document is being uploaded.",
|
||||
"enter-pdf-password": "Enter Pdf password",
|
||||
"correct-password": "Please provide correct password",
|
||||
@@ -724,7 +741,7 @@
|
||||
"public-tour-message": "The template needs to be public before you can generate a shareable link.",
|
||||
"add-user-template": "You need to add a role before you can add fields for it.",
|
||||
"pdf-uncompatible": "This pdf is not compatible, please contact {{appName}}",
|
||||
"text-field-tour": "Fields of type 'Text' must be filled in advance before the document is sent. If you need the signers to provide input, use the 'Text Input' field instead.",
|
||||
"text-field-tour": "'Prefill' fields must be filled in advance before the document is sent. If you need the signers to provide input, use signers fields instead.",
|
||||
"attach-signer-tour": "You need to attach a Signer to every role. You can do that by clicking this icon. Once you select a Signer it will be attached to all the fields associated with that role which appear in the same colour.",
|
||||
"allowed-signature-types": "Allowed signature types",
|
||||
"at-least-one-signature-type": "At least one signature type should be enabled.",
|
||||
@@ -750,12 +767,15 @@
|
||||
"delete-page": "Delete page",
|
||||
"merge-pdf": "Merge pdf",
|
||||
"add-pages": "Add pages",
|
||||
"reorder-pages": "Reorder pages",
|
||||
"delete-alert": "Can not delete single page",
|
||||
"delete-alert-2": "Are you sure you want to delete this page?",
|
||||
"delete-note": "Note: Once you delete this page, you cannot undo.",
|
||||
"Rotation-alert": "Rotate page",
|
||||
"bulk-import": "Bulk import",
|
||||
"contacts-file": "Contacts file (xlsx, csv)",
|
||||
"import-guideline": "Upload a CSV or Excel file with columns Name, Email and optional Phone. Only the first 100 records will be imported.",
|
||||
"download-sample": "Download sample file",
|
||||
"100-records-only": "Currently you can only import up to 100 records.",
|
||||
"csv-excel-support-only": "Upload a file in one of the following formats: CSV, XLSX or XLS.",
|
||||
"contact-imported": "{{imported}} contacts were imported. {{failed}} contacts failed to import.",
|
||||
@@ -769,7 +789,7 @@
|
||||
"agree-p1": "I confirm that I have read and understood the ",
|
||||
"agree-p2": "Electronic Record and Signature Disclosure",
|
||||
"agree-p3": "and consent to use electronic records and signatures.",
|
||||
"agrre-button": " Agree & Continue",
|
||||
"agrre-button": "I confirm & agree to continue",
|
||||
"term-cond-title": "Terms and conditions",
|
||||
"term-cond-h": "ELECTRONIC RECORD AND SIGNATURE DISCLOSURE",
|
||||
"term-cond-p1": "This Electronic Record and Signature Disclosure ('Disclosure') is an agreement between the Document Creator ('Sender') and the Signer ('You'), facilitated through the {{appName}} platform ('Platform'). By signing documents via {{appName}}, you agree to the terms outlined in this Disclosure. Please read it carefully before proceeding.",
|
||||
@@ -885,7 +905,7 @@
|
||||
"you-will-receive-email-shortly": "✅ That's it! You'll receive a confirmation email shortly.",
|
||||
"please-provide-templateid": "Please provide templateid",
|
||||
"this-template-is-not-public": "This template is not public",
|
||||
"invalid-templateid": "Invaldi templateid",
|
||||
"invalid-templateid": "Invalid templateid",
|
||||
"contact-billing-at-opensign": "To add more seats, please contact OpenSign™ at <1>billing@opensignlabs.com</1> for assistance",
|
||||
"title-length-alert": "Title must be at most 250 characters long.",
|
||||
"note-length-alert": "Note must be at most 200 characters long.",
|
||||
@@ -982,6 +1002,7 @@
|
||||
"review":"Review",
|
||||
"next-field":"Next Field",
|
||||
"required-mssg":"{{leftRequiredWidget}} of {{totalWidget}} fields left",
|
||||
"verify-document": "Verify document",
|
||||
"verify-document-signature": "Verify Document Signature",
|
||||
"select-pdf-document": "Select PDF Document",
|
||||
"selected-file": "Selected file",
|
||||
@@ -1022,5 +1043,104 @@
|
||||
"no-signer-info-in-pkcs7": "No signer info in PKCS#7",
|
||||
"could-not-parse-signer-info": "Could not parse signer info",
|
||||
"not-calculated": "Not calculated",
|
||||
"not-found-in-signature": "Not found in signature"
|
||||
"not-found-in-signature": "Not found in signature",
|
||||
"readonly-error": "Read-only {{widgetName}} widget must have a default value or you can make it optional.",
|
||||
"choose-one":"Choose One",
|
||||
"search-templates": "Search templates…",
|
||||
"search-documents": "Search documents…",
|
||||
"search-contacts": "Search contacts…",
|
||||
"add-role-alert":"Please add at least one role",
|
||||
"edit-draft":"Edit draft",
|
||||
"invalid-email-found": "Invalid email found: {{email}}",
|
||||
"duplicate-email-found": "Duplicate email found: {{email}}",
|
||||
"vertical": "Vertical",
|
||||
"horizontal": "Horizontal",
|
||||
"billing": "Billing",
|
||||
"console": "Console",
|
||||
"prefill-widget":"Prefill Widgets",
|
||||
"action-prohibited": "This action isn't allowed for your email domain. Please contact your administrator for assistance.",
|
||||
"must-have-at-least-one-vacant-role": "You must have at least one role unassigned before setting a template to 'public'.",
|
||||
"remove-duplicate": "Please remove duplicate option",
|
||||
"prefill-bulk-error":"Bulk send is not allowed when prefill widgets are added. Please remove the prefill widgets to proceed.",
|
||||
"session-expired-title": "Session Expired",
|
||||
"access-denied": "Access denied",
|
||||
"upgrade": "Upgrade",
|
||||
"do-not-access-app": "You don't have access to this application.",
|
||||
"dont-have-access": "You don't have access.",
|
||||
"valid-email-alert": "Please enter a valid email address.",
|
||||
"otp-not-validate": "OTP is not valid.",
|
||||
"domain-not-allowed": "This domain is not allowed",
|
||||
"atleast-one-recipient-alert": "Please add at least one recipient!",
|
||||
"incorrect-password-or-decryption-failed": "Incorrect password or decryption failed.",
|
||||
"incorrect-password-for-file": "Incorrect password for file: {{file}}",
|
||||
"error-uploading-pdf": "Error while uploading PDF.",
|
||||
"provide-password": "Please provide password.",
|
||||
"only-pdf-allowed": "Only PDF files are allowed.",
|
||||
"invalid-username-password-region": "Invalid username/password or region.",
|
||||
"pfx-extension-alert": "Please upload a file with a .pfx extension.",
|
||||
"email-already-exist": "Email already exists",
|
||||
"branding": "Branding",
|
||||
"branding-help": "Branding provides white labelling to your app",
|
||||
"custom-sub-domain": "Custom sub-domain",
|
||||
"app-name": "App Name",
|
||||
"provide-domain-name": "provide your domain name",
|
||||
"provide-app-name": "provide your app name",
|
||||
"logo":"Logo",
|
||||
"upload-app-logo": "upload your app logo",
|
||||
"prefill-unfilled-widget" :"The following required field(s) cannot be left empty: {{emptyWidget}} Please fill them out to proceed.",
|
||||
"Dashboard": "Dashboard",
|
||||
"Analytics": "Analytics",
|
||||
"Templates": "Templates",
|
||||
"Need your sign": "Need your sign",
|
||||
"In Progress": "In progress",
|
||||
"Completed": "Completed",
|
||||
"Drafts": "Drafts",
|
||||
"Declined": "Declined",
|
||||
"Expired": "Expired",
|
||||
"Contactbook": "Contactbook",
|
||||
"My Signature": "My signature",
|
||||
"API Token": "API token",
|
||||
"Webhook": "Webhook",
|
||||
"Preferences": "Preferences",
|
||||
"Teams": "Teams",
|
||||
"Users": "Users",
|
||||
"Drive": "Drive",
|
||||
"Branding": "Branding",
|
||||
"Mail": "Mail",
|
||||
"Storage": "Storage",
|
||||
"Signing certificate": "Signing certificate",
|
||||
"General": "General",
|
||||
"Organizations": "Organizations",
|
||||
"OrgAdmins": "OrgAdmins",
|
||||
"Debug Pdf": "Debug Pdf",
|
||||
"New Document": "New Document",
|
||||
"subscription": "subscription",
|
||||
"Draft document": "Draft document",
|
||||
"Draft template": "Draft template",
|
||||
"Public sign": "Public sign",
|
||||
"Signup": "Signup",
|
||||
"delete-contact": "Delete Contact",
|
||||
"total-records-found": "Total records found: {{count}}",
|
||||
"Invalid-records-found": "Invalid records found: {{records}}",
|
||||
"previous": "Previous",
|
||||
"page-n-of-n": "Page {{currentPage}} of {{totalPages}}",
|
||||
"import": "Import",
|
||||
"search": "Search",
|
||||
"viewed-on": "Viewed on: {{ViewedOn}}",
|
||||
"signed-on": "Signed on: {{SignedOn}}",
|
||||
"hide": "Hide",
|
||||
"show-more": "Show More",
|
||||
"browse-or-drag-to-replace-existing-file": "Browse or drag & drop a new file to replace the existing one",
|
||||
"optional-details": "Optional details",
|
||||
"mail-adapter-subscription-alert": "Please upgrade to Professional or Team plan to setup custom SMTP.",
|
||||
"connect-to-mail": "Connect to Gmail",
|
||||
"custom-smtp": "Custom SMTP",
|
||||
"default-smtp": "{{appName}} default SMTP",
|
||||
"host":"Host",
|
||||
"port":"Port",
|
||||
"sender-email": "Sender Email",
|
||||
"username": "Username",
|
||||
"use-default-mail-adapter": "Are you sure you want to use {{appName}}'s default mail servers to send your signature request emails? We recommend using your own Gmail or SMTP servers for improved inbox deliverability.",
|
||||
"verification-code-sent-registered-email": "A verification code has been sent to your registered email <1>{{useremail}}</1> confirm your settings. Please enter the code below.",
|
||||
"smpt-credentials": "SMTP Credentials"
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
"create-account": "Crear cuenta",
|
||||
"login": "Iniciar sesión",
|
||||
"language": "Idioma",
|
||||
"dark-mode": "Modo oscuro",
|
||||
"name": "Nombre",
|
||||
"phone": "Teléfono",
|
||||
"phone-optional": "opcional",
|
||||
@@ -25,6 +26,8 @@
|
||||
"Name": "Nombre",
|
||||
"Date": "Fecha"
|
||||
},
|
||||
"folder": "Carpeta",
|
||||
"pdf": "Pdf",
|
||||
"context-menu": {
|
||||
"Download": "Descargar",
|
||||
"Rename": "Renombrar",
|
||||
@@ -43,6 +46,10 @@
|
||||
"contact-now": "Contactar ahora",
|
||||
"upgrade-to": "Mejorar a",
|
||||
"plan": "Plan",
|
||||
"connect": "Conectar",
|
||||
"connect-to-g-drive": "Conectar con Google Drive",
|
||||
"reconnect-to-g-drive": "Volver a conectar con Google Drive",
|
||||
"gdrive-info-connect": "Cuando Google Drive está conectado, el documento completado se guardará en la carpeta {{appName}} de Google Drive.",
|
||||
"subscription-renew-warning": "Su suscripción vencerá en {{remainingDays}} días. Por favor, renueve su suscripción.",
|
||||
"subscribe-card-teamplan": "¡Libera todo el poder de la colaboración! Crea organizaciones, equipos y jerarquías ilimitadas. Comparte plantillas sin problemas entre equipos y asigna funciones de usuario personalizadas. ¡Mejora tu flujo de trabajo hoy mismo!",
|
||||
"subscribe-card-plan": "Desbloquea funciones premium desde solo {{premiumPrice}}/mes. Disfruta de un rendimiento mejorado y solo {{addonPrice}} por crédito adicional después de tus créditos premium incluidos.",
|
||||
@@ -57,7 +64,7 @@
|
||||
"welcome": "¡Bienvenido de nuevo!",
|
||||
"Login-to-your-account": "Ingresa a tu cuenta",
|
||||
"password": "Contraseña",
|
||||
"forgot-password": "¿Olvidaste la contraseña?",
|
||||
"forgot-password": "¿Olvidaste la contraseña",
|
||||
"loading": "Cargando...",
|
||||
"of": "de",
|
||||
"sign-SSO": "Ingresar con SSO",
|
||||
@@ -180,7 +187,9 @@
|
||||
"created-date": "Fecha de creación",
|
||||
"Type": "Tipo",
|
||||
"Logs": "Registros",
|
||||
"Expiry-date": "Date d'expiration"
|
||||
"Expiry-date": "Date d'expiration",
|
||||
"Company": "Compañía",
|
||||
"JobTitle": "Título del puesto"
|
||||
},
|
||||
"report-help": {
|
||||
"Draft Documents": "Estos son documentos que has iniciado pero no has finalizado para su envío.",
|
||||
@@ -192,16 +201,15 @@
|
||||
"Contactbook": "Esta es una lista de contactos/firmantes añadidos por ti. Aparecerán como sugerencias cuando intentes añadir firmantes a un nuevo documento.",
|
||||
"Templates": "Esta es una lista de plantillas que están a tu disposición para crear documentos. Puedes hacer clic en el botón «usar» para crear un nuevo documento utilizando una plantilla, modifica el documento y añade firmantes en el siguiente paso."
|
||||
},
|
||||
"form-name": {
|
||||
"Sign Yourself": "Firmar",
|
||||
"Request Signatures": "Solicitar firmas",
|
||||
"New Template": "Nueva plantilla"
|
||||
},
|
||||
"Sign Yourself": "Firmar",
|
||||
"Request Signatures": "Solicitar firmas",
|
||||
"New Template": "Nueva plantilla",
|
||||
"file-type": "pdf, png, jpg, jpeg",
|
||||
"docx": "docx",
|
||||
"file-selected": "archivo seleccionado",
|
||||
"template-title": "Título de la plantilla",
|
||||
"document-title": "Título del documento",
|
||||
"title": "Título",
|
||||
"description": "Descripción",
|
||||
"time-to-complete": "Plazo de finalización (días)",
|
||||
"send-in-order": "Enviar en orden",
|
||||
@@ -362,6 +370,7 @@
|
||||
"date": "fecha",
|
||||
"text": "texto",
|
||||
"text input": "entrada de texto",
|
||||
"cells": "células",
|
||||
"checkbox": "casilla",
|
||||
"dropdown": "desplegable",
|
||||
"radio button": "botón de radio",
|
||||
@@ -378,6 +387,7 @@
|
||||
"certificate": "Certificado",
|
||||
"decline": "Rechazar",
|
||||
"finish": "Finalizar",
|
||||
"done": "Hecho",
|
||||
"mail": "Correo",
|
||||
"sign-now": "Firmar ahora",
|
||||
"successfully-signed": "¡Firmado exitosamente!",
|
||||
@@ -387,6 +397,10 @@
|
||||
"Email-verified-alert-1": "El correo está verificado.",
|
||||
"Email-verified-alert-2": "El correo ya ha sido verificado.",
|
||||
"upload-stamp-image": "Subir imagen del sello",
|
||||
"draw-signature": "Dibujar firma",
|
||||
"draw-initials": "Dibujar iniciales",
|
||||
"enter-text": "Introducir texto",
|
||||
"enter-widgettype": "Introduzca {{widgetType}}",
|
||||
"draw": "Dibujar",
|
||||
"type": "Escribir",
|
||||
"color-type": {
|
||||
@@ -407,6 +421,7 @@
|
||||
"reset-password-alert-3": "Restablece tu contraseña",
|
||||
"faild-animation": "Error al cargar la animación",
|
||||
"apply": "Aplicar",
|
||||
"select-columns": "Seleccionar columnas",
|
||||
"copy-type": {
|
||||
"All pages": "Todas las páginas",
|
||||
"All pages but last": "Todas las páginas menos la última",
|
||||
@@ -416,10 +431,12 @@
|
||||
"options": "Opciones",
|
||||
"minimun-check": "Chequeo mínimo",
|
||||
"maximum-check": "Chequeo máximo",
|
||||
"cell-count": "recuento de células",
|
||||
"default-value": "Valor por defecto",
|
||||
"select": "Seleccionar",
|
||||
"read-only": "Es de solo lectura",
|
||||
"hide-labels": "Esconder etiquetas",
|
||||
"layout": "Diseño",
|
||||
"checkbox": "Casilla",
|
||||
"alert": "Alerta",
|
||||
"zoom-in": "Acercar",
|
||||
@@ -467,7 +484,7 @@
|
||||
"placeholder-alert-3": " ¿En definitiva quieres enviar este documento para ser firmado?",
|
||||
"placeholder-alert-4": "¡Has enviado exitosamente correos a todos los destinatarios!",
|
||||
"placeholder-mail-alert": "Has enviado un correo electrónico con éxito a {{name}}. Los siguientes firmantes recibirán un correo electrónico una vez que {{name}} firme el documento.",
|
||||
"placeholder-mail-alert-you": "Los firmantes posteriores recibirán correos electrónicos una vez que firme el documento.",
|
||||
"placeholder-mail-alert-you": "Los firmantes siguientes recibirán un correo electrónico una vez que usted firme el documento.",
|
||||
"placeholder-alert-5": "¿Quieres firmar documentos ahora mismo?",
|
||||
"placeholder-alert-6": "¡Por favor, configura el adaptador de correo para enviar correos!",
|
||||
"placeholder-alert-7": "¡Por favor, selecciona un firmante para agregar un marcador de posición!",
|
||||
@@ -724,7 +741,7 @@
|
||||
},
|
||||
"form-title-1": "Configuración del flujo de documentos",
|
||||
"form-title-2": "Configuración de seguridad",
|
||||
"text-field-tour": "Los campos de tipo 'Texto' deben completarse con anticipación antes de enviar el documento. Si necesita que los firmantes proporcionen información, utilice el campo 'Entrada de texto'",
|
||||
"text-field-tour": "Los campos de 'Rellenado previo' deben completarse antes de enviar el documento. Si necesita que los firmantes proporcionen información, utilice los campos de firmantes.",
|
||||
"attach-signer-tour": "Debe adjuntar un firmante a cada función. Puede hacerlo haciendo clic en este icono. Una vez que seleccione un Firmante, se adjuntará a todos los campos asociados con ese rol que aparecen en el mismo color.",
|
||||
"allowed-signature-types": "Tipos de firma permitidos",
|
||||
"at-least-one-signature-type": "Se debe habilitar al menos un tipo de firma.",
|
||||
@@ -750,12 +767,15 @@
|
||||
"delete-page": "eliminar página",
|
||||
"merge-pdf": "fusionar pdf",
|
||||
"add-pages": "Agregar páginas",
|
||||
"reorder-pages": "Reordenar páginas",
|
||||
"delete-alert": "No se puede eliminar una sola página",
|
||||
"delete-alert-2": "¿Está seguro de que desea eliminar esta página?",
|
||||
"delete-note": "Nota: una vez que elimines esta página, no podrás deshacerla",
|
||||
"Rotation-alert": "Girar página",
|
||||
"bulk-import": "Importación masiva",
|
||||
"contacts-file": "Archivo de contactos (xlsx, csv)",
|
||||
"import-guideline": "Sube un archivo CSV o Excel con las columnas Name, Email y opcionalmente Phone. Solo se importarán los primeros 100 contactos.",
|
||||
"download-sample": "Descargar archivo de ejemplo",
|
||||
"100-records-only": "Currently you can only import up to 100 records.",
|
||||
"csv-excel-support-only": "Cargue un archivo en uno de los siguientes formatos: CSV, XLSX o XLS.",
|
||||
"contact-imported": "Se importaron {{imported}} contactos. {{failed}} contactos no pudieron importarse.",
|
||||
@@ -769,7 +789,7 @@
|
||||
"agree-p1": "Confirmo que he leído y comprendido el ",
|
||||
"agree-p2": "Registro Electrónico y Divulgación de Firma",
|
||||
"agree-p3": "y consentimiento para utilizar registros y firmas electrónicas.",
|
||||
"agrre-button": "Aceptar y continuar",
|
||||
"agrre-button": "Confirmo y acepto continuar",
|
||||
"term-cond-title": "Términos y condiciones",
|
||||
"term-cond-h": "DIVULGACIÓN DE REGISTRO ELECTRÓNICO Y FIRMA",
|
||||
"term-cond-p1": "Esta Divulgación de Firma y Registro Electrónico ('Divulgación') es un acuerdo entre el Creador del Documento ('Remitente') y el Firmante ('Usted'), facilitado a través de la plataforma {{appName}} ('Plataforma'). Al firmar documentos a través de {{appName}}, usted acepta los términos descritos en esta Divulgación. Léala detenidamente antes de continuar.",
|
||||
@@ -982,6 +1002,7 @@
|
||||
"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",
|
||||
@@ -1022,5 +1043,104 @@
|
||||
"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"
|
||||
"not-found-in-signature": "No encontrado en la firma",
|
||||
"readonly-error": "El widget de solo lectura {{widgetName}} debe tener un valor predeterminado o puede hacerlo opcional.",
|
||||
"choose-one":"Elige uno",
|
||||
"search-templates": "Buscar plantillas…",
|
||||
"search-documents": "Buscar documentos…",
|
||||
"search-contacts": "Buscar contactos…",
|
||||
"invalid-email-found": "Correo electrónico no válido encontrado: {{email}}",
|
||||
"duplicate-email-found": "Correo electrónico duplicado encontrado: {{email}}",
|
||||
"vertical": "Vertical",
|
||||
"add-role-alert": "Por favor, agregue al menos un rol",
|
||||
"edit-draft": "Editar borrador",
|
||||
"horizontal": "Horizontal",
|
||||
"billing": "Facturación",
|
||||
"console": "Consola",
|
||||
"prefill-widget": "Widgets de Relleno Previo",
|
||||
"action-prohibited": "Esta acción no está permitida para su dominio de correo electrónico. Por favor, contacte con su administrador para obtener ayuda.",
|
||||
"must-have-at-least-one-vacant-role": "Debe dejar al menos un rol sin asignar antes de establecer un template como 'public'.",
|
||||
"remove-duplicate": "Por favor, elimina la opción duplicada",
|
||||
"prefill-bulk-error": "El envío masivo no está permitido cuando se han agregado widgets de pre-rellenado. Por favor, elimine los widgets de pre-rellenado para continuar.",
|
||||
"session-expired-title": "Sesión expirada",
|
||||
"access-denied": "Acceso denegado",
|
||||
"upgrade": "Actualizar",
|
||||
"do-not-access-app": "No tienes acceso a esta aplicación.",
|
||||
"dont-have-access": "No tienes acceso.",
|
||||
"valid-email-alert": "Por favor ingresa una dirección de correo electrónico válida.",
|
||||
"otp-not-validate": "OTP no es válido.",
|
||||
"domain-not-allowed": "Este dominio no está permitido",
|
||||
"atleast-one-recipient-alert": "¡Por favor agrega al menos un destinatario!",
|
||||
"incorrect-password-or-decryption-failed": "Contraseña incorrecta o fallo de descifrado.",
|
||||
"incorrect-password-for-file": "Contraseña incorrecta para el archivo: {{file}}",
|
||||
"error-uploading-pdf": "Error al subir PDF.",
|
||||
"provide-password": "Por favor proporciona la contraseña.",
|
||||
"only-pdf-allowed": "Solo se permiten archivos PDF.",
|
||||
"invalid-username-password-region": "Usuario/contraseña o región inválidos.",
|
||||
"pfx-extension-alert": "Por favor sube un archivo con extensión .pfx.",
|
||||
"email-already-exist": "El correo electrónico ya existe",
|
||||
"branding": "Marca",
|
||||
"branding-help": "El branding permite el white labelling de su aplicación",
|
||||
"custom-sub-domain": "Subdominio personalizado",
|
||||
"app-name": "Nombre de la aplicación",
|
||||
"provide-domain-name": "Proporcione su nombre de dominio",
|
||||
"provide-app-name": "Proporcione el nombre de su aplicación",
|
||||
"logo": "Logo",
|
||||
"upload-app-logo": "Suba el logotipo de su aplicación",
|
||||
"prefill-unfilled-widget": "Los siguientes campos obligatorios no pueden quedar vacíos: {{emptyWidget}}. Por favor, complételos para continuar.",
|
||||
"Dashboard": "Panel",
|
||||
"Analytics": "Analítica",
|
||||
"Templates": "Plantillas",
|
||||
"Need your sign": "Necesitan tu firma",
|
||||
"In Progress": "En progreso",
|
||||
"Completed": "Completado",
|
||||
"Drafts": "Borradores",
|
||||
"Declined": "Rechazados",
|
||||
"Expired": "Expirados",
|
||||
"Contactbook": "Agenda de contactos",
|
||||
"My Signature": "Mi firma",
|
||||
"API Token": "Token API",
|
||||
"Webhook": "Webhook",
|
||||
"Preferences": "Preferencias",
|
||||
"Teams": "Equipos",
|
||||
"Users": "Usuarios",
|
||||
"Drive": "Drive",
|
||||
"Branding": "Marca",
|
||||
"Mail": "Correo",
|
||||
"Storage": "Almacenamiento",
|
||||
"Signing certificate": "Certificado de firma",
|
||||
"General": "General",
|
||||
"Organizations": "Organizaciones",
|
||||
"OrgAdmins": "OrgAdmins",
|
||||
"Debug Pdf": "Depurar PDF",
|
||||
"New Document": "Nuevo documento",
|
||||
"subscription": "Suscripción",
|
||||
"Draft document": "Borrador de documento",
|
||||
"Draft template": "Borrador de plantilla",
|
||||
"Public sign": "Firma pública",
|
||||
"Signup": "Registrarse",
|
||||
"delete-contact": "Eliminar contacto",
|
||||
"total-records-found": "Total de registros encontrados: {{count}}",
|
||||
"Invalid-records-found": "Registros inválidos encontrados: {{records}}",
|
||||
"previous": "Anterior",
|
||||
"page-n-of-n": "Página {{currentPage}} de {{totalPages}}",
|
||||
"import": "Importar",
|
||||
"search": "Buscar",
|
||||
"viewed-on": "Visto el: {{ViewedOn}}",
|
||||
"signed-on": "Firmado el: {{SignedOn}}",
|
||||
"hide": "Ocultar",
|
||||
"show-more": "Mostrar más",
|
||||
"browse-or-drag-to-replace-existing-file": "Busque o arrastre y suelte un nuevo archivo para reemplazar el existente",
|
||||
"optional-details": "Detalles opcionales",
|
||||
"mail-adapter-subscription-alert": "Bitte upgraden Sie auf den Professional- oder Team-Plan, um ein benutzerdefiniertes SMTP einzurichten.",
|
||||
"connect-to-mail": "Conectar con Gmail",
|
||||
"custom-smtp": "SMTP personalizado",
|
||||
"default-smtp": "SMTP predeterminado de {{appName}}",
|
||||
"host": "Host",
|
||||
"port": "Puerto",
|
||||
"sender-email": "Correo del remitente",
|
||||
"username": "Nombre de usuario",
|
||||
"use-default-mail-adapter": "¿Está seguro de que desea usar los servidores de correo predeterminados de {{appName}} para enviar solicitudes de firma? Recomendamos usar su propio servidor de Gmail o SMTP para una mejor entregabilidad.",
|
||||
"verification-code-sent-registered-email": "Se ha enviado un código de verificación a su correo registrado <1>{{useremail}}</1>. Ingrese el código a continuación para confirmar su configuración.",
|
||||
"smpt-credentials": "Credenciales SMTP"
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"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)",
|
||||
@@ -25,6 +26,8 @@
|
||||
"Name": "Nom et Prénom",
|
||||
"Date": "Date"
|
||||
},
|
||||
"folder": "Dossier",
|
||||
"pdf": "Pdf",
|
||||
"context-menu": {
|
||||
"Download": "Télécharger",
|
||||
"Rename": "Renommer",
|
||||
@@ -44,6 +47,10 @@
|
||||
"upgrade-to": "Mettre à niveau vers",
|
||||
"pro": "PRO",
|
||||
"plan": "Offre",
|
||||
"connect": "Connecter",
|
||||
"connect-to-g-drive": "Se connecter à Google Drive",
|
||||
"reconnect-to-g-drive": "Se reconnecter à Google Drive",
|
||||
"gdrive-info-connect": "Lorsque Google Drive est connecté, le document complété sera enregistré dans le dossier {{appName}} sur Google Drive.",
|
||||
"subscription-renew-warning": "Votre abonnement expirera dans {{remainingDays}} jours. Veuillez renouveler votre abonnement.",
|
||||
"subscribe-card-teamplan": "Libérez toute la puissance de la collaboration ! Créez un nombre illimité d'organisations, d'équipes et de hiérarchies. Partagez des modèles de manière transparente entre les équipes et attribuez des rôles d'utilisateur personnalisés. Améliorez votre flux de travail dès aujourd'hui !",
|
||||
"subscribe-card-plan": "Débloquez des fonctionnalités premium à partir de seulement {{premiumPrice}}/mois. Bénéficiez de performances améliorées et de seulement {{addonPrice}} par crédit supplémentaire après vos crédits premium inclus.",
|
||||
@@ -57,7 +64,7 @@
|
||||
"welcome": "Content de te revoir!",
|
||||
"Login-to-your-account": "Se connecter à son compte",
|
||||
"password": "Mot de passe",
|
||||
"forgot-password": "Mot de passe oublié?",
|
||||
"forgot-password": "Mot de passe oublié",
|
||||
"loading": "Chargement...",
|
||||
"of": "de",
|
||||
"sign-SSO": "Connectez-vous avec SSO",
|
||||
@@ -154,7 +161,9 @@
|
||||
"created-date": "Date de création",
|
||||
"Type": "Saisir",
|
||||
"Logs": "Journaux",
|
||||
"Expiry-date": "Fecha de caducidad"
|
||||
"Expiry-date": "Fecha de caducidad",
|
||||
"Company": "Organisation",
|
||||
"JobTitle": "Votre Fonction"
|
||||
},
|
||||
"btnLabel": {
|
||||
"sign": "Signer",
|
||||
@@ -191,16 +200,15 @@
|
||||
"Contactbook": "Il s'agit d'une liste de contacts/signataires que vous avez ajoutés. Ceux-ci apparaîtront sous forme de suggestions lorsque vous tenterez d'ajouter des signataires à un nouveau document.",
|
||||
"Templates": "Il s'agit d'une liste de modèles à votre disposition pour créer des documents. Vous pouvez cliquer sur le bouton 'Utiliser' pour créer un nouveau document à l'aide d'un modèle, modifier le document et ajouter des signataires à l'étape suivante."
|
||||
},
|
||||
"form-name": {
|
||||
"Sign Yourself": "signez vous-même",
|
||||
"Request Signatures": "Demander des signatures",
|
||||
"New Template": "Nouveau modèle"
|
||||
},
|
||||
"Sign Yourself": "signez vous-même",
|
||||
"Request Signatures": "Demander des signatures",
|
||||
"New Template": "Nouveau modèle",
|
||||
"file-type": "pdf, png, jpg, jpeg",
|
||||
"docx": "docx",
|
||||
"file-selected": "fichier sélectionné",
|
||||
"template-title": "Titre du modèle",
|
||||
"document-title": "Titre du document",
|
||||
"title": "Titre",
|
||||
"description": "Description",
|
||||
"time-to-complete": "Temps de réalisation (jours)",
|
||||
"send-in-order": "Envoyer dans l'ordre ?",
|
||||
@@ -361,6 +369,7 @@
|
||||
"date": "date",
|
||||
"text": "texte",
|
||||
"text input": "saisie de texte",
|
||||
"cells": "cellules",
|
||||
"checkbox": "case à cocher",
|
||||
"dropdown": "dérouler",
|
||||
"radio button": "bouton radio",
|
||||
@@ -377,6 +386,7 @@
|
||||
"certificate": "Certificat",
|
||||
"decline": "refusé",
|
||||
"finish": "terminé",
|
||||
"done": "Terminé",
|
||||
"mail": "Mail",
|
||||
"sign-now": "Signez maintenant",
|
||||
"successfully-signed": "Signé avec succès !",
|
||||
@@ -386,6 +396,10 @@
|
||||
"Email-verified-alert-1": "L'e-mail est vérifié.",
|
||||
"Email-verified-alert-2": "L'e-mail est déjà vérifié.",
|
||||
"upload-stamp-image": "Télécharger l'image du tampon",
|
||||
"draw-signature": "Dessiner la signature",
|
||||
"draw-initials": "Dessiner les initiales",
|
||||
"enter-text": "Saisir du texte",
|
||||
"enter-widgettype": "Saisir {{widgetType}}",
|
||||
"draw": "Dessiner",
|
||||
"type": "Taper",
|
||||
"color-type": {
|
||||
@@ -415,10 +429,12 @@
|
||||
"options": "Possibilités",
|
||||
"minimun-check": "Vérification minimale",
|
||||
"maximum-check": "Contrôle maximum",
|
||||
"cell-count": "numération cellulaire",
|
||||
"default-value": "Valeur par défaut",
|
||||
"select": "Sélectionner",
|
||||
"read-only": "Est en lecture seule",
|
||||
"hide-labels": "Masquer les étiquettes",
|
||||
"layout": "Disposition",
|
||||
"checkbox": "Case à cocher",
|
||||
"alert": "Alerte",
|
||||
"zoom-in": "Agrandir",
|
||||
@@ -467,7 +483,7 @@
|
||||
"placeholder-alert-3": "Etes-vous sûr de vouloir envoyer ce document pour signature ? ",
|
||||
"placeholder-alert-4": "Vous avez envoyé avec succès des mails à tous les",
|
||||
"placeholder-mail-alert": "Vous avez envoyé un e-mail avec succès à {{name}}. Les signataires suivants recevront un e-mail une fois que {{name}} aura signé le document.",
|
||||
"placeholder-mail-alert-you": "Les signataires suivants recevront un e-mail dès que vous signez le document.",
|
||||
"placeholder-mail-alert-you": "Les signataires suivants recevront un e-mail une fois que vous aurez signé le document.",
|
||||
"placeholder-alert-5": "Voulez-vous signer des documents maintenant ?",
|
||||
"placeholder-alert-6": "Veuillez configurer l'adaptateur de messagerie pour envoyer du courrier !",
|
||||
"placeholder-alert-7": "Veuillez sélectionner le signataire pour ajouter un espace réservé !",
|
||||
@@ -692,7 +708,7 @@
|
||||
"quota-mail-info": "Pour maintenir la qualité du service et prévenir le spam, OpenSign permet jusqu'à 15 e-mails par mois avec le plan gratuit. Passez à l'offre supérieure pour un envoi illimité d'e-mails.",
|
||||
"quota-mail-reset": "Les crédits de votre email de demande de signature seront réinitialisés le",
|
||||
"quota-mail": "Vous avez atteint votre limite de 15 e-mails de demande de signature pour ce mois. Mettez à niveau maintenant pour continuer à envoyer des e-mails directement.",
|
||||
"quota-mail-tip-tip": "Astuce: Vous pouvez toujours signer un nombre <1>illimité de documents</1> en partageant manuellement le lien de demande de signature.",
|
||||
"quota-mail-tip": "Astuce: Vous pouvez toujours signer un nombre <1>illimité de documents</1> en partageant manuellement le lien de demande de signature.",
|
||||
"quota-mail-head": "Quota atteint",
|
||||
"unauthorized-modal": "Vous n'êtes pas autorisé à effectuer cette action, veuillez contacter {{adminEmail}}.",
|
||||
"sent-this-month": "envoyé ce mois-ci",
|
||||
@@ -724,7 +740,7 @@
|
||||
"public-tour-message": "Le modèle doit être public avant que vous puissiez générer un lien partageable.",
|
||||
"add-user-template": "Vous devez ajouter un rôle avant de pouvoir lui ajouter des champs. ",
|
||||
"pdf-uncompatible": "PDF n'est pas compatible, veuillez contacter {{appName}}",
|
||||
"text-field-tour": "Les champs de type 'Texte' doivent être remplis à l'avance avant l'envoi du document. Si vous avez besoin que les signataires fournissent des informations, utilisez plutôt le champ 'Saisie de texte'.",
|
||||
"text-field-tour": "Les champs 'Pré-remplissage' doivent être remplis avant l'envoi du document. Si vous avez besoin de l'intervention des signataires, utilisez plutôt les champs réservés aux signataires.",
|
||||
"attach-signer-tour": "Vous devez associer un signataire à chaque rôle. Vous pouvez le faire en cliquant sur cette icône. Une fois que vous avez sélectionné un signataire, il sera attaché à tous les champs associés à ce rôle qui apparaissent dans la même couleur.",
|
||||
"allowed-signature-types": "Types de signature autorisés",
|
||||
"at-least-one-signature-type": "Au moins un type de signature doit être activé.",
|
||||
@@ -750,12 +766,15 @@
|
||||
"delete-page": "supprimer la page",
|
||||
"merge-pdf": "Fusionner le pdf",
|
||||
"add-pages": "Ajouter des pages",
|
||||
"reorder-pages": "Réorganiser les pages",
|
||||
"delete-alert": "Impossible de supprimer une seule page",
|
||||
"delete-alert-2": "Etes-vous sûr de vouloir supprimer cette page ?",
|
||||
"delete-note": "Remarque : une fois cette page supprimée, vous ne pouvez plus l'annuler",
|
||||
"Rotation-alert": "Faire pivoter la page",
|
||||
"bulk-import": "Importation en masse",
|
||||
"contacts-file": "Fichier de contacts (xlsx, csv)",
|
||||
"import-guideline": "Téléchargez un fichier CSV ou Excel avec les colonnes Name, Email et éventuellement Phone. Seuls les 100 premiers contacts seront importés.",
|
||||
"download-sample": "Télécharger un fichier d'exemple",
|
||||
"100-records-only": "Actuellement, vous ne pouvez importer que 100 enregistrements.",
|
||||
"csv-excel-support-only": "Veuillez télécharger un fichier dans l'un des formats suivants : CSV, XLSX ou XLS.",
|
||||
"contact-imported": "{{imported}} contacts ont été importés. {{failed}} contacts n'ont pas pu être importés.",
|
||||
@@ -769,7 +788,7 @@
|
||||
"agree-p1": "Je confirme avoir lu et compris les ",
|
||||
"agree-p2": "Divulgation des enregistrements électroniques et des signatures",
|
||||
"agree-p3": "et consentez à l'utilisation d'enregistrements et de signatures électroniques.",
|
||||
"agrre-button": " Accepter et continuer ",
|
||||
"agrre-button": "Je confirme et j'accepte de continuer",
|
||||
"term-cond-title": "Termes et conditions",
|
||||
"term-cond-h": "DIVULGATION D'ENREGISTREMENT ÉLECTRONIQUE ET DE SIGNATURE",
|
||||
"term-cond-p1": "Cette divulgation d'enregistrement électronique et de signature (' Divulgation ') est un accord entre le créateur du document (' Expéditeur ') et le signataire (' Vous '), facilité via la plateforme {{appName}} ( Plateforme ). En signant des documents via {{appName}}, vous acceptez les conditions décrites dans cette divulgation. Veuillez la lire attentivement avant de continuer.",
|
||||
@@ -982,6 +1001,7 @@
|
||||
"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é",
|
||||
@@ -1022,5 +1042,104 @@
|
||||
"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"
|
||||
"not-found-in-signature": "Introuvable dans la signature",
|
||||
"readonly-error": "Le widget en lecture seule {{widgetName}} doit avoir une valeur par défaut ou vous pouvez le rendre facultatif.",
|
||||
"choose-one":"Choisissez-en un",
|
||||
"search-templates": "Rechercher des modèles…",
|
||||
"search-documents": "Rechercher des documents…",
|
||||
"search-contacts": "Rechercher des contacts…",
|
||||
"add-role-alert": "Veuillez ajouter au moins un rôle",
|
||||
"edit-draft": "Modifier le brouillon",
|
||||
"invalid-email-found": "Adresse e-mail invalide trouvée : {{email}}",
|
||||
"duplicate-email-found": "Adresse e-mail en double trouvée : {{email}}",
|
||||
"vertical": "Vertical",
|
||||
"horizontal": "Horizontal",
|
||||
"billing": "Facturation",
|
||||
"console": "Console",
|
||||
"prefill-widget": "Widgets de Préremplissage",
|
||||
"action-prohibited": "Cette action n'est pas autorisée pour votre domaine de messagerie. Veuillez contacter votre administrateur pour obtenir de l'aide.",
|
||||
"must-have-at-least-one-vacant-role": "Vous devez laisser au moins un rôle non attribué avant de définir un template comme 'public'.",
|
||||
"remove-duplicate": "Veuillez supprimer l'option en double",
|
||||
"prefill-bulk-error": "L'envoi en masse n'est pas autorisé lorsque des widgets de pré-remplissage sont ajoutés. Veuillez supprimer les widgets de pré-remplissage pour continuer.",
|
||||
"session-expired-title": "Session expirée",
|
||||
"access-denied": "Accès refusé",
|
||||
"upgrade": "Mettre à jour",
|
||||
"do-not-access-app": "Vous n'avez pas accès à cette application.",
|
||||
"dont-have-access": "Vous n'avez pas accès.",
|
||||
"valid-email-alert": "Veuillez saisir une adresse e-mail valide.",
|
||||
"otp-not-validate": "OTP non valide.",
|
||||
"domain-not-allowed": "Ce domaine n'est pas autorisé",
|
||||
"atleast-one-recipient-alert": "Veuillez ajouter au moins un destinataire !",
|
||||
"incorrect-password-or-decryption-failed": "Mot de passe incorrect ou échec du déchiffrement.",
|
||||
"incorrect-password-for-file": "Mot de passe incorrect pour le fichier : {{file}}",
|
||||
"error-uploading-pdf": "Erreur lors du téléchargement du PDF.",
|
||||
"provide-password": "Veuillez fournir le mot de passe.",
|
||||
"only-pdf-allowed": "Seuls les fichiers PDF sont autorisés.",
|
||||
"invalid-username-password-region": "Nom d'utilisateur/mot de passe ou région invalide.",
|
||||
"pfx-extension-alert": "Veuillez télécharger un fichier avec l'extension .pfx.",
|
||||
"email-already-exist": "L'e-mail existe déjà",
|
||||
"branding": "Image de marque",
|
||||
"branding-help": "Le branding permet le white labelling de votre application",
|
||||
"custom-sub-domain": "Sous-domaine personnalisé",
|
||||
"app-name": "Nom de l'application",
|
||||
"provide-domain-name": "Fournissez votre nom de domaine",
|
||||
"provide-app-name": "Fournissez le nom de votre application",
|
||||
"logo": "Logo",
|
||||
"upload-app-logo": "Téléchargez le logo de votre application",
|
||||
"prefill-unfilled-widget": "Les champs obligatoires suivants ne peuvent pas être vides : {{emptyWidget}}. Veuillez les remplir pour continuer.",
|
||||
"Dashboard": "Tableau de bord",
|
||||
"Analytics": "Analytique",
|
||||
"Templates": "Modèles",
|
||||
"Need your sign": "Besoin de votre signature",
|
||||
"In Progress": "En cours",
|
||||
"Completed": "Complété",
|
||||
"Drafts": "Brouillons",
|
||||
"Declined": "Signature refusé",
|
||||
"Expired": "Expiré",
|
||||
"Contactbook": "Carnet de contacts",
|
||||
"My Signature": "Ma signature",
|
||||
"API Token": "Jeton API",
|
||||
"Webhook": "Webhook",
|
||||
"Preferences": "Préférences",
|
||||
"Teams": "Équipe",
|
||||
"Users": "Utilisateurs",
|
||||
"Drive": "Lecteur",
|
||||
"Branding": "Image de marque",
|
||||
"Mail": "Courrier",
|
||||
"Storage": "Stockage",
|
||||
"Signing certificate": "Certificat de signature",
|
||||
"General": "Général",
|
||||
"Organizations": "Organisations",
|
||||
"OrgAdmins": "OrgAdmins",
|
||||
"Debug Pdf": "Déboguer le PDF",
|
||||
"New Document": "Nouveau document",
|
||||
"subscription": "Abonnement",
|
||||
"Draft document": "Brouillon de document",
|
||||
"Draft template": "Brouillon de modèle",
|
||||
"Public sign": "Signature publique",
|
||||
"Signup": "Inscription",
|
||||
"delete-contact": "Supprimer le contact",
|
||||
"total-records-found": "Nombre total d'enregistrements trouvés : {{count}}",
|
||||
"Invalid-records-found": "Enregistrements invalides trouvés : {{records}}",
|
||||
"previous": "Précédent",
|
||||
"page-n-of-n": "Page {{currentPage}} sur {{totalPages}}",
|
||||
"import": "Importer",
|
||||
"search": "Rechercher",
|
||||
"viewed-on": "Consulté le : {{ViewedOn}}",
|
||||
"signed-on": "Signé le : {{SignedOn}}",
|
||||
"hide": "Masquer",
|
||||
"show-more": "Afficher plus",
|
||||
"browse-or-drag-to-replace-existing-file": "Parcourez ou faites glisser un nouveau fichier pour remplacer l'existant",
|
||||
"optional-details": "Détails facultatifs",
|
||||
"mail-adapter-subscription-alert": "Veuillez passer au plan Professionnel ou Équipe pour configurer un SMTP personnalisé.",
|
||||
"connect-to-mail": "Se connecter à Gmail",
|
||||
"custom-smtp": "SMTP personnalisé",
|
||||
"default-smtp": "SMTP par défaut de {{appName}}",
|
||||
"host": "Hôte",
|
||||
"port": "Port",
|
||||
"sender-email": "Email de l'expéditeur",
|
||||
"username": "Nom d'utilisateur",
|
||||
"use-default-mail-adapter": "Voulez-vous vraiment utiliser les serveurs de messagerie par défaut de {{appName}} pour envoyer vos demandes de signature ? Nous vous recommandons d'utiliser votre propre serveur Gmail ou SMTP pour une meilleure délivrabilité.",
|
||||
"verification-code-sent-registered-email": "Un code de vérification a été envoyé à votre email enregistré <1>{{useremail}}</1>. Veuillez entrer le code ci-dessous pour confirmer vos paramètres.",
|
||||
"smpt-credentials": "Identifiants SMTP"
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"create-account": "खाता बनाएं",
|
||||
"login": "लॉग इन करें",
|
||||
"language": "भाषा",
|
||||
"dark-mode": "डार्क मोड",
|
||||
"name": "नाम",
|
||||
"phone": "फ़ोन",
|
||||
"phone-optional": "वैकल्पिक",
|
||||
@@ -25,6 +26,8 @@
|
||||
"Name": "नाम",
|
||||
"Date": "दिनांक"
|
||||
},
|
||||
"folder": "फ़ोल्डर",
|
||||
"pdf": "पीडीएफ़",
|
||||
"context-menu": {
|
||||
"Download": "डाउनलोड करें",
|
||||
"Rename": "नाम बदलें",
|
||||
@@ -43,6 +46,10 @@
|
||||
"contact-now": "अभी संपर्क करें",
|
||||
"upgrade-to": "इसमें अपग्रेड करें",
|
||||
"plan": "योजना",
|
||||
"connect": "कनेक्ट करें",
|
||||
"connect-to-g-drive": "Google Drive से कनेक्ट करें",
|
||||
"reconnect-to-g-drive": "Google Drive से फिर से कनेक्ट करें",
|
||||
"gdrive-info-connect": "जब Google Drive जुड़ा होता है, तो पूर्ण दस्तावेज़ {{appName}} फ़ोल्डर में Google Drive पर सहेजा जाएगा।",
|
||||
"subscription-renew-warning": "आपकी सदस्यता {{remainingDays}} दिनों में समाप्त हो जाएगी। कृपया अपनी सदस्यता नवीनीकृत करें।",
|
||||
"subscribe-card-teamplan": "सहयोग की पूरी शक्ति को अनलॉक करें! असीमित संगठन, टीम और पदानुक्रम बनाएं। टीमों में टेम्पलेट को निर्बाध रूप से साझा करें और कस्टम उपयोगकर्ता भूमिकाएँ असाइन करें। आज ही अपने वर्कफ़्लो को उन्नत करें!",
|
||||
"subscribe-card-plan": "केवल {{premiumPrice}}/माह से शुरू होने वाली प्रीमियम सुविधाओं को अनलॉक करें। अपने शामिल प्रीमियम क्रेडिट के बाद प्रति अतिरिक्त क्रेडिट केवल {{addonPrice}} पर बढ़ी हुई प्रदर्शन का आनंद लें।",
|
||||
@@ -57,7 +64,7 @@
|
||||
"welcome": "वापसी पर स्वागत है!",
|
||||
"Login-to-your-account": "अपने खाते में प्रवेश करे",
|
||||
"password": "पासवर्ड",
|
||||
"forgot-password": "पासवर्ड भूल गए?",
|
||||
"forgot-password": "पासवर्ड भूल गए",
|
||||
"loading": "लोड हो रहा है...",
|
||||
"of": "का",
|
||||
"sign-SSO": "SSO के साथ साइन इन करें",
|
||||
@@ -180,7 +187,9 @@
|
||||
"created-date": "निर्माण तिथि",
|
||||
"Type": "प्रकार",
|
||||
"Logs": "लॉग",
|
||||
"Expiry-date": "समाप्ति तिथि"
|
||||
"Expiry-date": "समाप्ति तिथि",
|
||||
"Company": "कंपनी",
|
||||
"JobTitle": "पद"
|
||||
},
|
||||
"report-help": {
|
||||
"Draft Documents": "ये वे दस्तावेज़ हैं जिन्हें आपने शुरू तो किया है लेकिन भेजने के लिए अंतिम रूप नहीं दिया है।",
|
||||
@@ -192,16 +201,15 @@
|
||||
"Contactbook": "यह आपके द्वारा जोड़े गए संपर्कों/हस्ताक्षरकर्ताओं की सूची है। जब आप किसी नए दस्तावेज़ में हस्ताक्षरकर्ताओं को जोड़ने का प्रयास करेंगे तो ये सुझावों के रूप में दिखाई देंगे।",
|
||||
"Templates": "यह उन टेम्पलेट्स की सूची है जो दस्तावेज़ बनाने के लिए आपके लिए उपलब्ध हैं। आप टेम्पलेट का उपयोग करके एक नया दस्तावेज़ बनाने के लिए 'उपयोग करें' बटन पर क्लिक कर सकते हैं, दस्तावेज़ को संशोधित कर सकते हैं और अगले चरण में हस्ताक्षरकर्ताओं को जोड़ सकते हैं।"
|
||||
},
|
||||
"form-name": {
|
||||
"Sign Yourself": "स्वयं हस्ताक्षर करें",
|
||||
"Request Signatures": "हस्ताक्षर का अनुरोध करें",
|
||||
"New Template": "नया टेम्पलेट"
|
||||
},
|
||||
"Sign Yourself": "स्वयं हस्ताक्षर करें",
|
||||
"Request Signatures": "हस्ताक्षर का अनुरोध करें",
|
||||
"New Template": "नया टेम्पलेट",
|
||||
"file-type": "pdf, png, jpg, jpeg",
|
||||
"docx": "docx",
|
||||
"file-selected": "फ़ाइल चयनित",
|
||||
"template-title": "टेम्पलेट शीर्षक",
|
||||
"document-title": "दस्तावेज़ शीर्षक",
|
||||
"title": "शीर्षक",
|
||||
"description": "विवरण",
|
||||
"time-to-complete": "पूरा करने का समय (दिन)",
|
||||
"send-in-order": "क्रम से भेजें",
|
||||
@@ -354,13 +362,14 @@
|
||||
"widgets-name": {
|
||||
"signature": "हस्ताक्षर",
|
||||
"stamp": "मोहर",
|
||||
"initials": "हस्ताक्षर",
|
||||
"initials": "प्रारंभिक अक्षर",
|
||||
"name": "नाम",
|
||||
"job title": "पद",
|
||||
"company": "कंपनी",
|
||||
"date": "दिनांक",
|
||||
"text": "पाठ",
|
||||
"text input": "पाठ इनपुट",
|
||||
"cells": "सेल्स",
|
||||
"checkbox": "चेकबॉक्स",
|
||||
"dropdown": "ड्रॉपडाउन",
|
||||
"radio button": "रेडियो बटन",
|
||||
@@ -377,6 +386,7 @@
|
||||
"certificate": "प्रमाण पत्र",
|
||||
"decline": "अस्वीकार करें",
|
||||
"finish": "समाप्त करें",
|
||||
"done": "हो गया",
|
||||
"mail": "मेल",
|
||||
"sign-now": "अभी हस्ताक्षर करें",
|
||||
"successfully-signed": "सफलतापूर्वक हस्ताक्षर किए गए!",
|
||||
@@ -386,8 +396,12 @@
|
||||
"Email-verified-alert-1": "ईमेल सत्यापित है।",
|
||||
"Email-verified-alert-2": "ईमेल पहले से ही सत्यापित है।",
|
||||
"upload-stamp-image": "मोहर छवि अपलोड करें",
|
||||
"draw-signature": "हस्ताक्षर बनाएं",
|
||||
"draw-initials": "इनिशियल्स बनाएं",
|
||||
"enter-text": "पाठ दर्ज करें",
|
||||
"enter-widgettype": "{{widgetType}} दर्ज करें",
|
||||
"draw": "आरेखित करें",
|
||||
"type": "प्रकार",
|
||||
"type": "टाइप",
|
||||
"color-type": {
|
||||
"red": "लाल",
|
||||
"blue": "नीला",
|
||||
@@ -415,10 +429,12 @@
|
||||
"options": "विकल्प",
|
||||
"minimun-check": "न्यूनतम जाँच",
|
||||
"maximum-check": "अधिकतम जाँच",
|
||||
"cell-count": "सेल्स संख्या",
|
||||
"default-value": "डिफ़ॉल्ट मान",
|
||||
"select": "चुनें",
|
||||
"read-only": "केवल पढ़ने के लिए है",
|
||||
"read-only": "सिर्फ पढ़ने के लिए है",
|
||||
"hide-labels": "लेबल छिपाएँ",
|
||||
"layout": "लेआउट",
|
||||
"checkbox": "चेकबॉक्स",
|
||||
"alert": "चेतावनी",
|
||||
"zoom-in": "ज़ूम इन करें",
|
||||
@@ -467,7 +483,7 @@
|
||||
"placeholder-alert-3": " क्या आप वाकई इस दस्तावेज़ को हस्ताक्षर के लिए भेजना चाहते हैं?",
|
||||
"placeholder-alert-4": "आपने सभी प्राप्तकर्ताओं को सफलतापूर्वक मेल भेज दिए हैं!",
|
||||
"placeholder-mail-alert": "आपने {{name}} को सफलतापूर्वक ईमेल भेज दिया है। {{name}} द्वारा दस्तावेज़ पर हस्ताक्षर करने के बाद बाद के हस्ताक्षरकर्ताओं को ईमेल प्राप्त होंगे",
|
||||
"placeholder-mail-alert-you": "आपके द्वारा दस्तावेज़ पर हस्ताक्षर करने के बाद बाद के हस्ताक्षरकर्ताओं को ईमेल प्राप्त होंगे।",
|
||||
"placeholder-mail-alert-you": "आपके दस्तावेज़ पर हस्ताक्षर करने के बाद अगले हस्ताक्षरकर्ता को ईमेल प्राप्त होगा।",
|
||||
"placeholder-alert-5": "क्या आप अभी दस्तावेज़ पर हस्ताक्षर करना चाहते हैं?",
|
||||
"placeholder-alert-6": "मेल भेजने के लिए कृपया मेल एडॉप्टर सेटअप करें!",
|
||||
"placeholder-alert-7": "प्लेसहोल्डर जोड़ने के लिए कृपया हस्ताक्षरकर्ता चुनें!",
|
||||
@@ -724,7 +740,7 @@
|
||||
"public-tour-message": "साझा करने योग्य लिंक उत्पन्न करने से पहले टेम्पलेट को सार्वजनिक होना चाहिए।",
|
||||
"add-user-template": "इसके लिए फ़ील्ड जोड़ने से पहले आपको एक भूमिका जोड़ने की आवश्यकता है।",
|
||||
"pdf-uncompatible": "यह पीडीएफ संगत नहीं है, कृपया {{appName}} से संपर्क करें",
|
||||
"text-field-tour": "दस्तावेज़ भेजे जाने से पहले 'टेक्स्ट' प्रकार के फ़ील्ड पहले से भरे जाने चाहिए। यदि आपको हस्ताक्षरकर्ताओं से इनपुट प्रदान करने की आवश्यकता है, तो इसके बजाय 'टेक्स्ट इनपुट' फ़ील्ड का उपयोग करें।",
|
||||
"text-field-tour": "दस्तावेज़ सबमिट करने से पहले 'प्रीफ़िल' फ़ील्ड को पहले से भरना होगा। यदि आपको हस्ताक्षरकर्ताओं से इनपुट की आवश्यकता है, तो इसके बजाय हस्ताक्षरकर्ता फ़ील्ड का उपयोग करें।",
|
||||
"attach-signer-tour": "आपको प्रत्येक भूमिका में एक हस्ताक्षरकर्ता संलग्न करने की आवश्यकता है। आप इस आइकन पर क्लिक करके ऐसा कर सकते हैं। एक बार जब आप एक हस्ताक्षरकर्ता का चयन कर लेते हैं तो यह उस भूमिका से जुड़े सभी फ़ील्ड से जुड़ जाएगा जो एक ही रंग में दिखाई देते हैं।",
|
||||
"allowed-signature-types": "अनुमत हस्ताक्षर प्रकार",
|
||||
"at-least-one-signature-type": "कम से कम एक हस्ताक्षर प्रकार सक्षम होना चाहिए।",
|
||||
@@ -750,12 +766,15 @@
|
||||
"delete-page": "पृष्ठ हटाएं",
|
||||
"merge-pdf": "पीडीएफ मर्ज करें",
|
||||
"add-pages": "पृष्ठ जोड़ें",
|
||||
"reorder-pages": "पृष्ठ पुनः व्यवस्थित करें",
|
||||
"delete-alert": "एकल पृष्ठ नहीं हटाया जा सकता",
|
||||
"delete-alert-2": "क्या आप वाकई इस पृष्ठ को हटाना चाहते हैं?",
|
||||
"delete-note": "ध्यान दें: एक बार जब आप इस पृष्ठ को हटा देते हैं, तो आप इसे पूर्ववत नहीं कर सकते।",
|
||||
"Rotation-alert": "पृष्ठ घुमाएँ",
|
||||
"bulk-import": "थोक आयात",
|
||||
"contacts-file": "संपर्क फ़ाइल (xlsx, csv)",
|
||||
"import-guideline": "नाम, ईमेल और वैकल्पिक फोन कॉलम वाली CSV या Excel फ़ाइल अपलोड करें। केवल पहले 100 संपर्क आयात किए जाएंगे.",
|
||||
"download-sample": "उदाहरण फ़ाइल डाउनलोड करें",
|
||||
"100-records-only": "वर्तमान में आप केवल 100 रिकॉर्ड तक आयात कर सकते हैं।",
|
||||
"csv-excel-support-only": "निम्नलिखित प्रारूपों में से किसी एक में फ़ाइल अपलोड करें: CSV, XLSX या XLS।",
|
||||
"contact-imported": "{{imported}} संपर्क आयात किए गए। {{failed}} संपर्क आयात करने में विफल रहे।",
|
||||
@@ -769,7 +788,7 @@
|
||||
"agree-p1": "मैं पुष्टि करता हूं कि मैंने पढ़ लिया है और समझ लिया है ",
|
||||
"agree-p2": "इलेक्ट्रॉनिक रिकॉर्ड और हस्ताक्षर प्रकटीकरण",
|
||||
"agree-p3": "और इलेक्ट्रॉनिक रिकॉर्ड और हस्ताक्षर का उपयोग करने के लिए सहमति।",
|
||||
"agrre-button": " सहमत हूँ और जारी रखें",
|
||||
"agrre-button": "मैं पुष्टि करता हूँ और आगे बढ़ने के लिए सहमत हूँ",
|
||||
"term-cond-title": "नियम और शर्तें",
|
||||
"term-cond-h": "इलेक्ट्रॉनिक रिकॉर्ड और हस्ताक्षर प्रकटीकरण",
|
||||
"term-cond-p1": "यह इलेक्ट्रॉनिक रिकॉर्ड और हस्ताक्षर प्रकटीकरण ('प्रकटीकरण') दस्तावेज़ निर्माता ('प्रेषक') और हस्ताक्षरकर्ता ('आप') के बीच एक समझौता है, जिसे {{appName}} प्लेटफ़ॉर्म ('प्लेटफ़ॉर्म') के माध्यम से सुगम बनाया गया है। {{appName}} के माध्यम से दस्तावेज़ों पर हस्ताक्षर करके, आप इस प्रकटीकरण में उल्लिखित शर्तों से सहमत होते हैं। कृपया आगे बढ़ने से पहले इसे ध्यान से पढ़ें।",
|
||||
@@ -982,6 +1001,7 @@
|
||||
"review": "समीक्षा",
|
||||
"next-field": "अगला फ़ील्ड",
|
||||
"required-mssg": "{{totalWidget}} में से {{leftRequiredWidget}} फ़ील्ड शेष हैं",
|
||||
"verify-document": "दस्तावेज़ सत्यापित करें",
|
||||
"verify-document-signature": "दस्तावेज़ हस्ताक्षर सत्यापित करें",
|
||||
"select-pdf-document": "पीडीएफ दस्तावेज़ चुनें",
|
||||
"selected-file": "चयनित फ़ाइल",
|
||||
@@ -1022,5 +1042,104 @@
|
||||
"no-signer-info-in-pkcs7": "PKCS#7 में कोई हस्ताक्षरकर्ता जानकारी नहीं है",
|
||||
"could-not-parse-signer-info": "हस्ताक्षरकर्ता जानकारी पार्स नहीं की जा सकी",
|
||||
"not-calculated": "गणना नहीं की गई",
|
||||
"not-found-in-signature": "हस्ताक्षर में नहीं मिला"
|
||||
"not-found-in-signature": "हस्ताक्षर में नहीं मिला",
|
||||
"readonly-error": "रीड-ओनली {{widgetName}} विजेट में एक डिफ़ॉल्ट मान होना चाहिए या आप इसे वैकल्पिक बना सकते हैं।",
|
||||
"choose-one":"एक का चयन",
|
||||
"search-templates": "टेम्पलेट खोजें…",
|
||||
"search-documents": "दस्तावेज़ खोजें…",
|
||||
"search-contacts": "संपर्क खोजें…",
|
||||
"add-role-alert": "कृपया कम से कम एक भूमिका जोड़ें",
|
||||
"edit-draft": "ड्राफ्ट संपादित करें",
|
||||
"invalid-email-found": "अमान्य ईमेल पाया गया: {{email}}",
|
||||
"duplicate-email-found": "डुप्लिकेट ईमेल पाया गया: {{email}}",
|
||||
"vertical": "वर्टिकल",
|
||||
"horizontal": "हॉरिज़ॉन्टल",
|
||||
"billing": "बिलिंग",
|
||||
"console": "कंसोल",
|
||||
"prefill-widget": "पूर्व भराव विजेट्स",
|
||||
"action-prohibited": "यह क्रिया आपके ईमेल डोमेन के लिए अनुमत नहीं है। कृपया सहायता के लिए अपने व्यवस्थापक से संपर्क करें।",
|
||||
"must-have-at-least-one-vacant-role": "किसी टेम्पलेट को 'public' पर सेट करने से पहले कम से कम एक भूमिका खाली होनी चाहिए।",
|
||||
"remove-duplicate": "कृपया डुप्लिकेट विकल्प हटाएं",
|
||||
"prefill-bulk-error": "जब प्रीफ़िल विजेट जोड़े जाते हैं तो बल्क भेजना अनुमति नहीं है। कृपया आगे बढ़ने के लिए प्रीफ़िल विजेट हटा दें।",
|
||||
"session-expired-title": "सत्र समाप्त हो गया",
|
||||
"access-denied": "पहुंच अस्वीकृत",
|
||||
"upgrade": "अपग्रेड",
|
||||
"do-not-access-app": "आपको इस एप्लिकेशन तक पहुंच नहीं है।",
|
||||
"dont-have-access": "आपको पहुंच नहीं है।",
|
||||
"valid-email-alert": "कृपया एक मान्य ईमेल पता दर्ज करें।",
|
||||
"otp-not-validate": "ओटीपी मान्य नहीं है।",
|
||||
"domain-not-allowed": "यह डोमेन अनुमति नहीं है",
|
||||
"atleast-one-recipient-alert": "कृपया कम से कम एक प्राप्तकर्ता जोड़ें!",
|
||||
"incorrect-password-or-decryption-failed": "गलत पासवर्ड या डिक्रिप्शन विफल।",
|
||||
"incorrect-password-for-file": "फ़ाइल के लिए गलत पासवर्ड: {{file}}",
|
||||
"error-uploading-pdf": "PDF अपलोड करते समय त्रुटि।",
|
||||
"provide-password": "कृपया पासवर्ड प्रदान करें।",
|
||||
"only-pdf-allowed": "केवल PDF फ़ाइलें अनुमत हैं।",
|
||||
"invalid-username-password-region": "अमान्य उपयोगकर्ता नाम/पासवर्ड या क्षेत्र।",
|
||||
"pfx-extension-alert": ".pfx एक्सटेंशन वाली फ़ाइल अपलोड करें।",
|
||||
"email-already-exist": "ईमेल पहले से मौजूद है",
|
||||
"branding": "ब्रांडिंग",
|
||||
"branding-help": "ब्रांडिंग आपकी ऐप में वाइट लेबलिंग की सुविधा देती है",
|
||||
"custom-sub-domain": "कस्टम सब-डोमेन",
|
||||
"app-name": "ऐप का नाम",
|
||||
"provide-domain-name": "अपना डोमेन नाम दर्ज करें",
|
||||
"provide-app-name": "अपना ऐप नाम दर्ज करें",
|
||||
"logo": "लोगो",
|
||||
"upload-app-logo": "अपने ऐप का लोगो अपलोड करें",
|
||||
"prefill-unfilled-widget": "निम्न अनिवार्य फ़ील्ड खाली नहीं छोड़े जा सकते: {{emptyWidget}}। कृपया आगे बढ़ने के लिए इन्हें भरें।",
|
||||
"Dashboard": "डैशबोर्ड",
|
||||
"Analytics": "विश्लेषिकी",
|
||||
"Templates": "टेम्पलेट्स",
|
||||
"Need your sign": "आपके हस्ताक्षर की आवश्यकता है",
|
||||
"In Progress": "प्रगति में है",
|
||||
"Completed": "पूरा हुआ",
|
||||
"Drafts": "ड्राफ्ट",
|
||||
"Declined": "अस्वीकृत",
|
||||
"Expired": "समाप्त",
|
||||
"Contactbook": "संपर्क पुस्तिका",
|
||||
"My Signature": "मेरा हस्ताक्षर",
|
||||
"API Token": "एपीआई टोकन",
|
||||
"Webhook": "वेबहूक",
|
||||
"Preferences": "वरीयताएँ",
|
||||
"Teams": "टीमें",
|
||||
"Users": "उपयोगकर्ता",
|
||||
"Drive": "ड्राइव",
|
||||
"Branding": "ब्रांडिंग",
|
||||
"Mail": "मेल",
|
||||
"Storage": "भंडारण",
|
||||
"Signing certificate": "हस्ताक्षर प्रमाण पत्र",
|
||||
"General": "सामान्य",
|
||||
"Organizations": "संगठन",
|
||||
"OrgAdmins": "संगठन व्यवस्थापक",
|
||||
"Debug Pdf": "पीडीएफ़ डीबग करें",
|
||||
"New Document": "नया दस्तावेज़",
|
||||
"subscription": "सब्सक्रिप्शन",
|
||||
"Draft document": "ड्राफ्ट दस्तावेज़",
|
||||
"Draft template": "ड्राफ्ट टेम्पलेट",
|
||||
"Public sign": "पब्लिक साइन",
|
||||
"Signup": "साइन अप करें",
|
||||
"delete-contact": "संपर्क हटाएं",
|
||||
"total-records-found": "कुल रिकॉर्ड मिले: {{count}}",
|
||||
"Invalid-records-found": "अमान्य रिकॉर्ड मिले: {{records}}",
|
||||
"previous": "पिछला",
|
||||
"page-n-of-n": "पृष्ठ {{currentPage}} का {{totalPages}}",
|
||||
"import": "आयात करें",
|
||||
"search": "खोजें",
|
||||
"viewed-on": "देखा गया: {{ViewedOn}}",
|
||||
"signed-on": "हस्ताक्षर किया गया: {{SignedOn}}",
|
||||
"hide": "छिपाएं",
|
||||
"show-more": "और दिखाएं",
|
||||
"browse-or-drag-to-replace-existing-file": "नई फ़ाइल ब्राउज़ करें या खींचकर छोड़ें ताकि मौजूदा फ़ाइल को बदला जा सके",
|
||||
"optional-details": "वैकल्पिक विवरण",
|
||||
"mail-adapter-subscription-alert": "कस्टम SMTP सेटअप करने के लिए कृपया प्रोफेशनल या टीम प्लान में अपग्रेड करें।",
|
||||
"connect-to-mail": "Gmail से कनेक्ट करें",
|
||||
"custom-smtp": "कस्टम SMTP",
|
||||
"default-smtp": "{{appName}} डिफ़ॉल्ट SMTP",
|
||||
"host": "होस्ट",
|
||||
"port": "पोर्ट",
|
||||
"sender-email": "प्रेषक ईमेल",
|
||||
"username": "यूज़रनेम",
|
||||
"use-default-mail-adapter": "क्या आप वाकई {{appName}} के डिफ़ॉल्ट मेल सर्वर का उपयोग करके सिग्नेचर अनुरोध भेजना चाहते हैं? बेहतर इनबॉक्स डिलीवरी के लिए हम अपने Gmail या SMTP सर्वर का उपयोग करने की सिफारिश करते हैं।",
|
||||
"verification-code-sent-registered-email": "आपके पंजीकृत ईमेल <1>{{useremail}}</1> पर एक सत्यापन कोड भेजा गया है। कृपया अपनी सेटिंग की पुष्टि के लिए नीचे कोड दर्ज करें।",
|
||||
"smpt-credentials": "SMTP क्रेडेंशियल्स"
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"create-account": "Crea Account",
|
||||
"login": "Accedi",
|
||||
"language": "Lingua",
|
||||
"dark-mode": "Modalità scura",
|
||||
"name": "Nome",
|
||||
"phone": "Telefono",
|
||||
"phone-optional": "facoltativo",
|
||||
@@ -25,6 +26,8 @@
|
||||
"Name": "Nome",
|
||||
"Date": "Data"
|
||||
},
|
||||
"folder": "Cartella",
|
||||
"pdf": "Pdf",
|
||||
"context-menu": {
|
||||
"Download": "Scarica",
|
||||
"Rename": "Rinomina",
|
||||
@@ -43,6 +46,10 @@
|
||||
"contact-now": "Contatta ora",
|
||||
"upgrade-to": "Aggiorna a",
|
||||
"plan": "Piano",
|
||||
"connect": "Connetti",
|
||||
"connect-to-g-drive": "Connetti a Google Drive",
|
||||
"reconnect-to-g-drive": "Riconnetti a Google Drive",
|
||||
"gdrive-info-connect": "Quando Google Drive è connesso, il documento completato verrà salvato nella cartella {{appName}} su Google Drive.",
|
||||
"subscription-renew-warning": "Il tuo abbonamento scadrà tra {{remainingDays}} giorni. Ti preghiamo di rinnovarlo.",
|
||||
"subscribe-card-teamplan": "Sblocca tutto il potenziale della collaborazione! Crea organizzazioni, team e gerarchie illimitati. Condividi modelli senza problemi tra i team e assegna ruoli personalizzati agli utenti. Migliora il tuo flusso di lavoro oggi stesso!",
|
||||
"subscribe-card-plan": "Sblocca le funzionalità premium a partire da soli {{premiumPrice}}/mese. Approfitta di prestazioni migliorate e paga solo {{addonPrice}} per ogni credito aggiuntivo dopo quelli inclusi.",
|
||||
@@ -57,7 +64,7 @@
|
||||
"welcome": "Bentornato!",
|
||||
"Login-to-your-account": "Accedi al tuo account",
|
||||
"password": "Password",
|
||||
"forgot-password": "Password dimenticata?",
|
||||
"forgot-password": "Password dimenticata",
|
||||
"loading": "Caricamento...",
|
||||
"of": "di",
|
||||
"sign-SSO": "Accedi con SSO",
|
||||
@@ -180,7 +187,9 @@
|
||||
"created-date": "Data di creazione",
|
||||
"Type": "Tipo",
|
||||
"Logs": "Log",
|
||||
"Expiry-date": "Data di scadenza"
|
||||
"Expiry-date": "Data di scadenza",
|
||||
"Company": "Azienda",
|
||||
"JobTitle": "Titolo professionale"
|
||||
},
|
||||
"report-help": {
|
||||
"Draft Documents": "Questi sono documenti che hai iniziato ma non hai ancora finalizzato per l'invio.",
|
||||
@@ -192,16 +201,15 @@
|
||||
"Contactbook": "Questa è una lista di contatti/firmatari aggiunti da te. Appariranno come suggerimenti quando provi ad aggiungere firmatari a un nuovo documento.",
|
||||
"Templates": "Questa è una lista di modelli disponibili per creare documenti. Puoi fare clic sul pulsante 'Usa' per creare un nuovo documento utilizzando un modello, modificare il documento e aggiungere firmatari nel passaggio successivo."
|
||||
},
|
||||
"form-name": {
|
||||
"Sign Yourself": "Firma tu stesso",
|
||||
"Request Signatures": "Richiedi firme",
|
||||
"New Template": "Nuovo modello"
|
||||
},
|
||||
"Sign Yourself": "Firma tu stesso",
|
||||
"Request Signatures": "Richiedi firme",
|
||||
"New Template": "Nuovo modello",
|
||||
"file-type": "pdf, png, jpg, jpeg",
|
||||
"docx": "docx",
|
||||
"file-selected": "file selezionato",
|
||||
"template-title": "Titolo del modello",
|
||||
"document-title": "Titolo del documento",
|
||||
"title": "Titolo",
|
||||
"description": "Descrizione",
|
||||
"time-to-complete": "Tempo per completare (giorni)",
|
||||
"send-in-order": "Invia in ordine",
|
||||
@@ -361,6 +369,7 @@
|
||||
"date": "data",
|
||||
"text": "testo",
|
||||
"text input": "campo di testo",
|
||||
"cells": "cellule",
|
||||
"checkbox": "casella di controllo",
|
||||
"dropdown": "menu a tendina",
|
||||
"radio button": "pulsante di opzione",
|
||||
@@ -377,6 +386,7 @@
|
||||
"certificate": "Certificato",
|
||||
"decline": "Rifiuta",
|
||||
"finish": "Completa",
|
||||
"done": "Fatto",
|
||||
"mail": "Email",
|
||||
"sign-now": "Firma ora",
|
||||
"successfully-signed": "Firmato con successo!",
|
||||
@@ -386,6 +396,10 @@
|
||||
"Email-verified-alert-1": "Email verificata.",
|
||||
"Email-verified-alert-2": "Email già verificata.",
|
||||
"upload-stamp-image": "Carica immagine timbro",
|
||||
"draw-signature": "Disegna la firma",
|
||||
"draw-initials": "Disegna le iniziali",
|
||||
"enter-text": "Inserisci testo",
|
||||
"enter-widgettype": "Inserisci {{widgetType}}",
|
||||
"draw": "Disegno",
|
||||
"type": "Digitata",
|
||||
"color-type": {
|
||||
@@ -415,10 +429,12 @@
|
||||
"options": "Opzioni",
|
||||
"minimun-check": "Controllo minimo",
|
||||
"maximum-check": "Controllo massimo",
|
||||
"cell-count": "conteggio delle cellule",
|
||||
"default-value": "Valore predefinita",
|
||||
"select": "Seleziona",
|
||||
"read-only": "È solo lettura",
|
||||
"read-only": "È di sola lettura",
|
||||
"hide-labels": "Nascondi etichette",
|
||||
"layout": "Layout",
|
||||
"checkbox": "Casella di controllo",
|
||||
"alert": "Avviso",
|
||||
"zoom-in": "Ingrandisci",
|
||||
@@ -467,7 +483,7 @@
|
||||
"placeholder-alert-3": "Sei sicuro di voler inviare questo documento per le firme?",
|
||||
"placeholder-alert-4": "Hai inviato con successo le mail a tutti i destinatari!",
|
||||
"placeholder-mail-alert": "Hai inviato con successo un'e-mail a {{name}}. I firmatari successivi riceveranno un'e-mail una volta che {{name}} avrà firmato il documento.",
|
||||
"placeholder-mail-alert-you": "I firmatari successivi riceveranno un'email non appena firmi il documento.",
|
||||
"placeholder-mail-alert-you": "I firmatari successivi riceveranno un'email dopo che avrai firmato il documento.",
|
||||
"placeholder-alert-5": "Vuoi firmare i documenti ora?",
|
||||
"placeholder-alert-6": "Configura l'adattatore email per inviare mail!",
|
||||
"placeholder-alert-7": "Seleziona il firmatario per aggiungere un segnaposto!",
|
||||
@@ -724,7 +740,7 @@
|
||||
"public-tour-message": "Il modello deve essere pubblico prima di poter generare un link condivisibile.",
|
||||
"add-user-template": "Devi aggiungere un ruolo prima di poter aggiungere i campi per esso.",
|
||||
"pdf-uncompatible": "Questo PDF non è compatibile, contatta {{appName}}",
|
||||
"text-field-tour": "I campi di tipo 'Testo' devono essere compilati in anticipo prima che il documento venga inviato. Se hai bisogno che i firmatari forniscano un input, usa invece il campo 'Testo di input'.",
|
||||
"text-field-tour": "I campi 'Prefill' devono essere compilati in anticipo prima dell'invio del documento. Se hai bisogno che i firmatari forniscano input, usa invece i campi dei firmatari.",
|
||||
"attach-signer-tour": "Devi allegare un firmatario a ogni ruolo. Puoi farlo cliccando su questa icona. Una volta selezionato un firmatario, sarà associato a tutti i campi associati a quel ruolo che appariranno dello stesso colore.",
|
||||
"allowed-signature-types": "Tipi di firma consentiti",
|
||||
"at-least-one-signature-type": "Almeno un tipo di firma deve essere abilitato.",
|
||||
@@ -750,12 +766,15 @@
|
||||
"delete-page": "Elimina pagina",
|
||||
"merge-pdf": "Unisci PDF",
|
||||
"add-pages": "Aggiungi pagine",
|
||||
"reorder-pages": "Riordina pagine",
|
||||
"delete-alert": "Non è possibile eliminare una singola pagina",
|
||||
"delete-alert-2": "Sei sicuro di voler eliminare questa pagina?",
|
||||
"delete-note": "Nota: Una volta eliminata questa pagina, non potrai annullare l'operazione.",
|
||||
"Rotation-alert": "Ruota pagina",
|
||||
"bulk-import": "Importazione massiva",
|
||||
"contacts-file": "File contatti (xlsx, csv)",
|
||||
"import-guideline": "Carica un file CSV o Excel con le colonne Name, Email e opzionale Phone. Verranno importati solo i primi 100 contatti.",
|
||||
"download-sample": "Scarica file di esempio",
|
||||
"100-records-only": "Attualmente puoi importare solo fino a 100 record.",
|
||||
"csv-excel-support-only": "Carica un file nei seguenti formati: CSV, XLSX o XLS.",
|
||||
"contact-imported": "{{imported}} contatti importati. {{failed}} contatti non sono stati importati.",
|
||||
@@ -769,7 +788,7 @@
|
||||
"agree-p1": "Confermo di aver letto e compreso il",
|
||||
"agree-p2": "Divulgazione sulle registrazioni e firme elettroniche",
|
||||
"agree-p3": "e acconsento all'uso di registrazioni e firme elettroniche.",
|
||||
"agrre-button": "Accetta e Continua",
|
||||
"agrre-button": "Confermo e accetto di continuare",
|
||||
"term-cond-title": "Termini e Condizioni",
|
||||
"term-cond-h": "DIVULGAZIONE SULLE REGISTRAZIONI E FIRME ELETTRONICHE",
|
||||
"term-cond-p1": "Questa Divulgazione sulle Registrazioni e Firme Elettroniche ('Divulgazione') è un accordo tra il Creatore del Documento ('Mittente') e il Firmatario ('Tu'), facilitato tramite la piattaforma {{appName}} (Piattaforma). Firmando documenti tramite {{appName}}, accetti i termini descritti in questa Divulgazione. Ti preghiamo di leggerla attentamente prima di procedere.",
|
||||
@@ -982,6 +1001,7 @@
|
||||
"review": "Rivedere",
|
||||
"next-field": "Campo successivo",
|
||||
"required-mssg":"{{leftRequiredWidget}} di {{totalWidget}} campi rimanenti",
|
||||
"verify-document": "Verifica documento",
|
||||
"verify-document-signature": "Verifica firma documento",
|
||||
"select-pdf-document": "Seleziona documento PDF",
|
||||
"selected-file": "File selezionato",
|
||||
@@ -1022,5 +1042,103 @@
|
||||
"no-signer-info-in-pkcs7": "Nessuna informazione firmatario in PKCS#7",
|
||||
"could-not-parse-signer-info": "Impossibile analizzare le informazioni del firmatario",
|
||||
"not-calculated": "Non calcolato",
|
||||
"not-found-in-signature": "Non trovato nella firma"
|
||||
"not-found-in-signature": "Non trovato nella firma",
|
||||
"readonly-error": "Il widget {{widgetName}} di sola lettura deve avere un valore predefinito oppure può essere reso facoltativo.",
|
||||
"choose-one":"Scegline uno",
|
||||
"search-templates": "Cerca modelli…",
|
||||
"search-documents": "Cerca documenti…",
|
||||
"search-contacts": "Cerca contatti…",
|
||||
"add-role-alert": "Si prega di aggiungere almeno un ruolo",
|
||||
"edit-draft": "Modifica bozza",
|
||||
"invalid-email-found": "Email non valida trovata: {{email}}",
|
||||
"duplicate-email-found": "Email duplicata trovata: {{email}}",
|
||||
"vertical": "Verticale",
|
||||
"horizontal": "Orizzontale",
|
||||
"billing": "Fatturazione",
|
||||
"console": "Console",
|
||||
"prefill-widget": "Widget Precompilati",
|
||||
"action-prohibited": "Questa azione non è consentita per il tuo dominio email. Contatta il tuo amministratore per ricevere assistenza.",
|
||||
"must-have-at-least-one-vacant-role": "Devi lasciare almeno un ruolo non assegnato prima di impostare un template su 'public'.",
|
||||
"remove-duplicate": "Si prega di rimuovere l'opzione duplicata",
|
||||
"prefill-bulk-error": "L'invio in blocco non è consentito quando sono stati aggiunti i widget di precompilazione. Si prega di rimuovere i widget di precompilazione per procedere.",
|
||||
"session-expired-title": "Sessione scaduta",
|
||||
"access-denied": "Accesso negato",
|
||||
"upgrade": "Aggiorna",
|
||||
"do-not-access-app": "Non hai accesso a questa applicazione.",
|
||||
"dont-have-access": "Non hai accesso.",
|
||||
"valid-email-alert": "Inserisci un indirizzo email valido.",
|
||||
"otp-not-validate": "OTP non valido.",
|
||||
"domain-not-allowed": "Questo dominio non è consentito",
|
||||
"atleast-one-recipient-alert": "Aggiungi almeno un destinatario!",
|
||||
"incorrect-password-or-decryption-failed": "Password errata o decrittazione non riuscita.",
|
||||
"incorrect-password-for-file": "Password errata per il file: {{file}}",
|
||||
"error-uploading-pdf": "Errore durante il caricamento del PDF.",
|
||||
"provide-password": "Fornisci la password.",
|
||||
"only-pdf-allowed": "Sono consentiti solo file PDF.",
|
||||
"invalid-username-password-region": "Nome utente/password o regione non valida.",
|
||||
"pfx-extension-alert": "Carica un file con estensione .pfx.",
|
||||
"email-already-exist": "L'email esiste già",
|
||||
"branding": "Marchio",
|
||||
"branding-help": "Il branding consente il white labelling della tua app",
|
||||
"custom-sub-domain": "Sottodominio personalizzato",
|
||||
"app-name": "Nome dell'app",
|
||||
"provide-domain-name": "Fornisci il tuo nome di dominio",
|
||||
"provide-app-name": "Fornisci il nome della tua app",
|
||||
"logo": "Logo",
|
||||
"upload-app-logo": "Carica il logo della tua app",
|
||||
"prefill-unfilled-widget": "I seguenti campi obbligatori non possono essere lasciati vuoti: {{emptyWidget}}. Per favore compilali per procedere.",
|
||||
"Dashboard": "Dashboard",
|
||||
"Analytics": "Analitica",
|
||||
"Templates": "Modelli",
|
||||
"Need your sign": "Necessita della tua firma",
|
||||
"In Progress": "In corso",
|
||||
"Completed": "Completati",
|
||||
"Drafts": "Bozze",
|
||||
"Declined": "Rifiutati",
|
||||
"Expired": "Scaduti",
|
||||
"Contactbook": "Rubrica",
|
||||
"My Signature": "La mia firma",
|
||||
"API Token": "Token API",
|
||||
"Webhook": "Webhook",
|
||||
"Preferences": "Preferenze",
|
||||
"Teams": "Team",
|
||||
"Users": "Utenti",
|
||||
"Drive": "Drive",
|
||||
"Branding": "Marchio",
|
||||
"Mail": "Posta",
|
||||
"Storage": "Archiviazione",
|
||||
"Signing certificate": "Certificato di firma",
|
||||
"General": "Generale",
|
||||
"Organizations": "Organizzazioni",
|
||||
"OrgAdmins": "OrgAdmins",
|
||||
"Debug Pdf": "Debug PDF",
|
||||
"New Document": "Nuovo documento",
|
||||
"subscription": "Abbonamento",
|
||||
"Draft document": "Bozza di documento",
|
||||
"Draft template": "Bozza di modello",
|
||||
"Public sign": "Firma pubblica",
|
||||
"Signup": "Registrazione",
|
||||
"delete-contact": "Elimina contatto",
|
||||
"total-records-found": "Totale record trovati: {{count}}",
|
||||
"Invalid-records-found": "Record non validi trovati: {{records}}",
|
||||
"previous": "Precedente",
|
||||
"page-n-of-n": "Pagina {{currentPage}} di {{totalPages}}",
|
||||
"import": "Importa",
|
||||
"search": "Cerca",
|
||||
"viewed-on": "Visualizzato il: {{ViewedOn}}",
|
||||
"signed-on": "Firmato il: {{SignedOn}}",
|
||||
"hide": "Nascondi",
|
||||
"show-more": "Mostra di più",
|
||||
"browse-or-drag-to-replace-existing-file": "Sfoglia o trascina un nuovo file per sostituire quello esistente",
|
||||
"optional-details": "Dettagli facoltativi",
|
||||
"mail-adapter-subscription-alert": "Esegui l'upgrade al piano Professional o Team per configurare un SMTP personalizzato.", "connect-to-mail": "Connetti a Gmail",
|
||||
"custom-smtp": "SMTP personalizzato",
|
||||
"default-smtp": "SMTP predefinito di {{appName}}",
|
||||
"host": "Host",
|
||||
"port": "Porta",
|
||||
"sender-email": "Email del mittente",
|
||||
"username": "Nome utente",
|
||||
"use-default-mail-adapter": "Sei sicuro di voler usare i server di posta predefiniti di {{appName}} per inviare le richieste di firma? Ti consigliamo di usare i tuoi server Gmail o SMTP per una migliore consegna in posta in arrivo.",
|
||||
"verification-code-sent-registered-email": "Un codice di verifica è stato inviato alla tua email registrata <1>{{useremail}}</1>. Inserisci il codice qui sotto per confermare le impostazioni.",
|
||||
"smpt-credentials": "Credenziali SMTP"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
Name,Email,Phone
|
||||
John Doe,john@example.com,1234567890
|
||||
Jane Smith,jane@example.com,9876543210
|
||||
Foo Bar,foo@example.com,5555555555
|
||||
|
Binary file not shown.
|
After Width: | Height: | Size: 95 KiB |
@@ -30,7 +30,7 @@ const AddAdmin = lazy(() => import("./pages/AddAdmin"));
|
||||
const UpdateExistUserAdmin = lazy(() => import("./pages/UpdateExistUserAdmin"));
|
||||
const Preferences = lazy(() => import("./pages/Preferences"));
|
||||
const Login = lazy(() => import("./pages/Login"));
|
||||
|
||||
const VerifyDocument = lazy(() => import("./pages/VerifyDocument"));
|
||||
pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/legacy/build/pdf.worker.min.mjs`;
|
||||
const AppLoader = () => {
|
||||
return (
|
||||
@@ -142,7 +142,7 @@ function App() {
|
||||
/>
|
||||
{/* signyouself route with no rowlevel data using docId from url */}
|
||||
<Route path="/signaturePdf/:docId" element={<SignYourSelf />} />
|
||||
{/* draft document route to handle and navigate route page accordiing to document status */}
|
||||
{/* draft document route to handle and navigate route page according to document status */}
|
||||
<Route path="/draftDocument" element={<DraftDocument />} />
|
||||
{/* recipient placeholder set route with no rowlevel data using docId from url*/}
|
||||
<Route
|
||||
@@ -166,6 +166,10 @@ function App() {
|
||||
element={<PdfRequestFiles />}
|
||||
/>
|
||||
<Route path="/users" element={<UserList />} />
|
||||
<Route
|
||||
path="/verify-document"
|
||||
element={<LazyPage Page={VerifyDocument} />}
|
||||
/>
|
||||
<Route
|
||||
path="/preferences"
|
||||
element={<LazyPage Page={Preferences} />}
|
||||
|
||||
@@ -15,12 +15,14 @@ const BulkSendUi = (props) => {
|
||||
const [isSignatureExist, setIsSignatureExist] = useState();
|
||||
const [isDisableBulkSend, setIsDisableBulkSend] = useState(false);
|
||||
const [isLoader, setIsLoader] = useState(false);
|
||||
const [signers, setSigners] = useState([]);
|
||||
const [emails, setEmails] = useState([]);
|
||||
useEffect(() => {
|
||||
signatureExist();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
//function to check atleast one signature field exist
|
||||
//function to check at least one signature field exist
|
||||
const signatureExist = async () => {
|
||||
setIsDisableBulkSend(false);
|
||||
const getPlaceholder = props?.Placeholders;
|
||||
@@ -47,7 +49,14 @@ const BulkSendUi = (props) => {
|
||||
(() => {
|
||||
if (props?.Placeholders?.length > 0) {
|
||||
let users = [];
|
||||
let emails = [];
|
||||
props?.Placeholders?.forEach((element) => {
|
||||
const signerEmail = element?.email || element?.signerPtr?.Email;
|
||||
|
||||
// only add when there's a non-empty signerEmail
|
||||
if (signerEmail) {
|
||||
emails = [...emails, signerEmail];
|
||||
}
|
||||
if (!element.signerObjId) {
|
||||
users = [
|
||||
...users,
|
||||
@@ -60,7 +69,10 @@ const BulkSendUi = (props) => {
|
||||
];
|
||||
}
|
||||
});
|
||||
setEmails(emails);
|
||||
setForms((prevForms) => [...prevForms, { Id: 1, fields: users }]);
|
||||
const signer = props.item?.Signers?.filter((x) => x?.objectId);
|
||||
setSigners(signer);
|
||||
}
|
||||
})();
|
||||
// eslint-disable-next-line
|
||||
@@ -82,10 +94,16 @@ const BulkSendUi = (props) => {
|
||||
|
||||
function validateEmails(data) {
|
||||
for (const item of data) {
|
||||
let email = "";
|
||||
for (const field of item.fields) {
|
||||
if (!emailRegex.test(field.email)) {
|
||||
alert(`Invalid email found: ${field.email}`);
|
||||
alert(t("invalid-email-found", { email: field.email }));
|
||||
return false;
|
||||
} else if (email === field.email || emails?.includes(field.email)) {
|
||||
alert(t("duplicate-email-found", { email: field.email }));
|
||||
return false;
|
||||
} else {
|
||||
email = field.email;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -143,15 +161,14 @@ const BulkSendUi = (props) => {
|
||||
Documents.push({
|
||||
...props.item,
|
||||
Placeholders: updatedPlaceholders,
|
||||
Signers: props.item.Signers
|
||||
? [...props.item.Signers, ...existSigner]
|
||||
: [...existSigner]
|
||||
Signers: signers ? [...signers, ...existSigner] : [...existSigner]
|
||||
});
|
||||
} else {
|
||||
Documents.push({
|
||||
...props.item,
|
||||
Placeholders: updatedPlaceholders,
|
||||
SignatureType: props.signatureType
|
||||
SignatureType: props.signatureType,
|
||||
Signers: signers
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import dp from "../assets/images/dp.png";
|
||||
import FullScreenButton from "./FullScreenButton";
|
||||
import ThemeToggle from "./ThemeToggle";
|
||||
import { useNavigate } from "react-router";
|
||||
import Parse from "parse";
|
||||
import { useWindowSize } from "../hook/useWindowSize";
|
||||
@@ -18,8 +19,13 @@ const Header = ({ showSidebar, setIsMenu, isConsole }) => {
|
||||
const { width } = useWindowSize();
|
||||
const username = localStorage.getItem("username") || "";
|
||||
const image = localStorage.getItem("profileImg") || dp;
|
||||
const isAdmin =
|
||||
localStorage.getItem("Extand_Class") &&
|
||||
JSON.parse(localStorage.getItem("Extand_Class"))?.[0]?.UserRole ===
|
||||
"contracts_Admin";
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [applogo, setAppLogo] = useState("");
|
||||
const [isDarkTheme, setIsDarkTheme] = useState();
|
||||
|
||||
const toggleDropdown = () => {
|
||||
setIsOpen(!isOpen);
|
||||
@@ -85,9 +91,30 @@ const Header = ({ showSidebar, setIsMenu, isConsole }) => {
|
||||
};
|
||||
}, [isOpen]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const updateThemeStatus = () => {
|
||||
const isDarkTheme =
|
||||
document.documentElement.getAttribute("data-theme") === "opensigndark";
|
||||
setIsDarkTheme(isDarkTheme);
|
||||
};
|
||||
updateThemeStatus();
|
||||
|
||||
const observer = new MutationObserver(() => {
|
||||
updateThemeStatus();
|
||||
});
|
||||
|
||||
observer.observe(document.documentElement, {
|
||||
attributes: true,
|
||||
attributeFilter: ["data-theme"]
|
||||
});
|
||||
|
||||
return () => observer.disconnect();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="op-navbar bg-base-100 shadow">
|
||||
<div className="op-navbar bg-base-100 shadow touch-none">
|
||||
<div className="flex-none">
|
||||
<button
|
||||
className="op-btn op-btn-square op-btn-ghost focus:outline-none hover:bg-transparent op-btn-sm no-animation"
|
||||
@@ -101,7 +128,11 @@ const Header = ({ showSidebar, setIsMenu, isConsole }) => {
|
||||
{applogo && (
|
||||
<img
|
||||
className="object-contain h-full w-auto"
|
||||
src={applogo}
|
||||
src={
|
||||
isDarkTheme
|
||||
? "/static/js/assets/images/logo-dark.png"
|
||||
: applogo
|
||||
}
|
||||
alt="logo"
|
||||
/>
|
||||
)}
|
||||
@@ -147,7 +178,7 @@ const Header = ({ showSidebar, setIsMenu, isConsole }) => {
|
||||
</div>
|
||||
<ul
|
||||
tabIndex={0}
|
||||
className={`mt-3 z-[1] p-2 shadow op-dropdown-open op-menu op-menu-sm op-dropdown-content text-base-content bg-base-100 rounded-box w-52 ${
|
||||
className={`mt-3 z-[1] p-2 shadow op-dropdown-open op-menu op-menu-sm op-dropdown-content text-base-content bg-base-100 rounded-box w-56 ${
|
||||
isOpen ? "" : "hidden"
|
||||
}`}
|
||||
>
|
||||
@@ -183,6 +214,27 @@ const Header = ({ showSidebar, setIsMenu, isConsole }) => {
|
||||
{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}>
|
||||
|
||||
@@ -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;
|
||||
@@ -29,7 +29,7 @@ const DashboardButton = (props) => {
|
||||
: "cursor-default"
|
||||
} w-full shadow-md px-3 py-2 op-card bg-base-100`}
|
||||
>
|
||||
<div className="flex flex-row items-center">
|
||||
<div className="flex flex-row items-center text-base-content">
|
||||
<div className="flex flex-row items-center">
|
||||
<span className="rounded-full bg-base-content bg-opacity-20 w-[60px] h-[60px] self-start flex justify-center items-center">
|
||||
<i
|
||||
@@ -39,7 +39,7 @@ const DashboardButton = (props) => {
|
||||
></i>
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-lg ml-3 text-base-content">
|
||||
<div className="text-lg ml-3">
|
||||
{t(`sidebar.${props.Label}`)}
|
||||
{props.Label === "Sign yourself" && (
|
||||
<div className="text-gray-500 text-xs mt-1">
|
||||
|
||||
@@ -148,7 +148,7 @@ const DashboardCard = (props) => {
|
||||
);
|
||||
let arr = [];
|
||||
for (const obj of listData) {
|
||||
const isSigner = obj.Signers.some(
|
||||
const isSigner = obj.Signers?.some(
|
||||
(item) => item.UserId.objectId === currentUser.id
|
||||
);
|
||||
if (isSigner) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useEffect, useState, useRef } from "react";
|
||||
import Parse from "parse";
|
||||
import ReportTable from "../../primitives/GetReportDisplay";
|
||||
import reportJson from "../../json/ReportJson";
|
||||
@@ -17,10 +17,16 @@ function DashboardReport(props) {
|
||||
const [isMoreDocs, setIsMoreDocs] = useState(true);
|
||||
const abortController = new AbortController();
|
||||
const docPerPage = 5;
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [mobileSearchOpen, setMobileSearchOpen] = useState(false);
|
||||
const [isSearchResult, setIsSearchResult] = useState(false);
|
||||
const debounceTimer = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
setReportName("");
|
||||
getReportData(props.Record.reportId);
|
||||
setSearchTerm("");
|
||||
setMobileSearchOpen(false);
|
||||
getReportData(props.Record.reportId, 0, 20, "");
|
||||
|
||||
// Function returned from useEffect is called on unmount
|
||||
return () => {
|
||||
@@ -36,12 +42,69 @@ function DashboardReport(props) {
|
||||
// below useEffect call when isNextRecord state is true and fetch next record
|
||||
useEffect(() => {
|
||||
if (isNextRecord) {
|
||||
getReportData(props.Record.reportId, List.length, 20);
|
||||
getReportData(props.Record.reportId, List.length, 20, searchTerm);
|
||||
}
|
||||
// eslint-disable-next-line
|
||||
}, [isNextRecord]);
|
||||
|
||||
const getReportData = async (id, skipUserRecord = 0, limit = 20) => {
|
||||
const handleSearchChange = async (e) => {
|
||||
const term = e.target.value.toLowerCase();
|
||||
setSearchTerm(term);
|
||||
if (debounceTimer.current) {
|
||||
clearTimeout(debounceTimer.current);
|
||||
}
|
||||
debounceTimer.current = setTimeout(async () => {
|
||||
try {
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
|
||||
sessiontoken: localStorage.getItem("accesstoken")
|
||||
};
|
||||
const url = `${localStorage.getItem("baseUrl")}functions/getReport`;
|
||||
const res = await axios.post(
|
||||
url,
|
||||
{
|
||||
reportId: props.Record.reportId,
|
||||
searchTerm: term,
|
||||
skip: 0,
|
||||
limit: docPerPage
|
||||
},
|
||||
{ headers }
|
||||
);
|
||||
const data = res.data?.result || [];
|
||||
if (!data.error) {
|
||||
setList(data);
|
||||
setIsMoreDocs(data.length >= docPerPage);
|
||||
setIsNextRecord(false);
|
||||
setIsSearchResult(true);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Search error:", err);
|
||||
}
|
||||
}, 300);
|
||||
setIsSearchResult(false);
|
||||
};
|
||||
|
||||
const handleSearchPaste = (e) => {
|
||||
setTimeout(() => {
|
||||
handleSearchChange({ target: { value: e.target.value } });
|
||||
}, 0);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (debounceTimer.current) {
|
||||
clearTimeout(debounceTimer.current);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
const getReportData = async (
|
||||
id,
|
||||
skipUserRecord = 0,
|
||||
limit = 20,
|
||||
term = searchTerm
|
||||
) => {
|
||||
setIsLoader(true);
|
||||
const json = reportJson(id);
|
||||
if (json) {
|
||||
@@ -59,6 +122,9 @@ function DashboardReport(props) {
|
||||
const skipRecord = id === "5Go51Q7T8r" ? 0 : skipUserRecord;
|
||||
const limitRecord = id === "5Go51Q7T8r" ? 200 : limit;
|
||||
const params = { reportId: id, skip: skipRecord, limit: limitRecord };
|
||||
if (term) {
|
||||
params.searchTerm = term;
|
||||
}
|
||||
const url = `${localStorage.getItem("baseUrl")}functions/getReport`;
|
||||
const res = await axios.post(url, params, {
|
||||
headers: headers,
|
||||
@@ -68,7 +134,7 @@ function DashboardReport(props) {
|
||||
const listData = res.data?.result.filter((x) => x.Signers.length > 0);
|
||||
let arr = [];
|
||||
for (const obj of listData) {
|
||||
const isSigner = obj.Signers.some(
|
||||
const isSigner = obj.Signers?.some(
|
||||
(item) => item.UserId.objectId === currentUser
|
||||
);
|
||||
if (isSigner) {
|
||||
@@ -141,11 +207,17 @@ function DashboardReport(props) {
|
||||
setIsNextRecord={setIsNextRecord}
|
||||
isMoreDocs={isMoreDocs}
|
||||
docPerPage={docPerPage}
|
||||
mobileSearchOpen={mobileSearchOpen}
|
||||
setMobileSearchOpen={setMobileSearchOpen}
|
||||
searchTerm={searchTerm}
|
||||
handleSearchChange={handleSearchChange}
|
||||
handleSearchPaste={handleSearchPaste}
|
||||
isSearchResult={isSearchResult}
|
||||
/>
|
||||
) : (
|
||||
<div className="flex items-center justify-center h-[100px] w-full bg-white rounded">
|
||||
<div className="text-center">
|
||||
<p className="text-xl text-black">{t("report-not-found")}</p>
|
||||
<p className="text-xl text-base-content">{t("report-not-found")}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -405,7 +405,7 @@ function DriveBody(props) {
|
||||
</tr>
|
||||
)
|
||||
) : listType === "list" && data.Type === "Folder" ? (
|
||||
<div key={ind} className="relative w-[100px] h-[100px] mx-2 my-3">
|
||||
<div className="relative w-[100px] h-[100px] mx-2 my-3">
|
||||
<ContextMenu.Root>
|
||||
<ContextMenu.Trigger className="flex flex-col justify-center items-center select-none-cls">
|
||||
{/* folder */}
|
||||
@@ -447,7 +447,6 @@ function DriveBody(props) {
|
||||
)}
|
||||
</div>
|
||||
</ContextMenu.Trigger>
|
||||
|
||||
<ContextMenu.Portal>
|
||||
<ContextMenu.Content
|
||||
className="ContextMenuContent"
|
||||
@@ -641,7 +640,7 @@ function DriveBody(props) {
|
||||
) : (
|
||||
<div className="flex flex-row flex-wrap items-center mt-1 pb-[20px] mx-[5px]">
|
||||
{props.pdfData.map((data, ind) => {
|
||||
return <div key={ind}>{handleFolderData(data, ind, "list")}</div>;
|
||||
return <React.Fragment key={ind}>{handleFolderData(data, ind, "list")}</React.Fragment>;
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -10,7 +10,7 @@ const AddRoleModal = (props) => {
|
||||
isOpen={props.isModalRole}
|
||||
handleClose={props.handleCloseRoleModal}
|
||||
>
|
||||
<div className="h-full py-[10px] px-[20px]">
|
||||
<div className="text-base-content h-full py-[10px] px-[20px]">
|
||||
<form className="flex flex-col" onSubmit={props.handleAddRole}>
|
||||
<input
|
||||
value={props.roleName}
|
||||
|
||||
@@ -9,7 +9,6 @@ function AgreementContent(props) {
|
||||
const h2Style = "text-base-content font-medium text-lg";
|
||||
const ulStyle = "list-disc px-4 py-3";
|
||||
const handleOnclick = () => {
|
||||
props.setIsAgreeTour(false);
|
||||
props.setIsAgree(true);
|
||||
props.setIsShowAgreeTerms(false);
|
||||
props.showFirstWidget();
|
||||
|
||||
@@ -4,7 +4,6 @@ import AgreementContent from "./AgreementContent";
|
||||
|
||||
function AgreementSign(props) {
|
||||
const { t } = useTranslation();
|
||||
const [isChecked, setIsChecked] = useState(false);
|
||||
const [isShowAgreeTerms, setIsShowAgreeTerms] = useState(false);
|
||||
|
||||
return (
|
||||
@@ -12,26 +11,12 @@ function AgreementSign(props) {
|
||||
<div className="op-modal op-modal-open absolute z-[448]">
|
||||
<div className="w-[95%] md:w-[60%] lg:w-[40%] op-modal-box overflow-y-auto hide-scrollbar text-sm p-4">
|
||||
<div className="flex flex-row items-center">
|
||||
<input
|
||||
data-tut="IsAgree"
|
||||
className="mr-3 op-checkbox op-checkbox-m"
|
||||
type="checkbox"
|
||||
value={isChecked}
|
||||
onChange={(e) => {
|
||||
setIsChecked(e.target.checked);
|
||||
if (e.target.checked) {
|
||||
props.setIsAgreeTour(false);
|
||||
}
|
||||
props.showFirstWidget();
|
||||
}}
|
||||
/>
|
||||
<div className="text-[11px] md:text-base">
|
||||
<div className="text-[11px] md:text-base text-base-content">
|
||||
<span>{t("agree-p1")}</span>
|
||||
<span
|
||||
className="font-bold text-blue-600 cursor-pointer"
|
||||
onClick={() => {
|
||||
setIsShowAgreeTerms(true);
|
||||
props.setIsAgreeTour(false);
|
||||
}}
|
||||
>
|
||||
{t("agree-p2")}
|
||||
@@ -39,29 +24,24 @@ function AgreementSign(props) {
|
||||
<span> {t("agree-p3")}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex ml-[35px] mt-3">
|
||||
<div className="flex mt-3">
|
||||
<button
|
||||
onClick={() => {
|
||||
if (isChecked) {
|
||||
props.setIsAgreeTour(false);
|
||||
props.setIsAgree(true);
|
||||
} else {
|
||||
props.setIsAgreeTour(true);
|
||||
}
|
||||
props.setIsAgree(true);
|
||||
props.showFirstWidget()
|
||||
}}
|
||||
className="op-btn op-btn-primary op-btn-sm w-full md:w-auto"
|
||||
>
|
||||
{t("agrre-button")}
|
||||
</button>
|
||||
</div>
|
||||
<div className="mt-2">
|
||||
<div className="mt-2 text-base-content">
|
||||
<span className="text-[11px]">{t("agreement-note")}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{isShowAgreeTerms && (
|
||||
<AgreementContent
|
||||
setIsAgreeTour={props.setIsAgreeTour}
|
||||
setIsAgree={props.setIsAgree}
|
||||
setIsShowAgreeTerms={setIsShowAgreeTerms}
|
||||
showFirstWidget={props.showFirstWidget}
|
||||
|
||||
@@ -12,12 +12,9 @@ function BorderResize(props) {
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
width: getHeight() || "14px",
|
||||
height: getHeight() || "14px"
|
||||
}}
|
||||
style={{ width: getHeight() || "14px", height: getHeight() || "14px" }}
|
||||
className={`${props.right ? `-right-[12px]` : "-right-[2px]"} ${
|
||||
props.top ? `-bottom-[12px]` : "-bottom-[2px] "
|
||||
props.top ? `-bottom-[12px]` : "-bottom-[2px]"
|
||||
} absolute inline-block hover:cursor-sw-resize border-r-[3px] border-b-[3px] border-[#188ae2]`}
|
||||
></div>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import ModalUi from "../../primitives/ModalUi";
|
||||
import { fontsizeArr, fontColorArr } from "../../constant/Utils";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function CellsSettingModal({
|
||||
isOpen,
|
||||
handleClose,
|
||||
defaultData,
|
||||
handleSave
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [name, setName] = useState("");
|
||||
const [cellCount, setCellCount] = useState(5);
|
||||
const [fontSize, setFontSize] = useState(12);
|
||||
const [fontColor, setFontColor] = useState("black");
|
||||
|
||||
useEffect(() => {
|
||||
if (defaultData) {
|
||||
setName(defaultData.options?.name || "Cells");
|
||||
setCellCount(defaultData.options?.cellCount || 5);
|
||||
setFontSize(defaultData.options?.fontSize || 12);
|
||||
setFontColor(defaultData.options?.fontColor || "black");
|
||||
}
|
||||
}, [defaultData]);
|
||||
|
||||
const onSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
handleSave &&
|
||||
handleSave({
|
||||
name,
|
||||
cellCount: parseInt(cellCount, 10),
|
||||
fontSize,
|
||||
fontColor
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<ModalUi isOpen={isOpen} handleClose={handleClose} title={t("widget-info")}>
|
||||
<form onSubmit={onSubmit} className="p-[20px] text-base-content flex flex-col gap-3">
|
||||
<div>
|
||||
<label htmlFor="name" className="text-[13px]">
|
||||
{t("name")} <span className="text-[red]">*</span>
|
||||
</label>
|
||||
<input
|
||||
required
|
||||
name="name"
|
||||
className="op-input op-input-bordered op-input-sm w-full text-xs focus:outline-none hover:border-base-content"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="cellCount" className="text-[13px]">
|
||||
{t("cell-count")}
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
min="1"
|
||||
name="cellCount"
|
||||
className="op-input op-input-bordered op-input-sm w-full text-xs focus:outline-none hover:border-base-content"
|
||||
value={cellCount}
|
||||
onChange={(e) => setCellCount(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col md:flex-row md:items-center gap-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="whitespace-nowrap">{t("font-size")}: </span>
|
||||
<select
|
||||
className="ml-[7px] w-[60%] op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs"
|
||||
value={fontSize}
|
||||
onChange={(e) => setFontSize(parseInt(e.target.value))}
|
||||
>
|
||||
{fontsizeArr.map((size, ind) => (
|
||||
<option className="text-[13px]" value={size} key={ind}>
|
||||
{size}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<span>{t("color")}: </span>
|
||||
<select
|
||||
className="ml-[33px] md:ml-4 w-[65%] md:w-full op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs"
|
||||
value={fontColor}
|
||||
onChange={(e) => setFontColor(e.target.value)}
|
||||
>
|
||||
{fontColorArr.map((color, ind) => (
|
||||
<option value={color} key={ind}>
|
||||
{t(`color-type.${color}`)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<span className="w-5 h-[19px] ml-1" style={{ background: fontColor }} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="h-[1px] w-full bg-[#b7b3b3] my-[16px]"></div>
|
||||
<button type="submit" className="op-btn op-btn-primary">
|
||||
{t("save")}
|
||||
</button>
|
||||
</form>
|
||||
</ModalUi>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
|
||||
const Cell = ({
|
||||
isEnabled,
|
||||
count,
|
||||
h,
|
||||
value,
|
||||
editable,
|
||||
onChange,
|
||||
onKeyDown,
|
||||
onBlur,
|
||||
inputRef,
|
||||
index,
|
||||
fontSize,
|
||||
fontColor,
|
||||
hint
|
||||
}) => (
|
||||
<div
|
||||
className={`${isEnabled ? "bg-white border-gray-400" : "select-none-cls pointer-events-none border-gray-500"} flex items-center justify-center border-[1px]`}
|
||||
style={{ flex: `0 0 ${100 / count}%`, height: h }}
|
||||
>
|
||||
<input
|
||||
disabled={!isEnabled}
|
||||
maxLength={1}
|
||||
value={value}
|
||||
readOnly={!editable}
|
||||
ref={inputRef}
|
||||
onChange={editable ? (e) => onChange && onChange(e, index) : undefined}
|
||||
onKeyDown={editable ? (e) => onKeyDown && onKeyDown(e, index) : undefined}
|
||||
// trigger validation when leaving a cell
|
||||
onBlur={editable ? (e) => onBlur && onBlur(e, index) : undefined}
|
||||
className={`${isEnabled ? "placeholder-gray-300" : "placeholder-gray-500"} w-full text-center focus:outline-none bg-transparent`}
|
||||
placeholder={hint}
|
||||
style={{ fontFamily: "Arial, sans-serif", fontSize, color: fontColor }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default function CellsWidget({
|
||||
isEnabled,
|
||||
count = 8,
|
||||
height = 40,
|
||||
value = "",
|
||||
editable = false,
|
||||
onChange,
|
||||
onKeyDown,
|
||||
onBlur,
|
||||
onCellCountChange,
|
||||
inputRefs,
|
||||
resizable = false,
|
||||
fontSize = "12px",
|
||||
fontColor = "black",
|
||||
hint = ""
|
||||
}) {
|
||||
const [cellCount, setCellCount] = useState(count);
|
||||
|
||||
// keep internal state in sync with prop updates
|
||||
useEffect(() => setCellCount(count), [count]);
|
||||
|
||||
const startX = useRef(0);
|
||||
const startCount = useRef(cellCount);
|
||||
|
||||
const capture = (downEv, onMove, onUp = () => {}) => {
|
||||
const id = downEv.pointerId;
|
||||
const move = (ev) => id === ev.pointerId && onMove(ev);
|
||||
const up = (ev) => {
|
||||
if (id !== ev.pointerId) return;
|
||||
onUp(ev);
|
||||
downEv.target.releasePointerCapture(id);
|
||||
window.removeEventListener("pointermove", move);
|
||||
window.removeEventListener("pointerup", up);
|
||||
};
|
||||
window.addEventListener("pointermove", move);
|
||||
window.addEventListener("pointerup", up);
|
||||
downEv.target.setPointerCapture(id);
|
||||
};
|
||||
|
||||
const onTopHandlePointerDown = (ev) => {
|
||||
// Prevent triggering the widget drag logic
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
startX.current = ev.clientX;
|
||||
startCount.current = cellCount;
|
||||
capture(ev, (moveEv) => {
|
||||
const dx = moveEv.clientX - startX.current;
|
||||
const delta = Math.floor(-dx / 5);
|
||||
let newCount = Math.max(1, startCount.current + delta);
|
||||
if (newCount !== cellCount) {
|
||||
setCellCount(newCount);
|
||||
onCellCountChange?.(newCount);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const cells = Array.from({ length: cellCount }).map((_, i) => value[i] || "");
|
||||
const hints = Array.from({ length: cellCount }).map((_, i) => hint[i] || "");
|
||||
|
||||
return (
|
||||
<div
|
||||
className="relative flex w-full h-full overflow-visible"
|
||||
style={{ height }}
|
||||
>
|
||||
{resizable && (
|
||||
<div
|
||||
className="cell-size-handle absolute left-1/2 -translate-x-1/2 -bottom-4 rotate-180 cursor-ew-resize touch-none"
|
||||
onPointerDown={onTopHandlePointerDown}
|
||||
>
|
||||
<svg
|
||||
className="w-4 h-4 text-blue-600"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="m9.69 18.933.003.001C9.89 19.02 10 19 10 19s.11.02.308-.066l.002-.001.006-.003.018-.008a5.741 5.741 0 0 0 .281-.14c.186-.096.446-.24.757-.433.62-.384 1.445-.966 2.274-1.765C15.302 14.988 17 12.493 17 9A7 7 0 1 0 3 9c0 3.492 1.698 5.988 3.355 7.584a13.731 13.731 0 0 0 2.273 1.765 11.842 11.842 0 0 0 .976.544l.062.029.018.008.006.003ZM10 11.25a2.25 2.25 0 1 0 0-4.5 2.25 2.25 0 0 0 0 4.5Z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
)}
|
||||
{cells.map((val, i) => (
|
||||
<Cell
|
||||
key={i}
|
||||
isEnabled={isEnabled}
|
||||
count={cellCount}
|
||||
h={height}
|
||||
value={val}
|
||||
editable={editable}
|
||||
onChange={onChange}
|
||||
onKeyDown={onKeyDown}
|
||||
onBlur={onBlur}
|
||||
inputRef={inputRefs ? (el) => (inputRefs.current[i] = el) : undefined}
|
||||
index={i}
|
||||
fontSize={fontSize}
|
||||
fontColor={fontColor}
|
||||
hint={hints[i]}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -3,43 +3,39 @@ 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 defaultSignImg = useSelector((state) => state.widget.defaultSignImg);
|
||||
const myInitial = useSelector((state) => state.widget.myInitial);
|
||||
const tabName = ["my-signature", "my-initials"];
|
||||
const [activeTab, setActiveTab] = useState(0);
|
||||
const confirmToaddDefaultSign = (type) => {
|
||||
if (!props.isAgree) {
|
||||
props.setIsAgreeTour(true);
|
||||
} else {
|
||||
if (props?.xyPosition.length > 0) {
|
||||
//check signature or initial widgets exist or not for auto signing
|
||||
const getCurrentSignerXY = props?.xyPosition.filter(
|
||||
(data) => data.Id === props.uniqueId
|
||||
);
|
||||
const checkIsSignInitialExist = getCurrentSignerXY?.every(
|
||||
(placeholderObj) =>
|
||||
placeholderObj?.placeHolder?.some((placeholder) =>
|
||||
placeholder?.pos?.some((posItem) => posItem?.type === type)
|
||||
)
|
||||
);
|
||||
if (checkIsSignInitialExist) {
|
||||
props?.setDefaultSignAlert({
|
||||
isShow: true,
|
||||
alertMessage: t("default-sign-alert", { widgetsType: type }),
|
||||
type: type
|
||||
});
|
||||
} else {
|
||||
props?.setDefaultSignAlert({
|
||||
isShow: true,
|
||||
alertMessage: t("defaultSign-alert", { widgetsType: type })
|
||||
});
|
||||
}
|
||||
if (props?.xyPosition.length > 0) {
|
||||
//check signature or initial widgets exist or not for auto signing
|
||||
const getCurrentSignerXY = props?.xyPosition.filter(
|
||||
(data) => data.Id === props.uniqueId
|
||||
);
|
||||
const checkIsSignInitialExist = getCurrentSignerXY?.every(
|
||||
(placeholderObj) =>
|
||||
placeholderObj?.placeHolder?.some((placeholder) =>
|
||||
placeholder?.pos?.some((posItem) => posItem?.type === type)
|
||||
)
|
||||
);
|
||||
if (checkIsSignInitialExist) {
|
||||
props?.setDefaultSignAlert({
|
||||
isShow: true,
|
||||
alertMessage: t("default-sign-alert", { widgetsType: type }),
|
||||
type: type
|
||||
});
|
||||
} else {
|
||||
props?.setDefaultSignAlert({
|
||||
isShow: true,
|
||||
alertMessage: t("please-select-position!")
|
||||
alertMessage: t("defaultSign-alert", { widgetsType: type })
|
||||
});
|
||||
}
|
||||
} else {
|
||||
props?.setDefaultSignAlert({
|
||||
isShow: true,
|
||||
alertMessage: t("please-select-position!")
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@ 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);
|
||||
@@ -17,11 +17,13 @@ function DropdownWidgetOption(props) {
|
||||
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"]);
|
||||
setDropdownOptionList(["Option-1", "Option-2"]);
|
||||
setDropdownName(props.currWidgetsDetails?.options?.name || props.type);
|
||||
setIsReadOnly(false);
|
||||
setIsHideLabel(false);
|
||||
@@ -29,6 +31,7 @@ function DropdownWidgetOption(props) {
|
||||
setMaxCount(0);
|
||||
setDefaultCheckbox([]);
|
||||
setDefaultValue("");
|
||||
setLayout("vertical");
|
||||
};
|
||||
useEffect(() => {
|
||||
if (
|
||||
@@ -48,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();
|
||||
@@ -103,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,
|
||||
@@ -113,7 +141,8 @@ function DropdownWidgetOption(props) {
|
||||
null,
|
||||
status,
|
||||
defaultData,
|
||||
isHideLabel
|
||||
isHideLabel,
|
||||
WidgetLayout
|
||||
);
|
||||
resetState();
|
||||
};
|
||||
@@ -137,17 +166,18 @@ 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
|
||||
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]">
|
||||
@@ -228,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>
|
||||
@@ -270,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 ||
|
||||
@@ -313,7 +341,10 @@ function DropdownWidgetOption(props) {
|
||||
className="op-checkbox op-checkbox-sm"
|
||||
onChange={(e) => setIsReadOnly(e.target.checked)}
|
||||
/>
|
||||
<label className="ml-1 mb-0" htmlFor="isreadonly">
|
||||
<label
|
||||
className="ml-2 mb-0 capitalize"
|
||||
htmlFor="isreadonly"
|
||||
>
|
||||
{t("read-only")}
|
||||
</label>
|
||||
</div>
|
||||
@@ -328,15 +359,45 @@ function DropdownWidgetOption(props) {
|
||||
onChange={(e) => setIsHideLabel(e.target.checked)}
|
||||
/>
|
||||
|
||||
<label className="ml-1 mb-0" htmlFor="ishidelabel">
|
||||
<label
|
||||
className="ml-2 mb-0 capitalize"
|
||||
htmlFor="ishidelabel"
|
||||
>
|
||||
{t("hide-labels")}
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{["checkbox", radioButtonWidget].includes(props.type) && (
|
||||
<>
|
||||
<div className="text-[13px] font-semibold mt-[5px] capitalize">
|
||||
{t("layout")}
|
||||
</div>
|
||||
<div
|
||||
className={`${props.type === "checkbox" ? "mb-[10px]" : ""} flex flex-row gap-[10px] mt-[0.5rem]`}
|
||||
>
|
||||
{layoutArr.map((data, ind) => (
|
||||
<div
|
||||
key={ind}
|
||||
className="flex flex-row gap-[5px] items-center"
|
||||
>
|
||||
<input
|
||||
className="op-radio op-radio-xs my-1"
|
||||
type="radio"
|
||||
name="layout"
|
||||
checked={layout.toLowerCase() === data.toLowerCase()}
|
||||
onChange={() => setLayout(data.toLowerCase())}
|
||||
/>
|
||||
<label className="text-[13px] font-500 mb-0">
|
||||
{t(data)}
|
||||
</label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={`${
|
||||
props.type === "checkbox" && props.isShowAdvanceFeature
|
||||
|
||||
@@ -286,7 +286,7 @@ const EditTemplate = ({
|
||||
htmlFor="fileUpload"
|
||||
className="cursor-pointer text-center mb-0"
|
||||
>
|
||||
Browse or drag & drop a new file to replace the existng one
|
||||
{t("browse-or-drag-to-replace-existing-file")}
|
||||
</label>
|
||||
</div>
|
||||
<input
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import React from "react";
|
||||
import { Quill } from "react-quill-new";
|
||||
// Use a third-party plugin so editors can toggle raw HTML view.
|
||||
import htmlEditButton from "quill-html-edit-button";
|
||||
|
||||
// Register the module which adds a "<>" button for HTML editing.
|
||||
Quill.register("modules/htmlEditButton", htmlEditButton);
|
||||
|
||||
// Custom Undo button icon component for Quill editor. You can import it directly
|
||||
// from 'quill/assets/icons/undo.svg' but I found that a number of loaders do not
|
||||
@@ -56,7 +61,9 @@ export const module1 = {
|
||||
container: "#toolbar1",
|
||||
handlers: { undo: undoChange, redo: redoChange }
|
||||
},
|
||||
history: { delay: 500, maxStack: 100, userOnly: true }
|
||||
history: { delay: 500, maxStack: 100, userOnly: true },
|
||||
// Enable the "<>" button registered above
|
||||
htmlEditButton: {}
|
||||
};
|
||||
|
||||
// Modules object for setting up the Quill editor
|
||||
@@ -65,7 +72,9 @@ export const module2 = {
|
||||
container: "#toolbar2",
|
||||
handlers: { undo: undoChange, redo: redoChange }
|
||||
},
|
||||
history: { delay: 500, maxStack: 100, userOnly: true }
|
||||
history: { delay: 500, maxStack: 100, userOnly: true },
|
||||
// Enable the "<>" button registered above
|
||||
htmlEditButton: {}
|
||||
};
|
||||
|
||||
// Formats objects for setting up the Quill editor
|
||||
|
||||
@@ -186,7 +186,7 @@ function EmailComponent({
|
||||
<input
|
||||
type="email"
|
||||
value={emailValue}
|
||||
className="p-[10px] pb-[20px] rounded-md w-full text-[15px] outline-none bg-transparent border-[1px] op-border-primary"
|
||||
className="p-[10px] pb-[20px] text-base-content rounded-md w-full text-[15px] outline-none bg-transparent border-[1px] op-border-primary"
|
||||
onChange={handleEmailValue}
|
||||
onKeyDown={handleEnterPress}
|
||||
placeholder={t("enter-email-plaholder")}
|
||||
@@ -204,19 +204,6 @@ function EmailComponent({
|
||||
{t("email-error-1")}
|
||||
</p>
|
||||
)}
|
||||
{/* <button
|
||||
className={`${
|
||||
emailValue ? "cursor-pointer" : "cursor-default"
|
||||
} op-btn op-btn-primary op-btn-sm m-2 shadow-md`}
|
||||
onClick={() => emailValue && handleEnterPress("add")}
|
||||
>
|
||||
<i className="fa-light fa-plus" aria-hidden="true"></i>
|
||||
</button>
|
||||
<div className="bg-[#e3e2e1] mt-[10px] p-[5px] rounded">
|
||||
<span className="font-bold">{t("report-heading.Note")}: </span>
|
||||
<span className="text-[15px]">{t("email-error-2")}</span>
|
||||
</div>
|
||||
<hr className="w-full my-[15px] bg-base-content" /> */}
|
||||
<div className="mt-2">
|
||||
<button
|
||||
type="button"
|
||||
@@ -227,7 +214,7 @@ function EmailComponent({
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-ghost ml-2"
|
||||
className="op-btn op-btn-ghost text-base-content ml-2"
|
||||
onClick={() => handleClose()}
|
||||
>
|
||||
{t("close")}
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
import React, { useEffect, useState, useRef } from "react";
|
||||
import ModalUi from "../../primitives/ModalUi";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function PageReorderModal({
|
||||
isOpen,
|
||||
handleClose,
|
||||
totalPages = 0,
|
||||
onSave
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [order, setOrder] = useState([]);
|
||||
// Keeps track of the page order relative to the original PDF
|
||||
const orderRef = useRef([]);
|
||||
// Captures the order when the modal opens
|
||||
const initialOrderRef = useRef([]);
|
||||
|
||||
// Initialize orderRef when total pages change (e.g. after upload)
|
||||
useEffect(() => {
|
||||
if (orderRef.current.length !== totalPages) {
|
||||
orderRef.current = Array.from({ length: totalPages }, (_, i) => i + 1);
|
||||
}
|
||||
}, [totalPages]);
|
||||
|
||||
// When modal opens, display the last saved order and store it as initial
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
setOrder(orderRef.current);
|
||||
initialOrderRef.current = [...orderRef.current];
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
const move = (index, dir) => {
|
||||
const swapIndex = index + dir;
|
||||
if (swapIndex < 0 || swapIndex >= order.length) return;
|
||||
const newOrder = [...order];
|
||||
[newOrder[index], newOrder[swapIndex]] = [newOrder[swapIndex], newOrder[index]];
|
||||
setOrder(newOrder);
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
const saveOrder = order.map((num) =>
|
||||
initialOrderRef.current.indexOf(num) + 1
|
||||
);
|
||||
// Persist the new display order for next time
|
||||
orderRef.current = [...order];
|
||||
onSave && onSave(saveOrder);
|
||||
};
|
||||
|
||||
const isUnchanged =
|
||||
order.length === initialOrderRef.current.length &&
|
||||
order.every((n, i) => n === initialOrderRef.current[i]);
|
||||
|
||||
return (
|
||||
<ModalUi isOpen={isOpen} handleClose={handleClose} title={t("reorder-pages")}>
|
||||
<div className="p-[20px] flex flex-col gap-2 text-base-content">
|
||||
{order.map((num, i) => (
|
||||
<div key={num} className="flex items-center justify-between">
|
||||
<span>
|
||||
{t("page")} {num}
|
||||
</span>
|
||||
<div className="flex gap-1">
|
||||
<button
|
||||
className="op-btn op-btn-xs op-btn-ghost"
|
||||
disabled={i === 0}
|
||||
onClick={() => move(i, -1)}
|
||||
>
|
||||
<i className="fa-light fa-arrow-up"></i>
|
||||
</button>
|
||||
<button
|
||||
className="op-btn op-btn-xs op-btn-ghost"
|
||||
disabled={i === order.length - 1}
|
||||
onClick={() => move(i, 1)}
|
||||
>
|
||||
<i className="fa-light fa-arrow-down"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<div className="h-[1px] bg-[#9f9f9f] w-full my-[15px]"></div>
|
||||
<button onClick={handleSave} type="button" className="op-btn op-btn-primary" disabled={isUnchanged}>
|
||||
{t("save")}
|
||||
</button>
|
||||
<button
|
||||
onClick={handleClose}
|
||||
type="button"
|
||||
className="op-btn op-btn-ghost ml-1"
|
||||
>
|
||||
{t("close")}
|
||||
</button>
|
||||
</div>
|
||||
</ModalUi>
|
||||
);
|
||||
}
|
||||
@@ -2,16 +2,21 @@ import React, { useRef, useState } from "react";
|
||||
import PrevNext from "./PrevNext";
|
||||
import {
|
||||
base64ToArrayBuffer,
|
||||
decryptPdf,
|
||||
deletePdfPage,
|
||||
flattenPdf,
|
||||
getFileAsArrayBuffer,
|
||||
handleDownloadCertificate,
|
||||
handleDownloadPdf,
|
||||
handleRemoveWidgets,
|
||||
handleToPrint
|
||||
handleToPrint,
|
||||
reorderPdfPages
|
||||
} from "../../constant/Utils";
|
||||
import "../../styles/signature.css";
|
||||
import { DropdownMenu } from "radix-ui";
|
||||
import ModalUi from "../../primitives/ModalUi";
|
||||
import Loader from "../../primitives/Loader";
|
||||
import PageReorderModal from "./PageReorderModal";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { PDFDocument } from "pdf-lib";
|
||||
import { maxFileSize } from "../../constant/const";
|
||||
@@ -24,6 +29,7 @@ function Header(props) {
|
||||
const isMobile = window.innerWidth < 767;
|
||||
const [isDownloading, setIsDownloading] = useState("");
|
||||
const [isDeletePage, setIsDeletePage] = useState(false);
|
||||
const [isReorderModal, setIsReorderModal] = useState(false);
|
||||
const mergePdfInputRef = useRef(null);
|
||||
const enabledBackBtn = props?.disabledBackBtn === true ? false : true;
|
||||
//function for show decline alert
|
||||
@@ -80,7 +86,42 @@ function Header(props) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const uploadedPdfBytes = await file.arrayBuffer();
|
||||
let uploadedPdfBytes = await file.arrayBuffer();
|
||||
try {
|
||||
uploadedPdfBytes = await flattenPdf(uploadedPdfBytes);
|
||||
} catch (err) {
|
||||
if (err?.message?.includes("is encrypted")) {
|
||||
try {
|
||||
const pdfFile = await decryptPdf(file, "");
|
||||
const pdfArrayBuffer = await getFileAsArrayBuffer(pdfFile);
|
||||
uploadedPdfBytes = await flattenPdf(pdfArrayBuffer);
|
||||
} catch (err) {
|
||||
if (err?.response?.status === 401) {
|
||||
const password = prompt(
|
||||
`PDF "${file.name}" is password-protected. Enter password:`
|
||||
);
|
||||
if (password) {
|
||||
try {
|
||||
const pdfFile = await decryptPdf(file, password);
|
||||
const pdfArrayBuffer = await getFileAsArrayBuffer(pdfFile);
|
||||
uploadedPdfBytes = await flattenPdf(pdfArrayBuffer);
|
||||
// Upload the file to Parse Server
|
||||
} catch (err) {
|
||||
console.error("Incorrect password or decryption failed", err);
|
||||
alert("Incorrect password or decryption failed.");
|
||||
}
|
||||
} else {
|
||||
alert("Please provided Password.");
|
||||
}
|
||||
} else {
|
||||
console.log("Err ", err);
|
||||
alert("error while uploading pdf.");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
alert("error while uploading pdf.");
|
||||
}
|
||||
}
|
||||
const uploadedPdfDoc = await PDFDocument.load(uploadedPdfBytes, {
|
||||
ignoreEncryption: true
|
||||
});
|
||||
@@ -106,12 +147,27 @@ function Header(props) {
|
||||
console.error("Error merging PDF:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleReorderSave = async (order) => {
|
||||
try {
|
||||
const pdfupdatedData = await reorderPdfPages(props.pdfArrayBuffer, order);
|
||||
if (pdfupdatedData) {
|
||||
props.setPdfArrayBuffer(pdfupdatedData.arrayBuffer);
|
||||
props.setPdfBase64Url(pdfupdatedData.base64);
|
||||
props.setAllPages(pdfupdatedData.totalPages);
|
||||
props.setPageNumber(1);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("error in reorder pdf pages", e);
|
||||
}
|
||||
setIsReorderModal(false);
|
||||
};
|
||||
return (
|
||||
<div className="flex py-[5px]">
|
||||
{isMobile && props?.isShowHeader ? (
|
||||
<div
|
||||
id="navbar"
|
||||
className="stickyHead"
|
||||
className="stickyHead touch-none"
|
||||
style={{
|
||||
width: window.innerWidth + "px"
|
||||
}}
|
||||
@@ -334,6 +390,17 @@ function Header(props) {
|
||||
</span>
|
||||
</div>
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item
|
||||
className="DropdownMenuItem"
|
||||
onClick={() => setIsReorderModal(true)}
|
||||
>
|
||||
<div className="flex flex-row">
|
||||
<i className="fa-light fa-list-ol text-gray-500 2xl:text-[30px] mr-[3px]"></i>
|
||||
<span className="font-[500]">
|
||||
{t("reorder-pages")}
|
||||
</span>
|
||||
</div>
|
||||
</DropdownMenu.Item>
|
||||
|
||||
<DropdownMenu.Item
|
||||
className="DropdownMenuItem"
|
||||
@@ -696,6 +763,12 @@ function Header(props) {
|
||||
</button>
|
||||
</div>
|
||||
</ModalUi>
|
||||
<PageReorderModal
|
||||
isOpen={isReorderModal}
|
||||
handleClose={() => setIsReorderModal(false)}
|
||||
totalPages={props.allPages}
|
||||
onSave={handleReorderSave}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,18 +1,24 @@
|
||||
import React, { useRef, useState } from "react";
|
||||
import { useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
base64ToArrayBuffer,
|
||||
decryptPdf,
|
||||
deletePdfPage,
|
||||
handleRemoveWidgets
|
||||
flattenPdf,
|
||||
getFileAsArrayBuffer,
|
||||
handleRemoveWidgets,
|
||||
reorderPdfPages
|
||||
} from "../../constant/Utils";
|
||||
import ModalUi from "../../primitives/ModalUi";
|
||||
import { PDFDocument } from "pdf-lib";
|
||||
import { maxFileSize } from "../../constant/const";
|
||||
import PageReorderModal from "./PageReorderModal";
|
||||
|
||||
function PdfZoom(props) {
|
||||
const { t } = useTranslation();
|
||||
const mergePdfInputRef = useRef(null);
|
||||
const [isDeletePage, setIsDeletePage] = useState(false);
|
||||
const [isReorderModal, setIsReorderModal] = useState(false);
|
||||
const handleDetelePage = async () => {
|
||||
props.setIsUploadPdf && props.setIsUploadPdf(true);
|
||||
try {
|
||||
@@ -67,7 +73,42 @@ function PdfZoom(props) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const uploadedPdfBytes = await file.arrayBuffer();
|
||||
let uploadedPdfBytes = await file.arrayBuffer();
|
||||
try {
|
||||
uploadedPdfBytes = await flattenPdf(uploadedPdfBytes);
|
||||
} catch (err) {
|
||||
if (err?.message?.includes("is encrypted")) {
|
||||
try {
|
||||
const pdfFile = await decryptPdf(file, "");
|
||||
const pdfArrayBuffer = await getFileAsArrayBuffer(pdfFile);
|
||||
uploadedPdfBytes = await flattenPdf(pdfArrayBuffer);
|
||||
} catch (err) {
|
||||
if (err?.response?.status === 401) {
|
||||
const password = prompt(
|
||||
`PDF "${file.name}" is password-protected. Enter password:`
|
||||
);
|
||||
if (password) {
|
||||
try {
|
||||
const pdfFile = await decryptPdf(file, password);
|
||||
const pdfArrayBuffer = await getFileAsArrayBuffer(pdfFile);
|
||||
uploadedPdfBytes = await flattenPdf(pdfArrayBuffer);
|
||||
// Upload the file to Parse Server
|
||||
} catch (err) {
|
||||
console.error("Incorrect password or decryption failed", err);
|
||||
alert("Incorrect password or decryption failed.");
|
||||
}
|
||||
} else {
|
||||
alert("Please provided Password.");
|
||||
}
|
||||
} else {
|
||||
console.log("Err ", err);
|
||||
alert("error while uploading pdf.");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
alert("error while uploading pdf.");
|
||||
}
|
||||
}
|
||||
const uploadedPdfDoc = await PDFDocument.load(uploadedPdfBytes, {
|
||||
ignoreEncryption: true
|
||||
});
|
||||
@@ -94,6 +135,21 @@ function PdfZoom(props) {
|
||||
}
|
||||
};
|
||||
|
||||
const handleReorderSave = async (order) => {
|
||||
try {
|
||||
const pdfupdatedData = await reorderPdfPages(props.pdfArrayBuffer, order);
|
||||
if (pdfupdatedData) {
|
||||
props.setPdfArrayBuffer(pdfupdatedData.arrayBuffer);
|
||||
props.setPdfBase64Url(pdfupdatedData.base64);
|
||||
props.setAllPages(pdfupdatedData.totalPages);
|
||||
props.setPageNumber(1);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("error in reorder pdf pages", e);
|
||||
}
|
||||
setIsReorderModal(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<span className="hidden md:flex flex-col gap-1 text-center md:w-[5%] mt-[42px]">
|
||||
@@ -120,6 +176,13 @@ function PdfZoom(props) {
|
||||
>
|
||||
<i className="fa-light fa-trash text-gray-500 2xl:text-[25px]"></i>
|
||||
</span>
|
||||
<span
|
||||
className="bg-gray-50 px-[4px] 2xl:py-[10px] cursor-pointer"
|
||||
onClick={() => setIsReorderModal(true)}
|
||||
title={t("reorder-pages")}
|
||||
>
|
||||
<i className="fa-light fa-list-ol text-gray-500 2xl:text-[25px]"></i>
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
<span
|
||||
@@ -185,6 +248,12 @@ function PdfZoom(props) {
|
||||
</button>
|
||||
</div>
|
||||
</ModalUi>
|
||||
<PageReorderModal
|
||||
isOpen={isReorderModal}
|
||||
handleClose={() => setIsReorderModal(false)}
|
||||
totalPages={props.allPages}
|
||||
onSave={handleReorderSave}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
import BorderResize from "./BorderResize";
|
||||
import { Rnd } from "react-rnd";
|
||||
import {
|
||||
@@ -13,7 +13,9 @@ import {
|
||||
onChangeInput,
|
||||
radioButtonWidget,
|
||||
textInputWidget,
|
||||
textWidget
|
||||
cellsWidget,
|
||||
textWidget,
|
||||
selectFormat
|
||||
} from "../../constant/Utils";
|
||||
import PlaceholderType from "./PlaceholderType";
|
||||
import moment from "moment";
|
||||
@@ -24,37 +26,6 @@ import { useDispatch } from "react-redux";
|
||||
import { setIsShowModal } from "../../redux/reducers/widgetSlice";
|
||||
import { themeColor } from "../../constant/const";
|
||||
|
||||
const selectFormat = (data) => {
|
||||
switch (data) {
|
||||
case "L":
|
||||
return "MM/dd/yyyy";
|
||||
case "MM/DD/YYYY":
|
||||
return "MM/dd/yyyy";
|
||||
case "DD-MM-YYYY":
|
||||
return "dd-MM-yyyy";
|
||||
case "DD/MM/YYYY":
|
||||
return "dd/MM/yyyy";
|
||||
case "LL":
|
||||
return "MMMM dd, yyyy";
|
||||
case "DD MMM, YYYY":
|
||||
return "dd MMM, yyyy";
|
||||
case "YYYY-MM-DD":
|
||||
return "yyyy-MM-dd";
|
||||
case "MM-DD-YYYY":
|
||||
return "MM-dd-yyyy";
|
||||
case "MM.DD.YYYY":
|
||||
return "MM.dd.yyyy";
|
||||
case "MMM DD, YYYY":
|
||||
return "MMM dd, yyyy";
|
||||
case "MMMM DD, YYYY":
|
||||
return "MMMM dd, yyyy";
|
||||
case "DD MMMM, YYYY":
|
||||
return "dd MMMM, yyyy";
|
||||
default:
|
||||
return "MM/dd/yyyy";
|
||||
}
|
||||
};
|
||||
|
||||
//function to get default format
|
||||
const getDefaultFormat = (dateFormat) => dateFormat || "MM/dd/yyyy";
|
||||
|
||||
@@ -80,11 +51,12 @@ function Placeholder(props) {
|
||||
props.pos?.options?.defaultValue || props.pos?.options?.response;
|
||||
const [isDateModal, setIsDateModal] = useState(false);
|
||||
const [containerScale, setContainerScale] = useState();
|
||||
const holdTimeout = useRef(null);
|
||||
const startTime = useRef(null); // Track when the user starts holdings
|
||||
const [selectDate, setSelectDate] = useState({});
|
||||
const [dateFormat, setDateFormat] = useState([]);
|
||||
const [clickonWidget, setClickonWidget] = useState({});
|
||||
const [isDateReadOnly, setIsDateReadOnly] = useState(
|
||||
props?.pos?.options?.isReadOnly || false
|
||||
);
|
||||
const startDate = props?.pos?.options?.response
|
||||
? getDefaultDate(
|
||||
props?.pos?.options?.response,
|
||||
@@ -108,7 +80,9 @@ function Placeholder(props) {
|
||||
"MMM DD, YYYY",
|
||||
"LL",
|
||||
"DD MMM, YYYY",
|
||||
"DD MMMM, YYYY"
|
||||
"DD MMMM, YYYY",
|
||||
"DD.MM.YYYY",
|
||||
"DD/MM/YYYY"
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
@@ -130,15 +104,18 @@ function Placeholder(props) {
|
||||
if (selectDate && selectDate.format === "dd-MM-yyyy") {
|
||||
const [day, month, year] = selectDate.date.split("-");
|
||||
date = new Date(`${year}-${month}-${day}`);
|
||||
} else if (selectDate && selectDate.format === "dd.MM.yyyy") {
|
||||
const [day, month, year] = selectDate.date.split(".");
|
||||
date = new Date(`${year}.${month}.${day}`);
|
||||
} else if (selectDate && selectDate.format === "dd/MM/yyyy") {
|
||||
const [day, month, year] = selectDate.date.split("/");
|
||||
date = new Date(`${year}/${month}/${day}`);
|
||||
} else {
|
||||
date = new Date(selectDate?.date);
|
||||
}
|
||||
const milliseconds = date.getTime();
|
||||
const newDate = moment(milliseconds).format(data);
|
||||
const dateObj = {
|
||||
date: newDate,
|
||||
format: selectFormat(data)
|
||||
};
|
||||
const dateObj = { date: newDate, format: selectFormat(data) };
|
||||
updateDate.push(dateObj);
|
||||
});
|
||||
setDateFormat(updateDate);
|
||||
@@ -203,13 +180,8 @@ function Placeholder(props) {
|
||||
|
||||
const widgetClickHandler = () => {
|
||||
props.setCurrWidgetsDetails && props.setCurrWidgetsDetails(props.pos);
|
||||
//condition to check in request signing flow user click on agree or not
|
||||
if (props?.data?.signerObjId === props?.signerObjId && !props.isDragging) {
|
||||
if (!props.isAgree && !props.isSelfSign) {
|
||||
props.setIsAgreeTour && props.setIsAgreeTour(true);
|
||||
} else {
|
||||
handleWidgetIdandPopup();
|
||||
}
|
||||
handleWidgetIdandPopup();
|
||||
} //condition for open contact details popup when sign templet from public signing flow
|
||||
else if (props?.uniqueId && props.data?.Id === props?.uniqueId) {
|
||||
handleWidgetIdandPopup();
|
||||
@@ -303,6 +275,13 @@ function Placeholder(props) {
|
||||
} else if (props.pos.type === "checkbox") {
|
||||
props?.setIsCheckbox(true);
|
||||
}
|
||||
// cells widget settings in sign yourself flow
|
||||
else if (
|
||||
props.pos.type === cellsWidget &&
|
||||
(props.isSignYourself || props.isSelfSign)
|
||||
) {
|
||||
props.handleCellSettingModal && props.handleCellSettingModal();
|
||||
}
|
||||
//condition to handle setting icon for signyour-self flow for all type text widgets
|
||||
else if (
|
||||
[
|
||||
@@ -382,9 +361,12 @@ function Placeholder(props) {
|
||||
|
||||
//function to save date and format on local array onchange date and onclick format
|
||||
const handleSaveDate = (data, isDateChange) => {
|
||||
const isSpecialDateFormat =
|
||||
data?.format &&
|
||||
["dd-MM-yyyy", "dd.MM.yyyy", "dd/MM/yyyy"].includes(data?.format);
|
||||
let updateDate = data.date;
|
||||
let date;
|
||||
if (data?.format === "dd-MM-yyyy") {
|
||||
if (isSpecialDateFormat) {
|
||||
date = isDateChange
|
||||
? moment(updateDate).format(changeDateToMomentFormat(data.format))
|
||||
: updateDate;
|
||||
@@ -406,11 +388,54 @@ function Placeholder(props) {
|
||||
false,
|
||||
data?.format,
|
||||
props.fontSize || props.pos?.options?.fontSize || 12,
|
||||
props.fontColor || props.pos?.options?.fontColor || "black"
|
||||
props.fontColor || props.pos?.options?.fontColor || "black",
|
||||
isDateReadOnly || false
|
||||
);
|
||||
setSelectDate({ date: date, format: data?.format });
|
||||
};
|
||||
|
||||
const setCellCount = (key, newCount) => {
|
||||
props.setXyPosition((prev) => {
|
||||
const isSignerList = prev.some((d) => d.signerPtr);
|
||||
if (isSignerList) {
|
||||
const signerId = props.data?.Id || props.uniqueId;
|
||||
const filterSignerPos = prev.filter((d) => d.Id === signerId);
|
||||
if (filterSignerPos.length > 0) {
|
||||
const getPlaceHolder = filterSignerPos[0].placeHolder;
|
||||
const updatedPlaceHolder = getPlaceHolder.map((ph) => {
|
||||
if (ph.pageNumber !== props.pageNumber) return ph;
|
||||
const newPos = ph.pos.map((p) =>
|
||||
p.key === key
|
||||
? { ...p, options: { ...p.options, cellCount: newCount } }
|
||||
: p
|
||||
);
|
||||
return { ...ph, pos: newPos };
|
||||
});
|
||||
return prev.map((obj) =>
|
||||
obj.Id === signerId
|
||||
? { ...obj, placeHolder: updatedPlaceHolder }
|
||||
: obj
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const updatePos = prev[props.index].pos.map((p) =>
|
||||
p.key === key
|
||||
? { ...p, options: { ...p.options, cellCount: newCount } }
|
||||
: p
|
||||
);
|
||||
return prev.map((obj, ind) =>
|
||||
ind === props.index ? { ...obj, pos: updatePos } : obj
|
||||
);
|
||||
}
|
||||
return prev;
|
||||
});
|
||||
};
|
||||
const PlaceholderIcon = () => {
|
||||
const isSettingForCells =
|
||||
props?.isAlllowModify && !props?.assignedWidgetId.includes(props.pos.key)
|
||||
? []
|
||||
: [cellsWidget];
|
||||
|
||||
// 1- If props.isShowBorder is true, display border's icon for all widgets. OR
|
||||
// 2- Use the combination of props?.isAlllowModify and !props?.assignedWidgetId.includes(props.pos.key) to determine when to show border's icon:
|
||||
// 1- When isAlllowModify is true, show border's icon.
|
||||
@@ -434,10 +459,15 @@ function Placeholder(props) {
|
||||
"name",
|
||||
"company",
|
||||
"job title",
|
||||
"email"
|
||||
"email",
|
||||
...isSettingForCells
|
||||
].includes(props.pos.type) &&
|
||||
(props.isSignYourself || props.isSelfSign) ? (
|
||||
<i
|
||||
onPointerDown={(e) => {
|
||||
e.stopPropagation();
|
||||
handleOnClickSettingIcon();
|
||||
}}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleOnClickSettingIcon();
|
||||
@@ -447,7 +477,14 @@ function Placeholder(props) {
|
||||
handleOnClickSettingIcon();
|
||||
}}
|
||||
className="fa-light fa-gear icon"
|
||||
style={{ color: "#188ae2", right: "29px", top: "-19px" }}
|
||||
style={{
|
||||
color: "#188ae2",
|
||||
right: "29px",
|
||||
top: "-19px",
|
||||
cursor: "pointer",
|
||||
zIndex: 99,
|
||||
pointerEvents: "auto"
|
||||
}}
|
||||
></i>
|
||||
) : (
|
||||
/* condition to add setting icon for placeholder & template flow for all widgets except signature and date */
|
||||
@@ -457,6 +494,10 @@ function Placeholder(props) {
|
||||
!props.isSignYourself &&
|
||||
!props.isSelfSign)) && (
|
||||
<i
|
||||
onPointerDown={(e) => {
|
||||
e.stopPropagation();
|
||||
handleOnClickSettingIcon();
|
||||
}}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleOnClickSettingIcon();
|
||||
@@ -469,7 +510,10 @@ function Placeholder(props) {
|
||||
style={{
|
||||
color: "#188ae2",
|
||||
right: props?.pos?.type === textWidget ? "32px" : "51px",
|
||||
top: "-19px"
|
||||
top: "-19px",
|
||||
cursor: "pointer",
|
||||
zIndex: 99,
|
||||
pointerEvents: "auto"
|
||||
}}
|
||||
></i>
|
||||
)
|
||||
@@ -507,6 +551,7 @@ function Placeholder(props) {
|
||||
{/* setting icon only for date widgets */}
|
||||
{props.pos.type === "date" && selectDate && (
|
||||
<i
|
||||
onPointerDown={(e) => e.stopPropagation()}
|
||||
onClick={(e) => {
|
||||
props.setCurrWidgetsDetails(props.pos);
|
||||
setIsDateModal(!isDateModal);
|
||||
@@ -539,7 +584,9 @@ function Placeholder(props) {
|
||||
top: "-18px",
|
||||
right: props.isPlaceholder ? "50px" : "30px",
|
||||
color: "#188ae2",
|
||||
fontSize: "14px"
|
||||
fontSize: "14px",
|
||||
cursor: "pointer",
|
||||
pointerEvents: "auto"
|
||||
}}
|
||||
className="fa-light fa-gear icon"
|
||||
></i>
|
||||
@@ -666,7 +713,7 @@ function Placeholder(props) {
|
||||
return "not-allowed";
|
||||
}
|
||||
} else {
|
||||
return "all-scroll";
|
||||
return "move";
|
||||
}
|
||||
};
|
||||
|
||||
@@ -686,21 +733,6 @@ function Placeholder(props) {
|
||||
return "rgba(203, 233, 237, 0.69)";
|
||||
}
|
||||
};
|
||||
const handleTouchEnd = () => {
|
||||
if (!props.isNeedSign || props.isAlllowModify) {
|
||||
const holdDuration = Date.now() - startTime.current; // Calculate hold time
|
||||
clearTimeout(holdTimeout.current); // Cancel timeout if touch ended early
|
||||
|
||||
if (holdDuration < 1000) {
|
||||
try {
|
||||
navigator.vibrate([]); // Cancel any ongoing vibration
|
||||
} catch (e) {
|
||||
console.log("error in navigator.vibrate", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!props.isNeedSign || props.isAlllowModify) handleOnClickPlaceholder();
|
||||
};
|
||||
|
||||
const fontSize = calculateFont(props.pos.options?.fontSize);
|
||||
const fontColor = props.pos.options?.fontColor || "black";
|
||||
@@ -751,20 +783,16 @@ function Placeholder(props) {
|
||||
id={props.pos.key}
|
||||
data-tut={props.pos.key === props.unSignedWidgetId ? "IsSigned" : ""}
|
||||
key={props.pos.key}
|
||||
cancel=".cell-size-handle, .icon"
|
||||
lockAspectRatio={
|
||||
!props.isFreeResize &&
|
||||
![
|
||||
textWidget,
|
||||
"email",
|
||||
"name",
|
||||
"company",
|
||||
"job title",
|
||||
textInputWidget
|
||||
].includes(props.pos.type) &&
|
||||
(props.pos.Width
|
||||
? props.pos.Width / props.pos.Height
|
||||
: defaultWidthHeight(props.pos.type).width /
|
||||
defaultWidthHeight(props.pos.type).height)
|
||||
props?.isAlllowModify &&
|
||||
!props?.assignedWidgetId.includes(props.pos.key)
|
||||
? false
|
||||
: !props.isFreeResize &&
|
||||
(props.pos.Width
|
||||
? props.pos.Width / props.pos.Height
|
||||
: defaultWidthHeight(props.pos.type).width /
|
||||
defaultWidthHeight(props.pos.type).height)
|
||||
}
|
||||
enableResizing={{
|
||||
top: false,
|
||||
@@ -804,8 +832,16 @@ function Placeholder(props) {
|
||||
props.isNeedSign && props.data?.Id !== props?.uniqueId && "0.4",
|
||||
background: handleBackground()
|
||||
}}
|
||||
onDrag={() => {
|
||||
onDrag={(_, d) => {
|
||||
props.handleTabDrag && props.handleTabDrag(props.pos.key);
|
||||
props.showGuidelines &&
|
||||
props.showGuidelines(
|
||||
true,
|
||||
d.x,
|
||||
d.y,
|
||||
d.node.offsetWidth,
|
||||
d.node.offsetHeight
|
||||
);
|
||||
}}
|
||||
size={{
|
||||
width:
|
||||
@@ -820,12 +856,32 @@ function Placeholder(props) {
|
||||
: props.posHeight(props.pos, props.isSignYourself)
|
||||
}}
|
||||
minHeight={
|
||||
props.pos.type !== "checkbox" &&
|
||||
calculateFont(props.pos.options?.fontSize, true)
|
||||
props.pos.type === cellsWidget
|
||||
? calculateFont(props.pos.options?.fontSize, true)
|
||||
: props.pos.type !== "checkbox" &&
|
||||
calculateFont(props.pos.options?.fontSize, true)
|
||||
}
|
||||
maxHeight="auto"
|
||||
onResizeStart={() => {
|
||||
onResizeStart={(e, dir, ref) => {
|
||||
props.setIsResize && props.setIsResize(true);
|
||||
props.showGuidelines &&
|
||||
props.showGuidelines(
|
||||
true,
|
||||
xPos(props.pos, props.isSignYourself),
|
||||
yPos(props.pos, props.isSignYourself),
|
||||
ref.offsetWidth,
|
||||
ref.offsetHeight
|
||||
);
|
||||
}}
|
||||
onResize={(e, dir, ref, delta, position) => {
|
||||
props.showGuidelines &&
|
||||
props.showGuidelines(
|
||||
true,
|
||||
position.x,
|
||||
position.y,
|
||||
ref.offsetWidth,
|
||||
ref.offsetHeight
|
||||
);
|
||||
}}
|
||||
onResizeStop={(e, direction, ref) => {
|
||||
setTimeout(() => {
|
||||
@@ -843,6 +899,7 @@ function Placeholder(props) {
|
||||
props.data && props.data.Id,
|
||||
props.isResize
|
||||
);
|
||||
props.showGuidelines && props.showGuidelines(false);
|
||||
}}
|
||||
onDragStop={(event, dragElement) => {
|
||||
props.handleStop &&
|
||||
@@ -852,6 +909,9 @@ function Placeholder(props) {
|
||||
props.data?.Id,
|
||||
props.pos?.key
|
||||
);
|
||||
props.isDragging &&
|
||||
props.showGuidelines &&
|
||||
props.showGuidelines(false);
|
||||
}}
|
||||
position={{
|
||||
x: xPos(props.pos, props.isSignYourself),
|
||||
@@ -888,24 +948,24 @@ function Placeholder(props) {
|
||||
![radioButtonWidget, "checkbox"].includes(props.pos.type) &&
|
||||
props.pos.key === props?.currWidgetsDetails?.key && <BorderResize />
|
||||
)}
|
||||
|
||||
{/* 1- Show a ouline if props.pos.key === props?.currWidgetsDetails?.key, indicating the current user's selected widget.
|
||||
2- If props.isShowBorder is true, display ouline for all widgets.
|
||||
3- Use the combination of props?.isAlllowModify and !props?.assignedWidgetId.includes(props.pos.key) to determine when to show ouline:
|
||||
3.1- When isAlllowModify is true, show ouline.
|
||||
3.2- Do not display ouline for widgets already assigned (props.assignedWidgetId.includes(props.pos.key) is true).
|
||||
{/* 1- Show a border if props.pos.key === props?.currWidgetsDetails?.key, indicating the current user's selected widget.
|
||||
2- If props.isShowBorder is true, display border for all widgets.
|
||||
3- Use the combination of props?.isAlllowModify and !props?.assignedWidgetId.includes(props.pos.key) to determine when to show border:
|
||||
3.1- When isAlllowModify is true, show border.
|
||||
3.2- Do not display border for widgets already assigned (props.assignedWidgetId.includes(props.pos.key) is true).
|
||||
*/}
|
||||
{props.pos.key === props?.currWidgetsDetails?.key &&
|
||||
(props.isShowBorder ||
|
||||
(props?.isAlllowModify &&
|
||||
!props?.assignedWidgetId.includes(props.pos.key))) && (
|
||||
<div
|
||||
style={{ borderColor: themeColor }}
|
||||
className="w-[calc(100%+21px)] h-[calc(100%+21px)] cursor-move absolute inline-block border-[1px] border-dashed"
|
||||
></div>
|
||||
)}
|
||||
<div
|
||||
className={`${
|
||||
props.pos.key === props?.currWidgetsDetails?.key &&
|
||||
(props.isShowBorder ||
|
||||
(props?.isAlllowModify &&
|
||||
!props?.assignedWidgetId.includes(props.pos.key)))
|
||||
? "outline-[0.3px] outline-dashed outline-offset-[10px]"
|
||||
: ""
|
||||
} flex items-stretch justify-center`}
|
||||
className="flex items-stretch justify-center"
|
||||
style={{
|
||||
outlineColor: themeColor,
|
||||
left: xPos(props.pos, props.isSignYourself),
|
||||
top: yPos(props.pos, props.isSignYourself),
|
||||
width: "100%",
|
||||
@@ -938,18 +998,19 @@ function Placeholder(props) {
|
||||
handleSaveDate={handleSaveDate}
|
||||
xPos={props.xPos}
|
||||
calculateFont={calculateFont}
|
||||
setCellCount={setCellCount}
|
||||
/>
|
||||
</div>
|
||||
</Rnd>
|
||||
)}
|
||||
|
||||
<ModalUi isOpen={isDateModal} title={t("widget-info")} showClose={false}>
|
||||
<div className="h-[100%] p-[20px]">
|
||||
<div className="flex flex-row items-center">
|
||||
<span>{t("format")} : </span>
|
||||
<div className="flex">
|
||||
<div className="text-base-content h-[100%] p-[20px]">
|
||||
<div className="flex flex-col md:flex-row md:items-center gap-y-3">
|
||||
<div className="flex flex-row items-center gap-x-1">
|
||||
<span className="capitalize">{t("format")} :</span>
|
||||
<select
|
||||
className="ml-[7px] op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs"
|
||||
className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs"
|
||||
defaultValue={""}
|
||||
onChange={(e) => {
|
||||
const selectedIndex = e.target.value;
|
||||
@@ -973,23 +1034,23 @@ function Placeholder(props) {
|
||||
{selectDate.format}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center mt-4 md:mt-2">
|
||||
<span>{t("font-size")} :</span>
|
||||
<select
|
||||
className="ml-[3px] md:ml:[7px] op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs"
|
||||
value={props.fontSize || clickonWidget.options?.fontSize || 12}
|
||||
onChange={(e) => props.setFontSize(parseInt(e.target.value))}
|
||||
>
|
||||
{fontsizeArr.map((size, ind) => {
|
||||
return (
|
||||
<div className="flex flex-col md:flex-row gap-y-2 md:gap-y-0 gap-x-2 mt-3">
|
||||
<div className="flex flex-row items-center">
|
||||
<span className="capitalize">{t("font-size")} :</span>
|
||||
<select
|
||||
className="ml-[3px] md:ml:[7px] op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs"
|
||||
value={props.fontSize || clickonWidget.options?.fontSize || 12}
|
||||
onChange={(e) => props.setFontSize(parseInt(e.target.value))}
|
||||
>
|
||||
{fontsizeArr.map((size, ind) => (
|
||||
<option className="text-[13px]" value={size} key={ind}>
|
||||
{size}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</select>
|
||||
<div className="flex flex-row gap-1 items-center ml-2 md:ml-4 ">
|
||||
<span>{t("color")}: </span>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="flex flex-row gap-1 items-center">
|
||||
<span className="capitalize">{t("color")} :</span>
|
||||
<select
|
||||
value={
|
||||
props.fontColor || clickonWidget.options?.fontColor || "black"
|
||||
@@ -997,13 +1058,11 @@ function Placeholder(props) {
|
||||
onChange={(e) => props.setFontColor(e.target.value)}
|
||||
className="ml-[4px] md:ml[7px] op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs"
|
||||
>
|
||||
{fontColorArr.map((color, ind) => {
|
||||
return (
|
||||
<option value={color} key={ind}>
|
||||
{t(`color-type.${color}`)}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
{fontColorArr.map((color, ind) => (
|
||||
<option value={color} key={ind}>
|
||||
{t(`color-type.${color}`)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<span
|
||||
style={{
|
||||
@@ -1014,7 +1073,26 @@ function Placeholder(props) {
|
||||
></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{props?.isPlaceholder && (
|
||||
<div className="flex items-center mt-3">
|
||||
<input
|
||||
id="isReadOnly"
|
||||
name="isReadOnly"
|
||||
type="checkbox"
|
||||
checked={
|
||||
isDateReadOnly || props.pos.options?.isReadOnly || false
|
||||
}
|
||||
className="op-checkbox op-checkbox-xs"
|
||||
onChange={() => setIsDateReadOnly(!isDateReadOnly)}
|
||||
/>
|
||||
<label
|
||||
className="ml-1.5 mb-0 capitalize text-[13px]"
|
||||
htmlFor="isreadonly"
|
||||
>
|
||||
{t("read-only")}
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
<div className="h-[1px] w-full my-[15px] bg-[#9f9f9f]"></div>
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import React, { useEffect, useState, forwardRef, useRef } from "react";
|
||||
import React, { useEffect, useState, forwardRef } from "react";
|
||||
import {
|
||||
getMonth,
|
||||
getYear,
|
||||
radioButtonWidget,
|
||||
textInputWidget,
|
||||
cellsWidget,
|
||||
textWidget,
|
||||
months,
|
||||
years,
|
||||
@@ -14,8 +15,9 @@ 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 bg-white";
|
||||
"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 =
|
||||
@@ -27,10 +29,13 @@ function PlaceholderType(props) {
|
||||
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?.defaultValue || props.pos?.options?.response;
|
||||
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("");
|
||||
@@ -56,16 +61,13 @@ function PlaceholderType(props) {
|
||||
[]
|
||||
);
|
||||
} else {
|
||||
if (widgetData) {
|
||||
setwidgetValue(widgetData);
|
||||
}
|
||||
// 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);
|
||||
} else {
|
||||
setHint(props.pos?.type);
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@@ -77,7 +79,8 @@ function PlaceholderType(props) {
|
||||
color: fontColor,
|
||||
fontFamily: "Arial, sans-serif"
|
||||
}}
|
||||
className={`${selectWidgetCls} overflow-hidden`}
|
||||
className={`${isReadOnly ? `select-none` : ``} ${selectWidgetCls} overflow-hidden`}
|
||||
disabled={isReadOnly}
|
||||
onClick={onClick}
|
||||
ref={ref}
|
||||
>
|
||||
@@ -105,7 +108,7 @@ function PlaceholderType(props) {
|
||||
alt="signature"
|
||||
draggable="false"
|
||||
src={props.pos.SignUrl}
|
||||
className="w-full h-full select-none-cls "
|
||||
className={`${props.pos.signatureType !== "type" ? "object-contain" : ""} w-full h-full select-none-cls`}
|
||||
/>
|
||||
) : (
|
||||
<div className={widgetCls}>
|
||||
@@ -129,7 +132,7 @@ function PlaceholderType(props) {
|
||||
alt="stamp"
|
||||
draggable="false"
|
||||
src={props.pos.SignUrl}
|
||||
className="w-full h-full select-none-cls"
|
||||
className="w-full h-full select-none-cls object-contain"
|
||||
/>
|
||||
) : (
|
||||
<div className={widgetCls}>
|
||||
@@ -148,68 +151,59 @@ function PlaceholderType(props) {
|
||||
</div>
|
||||
);
|
||||
case "checkbox":
|
||||
const checkBoxLayout = props.pos.options?.layout || "vertical";
|
||||
const isMultipleCheckbox =
|
||||
props.pos.options?.values?.length > 0 ? true : false;
|
||||
const checkBoxWrapperClass = `flex items-start ${
|
||||
checkBoxLayout === "horizontal"
|
||||
? `flex-row flex-wrap ${isMultipleCheckbox ? "gap-x-2" : ""}`
|
||||
: `flex-col ${isMultipleCheckbox ? "gap-y-[3px]" : ""}`
|
||||
}`; // Using gap-y-1 for consistency, adjust if needed
|
||||
|
||||
return (
|
||||
<div style={{ zIndex: props.isSignYourself && "99" }}>
|
||||
{props.pos.options?.values?.map((data, ind) => {
|
||||
return (
|
||||
<div key={ind} className="select-none-cls pointer-events-none">
|
||||
<label
|
||||
htmlFor={`checkbox-${props.pos.key + ind}`}
|
||||
style={{ fontSize: fontSize, color: fontColor }}
|
||||
className={`mb-0 flex items-center gap-1 ${
|
||||
ind > 0 ? "mt-[3px]" : "mt-[0px]"
|
||||
}`}
|
||||
>
|
||||
<input
|
||||
id={`checkbox-${props.pos.key + ind}`}
|
||||
style={{
|
||||
width: fontSize,
|
||||
height: fontSize
|
||||
}}
|
||||
className="op-checkbox rounded-[1px]"
|
||||
disabled={
|
||||
props.isNeedSign &&
|
||||
(props.pos.options?.isReadOnly ||
|
||||
props.data?.signerObjId !== props.signerObjId)
|
||||
}
|
||||
type="checkbox"
|
||||
readOnly
|
||||
checked={!!selectCheckbox(ind, selectedCheckbox)}
|
||||
/>
|
||||
{!props.pos.options?.isHideLabel && (
|
||||
<span className="leading-none">{data}</span>
|
||||
)}
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<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={`${
|
||||
props.pos.options?.isReadOnly ||
|
||||
props.data?.signerObjId !== props.signerObjId
|
||||
? "select-none"
|
||||
: textWidgetCls
|
||||
}`}
|
||||
className={`${textWidgetCls} ${isReadOnly ? "select-none" : ""}`}
|
||||
style={{
|
||||
fontSize: fontSize,
|
||||
color: fontColor,
|
||||
background: props.data?.blockColor,
|
||||
background: isReadOnly ? props.data?.blockColor : "white",
|
||||
pointerEvents: "none"
|
||||
}}
|
||||
readOnly
|
||||
disabled={
|
||||
props.isNeedSign &&
|
||||
(props.pos.options?.isReadOnly ||
|
||||
props.data?.signerObjId !== props.signerObjId)
|
||||
}
|
||||
disabled={props.isNeedSign && isReadOnly}
|
||||
cols="50"
|
||||
/>
|
||||
) : (
|
||||
@@ -217,13 +211,39 @@ function PlaceholderType(props) {
|
||||
<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 || hint || widgetTypeTranslation}
|
||||
{widgetData || t("choose-one")}
|
||||
<i className="fa-light fa-circle-chevron-down mr-1 "></i>
|
||||
</div>
|
||||
);
|
||||
@@ -233,7 +253,7 @@ function PlaceholderType(props) {
|
||||
alt="initials"
|
||||
draggable="false"
|
||||
src={props.pos.SignUrl}
|
||||
className="w-full h-full select-none-cls"
|
||||
className={`${props.pos.signatureType !== "type" ? "object-contain" : ""} w-full h-full select-none-cls`}
|
||||
/>
|
||||
) : (
|
||||
<div className={widgetCls}>
|
||||
@@ -255,17 +275,18 @@ function PlaceholderType(props) {
|
||||
return iswidgetEnable ? (
|
||||
<textarea
|
||||
readOnly
|
||||
ref={inputRef}
|
||||
placeholder={hint || widgetTypeTranslation}
|
||||
rows={1}
|
||||
value={widgetValue}
|
||||
className={textWidgetCls}
|
||||
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}>
|
||||
@@ -276,17 +297,18 @@ function PlaceholderType(props) {
|
||||
return iswidgetEnable ? (
|
||||
<textarea
|
||||
readOnly
|
||||
ref={inputRef}
|
||||
placeholder={hint || widgetTypeTranslation}
|
||||
rows={1}
|
||||
value={widgetValue}
|
||||
className={textWidgetCls}
|
||||
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">
|
||||
@@ -297,17 +319,18 @@ function PlaceholderType(props) {
|
||||
return iswidgetEnable ? (
|
||||
<textarea
|
||||
readOnly
|
||||
ref={inputRef}
|
||||
placeholder={hint || widgetTypeTranslation}
|
||||
rows={1}
|
||||
value={widgetValue}
|
||||
className={textWidgetCls}
|
||||
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">
|
||||
@@ -379,7 +402,7 @@ function PlaceholderType(props) {
|
||||
alt="image"
|
||||
draggable="false"
|
||||
src={props.pos.SignUrl}
|
||||
className="w-full h-full select-none-cls"
|
||||
className="w-full h-full select-none-cls object-contain"
|
||||
/>
|
||||
) : (
|
||||
<div className={widgetCls}>
|
||||
@@ -401,19 +424,18 @@ function PlaceholderType(props) {
|
||||
return iswidgetEnable ? (
|
||||
<textarea
|
||||
readOnly
|
||||
ref={inputRef}
|
||||
placeholder={hint || widgetTypeTranslation}
|
||||
rows={1}
|
||||
value={widgetValue}
|
||||
className={textWidgetCls}
|
||||
className={`${textWidgetCls} ${isReadOnly ? "select-none" : ""}`}
|
||||
style={{
|
||||
fontSize: fontSize,
|
||||
color: fontColor,
|
||||
fontFamily: "Arial, sans-serif",
|
||||
background: isReadOnly ? props.data?.blockColor : "white",
|
||||
pointerEvents: "none"
|
||||
}}
|
||||
disabled
|
||||
cols="1"
|
||||
disabled={props.isNeedSign && isReadOnly}
|
||||
/>
|
||||
) : (
|
||||
<div style={textWidgetStyle} className="select-none-cls">
|
||||
@@ -421,46 +443,39 @@ function PlaceholderType(props) {
|
||||
</div>
|
||||
);
|
||||
case radioButtonWidget:
|
||||
const radioLayout = props.pos.options?.layout || "vertical";
|
||||
const isOnlyOneBtn = props.pos.options?.values?.length > 0 ? true : false;
|
||||
const radioWrapperClass = `flex items-start ${
|
||||
radioLayout === "horizontal"
|
||||
? `flex-row flex-wrap ${isOnlyOneBtn ? "gap-x-[10px]" : ""}`
|
||||
: `flex-col ${isOnlyOneBtn ? "gap-y-[5px]" : ""}`
|
||||
}`; // Using gap-y-1 for consistency, adjust if needed
|
||||
return (
|
||||
<div>
|
||||
{props.pos.options?.values.map((data, ind) => {
|
||||
return (
|
||||
<div key={ind} className="select-none-cls pointer-events-none">
|
||||
<label
|
||||
htmlFor={`radio-${props.pos.key + ind}`}
|
||||
style={{
|
||||
fontSize: fontSize,
|
||||
color: fontColor,
|
||||
marginTop: ind > 0 ? "5px" : "0px"
|
||||
}}
|
||||
className="text-xs mb-0 flex items-center gap-1 "
|
||||
>
|
||||
<input
|
||||
readOnly
|
||||
id={`radio-${props.pos.key + ind}`}
|
||||
style={{
|
||||
width: fontSize,
|
||||
height: fontSize,
|
||||
lineHeight: 2
|
||||
}}
|
||||
className={`op-radio rounded-full border- border-black appearance-none bg-white inline-block align-middle relative ${
|
||||
handleRadioCheck(data) ? "checked-radio" : ""
|
||||
}`}
|
||||
type="radio"
|
||||
disabled={
|
||||
props.isNeedSign &&
|
||||
(props.pos.options?.isReadOnly ||
|
||||
props.data?.signerObjId !== props.signerObjId)
|
||||
}
|
||||
checked={handleRadioCheck(data)}
|
||||
/>
|
||||
{!props.pos.options?.isHideLabel && (
|
||||
<span className="leading-none">{data}</span>
|
||||
)}
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<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:
|
||||
@@ -474,7 +489,8 @@ function PlaceholderType(props) {
|
||||
style={{
|
||||
fontFamily: "Arial, sans-serif",
|
||||
fontSize: fontSize,
|
||||
color: fontColor
|
||||
color: fontColor,
|
||||
background: "white"
|
||||
}}
|
||||
cols="50"
|
||||
/>
|
||||
|
||||
@@ -31,7 +31,7 @@ const RecipientList = (props) => {
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
//handle draggable element drop and also used in mobile view on up and down key to chnage sequence of recipient's list
|
||||
//handle draggable element drop and also used in mobile view on up and down key to change sequence of recipient's list
|
||||
const handleChangeSequence = (e, ind, isUp, isDown, obj) => {
|
||||
e.preventDefault();
|
||||
let draggedItemId;
|
||||
@@ -82,7 +82,7 @@ const RecipientList = (props) => {
|
||||
};
|
||||
return (
|
||||
<>
|
||||
{props.signersdata.length > 0 &&
|
||||
{props.signersdata?.length > 0 &&
|
||||
props.signersdata.map((obj, ind) => {
|
||||
return (
|
||||
<div
|
||||
@@ -129,9 +129,9 @@ const RecipientList = (props) => {
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-row items-center w-full">
|
||||
<div className="flex flex-row items-center w-full overflow-hidden pr-2">
|
||||
<div
|
||||
className="flex w-[30px] h-[30px] rounded-full items-center justify-center mr-2"
|
||||
className="flex flex-shrink-0 w-[30px] h-[30px] rounded-full items-center justify-center mr-2"
|
||||
style={{
|
||||
background: obj?.blockColor
|
||||
? darkenColor(obj?.blockColor, 0.4)
|
||||
@@ -153,7 +153,7 @@ const RecipientList = (props) => {
|
||||
<div
|
||||
className={`${
|
||||
obj.Name ? "flex-col" : "flex-row"
|
||||
} flex items-center`}
|
||||
} flex overflow-hidden flex-grow-0`}
|
||||
>
|
||||
{obj.Name ? (
|
||||
<span
|
||||
@@ -162,7 +162,7 @@ const RecipientList = (props) => {
|
||||
props.isSelectListId === ind
|
||||
? "text-[#424242]"
|
||||
: "text-base-content"
|
||||
} text-[12px] font-bold w-[100px] whitespace-nowrap overflow-hidden text-ellipsis`}
|
||||
} text-[12px] font-bold truncate whitespace-nowrap`}
|
||||
>
|
||||
{obj.Name}
|
||||
</span>
|
||||
@@ -173,7 +173,7 @@ const RecipientList = (props) => {
|
||||
props.isSelectListId === ind
|
||||
? "text-[#424242]"
|
||||
: "text-base-content"
|
||||
} text-[12px] font-bold w-[100px] whitespace-nowrap overflow-hidden text-ellipsis cursor-pointer`}
|
||||
} text-[12px] font-bold truncate whitespace-nowrap cursor-pointer`}
|
||||
onClick={() => {
|
||||
setIsEdit({ [obj.Id]: true });
|
||||
props.setRoleName(obj.Role);
|
||||
@@ -182,7 +182,7 @@ const RecipientList = (props) => {
|
||||
{isEdit?.[obj.Id] && props.handleRoleChange ? (
|
||||
<input
|
||||
ref={inputRef}
|
||||
className="bg-transparent p-[3px]"
|
||||
className="bg-transparent p-[3px] w-full"
|
||||
value={obj.Role}
|
||||
onChange={(e) => props.handleRoleChange(e, obj.Id)}
|
||||
onBlur={() => {
|
||||
@@ -210,7 +210,7 @@ const RecipientList = (props) => {
|
||||
props.isSelectListId === ind
|
||||
? "text-[#424242]"
|
||||
: "text-base-content"
|
||||
} text-[10px] font-medium w-[100px] whitespace-nowrap overflow-hidden text-ellipsis`}
|
||||
} text-[10px] font-medium truncate whitespace-nowrap`}
|
||||
>
|
||||
{obj?.Role || obj?.Email}
|
||||
</span>
|
||||
|
||||
@@ -3,7 +3,12 @@ import { useTranslation } from "react-i18next";
|
||||
import { Document, Page } from "react-pdf";
|
||||
import { useSelector } from "react-redux";
|
||||
import { PDFDocument } from "pdf-lib";
|
||||
import { base64ToArrayBuffer } from "../../constant/Utils";
|
||||
import {
|
||||
base64ToArrayBuffer,
|
||||
decryptPdf,
|
||||
flattenPdf,
|
||||
getFileAsArrayBuffer
|
||||
} from "../../constant/Utils";
|
||||
import { maxFileSize } from "../../constant/const";
|
||||
|
||||
function RenderAllPdfPage(props) {
|
||||
@@ -87,7 +92,42 @@ function RenderAllPdfPage(props) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const uploadedPdfBytes = await file.arrayBuffer();
|
||||
let uploadedPdfBytes = await file.arrayBuffer();
|
||||
try {
|
||||
uploadedPdfBytes = await flattenPdf(uploadedPdfBytes);
|
||||
} catch (err) {
|
||||
if (err?.message?.includes("is encrypted")) {
|
||||
try {
|
||||
const pdfFile = await decryptPdf(file, "");
|
||||
const pdfArrayBuffer = await getFileAsArrayBuffer(pdfFile);
|
||||
uploadedPdfBytes = await flattenPdf(pdfArrayBuffer);
|
||||
} catch (err) {
|
||||
if (err?.response?.status === 401) {
|
||||
const password = prompt(
|
||||
`PDF "${file.name}" is password-protected. Enter password:`
|
||||
);
|
||||
if (password) {
|
||||
try {
|
||||
const pdfFile = await decryptPdf(file, password);
|
||||
const pdfArrayBuffer = await getFileAsArrayBuffer(pdfFile);
|
||||
uploadedPdfBytes = await flattenPdf(pdfArrayBuffer);
|
||||
// Upload the file to Parse Server
|
||||
} catch (err) {
|
||||
console.error("Incorrect password or decryption failed", err);
|
||||
alert("Incorrect password or decryption failed.");
|
||||
}
|
||||
} else {
|
||||
alert("Please provided Password.");
|
||||
}
|
||||
} else {
|
||||
console.log("Err ", err);
|
||||
alert("error while uploading pdf.");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
alert("error while uploading pdf.");
|
||||
}
|
||||
}
|
||||
const uploadedPdfDoc = await PDFDocument.load(uploadedPdfBytes, {
|
||||
ignoreEncryption: true
|
||||
});
|
||||
@@ -169,8 +209,8 @@ function RenderAllPdfPage(props) {
|
||||
onChange={handleFileUpload}
|
||||
/>
|
||||
<i className="fa-light fa-plus text-gray-500"></i>
|
||||
<span className="text-xs lg:text-sm text-base-content ">
|
||||
Add pages
|
||||
<span className="text-xs lg:text-sm text-base-content">
|
||||
{t("add-pages")}
|
||||
</span>
|
||||
</button>
|
||||
)}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState } from "react";
|
||||
import React, { useState, useRef } from "react";
|
||||
import RSC from "react-scrollbars-custom";
|
||||
import { Document, Page } from "react-pdf";
|
||||
import {
|
||||
@@ -11,13 +11,45 @@ import {
|
||||
import Placeholder from "./Placeholder";
|
||||
import Alert from "../../primitives/Alert";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import usePdfPinchZoom from "../../hook/usePdfPinchZoom";
|
||||
|
||||
function RenderPdf(props) {
|
||||
const { t } = useTranslation();
|
||||
const [scaledHeight, setScaledHeight] = useState();
|
||||
const [guideline, setGuideline] = useState({
|
||||
show: false,
|
||||
x1: 0,
|
||||
x2: 0,
|
||||
y1: 0,
|
||||
y2: 0
|
||||
});
|
||||
//check isGuestSigner is present in local if yes than handle login flow header in mobile view
|
||||
const isGuestSigner = localStorage.getItem("isGuestSigner");
|
||||
|
||||
const pdfContainerRef = useRef(null);
|
||||
|
||||
// enable pinch to zoom only on actual pdf wrapper
|
||||
usePdfPinchZoom(
|
||||
pdfContainerRef,
|
||||
props.scale,
|
||||
props.setScale,
|
||||
props.setZoomPercent
|
||||
);
|
||||
|
||||
const handleGuideline = (isShow, x = 0, y = 0, width = 0, height = 0) => {
|
||||
if (isShow) {
|
||||
setGuideline({
|
||||
show: true,
|
||||
x1: x,
|
||||
x2: x + width,
|
||||
y1: y,
|
||||
y2: y + height
|
||||
});
|
||||
} else {
|
||||
setGuideline({ show: false, x1: 0, x2: 0, y1: 0, y2: 0 });
|
||||
}
|
||||
};
|
||||
|
||||
// handle signature block width and height according to screen
|
||||
const posWidth = (pos, signYourself) => {
|
||||
const containerScale = getContainerScale(
|
||||
@@ -79,7 +111,7 @@ function RenderPdf(props) {
|
||||
}
|
||||
};
|
||||
|
||||
//function for render placeholder block over pdf document
|
||||
// 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
|
||||
@@ -91,73 +123,71 @@ function RenderPdf(props) {
|
||||
: [];
|
||||
return (
|
||||
checkSign.length === 0 &&
|
||||
data?.placeHolder?.map((placeData, key) => {
|
||||
return (
|
||||
<React.Fragment key={key}>
|
||||
{placeData.pageNumber === props.pageNumber &&
|
||||
placeData.pos.map((pos, ind) => {
|
||||
return (
|
||||
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}
|
||||
setIsCheckbox={props.setIsCheckbox}
|
||||
isFreeResize={false}
|
||||
isOpenSignPad={true}
|
||||
assignedWidgetId={props.assignedWidgetId}
|
||||
isApplyAll={true}
|
||||
setFontSize={props.setFontSize}
|
||||
fontSize={props.fontSize}
|
||||
fontColor={props.fontColor}
|
||||
setFontColor={props.setFontColor}
|
||||
setRequestSignTour={props.setRequestSignTour}
|
||||
calculateFontsize={calculateFontsize}
|
||||
currWidgetsDetails={props?.currWidgetsDetails}
|
||||
setTempSignerId={props.setTempSignerId}
|
||||
/>
|
||||
</React.Fragment>
|
||||
)
|
||||
);
|
||||
})}
|
||||
</React.Fragment>
|
||||
);
|
||||
})
|
||||
data?.placeHolder?.map((placeData, key) => (
|
||||
<React.Fragment key={key}>
|
||||
{placeData.pageNumber === props.pageNumber &&
|
||||
placeData.pos.map(
|
||||
(pos, ind) =>
|
||||
pos && (
|
||||
<React.Fragment key={ind}>
|
||||
<Placeholder
|
||||
pos={pos}
|
||||
handleSignYourselfImageResize={handleImageResize}
|
||||
index={props.pageNumber}
|
||||
xyPosition={props.signerPos}
|
||||
setXyPosition={props.setSignerPos}
|
||||
data={data}
|
||||
setIsResize={props.setIsResize}
|
||||
isShowBorder={props.isSelfSign}
|
||||
isAlllowModify={props.isAlllowModify}
|
||||
signerObjId={props.signerObjectId}
|
||||
isShowDropdown={true}
|
||||
isNeedSign={props.pdfRequest}
|
||||
isSelfSign={true}
|
||||
isSignYourself={false}
|
||||
posWidth={posWidth}
|
||||
posHeight={posHeight}
|
||||
showGuidelines={handleGuideline}
|
||||
isDragging={props.isDragging}
|
||||
pdfDetails={props.pdfDetails}
|
||||
unSignedWidgetId={props.unSignedWidgetId}
|
||||
setCurrWidgetsDetails={props.setCurrWidgetsDetails}
|
||||
uniqueId={props.uniqueId}
|
||||
scale={props.scale}
|
||||
containerWH={props.containerWH}
|
||||
pdfOriginalWH={props.pdfOriginalWH}
|
||||
pageNumber={props.pageNumber}
|
||||
ispublicTemplate={props.ispublicTemplate}
|
||||
handleUserDetails={props.handleUserDetails}
|
||||
isResize={props.isResize}
|
||||
handleTabDrag={props.handleTabDrag}
|
||||
handleStop={props.handleStop}
|
||||
setUniqueId={props.setUniqueId}
|
||||
setIsSelectId={props.setIsSelectId}
|
||||
handleDeleteSign={props.handleDeleteSign}
|
||||
setIsPageCopy={props.setIsPageCopy}
|
||||
handleTextSettingModal={props.handleTextSettingModal}
|
||||
handleCellSettingModal={props.handleCellSettingModal}
|
||||
setIsCheckbox={props.setIsCheckbox}
|
||||
isFreeResize={props.isSelfSign ? true : false}
|
||||
isOpenSignPad={true}
|
||||
assignedWidgetId={props.assignedWidgetId}
|
||||
isApplyAll={true}
|
||||
setCellCount={props.setCellCount}
|
||||
setFontSize={props.setFontSize}
|
||||
fontSize={props.fontSize}
|
||||
fontColor={props.fontColor}
|
||||
setFontColor={props.setFontColor}
|
||||
setRequestSignTour={props.setRequestSignTour}
|
||||
calculateFontsize={calculateFontsize}
|
||||
currWidgetsDetails={props?.currWidgetsDetails}
|
||||
setTempSignerId={props.setTempSignerId}
|
||||
/>
|
||||
</React.Fragment>
|
||||
)
|
||||
)}
|
||||
</React.Fragment>
|
||||
))
|
||||
);
|
||||
};
|
||||
|
||||
@@ -172,148 +202,145 @@ function RenderPdf(props) {
|
||||
}
|
||||
};
|
||||
const pdfDataBase64 = `data:application/pdf;base64,${props.pdfBase64Url}`;
|
||||
//calculate render height of pdf in mobile view
|
||||
// calculate render height of pdf in mobile view
|
||||
const handlePageLoadSuccess = (page) => {
|
||||
const containerWidth = props.divRef.current.offsetWidth; // Get container width
|
||||
const viewport = page.getViewport({ scale: 1 });
|
||||
const scale = containerWidth / viewport.width; // Scale to fit container width
|
||||
const scaleHeight = viewport.height * scale;
|
||||
setScaledHeight(scaleHeight);
|
||||
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>
|
||||
)}
|
||||
{isMobile ? (
|
||||
<RSC
|
||||
<RSC
|
||||
style={{
|
||||
position: "relative",
|
||||
boxShadow: "rgba(17, 12, 46, 0.15) 0px 48px 100px 0px",
|
||||
height: isMobile
|
||||
? isGuestSigner
|
||||
? window.innerHeight - 49 // 49 is height of header
|
||||
: scaledHeight
|
||||
: `${window.innerHeight}px`,
|
||||
zIndex: 0
|
||||
}}
|
||||
noScrollY={isMobile ? props.scale === 1 : false}
|
||||
noScrollX={props.scale === 1}
|
||||
>
|
||||
<div
|
||||
data-tut={isMobile ? "reactourForth" : undefined}
|
||||
className={
|
||||
isMobile
|
||||
? `${isGuestSigner ? "30px" : ""} border-[0.1px] border-[#ebe8e8] overflow-x-auto relative`
|
||||
: "relative"
|
||||
}
|
||||
style={{
|
||||
position: "relative",
|
||||
boxShadow: "rgba(17, 12, 46, 0.15) 0px 48px 100px 0px",
|
||||
//49 is height of header
|
||||
height: isGuestSigner ? window.innerHeight - 49 : scaledHeight,
|
||||
zIndex: 0
|
||||
width:
|
||||
props.containerWH?.width && props.containerWH?.width * props.scale
|
||||
}}
|
||||
noScrollY={props.scale === 1 ? true : false}
|
||||
noScrollX={props.scale === 1 ? true : false}
|
||||
ref={(node) => {
|
||||
pdfContainerRef.current = node;
|
||||
props.drop && props.drop(node);
|
||||
}}
|
||||
id="container"
|
||||
>
|
||||
<div
|
||||
data-tut="reactourForth"
|
||||
className={`${
|
||||
isGuestSigner ? "30px" : ""
|
||||
} border-[0.1px] border-[#ebe8e8] overflow-x-auto`}
|
||||
style={{
|
||||
width:
|
||||
props.containerWH?.width &&
|
||||
props.containerWH?.width * props.scale
|
||||
}}
|
||||
ref={props.drop}
|
||||
id="container"
|
||||
>
|
||||
{props.containerWH?.width &&
|
||||
props.pdfOriginalWH.length > 0 &&
|
||||
(props.pdfRequest || props.isSelfSign
|
||||
? props.signerPos?.map((data, key) => {
|
||||
return (
|
||||
{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 // placeholder mobile
|
||||
? props.signerPos?.map((data, ind) => {
|
||||
return (
|
||||
))
|
||||
: 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) => {
|
||||
return (
|
||||
<React.Fragment key={index}>
|
||||
{placeData.pageNumber === props.pageNumber &&
|
||||
placeData.pos.map((pos) => {
|
||||
return (
|
||||
<React.Fragment key={pos.key}>
|
||||
<Placeholder
|
||||
pos={pos}
|
||||
setIsPageCopy={props.setIsPageCopy}
|
||||
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
|
||||
}
|
||||
scale={props.scale}
|
||||
containerWH={props.containerWH}
|
||||
pdfOriginalWH={props.pdfOriginalWH}
|
||||
pageNumber={props.pageNumber}
|
||||
setIsSelectId={props.setIsSelectId}
|
||||
fontSize={props.fontSize}
|
||||
setFontSize={props.setFontSize}
|
||||
fontColor={props.fontColor}
|
||||
setFontColor={props.setFontColor}
|
||||
isResize={props.isResize}
|
||||
unSignedWidgetId={
|
||||
props.unSignedWidgetId
|
||||
}
|
||||
isFreeResize={true}
|
||||
calculateFontsize={
|
||||
calculateFontsize
|
||||
}
|
||||
currWidgetsDetails={
|
||||
props?.currWidgetsDetails
|
||||
}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
data?.placeHolder.map((placeData, index) => (
|
||||
<React.Fragment key={index}>
|
||||
{placeData.pageNumber === props.pageNumber &&
|
||||
placeData.pos.map((pos) => (
|
||||
<React.Fragment key={pos.key}>
|
||||
<Placeholder
|
||||
pos={pos}
|
||||
setIsPageCopy={props.setIsPageCopy}
|
||||
handleDeleteSign={
|
||||
props.handleDeleteSign
|
||||
}
|
||||
handleTabDrag={props.handleTabDrag}
|
||||
handleStop={props.handleStop}
|
||||
handleSignYourselfImageResize={
|
||||
handleImageResize
|
||||
}
|
||||
index={props.pageNumber}
|
||||
xyPosition={props.signerPos}
|
||||
setXyPosition={props.setSignerPos}
|
||||
data={data}
|
||||
setIsResize={props.setIsResize}
|
||||
setShowDropdown={props.setShowDropdown}
|
||||
isShowBorder={true}
|
||||
isPlaceholder={true}
|
||||
setUniqueId={props.setUniqueId}
|
||||
handleLinkUser={props.handleLinkUser}
|
||||
isSignYourself={false}
|
||||
posWidth={posWidth}
|
||||
posHeight={posHeight}
|
||||
showGuidelines={handleGuideline}
|
||||
isDragging={props.isDragging}
|
||||
setIsValidate={props.setIsValidate}
|
||||
setIsRadio={props.setIsRadio}
|
||||
setIsCheckbox={props.setIsCheckbox}
|
||||
setCurrWidgetsDetails={
|
||||
props.setCurrWidgetsDetails
|
||||
}
|
||||
handleNameModal={props.handleNameModal}
|
||||
setTempSignerId={props.setTempSignerId}
|
||||
uniqueId={props.uniqueId}
|
||||
handleTextSettingModal={
|
||||
props.handleTextSettingModal
|
||||
}
|
||||
handleCellSettingModal={
|
||||
props.handleCellSettingModal
|
||||
}
|
||||
scale={props.scale}
|
||||
containerWH={props.containerWH}
|
||||
pdfOriginalWH={props.pdfOriginalWH}
|
||||
pageNumber={props.pageNumber}
|
||||
setIsSelectId={props.setIsSelectId}
|
||||
fontSize={props.fontSize}
|
||||
setFontSize={props.setFontSize}
|
||||
setCellCount={props.setCellCount}
|
||||
fontColor={props.fontColor}
|
||||
setFontColor={props.setFontColor}
|
||||
isResize={props.isResize}
|
||||
unSignedWidgetId={
|
||||
props.unSignedWidgetId
|
||||
}
|
||||
isFreeResize={true}
|
||||
calculateFontsize={calculateFontsize}
|
||||
currWidgetsDetails={
|
||||
props?.currWidgetsDetails
|
||||
}
|
||||
/>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</React.Fragment>
|
||||
);
|
||||
})
|
||||
: !props.pdfDetails?.[0]?.IsCompleted &&
|
||||
props.xyPosition?.map((data, ind) => {
|
||||
return (
|
||||
))
|
||||
: !props.pdfDetails?.[0]?.IsCompleted && // signyourself flow
|
||||
props.xyPosition?.map((data, ind) => (
|
||||
<React.Fragment key={ind}>
|
||||
{data.pageNumber === props.pageNumber &&
|
||||
data.pos.map((pos, id) => {
|
||||
return (
|
||||
data.pos.map(
|
||||
(pos, id) =>
|
||||
pos && (
|
||||
<Placeholder
|
||||
key={id}
|
||||
@@ -333,6 +360,7 @@ function RenderPdf(props) {
|
||||
isSignYourself={true}
|
||||
posWidth={posWidth}
|
||||
posHeight={posHeight}
|
||||
showGuidelines={handleGuideline}
|
||||
pdfDetails={props.pdfDetails[0]}
|
||||
isDragging={props.isDragging}
|
||||
setIsCheckbox={props.setIsCheckbox}
|
||||
@@ -342,6 +370,9 @@ function RenderPdf(props) {
|
||||
handleTextSettingModal={
|
||||
props.handleTextSettingModal
|
||||
}
|
||||
handleCellSettingModal={
|
||||
props.handleCellSettingModal
|
||||
}
|
||||
scale={props.scale}
|
||||
pdfOriginalWH={props.pdfOriginalWH}
|
||||
pageNumber={props.pageNumber}
|
||||
@@ -351,7 +382,7 @@ function RenderPdf(props) {
|
||||
setFontColor={props.setFontColor}
|
||||
isResize={props.isResize}
|
||||
setIsResize={props.setIsResize}
|
||||
isFreeResize={false}
|
||||
isFreeResize={true}
|
||||
isOpenSignPad={true}
|
||||
calculateFontsize={calculateFontsize}
|
||||
currWidgetsDetails={
|
||||
@@ -359,247 +390,67 @@ function RenderPdf(props) {
|
||||
}
|
||||
/>
|
||||
)
|
||||
);
|
||||
})}
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
}))}
|
||||
{/* Mobile */}
|
||||
<Document
|
||||
error={<p className="mx-2">{t("failed-to-load-refresh-page")}</p>}
|
||||
onLoadError={() => props.setPdfLoad(false)}
|
||||
loading={t("loading-doc")}
|
||||
onLoadSuccess={props.pageDetails}
|
||||
onClick={() =>
|
||||
props.setCurrWidgetsDetails && props.setCurrWidgetsDetails({})
|
||||
}
|
||||
file={pdfDataBase64}
|
||||
>
|
||||
<Page
|
||||
onLoadSuccess={handlePageLoadSuccess}
|
||||
scale={props.scale || 1}
|
||||
key={props.index}
|
||||
pageNumber={props.pageNumber}
|
||||
width={props.containerWH.width}
|
||||
renderAnnotationLayer={false}
|
||||
renderTextLayer={false}
|
||||
onGetAnnotationsError={(error) => {
|
||||
console.log("annotation error", error);
|
||||
}}
|
||||
className="select-none touch-callout-none"
|
||||
/>
|
||||
</Document>
|
||||
</div>
|
||||
</RSC>
|
||||
) : (
|
||||
<RSC
|
||||
style={{
|
||||
position: "relative",
|
||||
boxShadow: "rgba(17, 12, 46, 0.15) 0px 48px 100px 0px",
|
||||
height: window.innerHeight + "px",
|
||||
zIndex: 0
|
||||
}}
|
||||
noScrollY={false}
|
||||
noScrollX={props.scale === 1 ? true : false}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
width:
|
||||
props.containerWH?.width &&
|
||||
props.containerWH?.width * props.scale
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
<Document
|
||||
error={<p className="mx-2">{t("failed-to-load-refresh-page")}</p>}
|
||||
onLoadError={(e) => {
|
||||
console.log("PDF load error", e);
|
||||
props.setPdfLoad(false);
|
||||
}}
|
||||
ref={props.drop}
|
||||
id="container"
|
||||
loading={t("loading-doc")}
|
||||
onLoadSuccess={(pdf) => {
|
||||
props.setPdfLoad(true);
|
||||
props.pageDetails(pdf);
|
||||
}}
|
||||
onClick={() =>
|
||||
props.setCurrWidgetsDetails && props.setCurrWidgetsDetails({})
|
||||
}
|
||||
file={pdfDataBase64}
|
||||
>
|
||||
{props.pdfLoad &&
|
||||
props.containerWH?.width &&
|
||||
props.pdfOriginalWH.length > 0 &&
|
||||
(props.pdfRequest || props.isSelfSign //pdf request sign flow
|
||||
? props.signerPos?.map((data, key) => {
|
||||
return (
|
||||
<React.Fragment key={key}>
|
||||
{checkSignedSigners(data)}
|
||||
</React.Fragment>
|
||||
);
|
||||
})
|
||||
: props.placeholder //placeholder and template flow
|
||||
? props.signerPos.map((data, ind) => {
|
||||
return (
|
||||
<React.Fragment key={ind}>
|
||||
{data?.placeHolder &&
|
||||
data?.placeHolder.map((placeData, index) => {
|
||||
return (
|
||||
<React.Fragment key={index}>
|
||||
{placeData.pageNumber === props.pageNumber &&
|
||||
placeData.pos.map((pos) => {
|
||||
return (
|
||||
<React.Fragment key={pos.key}>
|
||||
<Placeholder
|
||||
pos={pos}
|
||||
setIsPageCopy={props.setIsPageCopy}
|
||||
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
|
||||
}
|
||||
scale={props.scale}
|
||||
containerWH={props.containerWH}
|
||||
pdfOriginalWH={props.pdfOriginalWH}
|
||||
pageNumber={props.pageNumber}
|
||||
setIsSelectId={props.setIsSelectId}
|
||||
fontSize={props.fontSize}
|
||||
setFontSize={props.setFontSize}
|
||||
fontColor={props.fontColor}
|
||||
setFontColor={props.setFontColor}
|
||||
isResize={props.isResize}
|
||||
unSignedWidgetId={
|
||||
props.unSignedWidgetId
|
||||
}
|
||||
isFreeResize={true}
|
||||
calculateFontsize={
|
||||
calculateFontsize
|
||||
}
|
||||
currWidgetsDetails={
|
||||
props?.currWidgetsDetails
|
||||
}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</React.Fragment>
|
||||
);
|
||||
})
|
||||
: !props.pdfDetails?.[0]?.IsCompleted &&
|
||||
props.xyPosition?.map((data, ind) => {
|
||||
// signyourself flow
|
||||
return (
|
||||
<React.Fragment key={ind}>
|
||||
{data.pageNumber === props.pageNumber &&
|
||||
data.pos.map((pos) => {
|
||||
return (
|
||||
<React.Fragment key={pos.key}>
|
||||
<Placeholder
|
||||
pos={pos}
|
||||
setIsPageCopy={props.setIsPageCopy}
|
||||
handleDeleteSign={props.handleDeleteSign}
|
||||
handleTabDrag={props.handleTabDrag}
|
||||
handleStop={(event, dragElement) =>
|
||||
props.handleStop(
|
||||
event,
|
||||
dragElement,
|
||||
pos.type
|
||||
)
|
||||
}
|
||||
handleSignYourselfImageResize={
|
||||
handleSignYourselfImageResize
|
||||
}
|
||||
index={props.index}
|
||||
xyPosition={props.xyPosition}
|
||||
setXyPosition={props.setXyPosition}
|
||||
isShowBorder={true}
|
||||
isSignYourself={true}
|
||||
posWidth={posWidth}
|
||||
posHeight={posHeight}
|
||||
pdfDetails={props.pdfDetails[0]}
|
||||
isDragging={props.isDragging}
|
||||
setIsCheckbox={props.setIsCheckbox}
|
||||
setCurrWidgetsDetails={
|
||||
props.setCurrWidgetsDetails
|
||||
}
|
||||
handleTextSettingModal={
|
||||
props.handleTextSettingModal
|
||||
}
|
||||
scale={props.scale}
|
||||
containerWH={props.containerWH}
|
||||
pdfOriginalWH={props.pdfOriginalWH}
|
||||
pageNumber={props.pageNumber}
|
||||
fontSize={props.fontSize}
|
||||
setFontSize={props.setFontSize}
|
||||
fontColor={props.fontColor}
|
||||
setFontColor={props.setFontColor}
|
||||
isResize={props.isResize}
|
||||
setIsResize={props.setIsResize}
|
||||
isFreeResize={false}
|
||||
isOpenSignPad={true}
|
||||
calculateFontsize={calculateFontsize}
|
||||
currWidgetsDetails={
|
||||
props?.currWidgetsDetails
|
||||
}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</React.Fragment>
|
||||
);
|
||||
}))}
|
||||
{/* large device */}
|
||||
{/* this component for render pdf document is in middle of the component */}
|
||||
<Document
|
||||
error={<p className="mx-2">{t("failed-to-load-refresh-page")}</p>}
|
||||
onLoadError={() => props.setPdfLoad(false)}
|
||||
loading={t("loading-doc")}
|
||||
onLoadSuccess={props.pageDetails}
|
||||
onClick={() =>
|
||||
props.setCurrWidgetsDetails && props.setCurrWidgetsDetails({})
|
||||
}
|
||||
file={pdfDataBase64}
|
||||
>
|
||||
<Page
|
||||
key={props.index}
|
||||
width={props.containerWH.width}
|
||||
scale={props.scale || 1}
|
||||
className={"-z-[1]"} // when user zoom-in in tablet widgets move backward that's why pass -z-[1]
|
||||
pageNumber={props.pageNumber}
|
||||
renderAnnotationLayer={false}
|
||||
renderTextLayer={false}
|
||||
onGetAnnotationsError={(error) => {
|
||||
console.log("annotation error", error);
|
||||
}}
|
||||
<Page
|
||||
key={props.index}
|
||||
onLoadSuccess={handlePageLoadSuccess}
|
||||
width={props.containerWH.width}
|
||||
scale={props.scale || 1}
|
||||
className={isMobile ? "select-none touch-callout-none" : "-z-[1]"}
|
||||
pageNumber={props.pageNumber}
|
||||
renderAnnotationLayer={false}
|
||||
renderTextLayer={false}
|
||||
onGetAnnotationsError={(error) => {
|
||||
console.log("annotation error", error);
|
||||
}}
|
||||
/>
|
||||
</Document>
|
||||
{guideline.show && (
|
||||
<>
|
||||
{/* top guide */}
|
||||
<div
|
||||
className="absolute pointer-events-none z-[1000] left-0 w-full border-t-[1px] border-dashed border-[#3b82f6]"
|
||||
style={{ top: guideline.y1 }}
|
||||
/>
|
||||
</Document>
|
||||
</div>
|
||||
</RSC>
|
||||
)}
|
||||
{/* bottom guide */}
|
||||
<div
|
||||
className="absolute pointer-events-none z-[1000] left-0 w-full border-t-[1px] border-dashed border-[#3b82f6]"
|
||||
style={{ top: guideline.y2 }}
|
||||
/>
|
||||
{/* left guide */}
|
||||
<div
|
||||
className="absolute pointer-events-none z-[1000] top-0 h-full border-l-[1px] border-dashed border-[#3b82f6]"
|
||||
style={{ left: guideline.x1 }}
|
||||
/>
|
||||
{/* right guide */}
|
||||
<div
|
||||
className="absolute pointer-events-none z-[1000] top-0 h-full border-l-[1px] border-dashed border-[#3b82f6]"
|
||||
style={{ left: guideline.x2 }}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</RSC>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -24,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) => {
|
||||
|
||||
@@ -27,12 +27,12 @@ function SignerListComponent(props) {
|
||||
|
||||
return (
|
||||
<div
|
||||
className="rounded-xl mx-1 flex flex-row items-center py-[10px] mt-1"
|
||||
className="rounded-xl mx-1 flex flex-row flex-grow-0 items-center py-[10px] mt-1"
|
||||
style={{ background: checkSignerBackColor(props.obj) }}
|
||||
>
|
||||
<div
|
||||
style={{ background: checkUserNameColor(props.obj) }}
|
||||
className="flex w-[30px] h-[30px] rounded-full justify-center items-center mx-1"
|
||||
className="flex flex-shrink-0 w-[30px] h-[30px] rounded-full justify-center items-center mx-1"
|
||||
>
|
||||
<span className="text-[12px] text-center font-bold text-black uppercase">
|
||||
{getFirstLetter(
|
||||
@@ -40,11 +40,11 @@ function SignerListComponent(props) {
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<span className="text-[12px] font-bold text-[#424242] w-[100px] whitespace-nowrap overflow-hidden text-ellipsis">
|
||||
<div className="flex flex-grow-0 flex-col overflow-hidden pr-2">
|
||||
<span className="text-[12px] font-bold truncate whitespace-nowrap">
|
||||
{props.obj?.Name || props?.obj?.Role}
|
||||
</span>
|
||||
<span className="text-[10px] font-medium text-[#424242] w-[100px] whitespace-nowrap overflow-hidden text-ellipsis">
|
||||
<span className="text-[10px] font-medium truncate whitespace-nowrap">
|
||||
{props.obj?.Email || props.obj?.email}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -13,7 +13,7 @@ function TextFontSetting(props) {
|
||||
title={t("text-field")}
|
||||
handleClose={() => props.setIsTextSetting(false)}
|
||||
>
|
||||
<div className="h-full p-[20px]">
|
||||
<div className="h-full p-[20px] text-base-content">
|
||||
<div className="flex flex-col md:flex-row md:items-center gap-3">
|
||||
{/* Font Size Selector */}
|
||||
<div className="flex items-center gap-2">
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
isMobile,
|
||||
radioButtonWidget,
|
||||
textInputWidget,
|
||||
cellsWidget,
|
||||
textWidget,
|
||||
widgets
|
||||
} from "../../constant/Utils";
|
||||
@@ -37,6 +38,10 @@ function WidgetComponent(props) {
|
||||
type: "BOX",
|
||||
item: { type: "BOX", id: 7, text: textInputWidget }
|
||||
});
|
||||
const [, cells] = useDrag({
|
||||
type: "BOX",
|
||||
item: { type: "BOX", id: 17, text: cellsWidget }
|
||||
});
|
||||
const [, initials] = useDrag({
|
||||
type: "BOX",
|
||||
item: { type: "BOX", id: 8, text: "initials" }
|
||||
@@ -89,6 +94,7 @@ function WidgetComponent(props) {
|
||||
date,
|
||||
text,
|
||||
textInput,
|
||||
cells,
|
||||
checkbox,
|
||||
dropdown,
|
||||
radioButton,
|
||||
@@ -132,7 +138,9 @@ function WidgetComponent(props) {
|
||||
);
|
||||
const filterWidgets = widget.filter(
|
||||
(data) =>
|
||||
!["dropdown", radioButtonWidget, textInputWidget].includes(data.type)
|
||||
!["dropdown", radioButtonWidget, textInputWidget].includes(
|
||||
data.type
|
||||
)
|
||||
);
|
||||
const textWidgetData = widget.filter((data) => data.type !== textWidget);
|
||||
const updateWidgets = props.isSignYourself
|
||||
@@ -241,6 +249,7 @@ function WidgetComponent(props) {
|
||||
handleDivClick={props.handleDivClick}
|
||||
handleMouseLeave={props.handleMouseLeave}
|
||||
signRef={signRef}
|
||||
addPositionOfSignature={props.addPositionOfSignature}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,6 +5,7 @@ import RegexParser from "regex-parser";
|
||||
import {
|
||||
signatureTypes,
|
||||
textInputWidget,
|
||||
cellsWidget,
|
||||
textWidget
|
||||
} from "../../constant/Utils";
|
||||
import { fontColorArr, fontsizeArr } from "../../constant/Utils";
|
||||
@@ -19,7 +20,8 @@ const WidgetNameModal = (props) => {
|
||||
status: "required",
|
||||
hint: "",
|
||||
textvalidate: "",
|
||||
isReadOnly: false
|
||||
isReadOnly: false,
|
||||
cellCount: 5
|
||||
});
|
||||
const [isValid, setIsValid] = useState(true);
|
||||
const statusArr = ["Required", "Optional"];
|
||||
@@ -51,12 +53,14 @@ const WidgetNameModal = (props) => {
|
||||
props.defaultdata?.options?.validation?.type === "regex"
|
||||
? props.defaultdata?.options?.validation?.pattern
|
||||
: props.defaultdata?.options?.validation?.type || "",
|
||||
isReadOnly: props.defaultdata?.options?.isReadOnly || false
|
||||
isReadOnly: props.defaultdata?.options?.isReadOnly || false,
|
||||
cellCount: props.defaultdata?.options?.cellCount || 5
|
||||
});
|
||||
} else {
|
||||
setFormdata({
|
||||
...formdata,
|
||||
name: props.defaultdata?.options?.name || ""
|
||||
name: props.defaultdata?.options?.name || "",
|
||||
cellCount: props.defaultdata?.options?.cellCount || 5
|
||||
});
|
||||
}
|
||||
|
||||
@@ -83,6 +87,20 @@ const WidgetNameModal = (props) => {
|
||||
props.handleData(data, props.defaultdata?.type);
|
||||
}
|
||||
} else {
|
||||
const isTextInput = [textInputWidget, cellsWidget].includes(
|
||||
props.defaultdata?.type
|
||||
);
|
||||
const { isReadOnly, defaultValue, status } = formdata;
|
||||
// If it’s a text‐input widget, enforce that read-only fields have
|
||||
// either a defaultValue or an "optional" status.
|
||||
if (isTextInput) {
|
||||
const readOnlyWithoutValue =
|
||||
isReadOnly && !defaultValue && status !== "optional";
|
||||
if (readOnlyWithoutValue) {
|
||||
alert(t("readonly-error", { widgetName: props.defaultdata?.type }));
|
||||
return;
|
||||
}
|
||||
}
|
||||
props.handleData(formdata);
|
||||
}
|
||||
setFormdata({
|
||||
@@ -91,7 +109,8 @@ const WidgetNameModal = (props) => {
|
||||
defaultValue: "",
|
||||
status: "required",
|
||||
hint: "",
|
||||
textvalidate: ""
|
||||
textvalidate: "",
|
||||
cellCount: 5
|
||||
});
|
||||
setSignatureType(signTypes);
|
||||
}
|
||||
@@ -104,6 +123,23 @@ const WidgetNameModal = (props) => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleChangeValidateInput = (e) => {
|
||||
if (e) {
|
||||
if (e.target.value === "ssn") {
|
||||
setFormdata({
|
||||
...formdata,
|
||||
[e.target.name]: e.target.value,
|
||||
hint: "xxx-xx-xxxx",
|
||||
cellCount: 11
|
||||
});
|
||||
} else {
|
||||
setFormdata({ ...formdata, [e.target.name]: e.target.value });
|
||||
}
|
||||
} else {
|
||||
setFormdata({ ...formdata, textvalidate: "" });
|
||||
}
|
||||
};
|
||||
|
||||
const handledefaultChange = (e) => {
|
||||
if (formdata.textvalidate) {
|
||||
const regexObject = RegexParser(handleValidation(formdata.textvalidate));
|
||||
@@ -112,7 +148,11 @@ const WidgetNameModal = (props) => {
|
||||
} else {
|
||||
setIsValid(true);
|
||||
}
|
||||
setFormdata({ ...formdata, [e.target.name]: e.target.value });
|
||||
const val =
|
||||
props.defaultdata?.type === cellsWidget
|
||||
? e.target.value.slice(0, formdata.cellCount)
|
||||
: e.target.value;
|
||||
setFormdata({ ...formdata, [e.target.name]: val });
|
||||
};
|
||||
|
||||
function handleValidation(type) {
|
||||
@@ -122,7 +162,10 @@ const WidgetNameModal = (props) => {
|
||||
case "number":
|
||||
return "/^\\d+$/";
|
||||
case "text":
|
||||
return "/^[a-zA-Zs]+$/";
|
||||
//allow space in text regex
|
||||
return "/^[a-zA-Z ]+$/";
|
||||
case "ssn":
|
||||
return "/^(?!000|666|9\\d{2})\\d{3}-(?!00)\\d{2}-(?!0000)\\d{4}$/";
|
||||
default:
|
||||
return type;
|
||||
}
|
||||
@@ -150,7 +193,7 @@ const WidgetNameModal = (props) => {
|
||||
<form
|
||||
onSubmit={handleSubmit}
|
||||
className={`${
|
||||
props.defaultdata?.type === textInputWidget
|
||||
[textInputWidget, cellsWidget].includes(props.defaultdata?.type)
|
||||
? "pt-0"
|
||||
: ["signature", "initials"].includes(props.defaultdata?.type)
|
||||
? "pt-2"
|
||||
@@ -174,7 +217,21 @@ const WidgetNameModal = (props) => {
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{props.defaultdata?.type === textInputWidget && (
|
||||
{props.defaultdata?.type === cellsWidget && (
|
||||
<div className="mb-[0.75rem] text-[13px]">
|
||||
<label htmlFor="cellCount">{t("cell-count")}</label>
|
||||
<input
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
type="number"
|
||||
min="1"
|
||||
name="cellCount"
|
||||
value={formdata.cellCount}
|
||||
onChange={(e) => handleChange(e)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{[textInputWidget, cellsWidget].includes(props.defaultdata?.type) && (
|
||||
<>
|
||||
<div className="mb-[0.75rem]">
|
||||
<label htmlFor="name" className="text-[13px]">
|
||||
@@ -186,6 +243,11 @@ const WidgetNameModal = (props) => {
|
||||
value={formdata.defaultValue}
|
||||
onChange={(e) => handledefaultChange(e)}
|
||||
autoComplete="off"
|
||||
maxLength={
|
||||
props.defaultdata?.type === cellsWidget
|
||||
? formdata.cellCount
|
||||
: undefined
|
||||
}
|
||||
onBlur={() => {
|
||||
if (isValid === false) {
|
||||
setFormdata({ ...formdata, defaultValue: "" });
|
||||
@@ -237,7 +299,9 @@ const WidgetNameModal = (props) => {
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{[textInputWidget].includes(props.defaultdata?.type) && (
|
||||
{[textInputWidget, cellsWidget].includes(
|
||||
props.defaultdata?.type
|
||||
) && (
|
||||
<div className="flex items-center">
|
||||
<input
|
||||
id="isReadOnly"
|
||||
@@ -252,7 +316,10 @@ const WidgetNameModal = (props) => {
|
||||
}))
|
||||
}
|
||||
/>
|
||||
<label className="ml-1 mb-0" htmlFor="isreadonly">
|
||||
<label
|
||||
className="ml-1.5 mb-0 capitalize text-[13px]"
|
||||
htmlFor="isreadonly"
|
||||
>
|
||||
{t("read-only")}
|
||||
</label>
|
||||
</div>
|
||||
@@ -304,6 +371,7 @@ const WidgetNameModal = (props) => {
|
||||
{[
|
||||
textInputWidget,
|
||||
textWidget,
|
||||
cellsWidget,
|
||||
"name",
|
||||
"company",
|
||||
"job title",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -195,7 +195,7 @@ const SelectFolder = ({ required, onSuccess, folderCls, isReset }) => {
|
||||
? selectFolder.Name
|
||||
: t("OpenSign-drive", { appName: drivename })}
|
||||
</p>
|
||||
<div className="text-black text-sm">
|
||||
<div className="text-sm">
|
||||
<i
|
||||
className="fa-light fa-pencil cursor-pointer"
|
||||
title={t("select-folder")}
|
||||
|
||||
@@ -97,7 +97,7 @@ const SelectSigners = (props) => {
|
||||
const contactRes = axiosRes?.data?.result || [];
|
||||
if (contactRes) {
|
||||
const res = JSON.parse(JSON.stringify(contactRes));
|
||||
//compareArrays is a function where compare between two array (total signersList and dcument signers list)
|
||||
//compareArrays is a function where compare between two array (total signersList and document signers list)
|
||||
//and filter signers from total signer's list which already present in document's signers list
|
||||
// const compareArrays = (res, signerObj) => {
|
||||
// return res.filter(
|
||||
|
||||
@@ -122,7 +122,7 @@ const SignersInput = (props) => {
|
||||
);
|
||||
if (contactRes) {
|
||||
const res = JSON.parse(JSON.stringify(contactRes));
|
||||
//compareArrays is a function where compare between two array (total signersList and dcument signers list)
|
||||
//compareArrays is a function where compare between two array (total signersList and document signers list)
|
||||
//and filter signers from total signer's list which already present in document's signers list
|
||||
const compareArrays = (res, signerObj) => {
|
||||
return res.filter(
|
||||
|
||||
@@ -18,16 +18,16 @@ const Menu = ({ item, isOpen, closeSidebar }) => {
|
||||
className={({ isActive }) =>
|
||||
`${
|
||||
isActive ? " bg-base-300 text-base-content" : ""
|
||||
} flex items-center justify-start text-left p-3 lg:p-4 text-base-content hover:text-base-content focus:bg-base-300 hover:bg-base-300 hover:no-underline focus:outline-none`
|
||||
} flex gap-x-5 items-center justify-start text-left p-3 text-base-content hover:text-base-content focus:bg-base-300 hover:bg-base-300 hover:no-underline focus:outline-none`
|
||||
}
|
||||
onClick={closeSidebar}
|
||||
tabIndex={isOpen ? 0 : -1}
|
||||
role="menuitem"
|
||||
>
|
||||
<span className="w-[20px] h-[20px] flex justify-center">
|
||||
<i className={`${item.icon} text-[18px]`} aria-hidden="true"></i>
|
||||
<i className={`${item.icon} text-[20px]`} aria-hidden="true"></i>
|
||||
</span>
|
||||
<span className="ml-3 lg:ml-4">
|
||||
<span className="flex items-center mb-0.5">
|
||||
{t(`sidebar.${item.title}`, { appName: drivename })}
|
||||
</span>
|
||||
</NavLink>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
import Menu from "./Menu";
|
||||
import Submenu from "./SubMenu";
|
||||
import SocialMedia from "../SocialMedia";
|
||||
@@ -27,39 +27,18 @@ const Sidebar = ({ isOpen, closeSidebar }) => {
|
||||
if (localStorage.getItem("defaultmenuid")) {
|
||||
const Extand_Class = localStorage.getItem("Extand_Class");
|
||||
const extClass = Extand_Class && JSON.parse(Extand_Class);
|
||||
// console.log("extClass ", extClass);
|
||||
let userRole = "contracts_User";
|
||||
if (extClass && extClass.length > 0) {
|
||||
userRole = extClass[0].UserRole;
|
||||
}
|
||||
if (
|
||||
userRole === "contracts_Admin" ||
|
||||
userRole === "contracts_OrgAdmin"
|
||||
) {
|
||||
const newSidebarList = sidebarList.map((item) => {
|
||||
if (item.title === "Settings") {
|
||||
// Make a shallow copy of the item
|
||||
const newItem = { ...item };
|
||||
const arr = newItem.children.slice(0, 1);
|
||||
newItem.children = [...arr, ...subSetting];
|
||||
return newItem;
|
||||
}
|
||||
return item;
|
||||
});
|
||||
setmenuList(newSidebarList);
|
||||
} else {
|
||||
const newSidebarList = sidebarList.map((item) => {
|
||||
if (item.title === "Settings") {
|
||||
// Make a shallow copy of the item
|
||||
const newItem = { ...item };
|
||||
const arr = newItem.children.slice(0, 1);
|
||||
newItem.children = arr;
|
||||
return newItem;
|
||||
}
|
||||
return item;
|
||||
});
|
||||
setmenuList(newSidebarList);
|
||||
}
|
||||
const userRole = extClass?.[0]?.UserRole || "contracts_User";
|
||||
const isAdmin =
|
||||
userRole === "contracts_Admin" || userRole === "contracts_OrgAdmin";
|
||||
const newSidebarList = sidebarList.map((item) => {
|
||||
if (item.title !== "Settings") return item;
|
||||
const newItem = { ...item };
|
||||
const baseChildren = isAdmin ? subSetting : subSetting?.slice(0, 1);
|
||||
const mysignature = newItem.children.slice(0, 1);
|
||||
newItem.children = [...mysignature, ...baseChildren];
|
||||
return newItem;
|
||||
});
|
||||
setmenuList(newSidebarList);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Problem", e);
|
||||
|
||||
@@ -12,16 +12,16 @@ const Submenu = ({ item, closeSidebar, toggleSubmenu, submenuOpen }) => {
|
||||
<li role="none" className="my-0.5">
|
||||
<button
|
||||
onClick={() => toggleSubmenu(item.title)}
|
||||
className="flex items-center justify-start text-left p-3 lg:p-4 text-base-content hover:text-base-content focus:bg-base-300 hover:bg-base-300 hover:no-underline focus:outline-none "
|
||||
className="flex gap-x-5 items-center justify-start text-left p-3 text-base-content hover:text-base-content focus:bg-base-300 hover:bg-base-300 hover:no-underline focus:outline-none"
|
||||
aria-expanded={submenuOpen}
|
||||
aria-haspopup="true"
|
||||
aria-controls={`submenu-${title}`}
|
||||
>
|
||||
<span className="w-[20px] h-[20px] flex justify-center">
|
||||
<i className={`${icon} text-[18px]`}></i>
|
||||
<i className={`${icon} text-[20px]`}></i>
|
||||
</span>
|
||||
<div className="flex justify-between items-center w-full">
|
||||
<span className="ml-3 lg:ml-4 text-start">
|
||||
<span className="flex items-center mb-0.5">
|
||||
{t(`sidebar.${item.title}`, { appName })}
|
||||
</span>
|
||||
<i
|
||||
@@ -46,20 +46,20 @@ const Submenu = ({ item, closeSidebar, toggleSubmenu, submenuOpen }) => {
|
||||
}
|
||||
className={({ isActive }) =>
|
||||
`${
|
||||
isActive ? "bg-base-300 text-base-content" : ""
|
||||
} flex items-center justify-start text-left pl-6 md:pl-8 py-2 text-sm cursor-pointer text-base-content hover:text-base-content focus:bg-base-300 hover:bg-base-300 hover:no-underline focus:outline-none`
|
||||
isActive ? "bg-base-300 text-base-content " : ""
|
||||
} pl-4 flex items-center gap-x-5 py-2 text-sm cursor-pointer text-base-content hover:text-base-content focus:bg-base-300 hover:bg-base-300 hover:no-underline focus:outline-none`
|
||||
}
|
||||
onClick={closeSidebar}
|
||||
role="menuitem"
|
||||
tabIndex={submenuOpen ? 0 : -1}
|
||||
>
|
||||
<span className="w-[15px] h-[15px] flex justify-center">
|
||||
<span className="w-[18px] h-[18px] flex justify-center">
|
||||
<i
|
||||
className={`${childItem.icon} text-[18px]`}
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</span>
|
||||
<span className="ml-3 lg:ml-4">
|
||||
<span className="mb-0.5">
|
||||
{t(`sidebar.${item.title}-Children.${childItem.title}`, {
|
||||
appName: drivename
|
||||
})}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,6 +3,11 @@ export const templateCls = "contracts_Template";
|
||||
export const documentCls = "contracts_Document";
|
||||
export const themeColor = "#47a3ad";
|
||||
export const iconColor = "#686968";
|
||||
// Dynamic icon color function for better dark mode visibility
|
||||
export const getThemeIconColor = () => {
|
||||
const theme = document.documentElement.getAttribute("data-theme");
|
||||
return theme === "opensigndark" ? "#CCCCCC" : "#686968";
|
||||
};
|
||||
export const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
|
||||
export const maxFileSize = 10; // 10MB
|
||||
export const maxTitleLength = 250; // 250 characters
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
type Size = { width: number; height: number; };
|
||||
|
||||
/**
|
||||
* useSize
|
||||
* @param ref React ref object pointing to a DOM element
|
||||
* @returns current size of that element: { width, height }
|
||||
*/
|
||||
export function useElSize(ref: React.RefObject<HTMLElement>): Size {
|
||||
const [size, setSize] = useState<Size>({ width: 0, height: 0 });
|
||||
|
||||
useEffect(() => {
|
||||
const el = ref.current;
|
||||
if (!el) return;
|
||||
|
||||
// Initialize size
|
||||
setSize({
|
||||
width: el.offsetWidth,
|
||||
height: el.offsetHeight,
|
||||
});
|
||||
|
||||
// Watch for resize
|
||||
const observer = new ResizeObserver(entries => {
|
||||
for (let entry of entries) {
|
||||
const { width, height } = entry.contentRect;
|
||||
setSize({ width, height });
|
||||
}
|
||||
});
|
||||
observer.observe(el);
|
||||
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
};
|
||||
}, [ref]);
|
||||
|
||||
return size;
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
export default function usePdfPinchZoom(ref, scale, setScale, setZoomPercent) {
|
||||
const initialDistanceRef = useRef(null);
|
||||
const startScaleRef = useRef(scale);
|
||||
const currentScaleRef = useRef(scale);
|
||||
const pinchScaleRef = useRef(null); // stores relative scale during a gesture
|
||||
const frameRef = useRef(null);
|
||||
|
||||
// keep a reference to the latest scale value
|
||||
useEffect(() => {
|
||||
const element = ref.current;
|
||||
currentScaleRef.current = scale;
|
||||
// once React commits the new scale after a pinch we clear the transform
|
||||
if (element && !pinchScaleRef.current) {
|
||||
element.style.transform = "";
|
||||
}
|
||||
}, [scale, ref]);
|
||||
|
||||
useEffect(() => {
|
||||
const element = ref.current;
|
||||
if (!element) return;
|
||||
|
||||
const getDistance = (touches) => {
|
||||
const [touch1, touch2] = touches;
|
||||
const dx = touch1.clientX - touch2.clientX;
|
||||
const dy = touch1.clientY - touch2.clientY;
|
||||
return Math.hypot(dx, dy);
|
||||
};
|
||||
|
||||
const handleTouchStart = (e) => {
|
||||
if (e.touches.length === 2) {
|
||||
initialDistanceRef.current = getDistance(e.touches);
|
||||
startScaleRef.current = currentScaleRef.current;
|
||||
element.style.transformOrigin = "0 0";
|
||||
}
|
||||
};
|
||||
|
||||
const handleTouchMove = (e) => {
|
||||
if (e.touches.length === 2 && initialDistanceRef.current) {
|
||||
e.preventDefault();
|
||||
const newDistance = getDistance(e.touches);
|
||||
const pinchScale = newDistance / initialDistanceRef.current;
|
||||
const newScale = Math.max(1, startScaleRef.current * pinchScale);
|
||||
// store relative scale so it can be applied on top of current width
|
||||
pinchScaleRef.current = newScale / startScaleRef.current;
|
||||
|
||||
if (!frameRef.current) {
|
||||
frameRef.current = requestAnimationFrame(() => {
|
||||
element.style.transform = `scale(${pinchScaleRef.current})`;
|
||||
frameRef.current = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleTouchEnd = () => {
|
||||
if (pinchScaleRef.current) {
|
||||
let relativeScale = pinchScaleRef.current;
|
||||
const inlineTransform = element.style.transform;
|
||||
if (inlineTransform && inlineTransform.startsWith("scale(")) {
|
||||
const value = parseFloat(inlineTransform.slice(6, -1));
|
||||
if (!isNaN(value)) {
|
||||
relativeScale = value;
|
||||
}
|
||||
}
|
||||
const finalScale = Math.max(1, startScaleRef.current * relativeScale);
|
||||
const percent = parseFloat(((finalScale - 1) * 100).toFixed(2));
|
||||
const unchanged =
|
||||
Math.abs(finalScale - currentScaleRef.current) < 0.001;
|
||||
|
||||
pinchScaleRef.current = null;
|
||||
if (frameRef.current) {
|
||||
cancelAnimationFrame(frameRef.current);
|
||||
frameRef.current = null;
|
||||
}
|
||||
|
||||
if (unchanged) {
|
||||
// if scale didn't actually change, reset transform right away
|
||||
element.style.transform = "";
|
||||
} else {
|
||||
// keep transform applied until state update reflects the new scale
|
||||
element.style.transform = `scale(${relativeScale})`;
|
||||
}
|
||||
|
||||
setScale(finalScale);
|
||||
setZoomPercent(percent);
|
||||
|
||||
// ensure refs reflect the latest value before next gesture starts
|
||||
currentScaleRef.current = finalScale;
|
||||
startScaleRef.current = finalScale;
|
||||
}
|
||||
initialDistanceRef.current = null;
|
||||
};
|
||||
|
||||
element.addEventListener("touchstart", handleTouchStart, {
|
||||
passive: false
|
||||
});
|
||||
element.addEventListener("touchmove", handleTouchMove, { passive: false });
|
||||
element.addEventListener("touchend", handleTouchEnd);
|
||||
element.addEventListener("touchcancel", handleTouchEnd);
|
||||
|
||||
return () => {
|
||||
element.removeEventListener("touchstart", handleTouchStart);
|
||||
element.removeEventListener("touchmove", handleTouchMove);
|
||||
element.removeEventListener("touchend", handleTouchEnd);
|
||||
element.removeEventListener("touchcancel", handleTouchEnd);
|
||||
};
|
||||
}, [ref, setScale, setZoomPercent]);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useEffect } from "react";
|
||||
|
||||
/**`useScript` hook is generated scripte for google sign in button */
|
||||
/**`useScript` hook is generated script for google sign in button */
|
||||
export const useScript = (url, onload) => {
|
||||
useEffect(() => {
|
||||
const script = document.createElement("script");
|
||||
@@ -15,4 +15,3 @@ export const useScript = (url, onload) => {
|
||||
};
|
||||
}, [url, onload]);
|
||||
};
|
||||
|
||||
|
||||
@@ -98,4 +98,164 @@ body {
|
||||
*::-webkit-scrollbar-thumb {
|
||||
background-color: gray;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
/* Note: Dark mode styling is now handled via Tailwind utilities in tailwind.config.js */
|
||||
/* You can use classes like: icon-improved, icon-muted, icon-disabled, op-btn-vscode-disabled */
|
||||
|
||||
/* React-tour and ReactTooltip dark mode styling */
|
||||
[data-theme="opensigndark"] {
|
||||
/* React-tour modal styling */
|
||||
.reactour__helper {
|
||||
background-color: #1F2937 !important;
|
||||
color: #E5E7EB !important;
|
||||
border: 1px solid #374151 !important;
|
||||
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.5) !important;
|
||||
}
|
||||
|
||||
.reactour__close {
|
||||
color: #E5E7EB !important;
|
||||
background-color: #374151 !important;
|
||||
border: 1px solid #4B5563 !important;
|
||||
}
|
||||
|
||||
.reactour__close:hover {
|
||||
background-color: #4B5563 !important;
|
||||
color: #FFFFFF !important;
|
||||
}
|
||||
|
||||
/* React-tour navigation buttons */
|
||||
.reactour__controls {
|
||||
background-color: #1F2937 !important;
|
||||
border-top: 1px solid #374151 !important;
|
||||
}
|
||||
|
||||
.reactour__controls button {
|
||||
background-color: #374151 !important;
|
||||
color: #E5E7EB !important;
|
||||
border: 1px solid #4B5563 !important;
|
||||
}
|
||||
|
||||
.reactour__controls button:hover {
|
||||
background-color: #4B5563 !important;
|
||||
color: #FFFFFF !important;
|
||||
}
|
||||
|
||||
.reactour__controls button:disabled {
|
||||
background-color: #3C3C3C !important;
|
||||
color: #858585 !important;
|
||||
border-color: #565656 !important;
|
||||
cursor: not-allowed !important;
|
||||
}
|
||||
|
||||
/* ReactTooltip styling */
|
||||
.react-tooltip {
|
||||
background-color: #1F2937 !important;
|
||||
color: #E5E7EB !important;
|
||||
border: 1px solid #374151 !important;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4) !important;
|
||||
}
|
||||
|
||||
.react-tooltip.type-dark {
|
||||
background-color: #1F2937 !important;
|
||||
color: #E5E7EB !important;
|
||||
}
|
||||
|
||||
.react-tooltip.place-top:after,
|
||||
.react-tooltip.place-bottom:after,
|
||||
.react-tooltip.place-left:after,
|
||||
.react-tooltip.place-right:after {
|
||||
border-color: #1F2937 !important;
|
||||
}
|
||||
|
||||
/* Tour content improvements */
|
||||
.reactour__badge {
|
||||
background-color: #007ACC !important;
|
||||
color: #FFFFFF !important;
|
||||
}
|
||||
|
||||
.reactour__helper h1,
|
||||
.reactour__helper h2,
|
||||
.reactour__helper h3,
|
||||
.reactour__helper h4,
|
||||
.reactour__helper h5,
|
||||
.reactour__helper h6 {
|
||||
color: #FFFFFF !important;
|
||||
}
|
||||
|
||||
.reactour__helper p {
|
||||
color: #E5E7EB !important;
|
||||
}
|
||||
|
||||
/* VS Code-style tour buttons */
|
||||
.reactour__controls .op-btn {
|
||||
background-color: #007ACC !important;
|
||||
color: #FFFFFF !important;
|
||||
border: 1px solid #007ACC !important;
|
||||
}
|
||||
|
||||
.reactour__controls .op-btn:hover {
|
||||
background-color: #0086D1 !important;
|
||||
border-color: #0086D1 !important;
|
||||
}
|
||||
|
||||
.reactour__controls .op-btn-secondary {
|
||||
background-color: #374151 !important;
|
||||
color: #E5E7EB !important;
|
||||
border: 1px solid #4B5563 !important;
|
||||
}
|
||||
|
||||
.reactour__controls .op-btn-secondary:hover {
|
||||
background-color: #4B5563 !important;
|
||||
color: #FFFFFF !important;
|
||||
}
|
||||
|
||||
/* React-datepicker dark mode styling */
|
||||
.react-datepicker {
|
||||
background-color: #1F2937 !important;
|
||||
border: 1px solid #374151 !important;
|
||||
color: #E5E7EB !important;
|
||||
}
|
||||
|
||||
.react-datepicker__header {
|
||||
background-color: #374151 !important;
|
||||
border-bottom: 1px solid #4B5563 !important;
|
||||
color: #E5E7EB !important;
|
||||
}
|
||||
|
||||
.react-datepicker__current-month,
|
||||
.react-datepicker__day-name {
|
||||
color: #E5E7EB !important;
|
||||
}
|
||||
|
||||
.react-datepicker__day {
|
||||
color: #E5E7EB !important;
|
||||
}
|
||||
|
||||
.react-datepicker__day:hover {
|
||||
background-color: #4B5563 !important;
|
||||
color: #FFFFFF !important;
|
||||
}
|
||||
|
||||
.react-datepicker__day--selected {
|
||||
background-color: #007ACC !important;
|
||||
color: #FFFFFF !important;
|
||||
}
|
||||
|
||||
.react-datepicker__day--keyboard-selected {
|
||||
background-color: #374151 !important;
|
||||
color: #E5E7EB !important;
|
||||
}
|
||||
|
||||
.react-datepicker__day--outside-month {
|
||||
color: #6B7280 !important;
|
||||
}
|
||||
|
||||
.react-datepicker__navigation {
|
||||
color: #E5E7EB !important;
|
||||
}
|
||||
|
||||
.react-datepicker__navigation:hover {
|
||||
background-color: #4B5563 !important;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import "./index.css";
|
||||
import "./styles/dark-theme-improvements.css";
|
||||
import App from "./App";
|
||||
import { Provider } from "react-redux";
|
||||
import { store } from "./redux/store";
|
||||
@@ -24,6 +25,11 @@ const serverUrl = serverUrl_fn();
|
||||
Parse.initialize(appId);
|
||||
Parse.serverURL = serverUrl;
|
||||
|
||||
const savedTheme = localStorage.getItem("theme");
|
||||
if (savedTheme === "dark") {
|
||||
document.documentElement.setAttribute("data-theme", "opensigndark");
|
||||
}
|
||||
|
||||
const HTML5toTouch = {
|
||||
backends: [
|
||||
{
|
||||
|
||||
@@ -186,6 +186,14 @@ export default function reportJson(id) {
|
||||
btnIcon: "fa-light fa-envelope",
|
||||
redirectUrl: "",
|
||||
action: "saveastemplate"
|
||||
},
|
||||
{
|
||||
btnId: "8440",
|
||||
btnLabel: "Fix & resend",
|
||||
hoverLabel: "Fix & resend",
|
||||
btnIcon: "fa-light fa-paper-plane",
|
||||
redirectUrl: "",
|
||||
action: "recreatedocument"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+205
-237
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useNavigate, useParams } from "react-router";
|
||||
import { formJson } from "../json/FormJson";
|
||||
import Parse from "parse";
|
||||
@@ -12,9 +12,12 @@ import {
|
||||
flattenPdf,
|
||||
generatePdfName,
|
||||
generateTitleFromFilename,
|
||||
getFileName,
|
||||
getSecureUrl,
|
||||
toDataUrl
|
||||
toDataUrl,
|
||||
decryptPdf,
|
||||
base64ToFile,
|
||||
getFileAsArrayBuffer,
|
||||
removeTrailingSegment
|
||||
} from "../constant/Utils";
|
||||
import { PDFDocument } from "pdf-lib";
|
||||
import axios from "axios";
|
||||
@@ -68,6 +71,7 @@ const Forms = (props) => {
|
||||
AllowModifications: false
|
||||
});
|
||||
const [fileupload, setFileUpload] = useState("");
|
||||
const [selectedFiles, setSelectedFiles] = useState([]);
|
||||
const [fileload, setfileload] = useState(false);
|
||||
const [percentage, setpercentage] = useState(0);
|
||||
const [isReset, setIsReset] = useState(false);
|
||||
@@ -114,15 +118,6 @@ const Forms = (props) => {
|
||||
}));
|
||||
};
|
||||
|
||||
function getFileAsArrayBuffer(file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => resolve(e.target.result);
|
||||
reader.onerror = (e) => reject(e.target.error);
|
||||
reader.readAsArrayBuffer(file);
|
||||
});
|
||||
}
|
||||
|
||||
// `removeFile` is used to reset progress, percentage and remove file if exists
|
||||
const removeFile = (e) => {
|
||||
setfileload(false);
|
||||
@@ -134,219 +129,212 @@ const Forms = (props) => {
|
||||
const handleFileInput = async (e) => {
|
||||
setpercentage(0);
|
||||
try {
|
||||
let files = e.target.files;
|
||||
setFormData((prev) => ({ ...prev, file: e.target.files[0] }));
|
||||
if (typeof files[0] !== "undefined") {
|
||||
const mb = Math.round(files[0].size / Math.pow(1024, 2));
|
||||
if (mb > maxFileSize) {
|
||||
alert(`${t("file-alert-1")} ${maxFileSize} MB`);
|
||||
setFileUpload("");
|
||||
removeFile(e);
|
||||
return;
|
||||
} else {
|
||||
if (files?.[0]?.type === "application/pdf") {
|
||||
const size = files?.[0]?.size;
|
||||
const name = generatePdfName(16);
|
||||
const pdfName = `${name?.split(".")[0]}.pdf`;
|
||||
setfileload(true);
|
||||
try {
|
||||
const res = await getFileAsArrayBuffer(files[0]);
|
||||
const flatPdf = await flattenPdf(res);
|
||||
const parseFile = new Parse.File(
|
||||
pdfName,
|
||||
[...flatPdf],
|
||||
"application/pdf"
|
||||
);
|
||||
const files = Array.from(e.target.files);
|
||||
const filesNameArr = files.map((f) => f.name);
|
||||
setSelectedFiles(filesNameArr);
|
||||
if (!files.length) {
|
||||
alert(t("file-alert-2"));
|
||||
return;
|
||||
}
|
||||
// setFormData((prev) => ({ ...prev, file: files[0] }));
|
||||
const totalMb = Math.round(
|
||||
files.reduce((sum, f) => sum + f.size, 0) / Math.pow(1024, 2)
|
||||
);
|
||||
if (totalMb > maxFileSize) {
|
||||
alert(`${t("file-alert-1")} ${maxFileSize} MB`);
|
||||
setFileUpload("");
|
||||
setSelectedFiles([]);
|
||||
removeFile(e);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await parseFile.save({
|
||||
progress: (progressValue, loaded, total, { type }) => {
|
||||
if (type === "upload" && progressValue !== null) {
|
||||
const percentCompleted = Math.round(
|
||||
(loaded * 100) / total
|
||||
);
|
||||
setpercentage(percentCompleted);
|
||||
}
|
||||
}
|
||||
});
|
||||
// The response object will contain information about the uploaded file
|
||||
// You can access the URL of the uploaded file using response.url()
|
||||
if (response.url()) {
|
||||
const fileRes = await getSecureUrl(response.url());
|
||||
if (fileRes.url) {
|
||||
setFileUpload(fileRes.url);
|
||||
setfileload(false);
|
||||
const tenantId = localStorage.getItem("TenantId");
|
||||
const title = generateTitleFromFilename(files?.[0]?.name);
|
||||
setFormData((obj) => ({ ...obj, Name: title }));
|
||||
SaveFileSize(size, fileRes.url, tenantId);
|
||||
return fileRes.url;
|
||||
} else {
|
||||
removeFile(e);
|
||||
}
|
||||
} else {
|
||||
removeFile(e);
|
||||
}
|
||||
} catch (error) {
|
||||
removeFile(e);
|
||||
console.error("Error uploading file:", error);
|
||||
}
|
||||
const pdfBuffers = [];
|
||||
for (const file of files) {
|
||||
setFormData((prev) => ({ ...prev, file: file }));
|
||||
if (file.type === "application/pdf") {
|
||||
try {
|
||||
const buffer = await getFileAsArrayBuffer(file);
|
||||
const flat = await flattenPdf(buffer);
|
||||
pdfBuffers.push(flat);
|
||||
} catch (err) {
|
||||
if (err?.message?.includes("is encrypted")) {
|
||||
try {
|
||||
setIsDecrypting(true);
|
||||
const pdfFile = await decryptPdf(file, "");
|
||||
setIsDecrypting(false);
|
||||
setfileload(true);
|
||||
const res = await getFileAsArrayBuffer(pdfFile);
|
||||
const flatPdf = await flattenPdf(res);
|
||||
// Upload the file to Parse Server
|
||||
pdfBuffers.push(flatPdf);
|
||||
} catch (err) {
|
||||
if (err?.message?.includes("is encrypted")) {
|
||||
try {
|
||||
setIsDecrypting(true);
|
||||
const size = files?.[0].size;
|
||||
const name = generatePdfName(16);
|
||||
const url = "https://ai.nxglabs.in/decryptpdf"; //
|
||||
let formData = new FormData();
|
||||
formData.append("file", files[0]);
|
||||
formData.append("password", "");
|
||||
const config = {
|
||||
headers: { "content-type": "multipart/form-data" },
|
||||
responseType: "blob"
|
||||
};
|
||||
const response = await axios.post(url, formData, config);
|
||||
const pdfBlob = new Blob([response.data], {
|
||||
type: "application/pdf"
|
||||
});
|
||||
const pdfFile = new File([pdfBlob], name, {
|
||||
type: "application/pdf"
|
||||
});
|
||||
setIsDecrypting(false);
|
||||
setfileload(true);
|
||||
removeFile(e);
|
||||
if (err?.response?.status === 401) {
|
||||
// setIsPassword(true);
|
||||
const password = prompt(
|
||||
`PDF "${file.name}" is password-protected. Enter password:`
|
||||
);
|
||||
|
||||
if (password) {
|
||||
try {
|
||||
const pdfFile = await decryptPdf(file, password);
|
||||
setIsDecrypting(false);
|
||||
setfileload(true);
|
||||
const res = await getFileAsArrayBuffer(pdfFile);
|
||||
const flatPdf = await flattenPdf(res);
|
||||
// Upload the file to Parse Server
|
||||
const parseFile = new Parse.File(
|
||||
name,
|
||||
[...flatPdf],
|
||||
"application/pdf"
|
||||
pdfBuffers.push(flatPdf);
|
||||
} catch (err) {
|
||||
console.error(
|
||||
"Incorrect password or decryption failed",
|
||||
err
|
||||
);
|
||||
setSelectedFiles(
|
||||
filesNameArr.filter((f) => f !== file.name)
|
||||
);
|
||||
|
||||
await parseFile.save({
|
||||
progress: (progressValue, loaded, total, { type }) => {
|
||||
if (type === "upload" && progressValue !== null) {
|
||||
const percentCompleted = Math.round(
|
||||
(loaded * 100) / total
|
||||
);
|
||||
setpercentage(percentCompleted);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Retrieve the URL of the uploaded file
|
||||
if (parseFile.url()) {
|
||||
const fileRes = await getSecureUrl(parseFile.url());
|
||||
if (fileRes.url) {
|
||||
setFileUpload(fileRes.url);
|
||||
removeFile();
|
||||
const title = generateTitleFromFilename(
|
||||
files?.[0]?.name
|
||||
);
|
||||
setFormData((obj) => ({ ...obj, Name: title }));
|
||||
const tenantId = localStorage.getItem("TenantId");
|
||||
SaveFileSize(size, fileRes.url, tenantId);
|
||||
return fileRes.url;
|
||||
} else {
|
||||
removeFile(e);
|
||||
}
|
||||
} else {
|
||||
removeFile(e);
|
||||
}
|
||||
} catch (err) {
|
||||
removeFile();
|
||||
if (err?.response?.status === 401) {
|
||||
setIsPassword(true);
|
||||
} else {
|
||||
console.log("Error uploading file: ", err?.response);
|
||||
setIsDecrypting(false);
|
||||
e.target.value = "";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log("err ", err);
|
||||
setFileUpload("");
|
||||
removeFile(e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const isImage = files?.[0]?.type.includes("image/");
|
||||
if (isImage) {
|
||||
const image = await toDataUrl(files[0]);
|
||||
const pdfDoc = await PDFDocument.create();
|
||||
let embedImg;
|
||||
if (files?.[0]?.type === "image/png") {
|
||||
embedImg = await pdfDoc.embedPng(image);
|
||||
} else {
|
||||
embedImg = await pdfDoc.embedJpg(image);
|
||||
}
|
||||
|
||||
// Get image dimensions
|
||||
const imageWidth = embedImg.width;
|
||||
const imageHeight = embedImg.height;
|
||||
const page = pdfDoc.addPage([imageWidth, imageHeight]);
|
||||
page.drawImage(embedImg, {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: imageWidth,
|
||||
height: imageHeight
|
||||
});
|
||||
const size = files?.[0]?.size;
|
||||
const name = generatePdfName(16);
|
||||
const getFile = await pdfDoc.save({
|
||||
useObjectStreams: false
|
||||
});
|
||||
setfileload(true);
|
||||
const pdfName = `${name?.split(".")[0]}.pdf`;
|
||||
const parseFile = new Parse.File(
|
||||
pdfName,
|
||||
[...getFile],
|
||||
"application/pdf"
|
||||
);
|
||||
|
||||
try {
|
||||
const response = await parseFile.save({
|
||||
progress: (progressValue, loaded, total, { type }) => {
|
||||
if (type === "upload" && progressValue !== null) {
|
||||
const percentCompleted = Math.round(
|
||||
(loaded * 100) / total
|
||||
);
|
||||
setpercentage(percentCompleted);
|
||||
}
|
||||
}
|
||||
});
|
||||
// The response object will contain information about the uploaded file
|
||||
// You can access the URL of the uploaded file using response.url()
|
||||
if (response.url()) {
|
||||
const fileRes = await getSecureUrl(response.url());
|
||||
if (fileRes.url) {
|
||||
setFileUpload(fileRes.url);
|
||||
setfileload(false);
|
||||
const tenantId = localStorage.getItem("TenantId");
|
||||
const title = generateTitleFromFilename(files?.[0]?.name);
|
||||
setFormData((obj) => ({ ...obj, Name: title }));
|
||||
SaveFileSize(size, fileRes.url, tenantId);
|
||||
return fileRes.url;
|
||||
} else {
|
||||
removeFile(e);
|
||||
alert(`Incorrect password for file: ${file.name}`);
|
||||
}
|
||||
} else {
|
||||
console.error("password not provided");
|
||||
setSelectedFiles(
|
||||
filesNameArr.filter((f) => f !== file.name)
|
||||
);
|
||||
setIsDecrypting(false);
|
||||
setfileload(false);
|
||||
removeFile(e);
|
||||
}
|
||||
} catch (error) {
|
||||
} else {
|
||||
console.log("Error uploading file: ", err?.response);
|
||||
setIsDecrypting(false);
|
||||
e.target.value = "";
|
||||
removeFile(e);
|
||||
console.error("Error uploading file:", error);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log("err ", err);
|
||||
removeFile(e);
|
||||
}
|
||||
}
|
||||
} else if (file.type.includes("image/")) {
|
||||
const image = await toDataUrl(file);
|
||||
const pdfDoc = await PDFDocument.create();
|
||||
const embed =
|
||||
file.type === "image/png"
|
||||
? await pdfDoc.embedPng(image)
|
||||
: await pdfDoc.embedJpg(image);
|
||||
const page = pdfDoc.addPage([embed.width, embed.height]);
|
||||
page.drawImage(embed, {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: embed.width,
|
||||
height: embed.height
|
||||
});
|
||||
const bytes = await pdfDoc.save({ useObjectStreams: false });
|
||||
pdfBuffers.push(bytes);
|
||||
} else if (
|
||||
file.type ===
|
||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.document" ||
|
||||
file.name.toLowerCase().endsWith(".docx")
|
||||
) {
|
||||
try {
|
||||
const baseApi = localStorage.getItem("baseUrl") || "";
|
||||
const url = removeTrailingSegment(baseApi) + "/docxtopdf";
|
||||
let fd = new FormData();
|
||||
fd.append("file", file);
|
||||
setfileload(true);
|
||||
setpercentage(0);
|
||||
const config = {
|
||||
headers: {
|
||||
"content-type": "multipart/form-data",
|
||||
sessiontoken: Parse.User.current().getSessionToken()
|
||||
},
|
||||
signal: abortController.signal,
|
||||
onUploadProgress: (progressEvent) => {
|
||||
if (progressEvent.total) {
|
||||
const percentCompleted = Math.round(
|
||||
(progressEvent.loaded * 100) / progressEvent.total
|
||||
);
|
||||
setpercentage(percentCompleted);
|
||||
}
|
||||
}
|
||||
};
|
||||
const res = await axios.post(url, fd, config);
|
||||
if (res.data?.url) {
|
||||
const pdfRes = await axios.get(res.data.url, {
|
||||
responseType: "arraybuffer"
|
||||
});
|
||||
pdfBuffers.push(pdfRes.data);
|
||||
}
|
||||
setfileload(false);
|
||||
} catch (err) {
|
||||
setfileload(false);
|
||||
removeFile(e);
|
||||
console.log("err in docx to pdf ", err);
|
||||
const error = isOpenSignDomain
|
||||
? `${t("docx-error")} ${t("docx-error-contact")}`
|
||||
: t("docx-error");
|
||||
alert(error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
}
|
||||
|
||||
if (!pdfBuffers.length) {
|
||||
alert(t("file-alert-2"));
|
||||
return false;
|
||||
setSelectedFiles([]);
|
||||
return;
|
||||
}
|
||||
|
||||
setfileload(true);
|
||||
const merged = await PDFDocument.create();
|
||||
for (const bytes of pdfBuffers) {
|
||||
const doc = await PDFDocument.load(bytes, { ignoreEncryption: true });
|
||||
const pages = await merged.copyPages(doc, doc.getPageIndices());
|
||||
pages.forEach((p) => merged.addPage(p));
|
||||
}
|
||||
|
||||
const pdfBytes = await merged.save({ useObjectStreams: false });
|
||||
const name = generatePdfName(16);
|
||||
const pdfName = `${name}.pdf`;
|
||||
let uploadedUrl = "";
|
||||
const parseFile = new Parse.File(
|
||||
pdfName,
|
||||
[...pdfBytes],
|
||||
"application/pdf"
|
||||
);
|
||||
const response = await parseFile.save({
|
||||
progress: (progressValue, loaded, total, { type }) => {
|
||||
if (type === "upload" && progressValue !== null) {
|
||||
const percentCompleted = Math.round((loaded * 100) / total);
|
||||
setpercentage(percentCompleted);
|
||||
}
|
||||
}
|
||||
});
|
||||
if (response.url()) {
|
||||
const fileRes = await getSecureUrl(response.url());
|
||||
if (fileRes.url) {
|
||||
uploadedUrl = fileRes.url;
|
||||
}
|
||||
}
|
||||
if (uploadedUrl) {
|
||||
const tenantId = localStorage.getItem("TenantId");
|
||||
SaveFileSize(pdfBytes.byteLength, uploadedUrl, tenantId);
|
||||
setFileUpload(uploadedUrl);
|
||||
setfileload(false);
|
||||
const title = generateTitleFromFilename(filesNameArr?.[0]);
|
||||
setFormData((obj) => ({ ...obj, Name: title }));
|
||||
removeFile(e);
|
||||
} else {
|
||||
setfileload(false);
|
||||
removeFile(e);
|
||||
setSelectedFiles([]);
|
||||
}
|
||||
} catch (error) {
|
||||
alert(error.message);
|
||||
return false;
|
||||
setSelectedFiles([]);
|
||||
}
|
||||
};
|
||||
// `isValidURL` is used to check valid webhook url
|
||||
@@ -446,16 +434,13 @@ const Forms = (props) => {
|
||||
className: "contracts_Users",
|
||||
objectId: ExtCls[0].objectId
|
||||
});
|
||||
if (extUserData?.TenantId?.ActiveFileAdapter) {
|
||||
object.set("FileAdapterId", extUserData?.TenantId?.ActiveFileAdapter);
|
||||
}
|
||||
const res = await object.save();
|
||||
if (res) {
|
||||
setSigners([]);
|
||||
setBcc([]);
|
||||
setFolder({ ObjectId: "", Name: "" });
|
||||
const notifySign =
|
||||
extUserData?.NotifyOnSignatures
|
||||
extUserData?.NotifyOnSignatures !== undefined
|
||||
? extUserData?.NotifyOnSignatures
|
||||
: true;
|
||||
setFormData({
|
||||
@@ -478,6 +463,7 @@ const Forms = (props) => {
|
||||
AllowModifications: false
|
||||
});
|
||||
setFileUpload("");
|
||||
setSelectedFiles([]);
|
||||
setpercentage(0);
|
||||
navigate(`/${props?.redirectRoute}/${res.id}`);
|
||||
}
|
||||
@@ -534,7 +520,7 @@ const Forms = (props) => {
|
||||
setBcc([]);
|
||||
setFolder({ ObjectId: "", Name: "" });
|
||||
const notifySign =
|
||||
extUserData?.NotifyOnSignatures
|
||||
extUserData?.NotifyOnSignatures !== undefined
|
||||
? extUserData?.NotifyOnSignatures
|
||||
: true;
|
||||
let obj = {
|
||||
@@ -559,6 +545,7 @@ const Forms = (props) => {
|
||||
setFormData(obj);
|
||||
removeFile();
|
||||
setFileUpload("");
|
||||
setSelectedFiles([]);
|
||||
setTimeout(() => setIsReset(false), 50);
|
||||
};
|
||||
const handleCancel = () => {
|
||||
@@ -571,24 +558,7 @@ const Forms = (props) => {
|
||||
try {
|
||||
const size = formData?.file?.size;
|
||||
const name = generatePdfName(16);
|
||||
const url = "https://ai.nxglabs.in/decryptpdf"; //
|
||||
let Data = new FormData();
|
||||
Data.append("file", formData?.file);
|
||||
Data.append("password", formData.password);
|
||||
const config = {
|
||||
headers: {
|
||||
"content-type": "multipart/form-data"
|
||||
// sessiontoken: Parse.User.current().getSessionToken()
|
||||
},
|
||||
responseType: "blob"
|
||||
};
|
||||
const response = await axios.post(url, Data, config);
|
||||
const pdfBlob = new Blob([response.data], {
|
||||
type: "application/pdf"
|
||||
});
|
||||
const pdfFile = new File([pdfBlob], name, {
|
||||
type: "application/pdf"
|
||||
});
|
||||
const pdfFile = await decryptPdf(formData?.file, formData?.password);
|
||||
setIsDecrypting(false);
|
||||
const res = await getFileAsArrayBuffer(pdfFile);
|
||||
const flatPdf = await flattenPdf(res);
|
||||
@@ -710,9 +680,7 @@ const Forms = (props) => {
|
||||
</ModalUi>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="mb-[11px]">
|
||||
<h1 className="text-[20px] font-semibold">
|
||||
{t(`form-name.${props?.title}`)}
|
||||
</h1>
|
||||
<h1 className="text-[20px] font-semibold">{t(props?.title)}</h1>
|
||||
{props.title === "Sign Yourself" && (
|
||||
<div className="text-gray-500 text-xs mt-1">
|
||||
{t("signyour-self-description")}
|
||||
@@ -749,19 +717,20 @@ const Forms = (props) => {
|
||||
)}
|
||||
<div className="text-xs">
|
||||
<label className="block">
|
||||
{`${`${t("report-heading.File")} (${t("file-type")}`}${
|
||||
")"
|
||||
}`}
|
||||
{`${`${t("report-heading.File")} (${t("file-type")}`}${", docx)"}`}
|
||||
<span className="text-red-500 text-[13px]">*</span>
|
||||
</label>
|
||||
{fileupload.length > 0 ? (
|
||||
<div className="flex gap-1 justify-center items-center">
|
||||
<div className="flex justify-between items-center op-input op-input-bordered op-input-sm w-full h-full text-[13px]">
|
||||
<div className="break-all cursor-default">
|
||||
{t("file-selected")}: {getFileName(fileupload)}
|
||||
{t("file-selected")}: {selectedFiles.join(", ")}
|
||||
</div>
|
||||
<div
|
||||
onClick={() => setFileUpload("")}
|
||||
onClick={() => {
|
||||
setFileUpload("");
|
||||
setSelectedFiles([]);
|
||||
}}
|
||||
className="cursor-pointer px-[10px] text-[20px] font-bold text-red-500"
|
||||
>
|
||||
<i className="fa-light fa-xmark"></i>
|
||||
@@ -772,12 +741,11 @@ const Forms = (props) => {
|
||||
<div className="flex gap-1 justify-center items-center">
|
||||
<input
|
||||
type="file"
|
||||
multiple
|
||||
className="op-file-input op-file-input-bordered op-file-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
onChange={(e) => handleFileInput(e)}
|
||||
ref={inputFileRef}
|
||||
accept={
|
||||
"application/pdf,image/png,image/jpeg"
|
||||
}
|
||||
accept="application/pdf,application/vnd.openxmlformats-officedocument.wordprocessingml.document,image/png,image/jpeg"
|
||||
onInvalid={(e) =>
|
||||
e.target.setCustomValidity(t("input-required"))
|
||||
}
|
||||
|
||||
@@ -125,7 +125,11 @@ function Login() {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error while logging in user", error);
|
||||
showToast("danger", "Invalid username/password or region");
|
||||
if (error?.code === 1001) {
|
||||
showToast("danger", t("action-prohibited"));
|
||||
} else {
|
||||
showToast("danger", "Invalid username/password or region");
|
||||
}
|
||||
}
|
||||
};
|
||||
const handleLoginBtn = async (event) => {
|
||||
@@ -495,7 +499,7 @@ function Login() {
|
||||
to="/forgetpassword"
|
||||
className="text-[13px] op-link op-link-primary underline-offset-1 focus:outline-none ml-1"
|
||||
>
|
||||
{t("forgot-password")}
|
||||
{t("forgot-password")}?
|
||||
</NavLink>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -324,63 +324,58 @@ const ManageSign = () => {
|
||||
hidden
|
||||
/>
|
||||
<div className="relative">
|
||||
<div>
|
||||
{image ? (
|
||||
<div className="signatureCanvas relative border-[2px] border-[#888] rounded-box overflow-hidden">
|
||||
<img
|
||||
alt="signature"
|
||||
src={image}
|
||||
className="w-full h-full object-contain"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<SignatureCanvas
|
||||
ref={canvasRef}
|
||||
penColor={penColor}
|
||||
canvasProps={{
|
||||
width: "456px",
|
||||
height: "180px",
|
||||
className:
|
||||
"signatureCanvas border-[2px] border-[#888] rounded-box"
|
||||
}}
|
||||
// backgroundColor="rgb(255, 255, 255)"
|
||||
onEnd={() =>
|
||||
handleSignatureChange(canvasRef.current.toDataURL())
|
||||
}
|
||||
dotSize={1}
|
||||
{image ? (
|
||||
<div className="mysignatureCanvas relative border-[2px] border-[#888] rounded-box overflow-hidden">
|
||||
<img
|
||||
alt="signature"
|
||||
src={image}
|
||||
className="w-full h-full object-contain"
|
||||
/>
|
||||
)}
|
||||
<div className="penContainerDefault flex flex-row justify-between">
|
||||
<div>
|
||||
{!image && (
|
||||
<div className="flex flex-row gap-1.5 m-[5px]">
|
||||
{allColor.map((data, key) => {
|
||||
return (
|
||||
<i
|
||||
key={key}
|
||||
onClick={() => setPenColor(allColor[key])}
|
||||
className={`border-b-[2px] ${key === 0 && penColor === "blue" ? "border-blue-600" : key === 1 && penColor === "red" ? "border-red-500" : key === 2 && penColor === "black" ? "border-black" : "border-white"} text-[${data}] text-[16px] fa-light fa-pen-nib`}
|
||||
></i>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<SignatureCanvas
|
||||
ref={canvasRef}
|
||||
penColor={penColor}
|
||||
canvasProps={{
|
||||
className:
|
||||
"mysignatureCanvas border-[2px] border-[#888] rounded-box"
|
||||
}}
|
||||
onEnd={() =>
|
||||
handleSignatureChange(canvasRef.current.toDataURL())
|
||||
}
|
||||
dotSize={1}
|
||||
/>
|
||||
)}
|
||||
<div className="penContainerDefault flex flex-row justify-between">
|
||||
<div>
|
||||
{!image && (
|
||||
<div className="flex flex-row gap-1.5 m-[5px]">
|
||||
{allColor.map((data, key) => {
|
||||
return (
|
||||
<i
|
||||
key={key}
|
||||
onClick={() => setPenColor(allColor[key])}
|
||||
className={`border-b-[2px] ${key === 0 && penColor === "blue" ? "border-blue-600" : key === 1 && penColor === "red" ? "border-red-500" : key === 2 && penColor === "black" ? "border-black" : "border-white"} text-[${data}] text-[16px] fa-light fa-pen-nib`}
|
||||
></i>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-row gap-2 text-sm md:text-base mr-1">
|
||||
<div
|
||||
type="button"
|
||||
className="op-link"
|
||||
onClick={() => handleUploadBtn()}
|
||||
>
|
||||
{t("upload")}
|
||||
</div>
|
||||
<div className="flex flex-row gap-2 text-sm md:text-base mr-1">
|
||||
<div
|
||||
type="button"
|
||||
className="op-link"
|
||||
onClick={() => handleUploadBtn()}
|
||||
>
|
||||
{t("upload")}
|
||||
</div>
|
||||
<div
|
||||
type="button"
|
||||
className="op-link"
|
||||
onClick={() => handleClear()}
|
||||
>
|
||||
{t("clear")}
|
||||
</div>
|
||||
<div
|
||||
type="button"
|
||||
className="op-link"
|
||||
onClick={() => handleClear()}
|
||||
>
|
||||
{t("clear")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -420,7 +415,6 @@ const ManageSign = () => {
|
||||
canvasProps={{
|
||||
className: "intialSignature rounded-box"
|
||||
}}
|
||||
// backgroundColor="rgb(255, 255, 255)"
|
||||
onEnd={() => handleInitialsChange()}
|
||||
dotSize={1}
|
||||
/>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useEffect, useState, useRef } from "react";
|
||||
import "../styles/opensigndrive.css";
|
||||
import {
|
||||
iconColor,
|
||||
getThemeIconColor,
|
||||
} from "../constant/const";
|
||||
import {
|
||||
getDrive
|
||||
@@ -539,6 +539,12 @@ function Opensigndrive() {
|
||||
}
|
||||
}, 300);
|
||||
};
|
||||
|
||||
const handleSearchPaste = (e) => {
|
||||
setTimeout(() => {
|
||||
handleSearchChange({ target: { value: e.target.value } });
|
||||
}, 0);
|
||||
};
|
||||
// Cleanup on unmount
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
@@ -657,7 +663,8 @@ function Opensigndrive() {
|
||||
type="search"
|
||||
value={searchTerm}
|
||||
onChange={handleSearchChange}
|
||||
placeholder="Search documents…"
|
||||
placeholder={t("search-documents")}
|
||||
onPaste={handleSearchPaste}
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-64 text-xs"
|
||||
/>
|
||||
</div>
|
||||
@@ -668,7 +675,7 @@ function Opensigndrive() {
|
||||
onClick={() => setMobileSearchOpen((open) => !open)}
|
||||
>
|
||||
<i
|
||||
style={{ color: `${iconColor}` }}
|
||||
style={{ color: `${getThemeIconColor()}` }}
|
||||
className="fa-solid fa-magnifying-glass"
|
||||
></i>
|
||||
</button>
|
||||
@@ -681,7 +688,7 @@ function Opensigndrive() {
|
||||
<i
|
||||
className="fa-light fa-plus-square text-[24px]"
|
||||
aria-hidden="true"
|
||||
style={{ color: `${iconColor}` }}
|
||||
style={{ color: `${getThemeIconColor()}` }}
|
||||
></i>
|
||||
</div>
|
||||
<div
|
||||
@@ -705,14 +712,14 @@ function Opensigndrive() {
|
||||
onClick={() => navigate("/form/sHAnZphf69")}
|
||||
>
|
||||
<i className="fa-light fa-pen-nib mr-[5px]"></i>
|
||||
{t("form-name.Sign Yourself")}
|
||||
{t("Sign Yourself")}
|
||||
</span>
|
||||
<span
|
||||
className="dropdown-item text-[10px] md:text-[13px]"
|
||||
onClick={() => navigate("/form/8mZzFxbG1z")}
|
||||
>
|
||||
<i className="fa-light fa-file-signature mr-[5px]"></i>
|
||||
{t("form-name.Request Signatures")}
|
||||
{t("Request Signatures")}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -730,9 +737,9 @@ function Opensigndrive() {
|
||||
<i
|
||||
className="fa-light fa-sort-amount-asc mr-[5px] text-[19px]"
|
||||
aria-hidden="true"
|
||||
style={{ color: `${iconColor}` }}
|
||||
style={{ color: `${getThemeIconColor()}` }}
|
||||
></i>
|
||||
<span style={{ fontSize: "15px", color: `${iconColor}` }}>
|
||||
<span style={{ fontSize: "15px", color: `${getThemeIconColor()}` }}>
|
||||
{selectedSort}
|
||||
</span>
|
||||
</div>
|
||||
@@ -793,7 +800,7 @@ function Opensigndrive() {
|
||||
>
|
||||
<i
|
||||
className={`${isList ? "fa-light fa-th-large" : "fa-light fa-list"} text-[20px]`}
|
||||
style={{ color: `${iconColor}` }}
|
||||
style={{ color: `${getThemeIconColor()}` }}
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</div>
|
||||
@@ -809,7 +816,7 @@ function Opensigndrive() {
|
||||
<i
|
||||
className="fa-light fa-ellipsis-vertical fa-lg"
|
||||
aria-hidden="true"
|
||||
style={{ color: `${iconColor}` }}
|
||||
style={{ color: `${getThemeIconColor()}` }}
|
||||
></i>
|
||||
</div>
|
||||
<div
|
||||
@@ -833,14 +840,14 @@ function Opensigndrive() {
|
||||
onClick={() => navigate("/form/sHAnZphf69")}
|
||||
>
|
||||
<i className="fa-light fa-pen-nib mr-[5px]"></i>
|
||||
{t("form-name.Sign Yourself")}
|
||||
{t("Sign Yourself")}
|
||||
</span>
|
||||
<span
|
||||
className="dropdown-item text-[10px] md:text-[13px]"
|
||||
onClick={() => navigate("/form/8mZzFxbG1z")}
|
||||
>
|
||||
<i className="fa-light fa-file-signature mr-[5px]"></i>
|
||||
{t("form-name.Request Signatures")}
|
||||
{t("Request Signatures")}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -854,7 +861,8 @@ function Opensigndrive() {
|
||||
type="search"
|
||||
value={searchTerm}
|
||||
onChange={handleSearchChange}
|
||||
placeholder="Search documents…"
|
||||
placeholder={t("search-documents")}
|
||||
onPaste={handleSearchPaste}
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -23,7 +23,6 @@ import {
|
||||
pdfNewWidthFun,
|
||||
signPdfFun,
|
||||
addDefaultSignatureImg,
|
||||
radioButtonWidget,
|
||||
replaceMailVaribles,
|
||||
convertPdfArrayBuffer,
|
||||
contractUsers,
|
||||
@@ -47,7 +46,8 @@ import {
|
||||
mailTemplate,
|
||||
updateDateWidgetsRes,
|
||||
widgetDataValue,
|
||||
getOriginalWH
|
||||
getOriginalWH,
|
||||
handleCheckResponse,
|
||||
} from "../constant/Utils";
|
||||
import Header from "../components/pdf/PdfHeader";
|
||||
import RenderPdf from "../components/pdf/RenderPdf";
|
||||
@@ -75,9 +75,6 @@ function PdfRequestFiles(
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
const isShowModal = useSelector((state) => state.widget.isShowModal);
|
||||
const saveSignCheckbox = useSelector(
|
||||
(state) => state.widget.saveSignCheckbox
|
||||
);
|
||||
const defaultSignImg = useSelector((state) => state.widget.defaultSignImg);
|
||||
const myInitial = useSelector((state) => state.widget.myInitial);
|
||||
const appName =
|
||||
@@ -144,7 +141,6 @@ function PdfRequestFiles(
|
||||
const [signatureType, setSignatureType] = useState([]);
|
||||
const [pdfBase64Url, setPdfBase64Url] = useState("");
|
||||
const [isAgree, setIsAgree] = useState(false);
|
||||
const [isAgreeTour, setIsAgreeTour] = useState(false);
|
||||
const [redirectTimeLeft, setRedirectTimeLeft] = useState(5);
|
||||
const [isredirectCanceled, setIsredirectCanceled] = useState(true);
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
@@ -277,6 +273,15 @@ function PdfRequestFiles(
|
||||
let currUserId;
|
||||
//getting document details
|
||||
const documentData = await contractDocument(docId);
|
||||
// Filter out 'prefill' roles from the Placeholder array
|
||||
const filteredPlaceholder = documentData[0].Placeholders.filter(
|
||||
(data) => data.Role !== "prefill"
|
||||
);
|
||||
// Reassign the updated Placeholder back to the documentData array
|
||||
documentData[0] = {
|
||||
...documentData[0],
|
||||
Placeholders: filteredPlaceholder
|
||||
};
|
||||
if (documentData && documentData.length > 0) {
|
||||
const userSignatureType =
|
||||
documentData[0]?.ExtUserPtr?.SignatureType || signatureTypes;
|
||||
@@ -476,12 +481,7 @@ function PdfRequestFiles(
|
||||
} else {
|
||||
setRequestSignTour(false);
|
||||
}
|
||||
dispatch(
|
||||
setSaveSignCheckbox({
|
||||
...saveSignCheckbox,
|
||||
isVisible: true
|
||||
})
|
||||
);
|
||||
|
||||
//function to get default signatur of current user from `contracts_Signature` class
|
||||
const defaultSignRes = await getDefaultSignature(
|
||||
jsonSender?.objectId
|
||||
@@ -489,7 +489,6 @@ function PdfRequestFiles(
|
||||
if (defaultSignRes?.status === "success") {
|
||||
dispatch(
|
||||
setSaveSignCheckbox({
|
||||
...saveSignCheckbox,
|
||||
isVisible: true,
|
||||
signId: defaultSignRes?.res?.id
|
||||
})
|
||||
@@ -498,6 +497,8 @@ function PdfRequestFiles(
|
||||
const initials = defaultSignRes?.res?.defaultInitial || "";
|
||||
dispatch(setDefaultSignImg(sign));
|
||||
dispatch(setMyInitial(initials));
|
||||
} else {
|
||||
dispatch(setSaveSignCheckbox({ isVisible: true }));
|
||||
}
|
||||
} else if (res?.length === 0) {
|
||||
const res = await contactBook(currUserId);
|
||||
@@ -624,6 +625,7 @@ function PdfRequestFiles(
|
||||
} catch (err) {
|
||||
console.log("err in get email verification ", err);
|
||||
setHandleError(t("something-went-wrong-mssg"));
|
||||
setIsUiLoading(false);
|
||||
}
|
||||
}
|
||||
//check if isEmailVerified then go on next step
|
||||
@@ -633,153 +635,13 @@ function PdfRequestFiles(
|
||||
(data) => data.signerObjId === signerObjectId
|
||||
);
|
||||
if (checkUser && checkUser.length > 0) {
|
||||
let checkboxExist,
|
||||
requiredRadio,
|
||||
showAlert = false,
|
||||
widgetKey,
|
||||
radioExist,
|
||||
requiredCheckbox,
|
||||
TourPageNumber; // `pageNumber` is used to check on which page user did not fill widget's data then change current pageNumber and show tour message on that page
|
||||
|
||||
for (let i = 0; i < checkUser[0].placeHolder.length; i++) {
|
||||
for (let j = 0; j < checkUser[0].placeHolder[i].pos.length; j++) {
|
||||
//get current page
|
||||
const updatePage = checkUser[0].placeHolder[i]?.pageNumber;
|
||||
//checking checbox type widget
|
||||
checkboxExist =
|
||||
checkUser[0].placeHolder[i].pos[j].type === "checkbox";
|
||||
//checking radio button type widget
|
||||
radioExist =
|
||||
checkUser[0].placeHolder[i].pos[j].type === radioButtonWidget;
|
||||
//condition to check checkbox widget exist or not
|
||||
if (checkboxExist) {
|
||||
//get all required type checkbox
|
||||
requiredCheckbox = checkUser[0].placeHolder[i].pos.filter(
|
||||
(position) =>
|
||||
!position.options?.isReadOnly &&
|
||||
position.type === "checkbox"
|
||||
);
|
||||
//if required type checkbox data exit then check user checked all checkbox or some checkbox remain to check
|
||||
//also validate to minimum and maximum required checkbox
|
||||
if (requiredCheckbox && requiredCheckbox.length > 0) {
|
||||
for (let i = 0; i < requiredCheckbox.length; i++) {
|
||||
//get minimum required count if exit
|
||||
const minCount =
|
||||
requiredCheckbox[i].options?.validation?.minRequiredCount;
|
||||
const parseMin = minCount && parseInt(minCount);
|
||||
//get maximum required count if exit
|
||||
const maxCount =
|
||||
requiredCheckbox[i].options?.validation?.maxRequiredCount;
|
||||
const parseMax = maxCount && parseInt(maxCount);
|
||||
//in `response` variable is used to get how many checkbox checked by user
|
||||
const response =
|
||||
requiredCheckbox[i].options?.response?.length;
|
||||
//in `defaultValue` variable is used to get how many checkbox checked by default
|
||||
const defaultValue =
|
||||
requiredCheckbox[i].options?.defaultValue?.length;
|
||||
//condition to check parseMin and parseMax greater than 0 then consider it as a required check box
|
||||
if (
|
||||
parseMin > 0 &&
|
||||
parseMax > 0 &&
|
||||
!response &&
|
||||
!defaultValue &&
|
||||
!showAlert
|
||||
) {
|
||||
showAlert = true;
|
||||
widgetKey = requiredCheckbox[i].key;
|
||||
TourPageNumber = updatePage;
|
||||
setminRequiredCount(parseMin);
|
||||
}
|
||||
//else condition to validate minimum required checkbox
|
||||
else if (
|
||||
parseMin > 0 &&
|
||||
(parseMin > response || !response)
|
||||
) {
|
||||
if (!showAlert) {
|
||||
showAlert = true;
|
||||
widgetKey = requiredCheckbox[i].key;
|
||||
TourPageNumber = updatePage;
|
||||
setminRequiredCount(parseMin);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//condition to check radio widget exist or not
|
||||
else if (radioExist) {
|
||||
//get all required type radio button
|
||||
requiredRadio = checkUser[0].placeHolder[i].pos.filter(
|
||||
(position) =>
|
||||
!position.options?.isReadOnly &&
|
||||
position.type === radioButtonWidget
|
||||
);
|
||||
//if required type radio data exit then check user checked all radio button or some radio remain to check
|
||||
if (requiredRadio && requiredRadio?.length > 0) {
|
||||
let checkSigned;
|
||||
for (let i = 0; i < requiredRadio?.length; i++) {
|
||||
checkSigned = requiredRadio[i]?.options?.response;
|
||||
if (!checkSigned) {
|
||||
let checkDefaultSigned =
|
||||
requiredRadio[i]?.options?.defaultValue;
|
||||
if (!checkDefaultSigned && !showAlert) {
|
||||
showAlert = true;
|
||||
widgetKey = requiredRadio[i].key;
|
||||
TourPageNumber = updatePage;
|
||||
setminRequiredCount(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//else condition to check all type widget data fill or not except checkbox and radio button
|
||||
else {
|
||||
//get all required type widgets except checkbox and radio
|
||||
const requiredWidgets = checkUser[0].placeHolder[i].pos.filter(
|
||||
(position) =>
|
||||
position.options?.status === "required" &&
|
||||
position.type !== radioButtonWidget &&
|
||||
position.type !== "checkbox"
|
||||
);
|
||||
if (requiredWidgets && requiredWidgets?.length > 0) {
|
||||
let checkSigned;
|
||||
for (let i = 0; i < requiredWidgets?.length; i++) {
|
||||
checkSigned = requiredWidgets[i]?.options?.response;
|
||||
if (!checkSigned) {
|
||||
const checkSignUrl = requiredWidgets[i]?.pos?.SignUrl;
|
||||
if (!checkSignUrl) {
|
||||
let checkDefaultSigned =
|
||||
requiredWidgets[i]?.options?.defaultValue;
|
||||
if (!checkDefaultSigned && !showAlert) {
|
||||
showAlert = true;
|
||||
widgetKey = requiredWidgets[i].key;
|
||||
TourPageNumber = updatePage;
|
||||
setminRequiredCount(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//when showAlert is true then break the loop and show alert to fill required data in widgets
|
||||
if (showAlert) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (checkboxExist && requiredCheckbox && showAlert) {
|
||||
setUnSignedWidgetId(widgetKey);
|
||||
setPageNumber(TourPageNumber);
|
||||
setWidgetsTour(true);
|
||||
} else if (radioExist && showAlert) {
|
||||
setUnSignedWidgetId(widgetKey);
|
||||
setPageNumber(TourPageNumber);
|
||||
setWidgetsTour(true);
|
||||
} else if (showAlert) {
|
||||
setUnSignedWidgetId(widgetKey);
|
||||
setPageNumber(TourPageNumber);
|
||||
const status = handleCheckResponse(checkUser, setminRequiredCount);
|
||||
if (status?.showAlert) {
|
||||
setUnSignedWidgetId(status?.widgetKey);
|
||||
setPageNumber(status?.tourPageNumber);
|
||||
setWidgetsTour(true);
|
||||
setIsUiLoading(false);
|
||||
} else {
|
||||
setIsUiLoading(true);
|
||||
// `widgets` is Used to return widgets details with page number of current user
|
||||
const widgets = checkUser?.[0]?.placeHolder;
|
||||
let pdfArrBuffer;
|
||||
@@ -821,15 +683,13 @@ function PdfRequestFiles(
|
||||
await embedDocId(pdfOriginalWH, pdfDoc, docId);
|
||||
}
|
||||
}
|
||||
//embed all widgets in document
|
||||
//embed all widgets in document
|
||||
const pdfBytes = await multiSignEmbed(
|
||||
pdfOriginalWH,
|
||||
widgets,
|
||||
pdfDoc,
|
||||
isSignYourSelfFlow,
|
||||
scale
|
||||
);
|
||||
// console.log("pdfte", pdfBytes);
|
||||
//get ExistUserPtr object id of user class to get tenantDetails
|
||||
if (!pdfBytes?.error) {
|
||||
const objectId = pdfDetails?.[0]?.ExtUserPtr?.UserId?.objectId;
|
||||
@@ -855,17 +715,23 @@ function PdfRequestFiles(
|
||||
isSuccessRoute,
|
||||
contactId
|
||||
);
|
||||
const index = pdfDetails?.[0]?.Signers.findIndex(
|
||||
(x) => x.objectId === signerObjectId
|
||||
);
|
||||
const index =
|
||||
updatedDoc.updatedPdfDetails?.[0]?.Signers.findIndex(
|
||||
(x) => x.objectId === contactId
|
||||
);
|
||||
const newIndex = index + 1;
|
||||
const usermail = {
|
||||
Email: pdfDetails?.[0]?.Placeholders[newIndex]?.email || ""
|
||||
Email:
|
||||
updatedDoc.updatedPdfDetails?.[0]?.Placeholders[newIndex]
|
||||
?.email || ""
|
||||
};
|
||||
const user = usermail?.Email
|
||||
? usermail
|
||||
: pdfDetails?.[0]?.Signers[newIndex];
|
||||
if (sendmail !== "false" && sendInOrder) {
|
||||
: updatedDoc.updatedPdfDetails?.[0]?.Signers[newIndex];
|
||||
if (
|
||||
sendmail !== "false" &&
|
||||
sendInOrder
|
||||
) {
|
||||
const requestBody =
|
||||
updatedDoc.updatedPdfDetails?.[0]?.RequestBody;
|
||||
const requestSubject =
|
||||
@@ -1020,6 +886,7 @@ function PdfRequestFiles(
|
||||
isShow: true,
|
||||
alertMessage: t("something-went-wrong-mssg")
|
||||
});
|
||||
setIsUiLoading(false);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("err in embedsign", err);
|
||||
@@ -1031,8 +898,8 @@ function PdfRequestFiles(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleSignPdf = async () => {
|
||||
setIsUiLoading(true);
|
||||
await embedWidgetsData();
|
||||
};
|
||||
|
||||
@@ -1056,15 +923,15 @@ function PdfRequestFiles(
|
||||
let filterSignerPos = [];
|
||||
if (signerObjId) {
|
||||
//get current signerObjId placeholder details
|
||||
filterSignerPos = updateSignPos.filter(
|
||||
filterSignerPos = updateSignPos?.filter(
|
||||
(data) => data.Id === signerObjId
|
||||
);
|
||||
}
|
||||
|
||||
if (filterSignerPos.length > 0) {
|
||||
const getPlaceHolder = filterSignerPos[0].placeHolder;
|
||||
const getPlaceHolder = filterSignerPos[0]?.placeHolder;
|
||||
//get position of current pagenumber
|
||||
const getPageNumer = getPlaceHolder.filter(
|
||||
const getPageNumer = getPlaceHolder?.filter(
|
||||
(data) => data.pageNumber === pageNumber
|
||||
);
|
||||
if (getPageNumer.length > 0) {
|
||||
@@ -1108,9 +975,9 @@ function PdfRequestFiles(
|
||||
setIsTextSetting(value);
|
||||
};
|
||||
const handleSaveFontSize = () => {
|
||||
const filterSignerPos = signerPos.filter((data) => data.Id === uniqueId);
|
||||
const filterSignerPos = signerPos?.filter((data) => data.Id === uniqueId);
|
||||
if (filterSignerPos) {
|
||||
const placehoder = filterSignerPos[0].placeHolder;
|
||||
const placehoder = filterSignerPos[0]?.placeHolder;
|
||||
const getPageNumer = placehoder.filter(
|
||||
(data) => data.pageNumber === pageNumber
|
||||
);
|
||||
@@ -1226,7 +1093,7 @@ function PdfRequestFiles(
|
||||
const addDefaultSignature = () => {
|
||||
const type = defaultSignAlert?.type;
|
||||
//get current signers placeholder position data
|
||||
const currentSignerPosition = signerPos.filter(
|
||||
const currentSignerPosition = signerPos?.filter(
|
||||
(data) => data.signerObjId === signerObjectId
|
||||
);
|
||||
const defaultSign = type === "signature" ? defaultSignImg : myInitial;
|
||||
@@ -1251,7 +1118,8 @@ function PdfRequestFiles(
|
||||
setRequestSignTour(true);
|
||||
if (isDontShow) {
|
||||
const isEnableOTP = pdfDetails?.[0]?.IsEnableOTP || false;
|
||||
if (!isEnableOTP) {
|
||||
const sessionToken = localStorage.getItem("accesstoken");
|
||||
if (!isEnableOTP && !sessionToken) {
|
||||
try {
|
||||
await axios.post(
|
||||
`${localStorage.getItem("baseUrl")}functions/updatecontacttour`,
|
||||
@@ -1503,17 +1371,6 @@ function PdfRequestFiles(
|
||||
alert(t("expiry-date-error"));
|
||||
}
|
||||
};
|
||||
const AgreementTour = [
|
||||
{
|
||||
selector: '[data-tut="IsAgree"]',
|
||||
content: () => <p className="p-0">{t("agrrement-alert")}</p>,
|
||||
position: "top",
|
||||
style: { fontSize: "13px" }
|
||||
}
|
||||
];
|
||||
const handleCloseAgreeTour = () => {
|
||||
setIsAgreeTour(false);
|
||||
};
|
||||
// `handleRedirectCancel` is used to cancel redirecting to redirectUrl
|
||||
const handleRedirectCancel = () => {
|
||||
setIsredirectCanceled(true);
|
||||
@@ -1564,11 +1421,15 @@ function PdfRequestFiles(
|
||||
const widgetValue = widgetDataValue(dragTypeValue, parseUser);
|
||||
//adding and updating drop position in array when user drop signature button in div
|
||||
if (item === "onclick") {
|
||||
// `getBoundingClientRect()` is used to get accurate measurement width, height of the Pdf div
|
||||
const divWidth = divRef.current.getBoundingClientRect().width;
|
||||
const divHeight = divRef.current.getBoundingClientRect().height;
|
||||
// `getBoundingClientRect()` is used to get accurate measurement height of the div
|
||||
// Compute the pixel‐space center within the PDF viewport:
|
||||
const centerX_Pixels = divWidth / 2 - widgetWidth / 2;
|
||||
const xPosition_Final = centerX_Pixels / (containerScale * scale);
|
||||
dropObj = {
|
||||
//onclick put placeholder center on pdf
|
||||
xPosition: widgetWidth / 4 + containerWH.width / 2,
|
||||
xPosition: xPosition_Final,
|
||||
yPosition: widgetHeight + divHeight / 2,
|
||||
isStamp:
|
||||
(dragTypeValue === "stamp" || dragTypeValue === "image") && true,
|
||||
@@ -1612,11 +1473,11 @@ function PdfRequestFiles(
|
||||
}
|
||||
if (uniqueId) {
|
||||
let filterSignerPos, currentPagePosition;
|
||||
filterSignerPos = signerPos.find((data) => data.Id === uniqueId);
|
||||
filterSignerPos = signerPos?.find((data) => data.Id === uniqueId);
|
||||
const getPlaceHolder = filterSignerPos?.placeHolder;
|
||||
if (getPlaceHolder) {
|
||||
//checking exist placeholder on same page
|
||||
currentPagePosition = getPlaceHolder.find(
|
||||
currentPagePosition = getPlaceHolder?.find(
|
||||
(data) => data.pageNumber === pageNumber
|
||||
);
|
||||
}
|
||||
@@ -1644,12 +1505,6 @@ function PdfRequestFiles(
|
||||
);
|
||||
setSignerPos(updatesignerPos);
|
||||
}
|
||||
|
||||
// if (dragTypeValue === "dropdown") {
|
||||
// setShowDropdown(true);
|
||||
// } else if (dragTypeValue === "checkbox") {
|
||||
// setIsCheckbox(true);
|
||||
// } else
|
||||
if (
|
||||
[textWidget, "name", "company", "job title", "email"].includes(
|
||||
dragTypeValue
|
||||
@@ -1665,7 +1520,7 @@ function PdfRequestFiles(
|
||||
//function for delete signature block
|
||||
const handleDeleteSign = (key, Id) => {
|
||||
const updateData = [];
|
||||
const filterSignerPos = signerPos.filter((data) => data.Id === Id);
|
||||
const filterSignerPos = signerPos?.filter((data) => data.Id === Id);
|
||||
if (filterSignerPos.length > 0) {
|
||||
const getPlaceHolder = filterSignerPos[0].placeHolder;
|
||||
const getPageNumer = getPlaceHolder.filter(
|
||||
@@ -1693,7 +1548,7 @@ function PdfRequestFiles(
|
||||
});
|
||||
setSignerPos(newUpdateSigner);
|
||||
} else {
|
||||
const getRemainPage = filterSignerPos[0].placeHolder.filter(
|
||||
const getRemainPage = filterSignerPos[0]?.placeHolder?.filter(
|
||||
(data) => data.pageNumber !== pageNumber
|
||||
);
|
||||
//condition to check placeholder length is greater than 1 do not need to remove whole placeholder
|
||||
@@ -1706,11 +1561,11 @@ function PdfRequestFiles(
|
||||
return obj;
|
||||
});
|
||||
let signerupdate = [];
|
||||
signerupdate = signerPos.filter((data) => data.Id !== Id);
|
||||
signerupdate = signerPos?.filter((data) => data.Id !== Id);
|
||||
signerupdate.push(newUpdatePos[0]);
|
||||
setSignerPos(signerupdate);
|
||||
} else {
|
||||
const updatedData = signerPos.map((item) => {
|
||||
const updatedData = signerPos?.map((item) => {
|
||||
if (item.Id === Id) {
|
||||
// Create a copy of the item object and delete the placeHolder field
|
||||
const updatedItem = { ...item };
|
||||
@@ -1725,10 +1580,10 @@ function PdfRequestFiles(
|
||||
}
|
||||
}
|
||||
};
|
||||
//function to get first widget and page number to assign currect signer and tour message
|
||||
//function to get first widget id and page number to assign correct signer and show tour message
|
||||
const showFirstWidget = () => {
|
||||
if (!requestSignTour) {
|
||||
const getCurrentUserPlaceholder = signerPos.find(
|
||||
const getCurrentUserPlaceholder = signerPos?.find(
|
||||
(x) => x.Id === uniqueId
|
||||
);
|
||||
const placeholder = getCurrentUserPlaceholder.placeHolder;
|
||||
@@ -1749,7 +1604,6 @@ function PdfRequestFiles(
|
||||
setShowSignPagenumber(sortedPagenumber);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<DndProvider backend={HTML5Backend}>
|
||||
<Title
|
||||
@@ -1771,21 +1625,9 @@ function PdfRequestFiles(
|
||||
!isDecline?.isDeclined && (
|
||||
<AgreementSign
|
||||
setIsAgree={setIsAgree}
|
||||
setIsAgreeTour={setIsAgreeTour}
|
||||
showFirstWidget={showFirstWidget}
|
||||
/>
|
||||
)}
|
||||
<Tour
|
||||
showNumber={false}
|
||||
showNavigation={false}
|
||||
showNavigationNumber={false}
|
||||
onRequestClose={handleCloseAgreeTour}
|
||||
steps={AgreementTour}
|
||||
isOpen={isAgreeTour}
|
||||
rounded={5}
|
||||
closeWithMask={false}
|
||||
/>
|
||||
|
||||
{isUiLoading && (
|
||||
<div className="absolute h-[100vh] w-full flex flex-col justify-center items-center z-[999] bg-[#e6f2f2] bg-opacity-80">
|
||||
<Loader />
|
||||
@@ -1821,6 +1663,7 @@ function PdfRequestFiles(
|
||||
{!requestSignTour &&
|
||||
isAgree &&
|
||||
signerObjectId &&
|
||||
!alreadySign &&
|
||||
requestSignTourFunction()}
|
||||
<Tour
|
||||
showNumber={false}
|
||||
@@ -2137,8 +1980,6 @@ function PdfRequestFiles(
|
||||
scale={scale}
|
||||
uniqueId={uniqueId}
|
||||
pdfBase64Url={pdfBase64Url}
|
||||
setIsAgreeTour={setIsAgreeTour}
|
||||
isAgree={isAgree}
|
||||
handleTabDrag={handleTabDrag}
|
||||
handleStop={handleStop}
|
||||
isDragging={isDragging}
|
||||
@@ -2221,8 +2062,6 @@ function PdfRequestFiles(
|
||||
signatureType?.find((x) => x.name === "default")
|
||||
?.enabled || false
|
||||
}
|
||||
isAgree={isAgree}
|
||||
setIsAgreeTour={setIsAgreeTour}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -2259,6 +2098,8 @@ function PdfRequestFiles(
|
||||
index={pageNumber}
|
||||
setUniqueId={setUniqueId}
|
||||
tempSignerId={tempSignerId}
|
||||
signatureTypes={signatureType}
|
||||
allowCellResize={pdfDetails[0]?.AllowModifications ?? false}
|
||||
/>
|
||||
)}
|
||||
<DownloadPdfZip
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
multiSignEmbed,
|
||||
addWidgetOptions,
|
||||
textInputWidget,
|
||||
cellsWidget,
|
||||
textWidget,
|
||||
radioButtonWidget,
|
||||
color,
|
||||
@@ -193,7 +194,7 @@ function PlaceHolderSign() {
|
||||
);
|
||||
if (user) {
|
||||
try {
|
||||
const defaultRequestBody = `<p>Hi {{receiver_name}},</p><br><p>We hope this email finds you well. {{sender_name}} has requested you to review and sign {{document_title}}.</p><p>Your signature is crucial to proceed with the next steps as it signifies your agreement and authorization.</p><br><p>{{signing_url}}</p><br><p>If you have any questions or need further clarification regarding the document or the signing process, please contact the sender.</p><br><p>Thanks</p><p> Team ${appName}</p><br>`;
|
||||
const defaultRequestBody = `<p>Hi {{receiver_name}},</p><br><p>We hope this email finds you well. {{sender_name}} has requested you to review and sign {{document_title}}.</p><p>Your signature is crucial to proceed with the next steps as it signifies your agreement and authorization.</p><br><p><a href='{{signing_url}}' rel='noopener noreferrer' target='_blank'>Sign here</a></p><br><br><p>If you have any questions or need further clarification regarding the document or the signing process, please contact the sender.</p><br><p>Thanks</p><p> Team ${appName}</p><br>`;
|
||||
const defaultSubject = `{{sender_name}} has requested you to sign {{document_title}}`;
|
||||
setDefaultBody(defaultRequestBody);
|
||||
setDefaultSubject(defaultSubject);
|
||||
@@ -524,11 +525,15 @@ function PlaceHolderSign() {
|
||||
defaultWidthHeight(dragTypeValue).height * containerScale;
|
||||
//adding and updating drop position in array when user drop signature button in div
|
||||
if (item === "onclick") {
|
||||
// `getBoundingClientRect()` is used to get accurate measurement width, height of the Pdf div
|
||||
const divWidth = divRef.current.getBoundingClientRect().width;
|
||||
const divHeight = divRef.current.getBoundingClientRect().height;
|
||||
// `getBoundingClientRect()` is used to get accurate measurement height of the div
|
||||
// Compute the pixel‐space center within the PDF viewport:
|
||||
const centerX_Pixels = divWidth / 2 - widgetWidth / 2;
|
||||
const xPosition_Final = centerX_Pixels / (containerScale * scale);
|
||||
dropObj = {
|
||||
//onclick put placeholder center on pdf
|
||||
xPosition: widgetWidth / 4 + containerWH.width / 2,
|
||||
xPosition: xPosition_Final,
|
||||
yPosition: widgetHeight + divHeight / 2,
|
||||
isStamp:
|
||||
(dragTypeValue === "stamp" || dragTypeValue === "image") && true,
|
||||
@@ -858,7 +863,6 @@ function PlaceHolderSign() {
|
||||
try {
|
||||
//pdfOriginalWH contained all pdf's pages width,height & pagenumber in array format
|
||||
const pdfBase64 = await multiSignEmbed(
|
||||
pdfOriginalWH,
|
||||
placeholder,
|
||||
pdfDoc,
|
||||
isSignYourSelfFlow,
|
||||
@@ -1413,7 +1417,8 @@ function PlaceHolderSign() {
|
||||
deleteOption,
|
||||
status,
|
||||
defaultValue,
|
||||
isHideLabel
|
||||
isHideLabel,
|
||||
layout
|
||||
) => {
|
||||
const filterSignerPos = signerPos.filter((data) => data.Id === uniqueId);
|
||||
if (filterSignerPos.length > 0) {
|
||||
@@ -1448,6 +1453,8 @@ function PlaceHolderSign() {
|
||||
...position.options,
|
||||
name: dropdownName,
|
||||
values: dropdownOptions,
|
||||
status: status,
|
||||
layout: layout,
|
||||
isReadOnly: isReadOnly || false,
|
||||
isHideLabel: isHideLabel || false,
|
||||
defaultValue: defaultValue,
|
||||
@@ -1487,6 +1494,7 @@ function PlaceHolderSign() {
|
||||
maxRequiredCount: maxCount
|
||||
},
|
||||
defaultValue: defaultValue,
|
||||
layout: layout,
|
||||
isReadOnly: isReadOnly || false,
|
||||
isHideLabel: isHideLabel || false,
|
||||
fontSize:
|
||||
@@ -1585,6 +1593,30 @@ function PlaceHolderSign() {
|
||||
isReadOnly: defaultdata?.isReadOnly || false
|
||||
}
|
||||
};
|
||||
} else if (position.type === cellsWidget) {
|
||||
return {
|
||||
...position,
|
||||
options: {
|
||||
...position.options,
|
||||
name: defaultdata?.name || "Cells",
|
||||
status: defaultdata?.status || "required",
|
||||
hint: defaultdata?.hint || "",
|
||||
cellCount: parseInt(defaultdata?.cellCount || 5),
|
||||
defaultValue: (defaultdata?.defaultValue || "").slice(
|
||||
0,
|
||||
parseInt(defaultdata?.cellCount || 5)
|
||||
),
|
||||
validation:
|
||||
{},
|
||||
fontSize:
|
||||
fontSize || currWidgetsDetails?.options?.fontSize || 12,
|
||||
fontColor:
|
||||
fontColor ||
|
||||
currWidgetsDetails?.options?.fontColor ||
|
||||
"black",
|
||||
isReadOnly: defaultdata?.isReadOnly || false
|
||||
}
|
||||
};
|
||||
} else if (["signature"].includes(position.type)) {
|
||||
return {
|
||||
...position,
|
||||
@@ -1712,14 +1744,14 @@ function PlaceHolderSign() {
|
||||
objectId: data.objectId
|
||||
};
|
||||
const updatePlaceHolder = signerPos.map((x) => {
|
||||
if (x.Id === id) {
|
||||
if (x.Id === id || x.signerObjId === id) {
|
||||
return { ...x, signerPtr: signerPtr, signerObjId: data.objectId };
|
||||
}
|
||||
return { ...x };
|
||||
});
|
||||
setSignerPos(updatePlaceHolder);
|
||||
const updateSigner = signersdata.map((x) => {
|
||||
if (x.Id === id) {
|
||||
if (x.Id === id || x.objectId === id) {
|
||||
return { ...x, ...data, className: "contracts_Contactbook" };
|
||||
}
|
||||
return { ...x };
|
||||
@@ -1734,7 +1766,9 @@ function PlaceHolderSign() {
|
||||
}
|
||||
}
|
||||
setSignersData(updateSigner);
|
||||
const index = signersdata.findIndex((x) => x.Id === id);
|
||||
const index = signersdata.findIndex(
|
||||
(x) => x.Id === id || x.objectId === id
|
||||
);
|
||||
setIsSelectId(index);
|
||||
}
|
||||
if (isNewContact.status) {
|
||||
@@ -1898,7 +1932,9 @@ function PlaceHolderSign() {
|
||||
};
|
||||
//`handleInputChange` function to get signers list from dropdown
|
||||
const handleInputChange = (item, id) => {
|
||||
const signerExist = forms.some((x) => x.value === item.value);
|
||||
const signerExist = signersdata?.some(
|
||||
(x) => x.objectId && x.objectId === item.value
|
||||
);
|
||||
if (signerExist) {
|
||||
alert(t("already-exist-signer"));
|
||||
} else {
|
||||
@@ -2145,7 +2181,7 @@ function PlaceHolderSign() {
|
||||
navigate("/report/1MwEuxLEkF");
|
||||
}}
|
||||
>
|
||||
<div className="h-[100%] p-[20px]">
|
||||
<div className="h-[100%] p-[20px] text-base-content">
|
||||
{mailStatus === "success" ? (
|
||||
<div className="text-center mb-[10px]">
|
||||
<LottieWithLoader />
|
||||
@@ -2589,6 +2625,7 @@ function PlaceHolderSign() {
|
||||
isSave={true}
|
||||
tempSignerId={tempSignerId}
|
||||
setUniqueId={setUniqueId}
|
||||
signatureTypes={signatureType}
|
||||
/>
|
||||
)}
|
||||
<ModalUi
|
||||
@@ -2596,7 +2633,7 @@ function PlaceHolderSign() {
|
||||
title={t("document-alert")}
|
||||
showClose={false}
|
||||
>
|
||||
<div className="h-[100%] p-[20px]">
|
||||
<div className="h-[100%] p-[20px] text-base-content">
|
||||
<p>{isAlreadyPlace.message}</p>
|
||||
<div className="h-[1px] w-full my-[15px] bg-[#9f9f9f]"></div>
|
||||
<button
|
||||
|
||||
@@ -56,6 +56,10 @@ const Preferences = () => {
|
||||
const [dateFormat, setDateFormat] = useState("MM/DD/YYYY");
|
||||
const [is12HourTime, setIs12HourTime] = useState(false);
|
||||
const [isLTVEnabled, setIsLTVEnabled] = useState(false);
|
||||
const [isDefaultMail, setIsDefaultMail] = useState({
|
||||
requestMail: false,
|
||||
completionMail: false
|
||||
});
|
||||
useEffect(() => {
|
||||
fetchSignType();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@@ -213,12 +217,12 @@ const Preferences = () => {
|
||||
};
|
||||
const tenantEmailTemplate = async (tenantRes) => {
|
||||
if (tenantRes === "user does not exist!") {
|
||||
alert("User does not exist");
|
||||
alert(t("user-not-exist"));
|
||||
} else if (tenantRes) {
|
||||
setIsLoader(true);
|
||||
const updateRes = tenantRes;
|
||||
setTenantId(updateRes?.objectId);
|
||||
const defaultRequestBody = `<p>Hi {{receiver_name}},</p><br><p>We hope this email finds you well. {{sender_name}} has requested you to review and sign {{document_title}}.</p><p>Your signature is crucial to proceed with the next steps as it signifies your agreement and authorization.</p><br><p>{{signing_url}}</p><br><p>If you have any questions or need further clarification regarding the document or the signing process, please contact the sender.</p><br><p>Thanks</p><p> Team ${appName}</p><br>`;
|
||||
const defaultRequestBody = `<p>Hi {{receiver_name}},</p><br><p>We hope this email finds you well. {{sender_name}} has requested you to review and sign {{document_title}}.</p><p>Your signature is crucial to proceed with the next steps as it signifies your agreement and authorization.</p><br><p><a href='{{signing_url}}' rel='noopener noreferrer' target='_blank'>Sign here</a></p><br><br><p>If you have any questions or need further clarification regarding the document or the signing process, please contact the sender.</p><br><p>Thanks</p><p> Team ${appName}</p><br>`;
|
||||
if (updateRes?.RequestBody) {
|
||||
setRequestBody(updateRes?.RequestBody);
|
||||
setRequestSubject(updateRes?.RequestSubject);
|
||||
@@ -227,6 +231,7 @@ const Preferences = () => {
|
||||
setRequestSubject(
|
||||
`{{sender_name}} has requested you to sign {{document_title}}`
|
||||
);
|
||||
setIsDefaultMail((prev) => ({ ...prev, requestMail: true }));
|
||||
}
|
||||
setDefaultReqHtml({
|
||||
body: defaultRequestBody,
|
||||
@@ -241,6 +246,7 @@ const Preferences = () => {
|
||||
setCompletionSubject(
|
||||
`Document {{document_title}} has been signed by all parties`
|
||||
);
|
||||
setIsDefaultMail((prev) => ({ ...prev, completionMail: true }));
|
||||
}
|
||||
setDefaultCompHtml({
|
||||
body: defaultCompletionBody,
|
||||
@@ -301,7 +307,7 @@ const Preferences = () => {
|
||||
JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
|
||||
if (extUser && extUser?.objectId) {
|
||||
extUser.TenantId.RequestBody = updateRes?.RequestBody;
|
||||
extUser.TenantId.RequestBody = updateRes?.RequestSubject;
|
||||
extUser.TenantId.RequestSubject = updateRes?.RequestSubject;
|
||||
const _extUser = JSON.parse(JSON.stringify(extUser));
|
||||
localStorage.setItem("Extand_Class", JSON.stringify([_extUser]));
|
||||
}
|
||||
@@ -318,13 +324,45 @@ const Preferences = () => {
|
||||
};
|
||||
|
||||
//function to use reset form
|
||||
const handleReset = (request, completion) => {
|
||||
if (request) {
|
||||
const handleReset = async (request, completion) => {
|
||||
let extUser =
|
||||
localStorage.getItem("Extand_Class") &&
|
||||
JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
|
||||
handleModifyMail(request);
|
||||
if (request && !isDefaultMail?.requestMail) {
|
||||
setRequestBody(defaultReqHtml?.body);
|
||||
setRequestSubject(defaultReqHtml?.subject);
|
||||
} else if (completion) {
|
||||
try {
|
||||
await Parse.Cloud.run("updatetenant", {
|
||||
tenantId: tenantId,
|
||||
details: { RequestBody: "", RequestSubject: "" }
|
||||
});
|
||||
if (extUser && extUser?.objectId) {
|
||||
extUser.TenantId.RequestBody = "";
|
||||
extUser.TenantId.RequestSubject = "";
|
||||
const _extUser = JSON.parse(JSON.stringify(extUser));
|
||||
localStorage.setItem("Extand_Class", JSON.stringify([_extUser]));
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("Err in reseting request mail", err);
|
||||
}
|
||||
} else if (completion && !isDefaultMail?.completionMail) {
|
||||
setCompletionSubject(defaultCompHtml?.subject);
|
||||
SetCompletionBody(defaultCompHtml?.body);
|
||||
try {
|
||||
await Parse.Cloud.run("updatetenant", {
|
||||
tenantId: tenantId,
|
||||
details: { CompletionBody: "", CompletionSubject: "" }
|
||||
});
|
||||
if (extUser && extUser?.objectId) {
|
||||
extUser.TenantId.CompletionBody = "";
|
||||
extUser.TenantId.CompletionSubject = "";
|
||||
const _extUser = JSON.parse(JSON.stringify(extUser));
|
||||
localStorage.setItem("Extand_Class", JSON.stringify([_extUser]));
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("Err in reseting completion mail", err);
|
||||
}
|
||||
}
|
||||
};
|
||||
//function for handle ontext change and save again text in delta
|
||||
@@ -344,6 +382,11 @@ const Preferences = () => {
|
||||
const handleTourInput = () => setIsTourEnabled(!isTourEnabled);
|
||||
const handleSendinOrderInput = () => setSendinOrder(!sendinOrder);
|
||||
const tabName = (ind) => tab.find((t, i) => i === ind)?.name;
|
||||
const handleModifyMail = (mode) => {
|
||||
mode === "request"
|
||||
? setIsDefaultMail((p) => ({ ...p, requestMail: !p?.requestMail }))
|
||||
: setIsDefaultMail((p) => ({ ...p, completionMail: !p?.completionMail }));
|
||||
};
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Title title={t("Preferences")} />
|
||||
@@ -737,6 +780,18 @@ const Preferences = () => {
|
||||
{t("request-email")}
|
||||
</h1>
|
||||
<div className="relative mt-2 mb-4">
|
||||
{
|
||||
isDefaultMail?.requestMail && (
|
||||
<div className="absolute backdrop-blur-[2px] flex w-full h-full justify-center items-center bg-black/10 rounded-box select-none z-20">
|
||||
<button
|
||||
onClick={() => handleModifyMail("request")}
|
||||
className="op-btn op-btn-primary shadow-lg"
|
||||
>
|
||||
Modify
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<form
|
||||
onSubmit={handleSaveRequestEmail}
|
||||
className="p-3 border-[1px] border-base-content rounded-box"
|
||||
@@ -801,6 +856,18 @@ const Preferences = () => {
|
||||
{t("completion-email")}
|
||||
</h1>
|
||||
<div className="relative my-2">
|
||||
{
|
||||
isDefaultMail?.completionMail && (
|
||||
<div className="absolute backdrop-blur-[2px] flex w-full h-full justify-center items-center bg-black/10 rounded-box select-none z-20">
|
||||
<button
|
||||
onClick={() => handleModifyMail("completion")}
|
||||
className="op-btn op-btn-primary shadow-lg"
|
||||
>
|
||||
Modify
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<form
|
||||
onSubmit={handleSaveCompletionEmail}
|
||||
className="p-3 border-[1px] border-base-content rounded-box"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useEffect, useState, useRef } from "react";
|
||||
import ReportTable from "../primitives/GetReportDisplay";
|
||||
import Parse from "parse";
|
||||
import axios from "axios";
|
||||
@@ -27,12 +27,18 @@ const Report = () => {
|
||||
const [isImport, setIsImport] = useState(false);
|
||||
const abortController = new AbortController();
|
||||
const docPerPage = 10;
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [mobileSearchOpen, setMobileSearchOpen] = useState(false);
|
||||
const [isSearchResult, setIsSearchResult] = useState(false);
|
||||
const debounceTimer = useRef(null);
|
||||
|
||||
// below useEffect is call when id param change
|
||||
useEffect(() => {
|
||||
setReportName("");
|
||||
setList([]);
|
||||
getReportData();
|
||||
setSearchTerm("");
|
||||
setMobileSearchOpen(false);
|
||||
getReportData(0, docPerPage, "");
|
||||
|
||||
// Function returned from useEffect is called on unmount
|
||||
return () => {
|
||||
@@ -48,7 +54,7 @@ const Report = () => {
|
||||
// below useEffect call when isNextRecord state is true and fetch next record
|
||||
useEffect(() => {
|
||||
if (isNextRecord) {
|
||||
getReportData(List.length, 20);
|
||||
getReportData(List.length, 20, searchTerm);
|
||||
}
|
||||
// eslint-disable-next-line
|
||||
}, [isNextRecord]);
|
||||
@@ -56,7 +62,58 @@ const Report = () => {
|
||||
const handleDontShow = (isChecked) => {
|
||||
setIsDontShow(isChecked);
|
||||
};
|
||||
const getReportData = async (skipUserRecord = 0, limit = 20) => {
|
||||
|
||||
const handleSearchChange = async (e) => {
|
||||
const term = e.target.value.toLowerCase();
|
||||
setSearchTerm(term);
|
||||
if (debounceTimer.current) {
|
||||
clearTimeout(debounceTimer.current);
|
||||
}
|
||||
debounceTimer.current = setTimeout(async () => {
|
||||
try {
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
|
||||
sessiontoken: localStorage.getItem("accesstoken")
|
||||
};
|
||||
const url = `${localStorage.getItem("baseUrl")}functions/getReport`;
|
||||
const res = await axios.post(
|
||||
url,
|
||||
{ reportId: id, 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 (
|
||||
skipUserRecord = 0,
|
||||
limit = 20,
|
||||
term = searchTerm
|
||||
) => {
|
||||
// setIsLoader(true);
|
||||
const json = reportJson(id);
|
||||
if (json) {
|
||||
@@ -77,6 +134,9 @@ const Report = () => {
|
||||
const skipRecord = id === "4Hhwbp482K" ? 0 : skipUserRecord;
|
||||
const limitRecord = id === "4Hhwbp482K" ? 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,
|
||||
@@ -120,7 +180,7 @@ const Report = () => {
|
||||
const listData = res.data?.result.filter((x) => x.Signers.length > 0);
|
||||
let arr = [];
|
||||
for (const obj of listData) {
|
||||
const isSigner = obj.Signers.some(
|
||||
const isSigner = obj?.Signers?.some(
|
||||
(item) => item.UserId.objectId === currentUser
|
||||
);
|
||||
if (isSigner) {
|
||||
@@ -197,6 +257,12 @@ const Report = () => {
|
||||
report_help={reporthelp}
|
||||
tourData={tourData}
|
||||
isDontShow={isDontShow}
|
||||
mobileSearchOpen={mobileSearchOpen}
|
||||
setMobileSearchOpen={setMobileSearchOpen}
|
||||
searchTerm={searchTerm}
|
||||
handleSearchChange={handleSearchChange}
|
||||
handleSearchPaste={handleSearchPaste}
|
||||
isSearchResult={isSearchResult}
|
||||
isImport={isImport}
|
||||
/>
|
||||
) : (
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
randomId,
|
||||
getDate,
|
||||
textWidget,
|
||||
cellsWidget,
|
||||
convertPdfArrayBuffer,
|
||||
textInputWidget,
|
||||
fetchImageBase64,
|
||||
@@ -37,7 +38,8 @@ import {
|
||||
generatePdfName,
|
||||
handleRemoveWidgets,
|
||||
addWidgetSelfsignOptions,
|
||||
getOriginalWH
|
||||
getOriginalWH,
|
||||
signatureTypes
|
||||
} from "../constant/Utils";
|
||||
import { useParams } from "react-router";
|
||||
import Tour from "../primitives/Tour";
|
||||
@@ -67,15 +69,14 @@ import {
|
||||
resetWidgetState
|
||||
} from "../redux/reducers/widgetSlice.js";
|
||||
import WidgetsValueModal from "../components/pdf/WidgetsValueModal";
|
||||
import WidgetNameModal from "../components/pdf/WidgetNameModal";
|
||||
import CellsSettingModal from "../components/pdf/CellsSettingModal";
|
||||
//For signYourself inProgress section signer can add sign and complete doc sign.
|
||||
function SignYourSelf() {
|
||||
const { t } = useTranslation();
|
||||
const { docId } = useParams();
|
||||
const dispatch = useDispatch();
|
||||
const isShowModal = useSelector((state) => state.widget.isShowModal);
|
||||
const saveSignCheckbox = useSelector(
|
||||
(state) => state.widget.saveSignCheckbox
|
||||
);
|
||||
const appName =
|
||||
"OpenSign™";
|
||||
const divRef = useRef(null);
|
||||
@@ -117,6 +118,10 @@ function SignYourSelf() {
|
||||
const [isTextSetting, setIsTextSetting] = useState(false);
|
||||
const [currWidgetsDetails, setCurrWidgetsDetails] = useState({});
|
||||
const [isCheckbox, setIsCheckbox] = useState(false);
|
||||
const [isNameModal, setIsNameModal] = useState(false);
|
||||
const [isCellsSetting, setIsCellsSetting] = useState(false);
|
||||
const openNameModal = () => setIsNameModal(true);
|
||||
const openCellsSettingModal = () => setIsCellsSetting(true);
|
||||
const [pdfLoad, setPdfLoad] = useState(false);
|
||||
const [isAlert, setIsAlert] = useState({ isShow: false, alertMessage: "" });
|
||||
const [isDontShow, setIsDontShow] = useState(false);
|
||||
@@ -260,13 +265,14 @@ function SignYourSelf() {
|
||||
if (defaultSignRes?.status === "success") {
|
||||
dispatch(
|
||||
setSaveSignCheckbox({
|
||||
...saveSignCheckbox,
|
||||
isVisible: true,
|
||||
signId: defaultSignRes?.res?.id
|
||||
})
|
||||
);
|
||||
dispatch(setDefaultSignImg(defaultSignRes?.res?.defaultSignature));
|
||||
dispatch(setMyInitial(defaultSignRes?.res?.defaultInitial));
|
||||
} else {
|
||||
dispatch(setSaveSignCheckbox({ isVisible: true }));
|
||||
}
|
||||
const contractUsersRes = await contractUsers();
|
||||
if (contractUsersRes === "Error: Something went wrong!") {
|
||||
@@ -275,8 +281,6 @@ function SignYourSelf() {
|
||||
} else if (contractUsersRes[0] && contractUsersRes.length > 0) {
|
||||
setContractName("_Users");
|
||||
setSignerUserId(contractUsersRes[0].objectId);
|
||||
dispatch(setSaveSignCheckbox({ ...saveSignCheckbox, isVisible: true }));
|
||||
|
||||
const tourstatuss =
|
||||
contractUsersRes[0].TourStatus && contractUsersRes[0].TourStatus;
|
||||
if (tourstatuss && tourstatuss.length > 0 && !isCompleted) {
|
||||
@@ -355,9 +359,13 @@ function SignYourSelf() {
|
||||
);
|
||||
const dragTypeValue = item?.text ? item.text : monitor.type;
|
||||
const widgetValue = getWidgetValue(dragTypeValue);
|
||||
const widgetTypeExist = ["name", "company", "job title", "email"].includes(
|
||||
dragTypeValue
|
||||
);
|
||||
const widgetTypeExist = [
|
||||
"name",
|
||||
"company",
|
||||
"job title",
|
||||
"email",
|
||||
cellsWidget
|
||||
].includes(dragTypeValue);
|
||||
const containerScale = getContainerScale(
|
||||
pdfOriginalWH,
|
||||
pageNumber,
|
||||
@@ -365,15 +373,19 @@ function SignYourSelf() {
|
||||
);
|
||||
//adding and updating drop position in array when user drop signature button in div
|
||||
if (item === "onclick") {
|
||||
// `getBoundingClientRect()` is used to get accurate measurement height of the div
|
||||
// `getBoundingClientRect()` is used to get accurate measurement width, height of the Pdf div
|
||||
const divHeight = divRef.current.getBoundingClientRect().height;
|
||||
const divWidth = divRef.current.getBoundingClientRect().width;
|
||||
const getWidth = widgetTypeExist
|
||||
? calculateInitialWidthHeight(widgetValue).getWidth
|
||||
: defaultWidthHeight(dragTypeValue).width;
|
||||
const getHeight = defaultWidthHeight(dragTypeValue).height;
|
||||
|
||||
// Compute the pixel‐space center within the PDF viewport:
|
||||
const centerX_Pixels = divWidth / 2 - getWidth / 2;
|
||||
const xPosition_Final = centerX_Pixels / (containerScale * scale);
|
||||
dropObj = {
|
||||
xPosition: getWidth / 2 + containerWH.width / 2,
|
||||
xPosition: xPosition_Final,
|
||||
yPosition: getHeight + divHeight / 2,
|
||||
isStamp:
|
||||
(dragTypeValue === "stamp" || dragTypeValue === "image") && true,
|
||||
@@ -434,6 +446,7 @@ function SignYourSelf() {
|
||||
[
|
||||
textInputWidget,
|
||||
textWidget,
|
||||
cellsWidget,
|
||||
"name",
|
||||
"company",
|
||||
"job title",
|
||||
@@ -631,7 +644,6 @@ function SignYourSelf() {
|
||||
}
|
||||
//embed all widgets in document
|
||||
const pdfBytes = await multiSignEmbed(
|
||||
pdfOriginalWH,
|
||||
xyPosition,
|
||||
pdfDoc,
|
||||
isSignYourSelfFlow,
|
||||
@@ -926,7 +938,8 @@ function SignYourSelf() {
|
||||
deleteOption,
|
||||
status,
|
||||
defaultValue,
|
||||
isHideLabel
|
||||
isHideLabel,
|
||||
layout
|
||||
) => {
|
||||
const getPageNumer = xyPosition.filter(
|
||||
(data) => data.pageNumber === pageNumber
|
||||
@@ -934,6 +947,8 @@ function SignYourSelf() {
|
||||
if (getPageNumer.length > 0) {
|
||||
const getXYdata = getPageNumer[0].pos;
|
||||
const getPosData = getXYdata;
|
||||
const widgetLayout =
|
||||
currWidgetsDetails?.type === "checkbox" ? { layout: layout } : {};
|
||||
const addSignPos = getPosData.map((position) => {
|
||||
if (position.key === currWidgetsDetails?.key) {
|
||||
if (addOption) {
|
||||
@@ -957,6 +972,7 @@ function SignYourSelf() {
|
||||
...position.options,
|
||||
name: dropdownName,
|
||||
values: dropdownOptions,
|
||||
...widgetLayout,
|
||||
isReadOnly: isReadOnly,
|
||||
isHideLabel: isHideLabel || false,
|
||||
fontSize:
|
||||
@@ -1023,6 +1039,99 @@ function SignYourSelf() {
|
||||
handleTextSettingModal(false);
|
||||
}
|
||||
};
|
||||
|
||||
const setCellCount = (key, newCount) => {
|
||||
setXyPosition((prev) => {
|
||||
const getPageNumer = prev.filter(
|
||||
(data) => data.pageNumber === pageNumber
|
||||
);
|
||||
if (getPageNumer.length > 0) {
|
||||
const updatePos = getPageNumer[0].pos.map((p) =>
|
||||
p.key === key
|
||||
? { ...p, options: { ...p.options, cellCount: newCount } }
|
||||
: p
|
||||
);
|
||||
return prev.map((obj, ind) =>
|
||||
ind === index ? { ...obj, pos: updatePos } : obj
|
||||
);
|
||||
}
|
||||
return prev;
|
||||
});
|
||||
};
|
||||
|
||||
const handleWidgetdefaultdata = (defaultdata) => {
|
||||
const newFontSize =
|
||||
defaultdata?.fontSize !== undefined ? defaultdata.fontSize : fontSize;
|
||||
const newFontColor =
|
||||
defaultdata?.fontColor !== undefined ? defaultdata.fontColor : fontColor;
|
||||
|
||||
const getPageNumer = xyPosition.filter(
|
||||
(data) => data.pageNumber === pageNumber
|
||||
);
|
||||
if (getPageNumer.length > 0) {
|
||||
const updatePos = getPageNumer[0].pos.map((position) => {
|
||||
if (position.key === currWidgetsDetails?.key) {
|
||||
if (position.type === cellsWidget) {
|
||||
const count = parseInt(
|
||||
defaultdata?.cellCount ?? position.options?.cellCount ?? 5,
|
||||
10
|
||||
);
|
||||
return {
|
||||
...position,
|
||||
options: {
|
||||
...position.options,
|
||||
name: defaultdata?.name || position.options?.name || "Cells",
|
||||
cellCount: count,
|
||||
defaultValue: (defaultdata?.defaultValue || "").slice(0, count),
|
||||
fontSize: newFontSize || position.options?.fontSize || 12,
|
||||
fontColor:
|
||||
newFontColor || position.options?.fontColor || "black"
|
||||
}
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
...position,
|
||||
options: {
|
||||
...position.options,
|
||||
name: defaultdata?.name || position.options?.name,
|
||||
fontSize: newFontSize || position.options?.fontSize || 12,
|
||||
fontColor:
|
||||
newFontColor || position.options?.fontColor || "black"
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
return position;
|
||||
});
|
||||
const updateXYposition = xyPosition.map((obj, ind) =>
|
||||
ind === index ? { ...obj, pos: updatePos } : obj
|
||||
);
|
||||
setXyPosition(updateXYposition);
|
||||
}
|
||||
setFontSize();
|
||||
setFontColor();
|
||||
setCurrWidgetsDetails({});
|
||||
setIsNameModal(false);
|
||||
};
|
||||
|
||||
const handleNameModal = () => {
|
||||
setIsNameModal(false);
|
||||
setCurrWidgetsDetails({});
|
||||
setIsCheckbox(false);
|
||||
};
|
||||
|
||||
const handleCellsSettingSave = (data) => {
|
||||
// ensure font and color are updated before applying widget changes
|
||||
if (data?.fontSize !== undefined) setFontSize(data.fontSize);
|
||||
if (data?.fontColor !== undefined) setFontColor(data.fontColor);
|
||||
handleWidgetdefaultdata(data);
|
||||
setIsCellsSetting(false);
|
||||
};
|
||||
|
||||
const handleCellsSettingClose = () => {
|
||||
setIsCellsSetting(false);
|
||||
setCurrWidgetsDetails({});
|
||||
};
|
||||
const clickOnZoomIn = () => {
|
||||
onClickZoomIn(scale, zoomPercent, setScale, setZoomPercent);
|
||||
};
|
||||
@@ -1178,9 +1287,8 @@ function SignYourSelf() {
|
||||
title={t("document-signed")}
|
||||
handleClose={() => setShowAlreadySignDoc({ status: false })}
|
||||
>
|
||||
<div className="p-[20px] h-full">
|
||||
<div className="p-[20px] h-full text-base-content">
|
||||
<p>{showAlreadySignDoc.mssg}</p>
|
||||
|
||||
<div className="h-[1px] w-full my-[15px] bg-[#9f9f9f]"></div>
|
||||
<button
|
||||
className="op-btn op-btn-ghost shadow-md"
|
||||
@@ -1278,12 +1386,15 @@ function SignYourSelf() {
|
||||
setIsPageCopy={setIsPageCopy}
|
||||
setIsCheckbox={setIsCheckbox}
|
||||
setCurrWidgetsDetails={setCurrWidgetsDetails}
|
||||
handleNameModal={openNameModal}
|
||||
handleCellSettingModal={openCellsSettingModal}
|
||||
handleTextSettingModal={handleTextSettingModal}
|
||||
setScale={setScale}
|
||||
scale={scale}
|
||||
pdfBase64Url={pdfBase64Url}
|
||||
fontSize={fontSize}
|
||||
setFontSize={setFontSize}
|
||||
setCellCount={setCellCount}
|
||||
fontColor={fontColor}
|
||||
setFontColor={setFontColor}
|
||||
isResize={isResize}
|
||||
@@ -1325,13 +1436,32 @@ function SignYourSelf() {
|
||||
xyPosition={xyPosition} //placeholder details
|
||||
pageNumber={pageNumber} //current page number
|
||||
setXyPosition={setXyPosition} //placeholder details state
|
||||
setCellCount={setCellCount}
|
||||
setPageNumber={setPageNumber}
|
||||
setCurrWidgetsDetails={setCurrWidgetsDetails}
|
||||
currWidgetsDetails={currWidgetsDetails}
|
||||
index={index}
|
||||
isSave={true}
|
||||
signatureTypes={signatureTypes}
|
||||
/>
|
||||
)}
|
||||
<WidgetNameModal
|
||||
widgetName={currWidgetsDetails?.options?.name}
|
||||
defaultdata={currWidgetsDetails}
|
||||
isOpen={isNameModal}
|
||||
handleClose={handleNameModal}
|
||||
handleData={handleWidgetdefaultdata}
|
||||
fontSize={fontSize}
|
||||
setFontSize={setFontSize}
|
||||
fontColor={fontColor}
|
||||
setFontColor={setFontColor}
|
||||
/>
|
||||
<CellsSettingModal
|
||||
isOpen={isCellsSetting}
|
||||
defaultData={currWidgetsDetails}
|
||||
handleClose={handleCellsSettingClose}
|
||||
handleSave={handleCellsSettingSave}
|
||||
/>
|
||||
<RotateAlert
|
||||
showRotateAlert={showRotateAlert.status}
|
||||
setShowRotateAlert={setShowRotateAlert}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState, useRef } from "react";
|
||||
import { useEffect, useState, useRef } from "react";
|
||||
import RenderAllPdfPage from "../components/pdf/RenderAllPdfPage";
|
||||
import { useParams, useNavigate } from "react-router";
|
||||
import axios from "axios";
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
defaultWidthHeight,
|
||||
addWidgetOptions,
|
||||
textInputWidget,
|
||||
cellsWidget,
|
||||
radioButtonWidget,
|
||||
getContainerScale,
|
||||
convertBase64ToFile,
|
||||
@@ -376,11 +377,15 @@ const TemplatePlaceholder = () => {
|
||||
filterSignerPos;
|
||||
let placeHolder;
|
||||
if (item === "onclick") {
|
||||
// `getBoundingClientRect()` is used to get accurate measurement height of the div
|
||||
// `getBoundingClientRect()` is used to get accurate measurement width, height of the Pdf div
|
||||
const divWidth = divRef.current.getBoundingClientRect().width;
|
||||
const divHeight = divRef.current.getBoundingClientRect().height;
|
||||
// Compute the pixel‐space center within the PDF viewport:
|
||||
const centerX_Pixels = divWidth / 2 - widgetWidth / 2;
|
||||
const xPosition_Final = centerX_Pixels / (containerScale * scale);
|
||||
dropObj = {
|
||||
//onclick put placeholder center on pdf
|
||||
xPosition: widgetWidth / 4 + containerWH.width / 2,
|
||||
xPosition: xPosition_Final,
|
||||
yPosition: widgetHeight + divHeight / 2,
|
||||
isStamp:
|
||||
(dragTypeValue === "stamp" || dragTypeValue === "image") && true,
|
||||
@@ -828,7 +833,6 @@ const TemplatePlaceholder = () => {
|
||||
try {
|
||||
//pdfOriginalWH contained all pdf's pages width,height & pagenumber in array format
|
||||
const pdfBase64 = await multiSignEmbed(
|
||||
pdfOriginalWH,
|
||||
placeholder,
|
||||
pdfDoc,
|
||||
isSignYourSelfFlow,
|
||||
@@ -1270,7 +1274,8 @@ const TemplatePlaceholder = () => {
|
||||
deleteOption,
|
||||
status,
|
||||
defaultValue,
|
||||
isHideLabel
|
||||
isHideLabel,
|
||||
layout
|
||||
) => {
|
||||
const filterSignerPos = signerPos.filter((data) => data.Id === uniqueId);
|
||||
if (filterSignerPos.length > 0) {
|
||||
@@ -1309,6 +1314,7 @@ const TemplatePlaceholder = () => {
|
||||
name: dropdownName,
|
||||
values: dropdownOptions,
|
||||
status: status,
|
||||
layout: layout,
|
||||
defaultValue: defaultValue,
|
||||
isReadOnly: isReadOnly || false,
|
||||
isHideLabel: isHideLabel || false,
|
||||
@@ -1347,6 +1353,7 @@ const TemplatePlaceholder = () => {
|
||||
minRequiredCount: minCount,
|
||||
maxRequiredCount: maxCount
|
||||
},
|
||||
layout: layout,
|
||||
isReadOnly: isReadOnly || false,
|
||||
defaultValue: defaultValue,
|
||||
isHideLabel: isHideLabel || false,
|
||||
@@ -1447,6 +1454,36 @@ const TemplatePlaceholder = () => {
|
||||
"black"
|
||||
}
|
||||
};
|
||||
} else if (position.type === cellsWidget) {
|
||||
return {
|
||||
...position,
|
||||
options: {
|
||||
...position.options,
|
||||
name: defaultdata?.name || "Cells",
|
||||
status: defaultdata?.status || "required",
|
||||
hint: defaultdata?.hint || "",
|
||||
cellCount: parseInt(defaultdata?.cellCount || 5),
|
||||
defaultValue: (defaultdata?.defaultValue || "").slice(
|
||||
0,
|
||||
parseInt(defaultdata?.cellCount || 5)
|
||||
),
|
||||
validation:
|
||||
isSubscribe && inputype
|
||||
? {
|
||||
type: inputype,
|
||||
pattern:
|
||||
inputype === "regex" ? defaultdata.textvalidate : ""
|
||||
}
|
||||
: {},
|
||||
isReadOnly: defaultdata?.isReadOnly || false,
|
||||
fontSize:
|
||||
fontSize || currWidgetsDetails?.options?.fontSize || 12,
|
||||
fontColor:
|
||||
fontColor ||
|
||||
currWidgetsDetails?.options?.fontColor ||
|
||||
"black"
|
||||
}
|
||||
};
|
||||
} else if (["signature"].includes(position.type)) {
|
||||
return {
|
||||
...position,
|
||||
@@ -1506,6 +1543,22 @@ const TemplatePlaceholder = () => {
|
||||
setIsRadio(false);
|
||||
setIsCheckbox(false);
|
||||
};
|
||||
const setCellCount = (key, newCount) => {
|
||||
const updated = signerPos.map((signer) => {
|
||||
if (signer.Id !== uniqueId) return signer;
|
||||
const placeHolder = signer.placeHolder.map((ph) => {
|
||||
if (ph.pageNumber !== pageNumber) return ph;
|
||||
const pos = ph.pos.map((p) =>
|
||||
p.key === key
|
||||
? { ...p, options: { ...p.options, cellCount: newCount } }
|
||||
: p
|
||||
);
|
||||
return { ...ph, pos };
|
||||
});
|
||||
return { ...signer, placeHolder };
|
||||
});
|
||||
setSignerPos(updated);
|
||||
};
|
||||
|
||||
const clickOnZoomIn = () => {
|
||||
onClickZoomIn(scale, zoomPercent, setScale, setZoomPercent);
|
||||
@@ -1667,7 +1720,7 @@ const TemplatePlaceholder = () => {
|
||||
navigate("/report/6TeaPr321t");
|
||||
}}
|
||||
>
|
||||
<div className="h-full p-[20px] mb-2">
|
||||
<div className="h-full p-[20px] mb-2 text-base-content">
|
||||
<p>{t("template-created-alert")}</p>
|
||||
<div className="h-[1px] w-full my-[15px] bg-[#9f9f9f]"></div>
|
||||
<div className="flex gap-1 flex-col md:flex-row">
|
||||
@@ -1824,6 +1877,7 @@ const TemplatePlaceholder = () => {
|
||||
pdfBase64Url={pdfBase64Url}
|
||||
fontSize={fontSize}
|
||||
setFontSize={setFontSize}
|
||||
setCellCount={setCellCount}
|
||||
fontColor={fontColor}
|
||||
setFontColor={setFontColor}
|
||||
isResize={isResize}
|
||||
|
||||
@@ -284,7 +284,7 @@ const UserList = () => {
|
||||
handleClose={handleClose}
|
||||
>
|
||||
<div className="m-[20px]">
|
||||
<div className="text-lg font-normal text-black">
|
||||
<div className="text-lg font-normal text-base-content">
|
||||
{t("are-you-sure")}{" "}
|
||||
{item?.IsDisabled
|
||||
? t("activate")
|
||||
|
||||
@@ -286,7 +286,9 @@ function UserProfile() {
|
||||
style={{ width: `${percentage}%` }}
|
||||
></div>
|
||||
</div>
|
||||
<span className="text-black text-sm">{percentage}%</span>
|
||||
<span className="text-base-contentk text-sm">
|
||||
{percentage}%
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="text-base font-semibold pt-4">
|
||||
|
||||
@@ -91,7 +91,7 @@ function DownloadPdfZip(props) {
|
||||
title={t("download-files")}
|
||||
handleClose={() => props.setIsDownloadModal(false)}
|
||||
>
|
||||
<div className="p-[20px] h-full">
|
||||
<div className="p-[20px] h-full text-base-content">
|
||||
{downloadType.map((data, ind) => {
|
||||
return (
|
||||
<label
|
||||
|
||||
@@ -7,6 +7,7 @@ import ModalUi from "./ModalUi";
|
||||
import AddSigner from "../components/AddSigner";
|
||||
import {
|
||||
emailRegex,
|
||||
iconColor,
|
||||
} from "../constant/const";
|
||||
import Alert from "./Alert";
|
||||
import Tooltip from "./Tooltip";
|
||||
@@ -38,9 +39,12 @@ import { useTranslation } from "react-i18next";
|
||||
import DownloadPdfZip from "./DownloadPdfZip";
|
||||
import * as XLSX from "xlsx";
|
||||
import EditContactForm from "../components/EditContactForm";
|
||||
import { useElSize } from "../hook/useElSize";
|
||||
|
||||
const ReportTable = (props) => {
|
||||
const copyUrlRef = useRef(null);
|
||||
const titleRef = useRef(null);
|
||||
const titleElement = useElSize(titleRef);
|
||||
const appName =
|
||||
"OpenSign™";
|
||||
const drivename = appName === "OpenSign™" ? "OpenSign™" : "";
|
||||
@@ -87,11 +91,18 @@ const ReportTable = (props) => {
|
||||
const [contact, setContact] = useState({ Name: "", Email: "", Phone: "" });
|
||||
const [isSuccess, setIsSuccess] = useState({});
|
||||
const [templateId, setTemplateId] = useState("");
|
||||
const [sortOrder, setSortOrder] = useState("asc");
|
||||
const isTemplateReport = props.ReportName === "Templates";
|
||||
const recordsPerPage = 5;
|
||||
const startIndex = (currentPage - 1) * props.docPerPage;
|
||||
const { isMoreDocs, setIsNextRecord } = props;
|
||||
|
||||
useEffect(() => {
|
||||
if (props.isSearchResult) {
|
||||
setCurrentPage(1);
|
||||
}
|
||||
}, [props.isSearchResult]);
|
||||
|
||||
const getPaginationRange = () => {
|
||||
const totalPageNumbers = 7; // Adjust this value to show more/less page numbers
|
||||
const pages = [];
|
||||
@@ -427,7 +438,22 @@ const ReportTable = (props) => {
|
||||
// Get current list
|
||||
const indexOfLastDoc = currentPage * props.docPerPage;
|
||||
const indexOfFirstDoc = indexOfLastDoc - props.docPerPage;
|
||||
const currentList = props.List?.slice(indexOfFirstDoc, indexOfLastDoc);
|
||||
const sortedList = React.useMemo(() => {
|
||||
if (props.ReportName === "Contactbook") {
|
||||
const contacts = [...props.List];
|
||||
contacts.sort((a, b) => {
|
||||
const nameA = a?.Name?.toLowerCase() || "";
|
||||
const nameB = b?.Name?.toLowerCase() || "";
|
||||
if (nameA < nameB) return sortOrder === "asc" ? -1 : 1;
|
||||
if (nameA > nameB) return sortOrder === "asc" ? 1 : -1;
|
||||
return 0;
|
||||
});
|
||||
return contacts;
|
||||
}
|
||||
return props.List;
|
||||
}, [props.List, sortOrder, props.ReportName]);
|
||||
|
||||
const currentList = sortedList?.slice(indexOfFirstDoc, indexOfLastDoc);
|
||||
|
||||
// Change page
|
||||
const paginateFront = () => {
|
||||
@@ -447,6 +473,10 @@ const ReportTable = (props) => {
|
||||
setIsContactform(!isContactform);
|
||||
};
|
||||
|
||||
const toggleSortOrder = () => {
|
||||
setSortOrder(sortOrder === "asc" ? "desc" : "asc");
|
||||
};
|
||||
|
||||
const handleUserData = (data) => {
|
||||
props.setList((prevData) => [data, ...prevData]);
|
||||
};
|
||||
@@ -763,7 +793,7 @@ const ReportTable = (props) => {
|
||||
const body =
|
||||
doc?.RequestBody ||
|
||||
doc?.ExtUserPtr?.TenantId?.RequestBody ||
|
||||
`<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body><p>Hi {{receiver_name}},</p><br><p>We hope this email finds you well. {{sender_name}} has requested you to review and sign <b>"{{document_title}}"</b>.</p><p>Your signature is crucial to proceed with the next steps as it signifies your agreement and authorization.</p><br><p>{{signing_url}}</p><br><p>If you have any questions or need further clarification regarding the document or the signing process, please contact the sender.</p><br><p>Thanks</p><p> Team ${appName}</p><br></body> </html>`;
|
||||
`<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body><p>Hi {{receiver_name}},</p><br><p>We hope this email finds you well. {{sender_name}} has requested you to review and sign <b>"{{document_title}}"</b>.</p><p>Your signature is crucial to proceed with the next steps as it signifies your agreement and authorization.</p><br><p><a href='{{signing_url}}' rel='noopener noreferrer' target='_blank'>Sign here</a></p><br><br><p>If you have any questions or need further clarification regarding the document or the signing process, please contact the sender.</p><br><p>Thanks</p><p> Team ${appName}</p><br></body> </html>`;
|
||||
const res = replaceMailVaribles(subject, body, variables);
|
||||
setMail((prev) => ({ ...prev, subject: res.subject, body: res.body }));
|
||||
setIsNextStep({ [user.Id]: true });
|
||||
@@ -907,7 +937,6 @@ const ReportTable = (props) => {
|
||||
setActLoader({});
|
||||
}
|
||||
};
|
||||
|
||||
const handleUpdateExpiry = async (e, item) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
@@ -965,6 +994,7 @@ const ReportTable = (props) => {
|
||||
const timezone = extClass?.[0]?.Timezone || "";
|
||||
const DateFormat = extClass?.[0]?.DateFormat || "MM/DD/YYYY";
|
||||
const Is12Hr = extClass?.[0]?.Is12HourTime || false;
|
||||
const isCompletedReport = props?.ReportName === "Completed Documents";
|
||||
const signers = item?.Placeholders?.map((x, i) => {
|
||||
const audit = item?.AuditTrail?.find(
|
||||
(audit) => audit?.UserPtr?.objectId === x.signerObjId
|
||||
@@ -990,22 +1020,26 @@ const ReportTable = (props) => {
|
||||
{displaySigners?.map((x, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="text-sm font-medium flex flex-row gap-2 items-center"
|
||||
className={`text-sm flex flex-row gap-2 items-center ${
|
||||
i !== displaySigners.length - 1 ? "mb-2" : ""
|
||||
}`}
|
||||
>
|
||||
<button
|
||||
onClick={() => setIsModal({ [`${item.objectId}_${i}`]: true })}
|
||||
className={`${
|
||||
x.Activity === "SIGNED"
|
||||
? "op-border-primary op-text-primary"
|
||||
: x.Activity === "VIEWED"
|
||||
? "border-green-400 text-green-400"
|
||||
: "border-black text-black"
|
||||
} focus:outline-none border-2 w-[60px] h-[30px] text-[11px] rounded-full`}
|
||||
>
|
||||
{x?.Activity?.toUpperCase() || "-"}
|
||||
</button>
|
||||
<div className="py-2 font-bold text-[12px]">{x?.Email || "-"}</div>
|
||||
{isModal[`${item.objectId}_${i}`] && (
|
||||
{!isCompletedReport && (
|
||||
<button
|
||||
onClick={() => setIsModal({ [`${item.objectId}_${i}`]: true })}
|
||||
className={`${
|
||||
x.Activity === "SIGNED"
|
||||
? "op-border-primary op-text-primary"
|
||||
: x.Activity === "VIEWED"
|
||||
? "border-green-400 text-green-400"
|
||||
: "border-base-content text-base-content"
|
||||
} focus:outline-none border-2 w-[60px] h-[30px] text-[11px] rounded-full`}
|
||||
>
|
||||
{x?.Activity?.toUpperCase() || "-"}
|
||||
</button>
|
||||
)}
|
||||
<div className="text-[12px]">{x?.Email || "-"}</div>
|
||||
{!isCompletedReport && isModal[`${item.objectId}_${i}`] && (
|
||||
<ModalUi
|
||||
isOpen
|
||||
title={t("document-logs")}
|
||||
@@ -1028,7 +1062,7 @@ const ReportTable = (props) => {
|
||||
[item.objectId]: !isShowAllSigners[item.objectId]
|
||||
})
|
||||
}
|
||||
className="ml-2 text-xs font-medium text-blue-500 underline focus:outline-none"
|
||||
className="ml-2 mt-1 text-xs font-medium text-blue-500 underline focus:outline-none"
|
||||
>
|
||||
{isShowAllSigners[item.objectId] ? "Hide" : "Show More"}
|
||||
</button>
|
||||
@@ -1314,7 +1348,6 @@ const ReportTable = (props) => {
|
||||
try {
|
||||
const params = { docId: doc?.objectId };
|
||||
const templateRes = await Parse.Cloud.run("saveastemplate", params);
|
||||
// console.log("templateRes ", templateRes);
|
||||
setTemplateId(templateRes?.id);
|
||||
setIsSuccess({ [doc.objectId]: true });
|
||||
} catch (err) {
|
||||
@@ -1394,6 +1427,12 @@ const ReportTable = (props) => {
|
||||
setActLoader({});
|
||||
}
|
||||
};
|
||||
|
||||
const restrictBtn = (item, act) => {
|
||||
return item.IsSignyourself && act.action === "recreatedocument"
|
||||
? true
|
||||
: false;
|
||||
};
|
||||
return (
|
||||
<div className="relative">
|
||||
{Object.keys(actLoader)?.length > 0 && (
|
||||
@@ -1416,7 +1455,10 @@ const ReportTable = (props) => {
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<div className="flex flex-row items-center justify-between my-2 mx-3 text-[20px] md:text-[23px]">
|
||||
<div
|
||||
ref={titleRef}
|
||||
className="flex flex-row items-center justify-between my-2 mx-3 text-[20px] md:text-[23px]"
|
||||
>
|
||||
<div className="font-light">
|
||||
{t(`report-name.${props.ReportName}`)}{" "}
|
||||
{props.report_help && (
|
||||
@@ -1428,26 +1470,68 @@ const ReportTable = (props) => {
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-row justify-center items-center gap-3">
|
||||
{props.isImport && (
|
||||
<div className="cursor-pointer" onClick={() => handleImportBtn()}>
|
||||
<i className="fa-light fa-upload op-text-secondary text-[23px] md:text-[30px]"></i>
|
||||
<div className="flex flex-row justify-center items-center gap-3 mb-2">
|
||||
{/* Search input for report bigger in width */}
|
||||
{titleElement?.width > 500 && (
|
||||
<div className="flex">
|
||||
<input
|
||||
type="search"
|
||||
value={props.searchTerm}
|
||||
onChange={props.handleSearchChange}
|
||||
placeholder={
|
||||
props.ReportName === "Contactbook"
|
||||
? t("search-contacts")
|
||||
: isTemplateReport
|
||||
? t("search-templates")
|
||||
: t("search-documents")
|
||||
}
|
||||
onPaste={props.handleSearchPaste}
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-64 text-xs"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{/* contact import */}
|
||||
{props.isImport && (
|
||||
<div
|
||||
className="cursor-pointer flex"
|
||||
onClick={() => handleImportBtn()}
|
||||
>
|
||||
<i className="fa-light fa-upload op-text-secondary text-[23px] md:text-[25px]"></i>
|
||||
</div>
|
||||
)}
|
||||
{/* add contact form */}
|
||||
{props.form && (
|
||||
<div
|
||||
className="cursor-pointer"
|
||||
className="cursor-pointer flex"
|
||||
onClick={() => handleContactFormModal()}
|
||||
>
|
||||
<i className="fa-light fa-square-plus text-accent text-[30px] md:text-[35px]"></i>
|
||||
<i className="fa-light fa-square-plus text-accent text-[30px] md:text-[32px]"></i>
|
||||
</div>
|
||||
)}
|
||||
{/* create template form */}
|
||||
{isTemplateReport && (
|
||||
<i
|
||||
<div
|
||||
data-tut="reactourFirst"
|
||||
className="cursor-pointer flex"
|
||||
onClick={() => navigate("/form/template")}
|
||||
className="cursor-pointer fa-light fa-square-plus text-accent text-[30px] md:text-[35px]"
|
||||
></i>
|
||||
>
|
||||
<i className="cursor-pointer fa-light fa-square-plus text-accent text-[30px] md:text-[32px]"></i>
|
||||
</div>
|
||||
)}
|
||||
{/* search icon/magnifer icon */}
|
||||
{titleElement?.width < 500 && (
|
||||
<button
|
||||
className="flex justify-center items-center focus:outline-none rounded-md text-[18px]"
|
||||
aria-label="Search"
|
||||
onClick={() =>
|
||||
props.setMobileSearchOpen(!props.mobileSearchOpen)
|
||||
}
|
||||
>
|
||||
<i
|
||||
style={{ color: `${iconColor}` }}
|
||||
className="fa-solid fa-magnifying-glass"
|
||||
></i>
|
||||
</button>
|
||||
)}
|
||||
<ModalUi
|
||||
isOpen={isModal?.export}
|
||||
@@ -1477,6 +1561,17 @@ const ReportTable = (props) => {
|
||||
required
|
||||
className="op-file-input op-file-input-bordered op-file-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
/>
|
||||
<p className="mt-1 ml-2 text-[11px] text-gray-600">
|
||||
{t("import-guideline")}{" "}
|
||||
<a
|
||||
href="/sample_contacts.csv"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-primary underline"
|
||||
>
|
||||
{t("download-sample")}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-md m-2">
|
||||
<div className="flex flex-col md:flex-row gap-1">
|
||||
@@ -1536,6 +1631,19 @@ const ReportTable = (props) => {
|
||||
</ModalUi>
|
||||
</div>
|
||||
</div>
|
||||
{/* Search input for report smalle in width */}
|
||||
{titleElement?.width < 500 && props.mobileSearchOpen && (
|
||||
<div className="top-full left-0 w-full bg-white px-3 pt-1 pb-3">
|
||||
<input
|
||||
type="search"
|
||||
value={props.searchTerm}
|
||||
onChange={props.handleSearchChange}
|
||||
placeholder={t("search-documents")}
|
||||
onPaste={props.handleSearchPaste}
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={`overflow-auto w-full border-b ${
|
||||
props.List?.length > 0
|
||||
@@ -1552,7 +1660,25 @@ const ReportTable = (props) => {
|
||||
<tr className="border-y-[1px]">
|
||||
{props.heading?.map((item, index) => (
|
||||
<React.Fragment key={index}>
|
||||
<th className="p-2">{t(`report-heading.${item}`)}</th>
|
||||
<th className="p-2">
|
||||
{t(`report-heading.${item}`)}
|
||||
{props.ReportName === "Contactbook" &&
|
||||
item === "Name" && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={toggleSortOrder}
|
||||
className="ml-1"
|
||||
>
|
||||
<i
|
||||
className={
|
||||
sortOrder === "asc"
|
||||
? "fa-light fa-arrow-down-a-z"
|
||||
: "fa-light fa-arrow-up-a-z"
|
||||
}
|
||||
></i>
|
||||
</button>
|
||||
)}
|
||||
</th>
|
||||
</React.Fragment>
|
||||
))}
|
||||
{props.actions?.length > 0 && (
|
||||
@@ -1602,7 +1728,7 @@ const ReportTable = (props) => {
|
||||
handleClose={handleClose}
|
||||
>
|
||||
<div className="m-[20px]">
|
||||
<div className="text-lg font-normal text-black">
|
||||
<div className="text-lg font-normal text-base-content">
|
||||
{t("contact-delete-alert")}
|
||||
</div>
|
||||
<hr className="bg-[#ccc] mt-4 " />
|
||||
@@ -1687,12 +1813,16 @@ const ReportTable = (props) => {
|
||||
</td>
|
||||
)}
|
||||
{props.heading.includes("Signers") &&
|
||||
["In-progress documents", "Need your sign"].includes(
|
||||
props.ReportName
|
||||
) ? (
|
||||
[
|
||||
"In-progress documents",
|
||||
"Need your sign",
|
||||
"Completed Documents"
|
||||
].includes(props.ReportName) ? (
|
||||
<td className="px-1 py-2">
|
||||
{!item?.IsSignyourself && item?.Placeholders && (
|
||||
{!item?.IsSignyourself && item?.Placeholders ? (
|
||||
<>{formatStatusRow(item)}</>
|
||||
) : (
|
||||
<>-</>
|
||||
)}
|
||||
</td>
|
||||
) : (
|
||||
@@ -1745,7 +1875,7 @@ const ReportTable = (props) => {
|
||||
{/* template report */}
|
||||
{isOption[item.objectId] &&
|
||||
act.action === "option" && (
|
||||
<ul className="absolute -right-1 top-auto z-[70] w-52 op-dropdown-content op-menu shadow-black/20 shadow bg-base-100 text-base-content rounded-box">
|
||||
<ul className="absolute -right-1 top-auto z-[70] w-52 op-dropdown-content op-menu op-menu-sm shadow-black/20 shadow bg-base-100 text-base-content rounded-box">
|
||||
{act.subaction?.map((subact) => (
|
||||
<li
|
||||
key={subact.btnId}
|
||||
@@ -1836,34 +1966,40 @@ const ReportTable = (props) => {
|
||||
{/* doc report */}
|
||||
{isOption[item.objectId] &&
|
||||
act.action === "option" && (
|
||||
<ul className="absolute -right-1 top-auto z-[70] w-max op-dropdown-content op-menu shadow-black/20 shadow bg-base-100 text-base-content rounded-box">
|
||||
{act.subaction?.map((subact) => (
|
||||
<li
|
||||
key={subact.btnId}
|
||||
onClick={() =>
|
||||
handleActionBtn(
|
||||
subact,
|
||||
item
|
||||
)
|
||||
}
|
||||
title={t(
|
||||
`btnLabel.${subact.hoverLabel}`
|
||||
)}
|
||||
>
|
||||
<span>
|
||||
<i
|
||||
className={`${subact.btnIcon} mr-1.5`}
|
||||
></i>
|
||||
{subact.btnLabel && (
|
||||
<span className="text-[13px] capitalize font-medium">
|
||||
{t(
|
||||
`btnLabel.${subact.btnLabel}`
|
||||
<ul className="absolute -right-1 top-auto z-[70] w-max op-dropdown-content op-menu op-menu-sm shadow-black/20 shadow bg-base-100 text-base-content rounded-box">
|
||||
{act.subaction?.map(
|
||||
(subact) =>
|
||||
!restrictBtn(
|
||||
item,
|
||||
subact
|
||||
) && (
|
||||
<li
|
||||
key={subact.btnId}
|
||||
onClick={() =>
|
||||
handleActionBtn(
|
||||
subact,
|
||||
item
|
||||
)
|
||||
}
|
||||
title={t(
|
||||
`btnLabel.${subact.hoverLabel}`
|
||||
)}
|
||||
>
|
||||
<span>
|
||||
<i
|
||||
className={`${subact.btnIcon} mr-1.5`}
|
||||
></i>
|
||||
{subact.btnLabel && (
|
||||
<span className="text-[13px] capitalize font-medium">
|
||||
{t(
|
||||
`btnLabel.${subact.btnLabel}`
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</li>
|
||||
)
|
||||
)}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
@@ -1972,7 +2108,7 @@ const ReportTable = (props) => {
|
||||
</div>
|
||||
) : (
|
||||
<div className="m-[20px]">
|
||||
<div className="text-lg font-normal text-black">
|
||||
<div className="text-lg font-normal text-base-content">
|
||||
{t("save-as-template-?")}
|
||||
</div>
|
||||
<hr className="bg-[#ccc] mt-3" />
|
||||
@@ -2010,7 +2146,7 @@ const ReportTable = (props) => {
|
||||
</label>
|
||||
<input
|
||||
type="date"
|
||||
className="rounded-full mb-2 bg-base-300 w-full px-4 py-2 text-black border-2 hover:border-spacing-2"
|
||||
className="rounded-full mb-2 bg-base-300 w-full px-4 py-2 text-base-content border-2 hover:border-spacing-2"
|
||||
defaultValue={
|
||||
item?.ExpiryDate?.iso?.split("T")?.[0]
|
||||
}
|
||||
@@ -2120,7 +2256,7 @@ const ReportTable = (props) => {
|
||||
handleClose={handleClose}
|
||||
>
|
||||
<div className="m-[20px]">
|
||||
<div className="text-lg font-normal text-black">
|
||||
<div className="text-lg font-normal text-base-content">
|
||||
{t("delete-document-alert")}
|
||||
</div>
|
||||
<hr className="bg-[#ccc] mt-4" />
|
||||
@@ -2177,7 +2313,7 @@ const ReportTable = (props) => {
|
||||
{shareUrls.map((share, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="text-sm font-normal text-black flex my-2 justify-between items-center"
|
||||
className="text-sm font-normal text-base-content flex my-2 justify-between items-center"
|
||||
>
|
||||
<span className="w-[150px] mr-[5px] md:mr-0 md:w-[300px] whitespace-nowrap overflow-hidden text-ellipsis text-sm font-semibold">
|
||||
{share.email}
|
||||
@@ -2219,14 +2355,14 @@ const ReportTable = (props) => {
|
||||
handleClose={handleClose}
|
||||
>
|
||||
<div className="m-[20px]">
|
||||
<div className="text-sm md:text-lg font-normal text-black">
|
||||
<div className="text-sm md:text-lg font-normal text-base-content">
|
||||
{t("revoke-document-alert")}
|
||||
</div>
|
||||
<div className="mt-2">
|
||||
<textarea
|
||||
rows={3}
|
||||
placeholder="Reason (optional)"
|
||||
className="px-4 op-textarea op-textarea-bordered focus:outline-none hover:border-base-content w-full text-xs"
|
||||
className="px-4 op-textarea op-textarea-bordered text-base-content focus:outline-none hover:border-base-content w-full text-xs"
|
||||
value={reason}
|
||||
onChange={(e) => setReason(e.target.value)}
|
||||
></textarea>
|
||||
@@ -2337,7 +2473,7 @@ const ReportTable = (props) => {
|
||||
)}
|
||||
{Object?.keys(isNextStep) <= 0 && (
|
||||
<div className="flex justify-between items-center gap-2 my-2 px-3">
|
||||
<div className="text-black">
|
||||
<div className="text-base-content">
|
||||
{user?.signerPtr?.Name || "-"}{" "}
|
||||
{`<${
|
||||
user?.email
|
||||
|
||||
@@ -43,7 +43,7 @@ function CustomModal(props) {
|
||||
</h3>
|
||||
{!isExtendExpiry && (
|
||||
<div className="p-[10px] px-[20px]">
|
||||
<p className="text-[15px]">{props.bodyMssg && props.bodyMssg}</p>
|
||||
<p className="text-[15px] text-base-content">{props.bodyMssg && props.bodyMssg}</p>
|
||||
</div>
|
||||
)}
|
||||
{!isExtendExpiry && (
|
||||
@@ -68,7 +68,7 @@ function CustomModal(props) {
|
||||
)}
|
||||
{props.footerMessage && (
|
||||
<>
|
||||
<div className="mx-3">
|
||||
<div className="mx-3 text-base-content">
|
||||
<textarea
|
||||
rows={3}
|
||||
placeholder="Reason (optional)"
|
||||
@@ -108,7 +108,7 @@ function CustomModal(props) {
|
||||
</label>
|
||||
<input
|
||||
type="date"
|
||||
className="rounded-full bg-base-300 w-full px-4 py-2 text-black border-2 hover:border-spacing-2"
|
||||
className="rounded-full bg-base-300 w-full px-4 py-2 text-base-content border-2 hover:border-spacing-2"
|
||||
defaultValue={props?.doc?.ExpiryDate?.iso?.split("T")?.[0]}
|
||||
onChange={(e) => setExpiryDate(e.target.value)}
|
||||
/>
|
||||
|
||||
@@ -10,6 +10,10 @@
|
||||
background-position: right 0.7rem top 50%;
|
||||
background-size: 1rem auto;
|
||||
}
|
||||
[data-theme="opensigndark"] .validationlist{
|
||||
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAAZCAYAAADE6YVjAAAAAXNSR0IArs4c6QAAAQ1JREFUSEvtlDtKBTEARc/Fys8OXumndhl2ggt4jTuwsVJQK92DtY1Yuw3bp2jlDvxUcn2BDIQ4k2Sa1ziBNMmdHO4JGbGCoRUwmCCjLP8DXbYPgD3gVtLnkB/bm8AxsJD02Jfr1WX7EJjHD16BC0kf+QG2N4BzYDfu3Um6z3N/ILb3gbMs+AJcpo1igwDYybIh95Su9UFmwPVyrg+BCoBv4FTSexESNm1vRw1bGegNuAFOEkVdJABCi0VVVxewHS49aAve0/EDrGVrX8u1qz5AyBXfSaFRyhhs0IWqj7ECKjZohhTuqAlQ1ZU6yRo1A0ZBYqPw6I6AB0nPrX/J6p20HlTKTZBRFiddo3T9ArZOWBrGcf52AAAAAElFTkSuQmCC");
|
||||
}
|
||||
|
||||
|
||||
@media (max-width: 375px) {
|
||||
.validationlist {
|
||||
@@ -17,7 +21,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width:375px) and (max-width: 767px) {
|
||||
@media (min-width: 375px) and (max-width: 767px) {
|
||||
.validationlist {
|
||||
background-position: right 1rem top 50%;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
/* VS Code Dark Theme Improvements for OpenSign */
|
||||
|
||||
/* Better disabled button styling for dark mode */
|
||||
[data-theme="opensigndark"] {
|
||||
/* Primary button disabled state */
|
||||
.op-btn-primary:disabled {
|
||||
background-color: #3C3C3C !important;
|
||||
color: #CCCCCC !important;
|
||||
border-color: #565656 !important;
|
||||
opacity: 1 !important;
|
||||
cursor: not-allowed !important;
|
||||
}
|
||||
|
||||
.op-btn-primary:disabled:hover {
|
||||
background-color: #3C3C3C !important;
|
||||
color: #CCCCCC !important;
|
||||
border-color: #565656 !important;
|
||||
transform: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
/* Secondary button disabled state */
|
||||
.op-btn-secondary:disabled {
|
||||
background-color: #2A2A2A !important;
|
||||
color: #999999 !important;
|
||||
border-color: #444444 !important;
|
||||
opacity: 1 !important;
|
||||
cursor: not-allowed !important;
|
||||
}
|
||||
|
||||
/* Ghost button disabled state */
|
||||
.op-btn-ghost:disabled {
|
||||
background-color: transparent !important;
|
||||
color: #666666 !important;
|
||||
border-color: #444444 !important;
|
||||
opacity: 1 !important;
|
||||
cursor: not-allowed !important;
|
||||
}
|
||||
|
||||
/* Better icon visibility for various states */
|
||||
.icon-disabled,
|
||||
.fa-light.text-gray-400,
|
||||
.fa-light.text-gray-500 {
|
||||
color: #858585 !important;
|
||||
}
|
||||
|
||||
.icon-visible,
|
||||
.nav-icon,
|
||||
.folder-icon {
|
||||
color: #CCCCCC !important;
|
||||
}
|
||||
|
||||
/* Muted/inactive icons with better visibility */
|
||||
.muted-icon,
|
||||
.inactive-icon {
|
||||
color: #999999 !important;
|
||||
}
|
||||
|
||||
/* Hover states for better interactivity */
|
||||
.hover\\:bg-gray-200:hover {
|
||||
background-color: #2A2A2A !important;
|
||||
}
|
||||
|
||||
.hover\\:text-gray-600:hover {
|
||||
color: #E5E7EB !important;
|
||||
}
|
||||
|
||||
/* Form elements in disabled state */
|
||||
.op-input:disabled,
|
||||
.op-select:disabled,
|
||||
.op-textarea:disabled {
|
||||
background-color: #2A2A2A !important;
|
||||
color: #999999 !important;
|
||||
border-color: #444444 !important;
|
||||
cursor: not-allowed !important;
|
||||
}
|
||||
|
||||
/* Dropdown menu items */
|
||||
.dropdown-item {
|
||||
color: #E5E7EB !important;
|
||||
}
|
||||
|
||||
.dropdown-item:hover {
|
||||
background-color: #2A2A2A !important;
|
||||
color: #FFFFFF !important;
|
||||
}
|
||||
|
||||
/* Better text contrast for various elements */
|
||||
.text-gray-600 {
|
||||
color: #CCCCCC !important;
|
||||
}
|
||||
|
||||
.text-gray-500 {
|
||||
color: #999999 !important;
|
||||
}
|
||||
|
||||
.text-gray-400 {
|
||||
color: #858585 !important;
|
||||
}
|
||||
|
||||
/* Status indicators with better visibility */
|
||||
.status-badge {
|
||||
box-shadow: 0 2px 4px rgba(255, 255, 255, 0.1) !important;
|
||||
}
|
||||
|
||||
/* Tooltip improvements */
|
||||
.op-tooltip {
|
||||
background-color: #1F2937 !important;
|
||||
color: #E5E7EB !important;
|
||||
border-color: #4B5563 !important;
|
||||
}
|
||||
|
||||
/* Card and panel borders */
|
||||
.op-card,
|
||||
.border-gray-300 {
|
||||
border-color: #2C2C2C !important;
|
||||
}
|
||||
|
||||
/* Loading states */
|
||||
.opacity-50 {
|
||||
opacity: 0.7 !important;
|
||||
}
|
||||
|
||||
/* Focus states for better accessibility */
|
||||
.op-btn:focus-visible,
|
||||
.op-input:focus-visible,
|
||||
.op-select:focus-visible {
|
||||
outline: 2px solid #007ACC !important;
|
||||
outline-offset: 2px !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Ensure these styles don't affect light mode */
|
||||
[data-theme="opensigncss"] {
|
||||
/* Keep original colors for light mode */
|
||||
.icon-disabled {
|
||||
color: #9CA3AF;
|
||||
}
|
||||
|
||||
.icon-visible {
|
||||
color: #6B7280;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,26 @@
|
||||
/* Dark mode support for custom warning in Managesign */
|
||||
[data-theme="opensigndark"] .customwarning {
|
||||
background-color: #374151 !important;
|
||||
color: #E5E7EB !important;
|
||||
border-color: #4B5563 !important;
|
||||
}
|
||||
|
||||
[data-theme="opensigndark"] .customwarning::before {
|
||||
border-color: transparent transparent #4B5563 transparent !important;
|
||||
}
|
||||
|
||||
/* Dark mode support for signature management warning */
|
||||
[data-theme="opensigndark"] .signWarning {
|
||||
background-color: #374151 !important;
|
||||
color: #E5E7EB !important;
|
||||
}
|
||||
|
||||
/* Ensure the Managesign page background is consistent */
|
||||
[data-theme="opensigndark"] .managesign-container {
|
||||
background-color: #121212 !important;
|
||||
color: #F3F4F6 !important;
|
||||
}
|
||||
|
||||
.customwarning {
|
||||
position: absolute;
|
||||
padding: 8px;
|
||||
|
||||
@@ -204,6 +204,15 @@ a {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* Dark mode support for HoverCard */
|
||||
[data-theme="opensigndark"] .HoverCardContent {
|
||||
background-color: #1F2937;
|
||||
color: #E5E7EB;
|
||||
box-shadow:
|
||||
hsl(0 0% 0% / 50%) 0px 10px 38px -10px,
|
||||
hsl(0 0% 0% / 30%) 0px 10px 20px -15px;
|
||||
}
|
||||
|
||||
.HoverCardContent[data-side="top"] {
|
||||
animation-name: slideDownAndFade;
|
||||
}
|
||||
@@ -224,6 +233,11 @@ a {
|
||||
fill: white;
|
||||
}
|
||||
|
||||
/* Dark mode support for HoverCard arrow */
|
||||
[data-theme="opensigndark"] .HoverCardArrow {
|
||||
fill: #1F2937;
|
||||
}
|
||||
|
||||
@keyframes slideUpAndFade {
|
||||
0% {
|
||||
opacity: 0;
|
||||
|
||||
@@ -6,18 +6,21 @@
|
||||
.react-datepicker__input-container {
|
||||
position: initial !important;
|
||||
}
|
||||
.select-none-cls{
|
||||
|
||||
.select-none-cls {
|
||||
-webkit-user-select: none;
|
||||
/* Disable text selection in WebKit browsers */
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.widgets {
|
||||
-webkit-user-select: none;
|
||||
/* Disable text selection in WebKit browsers */
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.radioButton {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@@ -33,14 +36,28 @@
|
||||
width: 440px;
|
||||
height: 167px;
|
||||
}
|
||||
.tabWidth{
|
||||
|
||||
.tabWidth {
|
||||
border: 1px solid #f3f4f6;
|
||||
background-color: #f3f4f6;
|
||||
width: 440px;
|
||||
}
|
||||
|
||||
.mysignatureCanvas {
|
||||
width: 456px;
|
||||
height: 180px;
|
||||
}
|
||||
|
||||
[data-theme="opensigndark"] .tabWidth {
|
||||
border: 1px solid #1f2937 !important;
|
||||
background-color: #1f2937 !important;
|
||||
}
|
||||
|
||||
.intialSignatureCanvas {
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
|
||||
.checked-radio::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
@@ -49,15 +66,55 @@
|
||||
width: 50%;
|
||||
height: 50%;
|
||||
border-radius: 9999px;
|
||||
background-color: #111111; /* blue-500 */
|
||||
background-color: #111111;
|
||||
/* blue-500 */
|
||||
}
|
||||
|
||||
|
||||
.intialSignature {
|
||||
border: 2px solid #888;
|
||||
background-color: rgb(255, 255, 255);
|
||||
width: 183px;
|
||||
height: 183px;
|
||||
width: 180px;
|
||||
height: 180px;
|
||||
}
|
||||
|
||||
/* Dark mode support for initials box in /managesign */
|
||||
[data-theme="opensigndark"] .intialSignature {
|
||||
background-color: #1f2937 !important;
|
||||
border-color: #4b5563 !important;
|
||||
}
|
||||
|
||||
[data-theme="opensigndark"] .intialSignatureCanvas {
|
||||
background-color: #1f2937 !important;
|
||||
border-color: #4b5563 !important;
|
||||
}
|
||||
|
||||
/* Also support signature canvas for consistency */
|
||||
[data-theme="opensigndark"] .signatureCanvas {
|
||||
background-color: #1f2937 !important;
|
||||
border-color: #4b5563 !important;
|
||||
}
|
||||
|
||||
/* Dark mode support for initials box in /managesign */
|
||||
[data-theme="opensigndark"] .intialSignature {
|
||||
background-color: #1f2937 !important;
|
||||
border-color: #4b5563 !important;
|
||||
}
|
||||
|
||||
[data-theme="opensigndark"] .intialSignatureCanvas {
|
||||
background-color: #1f2937 !important;
|
||||
border-color: #4b5563 !important;
|
||||
}
|
||||
|
||||
/* Also support signature canvas for consistency */
|
||||
[data-theme="opensigndark"] .signatureCanvas {
|
||||
background-color: #1f2937 !important;
|
||||
border-color: #4b5563 !important;
|
||||
}
|
||||
|
||||
/* Also support signature canvas for consistency */
|
||||
[data-theme="opensigndark"] .mysignatureCanvas {
|
||||
background-color: #1f2937 !important;
|
||||
border-color: #4b5563 !important;
|
||||
}
|
||||
|
||||
.penContainerDefault {
|
||||
@@ -75,6 +132,7 @@
|
||||
.ScrollbarsCustom-TrackY {
|
||||
width: 4px !important;
|
||||
}
|
||||
|
||||
.ScrollbarsCustom-TrackX {
|
||||
height: 4px !important;
|
||||
}
|
||||
@@ -103,6 +161,7 @@
|
||||
overflow: hidden !important;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.disabled {
|
||||
opacity: 0.5;
|
||||
/* Example: reduce opacity to visually indicate disabled state */
|
||||
@@ -391,7 +450,7 @@ option {
|
||||
to prevent sudden quick movement (as the
|
||||
navigation bar gets a new position at the top of the
|
||||
page (position:fixed and top:0) */
|
||||
.stickyHead + .content {
|
||||
.stickyHead+.content {
|
||||
padding-top: 60px;
|
||||
}
|
||||
}
|
||||
@@ -401,7 +460,13 @@ option {
|
||||
width: 300px;
|
||||
height: 120px;
|
||||
}
|
||||
.tabWidth{
|
||||
|
||||
.mysignatureCanvas {
|
||||
width: 300px;
|
||||
height: 118px;
|
||||
}
|
||||
|
||||
.tabWidth {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
@@ -422,12 +487,19 @@ option {
|
||||
.scroll-hide::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 350px) and (min-width: 311px) {
|
||||
.signatureCanvas {
|
||||
width: 280px;
|
||||
height: 112px;
|
||||
}
|
||||
.tabWidth{
|
||||
|
||||
.mysignatureCanvas {
|
||||
width: 280px;
|
||||
height: 111px;
|
||||
}
|
||||
|
||||
.tabWidth {
|
||||
width: 280px;
|
||||
}
|
||||
|
||||
@@ -451,7 +523,13 @@ option {
|
||||
width: 230px;
|
||||
height: 92px;
|
||||
}
|
||||
.tabWidth{
|
||||
|
||||
.mysignatureCanvas {
|
||||
width: 230px;
|
||||
height: 91px;
|
||||
}
|
||||
|
||||
.tabWidth {
|
||||
width: 230px;
|
||||
}
|
||||
|
||||
@@ -467,4 +545,4 @@ option {
|
||||
.uploadImgLogo {
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,11 +6,67 @@ module.exports = {
|
||||
},
|
||||
plugins: [
|
||||
require("daisyui"),
|
||||
function ({ addUtilities }) {
|
||||
function ({ addUtilities, theme }) {
|
||||
addUtilities({
|
||||
// Prevent iOS long-press popup
|
||||
".touch-callout-none": {
|
||||
"-webkit-touch-callout": "none"
|
||||
},
|
||||
// VS Code-style disabled button for all themes
|
||||
".op-btn-vscode-disabled": {
|
||||
"background-color": "#3C3C3C !important",
|
||||
color: "#CCCCCC !important",
|
||||
"border-color": "#565656 !important",
|
||||
cursor: "not-allowed !important",
|
||||
opacity: "1 !important",
|
||||
"&:hover": {
|
||||
"background-color": "#3C3C3C !important",
|
||||
color: "#CCCCCC !important",
|
||||
"border-color": "#565656 !important",
|
||||
transform: "none !important"
|
||||
}
|
||||
},
|
||||
// Dark mode icon improvements using DaisyUI theme detection
|
||||
'[data-theme="opensigndark"] .icon-improved': {
|
||||
color: "#CCCCCC !important"
|
||||
},
|
||||
'[data-theme="opensigndark"] .icon-muted': {
|
||||
color: "#999999 !important"
|
||||
},
|
||||
'[data-theme="opensigndark"] .icon-disabled': {
|
||||
color: "#858585 !important"
|
||||
},
|
||||
// Gray text improvements for dark mode
|
||||
'[data-theme="opensigndark"] .text-gray-500': {
|
||||
color: "#CCCCCC !important"
|
||||
},
|
||||
'[data-theme="opensigndark"] .text-gray-400': {
|
||||
color: "#999999 !important"
|
||||
},
|
||||
'[data-theme="opensigndark"] .text-gray-600': {
|
||||
color: "#CCCCCC !important"
|
||||
},
|
||||
// CSS variable utilities that work with arbitrary values
|
||||
".icon-themed": {
|
||||
color: "var(--icon-color)"
|
||||
},
|
||||
".icon-themed-muted": {
|
||||
color: "var(--icon-color-muted)"
|
||||
},
|
||||
".icon-themed-disabled": {
|
||||
color: "var(--icon-color-disabled)"
|
||||
},
|
||||
".btn-themed-disabled": {
|
||||
"background-color": "var(--btn-disabled-bg)",
|
||||
color: "var(--btn-disabled-color)",
|
||||
"border-color": "var(--btn-disabled-border)",
|
||||
cursor: "not-allowed",
|
||||
"&:hover": {
|
||||
"background-color": "var(--btn-disabled-bg)",
|
||||
color: "var(--btn-disabled-color)",
|
||||
"border-color": "var(--btn-disabled-border)",
|
||||
transform: "none"
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -18,7 +74,48 @@ module.exports = {
|
||||
daisyui: {
|
||||
// themes: true,
|
||||
themes: [
|
||||
"dark",
|
||||
{
|
||||
opensigndark: {
|
||||
primary: "#007ACC", // VS Code blue - CTA & highlight color
|
||||
"primary-content": "#FFFFFF",
|
||||
|
||||
secondary: "#1F2937", // Sidebar background (darker slate)
|
||||
"secondary-content": "#E5E7EB",
|
||||
|
||||
accent: "#4A9EFF", // Lighter VS Code blue for hover, minor CTA
|
||||
"accent-content": "#FFFFFF",
|
||||
|
||||
neutral: "#3C3C3C", // VS Code inactive/disabled element background
|
||||
"neutral-content": "#CCCCCC", // VS Code inactive text color
|
||||
|
||||
"base-100": "#121212", // App background
|
||||
"base-200": "#181818", // Slight elevation (cards)
|
||||
"base-300": "#1E1E1E", // Further elevated items (panels)
|
||||
"base-content": "#F3F4F6", // Main text color (soft white)
|
||||
|
||||
info: "#2563EB", // For info panels like "Out for signature"
|
||||
success: "#22C55E", // Optional: for completed docs or alerts
|
||||
warning: "#FBBF24",
|
||||
error: "#EF4444",
|
||||
|
||||
"--rounded-btn": "1.9rem",
|
||||
"--tab-border": "2px",
|
||||
"--tab-radius": "0.7rem",
|
||||
|
||||
// Custom CSS variables for icon and button states
|
||||
"--icon-color": "#CCCCCC",
|
||||
"--icon-color-muted": "#999999",
|
||||
"--icon-color-disabled": "#858585",
|
||||
"--btn-disabled-bg": "#3C3C3C",
|
||||
"--btn-disabled-color": "#CCCCCC",
|
||||
"--btn-disabled-border": "#565656",
|
||||
|
||||
// Optional polish
|
||||
"--navbar-padding": "0.8rem",
|
||||
"--border-color": "#2C2C2C", // Card/table separation
|
||||
"--tooltip-color": "#1F2937"
|
||||
}
|
||||
},
|
||||
{
|
||||
opensigncss: {
|
||||
primary: "#002864",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { defineConfig } from "vite";
|
||||
import { defineConfig, splitVendorChunkPlugin } from "vite";
|
||||
import react from "@vitejs/plugin-react";
|
||||
import svgr from "vite-plugin-svgr";
|
||||
import { resolve } from "path";
|
||||
@@ -12,7 +12,8 @@ export default defineConfig(({ command, mode }) => {
|
||||
return {
|
||||
plugins: [
|
||||
react(),
|
||||
svgr() // Transform SVGs into React components
|
||||
svgr(), // Transform SVGs into React components
|
||||
splitVendorChunkPlugin()
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
FROM node:22.14.0
|
||||
|
||||
|
||||
# Install LibreOffice for DOCX to PDF conversions
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y libreoffice \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Set the working directory inside the container
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import axios from 'axios';
|
||||
dotenv.config();
|
||||
|
||||
export const cloudServerUrl = 'http://localhost:8080/app';
|
||||
export const serverAppId = process.env.APP_ID || 'opensign';
|
||||
export const appName = 'OpenSign™';
|
||||
|
||||
export const MAX_NAME_LENGTH = 250;
|
||||
@@ -288,6 +289,8 @@ export const selectFormat = data => {
|
||||
return 'MMMM dd, yyyy';
|
||||
case 'DD MMMM, YYYY':
|
||||
return 'dd MMMM, yyyy';
|
||||
case 'DD.MM.YYYY':
|
||||
return 'dd.MM.yyyy';
|
||||
default:
|
||||
return 'MM/dd/yyyy';
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ import express from 'express';
|
||||
import cors from 'cors';
|
||||
import dotenv from 'dotenv';
|
||||
import uploadFile from './uploadFile.js';
|
||||
import docxtopdf, { upload as docxUpload } from './docxtopdf.js';
|
||||
import decryptpdf, { upload as decryptUpload } from './decryptpdf.js';
|
||||
|
||||
export const app = express();
|
||||
|
||||
@@ -11,4 +13,5 @@ app.use(express.json({ limit: '50mb' }));
|
||||
app.use(express.urlencoded({ limit: '50mb', extended: true }));
|
||||
|
||||
app.post('/file_upload', uploadFile);
|
||||
|
||||
app.post('/docxtopdf', docxUpload.single('file'), docxtopdf);
|
||||
app.post('/decryptpdf', decryptUpload.single('file'), decryptpdf);
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import fs from 'node:fs';
|
||||
import multer from 'multer';
|
||||
import Coherentpdf from 'coherentpdf';
|
||||
|
||||
const storage = multer.diskStorage({
|
||||
destination(req, file, cb) {
|
||||
cb(null, 'exports');
|
||||
},
|
||||
filename(req, file, cb) {
|
||||
cb(null, file.originalname);
|
||||
},
|
||||
});
|
||||
|
||||
export const upload = multer({ storage });
|
||||
|
||||
export default async function decryptpdf(req, res) {
|
||||
const inputPath = req.file.path;
|
||||
const password = req.body.password || '';
|
||||
try {
|
||||
const file = fs.readFileSync(inputPath);
|
||||
const pdf = await Coherentpdf.fromMemory(file, password);
|
||||
await Coherentpdf.decryptPdf(pdf, password);
|
||||
// Get decrypted buffer directly from memory (no file I/O)
|
||||
const buffer = await Coherentpdf.toMemory(pdf, false, false);
|
||||
res.set({
|
||||
'Content-Type': 'application/pdf',
|
||||
'Content-Disposition': 'inline; filename="decrypted.pdf"',
|
||||
'Content-Length': buffer.length,
|
||||
});
|
||||
res.send(buffer);
|
||||
fs.unlink(inputPath, () => {});
|
||||
} catch (err) {
|
||||
fs.unlink(inputPath, () => {});
|
||||
console.log('Error in decrypt file: ', err);
|
||||
let code = err?.code ? err.code : 400;
|
||||
let message = err?.[2]?.c ? err[2].c : 'Something went wrong.';
|
||||
if (err?.[2]?.c?.includes('Bad password') || err?.[2]?.c?.includes('decrypt_pdf_inner')) {
|
||||
code = 401;
|
||||
message = 'Incorrect password.';
|
||||
}
|
||||
return res.status(code).json({ error: message });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
import fs from 'node:fs';
|
||||
import axios from 'axios';
|
||||
import multer from 'multer';
|
||||
import libre from 'libreoffice-convert';
|
||||
import util from 'node:util';
|
||||
import { cloudServerUrl, getSecureUrl, serverAppId } from '../../Utils.js';
|
||||
|
||||
libre.convertAsync = util.promisify(libre.convert);
|
||||
|
||||
const storage = multer.diskStorage({
|
||||
destination(req, file, cb) {
|
||||
cb(null, 'exports');
|
||||
},
|
||||
filename(req, file, cb) {
|
||||
cb(null, file.originalname);
|
||||
},
|
||||
});
|
||||
|
||||
export const upload = multer({ storage });
|
||||
|
||||
function generatePdfName(length) {
|
||||
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
let result = '';
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export default async function docxtopdf(req, res) {
|
||||
const serverUrl = cloudServerUrl;
|
||||
const appId = serverAppId;
|
||||
const masterKey = process.env.MASTER_KEY;
|
||||
const inputPath = req.file.path;
|
||||
const name = generatePdfName(16);
|
||||
const fileName = `${name}.pdf`;
|
||||
const outputPath = './exports/output.pdf';
|
||||
|
||||
try {
|
||||
const userRes = await axios.get(serverUrl + '/users/me', {
|
||||
headers: {
|
||||
'X-Parse-Application-Id': appId,
|
||||
'X-Parse-Session-Token': req.headers['sessiontoken'],
|
||||
},
|
||||
});
|
||||
const userId = JSON.stringify({
|
||||
UserId: {
|
||||
__type: 'Pointer',
|
||||
className: '_User',
|
||||
objectId: userRes.data.objectId,
|
||||
},
|
||||
});
|
||||
const resUser = await axios.get(
|
||||
serverUrl + `/classes/contracts_Users?where=${userId}&limit=1&include=TenantId`,
|
||||
{
|
||||
headers: {
|
||||
'X-Parse-Application-Id': appId,
|
||||
'X-Parse-Master-Key': masterKey,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (resUser?.data?.results?.length > 0) {
|
||||
const tenantId = resUser.data.results[0].TenantId?.objectId;
|
||||
const ext = '.pdf';
|
||||
const outPath = `./exports/output${ext}`;
|
||||
const docxBuf = fs.readFileSync(inputPath);
|
||||
const pdfBuffer = await libre.convertAsync(docxBuf, ext, undefined);
|
||||
fs.writeFileSync(outPath, pdfBuffer);
|
||||
const file = fs.readFileSync(outPath);
|
||||
const size = fs.statSync(outPath).size;
|
||||
const PartnersTenant = JSON.stringify({
|
||||
PartnersTenant: {
|
||||
__type: 'Pointer',
|
||||
className: 'partners_Tenant',
|
||||
objectId: tenantId,
|
||||
},
|
||||
});
|
||||
const resTenantCredit = await axios.get(
|
||||
serverUrl + `/classes/partners_TenantCredits?where=${PartnersTenant}&limit=1`,
|
||||
{
|
||||
headers: {
|
||||
'X-Parse-Application-Id': appId,
|
||||
'X-Parse-Master-Key': masterKey,
|
||||
},
|
||||
}
|
||||
);
|
||||
if (resTenantCredit.data?.results?.length > 0) {
|
||||
const tenantCreditsId = resTenantCredit.data.results[0].objectId;
|
||||
const activeFileAdapter = resUser.data.results[0].TenantId?.ActiveFileAdapter;
|
||||
let fileUrl;
|
||||
if (activeFileAdapter) {
|
||||
const params = {
|
||||
fileBase64: file.toString('base64'),
|
||||
fileName,
|
||||
id: activeFileAdapter,
|
||||
};
|
||||
const url = serverUrl + '/functions/savetofileadapter';
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Parse-Application-Id': appId,
|
||||
'X-Parse-Session-Token': req.headers['sessiontoken'],
|
||||
};
|
||||
try {
|
||||
const savetos3 = await axios.post(url, params, { headers });
|
||||
fileUrl = savetos3?.data?.result?.url;
|
||||
} catch (err) {
|
||||
console.log('err in save to customfile', err);
|
||||
}
|
||||
} else {
|
||||
const parsefile = await axios.post(serverUrl + `/files/${fileName}`, file, {
|
||||
headers: {
|
||||
'X-Parse-Application-Id': appId,
|
||||
'X-Parse-Master-Key': masterKey,
|
||||
'Content-Type': 'application/pdf',
|
||||
},
|
||||
});
|
||||
const fileRes = getSecureUrl(parsefile.data.url);
|
||||
fileUrl = fileRes.url;
|
||||
}
|
||||
const usedStorage = resTenantCredit.data.results[0].usedStorage
|
||||
? resTenantCredit.data.results[0].usedStorage + size
|
||||
: size;
|
||||
await axios.put(
|
||||
serverUrl + `/classes/partners_TenantCredits/${tenantCreditsId}`,
|
||||
{ usedStorage },
|
||||
{
|
||||
headers: {
|
||||
'X-Parse-Application-Id': appId,
|
||||
'X-Parse-Master-Key': masterKey,
|
||||
},
|
||||
}
|
||||
);
|
||||
await axios.post(
|
||||
serverUrl + '/classes/partners_DataFiles',
|
||||
{
|
||||
FileSize: size,
|
||||
FileUrl: fileUrl,
|
||||
TenantPtr: {
|
||||
__type: 'Pointer',
|
||||
className: 'partners_Tenant',
|
||||
objectId: tenantId,
|
||||
},
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
'X-Parse-Application-Id': appId,
|
||||
'X-Parse-Master-Key': masterKey,
|
||||
},
|
||||
}
|
||||
);
|
||||
[inputPath, outPath].forEach(p => fs.existsSync(p) && fs.unlinkSync(p));
|
||||
return res.status(200).json({ message: 'success.', url: fileUrl });
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
[inputPath, outputPath].forEach(p => fs.existsSync(p) && fs.unlinkSync(p));
|
||||
const msg =
|
||||
err?.response?.data?.error || err?.response?.data || err?.message || 'Something went wrong.';
|
||||
console.log(`Error converting file: ${msg}`);
|
||||
|
||||
const message =
|
||||
'We are currently experiencing some issues with processing DOCX files. Please upload the PDF version or contact us on support@opensignlabs.com';
|
||||
return res.status(400).json({ error: message });
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import multer from 'multer';
|
||||
import multerS3 from 'multer-s3';
|
||||
import aws from 'aws-sdk';
|
||||
import dotenv from 'dotenv';
|
||||
import { cloudServerUrl, useLocal } from '../../Utils.js';
|
||||
import { cloudServerUrl, serverAppId, useLocal } from '../../Utils.js';
|
||||
dotenv.config();
|
||||
|
||||
function sanitizeFileName(fileName) {
|
||||
@@ -50,7 +50,7 @@ async function uploadFile(req, res) {
|
||||
const DO_SPACE = process.env.DO_SPACE;
|
||||
|
||||
const parseBaseUrl = cloudServerUrl; //process.env.SERVER_URL;
|
||||
const parseAppId = process.env.APP_ID;
|
||||
const parseAppId = serverAppId;
|
||||
let fileStorage;
|
||||
if (useLocal === 'true') {
|
||||
fileStorage = multer.diskStorage({
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import axios from 'axios';
|
||||
import { cloudServerUrl } from '../../Utils.js';
|
||||
import { cloudServerUrl, serverAppId } from '../../Utils.js';
|
||||
const serverUrl = cloudServerUrl; //process.env.SERVER_URL;
|
||||
const APPID = process.env.APP_ID;
|
||||
const APPID = serverAppId;
|
||||
const masterKEY = process.env.MASTER_KEY;
|
||||
async function addTeamAndOrg(extUser) {
|
||||
try {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import axios from 'axios';
|
||||
import { cloudServerUrl } from '../../Utils.js';
|
||||
import { cloudServerUrl, serverAppId } from '../../Utils.js';
|
||||
async function AuthLoginAsMail(request) {
|
||||
try {
|
||||
//function for login user using user objectId without touching user's password
|
||||
const serverUrl = cloudServerUrl; //process.env.SERVER_URL;
|
||||
const APPID = process.env.APP_ID;
|
||||
const APPID = serverAppId;
|
||||
const masterKEY = process.env.MASTER_KEY;
|
||||
|
||||
let otpN = request.params.otp;
|
||||
@@ -92,7 +92,7 @@ async function AuthLoginAsMail(request) {
|
||||
} catch (err) {
|
||||
console.log('err in Auth');
|
||||
console.log(err);
|
||||
return 'Result not found', err;
|
||||
return 'Result not found';
|
||||
}
|
||||
}
|
||||
export default AuthLoginAsMail;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import axios from 'axios';
|
||||
import { appName, cloudServerUrl } from '../../Utils.js';
|
||||
import { appName, cloudServerUrl, serverAppId } from '../../Utils.js';
|
||||
|
||||
export default async function forwardDoc(request) {
|
||||
try {
|
||||
@@ -58,7 +58,7 @@ export default async function forwardDoc(request) {
|
||||
mailRes = await axios.post(`${cloudServerUrl}/functions/sendmailv3`, params, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Parse-Application-Id': process.env.APP_ID,
|
||||
'X-Parse-Application-Id': serverAppId,
|
||||
'X-Parse-Master-Key': process.env.MASTER_KEY,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import axios from 'axios';
|
||||
import { cloudServerUrl } from '../../Utils.js';
|
||||
import { cloudServerUrl, serverAppId } from '../../Utils.js';
|
||||
|
||||
export default async function GetTemplate(request) {
|
||||
const serverUrl = cloudServerUrl; //process.env.SERVER_URL;
|
||||
@@ -10,7 +10,7 @@ export default async function GetTemplate(request) {
|
||||
if (sessiontoken) {
|
||||
const userRes = await axios.get(serverUrl + '/users/me', {
|
||||
headers: {
|
||||
'X-Parse-Application-Id': process.env.APP_ID,
|
||||
'X-Parse-Application-Id': serverAppId,
|
||||
'X-Parse-Session-Token': sessiontoken,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import axios from 'axios';
|
||||
import { cloudServerUrl } from '../../Utils.js';
|
||||
const appId = process.env.APP_ID;
|
||||
import { cloudServerUrl, serverAppId } from '../../Utils.js';
|
||||
const appId = serverAppId;
|
||||
const masterkey = process.env.MASTER_KEY;
|
||||
export default async function createBatchContact(req) {
|
||||
if (!req?.user) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import axios from 'axios';
|
||||
import { cloudServerUrl, mailTemplate, replaceMailVaribles } from '../../Utils.js';
|
||||
import { cloudServerUrl, mailTemplate, replaceMailVaribles, serverAppId } from '../../Utils.js';
|
||||
const serverUrl = cloudServerUrl; //process.env.SERVER_URL;
|
||||
const appId = process.env.APP_ID;
|
||||
const appId = serverAppId;
|
||||
async function deductcount(docsCount, extUserId) {
|
||||
try {
|
||||
const extCls = new Parse.Object('contracts_Users');
|
||||
@@ -15,8 +15,6 @@ async function deductcount(docsCount, extUserId) {
|
||||
async function sendMail(document, publicUrl) {
|
||||
//sessionToken
|
||||
const baseUrl = new URL(publicUrl);
|
||||
|
||||
// console.log("pdfDetails", pdfDetails);
|
||||
const timeToCompleteDays = document?.TimeToCompleteDays || 15;
|
||||
const ExpireDate = new Date(document.createdAt);
|
||||
ExpireDate.setDate(ExpireDate.getDate() + timeToCompleteDays);
|
||||
@@ -86,7 +84,7 @@ async function sendMail(document, publicUrl) {
|
||||
};
|
||||
let params = {
|
||||
extUserId: document.ExtUserPtr.objectId,
|
||||
recipient: objectId ? existSigner?.Email : signerMail[i].email,
|
||||
recipient: existSigner?.Email || signerMail[i].email,
|
||||
subject: replaceVar?.subject ? replaceVar?.subject : mailTemplate(mailparam).subject,
|
||||
from: document.ExtUserPtr.Email,
|
||||
replyto: senderEmail || '',
|
||||
@@ -168,9 +166,9 @@ async function batchQuery(userId, Documents, Ip, parseConfig, type, publicUrl) {
|
||||
})),
|
||||
ACL: Acl,
|
||||
SentToOthers: true,
|
||||
RemindOnceInEvery: x.RemindOnceInEvery || 5,
|
||||
RemindOnceInEvery: x.RemindOnceInEvery ? parseInt(x.RemindOnceInEvery) : 5,
|
||||
AutomaticReminders: x.AutomaticReminders || false,
|
||||
TimeToCompleteDays: x.TimeToCompleteDays || 15,
|
||||
TimeToCompleteDays: x.TimeToCompleteDays ? parseInt(x.TimeToCompleteDays) : 15,
|
||||
OriginIp: Ip,
|
||||
DocSentAt: { __type: 'Date', iso: isoDate },
|
||||
IsEnableOTP: x?.IsEnableOTP || false,
|
||||
@@ -231,6 +229,7 @@ export default async function createBatchDocs(request) {
|
||||
const sessionToken = request.headers?.sessiontoken;
|
||||
const type = request.headers?.type || 'quicksend';
|
||||
const Documents = JSON.parse(strDocuments);
|
||||
|
||||
const Ip = request?.headers?.['x-real-ip'] || '';
|
||||
// Access the host from the headers
|
||||
const publicUrl = request.headers.public_url;
|
||||
|
||||
@@ -65,7 +65,7 @@ export default async function generateCertificatebydocId(req) {
|
||||
const certificate = await GenerateCertificate(doc);
|
||||
const certificatePdf = await PDFDocument.load(certificate);
|
||||
const p12 = new P12Signer(P12Buffer, { passphrase: process.env.PASS_PHRASE || null });
|
||||
// `pdflibAddPlaceholder` is used to add code of only digitial sign in certificate
|
||||
// `pdflibAddPlaceholder` is used to add code of only digital sign in certificate
|
||||
pdflibAddPlaceholder({
|
||||
pdfDoc: certificatePdf,
|
||||
reason: `Digitally signed by ${eSignName}.`,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import axios from 'axios';
|
||||
import { cloudServerUrl } from '../../Utils.js';
|
||||
import { cloudServerUrl, serverAppId } from '../../Utils.js';
|
||||
export default async function getDocument(request) {
|
||||
const serverUrl = cloudServerUrl; //process.env.SERVER_URL;
|
||||
const docId = request.params.docId;
|
||||
@@ -30,7 +30,7 @@ export default async function getDocument(request) {
|
||||
try {
|
||||
const userRes = await axios.get(serverUrl + '/users/me', {
|
||||
headers: {
|
||||
'X-Parse-Application-Id': process.env.APP_ID,
|
||||
'X-Parse-Application-Id': serverAppId,
|
||||
'X-Parse-Session-Token': sessiontoken,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import axios from 'axios';
|
||||
import { cloudServerUrl } from '../../Utils.js';
|
||||
import { cloudServerUrl, serverAppId } from '../../Utils.js';
|
||||
export default async function getDrive(request) {
|
||||
const serverUrl = cloudServerUrl; //process.env.SERVER_URL;
|
||||
const appId = process.env.APP_ID;
|
||||
const appId = serverAppId;
|
||||
const limit = request.params.limit;
|
||||
const skip = request.params.skip;
|
||||
const docId = request.params.docId;
|
||||
|
||||
@@ -1,20 +1,27 @@
|
||||
import { cloudServerUrl } from '../../Utils.js';
|
||||
import { cloudServerUrl, serverAppId } from '../../Utils.js';
|
||||
import reportJson from './reportsJson.js';
|
||||
import axios from 'axios';
|
||||
|
||||
// Escape regex special characters. Copied from filterDocs.js
|
||||
function escapeRegExp(str) {
|
||||
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
}
|
||||
|
||||
export default async function getReport(request) {
|
||||
const reportId = request.params.reportId;
|
||||
const limit = request.params.limit;
|
||||
const skip = request.params.skip;
|
||||
const searchTerm = request.params.searchTerm || '';
|
||||
|
||||
const serverUrl = cloudServerUrl; //process.env.SERVER_URL;
|
||||
const appId = process.env.APP_ID;
|
||||
const appId = serverAppId;
|
||||
const masterKey = process.env.MASTER_KEY;
|
||||
const sessionToken = request.headers['sessiontoken'] || request.headers['x-parse-session-token'];
|
||||
try {
|
||||
const userRes = await axios.get(serverUrl + '/users/me', {
|
||||
headers: {
|
||||
'X-Parse-Application-Id': appId,
|
||||
'X-Parse-Session-Token': request.headers['sessiontoken'],
|
||||
'X-Parse-Session-Token': sessionToken,
|
||||
},
|
||||
});
|
||||
const userId = userRes.data && userRes.data.objectId;
|
||||
@@ -25,7 +32,7 @@ export default async function getReport(request) {
|
||||
const { params, keys } = json;
|
||||
const orderBy = '-updatedAt';
|
||||
const strKeys = keys.join();
|
||||
let strParams = JSON.stringify(params);
|
||||
let paramsObj = { ...params };
|
||||
if (reportId == '6TeaPr321t') {
|
||||
const extUserQuery = new Parse.Query('contracts_Users');
|
||||
extUserQuery.equalTo('Email', userRes.data.email);
|
||||
@@ -36,8 +43,8 @@ export default async function getReport(request) {
|
||||
if (_extUser?.TeamIds && _extUser.TeamIds?.length > 0) {
|
||||
let teamArr = [];
|
||||
_extUser?.TeamIds?.forEach(x => (teamArr = [...teamArr, ...x.Ancestors]));
|
||||
strParams = JSON.stringify({
|
||||
...params,
|
||||
paramsObj = {
|
||||
...paramsObj,
|
||||
$or: [
|
||||
{ SharedWith: { $in: teamArr } },
|
||||
{
|
||||
@@ -55,15 +62,23 @@ export default async function getReport(request) {
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
};
|
||||
} else {
|
||||
strParams = JSON.stringify({
|
||||
...params,
|
||||
paramsObj = {
|
||||
...paramsObj,
|
||||
CreatedBy: { __type: 'Pointer', className: '_User', objectId: userId },
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
if (searchTerm) {
|
||||
const escaped = escapeRegExp(searchTerm);
|
||||
paramsObj = {
|
||||
...paramsObj,
|
||||
Name: { $regex: `.*${escaped}.*`, $options: 'i' },
|
||||
};
|
||||
}
|
||||
const strParams = JSON.stringify(paramsObj);
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Parse-Application-Id': appId,
|
||||
|
||||
@@ -1,25 +1,5 @@
|
||||
// `saveRoleContact` is used to save user in contracts_Guest role and create contact
|
||||
const saveRoleContact = async contact => {
|
||||
try {
|
||||
const Role = new Parse.Query(Parse.Role);
|
||||
const guestRole = await Role.equalTo('name', 'contracts_Guest').first();
|
||||
if (guestRole) {
|
||||
// Check if the user is already in the role
|
||||
const relation = guestRole.relation('users');
|
||||
const usersInRoleQuery = relation.query();
|
||||
usersInRoleQuery.equalTo('objectId', contact.UserId.objectId);
|
||||
const usersInRole = await usersInRoleQuery.find();
|
||||
if (usersInRole.length > 0) {
|
||||
console.log('User already added to Guest role.');
|
||||
} else {
|
||||
relation.add({ __type: 'Pointer', className: '_User', id: contact.UserId.objectId });
|
||||
await guestRole.save(null, { useMasterKey: true });
|
||||
// console.log('User added to Guest role successfully.');
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('err in role save', err);
|
||||
}
|
||||
const contactQuery = new Parse.Object('contracts_Contactbook');
|
||||
contactQuery.set('Name', contact.Name);
|
||||
contactQuery.set('Email', contact.Email);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user