mirror of
https://github.com/OpenSignLabs/OpenSign.git
synced 2026-09-05 01:07:39 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7204183542 | ||
|
|
5561673fdc | ||
|
|
0df44062d6 | ||
|
|
8983440763 | ||
|
|
5a28c0ed06 | ||
|
|
cbca22c308 | ||
|
|
b0e0b38e8f | ||
|
|
49c23fb52b | ||
|
|
da4b604710 | ||
|
|
82d518b3c9 | ||
|
|
dcbde2b661 | ||
|
|
27b0b426ad | ||
|
|
8788afaa8e | ||
|
|
428aa65b38 | ||
|
|
295d942427 | ||
|
|
c4e28e6e6e | ||
|
|
b19fb29b82 | ||
|
|
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 -
|
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
|
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 -
|
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.
|
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.
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
# Use an official Node runtime as the base image
|
|
||||||
FROM node:18
|
|
||||||
|
|
||||||
# Set the working directory inside the container
|
|
||||||
WORKDIR /usr/src/app
|
|
||||||
|
|
||||||
# Copy package.json and package-lock.json first to leverage Docker cache
|
|
||||||
COPY ./package*.json ./
|
|
||||||
|
|
||||||
# Install application dependencies
|
|
||||||
RUN npm install
|
|
||||||
|
|
||||||
# Copy the current directory contents into the container
|
|
||||||
COPY ./ .
|
|
||||||
COPY ./.husky .
|
|
||||||
|
|
||||||
# Make port 3000 available to the world outside this container
|
|
||||||
EXPOSE 3000
|
|
||||||
|
|
||||||
# Define environment variables if needed
|
|
||||||
# ENV NODE_ENV production
|
|
||||||
|
|
||||||
# Run the application
|
|
||||||
ENTRYPOINT npm run start
|
|
||||||
|
|
||||||
@@ -13,6 +13,10 @@ RUN npm install
|
|||||||
# Copy the current directory contents into the container
|
# Copy the current directory contents into the container
|
||||||
COPY apps/OpenSign/ .
|
COPY apps/OpenSign/ .
|
||||||
COPY apps/OpenSign/.husky .
|
COPY apps/OpenSign/.husky .
|
||||||
|
COPY apps/OpenSign/entrypoint.sh .
|
||||||
|
|
||||||
|
# make the entrypoint.sh file executable
|
||||||
|
RUN chmod +x entrypoint.sh
|
||||||
|
|
||||||
# Define environment variables if needed
|
# Define environment variables if needed
|
||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
@@ -20,8 +24,13 @@ ENV GENERATE_SOURCEMAP=false
|
|||||||
# build
|
# build
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
|
# Inject env.js loader into index.html
|
||||||
|
RUN sed -i '/<head>/a\<script src="/env.js"></script>' build/index.html
|
||||||
|
|
||||||
# Make port 3000 available to the world outside this container
|
# Make port 3000 available to the world outside this container
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|
||||||
|
ENTRYPOINT ["./entrypoint.sh"]
|
||||||
|
|
||||||
# Run the application
|
# Run the application
|
||||||
CMD ["npm", "start"]
|
CMD ["npm", "start"]
|
||||||
|
|||||||
@@ -0,0 +1,213 @@
|
|||||||
|
# Tailwind Dark Mode Usage Guide
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
This guide shows how to use the new Tailwind utilities for better dark mode visibility in OpenSign.
|
||||||
|
|
||||||
|
## Button Styling
|
||||||
|
|
||||||
|
### VS Code-style Disabled Buttons
|
||||||
|
```jsx
|
||||||
|
// Option 1: Direct VS Code styling
|
||||||
|
<button className="op-btn op-btn-primary op-btn-vscode-disabled" disabled>
|
||||||
|
Disabled Button
|
||||||
|
</button>
|
||||||
|
|
||||||
|
// Option 2: Themed disabled styling
|
||||||
|
<button className="op-btn btn-themed-disabled">
|
||||||
|
Themed Button
|
||||||
|
</button>
|
||||||
|
|
||||||
|
// Option 3: Conditional styling
|
||||||
|
<button
|
||||||
|
className={`op-btn op-btn-primary ${isDisabled ? 'op-btn-vscode-disabled' : ''}`}
|
||||||
|
disabled={isDisabled}
|
||||||
|
>
|
||||||
|
Dynamic Button
|
||||||
|
</button>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Icon Styling
|
||||||
|
|
||||||
|
### Theme-aware Icons
|
||||||
|
```jsx
|
||||||
|
// Better visibility in dark mode
|
||||||
|
<i className="fa-light fa-folder icon-improved"></i>
|
||||||
|
|
||||||
|
// Muted but still visible
|
||||||
|
<i className="fa-light fa-plus icon-muted"></i>
|
||||||
|
|
||||||
|
// Disabled state
|
||||||
|
<i className="fa-light fa-trash icon-disabled"></i>
|
||||||
|
```
|
||||||
|
|
||||||
|
### CSS Variable Approach
|
||||||
|
```jsx
|
||||||
|
// Using CSS variables
|
||||||
|
<i className="fa-light fa-search icon-themed"></i>
|
||||||
|
<i className="fa-light fa-settings icon-themed-muted"></i>
|
||||||
|
|
||||||
|
// Inline styles with CSS variables
|
||||||
|
<i
|
||||||
|
className="fa-light fa-plus"
|
||||||
|
style={{ color: 'var(--icon-color)' }}
|
||||||
|
/>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Legacy JavaScript Function (Still Supported)
|
||||||
|
```jsx
|
||||||
|
// Existing approach - still works
|
||||||
|
<i
|
||||||
|
className="fa-light fa-plus"
|
||||||
|
style={{ color: getThemeIconColor() }}
|
||||||
|
/>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Text Styling
|
||||||
|
|
||||||
|
### Improved Gray Text
|
||||||
|
```jsx
|
||||||
|
// These automatically improve in dark mode
|
||||||
|
<span className="text-gray-500">More visible in dark mode</span>
|
||||||
|
<span className="text-gray-400">Muted but readable</span>
|
||||||
|
<span className="text-gray-600">Clear text</span>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Complete Examples
|
||||||
|
|
||||||
|
### Toolbar with Better Visibility
|
||||||
|
```jsx
|
||||||
|
const Toolbar = () => (
|
||||||
|
<div className="flex space-x-2 p-2">
|
||||||
|
<button className="p-2 hover:bg-gray-200 rounded">
|
||||||
|
<i className="fa-light fa-plus icon-improved"></i>
|
||||||
|
</button>
|
||||||
|
<button className="p-2 hover:bg-gray-200 rounded" disabled>
|
||||||
|
<i className="fa-light fa-trash icon-disabled"></i>
|
||||||
|
</button>
|
||||||
|
<button className="p-2 hover:bg-gray-200 rounded">
|
||||||
|
<i className="fa-light fa-edit icon-improved"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Form with Disabled States
|
||||||
|
```jsx
|
||||||
|
const Form = ({ isSubmitting }) => (
|
||||||
|
<form>
|
||||||
|
<input className="op-input" />
|
||||||
|
<button
|
||||||
|
className={`op-btn op-btn-primary ${isSubmitting ? 'op-btn-vscode-disabled' : ''}`}
|
||||||
|
disabled={isSubmitting}
|
||||||
|
>
|
||||||
|
{isSubmitting ? 'Submitting...' : 'Submit'}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
## React-Tour and Tooltip Dark Mode Support
|
||||||
|
|
||||||
|
### React-Tour Modals
|
||||||
|
The react-tour modals now automatically support dark mode with VS Code-inspired styling:
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
// These components automatically get dark mode styling
|
||||||
|
<Tour
|
||||||
|
onRequestClose={closeTour}
|
||||||
|
steps={tourConfig}
|
||||||
|
isOpen={isOpen}
|
||||||
|
rounded={5}
|
||||||
|
/>
|
||||||
|
```
|
||||||
|
|
||||||
|
### ReactTooltip Components
|
||||||
|
All ReactTooltip instances now support dark mode:
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
// Automatically styled for dark mode
|
||||||
|
<ReactTooltip id="my-tooltip" className="z-[999]">
|
||||||
|
<div className="max-w-[200px]">
|
||||||
|
<p>Tooltip content</p>
|
||||||
|
</div>
|
||||||
|
</ReactTooltip>
|
||||||
|
```
|
||||||
|
|
||||||
|
### HoverCard Balloon UI
|
||||||
|
The balloon tooltips in OpenSign Drive now properly support dark mode:
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
// These automatically get dark styling in dark mode
|
||||||
|
<HoverCard>
|
||||||
|
<HoverCardContent>
|
||||||
|
Document information
|
||||||
|
</HoverCardContent>
|
||||||
|
</HoverCard>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dark Mode Features Added
|
||||||
|
|
||||||
|
### 1. **React-Tour Modal Styling**
|
||||||
|
- Background: `#1F2937` (VS Code modal background)
|
||||||
|
- Text: `#E5E7EB` (soft white for readability)
|
||||||
|
- Borders: `#374151` (subtle borders)
|
||||||
|
- Buttons: VS Code-style primary/secondary buttons
|
||||||
|
|
||||||
|
### 2. **ReactTooltip Styling**
|
||||||
|
- Background: `#1F2937` with proper contrast
|
||||||
|
- Border: `#374151` for definition
|
||||||
|
- Box shadow: Enhanced for dark backgrounds
|
||||||
|
- Text: `#E5E7EB` for optimal readability
|
||||||
|
|
||||||
|
### 3. **HoverCard Balloon UI**
|
||||||
|
- Background: `#1F2937` (matches VS Code)
|
||||||
|
- Text: `#E5E7EB` for readability
|
||||||
|
- Arrow: Automatically matches background color
|
||||||
|
- Enhanced shadows for dark backgrounds
|
||||||
|
|
||||||
|
### 4. **React-Datepicker Support**
|
||||||
|
- Calendar background: `#1F2937`
|
||||||
|
- Selected dates: VS Code blue (`#007ACC`)
|
||||||
|
- Hover states: Proper contrast ratios
|
||||||
|
- Navigation arrows: Themed appropriately
|
||||||
|
|
||||||
|
## CSS Classes Reference
|
||||||
|
|
||||||
|
| Class | Purpose | Dark Mode Color |
|
||||||
|
|-------|---------|----------------|
|
||||||
|
| `icon-improved` | Better icon visibility | `#CCCCCC` |
|
||||||
|
| `icon-muted` | Muted but visible icons | `#999999` |
|
||||||
|
| `icon-disabled` | Disabled icon state | `#858585` |
|
||||||
|
| `op-btn-vscode-disabled` | VS Code disabled button | Background: `#3C3C3C` |
|
||||||
|
| `btn-themed-disabled` | Themed disabled button | Uses CSS variables |
|
||||||
|
| `icon-themed` | Variable-based icon color | `var(--icon-color)` |
|
||||||
|
| `.reactour__helper` | `#1F2937` background |
|
||||||
|
| `.react-tooltip` | `#1F2937` background |
|
||||||
|
| `.HoverCardContent` | `#1F2937` background |
|
||||||
|
| `.react-datepicker` | `#1F2937` background |
|
||||||
|
|
||||||
|
## Migration Guide
|
||||||
|
|
||||||
|
### From JavaScript Function to Tailwind
|
||||||
|
```jsx
|
||||||
|
// Before
|
||||||
|
<i style={{ color: getThemeIconColor() }} className="fa-light fa-plus" />
|
||||||
|
|
||||||
|
// After
|
||||||
|
<i className="fa-light fa-plus icon-improved" />
|
||||||
|
```
|
||||||
|
|
||||||
|
### From Hardcoded Colors to Theme-aware
|
||||||
|
```jsx
|
||||||
|
// Before
|
||||||
|
<i className="fa-light fa-plus text-gray-500" />
|
||||||
|
|
||||||
|
// After (automatic improvement)
|
||||||
|
<i className="fa-light fa-plus text-gray-500" />
|
||||||
|
// OR explicitly
|
||||||
|
<i className="fa-light fa-plus icon-improved" />
|
||||||
|
```
|
||||||
|
|
||||||
|
## Migration Notes
|
||||||
|
|
||||||
|
All existing tooltip and tour components will automatically inherit the new dark mode styling when the theme is set to `opensigndark`. No code changes required for existing implementations.
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
ENV_FILE=./build/env.js
|
||||||
|
DOTENV_FILE=./.env.prod # ✅ use .env.prod
|
||||||
|
|
||||||
|
echo "Generating runtime env file at $ENV_FILE..."
|
||||||
|
|
||||||
|
echo "window.RUNTIME_ENV = {" > $ENV_FILE
|
||||||
|
|
||||||
|
# List of keys to include
|
||||||
|
RUNTIME_KEYS="REACT_APP_SERVERURL"
|
||||||
|
|
||||||
|
for key in $RUNTIME_KEYS; do
|
||||||
|
# First check docker env (-e), fallback to .env file
|
||||||
|
value=$(printenv "$key")
|
||||||
|
|
||||||
|
if [ -z "$value" ] && [ -f "$DOTENV_FILE" ]; then
|
||||||
|
# fallback: read from .env
|
||||||
|
value=$(grep "^$key=" "$DOTENV_FILE" | cut -d '=' -f2- | tr -d '\r\n' | sed 's/"/\\"/g')
|
||||||
|
else
|
||||||
|
value=$(echo "$value" | sed 's/"/\\"/g')
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo " $key: \"$value\"," >> $ENV_FILE
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "};" >> $ENV_FILE
|
||||||
|
|
||||||
|
exec "$@"
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
/**
|
||||||
|
* Tailwind Dark Mode Usage Examples for OpenSign
|
||||||
|
*
|
||||||
|
* This file demonstrates how to use the new Tailwind utilities
|
||||||
|
* for better dark mode visibility of buttons and icons.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Example 1: VS Code-style disabled buttons
|
||||||
|
const DisabledButtonExamples = () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{/* Option A: Using the VS Code disabled style */}
|
||||||
|
<button className="op-btn op-btn-primary op-btn-vscode-disabled" disabled>
|
||||||
|
VS Code Style Disabled Button
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Option B: Using themed disabled style */}
|
||||||
|
<button className="op-btn btn-themed-disabled">
|
||||||
|
Themed Disabled Button
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Option C: Conditional styling */}
|
||||||
|
<button
|
||||||
|
className={`op-btn op-btn-primary ${
|
||||||
|
isDisabled ? "op-btn-vscode-disabled" : ""
|
||||||
|
}`}
|
||||||
|
disabled={isDisabled}
|
||||||
|
>
|
||||||
|
Conditional Button
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Example 2: Icon visibility improvements
|
||||||
|
const IconExamples = () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{/* Theme-aware icons with better visibility */}
|
||||||
|
<i className="fa-light fa-folder icon-improved"></i>
|
||||||
|
<i className="fa-light fa-plus icon-muted"></i>
|
||||||
|
<i className="fa-light fa-trash icon-disabled"></i>
|
||||||
|
|
||||||
|
{/* Using CSS variables */}
|
||||||
|
<i className="fa-light fa-search icon-themed"></i>
|
||||||
|
<i className="fa-light fa-settings icon-themed-muted"></i>
|
||||||
|
|
||||||
|
{/* Gray text that automatically improves in dark mode */}
|
||||||
|
<span className="text-gray-500">
|
||||||
|
This text is now more visible in dark mode
|
||||||
|
</span>
|
||||||
|
<span className="text-gray-400">Muted but still readable</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Example 3: Using CSS variables in inline styles
|
||||||
|
const InlineStyleExamples = () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{/* Using CSS variables directly */}
|
||||||
|
<i className="fa-light fa-plus" style={{ color: "var(--icon-color)" }} />
|
||||||
|
|
||||||
|
{/* Using the existing JavaScript function */}
|
||||||
|
<i className="fa-light fa-minus" style={{ color: getThemeIconColor() }} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Example 4: Toolbar with improved icons
|
||||||
|
const ToolbarExample = () => {
|
||||||
|
return (
|
||||||
|
<div className="flex space-x-2 p-2">
|
||||||
|
<button className="p-2 hover:bg-gray-200 rounded">
|
||||||
|
<i className="fa-light fa-plus icon-improved"></i>
|
||||||
|
</button>
|
||||||
|
<button className="p-2 hover:bg-gray-200 rounded" disabled>
|
||||||
|
<i className="fa-light fa-trash icon-disabled"></i>
|
||||||
|
</button>
|
||||||
|
<button className="p-2 hover:bg-gray-200 rounded">
|
||||||
|
<i className="fa-light fa-edit icon-improved"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
DisabledButtonExamples,
|
||||||
|
IconExamples,
|
||||||
|
InlineStyleExamples,
|
||||||
|
ToolbarExample
|
||||||
|
};
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
/**
|
||||||
|
* Tailwind Dark Mode Usage Examples for OpenSign
|
||||||
|
*
|
||||||
|
* This file demonstrates how to use the new Tailwind utilities
|
||||||
|
* for better dark mode visibility of buttons and icons.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Example 1: VS Code-style disabled buttons
|
||||||
|
const DisabledButtonExamples = () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{/* Option A: Using the VS Code disabled style */}
|
||||||
|
<button className="op-btn op-btn-primary op-btn-vscode-disabled" disabled>
|
||||||
|
VS Code Style Disabled Button
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Option B: Using themed disabled style */}
|
||||||
|
<button className="op-btn btn-themed-disabled">
|
||||||
|
Themed Disabled Button
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Option C: Conditional styling */}
|
||||||
|
<button
|
||||||
|
className={`op-btn op-btn-primary ${isDisabled ? 'op-btn-vscode-disabled' : ''}`}
|
||||||
|
disabled={isDisabled}
|
||||||
|
>
|
||||||
|
Conditional Button
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Example 2: Icon visibility improvements
|
||||||
|
const IconExamples = () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{/* Theme-aware icons with better visibility */}
|
||||||
|
<i className="fa-light fa-folder icon-improved"></i>
|
||||||
|
<i className="fa-light fa-plus icon-muted"></i>
|
||||||
|
<i className="fa-light fa-trash icon-disabled"></i>
|
||||||
|
|
||||||
|
{/* Using CSS variables */}
|
||||||
|
<i className="fa-light fa-search icon-themed"></i>
|
||||||
|
<i className="fa-light fa-settings icon-themed-muted"></i>
|
||||||
|
|
||||||
|
{/* Gray text that automatically improves in dark mode */}
|
||||||
|
<span className="text-gray-500">This text is now more visible in dark mode</span>
|
||||||
|
<span className="text-gray-400">Muted but still readable</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Example 3: Using CSS variables in inline styles
|
||||||
|
const InlineStyleExamples = () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{/* Using CSS variables directly */}
|
||||||
|
<i
|
||||||
|
className="fa-light fa-plus"
|
||||||
|
style={{ color: 'var(--icon-color)' }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Using the existing JavaScript function */}
|
||||||
|
<i
|
||||||
|
className="fa-light fa-minus"
|
||||||
|
style={{ color: getThemeIconColor() }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Example 4: Toolbar with improved icons
|
||||||
|
const ToolbarExample = () => {
|
||||||
|
return (
|
||||||
|
<div className="flex space-x-2 p-2">
|
||||||
|
<button className="p-2 hover:bg-gray-200 rounded">
|
||||||
|
<i className="fa-light fa-plus icon-improved"></i>
|
||||||
|
</button>
|
||||||
|
<button className="p-2 hover:bg-gray-200 rounded" disabled>
|
||||||
|
<i className="fa-light fa-trash icon-disabled"></i>
|
||||||
|
</button>
|
||||||
|
<button className="p-2 hover:bg-gray-200 rounded">
|
||||||
|
<i className="fa-light fa-edit icon-improved"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
DisabledButtonExamples,
|
||||||
|
IconExamples,
|
||||||
|
InlineStyleExamples,
|
||||||
|
ToolbarExample
|
||||||
|
};
|
||||||
Generated
+939
-198
File diff suppressed because it is too large
Load Diff
+21
-17
@@ -4,11 +4,11 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@formkit/auto-animate": "^0.8.2",
|
"@formkit/auto-animate": "^0.8.2",
|
||||||
"@lottiefiles/dotlottie-react": "^0.13.5",
|
"@imgly/background-removal": "^1.6.0",
|
||||||
|
"@lottiefiles/dotlottie-react": "^0.14.0",
|
||||||
"@pdf-lib/fontkit": "^1.1.1",
|
"@pdf-lib/fontkit": "^1.1.1",
|
||||||
"@radix-ui/themes": "^3.2.1",
|
"@radix-ui/themes": "^3.2.1",
|
||||||
"@reduxjs/toolkit": "^2.8.2",
|
"@reduxjs/toolkit": "^2.8.2",
|
||||||
"@imgly/background-removal": "^1.6.0",
|
|
||||||
"axios": "^1.9.0",
|
"axios": "^1.9.0",
|
||||||
"date-fns-tz": "^3.2.0",
|
"date-fns-tz": "^3.2.0",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
@@ -20,13 +20,15 @@
|
|||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
"parse": "^6.1.1",
|
"parse": "^6.1.1",
|
||||||
"pdf-lib": "^1.17.1",
|
"pdf-lib": "^1.17.1",
|
||||||
|
"pkijs": "^3.0.8",
|
||||||
"print-js": "^1.6.0",
|
"print-js": "^1.6.0",
|
||||||
"prismjs": "^1.30.0",
|
"prismjs": "^1.30.0",
|
||||||
|
"quill-html-edit-button": "^3.0.0",
|
||||||
"radix-ui": "^1.4.2",
|
"radix-ui": "^1.4.2",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-bootstrap": "^2.10.10",
|
"react-bootstrap": "^2.10.10",
|
||||||
"react-confetti": "^6.4.0",
|
"react-confetti": "^6.4.0",
|
||||||
"react-datepicker": "^8.3.0",
|
"react-datepicker": "^8.4.0",
|
||||||
"react-dnd": "^16.0.1",
|
"react-dnd": "^16.0.1",
|
||||||
"react-dnd-html5-backend": "^16.0.1",
|
"react-dnd-html5-backend": "^16.0.1",
|
||||||
"react-dnd-multi-backend": "^9.0.0",
|
"react-dnd-multi-backend": "^9.0.0",
|
||||||
@@ -34,13 +36,13 @@
|
|||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-gtm-module": "^2.0.11",
|
"react-gtm-module": "^2.0.11",
|
||||||
"react-helmet": "^6.1.0",
|
"react-helmet": "^6.1.0",
|
||||||
"react-i18next": "^15.5.1",
|
"react-i18next": "^15.5.2",
|
||||||
"react-konva": "^18.2.10",
|
"react-konva": "^18.2.10",
|
||||||
"react-pdf": "^9.2.1",
|
"react-pdf": "^9.2.1",
|
||||||
"react-quill-new": "^3.4.6",
|
"react-quill-new": "^3.4.6",
|
||||||
"react-redux": "^9.2.0",
|
"react-redux": "^9.2.0",
|
||||||
"react-rnd": "^10.5.2",
|
"react-rnd": "^10.5.2",
|
||||||
"react-router": "^7.6.0",
|
"react-router": "^7.6.1",
|
||||||
"react-scrollbars-custom": "^4.1.1",
|
"react-scrollbars-custom": "^4.1.1",
|
||||||
"react-select": "^5.10.1",
|
"react-select": "^5.10.1",
|
||||||
"react-signature-canvas": "^1.1.0-alpha.2",
|
"react-signature-canvas": "^1.1.0-alpha.2",
|
||||||
@@ -51,7 +53,7 @@
|
|||||||
"regex-parser": "^2.3.1",
|
"regex-parser": "^2.3.1",
|
||||||
"serve": "^14.2.4",
|
"serve": "^14.2.4",
|
||||||
"styled-components": "^5.3.11",
|
"styled-components": "^5.3.11",
|
||||||
"web-vitals": "^5.0.1",
|
"web-vitals": "^5.0.2",
|
||||||
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz"
|
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -92,16 +94,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.27.1",
|
"@babel/core": "^7.27.4",
|
||||||
"@babel/preset-env": "^7.27.2",
|
"@babel/preset-env": "^7.27.2",
|
||||||
"@babel/preset-react": "^7.27.1",
|
"@babel/preset-react": "^7.27.1",
|
||||||
"@babel/runtime-corejs2": "^7.27.1",
|
"@babel/runtime-corejs2": "^7.27.4",
|
||||||
"@testing-library/jest-dom": "^6.6.3",
|
"@testing-library/jest-dom": "^6.6.3",
|
||||||
"@testing-library/react": "^16.3.0",
|
"@testing-library/react": "^16.3.0",
|
||||||
"@testing-library/user-event": "^14.6.1",
|
"@testing-library/user-event": "^14.6.1",
|
||||||
"@types/react": "^18.3.22",
|
"@types/react": "^18.3.23",
|
||||||
"@vitejs/plugin-react": "^4.4.1",
|
"@vitejs/plugin-react": "^4.5.1",
|
||||||
"@vitejs/plugin-react-swc": "^3.9.0",
|
"@vitejs/plugin-react-swc": "^3.10.1",
|
||||||
|
"@vitest/ui": "^3.2.0",
|
||||||
"autoprefixer": "^10.4.21",
|
"autoprefixer": "^10.4.21",
|
||||||
"babel-loader": "^10.0.0",
|
"babel-loader": "^10.0.0",
|
||||||
"commitizen": "^4.3.1",
|
"commitizen": "^4.3.1",
|
||||||
@@ -109,19 +112,20 @@
|
|||||||
"css-loader": "^7.1.2",
|
"css-loader": "^7.1.2",
|
||||||
"daisyui": "^4.12.24",
|
"daisyui": "^4.12.24",
|
||||||
"dotenv": "^16.5.0",
|
"dotenv": "^16.5.0",
|
||||||
"eslint": "^9.27.0",
|
"eslint": "^9.28.0",
|
||||||
"eslint-plugin-prettier": "^5.4.0",
|
"eslint-plugin-prettier": "^5.4.1",
|
||||||
"eslint-plugin-react": "^7.37.5",
|
"eslint-plugin-react": "^7.37.5",
|
||||||
"lint-staged": "^16.0.0",
|
"jsdom": "^26.1.0",
|
||||||
"postcss": "^8.5.3",
|
"lint-staged": "^16.1.0",
|
||||||
|
"postcss": "^8.5.4",
|
||||||
"prettier": "^3.5.3",
|
"prettier": "^3.5.3",
|
||||||
"pretty-quick": "^4.1.1",
|
"pretty-quick": "^4.2.2",
|
||||||
"rollup-plugin-node-polyfills": "^0.2.1",
|
"rollup-plugin-node-polyfills": "^0.2.1",
|
||||||
"tailwindcss": "^3.4.17",
|
"tailwindcss": "^3.4.17",
|
||||||
"vite": "^6.3.5",
|
"vite": "^6.3.5",
|
||||||
"vite-plugin-svgr": "^4.3.0",
|
"vite-plugin-svgr": "^4.3.0",
|
||||||
"vite-tsconfig-paths": "^5.1.4",
|
"vite-tsconfig-paths": "^5.1.4",
|
||||||
"vitest": "^3.1.4"
|
"vitest": "^3.2.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "18 || 20 || 22"
|
"node": "18 || 20 || 22"
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
{
|
{
|
||||||
"header-news": "Neue Funktion: Benutzer des Teams-Plans können jetzt ihre eigenen AWS S3-Buckets für die Dateispeicherung integrieren",
|
"header-news": "Neue Funktion: Benutzer des Teams-Plans können jetzt ihre eigenen AWS S3-Buckets für die Dateispeicherung integrieren",
|
||||||
"header-news-btn": "Jetzt einrichten",
|
"header-news-btn": "Jetzt einrichten",
|
||||||
|
"sandbox-news": "Dies ist eine Sandbox-Umgebung. Bitte nicht für produktive Zwecke verwenden.",
|
||||||
"create-account": "Konto erstellen",
|
"create-account": "Konto erstellen",
|
||||||
"login": "Anmelden",
|
"login": "Anmelden",
|
||||||
"language": "Sprache",
|
"language": "Sprache",
|
||||||
|
"dark-mode": "Dunkelmodus",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
"phone": "Telefon",
|
"phone": "Telefon",
|
||||||
"phone-optional": "optional",
|
"phone-optional": "optional",
|
||||||
@@ -360,6 +362,7 @@
|
|||||||
"date": "Datum",
|
"date": "Datum",
|
||||||
"text": "Text",
|
"text": "Text",
|
||||||
"text input": "Texteingabe",
|
"text input": "Texteingabe",
|
||||||
|
"cells": "Zellen",
|
||||||
"checkbox": "Checkbox",
|
"checkbox": "Checkbox",
|
||||||
"dropdown": "Dropdown",
|
"dropdown": "Dropdown",
|
||||||
"radio button": "Radiobutton",
|
"radio button": "Radiobutton",
|
||||||
@@ -414,10 +417,12 @@
|
|||||||
"options": "Optionen",
|
"options": "Optionen",
|
||||||
"minimun-check": "Minimale Anzahl",
|
"minimun-check": "Minimale Anzahl",
|
||||||
"maximum-check": "Maximale Anzahl",
|
"maximum-check": "Maximale Anzahl",
|
||||||
|
"cell-count": "Zellzahl",
|
||||||
"default-value": "Standardwert",
|
"default-value": "Standardwert",
|
||||||
"select": "Auswählen",
|
"select": "Auswählen",
|
||||||
"read-only": "Nur lesen",
|
"read-only": "Ist schreibgeschützt",
|
||||||
"hide-labels": "Labels ausblenden",
|
"hide-labels": "Labels ausblenden",
|
||||||
|
"layout": "Layout",
|
||||||
"checkbox": "Checkbox",
|
"checkbox": "Checkbox",
|
||||||
"alert": "Warnung",
|
"alert": "Warnung",
|
||||||
"zoom-in": "Vergrößern",
|
"zoom-in": "Vergrößern",
|
||||||
@@ -675,7 +680,7 @@
|
|||||||
"public-template-mssg-1": "Um OpenSign in Ihr React- oder Next.js-Projekt zu integrieren, führen Sie einfach den folgenden Befehl aus:",
|
"public-template-mssg-1": "Um OpenSign in Ihr React- oder Next.js-Projekt zu integrieren, führen Sie einfach den folgenden Befehl aus:",
|
||||||
"public-template-mssg-2": "Stellen Sie sicher, dass npm oder yarn in Ihrem Projekt eingerichtet ist. Wenn Sie Yarn verwenden, können Sie npm install durch yarn add @opensign/react ersetzen.",
|
"public-template-mssg-2": "Stellen Sie sicher, dass npm oder yarn in Ihrem Projekt eingerichtet ist. Wenn Sie Yarn verwenden, können Sie npm install durch yarn add @opensign/react ersetzen.",
|
||||||
"public-template-mssg-3": "Benötigen Sie weitere Details oder Beispiele?",
|
"public-template-mssg-3": "Benötigen Sie weitere Details oder Beispiele?",
|
||||||
"public-template-mssg-4": "Besuchen Sie die",
|
"public-template-mssg-4": "Besuchen Sie die ",
|
||||||
"public-template-mssg-5": " npm für die neuesten Updates, detaillierte Dokumentationen und Versionshistorie.",
|
"public-template-mssg-5": " npm für die neuesten Updates, detaillierte Dokumentationen und Versionshistorie.",
|
||||||
"public-template-mssg-6": "Bevor Sie diesen Code-Schnipsel verwenden können, müssen Sie diese Vorlage öffentlich machen.",
|
"public-template-mssg-6": "Bevor Sie diesen Code-Schnipsel verwenden können, müssen Sie diese Vorlage öffentlich machen.",
|
||||||
"public-template-mssg-7": "Bevor Sie einen öffentlichen Link generieren können, müssen Sie diese Vorlage öffentlich machen.",
|
"public-template-mssg-7": "Bevor Sie einen öffentlichen Link generieren können, müssen Sie diese Vorlage öffentlich machen.",
|
||||||
@@ -749,6 +754,7 @@
|
|||||||
"delete-page": "Seite löschen",
|
"delete-page": "Seite löschen",
|
||||||
"merge-pdf": "PDFs zusammenführen",
|
"merge-pdf": "PDFs zusammenführen",
|
||||||
"add-pages": "Seiten hinzufügen",
|
"add-pages": "Seiten hinzufügen",
|
||||||
|
"reorder-pages": "Seiten neu anordnen",
|
||||||
"delete-alert": "Einzelne Seite kann nicht gelöscht werden.",
|
"delete-alert": "Einzelne Seite kann nicht gelöscht werden.",
|
||||||
"delete-alert-2": "Sind Sie sicher, dass Sie diese Seite löschen möchten?",
|
"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.",
|
"delete-note": "Hinweis: Sobald Sie diese Seite löschen, kann dies nicht rückgängig gemacht werden.",
|
||||||
@@ -980,5 +986,52 @@
|
|||||||
"finish-mssg": "Sind Sie sicher, dass Sie das Dokument abschließen möchten?",
|
"finish-mssg": "Sind Sie sicher, dass Sie das Dokument abschließen möchten?",
|
||||||
"review": "Überprüfen",
|
"review": "Überprüfen",
|
||||||
"next-field": "Nächstes Feld",
|
"next-field": "Nächstes Feld",
|
||||||
"required-mssg": "{{leftRequiredWidget}} von {{totalWidget}} Feldern übrig"
|
"required-mssg": "{{leftRequiredWidget}} von {{totalWidget}} Feldern übrig",
|
||||||
|
"verify-document": "Dokument verifizieren",
|
||||||
|
"verify-document-signature": "Dokumentensignatur überprüfen",
|
||||||
|
"select-pdf-document": "PDF-Dokument auswählen",
|
||||||
|
"selected-file": "Ausgewählte Datei",
|
||||||
|
"verify-signature": "Signatur überprüfen",
|
||||||
|
"verification-status": "Überprüfungsstatus",
|
||||||
|
"verification-in-progress": "Überprüfung läuft...",
|
||||||
|
"verification-results-will-appear-here": "Überprüfungsergebnisse werden hier angezeigt",
|
||||||
|
"please-select-pdf": "Bitte wählen Sie eine gültige PDF-Datei aus",
|
||||||
|
"please-select-file-to-verify": "Bitte wählen Sie eine Datei zur Überprüfung aus",
|
||||||
|
"no-signature-found": "Keine Signatur im Dokument gefunden",
|
||||||
|
"error-verifying-pdf": "Fehler beim Überprüfen der PDF",
|
||||||
|
"signature-valid-basic": "Signatur ist gültig",
|
||||||
|
"signature-invalid-basic": "Signatur ist ungültig",
|
||||||
|
"all-signatures-verified-convincing": "Dokument überprüft: Alle Signaturen wurden erfolgreich validiert.",
|
||||||
|
"some-signatures-invalid-basic": "Einige Signaturen sind ungültig",
|
||||||
|
"no-signatures-processed": "Keine Signaturen verarbeitet",
|
||||||
|
"unnamed-signature-field": "Unbenanntes Signaturfeld",
|
||||||
|
"error-processing-signature": "Fehler beim Verarbeiten der Signatur",
|
||||||
|
"signer-info-not-available": "Signaturinformationen nicht verfügbar",
|
||||||
|
"cert-validity-not-checked": "Gültigkeit des Zertifikats nicht geprüft",
|
||||||
|
"valid": "Gültig",
|
||||||
|
"expired-or-not-yet-valid": "Abgelaufen oder noch nicht gültig",
|
||||||
|
"valid-from": "Gültig von",
|
||||||
|
"to": "bis",
|
||||||
|
"signer": "Unterzeichner",
|
||||||
|
"issuer": "Aussteller",
|
||||||
|
"not-available": "Nicht verfügbar",
|
||||||
|
"not-performed": "Nicht durchgeführt",
|
||||||
|
"missing-acrofield-dict": "Fehlendes Acrofield-Wörterbuch",
|
||||||
|
"signature-dictionary-not-found-or-invalid": "Signaturwörterbuch nicht gefunden oder ungültig",
|
||||||
|
"missing-or-invalid-byterange": "Fehlender oder ungültiger ByteRange",
|
||||||
|
"missing-or-invalid-contents": "Fehlender oder ungültiger Inhalt",
|
||||||
|
"missing-signature-contents": "Fehlender Signaturinhalt",
|
||||||
|
"invalid-signature-hex-format": "Ungültiges Signatur-Hex-Format",
|
||||||
|
"unsupported-signature-format-not-signeddata": "Nicht unterstütztes Signaturformat - nicht SignedData",
|
||||||
|
"signer-certificate-not-found": "Unterzeichnerzertifikat nicht gefunden",
|
||||||
|
"no-certificates-in-signature": "Keine Zertifikate in der Signatur",
|
||||||
|
"no-signer-info-in-pkcs7": "Keine Signaturinformationen in PKCS#7",
|
||||||
|
"could-not-parse-signer-info": "Signaturinformationen konnten nicht analysiert werden",
|
||||||
|
"not-calculated": "Nicht berechnet",
|
||||||
|
"not-found-in-signature": "Nicht in Signatur gefunden",
|
||||||
|
"readonly-error": "Das schreibgeschützte {{widgetName}}-Widget muss einen Standardwert haben oder kann optional gemacht werden.",
|
||||||
|
"choose-one":"Wählen Sie eine aus",
|
||||||
|
"search-templates": "Vorlagen durchsuchen…",
|
||||||
|
"search-documents": "Dokumente suchen…",
|
||||||
|
"search-contacts": "Kontakte durchsuchen…"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
{
|
{
|
||||||
"header-news": "New feature: Protect your account with Two-Factor Authentication (2FA) and enjoy the future of login with Passkeys — no passwords needed.",
|
"header-news": "New feature: Protect your account with Two-Factor Authentication (2FA) and enjoy the future of login with Passkeys — no passwords needed.",
|
||||||
"header-news-btn": "Setup now",
|
"header-news-btn": "Setup now",
|
||||||
|
"sandbox-news": "This is a sandbox environment. Please do not use it for production purposes.",
|
||||||
"create-account": "Create account",
|
"create-account": "Create account",
|
||||||
"login": "Login",
|
"login": "Login",
|
||||||
"language": "Language",
|
"language": "Language",
|
||||||
|
"dark-mode": "Dark mode",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
"phone": "Phone",
|
"phone": "Phone",
|
||||||
"phone-optional": "optional",
|
"phone-optional": "optional",
|
||||||
@@ -198,7 +200,7 @@
|
|||||||
},
|
},
|
||||||
"file-type": "pdf, png, jpg, jpeg",
|
"file-type": "pdf, png, jpg, jpeg",
|
||||||
"docx": "docx",
|
"docx": "docx",
|
||||||
"file-selected": "file selected",
|
"file-selected": "file(s) selected",
|
||||||
"template-title": "Template title",
|
"template-title": "Template title",
|
||||||
"document-title": "Document title",
|
"document-title": "Document title",
|
||||||
"description": "Description",
|
"description": "Description",
|
||||||
@@ -360,6 +362,7 @@
|
|||||||
"date": "date",
|
"date": "date",
|
||||||
"text": "text",
|
"text": "text",
|
||||||
"text input": "text input",
|
"text input": "text input",
|
||||||
|
"cells": "cells",
|
||||||
"checkbox": "checkbox",
|
"checkbox": "checkbox",
|
||||||
"dropdown": "dropdown",
|
"dropdown": "dropdown",
|
||||||
"radio button": "radio button",
|
"radio button": "radio button",
|
||||||
@@ -414,10 +417,12 @@
|
|||||||
"options": "Options",
|
"options": "Options",
|
||||||
"minimun-check": "Minimun check",
|
"minimun-check": "Minimun check",
|
||||||
"maximum-check": "Maximum check",
|
"maximum-check": "Maximum check",
|
||||||
|
"cell-count": "Cell count",
|
||||||
"default-value": "Default value",
|
"default-value": "Default value",
|
||||||
"select": "Select",
|
"select": "Select",
|
||||||
"read-only": "Is read only",
|
"read-only": "read only",
|
||||||
"hide-labels": "Hide labels",
|
"hide-labels": "Hide labels",
|
||||||
|
"layout": "Layout",
|
||||||
"checkbox": "Checkbox",
|
"checkbox": "Checkbox",
|
||||||
"alert": "Alert",
|
"alert": "Alert",
|
||||||
"zoom-in": "Zoom in",
|
"zoom-in": "Zoom in",
|
||||||
@@ -525,7 +530,7 @@
|
|||||||
"new-password": "New password",
|
"new-password": "New password",
|
||||||
"confirm-password": "Confirm password",
|
"confirm-password": "Confirm password",
|
||||||
"file-alert-1": "The selected file size is too large. Please select a file less than",
|
"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.",
|
"file-alert-3": "Please wait while the document is being uploaded.",
|
||||||
"enter-pdf-password": "Enter Pdf password",
|
"enter-pdf-password": "Enter Pdf password",
|
||||||
"correct-password": "Please provide correct password",
|
"correct-password": "Please provide correct password",
|
||||||
@@ -675,7 +680,7 @@
|
|||||||
"public-template-mssg-1": "To integrate OpenSign into your React or Next.js project, simply run the following command:",
|
"public-template-mssg-1": "To integrate OpenSign into your React or Next.js project, simply run the following command:",
|
||||||
"public-template-mssg-2": "Ensure you have npm or yarn set up in your project. If you're using Yarn, you can replace npm install with yarn add @opensign/react.",
|
"public-template-mssg-2": "Ensure you have npm or yarn set up in your project. If you're using Yarn, you can replace npm install with yarn add @opensign/react.",
|
||||||
"public-template-mssg-3": "Need more details or examples?",
|
"public-template-mssg-3": "Need more details or examples?",
|
||||||
"public-template-mssg-4": "Visit the",
|
"public-template-mssg-4": "Visit the ",
|
||||||
"public-template-mssg-5": " npm for the latest updates, detailed documentation, and version history.",
|
"public-template-mssg-5": " npm for the latest updates, detailed documentation, and version history.",
|
||||||
"public-template-mssg-6": "Before you can use this code snippet, you must make this template public.",
|
"public-template-mssg-6": "Before you can use this code snippet, you must make this template public.",
|
||||||
"public-template-mssg-7": "Before you can generate a public link you must make this template public.",
|
"public-template-mssg-7": "Before you can generate a public link you must make this template public.",
|
||||||
@@ -749,6 +754,7 @@
|
|||||||
"delete-page": "Delete page",
|
"delete-page": "Delete page",
|
||||||
"merge-pdf": "Merge pdf",
|
"merge-pdf": "Merge pdf",
|
||||||
"add-pages": "Add pages",
|
"add-pages": "Add pages",
|
||||||
|
"reorder-pages": "Reorder pages",
|
||||||
"delete-alert": "Can not delete single page",
|
"delete-alert": "Can not delete single page",
|
||||||
"delete-alert-2": "Are you sure you want to delete this page?",
|
"delete-alert-2": "Are you sure you want to delete this page?",
|
||||||
"delete-note": "Note: Once you delete this page, you cannot undo.",
|
"delete-note": "Note: Once you delete this page, you cannot undo.",
|
||||||
@@ -884,7 +890,7 @@
|
|||||||
"you-will-receive-email-shortly": "✅ That's it! You'll receive a confirmation email shortly.",
|
"you-will-receive-email-shortly": "✅ That's it! You'll receive a confirmation email shortly.",
|
||||||
"please-provide-templateid": "Please provide templateid",
|
"please-provide-templateid": "Please provide templateid",
|
||||||
"this-template-is-not-public": "This template is not public",
|
"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",
|
"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.",
|
"title-length-alert": "Title must be at most 250 characters long.",
|
||||||
"note-length-alert": "Note must be at most 200 characters long.",
|
"note-length-alert": "Note must be at most 200 characters long.",
|
||||||
@@ -980,5 +986,52 @@
|
|||||||
"finish-mssg":" Are you sure you want to finish the document ?",
|
"finish-mssg":" Are you sure you want to finish the document ?",
|
||||||
"review":"Review",
|
"review":"Review",
|
||||||
"next-field":"Next Field",
|
"next-field":"Next Field",
|
||||||
"required-mssg":"{{leftRequiredWidget}} of {{totalWidget}} fields left"
|
"required-mssg":"{{leftRequiredWidget}} of {{totalWidget}} fields left",
|
||||||
|
"verify-document": "Verify document",
|
||||||
|
"verify-document-signature": "Verify Document Signature",
|
||||||
|
"select-pdf-document": "Select PDF Document",
|
||||||
|
"selected-file": "Selected file",
|
||||||
|
"verify-signature": "Verify Signature",
|
||||||
|
"verification-status": "Verification Status",
|
||||||
|
"verification-in-progress": "Verification in progress...",
|
||||||
|
"verification-results-will-appear-here": "Verification results will appear here",
|
||||||
|
"please-select-pdf": "Please select a valid PDF file",
|
||||||
|
"please-select-file-to-verify": "Please select a file to verify",
|
||||||
|
"no-signature-found": "No signature found in the document",
|
||||||
|
"error-verifying-pdf": "Error verifying PDF",
|
||||||
|
"signature-valid-basic": "Signature is valid",
|
||||||
|
"signature-invalid-basic": "Signature is invalid",
|
||||||
|
"all-signatures-verified-convincing": "Document Verified: All signatures have been successfully validated.",
|
||||||
|
"some-signatures-invalid-basic": "Some signatures are invalid",
|
||||||
|
"no-signatures-processed": "No signatures were processed",
|
||||||
|
"unnamed-signature-field": "Unnamed Signature Field",
|
||||||
|
"error-processing-signature": "Error processing signature",
|
||||||
|
"signer-info-not-available": "Signer information not available",
|
||||||
|
"cert-validity-not-checked": "Certificate validity not checked",
|
||||||
|
"valid": "Valid",
|
||||||
|
"expired-or-not-yet-valid": "Expired or not yet valid",
|
||||||
|
"valid-from": "Valid from",
|
||||||
|
"to": "to",
|
||||||
|
"signer": "Signer",
|
||||||
|
"issuer": "Issuer",
|
||||||
|
"not-available": "Not available",
|
||||||
|
"not-performed": "Not performed",
|
||||||
|
"missing-acrofield-dict": "Missing acrofield dictionary",
|
||||||
|
"signature-dictionary-not-found-or-invalid": "Signature dictionary not found or invalid",
|
||||||
|
"missing-or-invalid-byterange": "Missing or invalid ByteRange",
|
||||||
|
"missing-or-invalid-contents": "Missing or invalid Contents",
|
||||||
|
"missing-signature-contents": "Missing signature contents",
|
||||||
|
"invalid-signature-hex-format": "Invalid signature hex format",
|
||||||
|
"unsupported-signature-format-not-signeddata": "Unsupported signature format - not SignedData",
|
||||||
|
"signer-certificate-not-found": "Signer certificate not found",
|
||||||
|
"no-certificates-in-signature": "No certificates in signature",
|
||||||
|
"no-signer-info-in-pkcs7": "No signer info in PKCS#7",
|
||||||
|
"could-not-parse-signer-info": "Could not parse signer info",
|
||||||
|
"not-calculated": "Not calculated",
|
||||||
|
"not-found-in-signature": "Not found in signature",
|
||||||
|
"readonly-error": "Read-only {{widgetName}} widget must have a default value or you can make it optional.",
|
||||||
|
"choose-one":"Choose One",
|
||||||
|
"search-templates": "Search templates…",
|
||||||
|
"search-documents": "Search documents…",
|
||||||
|
"search-contacts": "Search contacts…"
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
{
|
{
|
||||||
"header-news": "Nueva característica: los usuarios del plan Teams ahora pueden integrar sus propios depósitos de AWS S3 para el almacenamiento de archivos",
|
"header-news": "Nueva característica: los usuarios del plan Teams ahora pueden integrar sus propios depósitos de AWS S3 para el almacenamiento de archivos",
|
||||||
"header-news-btn": "Configurar ahora",
|
"header-news-btn": "Configurar ahora",
|
||||||
|
"sandbox-news": "Este es un entorno sandbox. Por favor, no lo utilice con fines de producción.",
|
||||||
"create-account": "Crear cuenta",
|
"create-account": "Crear cuenta",
|
||||||
"login": "Iniciar sesión",
|
"login": "Iniciar sesión",
|
||||||
"language": "Idioma",
|
"language": "Idioma",
|
||||||
|
"dark-mode": "Modo oscuro",
|
||||||
"name": "Nombre",
|
"name": "Nombre",
|
||||||
"phone": "Teléfono",
|
"phone": "Teléfono",
|
||||||
"phone-optional": "opcional",
|
"phone-optional": "opcional",
|
||||||
@@ -361,6 +363,7 @@
|
|||||||
"date": "fecha",
|
"date": "fecha",
|
||||||
"text": "texto",
|
"text": "texto",
|
||||||
"text input": "entrada de texto",
|
"text input": "entrada de texto",
|
||||||
|
"cells": "células",
|
||||||
"checkbox": "casilla",
|
"checkbox": "casilla",
|
||||||
"dropdown": "desplegable",
|
"dropdown": "desplegable",
|
||||||
"radio button": "botón de radio",
|
"radio button": "botón de radio",
|
||||||
@@ -415,10 +418,12 @@
|
|||||||
"options": "Opciones",
|
"options": "Opciones",
|
||||||
"minimun-check": "Chequeo mínimo",
|
"minimun-check": "Chequeo mínimo",
|
||||||
"maximum-check": "Chequeo máximo",
|
"maximum-check": "Chequeo máximo",
|
||||||
|
"cell-count": "recuento de células",
|
||||||
"default-value": "Valor por defecto",
|
"default-value": "Valor por defecto",
|
||||||
"select": "Seleccionar",
|
"select": "Seleccionar",
|
||||||
"read-only": "Es de solo lectura",
|
"read-only": "Es de solo lectura",
|
||||||
"hide-labels": "Esconder etiquetas",
|
"hide-labels": "Esconder etiquetas",
|
||||||
|
"layout": "Diseño",
|
||||||
"checkbox": "Casilla",
|
"checkbox": "Casilla",
|
||||||
"alert": "Alerta",
|
"alert": "Alerta",
|
||||||
"zoom-in": "Acercar",
|
"zoom-in": "Acercar",
|
||||||
@@ -675,7 +680,7 @@
|
|||||||
"public-template-mssg-1": "Para integrar OpenSign a tu proyecto React o Next.js, simplemente ejecuta los siguientes comandos:",
|
"public-template-mssg-1": "Para integrar OpenSign a tu proyecto React o Next.js, simplemente ejecuta los siguientes comandos:",
|
||||||
"public-template-mssg-2": "Asegúrate de tener «npm» o «yarn» configurado en tu proyecto. Si estás usando «yarn», puedes reemplazar «npm install» con «yarn add @opensign/react».",
|
"public-template-mssg-2": "Asegúrate de tener «npm» o «yarn» configurado en tu proyecto. Si estás usando «yarn», puedes reemplazar «npm install» con «yarn add @opensign/react».",
|
||||||
"public-template-mssg-3": "¿Necesitas más detalles o ejemplos?",
|
"public-template-mssg-3": "¿Necesitas más detalles o ejemplos?",
|
||||||
"public-template-mssg-4": "Visita la",
|
"public-template-mssg-4": "Visita la ",
|
||||||
"public-template-mssg-5": " «npm» para las últimas actualizaciones, documentación detallada e historial de versiones.",
|
"public-template-mssg-5": " «npm» para las últimas actualizaciones, documentación detallada e historial de versiones.",
|
||||||
"public-template-mssg-6": "Antes de que puedas usar este fragmento de código, debes convertir esta plantilla en pública.",
|
"public-template-mssg-6": "Antes de que puedas usar este fragmento de código, debes convertir esta plantilla en pública.",
|
||||||
"public-template-mssg-7": "Antes de poder generar un enlace público, debes hacer que esta plantilla sea pública.",
|
"public-template-mssg-7": "Antes de poder generar un enlace público, debes hacer que esta plantilla sea pública.",
|
||||||
@@ -749,6 +754,7 @@
|
|||||||
"delete-page": "eliminar página",
|
"delete-page": "eliminar página",
|
||||||
"merge-pdf": "fusionar pdf",
|
"merge-pdf": "fusionar pdf",
|
||||||
"add-pages": "Agregar páginas",
|
"add-pages": "Agregar páginas",
|
||||||
|
"reorder-pages": "Reordenar páginas",
|
||||||
"delete-alert": "No se puede eliminar una sola página",
|
"delete-alert": "No se puede eliminar una sola página",
|
||||||
"delete-alert-2": "¿Está seguro de que desea eliminar esta página?",
|
"delete-alert-2": "¿Está seguro de que desea eliminar esta página?",
|
||||||
"delete-note": "Nota: una vez que elimines esta página, no podrás deshacerla",
|
"delete-note": "Nota: una vez que elimines esta página, no podrás deshacerla",
|
||||||
@@ -980,5 +986,52 @@
|
|||||||
"finish-mssg": "¿Está seguro de que desea finalizar el documento?",
|
"finish-mssg": "¿Está seguro de que desea finalizar el documento?",
|
||||||
"review": "Revisar",
|
"review": "Revisar",
|
||||||
"next-field": "Siguiente campo",
|
"next-field": "Siguiente campo",
|
||||||
"required-mssg": "{{leftRequiredWidget}} de {{totalWidget}} campos restantes"
|
"required-mssg": "{{leftRequiredWidget}} de {{totalWidget}} campos restantes",
|
||||||
|
"verify-document": "Verificar documento",
|
||||||
|
"verify-document-signature": "Verificar firma del documento",
|
||||||
|
"select-pdf-document": "Seleccionar documento PDF",
|
||||||
|
"selected-file": "Archivo seleccionado",
|
||||||
|
"verify-signature": "Verificar firma",
|
||||||
|
"verification-status": "Estado de verificación",
|
||||||
|
"verification-in-progress": "Verificación en curso...",
|
||||||
|
"verification-results-will-appear-here": "Los resultados de la verificación aparecerán aquí",
|
||||||
|
"please-select-pdf": "Por favor, seleccione un archivo PDF válido",
|
||||||
|
"please-select-file-to-verify": "Por favor, seleccione un archivo para verificar",
|
||||||
|
"no-signature-found": "No se encontró ninguna firma en el documento",
|
||||||
|
"error-verifying-pdf": "Error al verificar el PDF",
|
||||||
|
"signature-valid-basic": "La firma es válida",
|
||||||
|
"signature-invalid-basic": "La firma no es válida",
|
||||||
|
"all-signatures-verified-convincing": "Documento verificado: Todas las firmas han sido validadas exitosamente.",
|
||||||
|
"some-signatures-invalid-basic": "Algunas firmas no son válidas",
|
||||||
|
"no-signatures-processed": "No se procesaron firmas",
|
||||||
|
"unnamed-signature-field": "Campo de firma sin nombre",
|
||||||
|
"error-processing-signature": "Error al procesar la firma",
|
||||||
|
"signer-info-not-available": "Información del firmante no disponible",
|
||||||
|
"cert-validity-not-checked": "Validez del certificado no verificada",
|
||||||
|
"valid": "Válido",
|
||||||
|
"expired-or-not-yet-valid": "Caducado o aún no válido",
|
||||||
|
"valid-from": "Válido desde",
|
||||||
|
"to": "hasta",
|
||||||
|
"signer": "Firmante",
|
||||||
|
"issuer": "Emisor",
|
||||||
|
"not-available": "No disponible",
|
||||||
|
"not-performed": "No realizado",
|
||||||
|
"missing-acrofield-dict": "Falta el diccionario Acrofield",
|
||||||
|
"signature-dictionary-not-found-or-invalid": "Diccionario de firmas no encontrado o inválido",
|
||||||
|
"missing-or-invalid-byterange": "ByteRange faltante o inválido",
|
||||||
|
"missing-or-invalid-contents": "Contenido faltante o inválido",
|
||||||
|
"missing-signature-contents": "Falta el contenido de la firma",
|
||||||
|
"invalid-signature-hex-format": "Formato hexadecimal de firma inválido",
|
||||||
|
"unsupported-signature-format-not-signeddata": "Formato de firma no compatible - no SignedData",
|
||||||
|
"signer-certificate-not-found": "Certificado del firmante no encontrado",
|
||||||
|
"no-certificates-in-signature": "No hay certificados en la firma",
|
||||||
|
"no-signer-info-in-pkcs7": "No hay información del firmante en PKCS#7",
|
||||||
|
"could-not-parse-signer-info": "No se pudo analizar la información del firmante",
|
||||||
|
"not-calculated": "No calculado",
|
||||||
|
"not-found-in-signature": "No encontrado en la firma",
|
||||||
|
"readonly-error": "El widget de solo lectura {{widgetName}} debe tener un valor predeterminado o puede hacerlo opcional.",
|
||||||
|
"choose-one":"Elige uno",
|
||||||
|
"search-templates": "Buscar plantillas…",
|
||||||
|
"search-documents": "Buscar documentos…",
|
||||||
|
"search-contacts": "Buscar contactos…"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
{
|
{
|
||||||
"header-news": "Nouvelle fonctionnalité : les utilisateurs du forfait Teams peuvent désormais intégrer leurs propres compartiments AWS S3 pour le stockage de fichiers",
|
"header-news": "Nouvelle fonctionnalité : les utilisateurs du forfait Teams peuvent désormais intégrer leurs propres compartiments AWS S3 pour le stockage de fichiers",
|
||||||
"header-news-btn": "Configurer maintenant",
|
"header-news-btn": "Configurer maintenant",
|
||||||
|
"sandbox-news": "Ceci est un environnement sandbox. Veuillez ne pas l'utiliser à des fins de production.",
|
||||||
"create-account": "Créer un compte",
|
"create-account": "Créer un compte",
|
||||||
"login": "Se Connecter",
|
"login": "Se Connecter",
|
||||||
"language": "Langue",
|
"language": "Langue",
|
||||||
|
"dark-mode": "Mode sombre",
|
||||||
"name": "Nom et Prénom",
|
"name": "Nom et Prénom",
|
||||||
"phone": "Téléphone",
|
"phone": "Téléphone",
|
||||||
"phone-optional": "(facultatif)",
|
"phone-optional": "(facultatif)",
|
||||||
@@ -360,6 +362,7 @@
|
|||||||
"date": "date",
|
"date": "date",
|
||||||
"text": "texte",
|
"text": "texte",
|
||||||
"text input": "saisie de texte",
|
"text input": "saisie de texte",
|
||||||
|
"cells": "cellules",
|
||||||
"checkbox": "case à cocher",
|
"checkbox": "case à cocher",
|
||||||
"dropdown": "dérouler",
|
"dropdown": "dérouler",
|
||||||
"radio button": "bouton radio",
|
"radio button": "bouton radio",
|
||||||
@@ -414,10 +417,12 @@
|
|||||||
"options": "Possibilités",
|
"options": "Possibilités",
|
||||||
"minimun-check": "Vérification minimale",
|
"minimun-check": "Vérification minimale",
|
||||||
"maximum-check": "Contrôle maximum",
|
"maximum-check": "Contrôle maximum",
|
||||||
|
"cell-count": "numération cellulaire",
|
||||||
"default-value": "Valeur par défaut",
|
"default-value": "Valeur par défaut",
|
||||||
"select": "Sélectionner",
|
"select": "Sélectionner",
|
||||||
"read-only": "Est en lecture seule",
|
"read-only": "Est en lecture seule",
|
||||||
"hide-labels": "Masquer les étiquettes",
|
"hide-labels": "Masquer les étiquettes",
|
||||||
|
"layout": "Disposition",
|
||||||
"checkbox": "Case à cocher",
|
"checkbox": "Case à cocher",
|
||||||
"alert": "Alerte",
|
"alert": "Alerte",
|
||||||
"zoom-in": "Agrandir",
|
"zoom-in": "Agrandir",
|
||||||
@@ -675,7 +680,7 @@
|
|||||||
"public-template-mssg-1": "Pour intégrer OpenSign dans votre projet React ou Next.js, exécutez simplement la commande suivante :",
|
"public-template-mssg-1": "Pour intégrer OpenSign dans votre projet React ou Next.js, exécutez simplement la commande suivante :",
|
||||||
"public-template-mssg-2": "Assurez-vous que npm ou Yarn est configuré dans votre projet. Si vous utilisez Yarn, vous pouvez remplacer npm install par Yarn Add @opensign/react.",
|
"public-template-mssg-2": "Assurez-vous que npm ou Yarn est configuré dans votre projet. Si vous utilisez Yarn, vous pouvez remplacer npm install par Yarn Add @opensign/react.",
|
||||||
"public-template-mssg-3": "Besoin de plus de détails ou d'exemples ?",
|
"public-template-mssg-3": "Besoin de plus de détails ou d'exemples ?",
|
||||||
"public-template-mssg-4": "Visitez le",
|
"public-template-mssg-4": "Visitez le ",
|
||||||
"public-template-mssg-5": "npm pour les dernières mises à jour, une documentation détaillée et l'historique des versions.",
|
"public-template-mssg-5": "npm pour les dernières mises à jour, une documentation détaillée et l'historique des versions.",
|
||||||
"public-template-mssg-6": "Avant de pouvoir utiliser cet extrait de code, vous devez rendre ce modèle public.",
|
"public-template-mssg-6": "Avant de pouvoir utiliser cet extrait de code, vous devez rendre ce modèle public.",
|
||||||
"public-template-mssg-7": "Avant de pouvoir générer un lien public, vous devez rendre ce modèle public.",
|
"public-template-mssg-7": "Avant de pouvoir générer un lien public, vous devez rendre ce modèle public.",
|
||||||
@@ -749,6 +754,7 @@
|
|||||||
"delete-page": "supprimer la page",
|
"delete-page": "supprimer la page",
|
||||||
"merge-pdf": "Fusionner le pdf",
|
"merge-pdf": "Fusionner le pdf",
|
||||||
"add-pages": "Ajouter des pages",
|
"add-pages": "Ajouter des pages",
|
||||||
|
"reorder-pages": "Réorganiser les pages",
|
||||||
"delete-alert": "Impossible de supprimer une seule page",
|
"delete-alert": "Impossible de supprimer une seule page",
|
||||||
"delete-alert-2": "Etes-vous sûr de vouloir supprimer cette 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",
|
"delete-note": "Remarque : une fois cette page supprimée, vous ne pouvez plus l'annuler",
|
||||||
@@ -980,5 +986,52 @@
|
|||||||
"finish-mssg": "Êtes-vous sûr de vouloir terminer le document ?",
|
"finish-mssg": "Êtes-vous sûr de vouloir terminer le document ?",
|
||||||
"review": "Revoir",
|
"review": "Revoir",
|
||||||
"next-field": "Champ suivant",
|
"next-field": "Champ suivant",
|
||||||
"required-mssg":"{{leftRequiredWidget}} champs sur {{totalWidget}} restants"
|
"required-mssg":"{{leftRequiredWidget}} champs sur {{totalWidget}} restants",
|
||||||
|
"verify-document": "Vérifier le document",
|
||||||
|
"verify-document-signature": "Vérifier la signature du document",
|
||||||
|
"select-pdf-document": "Sélectionner le document PDF",
|
||||||
|
"selected-file": "Fichier sélectionné",
|
||||||
|
"verify-signature": "Vérifier la signature",
|
||||||
|
"verification-status": "État de la vérification",
|
||||||
|
"verification-in-progress": "Vérification en cours...",
|
||||||
|
"verification-results-will-appear-here": "Les résultats de la vérification apparaîtront ici",
|
||||||
|
"please-select-pdf": "Veuillez sélectionner un fichier PDF valide",
|
||||||
|
"please-select-file-to-verify": "Veuillez sélectionner un fichier à vérifier",
|
||||||
|
"no-signature-found": "Aucune signature trouvée dans le document",
|
||||||
|
"error-verifying-pdf": "Erreur lors de la vérification du PDF",
|
||||||
|
"signature-valid-basic": "La signature est valide",
|
||||||
|
"signature-invalid-basic": "La signature est invalide",
|
||||||
|
"all-signatures-verified-convincing": "Document vérifié : Toutes les signatures ont été validées avec succès.",
|
||||||
|
"some-signatures-invalid-basic": "Certaines signatures sont invalides",
|
||||||
|
"no-signatures-processed": "Aucune signature traitée",
|
||||||
|
"unnamed-signature-field": "Champ de signature sans nom",
|
||||||
|
"error-processing-signature": "Erreur lors du traitement de la signature",
|
||||||
|
"signer-info-not-available": "Informations sur le signataire non disponibles",
|
||||||
|
"cert-validity-not-checked": "Validité du certificat non vérifiée",
|
||||||
|
"valid": "Valide",
|
||||||
|
"expired-or-not-yet-valid": "Expiré ou pas encore valide",
|
||||||
|
"valid-from": "Valide du",
|
||||||
|
"to": "au",
|
||||||
|
"signer": "Signataire",
|
||||||
|
"issuer": "Émetteur",
|
||||||
|
"not-available": "Non disponible",
|
||||||
|
"not-performed": "Non effectué",
|
||||||
|
"missing-acrofield-dict": "Dictionnaire Acrofield manquant",
|
||||||
|
"signature-dictionary-not-found-or-invalid": "Dictionnaire de signatures introuvable ou invalide",
|
||||||
|
"missing-or-invalid-byterange": "ByteRange manquant ou invalide",
|
||||||
|
"missing-or-invalid-contents": "Contenu manquant ou invalide",
|
||||||
|
"missing-signature-contents": "Contenu de la signature manquant",
|
||||||
|
"invalid-signature-hex-format": "Format hexadécimal de signature invalide",
|
||||||
|
"unsupported-signature-format-not-signeddata": "Format de signature non pris en charge - pas SignedData",
|
||||||
|
"signer-certificate-not-found": "Certificat du signataire introuvable",
|
||||||
|
"no-certificates-in-signature": "Aucun certificat dans la signature",
|
||||||
|
"no-signer-info-in-pkcs7": "Aucune information sur le signataire dans PKCS#7",
|
||||||
|
"could-not-parse-signer-info": "Impossible d'analyser les informations sur le signataire",
|
||||||
|
"not-calculated": "Non calculé",
|
||||||
|
"not-found-in-signature": "Introuvable dans la signature",
|
||||||
|
"readonly-error": "Le widget en lecture seule {{widgetName}} doit avoir une valeur par défaut ou vous pouvez le rendre facultatif.",
|
||||||
|
"choose-one":"Choisissez-en un",
|
||||||
|
"search-templates": "Rechercher des modèles…",
|
||||||
|
"search-documents": "Rechercher des documents…",
|
||||||
|
"search-contacts": "Rechercher des contacts…"
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,9 +1,11 @@
|
|||||||
{
|
{
|
||||||
"header-news": "Nuova funzionalità: Gli utenti del piano Teams possono ora integrare i propri bucket AWS S3 per l'archiviazione dei file",
|
"header-news": "Nuova funzionalità: Gli utenti del piano Teams possono ora integrare i propri bucket AWS S3 per l'archiviazione dei file",
|
||||||
"header-news-btn": "Configura Ora",
|
"header-news-btn": "Configura Ora",
|
||||||
|
"sandbox-news": "Questo è un ambiente sandbox. Si prega di non utilizzarlo per scopi di produzione.",
|
||||||
"create-account": "Crea Account",
|
"create-account": "Crea Account",
|
||||||
"login": "Accedi",
|
"login": "Accedi",
|
||||||
"language": "Lingua",
|
"language": "Lingua",
|
||||||
|
"dark-mode": "Modalità scura",
|
||||||
"name": "Nome",
|
"name": "Nome",
|
||||||
"phone": "Telefono",
|
"phone": "Telefono",
|
||||||
"phone-optional": "facoltativo",
|
"phone-optional": "facoltativo",
|
||||||
@@ -360,6 +362,7 @@
|
|||||||
"date": "data",
|
"date": "data",
|
||||||
"text": "testo",
|
"text": "testo",
|
||||||
"text input": "campo di testo",
|
"text input": "campo di testo",
|
||||||
|
"cells": "cellule",
|
||||||
"checkbox": "casella di controllo",
|
"checkbox": "casella di controllo",
|
||||||
"dropdown": "menu a tendina",
|
"dropdown": "menu a tendina",
|
||||||
"radio button": "pulsante di opzione",
|
"radio button": "pulsante di opzione",
|
||||||
@@ -414,10 +417,12 @@
|
|||||||
"options": "Opzioni",
|
"options": "Opzioni",
|
||||||
"minimun-check": "Controllo minimo",
|
"minimun-check": "Controllo minimo",
|
||||||
"maximum-check": "Controllo massimo",
|
"maximum-check": "Controllo massimo",
|
||||||
|
"cell-count": "conteggio delle cellule",
|
||||||
"default-value": "Valore predefinita",
|
"default-value": "Valore predefinita",
|
||||||
"select": "Seleziona",
|
"select": "Seleziona",
|
||||||
"read-only": "È solo lettura",
|
"read-only": "È di sola lettura",
|
||||||
"hide-labels": "Nascondi etichette",
|
"hide-labels": "Nascondi etichette",
|
||||||
|
"layout": "Layout",
|
||||||
"checkbox": "Casella di controllo",
|
"checkbox": "Casella di controllo",
|
||||||
"alert": "Avviso",
|
"alert": "Avviso",
|
||||||
"zoom-in": "Ingrandisci",
|
"zoom-in": "Ingrandisci",
|
||||||
@@ -675,7 +680,7 @@
|
|||||||
"public-template-mssg-1": "Per integrare OpenSign nel tuo progetto React o Next.js, esegui semplicemente il seguente comando:",
|
"public-template-mssg-1": "Per integrare OpenSign nel tuo progetto React o Next.js, esegui semplicemente il seguente comando:",
|
||||||
"public-template-mssg-2": "Assicurati di avere npm o yarn configurato nel tuo progetto. Se usi Yarn, puoi sostituire npm install con yarn add @opensign/react.",
|
"public-template-mssg-2": "Assicurati di avere npm o yarn configurato nel tuo progetto. Se usi Yarn, puoi sostituire npm install con yarn add @opensign/react.",
|
||||||
"public-template-mssg-3": "Hai bisogno di maggiori dettagli o esempi?",
|
"public-template-mssg-3": "Hai bisogno di maggiori dettagli o esempi?",
|
||||||
"public-template-mssg-4": "Visita il",
|
"public-template-mssg-4": "Visita il ",
|
||||||
"public-template-mssg-5": " npm per gli aggiornamenti più recenti, documentazione dettagliata e cronologia delle versioni.",
|
"public-template-mssg-5": " npm per gli aggiornamenti più recenti, documentazione dettagliata e cronologia delle versioni.",
|
||||||
"public-template-mssg-6": "Prima di poter utilizzare questo frammento di codice, devi rendere questo modello pubblico.",
|
"public-template-mssg-6": "Prima di poter utilizzare questo frammento di codice, devi rendere questo modello pubblico.",
|
||||||
"public-template-mssg-7": "Prima di poter generare un link pubblico, devi rendere questo modello pubblico.",
|
"public-template-mssg-7": "Prima di poter generare un link pubblico, devi rendere questo modello pubblico.",
|
||||||
@@ -749,6 +754,7 @@
|
|||||||
"delete-page": "Elimina pagina",
|
"delete-page": "Elimina pagina",
|
||||||
"merge-pdf": "Unisci PDF",
|
"merge-pdf": "Unisci PDF",
|
||||||
"add-pages": "Aggiungi pagine",
|
"add-pages": "Aggiungi pagine",
|
||||||
|
"reorder-pages": "Riordina pagine",
|
||||||
"delete-alert": "Non è possibile eliminare una singola pagina",
|
"delete-alert": "Non è possibile eliminare una singola pagina",
|
||||||
"delete-alert-2": "Sei sicuro di voler eliminare questa pagina?",
|
"delete-alert-2": "Sei sicuro di voler eliminare questa pagina?",
|
||||||
"delete-note": "Nota: Una volta eliminata questa pagina, non potrai annullare l'operazione.",
|
"delete-note": "Nota: Una volta eliminata questa pagina, non potrai annullare l'operazione.",
|
||||||
@@ -980,5 +986,52 @@
|
|||||||
"finish-mssg": "Sei sicuro di voler completare il documento?",
|
"finish-mssg": "Sei sicuro di voler completare il documento?",
|
||||||
"review": "Rivedere",
|
"review": "Rivedere",
|
||||||
"next-field": "Campo successivo",
|
"next-field": "Campo successivo",
|
||||||
"required-mssg":"{{leftRequiredWidget}} di {{totalWidget}} campi rimanenti"
|
"required-mssg":"{{leftRequiredWidget}} di {{totalWidget}} campi rimanenti",
|
||||||
|
"verify-document": "Verifica documento",
|
||||||
|
"verify-document-signature": "Verifica firma documento",
|
||||||
|
"select-pdf-document": "Seleziona documento PDF",
|
||||||
|
"selected-file": "File selezionato",
|
||||||
|
"verify-signature": "Verifica firma",
|
||||||
|
"verification-status": "Stato verifica",
|
||||||
|
"verification-in-progress": "Verifica in corso...",
|
||||||
|
"verification-results-will-appear-here": "I risultati della verifica appariranno qui",
|
||||||
|
"please-select-pdf": "Seleziona un file PDF valido",
|
||||||
|
"please-select-file-to-verify": "Seleziona un file da verificare",
|
||||||
|
"no-signature-found": "Nessuna firma trovata nel documento",
|
||||||
|
"error-verifying-pdf": "Errore durante la verifica del PDF",
|
||||||
|
"signature-valid-basic": "La firma è valida",
|
||||||
|
"signature-invalid-basic": "La firma non è valida",
|
||||||
|
"all-signatures-verified-convincing": "Documento Verificato: Tutte le firme sono state validate con successo.",
|
||||||
|
"some-signatures-invalid-basic": "Alcune firme non sono valide",
|
||||||
|
"no-signatures-processed": "Nessuna firma elaborata",
|
||||||
|
"unnamed-signature-field": "Campo firma senza nome",
|
||||||
|
"error-processing-signature": "Errore durante l'elaborazione della firma",
|
||||||
|
"signer-info-not-available": "Informazioni firmatario non disponibili",
|
||||||
|
"cert-validity-not-checked": "Validità certificato non verificata",
|
||||||
|
"valid": "Valido",
|
||||||
|
"expired-or-not-yet-valid": "Scaduto o non ancora valido",
|
||||||
|
"valid-from": "Valido dal",
|
||||||
|
"to": "al",
|
||||||
|
"signer": "Firmatario",
|
||||||
|
"issuer": "Emittente",
|
||||||
|
"not-available": "Non disponibile",
|
||||||
|
"not-performed": "Non eseguito",
|
||||||
|
"missing-acrofield-dict": "Dizionario Acrofield mancante",
|
||||||
|
"signature-dictionary-not-found-or-invalid": "Dizionario firme non trovato o non valido",
|
||||||
|
"missing-or-invalid-byterange": "ByteRange mancante o non valido",
|
||||||
|
"missing-or-invalid-contents": "Contenuto mancante o non valido",
|
||||||
|
"missing-signature-contents": "Contenuto firma mancante",
|
||||||
|
"invalid-signature-hex-format": "Formato esadecimale firma non valido",
|
||||||
|
"unsupported-signature-format-not-signeddata": "Formato firma non supportato - non SignedData",
|
||||||
|
"signer-certificate-not-found": "Certificato firmatario non trovato",
|
||||||
|
"no-certificates-in-signature": "Nessun certificato nella firma",
|
||||||
|
"no-signer-info-in-pkcs7": "Nessuna informazione firmatario in PKCS#7",
|
||||||
|
"could-not-parse-signer-info": "Impossibile analizzare le informazioni del firmatario",
|
||||||
|
"not-calculated": "Non calcolato",
|
||||||
|
"not-found-in-signature": "Non trovato nella firma",
|
||||||
|
"readonly-error": "Il widget {{widgetName}} di sola lettura deve avere un valore predefinito oppure può essere reso facoltativo.",
|
||||||
|
"choose-one":"Scegline uno",
|
||||||
|
"search-templates": "Cerca modelli…",
|
||||||
|
"search-documents": "Cerca documenti…",
|
||||||
|
"search-contacts": "Cerca contatti…"
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 95 KiB |
+16
-12
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useEffect, lazy } from "react";
|
import { useState, useEffect, lazy } from "react";
|
||||||
import { Routes, Route, BrowserRouter } from "react-router";
|
import { Routes, Route, BrowserRouter } from "react-router";
|
||||||
import { pdfjs } from "react-pdf";
|
import { pdfjs } from "react-pdf";
|
||||||
import Form from "./pages/Form";
|
import Form from "./pages/Form";
|
||||||
@@ -30,7 +30,7 @@ const AddAdmin = lazy(() => import("./pages/AddAdmin"));
|
|||||||
const UpdateExistUserAdmin = lazy(() => import("./pages/UpdateExistUserAdmin"));
|
const UpdateExistUserAdmin = lazy(() => import("./pages/UpdateExistUserAdmin"));
|
||||||
const Preferences = lazy(() => import("./pages/Preferences"));
|
const Preferences = lazy(() => import("./pages/Preferences"));
|
||||||
const Login = lazy(() => import("./pages/Login"));
|
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`;
|
pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/legacy/build/pdf.worker.min.mjs`;
|
||||||
const AppLoader = () => {
|
const AppLoader = () => {
|
||||||
return (
|
return (
|
||||||
@@ -106,10 +106,10 @@ function App() {
|
|||||||
element={<LazyPage Page={GuestLogin} />}
|
element={<LazyPage Page={GuestLogin} />}
|
||||||
/>
|
/>
|
||||||
<Route path="/debugpdf" element={<LazyPage Page={DebugPdf} />} />
|
<Route path="/debugpdf" element={<LazyPage Page={DebugPdf} />} />
|
||||||
<Route
|
<Route
|
||||||
path="/forgetpassword"
|
path="/forgetpassword"
|
||||||
element={<LazyPage Page={ForgetPassword} />}
|
element={<LazyPage Page={ForgetPassword} />}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
element={
|
element={
|
||||||
<ValidateSession>
|
<ValidateSession>
|
||||||
@@ -117,10 +117,10 @@ function App() {
|
|||||||
</ValidateSession>
|
</ValidateSession>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Route
|
<Route
|
||||||
path="/changepassword"
|
path="/changepassword"
|
||||||
element={<LazyPage Page={ChangePassword} />}
|
element={<LazyPage Page={ChangePassword} />}
|
||||||
/>
|
/>
|
||||||
<Route path="/form/:id" element={<Form />} />
|
<Route path="/form/:id" element={<Form />} />
|
||||||
<Route path="/report/:id" element={<Report />} />
|
<Route path="/report/:id" element={<Report />} />
|
||||||
<Route path="/dashboard/:id" element={<Dashboard />} />
|
<Route path="/dashboard/:id" element={<Dashboard />} />
|
||||||
@@ -142,7 +142,7 @@ function App() {
|
|||||||
/>
|
/>
|
||||||
{/* signyouself route with no rowlevel data using docId from url */}
|
{/* signyouself route with no rowlevel data using docId from url */}
|
||||||
<Route path="/signaturePdf/:docId" element={<SignYourSelf />} />
|
<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 />} />
|
<Route path="/draftDocument" element={<DraftDocument />} />
|
||||||
{/* recipient placeholder set route with no rowlevel data using docId from url*/}
|
{/* recipient placeholder set route with no rowlevel data using docId from url*/}
|
||||||
<Route
|
<Route
|
||||||
@@ -165,7 +165,11 @@ function App() {
|
|||||||
path="/recipientSignPdf/:docId"
|
path="/recipientSignPdf/:docId"
|
||||||
element={<PdfRequestFiles />}
|
element={<PdfRequestFiles />}
|
||||||
/>
|
/>
|
||||||
<Route path="/users" element={<UserList />} />
|
<Route path="/users" element={<UserList />} />
|
||||||
|
<Route
|
||||||
|
path="/verify-document"
|
||||||
|
element={<LazyPage Page={VerifyDocument} />}
|
||||||
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/preferences"
|
path="/preferences"
|
||||||
element={<LazyPage Page={Preferences} />}
|
element={<LazyPage Page={Preferences} />}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ const BulkSendUi = (props) => {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// 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 () => {
|
const signatureExist = async () => {
|
||||||
setIsDisableBulkSend(false);
|
setIsDisableBulkSend(false);
|
||||||
const getPlaceholder = props?.Placeholders;
|
const getPlaceholder = props?.Placeholders;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import dp from "../assets/images/dp.png";
|
import dp from "../assets/images/dp.png";
|
||||||
import FullScreenButton from "./FullScreenButton";
|
import FullScreenButton from "./FullScreenButton";
|
||||||
|
import ThemeToggle from "./ThemeToggle";
|
||||||
import { useNavigate } from "react-router";
|
import { useNavigate } from "react-router";
|
||||||
import Parse from "parse";
|
import Parse from "parse";
|
||||||
import { useWindowSize } from "../hook/useWindowSize";
|
import { useWindowSize } from "../hook/useWindowSize";
|
||||||
@@ -85,6 +86,34 @@ const Header = ({ showSidebar, setIsMenu, isConsole }) => {
|
|||||||
};
|
};
|
||||||
}, [isOpen]);
|
}, [isOpen]);
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const updateLogoForTheme = () => {
|
||||||
|
const isDarkMode =
|
||||||
|
document.documentElement.getAttribute("data-theme") === "opensigndark";
|
||||||
|
const logo = isDarkMode
|
||||||
|
? "/static/js/assets/images/logo-dark.png" // Path to the dark mode logo
|
||||||
|
: appInfo.applogo; // Use current logo for light mode
|
||||||
|
if (applogo !== logo) {
|
||||||
|
setAppLogo(logo);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set the logo immediately based on the current theme
|
||||||
|
updateLogoForTheme();
|
||||||
|
|
||||||
|
const observer = new MutationObserver(() => {
|
||||||
|
updateLogoForTheme();
|
||||||
|
});
|
||||||
|
|
||||||
|
observer.observe(document.documentElement, {
|
||||||
|
attributes: true,
|
||||||
|
attributeFilter: ["data-theme"]
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => observer.disconnect();
|
||||||
|
}, [applogo]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="op-navbar bg-base-100 shadow">
|
<div className="op-navbar bg-base-100 shadow">
|
||||||
@@ -147,7 +176,7 @@ const Header = ({ showSidebar, setIsMenu, isConsole }) => {
|
|||||||
</div>
|
</div>
|
||||||
<ul
|
<ul
|
||||||
tabIndex={0}
|
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"
|
isOpen ? "" : "hidden"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
@@ -172,15 +201,36 @@ const Header = ({ showSidebar, setIsMenu, isConsole }) => {
|
|||||||
<i className="fa-light fa-user"></i> {t("profile")}
|
<i className="fa-light fa-user"></i> {t("profile")}
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
|
<li
|
||||||
|
onClick={() => {
|
||||||
|
setIsOpen(false);
|
||||||
|
navigate("/changepassword");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
<i className="fa-light fa-lock"></i>{" "}
|
||||||
|
{t("change-password")}
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
<li
|
<li
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIsOpen(false);
|
setIsOpen(false);
|
||||||
navigate("/changepassword");
|
navigate("/verify-document");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span>
|
<span>
|
||||||
<i className="fa-light fa-lock"></i>{" "}
|
<i className="fa-light fa-check-square"></i>{" "}
|
||||||
{t("change-password")}
|
{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>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
const ThemeToggle = () => {
|
||||||
|
const [isDark, setIsDark] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const storedTheme = localStorage.getItem("theme");
|
||||||
|
if (storedTheme === "dark") {
|
||||||
|
setIsDark(true);
|
||||||
|
document.documentElement.setAttribute("data-theme", "opensigndark");
|
||||||
|
} else {
|
||||||
|
document.documentElement.setAttribute("data-theme", "opensigncss");
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleChange = () => {
|
||||||
|
const newTheme = !isDark;
|
||||||
|
setIsDark(newTheme);
|
||||||
|
if (newTheme) {
|
||||||
|
document.documentElement.setAttribute("data-theme", "opensigndark");
|
||||||
|
localStorage.setItem("theme", "dark");
|
||||||
|
} else {
|
||||||
|
document.documentElement.setAttribute("data-theme", "opensigncss");
|
||||||
|
localStorage.setItem("theme", "light");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<input
|
||||||
|
id="dark-mode-toggle"
|
||||||
|
type="checkbox"
|
||||||
|
className="op-toggle checked:[--tglbg:#3368ff] transition-all checked:bg-white"
|
||||||
|
checked={isDark}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ThemeToggle;
|
||||||
@@ -29,7 +29,7 @@ const DashboardButton = (props) => {
|
|||||||
: "cursor-default"
|
: "cursor-default"
|
||||||
} w-full shadow-md px-3 py-2 op-card bg-base-100`}
|
} 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">
|
<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">
|
<span className="rounded-full bg-base-content bg-opacity-20 w-[60px] h-[60px] self-start flex justify-center items-center">
|
||||||
<i
|
<i
|
||||||
@@ -39,7 +39,7 @@ const DashboardButton = (props) => {
|
|||||||
></i>
|
></i>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-lg ml-3 text-base-content">
|
<div className="text-lg ml-3">
|
||||||
{t(`sidebar.${props.Label}`)}
|
{t(`sidebar.${props.Label}`)}
|
||||||
{props.Label === "Sign yourself" && (
|
{props.Label === "Sign yourself" && (
|
||||||
<div className="text-gray-500 text-xs mt-1">
|
<div className="text-gray-500 text-xs mt-1">
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState, useRef } from "react";
|
||||||
import Parse from "parse";
|
import Parse from "parse";
|
||||||
import ReportTable from "../../primitives/GetReportDisplay";
|
import ReportTable from "../../primitives/GetReportDisplay";
|
||||||
import reportJson from "../../json/ReportJson";
|
import reportJson from "../../json/ReportJson";
|
||||||
@@ -17,10 +17,16 @@ function DashboardReport(props) {
|
|||||||
const [isMoreDocs, setIsMoreDocs] = useState(true);
|
const [isMoreDocs, setIsMoreDocs] = useState(true);
|
||||||
const abortController = new AbortController();
|
const abortController = new AbortController();
|
||||||
const docPerPage = 5;
|
const docPerPage = 5;
|
||||||
|
const [searchTerm, setSearchTerm] = useState("");
|
||||||
|
const [mobileSearchOpen, setMobileSearchOpen] = useState(false);
|
||||||
|
const [isSearchResult, setIsSearchResult] = useState(false);
|
||||||
|
const debounceTimer = useRef(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setReportName("");
|
setReportName("");
|
||||||
getReportData(props.Record.reportId);
|
setSearchTerm("");
|
||||||
|
setMobileSearchOpen(false);
|
||||||
|
getReportData(props.Record.reportId, 0, 20, "");
|
||||||
|
|
||||||
// Function returned from useEffect is called on unmount
|
// Function returned from useEffect is called on unmount
|
||||||
return () => {
|
return () => {
|
||||||
@@ -36,12 +42,69 @@ function DashboardReport(props) {
|
|||||||
// below useEffect call when isNextRecord state is true and fetch next record
|
// below useEffect call when isNextRecord state is true and fetch next record
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isNextRecord) {
|
if (isNextRecord) {
|
||||||
getReportData(props.Record.reportId, List.length, 20);
|
getReportData(props.Record.reportId, List.length, 20, searchTerm);
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
}, [isNextRecord]);
|
}, [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);
|
setIsLoader(true);
|
||||||
const json = reportJson(id);
|
const json = reportJson(id);
|
||||||
if (json) {
|
if (json) {
|
||||||
@@ -59,6 +122,9 @@ function DashboardReport(props) {
|
|||||||
const skipRecord = id === "5Go51Q7T8r" ? 0 : skipUserRecord;
|
const skipRecord = id === "5Go51Q7T8r" ? 0 : skipUserRecord;
|
||||||
const limitRecord = id === "5Go51Q7T8r" ? 200 : limit;
|
const limitRecord = id === "5Go51Q7T8r" ? 200 : limit;
|
||||||
const params = { reportId: id, skip: skipRecord, limit: limitRecord };
|
const params = { reportId: id, skip: skipRecord, limit: limitRecord };
|
||||||
|
if (term) {
|
||||||
|
params.searchTerm = term;
|
||||||
|
}
|
||||||
const url = `${localStorage.getItem("baseUrl")}functions/getReport`;
|
const url = `${localStorage.getItem("baseUrl")}functions/getReport`;
|
||||||
const res = await axios.post(url, params, {
|
const res = await axios.post(url, params, {
|
||||||
headers: headers,
|
headers: headers,
|
||||||
@@ -141,11 +207,17 @@ function DashboardReport(props) {
|
|||||||
setIsNextRecord={setIsNextRecord}
|
setIsNextRecord={setIsNextRecord}
|
||||||
isMoreDocs={isMoreDocs}
|
isMoreDocs={isMoreDocs}
|
||||||
docPerPage={docPerPage}
|
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="flex items-center justify-center h-[100px] w-full bg-white rounded">
|
||||||
<div className="text-center">
|
<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>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ const AddRoleModal = (props) => {
|
|||||||
isOpen={props.isModalRole}
|
isOpen={props.isModalRole}
|
||||||
handleClose={props.handleCloseRoleModal}
|
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}>
|
<form className="flex flex-col" onSubmit={props.handleAddRole}>
|
||||||
<input
|
<input
|
||||||
value={props.roleName}
|
value={props.roleName}
|
||||||
|
|||||||
@@ -12,20 +12,33 @@ function AgreementSign(props) {
|
|||||||
<div className="op-modal op-modal-open absolute z-[448]">
|
<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="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">
|
<div className="flex flex-row items-center">
|
||||||
<input
|
<label className="inline-flex justify-center items-center cursor-pointer mb-0">
|
||||||
data-tut="IsAgree"
|
{/* 1) This div becomes the “fake” checkbox */}
|
||||||
className="mr-3 op-checkbox op-checkbox-m"
|
<div
|
||||||
type="checkbox"
|
data-tut="IsAgree"
|
||||||
value={isChecked}
|
className={`w-6 h-6 border-2 mr-3 rounded-full flex text-center items-center justify-center ${isChecked ? "op-border-primary" : "border-red-500"}`}
|
||||||
onChange={(e) => {
|
>
|
||||||
setIsChecked(e.target.checked);
|
{isChecked ? (
|
||||||
if (e.target.checked) {
|
<span className="op-text-primary text-sm font-bold">✓</span>
|
||||||
props.setIsAgreeTour(false);
|
) : (
|
||||||
}
|
<span className="text-red-500 text-sm font-bold">X</span>
|
||||||
props.showFirstWidget();
|
)}
|
||||||
}}
|
</div>
|
||||||
/>
|
{/* 2) Visually hide the native checkbox but keep it in the DOM */}
|
||||||
<div className="text-[11px] md:text-base">
|
<input
|
||||||
|
className="sr-only"
|
||||||
|
type="checkbox"
|
||||||
|
checked={isChecked}
|
||||||
|
onChange={(e) => {
|
||||||
|
setIsChecked(e.target.checked);
|
||||||
|
if (e.target.checked) {
|
||||||
|
props.setIsAgreeTour(false);
|
||||||
|
}
|
||||||
|
props.showFirstWidget();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<div className="text-[11px] md:text-base text-base-content">
|
||||||
<span>{t("agree-p1")}</span>
|
<span>{t("agree-p1")}</span>
|
||||||
<span
|
<span
|
||||||
className="font-bold text-blue-600 cursor-pointer"
|
className="font-bold text-blue-600 cursor-pointer"
|
||||||
@@ -54,7 +67,7 @@ function AgreementSign(props) {
|
|||||||
{t("agrre-button")}
|
{t("agrre-button")}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-2">
|
<div className="mt-2 text-base-content">
|
||||||
<span className="text-[11px]">{t("agreement-note")}</span>
|
<span className="text-[11px]">{t("agreement-note")}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -12,12 +12,9 @@ function BorderResize(props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{ width: getHeight() || "14px", height: getHeight() || "14px" }}
|
||||||
width: getHeight() || "14px",
|
|
||||||
height: getHeight() || "14px"
|
|
||||||
}}
|
|
||||||
className={`${props.right ? `-right-[12px]` : "-right-[2px]"} ${
|
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]`}
|
} absolute inline-block hover:cursor-sw-resize border-r-[3px] border-b-[3px] border-[#188ae2]`}
|
||||||
></div>
|
></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -7,8 +7,8 @@ import { fontColorArr, fontsizeArr } from "../../constant/Utils";
|
|||||||
function DropdownWidgetOption(props) {
|
function DropdownWidgetOption(props) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [dropdownOptionList, setDropdownOptionList] = useState([
|
const [dropdownOptionList, setDropdownOptionList] = useState([
|
||||||
"option-1",
|
"Option-1",
|
||||||
"option-2"
|
"Option-2"
|
||||||
]);
|
]);
|
||||||
const [minCount, setMinCount] = useState(0);
|
const [minCount, setMinCount] = useState(0);
|
||||||
const [maxCount, setMaxCount] = useState(0);
|
const [maxCount, setMaxCount] = useState(0);
|
||||||
@@ -17,18 +17,21 @@ function DropdownWidgetOption(props) {
|
|||||||
const [isHideLabel, setIsHideLabel] = useState(false);
|
const [isHideLabel, setIsHideLabel] = useState(false);
|
||||||
const [status, setStatus] = useState("required");
|
const [status, setStatus] = useState("required");
|
||||||
const [defaultValue, setDefaultValue] = useState("");
|
const [defaultValue, setDefaultValue] = useState("");
|
||||||
const statusArr = ["required", "optional"];
|
|
||||||
const [defaultCheckbox, setDefaultCheckbox] = useState([]);
|
const [defaultCheckbox, setDefaultCheckbox] = useState([]);
|
||||||
|
const [layout, setLayout] = useState("vertical");
|
||||||
|
const statusArr = ["required", "optional"];
|
||||||
|
const layoutArr = ["vertical", "horizontal"];
|
||||||
|
|
||||||
const resetState = () => {
|
const resetState = () => {
|
||||||
setDropdownOptionList(["option-1", "option-2"]);
|
setDropdownOptionList(["Option-1", "Option-2"]);
|
||||||
setDropdownName( props.currWidgetsDetails?.options?.name || props.type);
|
setDropdownName(props.currWidgetsDetails?.options?.name || props.type);
|
||||||
setIsReadOnly(false);
|
setIsReadOnly(false);
|
||||||
setIsHideLabel(false);
|
setIsHideLabel(false);
|
||||||
setMinCount(0);
|
setMinCount(0);
|
||||||
setMaxCount(0);
|
setMaxCount(0);
|
||||||
setDefaultCheckbox([]);
|
setDefaultCheckbox([]);
|
||||||
setDefaultValue("");
|
setDefaultValue("");
|
||||||
|
setLayout("vertical");
|
||||||
};
|
};
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
@@ -48,6 +51,7 @@ function DropdownWidgetOption(props) {
|
|||||||
setStatus(props.currWidgetsDetails?.options?.status || "required");
|
setStatus(props.currWidgetsDetails?.options?.status || "required");
|
||||||
setDefaultValue(props.currWidgetsDetails?.options?.defaultValue || "");
|
setDefaultValue(props.currWidgetsDetails?.options?.defaultValue || "");
|
||||||
setDefaultCheckbox(props.currWidgetsDetails?.options?.defaultValue || []);
|
setDefaultCheckbox(props.currWidgetsDetails?.options?.defaultValue || []);
|
||||||
|
setLayout(props.currWidgetsDetails?.options?.layout || "vertical");
|
||||||
} else {
|
} else {
|
||||||
setStatus("required");
|
setStatus("required");
|
||||||
resetState();
|
resetState();
|
||||||
@@ -103,6 +107,30 @@ function DropdownWidgetOption(props) {
|
|||||||
? defaultCheckbox
|
? defaultCheckbox
|
||||||
: defaultValue;
|
: 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(
|
props.handleSaveWidgetsOptions(
|
||||||
dropdownName,
|
dropdownName,
|
||||||
dropdownOptionList,
|
dropdownOptionList,
|
||||||
@@ -113,9 +141,10 @@ function DropdownWidgetOption(props) {
|
|||||||
null,
|
null,
|
||||||
status,
|
status,
|
||||||
defaultData,
|
defaultData,
|
||||||
isHideLabel
|
isHideLabel,
|
||||||
|
WidgetLayout
|
||||||
);
|
);
|
||||||
resetState()
|
resetState();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -137,17 +166,18 @@ function DropdownWidgetOption(props) {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<label className="text-[13px] font-semibold">
|
<label htmlFor="title" className="text-[13px] font-semibold">
|
||||||
{t("name")}
|
{t("name")}
|
||||||
<span className="text-[red] text-[13px]"> *</span>
|
<span className="text-[red] text-[13px]"> *</span>
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
|
id="title"
|
||||||
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
|
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
|
||||||
onInput={(e) => e.target.setCustomValidity("")}
|
onInput={(e) => e.target.setCustomValidity("")}
|
||||||
required
|
|
||||||
value={dropdownName}
|
value={dropdownName}
|
||||||
onChange={(e) => setDropdownName(e.target.value)}
|
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"
|
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]">
|
<label className="text-[13px] font-semibold mt-[5px]">
|
||||||
@@ -228,28 +258,26 @@ function DropdownWidgetOption(props) {
|
|||||||
</select>
|
</select>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{props.type !== "checkbox" && props.type !== radioButtonWidget && (
|
{props.type !== "checkbox" && (
|
||||||
<>
|
<div className="flex flex-row gap-[10px] mt-[0.5rem]">
|
||||||
<div className="flex flex-row gap-[10px] mt-[0.5rem]">
|
{statusArr.map((data, ind) => (
|
||||||
{statusArr.map((data, ind) => {
|
<div
|
||||||
return (
|
key={ind}
|
||||||
<div
|
className="flex flex-row gap-[5px] items-center"
|
||||||
key={ind}
|
>
|
||||||
className="flex flex-row gap-[5px] items-center"
|
<input
|
||||||
>
|
className="op-radio op-radio-xs my-1"
|
||||||
<input
|
type="radio"
|
||||||
className="op-radio op-radio-xs my-1"
|
name="status"
|
||||||
type="radio"
|
onChange={() => setStatus(data.toLowerCase())}
|
||||||
name="status"
|
checked={status.toLowerCase() === data.toLowerCase()}
|
||||||
onChange={() => setStatus(data.toLowerCase())}
|
/>
|
||||||
checked={status.toLowerCase() === data.toLowerCase()}
|
<div className="text-[13px] font-500 capitalize">
|
||||||
/>
|
{data}
|
||||||
<div className="text-[13px] font-500">{data}</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
))}
|
||||||
})}
|
</div>
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
<div className="flex items-center mt-3 mb-3">
|
<div className="flex items-center mt-3 mb-3">
|
||||||
<span>{t("font-size")} :</span>
|
<span>{t("font-size")} :</span>
|
||||||
@@ -270,8 +298,8 @@ function DropdownWidgetOption(props) {
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</select>
|
</select>
|
||||||
<div className="flex flex-row gap-1 items-center ml-4 ">
|
<div className="flex flex-row gap-1 items-center ml-4">
|
||||||
<span>{t("color")} : </span>
|
<span className="capitalize">{t("color")} : </span>
|
||||||
<select
|
<select
|
||||||
value={
|
value={
|
||||||
props.fontColor ||
|
props.fontColor ||
|
||||||
@@ -313,7 +341,10 @@ function DropdownWidgetOption(props) {
|
|||||||
className="op-checkbox op-checkbox-sm"
|
className="op-checkbox op-checkbox-sm"
|
||||||
onChange={(e) => setIsReadOnly(e.target.checked)}
|
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")}
|
{t("read-only")}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@@ -328,15 +359,45 @@ function DropdownWidgetOption(props) {
|
|||||||
onChange={(e) => setIsHideLabel(e.target.checked)}
|
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")}
|
{t("hide-labels")}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{["checkbox", radioButtonWidget].includes(props.type) && (
|
||||||
|
<>
|
||||||
|
<div className="text-[13px] font-semibold mt-[5px] capitalize">
|
||||||
|
{t("layout")}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`${props.type === "checkbox" ? "mb-[10px]" : ""} flex flex-row gap-[10px] mt-[0.5rem]`}
|
||||||
|
>
|
||||||
|
{layoutArr.map((data, ind) => (
|
||||||
|
<div
|
||||||
|
key={ind}
|
||||||
|
className="flex flex-row gap-[5px] items-center"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
className="op-radio op-radio-xs my-1"
|
||||||
|
type="radio"
|
||||||
|
name="layout"
|
||||||
|
checked={layout.toLowerCase() === data.toLowerCase()}
|
||||||
|
onChange={() => setLayout(data.toLowerCase())}
|
||||||
|
/>
|
||||||
|
<label className="text-[13px] font-500 mb-0 capitalize">
|
||||||
|
{data}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={`${
|
className={`${
|
||||||
props.type === "checkbox" && props.isShowAdvanceFeature
|
props.type === "checkbox" && props.isShowAdvanceFeature
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, {
|
import {
|
||||||
useState,
|
useState,
|
||||||
useRef,
|
useRef,
|
||||||
} from "react";
|
} from "react";
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Quill } from "react-quill-new";
|
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
|
// 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
|
// 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",
|
container: "#toolbar1",
|
||||||
handlers: { undo: undoChange, redo: redoChange }
|
handlers: { undo: undoChange, redo: redoChange }
|
||||||
},
|
},
|
||||||
history: { delay: 500, maxStack: 100, userOnly: true }
|
history: { delay: 500, maxStack: 100, userOnly: true },
|
||||||
|
// Enable the "<>" button registered above
|
||||||
|
htmlEditButton: {}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Modules object for setting up the Quill editor
|
// Modules object for setting up the Quill editor
|
||||||
@@ -65,7 +72,9 @@ export const module2 = {
|
|||||||
container: "#toolbar2",
|
container: "#toolbar2",
|
||||||
handlers: { undo: undoChange, redo: redoChange }
|
handlers: { undo: undoChange, redo: redoChange }
|
||||||
},
|
},
|
||||||
history: { delay: 500, maxStack: 100, userOnly: true }
|
history: { delay: 500, maxStack: 100, userOnly: true },
|
||||||
|
// Enable the "<>" button registered above
|
||||||
|
htmlEditButton: {}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Formats objects for setting up the Quill editor
|
// Formats objects for setting up the Quill editor
|
||||||
|
|||||||
@@ -186,7 +186,7 @@ function EmailComponent({
|
|||||||
<input
|
<input
|
||||||
type="email"
|
type="email"
|
||||||
value={emailValue}
|
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}
|
onChange={handleEmailValue}
|
||||||
onKeyDown={handleEnterPress}
|
onKeyDown={handleEnterPress}
|
||||||
placeholder={t("enter-email-plaholder")}
|
placeholder={t("enter-email-plaholder")}
|
||||||
@@ -204,19 +204,6 @@ function EmailComponent({
|
|||||||
{t("email-error-1")}
|
{t("email-error-1")}
|
||||||
</p>
|
</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">
|
<div className="mt-2">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -227,7 +214,7 @@ function EmailComponent({
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="op-btn op-btn-ghost ml-2"
|
className="op-btn op-btn-ghost text-base-content ml-2"
|
||||||
onClick={() => handleClose()}
|
onClick={() => handleClose()}
|
||||||
>
|
>
|
||||||
{t("close")}
|
{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 PrevNext from "./PrevNext";
|
||||||
import {
|
import {
|
||||||
base64ToArrayBuffer,
|
base64ToArrayBuffer,
|
||||||
|
decryptPdf,
|
||||||
deletePdfPage,
|
deletePdfPage,
|
||||||
|
flattenPdf,
|
||||||
|
getFileAsArrayBuffer,
|
||||||
handleDownloadCertificate,
|
handleDownloadCertificate,
|
||||||
handleDownloadPdf,
|
handleDownloadPdf,
|
||||||
handleRemoveWidgets,
|
handleRemoveWidgets,
|
||||||
handleToPrint
|
handleToPrint,
|
||||||
|
reorderPdfPages
|
||||||
} from "../../constant/Utils";
|
} from "../../constant/Utils";
|
||||||
import "../../styles/signature.css";
|
import "../../styles/signature.css";
|
||||||
import { DropdownMenu } from "radix-ui";
|
import { DropdownMenu } from "radix-ui";
|
||||||
import ModalUi from "../../primitives/ModalUi";
|
import ModalUi from "../../primitives/ModalUi";
|
||||||
import Loader from "../../primitives/Loader";
|
import Loader from "../../primitives/Loader";
|
||||||
|
import PageReorderModal from "./PageReorderModal";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { PDFDocument } from "pdf-lib";
|
import { PDFDocument } from "pdf-lib";
|
||||||
import { maxFileSize } from "../../constant/const";
|
import { maxFileSize } from "../../constant/const";
|
||||||
@@ -24,6 +29,7 @@ function Header(props) {
|
|||||||
const isMobile = window.innerWidth < 767;
|
const isMobile = window.innerWidth < 767;
|
||||||
const [isDownloading, setIsDownloading] = useState("");
|
const [isDownloading, setIsDownloading] = useState("");
|
||||||
const [isDeletePage, setIsDeletePage] = useState(false);
|
const [isDeletePage, setIsDeletePage] = useState(false);
|
||||||
|
const [isReorderModal, setIsReorderModal] = useState(false);
|
||||||
const mergePdfInputRef = useRef(null);
|
const mergePdfInputRef = useRef(null);
|
||||||
const enabledBackBtn = props?.disabledBackBtn === true ? false : true;
|
const enabledBackBtn = props?.disabledBackBtn === true ? false : true;
|
||||||
//function for show decline alert
|
//function for show decline alert
|
||||||
@@ -80,7 +86,42 @@ function Header(props) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
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, {
|
const uploadedPdfDoc = await PDFDocument.load(uploadedPdfBytes, {
|
||||||
ignoreEncryption: true
|
ignoreEncryption: true
|
||||||
});
|
});
|
||||||
@@ -106,6 +147,21 @@ function Header(props) {
|
|||||||
console.error("Error merging PDF:", error);
|
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 (
|
return (
|
||||||
<div className="flex py-[5px]">
|
<div className="flex py-[5px]">
|
||||||
{isMobile && props?.isShowHeader ? (
|
{isMobile && props?.isShowHeader ? (
|
||||||
@@ -334,6 +390,17 @@ function Header(props) {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</DropdownMenu.Item>
|
</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
|
<DropdownMenu.Item
|
||||||
className="DropdownMenuItem"
|
className="DropdownMenuItem"
|
||||||
@@ -696,6 +763,12 @@ function Header(props) {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</ModalUi>
|
</ModalUi>
|
||||||
|
<PageReorderModal
|
||||||
|
isOpen={isReorderModal}
|
||||||
|
handleClose={() => setIsReorderModal(false)}
|
||||||
|
totalPages={props.allPages}
|
||||||
|
onSave={handleReorderSave}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,24 @@
|
|||||||
import React, { useRef, useState } from "react";
|
import { useRef, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import {
|
import {
|
||||||
base64ToArrayBuffer,
|
base64ToArrayBuffer,
|
||||||
|
decryptPdf,
|
||||||
deletePdfPage,
|
deletePdfPage,
|
||||||
handleRemoveWidgets
|
flattenPdf,
|
||||||
|
getFileAsArrayBuffer,
|
||||||
|
handleRemoveWidgets,
|
||||||
|
reorderPdfPages
|
||||||
} from "../../constant/Utils";
|
} from "../../constant/Utils";
|
||||||
import ModalUi from "../../primitives/ModalUi";
|
import ModalUi from "../../primitives/ModalUi";
|
||||||
import { PDFDocument } from "pdf-lib";
|
import { PDFDocument } from "pdf-lib";
|
||||||
import { maxFileSize } from "../../constant/const";
|
import { maxFileSize } from "../../constant/const";
|
||||||
|
import PageReorderModal from "./PageReorderModal";
|
||||||
|
|
||||||
function PdfZoom(props) {
|
function PdfZoom(props) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const mergePdfInputRef = useRef(null);
|
const mergePdfInputRef = useRef(null);
|
||||||
const [isDeletePage, setIsDeletePage] = useState(false);
|
const [isDeletePage, setIsDeletePage] = useState(false);
|
||||||
|
const [isReorderModal, setIsReorderModal] = useState(false);
|
||||||
const handleDetelePage = async () => {
|
const handleDetelePage = async () => {
|
||||||
props.setIsUploadPdf && props.setIsUploadPdf(true);
|
props.setIsUploadPdf && props.setIsUploadPdf(true);
|
||||||
try {
|
try {
|
||||||
@@ -67,7 +73,42 @@ function PdfZoom(props) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
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, {
|
const uploadedPdfDoc = await PDFDocument.load(uploadedPdfBytes, {
|
||||||
ignoreEncryption: true
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<span className="hidden md:flex flex-col gap-1 text-center md:w-[5%] mt-[42px]">
|
<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>
|
<i className="fa-light fa-trash text-gray-500 2xl:text-[25px]"></i>
|
||||||
</span>
|
</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
|
<span
|
||||||
@@ -185,6 +248,12 @@ function PdfZoom(props) {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</ModalUi>
|
</ModalUi>
|
||||||
|
<PageReorderModal
|
||||||
|
isOpen={isReorderModal}
|
||||||
|
handleClose={() => setIsReorderModal(false)}
|
||||||
|
totalPages={props.allPages}
|
||||||
|
onSave={handleReorderSave}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import React, { useState, useEffect, useRef } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import BorderResize from "./BorderResize";
|
import BorderResize from "./BorderResize";
|
||||||
import PlaceholderBorder from "./PlaceholderBorder";
|
|
||||||
import { Rnd } from "react-rnd";
|
import { Rnd } from "react-rnd";
|
||||||
import {
|
import {
|
||||||
changeDateToMomentFormat,
|
changeDateToMomentFormat,
|
||||||
@@ -14,6 +13,7 @@ import {
|
|||||||
onChangeInput,
|
onChangeInput,
|
||||||
radioButtonWidget,
|
radioButtonWidget,
|
||||||
textInputWidget,
|
textInputWidget,
|
||||||
|
cellsWidget,
|
||||||
textWidget
|
textWidget
|
||||||
} from "../../constant/Utils";
|
} from "../../constant/Utils";
|
||||||
import PlaceholderType from "./PlaceholderType";
|
import PlaceholderType from "./PlaceholderType";
|
||||||
@@ -23,6 +23,7 @@ import ModalUi from "../../primitives/ModalUi";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useDispatch } from "react-redux";
|
import { useDispatch } from "react-redux";
|
||||||
import { setIsShowModal } from "../../redux/reducers/widgetSlice";
|
import { setIsShowModal } from "../../redux/reducers/widgetSlice";
|
||||||
|
import { themeColor } from "../../constant/const";
|
||||||
|
|
||||||
const selectFormat = (data) => {
|
const selectFormat = (data) => {
|
||||||
switch (data) {
|
switch (data) {
|
||||||
@@ -78,14 +79,14 @@ function Placeholder(props) {
|
|||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const widgetData =
|
const widgetData =
|
||||||
props.pos?.options?.defaultValue || props.pos?.options?.response;
|
props.pos?.options?.defaultValue || props.pos?.options?.response;
|
||||||
const [placeholderBorder, setPlaceholderBorder] = useState({ w: 0, h: 0 });
|
|
||||||
const [isDateModal, setIsDateModal] = useState(false);
|
const [isDateModal, setIsDateModal] = useState(false);
|
||||||
const [containerScale, setContainerScale] = useState();
|
const [containerScale, setContainerScale] = useState();
|
||||||
const holdTimeout = useRef(null);
|
|
||||||
const startTime = useRef(null); // Track when the user starts holdings
|
|
||||||
const [selectDate, setSelectDate] = useState({});
|
const [selectDate, setSelectDate] = useState({});
|
||||||
const [dateFormat, setDateFormat] = useState([]);
|
const [dateFormat, setDateFormat] = useState([]);
|
||||||
const [clickonWidget, setClickonWidget] = useState({});
|
const [clickonWidget, setClickonWidget] = useState({});
|
||||||
|
const [isDateReadOnly, setIsDateReadOnly] = useState(
|
||||||
|
props?.pos?.options?.isReadOnly || false
|
||||||
|
);
|
||||||
const startDate = props?.pos?.options?.response
|
const startDate = props?.pos?.options?.response
|
||||||
? getDefaultDate(
|
? getDefaultDate(
|
||||||
props?.pos?.options?.response,
|
props?.pos?.options?.response,
|
||||||
@@ -93,10 +94,6 @@ function Placeholder(props) {
|
|||||||
)
|
)
|
||||||
: new Date();
|
: new Date();
|
||||||
|
|
||||||
const [getCheckboxRenderWidth, setGetCheckboxRenderWidth] = useState({
|
|
||||||
width: null,
|
|
||||||
height: null
|
|
||||||
});
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const getPdfPageWidth = props.pdfOriginalWH.find(
|
const getPdfPageWidth = props.pdfOriginalWH.find(
|
||||||
(data) => data.pageNumber === props.pageNumber
|
(data) => data.pageNumber === props.pageNumber
|
||||||
@@ -127,18 +124,6 @@ function Placeholder(props) {
|
|||||||
}
|
}
|
||||||
}, [widgetData]);
|
}, [widgetData]);
|
||||||
|
|
||||||
const handleGetDaynamicWH = () => {
|
|
||||||
if (
|
|
||||||
props?.pos?.type === "checkbox" ||
|
|
||||||
props?.pos?.type === radioButtonWidget
|
|
||||||
) {
|
|
||||||
const rndElement = document.getElementById(props.pos.key);
|
|
||||||
if (rndElement) {
|
|
||||||
const { width, height } = rndElement.getBoundingClientRect();
|
|
||||||
setGetCheckboxRenderWidth({ width: width, height: height });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
//function change format array list with selected date and format
|
//function change format array list with selected date and format
|
||||||
const changeDateFormat = () => {
|
const changeDateFormat = () => {
|
||||||
const updateDate = [];
|
const updateDate = [];
|
||||||
@@ -309,7 +294,6 @@ function Placeholder(props) {
|
|||||||
//The else condition is used to handle the case when the user clicks on a widget and open signature pad to draw sign
|
//The else condition is used to handle the case when the user clicks on a widget and open signature pad to draw sign
|
||||||
props.setCurrWidgetsDetails && props.setCurrWidgetsDetails(props.pos);
|
props.setCurrWidgetsDetails && props.setCurrWidgetsDetails(props.pos);
|
||||||
handleWidgetIdandPopup();
|
handleWidgetIdandPopup();
|
||||||
handleGetDaynamicWH();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
//`handleOnClickSettingIcon` is used set current widget details and open setting of it
|
//`handleOnClickSettingIcon` is used set current widget details and open setting of it
|
||||||
@@ -321,6 +305,13 @@ function Placeholder(props) {
|
|||||||
} else if (props.pos.type === "checkbox") {
|
} else if (props.pos.type === "checkbox") {
|
||||||
props?.setIsCheckbox(true);
|
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
|
//condition to handle setting icon for signyour-self flow for all type text widgets
|
||||||
else if (
|
else if (
|
||||||
[
|
[
|
||||||
@@ -424,11 +415,54 @@ function Placeholder(props) {
|
|||||||
false,
|
false,
|
||||||
data?.format,
|
data?.format,
|
||||||
props.fontSize || props.pos?.options?.fontSize || 12,
|
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 });
|
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 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
|
// 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:
|
// 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.
|
// 1- When isAlllowModify is true, show border's icon.
|
||||||
@@ -452,10 +486,15 @@ function Placeholder(props) {
|
|||||||
"name",
|
"name",
|
||||||
"company",
|
"company",
|
||||||
"job title",
|
"job title",
|
||||||
"email"
|
"email",
|
||||||
|
...isSettingForCells
|
||||||
].includes(props.pos.type) &&
|
].includes(props.pos.type) &&
|
||||||
(props.isSignYourself || props.isSelfSign) ? (
|
(props.isSignYourself || props.isSelfSign) ? (
|
||||||
<i
|
<i
|
||||||
|
onPointerDown={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handleOnClickSettingIcon();
|
||||||
|
}}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
handleOnClickSettingIcon();
|
handleOnClickSettingIcon();
|
||||||
@@ -465,7 +504,14 @@ function Placeholder(props) {
|
|||||||
handleOnClickSettingIcon();
|
handleOnClickSettingIcon();
|
||||||
}}
|
}}
|
||||||
className="fa-light fa-gear icon"
|
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>
|
></i>
|
||||||
) : (
|
) : (
|
||||||
/* condition to add setting icon for placeholder & template flow for all widgets except signature and date */
|
/* condition to add setting icon for placeholder & template flow for all widgets except signature and date */
|
||||||
@@ -475,6 +521,10 @@ function Placeholder(props) {
|
|||||||
!props.isSignYourself &&
|
!props.isSignYourself &&
|
||||||
!props.isSelfSign)) && (
|
!props.isSelfSign)) && (
|
||||||
<i
|
<i
|
||||||
|
onPointerDown={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handleOnClickSettingIcon();
|
||||||
|
}}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
handleOnClickSettingIcon();
|
handleOnClickSettingIcon();
|
||||||
@@ -487,7 +537,10 @@ function Placeholder(props) {
|
|||||||
style={{
|
style={{
|
||||||
color: "#188ae2",
|
color: "#188ae2",
|
||||||
right: props?.pos?.type === textWidget ? "32px" : "51px",
|
right: props?.pos?.type === textWidget ? "32px" : "51px",
|
||||||
top: "-19px"
|
top: "-19px",
|
||||||
|
cursor: "pointer",
|
||||||
|
zIndex: 99,
|
||||||
|
pointerEvents: "auto"
|
||||||
}}
|
}}
|
||||||
></i>
|
></i>
|
||||||
)
|
)
|
||||||
@@ -525,6 +578,7 @@ function Placeholder(props) {
|
|||||||
{/* setting icon only for date widgets */}
|
{/* setting icon only for date widgets */}
|
||||||
{props.pos.type === "date" && selectDate && (
|
{props.pos.type === "date" && selectDate && (
|
||||||
<i
|
<i
|
||||||
|
onPointerDown={(e) => e.stopPropagation()}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
props.setCurrWidgetsDetails(props.pos);
|
props.setCurrWidgetsDetails(props.pos);
|
||||||
setIsDateModal(!isDateModal);
|
setIsDateModal(!isDateModal);
|
||||||
@@ -557,7 +611,9 @@ function Placeholder(props) {
|
|||||||
top: "-18px",
|
top: "-18px",
|
||||||
right: props.isPlaceholder ? "50px" : "30px",
|
right: props.isPlaceholder ? "50px" : "30px",
|
||||||
color: "#188ae2",
|
color: "#188ae2",
|
||||||
fontSize: "14px"
|
fontSize: "14px",
|
||||||
|
cursor: "pointer",
|
||||||
|
pointerEvents: "auto"
|
||||||
}}
|
}}
|
||||||
className="fa-light fa-gear icon"
|
className="fa-light fa-gear icon"
|
||||||
></i>
|
></i>
|
||||||
@@ -684,7 +740,7 @@ function Placeholder(props) {
|
|||||||
return "not-allowed";
|
return "not-allowed";
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return "all-scroll";
|
return "move";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -704,21 +760,6 @@ function Placeholder(props) {
|
|||||||
return "rgba(203, 233, 237, 0.69)";
|
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 fontSize = calculateFont(props.pos.options?.fontSize);
|
||||||
const fontColor = props.pos.options?.fontColor || "black";
|
const fontColor = props.pos.options?.fontColor || "black";
|
||||||
@@ -769,20 +810,16 @@ function Placeholder(props) {
|
|||||||
id={props.pos.key}
|
id={props.pos.key}
|
||||||
data-tut={props.pos.key === props.unSignedWidgetId ? "IsSigned" : ""}
|
data-tut={props.pos.key === props.unSignedWidgetId ? "IsSigned" : ""}
|
||||||
key={props.pos.key}
|
key={props.pos.key}
|
||||||
|
cancel=".cell-size-handle, .icon"
|
||||||
lockAspectRatio={
|
lockAspectRatio={
|
||||||
!props.isFreeResize &&
|
props?.isAlllowModify &&
|
||||||
![
|
!props?.assignedWidgetId.includes(props.pos.key)
|
||||||
textWidget,
|
? false
|
||||||
"email",
|
: !props.isFreeResize &&
|
||||||
"name",
|
(props.pos.Width
|
||||||
"company",
|
? props.pos.Width / props.pos.Height
|
||||||
"job title",
|
: defaultWidthHeight(props.pos.type).width /
|
||||||
textInputWidget
|
defaultWidthHeight(props.pos.type).height)
|
||||||
].includes(props.pos.type) &&
|
|
||||||
(props.pos.Width
|
|
||||||
? props.pos.Width / props.pos.Height
|
|
||||||
: defaultWidthHeight(props.pos.type).width /
|
|
||||||
defaultWidthHeight(props.pos.type).height)
|
|
||||||
}
|
}
|
||||||
enableResizing={{
|
enableResizing={{
|
||||||
top: false,
|
top: false,
|
||||||
@@ -823,7 +860,6 @@ function Placeholder(props) {
|
|||||||
background: handleBackground()
|
background: handleBackground()
|
||||||
}}
|
}}
|
||||||
onDrag={() => {
|
onDrag={() => {
|
||||||
handleGetDaynamicWH();
|
|
||||||
props.handleTabDrag && props.handleTabDrag(props.pos.key);
|
props.handleTabDrag && props.handleTabDrag(props.pos.key);
|
||||||
}}
|
}}
|
||||||
size={{
|
size={{
|
||||||
@@ -838,7 +874,12 @@ function Placeholder(props) {
|
|||||||
? "auto"
|
? "auto"
|
||||||
: props.posHeight(props.pos, props.isSignYourself)
|
: props.posHeight(props.pos, props.isSignYourself)
|
||||||
}}
|
}}
|
||||||
minHeight={calculateFont(props.pos.options?.fontSize, true)}
|
minHeight={
|
||||||
|
props.pos.type === cellsWidget
|
||||||
|
? calculateFont(props.pos.options?.fontSize, true)
|
||||||
|
: props.pos.type !== "checkbox" &&
|
||||||
|
calculateFont(props.pos.options?.fontSize, true)
|
||||||
|
}
|
||||||
maxHeight="auto"
|
maxHeight="auto"
|
||||||
onResizeStart={() => {
|
onResizeStart={() => {
|
||||||
props.setIsResize && props.setIsResize(true);
|
props.setIsResize && props.setIsResize(true);
|
||||||
@@ -873,12 +914,6 @@ function Placeholder(props) {
|
|||||||
x: xPos(props.pos, props.isSignYourself),
|
x: xPos(props.pos, props.isSignYourself),
|
||||||
y: yPos(props.pos, props.isSignYourself)
|
y: yPos(props.pos, props.isSignYourself)
|
||||||
}}
|
}}
|
||||||
onResize={(e, direction, ref) => {
|
|
||||||
setPlaceholderBorder({
|
|
||||||
w: ref.offsetWidth / (props.scale * containerScale),
|
|
||||||
h: ref.offsetHeight / (props.scale * containerScale)
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
disableDragging={handleDragging()}
|
disableDragging={handleDragging()}
|
||||||
>
|
>
|
||||||
{props.pos.key === props?.currWidgetsDetails?.key &&
|
{props.pos.key === props?.currWidgetsDetails?.key &&
|
||||||
@@ -912,39 +947,27 @@ function Placeholder(props) {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 1- Show a border if props.pos.key === props?.currWidgetsDetails?.key, indicating the current user's selected widget.
|
{/* 1- Show a border if props.pos.key === props?.currWidgetsDetails?.key, indicating the current user's selected widget.
|
||||||
2- If props.isShowBorder is true, display borders for all widgets.
|
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 borders:
|
3- Use the combination of props?.isAlllowModify and !props?.assignedWidgetId.includes(props.pos.key) to determine when to show border:
|
||||||
1- When isAlllowModify is true, show borders.
|
3.1- When isAlllowModify is true, show border.
|
||||||
2- Do not display border for widgets already assigned (props.assignedWidgetId.includes(props.pos.key) is true).
|
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.pos.key === props?.currWidgetsDetails?.key &&
|
||||||
(props.isShowBorder ||
|
(props.isShowBorder ||
|
||||||
(props?.isAlllowModify &&
|
(props?.isAlllowModify &&
|
||||||
!props?.assignedWidgetId.includes(props.pos.key))) && (
|
!props?.assignedWidgetId.includes(props.pos.key))) && (
|
||||||
<PlaceholderBorder
|
<div
|
||||||
pos={props.pos}
|
style={{ borderColor: themeColor }}
|
||||||
isPlaceholder={props.isPlaceholder}
|
className="w-[calc(100%+21px)] h-[calc(100%+21px)] cursor-move absolute inline-block border-[1px] border-dashed"
|
||||||
getCheckboxRenderWidth={getCheckboxRenderWidth}
|
></div>
|
||||||
scale={props.scale}
|
|
||||||
containerScale={containerScale}
|
|
||||||
placeholderBorder={placeholderBorder}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
<div
|
<div
|
||||||
className="flex items-stretch justify-center"
|
className="flex items-stretch justify-center"
|
||||||
style={{
|
style={{
|
||||||
left: xPos(props.pos, props.isSignYourself),
|
left: xPos(props.pos, props.isSignYourself),
|
||||||
top: yPos(props.pos, props.isSignYourself),
|
top: yPos(props.pos, props.isSignYourself),
|
||||||
width:
|
width: "100%",
|
||||||
props.pos.type === radioButtonWidget ||
|
height: "100%",
|
||||||
props.pos.type === "checkbox"
|
|
||||||
? "auto"
|
|
||||||
: props.posWidth(props.pos, props.isSignYourself),
|
|
||||||
height:
|
|
||||||
props.pos.type === radioButtonWidget ||
|
|
||||||
props.pos.type === "checkbox"
|
|
||||||
? "auto"
|
|
||||||
: props.posHeight(props.pos, props.isSignYourself),
|
|
||||||
zIndex: "10"
|
zIndex: "10"
|
||||||
}}
|
}}
|
||||||
onTouchEnd={() => handleOnClickPlaceholder()}
|
onTouchEnd={() => handleOnClickPlaceholder()}
|
||||||
@@ -973,18 +996,19 @@ function Placeholder(props) {
|
|||||||
handleSaveDate={handleSaveDate}
|
handleSaveDate={handleSaveDate}
|
||||||
xPos={props.xPos}
|
xPos={props.xPos}
|
||||||
calculateFont={calculateFont}
|
calculateFont={calculateFont}
|
||||||
|
setCellCount={setCellCount}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Rnd>
|
</Rnd>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<ModalUi isOpen={isDateModal} title={t("widget-info")} showClose={false}>
|
<ModalUi isOpen={isDateModal} title={t("widget-info")} showClose={false}>
|
||||||
<div className="h-[100%] p-[20px]">
|
<div className="text-base-content h-[100%] p-[20px]">
|
||||||
<div className="flex flex-row items-center">
|
<div className="flex flex-col md:flex-row md:items-center gap-y-3">
|
||||||
<span>{t("format")} : </span>
|
<div className="flex flex-row items-center gap-x-1">
|
||||||
<div className="flex">
|
<span className="capitalize">{t("format")} :</span>
|
||||||
<select
|
<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={""}
|
defaultValue={""}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const selectedIndex = e.target.value;
|
const selectedIndex = e.target.value;
|
||||||
@@ -1008,23 +1032,23 @@ function Placeholder(props) {
|
|||||||
{selectDate.format}
|
{selectDate.format}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center mt-4 md:mt-2">
|
<div className="flex flex-col md:flex-row gap-y-2 md:gap-y-0 gap-x-2 mt-3">
|
||||||
<span>{t("font-size")} :</span>
|
<div className="flex flex-row items-center">
|
||||||
<select
|
<span className="capitalize">{t("font-size")} :</span>
|
||||||
className="ml-[3px] md:ml:[7px] op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs"
|
<select
|
||||||
value={props.fontSize || clickonWidget.options?.fontSize || 12}
|
className="ml-[3px] md:ml:[7px] op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs"
|
||||||
onChange={(e) => props.setFontSize(parseInt(e.target.value))}
|
value={props.fontSize || clickonWidget.options?.fontSize || 12}
|
||||||
>
|
onChange={(e) => props.setFontSize(parseInt(e.target.value))}
|
||||||
{fontsizeArr.map((size, ind) => {
|
>
|
||||||
return (
|
{fontsizeArr.map((size, ind) => (
|
||||||
<option className="text-[13px]" value={size} key={ind}>
|
<option className="text-[13px]" value={size} key={ind}>
|
||||||
{size}
|
{size}
|
||||||
</option>
|
</option>
|
||||||
);
|
))}
|
||||||
})}
|
</select>
|
||||||
</select>
|
</div>
|
||||||
<div className="flex flex-row gap-1 items-center ml-2 md:ml-4 ">
|
<div className="flex flex-row gap-1 items-center">
|
||||||
<span>{t("color")}: </span>
|
<span className="capitalize">{t("color")} :</span>
|
||||||
<select
|
<select
|
||||||
value={
|
value={
|
||||||
props.fontColor || clickonWidget.options?.fontColor || "black"
|
props.fontColor || clickonWidget.options?.fontColor || "black"
|
||||||
@@ -1032,13 +1056,11 @@ function Placeholder(props) {
|
|||||||
onChange={(e) => props.setFontColor(e.target.value)}
|
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"
|
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) => {
|
{fontColorArr.map((color, ind) => (
|
||||||
return (
|
<option value={color} key={ind}>
|
||||||
<option value={color} key={ind}>
|
{t(`color-type.${color}`)}
|
||||||
{t(`color-type.${color}`)}
|
</option>
|
||||||
</option>
|
))}
|
||||||
);
|
|
||||||
})}
|
|
||||||
</select>
|
</select>
|
||||||
<span
|
<span
|
||||||
style={{
|
style={{
|
||||||
@@ -1049,7 +1071,26 @@ function Placeholder(props) {
|
|||||||
></span>
|
></span>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
<div className="h-[1px] w-full my-[15px] bg-[#9f9f9f]"></div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@@ -1,56 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { themeColor } from "../../constant/const";
|
|
||||||
import {
|
|
||||||
defaultWidthHeight,
|
|
||||||
isMobile,
|
|
||||||
radioButtonWidget,
|
|
||||||
resizeBorderExtraWidth,
|
|
||||||
textWidget
|
|
||||||
} from "../../constant/Utils";
|
|
||||||
function PlaceholderBorder(props) {
|
|
||||||
const getResizeBorderExtraWidth = resizeBorderExtraWidth();
|
|
||||||
const defaultWidth = defaultWidthHeight(props.pos.type).width;
|
|
||||||
const defaultHeight = defaultWidthHeight(props.pos.type).height;
|
|
||||||
const width = () => {
|
|
||||||
const getWidth =
|
|
||||||
props.placeholderBorder.w || props.pos.Width || defaultWidth;
|
|
||||||
return (
|
|
||||||
getWidth * props.scale * props.containerScale + getResizeBorderExtraWidth
|
|
||||||
);
|
|
||||||
};
|
|
||||||
const height = () => {
|
|
||||||
const getHeight =
|
|
||||||
props.placeholderBorder.h || props.pos.Height || defaultHeight;
|
|
||||||
|
|
||||||
return (
|
|
||||||
getHeight * props.scale * props.containerScale + getResizeBorderExtraWidth
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleMinWidth = () => {
|
|
||||||
if (props.pos.type === "checkbox" || props.pos.type === radioButtonWidget) {
|
|
||||||
return props.getCheckboxRenderWidth.width + getResizeBorderExtraWidth;
|
|
||||||
} else {
|
|
||||||
return width();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const handleMinHeight = () => {
|
|
||||||
if (props.pos.type === "checkbox" || props.pos.type === radioButtonWidget) {
|
|
||||||
return props.getCheckboxRenderWidth.height + getResizeBorderExtraWidth;
|
|
||||||
} else {
|
|
||||||
return height();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className="absolute inline-block w-[14px] h-[14px] border-[0.2px] overflow-hidden border-dashed"
|
|
||||||
style={{
|
|
||||||
borderColor: themeColor,
|
|
||||||
minWidth: handleMinWidth() || 0,
|
|
||||||
minHeight: handleMinHeight() || 0
|
|
||||||
}}
|
|
||||||
></div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default PlaceholderBorder;
|
|
||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
getYear,
|
getYear,
|
||||||
radioButtonWidget,
|
radioButtonWidget,
|
||||||
textInputWidget,
|
textInputWidget,
|
||||||
|
cellsWidget,
|
||||||
textWidget,
|
textWidget,
|
||||||
months,
|
months,
|
||||||
years,
|
years,
|
||||||
@@ -14,8 +15,9 @@ import DatePicker from "react-datepicker";
|
|||||||
import "react-datepicker/dist/react-datepicker.css";
|
import "react-datepicker/dist/react-datepicker.css";
|
||||||
import "../../styles/signature.css";
|
import "../../styles/signature.css";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import CellsWidget from "./CellsWidget";
|
||||||
const textWidgetCls =
|
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 =
|
const selectWidgetCls =
|
||||||
"w-full h-full absolute left-0 top-0 border-[1px] border-[#007bff] rounded-[2px] focus:outline-none text-base-content";
|
"w-full h-full absolute left-0 top-0 border-[1px] border-[#007bff] rounded-[2px] focus:outline-none text-base-content";
|
||||||
const widgetCls =
|
const widgetCls =
|
||||||
@@ -27,8 +29,12 @@ function PlaceholderType(props) {
|
|||||||
props.isSignYourself ||
|
props.isSignYourself ||
|
||||||
((props.isSelfSign || props.isNeedSign) &&
|
((props.isSelfSign || props.isNeedSign) &&
|
||||||
props.data?.signerObjId === props.signerObjId);
|
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 =
|
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 widgetTypeTranslation = t(`widgets-name.${props?.pos?.type}`);
|
||||||
const inputRef = useRef(null);
|
const inputRef = useRef(null);
|
||||||
const [widgetValue, setwidgetValue] = useState();
|
const [widgetValue, setwidgetValue] = useState();
|
||||||
@@ -56,16 +62,13 @@ function PlaceholderType(props) {
|
|||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
if (widgetData) {
|
// keep displayed value in sync with the stored response
|
||||||
setwidgetValue(widgetData);
|
setwidgetValue(widgetData);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (props.pos?.options?.hint) {
|
if (props.pos?.options?.hint) {
|
||||||
setHint(props.pos?.options.hint);
|
setHint(props.pos?.options.hint);
|
||||||
} else if (props.pos?.options?.validation?.type) {
|
} else if (props.pos?.options?.validation?.type) {
|
||||||
checkRegularExpress(props.pos?.options?.validation?.type, setHint);
|
checkRegularExpress(props.pos?.options?.validation?.type, setHint);
|
||||||
} else {
|
|
||||||
setHint(props.pos?.type);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
@@ -77,7 +80,8 @@ function PlaceholderType(props) {
|
|||||||
color: fontColor,
|
color: fontColor,
|
||||||
fontFamily: "Arial, sans-serif"
|
fontFamily: "Arial, sans-serif"
|
||||||
}}
|
}}
|
||||||
className={`${selectWidgetCls} overflow-hidden`}
|
className={`${isReadOnly ? `select-none` : ``} ${selectWidgetCls} overflow-hidden`}
|
||||||
|
disabled={isReadOnly}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
>
|
>
|
||||||
@@ -98,22 +102,6 @@ function PlaceholderType(props) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
//handle height on enter press in text area
|
|
||||||
// const handleEnterPress = (e) => {
|
|
||||||
// const height = 18;
|
|
||||||
// if (e.key === "Enter") {
|
|
||||||
// //function to save height of text area
|
|
||||||
// onChangeHeightOfTextArea(
|
|
||||||
// height,
|
|
||||||
// props.pos.type,
|
|
||||||
// props.pos.key,
|
|
||||||
// props.xyPosition,
|
|
||||||
// props.index,
|
|
||||||
// props.setXyPosition,
|
|
||||||
// props.data && props.data?.Id
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "signature":
|
case "signature":
|
||||||
return props.pos.SignUrl ? (
|
return props.pos.SignUrl ? (
|
||||||
@@ -164,41 +152,42 @@ function PlaceholderType(props) {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
case "checkbox":
|
case "checkbox":
|
||||||
|
const checkBoxLayout = props.pos.options?.layout || "vertical";
|
||||||
|
const isMultipleCheckbox =
|
||||||
|
props.pos.options?.values?.length > 0 ? true : false;
|
||||||
|
const checkBoxWrapperClass = `flex items-start ${
|
||||||
|
checkBoxLayout === "horizontal"
|
||||||
|
? `flex-row flex-wrap ${isMultipleCheckbox ? "gap-x-2" : ""}`
|
||||||
|
: `flex-col ${isMultipleCheckbox ? "gap-y-[5px]" : ""}`
|
||||||
|
}`; // Using gap-y-1 for consistency, adjust if needed
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ zIndex: props.isSignYourself && "99" }}>
|
<div
|
||||||
{props.pos.options?.values?.map((data, ind) => {
|
className={checkBoxWrapperClass}
|
||||||
return (
|
style={{ zIndex: props.isSignYourself && "99" }}
|
||||||
<div
|
>
|
||||||
key={ind}
|
{props.pos.options?.values?.map((data, ind) => (
|
||||||
className="select-none-cls flex items-center text-center gap-0.5 pointer-events-none"
|
<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
|
<input
|
||||||
id={`checkbox-${props.pos.key + ind}`}
|
id={`checkbox-${props.pos.key + ind}`}
|
||||||
style={{ width: fontSize, height: fontSize }}
|
style={{ width: fontSize, height: fontSize }}
|
||||||
className={`${
|
className="op-checkbox rounded-[1px]"
|
||||||
ind === 0 ? "mt-0" : "mt-[5px]"
|
disabled={props.isNeedSign && isReadOnly}
|
||||||
} flex justify-center op-checkbox rounded-[1px] `}
|
|
||||||
disabled={
|
|
||||||
props.isNeedSign &&
|
|
||||||
(props.pos.options?.isReadOnly ||
|
|
||||||
props.data?.signerObjId !== props.signerObjId)
|
|
||||||
}
|
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
readOnly
|
readOnly
|
||||||
checked={!!selectCheckbox(ind, selectedCheckbox)}
|
checked={!!selectCheckbox(ind, selectedCheckbox)}
|
||||||
/>
|
/>
|
||||||
{!props.pos.options?.isHideLabel && (
|
{!props.pos.options?.isHideLabel && (
|
||||||
<label
|
<span className="leading-none">{data}</span>
|
||||||
htmlFor={`checkbox-${props.pos.key + ind}`}
|
|
||||||
style={{ fontSize: fontSize, color: fontColor }}
|
|
||||||
className="text-xs mb-0 text-center"
|
|
||||||
>
|
|
||||||
{data}
|
|
||||||
</label>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</label>
|
||||||
);
|
</div>
|
||||||
})}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
case textInputWidget:
|
case textInputWidget:
|
||||||
@@ -208,24 +197,15 @@ function PlaceholderType(props) {
|
|||||||
placeholder={hint || t("widgets-name.text")}
|
placeholder={hint || t("widgets-name.text")}
|
||||||
rows={1}
|
rows={1}
|
||||||
value={widgetValue}
|
value={widgetValue}
|
||||||
className={`${
|
className={`${textWidgetCls} ${isReadOnly ? "select-none" : ""}`}
|
||||||
props.pos.options?.isReadOnly ||
|
|
||||||
props.data?.signerObjId !== props.signerObjId
|
|
||||||
? "select-none"
|
|
||||||
: textWidgetCls
|
|
||||||
}`}
|
|
||||||
style={{
|
style={{
|
||||||
fontSize: fontSize,
|
fontSize: fontSize,
|
||||||
color: fontColor,
|
color: fontColor,
|
||||||
background: props.data?.blockColor,
|
background: isReadOnly ? props.data?.blockColor : "white",
|
||||||
pointerEvents: "none"
|
pointerEvents: "none"
|
||||||
}}
|
}}
|
||||||
readOnly
|
readOnly
|
||||||
disabled={
|
disabled={props.isNeedSign && isReadOnly}
|
||||||
props.isNeedSign &&
|
|
||||||
(props.pos.options?.isReadOnly ||
|
|
||||||
props.data?.signerObjId !== props.signerObjId)
|
|
||||||
}
|
|
||||||
cols="50"
|
cols="50"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
@@ -233,17 +213,42 @@ function PlaceholderType(props) {
|
|||||||
<span>{hint || widgetTypeTranslation}</span>
|
<span>{hint || widgetTypeTranslation}</span>
|
||||||
</div>
|
</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":
|
case "dropdown":
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={textWidgetStyle}
|
style={textWidgetStyle}
|
||||||
className="select-none-cls flex justify-between items-center"
|
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>
|
<i className="fa-light fa-circle-chevron-down mr-1 "></i>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
case "initials":
|
case "initials":
|
||||||
return props.pos.SignUrl ? (
|
return props.pos.SignUrl ? (
|
||||||
<img
|
<img
|
||||||
@@ -276,13 +281,15 @@ function PlaceholderType(props) {
|
|||||||
placeholder={hint || widgetTypeTranslation}
|
placeholder={hint || widgetTypeTranslation}
|
||||||
rows={1}
|
rows={1}
|
||||||
value={widgetValue}
|
value={widgetValue}
|
||||||
className={textWidgetCls}
|
className={`${textWidgetCls} ${isReadOnly ? "select-none" : ""}`}
|
||||||
style={{
|
style={{
|
||||||
fontSize: fontSize,
|
fontSize: fontSize,
|
||||||
color: fontColor,
|
color: fontColor,
|
||||||
|
background: isReadOnly ? props.data?.blockColor : "white",
|
||||||
pointerEvents: "none"
|
pointerEvents: "none"
|
||||||
}}
|
}}
|
||||||
cols="50"
|
cols="50"
|
||||||
|
disabled={props.isNeedSign && isReadOnly}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex h-full select-none-cls" style={textWidgetStyle}>
|
<div className="flex h-full select-none-cls" style={textWidgetStyle}>
|
||||||
@@ -297,13 +304,15 @@ function PlaceholderType(props) {
|
|||||||
placeholder={hint || widgetTypeTranslation}
|
placeholder={hint || widgetTypeTranslation}
|
||||||
rows={1}
|
rows={1}
|
||||||
value={widgetValue}
|
value={widgetValue}
|
||||||
className={textWidgetCls}
|
className={`${textWidgetCls} ${isReadOnly ? "select-none" : ""}`}
|
||||||
style={{
|
style={{
|
||||||
fontSize: fontSize,
|
fontSize: fontSize,
|
||||||
color: fontColor,
|
color: fontColor,
|
||||||
|
background: isReadOnly ? props.data?.blockColor : "white",
|
||||||
pointerEvents: "none"
|
pointerEvents: "none"
|
||||||
}}
|
}}
|
||||||
cols="50"
|
cols="50"
|
||||||
|
disabled={props.isNeedSign && isReadOnly}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div style={textWidgetStyle} className="select-none-cls">
|
<div style={textWidgetStyle} className="select-none-cls">
|
||||||
@@ -318,13 +327,15 @@ function PlaceholderType(props) {
|
|||||||
placeholder={hint || widgetTypeTranslation}
|
placeholder={hint || widgetTypeTranslation}
|
||||||
rows={1}
|
rows={1}
|
||||||
value={widgetValue}
|
value={widgetValue}
|
||||||
className={textWidgetCls}
|
className={`${textWidgetCls} ${isReadOnly ? "select-none" : ""}`}
|
||||||
style={{
|
style={{
|
||||||
fontSize: fontSize,
|
fontSize: fontSize,
|
||||||
color: fontColor,
|
color: fontColor,
|
||||||
|
background: isReadOnly ? props.data?.blockColor : "white",
|
||||||
pointerEvents: "none"
|
pointerEvents: "none"
|
||||||
}}
|
}}
|
||||||
cols="50"
|
cols="50"
|
||||||
|
disabled={props.isNeedSign && isReadOnly}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div style={textWidgetStyle} className="select-none-cls">
|
<div style={textWidgetStyle} className="select-none-cls">
|
||||||
@@ -422,15 +433,15 @@ function PlaceholderType(props) {
|
|||||||
placeholder={hint || widgetTypeTranslation}
|
placeholder={hint || widgetTypeTranslation}
|
||||||
rows={1}
|
rows={1}
|
||||||
value={widgetValue}
|
value={widgetValue}
|
||||||
className={textWidgetCls}
|
className={`${textWidgetCls} ${isReadOnly ? "select-none" : ""}`}
|
||||||
style={{
|
style={{
|
||||||
fontSize: fontSize,
|
fontSize: fontSize,
|
||||||
color: fontColor,
|
color: fontColor,
|
||||||
fontFamily: "Arial, sans-serif",
|
background: isReadOnly ? props.data?.blockColor : "white",
|
||||||
pointerEvents: "none"
|
pointerEvents: "none"
|
||||||
}}
|
}}
|
||||||
disabled
|
|
||||||
cols="1"
|
cols="1"
|
||||||
|
disabled={props.isNeedSign && isReadOnly}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div style={textWidgetStyle} className="select-none-cls">
|
<div style={textWidgetStyle} className="select-none-cls">
|
||||||
@@ -438,45 +449,39 @@ function PlaceholderType(props) {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
case radioButtonWidget:
|
case radioButtonWidget:
|
||||||
|
const radioLayout = props.pos.options?.layout || "vertical";
|
||||||
|
const isOnlyOneBtn = props.pos.options?.values?.length > 0 ? true : false;
|
||||||
|
const radioWrapperClass = `flex items-start ${
|
||||||
|
radioLayout === "horizontal"
|
||||||
|
? `flex-row flex-wrap ${isOnlyOneBtn ? "gap-x-2" : ""}`
|
||||||
|
: `flex-col ${isOnlyOneBtn ? "gap-y-[5px]" : ""}`
|
||||||
|
}`; // Using gap-y-1 for consistency, adjust if needed
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className={radioWrapperClass}>
|
||||||
{props.pos.options?.values.map((data, ind) => {
|
{props.pos.options?.values.map((data, ind) => (
|
||||||
return (
|
<div key={ind} className="select-none-cls pointer-events-none">
|
||||||
<div
|
<label
|
||||||
key={ind}
|
htmlFor={`radio-${props.pos.key + ind}`}
|
||||||
className="select-none-cls flex items-center text-center gap-0.5 pointer-events-none"
|
style={{ fontSize: fontSize, color: fontColor }}
|
||||||
|
className="text-xs mb-0 flex items-center gap-1"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
readOnly
|
readOnly
|
||||||
id={`radio-${props.pos.key + ind}`}
|
id={`radio-${props.pos.key + ind}`}
|
||||||
style={{
|
style={{ width: fontSize, height: fontSize, lineHeight: 2 }}
|
||||||
width: fontSize,
|
className={`op-radio rounded-full border-black appearance-none bg-white inline-block align-middle relative ${
|
||||||
height: fontSize,
|
|
||||||
marginTop: ind > 0 ? "10px" : "0px"
|
|
||||||
}}
|
|
||||||
className={`op-radio rounded-full border- border-black appearance-none bg-white inline-block align-middle relative ${
|
|
||||||
handleRadioCheck(data) ? "checked-radio" : ""
|
handleRadioCheck(data) ? "checked-radio" : ""
|
||||||
}`}
|
}`}
|
||||||
type="radio"
|
type="radio"
|
||||||
disabled={
|
disabled={props.isNeedSign && isReadOnly}
|
||||||
props.isNeedSign &&
|
|
||||||
(props.pos.options?.isReadOnly ||
|
|
||||||
props.data?.signerObjId !== props.signerObjId)
|
|
||||||
}
|
|
||||||
checked={handleRadioCheck(data)}
|
checked={handleRadioCheck(data)}
|
||||||
/>
|
/>
|
||||||
{!props.pos.options?.isHideLabel && (
|
{!props.pos.options?.isHideLabel && (
|
||||||
<label
|
<span className="leading-none">{data}</span>
|
||||||
htmlFor={`radio-${props.pos.key + ind}`}
|
|
||||||
style={{ fontSize: fontSize, color: fontColor }}
|
|
||||||
className="text-xs mb-0"
|
|
||||||
>
|
|
||||||
{data}
|
|
||||||
</label>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</label>
|
||||||
);
|
</div>
|
||||||
})}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
case textWidget:
|
case textWidget:
|
||||||
@@ -490,7 +495,8 @@ function PlaceholderType(props) {
|
|||||||
style={{
|
style={{
|
||||||
fontFamily: "Arial, sans-serif",
|
fontFamily: "Arial, sans-serif",
|
||||||
fontSize: fontSize,
|
fontSize: fontSize,
|
||||||
color: fontColor
|
color: fontColor,
|
||||||
|
background: "white"
|
||||||
}}
|
}}
|
||||||
cols="50"
|
cols="50"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ const RecipientList = (props) => {
|
|||||||
e.preventDefault();
|
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) => {
|
const handleChangeSequence = (e, ind, isUp, isDown, obj) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
let draggedItemId;
|
let draggedItemId;
|
||||||
|
|||||||
@@ -3,7 +3,12 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { Document, Page } from "react-pdf";
|
import { Document, Page } from "react-pdf";
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
import { PDFDocument } from "pdf-lib";
|
import { PDFDocument } from "pdf-lib";
|
||||||
import { base64ToArrayBuffer } from "../../constant/Utils";
|
import {
|
||||||
|
base64ToArrayBuffer,
|
||||||
|
decryptPdf,
|
||||||
|
flattenPdf,
|
||||||
|
getFileAsArrayBuffer
|
||||||
|
} from "../../constant/Utils";
|
||||||
import { maxFileSize } from "../../constant/const";
|
import { maxFileSize } from "../../constant/const";
|
||||||
|
|
||||||
function RenderAllPdfPage(props) {
|
function RenderAllPdfPage(props) {
|
||||||
@@ -87,7 +92,42 @@ function RenderAllPdfPage(props) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
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, {
|
const uploadedPdfDoc = await PDFDocument.load(uploadedPdfBytes, {
|
||||||
ignoreEncryption: true
|
ignoreEncryption: true
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -79,7 +79,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) => {
|
const checkSignedSigners = (data) => {
|
||||||
let checkSign = [];
|
let checkSign = [];
|
||||||
//condition to handle quick send flow and using normal request sign flow
|
//condition to handle quick send flow and using normal request sign flow
|
||||||
@@ -91,73 +91,72 @@ function RenderPdf(props) {
|
|||||||
: [];
|
: [];
|
||||||
return (
|
return (
|
||||||
checkSign.length === 0 &&
|
checkSign.length === 0 &&
|
||||||
data?.placeHolder?.map((placeData, key) => {
|
data?.placeHolder?.map((placeData, key) => (
|
||||||
return (
|
<React.Fragment key={key}>
|
||||||
<React.Fragment key={key}>
|
{placeData.pageNumber === props.pageNumber &&
|
||||||
{placeData.pageNumber === props.pageNumber &&
|
placeData.pos.map(
|
||||||
placeData.pos.map((pos, ind) => {
|
(pos, ind) =>
|
||||||
return (
|
pos && (
|
||||||
pos && (
|
<React.Fragment key={ind}>
|
||||||
<React.Fragment key={ind}>
|
<Placeholder
|
||||||
<Placeholder
|
pos={pos}
|
||||||
pos={pos}
|
handleSignYourselfImageResize={handleImageResize}
|
||||||
handleSignYourselfImageResize={handleImageResize}
|
index={props.pageNumber}
|
||||||
index={props.pageNumber}
|
xyPosition={props.signerPos}
|
||||||
xyPosition={props.signerPos}
|
setXyPosition={props.setSignerPos}
|
||||||
setXyPosition={props.setSignerPos}
|
data={data}
|
||||||
data={data}
|
setIsResize={props.setIsResize}
|
||||||
setIsResize={props.setIsResize}
|
isShowBorder={props.isSelfSign}
|
||||||
isShowBorder={props.isSelfSign}
|
isAlllowModify={props.isAlllowModify}
|
||||||
isAlllowModify={props.isAlllowModify}
|
signerObjId={props.signerObjectId}
|
||||||
signerObjId={props.signerObjectId}
|
isShowDropdown={true}
|
||||||
isShowDropdown={true}
|
isNeedSign={props.pdfRequest}
|
||||||
isNeedSign={props.pdfRequest}
|
isSelfSign={true}
|
||||||
isSelfSign={true}
|
isSignYourself={false}
|
||||||
isSignYourself={false}
|
posWidth={posWidth}
|
||||||
posWidth={posWidth}
|
posHeight={posHeight}
|
||||||
posHeight={posHeight}
|
isDragging={props.isDragging}
|
||||||
isDragging={props.isDragging}
|
pdfDetails={props.pdfDetails}
|
||||||
pdfDetails={props.pdfDetails}
|
unSignedWidgetId={props.unSignedWidgetId}
|
||||||
unSignedWidgetId={props.unSignedWidgetId}
|
setCurrWidgetsDetails={props.setCurrWidgetsDetails}
|
||||||
setCurrWidgetsDetails={props.setCurrWidgetsDetails}
|
uniqueId={props.uniqueId}
|
||||||
uniqueId={props.uniqueId}
|
scale={props.scale}
|
||||||
scale={props.scale}
|
containerWH={props.containerWH}
|
||||||
containerWH={props.containerWH}
|
pdfOriginalWH={props.pdfOriginalWH}
|
||||||
pdfOriginalWH={props.pdfOriginalWH}
|
pageNumber={props.pageNumber}
|
||||||
pageNumber={props.pageNumber}
|
ispublicTemplate={props.ispublicTemplate}
|
||||||
ispublicTemplate={props.ispublicTemplate}
|
handleUserDetails={props.handleUserDetails}
|
||||||
handleUserDetails={props.handleUserDetails}
|
isResize={props.isResize}
|
||||||
isResize={props.isResize}
|
setIsAgreeTour={props.setIsAgreeTour}
|
||||||
setIsAgreeTour={props.setIsAgreeTour}
|
isAgree={props.isAgree}
|
||||||
isAgree={props.isAgree}
|
handleTabDrag={props.handleTabDrag}
|
||||||
handleTabDrag={props.handleTabDrag}
|
handleStop={props.handleStop}
|
||||||
handleStop={props.handleStop}
|
setUniqueId={props.setUniqueId}
|
||||||
setUniqueId={props.setUniqueId}
|
setIsSelectId={props.setIsSelectId}
|
||||||
setIsSelectId={props.setIsSelectId}
|
handleDeleteSign={props.handleDeleteSign}
|
||||||
handleDeleteSign={props.handleDeleteSign}
|
setIsPageCopy={props.setIsPageCopy}
|
||||||
setIsPageCopy={props.setIsPageCopy}
|
handleTextSettingModal={props.handleTextSettingModal}
|
||||||
handleTextSettingModal={props.handleTextSettingModal}
|
handleCellSettingModal={props.handleCellSettingModal}
|
||||||
setIsCheckbox={props.setIsCheckbox}
|
setIsCheckbox={props.setIsCheckbox}
|
||||||
isFreeResize={false}
|
isFreeResize={props.isSelfSign ? true : false}
|
||||||
isOpenSignPad={true}
|
isOpenSignPad={true}
|
||||||
assignedWidgetId={props.assignedWidgetId}
|
assignedWidgetId={props.assignedWidgetId}
|
||||||
isApplyAll={true}
|
isApplyAll={true}
|
||||||
setFontSize={props.setFontSize}
|
setCellCount={props.setCellCount}
|
||||||
fontSize={props.fontSize}
|
setFontSize={props.setFontSize}
|
||||||
fontColor={props.fontColor}
|
fontSize={props.fontSize}
|
||||||
setFontColor={props.setFontColor}
|
fontColor={props.fontColor}
|
||||||
setRequestSignTour={props.setRequestSignTour}
|
setFontColor={props.setFontColor}
|
||||||
calculateFontsize={calculateFontsize}
|
setRequestSignTour={props.setRequestSignTour}
|
||||||
currWidgetsDetails={props?.currWidgetsDetails}
|
calculateFontsize={calculateFontsize}
|
||||||
setTempSignerId={props.setTempSignerId}
|
currWidgetsDetails={props?.currWidgetsDetails}
|
||||||
/>
|
setTempSignerId={props.setTempSignerId}
|
||||||
</React.Fragment>
|
/>
|
||||||
)
|
</React.Fragment>
|
||||||
);
|
)
|
||||||
})}
|
)}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
))
|
||||||
})
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -172,148 +171,141 @@ function RenderPdf(props) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
const pdfDataBase64 = `data:application/pdf;base64,${props.pdfBase64Url}`;
|
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 handlePageLoadSuccess = (page) => {
|
||||||
const containerWidth = props.divRef.current.offsetWidth; // Get container width
|
if (isMobile) {
|
||||||
const viewport = page.getViewport({ scale: 1 });
|
const containerWidth = props.divRef.current.offsetWidth; // Get container width
|
||||||
const scale = containerWidth / viewport.width; // Scale to fit container width
|
const viewport = page.getViewport({ scale: 1 });
|
||||||
const scaleHeight = viewport.height * scale;
|
const scale = containerWidth / viewport.width; // Scale to fit container width
|
||||||
setScaledHeight(scaleHeight);
|
const scaleHeight = viewport.height * scale;
|
||||||
|
setScaledHeight(scaleHeight);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{props.successEmail && (
|
{props.successEmail && (
|
||||||
<Alert type={"success"}>{t("success-email-alert")}</Alert>
|
<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`
|
||||||
|
: ""
|
||||||
|
}
|
||||||
style={{
|
style={{
|
||||||
position: "relative",
|
width:
|
||||||
boxShadow: "rgba(17, 12, 46, 0.15) 0px 48px 100px 0px",
|
props.containerWH?.width && props.containerWH?.width * props.scale
|
||||||
//49 is height of header
|
|
||||||
height: isGuestSigner ? window.innerHeight - 49 : scaledHeight,
|
|
||||||
zIndex: 0
|
|
||||||
}}
|
}}
|
||||||
noScrollY={props.scale === 1 ? true : false}
|
ref={props.drop}
|
||||||
noScrollX={props.scale === 1 ? true : false}
|
id="container"
|
||||||
>
|
>
|
||||||
<div
|
{props.pdfLoad !== false &&
|
||||||
data-tut="reactourForth"
|
props.containerWH?.width &&
|
||||||
className={`${
|
props.pdfOriginalWH.length > 0 && (
|
||||||
isGuestSigner ? "30px" : ""
|
<>
|
||||||
} border-[0.1px] border-[#ebe8e8] overflow-x-auto`}
|
{props.pdfRequest || props.isSelfSign
|
||||||
style={{
|
? // request sign, guest sign,
|
||||||
width:
|
props.signerPos?.map((data, key) => (
|
||||||
props.containerWH?.width &&
|
|
||||||
props.containerWH?.width * props.scale
|
|
||||||
}}
|
|
||||||
ref={props.drop}
|
|
||||||
id="container"
|
|
||||||
>
|
|
||||||
{props.containerWH?.width &&
|
|
||||||
props.pdfOriginalWH.length > 0 &&
|
|
||||||
(props.pdfRequest || props.isSelfSign
|
|
||||||
? props.signerPos?.map((data, key) => {
|
|
||||||
return (
|
|
||||||
<React.Fragment key={key}>
|
<React.Fragment key={key}>
|
||||||
{checkSignedSigners(data)}
|
{checkSignedSigners(data)}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
))
|
||||||
})
|
: props.placeholder // placeholdersign document, draft document, create template, draft template
|
||||||
: props.placeholder // placeholder mobile
|
? props.signerPos?.map((data, ind) => (
|
||||||
? props.signerPos?.map((data, ind) => {
|
|
||||||
return (
|
|
||||||
<React.Fragment key={ind}>
|
<React.Fragment key={ind}>
|
||||||
{data?.placeHolder &&
|
{data?.placeHolder &&
|
||||||
data?.placeHolder.map((placeData, index) => {
|
data?.placeHolder.map((placeData, index) => (
|
||||||
return (
|
<React.Fragment key={index}>
|
||||||
<React.Fragment key={index}>
|
{placeData.pageNumber === props.pageNumber &&
|
||||||
{placeData.pageNumber === props.pageNumber &&
|
placeData.pos.map((pos) => (
|
||||||
placeData.pos.map((pos) => {
|
<React.Fragment key={pos.key}>
|
||||||
return (
|
<Placeholder
|
||||||
<React.Fragment key={pos.key}>
|
pos={pos}
|
||||||
<Placeholder
|
setIsPageCopy={props.setIsPageCopy}
|
||||||
pos={pos}
|
handleDeleteSign={
|
||||||
setIsPageCopy={props.setIsPageCopy}
|
props.handleDeleteSign
|
||||||
handleDeleteSign={
|
}
|
||||||
props.handleDeleteSign
|
handleTabDrag={props.handleTabDrag}
|
||||||
}
|
handleStop={props.handleStop}
|
||||||
handleTabDrag={props.handleTabDrag}
|
handleSignYourselfImageResize={
|
||||||
handleStop={props.handleStop}
|
handleImageResize
|
||||||
handleSignYourselfImageResize={
|
}
|
||||||
handleImageResize
|
index={props.pageNumber}
|
||||||
}
|
xyPosition={props.signerPos}
|
||||||
index={props.pageNumber}
|
setXyPosition={props.setSignerPos}
|
||||||
xyPosition={props.signerPos}
|
data={data}
|
||||||
setXyPosition={props.setSignerPos}
|
setIsResize={props.setIsResize}
|
||||||
data={data}
|
setShowDropdown={props.setShowDropdown}
|
||||||
setIsResize={props.setIsResize}
|
isShowBorder={true}
|
||||||
setShowDropdown={
|
isPlaceholder={true}
|
||||||
props.setShowDropdown
|
setUniqueId={props.setUniqueId}
|
||||||
}
|
handleLinkUser={props.handleLinkUser}
|
||||||
isShowBorder={true}
|
isSignYourself={false}
|
||||||
isPlaceholder={true}
|
posWidth={posWidth}
|
||||||
setUniqueId={props.setUniqueId}
|
posHeight={posHeight}
|
||||||
handleLinkUser={
|
isDragging={props.isDragging}
|
||||||
props.handleLinkUser
|
setIsValidate={props.setIsValidate}
|
||||||
}
|
setIsRadio={props.setIsRadio}
|
||||||
isSignYourself={false}
|
setIsCheckbox={props.setIsCheckbox}
|
||||||
posWidth={posWidth}
|
setCurrWidgetsDetails={
|
||||||
posHeight={posHeight}
|
props.setCurrWidgetsDetails
|
||||||
isDragging={props.isDragging}
|
}
|
||||||
setIsValidate={props.setIsValidate}
|
handleNameModal={props.handleNameModal}
|
||||||
setIsRadio={props.setIsRadio}
|
setTempSignerId={props.setTempSignerId}
|
||||||
setIsCheckbox={props.setIsCheckbox}
|
uniqueId={props.uniqueId}
|
||||||
setCurrWidgetsDetails={
|
handleTextSettingModal={
|
||||||
props.setCurrWidgetsDetails
|
props.handleTextSettingModal
|
||||||
}
|
}
|
||||||
handleNameModal={
|
handleCellSettingModal={
|
||||||
props.handleNameModal
|
props.handleCellSettingModal
|
||||||
}
|
}
|
||||||
setTempSignerId={
|
scale={props.scale}
|
||||||
props.setTempSignerId
|
containerWH={props.containerWH}
|
||||||
}
|
pdfOriginalWH={props.pdfOriginalWH}
|
||||||
uniqueId={props.uniqueId}
|
pageNumber={props.pageNumber}
|
||||||
handleTextSettingModal={
|
setIsSelectId={props.setIsSelectId}
|
||||||
props.handleTextSettingModal
|
fontSize={props.fontSize}
|
||||||
}
|
setFontSize={props.setFontSize}
|
||||||
scale={props.scale}
|
setCellCount={props.setCellCount}
|
||||||
containerWH={props.containerWH}
|
fontColor={props.fontColor}
|
||||||
pdfOriginalWH={props.pdfOriginalWH}
|
setFontColor={props.setFontColor}
|
||||||
pageNumber={props.pageNumber}
|
isResize={props.isResize}
|
||||||
setIsSelectId={props.setIsSelectId}
|
unSignedWidgetId={
|
||||||
fontSize={props.fontSize}
|
props.unSignedWidgetId
|
||||||
setFontSize={props.setFontSize}
|
}
|
||||||
fontColor={props.fontColor}
|
isFreeResize={true}
|
||||||
setFontColor={props.setFontColor}
|
calculateFontsize={calculateFontsize}
|
||||||
isResize={props.isResize}
|
currWidgetsDetails={
|
||||||
unSignedWidgetId={
|
props?.currWidgetsDetails
|
||||||
props.unSignedWidgetId
|
}
|
||||||
}
|
/>
|
||||||
isFreeResize={true}
|
</React.Fragment>
|
||||||
calculateFontsize={
|
))}
|
||||||
calculateFontsize
|
</React.Fragment>
|
||||||
}
|
))}
|
||||||
currWidgetsDetails={
|
|
||||||
props?.currWidgetsDetails
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</React.Fragment>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</React.Fragment>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
))
|
||||||
})
|
: !props.pdfDetails?.[0]?.IsCompleted && // signyourself flow
|
||||||
: !props.pdfDetails?.[0]?.IsCompleted &&
|
props.xyPosition?.map((data, ind) => (
|
||||||
props.xyPosition?.map((data, ind) => {
|
|
||||||
return (
|
|
||||||
<React.Fragment key={ind}>
|
<React.Fragment key={ind}>
|
||||||
{data.pageNumber === props.pageNumber &&
|
{data.pageNumber === props.pageNumber &&
|
||||||
data.pos.map((pos, id) => {
|
data.pos.map(
|
||||||
return (
|
(pos, id) =>
|
||||||
pos && (
|
pos && (
|
||||||
<Placeholder
|
<Placeholder
|
||||||
key={id}
|
key={id}
|
||||||
@@ -342,6 +334,9 @@ function RenderPdf(props) {
|
|||||||
handleTextSettingModal={
|
handleTextSettingModal={
|
||||||
props.handleTextSettingModal
|
props.handleTextSettingModal
|
||||||
}
|
}
|
||||||
|
handleCellSettingModal={
|
||||||
|
props.handleCellSettingModal
|
||||||
|
}
|
||||||
scale={props.scale}
|
scale={props.scale}
|
||||||
pdfOriginalWH={props.pdfOriginalWH}
|
pdfOriginalWH={props.pdfOriginalWH}
|
||||||
pageNumber={props.pageNumber}
|
pageNumber={props.pageNumber}
|
||||||
@@ -351,7 +346,7 @@ function RenderPdf(props) {
|
|||||||
setFontColor={props.setFontColor}
|
setFontColor={props.setFontColor}
|
||||||
isResize={props.isResize}
|
isResize={props.isResize}
|
||||||
setIsResize={props.setIsResize}
|
setIsResize={props.setIsResize}
|
||||||
isFreeResize={false}
|
isFreeResize={true}
|
||||||
isOpenSignPad={true}
|
isOpenSignPad={true}
|
||||||
calculateFontsize={calculateFontsize}
|
calculateFontsize={calculateFontsize}
|
||||||
currWidgetsDetails={
|
currWidgetsDetails={
|
||||||
@@ -359,247 +354,43 @@ function RenderPdf(props) {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
);
|
)}
|
||||||
})}
|
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
))}
|
||||||
}))}
|
</>
|
||||||
{/* Mobile */}
|
)}
|
||||||
<Document
|
<Document
|
||||||
error={<p className="mx-2">{t("failed-to-load-refresh-page")}</p>}
|
error={<p className="mx-2">{t("failed-to-load-refresh-page")}</p>}
|
||||||
onLoadError={() => props.setPdfLoad(false)}
|
onLoadError={(e) => {
|
||||||
loading={t("loading-doc")}
|
console.log("PDF load error", e);
|
||||||
onLoadSuccess={props.pageDetails}
|
props.setPdfLoad(false);
|
||||||
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
|
|
||||||
}}
|
}}
|
||||||
ref={props.drop}
|
loading={t("loading-doc")}
|
||||||
id="container"
|
onLoadSuccess={(pdf) => {
|
||||||
|
props.setPdfLoad(true);
|
||||||
|
props.pageDetails(pdf);
|
||||||
|
}}
|
||||||
|
onClick={() =>
|
||||||
|
props.setCurrWidgetsDetails && props.setCurrWidgetsDetails({})
|
||||||
|
}
|
||||||
|
file={pdfDataBase64}
|
||||||
>
|
>
|
||||||
{props.pdfLoad &&
|
<Page
|
||||||
props.containerWH?.width &&
|
key={props.index}
|
||||||
props.pdfOriginalWH.length > 0 &&
|
onLoadSuccess={handlePageLoadSuccess}
|
||||||
(props.pdfRequest || props.isSelfSign //pdf request sign flow
|
width={props.containerWH.width}
|
||||||
? props.signerPos?.map((data, key) => {
|
scale={props.scale || 1}
|
||||||
return (
|
className={isMobile ? "select-none touch-callout-none" : "-z-[1]"}
|
||||||
<React.Fragment key={key}>
|
pageNumber={props.pageNumber}
|
||||||
{checkSignedSigners(data)}
|
renderAnnotationLayer={false}
|
||||||
</React.Fragment>
|
renderTextLayer={false}
|
||||||
);
|
onGetAnnotationsError={(error) => {
|
||||||
})
|
console.log("annotation error", error);
|
||||||
: props.placeholder //placeholder and template flow
|
}}
|
||||||
? props.signerPos.map((data, ind) => {
|
/>
|
||||||
return (
|
</Document>
|
||||||
<React.Fragment key={ind}>
|
</div>
|
||||||
{data?.placeHolder &&
|
</RSC>
|
||||||
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);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Document>
|
|
||||||
</div>
|
|
||||||
</RSC>
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ function SelectLanguage(props) {
|
|||||||
{ value: "es", text: "Española" }, //spanish
|
{ value: "es", text: "Española" }, //spanish
|
||||||
{ value: "fr", text: "Français" }, //french
|
{ value: "fr", text: "Français" }, //french
|
||||||
{ value: "it", text: "Italiano" }, //italian
|
{ value: "it", text: "Italiano" }, //italian
|
||||||
{ value: "de", text: "Deutsch" } //german
|
{ value: "de", text: "Deutsch" }, //german
|
||||||
|
{ value: "hi", text: "हिन्दी" } //hindi
|
||||||
];
|
];
|
||||||
const defaultLanguage = i18next.language || "en";
|
const defaultLanguage = i18next.language || "en";
|
||||||
const [lang, setLang] = useState(defaultLanguage);
|
const [lang, setLang] = useState(defaultLanguage);
|
||||||
@@ -23,14 +24,14 @@ function SelectLanguage(props) {
|
|||||||
<div
|
<div
|
||||||
className={`${
|
className={`${
|
||||||
!props.isProfile && " mt-[9px] pb-2 md:pb-0 "
|
!props.isProfile && " mt-[9px] pb-2 md:pb-0 "
|
||||||
} flex justify-center items-center `}
|
} flex justify-center items-center text-base-content`}
|
||||||
>
|
>
|
||||||
<select
|
<select
|
||||||
value={lang}
|
value={lang}
|
||||||
onChange={handleChangeLang}
|
onChange={handleChangeLang}
|
||||||
className={`${
|
className={`${
|
||||||
!props.isProfile ? " md:w-[15%] w-[50%]" : "w-[180px]"
|
!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>
|
<option disabled>select</option>
|
||||||
{languages.map((item) => {
|
{languages.map((item) => {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ function TextFontSetting(props) {
|
|||||||
title={t("text-field")}
|
title={t("text-field")}
|
||||||
handleClose={() => props.setIsTextSetting(false)}
|
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">
|
<div className="flex flex-col md:flex-row md:items-center gap-3">
|
||||||
{/* Font Size Selector */}
|
{/* Font Size Selector */}
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
isMobile,
|
isMobile,
|
||||||
radioButtonWidget,
|
radioButtonWidget,
|
||||||
textInputWidget,
|
textInputWidget,
|
||||||
|
cellsWidget,
|
||||||
textWidget,
|
textWidget,
|
||||||
widgets
|
widgets
|
||||||
} from "../../constant/Utils";
|
} from "../../constant/Utils";
|
||||||
@@ -37,6 +38,10 @@ function WidgetComponent(props) {
|
|||||||
type: "BOX",
|
type: "BOX",
|
||||||
item: { type: "BOX", id: 7, text: textInputWidget }
|
item: { type: "BOX", id: 7, text: textInputWidget }
|
||||||
});
|
});
|
||||||
|
const [, cells] = useDrag({
|
||||||
|
type: "BOX",
|
||||||
|
item: { type: "BOX", id: 17, text: cellsWidget }
|
||||||
|
});
|
||||||
const [, initials] = useDrag({
|
const [, initials] = useDrag({
|
||||||
type: "BOX",
|
type: "BOX",
|
||||||
item: { type: "BOX", id: 8, text: "initials" }
|
item: { type: "BOX", id: 8, text: "initials" }
|
||||||
@@ -89,6 +94,7 @@ function WidgetComponent(props) {
|
|||||||
date,
|
date,
|
||||||
text,
|
text,
|
||||||
textInput,
|
textInput,
|
||||||
|
cells,
|
||||||
checkbox,
|
checkbox,
|
||||||
dropdown,
|
dropdown,
|
||||||
radioButton,
|
radioButton,
|
||||||
@@ -132,7 +138,9 @@ function WidgetComponent(props) {
|
|||||||
);
|
);
|
||||||
const filterWidgets = widget.filter(
|
const filterWidgets = widget.filter(
|
||||||
(data) =>
|
(data) =>
|
||||||
!["dropdown", radioButtonWidget, textInputWidget].includes(data.type)
|
!["dropdown", radioButtonWidget, textInputWidget].includes(
|
||||||
|
data.type
|
||||||
|
)
|
||||||
);
|
);
|
||||||
const textWidgetData = widget.filter((data) => data.type !== textWidget);
|
const textWidgetData = widget.filter((data) => data.type !== textWidget);
|
||||||
const updateWidgets = props.isSignYourself
|
const updateWidgets = props.isSignYourself
|
||||||
@@ -241,6 +249,7 @@ function WidgetComponent(props) {
|
|||||||
handleDivClick={props.handleDivClick}
|
handleDivClick={props.handleDivClick}
|
||||||
handleMouseLeave={props.handleMouseLeave}
|
handleMouseLeave={props.handleMouseLeave}
|
||||||
signRef={signRef}
|
signRef={signRef}
|
||||||
|
addPositionOfSignature={props.addPositionOfSignature}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import RegexParser from "regex-parser";
|
|||||||
import {
|
import {
|
||||||
signatureTypes,
|
signatureTypes,
|
||||||
textInputWidget,
|
textInputWidget,
|
||||||
|
cellsWidget,
|
||||||
textWidget
|
textWidget
|
||||||
} from "../../constant/Utils";
|
} from "../../constant/Utils";
|
||||||
import { fontColorArr, fontsizeArr } from "../../constant/Utils";
|
import { fontColorArr, fontsizeArr } from "../../constant/Utils";
|
||||||
@@ -19,7 +20,8 @@ const WidgetNameModal = (props) => {
|
|||||||
status: "required",
|
status: "required",
|
||||||
hint: "",
|
hint: "",
|
||||||
textvalidate: "",
|
textvalidate: "",
|
||||||
isReadOnly: false
|
isReadOnly: false,
|
||||||
|
cellCount: 5
|
||||||
});
|
});
|
||||||
const [isValid, setIsValid] = useState(true);
|
const [isValid, setIsValid] = useState(true);
|
||||||
const statusArr = ["Required", "Optional"];
|
const statusArr = ["Required", "Optional"];
|
||||||
@@ -51,12 +53,14 @@ const WidgetNameModal = (props) => {
|
|||||||
props.defaultdata?.options?.validation?.type === "regex"
|
props.defaultdata?.options?.validation?.type === "regex"
|
||||||
? props.defaultdata?.options?.validation?.pattern
|
? props.defaultdata?.options?.validation?.pattern
|
||||||
: props.defaultdata?.options?.validation?.type || "",
|
: props.defaultdata?.options?.validation?.type || "",
|
||||||
isReadOnly: props.defaultdata?.options?.isReadOnly || false
|
isReadOnly: props.defaultdata?.options?.isReadOnly || false,
|
||||||
|
cellCount: props.defaultdata?.options?.cellCount || 5
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setFormdata({
|
setFormdata({
|
||||||
...formdata,
|
...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);
|
props.handleData(data, props.defaultdata?.type);
|
||||||
}
|
}
|
||||||
} else {
|
} 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);
|
props.handleData(formdata);
|
||||||
}
|
}
|
||||||
setFormdata({
|
setFormdata({
|
||||||
@@ -91,7 +109,8 @@ const WidgetNameModal = (props) => {
|
|||||||
defaultValue: "",
|
defaultValue: "",
|
||||||
status: "required",
|
status: "required",
|
||||||
hint: "",
|
hint: "",
|
||||||
textvalidate: ""
|
textvalidate: "",
|
||||||
|
cellCount: 5
|
||||||
});
|
});
|
||||||
setSignatureType(signTypes);
|
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) => {
|
const handledefaultChange = (e) => {
|
||||||
if (formdata.textvalidate) {
|
if (formdata.textvalidate) {
|
||||||
const regexObject = RegexParser(handleValidation(formdata.textvalidate));
|
const regexObject = RegexParser(handleValidation(formdata.textvalidate));
|
||||||
@@ -112,7 +148,11 @@ const WidgetNameModal = (props) => {
|
|||||||
} else {
|
} else {
|
||||||
setIsValid(true);
|
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) {
|
function handleValidation(type) {
|
||||||
@@ -122,7 +162,10 @@ const WidgetNameModal = (props) => {
|
|||||||
case "number":
|
case "number":
|
||||||
return "/^\\d+$/";
|
return "/^\\d+$/";
|
||||||
case "text":
|
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:
|
default:
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
@@ -150,7 +193,7 @@ const WidgetNameModal = (props) => {
|
|||||||
<form
|
<form
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
className={`${
|
className={`${
|
||||||
props.defaultdata?.type === textInputWidget
|
[textInputWidget, cellsWidget].includes(props.defaultdata?.type)
|
||||||
? "pt-0"
|
? "pt-0"
|
||||||
: ["signature", "initials"].includes(props.defaultdata?.type)
|
: ["signature", "initials"].includes(props.defaultdata?.type)
|
||||||
? "pt-2"
|
? "pt-2"
|
||||||
@@ -174,7 +217,21 @@ const WidgetNameModal = (props) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</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]">
|
<div className="mb-[0.75rem]">
|
||||||
<label htmlFor="name" className="text-[13px]">
|
<label htmlFor="name" className="text-[13px]">
|
||||||
@@ -186,6 +243,11 @@ const WidgetNameModal = (props) => {
|
|||||||
value={formdata.defaultValue}
|
value={formdata.defaultValue}
|
||||||
onChange={(e) => handledefaultChange(e)}
|
onChange={(e) => handledefaultChange(e)}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
|
maxLength={
|
||||||
|
props.defaultdata?.type === cellsWidget
|
||||||
|
? formdata.cellCount
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
onBlur={() => {
|
onBlur={() => {
|
||||||
if (isValid === false) {
|
if (isValid === false) {
|
||||||
setFormdata({ ...formdata, defaultValue: "" });
|
setFormdata({ ...formdata, defaultValue: "" });
|
||||||
@@ -237,7 +299,9 @@ const WidgetNameModal = (props) => {
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
{[textInputWidget].includes(props.defaultdata?.type) && (
|
{[textInputWidget, cellsWidget].includes(
|
||||||
|
props.defaultdata?.type
|
||||||
|
) && (
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<input
|
<input
|
||||||
id="isReadOnly"
|
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")}
|
{t("read-only")}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@@ -304,6 +371,7 @@ const WidgetNameModal = (props) => {
|
|||||||
{[
|
{[
|
||||||
textInputWidget,
|
textInputWidget,
|
||||||
textWidget,
|
textWidget,
|
||||||
|
cellsWidget,
|
||||||
"name",
|
"name",
|
||||||
"company",
|
"company",
|
||||||
"job title",
|
"job title",
|
||||||
|
|||||||
@@ -14,11 +14,12 @@ import {
|
|||||||
onSaveSign,
|
onSaveSign,
|
||||||
radioButtonWidget,
|
radioButtonWidget,
|
||||||
selectCheckbox,
|
selectCheckbox,
|
||||||
signatureTypes,
|
|
||||||
textInputWidget,
|
textInputWidget,
|
||||||
|
cellsWidget,
|
||||||
textWidget,
|
textWidget,
|
||||||
years
|
years
|
||||||
} from "../../constant/Utils";
|
} from "../../constant/Utils";
|
||||||
|
import CellsWidget from "./CellsWidget";
|
||||||
import DatePicker from "react-datepicker";
|
import DatePicker from "react-datepicker";
|
||||||
import "react-datepicker/dist/react-datepicker.css";
|
import "react-datepicker/dist/react-datepicker.css";
|
||||||
import SignatureCanvas from "react-signature-canvas";
|
import SignatureCanvas from "react-signature-canvas";
|
||||||
@@ -45,9 +46,9 @@ import RegexParser from "regex-parser";
|
|||||||
|
|
||||||
//function to get default format
|
//function to get default format
|
||||||
const getDefaultFormat = (dateFormat) => dateFormat || "MM/dd/yyyy";
|
const getDefaultFormat = (dateFormat) => dateFormat || "MM/dd/yyyy";
|
||||||
//function to convert formated date to new Date() format
|
//function to convert formatted date to new Date() format
|
||||||
const getDefaultDate = (dateStr, format) => {
|
const getDefaultDate = (dateStr, format) => {
|
||||||
//get valid date format for moment to convert formated date to new Date() format
|
//get valid date format for moment to convert formatted date to new Date() format
|
||||||
const formats = changeDateToMomentFormat(format);
|
const formats = changeDateToMomentFormat(format);
|
||||||
const parsedDate = moment(dateStr, formats);
|
const parsedDate = moment(dateStr, formats);
|
||||||
let date;
|
let date;
|
||||||
@@ -85,7 +86,10 @@ function WidgetsValueModal(props) {
|
|||||||
setXyPosition,
|
setXyPosition,
|
||||||
isSave,
|
isSave,
|
||||||
setUniqueId,
|
setUniqueId,
|
||||||
tempSignerId
|
tempSignerId,
|
||||||
|
signatureTypes,
|
||||||
|
setCellCount,
|
||||||
|
allowCellResize = true
|
||||||
} = props;
|
} = props;
|
||||||
const [penColor, setPenColor] = useState("blue");
|
const [penColor, setPenColor] = useState("blue");
|
||||||
const [isOptional, setIsOptional] = useState(true);
|
const [isOptional, setIsOptional] = useState(true);
|
||||||
@@ -93,7 +97,6 @@ function WidgetsValueModal(props) {
|
|||||||
const [isTab, setIsTab] = useState("");
|
const [isTab, setIsTab] = useState("");
|
||||||
const [textWidth, setTextWidth] = useState(0);
|
const [textWidth, setTextWidth] = useState(0);
|
||||||
const [textHeight, setTextHeight] = useState(0);
|
const [textHeight, setTextHeight] = useState(0);
|
||||||
const [signatureType, setSignatureType] = useState("");
|
|
||||||
const [isSignTypes, setIsSignTypes] = useState(true);
|
const [isSignTypes, setIsSignTypes] = useState(true);
|
||||||
const [typedSignature, setTypedSignature] = useState("");
|
const [typedSignature, setTypedSignature] = useState("");
|
||||||
const [selectDate, setSelectDate] = useState({});
|
const [selectDate, setSelectDate] = useState({});
|
||||||
@@ -119,13 +122,42 @@ function WidgetsValueModal(props) {
|
|||||||
const currentUserName = jsonSender && jsonSender?.name;
|
const currentUserName = jsonSender && jsonSender?.name;
|
||||||
const widgetTypeTranslation = t(`widgets-name.${currWidgetsDetails?.type}`);
|
const widgetTypeTranslation = t(`widgets-name.${currWidgetsDetails?.type}`);
|
||||||
const [widgetValue, setWidgetValue] = useState(
|
const [widgetValue, setWidgetValue] = useState(
|
||||||
currWidgetsDetails?.options?.response ||
|
currWidgetsDetails.type !== "checkbox" &&
|
||||||
currWidgetsDetails?.options?.defaultValue
|
(currWidgetsDetails?.options?.response ||
|
||||||
|
currWidgetsDetails?.options?.defaultValue)
|
||||||
);
|
);
|
||||||
const [selectedCheckbox, setSelectedCheckbox] = useState(
|
const [cellsValue, setCellsValue] = useState(() => {
|
||||||
currWidgetsDetails?.options?.response ||
|
const count = currWidgetsDetails?.options?.cellCount || 5;
|
||||||
|
const val =
|
||||||
|
currWidgetsDetails?.options?.response ||
|
||||||
currWidgetsDetails?.options?.defaultValue ||
|
currWidgetsDetails?.options?.defaultValue ||
|
||||||
[]
|
"";
|
||||||
|
return Array.from({ length: count }, (_, i) => val[i] || "");
|
||||||
|
});
|
||||||
|
const cellRefs = useRef([]);
|
||||||
|
// keep track of the first empty cell to automatically focus it after updates
|
||||||
|
useEffect(() => {
|
||||||
|
const index = cellsValue.findIndex((v) => !v);
|
||||||
|
if (index !== -1) {
|
||||||
|
setTimeout(() => {
|
||||||
|
cellRefs.current[index]?.focus();
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
}, [cellsValue]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const count = currWidgetsDetails?.options?.cellCount || 5;
|
||||||
|
const val =
|
||||||
|
currWidgetsDetails?.options?.response ||
|
||||||
|
currWidgetsDetails?.options?.defaultValue ||
|
||||||
|
"";
|
||||||
|
setCellsValue(Array.from({ length: count }, (_, i) => val[i] || ""));
|
||||||
|
}, [currWidgetsDetails?.key, currWidgetsDetails?.options?.cellCount]);
|
||||||
|
const [selectedCheckbox, setSelectedCheckbox] = useState(
|
||||||
|
currWidgetsDetails.type === "checkbox" &&
|
||||||
|
(currWidgetsDetails?.options?.response ||
|
||||||
|
currWidgetsDetails?.options?.defaultValue ||
|
||||||
|
[])
|
||||||
);
|
);
|
||||||
const [startDate, setStartDate] = useState(
|
const [startDate, setStartDate] = useState(
|
||||||
currWidgetsDetails?.options?.response
|
currWidgetsDetails?.options?.response
|
||||||
@@ -137,7 +169,7 @@ function WidgetsValueModal(props) {
|
|||||||
);
|
);
|
||||||
const allColor = ["blue", "red", "black"];
|
const allColor = ["blue", "red", "black"];
|
||||||
const textInputcls =
|
const textInputcls =
|
||||||
"op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs";
|
"op-input op-input-bordered op-input-sm focus:outline-none text-base-content hover:border-base-content w-full text-xs";
|
||||||
const isTabCls = "bg-[#002864] text-white rounded-[15px] px-[10px] py-[4px]";
|
const isTabCls = "bg-[#002864] text-white rounded-[15px] px-[10px] py-[4px]";
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
@@ -156,34 +188,6 @@ function WidgetsValueModal(props) {
|
|||||||
setHint(currWidgetsDetails?.type);
|
setHint(currWidgetsDetails?.type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//set already draw or save signature url/text url of signature text type and draw type for initial type and signature type widgets
|
|
||||||
if (currWidgetsDetails && canvasRef.current) {
|
|
||||||
const isWidgetType = currWidgetsDetails?.type;
|
|
||||||
const signatureType = currWidgetsDetails?.signatureType;
|
|
||||||
const url = currWidgetsDetails?.SignUrl;
|
|
||||||
//checking widget type and draw type signature url
|
|
||||||
if (currWidgetsDetails?.type === "initials") {
|
|
||||||
if (isWidgetType === "initials" && signatureType === "draw" && url) {
|
|
||||||
canvasRef.current.fromDataURL(url);
|
|
||||||
}
|
|
||||||
} else if (
|
|
||||||
isWidgetType === "signature" &&
|
|
||||||
signatureType === "draw" &&
|
|
||||||
url
|
|
||||||
) {
|
|
||||||
canvasRef.current.fromDataURL(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
const trimmedName = currentUserName && currentUserName?.trim();
|
|
||||||
const firstCharacter = trimmedName?.charAt(0);
|
|
||||||
const userName =
|
|
||||||
currWidgetsDetails?.type === "initials"
|
|
||||||
? firstCharacter
|
|
||||||
: currentUserName;
|
|
||||||
const signatureValue = currWidgetsDetails?.typeSignature;
|
|
||||||
setTypedSignature(signatureValue || userName || "");
|
|
||||||
setFontSelect("Fasthand");
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [currWidgetsDetails]); // Added currWidgetsDetails to dependency array for reset logic
|
}, [currWidgetsDetails]); // Added currWidgetsDetails to dependency array for reset logic
|
||||||
|
|
||||||
@@ -193,7 +197,7 @@ function WidgetsValueModal(props) {
|
|||||||
setRemoveBgEnabled(false);
|
setRemoveBgEnabled(false);
|
||||||
}, [currWidgetsDetails?.key]);
|
}, [currWidgetsDetails?.key]);
|
||||||
|
|
||||||
//function to save date and format after seleted new date in response field and after finish document it should be emebed new selected date instead of current date
|
//function to save date and format after selected new date in response field and after finish document it should embed the new selected date instead of current date
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (currWidgetsDetails?.type === "date") {
|
if (currWidgetsDetails?.type === "date") {
|
||||||
const isDateChange = true;
|
const isDateChange = true;
|
||||||
@@ -231,7 +235,9 @@ function WidgetsValueModal(props) {
|
|||||||
setXyPosition,
|
setXyPosition,
|
||||||
uniqueId,
|
uniqueId,
|
||||||
false,
|
false,
|
||||||
data?.format
|
data?.format,
|
||||||
|
currWidgetsDetails?.options?.fontSize || 12,
|
||||||
|
currWidgetsDetails?.options?.fontColor || "black"
|
||||||
);
|
);
|
||||||
setSelectDate({ date: date, format: data?.format });
|
setSelectDate({ date: date, format: data?.format });
|
||||||
};
|
};
|
||||||
@@ -398,10 +404,9 @@ function WidgetsValueModal(props) {
|
|||||||
|
|
||||||
if (getIndex !== -1) {
|
if (getIndex !== -1) {
|
||||||
setIsSignTypes(true);
|
setIsSignTypes(true);
|
||||||
const tab = signatureTypes[getIndex].name;
|
const tab = signatureTypes?.[getIndex].name;
|
||||||
if (tab === "draw") {
|
if (tab === "draw") {
|
||||||
setIsTab("draw");
|
setIsTab("draw");
|
||||||
setSignatureType("draw");
|
|
||||||
} else if (tab === "upload") {
|
} else if (tab === "upload") {
|
||||||
setIsImageSelect(true);
|
setIsImageSelect(true);
|
||||||
setIsTab("uploadImage");
|
setIsTab("uploadImage");
|
||||||
@@ -425,7 +430,7 @@ function WidgetsValueModal(props) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
function isTabEnabled(tabName) {
|
function isTabEnabled(tabName) {
|
||||||
const isEnabled = signatureTypes.find((x) => x.name === tabName)?.enabled;
|
const isEnabled = signatureTypes?.find((x) => x.name === tabName)?.enabled;
|
||||||
return isEnabled;
|
return isEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -447,7 +452,32 @@ function WidgetsValueModal(props) {
|
|||||||
setTypedSignature("");
|
setTypedSignature("");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
setWidgetValue("");
|
if (currWidgetsDetails?.type === cellsWidget) {
|
||||||
|
const count =
|
||||||
|
currWidgetsDetails?.options?.cellCount || cellsValue.length || 1;
|
||||||
|
const cleared = Array.from({ length: count }, () => "");
|
||||||
|
setCellsValue(cleared);
|
||||||
|
const combined = cleared.join("");
|
||||||
|
setWidgetValue(combined);
|
||||||
|
onChangeInput(
|
||||||
|
combined,
|
||||||
|
currWidgetsDetails?.key,
|
||||||
|
xyPosition,
|
||||||
|
props.index,
|
||||||
|
setXyPosition,
|
||||||
|
uniqueId
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
setWidgetValue("");
|
||||||
|
onChangeInput(
|
||||||
|
"",
|
||||||
|
currWidgetsDetails?.key,
|
||||||
|
xyPosition,
|
||||||
|
props.index,
|
||||||
|
setXyPosition,
|
||||||
|
uniqueId
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
//function for set signature url
|
//function for set signature url
|
||||||
@@ -581,7 +611,7 @@ function WidgetsValueModal(props) {
|
|||||||
if (isTab === "mysignature") {
|
if (isTab === "mysignature") {
|
||||||
setSignature("");
|
setSignature("");
|
||||||
if (currWidgetsDetails?.type === "initials") {
|
if (currWidgetsDetails?.type === "initials") {
|
||||||
handleSaveSignature(signatureType, "initials");
|
handleSaveSignature(isTab, "initials");
|
||||||
} else {
|
} else {
|
||||||
handleSaveSignature(null, "default");
|
handleSaveSignature(null, "default");
|
||||||
}
|
}
|
||||||
@@ -602,34 +632,32 @@ function WidgetsValueModal(props) {
|
|||||||
} else {
|
} else {
|
||||||
setSignature("");
|
setSignature("");
|
||||||
canvasRef?.current?.clear();
|
canvasRef?.current?.clear();
|
||||||
handleSaveSignature(signatureType);
|
handleSaveSignature(isTab);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setPenColor("blue");
|
setPenColor("blue");
|
||||||
} else {
|
} else {
|
||||||
setSignature("");
|
setSignature("");
|
||||||
handleSaveImage(signatureType);
|
handleSaveImage();
|
||||||
}
|
}
|
||||||
setIsImageSelect(false);
|
setIsImageSelect(false);
|
||||||
setIsDefaultSign(false);
|
setIsDefaultSign(false);
|
||||||
setImage();
|
setImage();
|
||||||
handleTab();
|
handleTab();
|
||||||
};
|
};
|
||||||
const autoSignAll = () => {
|
const autoSignAll = (
|
||||||
return (
|
<label className="mb-0 cursor-pointer flex items-center text-sm">
|
||||||
<label className="cursor-pointer flex items-center text-sm">
|
<input
|
||||||
<input
|
className="mr-2 md:mr-3 op-checkbox op-checkbox-xs md:op-checkbox-sm"
|
||||||
className="mr-2 md:mr-3 op-checkbox op-checkbox-xs md:op-checkbox-sm"
|
type="checkbox"
|
||||||
type="checkbox"
|
value={isAutoSign}
|
||||||
value={isAutoSign}
|
onChange={(e) => {
|
||||||
onChange={(e) => {
|
setIsAutoSign(e.target.checked);
|
||||||
setIsAutoSign(e.target.checked);
|
}}
|
||||||
}}
|
/>
|
||||||
/>
|
{t("auto-sign-mssg")}
|
||||||
{t("auto-sign-mssg")}
|
</label>
|
||||||
</label>
|
);
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadFont = async () => {
|
const loadFont = async () => {
|
||||||
@@ -649,24 +677,21 @@ function WidgetsValueModal(props) {
|
|||||||
}, [fontSelect]);
|
}, [fontSelect]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (currWidgetsDetails?.options?.response) {
|
||||||
["signature", "initials"].includes(currWidgetsDetails?.type) &&
|
const url = currWidgetsDetails?.options?.response;
|
||||||
widgetValue
|
if (["signature", "initials"].includes(currWidgetsDetails?.type)) {
|
||||||
) {
|
if (isTab === "draw" && currWidgetsDetails?.signatureType === "draw") {
|
||||||
if (isTab === "draw" && currWidgetsDetails?.signatureType === "draw") {
|
setSignature(url);
|
||||||
setSignature(widgetValue);
|
// Load the default signature after the component mounts
|
||||||
} else if (isTab === "uploadImage" && currWidgetsDetails?.ImageType) {
|
if (canvasRef.current) {
|
||||||
setImage({ imgType: currWidgetsDetails?.ImageType, src: widgetValue });
|
canvasRef.current.fromDataURL(url);
|
||||||
|
}
|
||||||
|
} else if (isTab === "uploadImage" && currWidgetsDetails?.ImageType) {
|
||||||
|
setImage({ imgType: currWidgetsDetails?.ImageType, src: url });
|
||||||
|
}
|
||||||
|
} else if (["image", "stamp"].includes(currWidgetsDetails?.type)) {
|
||||||
|
setImage({ imgType: currWidgetsDetails?.ImageType, src: url });
|
||||||
}
|
}
|
||||||
} else if (
|
|
||||||
["image", "stamp"].includes(currWidgetsDetails?.type) &&
|
|
||||||
widgetValue
|
|
||||||
) {
|
|
||||||
setImage({ imgType: currWidgetsDetails?.ImageType, src: widgetValue });
|
|
||||||
}
|
|
||||||
// Load the default signature after the component mounts
|
|
||||||
if (canvasRef.current) {
|
|
||||||
canvasRef.current.fromDataURL(signature);
|
|
||||||
}
|
}
|
||||||
if (isTab === "type") {
|
if (isTab === "type") {
|
||||||
const trimmedName = typedSignature
|
const trimmedName = typedSignature
|
||||||
@@ -683,57 +708,70 @@ function WidgetsValueModal(props) {
|
|||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [isTab]);
|
}, [isTab]);
|
||||||
//function for convert input text value in image
|
// function for convert input text value in image
|
||||||
const convertToImg = async (fontStyle, text, color) => {
|
const convertToImg = async (fontStyle, text, color) => {
|
||||||
//get text content to convert in image
|
// 1) Read the max widget dimensions:
|
||||||
const textContent = text;
|
const maxWidth = currWidgetsDetails.Width; // e.g. 150 px
|
||||||
const fontfamily = fontStyle
|
const maxHeight = currWidgetsDetails.Height; // e.g. 40 px
|
||||||
? fontStyle
|
|
||||||
: fontSelect
|
// 2) Pick a “baseline” font size for measurement:
|
||||||
? fontSelect
|
const baselineFontSizePx = 40;
|
||||||
: "Fasthand";
|
const chosenFontFamily = fontStyle || fontSelect || "Fasthand";
|
||||||
const fontSizeValue = "40px";
|
const fillColor = color || penColor;
|
||||||
//creating span for getting text content width
|
|
||||||
|
// 3) Create a temporary <span> (hidden) to measure the text at 40px:
|
||||||
const span = document.createElement("span");
|
const span = document.createElement("span");
|
||||||
span.textContent = textContent;
|
span.textContent = text;
|
||||||
span.style.font = `${fontSizeValue} ${fontfamily}`; // here put your text size and font family
|
span.style.font = `${baselineFontSizePx}px ${chosenFontFamily}`;
|
||||||
span.style.color = color ? color : penColor;
|
span.style.visibility = "hidden"; // keep it in the DOM so offsetWidth/Height works
|
||||||
span.style.display = "hidden";
|
span.style.whiteSpace = "nowrap"; // so we measure a single line
|
||||||
document.body.appendChild(span); // Replace 'container' with the ID of the container element
|
document.body.appendChild(span);
|
||||||
|
|
||||||
//create canvas to render text in canvas and convert in image
|
// Measured size at 40px:
|
||||||
const canvasElement = document.createElement("canvas");
|
const measuredWidth = span.offsetWidth;
|
||||||
// Draw the text content on the canvas
|
const measuredHeight = span.offsetHeight;
|
||||||
const ctx = canvasElement.getContext("2d");
|
document.body.removeChild(span);
|
||||||
|
|
||||||
|
// 4) Compute uniform scale so that 40px‐sized text fits inside (maxWidth × maxHeight):
|
||||||
|
const scaleX = maxWidth / measuredWidth;
|
||||||
|
const scaleY = maxHeight / measuredHeight;
|
||||||
|
const scale = Math.min(scaleX, scaleY, 1); // never scale up beyond 1
|
||||||
|
|
||||||
|
// 5) Final text size in **CSS px**:
|
||||||
|
const finalFontSizePx = baselineFontSizePx * scale;
|
||||||
|
|
||||||
|
// 6) Create a <canvas> that is ALWAYS maxWidth × maxHeight in **CSS px**,
|
||||||
|
// but use devicePixelRatio for sharpness.
|
||||||
const pixelRatio = window.devicePixelRatio || 1;
|
const pixelRatio = window.devicePixelRatio || 1;
|
||||||
const addExtraWidth = currWidgetsDetails?.type === "initials" ? 10 : 50;
|
const canvas = document.createElement("canvas");
|
||||||
const width = span.offsetWidth + addExtraWidth;
|
|
||||||
const height = span.offsetHeight;
|
|
||||||
setTextWidth(width);
|
|
||||||
setTextHeight(height);
|
|
||||||
const font = span.style["font"];
|
|
||||||
// Set the canvas dimensions to match the span
|
|
||||||
canvasElement.width = width * pixelRatio;
|
|
||||||
canvasElement.height = height * pixelRatio;
|
|
||||||
|
|
||||||
// You can customize text styles if needed
|
// ★ Instead of using `finalTextWidth/Height`, force it to be the max box:
|
||||||
ctx.font = font;
|
canvas.width = Math.ceil(maxWidth * pixelRatio);
|
||||||
ctx.fillStyle = color ? color : penColor; // Set the text color
|
canvas.height = Math.ceil(maxHeight * pixelRatio);
|
||||||
|
|
||||||
|
const ctx = canvas.getContext("2d");
|
||||||
|
ctx.scale(pixelRatio, pixelRatio);
|
||||||
|
|
||||||
|
// 7) Draw the text **centered** inside the full maxWidth×maxHeight box:
|
||||||
|
ctx.font = `${finalFontSizePx}px ${chosenFontFamily}`;
|
||||||
|
ctx.fillStyle = fillColor;
|
||||||
ctx.textAlign = "center";
|
ctx.textAlign = "center";
|
||||||
ctx.textBaseline = "middle";
|
ctx.textBaseline = "middle";
|
||||||
ctx.scale(pixelRatio, pixelRatio);
|
|
||||||
// Draw the content of the span onto the canvas
|
// ★ Center = (maxWidth/2, maxHeight/2):
|
||||||
ctx.fillText(span.textContent, width / 2, height / 2); // Adjust the x,y-coordinate as needed
|
const centerX = maxWidth / 2;
|
||||||
//remove span tag
|
const centerY = maxHeight / 2;
|
||||||
document.body.removeChild(span);
|
|
||||||
// Convert the canvas to image data
|
ctx.fillText(text, centerX, centerY);
|
||||||
const dataUrl = canvasElement.toDataURL("image/png");
|
|
||||||
|
// 8) Export to a PNG data-URL:
|
||||||
|
const dataUrl = canvas.toDataURL("image/png");
|
||||||
setSignature(dataUrl);
|
setSignature(dataUrl);
|
||||||
};
|
};
|
||||||
const PenColorComponent = (props) => {
|
const PenColorComponent = (props) => {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-row items-center m-[5px] gap-3">
|
<div className="flex flex-row items-center m-[5px] gap-3">
|
||||||
<span>Options</span>
|
<span className="text-base-content">Options</span>
|
||||||
{allColor.map((data, key) => {
|
{allColor.map((data, key) => {
|
||||||
return (
|
return (
|
||||||
<i
|
<i
|
||||||
@@ -857,7 +895,7 @@ function WidgetsValueModal(props) {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const savesigncheckbox = (
|
const savesigncheckbox = (
|
||||||
<label className="cursor-pointer flex items-center mb-0 text-center text-[11px] md:text-base">
|
<label className="cursor-pointer flex items-center mb-0 text-center text-sm">
|
||||||
<input
|
<input
|
||||||
className="mr-2 md:mr-3 op-checkbox op-checkbox-xs md:op-checkbox-sm"
|
className="mr-2 md:mr-3 op-checkbox op-checkbox-xs md:op-checkbox-sm"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
@@ -929,6 +967,7 @@ function WidgetsValueModal(props) {
|
|||||||
setStartDate(date);
|
setStartDate(date);
|
||||||
};
|
};
|
||||||
const handleOnchangeTextBox = (e) => {
|
const handleOnchangeTextBox = (e) => {
|
||||||
|
// hide any prior validation error while typing
|
||||||
setIsShowValidation(false);
|
setIsShowValidation(false);
|
||||||
setWidgetValue(e.target.value);
|
setWidgetValue(e.target.value);
|
||||||
onChangeInput(
|
onChangeInput(
|
||||||
@@ -940,6 +979,55 @@ function WidgetsValueModal(props) {
|
|||||||
uniqueId
|
uniqueId
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
const updateCells = (updated) => {
|
||||||
|
setCellsValue(updated);
|
||||||
|
const combined = updated.join("");
|
||||||
|
setWidgetValue(combined);
|
||||||
|
props.setCurrWidgetsDetails?.((prev) =>
|
||||||
|
prev && prev.key === currWidgetsDetails?.key
|
||||||
|
? { ...prev, options: { ...prev.options, response: combined } }
|
||||||
|
: prev
|
||||||
|
);
|
||||||
|
onChangeInput(
|
||||||
|
combined,
|
||||||
|
currWidgetsDetails?.key,
|
||||||
|
xyPosition,
|
||||||
|
props.index,
|
||||||
|
setXyPosition,
|
||||||
|
uniqueId
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCellsInput = (e, idx) => {
|
||||||
|
setIsShowValidation(false);
|
||||||
|
const val = e.target.value.slice(0, 1);
|
||||||
|
const updated = [...cellsValue];
|
||||||
|
updated[idx] = val;
|
||||||
|
updateCells(updated);
|
||||||
|
};
|
||||||
|
const handleCellsKeyDown = (e, idx) => {
|
||||||
|
if (e.key === "Backspace" && !cellsValue[idx] && idx > 0) {
|
||||||
|
e.preventDefault();
|
||||||
|
cellRefs.current[idx - 1]?.focus();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCellResize = (newCount) => {
|
||||||
|
let updated = [...cellsValue];
|
||||||
|
if (newCount > updated.length) {
|
||||||
|
updated = [...updated, ...Array(newCount - updated.length).fill("")];
|
||||||
|
} else if (newCount < updated.length) {
|
||||||
|
updated = updated.slice(0, newCount);
|
||||||
|
}
|
||||||
|
cellRefs.current = cellRefs.current.slice(0, newCount);
|
||||||
|
updateCells(updated);
|
||||||
|
setCellCount?.(currWidgetsDetails?.key, newCount);
|
||||||
|
};
|
||||||
|
|
||||||
|
// when focus leaves the cells widget, validate the input
|
||||||
|
const handleCellsBlur = (e, idx) => {
|
||||||
|
handleInputBlur();
|
||||||
|
};
|
||||||
//function is used to show widgets on modal according to selected widget type checkbox/date/radio/drodown/textbox/signature/image
|
//function is used to show widgets on modal according to selected widget type checkbox/date/radio/drodown/textbox/signature/image
|
||||||
const getWidgetType = (type) => {
|
const getWidgetType = (type) => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
@@ -958,7 +1046,7 @@ function WidgetsValueModal(props) {
|
|||||||
<>
|
<>
|
||||||
{currWidgetsDetails?.type !== "stamp" &&
|
{currWidgetsDetails?.type !== "stamp" &&
|
||||||
currWidgetsDetails?.type !== "image" && (
|
currWidgetsDetails?.type !== "image" && (
|
||||||
<div className="text-base-content bg-gray-100 border-[1px] border-gray-200 rounded-[4px] tabWidth">
|
<div className="text-base-content rounded-[4px] tabWidth">
|
||||||
<div className="ml-3 flex justify-start gap-4 text-[11px] md:text-base my-[3px]">
|
<div className="ml-3 flex justify-start gap-4 text-[11px] md:text-base my-[3px]">
|
||||||
<>
|
<>
|
||||||
{currWidgetsDetails?.type !== "initials" &&
|
{currWidgetsDetails?.type !== "initials" &&
|
||||||
@@ -970,7 +1058,6 @@ function WidgetsValueModal(props) {
|
|||||||
setIsDefaultSign(true);
|
setIsDefaultSign(true);
|
||||||
setIsImageSelect(true);
|
setIsImageSelect(true);
|
||||||
setIsTab("mysignature");
|
setIsTab("mysignature");
|
||||||
setSignatureType("");
|
|
||||||
setImage();
|
setImage();
|
||||||
}}
|
}}
|
||||||
className={`${
|
className={`${
|
||||||
@@ -990,7 +1077,6 @@ function WidgetsValueModal(props) {
|
|||||||
setIsDefaultSign(true);
|
setIsDefaultSign(true);
|
||||||
setIsImageSelect(true);
|
setIsImageSelect(true);
|
||||||
setIsTab("mysignature");
|
setIsTab("mysignature");
|
||||||
setSignatureType("");
|
|
||||||
setImage();
|
setImage();
|
||||||
}}
|
}}
|
||||||
className={`${
|
className={`${
|
||||||
@@ -1027,7 +1113,6 @@ function WidgetsValueModal(props) {
|
|||||||
setIsDefaultSign(false);
|
setIsDefaultSign(false);
|
||||||
setIsImageSelect(true);
|
setIsImageSelect(true);
|
||||||
setIsTab("uploadImage");
|
setIsTab("uploadImage");
|
||||||
setSignatureType("");
|
|
||||||
}}
|
}}
|
||||||
className={`${
|
className={`${
|
||||||
isTab === "uploadImage" && `${isTabCls}`
|
isTab === "uploadImage" && `${isTabCls}`
|
||||||
@@ -1044,7 +1129,6 @@ function WidgetsValueModal(props) {
|
|||||||
setIsDefaultSign(false);
|
setIsDefaultSign(false);
|
||||||
setIsImageSelect(false);
|
setIsImageSelect(false);
|
||||||
setIsTab("type");
|
setIsTab("type");
|
||||||
setSignatureType("");
|
|
||||||
setImage();
|
setImage();
|
||||||
}}
|
}}
|
||||||
className={`${
|
className={`${
|
||||||
@@ -1059,7 +1143,14 @@ function WidgetsValueModal(props) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="mt-4 h-full">
|
<div
|
||||||
|
className={`${
|
||||||
|
currWidgetsDetails?.type === "stamp" ||
|
||||||
|
currWidgetsDetails?.type === "image"
|
||||||
|
? ""
|
||||||
|
: "mt-3"
|
||||||
|
} h-full`}
|
||||||
|
>
|
||||||
{isDefaultSign ? (
|
{isDefaultSign ? (
|
||||||
<>
|
<>
|
||||||
{currWidgetsDetails?.type !== "initials" &&
|
{currWidgetsDetails?.type !== "initials" &&
|
||||||
@@ -1085,7 +1176,7 @@ function WidgetsValueModal(props) {
|
|||||||
{/* Standalone autoSignAll for "My Signature/Initials" (isDefaultSign) if conditions met */}
|
{/* Standalone autoSignAll for "My Signature/Initials" (isDefaultSign) if conditions met */}
|
||||||
{setIsAutoSign && uniqueId && (
|
{setIsAutoSign && uniqueId && (
|
||||||
<div className="flex justify-center my-2">
|
<div className="flex justify-center my-2">
|
||||||
{autoSignAll()}
|
{autoSignAll}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
@@ -1114,7 +1205,7 @@ function WidgetsValueModal(props) {
|
|||||||
{/* Standalone autoSignAll for "My Signature/Initials" (isDefaultSign) if conditions met */}
|
{/* Standalone autoSignAll for "My Signature/Initials" (isDefaultSign) if conditions met */}
|
||||||
{setIsAutoSign && uniqueId && (
|
{setIsAutoSign && uniqueId && (
|
||||||
<div className="flex justify-center my-2">
|
<div className="flex justify-center my-2">
|
||||||
{autoSignAll()}
|
{autoSignAll}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
@@ -1163,9 +1254,8 @@ function WidgetsValueModal(props) {
|
|||||||
["image", "stamp"].includes(
|
["image", "stamp"].includes(
|
||||||
currWidgetsDetails?.type
|
currWidgetsDetails?.type
|
||||||
)))) && (
|
)))) && (
|
||||||
<div className="flex justify-center items-center space-x-4 my-2">
|
<div className="flex justify-center items-center gap-x-2 my-2">
|
||||||
{setIsAutoSign && uniqueId && autoSignAll()}
|
{setIsAutoSign && uniqueId && autoSignAll}
|
||||||
|
|
||||||
{image &&
|
{image &&
|
||||||
(isImageSelect ||
|
(isImageSelect ||
|
||||||
["image", "stamp"].includes(
|
["image", "stamp"].includes(
|
||||||
@@ -1173,7 +1263,7 @@ function WidgetsValueModal(props) {
|
|||||||
)) && (
|
)) && (
|
||||||
<label
|
<label
|
||||||
htmlFor={`removeBgToggleModal-${currWidgetsDetails?.key}`}
|
htmlFor={`removeBgToggleModal-${currWidgetsDetails?.key}`}
|
||||||
className="cursor-pointer flex items-center text-sm"
|
className="mb-0 cursor-pointer flex items-center text-sm"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
@@ -1253,16 +1343,18 @@ function WidgetsValueModal(props) {
|
|||||||
<div className="flex flex-row justify-between mt-[10px]">
|
<div className="flex flex-row justify-between mt-[10px]">
|
||||||
<PenColorComponent />
|
<PenColorComponent />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col mt-2">
|
<div className="flex flex-row ml-1 mt-2 gap-x-3 text-base-content">
|
||||||
{/* Standalone autoSignAll for "Type" tab if conditions met */}
|
{/* Standalone autoSignAll for "Type" tab if conditions met */}
|
||||||
{setIsAutoSign && uniqueId && (
|
{setIsAutoSign && uniqueId && (
|
||||||
<div className="flex justify-start my-1">
|
<div className="flex justify-start my-1">
|
||||||
{autoSignAll()}
|
{autoSignAll}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{accesstoken && (
|
||||||
|
<div className="flex justify-start my-1">
|
||||||
|
{saveSignCheckbox?.isVisible && savesigncheckbox}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{accesstoken &&
|
|
||||||
saveSignCheckbox?.isVisible &&
|
|
||||||
savesigncheckbox}
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
@@ -1287,55 +1379,56 @@ function WidgetsValueModal(props) {
|
|||||||
<div className="flex flex-row justify-between mt-[10px]">
|
<div className="flex flex-row justify-between mt-[10px]">
|
||||||
<PenColorComponent />
|
<PenColorComponent />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col mt-2">
|
<div className="flex flex-row ml-1 mt-1 gap-x-3 text-base-content">
|
||||||
{/* Standalone autoSignAll for "Draw" tab if conditions met */}
|
{/* Standalone autoSignAll for "Draw" tab if conditions met */}
|
||||||
{setIsAutoSign && uniqueId && (
|
{setIsAutoSign && uniqueId && (
|
||||||
<div className="flex justify-start my-1">
|
<div className="flex justify-start my-1">
|
||||||
{autoSignAll()}
|
{autoSignAll}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{accesstoken && (
|
||||||
|
<div className="flex justify-start my-1">
|
||||||
|
{saveSignCheckbox?.isVisible && savesigncheckbox}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{accesstoken &&
|
|
||||||
saveSignCheckbox?.isVisible &&
|
|
||||||
savesigncheckbox}
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<div>
|
<div className="mx-3 mb-6 mt-3">
|
||||||
<div className="relative flex flex-row items-center justify-between">
|
<p>{t("at-least-one-signature-type")}</p>
|
||||||
<div className="text-base-content font-bold text-lg">
|
|
||||||
{t("signature")}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="text-[1.5rem] cursor-pointer"
|
|
||||||
onClick={handleCancelBtn}
|
|
||||||
>
|
|
||||||
×
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="mx-3 mb-6 mt-3">
|
|
||||||
<p>{t("at-least-one-signature-type")}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
case "checkbox":
|
case "checkbox":
|
||||||
|
const checkBoxLayout =
|
||||||
|
currWidgetsDetails?.options?.layout || "vertical";
|
||||||
|
const isMultipleCheckbox =
|
||||||
|
currWidgetsDetails?.options?.values?.length > 0 ? true : false;
|
||||||
|
const checkBoxWrapperClass = `flex items-start ${
|
||||||
|
checkBoxLayout === "horizontal"
|
||||||
|
? `flex-row flex-wrap ${isMultipleCheckbox ? "gap-x-2" : ""}`
|
||||||
|
: `flex-col ${isMultipleCheckbox ? "gap-y-[5px]" : ""}`
|
||||||
|
}`; // Using gap-y-1 for consistency, adjust if needed
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="border-[1px] border-gray-300 rounded-[2px] p-1 px-3">
|
<div
|
||||||
{currWidgetsDetails?.options?.values?.map((data, ind) => {
|
className={`border-[1px] border-gray-300 rounded-[2px] pt-1 px-2.5 ${checkBoxWrapperClass}`}
|
||||||
return (
|
>
|
||||||
<div
|
{currWidgetsDetails?.options?.values?.map((data, ind) => (
|
||||||
key={ind}
|
<div key={ind} className="text-base-content select-none-cls">
|
||||||
className=" select-none-cls flex items-center text-center gap-0.5"
|
<label
|
||||||
|
htmlFor={`checkbox-${currWidgetsDetails?.key + ind}`}
|
||||||
|
className="text-xs flex items-center gap-1"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
id={`checkbox-${currWidgetsDetails?.key + ind}`}
|
id={`checkbox-${currWidgetsDetails?.key + ind}`}
|
||||||
className={`${
|
className={`${
|
||||||
ind === 0 ? "mt-0" : "mt-[5px]"
|
ind === 0 ? "mt-0" : "mt-[5px]"
|
||||||
} op-checkbox op-checkbox-sm rounded-[1px] `}
|
} op-checkbox op-checkbox-xs rounded-[1px] mt-1`}
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={!!selectCheckbox(ind, selectedCheckbox)}
|
checked={!!selectCheckbox(ind, selectedCheckbox)}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
@@ -1360,17 +1453,10 @@ function WidgetsValueModal(props) {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{!currWidgetsDetails?.options?.isHideLabel && (
|
{data}
|
||||||
<label
|
</label>
|
||||||
htmlFor={`checkbox-${currWidgetsDetails?.key + ind}`}
|
</div>
|
||||||
className="text-xs mb-0 text-center"
|
))}
|
||||||
>
|
|
||||||
{data}
|
|
||||||
</label>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
case textInputWidget:
|
case textInputWidget:
|
||||||
@@ -1383,6 +1469,23 @@ function WidgetsValueModal(props) {
|
|||||||
className={textInputcls}
|
className={textInputcls}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
case cellsWidget:
|
||||||
|
return (
|
||||||
|
<CellsWidget
|
||||||
|
isEnabled={true}
|
||||||
|
count={cellsValue.length}
|
||||||
|
height="100%"
|
||||||
|
value={cellsValue.join("")}
|
||||||
|
editable={true}
|
||||||
|
resizable={allowCellResize}
|
||||||
|
onChange={handleCellsInput}
|
||||||
|
onKeyDown={handleCellsKeyDown}
|
||||||
|
onBlur={handleCellsBlur}
|
||||||
|
onCellCountChange={allowCellResize ? handleCellResize : undefined}
|
||||||
|
inputRefs={cellRefs}
|
||||||
|
hint={hint}
|
||||||
|
/>
|
||||||
|
);
|
||||||
case "dropdown":
|
case "dropdown":
|
||||||
return (
|
return (
|
||||||
<select
|
<select
|
||||||
@@ -1400,18 +1503,11 @@ function WidgetsValueModal(props) {
|
|||||||
>
|
>
|
||||||
{currWidgetsDetails?.options?.name}
|
{currWidgetsDetails?.options?.name}
|
||||||
</option>
|
</option>
|
||||||
|
{currWidgetsDetails?.options?.values?.map((data, ind) => (
|
||||||
{currWidgetsDetails?.options?.values?.map((data, ind) => {
|
<option key={ind} value={data}>
|
||||||
return (
|
{data}
|
||||||
<option
|
</option>
|
||||||
// style={{ fontSize: fontSize, color: fontColor }}
|
))}
|
||||||
key={ind}
|
|
||||||
value={data}
|
|
||||||
>
|
|
||||||
{data}
|
|
||||||
</option>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</select>
|
</select>
|
||||||
);
|
);
|
||||||
case "name":
|
case "name":
|
||||||
@@ -1450,7 +1546,7 @@ function WidgetsValueModal(props) {
|
|||||||
case "date":
|
case "date":
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`border-[1px] border-gray-300 rounded-[2px] p-1 px-3`}
|
className={`border-[1px] text-base-content data-[theme=opensigndark]:border-base-content data-[theme=opensigncss]:border-gray-300 rounded-[2px] p-1 px-3`}
|
||||||
>
|
>
|
||||||
<DatePicker
|
<DatePicker
|
||||||
renderCustomHeader={({ date, changeYear, changeMonth }) => (
|
renderCustomHeader={({ date, changeYear, changeMonth }) => (
|
||||||
@@ -1511,20 +1607,27 @@ function WidgetsValueModal(props) {
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
case radioButtonWidget:
|
case radioButtonWidget:
|
||||||
|
const radioLayout = currWidgetsDetails.options?.layout || "vertical";
|
||||||
|
const isOnlyOneBtn =
|
||||||
|
currWidgetsDetails.options?.values?.length > 0 ? true : false;
|
||||||
|
const radioWrapperClass = `flex items-start ${
|
||||||
|
radioLayout === "horizontal"
|
||||||
|
? `flex-row flex-wrap ${isOnlyOneBtn ? "gap-x-2" : ""}`
|
||||||
|
: `flex-col ${isOnlyOneBtn ? "gap-y-[5px]" : ""}`
|
||||||
|
}`; // Using gap-y-1 for consistency, adjust if needed
|
||||||
return (
|
return (
|
||||||
<div className="border-[1px] border-gray-300 rounded-[2px] p-1 px-3">
|
<div
|
||||||
{currWidgetsDetails?.options?.values.map((data, ind) => {
|
className={`border-[1px] border-gray-300 rounded-[2px] pt-1 px-2.5 ${radioWrapperClass}`}
|
||||||
return (
|
>
|
||||||
<div
|
{currWidgetsDetails?.options?.values.map((data, ind) => (
|
||||||
key={ind}
|
<div key={ind} className="text-base-content select-none-cls">
|
||||||
className="select-none-cls flex items-center text-center gap-0.5"
|
<label
|
||||||
|
htmlFor={`radio-${currWidgetsDetails?.key + ind}`}
|
||||||
|
className="cursor-pointer flex items-center text-sm gap-1"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
id={`radio-${currWidgetsDetails?.key + ind}`}
|
id={`radio-${currWidgetsDetails?.key + ind}`}
|
||||||
style={{
|
className={`op-radio op-radio-xs mt-1`}
|
||||||
marginTop: ind > 0 ? "10px" : "0px"
|
|
||||||
}}
|
|
||||||
className={`flex justify-center op-radio`}
|
|
||||||
type="radio"
|
type="radio"
|
||||||
value={data}
|
value={data}
|
||||||
checked={handleRadioCheck(data)}
|
checked={handleRadioCheck(data)}
|
||||||
@@ -1532,18 +1635,10 @@ function WidgetsValueModal(props) {
|
|||||||
handleCheckRadio(e.target.value);
|
handleCheckRadio(e.target.value);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{!currWidgetsDetails?.options?.isHideLabel && (
|
<span>{data}</span>
|
||||||
<label
|
</label>
|
||||||
htmlFor={`radio-${currWidgetsDetails?.key + ind}`}
|
</div>
|
||||||
// style={{ fontSize: fontSize, color: fontColor }}
|
))}
|
||||||
className="text-xs mb-0"
|
|
||||||
>
|
|
||||||
{data}
|
|
||||||
</label>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
case textWidget:
|
case textWidget:
|
||||||
@@ -1581,15 +1676,9 @@ function WidgetsValueModal(props) {
|
|||||||
const minCount =
|
const minCount =
|
||||||
currWidgetsDetails?.options?.validation?.minRequiredCount;
|
currWidgetsDetails?.options?.validation?.minRequiredCount;
|
||||||
const parseMin = minCount && parseInt(minCount);
|
const parseMin = minCount && parseInt(minCount);
|
||||||
//get maximum required count if exist
|
if (parseMin > 0) {
|
||||||
const maxCount =
|
|
||||||
currWidgetsDetails?.options?.validation?.maxRequiredCount;
|
|
||||||
const parseMax = maxCount && parseInt(maxCount);
|
|
||||||
if (parseMin > 0 && parseMax > 0) {
|
|
||||||
isRequired = true;
|
isRequired = true;
|
||||||
}
|
}
|
||||||
} else if (isRadio) {
|
|
||||||
isRequired = true;
|
|
||||||
} else {
|
} else {
|
||||||
isRequired = currWidgetsDetails.options?.status === "required";
|
isRequired = currWidgetsDetails.options?.status === "required";
|
||||||
}
|
}
|
||||||
@@ -1652,7 +1741,7 @@ function WidgetsValueModal(props) {
|
|||||||
const validateExpression = (regexValidation) => {
|
const validateExpression = (regexValidation) => {
|
||||||
if (widgetValue && regexValidation) {
|
if (widgetValue && regexValidation) {
|
||||||
let regexObject = regexValidation;
|
let regexObject = regexValidation;
|
||||||
if (props.pos?.options?.validation?.type === "regex") {
|
if (currWidgetsDetails?.options?.validation?.type === "regex") {
|
||||||
regexObject = RegexParser(regexValidation);
|
regexObject = RegexParser(regexValidation);
|
||||||
}
|
}
|
||||||
let isValidate = regexObject.test(widgetValue);
|
let isValidate = regexObject.test(widgetValue);
|
||||||
@@ -1661,7 +1750,7 @@ function WidgetsValueModal(props) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
//funtion is used when user enter value any textbox then check validation
|
//function is used when user enter value in any textbox then check validation
|
||||||
const handleInputBlur = () => {
|
const handleInputBlur = () => {
|
||||||
const validateType = currWidgetsDetails?.options?.validation?.type;
|
const validateType = currWidgetsDetails?.options?.validation?.type;
|
||||||
let regexValidation;
|
let regexValidation;
|
||||||
@@ -1674,13 +1763,22 @@ function WidgetsValueModal(props) {
|
|||||||
regexValidation = /^[0-9\s]*$/;
|
regexValidation = /^[0-9\s]*$/;
|
||||||
validateExpression(regexValidation);
|
validateExpression(regexValidation);
|
||||||
break;
|
break;
|
||||||
|
case "ssn":
|
||||||
|
regexValidation = /^(?!000|666|9\d{2})\d{3}-(?!00)\d{2}-(?!0000)\d{4}$/;
|
||||||
|
validateExpression(regexValidation);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
regexValidation = props.pos?.options?.validation?.pattern || "";
|
// Grab the current pattern (if it exists)
|
||||||
|
const pattern = currWidgetsDetails?.options?.validation?.pattern;
|
||||||
|
// Removed `backwordSupportPattern` (/^[a-zA-Z0-9s]+$/) — it blocked spaces and special characters.
|
||||||
|
const backwordSupportPattern =
|
||||||
|
pattern && pattern === "/^[a-zA-Z0-9s]+$/" ? "" : pattern; // If it matches exactly '/^[a-zA-Z0-9s]+$/', clear it
|
||||||
|
regexValidation = backwordSupportPattern || "";
|
||||||
validateExpression(regexValidation);
|
validateExpression(regexValidation);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
//funtion too uuse on click on next/finish button then update modal ui according to cuurent widgets
|
//function too use on click on next/finish button then update modal UI according to current widgets
|
||||||
const handleClickOnNext = (isFinishDoc) => {
|
const handleClickOnNext = (isFinishDoc) => {
|
||||||
if (
|
if (
|
||||||
["signature", "stamp", "image", "initials"].includes(
|
["signature", "stamp", "image", "initials"].includes(
|
||||||
@@ -1779,27 +1877,81 @@ function WidgetsValueModal(props) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
const handleclose = () => {
|
const handleclose = () => {
|
||||||
|
// If validation is shown, clear the response and close the modal
|
||||||
|
if (isShowValidation) {
|
||||||
|
handleClear();
|
||||||
|
setIsShowValidation(false);
|
||||||
|
}
|
||||||
dispatch(setIsShowModal({}));
|
dispatch(setIsShowModal({}));
|
||||||
dispatch(setLastIndex(""));
|
dispatch(setLastIndex(""));
|
||||||
if (currWidgetsDetails?.type === textWidget && uniqueId) {
|
if (currWidgetsDetails?.type === textWidget && uniqueId) {
|
||||||
setUniqueId(tempSignerId);
|
setUniqueId(tempSignerId);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
//function is used to execute the finish button functionality
|
||||||
const handleFinish = () => {
|
const handleFinish = () => {
|
||||||
props?.finishDocument();
|
props?.finishDocument();
|
||||||
dispatch(setIsShowModal({}));
|
dispatch(setIsShowModal({}));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isSave) {
|
||||||
|
handleFinishButton();
|
||||||
|
}
|
||||||
|
}, [isSave]);
|
||||||
|
//'handleFinishButton' function is used to show finish button click on any widget if all required widgets have response
|
||||||
|
const handleFinishButton = () => {
|
||||||
|
const widgetsPosition = xyPosition?.find((data) => data.Id === uniqueId);
|
||||||
|
//using 'flatMap' create all nested array in one level
|
||||||
|
const editableWidgets = widgetsPosition?.placeHolder?.flatMap((page) =>
|
||||||
|
page.pos
|
||||||
|
.filter((widget) => !widget.options?.isReadOnly)
|
||||||
|
.map((widget) => widget)
|
||||||
|
);
|
||||||
|
const getcurrentwidget = editableWidgets?.find(
|
||||||
|
(data) => data?.key === currWidgetsDetails?.key
|
||||||
|
);
|
||||||
|
if (getcurrentwidget?.options?.response) {
|
||||||
|
props?.setCurrWidgetsDetails(getcurrentwidget);
|
||||||
|
}
|
||||||
|
let isResponse = true;
|
||||||
|
//condition to check all required widgets have response or not then show finish buutton
|
||||||
|
for (const data of editableWidgets) {
|
||||||
|
if (data?.type === "checkbox") {
|
||||||
|
const minCount = data.options?.validation?.minRequiredCount;
|
||||||
|
const parseMin = minCount && parseInt(minCount);
|
||||||
|
const hasNoResponse =
|
||||||
|
(!Array.isArray(data?.options?.response) ||
|
||||||
|
data.options.response.length === 0) &&
|
||||||
|
(!Array.isArray(data?.options?.defaultValue) ||
|
||||||
|
data.options.defaultValue.length === 0);
|
||||||
|
if (parseMin > 0 && hasNoResponse) {
|
||||||
|
isResponse = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
!data.options.response &&
|
||||||
|
!data?.options?.defaultValue &&
|
||||||
|
data.options?.status === "required"
|
||||||
|
) {
|
||||||
|
isResponse = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isResponse) {
|
||||||
|
setIsLastWidget(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ModalUi
|
<ModalUi
|
||||||
isOpen={true}
|
isOpen={true}
|
||||||
handleClose={() => !isShowValidation && handleclose()}
|
handleClose={() => handleclose()}
|
||||||
|
position="bottom"
|
||||||
>
|
>
|
||||||
<div className="h-[100%] p-[20px]">
|
<div className="h-[100%] p-[18px]">
|
||||||
{isFinish ? (
|
{isFinish ? (
|
||||||
<>
|
<>
|
||||||
{" "}
|
|
||||||
<div className="p-1 mt-3">
|
<div className="p-1 mt-3">
|
||||||
<span className="text-base">{t("finish-mssg")}</span>
|
<span className="text-base">{t("finish-mssg")}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -1824,9 +1976,9 @@ function WidgetsValueModal(props) {
|
|||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<div className="p-1 m-1">
|
<div>
|
||||||
<div className="relative inline-block">
|
<div className="relative inline-block">
|
||||||
<span className="text-base">
|
<span className="text-base text-base-content">
|
||||||
{currWidgetsDetails?.options?.name || widgetTypeTranslation}
|
{currWidgetsDetails?.options?.name || widgetTypeTranslation}
|
||||||
</span>
|
</span>
|
||||||
{!isOptional && (
|
{!isOptional && (
|
||||||
@@ -1835,7 +1987,7 @@ function WidgetsValueModal(props) {
|
|||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col justify-center m-2 mt-4">
|
<div className="flex flex-col justify-center m-2 mt-3">
|
||||||
<div className="flex justify-center">
|
<div className="flex justify-center">
|
||||||
{getWidgetType(currWidgetsDetails?.type)}
|
{getWidgetType(currWidgetsDetails?.type)}
|
||||||
</div>
|
</div>
|
||||||
@@ -1852,15 +2004,18 @@ function WidgetsValueModal(props) {
|
|||||||
) ? (
|
) ? (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="op-btn op-btn-ghost mr-1 mt-[2px]"
|
className="op-btn op-btn-ghost op-btn-sm text-base-content mr-1"
|
||||||
onClick={() => handleClear()}
|
onClick={() => handleClear()}
|
||||||
>
|
>
|
||||||
{t("clear")}
|
{t("clear")}
|
||||||
</button>
|
</button>
|
||||||
) : (
|
) : (
|
||||||
<div className="w-[80px]"></div>
|
<button
|
||||||
|
type="button"
|
||||||
|
className="op-btn op-btn-ghost op-btn-sm mr-1 cursor-default"
|
||||||
|
></button>
|
||||||
)}
|
)}
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{!isSave && <HandleRequiredField />}
|
{!isSave && <HandleRequiredField />}
|
||||||
{isSave ? (
|
{isSave ? (
|
||||||
<button
|
<button
|
||||||
@@ -1888,7 +2043,7 @@ function WidgetsValueModal(props) {
|
|||||||
) : (
|
) : (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="op-btn op-btn-primary op-btn-sm"
|
className="op-btn op-btn-primary op-btn-sm text-xs md:text-sm"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleClickOnNext();
|
handleClickOnNext();
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -195,7 +195,7 @@ const SelectFolder = ({ required, onSuccess, folderCls, isReset }) => {
|
|||||||
? selectFolder.Name
|
? selectFolder.Name
|
||||||
: t("OpenSign-drive", { appName: drivename })}
|
: t("OpenSign-drive", { appName: drivename })}
|
||||||
</p>
|
</p>
|
||||||
<div className="text-black text-sm">
|
<div className="text-sm">
|
||||||
<i
|
<i
|
||||||
className="fa-light fa-pencil cursor-pointer"
|
className="fa-light fa-pencil cursor-pointer"
|
||||||
title={t("select-folder")}
|
title={t("select-folder")}
|
||||||
|
|||||||
@@ -2,9 +2,19 @@ import React, { useEffect, useState } from "react";
|
|||||||
import AsyncSelect from "react-select/async";
|
import AsyncSelect from "react-select/async";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
import { handleUnlinkSigner } from "../../../constant/Utils";
|
||||||
|
|
||||||
const SelectSigners = (props) => {
|
const SelectSigners = (props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const {
|
||||||
|
signerPos,
|
||||||
|
setSignerPos,
|
||||||
|
signersData,
|
||||||
|
setSignersData,
|
||||||
|
uniqueId,
|
||||||
|
isRemove,
|
||||||
|
handleAddUser
|
||||||
|
} = props;
|
||||||
const [userList, setUserList] = useState([]);
|
const [userList, setUserList] = useState([]);
|
||||||
const [selected, setSelected] = useState();
|
const [selected, setSelected] = useState();
|
||||||
const [userData, setUserData] = useState({});
|
const [userData, setUserData] = useState({});
|
||||||
@@ -30,7 +40,7 @@ const SelectSigners = (props) => {
|
|||||||
//checking if user select no signer option from dropdown
|
//checking if user select no signer option from dropdown
|
||||||
if (item) {
|
if (item) {
|
||||||
//checking selected signer is already assign to the document or not
|
//checking selected signer is already assign to the document or not
|
||||||
const alreadyAssign = props.signersData.some(
|
const alreadyAssign = signersData.some(
|
||||||
(item2) => item2.objectId === item.value
|
(item2) => item2.objectId === item.value
|
||||||
);
|
);
|
||||||
if (alreadyAssign) {
|
if (alreadyAssign) {
|
||||||
@@ -49,7 +59,7 @@ const SelectSigners = (props) => {
|
|||||||
};
|
};
|
||||||
const handleAdd = () => {
|
const handleAdd = () => {
|
||||||
if (userData && userData.objectId) {
|
if (userData && userData.objectId) {
|
||||||
props.details(userData);
|
handleAddUser(userData);
|
||||||
if (props.closePopup) {
|
if (props.closePopup) {
|
||||||
props.closePopup();
|
props.closePopup();
|
||||||
}
|
}
|
||||||
@@ -60,7 +70,13 @@ const SelectSigners = (props) => {
|
|||||||
};
|
};
|
||||||
//function to use remove signer from assigned widgets in create template flow
|
//function to use remove signer from assigned widgets in create template flow
|
||||||
const handleRemove = () => {
|
const handleRemove = () => {
|
||||||
props.handleUnlinkSigner();
|
handleUnlinkSigner(
|
||||||
|
signerPos,
|
||||||
|
setSignerPos,
|
||||||
|
signersData,
|
||||||
|
setSignersData,
|
||||||
|
uniqueId
|
||||||
|
);
|
||||||
if (props.closePopup) {
|
if (props.closePopup) {
|
||||||
props.closePopup();
|
props.closePopup();
|
||||||
}
|
}
|
||||||
@@ -81,7 +97,7 @@ const SelectSigners = (props) => {
|
|||||||
const contactRes = axiosRes?.data?.result || [];
|
const contactRes = axiosRes?.data?.result || [];
|
||||||
if (contactRes) {
|
if (contactRes) {
|
||||||
const res = JSON.parse(JSON.stringify(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
|
//and filter signers from total signer's list which already present in document's signers list
|
||||||
// const compareArrays = (res, signerObj) => {
|
// const compareArrays = (res, signerObj) => {
|
||||||
// return res.filter(
|
// return res.filter(
|
||||||
@@ -161,7 +177,7 @@ const SelectSigners = (props) => {
|
|||||||
<button className="op-btn op-btn-primary" onClick={() => handleAdd()}>
|
<button className="op-btn op-btn-primary" onClick={() => handleAdd()}>
|
||||||
{t("submit")}
|
{t("submit")}
|
||||||
</button>
|
</button>
|
||||||
{props.isExistSigner && props.handleUnlinkSigner && (
|
{props.isExistSigner && isRemove && (
|
||||||
<button
|
<button
|
||||||
className="op-btn op-btn-accent op-btn-outline"
|
className="op-btn op-btn-accent op-btn-outline"
|
||||||
onClick={() => handleRemove()}
|
onClick={() => handleRemove()}
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ const SignersInput = (props) => {
|
|||||||
);
|
);
|
||||||
if (contactRes) {
|
if (contactRes) {
|
||||||
const res = JSON.parse(JSON.stringify(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
|
//and filter signers from total signer's list which already present in document's signers list
|
||||||
const compareArrays = (res, signerObj) => {
|
const compareArrays = (res, signerObj) => {
|
||||||
return res.filter(
|
return res.filter(
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import React from "react";
|
|
||||||
import { PDFDocument, rgb, degrees } from "pdf-lib";
|
import { PDFDocument, rgb, degrees } from "pdf-lib";
|
||||||
import Parse from "parse";
|
import Parse from "parse";
|
||||||
import { appInfo } from "./appinfo";
|
import { appInfo } from "./appinfo";
|
||||||
@@ -21,7 +20,10 @@ export const isTabAndMobile = window.innerWidth < 1023;
|
|||||||
export const textInputWidget = "text input";
|
export const textInputWidget = "text input";
|
||||||
export const textWidget = "text";
|
export const textWidget = "text";
|
||||||
export const radioButtonWidget = "radio button";
|
export const radioButtonWidget = "radio button";
|
||||||
|
export const cellsWidget = "cells";
|
||||||
|
export function getEnv() {
|
||||||
|
return window?.RUNTIME_ENV || {};
|
||||||
|
}
|
||||||
|
|
||||||
//function for create list of year for date widget
|
//function for create list of year for date widget
|
||||||
export const range = (start, end, step) => {
|
export const range = (start, end, step) => {
|
||||||
@@ -65,6 +67,17 @@ export const openInNewTab = (url, target) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getUserCountry = async () => {
|
||||||
|
try {
|
||||||
|
const res = await fetch("https://ipapi.co/json/");
|
||||||
|
const data = await res.json();
|
||||||
|
return data?.country_code;
|
||||||
|
} catch (err) {
|
||||||
|
console.log("Error fetching country", err);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// `getSecureUrl` is used to return local secure url if local files
|
// `getSecureUrl` is used to return local secure url if local files
|
||||||
export const getSecureUrl = async (url) => {
|
export const getSecureUrl = async (url) => {
|
||||||
const fileUrl = new URL(url)?.pathname?.includes("files");
|
const fileUrl = new URL(url)?.pathname?.includes("files");
|
||||||
@@ -85,6 +98,27 @@ export const getSecureUrl = async (url) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a trailing path segment from a URL string, if present.
|
||||||
|
*
|
||||||
|
* @param {string} url - The original URL.
|
||||||
|
* @param {string} segment - The segment to strip off (default: "app").
|
||||||
|
* @returns {string} - The URL with the trailing segment removed, or unmodified if it didn’t match.
|
||||||
|
*/
|
||||||
|
export function removeTrailingSegment(url, segment = "app") {
|
||||||
|
// Normalize a trailing slash (e.g. “/app/” → “/app”)
|
||||||
|
const normalized = url.endsWith("/") ? url.slice(0, -1) : url;
|
||||||
|
|
||||||
|
const lastSlash = normalized.lastIndexOf("/");
|
||||||
|
const lastPart = normalized.slice(lastSlash + 1);
|
||||||
|
|
||||||
|
if (lastPart === segment) {
|
||||||
|
return normalized.slice(0, lastSlash);
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
export const color = [
|
export const color = [
|
||||||
"#93a3db",
|
"#93a3db",
|
||||||
"#e6c3db",
|
"#e6c3db",
|
||||||
@@ -256,6 +290,7 @@ export const widgets = [
|
|||||||
{ type: "date", icon: "fa-light fa-calendar-days", iconSize: "20px" },
|
{ type: "date", icon: "fa-light fa-calendar-days", iconSize: "20px" },
|
||||||
{ type: textWidget, icon: "fa-light fa-text-width", iconSize: "20px" },
|
{ type: textWidget, icon: "fa-light fa-text-width", iconSize: "20px" },
|
||||||
{ type: textInputWidget, icon: "fa-light fa-font", iconSize: "21px" },
|
{ type: textInputWidget, icon: "fa-light fa-font", iconSize: "21px" },
|
||||||
|
{ type: cellsWidget, icon: "fa-light fa-table-cells", iconSize: "20px" },
|
||||||
{ type: "checkbox", icon: "fa-light fa-square-check", iconSize: "22px" },
|
{ type: "checkbox", icon: "fa-light fa-square-check", iconSize: "22px" },
|
||||||
{
|
{
|
||||||
type: "dropdown",
|
type: "dropdown",
|
||||||
@@ -348,6 +383,15 @@ export const addWidgetOptions = (type, signer, widgetValue) => {
|
|||||||
};
|
};
|
||||||
case textInputWidget:
|
case textInputWidget:
|
||||||
return { ...status, name: "Text", isReadOnly: false };
|
return { ...status, name: "Text", isReadOnly: false };
|
||||||
|
case cellsWidget:
|
||||||
|
return {
|
||||||
|
...status,
|
||||||
|
name: "Cells",
|
||||||
|
cellCount: 5,
|
||||||
|
defaultValue: "",
|
||||||
|
validation: { type: "", pattern: "" },
|
||||||
|
isReadOnly: false
|
||||||
|
};
|
||||||
case "initials":
|
case "initials":
|
||||||
return { ...status, name: "Initials" };
|
return { ...status, name: "Initials" };
|
||||||
case "name":
|
case "name":
|
||||||
@@ -389,7 +433,7 @@ export const addWidgetOptions = (type, signer, widgetValue) => {
|
|||||||
defaultValue: widgetValue ? widgetValue : ""
|
defaultValue: widgetValue ? widgetValue : ""
|
||||||
};
|
};
|
||||||
case "dropdown":
|
case "dropdown":
|
||||||
return { ...status, name: "Dropdown" };
|
return { ...status, name: "Choose one" };
|
||||||
case radioButtonWidget:
|
case radioButtonWidget:
|
||||||
return {
|
return {
|
||||||
...status,
|
...status,
|
||||||
@@ -415,6 +459,14 @@ export const addWidgetSelfsignOptions = (type, getWidgetValue, owner) => {
|
|||||||
return { name: "Checkbox" };
|
return { name: "Checkbox" };
|
||||||
case textWidget:
|
case textWidget:
|
||||||
return { name: "Text" };
|
return { name: "Text" };
|
||||||
|
case cellsWidget:
|
||||||
|
return {
|
||||||
|
name: "Cells",
|
||||||
|
cellCount: 5,
|
||||||
|
defaultValue: "",
|
||||||
|
validation: { type: "", pattern: "" },
|
||||||
|
isReadOnly: false
|
||||||
|
};
|
||||||
case "initials":
|
case "initials":
|
||||||
return { name: "Initials" };
|
return { name: "Initials" };
|
||||||
case "name":
|
case "name":
|
||||||
@@ -485,6 +537,8 @@ export const defaultWidthHeight = (type) => {
|
|||||||
return { width: 15, height: 19 };
|
return { width: 15, height: 19 };
|
||||||
case textInputWidget:
|
case textInputWidget:
|
||||||
return { width: 150, height: 19 };
|
return { width: 150, height: 19 };
|
||||||
|
case cellsWidget:
|
||||||
|
return { width: 112, height: 22 };
|
||||||
case "dropdown":
|
case "dropdown":
|
||||||
return { width: 120, height: 22 };
|
return { width: 120, height: 22 };
|
||||||
case "initials":
|
case "initials":
|
||||||
@@ -510,10 +564,6 @@ export const defaultWidthHeight = (type) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const resizeBorderExtraWidth = () => {
|
|
||||||
return 20;
|
|
||||||
};
|
|
||||||
|
|
||||||
export async function getBase64FromUrl(url, autosign) {
|
export async function getBase64FromUrl(url, autosign) {
|
||||||
const data = await fetch(url);
|
const data = await fetch(url);
|
||||||
const blob = await data.blob();
|
const blob = await data.blob();
|
||||||
@@ -686,10 +736,14 @@ export const signPdfFun = async (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const randomId = () => {
|
export const randomId = () => {
|
||||||
const randomBytes = crypto.getRandomValues(new Uint16Array(1));
|
// 1. Grab a cryptographically-secure 32-bit random value
|
||||||
const randomValue = randomBytes[0];
|
const randomBytes = crypto.getRandomValues(new Uint32Array(1));
|
||||||
const randomDigit = 1000 + (randomValue % 9000);
|
const raw = randomBytes[0]; // 0 … 4 294 967 295
|
||||||
return randomDigit;
|
|
||||||
|
// 2. Collapse into a 90 000 000-wide band (0…89 999 999), then shift to 10 000 000…99 999 999
|
||||||
|
const eightDigit = 10_000_000 + (raw % 90_000_000);
|
||||||
|
|
||||||
|
return eightDigit;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createDocument = async (
|
export const createDocument = async (
|
||||||
@@ -862,7 +916,8 @@ export const onChangeInput = (
|
|||||||
initial,
|
initial,
|
||||||
dateFormat,
|
dateFormat,
|
||||||
fontSize,
|
fontSize,
|
||||||
fontColor
|
fontColor,
|
||||||
|
isDateReadOnly
|
||||||
) => {
|
) => {
|
||||||
const isSigners = xyPosition.some((data) => data.signerPtr);
|
const isSigners = xyPosition.some((data) => data.signerPtr);
|
||||||
let filterSignerPos;
|
let filterSignerPos;
|
||||||
@@ -891,6 +946,7 @@ export const onChangeInput = (
|
|||||||
response: value,
|
response: value,
|
||||||
fontSize: fontSize,
|
fontSize: fontSize,
|
||||||
fontColor: fontColor,
|
fontColor: fontColor,
|
||||||
|
isReadOnly: isDateReadOnly || false,
|
||||||
validation: {
|
validation: {
|
||||||
type: "date-format",
|
type: "date-format",
|
||||||
format: dateFormat // This indicates the required date format explicitly.
|
format: dateFormat // This indicates the required date format explicitly.
|
||||||
@@ -968,7 +1024,6 @@ export const onChangeInput = (
|
|||||||
setXyPosition(updatePlaceholder);
|
setXyPosition(updatePlaceholder);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
//function to increase height of text area on press enter
|
//function to increase height of text area on press enter
|
||||||
export const onChangeHeightOfTextArea = (
|
export const onChangeHeightOfTextArea = (
|
||||||
height,
|
height,
|
||||||
@@ -1114,7 +1169,7 @@ export const addInitialData = (signerPos, setXyPosition, value, userId) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
//function for embed document id
|
//function for embed document id
|
||||||
export const embedDocId = async (pdfDoc, documentId, allPages) => {
|
export const embedDocId = async (pdfOriginalWH, pdfDoc, documentId) => {
|
||||||
const appName =
|
const appName =
|
||||||
"OpenSign™";
|
"OpenSign™";
|
||||||
// `fontBytes` is used to embed custom font in pdf
|
// `fontBytes` is used to embed custom font in pdf
|
||||||
@@ -1123,19 +1178,21 @@ export const embedDocId = async (pdfDoc, documentId, allPages) => {
|
|||||||
);
|
);
|
||||||
pdfDoc.registerFontkit(fontkit);
|
pdfDoc.registerFontkit(fontkit);
|
||||||
const font = await pdfDoc.embedFont(fontBytes, { subset: true });
|
const font = await pdfDoc.embedFont(fontBytes, { subset: true });
|
||||||
for (let i = 0; i < allPages; i++) {
|
//pdfOriginalWH contained all pdf's pages width and height
|
||||||
|
for (let i = 0; i < pdfOriginalWH?.length; i++) {
|
||||||
const fontSize = 10;
|
const fontSize = 10;
|
||||||
const textContent =
|
const textContent =
|
||||||
documentId && `${appName} DocumentId: ${documentId} `;
|
documentId && `${appName} DocumentId: ${documentId} `;
|
||||||
const pages = pdfDoc.getPages();
|
const pages = pdfDoc.getPages();
|
||||||
const page = pages[i];
|
const page = pages[i];
|
||||||
|
const getSize = pdfOriginalWH[i];
|
||||||
try {
|
try {
|
||||||
const getObj = compensateRotation(
|
const getObj = compensateRotation(
|
||||||
page.getRotation().angle,
|
page.getRotation().angle,
|
||||||
10,
|
10,
|
||||||
5,
|
5,
|
||||||
1,
|
1,
|
||||||
page.getSize(),
|
getSize,
|
||||||
fontSize,
|
fontSize,
|
||||||
rgb(0.5, 0.5, 0.5),
|
rgb(0.5, 0.5, 0.5),
|
||||||
font,
|
font,
|
||||||
@@ -1210,8 +1267,8 @@ export function onSaveSign(
|
|||||||
}
|
}
|
||||||
return obj;
|
return obj;
|
||||||
});
|
});
|
||||||
//condition when user click on apply(signature,image,typed signature or defaullt signature) all widgets on signature pad for same widgets
|
//condition when draw/upload signature/initials then apply it all related to widgets (signature,image,typed signature or default signature)
|
||||||
if (isAutoSign) {
|
if (isApplyAll || isAutoSign) {
|
||||||
const updatedArray = updateXYposition.map((page) => ({
|
const updatedArray = updateXYposition.map((page) => ({
|
||||||
...page,
|
...page,
|
||||||
pos: page.pos.map(
|
pos: page.pos.map(
|
||||||
@@ -1230,26 +1287,6 @@ export function onSaveSign(
|
|||||||
)
|
)
|
||||||
}));
|
}));
|
||||||
return updatedArray;
|
return updatedArray;
|
||||||
} //condition when user edit signature/initial then updated signature apply all existing drawn signatures
|
|
||||||
else if (isApplyAll) {
|
|
||||||
const updatedArray = updateXYposition.map((page) => ({
|
|
||||||
...page,
|
|
||||||
pos: page.pos.map(
|
|
||||||
(item) =>
|
|
||||||
item.SignUrl && item.type === widgetsType
|
|
||||||
? {
|
|
||||||
...item,
|
|
||||||
Width: posWidth,
|
|
||||||
Height: posHeight,
|
|
||||||
SignUrl: signatureImg,
|
|
||||||
...(type && { signatureType: type }),
|
|
||||||
options: { ...item.options, response: signatureImg },
|
|
||||||
...(typedSignature && { typeSignature: typedSignature })
|
|
||||||
}
|
|
||||||
: item // Otherwise, keep it unchanged
|
|
||||||
)
|
|
||||||
}));
|
|
||||||
return updatedArray;
|
|
||||||
} else {
|
} else {
|
||||||
return updateXYposition;
|
return updateXYposition;
|
||||||
}
|
}
|
||||||
@@ -1358,35 +1395,13 @@ export function onSaveImage(
|
|||||||
}
|
}
|
||||||
return obj;
|
return obj;
|
||||||
});
|
});
|
||||||
//condition when user click on apply(stamp) all widgets on signature pad for same widgets
|
//condition when user upload(stamp) then apply it all related to widgets
|
||||||
if (isAutoSign) {
|
if (isApplyAll || isAutoSign) {
|
||||||
const updatedArray = updateXYposition.map((page) => ({
|
const updatedArray = updateXYposition.map((page) => ({
|
||||||
...page,
|
...page,
|
||||||
pos: page.pos.map(
|
pos: page.pos.map(
|
||||||
(item) =>
|
(item) =>
|
||||||
item.type === widgetsType
|
item.type === widgetsType && item.type !== "image"
|
||||||
? {
|
|
||||||
...item,
|
|
||||||
Width: getIMGWH.newWidth,
|
|
||||||
Height: getIMGWH.newHeight,
|
|
||||||
SignUrl: image.src,
|
|
||||||
ImageType: image.imgType,
|
|
||||||
options: {
|
|
||||||
...item.options,
|
|
||||||
response: image.src
|
|
||||||
}
|
|
||||||
}
|
|
||||||
: item // Otherwise, keep it unchanged
|
|
||||||
)
|
|
||||||
}));
|
|
||||||
return updatedArray;
|
|
||||||
} //condition when user edit stamp then updated signature apply all existing drawn signatures
|
|
||||||
else if (isApplyAll) {
|
|
||||||
const updatedArray = updateXYposition.map((page) => ({
|
|
||||||
...page,
|
|
||||||
pos: page.pos.map(
|
|
||||||
(item) =>
|
|
||||||
item.SignUrl && item.type === widgetsType && item.type !== "image"
|
|
||||||
? {
|
? {
|
||||||
...item,
|
...item,
|
||||||
Width: getIMGWH.newWidth,
|
Width: getIMGWH.newWidth,
|
||||||
@@ -1537,7 +1552,13 @@ const getWidgetsFontColor = (type) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
//function for embed multiple signature using pdf-lib
|
//function for embed multiple signature using pdf-lib
|
||||||
export const multiSignEmbed = async (widgets, pdfDoc, signyourself, scale) => {
|
export const multiSignEmbed = async (
|
||||||
|
pdfOriginalWH,
|
||||||
|
widgets,
|
||||||
|
pdfDoc,
|
||||||
|
signyourself,
|
||||||
|
scale
|
||||||
|
) => {
|
||||||
// `fontBytes` is used to embed custom font in pdf
|
// `fontBytes` is used to embed custom font in pdf
|
||||||
const fontBytes = await fileasbytes(
|
const fontBytes = await fileasbytes(
|
||||||
"https://cdn.opensignlabs.com/webfonts/times.ttf"
|
"https://cdn.opensignlabs.com/webfonts/times.ttf"
|
||||||
@@ -1546,6 +1567,11 @@ export const multiSignEmbed = async (widgets, pdfDoc, signyourself, scale) => {
|
|||||||
const font = await pdfDoc.embedFont(fontBytes, { subset: true });
|
const font = await pdfDoc.embedFont(fontBytes, { subset: true });
|
||||||
let hasError = false;
|
let hasError = false;
|
||||||
for (let item of widgets) {
|
for (let item of widgets) {
|
||||||
|
//pdfOriginalWH contained all pdf's pages width and height
|
||||||
|
//'getSize' is used to get particular pdf's page width and height
|
||||||
|
const getSize = pdfOriginalWH.find(
|
||||||
|
(page) => page?.pageNumber === item?.pageNumber
|
||||||
|
);
|
||||||
if (hasError) break; // Stop the outer loop if an error occurred
|
if (hasError) break; // Stop the outer loop if an error occurred
|
||||||
const typeExist = item.pos.some((data) => data?.type);
|
const typeExist = item.pos.some((data) => data?.type);
|
||||||
let updateItem;
|
let updateItem;
|
||||||
@@ -1641,6 +1667,7 @@ export const multiSignEmbed = async (widgets, pdfDoc, signyourself, scale) => {
|
|||||||
const WidgetsTypeTextExist = [
|
const WidgetsTypeTextExist = [
|
||||||
textWidget,
|
textWidget,
|
||||||
textInputWidget,
|
textInputWidget,
|
||||||
|
cellsWidget,
|
||||||
"name",
|
"name",
|
||||||
"company",
|
"company",
|
||||||
"job title",
|
"job title",
|
||||||
@@ -1654,9 +1681,10 @@ export const multiSignEmbed = async (widgets, pdfDoc, signyourself, scale) => {
|
|||||||
const color = position?.options?.fontColor;
|
const color = position?.options?.fontColor;
|
||||||
const updateColorInRgb = getWidgetsFontColor(color);
|
const updateColorInRgb = getWidgetsFontColor(color);
|
||||||
const fontSize = parseInt(position?.options?.fontSize || 12);
|
const fontSize = parseInt(position?.options?.fontSize || 12);
|
||||||
const widgetTypeExist = [
|
const isTextTypeWidget = [
|
||||||
textWidget,
|
textWidget,
|
||||||
textInputWidget,
|
textInputWidget,
|
||||||
|
cellsWidget,
|
||||||
"name",
|
"name",
|
||||||
"company",
|
"company",
|
||||||
"job title",
|
"job title",
|
||||||
@@ -1664,138 +1692,185 @@ export const multiSignEmbed = async (widgets, pdfDoc, signyourself, scale) => {
|
|||||||
"email"
|
"email"
|
||||||
].includes(position.type);
|
].includes(position.type);
|
||||||
if (position.type === "checkbox") {
|
if (position.type === "checkbox") {
|
||||||
let checkboxGapFromTop, isCheck;
|
// Determine layout mode: 'vertical' (default) or 'horizontal'
|
||||||
let y = yPos(position);
|
// const isHorizontal = position.layout === "horizontal";
|
||||||
const optionsFontSize = fontSize || 13;
|
const isHorizontal =
|
||||||
const checkboxSize = fontSize;
|
position?.options?.layout === "horizontal" ? true : false;
|
||||||
const checkboxTextGapFromLeft = fontSize + 5 || 22;
|
// Initial “cursor” positions
|
||||||
|
let currentX = xPos(position);
|
||||||
|
let currentY = yPos(position) + 2;
|
||||||
|
// Size and spacing settings
|
||||||
|
const checkboxSize = fontSize - 1; // checkbox diameter
|
||||||
|
const checkboxTextGapFromLeft = fontSize + 5; // gap between box and its label
|
||||||
|
const verticalGap = fontSize + 3.2; // gap between two rows (vertical layout)
|
||||||
|
let horizontalGap = 0; // will compute after drawing each label
|
||||||
if (position?.options?.values.length > 0) {
|
if (position?.options?.values.length > 0) {
|
||||||
position?.options?.values.forEach((item, ind) => {
|
position.options.values.forEach((item, ind) => {
|
||||||
const checkboxRandomId = "checkbox" + randomId();
|
// 1. Advance the “cursor” on second+ iteration
|
||||||
if (
|
|
||||||
position?.options?.response &&
|
|
||||||
position?.options?.response?.length > 0
|
|
||||||
) {
|
|
||||||
isCheck = position?.options?.response?.includes(ind);
|
|
||||||
} else if (position?.options?.defaultValue) {
|
|
||||||
isCheck = position?.options?.defaultValue?.includes(ind);
|
|
||||||
}
|
|
||||||
|
|
||||||
const checkbox = form.createCheckBox(checkboxRandomId);
|
|
||||||
|
|
||||||
if (ind > 0) {
|
if (ind > 0) {
|
||||||
y = y + checkboxGapFromTop;
|
if (isHorizontal) {
|
||||||
} else {
|
currentX += horizontalGap;
|
||||||
checkboxGapFromTop = fontSize + 5 || 26;
|
} else {
|
||||||
|
currentY += verticalGap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 2. Determine whether this checkbox should be checked
|
||||||
|
let isCheck = false;
|
||||||
|
if (
|
||||||
|
position.options.response &&
|
||||||
|
position.options.response.length > 0
|
||||||
|
) {
|
||||||
|
isCheck = position.options.response.includes(ind);
|
||||||
|
} else if (position.options.defaultValue) {
|
||||||
|
isCheck = position.options.defaultValue.includes(ind);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!position?.options?.isHideLabel) {
|
// 3. Draw the label (if labels are not hidden)
|
||||||
// below line of code is used to embed label with radio button in pdf
|
if (!position.options.isHideLabel) {
|
||||||
|
const labelX = currentX + checkboxTextGapFromLeft;
|
||||||
|
const labelY = currentY - 3;
|
||||||
|
|
||||||
const optionsPosition = compensateRotation(
|
const optionsPosition = compensateRotation(
|
||||||
page.getRotation().angle,
|
page.getRotation().angle,
|
||||||
xPos(position) + checkboxTextGapFromLeft,
|
labelX,
|
||||||
y,
|
labelY,
|
||||||
1,
|
1,
|
||||||
page.getSize(),
|
getSize,
|
||||||
optionsFontSize,
|
fontSize,
|
||||||
updateColorInRgb,
|
updateColorInRgb,
|
||||||
font,
|
font,
|
||||||
page
|
page
|
||||||
);
|
);
|
||||||
page.drawText(item, optionsPosition);
|
page.drawText(item, optionsPosition);
|
||||||
}
|
}
|
||||||
|
// 4. Create and place the actual checkbox
|
||||||
|
const checkboxRandomId = "checkbox" + randomId();
|
||||||
|
const checkbox = form.createCheckBox(checkboxRandomId);
|
||||||
let checkboxObj = {
|
let checkboxObj = {
|
||||||
x: xPos(position),
|
x: currentX,
|
||||||
y: y,
|
y: currentY,
|
||||||
width: checkboxSize,
|
width: checkboxSize,
|
||||||
height: checkboxSize
|
height: checkboxSize
|
||||||
};
|
};
|
||||||
checkboxObj = getWidgetPosition(page, checkboxObj, 1);
|
checkboxObj = getWidgetPosition(page, checkboxObj, 1, getSize);
|
||||||
checkbox.addToPage(page, checkboxObj);
|
checkbox.addToPage(page, checkboxObj);
|
||||||
|
// 5. Check or uncheck as needed, then make read‐only
|
||||||
//applied which checkbox should be checked
|
|
||||||
if (isCheck) {
|
if (isCheck) {
|
||||||
checkbox.check();
|
checkbox.check();
|
||||||
} else {
|
} else {
|
||||||
checkbox.uncheck();
|
checkbox.uncheck();
|
||||||
}
|
}
|
||||||
checkbox.enableReadOnly();
|
checkbox.enableReadOnly();
|
||||||
|
// 6. If horizontal layout, compute how far to shift next checkbox‐circle
|
||||||
|
if (isHorizontal) {
|
||||||
|
// Measure the width of this label text at `fontSize`
|
||||||
|
const textWidth = font.widthOfTextAtSize(item, fontSize);
|
||||||
|
// Next checkbox should come after: [box] + gap + [label text] + extra 10pt padding
|
||||||
|
horizontalGap =
|
||||||
|
checkboxSize + checkboxTextGapFromLeft + textWidth;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if (widgetTypeExist) {
|
} else if (isTextTypeWidget) {
|
||||||
let textContent;
|
let textContent = "";
|
||||||
if (position?.options?.response) {
|
if (position?.options?.response) {
|
||||||
textContent = position.options?.response;
|
textContent = position.options?.response;
|
||||||
} else if (position?.options?.defaultValue) {
|
} else if (position?.options?.defaultValue) {
|
||||||
textContent = position?.options?.defaultValue;
|
textContent = position?.options?.defaultValue;
|
||||||
}
|
}
|
||||||
const fixedWidth = widgetWidth; // Set your fixed width
|
if (position.type === cellsWidget) {
|
||||||
const isNewOnEnterLineExist = textContent.includes("\n");
|
const cellCount =
|
||||||
|
position?.options?.cellCount || textContent.length || 1;
|
||||||
// Function to break text into lines based on the fixed width
|
const charWidth = widgetWidth / cellCount;
|
||||||
const NewbreakTextIntoLines = (textContent, width) => {
|
const y = yPos(position) - 4;
|
||||||
const lines = [];
|
for (let i = 0; i < cellCount; i++) {
|
||||||
let currentLine = "";
|
const ch = textContent[i] || "";
|
||||||
|
const charX =
|
||||||
for (const word of textContent.split(" ")) {
|
xPos(position) +
|
||||||
//get text line width
|
charWidth * i +
|
||||||
const lineWidth = font.widthOfTextAtSize(
|
(charWidth - font.widthOfTextAtSize(ch, fontSize)) / 2;
|
||||||
`${currentLine} ${word}`,
|
const textPosition = compensateRotation(
|
||||||
fontSize
|
page.getRotation().angle,
|
||||||
|
charX,
|
||||||
|
y,
|
||||||
|
1,
|
||||||
|
getSize,
|
||||||
|
fontSize,
|
||||||
|
updateColorInRgb,
|
||||||
|
font,
|
||||||
|
page
|
||||||
);
|
);
|
||||||
//check text content line width is less or equal to container width
|
if (ch) page.drawText(ch, textPosition);
|
||||||
if (lineWidth <= width) {
|
|
||||||
currentLine += ` ${word}`;
|
|
||||||
} else {
|
|
||||||
lines.push(currentLine.trim());
|
|
||||||
currentLine = `${word}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lines.push(currentLine.trim());
|
|
||||||
return lines;
|
|
||||||
};
|
|
||||||
// Function to break text into lines based on when user go next line on press enter button
|
|
||||||
const breakTextIntoLines = (textContent, width) => {
|
|
||||||
const lines = [];
|
|
||||||
for (const word of textContent.split("\n")) {
|
|
||||||
const lineWidth = font.widthOfTextAtSize(`${word}`, fontSize);
|
|
||||||
//checking string length to container width
|
|
||||||
//if string length is less then container width it means user press enter button
|
|
||||||
if (lineWidth <= width) {
|
|
||||||
lines.push(word);
|
|
||||||
}
|
|
||||||
//else adjust text content according to width and send it in new line
|
|
||||||
else {
|
|
||||||
const newLine = NewbreakTextIntoLines(word, width);
|
|
||||||
lines.push(...newLine);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
const fixedWidth = widgetWidth; // Set your fixed width
|
||||||
|
const isNewOnEnterLineExist = textContent.includes("\n");
|
||||||
|
|
||||||
return lines;
|
// Function to break text into lines based on the fixed width
|
||||||
};
|
const NewbreakTextIntoLines = (textContent, width) => {
|
||||||
//check if text content have `\n` string it means user press enter to go next line and handle condition
|
const lines = [];
|
||||||
//else auto adjust text content according to container width
|
let currentLine = "";
|
||||||
const lines = isNewOnEnterLineExist
|
|
||||||
? breakTextIntoLines(textContent, fixedWidth)
|
for (const word of textContent.split(" ")) {
|
||||||
: NewbreakTextIntoLines(textContent, fixedWidth);
|
//get text line width
|
||||||
// Set initial y-coordinate for the first line
|
const lineWidth = font.widthOfTextAtSize(
|
||||||
let x = xPos(position);
|
`${currentLine} ${word}`,
|
||||||
let y = yPos(position);
|
fontSize
|
||||||
// Embed each line on the page
|
);
|
||||||
for (const line of lines) {
|
//check text content line width is less or equal to container width
|
||||||
const textPosition = compensateRotation(
|
if (lineWidth <= width) {
|
||||||
page.getRotation().angle,
|
currentLine += ` ${word}`;
|
||||||
x,
|
} else {
|
||||||
y,
|
lines.push(currentLine.trim());
|
||||||
1,
|
currentLine = `${word}`;
|
||||||
page.getSize(),
|
}
|
||||||
fontSize,
|
}
|
||||||
updateColorInRgb,
|
lines.push(currentLine.trim());
|
||||||
font,
|
return lines;
|
||||||
page
|
};
|
||||||
);
|
// Function to break text into lines based on when user go next line on press enter button
|
||||||
page.drawText(line, textPosition);
|
const breakTextIntoLines = (textContent, width) => {
|
||||||
y += 18; // Adjust the line height as needed
|
const lines = [];
|
||||||
|
for (const word of textContent.split("\n")) {
|
||||||
|
const lineWidth = font.widthOfTextAtSize(`${word}`, fontSize);
|
||||||
|
//checking string length to container width
|
||||||
|
//if string length is less then container width it means user press enter button
|
||||||
|
if (lineWidth <= width) {
|
||||||
|
lines.push(word);
|
||||||
|
}
|
||||||
|
//else adjust text content according to width and send it in new line
|
||||||
|
else {
|
||||||
|
const newLine = NewbreakTextIntoLines(word, width);
|
||||||
|
lines.push(...newLine);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return lines;
|
||||||
|
};
|
||||||
|
//check if text content have `\n` string it means user press enter to go next line and handle condition
|
||||||
|
//else auto adjust text content according to container width
|
||||||
|
const lines = isNewOnEnterLineExist
|
||||||
|
? breakTextIntoLines(textContent, fixedWidth)
|
||||||
|
: NewbreakTextIntoLines(textContent, fixedWidth);
|
||||||
|
// Set initial y-coordinate for the first line
|
||||||
|
let x = xPos(position);
|
||||||
|
let y = yPos(position) - 4;
|
||||||
|
// Embed each line on the page
|
||||||
|
for (const line of lines) {
|
||||||
|
const textPosition = compensateRotation(
|
||||||
|
page.getRotation().angle,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
1,
|
||||||
|
getSize,
|
||||||
|
fontSize,
|
||||||
|
updateColorInRgb,
|
||||||
|
font,
|
||||||
|
page
|
||||||
|
);
|
||||||
|
page.drawText(line, textPosition);
|
||||||
|
y += 18; // Adjust the line height as needed
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (position.type === "dropdown") {
|
} else if (position.type === "dropdown") {
|
||||||
const dropdownRandomId = "dropdown" + randomId();
|
const dropdownRandomId = "dropdown" + randomId();
|
||||||
@@ -1822,7 +1897,12 @@ export const multiSignEmbed = async (widgets, pdfDoc, signyourself, scale) => {
|
|||||||
width: widgetWidth,
|
width: widgetWidth,
|
||||||
height: widgetHeight
|
height: widgetHeight
|
||||||
};
|
};
|
||||||
const dropdownOption = getWidgetPosition(page, dropdownObj, 1);
|
const dropdownOption = getWidgetPosition(
|
||||||
|
page,
|
||||||
|
dropdownObj,
|
||||||
|
1,
|
||||||
|
getSize
|
||||||
|
);
|
||||||
const dropdownSelected = { ...dropdownOption, font: font };
|
const dropdownSelected = { ...dropdownOption, font: font };
|
||||||
dropdown.defaultUpdateAppearances(font);
|
dropdown.defaultUpdateAppearances(font);
|
||||||
dropdown.addToPage(page, dropdownSelected);
|
dropdown.addToPage(page, dropdownSelected);
|
||||||
@@ -1830,27 +1910,46 @@ export const multiSignEmbed = async (widgets, pdfDoc, signyourself, scale) => {
|
|||||||
} else if (position.type === radioButtonWidget) {
|
} else if (position.type === radioButtonWidget) {
|
||||||
const radioRandomId = "radio" + randomId();
|
const radioRandomId = "radio" + randomId();
|
||||||
const radioGroup = form.createRadioGroup(radioRandomId);
|
const radioGroup = form.createRadioGroup(radioRandomId);
|
||||||
let radioOptionGapFromTop;
|
//getting radio buttons options text font size
|
||||||
const optionsFontSize = fontSize || 13;
|
const optionsFontSize = fontSize; // font size for option text
|
||||||
const radioTextGapFromLeft = fontSize + 5 || 20;
|
const radioTextGapFromLeft = fontSize + 6; // gap between circle and its label
|
||||||
const radioSize = fontSize;
|
const radioSize = fontSize; // circle diameter (square of width×height)
|
||||||
let y = yPos(position);
|
// Initial “cursor” positions (from your existing helpers)
|
||||||
|
let currentX = xPos(position) + 2;
|
||||||
|
let currentY = yPos(position);
|
||||||
|
// Vertical gap between two radio‐rows
|
||||||
|
const verticalGap = fontSize + 6;
|
||||||
|
// We’ll compute horizontalGap on the fly—after drawing each label
|
||||||
|
// Initialize to zero (will be set after first option is placed)
|
||||||
|
let horizontalGap = 0;
|
||||||
|
// Determine layout mode: 'vertical' or 'horizontal'.
|
||||||
|
// (You mentioned “add one variable called layout” – here we read it from position.layout.)
|
||||||
|
const isHorizontal =
|
||||||
|
position?.options?.layout === "horizontal" ? true : false;
|
||||||
|
// Loop through each option in the group
|
||||||
if (position?.options?.values.length > 0) {
|
if (position?.options?.values.length > 0) {
|
||||||
position?.options?.values.forEach((item, ind) => {
|
position.options.values.forEach((item, ind) => {
|
||||||
|
// 1. Advance cursor on second+ iteration
|
||||||
if (ind > 0) {
|
if (ind > 0) {
|
||||||
y = y + radioOptionGapFromTop;
|
if (isHorizontal) {
|
||||||
} else {
|
// Move to the right by horizontalGap
|
||||||
radioOptionGapFromTop = fontSize + 10 || 25;
|
currentX += horizontalGap;
|
||||||
|
} else {
|
||||||
|
// Move down by verticalGap (vertical stacking)
|
||||||
|
currentY += verticalGap;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
// 2. Draw the label text (if not hidden)
|
||||||
if (!position?.options?.isHideLabel) {
|
if (!position?.options?.isHideLabel) {
|
||||||
// below line of code is used to embed label with radio button in pdf
|
// Compute where to draw the text (just to the right of the circle)
|
||||||
|
const labelX = currentX + radioTextGapFromLeft;
|
||||||
|
const labelY = currentY - 2;
|
||||||
const optionsPosition = compensateRotation(
|
const optionsPosition = compensateRotation(
|
||||||
page.getRotation().angle,
|
page.getRotation().angle,
|
||||||
xPos(position) + radioTextGapFromLeft,
|
labelX,
|
||||||
y,
|
labelY,
|
||||||
1,
|
1,
|
||||||
page.getSize(),
|
getSize,
|
||||||
optionsFontSize,
|
optionsFontSize,
|
||||||
updateColorInRgb,
|
updateColorInRgb,
|
||||||
font,
|
font,
|
||||||
@@ -1859,22 +1958,33 @@ export const multiSignEmbed = async (widgets, pdfDoc, signyourself, scale) => {
|
|||||||
|
|
||||||
page.drawText(item, optionsPosition);
|
page.drawText(item, optionsPosition);
|
||||||
}
|
}
|
||||||
|
// 3. Place the radio‐circle itself at (currentX, currentY)
|
||||||
let radioObj = {
|
let radioObj = {
|
||||||
x: xPos(position),
|
x: currentX,
|
||||||
y: y,
|
y: currentY,
|
||||||
width: radioSize,
|
width: radioSize,
|
||||||
height: radioSize
|
height: radioSize
|
||||||
};
|
};
|
||||||
|
|
||||||
radioObj = getWidgetPosition(page, radioObj, 1);
|
radioObj = getWidgetPosition(page, radioObj, 1, getSize);
|
||||||
radioGroup.addOptionToPage(item, page, radioObj);
|
radioGroup.addOptionToPage(item, page, radioObj);
|
||||||
|
// 4. If horizontal layout, re-compute horizontalGap for next iteration:
|
||||||
|
if (isHorizontal) {
|
||||||
|
// Measure how wide the label text is, so we know how far to shift next circle
|
||||||
|
const textWidth = font.widthOfTextAtSize(item, optionsFontSize);
|
||||||
|
// radioSize = the circle. radioTextGapFromLeft = gap between circle and label.
|
||||||
|
// Add a small extra padding (e.g. 10pt) before placing next circle.
|
||||||
|
horizontalGap = radioSize + radioTextGapFromLeft + textWidth;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
// 5. Pre‐select a value if provided
|
||||||
if (position?.options?.response) {
|
if (position?.options?.response) {
|
||||||
radioGroup.select(position.options?.response);
|
radioGroup.select(position.options?.response);
|
||||||
} else if (position?.options?.defaultValue) {
|
} else if (position?.options?.defaultValue) {
|
||||||
radioGroup.select(position?.options?.defaultValue);
|
radioGroup.select(position?.options?.defaultValue);
|
||||||
}
|
}
|
||||||
|
// 6. Set to read‐only (if required)
|
||||||
radioGroup.enableReadOnly();
|
radioGroup.enableReadOnly();
|
||||||
} else {
|
} else {
|
||||||
const signature = {
|
const signature = {
|
||||||
@@ -1884,7 +1994,7 @@ export const multiSignEmbed = async (widgets, pdfDoc, signyourself, scale) => {
|
|||||||
height: widgetHeight
|
height: widgetHeight
|
||||||
};
|
};
|
||||||
|
|
||||||
const imageOptions = getWidgetPosition(page, signature, 1);
|
const imageOptions = getWidgetPosition(page, signature, 1, getSize);
|
||||||
page.drawImage(img, imageOptions);
|
page.drawImage(img, imageOptions);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -2611,7 +2721,7 @@ function compensateRotation(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// `getWidgetPosition` is used to calulcate position of image type widget like x, y, width, height for pdf-lib
|
// `getWidgetPosition` is used to calulcate position of image type widget like x, y, width, height for pdf-lib
|
||||||
function getWidgetPosition(page, image, sizeRatio) {
|
function getWidgetPosition(page, image, sizeRatio, getSize) {
|
||||||
let pageWidth;
|
let pageWidth;
|
||||||
// pageHeight;
|
// pageHeight;
|
||||||
if ([90, 270].includes(page.getRotation().angle)) {
|
if ([90, 270].includes(page.getRotation().angle)) {
|
||||||
@@ -2635,7 +2745,7 @@ function getWidgetPosition(page, image, sizeRatio) {
|
|||||||
imageX,
|
imageX,
|
||||||
imageYFromTop,
|
imageYFromTop,
|
||||||
1,
|
1,
|
||||||
page.getSize(),
|
getSize,
|
||||||
imageHeight
|
imageHeight
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -2954,6 +3064,23 @@ export const deletePdfPage = async (pdfArrayBuffer, pageNumber) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const reorderPdfPages = async (pdfArrayBuffer, orderArr) => {
|
||||||
|
try {
|
||||||
|
const pdfDoc = await PDFDocument.load(pdfArrayBuffer);
|
||||||
|
const newPdf = await PDFDocument.create();
|
||||||
|
const pages = await newPdf.copyPages(
|
||||||
|
pdfDoc,
|
||||||
|
orderArr.map((n) => n - 1)
|
||||||
|
);
|
||||||
|
pages.forEach((p) => newPdf.addPage(p));
|
||||||
|
const pdfBase64 = await newPdf.saveAsBase64({ useObjectStreams: false });
|
||||||
|
const arrayBuffer = base64ToArrayBuffer(pdfBase64);
|
||||||
|
return { arrayBuffer, base64: pdfBase64, totalPages: orderArr.length };
|
||||||
|
} catch (err) {
|
||||||
|
console.log("Err while reordering pages", err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// `generatePdfName` is used to generate file name
|
// `generatePdfName` is used to generate file name
|
||||||
export function generatePdfName(length) {
|
export function generatePdfName(length) {
|
||||||
const characters =
|
const characters =
|
||||||
@@ -3066,7 +3193,7 @@ export const mailTemplate = (param) => {
|
|||||||
"</td></tr><tr><td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Note</td><td></td><td style='color:#626363;font-weight:bold'>" +
|
"</td></tr><tr><td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Note</td><td></td><td style='color:#626363;font-weight:bold'>" +
|
||||||
param.note +
|
param.note +
|
||||||
"</td></tr><tr><td></td><td></td></tr></table></div> <div style='margin-left:70px'><a target=_blank href=" +
|
"</td></tr><tr><td></td><td></td></tr></table></div> <div style='margin-left:70px'><a target=_blank href=" +
|
||||||
param.sigingUrl +
|
param.signingUrl +
|
||||||
"><button style='padding:12px;background-color:#d46b0f;color:white;border:0px;font-weight:bold;margin-top:30px'>Sign here</button></a></div><div style='display:flex;justify-content:center;margin-top:10px'></div></div></div><div><p> This is an automated email from " +
|
"><button style='padding:12px;background-color:#d46b0f;color:white;border:0px;font-weight:bold;margin-top:30px'>Sign here</button></a></div><div style='display:flex;justify-content:center;margin-top:10px'></div></div></div><div><p> This is an automated email from " +
|
||||||
appName +
|
appName +
|
||||||
". For any queries regarding this email, please contact the sender " +
|
". For any queries regarding this email, please contact the sender " +
|
||||||
@@ -3157,7 +3284,225 @@ export const checkRegularExpress = (validateType, setValidatePlaceholder) => {
|
|||||||
case "text":
|
case "text":
|
||||||
setValidatePlaceholder("please enter text");
|
setValidatePlaceholder("please enter text");
|
||||||
break;
|
break;
|
||||||
|
case "ssn":
|
||||||
|
setValidatePlaceholder("123-45-6789");
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
setValidatePlaceholder("please enter value");
|
setValidatePlaceholder("please enter value");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
//function to use unlink signer from widgets
|
||||||
|
export const handleUnlinkSigner = (
|
||||||
|
signerPos,
|
||||||
|
setSignerPos,
|
||||||
|
signersdata,
|
||||||
|
setSignersData,
|
||||||
|
uniqueId
|
||||||
|
) => {
|
||||||
|
//remove existing signer's details from 'signerPos' array
|
||||||
|
const updatePlaceHolder = signerPos.map((x) => {
|
||||||
|
if (x.Id === uniqueId) {
|
||||||
|
return { ...x, signerPtr: {}, signerObjId: "" };
|
||||||
|
}
|
||||||
|
return { ...x };
|
||||||
|
});
|
||||||
|
setSignerPos(updatePlaceHolder);
|
||||||
|
//remove existing signer's details from 'signersdata' array and keep role and id
|
||||||
|
const updateSigner = signersdata.map((item) => {
|
||||||
|
if (item.Id == uniqueId) {
|
||||||
|
return { Role: item.Role, Id: item.Id, blockColor: item.blockColor };
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
setSignersData(updateSigner);
|
||||||
|
};
|
||||||
|
//function is used to get pdf original width and height
|
||||||
|
export const getOriginalWH = async (pdf) => {
|
||||||
|
let pdfWHObj = [];
|
||||||
|
//get total page number
|
||||||
|
const totalPages = pdf?.numPages;
|
||||||
|
//according to page number get all pdf's pages width and height
|
||||||
|
for (let index = 0; index < totalPages; index++) {
|
||||||
|
try {
|
||||||
|
const getPage = await pdf.getPage(index + 1);
|
||||||
|
const width = getPage?.view[2];
|
||||||
|
const height = getPage?.view[3];
|
||||||
|
pdfWHObj.push({ pageNumber: index + 1, width, height });
|
||||||
|
} catch (e) {
|
||||||
|
console.log(`Error getting page ${index + 1} of PDF: ${e.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pdfWHObj;
|
||||||
|
};
|
||||||
|
|
||||||
|
//function is used to check required and optional widgets and ensure required widget should be response
|
||||||
|
export const handleCheckResponse = (checkUser, setminRequiredCount) => {
|
||||||
|
let checkboxExist,
|
||||||
|
showAlert = false,
|
||||||
|
widgetKey,
|
||||||
|
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";
|
||||||
|
//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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//else condition to check all type widget data fill or not except checkbox
|
||||||
|
else {
|
||||||
|
//get all required type widgets except checkbox and radio
|
||||||
|
const requiredWidgets = checkUser[0].placeHolder[i].pos.filter(
|
||||||
|
(position) =>
|
||||||
|
position.type === "signature" ||
|
||||||
|
(position.options?.status === "required" &&
|
||||||
|
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) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
tourPageNumber,
|
||||||
|
widgetKey,
|
||||||
|
showAlert
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* decryptPdf
|
||||||
|
* @param {File} file - The password-protected PDF file to decrypt.
|
||||||
|
* @param {string | undefined} password - The password used to unlock the PDF.
|
||||||
|
* @returns {Promise<File>} - A Promise that resolves to a decrypted PDF as a File object.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export const decryptPdf = async (file, password) => {
|
||||||
|
const name = generatePdfName(16);
|
||||||
|
const baseApi = localStorage.getItem("baseUrl") || "";
|
||||||
|
const url = removeTrailingSegment(baseApi) + "/decryptpdf?ts=" + Date.now();
|
||||||
|
let formData = new FormData();
|
||||||
|
formData.append("file", file);
|
||||||
|
formData.append("password", 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" });
|
||||||
|
return new File([pdfBlob], name, { type: "application/pdf" });
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a Base64 string to a File object.
|
||||||
|
*
|
||||||
|
* @param {string} base64String - The Base64 string, with or without the data URI prefix.
|
||||||
|
* e.g. "data:image/png;base64,iVBORw0KGgoAAAANS…" or "iVBORw0KGgoAAAANS…"
|
||||||
|
* @param {string} filename - Desired filename for the File object, e.g. "photo.png"
|
||||||
|
* @returns {File} - The resulting File object
|
||||||
|
*/
|
||||||
|
export function base64ToFile(base64String, filename) {
|
||||||
|
// Separate out the mime-type and the actual Base64 payload
|
||||||
|
const [header, payload] = base64String.includes(",")
|
||||||
|
? base64String.split(",")
|
||||||
|
: [null, base64String];
|
||||||
|
// Determine the MIME type (fallback to application/octet-stream)
|
||||||
|
const mimeMatch = header?.match(/data:(.*?);base64/);
|
||||||
|
const mime = mimeMatch ? mimeMatch[1] : "application/octet-stream";
|
||||||
|
|
||||||
|
// Decode Base64 to raw binary data held in a string
|
||||||
|
const binaryString = atob(payload);
|
||||||
|
// Create an ArrayBuffer and a view (as unsigned 8-bit)
|
||||||
|
const len = binaryString.length;
|
||||||
|
const u8arr = new Uint8Array(len);
|
||||||
|
for (let i = 0; i < len; i++) {
|
||||||
|
u8arr[i] = binaryString.charCodeAt(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a File object (Blob subclass) with the binary data
|
||||||
|
return new File([u8arr], filename, { type: mime });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the given File object and returns its contents as an ArrayBuffer.
|
||||||
|
*
|
||||||
|
* @param {File} file
|
||||||
|
* The File instance to be read.
|
||||||
|
* @returns {Promise<ArrayBuffer>}
|
||||||
|
* A promise that resolves with the file’s binary data as an ArrayBuffer,
|
||||||
|
* or rejects with an error if the read fails.
|
||||||
|
*/
|
||||||
|
export 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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
import logo from "../assets/images/logo.png";
|
import logo from "../assets/images/logo.png";
|
||||||
|
import { getEnv } from "./Utils";
|
||||||
|
|
||||||
export function serverUrl_fn() {
|
export function serverUrl_fn() {
|
||||||
let baseUrl = process.env.REACT_APP_SERVERURL
|
const env = getEnv();
|
||||||
? process.env.REACT_APP_SERVERURL
|
const serverurl = env?.REACT_APP_SERVERURL
|
||||||
: window.location.origin + "/api/app";
|
? env.REACT_APP_SERVERURL // env.REACT_APP_SERVERURL is used for prod
|
||||||
|
: process.env.REACT_APP_SERVERURL; // process.env.REACT_APP_SERVERURL is used for dev (locally)
|
||||||
|
let baseUrl = serverurl ? serverurl : window.location.origin + "/api/app";
|
||||||
return baseUrl;
|
return baseUrl;
|
||||||
}
|
}
|
||||||
export const appInfo = {
|
export const appInfo = {
|
||||||
|
|||||||
@@ -3,6 +3,11 @@ export const templateCls = "contracts_Template";
|
|||||||
export const documentCls = "contracts_Document";
|
export const documentCls = "contracts_Document";
|
||||||
export const themeColor = "#47a3ad";
|
export const themeColor = "#47a3ad";
|
||||||
export const iconColor = "#686968";
|
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 emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
|
||||||
export const maxFileSize = 10; // 10MB
|
export const maxFileSize = 10; // 10MB
|
||||||
export const maxTitleLength = 250; // 250 characters
|
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;
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useEffect } from "react";
|
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) => {
|
export const useScript = (url, onload) => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const script = document.createElement("script");
|
const script = document.createElement("script");
|
||||||
@@ -15,4 +15,3 @@ export const useScript = (url, onload) => {
|
|||||||
};
|
};
|
||||||
}, [url, onload]);
|
}, [url, onload]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ i18n
|
|||||||
interpolation: {
|
interpolation: {
|
||||||
escapeValue: false // Not needed for react as it escapes by default
|
escapeValue: false // Not needed for react as it escapes by default
|
||||||
},
|
},
|
||||||
whitelist: ["en", "es", "fr", "it", "de"] // List of allowed languages
|
whitelist: ["en", "es", "fr", "it", "de", "hi"] // List of allowed languages
|
||||||
});
|
});
|
||||||
|
|
||||||
export default i18n;
|
export default i18n;
|
||||||
|
|||||||
@@ -98,4 +98,164 @@ body {
|
|||||||
*::-webkit-scrollbar-thumb {
|
*::-webkit-scrollbar-thumb {
|
||||||
background-color: gray;
|
background-color: gray;
|
||||||
border-radius: 10px;
|
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 React from "react";
|
||||||
import ReactDOM from "react-dom/client";
|
import ReactDOM from "react-dom/client";
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
|
import "./styles/dark-theme-improvements.css";
|
||||||
import App from "./App";
|
import App from "./App";
|
||||||
import { Provider } from "react-redux";
|
import { Provider } from "react-redux";
|
||||||
import { store } from "./redux/store";
|
import { store } from "./redux/store";
|
||||||
@@ -24,6 +25,11 @@ const serverUrl = serverUrl_fn();
|
|||||||
Parse.initialize(appId);
|
Parse.initialize(appId);
|
||||||
Parse.serverURL = serverUrl;
|
Parse.serverURL = serverUrl;
|
||||||
|
|
||||||
|
const savedTheme = localStorage.getItem("theme");
|
||||||
|
if (savedTheme === "dark") {
|
||||||
|
document.documentElement.setAttribute("data-theme", "opensigndark");
|
||||||
|
}
|
||||||
|
|
||||||
const HTML5toTouch = {
|
const HTML5toTouch = {
|
||||||
backends: [
|
backends: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -186,6 +186,14 @@ export default function reportJson(id) {
|
|||||||
btnIcon: "fa-light fa-envelope",
|
btnIcon: "fa-light fa-envelope",
|
||||||
redirectUrl: "",
|
redirectUrl: "",
|
||||||
action: "saveastemplate"
|
action: "saveastemplate"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
btnId: "8440",
|
||||||
|
btnLabel: "Fix & resend",
|
||||||
|
hoverLabel: "Fix & resend",
|
||||||
|
btnIcon: "fa-light fa-paper-plane",
|
||||||
|
redirectUrl: "",
|
||||||
|
action: "recreatedocument"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,13 @@
|
|||||||
|
const userssetting = [
|
||||||
|
{
|
||||||
|
icon: "fa-light fa-users fa-fw",
|
||||||
|
title: "Users",
|
||||||
|
target: "_self",
|
||||||
|
pageType: "",
|
||||||
|
description: "",
|
||||||
|
objectId: "users"
|
||||||
|
}
|
||||||
|
];
|
||||||
export const subSetting = [
|
export const subSetting = [
|
||||||
{
|
{
|
||||||
icon: "fa-light fa-sliders",
|
icon: "fa-light fa-sliders",
|
||||||
@@ -7,14 +17,7 @@ export const subSetting = [
|
|||||||
description: "",
|
description: "",
|
||||||
objectId: "preferences"
|
objectId: "preferences"
|
||||||
},
|
},
|
||||||
{
|
...userssetting
|
||||||
icon: "fa-light fa-users fa-fw",
|
|
||||||
title: "Users",
|
|
||||||
target: "_self",
|
|
||||||
pageType: "",
|
|
||||||
description: "",
|
|
||||||
objectId: "users"
|
|
||||||
}
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const sidebarList = [
|
const sidebarList = [
|
||||||
@@ -65,7 +68,7 @@ const sidebarList = [
|
|||||||
pageType: "report",
|
pageType: "report",
|
||||||
description: "",
|
description: "",
|
||||||
objectId: "6TeaPr321t"
|
objectId: "6TeaPr321t"
|
||||||
},
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
+204
-234
@@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { useNavigate, useParams } from "react-router";
|
import { useNavigate, useParams } from "react-router";
|
||||||
import { formJson } from "../json/FormJson";
|
import { formJson } from "../json/FormJson";
|
||||||
import Parse from "parse";
|
import Parse from "parse";
|
||||||
@@ -12,9 +12,12 @@ import {
|
|||||||
flattenPdf,
|
flattenPdf,
|
||||||
generatePdfName,
|
generatePdfName,
|
||||||
generateTitleFromFilename,
|
generateTitleFromFilename,
|
||||||
getFileName,
|
|
||||||
getSecureUrl,
|
getSecureUrl,
|
||||||
toDataUrl
|
toDataUrl,
|
||||||
|
decryptPdf,
|
||||||
|
base64ToFile,
|
||||||
|
getFileAsArrayBuffer,
|
||||||
|
removeTrailingSegment
|
||||||
} from "../constant/Utils";
|
} from "../constant/Utils";
|
||||||
import { PDFDocument } from "pdf-lib";
|
import { PDFDocument } from "pdf-lib";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
@@ -68,6 +71,7 @@ const Forms = (props) => {
|
|||||||
AllowModifications: false
|
AllowModifications: false
|
||||||
});
|
});
|
||||||
const [fileupload, setFileUpload] = useState("");
|
const [fileupload, setFileUpload] = useState("");
|
||||||
|
const [selectedFiles, setSelectedFiles] = useState([]);
|
||||||
const [fileload, setfileload] = useState(false);
|
const [fileload, setfileload] = useState(false);
|
||||||
const [percentage, setpercentage] = useState(0);
|
const [percentage, setpercentage] = useState(0);
|
||||||
const [isReset, setIsReset] = useState(false);
|
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
|
// `removeFile` is used to reset progress, percentage and remove file if exists
|
||||||
const removeFile = (e) => {
|
const removeFile = (e) => {
|
||||||
setfileload(false);
|
setfileload(false);
|
||||||
@@ -134,219 +129,212 @@ const Forms = (props) => {
|
|||||||
const handleFileInput = async (e) => {
|
const handleFileInput = async (e) => {
|
||||||
setpercentage(0);
|
setpercentage(0);
|
||||||
try {
|
try {
|
||||||
let files = e.target.files;
|
const files = Array.from(e.target.files);
|
||||||
setFormData((prev) => ({ ...prev, file: e.target.files[0] }));
|
const filesNameArr = files.map((f) => f.name);
|
||||||
if (typeof files[0] !== "undefined") {
|
setSelectedFiles(filesNameArr);
|
||||||
const mb = Math.round(files[0].size / Math.pow(1024, 2));
|
if (!files.length) {
|
||||||
if (mb > maxFileSize) {
|
alert(t("file-alert-2"));
|
||||||
alert(`${t("file-alert-1")} ${maxFileSize} MB`);
|
return;
|
||||||
setFileUpload("");
|
}
|
||||||
removeFile(e);
|
// setFormData((prev) => ({ ...prev, file: files[0] }));
|
||||||
return;
|
const totalMb = Math.round(
|
||||||
} else {
|
files.reduce((sum, f) => sum + f.size, 0) / Math.pow(1024, 2)
|
||||||
if (files?.[0]?.type === "application/pdf") {
|
);
|
||||||
const size = files?.[0]?.size;
|
if (totalMb > maxFileSize) {
|
||||||
const name = generatePdfName(16);
|
alert(`${t("file-alert-1")} ${maxFileSize} MB`);
|
||||||
const pdfName = `${name?.split(".")[0]}.pdf`;
|
setFileUpload("");
|
||||||
setfileload(true);
|
setSelectedFiles([]);
|
||||||
try {
|
removeFile(e);
|
||||||
const res = await getFileAsArrayBuffer(files[0]);
|
return;
|
||||||
const flatPdf = await flattenPdf(res);
|
}
|
||||||
const parseFile = new Parse.File(
|
|
||||||
pdfName,
|
|
||||||
[...flatPdf],
|
|
||||||
"application/pdf"
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
const pdfBuffers = [];
|
||||||
const response = await parseFile.save({
|
for (const file of files) {
|
||||||
progress: (progressValue, loaded, total, { type }) => {
|
setFormData((prev) => ({ ...prev, file: file }));
|
||||||
if (type === "upload" && progressValue !== null) {
|
if (file.type === "application/pdf") {
|
||||||
const percentCompleted = Math.round(
|
try {
|
||||||
(loaded * 100) / total
|
const buffer = await getFileAsArrayBuffer(file);
|
||||||
);
|
const flat = await flattenPdf(buffer);
|
||||||
setpercentage(percentCompleted);
|
pdfBuffers.push(flat);
|
||||||
}
|
} catch (err) {
|
||||||
}
|
if (err?.message?.includes("is encrypted")) {
|
||||||
});
|
try {
|
||||||
// The response object will contain information about the uploaded file
|
setIsDecrypting(true);
|
||||||
// You can access the URL of the uploaded file using response.url()
|
const pdfFile = await decryptPdf(file, "");
|
||||||
if (response.url()) {
|
setIsDecrypting(false);
|
||||||
const fileRes = await getSecureUrl(response.url());
|
setfileload(true);
|
||||||
if (fileRes.url) {
|
const res = await getFileAsArrayBuffer(pdfFile);
|
||||||
setFileUpload(fileRes.url);
|
const flatPdf = await flattenPdf(res);
|
||||||
setfileload(false);
|
// Upload the file to Parse Server
|
||||||
const tenantId = localStorage.getItem("TenantId");
|
pdfBuffers.push(flatPdf);
|
||||||
const title = generateTitleFromFilename(files?.[0]?.name);
|
|
||||||
setFormData((obj) => ({ ...obj, Name: title }));
|
|
||||||
SaveFileSize(size, fileRes.url, tenantId);
|
|
||||||
return fileRes.url;
|
|
||||||
} else {
|
|
||||||
removeFile(e);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
removeFile(e);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
removeFile(e);
|
|
||||||
console.error("Error uploading file:", error);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err?.message?.includes("is encrypted")) {
|
removeFile(e);
|
||||||
try {
|
if (err?.response?.status === 401) {
|
||||||
setIsDecrypting(true);
|
// setIsPassword(true);
|
||||||
const size = files?.[0].size;
|
const password = prompt(
|
||||||
const name = generatePdfName(16);
|
`PDF "${file.name}" is password-protected. Enter password:`
|
||||||
const url = "https://ai.nxglabs.in/decryptpdf"; //
|
);
|
||||||
let formData = new FormData();
|
|
||||||
formData.append("file", files[0]);
|
if (password) {
|
||||||
formData.append("password", "");
|
try {
|
||||||
const config = {
|
const pdfFile = await decryptPdf(file, password);
|
||||||
headers: { "content-type": "multipart/form-data" },
|
setIsDecrypting(false);
|
||||||
responseType: "blob"
|
setfileload(true);
|
||||||
};
|
|
||||||
const response = await axios.post(url, formData, config);
|
|
||||||
const pdfBlob = new Blob([response.data], {
|
|
||||||
type: "application/pdf"
|
|
||||||
});
|
|
||||||
const pdfFile = new File([pdfBlob], name, {
|
|
||||||
type: "application/pdf"
|
|
||||||
});
|
|
||||||
setIsDecrypting(false);
|
|
||||||
setfileload(true);
|
|
||||||
const res = await getFileAsArrayBuffer(pdfFile);
|
const res = await getFileAsArrayBuffer(pdfFile);
|
||||||
const flatPdf = await flattenPdf(res);
|
const flatPdf = await flattenPdf(res);
|
||||||
// Upload the file to Parse Server
|
// Upload the file to Parse Server
|
||||||
const parseFile = new Parse.File(
|
pdfBuffers.push(flatPdf);
|
||||||
name,
|
} catch (err) {
|
||||||
[...flatPdf],
|
console.error(
|
||||||
"application/pdf"
|
"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);
|
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);
|
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);
|
removeFile(e);
|
||||||
|
alert(`Incorrect password for file: ${file.name}`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
console.error("password not provided");
|
||||||
|
setSelectedFiles(
|
||||||
|
filesNameArr.filter((f) => f !== file.name)
|
||||||
|
);
|
||||||
|
setIsDecrypting(false);
|
||||||
|
setfileload(false);
|
||||||
removeFile(e);
|
removeFile(e);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} else {
|
||||||
|
console.log("Error uploading file: ", err?.response);
|
||||||
|
setIsDecrypting(false);
|
||||||
|
e.target.value = "";
|
||||||
removeFile(e);
|
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"));
|
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) {
|
} catch (error) {
|
||||||
alert(error.message);
|
alert(error.message);
|
||||||
return false;
|
setSelectedFiles([]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// `isValidURL` is used to check valid webhook url
|
// `isValidURL` is used to check valid webhook url
|
||||||
@@ -446,16 +434,13 @@ const Forms = (props) => {
|
|||||||
className: "contracts_Users",
|
className: "contracts_Users",
|
||||||
objectId: ExtCls[0].objectId
|
objectId: ExtCls[0].objectId
|
||||||
});
|
});
|
||||||
if (extUserData?.TenantId?.ActiveFileAdapter) {
|
|
||||||
object.set("FileAdapterId", extUserData?.TenantId?.ActiveFileAdapter);
|
|
||||||
}
|
|
||||||
const res = await object.save();
|
const res = await object.save();
|
||||||
if (res) {
|
if (res) {
|
||||||
setSigners([]);
|
setSigners([]);
|
||||||
setBcc([]);
|
setBcc([]);
|
||||||
setFolder({ ObjectId: "", Name: "" });
|
setFolder({ ObjectId: "", Name: "" });
|
||||||
const notifySign =
|
const notifySign =
|
||||||
extUserData?.NotifyOnSignatures
|
extUserData?.NotifyOnSignatures !== undefined
|
||||||
? extUserData?.NotifyOnSignatures
|
? extUserData?.NotifyOnSignatures
|
||||||
: true;
|
: true;
|
||||||
setFormData({
|
setFormData({
|
||||||
@@ -478,6 +463,7 @@ const Forms = (props) => {
|
|||||||
AllowModifications: false
|
AllowModifications: false
|
||||||
});
|
});
|
||||||
setFileUpload("");
|
setFileUpload("");
|
||||||
|
setSelectedFiles([]);
|
||||||
setpercentage(0);
|
setpercentage(0);
|
||||||
navigate(`/${props?.redirectRoute}/${res.id}`);
|
navigate(`/${props?.redirectRoute}/${res.id}`);
|
||||||
}
|
}
|
||||||
@@ -534,7 +520,7 @@ const Forms = (props) => {
|
|||||||
setBcc([]);
|
setBcc([]);
|
||||||
setFolder({ ObjectId: "", Name: "" });
|
setFolder({ ObjectId: "", Name: "" });
|
||||||
const notifySign =
|
const notifySign =
|
||||||
extUserData?.NotifyOnSignatures
|
extUserData?.NotifyOnSignatures !== undefined
|
||||||
? extUserData?.NotifyOnSignatures
|
? extUserData?.NotifyOnSignatures
|
||||||
: true;
|
: true;
|
||||||
let obj = {
|
let obj = {
|
||||||
@@ -559,6 +545,7 @@ const Forms = (props) => {
|
|||||||
setFormData(obj);
|
setFormData(obj);
|
||||||
removeFile();
|
removeFile();
|
||||||
setFileUpload("");
|
setFileUpload("");
|
||||||
|
setSelectedFiles([]);
|
||||||
setTimeout(() => setIsReset(false), 50);
|
setTimeout(() => setIsReset(false), 50);
|
||||||
};
|
};
|
||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
@@ -571,24 +558,7 @@ const Forms = (props) => {
|
|||||||
try {
|
try {
|
||||||
const size = formData?.file?.size;
|
const size = formData?.file?.size;
|
||||||
const name = generatePdfName(16);
|
const name = generatePdfName(16);
|
||||||
const url = "https://ai.nxglabs.in/decryptpdf"; //
|
const pdfFile = await decryptPdf(formData?.file, formData?.password);
|
||||||
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"
|
|
||||||
});
|
|
||||||
setIsDecrypting(false);
|
setIsDecrypting(false);
|
||||||
const res = await getFileAsArrayBuffer(pdfFile);
|
const res = await getFileAsArrayBuffer(pdfFile);
|
||||||
const flatPdf = await flattenPdf(res);
|
const flatPdf = await flattenPdf(res);
|
||||||
@@ -749,19 +719,20 @@ const Forms = (props) => {
|
|||||||
)}
|
)}
|
||||||
<div className="text-xs">
|
<div className="text-xs">
|
||||||
<label className="block">
|
<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>
|
<span className="text-red-500 text-[13px]">*</span>
|
||||||
</label>
|
</label>
|
||||||
{fileupload.length > 0 ? (
|
{fileupload.length > 0 ? (
|
||||||
<div className="flex gap-1 justify-center items-center">
|
<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="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">
|
<div className="break-all cursor-default">
|
||||||
{t("file-selected")}: {getFileName(fileupload)}
|
{t("file-selected")}: {selectedFiles.join(", ")}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
onClick={() => setFileUpload("")}
|
onClick={() => {
|
||||||
|
setFileUpload("");
|
||||||
|
setSelectedFiles([]);
|
||||||
|
}}
|
||||||
className="cursor-pointer px-[10px] text-[20px] font-bold text-red-500"
|
className="cursor-pointer px-[10px] text-[20px] font-bold text-red-500"
|
||||||
>
|
>
|
||||||
<i className="fa-light fa-xmark"></i>
|
<i className="fa-light fa-xmark"></i>
|
||||||
@@ -772,12 +743,11 @@ const Forms = (props) => {
|
|||||||
<div className="flex gap-1 justify-center items-center">
|
<div className="flex gap-1 justify-center items-center">
|
||||||
<input
|
<input
|
||||||
type="file"
|
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"
|
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)}
|
onChange={(e) => handleFileInput(e)}
|
||||||
ref={inputFileRef}
|
ref={inputFileRef}
|
||||||
accept={
|
accept="application/pdf,application/vnd.openxmlformats-officedocument.wordprocessingml.document,image/png,image/jpeg"
|
||||||
"application/pdf,image/png,image/jpeg"
|
|
||||||
}
|
|
||||||
onInvalid={(e) =>
|
onInvalid={(e) =>
|
||||||
e.target.setCustomValidity(t("input-required"))
|
e.target.setCustomValidity(t("input-required"))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ function Login() {
|
|||||||
setState({ ...state, loading: false, alertType: type, alertMsg: msg });
|
setState({ ...state, loading: false, alertType: type, alertMsg: msg });
|
||||||
setTimeout(() => setState({ ...state, alertMsg: "" }), 2000);
|
setTimeout(() => setState({ ...state, alertMsg: "" }), 2000);
|
||||||
};
|
};
|
||||||
|
|
||||||
const checkUserExt = async () => {
|
const checkUserExt = async () => {
|
||||||
const app = await getAppLogo();
|
const app = await getAppLogo();
|
||||||
if (app?.error === "invalid_json") {
|
if (app?.error === "invalid_json") {
|
||||||
@@ -82,11 +83,11 @@ function Login() {
|
|||||||
} else {
|
} else {
|
||||||
setImage(appInfo?.applogo || undefined);
|
setImage(appInfo?.applogo || undefined);
|
||||||
}
|
}
|
||||||
|
dispatch(fetchAppInfo());
|
||||||
if (localStorage.getItem("accesstoken")) {
|
if (localStorage.getItem("accesstoken")) {
|
||||||
setState({ ...state, loading: true });
|
setState({ ...state, loading: true });
|
||||||
GetLoginData();
|
GetLoginData();
|
||||||
}
|
}
|
||||||
dispatch(fetchAppInfo());
|
|
||||||
};
|
};
|
||||||
const handleChange = (event) => {
|
const handleChange = (event) => {
|
||||||
let { name, value } = event.target;
|
let { name, value } = event.target;
|
||||||
@@ -96,20 +97,15 @@ function Login() {
|
|||||||
setState({ ...state, [name]: value });
|
setState({ ...state, [name]: value });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = async (event) => {
|
const handleLogin = async (
|
||||||
localStorage.removeItem("accesstoken");
|
) => {
|
||||||
event.preventDefault();
|
const email = state?.email
|
||||||
|
const password = state?.password
|
||||||
|
|
||||||
if (!emailRegex.test(state.email)) {
|
|
||||||
alert("Please enter a valid email address.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { email, password } = state;
|
|
||||||
if (!email || !password) {
|
if (!email || !password) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
localStorage.removeItem("accesstoken");
|
||||||
try {
|
try {
|
||||||
setState({ ...state, loading: true });
|
setState({ ...state, loading: true });
|
||||||
localStorage.setItem("appLogo", appInfo.applogo);
|
localStorage.setItem("appLogo", appInfo.applogo);
|
||||||
@@ -132,6 +128,14 @@ function Login() {
|
|||||||
showToast("danger", "Invalid username/password or region");
|
showToast("danger", "Invalid username/password or region");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
const handleLoginBtn = async (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
if (!emailRegex.test(state.email)) {
|
||||||
|
alert("Please enter a valid email address.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await handleLogin();
|
||||||
|
};
|
||||||
|
|
||||||
const setThirdpartyLoader = (value) => {
|
const setThirdpartyLoader = (value) => {
|
||||||
setState({ ...state, thirdpartyLoader: value });
|
setState({ ...state, thirdpartyLoader: value });
|
||||||
@@ -275,7 +279,6 @@ function Login() {
|
|||||||
const userInformation = JSON.parse(
|
const userInformation = JSON.parse(
|
||||||
localStorage.getItem("UserInformation")
|
localStorage.getItem("UserInformation")
|
||||||
);
|
);
|
||||||
// console.log("payload ", payload);
|
|
||||||
if (payload && payload.sessionToken) {
|
if (payload && payload.sessionToken) {
|
||||||
const params = {
|
const params = {
|
||||||
userDetails: {
|
userDetails: {
|
||||||
@@ -289,7 +292,6 @@ function Login() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
const userSignUp = await Parse.Cloud.run("usersignup", params);
|
const userSignUp = await Parse.Cloud.run("usersignup", params);
|
||||||
// console.log("userSignUp ", userSignUp);
|
|
||||||
if (userSignUp && userSignUp.sessionToken) {
|
if (userSignUp && userSignUp.sessionToken) {
|
||||||
const LocalUserDetails = {
|
const LocalUserDetails = {
|
||||||
name: userInformation.name,
|
name: userInformation.name,
|
||||||
@@ -430,7 +432,7 @@ function Login() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-2">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-2">
|
||||||
<div>
|
<div>
|
||||||
<form onSubmit={handleSubmit} aria-label="Login Form">
|
<form onSubmit={handleLoginBtn} aria-label="Login Form">
|
||||||
<h1 className="text-[30px] mt-6">{t("welcome")}</h1>
|
<h1 className="text-[30px] mt-6">{t("welcome")}</h1>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend className="text-[12px] text-[#878787]">
|
<legend className="text-[12px] text-[#878787]">
|
||||||
@@ -488,15 +490,14 @@ function Login() {
|
|||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="relative mt-1">
|
||||||
<div className="relative mt-1">
|
<NavLink
|
||||||
<NavLink
|
to="/forgetpassword"
|
||||||
to="/forgetpassword"
|
className="text-[13px] op-link op-link-primary underline-offset-1 focus:outline-none ml-1"
|
||||||
className="text-[13px] op-link op-link-primary underline-offset-1 focus:outline-none ml-1"
|
>
|
||||||
>
|
{t("forgot-password")}
|
||||||
{t("forgot-password")}
|
</NavLink>
|
||||||
</NavLink>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-2 text-center text-xs font-bold mt-2">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-2 text-center text-xs font-bold mt-2">
|
||||||
|
|||||||
@@ -324,63 +324,65 @@ const ManageSign = () => {
|
|||||||
hidden
|
hidden
|
||||||
/>
|
/>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<div>
|
{isLoader && (
|
||||||
{image ? (
|
<div className="absolute bg-black bg-opacity-30 z-50 w-full h-full flex justify-center items-center">
|
||||||
<div className="signatureCanvas relative border-[2px] border-[#888] rounded-box overflow-hidden">
|
<Loader />
|
||||||
<img
|
</div>
|
||||||
alt="signature"
|
)}
|
||||||
src={image}
|
{image ? (
|
||||||
className="w-full h-full object-contain"
|
<div className="signatureCanvas relative border-[2px] border-[#888] rounded-box overflow-hidden">
|
||||||
/>
|
<img
|
||||||
</div>
|
alt="signature"
|
||||||
) : (
|
src={image}
|
||||||
<SignatureCanvas
|
className="w-full h-full object-contain"
|
||||||
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}
|
|
||||||
/>
|
/>
|
||||||
)}
|
</div>
|
||||||
<div className="penContainerDefault flex flex-row justify-between">
|
) : (
|
||||||
<div>
|
<SignatureCanvas
|
||||||
{!image && (
|
ref={canvasRef}
|
||||||
<div className="flex flex-row gap-1.5 m-[5px]">
|
penColor={penColor}
|
||||||
{allColor.map((data, key) => {
|
canvasProps={{
|
||||||
return (
|
width: "456px",
|
||||||
<i
|
height: "180px",
|
||||||
key={key}
|
className:
|
||||||
onClick={() => setPenColor(allColor[key])}
|
"signatureCanvas border-[2px] border-[#888] rounded-box"
|
||||||
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>
|
onEnd={() =>
|
||||||
);
|
handleSignatureChange(canvasRef.current.toDataURL())
|
||||||
})}
|
}
|
||||||
</div>
|
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>
|
||||||
<div className="flex flex-row gap-2 text-sm md:text-base mr-1">
|
<div
|
||||||
<div
|
type="button"
|
||||||
type="button"
|
className="op-link"
|
||||||
className="op-link"
|
onClick={() => handleClear()}
|
||||||
onClick={() => handleUploadBtn()}
|
>
|
||||||
>
|
{t("clear")}
|
||||||
{t("upload")}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
type="button"
|
|
||||||
className="op-link"
|
|
||||||
onClick={() => handleClear()}
|
|
||||||
>
|
|
||||||
{t("clear")}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -420,7 +422,6 @@ const ManageSign = () => {
|
|||||||
canvasProps={{
|
canvasProps={{
|
||||||
className: "intialSignature rounded-box"
|
className: "intialSignature rounded-box"
|
||||||
}}
|
}}
|
||||||
// backgroundColor="rgb(255, 255, 255)"
|
|
||||||
onEnd={() => handleInitialsChange()}
|
onEnd={() => handleInitialsChange()}
|
||||||
dotSize={1}
|
dotSize={1}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React, { useEffect, useState, useRef } from "react";
|
import React, { useEffect, useState, useRef } from "react";
|
||||||
import "../styles/opensigndrive.css";
|
import "../styles/opensigndrive.css";
|
||||||
import {
|
import {
|
||||||
iconColor,
|
getThemeIconColor,
|
||||||
} from "../constant/const";
|
} from "../constant/const";
|
||||||
import {
|
import {
|
||||||
getDrive
|
getDrive
|
||||||
@@ -539,6 +539,12 @@ function Opensigndrive() {
|
|||||||
}
|
}
|
||||||
}, 300);
|
}, 300);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSearchPaste = (e) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
handleSearchChange({ target: { value: e.target.value } });
|
||||||
|
}, 0);
|
||||||
|
};
|
||||||
// Cleanup on unmount
|
// Cleanup on unmount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
@@ -657,7 +663,8 @@ function Opensigndrive() {
|
|||||||
type="search"
|
type="search"
|
||||||
value={searchTerm}
|
value={searchTerm}
|
||||||
onChange={handleSearchChange}
|
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"
|
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-64 text-xs"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -668,7 +675,7 @@ function Opensigndrive() {
|
|||||||
onClick={() => setMobileSearchOpen((open) => !open)}
|
onClick={() => setMobileSearchOpen((open) => !open)}
|
||||||
>
|
>
|
||||||
<i
|
<i
|
||||||
style={{ color: `${iconColor}` }}
|
style={{ color: `${getThemeIconColor()}` }}
|
||||||
className="fa-solid fa-magnifying-glass"
|
className="fa-solid fa-magnifying-glass"
|
||||||
></i>
|
></i>
|
||||||
</button>
|
</button>
|
||||||
@@ -681,7 +688,7 @@ function Opensigndrive() {
|
|||||||
<i
|
<i
|
||||||
className="fa-light fa-plus-square text-[24px]"
|
className="fa-light fa-plus-square text-[24px]"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
style={{ color: `${iconColor}` }}
|
style={{ color: `${getThemeIconColor()}` }}
|
||||||
></i>
|
></i>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -730,9 +737,9 @@ function Opensigndrive() {
|
|||||||
<i
|
<i
|
||||||
className="fa-light fa-sort-amount-asc mr-[5px] text-[19px]"
|
className="fa-light fa-sort-amount-asc mr-[5px] text-[19px]"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
style={{ color: `${iconColor}` }}
|
style={{ color: `${getThemeIconColor()}` }}
|
||||||
></i>
|
></i>
|
||||||
<span style={{ fontSize: "15px", color: `${iconColor}` }}>
|
<span style={{ fontSize: "15px", color: `${getThemeIconColor()}` }}>
|
||||||
{selectedSort}
|
{selectedSort}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -793,7 +800,7 @@ function Opensigndrive() {
|
|||||||
>
|
>
|
||||||
<i
|
<i
|
||||||
className={`${isList ? "fa-light fa-th-large" : "fa-light fa-list"} text-[20px]`}
|
className={`${isList ? "fa-light fa-th-large" : "fa-light fa-list"} text-[20px]`}
|
||||||
style={{ color: `${iconColor}` }}
|
style={{ color: `${getThemeIconColor()}` }}
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
></i>
|
></i>
|
||||||
</div>
|
</div>
|
||||||
@@ -809,7 +816,7 @@ function Opensigndrive() {
|
|||||||
<i
|
<i
|
||||||
className="fa-light fa-ellipsis-vertical fa-lg"
|
className="fa-light fa-ellipsis-vertical fa-lg"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
style={{ color: `${iconColor}` }}
|
style={{ color: `${getThemeIconColor()}` }}
|
||||||
></i>
|
></i>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -854,7 +861,8 @@ function Opensigndrive() {
|
|||||||
type="search"
|
type="search"
|
||||||
value={searchTerm}
|
value={searchTerm}
|
||||||
onChange={handleSearchChange}
|
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"
|
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useRef, useEffect } from "react";
|
import { useState, useRef, useEffect } from "react";
|
||||||
import { PDFDocument } from "pdf-lib";
|
import { PDFDocument } from "pdf-lib";
|
||||||
import "../styles/signature.css";
|
import "../styles/signature.css";
|
||||||
import Parse from "parse";
|
import Parse from "parse";
|
||||||
@@ -23,7 +23,6 @@ import {
|
|||||||
pdfNewWidthFun,
|
pdfNewWidthFun,
|
||||||
signPdfFun,
|
signPdfFun,
|
||||||
addDefaultSignatureImg,
|
addDefaultSignatureImg,
|
||||||
radioButtonWidget,
|
|
||||||
replaceMailVaribles,
|
replaceMailVaribles,
|
||||||
convertPdfArrayBuffer,
|
convertPdfArrayBuffer,
|
||||||
contractUsers,
|
contractUsers,
|
||||||
@@ -46,7 +45,9 @@ import {
|
|||||||
textWidget,
|
textWidget,
|
||||||
mailTemplate,
|
mailTemplate,
|
||||||
updateDateWidgetsRes,
|
updateDateWidgetsRes,
|
||||||
widgetDataValue
|
widgetDataValue,
|
||||||
|
getOriginalWH,
|
||||||
|
handleCheckResponse,
|
||||||
} from "../constant/Utils";
|
} from "../constant/Utils";
|
||||||
import Header from "../components/pdf/PdfHeader";
|
import Header from "../components/pdf/PdfHeader";
|
||||||
import RenderPdf from "../components/pdf/RenderPdf";
|
import RenderPdf from "../components/pdf/RenderPdf";
|
||||||
@@ -276,6 +277,15 @@ function PdfRequestFiles(
|
|||||||
let currUserId;
|
let currUserId;
|
||||||
//getting document details
|
//getting document details
|
||||||
const documentData = await contractDocument(docId);
|
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) {
|
if (documentData && documentData.length > 0) {
|
||||||
const userSignatureType =
|
const userSignatureType =
|
||||||
documentData[0]?.ExtUserPtr?.SignatureType || signatureTypes;
|
documentData[0]?.ExtUserPtr?.SignatureType || signatureTypes;
|
||||||
@@ -623,6 +633,7 @@ function PdfRequestFiles(
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log("err in get email verification ", err);
|
console.log("err in get email verification ", err);
|
||||||
setHandleError(t("something-went-wrong-mssg"));
|
setHandleError(t("something-went-wrong-mssg"));
|
||||||
|
setIsUiLoading(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//check if isEmailVerified then go on next step
|
//check if isEmailVerified then go on next step
|
||||||
@@ -632,153 +643,13 @@ function PdfRequestFiles(
|
|||||||
(data) => data.signerObjId === signerObjectId
|
(data) => data.signerObjId === signerObjectId
|
||||||
);
|
);
|
||||||
if (checkUser && checkUser.length > 0) {
|
if (checkUser && checkUser.length > 0) {
|
||||||
let checkboxExist,
|
const status = handleCheckResponse(checkUser,setminRequiredCount)
|
||||||
requiredRadio,
|
if (status?.showAlert) {
|
||||||
showAlert = false,
|
setUnSignedWidgetId(status?.widgetKey);
|
||||||
widgetKey,
|
setPageNumber(status?.tourPageNumber);
|
||||||
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);
|
|
||||||
setWidgetsTour(true);
|
setWidgetsTour(true);
|
||||||
|
setIsUiLoading(false);
|
||||||
} else {
|
} else {
|
||||||
setIsUiLoading(true);
|
|
||||||
// `widgets` is Used to return widgets details with page number of current user
|
// `widgets` is Used to return widgets details with page number of current user
|
||||||
const widgets = checkUser?.[0]?.placeHolder;
|
const widgets = checkUser?.[0]?.placeHolder;
|
||||||
let pdfArrBuffer;
|
let pdfArrBuffer;
|
||||||
@@ -816,17 +687,18 @@ function PdfRequestFiles(
|
|||||||
//embed document's object id to all pages in pdf document
|
//embed document's object id to all pages in pdf document
|
||||||
if (!HeaderDocId) {
|
if (!HeaderDocId) {
|
||||||
if (!isDocId) {
|
if (!isDocId) {
|
||||||
await embedDocId(pdfDoc, docId, allPages);
|
//pdfOriginalWH contained all pdf's pages width,height & pagenumber in array format
|
||||||
|
await embedDocId(pdfOriginalWH, pdfDoc, docId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//embed multi signature in pdf
|
//embed all widgets in document
|
||||||
const pdfBytes = await multiSignEmbed(
|
const pdfBytes = await multiSignEmbed(
|
||||||
|
pdfOriginalWH,
|
||||||
widgets,
|
widgets,
|
||||||
pdfDoc,
|
pdfDoc,
|
||||||
isSignYourSelfFlow,
|
isSignYourSelfFlow,
|
||||||
scale
|
scale
|
||||||
);
|
);
|
||||||
// console.log("pdfte", pdfBytes);
|
|
||||||
//get ExistUserPtr object id of user class to get tenantDetails
|
//get ExistUserPtr object id of user class to get tenantDetails
|
||||||
if (!pdfBytes?.error) {
|
if (!pdfBytes?.error) {
|
||||||
const objectId = pdfDetails?.[0]?.ExtUserPtr?.UserId?.objectId;
|
const objectId = pdfDetails?.[0]?.ExtUserPtr?.UserId?.objectId;
|
||||||
@@ -852,17 +724,23 @@ function PdfRequestFiles(
|
|||||||
isSuccessRoute,
|
isSuccessRoute,
|
||||||
contactId
|
contactId
|
||||||
);
|
);
|
||||||
const index = pdfDetails?.[0]?.Signers.findIndex(
|
const index =
|
||||||
(x) => x.objectId === signerObjectId
|
updatedDoc.updatedPdfDetails?.[0]?.Signers.findIndex(
|
||||||
);
|
(x) => x.objectId === contactId
|
||||||
|
);
|
||||||
const newIndex = index + 1;
|
const newIndex = index + 1;
|
||||||
const usermail = {
|
const usermail = {
|
||||||
Email: pdfDetails?.[0]?.Placeholders[newIndex]?.email || ""
|
Email:
|
||||||
|
updatedDoc.updatedPdfDetails?.[0]?.Placeholders[newIndex]
|
||||||
|
?.email || ""
|
||||||
};
|
};
|
||||||
const user = usermail?.Email
|
const user = usermail?.Email
|
||||||
? usermail
|
? usermail
|
||||||
: pdfDetails?.[0]?.Signers[newIndex];
|
: updatedDoc.updatedPdfDetails?.[0]?.Signers[newIndex];
|
||||||
if (sendmail !== "false" && sendInOrder) {
|
if (
|
||||||
|
sendmail !== "false" &&
|
||||||
|
sendInOrder
|
||||||
|
) {
|
||||||
const requestBody =
|
const requestBody =
|
||||||
updatedDoc.updatedPdfDetails?.[0]?.RequestBody;
|
updatedDoc.updatedPdfDetails?.[0]?.RequestBody;
|
||||||
const requestSubject =
|
const requestSubject =
|
||||||
@@ -916,7 +794,7 @@ function PdfRequestFiles(
|
|||||||
const htmlReqBody =
|
const htmlReqBody =
|
||||||
"<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body>" +
|
"<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body>" +
|
||||||
replacedRequestBody +
|
replacedRequestBody +
|
||||||
"</body> </html>";
|
"</body></html>";
|
||||||
|
|
||||||
const variables = {
|
const variables = {
|
||||||
document_title: documentName,
|
document_title: documentName,
|
||||||
@@ -929,7 +807,7 @@ function PdfRequestFiles(
|
|||||||
receiver_phone: user?.Phone || "",
|
receiver_phone: user?.Phone || "",
|
||||||
expiry_date: localExpireDate,
|
expiry_date: localExpireDate,
|
||||||
company_name: orgName,
|
company_name: orgName,
|
||||||
signing_url: `<a href=${signPdf} target=_blank>Sign here</a>`
|
signing_url: signPdf
|
||||||
};
|
};
|
||||||
replaceVar = replaceMailVaribles(
|
replaceVar = replaceMailVaribles(
|
||||||
requestSubject,
|
requestSubject,
|
||||||
@@ -944,7 +822,7 @@ function PdfRequestFiles(
|
|||||||
title: documentName,
|
title: documentName,
|
||||||
organization: orgName,
|
organization: orgName,
|
||||||
localExpireDate: localExpireDate,
|
localExpireDate: localExpireDate,
|
||||||
sigingUrl: signPdf
|
signingUrl: signPdf
|
||||||
};
|
};
|
||||||
let params = {
|
let params = {
|
||||||
replyto: senderEmail || "",
|
replyto: senderEmail || "",
|
||||||
@@ -1017,6 +895,7 @@ function PdfRequestFiles(
|
|||||||
isShow: true,
|
isShow: true,
|
||||||
alertMessage: t("something-went-wrong-mssg")
|
alertMessage: t("something-went-wrong-mssg")
|
||||||
});
|
});
|
||||||
|
setIsUiLoading(false);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log("err in embedsign", err);
|
console.log("err in embedsign", err);
|
||||||
@@ -1028,8 +907,8 @@ function PdfRequestFiles(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSignPdf = async () => {
|
const handleSignPdf = async () => {
|
||||||
|
setIsUiLoading(true);
|
||||||
await embedWidgetsData();
|
await embedWidgetsData();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1053,15 +932,15 @@ function PdfRequestFiles(
|
|||||||
let filterSignerPos = [];
|
let filterSignerPos = [];
|
||||||
if (signerObjId) {
|
if (signerObjId) {
|
||||||
//get current signerObjId placeholder details
|
//get current signerObjId placeholder details
|
||||||
filterSignerPos = updateSignPos.filter(
|
filterSignerPos = updateSignPos?.filter(
|
||||||
(data) => data.Id === signerObjId
|
(data) => data.Id === signerObjId
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filterSignerPos.length > 0) {
|
if (filterSignerPos.length > 0) {
|
||||||
const getPlaceHolder = filterSignerPos[0].placeHolder;
|
const getPlaceHolder = filterSignerPos[0]?.placeHolder;
|
||||||
//get position of current pagenumber
|
//get position of current pagenumber
|
||||||
const getPageNumer = getPlaceHolder.filter(
|
const getPageNumer = getPlaceHolder?.filter(
|
||||||
(data) => data.pageNumber === pageNumber
|
(data) => data.pageNumber === pageNumber
|
||||||
);
|
);
|
||||||
if (getPageNumer.length > 0) {
|
if (getPageNumer.length > 0) {
|
||||||
@@ -1105,9 +984,9 @@ function PdfRequestFiles(
|
|||||||
setIsTextSetting(value);
|
setIsTextSetting(value);
|
||||||
};
|
};
|
||||||
const handleSaveFontSize = () => {
|
const handleSaveFontSize = () => {
|
||||||
const filterSignerPos = signerPos.filter((data) => data.Id === uniqueId);
|
const filterSignerPos = signerPos?.filter((data) => data.Id === uniqueId);
|
||||||
if (filterSignerPos) {
|
if (filterSignerPos) {
|
||||||
const placehoder = filterSignerPos[0].placeHolder;
|
const placehoder = filterSignerPos[0]?.placeHolder;
|
||||||
const getPageNumer = placehoder.filter(
|
const getPageNumer = placehoder.filter(
|
||||||
(data) => data.pageNumber === pageNumber
|
(data) => data.pageNumber === pageNumber
|
||||||
);
|
);
|
||||||
@@ -1169,14 +1048,7 @@ function PdfRequestFiles(
|
|||||||
];
|
];
|
||||||
//function for get pdf page details
|
//function for get pdf page details
|
||||||
const pageDetails = async (pdf) => {
|
const pageDetails = async (pdf) => {
|
||||||
let pdfWHObj = [];
|
const pdfWHObj = await getOriginalWH(pdf);
|
||||||
const totalPages = pdf.numPages; // Get the total number of pages
|
|
||||||
for (let index = 0; index < totalPages; index++) {
|
|
||||||
const getPage = await pdf.getPage(index + 1);
|
|
||||||
const scale = 1;
|
|
||||||
const { width, height } = getPage.getViewport({ scale });
|
|
||||||
pdfWHObj.push({ pageNumber: index + 1, width, height });
|
|
||||||
}
|
|
||||||
setPdfOriginalWH(pdfWHObj);
|
setPdfOriginalWH(pdfWHObj);
|
||||||
setPdfLoad(true);
|
setPdfLoad(true);
|
||||||
};
|
};
|
||||||
@@ -1230,7 +1102,7 @@ function PdfRequestFiles(
|
|||||||
const addDefaultSignature = () => {
|
const addDefaultSignature = () => {
|
||||||
const type = defaultSignAlert?.type;
|
const type = defaultSignAlert?.type;
|
||||||
//get current signers placeholder position data
|
//get current signers placeholder position data
|
||||||
const currentSignerPosition = signerPos.filter(
|
const currentSignerPosition = signerPos?.filter(
|
||||||
(data) => data.signerObjId === signerObjectId
|
(data) => data.signerObjId === signerObjectId
|
||||||
);
|
);
|
||||||
const defaultSign = type === "signature" ? defaultSignImg : myInitial;
|
const defaultSign = type === "signature" ? defaultSignImg : myInitial;
|
||||||
@@ -1255,7 +1127,8 @@ function PdfRequestFiles(
|
|||||||
setRequestSignTour(true);
|
setRequestSignTour(true);
|
||||||
if (isDontShow) {
|
if (isDontShow) {
|
||||||
const isEnableOTP = pdfDetails?.[0]?.IsEnableOTP || false;
|
const isEnableOTP = pdfDetails?.[0]?.IsEnableOTP || false;
|
||||||
if (!isEnableOTP) {
|
const sessionToken = localStorage.getItem("accesstoken");
|
||||||
|
if (!isEnableOTP && !sessionToken) {
|
||||||
try {
|
try {
|
||||||
await axios.post(
|
await axios.post(
|
||||||
`${localStorage.getItem("baseUrl")}functions/updatecontacttour`,
|
`${localStorage.getItem("baseUrl")}functions/updatecontacttour`,
|
||||||
@@ -1568,11 +1441,15 @@ function PdfRequestFiles(
|
|||||||
const widgetValue = widgetDataValue(dragTypeValue, parseUser);
|
const widgetValue = widgetDataValue(dragTypeValue, parseUser);
|
||||||
//adding and updating drop position in array when user drop signature button in div
|
//adding and updating drop position in array when user drop signature button in div
|
||||||
if (item === "onclick") {
|
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;
|
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 = {
|
dropObj = {
|
||||||
//onclick put placeholder center on pdf
|
//onclick put placeholder center on pdf
|
||||||
xPosition: widgetWidth / 4 + containerWH.width / 2,
|
xPosition: xPosition_Final,
|
||||||
yPosition: widgetHeight + divHeight / 2,
|
yPosition: widgetHeight + divHeight / 2,
|
||||||
isStamp:
|
isStamp:
|
||||||
(dragTypeValue === "stamp" || dragTypeValue === "image") && true,
|
(dragTypeValue === "stamp" || dragTypeValue === "image") && true,
|
||||||
@@ -1616,11 +1493,11 @@ function PdfRequestFiles(
|
|||||||
}
|
}
|
||||||
if (uniqueId) {
|
if (uniqueId) {
|
||||||
let filterSignerPos, currentPagePosition;
|
let filterSignerPos, currentPagePosition;
|
||||||
filterSignerPos = signerPos.find((data) => data.Id === uniqueId);
|
filterSignerPos = signerPos?.find((data) => data.Id === uniqueId);
|
||||||
const getPlaceHolder = filterSignerPos?.placeHolder;
|
const getPlaceHolder = filterSignerPos?.placeHolder;
|
||||||
if (getPlaceHolder) {
|
if (getPlaceHolder) {
|
||||||
//checking exist placeholder on same page
|
//checking exist placeholder on same page
|
||||||
currentPagePosition = getPlaceHolder.find(
|
currentPagePosition = getPlaceHolder?.find(
|
||||||
(data) => data.pageNumber === pageNumber
|
(data) => data.pageNumber === pageNumber
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1648,12 +1525,6 @@ function PdfRequestFiles(
|
|||||||
);
|
);
|
||||||
setSignerPos(updatesignerPos);
|
setSignerPos(updatesignerPos);
|
||||||
}
|
}
|
||||||
|
|
||||||
// if (dragTypeValue === "dropdown") {
|
|
||||||
// setShowDropdown(true);
|
|
||||||
// } else if (dragTypeValue === "checkbox") {
|
|
||||||
// setIsCheckbox(true);
|
|
||||||
// } else
|
|
||||||
if (
|
if (
|
||||||
[textWidget, "name", "company", "job title", "email"].includes(
|
[textWidget, "name", "company", "job title", "email"].includes(
|
||||||
dragTypeValue
|
dragTypeValue
|
||||||
@@ -1669,7 +1540,7 @@ function PdfRequestFiles(
|
|||||||
//function for delete signature block
|
//function for delete signature block
|
||||||
const handleDeleteSign = (key, Id) => {
|
const handleDeleteSign = (key, Id) => {
|
||||||
const updateData = [];
|
const updateData = [];
|
||||||
const filterSignerPos = signerPos.filter((data) => data.Id === Id);
|
const filterSignerPos = signerPos?.filter((data) => data.Id === Id);
|
||||||
if (filterSignerPos.length > 0) {
|
if (filterSignerPos.length > 0) {
|
||||||
const getPlaceHolder = filterSignerPos[0].placeHolder;
|
const getPlaceHolder = filterSignerPos[0].placeHolder;
|
||||||
const getPageNumer = getPlaceHolder.filter(
|
const getPageNumer = getPlaceHolder.filter(
|
||||||
@@ -1697,7 +1568,7 @@ function PdfRequestFiles(
|
|||||||
});
|
});
|
||||||
setSignerPos(newUpdateSigner);
|
setSignerPos(newUpdateSigner);
|
||||||
} else {
|
} else {
|
||||||
const getRemainPage = filterSignerPos[0].placeHolder.filter(
|
const getRemainPage = filterSignerPos[0]?.placeHolder?.filter(
|
||||||
(data) => data.pageNumber !== pageNumber
|
(data) => data.pageNumber !== pageNumber
|
||||||
);
|
);
|
||||||
//condition to check placeholder length is greater than 1 do not need to remove whole placeholder
|
//condition to check placeholder length is greater than 1 do not need to remove whole placeholder
|
||||||
@@ -1710,11 +1581,11 @@ function PdfRequestFiles(
|
|||||||
return obj;
|
return obj;
|
||||||
});
|
});
|
||||||
let signerupdate = [];
|
let signerupdate = [];
|
||||||
signerupdate = signerPos.filter((data) => data.Id !== Id);
|
signerupdate = signerPos?.filter((data) => data.Id !== Id);
|
||||||
signerupdate.push(newUpdatePos[0]);
|
signerupdate.push(newUpdatePos[0]);
|
||||||
setSignerPos(signerupdate);
|
setSignerPos(signerupdate);
|
||||||
} else {
|
} else {
|
||||||
const updatedData = signerPos.map((item) => {
|
const updatedData = signerPos?.map((item) => {
|
||||||
if (item.Id === Id) {
|
if (item.Id === Id) {
|
||||||
// Create a copy of the item object and delete the placeHolder field
|
// Create a copy of the item object and delete the placeHolder field
|
||||||
const updatedItem = { ...item };
|
const updatedItem = { ...item };
|
||||||
@@ -1732,7 +1603,7 @@ function PdfRequestFiles(
|
|||||||
//function to get first widget and page number to assign currect signer and tour message
|
//function to get first widget and page number to assign currect signer and tour message
|
||||||
const showFirstWidget = () => {
|
const showFirstWidget = () => {
|
||||||
if (!requestSignTour) {
|
if (!requestSignTour) {
|
||||||
const getCurrentUserPlaceholder = signerPos.find(
|
const getCurrentUserPlaceholder = signerPos?.find(
|
||||||
(x) => x.Id === uniqueId
|
(x) => x.Id === uniqueId
|
||||||
);
|
);
|
||||||
const placeholder = getCurrentUserPlaceholder.placeHolder;
|
const placeholder = getCurrentUserPlaceholder.placeHolder;
|
||||||
@@ -1753,7 +1624,6 @@ function PdfRequestFiles(
|
|||||||
setShowSignPagenumber(sortedPagenumber);
|
setShowSignPagenumber(sortedPagenumber);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DndProvider backend={HTML5Backend}>
|
<DndProvider backend={HTML5Backend}>
|
||||||
<Title
|
<Title
|
||||||
@@ -1825,6 +1695,7 @@ function PdfRequestFiles(
|
|||||||
{!requestSignTour &&
|
{!requestSignTour &&
|
||||||
isAgree &&
|
isAgree &&
|
||||||
signerObjectId &&
|
signerObjectId &&
|
||||||
|
!alreadySign &&
|
||||||
requestSignTourFunction()}
|
requestSignTourFunction()}
|
||||||
<Tour
|
<Tour
|
||||||
showNumber={false}
|
showNumber={false}
|
||||||
@@ -2263,6 +2134,8 @@ function PdfRequestFiles(
|
|||||||
index={pageNumber}
|
index={pageNumber}
|
||||||
setUniqueId={setUniqueId}
|
setUniqueId={setUniqueId}
|
||||||
tempSignerId={tempSignerId}
|
tempSignerId={tempSignerId}
|
||||||
|
signatureTypes={signatureType}
|
||||||
|
allowCellResize={pdfDetails[0]?.AllowModifications ?? false}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<DownloadPdfZip
|
<DownloadPdfZip
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import {
|
|||||||
multiSignEmbed,
|
multiSignEmbed,
|
||||||
addWidgetOptions,
|
addWidgetOptions,
|
||||||
textInputWidget,
|
textInputWidget,
|
||||||
|
cellsWidget,
|
||||||
textWidget,
|
textWidget,
|
||||||
radioButtonWidget,
|
radioButtonWidget,
|
||||||
color,
|
color,
|
||||||
@@ -44,7 +45,8 @@ import {
|
|||||||
handleSignatureType,
|
handleSignatureType,
|
||||||
getBase64FromUrl,
|
getBase64FromUrl,
|
||||||
generatePdfName,
|
generatePdfName,
|
||||||
mailTemplate
|
mailTemplate,
|
||||||
|
getOriginalWH
|
||||||
} from "../constant/Utils";
|
} from "../constant/Utils";
|
||||||
import RenderPdf from "../components/pdf/RenderPdf";
|
import RenderPdf from "../components/pdf/RenderPdf";
|
||||||
import { useNavigate } from "react-router";
|
import { useNavigate } from "react-router";
|
||||||
@@ -192,7 +194,7 @@ function PlaceHolderSign() {
|
|||||||
);
|
);
|
||||||
if (user) {
|
if (user) {
|
||||||
try {
|
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}}`;
|
const defaultSubject = `{{sender_name}} has requested you to sign {{document_title}}`;
|
||||||
setDefaultBody(defaultRequestBody);
|
setDefaultBody(defaultRequestBody);
|
||||||
setDefaultSubject(defaultSubject);
|
setDefaultSubject(defaultSubject);
|
||||||
@@ -523,11 +525,15 @@ function PlaceHolderSign() {
|
|||||||
defaultWidthHeight(dragTypeValue).height * containerScale;
|
defaultWidthHeight(dragTypeValue).height * containerScale;
|
||||||
//adding and updating drop position in array when user drop signature button in div
|
//adding and updating drop position in array when user drop signature button in div
|
||||||
if (item === "onclick") {
|
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;
|
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 = {
|
dropObj = {
|
||||||
//onclick put placeholder center on pdf
|
//onclick put placeholder center on pdf
|
||||||
xPosition: widgetWidth / 4 + containerWH.width / 2,
|
xPosition: xPosition_Final,
|
||||||
yPosition: widgetHeight + divHeight / 2,
|
yPosition: widgetHeight + divHeight / 2,
|
||||||
isStamp:
|
isStamp:
|
||||||
(dragTypeValue === "stamp" || dragTypeValue === "image") && true,
|
(dragTypeValue === "stamp" || dragTypeValue === "image") && true,
|
||||||
@@ -671,14 +677,7 @@ function PlaceHolderSign() {
|
|||||||
|
|
||||||
//function for get pdf page details
|
//function for get pdf page details
|
||||||
const pageDetails = async (pdf) => {
|
const pageDetails = async (pdf) => {
|
||||||
let pdfWHObj = [];
|
const pdfWHObj = await getOriginalWH(pdf);
|
||||||
const totalPages = pdf?.numPages;
|
|
||||||
for (let index = 0; index < totalPages; index++) {
|
|
||||||
const getPage = await pdf.getPage(index + 1);
|
|
||||||
const scale = 1;
|
|
||||||
const { width, height } = getPage.getViewport({ scale });
|
|
||||||
pdfWHObj.push({ pageNumber: index + 1, width, height });
|
|
||||||
}
|
|
||||||
setPdfOriginalWH(pdfWHObj);
|
setPdfOriginalWH(pdfWHObj);
|
||||||
setPdfLoad(true);
|
setPdfLoad(true);
|
||||||
};
|
};
|
||||||
@@ -862,7 +861,9 @@ function PlaceHolderSign() {
|
|||||||
});
|
});
|
||||||
const isSignYourSelfFlow = false;
|
const isSignYourSelfFlow = false;
|
||||||
try {
|
try {
|
||||||
|
//pdfOriginalWH contained all pdf's pages width,height & pagenumber in array format
|
||||||
const pdfBase64 = await multiSignEmbed(
|
const pdfBase64 = await multiSignEmbed(
|
||||||
|
pdfOriginalWH,
|
||||||
placeholder,
|
placeholder,
|
||||||
pdfDoc,
|
pdfDoc,
|
||||||
isSignYourSelfFlow,
|
isSignYourSelfFlow,
|
||||||
@@ -1220,7 +1221,7 @@ function PlaceHolderSign() {
|
|||||||
receiver_phone: signerMail[i]?.Phone || "",
|
receiver_phone: signerMail[i]?.Phone || "",
|
||||||
expiry_date: localExpireDate,
|
expiry_date: localExpireDate,
|
||||||
company_name: orgName,
|
company_name: orgName,
|
||||||
signing_url: `<a href=${signPdf} target=_blank>Sign here</a>`
|
signing_url: signPdf
|
||||||
};
|
};
|
||||||
replaceVar = replaceMailVaribles(
|
replaceVar = replaceMailVaribles(
|
||||||
requestSubject,
|
requestSubject,
|
||||||
@@ -1249,7 +1250,7 @@ function PlaceHolderSign() {
|
|||||||
receiver_phone: signerMail[i]?.Phone || "",
|
receiver_phone: signerMail[i]?.Phone || "",
|
||||||
expiry_date: localExpireDate,
|
expiry_date: localExpireDate,
|
||||||
company_name: orgName,
|
company_name: orgName,
|
||||||
signing_url: `<a href=${signPdf} target=_blank>Sign here</a>`
|
signing_url: signPdf
|
||||||
};
|
};
|
||||||
replaceVar = replaceMailVaribles(mailSubject, htmlReqBody, variables);
|
replaceVar = replaceMailVaribles(mailSubject, htmlReqBody, variables);
|
||||||
}
|
}
|
||||||
@@ -1260,7 +1261,7 @@ function PlaceHolderSign() {
|
|||||||
title: documentName,
|
title: documentName,
|
||||||
organization: orgName,
|
organization: orgName,
|
||||||
localExpireDate: localExpireDate,
|
localExpireDate: localExpireDate,
|
||||||
sigingUrl: signPdf
|
signingUrl: signPdf
|
||||||
};
|
};
|
||||||
let params = {
|
let params = {
|
||||||
extUserId: owner?.objectId,
|
extUserId: owner?.objectId,
|
||||||
@@ -1417,7 +1418,8 @@ function PlaceHolderSign() {
|
|||||||
deleteOption,
|
deleteOption,
|
||||||
status,
|
status,
|
||||||
defaultValue,
|
defaultValue,
|
||||||
isHideLabel
|
isHideLabel,
|
||||||
|
layout
|
||||||
) => {
|
) => {
|
||||||
const filterSignerPos = signerPos.filter((data) => data.Id === uniqueId);
|
const filterSignerPos = signerPos.filter((data) => data.Id === uniqueId);
|
||||||
if (filterSignerPos.length > 0) {
|
if (filterSignerPos.length > 0) {
|
||||||
@@ -1452,6 +1454,8 @@ function PlaceHolderSign() {
|
|||||||
...position.options,
|
...position.options,
|
||||||
name: dropdownName,
|
name: dropdownName,
|
||||||
values: dropdownOptions,
|
values: dropdownOptions,
|
||||||
|
status: status,
|
||||||
|
layout: layout,
|
||||||
isReadOnly: isReadOnly || false,
|
isReadOnly: isReadOnly || false,
|
||||||
isHideLabel: isHideLabel || false,
|
isHideLabel: isHideLabel || false,
|
||||||
defaultValue: defaultValue,
|
defaultValue: defaultValue,
|
||||||
@@ -1491,6 +1495,7 @@ function PlaceHolderSign() {
|
|||||||
maxRequiredCount: maxCount
|
maxRequiredCount: maxCount
|
||||||
},
|
},
|
||||||
defaultValue: defaultValue,
|
defaultValue: defaultValue,
|
||||||
|
layout: layout,
|
||||||
isReadOnly: isReadOnly || false,
|
isReadOnly: isReadOnly || false,
|
||||||
isHideLabel: isHideLabel || false,
|
isHideLabel: isHideLabel || false,
|
||||||
fontSize:
|
fontSize:
|
||||||
@@ -1589,6 +1594,30 @@ function PlaceHolderSign() {
|
|||||||
isReadOnly: defaultdata?.isReadOnly || false
|
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)) {
|
} else if (["signature"].includes(position.type)) {
|
||||||
return {
|
return {
|
||||||
...position,
|
...position,
|
||||||
@@ -2149,7 +2178,7 @@ function PlaceHolderSign() {
|
|||||||
navigate("/report/1MwEuxLEkF");
|
navigate("/report/1MwEuxLEkF");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="h-[100%] p-[20px]">
|
<div className="h-[100%] p-[20px] text-base-content">
|
||||||
{mailStatus === "success" ? (
|
{mailStatus === "success" ? (
|
||||||
<div className="text-center mb-[10px]">
|
<div className="text-center mb-[10px]">
|
||||||
<LottieWithLoader />
|
<LottieWithLoader />
|
||||||
@@ -2593,6 +2622,7 @@ function PlaceHolderSign() {
|
|||||||
isSave={true}
|
isSave={true}
|
||||||
tempSignerId={tempSignerId}
|
tempSignerId={tempSignerId}
|
||||||
setUniqueId={setUniqueId}
|
setUniqueId={setUniqueId}
|
||||||
|
signatureTypes={signatureType}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<ModalUi
|
<ModalUi
|
||||||
@@ -2600,7 +2630,7 @@ function PlaceHolderSign() {
|
|||||||
title={t("document-alert")}
|
title={t("document-alert")}
|
||||||
showClose={false}
|
showClose={false}
|
||||||
>
|
>
|
||||||
<div className="h-[100%] p-[20px]">
|
<div className="h-[100%] p-[20px] text-base-content">
|
||||||
<p>{isAlreadyPlace.message}</p>
|
<p>{isAlreadyPlace.message}</p>
|
||||||
<div className="h-[1px] w-full my-[15px] bg-[#9f9f9f]"></div>
|
<div className="h-[1px] w-full my-[15px] bg-[#9f9f9f]"></div>
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -218,7 +218,7 @@ const Preferences = () => {
|
|||||||
setIsLoader(true);
|
setIsLoader(true);
|
||||||
const updateRes = tenantRes;
|
const updateRes = tenantRes;
|
||||||
setTenantId(updateRes?.objectId);
|
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) {
|
if (updateRes?.RequestBody) {
|
||||||
setRequestBody(updateRes?.RequestBody);
|
setRequestBody(updateRes?.RequestBody);
|
||||||
setRequestSubject(updateRes?.RequestSubject);
|
setRequestSubject(updateRes?.RequestSubject);
|
||||||
@@ -301,7 +301,7 @@ const Preferences = () => {
|
|||||||
JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
|
JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
|
||||||
if (extUser && extUser?.objectId) {
|
if (extUser && extUser?.objectId) {
|
||||||
extUser.TenantId.RequestBody = updateRes?.RequestBody;
|
extUser.TenantId.RequestBody = updateRes?.RequestBody;
|
||||||
extUser.TenantId.RequestBody = updateRes?.RequestSubject;
|
extUser.TenantId.RequestSubject = updateRes?.RequestSubject;
|
||||||
const _extUser = JSON.parse(JSON.stringify(extUser));
|
const _extUser = JSON.parse(JSON.stringify(extUser));
|
||||||
localStorage.setItem("Extand_Class", JSON.stringify([_extUser]));
|
localStorage.setItem("Extand_Class", JSON.stringify([_extUser]));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState, useRef } from "react";
|
||||||
import ReportTable from "../primitives/GetReportDisplay";
|
import ReportTable from "../primitives/GetReportDisplay";
|
||||||
import Parse from "parse";
|
import Parse from "parse";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
@@ -27,12 +27,18 @@ const Report = () => {
|
|||||||
const [isImport, setIsImport] = useState(false);
|
const [isImport, setIsImport] = useState(false);
|
||||||
const abortController = new AbortController();
|
const abortController = new AbortController();
|
||||||
const docPerPage = 10;
|
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
|
// below useEffect is call when id param change
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setReportName("");
|
setReportName("");
|
||||||
setList([]);
|
setList([]);
|
||||||
getReportData();
|
setSearchTerm("");
|
||||||
|
setMobileSearchOpen(false);
|
||||||
|
getReportData(0, docPerPage, "");
|
||||||
|
|
||||||
// Function returned from useEffect is called on unmount
|
// Function returned from useEffect is called on unmount
|
||||||
return () => {
|
return () => {
|
||||||
@@ -48,7 +54,7 @@ const Report = () => {
|
|||||||
// below useEffect call when isNextRecord state is true and fetch next record
|
// below useEffect call when isNextRecord state is true and fetch next record
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isNextRecord) {
|
if (isNextRecord) {
|
||||||
getReportData(List.length, 20);
|
getReportData(List.length, 20, searchTerm);
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
}, [isNextRecord]);
|
}, [isNextRecord]);
|
||||||
@@ -56,7 +62,58 @@ const Report = () => {
|
|||||||
const handleDontShow = (isChecked) => {
|
const handleDontShow = (isChecked) => {
|
||||||
setIsDontShow(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);
|
// setIsLoader(true);
|
||||||
const json = reportJson(id);
|
const json = reportJson(id);
|
||||||
if (json) {
|
if (json) {
|
||||||
@@ -77,6 +134,9 @@ const Report = () => {
|
|||||||
const skipRecord = id === "4Hhwbp482K" ? 0 : skipUserRecord;
|
const skipRecord = id === "4Hhwbp482K" ? 0 : skipUserRecord;
|
||||||
const limitRecord = id === "4Hhwbp482K" ? 200 : limit;
|
const limitRecord = id === "4Hhwbp482K" ? 200 : limit;
|
||||||
const params = { reportId: id, skip: skipRecord, limit: limitRecord };
|
const params = { reportId: id, skip: skipRecord, limit: limitRecord };
|
||||||
|
if (term) {
|
||||||
|
params.searchTerm = term;
|
||||||
|
}
|
||||||
const url = `${localStorage.getItem("baseUrl")}functions/getReport`;
|
const url = `${localStorage.getItem("baseUrl")}functions/getReport`;
|
||||||
const res = await axios.post(url, params, {
|
const res = await axios.post(url, params, {
|
||||||
headers: headers,
|
headers: headers,
|
||||||
@@ -197,6 +257,12 @@ const Report = () => {
|
|||||||
report_help={reporthelp}
|
report_help={reporthelp}
|
||||||
tourData={tourData}
|
tourData={tourData}
|
||||||
isDontShow={isDontShow}
|
isDontShow={isDontShow}
|
||||||
|
mobileSearchOpen={mobileSearchOpen}
|
||||||
|
setMobileSearchOpen={setMobileSearchOpen}
|
||||||
|
searchTerm={searchTerm}
|
||||||
|
handleSearchChange={handleSearchChange}
|
||||||
|
handleSearchPaste={handleSearchPaste}
|
||||||
|
isSearchResult={isSearchResult}
|
||||||
isImport={isImport}
|
isImport={isImport}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useRef, useEffect } from "react";
|
import { useState, useRef, useEffect } from "react";
|
||||||
import { PDFDocument } from "pdf-lib";
|
import { PDFDocument } from "pdf-lib";
|
||||||
import "../styles/signature.css";
|
import "../styles/signature.css";
|
||||||
import Parse from "parse";
|
import Parse from "parse";
|
||||||
@@ -22,6 +22,7 @@ import {
|
|||||||
randomId,
|
randomId,
|
||||||
getDate,
|
getDate,
|
||||||
textWidget,
|
textWidget,
|
||||||
|
cellsWidget,
|
||||||
convertPdfArrayBuffer,
|
convertPdfArrayBuffer,
|
||||||
textInputWidget,
|
textInputWidget,
|
||||||
fetchImageBase64,
|
fetchImageBase64,
|
||||||
@@ -32,12 +33,13 @@ import {
|
|||||||
onClickZoomIn,
|
onClickZoomIn,
|
||||||
onClickZoomOut,
|
onClickZoomOut,
|
||||||
rotatePdfPage,
|
rotatePdfPage,
|
||||||
signatureTypes,
|
|
||||||
getBase64FromUrl,
|
getBase64FromUrl,
|
||||||
convertBase64ToFile,
|
convertBase64ToFile,
|
||||||
generatePdfName,
|
generatePdfName,
|
||||||
handleRemoveWidgets,
|
handleRemoveWidgets,
|
||||||
addWidgetSelfsignOptions
|
addWidgetSelfsignOptions,
|
||||||
|
getOriginalWH,
|
||||||
|
signatureTypes
|
||||||
} from "../constant/Utils";
|
} from "../constant/Utils";
|
||||||
import { useParams } from "react-router";
|
import { useParams } from "react-router";
|
||||||
import Tour from "../primitives/Tour";
|
import Tour from "../primitives/Tour";
|
||||||
@@ -67,6 +69,8 @@ import {
|
|||||||
resetWidgetState
|
resetWidgetState
|
||||||
} from "../redux/reducers/widgetSlice.js";
|
} from "../redux/reducers/widgetSlice.js";
|
||||||
import WidgetsValueModal from "../components/pdf/WidgetsValueModal";
|
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.
|
//For signYourself inProgress section signer can add sign and complete doc sign.
|
||||||
function SignYourSelf() {
|
function SignYourSelf() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -117,6 +121,10 @@ function SignYourSelf() {
|
|||||||
const [isTextSetting, setIsTextSetting] = useState(false);
|
const [isTextSetting, setIsTextSetting] = useState(false);
|
||||||
const [currWidgetsDetails, setCurrWidgetsDetails] = useState({});
|
const [currWidgetsDetails, setCurrWidgetsDetails] = useState({});
|
||||||
const [isCheckbox, setIsCheckbox] = useState(false);
|
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 [pdfLoad, setPdfLoad] = useState(false);
|
||||||
const [isAlert, setIsAlert] = useState({ isShow: false, alertMessage: "" });
|
const [isAlert, setIsAlert] = useState({ isShow: false, alertMessage: "" });
|
||||||
const [isDontShow, setIsDontShow] = useState(false);
|
const [isDontShow, setIsDontShow] = useState(false);
|
||||||
@@ -355,9 +363,13 @@ function SignYourSelf() {
|
|||||||
);
|
);
|
||||||
const dragTypeValue = item?.text ? item.text : monitor.type;
|
const dragTypeValue = item?.text ? item.text : monitor.type;
|
||||||
const widgetValue = getWidgetValue(dragTypeValue);
|
const widgetValue = getWidgetValue(dragTypeValue);
|
||||||
const widgetTypeExist = ["name", "company", "job title", "email"].includes(
|
const widgetTypeExist = [
|
||||||
dragTypeValue
|
"name",
|
||||||
);
|
"company",
|
||||||
|
"job title",
|
||||||
|
"email",
|
||||||
|
cellsWidget
|
||||||
|
].includes(dragTypeValue);
|
||||||
const containerScale = getContainerScale(
|
const containerScale = getContainerScale(
|
||||||
pdfOriginalWH,
|
pdfOriginalWH,
|
||||||
pageNumber,
|
pageNumber,
|
||||||
@@ -365,15 +377,19 @@ function SignYourSelf() {
|
|||||||
);
|
);
|
||||||
//adding and updating drop position in array when user drop signature button in div
|
//adding and updating drop position in array when user drop signature button in div
|
||||||
if (item === "onclick") {
|
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 divHeight = divRef.current.getBoundingClientRect().height;
|
||||||
|
const divWidth = divRef.current.getBoundingClientRect().width;
|
||||||
const getWidth = widgetTypeExist
|
const getWidth = widgetTypeExist
|
||||||
? calculateInitialWidthHeight(widgetValue).getWidth
|
? calculateInitialWidthHeight(widgetValue).getWidth
|
||||||
: defaultWidthHeight(dragTypeValue).width;
|
: defaultWidthHeight(dragTypeValue).width;
|
||||||
const getHeight = defaultWidthHeight(dragTypeValue).height;
|
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 = {
|
dropObj = {
|
||||||
xPosition: getWidth / 2 + containerWH.width / 2,
|
xPosition: xPosition_Final,
|
||||||
yPosition: getHeight + divHeight / 2,
|
yPosition: getHeight + divHeight / 2,
|
||||||
isStamp:
|
isStamp:
|
||||||
(dragTypeValue === "stamp" || dragTypeValue === "image") && true,
|
(dragTypeValue === "stamp" || dragTypeValue === "image") && true,
|
||||||
@@ -434,6 +450,7 @@ function SignYourSelf() {
|
|||||||
[
|
[
|
||||||
textInputWidget,
|
textInputWidget,
|
||||||
textWidget,
|
textWidget,
|
||||||
|
cellsWidget,
|
||||||
"name",
|
"name",
|
||||||
"company",
|
"company",
|
||||||
"job title",
|
"job title",
|
||||||
@@ -626,10 +643,12 @@ function SignYourSelf() {
|
|||||||
const HeaderDocId = extUserPtr?.HeaderDocId;
|
const HeaderDocId = extUserPtr?.HeaderDocId;
|
||||||
//embed document's object id to all pages in pdf document
|
//embed document's object id to all pages in pdf document
|
||||||
if (!HeaderDocId) {
|
if (!HeaderDocId) {
|
||||||
await embedDocId(pdfDoc, documentId, allPages);
|
//pdfOriginalWH contained all pdf's pages width,height & pagenumber in array format
|
||||||
|
await embedDocId(pdfOriginalWH, pdfDoc, documentId);
|
||||||
}
|
}
|
||||||
//embed multi signature in pdf
|
//embed all widgets in document
|
||||||
const pdfBytes = await multiSignEmbed(
|
const pdfBytes = await multiSignEmbed(
|
||||||
|
pdfOriginalWH,
|
||||||
xyPosition,
|
xyPosition,
|
||||||
pdfDoc,
|
pdfDoc,
|
||||||
isSignYourSelfFlow,
|
isSignYourSelfFlow,
|
||||||
@@ -738,7 +757,6 @@ function SignYourSelf() {
|
|||||||
getDocumentDetails(false);
|
getDocumentDetails(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
//function for save x and y position and show signature tab on that position
|
//function for save x and y position and show signature tab on that position
|
||||||
const handleTabDrag = (key) => {
|
const handleTabDrag = (key) => {
|
||||||
setDragKey(key);
|
setDragKey(key);
|
||||||
@@ -789,14 +807,7 @@ function SignYourSelf() {
|
|||||||
};
|
};
|
||||||
//function for get pdf page details
|
//function for get pdf page details
|
||||||
const pageDetails = async (pdf) => {
|
const pageDetails = async (pdf) => {
|
||||||
let pdfWHObj = [];
|
const pdfWHObj = await getOriginalWH(pdf);
|
||||||
const totalPages = pdf?.numPages;
|
|
||||||
for (let index = 0; index < totalPages; index++) {
|
|
||||||
const getPage = await pdf.getPage(index + 1);
|
|
||||||
const scale = 1;
|
|
||||||
const { width, height } = getPage.getViewport({ scale });
|
|
||||||
pdfWHObj.push({ pageNumber: index + 1, width, height });
|
|
||||||
}
|
|
||||||
setPdfOriginalWH(pdfWHObj);
|
setPdfOriginalWH(pdfWHObj);
|
||||||
setPdfLoad(true);
|
setPdfLoad(true);
|
||||||
};
|
};
|
||||||
@@ -932,7 +943,8 @@ function SignYourSelf() {
|
|||||||
deleteOption,
|
deleteOption,
|
||||||
status,
|
status,
|
||||||
defaultValue,
|
defaultValue,
|
||||||
isHideLabel
|
isHideLabel,
|
||||||
|
layout
|
||||||
) => {
|
) => {
|
||||||
const getPageNumer = xyPosition.filter(
|
const getPageNumer = xyPosition.filter(
|
||||||
(data) => data.pageNumber === pageNumber
|
(data) => data.pageNumber === pageNumber
|
||||||
@@ -940,6 +952,8 @@ function SignYourSelf() {
|
|||||||
if (getPageNumer.length > 0) {
|
if (getPageNumer.length > 0) {
|
||||||
const getXYdata = getPageNumer[0].pos;
|
const getXYdata = getPageNumer[0].pos;
|
||||||
const getPosData = getXYdata;
|
const getPosData = getXYdata;
|
||||||
|
const widgetLayout =
|
||||||
|
currWidgetsDetails?.type === "checkbox" ? { layout: layout } : {};
|
||||||
const addSignPos = getPosData.map((position) => {
|
const addSignPos = getPosData.map((position) => {
|
||||||
if (position.key === currWidgetsDetails?.key) {
|
if (position.key === currWidgetsDetails?.key) {
|
||||||
if (addOption) {
|
if (addOption) {
|
||||||
@@ -963,6 +977,7 @@ function SignYourSelf() {
|
|||||||
...position.options,
|
...position.options,
|
||||||
name: dropdownName,
|
name: dropdownName,
|
||||||
values: dropdownOptions,
|
values: dropdownOptions,
|
||||||
|
...widgetLayout,
|
||||||
isReadOnly: isReadOnly,
|
isReadOnly: isReadOnly,
|
||||||
isHideLabel: isHideLabel || false,
|
isHideLabel: isHideLabel || false,
|
||||||
fontSize:
|
fontSize:
|
||||||
@@ -1029,6 +1044,92 @@ function SignYourSelf() {
|
|||||||
handleTextSettingModal(false);
|
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 = () => {
|
const clickOnZoomIn = () => {
|
||||||
onClickZoomIn(scale, zoomPercent, setScale, setZoomPercent);
|
onClickZoomIn(scale, zoomPercent, setScale, setZoomPercent);
|
||||||
};
|
};
|
||||||
@@ -1184,9 +1285,8 @@ function SignYourSelf() {
|
|||||||
title={t("document-signed")}
|
title={t("document-signed")}
|
||||||
handleClose={() => setShowAlreadySignDoc({ status: false })}
|
handleClose={() => setShowAlreadySignDoc({ status: false })}
|
||||||
>
|
>
|
||||||
<div className="p-[20px] h-full">
|
<div className="p-[20px] h-full text-base-content">
|
||||||
<p>{showAlreadySignDoc.mssg}</p>
|
<p>{showAlreadySignDoc.mssg}</p>
|
||||||
|
|
||||||
<div className="h-[1px] w-full my-[15px] bg-[#9f9f9f]"></div>
|
<div className="h-[1px] w-full my-[15px] bg-[#9f9f9f]"></div>
|
||||||
<button
|
<button
|
||||||
className="op-btn op-btn-ghost shadow-md"
|
className="op-btn op-btn-ghost shadow-md"
|
||||||
@@ -1284,12 +1384,15 @@ function SignYourSelf() {
|
|||||||
setIsPageCopy={setIsPageCopy}
|
setIsPageCopy={setIsPageCopy}
|
||||||
setIsCheckbox={setIsCheckbox}
|
setIsCheckbox={setIsCheckbox}
|
||||||
setCurrWidgetsDetails={setCurrWidgetsDetails}
|
setCurrWidgetsDetails={setCurrWidgetsDetails}
|
||||||
|
handleNameModal={openNameModal}
|
||||||
|
handleCellSettingModal={openCellsSettingModal}
|
||||||
handleTextSettingModal={handleTextSettingModal}
|
handleTextSettingModal={handleTextSettingModal}
|
||||||
setScale={setScale}
|
setScale={setScale}
|
||||||
scale={scale}
|
scale={scale}
|
||||||
pdfBase64Url={pdfBase64Url}
|
pdfBase64Url={pdfBase64Url}
|
||||||
fontSize={fontSize}
|
fontSize={fontSize}
|
||||||
setFontSize={setFontSize}
|
setFontSize={setFontSize}
|
||||||
|
setCellCount={setCellCount}
|
||||||
fontColor={fontColor}
|
fontColor={fontColor}
|
||||||
setFontColor={setFontColor}
|
setFontColor={setFontColor}
|
||||||
isResize={isResize}
|
isResize={isResize}
|
||||||
@@ -1331,13 +1434,32 @@ function SignYourSelf() {
|
|||||||
xyPosition={xyPosition} //placeholder details
|
xyPosition={xyPosition} //placeholder details
|
||||||
pageNumber={pageNumber} //current page number
|
pageNumber={pageNumber} //current page number
|
||||||
setXyPosition={setXyPosition} //placeholder details state
|
setXyPosition={setXyPosition} //placeholder details state
|
||||||
|
setCellCount={setCellCount}
|
||||||
setPageNumber={setPageNumber}
|
setPageNumber={setPageNumber}
|
||||||
setCurrWidgetsDetails={setCurrWidgetsDetails}
|
setCurrWidgetsDetails={setCurrWidgetsDetails}
|
||||||
currWidgetsDetails={currWidgetsDetails}
|
currWidgetsDetails={currWidgetsDetails}
|
||||||
index={index}
|
index={index}
|
||||||
isSave={true}
|
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
|
<RotateAlert
|
||||||
showRotateAlert={showRotateAlert.status}
|
showRotateAlert={showRotateAlert.status}
|
||||||
setShowRotateAlert={setShowRotateAlert}
|
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 RenderAllPdfPage from "../components/pdf/RenderAllPdfPage";
|
||||||
import { useParams, useNavigate } from "react-router";
|
import { useParams, useNavigate } from "react-router";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
@@ -22,6 +22,7 @@ import {
|
|||||||
defaultWidthHeight,
|
defaultWidthHeight,
|
||||||
addWidgetOptions,
|
addWidgetOptions,
|
||||||
textInputWidget,
|
textInputWidget,
|
||||||
|
cellsWidget,
|
||||||
radioButtonWidget,
|
radioButtonWidget,
|
||||||
getContainerScale,
|
getContainerScale,
|
||||||
convertBase64ToFile,
|
convertBase64ToFile,
|
||||||
@@ -38,7 +39,8 @@ import {
|
|||||||
convertPdfArrayBuffer,
|
convertPdfArrayBuffer,
|
||||||
generatePdfName,
|
generatePdfName,
|
||||||
textWidget,
|
textWidget,
|
||||||
multiSignEmbed
|
multiSignEmbed,
|
||||||
|
getOriginalWH
|
||||||
} from "../constant/Utils";
|
} from "../constant/Utils";
|
||||||
import RenderPdf from "../components/pdf/RenderPdf";
|
import RenderPdf from "../components/pdf/RenderPdf";
|
||||||
import "../styles/AddUser.css";
|
import "../styles/AddUser.css";
|
||||||
@@ -375,11 +377,15 @@ const TemplatePlaceholder = () => {
|
|||||||
filterSignerPos;
|
filterSignerPos;
|
||||||
let placeHolder;
|
let placeHolder;
|
||||||
if (item === "onclick") {
|
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;
|
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 = {
|
dropObj = {
|
||||||
//onclick put placeholder center on pdf
|
//onclick put placeholder center on pdf
|
||||||
xPosition: widgetWidth / 4 + containerWH.width / 2,
|
xPosition: xPosition_Final,
|
||||||
yPosition: widgetHeight + divHeight / 2,
|
yPosition: widgetHeight + divHeight / 2,
|
||||||
isStamp:
|
isStamp:
|
||||||
(dragTypeValue === "stamp" || dragTypeValue === "image") && true,
|
(dragTypeValue === "stamp" || dragTypeValue === "image") && true,
|
||||||
@@ -534,14 +540,7 @@ const TemplatePlaceholder = () => {
|
|||||||
|
|
||||||
//function for get pdf page details
|
//function for get pdf page details
|
||||||
const pageDetails = async (pdf) => {
|
const pageDetails = async (pdf) => {
|
||||||
let pdfWHObj = [];
|
const pdfWHObj = await getOriginalWH(pdf);
|
||||||
const totalPages = pdf?.numPages;
|
|
||||||
for (let index = 0; index < totalPages; index++) {
|
|
||||||
const getPage = await pdf.getPage(index + 1);
|
|
||||||
const scale = 1;
|
|
||||||
const { width, height } = getPage.getViewport({ scale });
|
|
||||||
pdfWHObj.push({ pageNumber: index + 1, width, height });
|
|
||||||
}
|
|
||||||
setPdfOriginalWH(pdfWHObj);
|
setPdfOriginalWH(pdfWHObj);
|
||||||
setPdfLoad(true);
|
setPdfLoad(true);
|
||||||
};
|
};
|
||||||
@@ -832,7 +831,9 @@ const TemplatePlaceholder = () => {
|
|||||||
});
|
});
|
||||||
const isSignYourSelfFlow = false;
|
const isSignYourSelfFlow = false;
|
||||||
try {
|
try {
|
||||||
|
//pdfOriginalWH contained all pdf's pages width,height & pagenumber in array format
|
||||||
const pdfBase64 = await multiSignEmbed(
|
const pdfBase64 = await multiSignEmbed(
|
||||||
|
pdfOriginalWH,
|
||||||
placeholder,
|
placeholder,
|
||||||
pdfDoc,
|
pdfDoc,
|
||||||
isSignYourSelfFlow,
|
isSignYourSelfFlow,
|
||||||
@@ -1149,25 +1150,6 @@ const TemplatePlaceholder = () => {
|
|||||||
const handleLinkUser = (id) => {
|
const handleLinkUser = (id) => {
|
||||||
setIsAddUser({ [id]: true });
|
setIsAddUser({ [id]: true });
|
||||||
};
|
};
|
||||||
//function to use unlink signer from widgets
|
|
||||||
const handleUnlinkSigner = () => {
|
|
||||||
//remove existing signer's details from 'signerPos' array
|
|
||||||
const updatePlaceHolder = signerPos.map((x) => {
|
|
||||||
if (x.Id === uniqueId) {
|
|
||||||
return { ...x, signerPtr: {}, signerObjId: "" };
|
|
||||||
}
|
|
||||||
return { ...x };
|
|
||||||
});
|
|
||||||
setSignerPos(updatePlaceHolder);
|
|
||||||
//remove existing signer's details from 'signersdata' array and keep role and id
|
|
||||||
const updateSigner = signersdata.map((item) => {
|
|
||||||
if (item.Id == uniqueId) {
|
|
||||||
return { Role: item.Role, Id: item.Id, blockColor: item.blockColor };
|
|
||||||
}
|
|
||||||
return item;
|
|
||||||
});
|
|
||||||
setSignersData(updateSigner);
|
|
||||||
};
|
|
||||||
// `handleAddUser` is used to adduser
|
// `handleAddUser` is used to adduser
|
||||||
const handleAddUser = (data) => {
|
const handleAddUser = (data) => {
|
||||||
const signerPtr = {
|
const signerPtr = {
|
||||||
@@ -1293,7 +1275,8 @@ const TemplatePlaceholder = () => {
|
|||||||
deleteOption,
|
deleteOption,
|
||||||
status,
|
status,
|
||||||
defaultValue,
|
defaultValue,
|
||||||
isHideLabel
|
isHideLabel,
|
||||||
|
layout
|
||||||
) => {
|
) => {
|
||||||
const filterSignerPos = signerPos.filter((data) => data.Id === uniqueId);
|
const filterSignerPos = signerPos.filter((data) => data.Id === uniqueId);
|
||||||
if (filterSignerPos.length > 0) {
|
if (filterSignerPos.length > 0) {
|
||||||
@@ -1332,6 +1315,7 @@ const TemplatePlaceholder = () => {
|
|||||||
name: dropdownName,
|
name: dropdownName,
|
||||||
values: dropdownOptions,
|
values: dropdownOptions,
|
||||||
status: status,
|
status: status,
|
||||||
|
layout: layout,
|
||||||
defaultValue: defaultValue,
|
defaultValue: defaultValue,
|
||||||
isReadOnly: isReadOnly || false,
|
isReadOnly: isReadOnly || false,
|
||||||
isHideLabel: isHideLabel || false,
|
isHideLabel: isHideLabel || false,
|
||||||
@@ -1370,6 +1354,7 @@ const TemplatePlaceholder = () => {
|
|||||||
minRequiredCount: minCount,
|
minRequiredCount: minCount,
|
||||||
maxRequiredCount: maxCount
|
maxRequiredCount: maxCount
|
||||||
},
|
},
|
||||||
|
layout: layout,
|
||||||
isReadOnly: isReadOnly || false,
|
isReadOnly: isReadOnly || false,
|
||||||
defaultValue: defaultValue,
|
defaultValue: defaultValue,
|
||||||
isHideLabel: isHideLabel || false,
|
isHideLabel: isHideLabel || false,
|
||||||
@@ -1470,6 +1455,36 @@ const TemplatePlaceholder = () => {
|
|||||||
"black"
|
"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)) {
|
} else if (["signature"].includes(position.type)) {
|
||||||
return {
|
return {
|
||||||
...position,
|
...position,
|
||||||
@@ -1529,6 +1544,22 @@ const TemplatePlaceholder = () => {
|
|||||||
setIsRadio(false);
|
setIsRadio(false);
|
||||||
setIsCheckbox(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 = () => {
|
const clickOnZoomIn = () => {
|
||||||
onClickZoomIn(scale, zoomPercent, setScale, setZoomPercent);
|
onClickZoomIn(scale, zoomPercent, setScale, setZoomPercent);
|
||||||
@@ -1690,7 +1721,7 @@ const TemplatePlaceholder = () => {
|
|||||||
navigate("/report/6TeaPr321t");
|
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>
|
<p>{t("template-created-alert")}</p>
|
||||||
<div className="h-[1px] w-full my-[15px] bg-[#9f9f9f]"></div>
|
<div className="h-[1px] w-full my-[15px] bg-[#9f9f9f]"></div>
|
||||||
<div className="flex gap-1 flex-col md:flex-row">
|
<div className="flex gap-1 flex-col md:flex-row">
|
||||||
@@ -1847,6 +1878,7 @@ const TemplatePlaceholder = () => {
|
|||||||
pdfBase64Url={pdfBase64Url}
|
pdfBase64Url={pdfBase64Url}
|
||||||
fontSize={fontSize}
|
fontSize={fontSize}
|
||||||
setFontSize={setFontSize}
|
setFontSize={setFontSize}
|
||||||
|
setCellCount={setCellCount}
|
||||||
fontColor={fontColor}
|
fontColor={fontColor}
|
||||||
setFontColor={setFontColor}
|
setFontColor={setFontColor}
|
||||||
isResize={isResize}
|
isResize={isResize}
|
||||||
@@ -1950,7 +1982,9 @@ const TemplatePlaceholder = () => {
|
|||||||
closePopup={closePopup}
|
closePopup={closePopup}
|
||||||
signersData={signersdata}
|
signersData={signersdata}
|
||||||
signerPos={signerPos}
|
signerPos={signerPos}
|
||||||
handleUnlinkSigner={handleUnlinkSigner}
|
setSignerPos={setSignerPos}
|
||||||
|
setSignersData={setSignersData}
|
||||||
|
isRemove={true}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -284,7 +284,7 @@ const UserList = () => {
|
|||||||
handleClose={handleClose}
|
handleClose={handleClose}
|
||||||
>
|
>
|
||||||
<div className="m-[20px]">
|
<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")}{" "}
|
{t("are-you-sure")}{" "}
|
||||||
{item?.IsDisabled
|
{item?.IsDisabled
|
||||||
? t("activate")
|
? t("activate")
|
||||||
|
|||||||
@@ -286,7 +286,7 @@ function UserProfile() {
|
|||||||
style={{ width: `${percentage}%` }}
|
style={{ width: `${percentage}%` }}
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-black text-sm">{percentage}%</span>
|
<span className="text-base-contentk text-sm">{percentage}%</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="text-base font-semibold pt-4">
|
<div className="text-base font-semibold pt-4">
|
||||||
@@ -423,9 +423,9 @@ function UserProfile() {
|
|||||||
onClick={() =>
|
onClick={() =>
|
||||||
editmode ? handleCancel() : navigate("/changepassword")
|
editmode ? handleCancel() : navigate("/changepassword")
|
||||||
}
|
}
|
||||||
className={`op-btn ${
|
className={
|
||||||
editmode ? "op-btn-ghost w-[100px]" : "op-btn-secondary"
|
`op-btn ${editmode ? "op-btn-ghost w-[100px]" : "op-btn-secondary"}`
|
||||||
}`}
|
}
|
||||||
>
|
>
|
||||||
{editmode ? t("cancel") : t("change-password")}
|
{editmode ? t("cancel") : t("change-password")}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -0,0 +1,864 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { PDFDocument, PDFName, PDFSignature, PDFRef, PDFDict } from 'pdf-lib'; // Updated import
|
||||||
|
import * as asn1js from 'asn1js';
|
||||||
|
import { Certificate, ContentInfo, SignedData, IssuerAndSerialNumber } from 'pkijs';
|
||||||
|
// import * as jsrsasign from 'jsrsasign'; // Removed for dynamic loading
|
||||||
|
|
||||||
|
const VerifyDocument = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [selectedFile, setSelectedFile] = useState(null);
|
||||||
|
const [fileBuffer, setFileBuffer] = useState(null);
|
||||||
|
const [verificationResult, setVerificationResult] = useState('');
|
||||||
|
const [detailedResults, setDetailedResults] = useState([]);
|
||||||
|
const [collapsedSections, setCollapsedSections] = useState({});
|
||||||
|
// const [jsrsasignStatus, setJsrsasignStatus] = useState('loading'); // Removed
|
||||||
|
|
||||||
|
// OID to human-readable label mapping
|
||||||
|
const oidMapping = {
|
||||||
|
'2.5.4.6': 'Country',
|
||||||
|
'2.5.4.10': 'Organization',
|
||||||
|
'2.5.4.11': 'Organizational Unit',
|
||||||
|
'2.5.4.17': 'Postal Code',
|
||||||
|
'2.5.4.8': 'State',
|
||||||
|
'2.5.4.7': 'Locality', // Alternative for City
|
||||||
|
'2.5.4.9': 'City',
|
||||||
|
'2.5.4.51': 'Address',
|
||||||
|
'2.5.4.3': 'Common Name',
|
||||||
|
'2.5.4.4': 'Surname',
|
||||||
|
'2.5.4.5': 'Serial Number',
|
||||||
|
'2.5.4.12': 'Title',
|
||||||
|
'2.5.4.13': 'Description',
|
||||||
|
'2.5.4.16': 'Postal Address',
|
||||||
|
'2.5.4.18': 'Post Office Box',
|
||||||
|
'2.5.4.20': 'Telephone Number',
|
||||||
|
'1.2.840.113549.1.9.1': 'Email Address',
|
||||||
|
// Common alternative OIDs
|
||||||
|
'C': 'Country',
|
||||||
|
'O': 'Organization',
|
||||||
|
'OU': 'Organizational Unit',
|
||||||
|
'CN': 'Common Name',
|
||||||
|
'ST': 'State',
|
||||||
|
'L': 'Locality',
|
||||||
|
'STREET': 'Address',
|
||||||
|
'emailAddress': 'Email Address',
|
||||||
|
'serialNumber': 'Serial Number'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Function to parse certificate subject/issuer into structured data
|
||||||
|
const parseCertificateInfo = (certString) => {
|
||||||
|
if (!certString) return {};
|
||||||
|
|
||||||
|
const parsed = {};
|
||||||
|
|
||||||
|
// Find all OID patterns and their positions
|
||||||
|
const oidPattern = /(\d+\.\d+\.\d+\.\d+|\w+)=/g;
|
||||||
|
const matches = [];
|
||||||
|
let match;
|
||||||
|
|
||||||
|
while ((match = oidPattern.exec(certString)) !== null) {
|
||||||
|
matches.push({
|
||||||
|
oid: match[1],
|
||||||
|
startIndex: match.index,
|
||||||
|
equalIndex: match.index + match[1].length
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract value for each OID
|
||||||
|
for (let i = 0; i < matches.length; i++) {
|
||||||
|
const currentMatch = matches[i];
|
||||||
|
const nextMatch = matches[i + 1];
|
||||||
|
|
||||||
|
const valueStart = currentMatch.equalIndex + 1; // Skip the "=" character
|
||||||
|
const valueEnd = nextMatch ? nextMatch.startIndex - 2 : certString.length; // -2 to remove ", " before next OID
|
||||||
|
|
||||||
|
const value = certString.substring(valueStart, valueEnd).trim();
|
||||||
|
const label = oidMapping[currentMatch.oid] || currentMatch.oid;
|
||||||
|
|
||||||
|
parsed[label] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsed;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Function to determine if status should show success icon
|
||||||
|
const isSuccessStatus = (status) => {
|
||||||
|
const successTerms = ['valid', 'success', 'parsed', 'verified'];
|
||||||
|
const errorTerms = ['error', 'invalid', 'failed', 'expired'];
|
||||||
|
|
||||||
|
const statusLower = status.toLowerCase();
|
||||||
|
|
||||||
|
// Check for explicit error terms first
|
||||||
|
if (errorTerms.some(term => statusLower.includes(term))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for success terms
|
||||||
|
return successTerms.some(term => statusLower.includes(term));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Function to determine if certificate validity should show success icon
|
||||||
|
const isCertificateValid = (validityText) => {
|
||||||
|
const validityLower = validityText.toLowerCase();
|
||||||
|
|
||||||
|
// If it contains "valid" and doesn't contain negative terms
|
||||||
|
return validityLower.includes('valid') &&
|
||||||
|
!validityLower.includes('expired') &&
|
||||||
|
!validityLower.includes('not yet valid') &&
|
||||||
|
!validityLower.includes('invalid');
|
||||||
|
};
|
||||||
|
|
||||||
|
// Toggle collapsible sections
|
||||||
|
const toggleSection = (signatureIndex, section) => {
|
||||||
|
const key = `${signatureIndex}-${section}`;
|
||||||
|
setCollapsedSections(prev => ({
|
||||||
|
...prev,
|
||||||
|
[key]: !prev[key]
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
// useEffect for jsrsasign loading removed
|
||||||
|
|
||||||
|
const handleFileChange = (event) => {
|
||||||
|
const file = event.target.files[0];
|
||||||
|
if (file && file.type === 'application/pdf') {
|
||||||
|
setSelectedFile(file);
|
||||||
|
setVerificationResult('');
|
||||||
|
setDetailedResults([]);
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = (e) => {
|
||||||
|
setFileBuffer(e.target.result);
|
||||||
|
};
|
||||||
|
reader.readAsArrayBuffer(file);
|
||||||
|
} else {
|
||||||
|
setSelectedFile(null);
|
||||||
|
setFileBuffer(null);
|
||||||
|
setDetailedResults([]);
|
||||||
|
setVerificationResult(t('please-select-pdf'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const parseSignature = async (pdfDoc) => {
|
||||||
|
const signatureFields = pdfDoc.getForm().getFields().filter(field => field instanceof PDFSignature); // Updated filter logic
|
||||||
|
if (!signatureFields.length) {
|
||||||
|
return { error: t('no-signature-found') };
|
||||||
|
}
|
||||||
|
|
||||||
|
const results = [];
|
||||||
|
|
||||||
|
for (const field of signatureFields) {
|
||||||
|
try {
|
||||||
|
if (!field.acroField || !field.acroField.dict) {
|
||||||
|
results.push({
|
||||||
|
name: field.getName() || t('unnamed-signature-field'),
|
||||||
|
status: t('error-processing-signature'),
|
||||||
|
errorDetails: t('missing-acrofield-dict'),
|
||||||
|
signerInfo: t('signer-info-not-available'),
|
||||||
|
certificateSubject: '',
|
||||||
|
certificateIssuer: '',
|
||||||
|
certificateValidity: t('cert-validity-not-checked'),
|
||||||
|
isCertificateDateValid: false,
|
||||||
|
calculatedDocumentHash: t('not-available'),
|
||||||
|
messageDigestInSignature: t('not-available'),
|
||||||
|
hashComparisonResult: t('not-performed'),
|
||||||
|
authenticatedAttributesSignatureResult: t('not-performed'),
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// New logic to determine the actual signature dictionary
|
||||||
|
const fieldDict = field.acroField.dict;
|
||||||
|
const vEntry = fieldDict.get(PDFName.of('V'));
|
||||||
|
let actualSignatureDict = null;
|
||||||
|
|
||||||
|
if (vEntry) {
|
||||||
|
if (vEntry instanceof PDFRef) {
|
||||||
|
const lookedUp = pdfDoc.context.lookup(vEntry);
|
||||||
|
if (lookedUp instanceof PDFDict) {
|
||||||
|
actualSignatureDict = lookedUp;
|
||||||
|
}
|
||||||
|
} else if (vEntry instanceof PDFDict) {
|
||||||
|
actualSignatureDict = vEntry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use actualSignatureDict if found, otherwise behavior might be problematic (as per existing logic)
|
||||||
|
// If actualSignatureDict is null, subsequent checks for byteRangeObject etc. will fail,
|
||||||
|
// leading to an error message for this signature, which is acceptable.
|
||||||
|
const signatureDict = actualSignatureDict;
|
||||||
|
|
||||||
|
// Check if signatureDict is null (meaning actualSignatureDict was not resolved)
|
||||||
|
// and push an error if it is, before trying to get ByteRange or Contents.
|
||||||
|
if (!signatureDict) {
|
||||||
|
results.push({
|
||||||
|
name: field.getName() || t('unnamed-signature-field'),
|
||||||
|
status: t('error-processing-signature'),
|
||||||
|
errorDetails: t('signature-dictionary-not-found-or-invalid'), // New error message
|
||||||
|
signerInfo: t('signer-info-not-available'),
|
||||||
|
certificateSubject: '',
|
||||||
|
certificateIssuer: '',
|
||||||
|
certificateValidity: t('cert-validity-not-checked'),
|
||||||
|
isCertificateDateValid: false,
|
||||||
|
calculatedDocumentHash: t('not-available'),
|
||||||
|
messageDigestInSignature: t('not-available'),
|
||||||
|
hashComparisonResult: t('not-performed'),
|
||||||
|
authenticatedAttributesSignatureResult: t('not-performed'),
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const byteRangeObject = signatureDict.get(PDFName.of('ByteRange'));
|
||||||
|
let byteRange; // Will be assigned after validation
|
||||||
|
|
||||||
|
// Comprehensive validation for byteRangeObject and its contents
|
||||||
|
if (!byteRangeObject ||
|
||||||
|
!byteRangeObject.array ||
|
||||||
|
!Array.isArray(byteRangeObject.array) ||
|
||||||
|
byteRangeObject.array.length === 0 ||
|
||||||
|
byteRangeObject.array.length % 2 !== 0) {
|
||||||
|
results.push({
|
||||||
|
name: field.getName() || t('unnamed-signature-field'),
|
||||||
|
status: t('error-processing-signature'),
|
||||||
|
errorDetails: t('missing-or-invalid-byterange'),
|
||||||
|
signerInfo: t('signer-info-not-available'),
|
||||||
|
certificateSubject: '',
|
||||||
|
certificateIssuer: '',
|
||||||
|
certificateValidity: t('cert-validity-not-checked'),
|
||||||
|
isCertificateDateValid: false,
|
||||||
|
calculatedDocumentHash: t('not-available'),
|
||||||
|
messageDigestInSignature: t('not-available'),
|
||||||
|
hashComparisonResult: t('not-performed'),
|
||||||
|
authenticatedAttributesSignatureResult: t('not-performed'),
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const byteRangeNumbers = [];
|
||||||
|
let byteRangeIsValid = true;
|
||||||
|
for (const pdfObject of byteRangeObject.array) {
|
||||||
|
if (!pdfObject || typeof pdfObject.asNumber !== 'function') {
|
||||||
|
byteRangeIsValid = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const num = pdfObject.asNumber();
|
||||||
|
if (!Number.isFinite(num)) { // Checks for NaN, Infinity, -Infinity
|
||||||
|
byteRangeIsValid = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
byteRangeNumbers.push(num);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!byteRangeIsValid) {
|
||||||
|
results.push({
|
||||||
|
name: field.getName() || t('unnamed-signature-field'),
|
||||||
|
status: t('error-processing-signature'),
|
||||||
|
errorDetails: t('missing-or-invalid-byterange'), // Or a more specific error like "ByteRange contains non-numeric values"
|
||||||
|
signerInfo: t('signer-info-not-available'),
|
||||||
|
certificateSubject: '',
|
||||||
|
certificateIssuer: '',
|
||||||
|
certificateValidity: t('cert-validity-not-checked'),
|
||||||
|
isCertificateDateValid: false,
|
||||||
|
calculatedDocumentHash: t('not-available'),
|
||||||
|
messageDigestInSignature: t('not-available'),
|
||||||
|
hashComparisonResult: t('not-performed'),
|
||||||
|
authenticatedAttributesSignatureResult: t('not-performed'),
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
byteRange = byteRangeNumbers; // Assign the validated numbers to byteRange
|
||||||
|
|
||||||
|
const contentsObject = signatureDict.get(PDFName.of('Contents'));
|
||||||
|
if (!contentsObject || typeof contentsObject.asString !== 'function') {
|
||||||
|
results.push({
|
||||||
|
name: field.getName() || t('unnamed-signature-field'),
|
||||||
|
status: t('error-processing-signature'),
|
||||||
|
errorDetails: t('missing-or-invalid-contents'),
|
||||||
|
signerInfo: t('signer-info-not-available'),
|
||||||
|
certificateSubject: '',
|
||||||
|
certificateIssuer: '',
|
||||||
|
certificateValidity: t('cert-validity-not-checked'),
|
||||||
|
isCertificateDateValid: false,
|
||||||
|
calculatedDocumentHash: t('not-available'),
|
||||||
|
messageDigestInSignature: t('not-available'),
|
||||||
|
hashComparisonResult: t('not-performed'),
|
||||||
|
authenticatedAttributesSignatureResult: t('not-performed'),
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const contents = contentsObject.asString();
|
||||||
|
|
||||||
|
// The old basic check can be removed now as the more specific checks above cover these cases.
|
||||||
|
// if (!byteRange || !contents) { ... }
|
||||||
|
|
||||||
|
// Calculate totalSignedLength for accurate buffer initialization
|
||||||
|
let totalSignedLength = 0;
|
||||||
|
for (let i = 1; i < byteRange.length; i += 2) {
|
||||||
|
totalSignedLength += byteRange[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totalSignedLength <= 0) {
|
||||||
|
results.push({
|
||||||
|
name: field.getName() || t('unnamed-signature-field'),
|
||||||
|
status: t('error-processing-signature'),
|
||||||
|
errorDetails: t('missing-or-invalid-byterange'), // totalSignedLength being non-positive implies invalid ByteRange
|
||||||
|
signerInfo: t('signer-info-not-available'),
|
||||||
|
certificateSubject: '',
|
||||||
|
certificateIssuer: '',
|
||||||
|
certificateValidity: t('cert-validity-not-checked'),
|
||||||
|
isCertificateDateValid: false,
|
||||||
|
calculatedDocumentHash: t('not-available'),
|
||||||
|
messageDigestInSignature: t('not-available'),
|
||||||
|
hashComparisonResult: t('not-performed'),
|
||||||
|
authenticatedAttributesSignatureResult: t('not-performed'),
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const pdfSignedDataBytes = new Uint8Array(totalSignedLength);
|
||||||
|
let offset = 0;
|
||||||
|
let reconstructionFailed = false;
|
||||||
|
|
||||||
|
for (let i = 0; i < byteRange.length; i += 2) {
|
||||||
|
const start = byteRange[i];
|
||||||
|
const length = byteRange[i+1];
|
||||||
|
|
||||||
|
if (start < 0 || length <= 0 || start + length > fileBuffer.byteLength) {
|
||||||
|
reconstructionFailed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
pdfSignedDataBytes.set(new Uint8Array(fileBuffer.slice(start, start + length)), offset);
|
||||||
|
offset += length;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reconstructionFailed) {
|
||||||
|
results.push({
|
||||||
|
name: field.getName() || t('unnamed-signature-field'),
|
||||||
|
status: t('error-processing-signature'),
|
||||||
|
errorDetails: t('missing-or-invalid-byterange'), // Error during reconstruction due to invalid segment
|
||||||
|
signerInfo: t('signer-info-not-available'),
|
||||||
|
certificateSubject: '',
|
||||||
|
certificateIssuer: '',
|
||||||
|
certificateValidity: t('cert-validity-not-checked'),
|
||||||
|
isCertificateDateValid: false,
|
||||||
|
calculatedDocumentHash: t('not-available'),
|
||||||
|
messageDigestInSignature: t('not-available'),
|
||||||
|
hashComparisonResult: t('not-performed'),
|
||||||
|
authenticatedAttributesSignatureResult: t('not-performed'),
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove leading/trailing null bytes from hex if present from PDF content
|
||||||
|
const pkcs7Hex = contents.trim();
|
||||||
|
|
||||||
|
if (!pkcs7Hex) {
|
||||||
|
results.push({
|
||||||
|
name: field.getName() || t('unnamed-signature-field'),
|
||||||
|
status: t('signature-invalid-basic'),
|
||||||
|
errorDetails: t('missing-signature-contents'), // New i18n key
|
||||||
|
signerInfo: t('signer-info-not-available'),
|
||||||
|
certificateSubject: '',
|
||||||
|
certificateIssuer: '',
|
||||||
|
certificateValidity: t('cert-validity-not-checked'),
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert hex string to ArrayBuffer
|
||||||
|
let cmsContentBuffer;
|
||||||
|
try {
|
||||||
|
cmsContentBuffer = new Uint8Array(pkcs7Hex.match(/.{1,2}/g).map(byte => parseInt(byte, 16))).buffer;
|
||||||
|
} catch (hexError) {
|
||||||
|
// console.error('Error converting hex string to ArrayBuffer:', hexError); // Removed
|
||||||
|
results.push({
|
||||||
|
name: field.getName() || t('unnamed-signature-field'),
|
||||||
|
status: t('signature-invalid-basic'),
|
||||||
|
errorDetails: t('invalid-signature-hex-format'), // New i18n key
|
||||||
|
signerInfo: t('signer-info-not-available'),
|
||||||
|
certificateSubject: '',
|
||||||
|
certificateIssuer: '',
|
||||||
|
certificateValidity: t('cert-validity-not-checked'),
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Parse the CMS ContentInfo
|
||||||
|
const asn1 = asn1js.fromBER(cmsContentBuffer);
|
||||||
|
if (asn1.offset === -1) {
|
||||||
|
// console.error('Error parsing ASN.1 from signature data'); // Removed
|
||||||
|
results.push({
|
||||||
|
name: field.getName() || t('unnamed-signature-field'),
|
||||||
|
status: t('signature-invalid-basic'),
|
||||||
|
errorDetails: 'ASN.1 parsing error from signature data.',
|
||||||
|
signerInfo: t('signer-info-not-available'),
|
||||||
|
certificateSubject: '',
|
||||||
|
certificateIssuer: '',
|
||||||
|
certificateValidity: t('cert-validity-not-checked'),
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cmsContentInfo = new ContentInfo({ schema: asn1.result });
|
||||||
|
if (String(cmsContentInfo.contentType).trim() !== String(ContentInfo.SIGNED_DATA).trim()) {
|
||||||
|
// console.error('Not a SignedData content type. Actual type:', cmsContentInfo.contentType, 'Expected:', ContentInfo.SIGNED_DATA); // Removed
|
||||||
|
results.push({
|
||||||
|
name: field.getName() || t('unnamed-signature-field'),
|
||||||
|
status: t('signature-invalid-basic'),
|
||||||
|
errorDetails: t('unsupported-signature-format-not-signeddata'), // New i18n key
|
||||||
|
signerInfo: t('signer-info-not-available'),
|
||||||
|
certificateSubject: '',
|
||||||
|
certificateIssuer: '',
|
||||||
|
certificateValidity: t('cert-validity-not-checked'),
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const signedData = new SignedData({ schema: cmsContentInfo.content });
|
||||||
|
|
||||||
|
let signerInfoText = t('signer-info-not-available');
|
||||||
|
let certSubject = '';
|
||||||
|
let certIssuer = '';
|
||||||
|
let certValidity = t('cert-validity-not-checked');
|
||||||
|
let isValid = false;
|
||||||
|
|
||||||
|
if (signedData.signerInfos && signedData.signerInfos.length > 0) {
|
||||||
|
const signerInfo = signedData.signerInfos[0];
|
||||||
|
|
||||||
|
if (signedData.certificates && signedData.certificates.length > 0) {
|
||||||
|
let signerCertificate = null;
|
||||||
|
for (const cert of signedData.certificates) {
|
||||||
|
if (cert instanceof Certificate) {
|
||||||
|
const issuerAndSerialNumber = signerInfo.sid;
|
||||||
|
if (issuerAndSerialNumber instanceof IssuerAndSerialNumber) {
|
||||||
|
let certMatch = true;
|
||||||
|
if (cert.issuer.typesAndValues.length === issuerAndSerialNumber.issuer.typesAndValues.length) {
|
||||||
|
for (let i = 0; i < cert.issuer.typesAndValues.length; i++) {
|
||||||
|
if (cert.issuer.typesAndValues[i].type !== issuerAndSerialNumber.issuer.typesAndValues[i].type ||
|
||||||
|
cert.issuer.typesAndValues[i].value.valueBlock.value !== issuerAndSerialNumber.issuer.typesAndValues[i].value.valueBlock.value) {
|
||||||
|
certMatch = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
certMatch = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (certMatch && cert.serialNumber.valueBlock.valueHexView.join('') === issuerAndSerialNumber.serialNumber.valueBlock.valueHexView.join('')) {
|
||||||
|
signerCertificate = cert;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (signerCertificate) {
|
||||||
|
certSubject = signerCertificate.subject.typesAndValues.map(tv => `${tv.type}=${tv.value.valueBlock.value}`).join(', ');
|
||||||
|
certIssuer = signerCertificate.issuer.typesAndValues.map(tv => `${tv.type}=${tv.value.valueBlock.value}`).join(', ');
|
||||||
|
signerInfoText = `${t('signer')}: ${certSubject}, ${t('issuer')}: ${certIssuer}`;
|
||||||
|
|
||||||
|
const notBefore = signerCertificate.notBefore.value;
|
||||||
|
const notAfter = signerCertificate.notAfter.value;
|
||||||
|
const currentDate = new Date();
|
||||||
|
certValidity = `${t('valid-from')} ${notBefore.toLocaleDateString()} ${t('to')} ${notAfter.toLocaleDateString()}`;
|
||||||
|
if (currentDate < notBefore || currentDate > notAfter) {
|
||||||
|
certValidity += ` (${t('expired-or-not-yet-valid')})`;
|
||||||
|
isValid = false; // Explicitly false if expired
|
||||||
|
} else {
|
||||||
|
certValidity += ` (${t('valid')})`;
|
||||||
|
isValid = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
signerInfoText = t('signer-certificate-not-found'); // New i18n key
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
signerInfoText = t('no-certificates-in-signature'); // New i18n key
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
signerInfoText = t('no-signer-info-in-pkcs7'); // Re-use existing key, or make new one
|
||||||
|
}
|
||||||
|
|
||||||
|
results.push({
|
||||||
|
name: field.getName() || t('unnamed-signature-field'),
|
||||||
|
status: isValid ? t('signature-valid-basic') : t('signature-invalid-basic'),
|
||||||
|
signerInfo: signerInfoText,
|
||||||
|
certificateSubject: certSubject,
|
||||||
|
certificateIssuer: certIssuer,
|
||||||
|
certificateValidity: certValidity,
|
||||||
|
errorDetails: !isValid && signerInfoText === t('signer-info-not-available') ? t('could-not-parse-signer-info') : undefined, // New i18n key
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error processing signature field with pkijs:', field.getName(), e);
|
||||||
|
results.push({
|
||||||
|
name: field.getName() || t('unnamed-signature-field'),
|
||||||
|
status: t('error-processing-signature'),
|
||||||
|
errorDetails: e.message,
|
||||||
|
signerInfo: t('signer-info-not-available'),
|
||||||
|
certificateSubject: '',
|
||||||
|
certificateIssuer: '',
|
||||||
|
certificateValidity: t('cert-validity-not-checked'),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { results };
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const handleVerifyDocument = async () => {
|
||||||
|
// Removed jsrsasignStatus check
|
||||||
|
|
||||||
|
if (!fileBuffer) {
|
||||||
|
setVerificationResult(t('please-select-file-to-verify'));
|
||||||
|
setDetailedResults([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setVerificationResult(t('verification-in-progress'));
|
||||||
|
setDetailedResults([]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Removed window.KJUR and window.X509 check
|
||||||
|
|
||||||
|
const pdfDoc = await PDFDocument.load(fileBuffer, { ignoreEncryption: true });
|
||||||
|
const signatureInfo = await parseSignature(pdfDoc);
|
||||||
|
|
||||||
|
if (signatureInfo.error) {
|
||||||
|
setVerificationResult(signatureInfo.error);
|
||||||
|
} else if (signatureInfo.results && signatureInfo.results.length > 0) {
|
||||||
|
setDetailedResults(signatureInfo.results);
|
||||||
|
// Overall status can be determined by checking if all signatures are valid
|
||||||
|
const allValid = signatureInfo.results.every(res => res.status === t('signature-valid-basic'));
|
||||||
|
setVerificationResult(allValid ? t('all-signatures-verified-convincing') : t('some-signatures-invalid-basic'));
|
||||||
|
} else {
|
||||||
|
setVerificationResult(t('no-signatures-processed')); // Should be caught by no-signature-found earlier
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error during PDF processing or signature verification:', e);
|
||||||
|
setVerificationResult(`${t('error-verifying-pdf')}: ${e.message}`);
|
||||||
|
setDetailedResults([]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto p-6 bg-base-100 shadow-xl rounded-lg mt-10">
|
||||||
|
<style>{`
|
||||||
|
.checkmark__circle {
|
||||||
|
stroke-dasharray: 166;
|
||||||
|
stroke-dashoffset: 166;
|
||||||
|
stroke-width: 2;
|
||||||
|
stroke-miterlimit: 10;
|
||||||
|
stroke: #7ac142; /* Green color */
|
||||||
|
fill: none;
|
||||||
|
animation: stroke 0.6s cubic-bezier(0.65, 0, 0.45, 1) forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkmark {
|
||||||
|
width: 56px;
|
||||||
|
height: 56px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: block;
|
||||||
|
stroke-width: 2;
|
||||||
|
stroke: #fff; /* White check path */
|
||||||
|
stroke-miterlimit: 10;
|
||||||
|
margin: 10px auto; /* Example margin */
|
||||||
|
box-shadow: inset 0px 0px 0px #7ac142;
|
||||||
|
animation: fill .4s ease-in-out .4s forwards, scale .3s ease-in-out .9s both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkmark__check {
|
||||||
|
transform-origin: 50% 50%;
|
||||||
|
stroke-dasharray: 48;
|
||||||
|
stroke-dashoffset: 48;
|
||||||
|
animation: stroke 0.3s cubic-bezier(0.65, 0, 0.45, 1) 0.8s forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes stroke {
|
||||||
|
100% {
|
||||||
|
stroke-dashoffset: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes scale {
|
||||||
|
0%, 100% {
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: scale3d(1.1, 1.1, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fill {
|
||||||
|
100% {
|
||||||
|
box-shadow: inset 0px 0px 0px 30px #7ac142;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`}</style>
|
||||||
|
<h1 className="text-3xl font-bold mb-6 text-center text-base-content">
|
||||||
|
{t('verify-document-signature')}
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<div className="mb-6 p-6 border border-base-300 rounded-lg bg-base-200/30 shadow-sm">
|
||||||
|
<label
|
||||||
|
htmlFor="document-upload"
|
||||||
|
className="block text-lg font-medium text-base-content mb-2"
|
||||||
|
>
|
||||||
|
{t('select-pdf-document')}
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
id="document-upload"
|
||||||
|
accept=".pdf"
|
||||||
|
onChange={handleFileChange}
|
||||||
|
className="file-input file-input-bordered file-input-primary w-full max-w-xs"
|
||||||
|
/>
|
||||||
|
{selectedFile && (
|
||||||
|
<p className="mt-2 text-sm text-base-content w-full truncate">
|
||||||
|
{t('selected-file')}: {selectedFile.name}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="text-center mb-6">
|
||||||
|
<button
|
||||||
|
onClick={handleVerifyDocument}
|
||||||
|
className="op-btn op-btn-primary op-btn-md"
|
||||||
|
disabled={!selectedFile || verificationResult === t('verification-in-progress')}
|
||||||
|
>
|
||||||
|
{/* Removed jsrsasignStatus === 'loading' condition for spinner */}
|
||||||
|
{verificationResult === t('verification-in-progress') ? (
|
||||||
|
<span className="loading loading-spinner"></span>
|
||||||
|
) : (
|
||||||
|
t('verify-signature')
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{verificationResult && verificationResult !== t('verification-in-progress') && (
|
||||||
|
<div className="mt-8 p-6 border border-base-300 rounded-lg bg-base-200 shadow-md min-h-[120px] flex flex-col items-center justify-center">
|
||||||
|
<h2 className="text-2xl font-bold mb-4 text-base-content text-center">
|
||||||
|
{t('verification-status')}
|
||||||
|
</h2>
|
||||||
|
{verificationResult === "Document Verified: All signatures have been successfully validated." && (
|
||||||
|
<div className="flex flex-col items-center my-4">
|
||||||
|
<svg className="checkmark" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 52 52">
|
||||||
|
<circle className="checkmark__circle" cx="26" cy="26" r="25" fill="none"/>
|
||||||
|
<path className="checkmark__check" fill="none" d="M14.1 27.2l7.1 7.2 16.7-16.8"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<p className="text-lg text-base-content mb-4 text-center">{verificationResult}</p>
|
||||||
|
{detailedResults.length > 0 && (
|
||||||
|
<div className="w-full space-y-6">
|
||||||
|
{detailedResults.map((res, index) => {
|
||||||
|
const signerInfo = parseCertificateInfo(res.certificateSubject);
|
||||||
|
const issuerInfo = parseCertificateInfo(res.certificateIssuer);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={index} className="bg-white border border-gray-200 rounded-xl shadow-lg overflow-hidden">
|
||||||
|
{/* Header Section */}
|
||||||
|
<div className="bg-gradient-to-r from-blue-600 to-indigo-600 text-white p-6">
|
||||||
|
<div className="flex items-center space-x-3">
|
||||||
|
<span className="text-2xl">🔏</span>
|
||||||
|
<div>
|
||||||
|
<h4 className="text-xl font-bold">Signature Details</h4>
|
||||||
|
<p className="text-blue-100 text-sm">Digital Certificate Information</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Basic Info Section */}
|
||||||
|
<div className="p-6 border-b border-gray-100">
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<span className="text-sm font-medium text-gray-500 uppercase tracking-wide">Field Name</span>
|
||||||
|
<p className="mt-1 text-lg font-semibold text-gray-900 font-mono">{res.name}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className="text-sm font-medium text-gray-500 uppercase tracking-wide">Overall Status</span>
|
||||||
|
<div className="mt-1 flex items-center space-x-2">
|
||||||
|
<span className={`text-lg ${isSuccessStatus(res.status) ? 'text-green-600' : 'text-red-600'}`}>
|
||||||
|
{isSuccessStatus(res.status) ? '✅' : '❌'}
|
||||||
|
</span>
|
||||||
|
<span className="text-lg font-semibold text-gray-900">{res.status}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Signer Information Section */}
|
||||||
|
{Object.keys(signerInfo).length > 0 && (
|
||||||
|
<div className="border-b border-gray-100">
|
||||||
|
<button
|
||||||
|
onClick={() => toggleSection(index, 'signer')}
|
||||||
|
className="w-full px-6 py-4 text-left hover:bg-gray-50 transition-colors duration-200 focus:outline-none focus:bg-gray-50"
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex items-center space-x-3">
|
||||||
|
<span className="text-xl">📇</span>
|
||||||
|
<h5 className="text-lg font-semibold text-gray-900">Signer Information</h5>
|
||||||
|
</div>
|
||||||
|
<svg
|
||||||
|
className={`w-5 h-5 text-gray-400 transition-transform duration-200 ${
|
||||||
|
collapsedSections[`${index}-signer`] ? 'transform rotate-180' : ''
|
||||||
|
}`}
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
{!collapsedSections[`${index}-signer`] && (
|
||||||
|
<div className="px-6 pb-6">
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
|
{Object.entries(signerInfo).map(([label, value]) => (
|
||||||
|
<div key={label} className="bg-gray-50 rounded-lg p-4">
|
||||||
|
<span className="text-xs font-medium text-gray-500 uppercase tracking-wide">{label}</span>
|
||||||
|
<p className="mt-1 text-sm font-mono text-gray-900 break-all">{value}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Issuer Information Section */}
|
||||||
|
{Object.keys(issuerInfo).length > 0 && (
|
||||||
|
<div className="border-b border-gray-100">
|
||||||
|
<button
|
||||||
|
onClick={() => toggleSection(index, 'issuer')}
|
||||||
|
className="w-full px-6 py-4 text-left hover:bg-gray-50 transition-colors duration-200 focus:outline-none focus:bg-gray-50"
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex items-center space-x-3">
|
||||||
|
<span className="text-xl">🏢</span>
|
||||||
|
<h5 className="text-lg font-semibold text-gray-900">Issuer Details</h5>
|
||||||
|
</div>
|
||||||
|
<svg
|
||||||
|
className={`w-5 h-5 text-gray-400 transition-transform duration-200 ${
|
||||||
|
collapsedSections[`${index}-issuer`] ? 'transform rotate-180' : ''
|
||||||
|
}`}
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
{!collapsedSections[`${index}-issuer`] && (
|
||||||
|
<div className="px-6 pb-6">
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
|
{Object.entries(issuerInfo).map(([label, value]) => (
|
||||||
|
<div key={label} className="bg-gray-50 rounded-lg p-4">
|
||||||
|
<span className="text-xs font-medium text-gray-500 uppercase tracking-wide">{label}</span>
|
||||||
|
<p className="mt-1 text-sm font-mono text-gray-900 break-all">{value}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Certificate Validity Section */}
|
||||||
|
{res.certificateValidity && (
|
||||||
|
<div className="p-6 bg-gray-50">
|
||||||
|
<div className="flex items-center space-x-3 mb-4">
|
||||||
|
<span className="text-xl">🕒</span>
|
||||||
|
<h5 className="text-lg font-semibold text-gray-900">Certificate Validity</h5>
|
||||||
|
</div>
|
||||||
|
<div className="bg-white rounded-lg p-4 border">
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<span className={`text-lg ${isCertificateValid(res.certificateValidity) ? 'text-green-600' : 'text-red-600'}`}>
|
||||||
|
{isCertificateValid(res.certificateValidity) ? '✅' : '❌'}
|
||||||
|
</span>
|
||||||
|
<span className="text-sm font-mono text-gray-900">{res.certificateValidity}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Technical Details Section (if any) */}
|
||||||
|
{(res.calculatedDocumentHash || res.messageDigestInSignature || res.hashComparisonResult || res.authenticatedAttributesSignatureResult || res.errorDetails || res.certificateSubject || res.certificateIssuer) && (
|
||||||
|
<div className="p-6 bg-gray-50 border-t">
|
||||||
|
<details className="group">
|
||||||
|
<summary className="flex items-center justify-between cursor-pointer text-sm font-medium text-gray-700 hover:text-gray-900">
|
||||||
|
<span>🔧 Technical Details</span>
|
||||||
|
<svg className="w-4 h-4 transition-transform group-open:rotate-180" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
||||||
|
</svg>
|
||||||
|
</summary>
|
||||||
|
<div className="mt-4 space-y-3">
|
||||||
|
{/* Raw Certificate Data */}
|
||||||
|
{res.certificateSubject && (
|
||||||
|
<div className="bg-white rounded p-3 border">
|
||||||
|
<span className="text-xs font-medium text-gray-500 uppercase tracking-wide block mb-1">Raw Certificate Subject</span>
|
||||||
|
<code className="text-xs text-gray-800 break-all bg-gray-100 p-2 rounded block">{res.certificateSubject}</code>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{res.certificateIssuer && (
|
||||||
|
<div className="bg-white rounded p-3 border">
|
||||||
|
<span className="text-xs font-medium text-gray-500 uppercase tracking-wide block mb-1">Raw Certificate Issuer</span>
|
||||||
|
<code className="text-xs text-gray-800 break-all bg-gray-100 p-2 rounded block">{res.certificateIssuer}</code>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{res.calculatedDocumentHash && res.calculatedDocumentHash !== t('not-available') && res.calculatedDocumentHash !== t('not-calculated') && (
|
||||||
|
<div className="bg-white rounded p-3 border">
|
||||||
|
<span className="text-xs font-medium text-gray-500 uppercase tracking-wide block mb-1">Calculated Document Hash</span>
|
||||||
|
<code className="text-xs text-gray-800 break-all bg-gray-100 p-2 rounded block">{res.calculatedDocumentHash}</code>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{res.messageDigestInSignature && res.messageDigestInSignature !== t('not-available') && res.messageDigestInSignature !== t('not-found-in-signature') && (
|
||||||
|
<div className="bg-white rounded p-3 border">
|
||||||
|
<span className="text-xs font-medium text-gray-500 uppercase tracking-wide block mb-1">Message Digest in Signature</span>
|
||||||
|
<code className="text-xs text-gray-800 break-all bg-gray-100 p-2 rounded block">{res.messageDigestInSignature}</code>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{res.hashComparisonResult && res.hashComparisonResult !== t('not-performed') && (
|
||||||
|
<div className="bg-white rounded p-3 border">
|
||||||
|
<span className="text-xs font-medium text-gray-500 uppercase tracking-wide block mb-1">Hash Comparison</span>
|
||||||
|
<span className="text-sm text-gray-800">{res.hashComparisonResult}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{res.authenticatedAttributesSignatureResult && res.authenticatedAttributesSignatureResult !== t('not-performed') && (
|
||||||
|
<div className="bg-white rounded p-3 border">
|
||||||
|
<span className="text-xs font-medium text-gray-500 uppercase tracking-wide block mb-1">Attributes Signature Verification</span>
|
||||||
|
<span className="text-sm text-gray-800">{res.authenticatedAttributesSignatureResult}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{res.errorDetails && (
|
||||||
|
<div className="bg-red-50 border border-red-200 rounded p-3">
|
||||||
|
<span className="text-xs font-medium text-red-600 uppercase tracking-wide block mb-1">Error Details</span>
|
||||||
|
<span className="text-sm text-red-800">{res.errorDetails}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{verificationResult === t('verification-in-progress') && (
|
||||||
|
<div className="mt-8 p-4 border border-base-300 rounded-lg bg-base-200 min-h-[100px] flex justify-center items-center">
|
||||||
|
<span className="loading loading-lg loading-dots"></span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!verificationResult && !selectedFile && (
|
||||||
|
<div className="mt-8 p-4 border border-base-300 rounded-lg bg-base-200 min-h-[100px] flex justify-center items-center"> {/* Added flex for centering */}
|
||||||
|
<p className="text-base-content/60 italic text-center">{t('verification-results-will-appear-here')}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default VerifyDocument;
|
||||||
@@ -91,7 +91,7 @@ function DownloadPdfZip(props) {
|
|||||||
title={t("download-files")}
|
title={t("download-files")}
|
||||||
handleClose={() => props.setIsDownloadModal(false)}
|
handleClose={() => props.setIsDownloadModal(false)}
|
||||||
>
|
>
|
||||||
<div className="p-[20px] h-full">
|
<div className="p-[20px] h-full text-base-content">
|
||||||
{downloadType.map((data, ind) => {
|
{downloadType.map((data, ind) => {
|
||||||
return (
|
return (
|
||||||
<label
|
<label
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import ModalUi from "./ModalUi";
|
|||||||
import AddSigner from "../components/AddSigner";
|
import AddSigner from "../components/AddSigner";
|
||||||
import {
|
import {
|
||||||
emailRegex,
|
emailRegex,
|
||||||
|
iconColor,
|
||||||
} from "../constant/const";
|
} from "../constant/const";
|
||||||
import Alert from "./Alert";
|
import Alert from "./Alert";
|
||||||
import Tooltip from "./Tooltip";
|
import Tooltip from "./Tooltip";
|
||||||
@@ -38,9 +39,12 @@ import { useTranslation } from "react-i18next";
|
|||||||
import DownloadPdfZip from "./DownloadPdfZip";
|
import DownloadPdfZip from "./DownloadPdfZip";
|
||||||
import * as XLSX from "xlsx";
|
import * as XLSX from "xlsx";
|
||||||
import EditContactForm from "../components/EditContactForm";
|
import EditContactForm from "../components/EditContactForm";
|
||||||
|
import { useElSize } from "../hook/useElSize";
|
||||||
|
|
||||||
const ReportTable = (props) => {
|
const ReportTable = (props) => {
|
||||||
const copyUrlRef = useRef(null);
|
const copyUrlRef = useRef(null);
|
||||||
|
const titleRef = useRef(null);
|
||||||
|
const titleElement = useElSize(titleRef);
|
||||||
const appName =
|
const appName =
|
||||||
"OpenSign™";
|
"OpenSign™";
|
||||||
const drivename = appName === "OpenSign™" ? "OpenSign™" : "";
|
const drivename = appName === "OpenSign™" ? "OpenSign™" : "";
|
||||||
@@ -92,6 +96,12 @@ const ReportTable = (props) => {
|
|||||||
const startIndex = (currentPage - 1) * props.docPerPage;
|
const startIndex = (currentPage - 1) * props.docPerPage;
|
||||||
const { isMoreDocs, setIsNextRecord } = props;
|
const { isMoreDocs, setIsNextRecord } = props;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (props.isSearchResult) {
|
||||||
|
setCurrentPage(1);
|
||||||
|
}
|
||||||
|
}, [props.isSearchResult]);
|
||||||
|
|
||||||
const getPaginationRange = () => {
|
const getPaginationRange = () => {
|
||||||
const totalPageNumbers = 7; // Adjust this value to show more/less page numbers
|
const totalPageNumbers = 7; // Adjust this value to show more/less page numbers
|
||||||
const pages = [];
|
const pages = [];
|
||||||
@@ -679,7 +689,7 @@ const ReportTable = (props) => {
|
|||||||
receiver_phone: userDetails?.Phone || "",
|
receiver_phone: userDetails?.Phone || "",
|
||||||
expiry_date: localExpireDate,
|
expiry_date: localExpireDate,
|
||||||
company_name: doc.ExtUserPtr.Company,
|
company_name: doc.ExtUserPtr.Company,
|
||||||
signing_url: `<a href=${signPdf} target=_blank>Sign here</a>`
|
signing_url: signPdf
|
||||||
};
|
};
|
||||||
const res = replaceMailVaribles(subject, "", variables);
|
const res = replaceMailVaribles(subject, "", variables);
|
||||||
setMail((prev) => ({ ...prev, subject: res.subject }));
|
setMail((prev) => ({ ...prev, subject: res.subject }));
|
||||||
@@ -710,7 +720,7 @@ const ReportTable = (props) => {
|
|||||||
receiver_phone: userDetails?.Phone || "",
|
receiver_phone: userDetails?.Phone || "",
|
||||||
expiry_date: localExpireDate,
|
expiry_date: localExpireDate,
|
||||||
company_name: doc.ExtUserPtr.Company,
|
company_name: doc.ExtUserPtr.Company,
|
||||||
signing_url: `<a href=${signPdf} target=_blank>Sign here</a>`
|
signing_url: signPdf
|
||||||
};
|
};
|
||||||
const res = replaceMailVaribles("", body, variables);
|
const res = replaceMailVaribles("", body, variables);
|
||||||
|
|
||||||
@@ -754,7 +764,7 @@ const ReportTable = (props) => {
|
|||||||
receiver_phone: user?.signerPtr?.Phone || "",
|
receiver_phone: user?.signerPtr?.Phone || "",
|
||||||
expiry_date: localExpireDate,
|
expiry_date: localExpireDate,
|
||||||
company_name: doc?.ExtUserPtr?.Company || "",
|
company_name: doc?.ExtUserPtr?.Company || "",
|
||||||
signing_url: `<a href=${signPdf} target=_blank>Sign here</a>`
|
signing_url: signPdf
|
||||||
};
|
};
|
||||||
const subject =
|
const subject =
|
||||||
doc?.RequestSubject ||
|
doc?.RequestSubject ||
|
||||||
@@ -763,7 +773,7 @@ const ReportTable = (props) => {
|
|||||||
const body =
|
const body =
|
||||||
doc?.RequestBody ||
|
doc?.RequestBody ||
|
||||||
doc?.ExtUserPtr?.TenantId?.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);
|
const res = replaceMailVaribles(subject, body, variables);
|
||||||
setMail((prev) => ({ ...prev, subject: res.subject, body: res.body }));
|
setMail((prev) => ({ ...prev, subject: res.subject, body: res.body }));
|
||||||
setIsNextStep({ [user.Id]: true });
|
setIsNextStep({ [user.Id]: true });
|
||||||
@@ -907,7 +917,6 @@ const ReportTable = (props) => {
|
|||||||
setActLoader({});
|
setActLoader({});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUpdateExpiry = async (e, item) => {
|
const handleUpdateExpiry = async (e, item) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@@ -999,7 +1008,7 @@ const ReportTable = (props) => {
|
|||||||
? "op-border-primary op-text-primary"
|
? "op-border-primary op-text-primary"
|
||||||
: x.Activity === "VIEWED"
|
: x.Activity === "VIEWED"
|
||||||
? "border-green-400 text-green-400"
|
? "border-green-400 text-green-400"
|
||||||
: "border-black text-black"
|
: "border-base-content text-base-content"
|
||||||
} focus:outline-none border-2 w-[60px] h-[30px] text-[11px] rounded-full`}
|
} focus:outline-none border-2 w-[60px] h-[30px] text-[11px] rounded-full`}
|
||||||
>
|
>
|
||||||
{x?.Activity?.toUpperCase() || "-"}
|
{x?.Activity?.toUpperCase() || "-"}
|
||||||
@@ -1314,7 +1323,6 @@ const ReportTable = (props) => {
|
|||||||
try {
|
try {
|
||||||
const params = { docId: doc?.objectId };
|
const params = { docId: doc?.objectId };
|
||||||
const templateRes = await Parse.Cloud.run("saveastemplate", params);
|
const templateRes = await Parse.Cloud.run("saveastemplate", params);
|
||||||
// console.log("templateRes ", templateRes);
|
|
||||||
setTemplateId(templateRes?.id);
|
setTemplateId(templateRes?.id);
|
||||||
setIsSuccess({ [doc.objectId]: true });
|
setIsSuccess({ [doc.objectId]: true });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -1394,6 +1402,12 @@ const ReportTable = (props) => {
|
|||||||
setActLoader({});
|
setActLoader({});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const restrictBtn = (item, act) => {
|
||||||
|
return item.IsSignyourself && act.action === "recreatedocument"
|
||||||
|
? true
|
||||||
|
: false;
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
{Object.keys(actLoader)?.length > 0 && (
|
{Object.keys(actLoader)?.length > 0 && (
|
||||||
@@ -1416,7 +1430,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">
|
<div className="font-light">
|
||||||
{t(`report-name.${props.ReportName}`)}{" "}
|
{t(`report-name.${props.ReportName}`)}{" "}
|
||||||
{props.report_help && (
|
{props.report_help && (
|
||||||
@@ -1428,26 +1445,68 @@ const ReportTable = (props) => {
|
|||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row justify-center items-center gap-3">
|
<div className="flex flex-row justify-center items-center gap-3 mb-2">
|
||||||
{props.isImport && (
|
{/* Search input for report bigger in width */}
|
||||||
<div className="cursor-pointer" onClick={() => handleImportBtn()}>
|
{titleElement?.width > 500 && (
|
||||||
<i className="fa-light fa-upload op-text-secondary text-[23px] md:text-[30px]"></i>
|
<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>
|
</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 && (
|
{props.form && (
|
||||||
<div
|
<div
|
||||||
className="cursor-pointer"
|
className="cursor-pointer flex"
|
||||||
onClick={() => handleContactFormModal()}
|
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>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{/* create template form */}
|
||||||
{isTemplateReport && (
|
{isTemplateReport && (
|
||||||
<i
|
<div
|
||||||
data-tut="reactourFirst"
|
data-tut="reactourFirst"
|
||||||
|
className="cursor-pointer flex"
|
||||||
onClick={() => navigate("/form/template")}
|
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
|
<ModalUi
|
||||||
isOpen={isModal?.export}
|
isOpen={isModal?.export}
|
||||||
@@ -1536,6 +1595,19 @@ const ReportTable = (props) => {
|
|||||||
</ModalUi>
|
</ModalUi>
|
||||||
</div>
|
</div>
|
||||||
</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
|
<div
|
||||||
className={`overflow-auto w-full border-b ${
|
className={`overflow-auto w-full border-b ${
|
||||||
props.List?.length > 0
|
props.List?.length > 0
|
||||||
@@ -1602,7 +1674,7 @@ const ReportTable = (props) => {
|
|||||||
handleClose={handleClose}
|
handleClose={handleClose}
|
||||||
>
|
>
|
||||||
<div className="m-[20px]">
|
<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")}
|
{t("contact-delete-alert")}
|
||||||
</div>
|
</div>
|
||||||
<hr className="bg-[#ccc] mt-4 " />
|
<hr className="bg-[#ccc] mt-4 " />
|
||||||
@@ -1837,33 +1909,39 @@ const ReportTable = (props) => {
|
|||||||
{isOption[item.objectId] &&
|
{isOption[item.objectId] &&
|
||||||
act.action === "option" && (
|
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">
|
<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) => (
|
{act.subaction?.map(
|
||||||
<li
|
(subact) =>
|
||||||
key={subact.btnId}
|
!restrictBtn(
|
||||||
onClick={() =>
|
item,
|
||||||
handleActionBtn(
|
subact
|
||||||
subact,
|
) && (
|
||||||
item
|
<li
|
||||||
)
|
key={subact.btnId}
|
||||||
}
|
onClick={() =>
|
||||||
title={t(
|
handleActionBtn(
|
||||||
`btnLabel.${subact.hoverLabel}`
|
subact,
|
||||||
)}
|
item
|
||||||
>
|
)
|
||||||
<span>
|
}
|
||||||
<i
|
title={t(
|
||||||
className={`${subact.btnIcon} mr-1.5`}
|
`btnLabel.${subact.hoverLabel}`
|
||||||
></i>
|
)}
|
||||||
{subact.btnLabel && (
|
>
|
||||||
<span className="text-[13px] capitalize font-medium">
|
<span>
|
||||||
{t(
|
<i
|
||||||
`btnLabel.${subact.btnLabel}`
|
className={`${subact.btnIcon} mr-1.5`}
|
||||||
|
></i>
|
||||||
|
{subact.btnLabel && (
|
||||||
|
<span className="text-[13px] capitalize font-medium">
|
||||||
|
{t(
|
||||||
|
`btnLabel.${subact.btnLabel}`
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
)}
|
</li>
|
||||||
</span>
|
)
|
||||||
</li>
|
)}
|
||||||
))}
|
|
||||||
</ul>
|
</ul>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -1972,7 +2050,7 @@ const ReportTable = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="m-[20px]">
|
<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-?")}
|
{t("save-as-template-?")}
|
||||||
</div>
|
</div>
|
||||||
<hr className="bg-[#ccc] mt-3" />
|
<hr className="bg-[#ccc] mt-3" />
|
||||||
@@ -2010,7 +2088,7 @@ const ReportTable = (props) => {
|
|||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="date"
|
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={
|
defaultValue={
|
||||||
item?.ExpiryDate?.iso?.split("T")?.[0]
|
item?.ExpiryDate?.iso?.split("T")?.[0]
|
||||||
}
|
}
|
||||||
@@ -2120,7 +2198,7 @@ const ReportTable = (props) => {
|
|||||||
handleClose={handleClose}
|
handleClose={handleClose}
|
||||||
>
|
>
|
||||||
<div className="m-[20px]">
|
<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")}
|
{t("delete-document-alert")}
|
||||||
</div>
|
</div>
|
||||||
<hr className="bg-[#ccc] mt-4" />
|
<hr className="bg-[#ccc] mt-4" />
|
||||||
@@ -2177,7 +2255,7 @@ const ReportTable = (props) => {
|
|||||||
{shareUrls.map((share, i) => (
|
{shareUrls.map((share, i) => (
|
||||||
<div
|
<div
|
||||||
key={i}
|
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">
|
<span className="w-[150px] mr-[5px] md:mr-0 md:w-[300px] whitespace-nowrap overflow-hidden text-ellipsis text-sm font-semibold">
|
||||||
{share.email}
|
{share.email}
|
||||||
@@ -2219,14 +2297,14 @@ const ReportTable = (props) => {
|
|||||||
handleClose={handleClose}
|
handleClose={handleClose}
|
||||||
>
|
>
|
||||||
<div className="m-[20px]">
|
<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")}
|
{t("revoke-document-alert")}
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-2">
|
<div className="mt-2">
|
||||||
<textarea
|
<textarea
|
||||||
rows={3}
|
rows={3}
|
||||||
placeholder="Reason (optional)"
|
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}
|
value={reason}
|
||||||
onChange={(e) => setReason(e.target.value)}
|
onChange={(e) => setReason(e.target.value)}
|
||||||
></textarea>
|
></textarea>
|
||||||
@@ -2337,7 +2415,7 @@ const ReportTable = (props) => {
|
|||||||
)}
|
)}
|
||||||
{Object?.keys(isNextStep) <= 0 && (
|
{Object?.keys(isNextStep) <= 0 && (
|
||||||
<div className="flex justify-between items-center gap-2 my-2 px-3">
|
<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?.signerPtr?.Name || "-"}{" "}
|
||||||
{`<${
|
{`<${
|
||||||
user?.email
|
user?.email
|
||||||
|
|||||||
@@ -25,12 +25,10 @@ const LinkUserModal = (props) => {
|
|||||||
handleClose={props.closePopup}
|
handleClose={props.closePopup}
|
||||||
>
|
>
|
||||||
<SelectSigners
|
<SelectSigners
|
||||||
details={props.handleAddUser}
|
{...props}
|
||||||
closePopup={props.closePopup}
|
closePopup={props.closePopup}
|
||||||
signersData={props?.signersData}
|
|
||||||
isContact={isContact}
|
isContact={isContact}
|
||||||
setIsContact={setIsContact}
|
setIsContact={setIsContact}
|
||||||
handleUnlinkSigner={props.handleUnlinkSigner}
|
|
||||||
isExistSigner={isExistSigner}
|
isExistSigner={isExistSigner}
|
||||||
/>
|
/>
|
||||||
{isContact && (
|
{isContact && (
|
||||||
|
|||||||
@@ -8,13 +8,18 @@ const ModalUi = ({
|
|||||||
handleClose,
|
handleClose,
|
||||||
showHeader = true,
|
showHeader = true,
|
||||||
showClose = true,
|
showClose = true,
|
||||||
reduceWidth
|
reduceWidth,
|
||||||
|
position
|
||||||
}) => {
|
}) => {
|
||||||
const width = reduceWidth;
|
const width = reduceWidth;
|
||||||
|
const isBottom = position === "bottom" ? "items-end pb-2" : "";
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
<dialog id="selectSignerModal" className="op-modal op-modal-open">
|
<dialog
|
||||||
|
id="selectSignerModal"
|
||||||
|
className={`${isBottom} op-modal op-modal-open`}
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
className={`${
|
className={`${
|
||||||
width || "md:min-w-[500px]"
|
width || "md:min-w-[500px]"
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ function CustomModal(props) {
|
|||||||
</h3>
|
</h3>
|
||||||
{!isExtendExpiry && (
|
{!isExtendExpiry && (
|
||||||
<div className="p-[10px] px-[20px]">
|
<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>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!isExtendExpiry && (
|
{!isExtendExpiry && (
|
||||||
@@ -68,7 +68,7 @@ function CustomModal(props) {
|
|||||||
)}
|
)}
|
||||||
{props.footerMessage && (
|
{props.footerMessage && (
|
||||||
<>
|
<>
|
||||||
<div className="mx-3">
|
<div className="mx-3 text-base-content">
|
||||||
<textarea
|
<textarea
|
||||||
rows={3}
|
rows={3}
|
||||||
placeholder="Reason (optional)"
|
placeholder="Reason (optional)"
|
||||||
@@ -108,7 +108,7 @@ function CustomModal(props) {
|
|||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="date"
|
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]}
|
defaultValue={props?.doc?.ExpiryDate?.iso?.split("T")?.[0]}
|
||||||
onChange={(e) => setExpiryDate(e.target.value)}
|
onChange={(e) => setExpiryDate(e.target.value)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -10,6 +10,10 @@
|
|||||||
background-position: right 0.7rem top 50%;
|
background-position: right 0.7rem top 50%;
|
||||||
background-size: 1rem auto;
|
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) {
|
@media (max-width: 375px) {
|
||||||
.validationlist {
|
.validationlist {
|
||||||
@@ -17,7 +21,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width:375px) and (max-width: 767px) {
|
@media (min-width: 375px) and (max-width: 767px) {
|
||||||
.validationlist {
|
.validationlist {
|
||||||
background-position: right 1rem top 50%;
|
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 {
|
.customwarning {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
|
|||||||
@@ -204,6 +204,15 @@ a {
|
|||||||
align-items: center;
|
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"] {
|
.HoverCardContent[data-side="top"] {
|
||||||
animation-name: slideDownAndFade;
|
animation-name: slideDownAndFade;
|
||||||
}
|
}
|
||||||
@@ -224,6 +233,11 @@ a {
|
|||||||
fill: white;
|
fill: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Dark mode support for HoverCard arrow */
|
||||||
|
[data-theme="opensigndark"] .HoverCardArrow {
|
||||||
|
fill: #1F2937;
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes slideUpAndFade {
|
@keyframes slideUpAndFade {
|
||||||
0% {
|
0% {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
.react-datepicker__input-container {
|
.react-datepicker__input-container {
|
||||||
position: initial !important;
|
position: initial !important;
|
||||||
}
|
}
|
||||||
.select-none-cls{
|
.select-none-cls {
|
||||||
-webkit-user-select: none;
|
-webkit-user-select: none;
|
||||||
/* Disable text selection in WebKit browsers */
|
/* Disable text selection in WebKit browsers */
|
||||||
-moz-user-select: none;
|
-moz-user-select: none;
|
||||||
@@ -33,10 +33,17 @@
|
|||||||
width: 440px;
|
width: 440px;
|
||||||
height: 167px;
|
height: 167px;
|
||||||
}
|
}
|
||||||
.tabWidth{
|
.tabWidth {
|
||||||
|
border: 1px solid #f3f4f6;
|
||||||
|
background-color: #f3f4f6;
|
||||||
width: 440px;
|
width: 440px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[data-theme="opensigndark"] .tabWidth {
|
||||||
|
border: 1px solid #1f2937 !important;
|
||||||
|
background-color: #1f2937 !important;
|
||||||
|
}
|
||||||
|
|
||||||
.intialSignatureCanvas {
|
.intialSignatureCanvas {
|
||||||
width: 150px;
|
width: 150px;
|
||||||
height: 150px;
|
height: 150px;
|
||||||
@@ -52,7 +59,6 @@
|
|||||||
background-color: #111111; /* blue-500 */
|
background-color: #111111; /* blue-500 */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.intialSignature {
|
.intialSignature {
|
||||||
border: 2px solid #888;
|
border: 2px solid #888;
|
||||||
background-color: rgb(255, 255, 255);
|
background-color: rgb(255, 255, 255);
|
||||||
@@ -60,6 +66,40 @@
|
|||||||
height: 183px;
|
height: 183px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 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;
|
||||||
|
}
|
||||||
|
|
||||||
.penContainerDefault {
|
.penContainerDefault {
|
||||||
width: 460px;
|
width: 460px;
|
||||||
}
|
}
|
||||||
@@ -401,7 +441,7 @@ option {
|
|||||||
width: 300px;
|
width: 300px;
|
||||||
height: 120px;
|
height: 120px;
|
||||||
}
|
}
|
||||||
.tabWidth{
|
.tabWidth {
|
||||||
width: 300px;
|
width: 300px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -427,7 +467,7 @@ option {
|
|||||||
width: 280px;
|
width: 280px;
|
||||||
height: 112px;
|
height: 112px;
|
||||||
}
|
}
|
||||||
.tabWidth{
|
.tabWidth {
|
||||||
width: 280px;
|
width: 280px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -451,7 +491,7 @@ option {
|
|||||||
width: 230px;
|
width: 230px;
|
||||||
height: 92px;
|
height: 92px;
|
||||||
}
|
}
|
||||||
.tabWidth{
|
.tabWidth {
|
||||||
width: 230px;
|
width: 230px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,11 +6,67 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
require("daisyui"),
|
require("daisyui"),
|
||||||
function ({ addUtilities }) {
|
function ({ addUtilities, theme }) {
|
||||||
addUtilities({
|
addUtilities({
|
||||||
// Prevent iOS long-press popup
|
// Prevent iOS long-press popup
|
||||||
".touch-callout-none": {
|
".touch-callout-none": {
|
||||||
"-webkit-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: {
|
daisyui: {
|
||||||
// themes: true,
|
// themes: true,
|
||||||
themes: [
|
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: {
|
opensigncss: {
|
||||||
primary: "#002864",
|
primary: "#002864",
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
# Use an official Node runtime as the base image
|
|
||||||
FROM node:20
|
|
||||||
|
|
||||||
# Set the working directory inside the container
|
|
||||||
WORKDIR /usr/src/app
|
|
||||||
|
|
||||||
# Copy package.json and package-lock.json first to leverage Docker cache
|
|
||||||
COPY ./package*.json ./
|
|
||||||
|
|
||||||
# Install application dependencies
|
|
||||||
RUN npm install
|
|
||||||
|
|
||||||
# If you have native dependencies, you'll need extra tools. Uncomment the following line if needed.
|
|
||||||
# RUN apk add --no-cache make gcc g++ python3
|
|
||||||
|
|
||||||
# Copy the current directory contents into the container
|
|
||||||
COPY ./ .
|
|
||||||
|
|
||||||
# Make port 8080 available to the world outside this container
|
|
||||||
EXPOSE 8080
|
|
||||||
|
|
||||||
# Define environment variables if needed
|
|
||||||
# ENV NODE_ENV production
|
|
||||||
# ENV DATABASE_URL mongodb://db:27017
|
|
||||||
|
|
||||||
# Run the application
|
|
||||||
ENTRYPOINT npm start
|
|
||||||
@@ -2,6 +2,11 @@
|
|||||||
FROM node:22.14.0
|
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
|
# Set the working directory inside the container
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
|
|||||||
@@ -252,7 +252,7 @@ export const mailTemplate = param => {
|
|||||||
"</td></tr><tr><td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Note</td><td></td><td style='color:#626363;font-weight:bold'>" +
|
"</td></tr><tr><td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Note</td><td></td><td style='color:#626363;font-weight:bold'>" +
|
||||||
param.note +
|
param.note +
|
||||||
"</td></tr><tr><td></td><td></td></tr></table></div> <div style='margin-left:70px'><a target=_blank href=" +
|
"</td></tr><tr><td></td><td></td></tr></table></div> <div style='margin-left:70px'><a target=_blank href=" +
|
||||||
param.sigingUrl +
|
param.signingUrl +
|
||||||
"><button style='padding:12px;background-color:#d46b0f;color:white;border:0px;font-weight:bold;margin-top:30px'>Sign here</button></a></div><div style='display:flex;justify-content:center;margin-top:10px'></div></div></div><div><p> This is an automated email from " +
|
"><button style='padding:12px;background-color:#d46b0f;color:white;border:0px;font-weight:bold;margin-top:30px'>Sign here</button></a></div><div style='display:flex;justify-content:center;margin-top:10px'></div></div></div><div><p> This is an automated email from " +
|
||||||
AppName +
|
AppName +
|
||||||
'. For any queries regarding this email, please contact the sender ' +
|
'. For any queries regarding this email, please contact the sender ' +
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import express from 'express';
|
|||||||
import cors from 'cors';
|
import cors from 'cors';
|
||||||
import dotenv from 'dotenv';
|
import dotenv from 'dotenv';
|
||||||
import uploadFile from './uploadFile.js';
|
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();
|
export const app = express();
|
||||||
|
|
||||||
@@ -11,4 +13,5 @@ app.use(express.json({ limit: '50mb' }));
|
|||||||
app.use(express.urlencoded({ limit: '50mb', extended: true }));
|
app.use(express.urlencoded({ limit: '50mb', extended: true }));
|
||||||
|
|
||||||
app.post('/file_upload', uploadFile);
|
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 } 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 = process.env.APP_ID;
|
||||||
|
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 });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,9 +4,9 @@ export default async function Newsletter(request) {
|
|||||||
const email = request.params?.email?.toLowerCase()?.replace(/\s/g, '');
|
const email = request.params?.email?.toLowerCase()?.replace(/\s/g, '');
|
||||||
const domain = request.params.domain;
|
const domain = request.params.domain;
|
||||||
try {
|
try {
|
||||||
const envAppId = process.env.REACT_APP_APPID || 'opensign';
|
const envAppId = 'opensign';
|
||||||
const headers = { 'Content-Type': 'application/json', 'X-Parse-Application-Id': envAppId };
|
const headers = { 'Content-Type': 'application/json', 'X-Parse-Application-Id': envAppId };
|
||||||
const envProdServer = process.env.REACT_APP_SERVERURL || 'https://app.opensignlabs.com/api/app';
|
const envProdServer = 'https://app.opensignlabs.com/api/app';
|
||||||
const newsletter = await axios.post(
|
const newsletter = await axios.post(
|
||||||
`${envProdServer}/classes/Newsletter`,
|
`${envProdServer}/classes/Newsletter`,
|
||||||
{ Name: name, Email: email, Domain: domain },
|
{ Name: name, Email: email, Domain: domain },
|
||||||
|
|||||||
@@ -14,9 +14,7 @@ async function deductcount(docsCount, extUserId) {
|
|||||||
}
|
}
|
||||||
async function sendMail(document, publicUrl) {
|
async function sendMail(document, publicUrl) {
|
||||||
//sessionToken
|
//sessionToken
|
||||||
const baseUrl = new URL(publicUrl); //process.env.PUBLIC_URL
|
const baseUrl = new URL(publicUrl);
|
||||||
|
|
||||||
// console.log("pdfDetails", pdfDetails);
|
|
||||||
const timeToCompleteDays = document?.TimeToCompleteDays || 15;
|
const timeToCompleteDays = document?.TimeToCompleteDays || 15;
|
||||||
const ExpireDate = new Date(document.createdAt);
|
const ExpireDate = new Date(document.createdAt);
|
||||||
ExpireDate.setDate(ExpireDate.getDate() + timeToCompleteDays);
|
ExpireDate.setDate(ExpireDate.getDate() + timeToCompleteDays);
|
||||||
@@ -71,7 +69,7 @@ async function sendMail(document, publicUrl) {
|
|||||||
receiver_phone: existSigner?.Phone || '',
|
receiver_phone: existSigner?.Phone || '',
|
||||||
expiry_date: localExpireDate,
|
expiry_date: localExpireDate,
|
||||||
company_name: orgName,
|
company_name: orgName,
|
||||||
signing_url: `<a href=${signPdf} target=_blank>Sign here</a>`,
|
signing_url: signPdf,
|
||||||
};
|
};
|
||||||
replaceVar = replaceMailVaribles(mailSubject, htmlReqBody, variables);
|
replaceVar = replaceMailVaribles(mailSubject, htmlReqBody, variables);
|
||||||
}
|
}
|
||||||
@@ -82,7 +80,7 @@ async function sendMail(document, publicUrl) {
|
|||||||
title: document.Name,
|
title: document.Name,
|
||||||
organization: orgName,
|
organization: orgName,
|
||||||
localExpireDate: localExpireDate,
|
localExpireDate: localExpireDate,
|
||||||
sigingUrl: signPdf,
|
signingUrl: signPdf,
|
||||||
};
|
};
|
||||||
let params = {
|
let params = {
|
||||||
extUserId: document.ExtUserPtr.objectId,
|
extUserId: document.ExtUserPtr.objectId,
|
||||||
@@ -168,9 +166,9 @@ async function batchQuery(userId, Documents, Ip, parseConfig, type, publicUrl) {
|
|||||||
})),
|
})),
|
||||||
ACL: Acl,
|
ACL: Acl,
|
||||||
SentToOthers: true,
|
SentToOthers: true,
|
||||||
RemindOnceInEvery: x.RemindOnceInEvery || 5,
|
RemindOnceInEvery: x.RemindOnceInEvery ? parseInt(x.RemindOnceInEvery) : 5,
|
||||||
AutomaticReminders: x.AutomaticReminders || false,
|
AutomaticReminders: x.AutomaticReminders || false,
|
||||||
TimeToCompleteDays: x.TimeToCompleteDays || 15,
|
TimeToCompleteDays: x.TimeToCompleteDays ? parseInt(x.TimeToCompleteDays) : 15,
|
||||||
OriginIp: Ip,
|
OriginIp: Ip,
|
||||||
DocSentAt: { __type: 'Date', iso: isoDate },
|
DocSentAt: { __type: 'Date', iso: isoDate },
|
||||||
IsEnableOTP: x?.IsEnableOTP || false,
|
IsEnableOTP: x?.IsEnableOTP || false,
|
||||||
@@ -231,6 +229,7 @@ export default async function createBatchDocs(request) {
|
|||||||
const sessionToken = request.headers?.sessiontoken;
|
const sessionToken = request.headers?.sessiontoken;
|
||||||
const type = request.headers?.type || 'quicksend';
|
const type = request.headers?.type || 'quicksend';
|
||||||
const Documents = JSON.parse(strDocuments);
|
const Documents = JSON.parse(strDocuments);
|
||||||
|
|
||||||
const Ip = request?.headers?.['x-real-ip'] || '';
|
const Ip = request?.headers?.['x-real-ip'] || '';
|
||||||
// Access the host from the headers
|
// Access the host from the headers
|
||||||
const publicUrl = request.headers.public_url;
|
const publicUrl = request.headers.public_url;
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export default async function generateCertificatebydocId(req) {
|
|||||||
const certificate = await GenerateCertificate(doc);
|
const certificate = await GenerateCertificate(doc);
|
||||||
const certificatePdf = await PDFDocument.load(certificate);
|
const certificatePdf = await PDFDocument.load(certificate);
|
||||||
const p12 = new P12Signer(P12Buffer, { passphrase: process.env.PASS_PHRASE || null });
|
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({
|
pdflibAddPlaceholder({
|
||||||
pdfDoc: certificatePdf,
|
pdfDoc: certificatePdf,
|
||||||
reason: `Digitally signed by ${eSignName}.`,
|
reason: `Digitally signed by ${eSignName}.`,
|
||||||
|
|||||||
@@ -2,19 +2,26 @@ import { cloudServerUrl } from '../../Utils.js';
|
|||||||
import reportJson from './reportsJson.js';
|
import reportJson from './reportsJson.js';
|
||||||
import axios from 'axios';
|
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) {
|
export default async function getReport(request) {
|
||||||
const reportId = request.params.reportId;
|
const reportId = request.params.reportId;
|
||||||
const limit = request.params.limit;
|
const limit = request.params.limit;
|
||||||
const skip = request.params.skip;
|
const skip = request.params.skip;
|
||||||
|
const searchTerm = request.params.searchTerm || '';
|
||||||
|
|
||||||
const serverUrl = cloudServerUrl; //process.env.SERVER_URL;
|
const serverUrl = cloudServerUrl; //process.env.SERVER_URL;
|
||||||
const appId = process.env.APP_ID;
|
const appId = process.env.APP_ID;
|
||||||
const masterKey = process.env.MASTER_KEY;
|
const masterKey = process.env.MASTER_KEY;
|
||||||
|
const sessionToken = request.headers['sessiontoken'] || request.headers['x-parse-session-token'];
|
||||||
try {
|
try {
|
||||||
const userRes = await axios.get(serverUrl + '/users/me', {
|
const userRes = await axios.get(serverUrl + '/users/me', {
|
||||||
headers: {
|
headers: {
|
||||||
'X-Parse-Application-Id': appId,
|
'X-Parse-Application-Id': appId,
|
||||||
'X-Parse-Session-Token': request.headers['sessiontoken'],
|
'X-Parse-Session-Token': sessionToken,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const userId = userRes.data && userRes.data.objectId;
|
const userId = userRes.data && userRes.data.objectId;
|
||||||
@@ -25,7 +32,7 @@ export default async function getReport(request) {
|
|||||||
const { params, keys } = json;
|
const { params, keys } = json;
|
||||||
const orderBy = '-updatedAt';
|
const orderBy = '-updatedAt';
|
||||||
const strKeys = keys.join();
|
const strKeys = keys.join();
|
||||||
let strParams = JSON.stringify(params);
|
let paramsObj = { ...params };
|
||||||
if (reportId == '6TeaPr321t') {
|
if (reportId == '6TeaPr321t') {
|
||||||
const extUserQuery = new Parse.Query('contracts_Users');
|
const extUserQuery = new Parse.Query('contracts_Users');
|
||||||
extUserQuery.equalTo('Email', userRes.data.email);
|
extUserQuery.equalTo('Email', userRes.data.email);
|
||||||
@@ -36,8 +43,8 @@ export default async function getReport(request) {
|
|||||||
if (_extUser?.TeamIds && _extUser.TeamIds?.length > 0) {
|
if (_extUser?.TeamIds && _extUser.TeamIds?.length > 0) {
|
||||||
let teamArr = [];
|
let teamArr = [];
|
||||||
_extUser?.TeamIds?.forEach(x => (teamArr = [...teamArr, ...x.Ancestors]));
|
_extUser?.TeamIds?.forEach(x => (teamArr = [...teamArr, ...x.Ancestors]));
|
||||||
strParams = JSON.stringify({
|
paramsObj = {
|
||||||
...params,
|
...paramsObj,
|
||||||
$or: [
|
$or: [
|
||||||
{ SharedWith: { $in: teamArr } },
|
{ SharedWith: { $in: teamArr } },
|
||||||
{
|
{
|
||||||
@@ -55,15 +62,23 @@ export default async function getReport(request) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
};
|
||||||
} else {
|
} else {
|
||||||
strParams = JSON.stringify({
|
paramsObj = {
|
||||||
...params,
|
...paramsObj,
|
||||||
CreatedBy: { __type: 'Pointer', className: '_User', objectId: userId },
|
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 = {
|
const headers = {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'X-Parse-Application-Id': appId,
|
'X-Parse-Application-Id': appId,
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ async function sendNotifyMail(doc, signUser, mailProvider, publicUrl) {
|
|||||||
const creatorEmail = doc.ExtUserPtr.Email;
|
const creatorEmail = doc.ExtUserPtr.Email;
|
||||||
const signerName = signUser.Name;
|
const signerName = signUser.Name;
|
||||||
const signerEmail = signUser.Email;
|
const signerEmail = signUser.Email;
|
||||||
const viewDocUrl = `${publicUrl}/recipientSignPdf/${doc.objectId}`; // ` ${process.env.PUBLIC_URL}/recipientSignPdf/${doc.objectId}`;
|
const viewDocUrl = `${publicUrl}/recipientSignPdf/${doc.objectId}`;
|
||||||
const subject = `Document "${pdfName}" has been signed by ${signerName}`;
|
const subject = `Document "${pdfName}" has been signed by ${signerName}`;
|
||||||
const body =
|
const body =
|
||||||
"<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8'/></head><body><div style='background-color:#f5f5f5;padding:20px'><div style='background-color:white'>" +
|
"<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8'/></head><body><div style='background-color:#f5f5f5;padding:20px'><div style='background-color:white'>" +
|
||||||
|
|||||||
@@ -14,11 +14,14 @@ export default async function recreateDocument(request) {
|
|||||||
if (!doc) {
|
if (!doc) {
|
||||||
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Document not found');
|
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Document not found');
|
||||||
}
|
}
|
||||||
|
if (doc?.get('IsSignyourself')) {
|
||||||
|
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Signyourself Document not allowed');
|
||||||
|
}
|
||||||
const _docRes = doc?.toJSON();
|
const _docRes = doc?.toJSON();
|
||||||
const { objectId, SignedUrl, AuditTrail, ACL, DeclineBy, DeclineReason, ...docRes } = _docRes;
|
const { objectId, SignedUrl, AuditTrail, ACL, DeclineBy, DeclineReason, ...docRes } = _docRes;
|
||||||
const createDoc = new Parse.Object('contracts_Document');
|
const createDoc = new Parse.Object('contracts_Document');
|
||||||
Object.entries(docRes).forEach(([key, value]) => {
|
Object.entries(docRes).forEach(([key, value]) => {
|
||||||
if (key === 'IsDeclined') {
|
if (key === 'IsDeclined' || key === 'IsCompleted') {
|
||||||
createDoc.set(key, false);
|
createDoc.set(key, false);
|
||||||
} else {
|
} else {
|
||||||
createDoc.set(key, value);
|
createDoc.set(key, value);
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ export default function reportJson(id, userId) {
|
|||||||
'ExpiryDate',
|
'ExpiryDate',
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
// In progess report
|
// In progress report
|
||||||
case '1MwEuxLEkF':
|
case '1MwEuxLEkF':
|
||||||
return {
|
return {
|
||||||
reportName: 'In-progress documents',
|
reportName: 'In-progress documents',
|
||||||
@@ -123,6 +123,8 @@ export default function reportJson(id, userId) {
|
|||||||
'TimeToCompleteDays',
|
'TimeToCompleteDays',
|
||||||
'IsSignyourself',
|
'IsSignyourself',
|
||||||
'IsCompleted',
|
'IsCompleted',
|
||||||
|
'ExpiryDate',
|
||||||
|
'IsSignyourself',
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
// declined documents report
|
// declined documents report
|
||||||
|
|||||||
@@ -51,6 +51,17 @@ export default async function saveAsTemplate(request) {
|
|||||||
|
|
||||||
if (_docRes?.Placeholders?.length > 0) {
|
if (_docRes?.Placeholders?.length > 0) {
|
||||||
if (_docRes?.IsSignyourself) {
|
if (_docRes?.IsSignyourself) {
|
||||||
|
//add required option for all widget when save as template using signyour-self draft document
|
||||||
|
const updatedPlaceholder = _docRes?.Placeholders.map(pageItem => ({
|
||||||
|
...pageItem,
|
||||||
|
pos: pageItem.pos.map(p => ({
|
||||||
|
...p,
|
||||||
|
options: {
|
||||||
|
...p.options,
|
||||||
|
status: 'required',
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
}));
|
||||||
const placeHolders = {
|
const placeHolders = {
|
||||||
signerObjId: '',
|
signerObjId: '',
|
||||||
signerPtr: {},
|
signerPtr: {},
|
||||||
@@ -58,7 +69,7 @@ export default async function saveAsTemplate(request) {
|
|||||||
blockColor: '#93a3db',
|
blockColor: '#93a3db',
|
||||||
Role: 'Role 1',
|
Role: 'Role 1',
|
||||||
email: '',
|
email: '',
|
||||||
placeHolder: _docRes?.Placeholders,
|
placeHolder: updatedPlaceholder,
|
||||||
};
|
};
|
||||||
templateCls.set('Placeholders', [placeHolders]);
|
templateCls.set('Placeholders', [placeHolders]);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -53,10 +53,16 @@ const makeEmail = async (
|
|||||||
const isSecure =
|
const isSecure =
|
||||||
new URL(url)?.protocol === 'https:' && new URL(url)?.hostname !== 'localhost';
|
new URL(url)?.protocol === 'https:' && new URL(url)?.hostname !== 'localhost';
|
||||||
if (isSecure) {
|
if (isSecure) {
|
||||||
https.get(url, async function (response) {
|
https
|
||||||
response.pipe(Pdf);
|
.get(url, async function (response) {
|
||||||
response.on('end', () => resolve('success'));
|
response.pipe(Pdf);
|
||||||
});
|
Pdf.on('finish', () => resolve('success'));
|
||||||
|
Pdf.on('error', () => resolve('error'));
|
||||||
|
})
|
||||||
|
.on('error', e => {
|
||||||
|
console.error(`error: ${e.message}`);
|
||||||
|
resolve('error');
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
const httpsAgent = new https.Agent({ rejectUnauthorized: false }); // Disable SSL validation
|
const httpsAgent = new https.Agent({ rejectUnauthorized: false }); // Disable SSL validation
|
||||||
axios
|
axios
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ app.use(express.urlencoded({ limit: '50mb', extended: true }));
|
|||||||
app.use(function (req, res, next) {
|
app.use(function (req, res, next) {
|
||||||
req.headers['x-real-ip'] = getUserIP(req);
|
req.headers['x-real-ip'] = getUserIP(req);
|
||||||
const publicUrl = 'https://' + req?.get('host');
|
const publicUrl = 'https://' + req?.get('host');
|
||||||
req.headers['public_url'] = publicUrl; // process.env.PUBLIC_URL
|
req.headers['public_url'] = publicUrl;
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
function getUserIP(request) {
|
function getUserIP(request) {
|
||||||
|
|||||||
Generated
+3056
-3976
File diff suppressed because it is too large
Load Diff
@@ -18,35 +18,37 @@
|
|||||||
"watch": "nodemon index.js"
|
"watch": "nodemon index.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-s3": "^3.812.0",
|
"@aws-sdk/client-s3": "^3.828.0",
|
||||||
"@aws-sdk/s3-request-presigner": "^3.812.0",
|
"@aws-sdk/s3-request-presigner": "^3.828.0",
|
||||||
"@parse/fs-files-adapter": "^3.0.0",
|
"@parse/fs-files-adapter": "^3.0.0",
|
||||||
"@parse/s3-files-adapter": "^4.1.0",
|
"@parse/s3-files-adapter": "^4.2.0",
|
||||||
"@pdf-lib/fontkit": "^1.1.1",
|
"@pdf-lib/fontkit": "^1.1.1",
|
||||||
"@signpdf/placeholder-pdf-lib": "^3.2.6",
|
"@signpdf/placeholder-pdf-lib": "^3.2.6",
|
||||||
"@signpdf/signer-p12": "^3.2.4",
|
"@signpdf/signer-p12": "^3.2.4",
|
||||||
"@signpdf/signpdf": "^3.2.5",
|
"@signpdf/signpdf": "^3.2.5",
|
||||||
"aws-sdk": "^2.1692.0",
|
"aws-sdk": "^2.1692.0",
|
||||||
"axios": "^1.9.0",
|
"axios": "^1.10.0",
|
||||||
|
"coherentpdf": "^2.5.5",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"date-fns-tz": "^3.2.0",
|
"date-fns-tz": "^3.2.0",
|
||||||
"dotenv": "^16.5.0",
|
"dotenv": "^16.5.0",
|
||||||
"express": "^5.1.0",
|
"express": "^5.1.0",
|
||||||
"form-data": "^4.0.2",
|
"form-data": "^4.0.3",
|
||||||
"generate-api-key": "^1.0.2",
|
"generate-api-key": "^1.0.2",
|
||||||
"googleapis": "^148.0.0",
|
"googleapis": "^150.0.1",
|
||||||
"mailgun.js": "^12.0.1",
|
"libreoffice-convert": "^1.6.1",
|
||||||
"mongodb": "^6.16.0",
|
"mailgun.js": "^12.0.2",
|
||||||
"multer": "^2.0.0",
|
"mongodb": "^6.17.0",
|
||||||
|
"multer": "^2.0.1",
|
||||||
"multer-s3": "^3.0.1",
|
"multer-s3": "^3.0.1",
|
||||||
"node-forge": "^1.3.1",
|
"node-forge": "^1.3.1",
|
||||||
"nodemailer": "^7.0.3",
|
"nodemailer": "^7.0.3",
|
||||||
"parse": "^6.1.1",
|
"parse": "^6.1.1",
|
||||||
"parse-dbtool": "^1.2.0",
|
"parse-dbtool": "^1.2.0",
|
||||||
"parse-server": "^8.2.0",
|
"parse-server": "^8.2.1",
|
||||||
"parse-server-api-mail-adapter": "^4.1.0",
|
"parse-server-api-mail-adapter": "^4.1.0",
|
||||||
"pdf-lib": "^1.17.1",
|
"pdf-lib": "^1.17.1",
|
||||||
"posthog-node": "^4.17.1",
|
"posthog-node": "^5.1.0",
|
||||||
"qrcode": "^1.5.4",
|
"qrcode": "^1.5.4",
|
||||||
"rate-limiter-flexible": "^7.1.1",
|
"rate-limiter-flexible": "^7.1.1",
|
||||||
"speakeasy": "^2.0.0",
|
"speakeasy": "^2.0.0",
|
||||||
@@ -54,10 +56,10 @@
|
|||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/eslint-parser": "^7.27.1",
|
"@babel/eslint-parser": "^7.27.5",
|
||||||
"eslint": "^9.27.0",
|
"eslint": "^9.29.0",
|
||||||
"jasmine": "^5.7.1",
|
"jasmine": "^5.8.0",
|
||||||
"mongodb-runner": "^5.8.3",
|
"mongodb-runner": "^5.9.0",
|
||||||
"nodemon": "^3.1.10",
|
"nodemon": "^3.1.10",
|
||||||
"nyc": "^17.1.0",
|
"nyc": "^17.1.0",
|
||||||
"prettier": "^3.5.3"
|
"prettier": "^3.5.3"
|
||||||
|
|||||||
Reference in New Issue
Block a user