Compare commits

..
444 changed files with 320871 additions and 59858 deletions
-9
View File
@@ -232,15 +232,6 @@
"contributions": [
"code"
]
},
{
"login": "1024mb",
"name": "1024mb",
"avatar_url": "https://avatars.githubusercontent.com/u/9301204?v=4",
"profile": "https://github.com/1024mb",
"contributions": [
"translation"
]
}
]
}
+3 -4
View File
@@ -8,11 +8,13 @@ PUBLIC_URL=https://localhost:3001
GENERATE_SOURCEMAP=false
# Set it to the URL from where APIs will be accessible, for local development it should be localhost:3000/api/app (use your local port number instead)
# REACT_APP_SERVERURL=http://localhost:8080/app
# (DEPRECATED) This should not be changed if provided; it should be 'opensign'.
# A 12 character long random app identifier. The value of this should be same as APP_ID which is a variable used by backend API.
REACT_APP_APPID=opensign
# Backend ExpressJS config ****************************************************************************************************************************************************************************************
# A 12 character long random app identifier. The value of this should be same as REACT_APP_APPID which is a variable used by Frontend React App.
APP_ID=opensign
# Name of the app. It will be visible in the verification emails sent out.
appName=open_sign_server
# A 12 character long random secret key that allows access to all the data. It is used in Parse dashboard config to view all the data in the database.
@@ -109,6 +111,3 @@ CRUxFgQUDYlgGVxSxuOknhQc256x3++7BDwwMTAhMAkGBSsOAwIaBQAEFFjASdYl
# Provide Pass pharse of above PFX or p12 document
PASS_PHRASE=opensign
# (DEPRECATED) This should not be changed if provided; it should be 'opensign'.
APP_ID=opensign
-7
View File
@@ -1,15 +1,8 @@
name: ci
# 👇 add this block
permissions:
contents: read # allow checkout & metadata-action to read repo
id-token: write # needed by docker/metadata-action v4
on:
push:
branches:
- 'main'
- 'staging'
jobs:
docker:
+2 -2
View File
@@ -77,6 +77,6 @@ Engage with the community on our [Gitter Channel](https://gitter.im/OpenSignLabs
## License
By contributing, you agree that your contributions will be licensed under its [AGPL-V3 License](LICENSE).
By contributing, you agree that your contributions will be licensed under its [MIT License](LICENSE).
---
---
+2 -2
View File
@@ -1,7 +1,7 @@
{$HOST_URL} {
reverse_proxy client:3000
handle_path /api/* {
handle_path /app/* {
reverse_proxy server:8080
rewrite * {uri}
rewrite * /app{uri}
}
}
+1 -6
View File
@@ -1,9 +1,4 @@
* All content that resides under the "apps/OpenSignServer/cloud/customRoute" directory of this repository, if that directory exists, is licensed under the license defined in "apps/OpenSignServer/cloud/customRoute".
* All third party components incorporated into the OpenSign Software are licensed under the original license provided by the owner of the applicable component.
* Content outside of the above mentioned directories or restrictions above is available under the "GNU AFFERO GENERAL PUBLIC LICENSE" as defined below -
GNU AFFERO GENERAL PUBLIC LICENSE
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
+3 -22
View File
@@ -73,27 +73,11 @@ Welcome to OpenSign, the premier open source docusign alternative - document e-s
---
### Deploy
### Installation
Note: The default MongoDB instance used in deployment is not persistant and will be cleared on every restart. To retain your data, configure and supply your own MongoDB connection URL.
#### DigitalOcean
[![Deploy on DigitalOcean](https://www.deploytodo.com/do-btn-blue.svg)](https://cloud.digitalocean.com/apps/new?repo=https://github.com/OpenSignLabs/Deploy-OpenSign-to-Digital-Ocean/tree/main&refcode=30db1c901ab0)
#### Docker
The simplest way to install OpenSign on your own server is using official docker images by running the following command -
**Command for linux/MacOS**
```
export HOST_URL=https://opensign.yourdomain.com && curl --remote-name-all https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/docker-compose.yml https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/Caddyfile https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/.env.local_dev && mv .env.local_dev .env.prod && docker compose up --force-recreate
```
**Command for Windows (Powershell)**
```
$env:HOST_URL="https://opensign.yourdomain.com"; Invoke-WebRequest -Uri https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/docker-compose.yml -OutFile docker-compose.yml; Invoke-WebRequest -Uri https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/Caddyfile -OutFile Caddyfile; Invoke-WebRequest -Uri https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/.env.local_dev -OutFile .env.local_dev; Rename-Item -Path .env.local_dev -NewName .env.prod; docker compose up --force-recreate
```
**Command for Windows (CMD/Terminal)**
```
set HOST_URL=https://opensign.yourdomain.com && curl -O https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/docker-compose.yml && curl -O https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/Caddyfile && curl -O https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/.env.local_dev && rename .env.local_dev .env.prod && docker compose up --force-recreate
export HOST_URL=https://opensign.yourdomain.com && curl --remote-name-all https://raw.githubusercontent.com/OpenSignLabs/OpenSign/docker_beta/docker-compose.yml https://raw.githubusercontent.com/OpenSignLabs/OpenSign/docker_beta/Caddyfile https://raw.githubusercontent.com/OpenSignLabs/OpenSign/docker_beta/.env.local_dev && mv .env.local_dev .env.prod && docker compose up --force-recreate
```
Make sure that you have `Docker` and `git` installed before you run this command -
@@ -163,8 +147,7 @@ We would like to thank all our contributors and users for their support and feed
<td align="center" valign="top" width="14.28%"><a href="https://github.com/VikramNagwal"><img src="https://avatars.githubusercontent.com/u/123088024?v=4?s=100" width="100px;" alt="Vikram"/><br /><sub><b>Vikram</b></sub></a><br /><a href="#code-VikramNagwal" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ugoconsonni"><img src="https://avatars.githubusercontent.com/u/13661702?v=4?s=100" width="100px;" alt="ugoconsonni"/><br /><sub><b>ugoconsonni</b></sub></a><br /><a href="#code-ugoconsonni" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/daniel-mutwiri"><img src="https://avatars.githubusercontent.com/u/8936960?v=4?s=100" width="100px;" alt="Daniel Mutwiri"/><br /><sub><b>Daniel Mutwiri</b></sub></a><br /><a href="#code-daniel-mutwiri" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Zathiel"><img src="https://avatars.githubusercontent.com/u/26553418?v=4?s=100" width="100px;" alt="Zathiel"/><br /><sub><b>Zathiel</b></sub></a><br /><a href="#code-Zathiel" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/1024mb"><img src="https://avatars.githubusercontent.com/u/9301204?v=4?s=100" width="100px;" alt="1024mb"/><br /><sub><b>1024mb</b></sub></a><br /><a href="#translation-1024mb" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Zathiel"><img src="https://avatars.githubusercontent.com/u/26553418?v=4?s=100" width="100px;" alt="Zathiel"/><br /><sub><b>Zathiel</b></sub></a><br /><a href="#code-Zathiel" title="Code">💻</a><a href="#security-Zathiel" title="Security">🛡️</a></td>
</tr>
</tbody>
</table>
@@ -174,5 +157,3 @@ We would like to thank all our contributors and users for their support and feed
<!-- ALL-CONTRIBUTORS-LIST:END -->
This project is tested with BrowserStack.
Regular → Executable
View File
+25
View File
@@ -0,0 +1,25 @@
# 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
+3 -12
View File
@@ -1,5 +1,5 @@
# Use an official Node runtime as the base image
FROM node:22.14.0
FROM node:18
# Set the working directory inside the container
WORKDIR /usr/src/app
@@ -13,24 +13,15 @@ RUN npm install
# Copy the current directory contents into the container
COPY apps/OpenSign/ .
COPY apps/OpenSign/.husky .
COPY apps/OpenSign/entrypoint.sh .
# make the entrypoint.sh file executable
RUN chmod +x entrypoint.sh
# Define environment variables if needed
ENV NODE_ENV=production
ENV GENERATE_SOURCEMAP=false
# build
RUN npm run build
# Inject env.js loader into index.html
RUN sed -i '/<head>/a\<script src="/env.js"></script>' build/index.html
# Make port 3000 available to the world outside this container
EXPOSE 3000
ENTRYPOINT ["./entrypoint.sh"]
# Define environment variables if needed
# ENV NODE_ENV production
# Run the application
CMD ["npm", "start"]
-213
View File
@@ -1,213 +0,0 @@
# Tailwind Dark Mode Usage Guide
## Overview
This guide shows how to use the new Tailwind utilities for better dark mode visibility in OpenSign.
## Button Styling
### VS Code-style Disabled Buttons
```jsx
// Option 1: Direct VS Code styling
<button className="op-btn op-btn-primary op-btn-vscode-disabled" disabled>
Disabled Button
</button>
// Option 2: Themed disabled styling
<button className="op-btn btn-themed-disabled">
Themed Button
</button>
// Option 3: Conditional styling
<button
className={`op-btn op-btn-primary ${isDisabled ? 'op-btn-vscode-disabled' : ''}`}
disabled={isDisabled}
>
Dynamic Button
</button>
```
## Icon Styling
### Theme-aware Icons
```jsx
// Better visibility in dark mode
<i className="fa-light fa-folder icon-improved"></i>
// Muted but still visible
<i className="fa-light fa-plus icon-muted"></i>
// Disabled state
<i className="fa-light fa-trash icon-disabled"></i>
```
### CSS Variable Approach
```jsx
// Using CSS variables
<i className="fa-light fa-search icon-themed"></i>
<i className="fa-light fa-settings icon-themed-muted"></i>
// Inline styles with CSS variables
<i
className="fa-light fa-plus"
style={{ color: 'var(--icon-color)' }}
/>
```
### Legacy JavaScript Function (Still Supported)
```jsx
// Existing approach - still works
<i
className="fa-light fa-plus"
style={{ color: getThemeIconColor() }}
/>
```
## Text Styling
### Improved Gray Text
```jsx
// These automatically improve in dark mode
<span className="text-gray-500">More visible in dark mode</span>
<span className="text-gray-400">Muted but readable</span>
<span className="text-gray-600">Clear text</span>
```
## Complete Examples
### Toolbar with Better Visibility
```jsx
const Toolbar = () => (
<div className="flex space-x-2 p-2">
<button className="p-2 hover:bg-gray-200 rounded">
<i className="fa-light fa-plus icon-improved"></i>
</button>
<button className="p-2 hover:bg-gray-200 rounded" disabled>
<i className="fa-light fa-trash icon-disabled"></i>
</button>
<button className="p-2 hover:bg-gray-200 rounded">
<i className="fa-light fa-edit icon-improved"></i>
</button>
</div>
);
```
### Form with Disabled States
```jsx
const Form = ({ isSubmitting }) => (
<form>
<input className="op-input" />
<button
className={`op-btn op-btn-primary ${isSubmitting ? 'op-btn-vscode-disabled' : ''}`}
disabled={isSubmitting}
>
{isSubmitting ? 'Submitting...' : 'Submit'}
</button>
</form>
);
```
## React-Tour and Tooltip Dark Mode Support
### React-Tour Modals
The react-tour modals now automatically support dark mode with VS Code-inspired styling:
```jsx
// These components automatically get dark mode styling
<Tour
onRequestClose={closeTour}
steps={tourConfig}
isOpen={isOpen}
rounded={5}
/>
```
### ReactTooltip Components
All ReactTooltip instances now support dark mode:
```jsx
// Automatically styled for dark mode
<ReactTooltip id="my-tooltip" className="z-[999]">
<div className="max-w-[200px]">
<p>Tooltip content</p>
</div>
</ReactTooltip>
```
### HoverCard Balloon UI
The balloon tooltips in OpenSign Drive now properly support dark mode:
```jsx
// These automatically get dark styling in dark mode
<HoverCard>
<HoverCardContent>
Document information
</HoverCardContent>
</HoverCard>
```
## Dark Mode Features Added
### 1. **React-Tour Modal Styling**
- Background: `#1F2937` (VS Code modal background)
- Text: `#E5E7EB` (soft white for readability)
- Borders: `#374151` (subtle borders)
- Buttons: VS Code-style primary/secondary buttons
### 2. **ReactTooltip Styling**
- Background: `#1F2937` with proper contrast
- Border: `#374151` for definition
- Box shadow: Enhanced for dark backgrounds
- Text: `#E5E7EB` for optimal readability
### 3. **HoverCard Balloon UI**
- Background: `#1F2937` (matches VS Code)
- Text: `#E5E7EB` for readability
- Arrow: Automatically matches background color
- Enhanced shadows for dark backgrounds
### 4. **React-Datepicker Support**
- Calendar background: `#1F2937`
- Selected dates: VS Code blue (`#007ACC`)
- Hover states: Proper contrast ratios
- Navigation arrows: Themed appropriately
## CSS Classes Reference
| Class | Purpose | Dark Mode Color |
|-------|---------|----------------|
| `icon-improved` | Better icon visibility | `#CCCCCC` |
| `icon-muted` | Muted but visible icons | `#999999` |
| `icon-disabled` | Disabled icon state | `#858585` |
| `op-btn-vscode-disabled` | VS Code disabled button | Background: `#3C3C3C` |
| `btn-themed-disabled` | Themed disabled button | Uses CSS variables |
| `icon-themed` | Variable-based icon color | `var(--icon-color)` |
| `.reactour__helper` | `#1F2937` background |
| `.react-tooltip` | `#1F2937` background |
| `.HoverCardContent` | `#1F2937` background |
| `.react-datepicker` | `#1F2937` background |
## Migration Guide
### From JavaScript Function to Tailwind
```jsx
// Before
<i style={{ color: getThemeIconColor() }} className="fa-light fa-plus" />
// After
<i className="fa-light fa-plus icon-improved" />
```
### From Hardcoded Colors to Theme-aware
```jsx
// Before
<i className="fa-light fa-plus text-gray-500" />
// After (automatic improvement)
<i className="fa-light fa-plus text-gray-500" />
// OR explicitly
<i className="fa-light fa-plus icon-improved" />
```
## Migration Notes
All existing tooltip and tour components will automatically inherit the new dark mode styling when the theme is set to `opensigndark`. No code changes required for existing implementations.
-29
View File
@@ -1,29 +0,0 @@
#!/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 "$@"
@@ -1,92 +0,0 @@
/**
* Tailwind Dark Mode Usage Examples for OpenSign
*
* This file demonstrates how to use the new Tailwind utilities
* for better dark mode visibility of buttons and icons.
*/
// Example 1: VS Code-style disabled buttons
const DisabledButtonExamples = () => {
return (
<div>
{/* Option A: Using the VS Code disabled style */}
<button className="op-btn op-btn-primary op-btn-vscode-disabled" disabled>
VS Code Style Disabled Button
</button>
{/* Option B: Using themed disabled style */}
<button className="op-btn btn-themed-disabled">
Themed Disabled Button
</button>
{/* Option C: Conditional styling */}
<button
className={`op-btn op-btn-primary ${
isDisabled ? "op-btn-vscode-disabled" : ""
}`}
disabled={isDisabled}
>
Conditional Button
</button>
</div>
);
};
// Example 2: Icon visibility improvements
const IconExamples = () => {
return (
<div>
{/* Theme-aware icons with better visibility */}
<i className="fa-light fa-folder icon-improved"></i>
<i className="fa-light fa-plus icon-muted"></i>
<i className="fa-light fa-trash icon-disabled"></i>
{/* Using CSS variables */}
<i className="fa-light fa-search icon-themed"></i>
<i className="fa-light fa-settings icon-themed-muted"></i>
{/* Gray text that automatically improves in dark mode */}
<span className="text-gray-500">
This text is now more visible in dark mode
</span>
<span className="text-gray-400">Muted but still readable</span>
</div>
);
};
// Example 3: Using CSS variables in inline styles
const InlineStyleExamples = () => {
return (
<div>
{/* Using CSS variables directly */}
<i className="fa-light fa-plus" style={{ color: "var(--icon-color)" }} />
{/* Using the existing JavaScript function */}
<i className="fa-light fa-minus" style={{ color: getThemeIconColor() }} />
</div>
);
};
// Example 4: Toolbar with improved icons
const ToolbarExample = () => {
return (
<div className="flex space-x-2 p-2">
<button className="p-2 hover:bg-gray-200 rounded">
<i className="fa-light fa-plus icon-improved"></i>
</button>
<button className="p-2 hover:bg-gray-200 rounded" disabled>
<i className="fa-light fa-trash icon-disabled"></i>
</button>
<button className="p-2 hover:bg-gray-200 rounded">
<i className="fa-light fa-edit icon-improved"></i>
</button>
</div>
);
};
export {
DisabledButtonExamples,
IconExamples,
InlineStyleExamples,
ToolbarExample
};
@@ -1,94 +0,0 @@
/**
* 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
};
+26184
View File
File diff suppressed because it is too large Load Diff
+73 -77
View File
@@ -1,72 +1,73 @@
{
"name": "open_sign",
"version": "2.30.0",
"version": "0.1.0",
"private": true,
"dependencies": {
"@formkit/auto-animate": "^0.9.0",
"@imgly/background-removal": "^1.7.0",
"@lottiefiles/dotlottie-react": "^0.16.2",
"@formkit/auto-animate": "^0.8.2",
"@lottiefiles/dotlottie-react": "^0.9.0",
"@pdf-lib/fontkit": "^1.1.1",
"@radix-ui/themes": "^3.2.1",
"@reduxjs/toolkit": "^2.8.2",
"axios": "^1.12.2",
"date-fns-tz": "^3.2.0",
"@radix-ui/themes": "^3.1.3",
"@react-pdf/renderer": "^4.0.0",
"@reduxjs/toolkit": "^2.2.7",
"axios": "^1.7.7",
"file-saver": "^2.0.5",
"i18next": "^25.4.0",
"i18next-browser-languagedetector": "^8.2.0",
"i18next-http-backend": "^3.0.2",
"i18next": "^23.15.1",
"i18next-browser-languagedetector": "^8.0.0",
"i18next-http-backend": "^2.6.1",
"jszip": "^3.10.1",
"jwt-decode": "^4.0.0",
"moment": "^2.30.1",
"parse": "^6.1.1",
"nth-check": "^2.1.1",
"parse": "^5.3.0",
"pdf-lib": "^1.17.1",
"pkijs": "^3.0.8",
"print-js": "^1.6.0",
"prismjs": "^1.30.0",
"quill-html-edit-button": "^3.0.0",
"radix-ui": "^1.4.3",
"react": "^18.3.1",
"react-bootstrap": "^2.10.10",
"react-confetti": "^6.4.0",
"react-datepicker": "^8.7.0",
"radix-ui": "^1.0.1",
"react": "^18.2.0",
"react-bootstrap": "^2.10.4",
"react-confetti": "^6.1.0",
"react-cookie": "^7.2.0",
"react-datepicker": "^7.4.0",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
"react-dnd-multi-backend": "^9.0.0",
"react-dnd-multi-backend": "^8.0.3",
"react-dnd-touch-backend": "^16.0.1",
"react-dom": "^18.3.1",
"react-dom": "^18.2.0",
"react-gtm-module": "^2.0.11",
"react-helmet": "^6.1.0",
"react-i18next": "^15.7.4",
"react-konva": "^18.2.12",
"react-pdf": "^9.2.1",
"react-quill-new": "^3.6.0",
"react-redux": "^9.2.0",
"react-rnd": "^10.5.2",
"react-router": "^7.9.3",
"react-i18next": "^15.0.2",
"react-konva": "^18.2.10",
"react-pdf": "^9.1.1",
"react-quill-new": "^3.3.2",
"react-redux": "^9.1.2",
"react-rnd": "^10.4.13",
"react-router-dom": "^6.26.2",
"react-scripts": "5.0.1",
"react-scrollbars-custom": "^4.1.1",
"react-select": "^5.10.2",
"react-signature-canvas": "^1.1.0-alpha.2",
"react-timezone-select": "^3.2.8",
"react-tooltip": "^5.29.1",
"react-select": "^5.8.1",
"react-signature-canvas": "^1.0.6",
"react-syntax-highlighter": "^15.5.0",
"react-tooltip": "^5.28.0",
"react-web-share": "^2.0.2",
"reactour": "^1.19.4",
"redux": "^5.0.1",
"regex-parser": "^2.3.1",
"serve": "^14.2.5",
"styled-components": "^5.3.11",
"web-vitals": "^5.1.0",
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz"
"redux-thunk": "^3.1.0",
"regex-parser": "^2.3.0",
"serve": "^14.2.3",
"styled-components": "^5.3.0",
"web-vitals": "^4.2.3",
"ws": "^8.18.0"
},
"scripts": {
"build": "npm run version && NODE_OPTIONS=\"--max-old-space-size=8192\" vite build",
"start-dev": "vite",
"dev": "vite",
"preview": "vite preview",
"build-webpack": "webpack --config webpack.config.js",
"build": "npm run version && npm run build-webpack && react-scripts build",
"build-watch": "webpack --config webpack.config.js --watch",
"start-dev": "react-scripts start",
"start": "serve -s build",
"version": "curl -s https://api.github.com/repos/opensignlabs/opensign/releases/latest | grep '\"tag_name\":' | awk -F '\"' '{print $4}' > ./public/version.txt",
"version-win": "powershell -Command \"Invoke-RestMethod -Uri 'https://api.github.com/repos/opensignlabs/opensign/releases/latest' | Select-Object -ExpandProperty tag_name | Out-File -FilePath ./public/version.txt\"",
"build-win": "npm run version-win && vite build",
"test": "vitest run",
"test:watch": "vitest",
"build-win": "npm run version-win && npm run build-webpack && react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"release": "standard-version",
"commit": "cz"
},
@@ -94,38 +95,33 @@
}
},
"devDependencies": {
"@babel/core": "^7.28.3",
"@babel/preset-env": "^7.28.3",
"@babel/preset-react": "^7.27.1",
"@babel/runtime-corejs2": "^7.28.3",
"@testing-library/jest-dom": "^6.8.0",
"@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^14.6.1",
"@types/react": "^19.1.13",
"@vitejs/plugin-react": "^4.7.0",
"@vitest/ui": "^3.2.4",
"autoprefixer": "^10.4.21",
"babel-loader": "^10.0.0",
"commitizen": "^4.3.1",
"concurrently": "^9.2.1",
"@babel/core": "^7.25.2",
"@babel/preset-env": "^7.25.4",
"@babel/preset-react": "^7.24.7",
"@babel/runtime-corejs2": "^7.25.6",
"autoprefixer": "^10.4.20",
"babel-loader": "^9.2.1",
"clean-webpack-plugin": "^4.0.0",
"commitizen": "^4.3.0",
"concurrently": "^9.0.1",
"css-loader": "^7.1.2",
"daisyui": "^4.12.24",
"eslint": "^9.37.0",
"eslint-plugin-prettier": "^5.5.4",
"eslint-plugin-react": "^7.37.5",
"jsdom": "^27.0.0",
"lint-staged": "^16.1.6",
"postcss": "^8.5.6",
"prettier": "^3.6.2",
"pretty-quick": "^4.2.2",
"rollup-plugin-node-polyfills": "^0.2.1",
"tailwindcss": "^3.4.17",
"vite": "^7.1.4",
"vite-plugin-svgr": "^4.5.0",
"vite-tsconfig-paths": "^5.1.4",
"vitest": "^3.2.4"
"daisyui": "^4.12.10",
"dotenv": "^16.4.5",
"dotenv-webpack": "^8.1.0",
"eslint": "^9.11.1",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-react": "^7.36.1",
"lint-staged": "^15.2.10",
"mini-css-extract-plugin": "^2.9.1",
"postcss": "^8.4.47",
"prettier": "^3.3.3",
"pretty-quick": "^4.0.0",
"tailwindcss": "^3.4.13",
"terser-webpack-plugin": "^5.3.10",
"webpack-cli": "^5.1.4"
},
"engines": {
"node": "18 || 20 || 22"
"overrides": {
"nth-check": "$nth-check",
"ws": "$ws"
}
}
}
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

@@ -1,22 +1,20 @@
<!DOCTYPE html>
<html lang="en" data-theme="opensigncss">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="The fastest way to sign PDFs & request signatures from others" />
<!-- <link rel="apple-touch-icon" href="/logo192.png" /> -->
<!-- <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> -->
<link rel="manifest" href="/manifest.json" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.0.0/dist/css/bootstrap.min.css"
integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous" />
<link rel="stylesheet" href="https://cdn.opensignlabs.com/fonts.css" />
<title>...Loading</title>
<link rel="stylesheet" href="https://cdn.opensignlabs.com/fonts.css" />
<title>OpenSign™</title>
</head>
<body>
<div id="root" style="touch-action:pan-x pan-y;"></div>
<script type="module" src="/src/index.jsx"></script>
<div id="root"></div>
</body>
</html>
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

-4
View File
@@ -1,4 +0,0 @@
Name,Email,Phone,Company,JobTitle
John Doe,john@example.com,1234567890,abc firm,dev
Jane Smith,jane@example.com,9876543210,xyz firm,manager
Foo Bar,foo@example.com,5555555555,xyz firm,director
1 Name Email Phone Company JobTitle
2 John Doe john@example.com 1234567890 abc firm dev
3 Jane Smith jane@example.com 9876543210 xyz firm manager
4 Foo Bar foo@example.com 5555555555 xyz firm director
Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

File diff suppressed because one or more lines are too long
Binary file not shown.
-1
View File
@@ -1 +0,0 @@
import "@testing-library/jest-dom";
+211
View File
@@ -0,0 +1,211 @@
import React, { useState, useEffect, lazy } from "react";
import { Routes, Route, BrowserRouter } from "react-router-dom";
import { pdfjs } from "react-pdf";
import Login from "./pages/Login";
import Form from "./pages/Form";
import Report from "./pages/Report";
import Dashboard from "./pages/Dashboard";
import HomeLayout from "./layout/HomeLayout";
import PageNotFound from "./pages/PageNotFound";
import ValidateRoute from "./primitives/ValidateRoute";
import Validate from "./primitives/Validate";
import TemplatePlaceholder from "./pages/TemplatePlaceholder";
import SignYourSelf from "./pages/SignyourselfPdf";
import DraftDocument from "./components/pdf/DraftDocument";
import PlaceHolderSign from "./pages/PlaceHolderSign";
import PdfRequestFiles from "./pages/PdfRequestFiles";
import LazyPage from "./primitives/LazyPage";
import { isEnableSubscription } from "./constant/const";
import SSOVerify from "./pages/SSOVerify";
import Loader from "./primitives/Loader";
import TeamList from "./pages/TeamList";
import UserList from "./pages/UserList";
import { serverUrl_fn } from "./constant/appinfo";
const DebugPdf = lazy(() => import("./pages/DebugPdf"));
const ForgetPassword = lazy(() => import("./pages/ForgetPassword"));
const GuestLogin = lazy(() => import("./pages/GuestLogin"));
const Pgsignup = lazy(() => import("./pages/Pgsignup"));
const Subscriptions = lazy(() => import("./pages/PlanSubscriptions"));
const ChangePassword = lazy(() => import("./pages/ChangePassword"));
const UserProfile = lazy(() => import("./pages/UserProfile"));
const Signup = lazy(() => import("./pages/Signup"));
const Opensigndrive = lazy(() => import("./pages/Opensigndrive"));
const ManageSign = lazy(() => import("./pages/Managesign"));
const GenerateToken = lazy(() => import("./pages/GenerateToken"));
const Webhook = lazy(() => import("./pages/Webhook"));
const AddAdmin = lazy(() => import("./pages/AddAdmin"));
const UpdateExistUserAdmin = lazy(() => import("./pages/UpdateExistUserAdmin"));
pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/legacy/build/pdf.worker.min.mjs`;
const AppLoader = () => {
return (
<div className="flex justify-center items-center h-[100vh]">
<Loader />
</div>
);
};
function App() {
const [isloading, setIsLoading] = useState(true);
useEffect(() => {
handleCredentials();
}, []);
const handleCredentials = () => {
const appId = process.env.REACT_APP_APPID
? process.env.REACT_APP_APPID
: "opensign";
const baseurl = serverUrl_fn();
try {
localStorage.setItem("baseUrl", `${baseurl}/`);
localStorage.setItem("parseAppId", appId);
setIsLoading(false);
} catch (error) {
console.log("err ", error);
}
};
return (
<div className="bg-base-200">
{isloading ? (
<AppLoader />
) : (
<BrowserRouter>
<Routes>
<Route element={<ValidateRoute />}>
<Route exact path="/" element={<Login />} />
{isEnableSubscription && (
<Route path="/signup" element={<LazyPage Page={Signup} />} />
)}
{!isEnableSubscription && (
<>
<Route
path="/addadmin"
element={<LazyPage Page={AddAdmin} />}
/>
<Route
path="/upgrade-2.1"
element={<LazyPage Page={UpdateExistUserAdmin} />}
/>
</>
)}
</Route>
<Route element={<Validate />}>
<Route
path="/load/template/:templateId"
element={<TemplatePlaceholder />}
/>
<Route
exact
path="/load/placeholdersign/:docId"
element={<PlaceHolderSign />}
/>
<Route
exact
path="/load/recipientSignPdf/:docId/:contactBookId"
element={<PdfRequestFiles />}
/>
</Route>
<Route
path="/loadmf/signmicroapp/login/:id/:userMail/:contactBookId/:serverUrl"
element={<LazyPage Page={GuestLogin} />}
/>
<Route
path="/login/:id/:userMail/:contactBookId/:serverUrl"
element={<LazyPage Page={GuestLogin} />}
/>
<Route
path="/login/:base64url"
element={<LazyPage Page={GuestLogin} />}
/>
<Route path="/debugpdf" element={<LazyPage Page={DebugPdf} />} />
<Route
path="/forgetpassword"
element={<LazyPage Page={ForgetPassword} />}
/>
{isEnableSubscription && (
<>
<Route
path="/pgsignup"
element={<LazyPage Page={Pgsignup} />}
/>
<Route
path="/subscription"
element={<LazyPage Page={Subscriptions} />}
/>
</>
)}
<Route element={<HomeLayout />}>
<Route
path="/changepassword"
element={<LazyPage Page={ChangePassword} />}
/>
<Route path="/form/:id" element={<Form />} />
<Route path="/report/:id" element={<Report />} />
<Route path="/dashboard/:id" element={<Dashboard />} />
<Route
path="/profile"
element={<LazyPage Page={UserProfile} />}
/>
<Route
path="/opensigndrive"
element={<LazyPage Page={Opensigndrive} />}
/>
<Route
path="/managesign"
element={<LazyPage Page={ManageSign} />}
/>
<Route
path="/template/:templateId"
element={<TemplatePlaceholder />}
/>
{/* signyouself route with no rowlevel data using docId from url */}
<Route path="/signaturePdf/:docId" element={<SignYourSelf />} />
{/* draft document route to handle and navigate route page accordiing to document status */}
<Route path="/draftDocument" element={<DraftDocument />} />
{/* recipient placeholder set route with no rowlevel data using docId from url*/}
<Route
path="/placeHolderSign/:docId"
element={<PlaceHolderSign />}
/>
{/* for user signature (need your sign route) with row level data */}
<Route path="/pdfRequestFiles" element={<PdfRequestFiles />} />
{/* for user signature (need your sign route) with no row level data */}
<Route
path="/pdfRequestFiles/:docId"
element={<PdfRequestFiles />}
/>
{/* recipient signature route with no rowlevel data using docId from url */}
<Route
path="/recipientSignPdf/:docId/:contactBookId"
element={<PdfRequestFiles />}
/>
<Route
path="/recipientSignPdf/:docId"
element={<PdfRequestFiles />}
/>
{isEnableSubscription && (
<>
<Route path="/teams" element={<TeamList />} />
<Route
path="/generatetoken"
element={<LazyPage Page={GenerateToken} />}
/>
<Route
path="/webhook"
element={<LazyPage Page={Webhook} />}
/>
</>
)}
<Route path="/users" element={<UserList />} />
</Route>
<Route path="/sso" element={<SSOVerify />} />
<Route path="*" element={<PageNotFound />} />
</Routes>
</BrowserRouter>
)}
</div>
);
}
export default App;
-148
View File
@@ -1,148 +0,0 @@
import { useState, useEffect } from "react";
import { lazyWithRetry, hideUpgradeProgress } from "./utils";
import { Routes, Route, BrowserRouter } from "react-router";
import { pdfjs } from "react-pdf";
import Form from "./pages/Form";
import Report from "./pages/Report";
import Dashboard from "./pages/Dashboard";
import HomeLayout from "./layout/HomeLayout";
import PageNotFound from "./pages/PageNotFound";
import ValidateRoute from "./primitives/ValidateRoute";
import Validate from "./primitives/Validate";
import TemplatePlaceholder from "./pages/TemplatePlaceholder";
import SignYourSelf from "./pages/SignyourselfPdf";
import DraftDocument from "./components/pdf/DraftDocument";
import PlaceHolderSign from "./pages/PlaceHolderSign";
import PdfRequestFiles from "./pages/PdfRequestFiles";
import Lazy from "./primitives/LazyPage";
import Loader from "./primitives/Loader";
import UserList from "./pages/UserList";
import { serverUrl_fn } from "./constant/appinfo";
import DocSuccessPage from "./pages/DocSuccessPage";
import DragProvider from "./components/DragProivder";
import Title from "./components/Title";
const DebugPdf = lazyWithRetry(() => import("./pages/DebugPdf"));
const ForgetPassword = lazyWithRetry(() => import("./pages/ForgetPassword"));
const GuestLogin = lazyWithRetry(() => import("./pages/GuestLogin"));
const ChangePassword = lazyWithRetry(() => import("./pages/ChangePassword"));
const UserProfile = lazyWithRetry(() => import("./pages/UserProfile"));
const Opensigndrive = lazyWithRetry(() => import("./pages/Opensigndrive"));
const ManageSign = lazyWithRetry(() => import("./pages/Managesign"));
const AddAdmin = lazyWithRetry(() => import("./pages/AddAdmin"));
const UpdateExistUserAdmin = lazyWithRetry(
() => import("./pages/UpdateExistUserAdmin")
);
const Preferences = lazyWithRetry(() => import("./pages/Preferences"));
const Login = lazyWithRetry(() => import("./pages/Login"));
const VerifyDocument = lazyWithRetry(() => import("./pages/VerifyDocument"));
pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/legacy/build/pdf.worker.min.mjs`;
const AppLoader = () => {
return (
<div className="flex justify-center items-center h-[100vh]">
<Loader />
</div>
);
};
function App() {
const [isloading, setIsLoading] = useState(true);
useEffect(() => {
// initialize creds
const id = process.env.REACT_APP_APPID ?? "opensign";
localStorage.setItem("parseAppId", id);
localStorage.setItem("baseUrl", `${serverUrl_fn()}/`);
hideUpgradeProgress();
localStorage.removeItem("showUpgradeProgress");
setIsLoading(false);
}, []);
return (
<div className="bg-base-200">
{isloading ? (
<AppLoader />
) : (
<BrowserRouter>
<Title />
<Routes>
<Route element={<ValidateRoute />}>
<Route exact path="/" element={<Lazy Page={Login} />} />
<Route path="/addadmin" element={<Lazy Page={AddAdmin} />} />
<Route
path="/upgrade-2.1"
element={<Lazy Page={UpdateExistUserAdmin} />}
/>
</Route>
<Route element={<Validate />}>
<Route
exact
path="/load/recipientSignPdf/:docId/:contactBookId"
element={<DragProvider Page={PdfRequestFiles} />}
/>
</Route>
<Route
path="/login/:base64url"
element={<Lazy Page={GuestLogin} />}
/>
<Route path="/debugpdf" element={<Lazy Page={DebugPdf} />} />
<Route
path="/forgetpassword"
element={<Lazy Page={ForgetPassword} />}
/>
<Route element={<HomeLayout />}>
<Route path="/users" element={<UserList />} />
<Route
path="/changepassword"
element={<Lazy Page={ChangePassword} />}
/>
<Route path="/form/:id" element={<Form />} />
<Route path="/report/:id" element={<Report />} />
<Route path="/dashboard/:id" element={<Dashboard />} />
<Route path="/profile" element={<Lazy Page={UserProfile} />} />
<Route path="/drive" element={<Lazy Page={Opensigndrive} />} />
<Route path="/managesign" element={<Lazy Page={ManageSign} />} />
<Route
path="/template/:templateId"
element={<DragProvider Page={TemplatePlaceholder} />}
/>
{/* signyouself route with no rowlevel data using docId from url */}
<Route
path="/signaturePdf/:docId"
element={<DragProvider Page={SignYourSelf} />}
/>
{/* draft document route to handle and navigate route page according to document status */}
<Route
path="/draftDocument"
element={<DragProvider Page={DraftDocument} />}
/>
{/* recipient placeholder set route with no rowlevel data using docId from url*/}
<Route
path="/placeHolderSign/:docId"
element={<DragProvider Page={PlaceHolderSign} />}
/>
{/* recipient signature route with no rowlevel data using docId from url */}
<Route
path="/recipientSignPdf/:docId/:contactBookId"
element={<DragProvider Page={PdfRequestFiles} />}
/>
<Route
path="/recipientSignPdf/:docId"
element={<DragProvider Page={PdfRequestFiles} />}
/>
<Route
path="/verify-document"
element={<Lazy Page={VerifyDocument} />}
/>
<Route
path="/preferences"
element={<Lazy Page={Preferences} />}
/>
</Route>
<Route path="/success" element={<DocSuccessPage />} />
<Route path="*" element={<PageNotFound />} />
</Routes>
</BrowserRouter>
)}
</div>
);
}
export default App;
Binary file not shown.

After

Width:  |  Height:  |  Size: 805 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 86 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

@@ -2,7 +2,6 @@ import React, { useState, useEffect } from "react";
import Parse from "parse";
import Loader from "../primitives/Loader";
import { useTranslation } from "react-i18next";
import { emailRegex } from "../constant/const";
const AddSigner = (props) => {
const { t } = useTranslation();
@@ -24,7 +23,7 @@ const AddSigner = (props) => {
if (savedUserDetails && addYourself) {
setName(savedUserDetails.name);
setPhone(savedUserDetails?.phone || "");
setEmail(savedUserDetails.email?.toLowerCase()?.replace(/\s/g, ""));
setEmail(savedUserDetails.email);
}
}, [addYourself]);
@@ -48,145 +47,139 @@ const AddSigner = (props) => {
const handleSubmit = async (e) => {
e.preventDefault();
e.stopPropagation();
if (!emailRegex.test(email)) {
alert(t("valid-email-alert"));
} else {
setIsLoader(true);
if (localStorage.getItem("TenantId")) {
try {
const user = Parse.User.current();
const query = new Parse.Query("contracts_Contactbook");
query.equalTo("CreatedBy", user);
query.notEqualTo("IsDeleted", true);
query.equalTo("Email", email);
const res = await query.first();
// console.log(res);
if (!res) {
const contactQuery = new Parse.Object("contracts_Contactbook");
contactQuery.set("Name", name);
if (phone) {
contactQuery.set("Phone", phone);
}
contactQuery.set("Email", email);
contactQuery.set("UserRole", "contracts_Guest");
contactQuery.set("TenantId", {
__type: "Pointer",
className: "partners_Tenant",
objectId: localStorage.getItem("TenantId")
});
try {
const _users = Parse.Object.extend("User");
const _user = new _users();
_user.set("name", name);
_user.set("username", email);
_user.set("email", email);
_user.set("password", email);
if (phone) {
_user.set("phone", phone);
}
const user = await _user.save();
if (user) {
const currentUser = Parse.User.current();
contactQuery.set(
"CreatedBy",
Parse.User.createWithoutData(currentUser.id)
);
contactQuery.set("UserId", user);
const acl = new Parse.ACL();
acl.setPublicReadAccess(true);
acl.setPublicWriteAccess(true);
acl.setReadAccess(currentUser.id, true);
acl.setWriteAccess(currentUser.id, true);
contactQuery.setACL(acl);
const res = await contactQuery.save();
const parseData = JSON.parse(JSON.stringify(res));
if (props.details) {
props.details({
value: parseData[props.valueKey],
label: parseData[props.displayKey],
email: parseData?.Email
});
}
if (props.closePopup) {
props.closePopup();
}
if (props.handleUserData) {
props.handleUserData(parseData);
}
setIsLoader(false);
// Reset the form fields
setAddYourself(false);
setName("");
setPhone("");
setEmail("");
}
} catch (err) {
console.log("err ", err);
if (err.code === 202) {
const params = { email: email };
const userRes = await Parse.Cloud.run("getUserId", params);
const currentUser = Parse.User.current();
contactQuery.set(
"CreatedBy",
Parse.User.createWithoutData(currentUser.id)
);
contactQuery.set("UserId", {
__type: "Pointer",
className: "_User",
objectId: userRes.id
});
const acl = new Parse.ACL();
acl.setPublicReadAccess(true);
acl.setPublicWriteAccess(true);
acl.setReadAccess(currentUser.id, true);
acl.setWriteAccess(currentUser.id, true);
contactQuery.setACL(acl);
const res = await contactQuery.save();
const parseData = JSON.parse(JSON.stringify(res));
if (props.details) {
props.details({
value: parseData[props.valueKey],
label: parseData[props.displayKey],
email: parseData?.Email
});
}
if (props.closePopup) {
props.closePopup();
}
if (props.handleUserData) {
props.handleUserData(parseData);
}
setIsLoader(false);
// Reset the form fields
setAddYourself(false);
setName("");
setPhone("");
setEmail("");
}
}
} else {
alert(t("add-signer-alert"));
setIsLoader(false);
setIsLoader(true);
if (localStorage.getItem("TenantId")) {
try {
const user = Parse.User.current();
const query = new Parse.Query("contracts_Contactbook");
query.equalTo("CreatedBy", user);
query.notEqualTo("IsDeleted", true);
query.equalTo("Email", email);
const res = await query.first();
// console.log(res);
if (!res) {
const contactQuery = new Parse.Object("contracts_Contactbook");
contactQuery.set("Name", name);
if (phone) {
contactQuery.set("Phone", phone);
}
} catch (err) {
console.log("err in fetch contact", err);
contactQuery.set("Email", email);
contactQuery.set("UserRole", "contracts_Guest");
contactQuery.set("TenantId", {
__type: "Pointer",
className: "partners_Tenant",
objectId: localStorage.getItem("TenantId")
});
try {
const _users = Parse.Object.extend("User");
const _user = new _users();
_user.set("name", name);
_user.set("username", email);
_user.set("email", email);
_user.set("password", email);
if (phone) {
_user.set("phone", phone);
}
const user = await _user.save();
if (user) {
const currentUser = Parse.User.current();
contactQuery.set(
"CreatedBy",
Parse.User.createWithoutData(currentUser.id)
);
contactQuery.set("UserId", user);
const acl = new Parse.ACL();
acl.setPublicReadAccess(true);
acl.setPublicWriteAccess(true);
acl.setReadAccess(currentUser.id, true);
acl.setWriteAccess(currentUser.id, true);
contactQuery.setACL(acl);
const res = await contactQuery.save();
const parseData = JSON.parse(JSON.stringify(res));
if (props.details) {
props.details({
value: parseData[props.valueKey],
label: parseData[props.displayKey]
});
}
if (props.closePopup) {
props.closePopup();
}
if (props.handleUserData) {
props.handleUserData(parseData);
}
setIsLoader(false);
// Reset the form fields
setAddYourself(false);
setName("");
setPhone("");
setEmail("");
}
} catch (err) {
console.log("err ", err);
if (err.code === 202) {
const params = { email: email };
const userRes = await Parse.Cloud.run("getUserId", params);
const currentUser = Parse.User.current();
contactQuery.set(
"CreatedBy",
Parse.User.createWithoutData(currentUser.id)
);
contactQuery.set("UserId", {
__type: "Pointer",
className: "_User",
objectId: userRes.id
});
const acl = new Parse.ACL();
acl.setPublicReadAccess(true);
acl.setPublicWriteAccess(true);
acl.setReadAccess(currentUser.id, true);
acl.setWriteAccess(currentUser.id, true);
contactQuery.setACL(acl);
const res = await contactQuery.save();
const parseData = JSON.parse(JSON.stringify(res));
if (props.details) {
props.details({
value: parseData[props.valueKey],
label: parseData[props.displayKey]
});
}
if (props.closePopup) {
props.closePopup();
}
if (props.handleUserData) {
props.handleUserData(parseData);
}
setIsLoader(false);
// Reset the form fields
setAddYourself(false);
setName("");
setPhone("");
setEmail("");
}
}
} else {
alert(t("add-signer-alert"));
setIsLoader(false);
alert(t("something-went-wrong-mssg"));
}
} else {
} catch (err) {
console.log("err in fetch contact", err);
setIsLoader(false);
alert(t("something-went-wrong-mssg"));
}
} else {
setIsLoader(false);
alert(t("something-went-wrong-mssg"));
}
};
@@ -213,7 +206,6 @@ const AddSigner = (props) => {
</div>
)}
<div className="w-full mx-auto p-2 text-base-content">
<p className="mb-3 text-xs text-gray-500">{t("add-signer-note")}</p>
{isUserExist && (
<div className="mb-3 flex items-center">
<input
@@ -244,7 +236,6 @@ const AddSigner = (props) => {
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
onInput={(e) => e.target.setCustomValidity("")}
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
placeholder={t("enter-name")}
/>
</div>
<div className="mb-3">
@@ -264,7 +255,6 @@ const AddSigner = (props) => {
onInput={(e) => e.target.setCustomValidity("")}
disabled={addYourself}
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
placeholder={t("enter-email")}
/>
</div>
<div className="mb-3">
@@ -278,7 +268,6 @@ const AddSigner = (props) => {
onChange={(e) => setPhone(e.target.value)}
disabled={addYourself}
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
placeholder={t("phone-optional")}
/>
</div>
<div className="mt-4 flex gap-x-2 justify-start">
+198
View File
@@ -0,0 +1,198 @@
import React, { useEffect, useState } from "react";
import Parse from "parse";
import Title from "./Title";
import Loader from "../primitives/Loader";
import { useTranslation } from "react-i18next";
const AddTeam = (props) => {
const { t } = useTranslation();
const [formdata, setFormdata] = useState({
name: "",
team: ""
});
const [isLoader, setIsLoader] = useState(false);
const [teamList, setTeamList] = useState([]);
useEffect(() => {
getTeamList();
}, []);
const getTeamList = async () => {
setIsLoader(true);
try {
const teams = await Parse.Cloud.run("getteams", { active: true });
const teamRes = JSON.parse(JSON.stringify(teams));
if (teamRes.length > 0) {
const _teamRes = JSON.parse(JSON.stringify(teamRes));
const allUsersteam = _teamRes.find((x) => x.Name === "All Users");
if (allUsersteam) {
setFormdata({ team: allUsersteam.objectId });
}
setTeamList(_teamRes);
}
} catch (err) {
console.log("Err in fetch top level teamList", err);
} finally {
setIsLoader(false);
}
};
const handleDropdown = (e) => {
setFormdata((prev) => ({ ...prev, [e.target.name]: e.target.value }));
};
// Define a function to handle form submission
const handleSubmit = async (e) => {
e.preventDefault();
e.stopPropagation();
// Extracting values except for the 'name' key
let updatedAncestors = [];
if (formdata.team) {
const Ancestors = teamList.find(
(x) => x.objectId === formdata.team
)?.Ancestors;
if (Ancestors && Ancestors.length > 0) {
updatedAncestors = Ancestors.map((x) => ({
__type: "Pointer",
className: "contracts_Teams",
objectId: x.objectId
}));
} else {
const AllUser = teamList.find((x) => x.objectId === "All Users");
updatedAncestors = [
AllUser,
{
__type: "Pointer",
className: "contracts_Teams",
objectId: formdata.team
}
];
}
}
try {
setIsLoader(true);
let data = { Name: formdata.name };
if (updatedAncestors.length > 0) {
const ParentId = updatedAncestors[updatedAncestors.length - 1];
data["ParentId"] = ParentId?.objectId;
data["Ancestors"] = updatedAncestors;
}
const newTeamRes = await Parse.Cloud.run("addteam", data);
// console.log("teamRes ", newTeamRes);
if (updatedAncestors.length > 0) {
const ParentId = teamList.find((x) => x.objectId === formdata.team);
props.handleTeamInfo({
objectId: newTeamRes.id,
Name: formdata.name,
ParentId: ParentId,
Ancestors: updatedAncestors,
IsActive: true
});
} else {
props.handleTeamInfo({
objectId: newTeamRes.id,
Name: formdata.name,
ParentId: "",
Ancestors: "",
IsActive: true
});
}
if (props.closePopup) {
props.closePopup();
}
setFormdata({ name: "", team: { name: "", objectId: "" } });
props.setIsAlert({ type: "success", msg: "Team created successfully." });
} catch (err) {
console.log("err in save team", err);
if (err.code === 137) {
props.setIsAlert({ type: "danger", msg: "Teams already exists." });
} else if (err.code === 102) {
props.setIsAlert({ type: "warning", msg: "Provide team name." });
} else {
props.setIsAlert({ type: "danger", msg: "Something went wrong." });
}
} finally {
setTimeout(() => props.setIsAlert({ type: "success", msg: "" }), 1500);
setIsLoader(false);
}
};
// Define a function to handle the "add yourself" checkbox
const handleReset = () => {
setFormdata({
name: "",
team: { name: "", objectId: "" }
});
};
const handleChange = (e) => {
setFormdata((prev) => ({ ...prev, [e.target.name]: e.target.value }));
};
return (
<div className="shadow-md rounded-box my-[1px] p-3 bg-base-100 relative">
<Title title="Add Team" />
{isLoader && (
<div className="absolute w-full h-full inset-0 flex justify-center items-center bg-base-content/30 z-50">
<Loader />
</div>
)}
<div className="w-full mx-auto">
<form onSubmit={handleSubmit}>
{/* <h1 className="text-[20px] font-semibold mb-4">Add User</h1> */}
<div className="mb-3">
<label
htmlFor="name"
className="block text-xs text-gray-700 font-semibold"
>
{t("name")}
<span className="text-[red] text-[13px]"> *</span>
</label>
<input
type="text"
name="name"
value={formdata.name}
onChange={(e) => handleChange(e)}
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
onInput={(e) => e.target.setCustomValidity("")}
required
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
/>
</div>
<div className="mb-3">
<label
htmlFor="phone"
className="block text-xs text-gray-700 font-semibold"
>
{t("report-heading.Parent Team")}
</label>
<select
value={formdata.team}
onChange={(e) => handleDropdown(e)}
name="team"
className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content w-full text-xs"
>
{teamList.length > 0 &&
teamList.map((x) => (
<option key={x.objectId} value={x.objectId}>
{x.Name}
</option>
))}
</select>
</div>
<div className="flex items-center mt-3 gap-2 text-white">
<button type="submit" className="op-btn op-btn-primary">
{t("submit")}
</button>
<div
type="button"
onClick={() => handleReset()}
className="op-btn op-btn-secondary"
>
{t("reset")}
</div>
</div>
</form>
</div>
</div>
);
};
export default AddTeam;
+554
View File
@@ -0,0 +1,554 @@
import React, { useEffect, useState } from "react";
import Parse from "parse";
import Title from "./Title";
import Loader from "../primitives/Loader";
import { copytoData, fetchSubscriptionInfo } from "../constant/Utils";
import { isEnableSubscription } from "../constant/const";
import { useTranslation } from "react-i18next";
function generatePassword(length) {
const characters =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let result = "";
const charactersLength = characters.length;
for (let i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}
const AddUser = (props) => {
const { t } = useTranslation();
const [formdata, setFormdata] = useState({
name: "",
phone: "",
email: "",
team: "",
password: "",
role: ""
});
const [amount, setAmount] = useState({ quantity: 1, price: 0 });
const [planInfo, setPlanInfo] = useState({
priceperUser: 0,
price: 0,
totalAllowedUser: 0,
adminId: ""
});
const [isFormLoader, setIsFormLoader] = useState(false);
const [isLoader, setIsLoader] = useState(false);
const [teamList, setTeamList] = useState([]);
const [allowedUser, setAllowedUser] = useState(0);
const [err, setErr] = useState("");
const role = ["OrgAdmin", "Editor", "User"];
const extUser =
localStorage.getItem("Extand_Class") &&
JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
useEffect(() => {
getTeamList();
// eslint-disable-next-line
}, []);
const getTeamList = async () => {
if (isEnableSubscription) {
try {
setIsLoader(true);
const resSub = await fetchSubscriptionInfo();
if (!resSub.error) {
setPlanInfo((prev) => ({
...prev,
priceperUser: resSub.price,
totalAllowedUser: resSub.totalAllowedUser,
adminId: resSub.adminId
}));
setAmount((prev) => ({ ...prev, price: resSub.price }));
const res = await Parse.Cloud.run("allowedusers");
if (props.setFormHeader) {
if (res > 0) {
props.setFormHeader(t("add-user"));
} else {
props.setFormHeader(
resSub.adminId !== extUser?.objectId
? "Unauthorized"
: t("Add-seats")
);
}
}
setAllowedUser(res);
} else {
setAllowedUser(0);
}
} catch (err) {
setAllowedUser(0);
console.log("Err in alloweduser", err);
setErr(err.message);
} finally {
setIsLoader(false);
}
}
setFormdata((prev) => ({ ...prev, password: generatePassword(12) }));
const teamRes = await Parse.Cloud.run("getteams", { active: true });
if (teamRes.length > 0) {
const _teamRes = JSON.parse(JSON.stringify(teamRes));
setTeamList(_teamRes);
if (!isEnableSubscription) {
const allUserId =
_teamRes.find((x) => x.Name === "All Users")?.objectId || "";
setFormdata((prev) => ({ ...prev, team: allUserId }));
}
}
};
const checkUserExist = async () => {
try {
const res = await Parse.Cloud.run("getUserDetails", {
email: formdata.email
});
if (res) {
return true;
} else {
return false;
}
} catch (err) {
console.log("err", err);
}
};
// Define a function to handle form submission
const handleSubmit = async (e) => {
e.preventDefault();
e.stopPropagation();
const localUser = JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
setIsFormLoader(true);
const res = await checkUserExist();
if (res) {
props.setIsAlert({ type: "danger", msg: t("user-already-exist") });
setIsFormLoader(false);
setTimeout(() => props.setIsAlert({ type: "success", msg: "" }), 1000);
} else {
try {
const extUser = new Parse.Object("contracts_Users");
extUser.set("Name", formdata.name);
if (formdata.phone) {
extUser.set("Phone", formdata.phone);
}
extUser.set("Email", formdata.email);
extUser.set("UserRole", `contracts_${formdata.role}`);
if (formdata?.team) {
extUser.set("TeamIds", [
{
__type: "Pointer",
className: "contracts_Teams",
objectId: formdata.team
}
]);
}
if (localUser && localUser.OrganizationId) {
extUser.set("OrganizationId", {
__type: "Pointer",
className: "contracts_Organizations",
objectId: localUser.OrganizationId.objectId
});
}
if (localUser && localUser.Company) {
extUser.set("Company", localUser.Company);
}
if (localStorage.getItem("TenantId")) {
extUser.set("TenantId", {
__type: "Pointer",
className: "partners_Tenant",
objectId: localStorage.getItem("TenantId")
});
}
try {
const _users = Parse.Object.extend("User");
const _user = new _users();
_user.set("name", formdata.name);
_user.set("username", formdata.email);
_user.set("email", formdata.email);
_user.set("password", formdata.password);
if (formdata.phone) {
_user.set("phone", formdata.phone);
}
const user = await _user.save();
if (user) {
const currentUser = Parse.User.current();
extUser.set(
"CreatedBy",
Parse.User.createWithoutData(currentUser.id)
);
extUser.set("UserId", user);
const acl = new Parse.ACL();
acl.setPublicReadAccess(true);
acl.setPublicWriteAccess(true);
acl.setReadAccess(currentUser.id, true);
acl.setWriteAccess(currentUser.id, true);
extUser.setACL(acl);
const res = await extUser.save();
const parseData = JSON.parse(JSON.stringify(res));
if (props.closePopup) {
props.closePopup();
}
if (props.handleUserData) {
if (formdata?.team) {
const team = teamList.find((x) => x.objectId === formdata.team);
parseData.TeamIds = parseData.TeamIds.map((y) =>
y.objectId === team.objectId ? team : y
);
}
props.handleUserData(parseData);
}
setIsFormLoader(false);
setFormdata({ name: "", email: "", phone: "", team: "", role: "" });
}
} catch (err) {
console.log("err ", err);
if (err.code === 202) {
const params = { email: formdata.email };
const userRes = await Parse.Cloud.run("getUserId", params);
const currentUser = Parse.User.current();
extUser.set(
"CreatedBy",
Parse.User.createWithoutData(currentUser.id)
);
extUser.set("UserId", {
__type: "Pointer",
className: "_User",
objectId: userRes.id
});
const acl = new Parse.ACL();
acl.setPublicReadAccess(true);
acl.setPublicWriteAccess(true);
acl.setReadAccess(currentUser.id, true);
acl.setWriteAccess(currentUser.id, true);
extUser.setACL(acl);
const res = await extUser.save();
const parseData = JSON.parse(JSON.stringify(res));
if (props.closePopup) {
props.closePopup();
}
if (props.handleUserData) {
if (formdata?.team) {
const team = teamList.find((x) => x.objectId === formdata.team);
parseData.TeamIds = parseData.TeamIds.map((y) =>
y.objectId === team.objectId ? team : y
);
}
props.handleUserData(parseData);
}
setIsFormLoader(false);
setFormdata({ name: "", email: "", phone: "", team: "", role: "" });
}
}
} catch (err) {
console.log("err", err);
setIsFormLoader(false);
props.setIsAlert({
type: "danger",
msg: t("something-went-wrong-mssg")
});
} finally {
setTimeout(() => props.setIsAlert({ type: "success", msg: "" }), 1500);
}
}
};
// Define a function to handle the "add yourself" checkbox
const handleReset = () => {
setFormdata({ name: "", email: "", phone: "", team: "", role: "" });
if (props.closePopup) {
props.closePopup();
}
};
const handleChange = (event) => {
let { name, value } = event.target;
if (name === "email") {
value = value?.toLowerCase()?.replace(/\s/g, "");
}
setFormdata((prev) => ({ ...prev, [name]: value }));
};
const copytoclipboard = (text) => {
copytoData(text);
props.setIsAlert({ type: "success", msg: t("copied") });
setTimeout(() => props.setIsAlert({ type: "success", msg: "" }), 1500); // Reset copied state after 1.5 seconds
};
const handlePricePerUser = (e) => {
const quantity = e.target.value;
const price = e.target?.value > 0 ? planInfo.priceperUser * quantity : 0;
setAmount((prev) => ({ ...prev, quantity: quantity, price: price }));
};
const handleAddOnSubmit = async (e) => {
e.preventDefault();
e.stopPropagation();
setIsFormLoader(true);
try {
const resAddon = await Parse.Cloud.run("buyaddonusers", {
users: amount.quantity
});
if (resAddon) {
const _resAddon = JSON.parse(JSON.stringify(resAddon));
if (_resAddon.status === "success") {
setAllowedUser(amount.quantity);
setPlanInfo((obj) => ({ ...obj, totalAllowedUser: _resAddon.addon }));
}
if (props.handleBuyUsers) {
props.handleBuyUsers(amount.quantity, _resAddon.addon);
}
}
} catch (err) {
console.log("Err in buy addon", err);
props.setIsAlert({ type: "danger", msg: t("something-went-wrong-mssg") });
} finally {
setTimeout(() => props.setIsAlert({ type: "success", msg: "" }), 2000);
setIsFormLoader(false);
}
};
return (
<div className="shadow-md rounded-box my-[1px] p-3 bg-base-100 relative">
<Title title={t("add-user")} />
{isFormLoader && (
<div className="absolute w-full h-full inset-0 flex justify-center items-center bg-base-content/30 z-50">
<Loader />
</div>
)}
{!isLoader ? (
<>
{err ? (
<div className="flex justify-center items-center h-[70px] text-[18px]">
{err}
</div>
) : (
<div className="w-full mx-auto">
{!isEnableSubscription || allowedUser > 0 ? (
<form onSubmit={handleSubmit}>
{isEnableSubscription && (
<p className="flex justify-center mb-1">
{t("remaing-users")}{" "}
<span
className={`${
allowedUser < 2 ? "op-text-accent" : "op-text-primary"
} font-medium ml-1`}
>
{allowedUser} {t("of")} {planInfo.totalAllowedUser}
</span>
</p>
)}
<div className="mb-3">
<label
htmlFor="name"
className="block text-xs text-gray-700 font-semibold"
>
{t("name")}
<span className="text-[red] text-[13px]"> *</span>
</label>
<input
type="text"
name="name"
value={formdata.name}
onChange={(e) => handleChange(e)}
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
required
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
/>
</div>
<div className="mb-3">
<label
htmlFor="email"
className="block text-xs text-gray-700 font-semibold"
>
{t("email")}
<span className="text-[red] text-[13px]"> *</span>
</label>
<input
type="email"
name="email"
value={formdata.email}
onChange={(e) => handleChange(e)}
required
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
/>
</div>
<div className="mb-3">
<label className="block text-xs text-gray-700 font-semibold">
{t("password")}
</label>
<div className="flex justify-between items-center op-input op-input-bordered op-input-sm text-base-content w-full h-full text-[13px]">
<div className="break-all">{formdata?.password}</div>
<i
onClick={() => copytoclipboard(formdata?.password)}
className="fa-light fa-copy rounded-full hover:bg-base-300 p-[8px] cursor-pointer "
></i>
</div>
<div className="text-[12px] ml-2 mb-0 text-[red] select-none">
{t("password-generateed")}
</div>
</div>
<div className="mb-3">
<label
htmlFor="phone"
className="block text-xs text-gray-700 font-semibold"
>
{t("phone")}
{/* <span className="text-[red] text-[13px]"> *</span> */}
</label>
<input
type="text"
name="phone"
placeholder="optional"
value={formdata.phone}
onChange={(e) => handleChange(e)}
// required
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
/>
</div>
{isEnableSubscription && (
<div className="mb-3">
<label
htmlFor="phone"
className="block text-xs text-gray-700 font-semibold"
>
{t("team")}
<span className="text-[red] text-[13px]"> *</span>
</label>
<select
name="team"
value={formdata.team}
onChange={(e) => handleChange(e)}
className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content w-full text-xs"
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
required
>
<option defaultValue={""} value={""}>
{t("Select")}
</option>
{teamList.length > 0 &&
teamList.map((x) => (
<option key={x.objectId} value={x.objectId}>
{x.Name}
</option>
))}
</select>
</div>
)}
<div className="mb-3">
<label
htmlFor="phone"
className="block text-xs text-gray-700 font-semibold"
>
{t("Role")}
<span className="text-[red] text-[13px]"> *</span>
</label>
<select
value={formdata.role}
onChange={(e) => handleChange(e)}
name="role"
className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content w-full text-xs"
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
required
>
<option defaultValue={""} value={""}>
{t("Select")}
</option>
{role.length > 0 &&
role.map((x) => (
<option key={x} value={x}>
{x}
</option>
))}
</select>
</div>
<div className="flex items-center mt-3 gap-2 text-white">
<button type="submit" className="op-btn op-btn-primary">
{t("submit")}
</button>
<div
type="button"
onClick={() => handleReset()}
className="op-btn op-btn-secondary"
>
{t("cancel")}
</div>
</div>
</form>
) : (
<>
{planInfo.adminId !== extUser?.objectId ? (
<div className="mb-3 mt-1 flex justify-center text-center items-center font-medium break-all">
{t("unauthorized-modal", {
adminEmail: extUser?.TenantId?.EmailAddress
})}
</div>
) : (
<form onSubmit={handleAddOnSubmit}>
<p className="flex justify-center text-center mx-2 mb-3 text-base op-text-accent font-medium">
{t("additional-users")}
</p>
<div className="mb-3 flex justify-between">
<label
htmlFor="quantity"
className="block text-xs text-gray-700 font-semibold"
>
{t("Quantity-of-users")}
<span className="text-[red] text-[13px]"> *</span>
</label>
<input
type="number"
name="quantity"
value={amount.quantity}
onChange={(e) => handlePricePerUser(e)}
className="w-1/4 op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content text-xs"
required
/>
</div>
<div className="mb-3 flex justify-between">
<label className="block text-xs text-gray-700 font-semibold">
{t("Price")} (1 * {planInfo.priceperUser})
</label>
<div className="w-1/4 flex justify-center items-center text-sm">
USD {amount.price}
</div>
</div>
<hr className="text-base-content mb-3" />
<button className="op-btn op-btn-primary w-full">
{t("Proceed")}
</button>
</form>
)}
</>
)}
</div>
)}
</>
) : (
<div className="w-full h-[200px] inset-0 flex justify-center items-center z-50">
<Loader />
</div>
)}
</div>
);
};
export default AddUser;
-287
View File
@@ -1,287 +0,0 @@
import { useEffect, useState } from "react";
import Parse from "parse";
import Loader from "../primitives/Loader";
import {
copytoData,
usertimezone
} from "../constant/Utils";
import {
emailRegex,
} from "../constant/const";
import {
useTranslation
} from "react-i18next";
function generatePassword(length) {
const characters =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let result = "";
const charactersLength = characters.length;
for (let i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}
const AddUser = (props) => {
const { t } = useTranslation();
const [formdata, setFormdata] = useState({
name: "",
phone: "",
email: "",
team: "",
password: "",
role: ""
});
const [isFormLoader, setIsFormLoader] = useState(false);
const [teamList, setTeamList] = useState([]);
const role = ["OrgAdmin", "Editor", "User"];
useEffect(() => {
getTeamList();
// eslint-disable-next-line
}, []);
const getTeamList = async () => {
setFormdata((prev) => ({ ...prev, password: generatePassword(12) }));
const teamRes = await Parse.Cloud.run("getteams", { active: true });
if (teamRes.length > 0) {
const _teamRes = JSON.parse(JSON.stringify(teamRes));
setTeamList(_teamRes);
const allUserId =
_teamRes.find((x) => x.Name === "All Users")?.objectId || "";
setFormdata((prev) => ({ ...prev, team: allUserId }));
}
};
const checkUserExist = async () => {
try {
const res = await Parse.Cloud.run("getUserDetails", {
email: formdata.email
});
if (res) {
return true;
} else {
return false;
}
} catch (err) {
console.log("err", err);
}
};
// Define a function to handle form submission
const handleSubmit = async (e) => {
e.preventDefault();
e.stopPropagation();
if (!emailRegex.test(formdata.email)) {
alert(t("valid-email-alert"));
} else {
const localUser = JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
setIsFormLoader(true);
const res = await checkUserExist();
if (res) {
props.showAlert("danger", t("user-already-exist"));
setIsFormLoader(false);
} else {
if (localStorage.getItem("TenantId")) {
const timezone = usertimezone;
try {
const params = {
name: formdata.name,
email: formdata.email,
phone: formdata.phone,
password: formdata.password,
role: formdata.role,
team: formdata.team,
timezone: timezone,
tenantId: localStorage.getItem("TenantId"),
organization: {
objectId: localUser?.OrganizationId?.objectId,
company: localUser?.Company
},
};
const res = await Parse.Cloud.run("adduser", params);
const parseData = JSON.parse(JSON.stringify(res));
if (props.closePopup) {
props.closePopup();
}
if (props.handleUserData) {
if (formdata?.team) {
const team = teamList.find((x) => x.objectId === formdata.team);
parseData.TeamIds = parseData.TeamIds.map((y) =>
y.objectId === team.objectId ? team : y
);
}
props.handleUserData(parseData);
}
setIsFormLoader(false);
setFormdata({
name: "",
email: "",
phone: "",
team: "",
role: ""
});
props.showAlert("success", t("user-created-successfully"));
} catch (err) {
console.log("err", err);
setIsFormLoader(false);
props.showAlert("danger", t("something-went-wrong-mssg"));
}
} else {
props.showAlert("danger", t("something-went-wrong-mssg"));
}
}
}
};
// Define a function to handle the "add yourself" checkbox
const handleReset = () => {
setFormdata({ name: "", email: "", phone: "", team: "", role: "" });
if (props.closePopup) {
props.closePopup();
}
};
const handleChange = (event) => {
let { name, value } = event.target;
if (name === "email") {
value = value?.toLowerCase()?.replace(/\s/g, "");
}
setFormdata((prev) => ({ ...prev, [name]: value }));
};
const copytoclipboard = (text) => {
copytoData(text);
props.showAlert("success", t("copied"));
};
return (
<div className="shadow-md rounded-box my-[1px] p-3 bg-base-100 relative">
{isFormLoader && (
<div className="absolute w-full h-full inset-0 flex justify-center items-center bg-base-content/30 z-50">
<Loader />
</div>
)}
<div className="w-full mx-auto">
<form onSubmit={handleSubmit}>
<div className="mb-3">
<label
htmlFor="name"
className="block text-xs font-semibold"
>
{t("name")}
<span className="text-[red] text-[13px]"> *</span>
</label>
<input
type="text"
name="name"
value={formdata.name}
onChange={(e) => handleChange(e)}
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
required
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
placeholder={t("enter-name")}
/>
</div>
<div className="mb-3">
<label
htmlFor="email"
className="block text-xs font-semibold"
>
{t("email")}
<span className="text-[red] text-[13px]"> *</span>
</label>
<input
type="email"
name="email"
value={formdata.email}
onChange={(e) => handleChange(e)}
required
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
placeholder={t("enter-email")}
/>
</div>
<div className="mb-3">
<label className="block text-xs font-semibold">
{t("password")}
</label>
<div className="flex justify-between items-center op-input op-input-bordered op-input-sm text-base-content w-full h-full text-[13px]">
<div className="break-all">{formdata?.password}</div>
<i
onClick={() => copytoclipboard(formdata?.password)}
className="fa-light fa-copy rounded-full hover:bg-base-300 p-[8px] cursor-pointer "
></i>
</div>
<div className="text-[12px] ml-2 mb-0 text-[red] select-none">
{t("password-generateed")}
</div>
</div>
<div className="mb-3">
<label
htmlFor="phone"
className="block text-xs font-semibold"
>
{t("phone")}
{/* <span className="text-[red] text-[13px]"> *</span> */}
</label>
<input
type="text"
name="phone"
placeholder={t("phone-optional")}
value={formdata.phone}
onChange={(e) => handleChange(e)}
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
/>
</div>
<div className="mb-3">
<label
htmlFor="phone"
className="block text-xs font-semibold"
>
{t("Role")}
<span className="text-[red] text-[13px]"> *</span>
</label>
<select
value={formdata.role}
onChange={(e) => handleChange(e)}
name="role"
className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content w-full text-xs"
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
required
>
<option defaultValue={""} value={""}>
{t("Select")}
</option>
{role.length > 0 &&
role.map((x) => (
<option key={x} value={x}>
{x}
</option>
))}
</select>
</div>
<div className="flex items-center mt-3 gap-2 text-white">
<button type="submit" className="op-btn op-btn-primary">
{t("submit")}
</button>
<div
type="button"
onClick={() => handleReset()}
className="op-btn op-btn-secondary"
>
{t("cancel")}
</div>
</div>
</form>
</div>
</div>
);
};
export default AddUser;
+505
View File
@@ -0,0 +1,505 @@
import React, { useState, useEffect, useRef } from "react";
import axios from "axios";
import SuggestionInput from "./shared/fields/SuggestionInput";
import Loader from "../primitives/Loader";
import { useTranslation } from "react-i18next";
import Parse from "parse";
import ModalUi from "../primitives/ModalUi";
import { isEnableSubscription } from "../constant/const";
import { fetchSubscription } from "../constant/Utils";
import { useNavigate } from "react-router-dom";
const BulkSendUi = (props) => {
const { t } = useTranslation();
const navigate = useNavigate();
const [forms, setForms] = useState([]);
const [formId, setFormId] = useState(2);
const formRef = useRef(null);
const [scrollOnNextUpdate, setScrollOnNextUpdate] = useState(false);
const [isSubmit, setIsSubmit] = useState(false);
const [allowedForm, setAllowedForm] = useState(0);
const [isSignatureExist, setIsSignatureExist] = useState();
const [isBulkAvailable, setIsBulkAvailable] = useState(false);
const quantityList = [500, 1000, 5000, 50000];
const [amount, setAmount] = useState({
price: (75.0).toFixed(2),
quantity: 500,
priceperbulksend: 0.15,
totalcredits: 0
});
const [isQuotaReached, setIsQuotaReached] = useState(false);
const [isLoader, setIsLoader] = useState(false);
const [isFreePlan, setIsFreePlan] = useState(false);
const [admin, setAdmin] = useState({
objectId: "",
email: "",
isAdmin: true
});
const allowedSigners = 50;
useEffect(() => {
signatureExist();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
//function to check atleast one signature field exist
const signatureExist = async () => {
if (isEnableSubscription) {
setIsLoader(true);
try {
const subscription = await fetchSubscription();
const extUser =
localStorage.getItem("Extand_Class") &&
JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
if (
subscription?.adminId &&
extUser?.objectId === subscription?.adminId
) {
setAdmin((obj) => ({
...obj,
objectId: subscription?.adminId,
isAdmin: true
}));
} else {
setAdmin((obj) => ({
...obj,
isAdmin: false,
email: extUser?.TenantId?.EmailAddress
}));
}
setAdmin((obj) => ({ ...obj, objectId: subscription?.adminId }));
if (subscription?.plan === "freeplan") {
setIsFreePlan(true);
}
const resCredits = await Parse.Cloud.run("allowedcredits");
if (resCredits) {
const allowedcredits = resCredits?.allowedcredits || 0;
const addoncredits = resCredits?.addoncredits || 0;
const totalcredits = allowedcredits + addoncredits;
if (totalcredits > 0) {
if (subscription?.plan !== "freeplan") {
setIsBulkAvailable(true);
}
}
setAmount((obj) => ({ ...obj, totalcredits: totalcredits }));
}
const getPlaceholder = props.item?.Placeholders;
const checkIsSignatureExistt = getPlaceholder?.every((placeholderObj) =>
placeholderObj?.placeHolder?.some((holder) =>
holder?.pos?.some((posItem) => posItem?.type === "signature")
)
);
setIsSignatureExist(checkIsSignatureExistt);
setIsLoader(false);
} catch (err) {
setIsLoader(false);
alert(t("something-went-wrong-mssg"));
console.log("Err", err);
}
} else {
setIsBulkAvailable(true);
setAdmin((obj) => ({ ...obj, isAdmin: true }));
const getPlaceholder = props.item?.Placeholders;
const checkIsSignatureExistt = getPlaceholder?.every((placeholderObj) =>
placeholderObj?.placeHolder?.some((holder) =>
holder?.pos?.some((posItem) => posItem?.type === "signature")
)
);
setIsSignatureExist(checkIsSignatureExistt);
setIsLoader(false);
}
};
useEffect(() => {
if (scrollOnNextUpdate && formRef.current) {
formRef.current.scrollIntoView({
behavior: "smooth",
block: "end",
inline: "nearest"
});
setScrollOnNextUpdate(false);
}
}, [forms, scrollOnNextUpdate]);
useEffect(() => {
(() => {
if (props?.Placeholders?.length > 0) {
let users = [];
props?.Placeholders?.forEach((element) => {
if (!element.signerObjId) {
users = [
...users,
{
fieldId: element.Id,
email: "",
label: element.Role,
signer: {}
}
];
}
});
setForms((prevForms) => [...prevForms, { Id: 1, fields: users }]);
const totalForms = Math.floor(allowedSigners / users?.length);
setAllowedForm(totalForms);
}
})();
// eslint-disable-next-line
}, []);
const handleInputChange = (index, signer, fieldIndex) => {
const newForms = [...forms];
newForms[index].fields[fieldIndex].email = signer?.Email
? signer?.Email
: signer || "";
newForms[index].fields[fieldIndex].signer = signer?.objectId ? signer : "";
setForms(newForms);
};
const handleAddForm = (e) => {
e.preventDefault();
// Check if the quick send limit has been reached
if (isEnableSubscription && forms.length >= amount.totalcredits) {
setIsQuotaReached(true);
} else {
if (forms?.length < allowedForm) {
if (props?.Placeholders.length > 0) {
let newForm = [];
props?.Placeholders?.forEach((element) => {
if (!element.signerObjId) {
newForm = [
...newForm,
{
fieldId: element.Id,
email: "",
label: element.Role,
signer: {}
}
];
}
});
setForms([...forms, { Id: formId, fields: newForm }]);
}
setFormId(formId + 1);
setScrollOnNextUpdate(true);
} else {
// If the limit has been reached, throw an error with the appropriate message
alert(t("quick-send-alert-4"));
}
}
};
const handleRemoveForm = (index) => {
const updatedForms = forms.filter((_, i) => i !== index);
setForms(updatedForms);
};
const handleSubmit = async (e) => {
e.preventDefault();
e.stopPropagation();
setIsSubmit(true);
// Create a copy of Placeholders array from props.item
let Placeholders = [...props.item.Placeholders];
// Initialize an empty array to store updated documents
let Documents = [];
// Loop through each form
forms.forEach((form) => {
//checking if user enter email which already exist as a signer then add user in a signers array
let existSigner = [];
form.fields.map((data) => {
if (data.signer) {
existSigner.push(data.signer);
}
});
// Map through the copied Placeholders array to update email values
const updatedPlaceholders = Placeholders.map((placeholder) => {
// Find the field in the current form that matches the placeholder Id
const field = form.fields.find(
(element) => parseInt(element.fieldId) === placeholder.Id
);
// If a matching field is found, update the email value in the placeholder
const signer = field?.signer?.objectId ? field.signer : "";
if (field) {
if (signer) {
return {
...placeholder,
signerObjId: field?.signer?.objectId || "",
signerPtr: signer
};
} else {
return {
...placeholder,
email: field.email,
signerObjId: field?.signer?.objectId || "",
signerPtr: signer
};
}
}
// If no matching field is found, keep the placeholder as is
return placeholder;
});
// Push a new document object with updated Placeholders into the Documents array
if (existSigner?.length > 0) {
Documents.push({
...props.item,
Placeholders: updatedPlaceholders,
Signers: props.item.Signers
? [...props.item.Signers, ...existSigner]
: [...existSigner]
});
} else {
Documents.push({ ...props.item, Placeholders: updatedPlaceholders });
}
});
await batchQuery(Documents);
};
const batchQuery = async (Documents) => {
const serverUrl = localStorage.getItem("baseUrl");
const functionsUrl = `${serverUrl}functions/batchdocuments`;
const headers = {
"Content-Type": "application/json",
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
sessionToken: localStorage.getItem("accesstoken")
};
const params = { Documents: JSON.stringify(Documents) };
try {
const res = await axios.post(functionsUrl, params, { headers: headers });
// console.log("res ", res);
if (res.data && res.data.result) {
props.handleClose("success", Documents?.length);
}
} catch (err) {
console.log("Err ", err);
props.handleClose("error", 0);
} finally {
setIsSubmit(false);
}
};
const handleAddOnQuickSubmit = async (e) => {
e.preventDefault();
e.stopPropagation();
setIsSubmit(true);
try {
const resAddon = await Parse.Cloud.run("buycredits", {
credits: amount.quantity
});
if (resAddon) {
const _resAddon = JSON.parse(JSON.stringify(resAddon));
if (_resAddon.status === "success") {
setIsBulkAvailable(true);
handleCloseQuotaReached();
setAmount((obj) => ({
...obj,
quantity: 500,
priceperbulksend: 0.15,
price: (75.0).toFixed(2),
totalcredits: obj?.totalcredits + _resAddon.addon
}));
}
}
} catch (err) {
console.log("Err in buy addon", err);
alert(t("something-went-wrong-mssg"));
} finally {
setIsSubmit(false);
}
};
const handlePricePerQuick = async (e) => {
const quantity = e.target?.value;
const price =
quantity > 0
? (Math.round(quantity * amount.priceperbulksend * 100) / 100).toFixed(
2
)
: 500 * amount.priceperbulksend;
setAmount((prev) => ({ ...prev, quantity: quantity, price: price }));
};
const handleCloseQuotaReached = () => {
setIsQuotaReached(false);
};
const handleNavigation = () => {
navigate("/subscription");
};
return (
<>
{isLoader ? (
<div className="w-full h-[100px] flex justify-center items-center z-[999]">
<Loader />
</div>
) : (
<>
{isSubmit && (
<div className="absolute z-[999] h-full w-full flex justify-center items-center bg-black bg-opacity-30">
<Loader />
</div>
)}
{isBulkAvailable ? (
<>
{props.Placeholders?.length > 0 ? (
isSignatureExist ? (
<>
{props.Placeholders?.some((x) => !x.signerObjId) ? (
<div>
<form onSubmit={handleSubmit}>
<div className="min-h-max max-h-[250px] overflow-y-auto">
{forms?.map((form, index) => (
<div
key={form.Id}
className="p-3 op-card border-[1px] border-gray-400 mt-3 mx-4 mb-4 bg-base-200 text-base-content grid grid-cols-1 md:grid-cols-2 gap-2 relative"
>
{form?.fields?.map((field, fieldIndex) => (
<div
className="flex flex-col"
key={field.fieldId}
>
<label>{field.label}</label>
<SuggestionInput
required
type="email"
value={field.value}
index={fieldIndex}
onChange={(signer) =>
handleInputChange(
index,
signer,
fieldIndex
)
}
/>
</div>
))}
{forms?.length > 1 && (
<button
onClick={() => handleRemoveForm(index)}
className="absolute right-3 top-1 text-[red] border-[1px] border-[red] rounded-lg w-[1.7rem] h-[1.7rem]"
>
<i className="fa-light fa-trash"></i>
</button>
)}
<div ref={formRef}></div>
</div>
))}
</div>
<div className="flex flex-col mx-4 mb-4 gap-3">
{isEnableSubscription && (
<button
onClick={handleAddForm}
className="op-btn op-btn-primary focus:outline-none"
>
<i className="fa-light fa-plus"></i>{" "}
<span>{t("add-new")}</span>
</button>
)}
<button
type="submit"
className="op-btn op-btn-accent focus:outline-none"
>
<i className="fa-light fa-paper-plane"></i>{" "}
<span>{t("send")}</span>
</button>
</div>
</form>
<ModalUi
isOpen={isQuotaReached}
handleClose={() => handleCloseQuotaReached()}
>
<div className="p-4 flex justify-center items-center flex-col gap-y-3">
<p className="text-center text-base-content">
{t("quota-err-quicksend")}
</p>
<button
onClick={() => setIsBulkAvailable(false)}
className=" op-btn op-btn-primary w-[200px]"
>
{t("buy-credits")}
</button>
</div>
</ModalUi>
</div>
) : (
<div className="text-black p-3 bg-white w-full text-sm md:text-base flex justify-center items-center">
{t("quick-send-alert-1")}
</div>
)}
</>
) : (
<div className="text-black p-3 bg-white w-full text-sm md:text-base flex justify-center items-center">
{t("quick-send-alert-2")}
</div>
)
) : (
<div className="text-black p-3 bg-white w-full text-sm md:text-base flex justify-center items-center">
{t("quick-send-alert-3")}
</div>
)}
</>
) : (
<>
{isFreePlan ? (
<div className="w-full h-[130px] flex flex-col justify-center items-center text-center p-4">
<p className="text-base font-medium mb-2.5">
{t("bulk-send-subcription-alert")}
</p>
<button
onClick={() => handleNavigation()}
className="op-btn op-btn-primary"
>
{t("upgrade-now")}
</button>
</div>
) : (
<>
{admin?.isAdmin ? (
<form onSubmit={handleAddOnQuickSubmit} className="p-3">
<p className="flex justify-center text-center mx-2 mb-3 text-base op-text-accent font-medium">
{t("additional-credits")}
</p>
<div className="mb-3 flex justify-between">
<label
htmlFor="quantity"
className="block text-xs text-gray-700 font-semibold"
>
{t("quantity-of-credits")}
<span className="text-[red] text-[13px]">*</span>
</label>
<select
value={amount.quantity}
onChange={(e) => handlePricePerQuick(e)}
name="quantity"
className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content w-1/4 text-xs"
required
>
{quantityList.length > 0 &&
quantityList.map((x) => (
<option key={x} value={x}>
{x}
</option>
))}
</select>
</div>
<div className="mb-3 flex justify-between">
<label className="block text-xs text-gray-700 font-semibold">
{t("Price")} (1 * {amount.priceperbulksend})
</label>
<div className="w-1/4 flex justify-center items-center text-sm">
USD {amount.price}
</div>
</div>
<hr className="text-base-content mb-3" />
<button className="op-btn op-btn-primary w-full mt-2">
{t("Proceed")}
</button>
</form>
) : (
<div className="mx-8 mt-4 mb-8 flex justify-center text-center items-center font-medium break-all">
{t("unauthorized-modal", { adminEmail: admin?.email })}
</div>
)}
</>
)}
</>
)}
</>
)}
</>
);
};
export default BulkSendUi;
-316
View File
@@ -1,316 +0,0 @@
import React, { useState, useEffect, useRef } from "react";
import axios from "axios";
import SuggestionInput from "./shared/fields/SuggestionInput";
import Loader from "../primitives/Loader";
import { useTranslation } from "react-i18next";
import {
emailRegex,
} from "../constant/const";
const BulkSendUi = (props) => {
const { t } = useTranslation();
const [forms, setForms] = useState([]);
const formRef = useRef(null);
const [scrollOnNextUpdate, setScrollOnNextUpdate] = useState(false);
const [isSubmit, setIsSubmit] = useState(false);
const [isSignatureExist, setIsSignatureExist] = useState();
const [isDisableBulkSend, setIsDisableBulkSend] = useState(false);
const [isLoader, setIsLoader] = useState(false);
const [signers, setSigners] = useState([]);
const [emails, setEmails] = useState([]);
const [isPrefillExist, setIsPrefillExist] = useState(false);
useEffect(() => {
signatureExist();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
//function to check at least one signature field exist
const signatureExist = async () => {
const isPrefill = props?.Placeholders.some((x) => x?.Role === "prefill");
if (isPrefill) {
setIsPrefillExist(isPrefill);
}
setIsDisableBulkSend(false);
const getPlaceholder = props?.Placeholders;
const removePrefill = getPlaceholder.filter((x) => x?.Role !== "prefill");
const checkIsSignatureExistt = removePrefill?.every((placeholderObj) =>
placeholderObj?.placeHolder?.some((holder) =>
holder?.pos?.some((posItem) => posItem?.type === "signature")
)
);
setIsSignatureExist(checkIsSignatureExistt);
setIsLoader(false);
};
useEffect(() => {
if (scrollOnNextUpdate && formRef.current) {
formRef.current.scrollIntoView({
behavior: "smooth",
block: "end",
inline: "nearest"
});
setScrollOnNextUpdate(false);
}
}, [forms, scrollOnNextUpdate]);
useEffect(() => {
(() => {
if (props?.Placeholders?.length > 0) {
let users = [];
let emails = [];
props?.Placeholders?.forEach((element) => {
const signerEmail = element?.email || element?.signerPtr?.Email;
// only add when there's a non-empty signerEmail
if (signerEmail) {
emails = [...emails, signerEmail];
}
if (!element.signerObjId) {
users = [
...users,
{
fieldId: element.Id,
email: "",
label: element.Role,
signer: {}
}
];
}
});
setEmails(emails);
setForms((prevForms) => [...prevForms, { Id: 1, fields: users }]);
const signer = props.item?.Signers?.filter((x) => x?.objectId);
setSigners(signer);
}
})();
// eslint-disable-next-line
}, []);
const handleInputChange = (index, signer, fieldIndex) => {
const newForms = [...forms];
newForms[index].fields[fieldIndex].email = signer?.Email
? signer?.Email
: signer || "";
newForms[index].fields[fieldIndex].signer = signer?.objectId ? signer : "";
setForms(newForms);
};
const handleRemoveForm = (index) => {
const updatedForms = forms.filter((_, i) => i !== index);
setForms(updatedForms);
};
function validateEmails(data) {
for (const item of data) {
let email = "";
for (const field of item.fields) {
if (!emailRegex.test(field.email)) {
alert(t("invalid-email-found", { email: field.email }));
return false;
} else if (email === field.email || emails?.includes(field.email)) {
alert(t("duplicate-email-found", { email: field.email }));
return false;
} else {
email = field.email;
}
}
}
return true;
}
const handleSubmit = async (e) => {
e.preventDefault();
e.stopPropagation();
setIsSubmit(true);
if (validateEmails(forms)) {
// Create a copy of Placeholders array from props.item
let Placeholders = [...props.Placeholders];
// Initialize an empty array to store updated documents
let Documents = [];
// Loop through each form
forms.forEach((form) => {
//checking if user enter email which already exist as a signer then add user in a signers array
let existSigner = [];
form.fields.map((data) => {
if (data.signer) {
existSigner.push(data.signer);
}
});
// Map through the copied Placeholders array to update email values
const updatedPlaceholders = Placeholders.map((placeholder) => {
// Find the field in the current form that matches the placeholder Id
const field = form.fields.find(
(element) => parseInt(element.fieldId) === placeholder.Id
);
// If a matching field is found, update the email value in the placeholder
const signer = field?.signer?.objectId ? field.signer : "";
if (field) {
if (signer) {
return {
...placeholder,
signerObjId: field?.signer?.objectId || "",
signerPtr: signer
};
} else {
return {
...placeholder,
email: field.email,
signerObjId: field?.signer?.objectId || "",
signerPtr: signer
};
}
}
// If no matching field is found, keep the placeholder as is
return placeholder;
});
// Push a new document object with updated Placeholders into the Documents array
if (existSigner?.length > 0) {
Documents.push({
...props.item,
Placeholders: updatedPlaceholders,
Signers: signers ? [...signers, ...existSigner] : [...existSigner]
});
} else {
Documents.push({
...props.item,
Placeholders: updatedPlaceholders,
SignatureType: props.signatureType,
Signers: signers
});
}
});
await batchQuery(Documents);
} else {
setIsSubmit(false);
}
};
const batchQuery = async (Documents) => {
const token =
{ "X-Parse-Session-Token": localStorage.getItem("accesstoken") };
const functionsUrl = `${localStorage.getItem(
"baseUrl"
)}functions/batchdocuments`;
const headers = {
"Content-Type": "application/json",
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
...token,
};
const params = { Documents: JSON.stringify(Documents) };
try {
const res = await axios.post(functionsUrl, params, { headers: headers });
// console.log("res ", res);
if (res.data && res.data.result) {
props.handleClose("success", Documents?.length);
}
} catch (err) {
console.log("Err ", err);
props.handleClose("error", 0);
} finally {
setIsSubmit(false);
}
};
return (
<>
{isLoader ? (
<div className="w-full h-[100px] flex justify-center items-center z-[999]">
<Loader />
</div>
) : (
<>
{isSubmit && (
<div className="absolute z-[999] h-full w-full flex justify-center items-center bg-black bg-opacity-30">
<Loader />
</div>
)}
{isPrefillExist ? (
<div className="text-black p-3 bg-white w-full text-sm md:text-base flex justify-center items-center">
{t("prefill-bulk-error")}
</div>
) : !isDisableBulkSend ? (
<>
{props.Placeholders?.length > 0 ? (
isSignatureExist ? (
<>
{props.Placeholders?.some((x) => !x.signerObjId) ? (
<>
<form onSubmit={handleSubmit}>
<div className="min-h-max max-h-[250px] overflow-y-auto">
{forms?.map((form, index) => (
<div
key={form.Id}
className="p-3 op-card border-[1px] border-gray-400 mt-3 mx-4 mb-4 bg-base-200 text-base-content grid grid-cols-1 md:grid-cols-2 gap-2 relative"
>
{form?.fields?.map((field, fieldIndex) => (
<div
className="flex flex-col"
key={field.fieldId}
>
<label className="block text-xs font-semibold">
{field.label}
</label>
<SuggestionInput
required
type="email"
value={field.value}
index={fieldIndex}
onChange={(signer) =>
handleInputChange(
index,
signer,
fieldIndex
)
}
/>
</div>
))}
{forms?.length > 1 && (
<button
onClick={() => handleRemoveForm(index)}
className="absolute right-3 top-1 text-[red] border-[1px] border-[red] rounded-lg w-[1.7rem] h-[1.7rem]"
>
<i className="fa-light fa-trash"></i>
</button>
)}
<div ref={formRef}></div>
</div>
))}
</div>
<div className="flex flex-col mx-4 mb-4 gap-3">
<button
type="submit"
className="op-btn op-btn-accent focus:outline-none"
>
<i className="fa-light fa-paper-plane"></i>{" "}
<span>{t("send")}</span>
</button>
</div>
</form>
</>
) : (
<div className="text-black p-3 bg-white w-full text-sm md:text-base flex justify-center items-center">
{t("quick-send-alert-1")}
</div>
)}
</>
) : (
<div className="text-black p-3 bg-white w-full text-sm md:text-base flex justify-center items-center">
{t("quick-send-alert-2")}
</div>
)
) : (
<div className="text-black p-3 bg-white w-full text-sm md:text-base flex justify-center items-center">
{t("quick-send-alert-3")}
</div>
)}
</>
) : (
<>
</>
)}
</>
)}
</>
);
};
export default BulkSendUi;
@@ -1,63 +0,0 @@
import React, { useState, useEffect } from "react";
import ModalUi from "../primitives/ModalUi";
import { useTranslation } from "react-i18next";
const ColumnSelector = ({
isOpen,
allColumns = [],
visibleColumns = [],
columnLabels = {},
defaultColumns = [],
onApply,
onClose
}) => {
const { t } = useTranslation();
const [selected, setSelected] = useState(visibleColumns);
const [names, setNames] = useState(columnLabels);
useEffect(() => {
setSelected(visibleColumns);
setNames(columnLabels);
}, [visibleColumns, columnLabels]);
const handleChange = (col) => {
setSelected((prev) =>
prev.includes(col) ? prev.filter((c) => c !== col) : [...prev, col]
);
};
const handleApply = () => {
onApply && onApply(selected, names);
onClose && onClose();
};
return (
<ModalUi isOpen={isOpen} title={t("select-columns")} handleClose={onClose}>
<div className="p-[20px] flex flex-col gap-2">
{allColumns.map((col, i) => (
<div key={col} className="flex justify-between items-center gap-2">
<span className="flex justify-center items-center h-full">
<input
id={col + "_" + i}
type="checkbox"
checked={selected.includes(col)}
onChange={() => handleChange(col)}
className="mb-0"
/>
<span className="whitespace-nowrap ml-1">
{t(`report-heading.${col}`, { defaultValue: col })}
</span>
</span>
</div>
))}
<div className="flex justify-start mt-2">
<button onClick={handleApply} className="op-btn op-btn-primary">
{t("apply")}
</button>
</div>
</div>
</ModalUi>
);
};
export default ColumnSelector;
@@ -1,51 +0,0 @@
import { HTML5Backend } from "react-dnd-html5-backend";
import { TouchBackend } from "react-dnd-touch-backend";
import {
DndProvider,
TouchTransition,
MouseTransition,
Preview
} from "react-dnd-multi-backend";
import DragElement from "./pdf/DragElement";
import LazyPage from "../primitives/LazyPage";
import { GuidelinesProvider } from "../context/GuidelinesContext";
const HTML5toTouch = {
backends: [
{
id: "html5",
backend: HTML5Backend,
transition: MouseTransition
},
{
id: "touch",
backend: TouchBackend,
options: { enableMouseEvents: true },
preview: true,
transition: TouchTransition
}
]
};
/* This handling in only for devices which support touch */
const generatePreview = (props) => {
const { item, style } = props;
const newStyle = { ...style };
return (
<div style={newStyle}>
<DragElement {...item} />
</div>
);
};
export default function DragProvider({ Page, lazy = false }) {
return (
<DndProvider options={HTML5toTouch}>
<Preview>{generatePreview}</Preview>
<GuidelinesProvider>
{lazy ? <LazyPage Page={Page} /> : <Page />}
</GuidelinesProvider>
</DndProvider>
);
}
@@ -4,7 +4,6 @@ import axios from "axios";
import { openInNewTab } from "../constant/Utils";
import { useTranslation } from "react-i18next";
const Footer = () => {
const appName = "OpenSign™";
const { t } = useTranslation();
const [showButton, setShowButton] = useState(false);
const [version, setVersion] = useState("");
@@ -40,6 +39,7 @@ const Footer = () => {
};
}, []);
const appName = "OpenSign™";
const openUrl = () => {
openInNewTab(
"https://github.com/OpenSignLabs/OpenSign/releases/tag/" + version
+300
View File
@@ -0,0 +1,300 @@
import React, { useState, useEffect } from "react";
import dp from "../assets/images/dp.png";
import FullScreenButton from "./FullScreenButton";
import { useNavigate } from "react-router-dom";
import Parse from "parse";
import { useWindowSize } from "../hook/useWindowSize";
import {
checkIsSubscribed,
getAppLogo,
openInNewTab,
saveLanguageInLocal
} from "../constant/Utils";
import { isEnableSubscription, isStaging } from "../constant/const";
import { validplan } from "../json/plansArr";
import { useTranslation } from "react-i18next";
import ModalUi from "../primitives/ModalUi";
import QuotaCard from "../primitives/QuotaCard";
const Header = ({ showSidebar }) => {
const { t, i18n } = useTranslation();
const navigate = useNavigate();
const { width } = useWindowSize();
const current_notification_version = "1"; // Update this with each new feature release
// Get the dismissed version from local storage
const dismissedVersion = localStorage.getItem("notificationDismissedVersion");
const username = localStorage.getItem("username") || "";
const image = localStorage.getItem("profileImg") || dp;
const [isOpen, setIsOpen] = useState(false);
const [isSubscribe, setIsSubscribe] = useState(true);
const [isTeam, setIsTeam] = useState({ plan: "", isValid: false });
const [applogo, setAppLogo] = useState(
localStorage.getItem("appLogo") || " "
);
const [emailUsed, setEmailUsed] = useState(0);
const [isModal, setIsModal] = useState(false);
const [showNotification, setShowNotification] = useState(
(!dismissedVersion || dismissedVersion !== current_notification_version) &&
true
);
const toggleDropdown = () => {
setIsOpen(!isOpen);
if (width <= 768) {
showSidebar();
}
};
useEffect(() => {
checkSubscription();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
async function checkSubscription() {
if (isEnableSubscription) {
const subscribe = await checkIsSubscribed();
if (subscribe.isValid) {
const applogo = await getAppLogo();
if (applogo?.logo) {
setAppLogo(applogo?.logo);
} else {
setAppLogo(localStorage.getItem("appLogo") || "");
}
}
setIsTeam(subscribe);
setIsSubscribe(subscribe.isValid);
try {
const extUser = await Parse.Cloud.run("getUserDetails");
const MonthlyFreeEmails = extUser?.get("MonthlyFreeEmails") || 0;
setEmailUsed(MonthlyFreeEmails);
} catch (err) {
console.log("err in while fetching monthlyfreeEmails", err);
}
} else {
const applogo = await getAppLogo();
if (applogo?.logo) {
setAppLogo(applogo?.logo);
} else {
setAppLogo(localStorage.getItem("appLogo") || "");
}
}
}
const closeDropdown = async () => {
setIsOpen(false);
try {
await Parse.User.logOut();
} catch (err) {
console.log("Err while logging out", err);
}
let appdata = localStorage.getItem("userSettings");
let applogo = localStorage.getItem("appLogo");
let defaultmenuid = localStorage.getItem("defaultmenuid");
let PageLanding = localStorage.getItem("PageLanding");
let baseUrl = localStorage.getItem("baseUrl");
let appid = localStorage.getItem("parseAppId");
localStorage.clear();
saveLanguageInLocal(i18n);
localStorage.setItem("appLogo", applogo);
localStorage.setItem("defaultmenuid", defaultmenuid);
localStorage.setItem("PageLanding", PageLanding);
localStorage.setItem("userSettings", appdata);
localStorage.setItem("baseUrl", baseUrl);
localStorage.setItem("parseAppId", appid);
navigate("/");
};
//handle to close profile drop down menu onclick screen
useEffect(() => {
const closeMenuOnOutsideClick = (e) => {
if (isOpen && !e.target.closest("#profile-menu")) {
setIsOpen(false);
}
};
document.addEventListener("click", closeMenuOnOutsideClick);
return () => {
// Cleanup the event listener when the component unmounts
document.removeEventListener("click", closeMenuOnOutsideClick);
};
}, [isOpen]);
const handleConsoleRedirect = () => {
if (isStaging) {
window.open(" https://staging-console.opensignlabs.com/");
} else {
window.open("https://console.opensignlabs.com/");
}
};
const handleNavigation = () => {
navigate("/subscription");
};
const handleMailUsed = () => {
setIsModal(!isModal);
};
// Handle dismissing the notification
const dismissNotification = () => {
// Store the current version as dismissed in local storage
localStorage.setItem(
"notificationDismissedVersion",
current_notification_version
);
setShowNotification(false);
};
return (
<div>
{isEnableSubscription && showNotification && (
<div className="flex justify-between items-center shadow py-1 bg-[#CAE4FA] p-1">
<div className="text-center text-[14px] ml-auto">
{t("header-news")}
<span
className="cursor-pointer font-medium underline text-blue-800"
onClick={() => navigate("/profile")}
>
{" " + t("header-news-btn") + "."}
</span>
</div>
<div
className="ml-auto mr-1 cursor-pointer"
onClick={() => dismissNotification()}
>
<i className="fa-light fa-xmark"></i>
</div>
</div>
)}
<div className="op-navbar bg-base-100 shadow">
<div className="flex-none">
<button
className="op-btn op-btn-square op-btn-ghost focus:outline-none hover:bg-transparent op-btn-sm no-animation"
onClick={showSidebar}
>
<i className="fa-light fa-bars text-xl text-base-content"></i>
</button>
</div>
<div className="flex-1 ml-2">
<div className="h-[25px] md:h-[40px] w-auto overflow-hidden">
<img
className="object-contain h-full w-auto"
src={applogo}
alt="img"
/>
</div>
</div>
<div id="profile-menu" className="flex-none gap-2">
{!isSubscribe && (
<div>
<button
className="text-xs md:text-sm shadow op-btn op-btn-outline op-btn-sm md:op-btn-md op-btn-accent"
onClick={() => handleNavigation()}
>
{t("upgrade-now")}
</button>
</div>
)}
{isTeam.isValid && !validplan[isTeam.plan] && (
<div className="w-[35px] h-[35px] bg-white rounded-full ring-[1px] ring-offset-2 ring-[#002862] text-[#002862] overflow-hidden font-semibold flex items-center justify-center">
{t("pro")}
</div>
)}
{validplan[isTeam.plan] && (
<div className="w-[35px] h-[35px] bg-white rounded-full ring-[1px] text-[13px] ring-offset-2 ring-[#002862] text-[#002862] overflow-hidden font-semibold flex items-center justify-center">
{t("team")}
</div>
)}
<div>
<FullScreenButton />
</div>
{width >= 768 && (
<div
onClick={toggleDropdown}
className="cursor-pointer w-[35px] h-[35px] rounded-full ring-[1px] ring-offset-2 ring-gray-400 overflow-hidden"
>
<img
className="w-[35px] h-[35px] object-contain"
src={image}
alt="img"
/>
</div>
)}
{width >= 768 && (
<div
onClick={toggleDropdown}
className="cursor-pointer text-base-content text-sm"
>
{username && username}
</div>
)}
<div className="op-dropdown op-dropdown-end" id="profile-menu">
<div
tabIndex={0}
role="button"
className="op-btn op-btn-ghost op-btn-xs w-[10px] h-[20px] hover:bg-transparent"
>
<i
tabIndex={0}
role="button"
onClick={toggleDropdown}
className="fa-light fa-angle-down text-base-content"
></i>
</div>
<ul
tabIndex={0}
className="mt-3 z-[1] p-2 shadow op-menu op-menu-sm op-dropdown-content text-base-content bg-base-100 rounded-box w-52"
>
<li onClick={() => openInNewTab("https://docs.opensignlabs.com")}>
<span>
<i className="fa-light fa-book"></i> {t("docs")}
</span>
</li>
<li
onClick={() => {
setIsOpen(false);
navigate("/profile");
}}
>
<span>
<i className="fa-light fa-user"></i> {t("profile")}
</span>
</li>
<li
onClick={() => {
setIsOpen(false);
navigate("/changepassword");
}}
>
<span>
<i className="fa-light fa-lock"></i> {t("change-password")}
</span>
</li>
{isEnableSubscription && (
<li onClick={() => handleConsoleRedirect()}>
<span>
<i className="fa-light fa-id-card"></i> Console
</span>
</li>
)}
{isEnableSubscription && isTeam?.plan === "freeplan" && (
<li className="cursor-pointer" onClick={handleMailUsed}>
<span>
<i className="fa-light fa-envelope"></i>
{emailUsed}/15 {t("sent-this-month")}
</span>
</li>
)}
<li onClick={closeDropdown}>
<span>
<i className="fa-light fa-arrow-right-from-bracket"></i>{" "}
{t("log-out")}
</span>
</li>
</ul>
</div>
</div>
</div>
<ModalUi isOpen={isModal}>
<QuotaCard isPaidInfo={true} handlClose={handleMailUsed} />
</ModalUi>
</div>
);
};
export default Header;
-271
View File
@@ -1,271 +0,0 @@
import { useState, useEffect } from "react";
import dp from "../assets/images/dp.png";
import FullScreenButton from "./FullScreenButton";
import ThemeToggle from "./ThemeToggle";
import { useNavigate } from "react-router";
import Parse from "parse";
import { useWindowSize } from "../hook/useWindowSize";
import {
getAppLogo,
openInNewTab,
saveLanguageInLocal
} from "../constant/Utils";
import { useTranslation } from "react-i18next";
import { appInfo } from "../constant/appinfo";
import { useDispatch } from "react-redux";
import { toggleSidebar } from "../redux/reducers/sidebarReducer.js";
import { sessionStatus } from "../redux/reducers/userReducer.js";
const Header = ({ isConsole, setIsLoggingOut }) => {
const { t, i18n } = useTranslation();
const navigate = useNavigate();
const { width } = useWindowSize();
const dispatch = useDispatch();
const username = localStorage.getItem("username") || "";
const image = localStorage.getItem("profileImg") || dp;
const [isOpen, setIsOpen] = useState(false);
const [applogo, setAppLogo] = useState("");
const [isDarkTheme, setIsDarkTheme] = useState();
const toggleDropdown = () => {
setIsOpen(!isOpen);
closeSidebar();
};
const closeSidebar = () => {
if (width && width <= 768) {
dispatch(toggleSidebar(false));
}
};
useEffect(() => {
initializeHead();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
closeSidebar();
}, [width]);
const showSidebar = () => {
dispatch(toggleSidebar());
};
async function initializeHead() {
const applogo = await getAppLogo();
if (applogo?.logo) {
setAppLogo(applogo?.logo);
} else {
const logo = localStorage.getItem("appLogo") || appInfo.applogo;
setAppLogo(logo);
}
}
const handleLogout = async () => {
setIsOpen(false);
setIsLoggingOut(true);
try {
await Parse.User.logOut();
} catch (err) {
console.log("Err while logging out", err);
} finally {
dispatch(sessionStatus(true));
}
let appdata = localStorage.getItem("userSettings");
let applogo = localStorage.getItem("appLogo");
let defaultmenuid = localStorage.getItem("defaultmenuid");
let PageLanding = localStorage.getItem("PageLanding");
let baseUrl = localStorage.getItem("baseUrl");
let appid = localStorage.getItem("parseAppId");
let favicon = localStorage.getItem("favicon");
localStorage.clear();
saveLanguageInLocal(i18n);
localStorage.setItem("appLogo", applogo);
localStorage.setItem("defaultmenuid", defaultmenuid);
localStorage.setItem("PageLanding", PageLanding);
localStorage.setItem("userSettings", appdata);
localStorage.setItem("baseUrl", baseUrl);
localStorage.setItem("parseAppId", appid);
localStorage.setItem("favicon", favicon);
setIsLoggingOut(false);
navigate("/");
};
//handle to close profile drop down menu onclick screen
useEffect(() => {
const closeMenuOnOutsideClick = (e) => {
if (isOpen && !e.target.closest("#profile-menu")) {
setIsOpen(false);
}
};
document.addEventListener("click", closeMenuOnOutsideClick);
return () => {
// Cleanup the event listener when the component unmounts
document.removeEventListener("click", closeMenuOnOutsideClick);
};
}, [isOpen]);
useEffect(() => {
const updateThemeStatus = () => {
const isDarkTheme =
document.documentElement.getAttribute("data-theme") === "opensigndark";
setIsDarkTheme(isDarkTheme);
};
updateThemeStatus();
const observer = new MutationObserver(() => {
updateThemeStatus();
});
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ["data-theme"]
});
return () => observer.disconnect();
}, []);
return (
<>
<div className="op-navbar bg-base-100 shadow touch-none">
<div className="flex-none">
<button
className="op-btn op-btn-square op-btn-ghost focus:outline-none hover:bg-transparent op-btn-sm no-animation"
onClick={showSidebar}
>
<i className="fa-light fa-bars text-xl text-base-content"></i>
</button>
</div>
<div className="flex-1 ml-2">
<div className="h-[25px] md:h-[40px] w-auto overflow-hidden">
{applogo && (
<img
className="object-contain h-full w-auto"
src={
isDarkTheme
? "/static/js/assets/images/logo-dark.png"
: applogo
}
alt="logo"
/>
)}
</div>
</div>
<div id="profile-menu" className="flex-none gap-2">
<div>
<FullScreenButton />
</div>
{width >= 768 && (
<div
onClick={toggleDropdown}
className="cursor-pointer w-[35px] h-[35px] rounded-full ring-[1px] ring-offset-2 ring-gray-400 overflow-hidden"
>
<img
className="w-[35px] h-[35px] object-contain"
src={image}
alt="img"
/>
</div>
)}
{width >= 768 && (
<div
onClick={toggleDropdown}
role="button"
tabIndex="0"
className="cursor-pointer text-base-content text-sm"
>
{username && username}
</div>
)}
<div
className="op-dropdown op-dropdown-open op-dropdown-end"
id="profile-menu"
>
<div
tabIndex={0}
role="button"
onClick={toggleDropdown}
className="op-btn op-btn-ghost op-btn-xs w-[10px] h-[20px] hover:bg-transparent"
>
<i className="fa-light fa-angle-down text-base-content"></i>
</div>
<ul
tabIndex={0}
className={`mt-3 z-[1] p-2 shadow op-dropdown-open op-menu op-menu-sm op-dropdown-content text-base-content bg-base-100 rounded-box w-56 ${
isOpen ? "" : "hidden"
}`}
>
{!isConsole && (
<>
<li
onClick={() =>
openInNewTab("https://docs.opensignlabs.com")
}
>
<span>
<i className="fa-light fa-book"></i> {t("docs")}
</span>
</li>
<li
onClick={() => {
setIsOpen(false);
navigate("/profile");
}}
>
<span>
<i className="fa-light fa-user"></i> {t("profile")}
</span>
</li>
<li
onClick={() => {
setIsOpen(false);
navigate("/changepassword");
}}
>
<span>
<i className="fa-light fa-lock"></i>{" "}
{t("change-password")}
</span>
</li>
<li
onClick={() => {
setIsOpen(false);
navigate("/verify-document");
}}
>
<span>
<i className="fa-light fa-check-square"></i>{" "}
{t("verify-document")}
</span>
</li>
<li>
<span>
<i className="fa-light fa-moon"></i>
{t("dark-mode")}
<span className="text-[10px] font-semibold bg-base-300 text-base-content px-1 rounded-md">
BETA
</span>
<ThemeToggle />
</span>
</li>
</>
)}
<li onClick={handleLogout}>
<span>
<i className="fa-light fa-arrow-right-from-bracket"></i>{" "}
{t("log-out")}
</span>
</li>
</ul>
</div>
</div>
</div>
</>
);
};
export default Header;
@@ -0,0 +1,220 @@
import React, { useState } from "react";
import Parse from "parse";
import Loader from "../primitives/Loader";
import { useTranslation } from "react-i18next";
// import FacebookLogin from "react-facebook-login";
const LoginFacebook = ({
FBCred,
thirdpartyLoginfn,
thirdpartyLoader,
setThirdpartyLoader
}) => {
const { t } = useTranslation();
const [isModal, setIsModal] = useState(false);
const [userDetails, setUserDetails] = useState({
Phone: "",
Company: "",
Name: "",
Email: ""
});
const [fBDetails, setFBDetails] = useState({
Id: "",
AccessToken: "",
Name: "",
Email: ""
});
const responseFacebook = async (response) => {
// console.log("response ", response);
if (response.userID) {
setThirdpartyLoader(true);
const details = {
Email: response.email,
Name: response.name
};
setUserDetails({ ...userDetails, ...details });
const fDetails = {
Id: response.userID,
AccessToken: response.accessToken,
Name: response.name,
Email: response.email
};
setFBDetails({ ...fBDetails, ...fDetails });
const res = await checkExtUser(fDetails);
}
};
const checkExtUser = async (details) => {
// const extUser = new Parse.Query("contracts_Users");
// extUser.equalTo("Email", details.Email);
// const extRes = await extUser.first();
const params = { email: details.Email };
const extRes = await Parse.Cloud.run("getUserDetails", params);
// console.log("extRes ", extRes);
if (extRes) {
const params = { ...details, Phone: extRes?.get("Phone") || "" };
const payload = await Parse.Cloud.run("facebooksign", params);
// console.log("payload ", payload);
if (payload && payload.sessiontoken) {
const billingDate =
extRes.get("Next_billing_date") && extRes.get("Next_billing_date");
// console.log("billingDate expired", billingDate > new Date());
const LocalUserDetails = {
name: details.Name,
email: details.Email,
phone: extRes?.get("Phone") || "",
company: extRes.get("Company")
};
localStorage.setItem("userDetails", JSON.stringify(LocalUserDetails));
thirdpartyLoginfn(payload.sessiontoken, billingDate);
}
return { msg: "exist" };
} else {
setIsModal(true);
setThirdpartyLoader(false);
return { msg: "notexist" };
}
};
const handleSubmitbtn = async () => {
if (userDetails.Phone && userDetails.Company) {
setThirdpartyLoader(true);
const params = { ...fBDetails, Phone: userDetails?.Phone || "" };
const payload = await Parse.Cloud.run("facebooksign", params);
// console.log("payload ", payload);
if (payload && payload.sessiontoken) {
const params = {
userDetails: {
name: userDetails.Name,
email: userDetails.Email,
// "passsword":userDetails.Phone,
phone: userDetails?.Phone || "",
role: "contracts_User",
company: userDetails.Company
}
};
const userSignUp = await Parse.Cloud.run("usersignup", params);
// console.log("userSignUp ", userSignUp);
if (userSignUp && userSignUp.sessionToken) {
const LocalUserDetails = {
name: userDetails.Name,
email: userDetails.Email,
phone: userDetails?.Phone || "",
company: userDetails.Company
};
localStorage.setItem("userDetails", JSON.stringify(LocalUserDetails));
thirdpartyLoginfn(userSignUp.sessionToken);
} else {
alert(userSignUp.message);
}
} else {
alert(t("server-error"));
}
} else {
alert(t("fill-required-details!"));
}
};
return (
<div className="relative">
{thirdpartyLoader && (
<div className="fixed flex justify-center items-center inset-0 bg-black bg-opacity-25 z-20 ">
<Loader />
</div>
)}
{/* <FacebookLogin
appId={FBCred}
fields="name,email,picture"
callback={responseFacebook}
cssClass="btn btn-info btn-md btn-fb"
icon="fa fa-facebook-f"
/> */}
{isModal && (
<div
className="modal fade show block z-[1]"
id="exampleModal"
tabIndex="-1"
role="dialog"
>
<div className="modal-dialog" role="document">
<div className="modal-content">
<div className="modal-header">
<h5 className="modal-title">{t("sign-up-form")}</h5>
<span>
<span></span>
</span>
</div>
<div className="modal-body">
<form>
<div className="form-group">
<label htmlFor="Phone" className="col-form-label">
{t("phone")}{" "}
<span className="text-[red] text-[13px]">*</span>
</label>
<input
type="tel"
className="form-control"
id="Phone"
value={userDetails.Phone}
onChange={(e) =>
setUserDetails({
...userDetails,
Phone: e.target.value
})
}
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
required
/>
</div>
<div className="form-group">
<label htmlFor="Company" className="col-form-label">
{t("company")}{" "}
<span className="text-[red] text-[13px]">*</span>
</label>
<input
type="text"
className="form-control"
id="Company"
value={userDetails.Company}
onChange={(e) =>
setUserDetails({
...userDetails,
Company: e.target.value
})
}
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
required
/>
</div>
<div>
<button
type="button"
onClick={() => handleSubmitbtn()}
className="btn btn-info mr-[10px]"
>
{t("sign-up")}
</button>
<button
type="button"
className="btn btn-secondary w-[90px]"
onClick={() => setIsModal(false)}
>
{t("cancel")}
</button>
</div>
</form>
</div>
</div>
</div>
</div>
)}
</div>
);
};
export default LoginFacebook;
+280
View File
@@ -0,0 +1,280 @@
import React, { useState, useRef } from "react";
import Parse from "parse";
import { jwtDecode } from "jwt-decode";
import { useScript } from "../hook/useScript";
import ModalUi from "../primitives/ModalUi";
import Loader from "../primitives/Loader";
import { useTranslation } from "react-i18next";
import { saveLanguageInLocal } from "../constant/Utils";
/*
* `GoogleSignInBtn`as it's name indicates it render google sign in button
* and in this `useScript` in which we have created for generate google sign button
* and when user click on sign in with google it will be verify on server side
* and then generate token
*/
const GoogleSignInBtn = ({
GoogleCred,
thirdpartyLoginfn,
thirdpartyLoader,
setThirdpartyLoader
}) => {
const { t, i18n } = useTranslation();
const [isModal, setIsModal] = useState(false);
const googleBtn = useRef();
const [userDetails, setUserDetails] = useState({
Phone: "",
Company: "",
Name: "",
Email: "",
Destination: ""
});
const [googleDetails, setGoogleDetails] = useState({
Id: "",
TokenId: "",
Gmail: "",
Name: ""
});
useScript("https://accounts.google.com/gsi/client", () => {
window.google.accounts.id.initialize({
client_id: GoogleCred,
callback: responseGoogle,
auto_select: false
});
window.google.accounts.id.renderButton(googleBtn.current, {
theme: "outline",
size: "large",
width: "187px"
});
});
const clearStorage = async () => {
try {
await Parse.User.logOut();
} catch (err) {
console.log("Err while logging out", err);
}
let baseUrl = localStorage.getItem("baseUrl");
let appid = localStorage.getItem("parseAppId");
let applogo = localStorage.getItem("appLogo");
let defaultmenuid = localStorage.getItem("defaultmenuid");
let PageLanding = localStorage.getItem("PageLanding");
let userSettings = localStorage.getItem("userSettings");
localStorage.clear();
saveLanguageInLocal(i18n);
localStorage.setItem("baseUrl", baseUrl);
localStorage.setItem("parseAppId", appid);
localStorage.setItem("appLogo", applogo);
localStorage.setItem("defaultmenuid", defaultmenuid);
localStorage.setItem("PageLanding", PageLanding);
localStorage.setItem("userSettings", userSettings);
localStorage.setItem("baseUrl", baseUrl);
localStorage.setItem("parseAppId", appid);
};
const responseGoogle = async (response) => {
clearStorage();
setThirdpartyLoader(true);
// console.log("response ", response);
if (response.credential) {
const data = jwtDecode(response.credential);
// console.log("data ", data);
if (data.sub && data.email) {
const details = { Email: data.email, Name: data.name };
setUserDetails({ ...userDetails, ...details });
const Gdetails = {
Id: data.sub,
TokenId: response.credential,
Gmail: data.email,
Name: data.name
};
setGoogleDetails({ ...googleDetails, ...Gdetails });
await checkExtUser(Gdetails);
}
}
};
const checkExtUser = async (details) => {
const params = { email: details.Gmail };
const extRes = await Parse.Cloud.run("getUserDetails", params);
if (extRes) {
// const params = { ...details, Phone: extRes?.get("Phone") || "" };
const params = { ...details, extUserId: extRes.objectId };
const payload = await Parse.Cloud.run("googlesign", params);
if (payload && payload.sessiontoken) {
const LocalUserDetails = {
name: details.Name,
email: details.Gmail,
phone: payload?.phone || "",
company: payload.company
};
localStorage.setItem("userDetails", JSON.stringify(LocalUserDetails));
thirdpartyLoginfn(payload.sessiontoken);
}
return { msg: "exist" };
} else {
setIsModal(true);
setThirdpartyLoader(false);
return { msg: "notexist" };
}
};
const handleSubmitbtn = async () => {
if (userDetails.Phone && userDetails.Company) {
setThirdpartyLoader(true);
// e.preventDefault()
// console.log("handelSubmit", userDetails);
const params = { ...googleDetails, Phone: userDetails?.Phone };
const payload = await Parse.Cloud.run("googlesign", params);
// console.log("payload ", payload);
if (payload && payload.sessiontoken) {
const params = {
userDetails: {
name: userDetails.Name,
email: userDetails.Email,
// "passsword":userDetails.Phone,
phone: userDetails?.Phone || "",
role: "contracts_User",
company: userDetails.Company,
jobTitle: userDetails.Destination
}
};
const userSignUp = await Parse.Cloud.run("usersignup", params);
// console.log("userSignUp ", userSignUp);
if (userSignUp && userSignUp.sessionToken) {
const LocalUserDetails = {
name: userDetails.Name,
email: userDetails.Email,
phone: userDetails?.Phone || "",
company: userDetails.Company
// jobTitle: userDetails.JobTitle
};
localStorage.setItem("userDetails", JSON.stringify(LocalUserDetails));
thirdpartyLoginfn(userSignUp.sessionToken);
} else {
alert(userSignUp.message);
}
} else if (
payload &&
payload.message.replace(/ /g, "_") === "Internal_server_err"
) {
alert(t("server-error"));
}
} else {
alert(t("fill-required-details!"));
}
};
const handleCloseModal = async () => {
setIsModal(false);
try {
await Parse.User.logOut();
} catch (err) {
console.log("Err while logging out", err);
}
let appdata = localStorage.getItem("userSettings");
let applogo = localStorage.getItem("appLogo");
let defaultmenuid = localStorage.getItem("defaultmenuid");
let PageLanding = localStorage.getItem("PageLanding");
let baseUrl = localStorage.getItem("baseUrl");
let appid = localStorage.getItem("parseAppId");
localStorage.clear();
saveLanguageInLocal(i18n);
localStorage.setItem("appLogo", applogo);
localStorage.setItem("defaultmenuid", defaultmenuid);
localStorage.setItem("PageLanding", PageLanding);
localStorage.setItem("userSettings", appdata);
localStorage.setItem("baseUrl", baseUrl);
localStorage.setItem("parseAppId", appid);
};
return (
<div className="relative">
{thirdpartyLoader && (
<div className="fixed flex justify-center items-center inset-0 bg-black bg-opacity-25 z-20 ">
<Loader />
</div>
)}
<div ref={googleBtn} className="text-sm"></div>
<ModalUi showClose={false} isOpen={isModal} title={t("sign-up-form")}>
<form className="px-4 py-3 text-base-content">
<div className="mb-3">
<label htmlFor="Phone" className="block text-xs font-semibold">
{t("phone")} <span className="text-[13px] text-[red]">*</span>
</label>
<input
type="tel"
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
id="Phone"
value={userDetails.Phone}
onChange={(e) =>
setUserDetails({
...userDetails,
Phone: e.target.value
})
}
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
onInput={(e) => e.target.setCustomValidity("")}
required
/>
</div>
<div className="mb-3">
<label htmlFor="Company" className="block text-xs font-semibold">
{t("company")} <span className="text-[13px] text-[red]">*</span>
</label>
<input
type="text"
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
id="Company"
value={userDetails.Company}
onChange={(e) =>
setUserDetails({
...userDetails,
Company: e.target.value
})
}
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
onInput={(e) => e.target.setCustomValidity("")}
required
/>
</div>
<div className="mb-3">
<label htmlFor="JobTitle" className="block text-xs font-semibold">
{t("job-title")} <span className="text-[13px] text-[red]">*</span>
</label>
<input
type="text"
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
id="JobTitle"
value={userDetails.Destination}
onChange={(e) =>
setUserDetails({
...userDetails,
Destination: e.target.value
})
}
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
onInput={(e) => e.target.setCustomValidity("")}
required
/>
</div>
<div className="flex flex-row gap-2">
<button
type="button"
className="op-btn op-btn-primary"
onClick={() => handleSubmitbtn()}
>
{t("sign-up")}
</button>
<button
type="button"
className="op-btn op-btn-ghost"
onClick={handleCloseModal}
>
{t("cancel")}
</button>
</div>
</form>
</ModalUi>
</div>
);
};
export default GoogleSignInBtn;
@@ -1,9 +1,7 @@
import React from "react";
import { Document, Page } from "react-pdf";
import { Stage, Layer, Rect, Text } from "react-konva";
import { useTranslation } from "react-i18next";
const RenderDebugPdf = (props) => {
const { t } = useTranslation();
return (
<div>
<div className="sticky top-0 p-[10px] z-10 bg-white border-[1px] border-[gray] my-[5px]">
@@ -14,9 +12,10 @@ const RenderDebugPdf = (props) => {
onMouseMove={props.handleMouseMoveDiv}
>
<Document
onLoadError={() => props.setPdfLoadFail(false)}
loading={t("loading-doc")}
error={<p className="mx-2">{t("failed-to-load-refresh-page")}</p>}
onLoadError={() => {
props.setPdfLoadFail(false);
}}
loading={"Loading Document.."}
onLoadSuccess={props.pageDetails}
ref={props.pdfRef}
file={props.pdfUrl}
@@ -6,14 +6,22 @@ function RotateAlert(props) {
const { t } = useTranslation();
return (
<ModalUi
isOpen={props.showRotateAlert}
isOpen={props.isRotate}
title={t("Rotation-alert")}
handleClose={() => props.setShowRotateAlert({ status: false, degree: 0 })}
handleClose={() => props.setIsRotate({ status: false, degree: 0 })}
>
{" "}
<div className="p-[20px] h-full">
<p>{t("rotate-alert-mssg")}</p>
<div className="h-[1px] w-full my-[15px] bg-[#9f9f9f]"></div>
<div className="flex gap-1">
<button
onClick={() => props.setIsRotate({ status: false, degree: 0 })}
type="button"
className="op-btn op-btn-ghost shadow-md"
>
{t("no")}
</button>
<button
onClick={() => props.handleRemoveWidgets()}
type="button"
@@ -21,15 +29,6 @@ function RotateAlert(props) {
>
{t("yes")}
</button>
<button
onClick={() =>
props.setShowRotateAlert({ status: false, degree: 0 })
}
type="button"
className="op-btn op-btn-ghost text-base-content shadow-md"
>
{t("no")}
</button>
</div>
</div>
</ModalUi>
@@ -1,41 +0,0 @@
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;
+19
View File
@@ -0,0 +1,19 @@
import React from "react";
import { Helmet } from "react-helmet";
function Title({ title, drive }) {
return (
<Helmet>
<title>{drive ? title : `${title} - OpenSign™`}</title>
<meta name="description" content={`${title} - OpenSign™`} />
<link
rel="icon"
type="image/png"
href={localStorage.getItem("fev_Icon")}
sizes="40x40"
/>
</Helmet>
);
}
export default Title;
-82
View File
@@ -1,82 +0,0 @@
import { Helmet } from "react-helmet";
import { useLocation, matchPath } from "react-router";
import { useTranslation } from "react-i18next";
import { useMemo } from "react";
import { useManifestUrl } from "../hook/useManifestUrl";
const TITLE_MAP = {
"/": "login",
// Homelayout
"/dashboard/35KBoSgoAK": "Dashboard",
"/form/sHAnZphf69": "Sign Yourself",
"/form/8mZzFxbG1z": "Request Signatures",
"/form/template": "New Template",
"/report/6TeaPr321t": "Templates",
"/report/4Hhwbp482K": "Need your sign",
"/report/1MwEuxLEkF": "In Progress",
"/report/kQUoW4hUXz": "Completed",
"/report/ByHuevtCFY": "Drafts",
"/report/UPr2Fm5WY3": "Declined",
"/report/zNqBHXHsYH": "Expired",
"/report/contacts": "Contactbook",
"/drive": "Drive",
"/managesign": "My Signature",
"/preferences": "Preferences",
"/users": "Users",
"/profile": "profile",
"/changepassword": "change-password",
"/verify-document": "verify-document",
"/signaturePdf/:docId": "Sign Yourself",
"/placeHolderSign/:docId": "Request Signatures",
"/template/:templateId": "New Template",
"/recipientSignPdf/:docId": "Request Signatures",
"/recipientSignPdf/:docId/:contactBookId": "Request Signatures",
"/load/recipientSignPdf/:docId/:contactBookId": "Request Signatures",
// alone
"/debugpdf": "Debug Pdf",
"/forgetpassword": "forgot-password",
"/success": "success",
"/addadmin": "add-admin",
"/upgrade-2.1": "add-admin",
"/draftDocument": "New Document",
"/login/:base64url": "Request Signatures",
};
function resolveTitle(pathname, override) {
if (override) return override;
for (let [pattern, label] of Object.entries(TITLE_MAP)) {
if (matchPath({ path: pattern, end: true }, pathname)) {
return label;
}
}
return "";
}
export default function Title() {
const { pathname, state } = useLocation();
const { t } = useTranslation();
const appName =
"OpenSign™";
const logo = useMemo(() => localStorage.getItem("favicon"), []);
const prefix = useMemo(
() => resolveTitle(pathname, state?.title),
[pathname, state?.title]
);
const title = useMemo(
() => (prefix ? `${t(prefix)} - ${appName}` : appName),
[t, prefix, appName]
);
const manifestUrl = useManifestUrl(appName, logo);
return (
<Helmet>
<title>{title}</title>
<meta name="description" content={title} />
{logo && <link rel="icon" type="image/png" href={logo} />}
<link rel="manifest" href={manifestUrl} />
</Helmet>
);
}
@@ -1,5 +1,5 @@
import React from "react";
import { useNavigate } from "react-router";
import { useNavigate } from "react-router-dom";
import { openInNewTab } from "../../constant/Utils";
import { useTranslation } from "react-i18next";
@@ -29,28 +29,16 @@ const DashboardButton = (props) => {
: "cursor-default"
} w-full shadow-md px-3 py-2 op-card bg-base-100`}
>
<div className="flex flex-row items-center text-base-content">
<div className="flex flex-row items-center">
<span className="rounded-full bg-base-content bg-opacity-20 w-[60px] h-[60px] self-start flex justify-center items-center">
<i
className={`${
props.Icon ? props.Icon : "fa-light fa-info"
} text-[25px] lg:text-[30px]`}
></i>
</span>
</div>
<div className="text-lg ml-3">
<div className="flex flex-row items-center">
<span className="rounded-full bg-base-content bg-opacity-20 w-[60px] h-[60px] self-start flex justify-center items-center">
<i
className={`${
props.Icon ? props.Icon : "fa-light fa-info"
} text-[25px] lg:text-[30px]`}
></i>
</span>
<div className=" text-lg ml-3 text-base-content">
{t(`sidebar.${props.Label}`)}
{props.Label === "Sign yourself" && (
<div className="text-gray-500 text-xs mt-1">
{t("signyour-self-button")}
</div>
)}
{props.Label === "Request signatures" && (
<div className="text-gray-500 text-xs mt-1">
{t("requestsign-button")}
</div>
)}
</div>
</div>
</div>
@@ -1,8 +1,8 @@
import { useState, useEffect } from "react";
import React, { useState, useEffect } from "react";
import axios from "axios";
import Parse from "parse";
import getReplacedHashQuery from "../../constant/getReplacedHashQuery";
import { useNavigate } from "react-router";
import { useNavigate } from "react-router-dom";
import Tooltip from "../../primitives/Tooltip";
import { useTranslation } from "react-i18next";
@@ -133,7 +133,7 @@ const DashboardCard = (props) => {
skip: 0,
limit: 200
};
const url = `${parseBaseUrl}functions/getReport`;
const url = `${parseBaseUrl}/functions/getReport`;
await axios
.post(url, params, {
headers: {
@@ -148,7 +148,7 @@ const DashboardCard = (props) => {
);
let arr = [];
for (const obj of listData) {
const isSigner = obj.Signers?.some(
const isSigner = obj.Signers.some(
(item) => item.UserId.objectId === currentUser.id
);
if (isSigner) {
@@ -168,21 +168,25 @@ const DashboardCard = (props) => {
}
}
setresponse(arr.length);
setLoading(false);
});
} else {
await axios.get(url, { headers: headers }).then((res) => {
if (res?.data?.[props.Data.key]) {
setresponse(parseInt(res.data[props.Data.key]));
} else if (res?.data?.results?.length > 0) {
setresponse(res.data.results.length);
if (res.data.results.length > 0) {
setLoading(false);
if (props.Data.key !== "count") {
setresponse(res.data.results[0][props.Data.key]);
} else {
setresponse(res.data[props.Data.key]);
}
} else {
setresponse(0);
setLoading(false);
}
});
}
} catch (e) {
console.error("Problem", e);
} finally {
setLoading(false);
}
}
@@ -342,6 +346,7 @@ const DashboardCard = (props) => {
id={props.Label}
iconColor={"white"}
message={t(`tour-mssg.${props.Label}`)}
// {props?.Data?.tourMessage}
/>
</div>
</div>
@@ -1,6 +1,6 @@
import { useEffect, useState, useRef } from "react";
import React, { useEffect, useState } from "react";
import Parse from "parse";
import DocumentsReport from "../../reports/document/DocumentsReport";
import ReportTable from "../../primitives/GetReportDisplay";
import reportJson from "../../json/ReportJson";
import axios from "axios";
import Loader from "../../primitives/Loader";
@@ -17,16 +17,10 @@ function DashboardReport(props) {
const [isMoreDocs, setIsMoreDocs] = useState(true);
const abortController = new AbortController();
const docPerPage = 5;
const [searchTerm, setSearchTerm] = useState("");
const [mobileSearchOpen, setMobileSearchOpen] = useState(false);
const [isSearchResult, setIsSearchResult] = useState(false);
const debounceTimer = useRef(null);
useEffect(() => {
setReportName("");
setSearchTerm("");
setMobileSearchOpen(false);
getReportData(props.Record.reportId, 0, 20, "");
getReportData(props.Record.reportId);
// Function returned from useEffect is called on unmount
return () => {
@@ -42,69 +36,12 @@ function DashboardReport(props) {
// below useEffect call when isNextRecord state is true and fetch next record
useEffect(() => {
if (isNextRecord) {
getReportData(props.Record.reportId, List.length, 20, searchTerm);
getReportData(props.Record.reportId, List.length, 200);
}
// eslint-disable-next-line
}, [isNextRecord]);
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
) => {
const getReportData = async (id, skipUserRecord = 0, limit = 200) => {
setIsLoader(true);
const json = reportJson(id);
if (json) {
@@ -119,13 +56,8 @@ function DashboardReport(props) {
sessiontoken: localStorage.getItem("accesstoken")
};
try {
const skipRecord = id === "5Go51Q7T8r" ? 0 : skipUserRecord;
const limitRecord = id === "5Go51Q7T8r" ? 200 : limit;
const params = { reportId: id, skip: skipRecord, limit: limitRecord };
if (term) {
params.searchTerm = term;
}
const url = `${localStorage.getItem("baseUrl")}functions/getReport`;
const params = { reportId: id, skip: skipUserRecord, limit: limit };
const url = `${localStorage.getItem("baseUrl")}/functions/getReport`;
const res = await axios.post(url, params, {
headers: headers,
signal: abortController.signal // is used to cancel fetch query
@@ -134,7 +66,7 @@ function DashboardReport(props) {
const listData = res.data?.result.filter((x) => x.Signers.length > 0);
let arr = [];
for (const obj of listData) {
const isSigner = obj.Signers?.some(
const isSigner = obj.Signers.some(
(item) => item.UserId.objectId === currentUser
);
if (isSigner) {
@@ -162,7 +94,7 @@ function DashboardReport(props) {
prevRecord.length > 0 ? [...prevRecord, ...arr] : arr
);
} else {
if (res.data.result.length >= docPerPage) {
if (res.data.result.length === docPerPage) {
setIsMoreDocs(true);
} else {
setIsMoreDocs(false);
@@ -198,7 +130,7 @@ function DashboardReport(props) {
) : (
<>
{reportName ? (
<DocumentsReport
<ReportTable
ReportName={reportName}
List={List}
setList={setList}
@@ -207,17 +139,11 @@ function DashboardReport(props) {
setIsNextRecord={setIsNextRecord}
isMoreDocs={isMoreDocs}
docPerPage={docPerPage}
mobileSearchOpen={mobileSearchOpen}
setMobileSearchOpen={setMobileSearchOpen}
searchTerm={searchTerm}
handleSearchChange={handleSearchChange}
handleSearchPaste={handleSearchPaste}
isSearchResult={isSearchResult}
/>
) : (
<div className="flex items-center justify-center h-[100px] w-full bg-white rounded-box">
<div className="text-center text-xl text-base-content">
{t("report-not-found")}
<div className="flex items-center justify-center h-[100px] w-full bg-white rounded">
<div className="text-center">
<p className="text-xl text-black">{t("report-not-found")}</p>
</div>
</div>
)}
@@ -1,9 +1,8 @@
import React, { Suspense } from "react";
import { lazyWithRetry } from "../../utils";
import React, { Suspense, lazy } from "react";
import { useTranslation } from "react-i18next";
const DashboardButton = lazyWithRetry(() => import("./DashboardButton"));
const DashboardCard = lazyWithRetry(() => import("./DashboardCard"));
const DashboardReport = lazyWithRetry(() => import("./DashboardReport"));
const DashboardButton = lazy(() => import("./DashboardButton"));
const DashboardCard = lazy(() => import("./DashboardCard"));
const DashboardReport = lazy(() => import("./DashboardReport"));
const buttonList = [
{
label: "Sign yourself",
@@ -1,15 +1,15 @@
import React, { useState, useEffect, useRef } from "react";
import "../../styles/opensigndrive.css";
import axios from "axios";
import { ContextMenu } from "radix-ui";
import { useNavigate } from "react-router";
import * as ContextMenu from "@radix-ui/react-context-menu";
import { saveAs } from "file-saver";
import { useNavigate } from "react-router-dom";
import Table from "react-bootstrap/Table";
import { HoverCard } from "radix-ui";
import * as HoverCard from "@radix-ui/react-hover-card";
import ModalUi from "../../primitives/ModalUi";
import FolderModal from "../shared/fields/FolderModal";
import { useTranslation } from "react-i18next";
import { handleDownloadPdf, isMobile } from "../../constant/Utils";
import Parse from "parse";
import { handleDownloadPdf } from "../../constant/Utils";
function DriveBody(props) {
const { t } = useTranslation();
@@ -18,7 +18,7 @@ function DriveBody(props) {
const inputRef = useRef(null);
const [isOpenMoveModal, setIsOpenMoveModal] = useState(false);
const [selectDoc, setSelectDoc] = useState();
const [isDeleteDoc, setIsDeleteDoc] = useState({});
const [isDeleteDoc, setIsDeleteDoc] = useState(false);
const contextMenu = [
{ type: "Download", icon: "fa-light fa-arrow-down" },
{ type: "Rename", icon: "fa-light fa-font" },
@@ -71,9 +71,11 @@ function DriveBody(props) {
});
props.setPdfData(updatedData);
props.sortingData(null, null, updatedData);
try {
await axios.put(
`${localStorage.getItem("baseUrl")}classes/contracts_Document/${docId}`,
await axios
.put(
`${localStorage.getItem(
"baseUrl"
)}classes/contracts_Document/${docId}`,
updateName,
{
headers: {
@@ -82,14 +84,18 @@ function DriveBody(props) {
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
}
}
);
} catch (err) {
console.log("Err ", err);
props.setIsAlert({
isShow: true,
alertMessage: t("something-went-wrong-mssg")
)
.then(() => {
// const res = result.data;
// console.log("res", res);
})
.catch((err) => {
console.log("Err ", err);
props.setIsAlert({
isShow: true,
alertMessage: t("something-went-wrong-mssg")
});
});
}
}
};
@@ -132,7 +138,7 @@ function DriveBody(props) {
}
};
const handleMenuItemClick = async (selectType, data, deleteType) => {
const handleMenuItemClick = async (selectType, data) => {
switch (selectType) {
case "Download": {
await handleDownloadPdf([data]);
@@ -144,7 +150,7 @@ function DriveBody(props) {
break;
}
case "Delete": {
setIsDeleteDoc({ status: true, deleteType });
setIsDeleteDoc(true);
setSelectDoc(data);
break;
}
@@ -158,7 +164,7 @@ function DriveBody(props) {
};
//function for delete document
const handleDeleteDocument = async (docData) => {
setIsDeleteDoc({});
setIsDeleteDoc(false);
const docId = docData.objectId;
const data = {
IsArchive: true
@@ -239,6 +245,7 @@ function DriveBody(props) {
)
.then((Listdata) => {
// console.log("Listdata ", Listdata);
const res = Listdata.data;
if (res) {
const updatedData = props.pdfData.filter(
@@ -257,56 +264,18 @@ function DriveBody(props) {
setIsOpenMoveModal(false);
}
};
const sanitizeFileName = (pdfName) => {
// Replace spaces with underscore
return pdfName.replace(/ /g, "_");
};
const handleEnterPress = (e, data) => {
if (e.key === "Enter") {
handledRenameDoc(data);
}
};
const checkFolderEmpty = async (docData) => {
let isEmptyFolder = true;
const query = new Parse.Query("contracts_Document");
query.equalTo("Folder", {
__type: "Pointer",
className: "contracts_Document",
objectId: docData.objectId
});
query.notEqualTo("IsArchive", true);
const res = await query.find();
const jsonRes = JSON.parse(JSON.stringify(res));
if (jsonRes && jsonRes.length > 0) {
isEmptyFolder = false;
return isEmptyFolder;
} else {
return isEmptyFolder;
}
};
const handleDeleteFolder = async (docData) => {
setIsDeleteDoc({});
const isEmptyFolder = await checkFolderEmpty(docData);
if (isEmptyFolder) {
const docId = docData?.objectId;
try {
const updateQuery = new Parse.Query("contracts_Document");
const updateObj = await updateQuery.get(docId);
updateObj.set("IsArchive", true);
const res = await updateObj.save();
if (res) {
const updatedData = props.pdfData.filter((x) => x.objectId !== docId);
props.setPdfData(updatedData);
}
} catch (err) {
console.log("Err ", err);
props.setIsAlert({
isShow: true,
alertMessage: t("something-went-wrong-mssg")
});
}
} else {
alert(t("delete-folder-alert-1"));
}
};
//component to handle type of document and render according to type
const handleFolderData = (data, ind, listType) => {
let createddate, status, isDecline, signerExist, isComplete;
@@ -340,18 +309,15 @@ function DriveBody(props) {
}
const signersName = () => {
const getSignersName =
signerExist?.length > 0 && signerExist?.map((data) => data?.Name || "");
const signerName =
getSignersName?.length > 0 ? getSignersName?.join(", ") : "";
const getSignersName = signerExist.map((data) => data.Name);
const signerName = getSignersName.join(", ");
return (
<span className="text-[12px] font-medium w-[90%] break-words">
{signerName && signerName}
{signerName}
</span>
);
};
return listType === "table" ? (
data.Type === "Folder" ? (
<tr onClick={() => handleOnclikFolder(data)}>
@@ -359,14 +325,14 @@ function DriveBody(props) {
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
className="w-[26px] h-[26px] fill-current"
className="w-[26px] h-[26px] fill-current op-text-secondary"
>
<path d="M64 480H448c35.3 0 64-28.7 64-64V160c0-35.3-28.7-64-64-64H288c-10.1 0-19.6-4.7-25.6-12.8L243.2 57.6C231.1 41.5 212.1 32 192 32H64C28.7 32 0 60.7 0 96V416c0 35.3 28.7 64 64 64z" />
</svg>
<span className="text-[12px] font-medium">{data.Name}</span>
</td>
<td>_</td>
<td>{t("folder")}</td>
<td>Folder</td>
<td>_</td>
<td>_</td>
</tr>
@@ -383,7 +349,7 @@ function DriveBody(props) {
<span className="text-[12px] font-medium">{data.Name}</span>
</td>
<td>{createddate}</td>
<td>{t("pdf")}</td>
<td>Pdf</td>
<td>{t(`drive-document-status.${status}`)}</td>
<td>
<i
@@ -398,9 +364,9 @@ function DriveBody(props) {
</tr>
)
) : listType === "list" && data.Type === "Folder" ? (
<div className="relative w-[100px] h-[100px] mx-2 my-3">
<div key={ind} className="relative w-[100px] h-[100px] mx-2 my-3">
<ContextMenu.Root>
<ContextMenu.Trigger className="flex flex-col justify-center items-center select-none-cls">
<ContextMenu.Trigger className="flex flex-col justify-center items-center">
{/* folder */}
<div
data-tut={props.dataTutSeventh}
@@ -414,7 +380,7 @@ function DriveBody(props) {
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
className="w-[100px] h-[100px] fill-current"
className="w-[100px] h-[100px] fill-current op-text-secondary"
>
<path d="M64 480H448c35.3 0 64-28.7 64-64V160c0-35.3-28.7-64-64-64H288c-10.1 0-19.6-4.7-25.6-12.8L243.2 57.6C231.1 41.5 212.1 32 192 32H64C28.7 32 0 60.7 0 96V416c0 35.3 28.7 64 64 64z" />
</svg>
@@ -436,10 +402,11 @@ function DriveBody(props) {
className="op-input op-input-bordered op-input-xs w-[100px] focus:outline-none hover:border-base-content text-[10px]"
/>
) : (
<span className="fileName select-none-cls">{data.Name}</span>
<span className="fileName">{data.Name}</span>
)}
</div>
</ContextMenu.Trigger>
<ContextMenu.Portal>
<ContextMenu.Content
className="ContextMenuContent"
@@ -451,14 +418,7 @@ function DriveBody(props) {
className="ContextMenuItem"
>
<i className="fa-light fa-font mr-[8px]"></i>
<span>{t(`context-menu.Rename`)}</span>
</ContextMenu.Item>
<ContextMenu.Item
onClick={() => handleMenuItemClick("Delete", data, data.Type)}
className="ContextMenuItem"
>
<i className="fa-light fa-trash mr-[8px]"></i>
<span>{t(`context-menu.Delete`)}</span>
<span>Rename</span>
</ContextMenu.Item>
</ContextMenu.Content>
</ContextMenu.Portal>
@@ -466,7 +426,7 @@ function DriveBody(props) {
</div>
) : (
<HoverCard.Root
open={rename || isMobile ? false : undefined}
open={rename ? false : undefined}
openDelay={0}
closeDelay={100}
>
@@ -474,10 +434,7 @@ function DriveBody(props) {
<div>
<ContextMenu.Root>
<div className="relative w-[100px] h-[100px] mx-2 my-3">
<ContextMenu.Trigger
asChild
className="flex flex-col justify-center items-center select-none-cls"
>
<ContextMenu.Trigger className="flex flex-col justify-center items-center">
{/* pdf */}
<div
data-tut={props.dataTutSixth}
@@ -506,16 +463,14 @@ function DriveBody(props) {
}
}}
onBlur={() => handledRenameDoc(data)}
onKeyDown={(e) => handleEnterPress(e, data, data.Type)}
onKeyDown={(e) => handleEnterPress(e, data)}
ref={inputRef}
defaultValue={renameValue}
onChange={(e) => setRenameValue(e.target.value)}
className="op-input op-input-bordered op-input-xs w-[100px] focus:outline-none hover:border-base-content text-[10px]"
/>
) : (
<span className="fileName select-none-cls">
{data.Name}
</span>
<span className="fileName">{data.Name}</span>
)}
</div>
</ContextMenu.Trigger>
@@ -543,6 +498,7 @@ function DriveBody(props) {
)
)}
</div>
<ContextMenu.Portal>
<ContextMenu.Content
className="ContextMenuContent"
@@ -595,6 +551,7 @@ function DriveBody(props) {
{signersName()}
</>
)}
<HoverCard.Arrow className="HoverCardArrow" />
</HoverCard.Content>
</HoverCard.Portal>
@@ -618,21 +575,21 @@ function DriveBody(props) {
</tr>
</thead>
<tbody>
{props?.pdfData?.map((data, ind) => (
<React.Fragment key={ind}>
{handleFolderData(data, ind, "table")}
</React.Fragment>
))}
{props.pdfData.map((data, ind) => {
return (
<React.Fragment key={ind}>
{handleFolderData(data, ind, "table")}
</React.Fragment>
);
})}
</tbody>
</Table>
</div>
) : (
<div className="flex flex-row flex-wrap items-center mt-1 pb-[20px] mx-[5px]">
{props?.pdfData?.map((data, ind) => (
<React.Fragment key={ind}>
{handleFolderData(data, ind, "list")}
</React.Fragment>
))}
{props.pdfData.map((data, ind) => {
return <div key={ind}>{handleFolderData(data, ind, "list")}</div>;
})}
</div>
)}
@@ -646,33 +603,22 @@ function DriveBody(props) {
/>
)}
<ModalUi
isOpen={isDeleteDoc.status}
isOpen={isDeleteDoc}
title={t("delete-document")}
handleClose={() => setIsDeleteDoc({})}
handleClose={() => setIsDeleteDoc(false)}
>
<div className="h-full p-[20px] text-base-content">
{isDeleteDoc.deleteType ? (
<p>{t("delete-folder-alert")}</p>
) : (
<p>{t("delete-document-alert")}</p>
)}
<p>{t("delete-document-alert")}</p>
<div className="h-[1px] w-full bg-[#9f9f9f] my-[15px]"></div>
<button
onClick={() => {
if (isDeleteDoc.deleteType) {
handleDeleteFolder(selectDoc);
} else {
handleDeleteDocument(selectDoc);
}
}}
onClick={() => handleDeleteDocument(selectDoc)}
type="button"
className="op-btn op-btn-primary mr-2"
>
{t("yes")}
</button>
<button
onClick={() => setIsDeleteDoc({})}
onClick={() => setIsDeleteDoc(false)}
type="button"
className="op-btn op-btn-neutral"
>
@@ -10,15 +10,15 @@ const AddRoleModal = (props) => {
isOpen={props.isModalRole}
handleClose={props.handleCloseRoleModal}
>
<div className="text-base-content h-full py-[10px] px-[20px]">
<div className="h-full py-[10px] px-[20px]">
<form className="flex flex-col" onSubmit={props.handleAddRole}>
<input
value={props.roleName}
onChange={(e) => props.setRoleName(e.target.value)}
placeholder={
props.signersdata.length > 0
? "Role " + (props.signersdata.length + 1)
: "Role 1"
? "User " + (props.signersdata.length + 1)
: "User 1"
}
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs mt-1"
/>
@@ -33,7 +33,7 @@ const AddRoleModal = (props) => {
<button
onClick={props.handleCloseRoleModal}
type="button"
className="op-btn op-btn-ghost text-base-content ml-2"
className="op-btn op-btn-ghost ml-2"
>
{t("close")}
</button>
@@ -1,133 +0,0 @@
import React from "react";
import { useTranslation } from "react-i18next";
import ModalUi from "../../primitives/ModalUi";
function AgreementContent(props) {
const { t } = useTranslation();
const appName =
"OpenSign™";
const h2Style = "text-base-content font-medium text-lg";
const ulStyle = "list-disc px-4 py-3";
const handleOnclick = () => {
props.setIsAgree(true);
props.setIsShowAgreeTerms(false);
props.showFirstWidget();
};
return (
<div>
<ModalUi
isOpen={true}
title={t("term-cond-title")}
handleClose={() => props.setIsShowAgreeTerms(false)}
>
<div className="h-[100%] p-[20px]">
<h2 className={h2Style}>{t("term-cond-h")}</h2>
<span className="mt-2">
{t("term-cond-p1", { appName: appName })}
</span>
<hr className="bg-[#9f9f9f] w-full my-[15px]" />
<h2 className={h2Style}>{t("term-cond-h1")}</h2>
<span className="mt-2">
{t("term-cond-p2", { appName: appName })}
</span>
<ul className={ulStyle}>
<li>{t("term-cond-p3", { appName: appName })}</li>
<li>{t("term-cond-p4")}</li>
</ul>
<hr className="bg-[#9f9f9f] w-full my-[15px]" />
<h2 className={h2Style}>{t("term-cond-h2")}</h2>
<span className="mt-2">{t("term-cond-p5")}</span>
<ul className={ulStyle}>
<li>{t("term-cond-p6", { appName: appName })}</li>
<li>{t("term-cond-p7", { appName: appName })}</li>
<li>{t("term-cond-p8")}</li>
</ul>
<hr className="bg-[#9f9f9f] w-full my-[15px]" />
<h2 className={h2Style}>{t("term-cond-h3")}</h2>
<span className="mt-2">{t("term-cond-p9")}</span>
<ul className={ulStyle}>
<li>{t("term-cond-p10")}</li>
<li>{t("term-cond-p11")}</li>
</ul>
<hr className="bg-[#9f9f9f] w-full my-[15px]" />
<h2 className={h2Style}>{t("term-cond-h4")}</h2>
<span className="mt-2">
{t("term-cond-p12", { appName: appName })}
</span>
<ul className={ulStyle}>
<li>{t("term-cond-p13")}</li>
<li>{t("term-cond-p14")}</li>
<li>{t("term-cond-p15")}</li>
</ul>
<span>{t("term-cond-p16", { appName: appName })}</span>
<hr className="bg-[#9f9f9f] w-full my-[15px]" />
<h2 className={h2Style}>{t("term-cond-h5")}</h2>
<p>{t("term-cond-p17", { appName: appName })}</p>
<p className="mt-2">{t("term-cond-p18")}</p>
<hr className="bg-[#9f9f9f] w-full my-[15px]" />
<h2 className={h2Style}>{t("term-cond-h6")}</h2>
<span className="mt-2">{t("term-cond-p19")}</span>
<ul className={ulStyle}>
<li>{t("term-cond-p20")}</li>
<li>{t("term-cond-p21")}</li>
<li>{t("term-cond-p22", { appName: appName })}</li>
</ul>
<hr className="bg-[#9f9f9f] w-full my-[15px]" />
<h2 className={h2Style}>{t("term-cond-h7")}</h2>
<span className="mt-2">
{t("term-cond-p23", { appName: appName })}
</span>
<ul className={ulStyle}>
<li>{t("term-cond-p24")}</li>
<li>{t("term-cond-p25")}</li>
</ul>
<hr className="bg-[#9f9f9f] w-full my-[15px]" />
<h2 className={h2Style}>{t("term-cond-h8")}</h2>
<span className="mt-2">
{t("term-cond-p26", { appName: appName })}
</span>
<hr className="bg-[#9f9f9f] w-full my-[15px]" />
<h2 className={h2Style}>{t("term-cond-h9")}</h2>
<span className="mt-2">
{t("term-cond-p27", { appName: appName })}
</span>
<hr className="bg-[#9f9f9f] w-full my-[15px]" />
<span className="mt-2 font-medium">
{t("term-cond-p28", { appName: appName })}
</span>
<hr className="bg-[#9f9f9f] w-full my-[15px]" />
<span className="mt-2">
{t("term-cond-p29", { appName: appName })}
</span>
<a
href="www.opensignlabs.com"
target="_blank"
className="text-blue-700 cursor-pointer"
>
www.opensignlabs.com
</a>
<span>{t("term-cond-p30")}</span>
<span className="font-medium"> support@opensignlabs.com </span>
<hr className="bg-[#9f9f9f] w-full my-[15px]" />
<div className="mt-6 flex justify-start gap-2">
<button
onClick={() => handleOnclick()}
className="op-btn op-btn-primary"
>
{t("agrre-button")}
</button>
<button
className="op-btn op-btn-ghost text-base-content"
onClick={() => props.setIsShowAgreeTerms(false)}
>
{t("close")}
</button>
</div>
</div>
</ModalUi>
</div>
);
}
export default AgreementContent;
@@ -1,54 +0,0 @@
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import AgreementContent from "./AgreementContent";
function AgreementSign(props) {
const { t } = useTranslation();
const [isShowAgreeTerms, setIsShowAgreeTerms] = useState(false);
return (
<>
<div className="op-modal op-modal-open absolute z-[448]">
<div className="w-[95%] md:w-[60%] lg:w-[40%] op-modal-box overflow-y-auto hide-scrollbar text-sm p-4">
<div className="flex flex-row items-center">
<div className="text-[11px] md:text-base text-base-content">
<span>{t("agree-p1")}</span>
<span
className="font-bold text-blue-600 cursor-pointer"
onClick={() => {
setIsShowAgreeTerms(true);
}}
>
{t("agree-p2")}
</span>
<span> {t("agree-p3")}</span>
</div>
</div>
<div className="flex mt-3">
<button
onClick={() => {
props.setIsAgree(true);
props.showFirstWidget();
}}
className="op-btn op-btn-primary op-btn-sm w-full md:w-auto"
>
{t("agrre-button")}
</button>
</div>
<div className="mt-2 text-base-content">
<span className="text-[11px]">{t("agreement-note")}</span>
</div>
</div>
</div>
{isShowAgreeTerms && (
<AgreementContent
setIsAgree={props.setIsAgree}
setIsShowAgreeTerms={setIsShowAgreeTerms}
showFirstWidget={props.showFirstWidget}
/>
)}
</>
);
}
export default AgreementSign;
@@ -0,0 +1,13 @@
import React from "react";
function BorderResize({ right, top }) {
return (
<div
className={`${right ? `-right-[12px]` : "-right-[1px]"} ${
top ? `-bottom-[11px]` : "-bottom-[1px]"
} absolute inline-block w-[14px] h-[14px] hover:cursor-sw-resize border-r-[3px] border-b-[3px] border-[#188ae2]`}
></div>
);
}
export default BorderResize;
@@ -1,23 +0,0 @@
import React from "react";
function BorderResize(props) {
const getHeight = () => {
const height = props.posHeight(props.pos, props.isSignYourself);
if (height > 14) {
return "14px";
} else {
return `${height}px`;
}
};
return (
<div
style={{ width: getHeight() || "14px", height: getHeight() || "14px" }}
className={`${props.right ? `-right-[12px]` : "-right-[2px]"} ${
props.top ? `-bottom-[12px]` : "-bottom-[2px]"
} absolute inline-block hover:cursor-sw-resize border-r-[3px] border-b-[3px] border-[#188ae2]`}
></div>
);
}
export default BorderResize;
@@ -1,104 +0,0 @@
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>
);
}
@@ -1,143 +0,0 @@
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>
);
}
@@ -0,0 +1,276 @@
import React, { useEffect, useState } from "react";
import opensignLogo from "../../assets/images/logo.png";
import {
Page,
Text,
View,
Document,
StyleSheet,
Image
} from "@react-pdf/renderer";
function Certificate({ pdfData }) {
const [isMultiSigners, setIsMultiSigners] = useState();
const [multiSigner, setMultiSigners] = useState([]);
const [isLoad, setIsLoad] = useState(false);
useEffect(() => {
handleSignerData();
// eslint-disable-next-line
}, []);
const handleSignerData = () => {
const checkSigners = pdfData.filter((data) => data.Signers);
if (checkSigners && checkSigners.length > 0) {
setIsMultiSigners(true);
const checkSignSigners =
pdfData[0].AuditTrail &&
pdfData[0].AuditTrail.length > 0 &&
pdfData[0].AuditTrail.filter((data) => data.Activity === "Signed");
setMultiSigners(checkSignSigners);
} else {
setIsMultiSigners(false);
}
setIsLoad(true);
};
const styles = StyleSheet.create({
page: {
borderRadius: "5px",
padding: "10px",
backgroundColor: "white"
},
section1: {
border: "1px solid rgb(177, 174, 174)",
padding: "20px"
},
textStyle: {
fontWeight: "bold",
fontSize: "11px",
marginBottom: "10px"
},
textStyle2: {
fontWeight: "600",
fontSize: "11px",
marginBottom: "10px",
color: "gray"
},
image: {
width: "71px",
height: "17px"
}
});
const generatedDate = () => {
const newDate = new Date();
const utcTime = newDate.toUTCString();
return (
<Text
style={{
color: "gray",
fontSize: "10px"
}}
>
Generated On {utcTime}
</Text>
);
};
const changeCompletedDate = () => {
const completedOn = pdfData[0].updatedAt;
const newDate = new Date(completedOn);
const utcTime = newDate.toUTCString();
return <Text style={styles.textStyle2}>{utcTime}</Text>;
};
const signerName = (data) => {
const getSignerName = pdfData[0].Signers.filter(
(sign) => sign.objectId === data.UserPtr.objectId
);
return (
getSignerName[0] &&
getSignerName.length > 0 && (
<>
<Text style={styles.textStyle}>
Name : &nbsp;
<Text style={styles.textStyle2}>{getSignerName[0].Name}</Text>
</Text>
<Text style={styles.textStyle}>
Email : &nbsp;
<Text style={styles.textStyle2}>{getSignerName[0].Email}</Text>
</Text>
</>
)
);
};
return (
isLoad && (
<Document>
{/** Page defines a single page of content. */}
<Page size="A4" style={styles.page}>
<View style={styles.section1}>
<View
style={{
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
marginBottom: "30px"
}}
>
<Image src={opensignLogo} style={styles.image} />
{generatedDate()}
</View>
<View style={{ justifyContent: "center" }}>
<Text
style={{
textAlign: "center",
fontSize: "20px",
fontWeight: "bold",
color: "#31bceb",
marginBottom: "10px"
}}
>
{" "}
Certificate of Completion
</Text>
<View style={{ border: "1px solid #bdbbbb" }}></View>
<View>
<View>
<Text
style={{
fontSize: "16px",
fontWeight: "bold",
color: "#31bceb",
margin: "10px 0px 10px 0px"
}}
>
Summary
</Text>
</View>
<View style={{ display: "flex", flexDirection: "column" }}>
<Text style={styles.textStyle}>
Document ID : &nbsp;
<Text style={styles.textStyle2}>{pdfData[0].objectId}</Text>
</Text>
<Text style={styles.textStyle}>
Document Name : &nbsp;
<Text style={styles.textStyle2}>{pdfData[0].Name}</Text>
</Text>
<Text style={styles.textStyle}>
Organization : &nbsp;
<Text style={styles.textStyle2}>
{pdfData[0].ExtUserPtr.Company}
</Text>
</Text>
<Text style={styles.textStyle}>
Completed on : &nbsp;{changeCompletedDate()}
</Text>
{multiSigner && multiSigner.length > 0 && (
<Text style={styles.textStyle}>
Signers : &nbsp;
<Text style={styles.textStyle2}>
{multiSigner.length}
</Text>
</Text>
)}
</View>
{isMultiSigners ? (
<View style={{ display: "flex", flexDirection: "column" }}>
<Text
style={{
fontSize: "16px",
fontWeight: "bold",
color: "#31bceb",
margin: "10px 0px 10px 0px"
}}
>
Recipients
</Text>
<View>
{multiSigner &&
multiSigner.map((data, ind) => {
return (
<View
key={ind}
style={{
display: "flex",
flexDirection: "column"
}}
>
<View
style={{
border: "0.4px solid #bdbbbb",
marginBottom: "10px"
}}
></View>
{signerName(data)}
<Text style={styles.textStyle}>
Accessed from : &nbsp;
<Text style={styles.textStyle2}>
{data.ipAddress}
</Text>
</Text>
</View>
);
})}
</View>
</View>
) : (
<View style={{ display: "flex", flexDirection: "column" }}>
<Text
style={{
fontSize: "16px",
fontWeight: "bold",
color: "#31bceb",
margin: "10px 0px 10px 0px"
}}
>
Recipients
</Text>
<Text style={styles.textStyle}>
Signers : &nbsp;<Text style={styles.textStyle2}>1</Text>
</Text>
<Text style={styles.textStyle}>
Name : &nbsp;
<Text style={styles.textStyle2}>
{pdfData[0].ExtUserPtr.Name}
</Text>
</Text>
<Text style={styles.textStyle}>
Email : &nbsp;
<Text style={styles.textStyle2}>
{pdfData[0].ExtUserPtr.Email}
</Text>
</Text>
<Text style={styles.textStyle}>
Accessed from : &nbsp;
<Text style={styles.textStyle2}>
{pdfData[0].AuditTrail &&
pdfData[0].AuditTrail[0].ipAddress}
</Text>
</Text>
<Text style={styles.textStyle}>
Signed on : &nbsp;{changeCompletedDate()}
</Text>
</View>
)}
</View>
</View>
</View>
</Page>
</Document>
)
);
}
export default Certificate;
@@ -1,155 +0,0 @@
import { useRef, useState } from "react";
import ModalUi from "../../primitives/ModalUi";
import { EmailBody } from "./EmailBody";
import {
contractDocument,
sendEmailToSigners
} from "../../constant/Utils";
import { useTranslation } from "react-i18next";
import Loader from "../../primitives/Loader";
import { useNavigate } from "react-router";
function CustomizeMail(props) {
const { t } = useTranslation();
const editorRef = useRef();
const navigate = useNavigate();
const copyUrlRef = useRef(null);
const [isCustomize, setIsCustomize] = useState(false);
const [isLoader, setIsLoader] = useState(false);
const handleCloseSendmailModal = () => {
if (props?.handleClose) {
props?.handleClose();
return;
}
props?.setIsMailModal(false);
navigate("/report/1MwEuxLEkF");
};
const handleOnchangeRequest = () => {
if (editorRef.current) {
const html = editorRef.current.editor.root.innerHTML;
props?.setCustomizeMail((prev) => ({
...prev,
body: html
}));
}
};
const handleEmailSendToSigners = async () => {
setIsLoader(true);
const documentData = await contractDocument(props?.documentId);
if (documentData && documentData?.length > 0) {
props?.setDocumentDetails(documentData[0]);
if (
documentData?.[0]?.SendinOrder &&
documentData?.[0]?.SendinOrder === true
) {
const ownerEmail = documentData[0].ExtUserPtr.Email;
const ownerDetails = documentData[0].Signers.find(
(x) => x.Email === ownerEmail
);
props?.setCurrUserId(ownerDetails?.objectId);
}
//function is used to send email to signers for sign the document
const mailRes = await sendEmailToSigners(
documentData,
props?.signerList,
props?.customizeMail,
props?.defaultMail,
isCustomize,
);
props?.setIsMailModal(false);
props?.setIsSend(true);
setIsLoader(false);
if (mailRes?.status === "success") {
props?.setMailStatus("success");
} else if (mailRes?.status === "quota-reached") {
props?.setMailStatus("quotareached");
} else if (mailRes?.status === "daily-quota-reached") {
props?.setMailStatus("dailyquotareached");
} else {
props?.setMailStatus("failed");
}
// setMailStatus(mail_status);
} else {
alert("something-went-wrong-mssg");
}
};
return (
<>
{isLoader ? (
<div className="absolute w-full h-full flex justify-center items-center bg-black/30 rounded-box z-30">
<Loader />
</div>
) : (
<ModalUi
isOpen={props?.isMailModal}
title={t("send-mail")}
handleClose={() => handleCloseSendmailModal()}
>
<div className="max-h-96 overflow-y-scroll scroll-hide p-[20px] text-base-content">
{!isCustomize && <span>{t("placeholder-alert-3")}</span>}
{
isCustomize && (
<>
<EmailBody
editorRef={editorRef}
requestBody={props?.customizeMail.body}
requestSubject={props?.customizeMail.subject}
handleOnchangeRequest={handleOnchangeRequest}
setCustomizeMail={props?.setCustomizeMail}
/>
<div
className="flex justify-end items-center gap-1 mt-2 op-link op-link-primary"
onClick={() => {
props?.setCustomizeMail(props?.defaultMail);
}}
>
<span>{t("reset-to-default")}</span>
</div>
</>
)
}
<div className="flex flex-row items-center gap-2 md:gap-6 mt-2">
<div className="flex flex-row gap-2">
<button
onClick={() => handleEmailSendToSigners()}
className="op-btn op-btn-primary font-[500] text-sm shadow"
>
{t("send")}
</button>
{isCustomize && (
<button
onClick={() => setIsCustomize(false)}
className="op-btn op-btn-ghost font-[500] text-sm"
>
{t("close")}
</button>
)}
</div>
{
!isCustomize && (
<span
className="op-link op-link-accent text-sm"
onClick={() => setIsCustomize(!isCustomize)}
>
{t("cutomize-email")}
</span>
)
}
</div>
<div className="flex justify-center items-center mt-3">
<span className="h-[1px] w-[20%] bg-[#ccc]"></span>
<span className="ml-[5px] mr-[5px]">{t("or")}</span>
<span className="h-[1px] w-[20%] bg-[#ccc]"></span>
</div>
<div className="my-3">{props?.handleShareList()}</div>
<p id="copyUrl" ref={copyUrlRef} className="hidden"></p>
</div>
</ModalUi>
)}
</>
);
}
export default CustomizeMail;
@@ -0,0 +1,46 @@
import React from "react";
import { useTranslation } from "react-i18next";
function DefaultSignature({ defaultSignImg, xyPostion, setDefaultSignAlert }) {
const { t } = useTranslation();
const confirmToaddDefaultSign = () => {
if (xyPostion.length > 0) {
setDefaultSignAlert({
isShow: true,
alertMessage: t("default-sign-alert")
});
} else {
setDefaultSignAlert({
isShow: true,
alertMessage: t("please-select-position!")
});
}
};
return (
<div data-tut="reactourThird">
<div className="mx-2 pr-2 pt-2 pb-1 text-[15px] text-base-content font-semibold border-b-[1px] border-base-300">
{t("signature")}
</div>
<div className="flex flex-col items-center mt-[10px] font-semibold">
<p className="text-base-content">{t("Your-Signature")}</p>
<div className="op-card shadow-md h-[111px] w-[90%] p-2">
<img
alt="signature"
className="w-full h-full object-contain"
src={defaultSignImg}
/>
</div>
<button
type="button"
className="op-btn op-btn-primary op-btn-sm mt-[10px]"
onClick={() => confirmToaddDefaultSign()}
>
{t("auto-sign-all")}
</button>
</div>
</div>
);
}
export default DefaultSignature;
@@ -1,118 +0,0 @@
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { useSelector } from "react-redux";
function DefaultSignature(props) {
const { t } = useTranslation();
const defaultSignImg = useSelector((state) => state.widget.defaultSignImg);
const myInitial = useSelector((state) => state.widget.myInitial);
const tabName = ["my-signature", "my-initials"];
const [activeTab, setActiveTab] = useState(0);
const confirmToaddDefaultSign = (type) => {
if (props?.xyPosition.length > 0) {
//check signature or initial widgets exist or not for auto signing
const getCurrentSignerXY = props?.xyPosition.filter(
(data) => data.Id === props.uniqueId
);
const checkIsSignInitialExist = getCurrentSignerXY?.every(
(placeholderObj) =>
placeholderObj?.placeHolder?.some((placeholder) =>
placeholder?.pos?.some((posItem) => posItem?.type === type)
)
);
if (checkIsSignInitialExist) {
props?.setDefaultSignAlert({
isShow: true,
alertMessage: t("default-sign-alert", { widgetsType: type }),
type: type
});
} else {
props?.setDefaultSignAlert({
isShow: true,
alertMessage: t("defaultSign-alert", { widgetsType: type })
});
}
} else {
props?.setDefaultSignAlert({
isShow: true,
alertMessage: t("please-select-position!")
});
}
};
return (
<div data-tut="reactourThird">
<div className="mx-2 pr-2 pt-2 pb-1 text-[15px] text-base-content font-semibold border-b-[1px] border-base-300">
<p className="text-base-content">{t("signature")}</p>
</div>
<div className="flex justify-center items-center mt-2">
<div role="tablist" className="op-tabs op-tabs-bordered">
{tabName.map((tabData, ind) => (
<div
onClick={() => setActiveTab(ind)}
key={ind}
role="tab"
className={`${
activeTab === ind ? "op-tab-active" : ""
} op-tab flex items-center pb-10 md:pb-0`}
>
<span className="ml-1 text-[7px] font-medium md:font-normal md:text-[12px]">
{t(`${tabData}`)}
</span>
</div>
))}
</div>
</div>
<div className="flex flex-col items-center mt-[10px] font-semibold relative">
<div className="op-card shadow-md h-[111px] w-[90%] p-2">
{activeTab === 0 ? (
<img
alt="signature"
className="w-full h-full object-contain"
src={defaultSignImg}
/>
) : (
activeTab === 1 &&
(myInitial ? (
<img
alt="signature"
className="w-full h-full object-contain"
src={myInitial}
/>
) : (
<div className="flex justify-center items-center h-full">
<span>{t("initial-alert")}</span>
</div>
))
)}
</div>
<button
type="button"
className="op-btn op-btn-primary op-btn-sm mt-[10px]"
onClick={() =>
confirmToaddDefaultSign(
activeTab === 0 ? "signature" : activeTab === 1 && "initials"
)
}
disabled={
activeTab === 0 && !props?.isDefault
? true
: activeTab === 1 && !myInitial
? true
: false
}
>
{t("auto-sign-all")}
</button>
{!props.isDefault && (
<div className="absolute bg-black/70 text-white w-full h-full flex items-center justify-center text-[11px] cursor-default">
<span className="-rotate-45">
Document creator has disabled this option
</span>
</div>
)}
</div>
</div>
);
}
export default DefaultSignature;
@@ -2,7 +2,7 @@ import React, { useEffect, useState } from "react";
import LoaderWithMsg from "../../primitives/LoaderWithMsg";
import { contractDocument } from "../../constant/Utils";
import HandleError from "../../primitives/HandleError";
import { useLocation, useNavigate } from "react-router";
import { useLocation, useNavigate } from "react-router-dom";
import { useTranslation } from "react-i18next";
function useQuery() {
return new URLSearchParams(useLocation().search);
@@ -30,19 +30,15 @@ function DraftDocument() {
documentData === "Error: Something went wrong!" ||
(documentData.result && documentData.result.error)
) {
if (documentData?.result?.error?.includes("deleted")) {
setIsLoading({
isLoader: false,
message: t("document-deleted")
});
} else {
setIsLoading({
isLoader: false,
message: t("something-went-wrong-mssg")
});
}
setIsLoading({
isLoader: false,
message: "Error: Something went wrong!"
});
} else {
setIsLoading({ isLoader: false, message: t("no-data") });
setIsLoading({
isLoader: false,
message: "No data found!"
});
}
};
@@ -1,5 +1,5 @@
import { widgets } from "../../constant/Utils";
import getWidgetType from "./getWidgetType";
import React from "react";
import { getWidgetType, widgets } from "../../constant/Utils";
function DragElement(item) {
const getWidgets = widgets;
@@ -1,43 +1,42 @@
import React, { useEffect, useState } from "react";
import { isEnableSubscription } from "../../constant/const";
import ModalUi from "../../primitives/ModalUi";
import { radioButtonWidget } from "../../constant/Utils";
import Upgrade from "../../primitives/Upgrade";
import { useTranslation } from "react-i18next";
import { fontColorArr, fontsizeArr } from "../../constant/Utils";
function DropdownWidgetOption(props) {
const { t } = useTranslation();
const [dropdownOptionList, setDropdownOptionList] = useState([
"Option-1",
"Option-2"
"option-1",
"option-2"
]);
const [minCount, setMinCount] = useState(0);
const [maxCount, setMaxCount] = useState(0);
const [dropdownName, setDropdownName] = useState();
const [dropdownName, setDropdownName] = useState(props.type);
const [isReadOnly, setIsReadOnly] = useState(false);
const [isHideLabel, setIsHideLabel] = useState(false);
const [status, setStatus] = useState("required");
const [defaultValue, setDefaultValue] = useState("");
const [defaultCheckbox, setDefaultCheckbox] = useState([]);
const [layout, setLayout] = useState("vertical");
const statusArr = ["required", "optional"];
const layoutArr = ["vertical", "horizontal"];
const isPrefillExist = props?.roleName === "prefill";
const [defaultCheckbox, setDefaultCheckbox] = useState([]);
const resetState = () => {
setDropdownOptionList(["Option-1", "Option-2"]);
setDropdownName(props.currWidgetsDetails?.options?.name || props.type);
setDropdownOptionList(["option-1", "option-2"]);
setDropdownName(props.type);
setIsReadOnly(false);
setIsHideLabel(false);
setMinCount(0);
setMaxCount(0);
setDefaultCheckbox([]);
setDefaultValue("");
setLayout("vertical");
};
useEffect(() => {
if (
props.currWidgetsDetails?.options?.name &&
props.currWidgetsDetails?.options?.values?.length > 0
props.currWidgetsDetails?.options?.values
) {
setDropdownName(props.currWidgetsDetails?.options?.name);
setDropdownOptionList(props.currWidgetsDetails?.options?.values);
@@ -52,7 +51,6 @@ function DropdownWidgetOption(props) {
setStatus(props.currWidgetsDetails?.options?.status || "required");
setDefaultValue(props.currWidgetsDetails?.options?.defaultValue || "");
setDefaultCheckbox(props.currWidgetsDetails?.options?.defaultValue || []);
setLayout(props.currWidgetsDetails?.options?.layout || "vertical");
} else {
setStatus("required");
resetState();
@@ -88,7 +86,7 @@ function DropdownWidgetOption(props) {
const deleteOption = true;
const addOption = false;
const getUpdatedOptions = dropdownOptionList.filter(
(_, index) => index !== ind
(data, index) => index !== ind
);
setDropdownOptionList(getUpdatedOptions);
props.handleSaveWidgetsOptions(
@@ -103,43 +101,11 @@ function DropdownWidgetOption(props) {
};
const handleSaveOption = () => {
if (["checkbox", radioButtonWidget, "dropdown"].includes(props.type)) {
const allUnique =
new Set(dropdownOptionList).size === dropdownOptionList.length;
if (!allUnique) {
alert("Please remove duplicate option");
return;
}
}
const defaultData =
defaultCheckbox && defaultCheckbox.length > 0
? defaultCheckbox
: defaultValue;
const isDropdownOrRadio =
props?.type === "dropdown" || props?.type === radioButtonWidget;
const readOnlyWithoutValue =
isReadOnly && !defaultValue && status !== "optional";
const isCheckbox = props?.type === "checkbox";
const WidgetLayout = ["checkbox", radioButtonWidget].includes(props.type)
? layout
: null;
// If its a dropdown and its read-only without a value (nor marked optional), stop here.
if (isDropdownOrRadio && readOnlyWithoutValue) {
alert(t("readonly-error", { widgetName: props?.type }));
return;
} else if (
isCheckbox &&
isReadOnly &&
minCount > 0 &&
defaultCheckbox?.length === 0
) {
alert(t("readonly-error", { widgetName: props?.type }));
return;
}
// Otherwise (either not a dropdown, or a valid dropdown), do the save + reset exactly once.
props.handleSaveWidgetsOptions(
dropdownName,
dropdownOptionList,
@@ -150,12 +116,28 @@ function DropdownWidgetOption(props) {
null,
status,
defaultData,
isHideLabel,
WidgetLayout
isHideLabel
);
resetState();
// props.setShowDropdown(false);
setDropdownOptionList(["option-1", "option-2"]);
setDropdownName(props.type);
// props.setCurrWidgetsDetails({});
setIsReadOnly(false);
setIsHideLabel(false);
setMinCount(0);
setMaxCount(0);
setDefaultCheckbox([]);
setDefaultValue("");
};
const handleSetMinMax = (e) => {
const minValue = e.target.value;
if (minValue > dropdownOptionList.length) {
return "";
} else {
return minValue;
}
};
const handleDefaultCheck = (index) => {
const getDefaultCheck = defaultCheckbox.includes(index);
@@ -165,6 +147,7 @@ function DropdownWidgetOption(props) {
return false;
}
};
return (
<ModalUi isOpen={props.showDropdown} title={props.title} showClose={false}>
<div className="h-full p-[15px] text-base-content">
@@ -175,30 +158,30 @@ function DropdownWidgetOption(props) {
}}
>
<div>
<label htmlFor="title" className="text-[13px] font-semibold">
<label className="text-[13px] font-semibold">
{t("name")}
<span className="text-[red] text-[13px]"> *</span>
</label>
<input
id="title"
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
onInput={(e) => e.target.setCustomValidity("")}
required
defaultValue={dropdownName}
value={dropdownName}
onChange={(e) => setDropdownName(e.target.value)}
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
required
/>
<label className="text-[13px] font-semibold mt-[5px]">
{t("options")}
</label>
<div className="flex flex-col">
{dropdownOptionList?.map((option, index) => (
{dropdownOptionList.map((option, index) => (
<div
key={index}
className="flex flex-row mb-[5px] items-center"
>
{props.type === "checkbox" && props.isShowAdvanceFeature && (
{props.type === "checkbox" && !props.isSignYourself && (
<input
type="checkbox"
checked={handleDefaultCheck(index)}
@@ -237,59 +220,117 @@ function DropdownWidgetOption(props) {
></i>
</div>
))}
<div>
<i
className="fa-light fa-square-plus text-[25px] ml-[10px] op-text-primary cursor-pointer"
aria-label="Add option"
onClick={handleAddInput}
></i>
</div>
</div>
{["dropdown", radioButtonWidget].includes(props.type) &&
!isPrefillExist && (
<>
<label className="text-[13px] font-semibold mt-[5px]">
{t("default-value")}
</label>
<select
value={defaultValue}
onChange={(e) => setDefaultValue(e.target.value)}
className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content w-full text-xs"
name="defaultvalue"
>
<option value="" disabled hidden className="text-[13px]">
{t("select")}...
</option>
{dropdownOptionList.map((data, ind) => {
return (
<option className="text-[13px]" key={ind} value={data}>
{data}
</option>
);
})}
</select>
</>
<i
onClick={handleAddInput}
className="fa-light fa-square-plus text-[25px] ml-[10px] op-text-primary cursor-pointer"
></i>
{isEnableSubscription && (
<div>
{props.type === "checkbox" && !props.isSignYourself && (
<>
<label
className={`${
!props.isSubscribe ? "text-[gray]" : ""
} text-[13px] font-semibold`}
>
{t("minimun-check")}
</label>
{!props.isSubscribe && isEnableSubscription && (
<Upgrade />
)}
<input
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
required
defaultValue={0}
value={minCount}
disabled={props.isSubscribe ? false : true}
onChange={(e) => {
const count = handleSetMinMax(e);
setMinCount(count);
}}
className={`${
props.isSubscribe ? "" : "pointer-events-none"
} op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs`}
/>
<label
className={`${
!props.isSubscribe ? "text-[gray]" : ""
} text-[13px] font-semibold`}
>
{t("maximum-check")}
</label>
<input
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
required
defaultValue={0}
value={maxCount}
disabled={props.isSubscribe ? false : true}
onChange={(e) => {
const count = handleSetMinMax(e);
setMaxCount(count);
}}
className={`${
props.isSubscribe ? "" : "pointer-events-none"
} op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs`}
/>
</>
)}
</div>
)}
{props.type !== "checkbox" && !isPrefillExist && (
<div className="flex flex-row gap-[10px] mt-[0.5rem]">
{statusArr.map((data, ind) => (
<div
key={ind}
className="flex flex-row gap-[5px] items-center"
>
<input
className="op-radio op-radio-xs my-1"
type="radio"
name="status"
onChange={() => setStatus(data.toLowerCase())}
checked={status.toLowerCase() === data.toLowerCase()}
/>
<div className="text-[13px] font-500 capitalize">
{data}
</div>
</div>
))}
</div>
</div>
{["dropdown", radioButtonWidget].includes(props.type) && (
<>
<label className="text-[13px] font-semibold mt-[5px]">
{t("default-value")}
</label>
<select
onChange={(e) => setDefaultValue(e.target.value)}
className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content w-full text-xs"
name="defaultvalue"
value={defaultValue}
placeholder="select default value"
>
<option value="" disabled hidden className="text-[13px]">
{t("select")}...
</option>
{dropdownOptionList.map((data, ind) => {
return (
<option className="text-[13px]" key={ind} value={data}>
{data}
</option>
);
})}
</select>
</>
)}
{props.type !== "checkbox" && props.type !== radioButtonWidget && (
<>
<div className="flex flex-row gap-[10px] mt-[0.5rem]">
{statusArr.map((data, ind) => {
return (
<div
key={ind}
className="flex flex-row gap-[5px] items-center"
>
<input
className="op-radio op-radio-xs my-1"
type="radio"
name="status"
onChange={() => setStatus(data.toLowerCase())}
checked={status.toLowerCase() === data.toLowerCase()}
/>
<div className="text-[13px] font-500">{data}</div>
</div>
);
})}
</div>
</>
)}
<div className="flex items-center mt-3 mb-3">
<span>{t("font-size")} :</span>
@@ -298,9 +339,9 @@ function DropdownWidgetOption(props) {
value={
props.fontSize ||
props.currWidgetsDetails?.options?.fontSize ||
12
"12"
}
onChange={(e) => props.setFontSize(parseInt(e.target.value))}
onChange={(e) => props.setFontSize(e.target.value)}
>
{fontsizeArr.map((size, ind) => {
return (
@@ -310,8 +351,8 @@ function DropdownWidgetOption(props) {
);
})}
</select>
<div className="flex flex-row gap-1 items-center ml-4">
<span className="capitalize">{t("color")} : </span>
<div className="flex flex-row gap-1 items-center ml-4 ">
<span>{t("color")} : </span>
<select
value={
props.fontColor ||
@@ -340,11 +381,9 @@ function DropdownWidgetOption(props) {
></span>
</div>
</div>
{["checkbox", radioButtonWidget, "dropdown"].includes(
props.type
) && (
{["checkbox", radioButtonWidget].includes(props.type) && (
<div className="flex flex-row gap-5 my-2 items-center text-center">
{props.isShowAdvanceFeature && !isPrefillExist && (
{!props.isSignYourself && (
<div className="flex items-center">
<input
id="isreadonly"
@@ -353,66 +392,31 @@ function DropdownWidgetOption(props) {
className="op-checkbox op-checkbox-sm"
onChange={(e) => setIsReadOnly(e.target.checked)}
/>
<label
className="ml-2 mb-0 capitalize"
htmlFor="isreadonly"
>
<label className="ml-1 mb-0" htmlFor="isreadonly">
{t("read-only")}
</label>
</div>
)}
{props.type !== "dropdown" && (
<div className="flex items-center">
<input
id="ishidelabel"
type="checkbox"
checked={isHideLabel}
className="op-checkbox op-checkbox-sm"
onChange={(e) => setIsHideLabel(e.target.checked)}
/>
<div className="flex items-center">
<input
id="ishidelabel"
type="checkbox"
checked={isHideLabel}
className="op-checkbox op-checkbox-sm"
onChange={(e) => setIsHideLabel(e.target.checked)}
/>
<label
className="ml-2 mb-0 capitalize"
htmlFor="ishidelabel"
>
{t("hide-labels")}
</label>
</div>
)}
<label className="ml-1 mb-0" htmlFor="ishidelabel">
{t("hide-labels")}
</label>
</div>
</div>
)}
{["checkbox", radioButtonWidget].includes(props.type) && (
<>
<div className="text-[13px] font-semibold mt-[5px] capitalize">
{t("layout")}
</div>
<div
className={`${props.type === "checkbox" ? "mb-[10px]" : ""} flex flex-row gap-[10px] mt-[0.5rem]`}
>
{layoutArr.map((data, ind) => (
<div
key={ind}
className="flex flex-row gap-[5px] items-center"
>
<input
className="op-radio op-radio-xs my-1"
type="radio"
name="layout"
checked={layout.toLowerCase() === data.toLowerCase()}
onChange={() => setLayout(data.toLowerCase())}
/>
<label className="text-[13px] font-500 mb-0">
{t(data)}
</label>
</div>
))}
</div>
</>
)}
</div>
<div
className={`${
props.type === "checkbox" && props.isShowAdvanceFeature
props.type === "checkbox" && !props.isSignYourself
? "mb-[15px]"
: "my-[15px]"
} w-full h-[1px] bg-[#9f9f9f]`}
@@ -428,7 +432,7 @@ function DropdownWidgetOption(props) {
{props.currWidgetsDetails?.options?.values?.length > 0 && (
<button
type="submit"
className="op-btn op-btn-ghost text-base-content ml-1"
className="op-btn op-btn-ghost ml-1"
onClick={() => {
props.handleClose && props.handleClose();
resetState();
@@ -0,0 +1,270 @@
import React, { useState, useEffect } from "react";
import { checkIsSubscribed, getFileName } from "../../constant/Utils";
import Upgrade from "../../primitives/Upgrade";
import { isEnableSubscription } from "../../constant/const";
import { useTranslation } from "react-i18next";
import { Tooltip } from "react-tooltip";
// import SelectFolder from "../../premitives/SelectFolder";
const EditTemplate = ({ template, onSuccess }) => {
const { t } = useTranslation();
// const [folder, setFolder] = useState({ ObjectId: "", Name: "" });
const [formData, setFormData] = useState({
Name: template?.Name || "",
Note: template?.Note || "",
Description: template?.Description || "",
SendinOrder: template?.SendinOrder ? `${template?.SendinOrder}` : "false",
AutomaticReminders: template?.AutomaticReminders || false,
RemindOnceInEvery: template?.RemindOnceInEvery || 5,
IsEnableOTP: template?.IsEnableOTP ? `${template?.IsEnableOTP}` : "false"
});
const [isSubscribe, setIsSubscribe] = useState(false);
useEffect(() => {
fetchSubscription();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const fetchSubscription = async () => {
if (isEnableSubscription) {
const subscribe = await checkIsSubscribed();
setIsSubscribe(subscribe.isValid);
}
};
const handleStrInput = (e) => {
setFormData({ ...formData, [e.target.name]: e.target.value });
};
// const handleFolder = (data) => {
// console.log("handleFolder ", data)
// setFolder(data);
// };
// Define a function to handle form submission
const handleSubmit = async (e) => {
e.preventDefault();
e.stopPropagation();
const isChecked = formData.SendinOrder === "true" ? true : false;
const AutoReminder = formData?.AutomaticReminders || false;
const IsEnableOTP = formData.IsEnableOTP === "true" ? true : false;
let reminderDate = {};
if (AutoReminder) {
const RemindOnceInEvery = parseInt(formData?.RemindOnceInEvery);
const ReminderDate = new Date(template?.createdAt);
ReminderDate.setDate(ReminderDate.getDate() + RemindOnceInEvery);
reminderDate = { NextReminderDate: ReminderDate };
}
const data = {
...formData,
SendinOrder: isChecked,
IsEnableOTP: IsEnableOTP,
...reminderDate
};
onSuccess(data);
};
const handleAutoReminder = () => {
setFormData((prev) => ({
...prev,
AutomaticReminders: !formData.AutomaticReminders
}));
};
return (
<div className="max-h-[300px] md:max-h-[400px] overflow-y-scroll p-[10px]">
<div className="text-base-content">
<form onSubmit={handleSubmit}>
<div className="mb-[0.35rem]">
<label htmlFor="name" className="text-[13px]">
{t("report-heading.File")}
</label>
<div className="op-input op-input-bordered op-input-sm focus:outline-none py-2 font-semibold w-full text-xs">
{getFileName(template.URL)}
</div>
</div>
<div className="mb-[0.35rem]">
<label htmlFor="name" className="text-[13px]">
{t("name")}
<span className="text-[13px] text-[red]"> *</span>
</label>
<input
type="text"
name="Name"
value={formData.Name}
onChange={(e) => handleStrInput(e)}
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
onInput={(e) => e.target.setCustomValidity("")}
required
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
/>
</div>
<div className="mb-[0.35rem]">
<label htmlFor="Note" className="text-[13px]">
{t("report-heading.Note")}
</label>
<input
type="text"
name="Note"
id="Note"
value={formData.Note}
onChange={(e) => handleStrInput(e)}
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
/>
</div>
<div className="mb-[0.35rem]">
<label htmlFor="Description" className="text-[13px]">
{t("description")}
</label>
<input
type="text"
name="Description"
id="Description"
value={formData.Description}
onChange={(e) => handleStrInput(e)}
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
/>
</div>
<div className="mb-[0.35rem]">
<label className="text-[13px]">{t("send-in-order")}</label>
<div className="flex items-center gap-[8px] ml-[8px] mb-[5px]">
<input
type="radio"
value={"true"}
className="op-radio op-radio-xs"
name="SendinOrder"
checked={formData.SendinOrder === "true"}
onChange={handleStrInput}
/>
<div className="text-[12px]">{t("yes")}</div>
</div>
<div className="flex items-center gap-[8px] ml-[8px] mb-[5px]">
<input
type="radio"
value={"false"}
name="SendinOrder"
className="op-radio op-radio-xs"
checked={formData.SendinOrder === "false"}
onChange={handleStrInput}
/>
<div className="text-[12px]">{t("no")}</div>
</div>
</div>
{isEnableSubscription && (
<div className="text-xs mt-2">
<span
className={
isSubscribe ? "font-semibold" : "font-semibold text-gray-300"
}
>
{t("auto-reminder")}
{" "}
{!isSubscribe && isEnableSubscription && <Upgrade />}
</span>
<label
className={`${
isSubscribe
? "cursor-pointer "
: "pointer-events-none opacity-50"
} relative block items-center mb-0`}
>
<input
checked={formData.AutomaticReminders}
onChange={handleAutoReminder}
type="checkbox"
className="op-toggle transition-all checked:[--tglbg:#3368ff] checked:bg-white mt-2"
/>
</label>
</div>
)}
{isSubscribe && formData?.AutomaticReminders === true && (
<div className="text-xs mt-2">
<label className="block">
{t("remind-once")}
<span className="text-red-500 text-[13px]">*</span>
</label>
<input
type="number"
value={formData.RemindOnceInEvery}
name="RemindOnceInEvery"
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
onChange={handleStrInput}
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
required
/>
</div>
)}
{isEnableSubscription && (
<div className="text-xs mt-2">
<label className="block">
<span className={isSubscribe ? "" : " text-gray-300"}>
{t("isenable-otp")}{" "}
<a data-tooltip-id="isenableotp-tooltip" className="ml-1">
<sup>
<i className="fa-light fa-question rounded-full border-[#33bbff] text-[#33bbff] text-[13px] border-[1px] py-[1.5px] px-[4px]"></i>
</sup>
</a>{" "}
{!isSubscribe && isEnableSubscription && <Upgrade />}
</span>
<Tooltip id="isenableotp-tooltip" className="z-50">
<div className="max-w-[200px] md:max-w-[450px]">
<p className="font-bold">{t("isenable-otp")}</p>
<p>{t("isenable-otp-help.p1")}</p>
<p className="p-[5px]">
<ol className="list-disc">
<li>
<span className="font-bold">{t("yes")}: </span>
<span>{t("isenable-otp-help.p2")}</span>
</li>
<li>
<span className="font-bold">{t("no")}: </span>
<span>{t("isenable-otp-help.p3")}</span>
</li>
</ol>
</p>
<p>{t("isenable-otp-help.p4")}</p>
</div>
</Tooltip>
</label>
<div
className={`${
isSubscribe ? "" : "pointer-events-none opacity-50"
} flex items-center gap-2 ml-2 mb-1 `}
>
<input
type="radio"
value={"true"}
className="op-radio op-radio-xs"
name="IsEnableOTP"
checked={formData.IsEnableOTP === "true"}
onChange={handleStrInput}
/>
<div className="text-center">{t("yes")}</div>
</div>
<div
className={`${
isSubscribe ? "" : "pointer-events-none opacity-50"
} flex items-center gap-2 ml-2 mb-1 `}
>
<input
type="radio"
value={"false"}
name="IsEnableOTP"
className="op-radio op-radio-xs"
checked={formData.IsEnableOTP === "false"}
onChange={handleStrInput}
/>
<div className="text-center">{t("no")}</div>
</div>
</div>
)}
<div className="mt-[1rem] flex justify-start">
<button type="submit" className="op-btn op-btn-primary">
{t("submit")}
</button>
</div>
</form>
</div>
</div>
);
};
export default EditTemplate;
@@ -1,542 +0,0 @@
import {
useState,
useRef,
} from "react";
import {
base64ToArrayBuffer,
convertBase64ToFile,
generatePdfName,
getFileName
} from "../../constant/Utils";
import {
maxDescriptionLength,
maxNoteLength,
maxTitleLength
} from "../../constant/const";
import { useTranslation } from "react-i18next";
import { Tooltip } from "react-tooltip";
import SignersInput from "../shared/fields/SignersInput";
import { PDFDocument } from "pdf-lib";
import ModalUi from "../../primitives/ModalUi";
import { SaveFileSize } from "../../constant/saveFileSize";
const EditTemplate = ({
title,
handleClose,
pdfbase64,
template,
onSuccess,
setPdfArrayBuffer,
setPdfBase64Url,
isAddYourSelfCheckbox,
}) => {
const appName =
"OpenSign™";
const { t } = useTranslation();
const inputFileRef = useRef(null);
const [formData, setFormData] = useState({
Name: template?.Name || "",
Note: template?.Note || "",
Description: template?.Description || "",
SendinOrder: template?.SendinOrder ? `${template?.SendinOrder}` : "false",
AutomaticReminders: template?.AutomaticReminders || false,
RemindOnceInEvery: template?.RemindOnceInEvery || 5,
IsEnableOTP: template?.IsEnableOTP ? `${template?.IsEnableOTP}` : "false",
IsTourEnabled: template?.IsTourEnabled
? `${template?.IsTourEnabled}`
: "false",
NotifyOnSignatures:
template?.NotifyOnSignatures !== undefined
? template?.NotifyOnSignatures
: false,
Bcc: template?.Bcc,
RedirectUrl: template?.RedirectUrl || "",
AllowModifications: template?.AllowModifications || false,
TimeToCompleteDays: template?.TimeToCompleteDays || 15,
});
const [isUpdate, setIsUpdate] = useState(false);
const [showConfirm, setShowConfirm] = useState(false);
const [uploadPdf, setUploadPdf] = useState({
name: "",
base64: "",
url: ""
});
const handleDrop = (e) => {
e.preventDefault();
const file = e.dataTransfer.files[0];
handleFile(file);
};
const handleFile = (file) => {
if (file && file.type === "application/pdf") {
handleReplaceFileValdition(file);
// You can handle the file here
} else {
alert(t("only-pdf-allowed"));
if (inputFileRef.current) inputFileRef.current.value = "";
}
};
const handleDragOver = (e) => {
e.preventDefault();
};
// `isValidURL` is used to check valid webhook url
function isValidURL(value) {
try {
const url = new URL(value);
return url.protocol === "https:" || url.protocol === "http:";
} catch (error) {
return false;
}
}
const handleStrInput = (e) => {
setIsUpdate(true);
setFormData({ ...formData, [e.target.name]: e.target.value });
};
const getPdfMetadataHash = async (pdfBytes) => {
const pdfDoc = await PDFDocument.load(pdfBytes);
const pages = pdfDoc.getPages();
const metaString = pages
.map((page, index) => {
const { width, height } = page.getSize();
return `${index + 1}:${Math.round(width)}x${Math.round(height)}`;
})
.join("|");
const encoder = new TextEncoder();
const data = encoder.encode(metaString);
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
return Array.from(new Uint8Array(hashBuffer))
.map((b) => b.toString(16).padStart(2, "0"))
.join("");
};
const handleFileInput = async (e) => {
const file = e.target.files[0];
if (!file) return;
handleReplaceFileValdition(file);
};
const handleReplaceFileValdition = async (file) => {
try {
const basePdfBytes = base64ToArrayBuffer(pdfbase64);
const expectedHash = await getPdfMetadataHash(basePdfBytes);
const fileReader = new FileReader();
fileReader.onload = async (event) => {
const uploadedPdfBytes = event.target.result;
const uploadedHash = await getPdfMetadataHash(uploadedPdfBytes);
if (expectedHash === uploadedHash) {
const arrayBuffer = uploadedPdfBytes;
const uint8Array = new Uint8Array(arrayBuffer);
const binaryString = Array.from(uint8Array)
.map((b) => String.fromCharCode(b))
.join("");
const base64 = btoa(binaryString);
const pdfName = generatePdfName(16);
setIsUpdate(true);
setUploadPdf((prev) => ({ ...prev, name: pdfName, base64: base64 }));
// alert("✅ PDFs match (based on page number, width, height)");
} else {
alert("❌ PDF do NOT match based on page number, width, height");
if (inputFileRef.current) inputFileRef.current.value = "";
}
};
fileReader.readAsArrayBuffer(file);
} catch (err) {
alert("Error: " + err.message);
if (inputFileRef.current) inputFileRef.current.value = "";
}
};
// Define a function to handle form submission
const handleSubmit = async (e) => {
e.preventDefault();
e.stopPropagation();
if (formData.RedirectUrl && !isValidURL(formData?.RedirectUrl)) {
alert(t("invalid-redirect-url"));
return;
}
if (formData?.Name?.length > maxTitleLength) {
alert(t("title-length-alert"));
return;
}
if (formData?.Note?.length > maxNoteLength) {
alert(t("note-length-alert"));
return;
}
if (formData?.Description?.length > maxDescriptionLength) {
alert(t("description-length-alert"));
return;
}
let pdfUrl;
if (uploadPdf?.base64) {
pdfUrl = await convertBase64ToFile(
uploadPdf.name,
uploadPdf.base64,
);
setUploadPdf((prev) => ({ ...prev, url: pdfUrl }));
const pdfBuffer = base64ToArrayBuffer(uploadPdf.base64);
setPdfArrayBuffer && setPdfArrayBuffer(pdfBuffer);
setPdfBase64Url && setPdfBase64Url(uploadPdf.base64);
const tenantId =
localStorage.getItem("TenantId") ||
template?.ExtUserPtr?.TenantId?.objectId;
const buffer = atob(uploadPdf.base64);
const userId = template?.ExtUserPtr?.UserId?.objectId;
SaveFileSize(buffer.length, pdfUrl, tenantId, userId);
}
const isChecked = formData.SendinOrder === "true" ? true : false;
const isTourEnabled = formData?.IsTourEnabled === "false" ? false : true;
const AutoReminder = formData?.AutomaticReminders || false;
const IsEnableOTP = formData.IsEnableOTP === "true" ? true : false;
const allowModify = formData?.AllowModifications || false;
let reminderDate = {};
const remindOnceInEvery = formData?.RemindOnceInEvery;
const TimeToCompleteDays = parseInt(formData?.TimeToCompleteDays);
const reminderCount = TimeToCompleteDays / remindOnceInEvery;
if (AutoReminder && reminderCount > 15) {
alert(t("only-15-reminder-allowed"));
return;
}
if (AutoReminder) {
const RemindOnceInEvery = parseInt(formData?.RemindOnceInEvery);
const ReminderDate = new Date(template?.createdAt);
ReminderDate.setDate(ReminderDate.getDate() + RemindOnceInEvery);
reminderDate = { NextReminderDate: ReminderDate };
}
const data = {
...formData,
...(pdfUrl ? { URL: pdfUrl } : {}),
SendinOrder: isChecked,
IsEnableOTP: IsEnableOTP,
IsTourEnabled: isTourEnabled,
AllowModifications: allowModify,
...reminderDate
};
onSuccess(data);
};
// `handleNotifySignChange` is trigger when user change radio of notify on signatures
const handleNotifySignChange = (value) => {
setIsUpdate(true);
setFormData((obj) => ({ ...obj, NotifyOnSignatures: value }));
};
const handleBcc = (data) => {
if (data && data.length > 0) {
const trimEmail = data.map((item) => ({
objectId: item?.value,
Name: item?.label,
Email: item?.email
}));
setIsUpdate(true);
setFormData((prev) => ({ ...prev, Bcc: trimEmail }));
}
};
const handleEditTemplateClose = () => {
if (isUpdate) {
setShowConfirm(true);
} else {
handleClose();
}
};
const discardChanges = () => {
setShowConfirm(false);
handleClose();
};
return (
<ModalUi
isOpen
title={isUpdate ? `${title} (unsaved)` : title}
handleClose={handleEditTemplateClose}
>
<ModalUi isOpen={showConfirm} showClose={false}>
<div className="p-[20px]">
<p className="text-base font-normal text-base-content py-[5px] md:py-[6px] px-[5px]">
{t("unsaved-changes-discard-them?")}
</p>
<div className="flex items-center mt-2.5 gap-2 md:gap-3 text-white">
<button
className="op-btn op-btn-primary px-6"
onClick={discardChanges}
>
{t("yes-discard")}
</button>
<button
className="op-btn op-btn-secondary px-4 md:px-6"
onClick={() => setShowConfirm(false)}
>
{t("cancel")}
</button>
</div>
</div>
</ModalUi>
<div className="max-h-[300px] md:max-h-[400px] overflow-y-scroll p-[10px]">
<div className="text-base-content">
<form onSubmit={handleSubmit}>
<div className="mb-[0.35rem]">
<label htmlFor="name" className="text-[13px]">
{t("report-heading.File")}
</label>
<div
className="border-[1.5px] border-dashed border-gray-300 rounded-lg px-4 py-6 text-center text-gray-500 bg-white cursor-pointer hover:border-base-content transition"
onDrop={handleDrop}
onDragOver={handleDragOver}
onClick={() => inputFileRef?.current?.click()}
>
<label
htmlFor="fileUpload"
className="cursor-pointer text-center mb-0"
>
{t("browse-or-drag-to-replace-existing-file")}
</label>
</div>
<input
ref={inputFileRef}
type="file"
className="hidden"
accept="application/pdf"
onChange={(e) => handleFileInput(e)}
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
/>
{uploadPdf?.name && (
<div
onClick={() => inputFileRef?.current?.click()}
className="mt-2 cursor-pointer op-input op-input-bordered op-input-sm focus:outline-none py-2 font-semibold w-full text-xs"
>
selected:{" "}
{uploadPdf?.url
? `${uploadPdf?.name}.pdf`
: getFileName(template.URL)}
</div>
)}
</div>
<div className="mb-[0.35rem]">
<label htmlFor="name" className="text-[13px]">
{t("Title")}
<span className="text-[13px] text-[red]"> *</span>
</label>
<input
type="text"
name="Name"
value={formData.Name}
onChange={(e) => handleStrInput(e)}
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
required
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
/>
</div>
<div className="mb-[0.35rem]">
<label htmlFor="Note" className="text-[13px]">
{t("report-heading.Note")}
</label>
<input
type="text"
name="Note"
id="Note"
value={formData.Note}
onChange={(e) => handleStrInput(e)}
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
/>
</div>
<div className="mb-[0.35rem]">
<label htmlFor="Description" className="text-[13px]">
{t("description")}
</label>
<input
type="text"
name="Description"
id="Description"
value={formData.Description}
onChange={(e) => handleStrInput(e)}
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
/>
</div>
<div className="mb-[0.35rem]">
<label className="text-[13px]">{t("send-in-order")}</label>
<div className="flex flex-col md:flex-row md:gap-4">
<div className="flex items-center gap-[8px] ml-[8px] mb-[5px]">
<input
type="radio"
value={"true"}
className="op-radio op-radio-xs"
name="SendinOrder"
checked={formData.SendinOrder === "true"}
onChange={handleStrInput}
/>
<div className="text-[12px]">{t("yes")}</div>
</div>
<div className="flex items-center gap-[8px] ml-[8px] mb-[5px]">
<input
type="radio"
value={"false"}
name="SendinOrder"
className="op-radio op-radio-xs"
checked={formData.SendinOrder === "false"}
onChange={handleStrInput}
/>
<div className="text-[12px]">{t("no")}</div>
</div>
</div>
</div>
<div className="text-xs mt-3">
<label className="block">
<span>
{t("enable-tour")}
<a data-tooltip-id="istourenabled-tooltip" className="ml-1">
<sup>
<i className="fa-light fa-question rounded-full border-[#33bbff] text-[#33bbff] text-[13px] border-[1px] py-[1.5px] px-[4px]"></i>
</sup>
</a>{" "}
</span>
<Tooltip id="istourenabled-tooltip" className="z-50">
<div className="max-w-[200px] md:max-w-[450px]">
<p className="font-bold">{t("enable-tour")}</p>
<div className="p-[5px]">
<ol className="list-disc">
<li>
<span className="font-bold">{t("yes")}: </span>
<span>{t("istourenabled-help.p1")}</span>
</li>
<li>
<span className="font-bold">{t("no")}: </span>
<span>{t("istourenabled-help.p2")}</span>
</li>
</ol>
</div>
<p>{t("istourenabled-help.p3", { appName: appName })}</p>
</div>
</Tooltip>
</label>
<div className="flex flex-col md:flex-row md:gap-4">
<div className="flex items-center gap-2 ml-2 mb-1">
<input
type="radio"
value={"true"}
className="op-radio op-radio-xs"
name="IsTourEnabled"
checked={formData.IsTourEnabled === "true"}
onChange={handleStrInput}
/>
<div className="text-center">{t("yes")}</div>
</div>
<div className="flex items-center gap-2 ml-2 mb-1">
<input
type="radio"
value={"false"}
name="IsTourEnabled"
className="op-radio op-radio-xs"
checked={formData.IsTourEnabled === "false"}
onChange={handleStrInput}
/>
<div className="text-center">{t("no")}</div>
</div>
</div>
</div>
<div className="text-xs mt-3">
<label>
{t("notify-on-signatures")}
<a data-tooltip-id="nos-tooltip" className="ml-1">
<sup>
<i className="fa-light fa-question rounded-full border-[#33bbff] text-[#33bbff] text-[13px] border-[1px] py-[1.5px] px-[4px]"></i>
</sup>
</a>{" "}
<Tooltip id="nos-tooltip" className="z-[999]">
<div className="max-w-[200px] md:max-w-[450px] text-[11px]">
<p className="font-bold">{t("notify-on-signatures")}</p>
<p>{t("notify-on-signatures-help.p1")}</p>
<p>{t("notify-on-signatures-help.note")}</p>
</div>
</Tooltip>
</label>
<div className="flex flex-col md:flex-row md:gap-4">
<div
className={
`flex items-center gap-2 ml-2 mb-1`
}
>
<input
className="mr-[2px] op-radio op-radio-xs"
type="radio"
onChange={() => handleNotifySignChange(true)}
checked={formData.NotifyOnSignatures === true}
/>
<div className="text-center">{t("yes")}</div>
</div>
<div
className={
`flex items-center gap-2 ml-2 mb-1`
}
>
<input
className="mr-[2px] op-radio op-radio-xs"
type="radio"
onChange={() => handleNotifySignChange(false)}
checked={formData.NotifyOnSignatures === false}
/>
<div className="text-center">{t("no")}</div>
</div>
</div>
</div>
<div className="text-xs mt-3">
<SignersInput
label={t("Bcc")}
initialData={template?.Bcc}
onChange={handleBcc}
helptextZindex={50}
helpText={t("bcc-help")}
isCaptureAllData
isAddYourSelfCheckbox={isAddYourSelfCheckbox}
/>
</div>
<div className="text-xs mt-2">
<label className="block">{t("redirect-url")}</label>
<input
name="RedirectUrl"
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
value={formData.RedirectUrl}
onChange={handleStrInput}
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
/>
</div>
<div className="text-xs mt-2">
<label className="block">
{t("time-to-complete")}
<span className="text-red-500 text-[13px]">*</span>
</label>
<input
type="number"
name="TimeToCompleteDays"
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
value={formData.TimeToCompleteDays}
onChange={(e) => handleStrInput(e)}
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
required
/>
</div>
<div className="mt-[1rem] flex justify-start">
<button type="submit" className="op-btn op-btn-primary">
{t("submit")}
</button>
</div>
</form>
</div>
</div>
</ModalUi>
);
};
export default EditTemplate;
@@ -1,10 +1,5 @@
import React from "react";
import { Quill } from "react-quill-new";
// Use a third-party plugin so editors can toggle raw HTML view.
import htmlEditButton from "quill-html-edit-button";
// Register the module which adds a "<>" button for HTML editing.
Quill.register("modules/htmlEditButton", htmlEditButton);
// Custom Undo button icon component for Quill editor. You can import it directly
// from 'quill/assets/icons/undo.svg' but I found that a number of loaders do not
@@ -59,24 +54,17 @@ Quill.register(Font, true);
export const module1 = {
toolbar: {
container: "#toolbar1",
handlers: { undo: undoChange, redo: redoChange }
handlers: {
undo: undoChange,
redo: redoChange
}
},
history: { delay: 500, maxStack: 100, userOnly: true },
// Enable the "<>" button registered above
htmlEditButton: {}
history: {
delay: 500,
maxStack: 100,
userOnly: true
}
};
// Modules object for setting up the Quill editor
export const module2 = {
toolbar: {
container: "#toolbar2",
handlers: { undo: undoChange, redo: redoChange }
},
history: { delay: 500, maxStack: 100, userOnly: true },
// Enable the "<>" button registered above
htmlEditButton: {}
};
// Formats objects for setting up the Quill editor
export const formats = [
"header",
@@ -18,13 +18,7 @@ export function EmailBody(props) {
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
onInput={(e) => e.target.setCustomValidity("")}
value={props.requestSubject}
onChange={(e) => {
props?.setRequestSubject(e.target.value);
props?.setCustomizeMail((prev) => ({
...prev,
subject: e.target.value
}));
}}
onChange={(e) => props.setRequestSubject(e.target.value)}
placeholder='${senderName} has requested you to sign "${documentName}"'
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
/>
@@ -1,21 +1,21 @@
import React, { useState } from "react";
import {
handleToPrint,
} from "../../constant/Utils";
import {
emailRegex,
} from "../../constant/const";
import axios from "axios";
import { handleToPrint } from "../../constant/Utils";
import { themeColor, emailRegex } from "../../constant/const";
import Loader from "../../primitives/Loader";
import ModalUi from "../../primitives/ModalUi";
import { useTranslation } from "react-i18next";
import Parse from "parse";
function EmailComponent({
isEmail,
pdfUrl,
setIsEmail,
setSuccessEmail,
pdfDetails,
sender,
setIsAlert,
extUserId,
activeMailAdapter,
setIsDownloadModal
}) {
const { t } = useTranslation();
@@ -25,13 +25,79 @@ function EmailComponent({
const [emailErr, setEmailErr] = useState(false);
const [isDownloading, setIsDownloading] = useState("");
const isAndroid = /Android/i.test(navigator.userAgent);
//function for send email
const sendEmail = async () => {
const pdfName = pdfDetails[0]?.Name;
setIsLoading(true);
const params = { docId: pdfDetails?.[0]?.objectId, recipients: emailList };
const sendmail = await Parse.Cloud.run("forwarddoc", params);
if (sendmail?.status === "success") {
let sendMail;
const docId = !pdfDetails?.[0]?.IsEnableOTP
? pdfDetails?.[0]?.objectId
: "";
let presignedUrl = pdfUrl;
try {
// const url = await Parse.Cloud.run("getsignedurl", { url: pdfUrl });
const axiosRes = await axios.post(
`${localStorage.getItem("baseUrl")}/functions/getsignedurl`,
{ url: pdfUrl, docId: docId },
{
headers: {
"content-type": "Application/json",
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
}
}
);
presignedUrl = axiosRes.data.result;
} catch (err) {
console.log("err in getsignedurl", err);
}
for (let i = 0; i < emailList.length; i++) {
try {
const imgPng =
"https://qikinnovation.ams3.digitaloceanspaces.com/logo.png";
let url = `${localStorage.getItem("baseUrl")}functions/sendmailv3`;
const headers = {
"Content-Type": "application/json",
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
sessionToken: localStorage.getItem("accesstoken")
};
const openSignUrl = "https://www.opensignlabs.com/contact-us";
const themeBGcolor = themeColor;
let params = {
mailProvider: activeMailAdapter,
extUserId: extUserId,
pdfName: pdfName,
url: presignedUrl,
recipient: emailList[i],
subject: `${sender.name} has signed the doc - ${pdfName}`,
from: sender.email,
html:
"<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body> <div style='background-color:#f5f5f5;padding:20px'> <div style='box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;background-color:white;'> <div><img src=" +
imgPng +
" height='50' style='padding:20px,width:170px,height:40px'/> </div><div style='padding:2px;font-family:system-ui; background-color:" +
themeBGcolor +
";'> <p style='font-size:20px;font-weight:400;color:white;padding-left:20px',> Document Copy</p></div><div><p style='padding:20px;font-family:system-ui;font-size:14px'>A copy of the document <strong>" +
pdfName +
" </strong>is attached to this email. Kindly download the document from the attachment.</p></div> </div><div><p>This is an automated email from OpenSign™. For any queries regarding this email, please contact the sender " +
sender.email +
" directly. If you think this email is inappropriate or spam, you may file a complaint with OpenSign™ <a href= " +
openSignUrl +
" target=_blank>here</a> </p></div></div></body></html>"
};
sendMail = await axios.post(url, params, { headers: headers });
} catch (error) {
console.log("error", error);
setIsLoading(false);
setIsEmail(false);
setIsAlert({
isShow: true,
alertMessage: t("something-went-wrong-mssg")
});
}
}
if (sendMail?.data?.result?.status === "success") {
setSuccessEmail(true);
setIsEmail(false);
setTimeout(() => {
@@ -40,8 +106,7 @@ function EmailComponent({
setEmailList([]);
}, 1500);
setIsLoading(false);
}
else {
} else {
setIsLoading(false);
setIsEmail(false);
setIsAlert({
@@ -111,23 +176,23 @@ function EmailComponent({
</div>
)}
<div className="flex justify-between items-center py-[10px] px-[20px] border-b-[1px] border-base-content">
<span className="text-base-content font-bold text-sm md:text-lg">
<span className="text-base-content font-semibold">
{t("successfully-signed")}
</span>
<div className="flex flex-row">
{!isAndroid && (
<button
onClick={(e) =>
handleToPrint(e, setIsDownloading, pdfDetails)
handleToPrint(e, pdfUrl, setIsDownloading, pdfDetails)
}
className="op-btn op-btn-neutral op-btn-sm text-xs md:text-[15px]"
className="op-btn op-btn-neutral op-btn-sm text-[15px]"
>
<i className="fa-light fa-print" aria-hidden="true"></i>
{t("print")}
</button>
)}
<button
className="op-btn op-btn-primary op-btn-sm text-xs md:text-[15px] ml-2"
className="op-btn op-btn-primary op-btn-sm text-[15px] ml-2"
onClick={() => {
handleClose();
setIsDownloadModal(true);
@@ -143,12 +208,12 @@ function EmailComponent({
{t("email-mssg")}
</p>
{emailList.length > 0 ? (
<div className="p-0 border-[1px] op-border-primary w-full rounded-md text-[15px] overflow-hidden">
<div className="p-0 border-[1.5px] op-border-primary rounded w-full text-[15px]">
<div className="flex flex-row flex-wrap">
{emailList.map((data, ind) => {
return (
<div
className="flex flex-row items-center op-bg-primary mx-[2px] mt-[2px] rounded-md py-[5px] px-[10px]"
className="flex flex-row items-center op-bg-primary m-[4px] rounded-md py-[5px] px-[10px]"
key={ind}
>
<span className="text-base-100 text-[13px]">
@@ -168,7 +233,7 @@ function EmailComponent({
<input
type="email"
value={emailValue}
className="p-[10px] rounded-md w-full text-[15px] bg-transparent outline-none"
className="p-[10px] pb-[20px] rounded w-full text-[15px] bg-transparent outline-none"
onChange={handleEmailValue}
onKeyDown={handleEnterPress}
onBlur={() => emailValue && handleEnterPress("add")}
@@ -185,7 +250,7 @@ function EmailComponent({
<input
type="email"
value={emailValue}
className="p-[10px] pb-[20px] text-base-content rounded-md w-full text-[15px] outline-none bg-transparent border-[1px] op-border-primary"
className="p-[10px] pb-[20px] rounded w-full text-[15px] outline-none bg-transparent border-[1.5px] op-border-primary"
onChange={handleEmailValue}
onKeyDown={handleEnterPress}
placeholder={t("enter-email-plaholder")}
@@ -203,22 +268,34 @@ function EmailComponent({
{t("email-error-1")}
</p>
)}
<div className="mt-2">
<button
type="button"
className="op-btn op-btn-secondary"
onClick={() => emailList.length > 0 && sendEmail()}
>
{t("send")}
</button>
<button
type="button"
className="op-btn op-btn-ghost text-base-content ml-2"
onClick={() => handleClose()}
>
{t("close")}
</button>
<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" />
<button
type="button"
className="op-btn op-btn-secondary"
onClick={() => emailList.length > 0 && sendEmail()}
>
{t("send")}
</button>
<button
type="button"
className="op-btn op-btn-ghost ml-2"
onClick={() => handleClose()}
>
{t("close")}
</button>
</div>
</ModalUi>
)}
@@ -0,0 +1,267 @@
import React, { useState } from "react";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { tomorrow } from "react-syntax-highlighter/dist/esm/styles/prism";
import { copytoData } from "../../constant/Utils";
import { useTranslation } from "react-i18next";
function EmbedTab(props) {
const { t } = useTranslation();
const tabName = [
{ title: "React/Next.js", icon: "fa-brands fa-react", color: "#61dafb" },
{ title: "JavaScript", icon: "fa-brands fa-js", color: "#ffd43b" },
{ title: "Angular", icon: "fa-brands fa-angular", color: "#ff5733" }
];
const [activeTab, setActiveTab] = useState(0);
// State to track if the code has been copied
const [isCopied, setIsCopied] = useState(false);
const reactCode = [
{
id: 0,
title: "Installation",
codeString: `
npm install @opensign/react`
},
{
id: 1,
title: "Usage",
codeString: `
import React from "react";
import Opensign from "@opensign/react";
export function App() {
return (
<div className="app">
<Opensign
onLoad={() => console.log("success")}
onLoadError={(error) => console.log(error)}
templateId= {"${props.templateId}"}
/>
</div>
);
}
`
}
];
const angularCode = [
{
id: 0,
title: "Installation",
codeString: `
npm install @opensign/angular`
},
{
id: 1,
title: "Usage",
codeString: `
import { Component } from '@angular/core';
import {OpensignComponent} from "@opensign/angular"
@Component({
selector:'app-root',
standalone: true,
imports: [OpensignComponent],
template: '<opensign templateId={"${props.templateId}"}
(onLoad)="handleLoad()"
(onLoadError)="handleError($event)"
></opensign>',
})
export class AppComponent{
handleLoad() {
console.log("success");
}
handleError(error: string) {
console.log(error);
}
}
`
}
];
const jsCodeString = `
<script
src= "https://app.opensignlabs.com/static/js/public-template.bundle.js"
id="opensign-script"
templateId=${props.templateId}
></script>
`;
const handleCopy = (code, ind) => {
copytoData(code);
setIsCopied({ ...isCopied, [ind]: true });
setTimeout(() => setIsCopied(false), 3000);
};
return (
<div className="mt-4 border-t-[1px]">
<div className="flex justify-center items-center mt-2">
<div role="tablist" className="op-tabs op-tabs-bordered">
{tabName.map((tabData, ind) => (
<div
onClick={() => setActiveTab(ind)}
key={ind}
role="tab"
className={`${
activeTab === ind ? "op-tab-active" : ""
} op-tab flex items-center`}
>
<i
className={`${tabData.icon}`}
style={{ color: tabData.color }}
></i>
<span className="ml-1 text-[10px] font-medium md:font-normal md:text-[15px]">
{tabData.title}
</span>
</div>
))}
</div>
</div>
<div>
{activeTab === 0 ? (
<div className="mt-4">
{reactCode.map((data, ind) => {
return (
<div key={ind}>
<p className="font-medium text-[18px]">
{t(`${data.title}`)}
</p>
<p className="text-[13px] mt-2">
{t("public-template-mssg-1")}
</p>
<div className="relative p-1">
<div
onClick={() => handleCopy(data.codeString, ind)}
className="absolute top-[20px] right-[20px] cursor-pointer"
>
<i className="fa-light fa-copy text-white mr-[2px]" />
<span className=" text-white">
{isCopied[ind] ? t("copied-code") : t("copy-code")}
</span>
</div>
<SyntaxHighlighter
customStyle={{
borderRadius: "15px"
}}
language="javascript"
style={tomorrow}
>
{data.codeString}
</SyntaxHighlighter>
</div>
</div>
);
})}
<p className="font-medium text-[15px]">
{t("public-template-mssg-3")}
</p>
<p className="my-[6px]">
{" "}
{t("public-template-mssg-4")}
<a
href="https://www.npmjs.com/package/@opensign/react"
target="_blank"
rel="noreferrer"
className="cursor-pointer text-blue-700 "
>
{" "}
OpenSign React package{" "}
</a>
{t("public-template-mssg-5")}
</p>
</div>
) : activeTab === 1 ? (
<div className="mt-4">
<div>
{/* <p className="font-medium text-[18px]">{t(`${data.title}`)}</p> */}
<div className="relative p-1">
<div
onClick={() => handleCopy(jsCodeString, 0)}
className="absolute top-[20px] right-[20px] cursor-pointer"
>
<i className="fa-light fa-copy text-white mr-[2px]" />
<span className=" text-white">
{isCopied[0] ? t("copied-code") : t("copy-code")}
</span>
</div>
<SyntaxHighlighter
customStyle={{
borderRadius: "15px"
}}
language="javascript"
style={tomorrow}
>
{jsCodeString}
</SyntaxHighlighter>
</div>
</div>
</div>
) : (
activeTab === 2 && (
<div className="mt-4">
{angularCode.map((data, ind) => {
return (
<div key={ind}>
<p className="font-medium text-[18px]">
{t(`${data.title}`)}
</p>
<p className="text-[13px] mt-2">
{t("angular-npm-mssg-1")}
</p>
<div className="relative p-1">
<div
onClick={() => handleCopy(data.codeString, ind)}
className="absolute top-[20px] right-[20px] cursor-pointer"
>
<i className="fa-light fa-copy text-white mr-[2px]" />
<span className=" text-white">
{isCopied[ind] ? t("copied-code") : t("copy-code")}
</span>
</div>
<SyntaxHighlighter
customStyle={{
borderRadius: "15px"
}}
language="javascript"
style={tomorrow}
>
{data.codeString}
</SyntaxHighlighter>
</div>
</div>
);
})}
<p className="font-medium text-[15px]">
{t("public-template-mssg-3")}
</p>
<p className="my-[6px]">
{" "}
{t("public-template-mssg-4")}
<a
href="https://www.npmjs.com/package/@opensign/angular"
target="_blank"
rel="noreferrer"
className="cursor-pointer text-blue-700 "
>
{" "}
OpenSign Angular package{" "}
</a>
{t("public-template-mssg-5")}
</p>
</div>
)
)}
</div>
</div>
);
}
export default EmbedTab;
@@ -1,30 +0,0 @@
const Guidelines = ({ x1, x2, y1, y2 }) => {
return (
<>
{/* Horizontal guidelines */}
{/* top guide */}{" "}
<div
className="absolute pointer-events-none z-[1000] left-0 w-full border-t-[1px] border-dashed border-[#3b82f6]"
style={{ top: y1 }}
/>
{/* bottom guide */}
<div
className="absolute pointer-events-none z-[1000] left-0 w-full border-t-[1px] border-dashed border-[#3b82f6]"
style={{ top: y2 }}
/>
{/* Vertical guidelines */}
{/* left guide */}
<div
className="absolute pointer-events-none z-[1000] top-0 h-full border-l-[1px] border-dashed border-[#3b82f6]"
style={{ left: x1 }}
/>
{/* right guide */}
<div
className="absolute pointer-events-none z-[1000] top-0 h-full border-l-[1px] border-dashed border-[#3b82f6]"
style={{ left: x2 }}
/>
</>
);
};
export default Guidelines;
@@ -1,94 +0,0 @@
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 text-base-content"
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 text-base-content"
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 text-base-content ml-1"
>
{t("close")}
</button>
</div>
</ModalUi>
);
}
@@ -0,0 +1,560 @@
import React, { useState } from "react";
import PrevNext from "./PrevNext";
import {
handleDownloadCertificate,
handleDownloadPdf,
handleToPrint
} from "../../constant/Utils";
import "../../styles/signature.css";
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
import ModalUi from "../../primitives/ModalUi";
import Loader from "../../primitives/Loader";
import { useTranslation } from "react-i18next";
function Header({
isPdfRequestFiles,
isPlaceholder,
setIsDecline,
pageNumber,
allPages,
changePage,
pdfUrl,
embedWidgetsData,
pdfDetails,
signerPos,
signersdata,
isMailSend,
alertSendEmail,
isCompleted,
isShowHeader,
decline,
currentSigner,
alreadySign,
isSignYourself,
setIsEmail,
completeBtnTitle,
setIsEditTemplate,
isPublicTemplate,
clickOnZoomIn,
clickOnZoomOut,
handleRotationFun,
isDisableRotate,
templateId,
setIsDownloadModal
}) {
const { t } = useTranslation();
const filterPrefill =
signerPos && signerPos?.filter((data) => data.Role !== "prefill");
const isMobile = window.innerWidth < 767;
const [isDownloading, setIsDownloading] = useState("");
const isGuestSigner = localStorage.getItem("isGuestSigner");
//function for show decline alert
const handleDeclinePdfAlert = async () => {
const currentDecline = { currnt: "Sure", isDeclined: true };
setIsDecline(currentDecline);
};
return (
<div className="flex py-[5px]">
{isMobile && isShowHeader ? (
<div
id="navbar"
className={isGuestSigner ? "stickySignerHead" : "stickyHead"}
style={{
width: isGuestSigner
? window.innerWidth
: window.innerWidth - 30 + "px"
}}
>
<div className="flex justify-between items-center py-[5px] px-[10px] ">
<div onClick={() => window.history.go(-2)}>
<i
className="fa-light fa-arrow-left text-base-content"
aria-hidden="true"
></i>
</div>
<PrevNext
pageNumber={pageNumber}
allPages={allPages}
changePage={changePage}
/>
{isCompleted || alreadySign ? (
<DropdownMenu.Root>
<DropdownMenu.Trigger asChild>
<div className="op-link op-link-primary no-underline text-[16px] font-semibold pr-[3px] pl-[5px]">
<i
className="fa-light fa-ellipsis-v"
aria-hidden="true"
></i>
</div>
</DropdownMenu.Trigger>
<DropdownMenu.Portal>
<DropdownMenu.Content
className="DropdownMenuContent"
sideOffset={5}
>
<DropdownMenu.Item
className="DropdownMenuItem"
onClick={() => {
if (isCompleted) {
setIsDownloadModal(true);
} else {
handleDownloadPdf(pdfDetails, setIsDownloading);
}
}}
>
<div className="flex flex-row">
<i
className="fa-light fa-arrow-down mr-[3px]"
aria-hidden="true"
></i>
{t("download")}
</div>
</DropdownMenu.Item>
{isCompleted && (
<DropdownMenu.Item
className="DropdownMenuItem"
onClick={() =>
handleDownloadCertificate(
pdfDetails,
setIsDownloading
)
}
>
<div className="border-none bg-[#fff]">
<i
className="fa-light fa-award mr-[3px]"
aria-hidden="true"
></i>
{t("certificate")}
</div>
</DropdownMenu.Item>
)}
{isSignYourself && (
<DropdownMenu.Item
className="DropdownMenuItem"
onClick={() => setIsEmail(true)}
>
<div className="flex flex-row">
<i
className="fa-light fa-envelope mr-[3px]"
aria-hidden="true"
></i>
{t("mail")}
</div>
</DropdownMenu.Item>
)}
<DropdownMenu.Item
className="DropdownMenuItem"
onClick={(e) =>
handleToPrint(e, pdfUrl, setIsDownloading, pdfDetails)
}
>
<div className="flex flex-row">
<i
className="fa-light fa-print mr-[3px]"
aria-hidden="true"
></i>
{t("print")}
</div>
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu.Root>
) : (
<div className="flex justify-around items-center">
{/* current signer is checking user send request and check status of pdf sign than if current
user exist than show finish button else no
*/}
{currentSigner && (
<div className="flex items-center" data-tut="reactourFifth">
{decline && (
<div
onClick={() => handleDeclinePdfAlert()}
className="text-[red] border-none font-[650] text-[14px] mr-2"
>
{t("decline")}
</div>
)}
{isPlaceholder ? (
<div
onClick={() => {
if (!isMailSend) {
alertSendEmail();
}
}}
className={`${
isMailSend ? "" : "op-link-primary"
} op-link no-underline font-[650] text-[14px]`}
data-tut="headerArea"
>
{completeBtnTitle ? completeBtnTitle : t("send")}
</div>
) : (
<div
data-tut="reactourThird"
onClick={() => embedWidgetsData()}
className="border-none font-[650] text-[14px] op-link op-link-primary no-underline"
>
{t("finish")}
</div>
)}
<DropdownMenu.Root>
<DropdownMenu.Trigger asChild>
<div className="font-[650] text-[18px] px-2 ml-[4px] text-base-content no-underline">
<i
className="fa-light fa-ellipsis-v"
aria-hidden="true"
></i>
</div>
</DropdownMenu.Trigger>
<DropdownMenu.Portal>
<DropdownMenu.Content
className="bg-white shadow-md rounded-md px-3 py-2"
sideOffset={5}
>
<DropdownMenu.Item
className="DropdownMenuItem"
onClick={() =>
handleDownloadPdf(pdfDetails, setIsDownloading)
}
>
<div className="flex flex-row">
<i
className="fa-light fa-arrow-down mr-[3px]"
aria-hidden="true"
></i>
<span className="font-[500]">
{t("download")}
</span>
</div>
</DropdownMenu.Item>
{!isDisableRotate && (
<>
<DropdownMenu.Item
className="DropdownMenuItem"
onClick={() => handleRotationFun(90)}
>
<div className="flex flex-row">
<i className="fa-light fa-rotate-right text-gray-500 2xl:text-[30px] mr-[3px]"></i>
<span className="font-[500]">
{t("rotate-right")}
</span>
</div>
</DropdownMenu.Item>
<DropdownMenu.Item
className="DropdownMenuItem"
onClick={() => handleRotationFun(-90)}
>
<div className="flex flex-row">
<i className="fa-light fa-rotate-left text-gray-500 2xl:text-[30px] mr-[3px]"></i>
<span className="font-[500]">
{t("rotate-left")}
</span>
</div>
</DropdownMenu.Item>
</>
)}
<DropdownMenu.Item
className="DropdownMenuItem"
onClick={() => clickOnZoomIn()}
>
<div className="flex flex-row">
<i className="fa-light fa-magnifying-glass-plus text-gray-500 2xl:text-[30px] mr-[3px]"></i>
<span className="font-[500]">{t("zoom-in")}</span>
</div>
</DropdownMenu.Item>
<DropdownMenu.Item
className="DropdownMenuItem"
onClick={() => clickOnZoomOut()}
>
<div className="flex flex-row">
<i className="fa-light fa-magnifying-glass-minus text-gray-500 2xl:text-[30px] mr-[3px]"></i>
<span className="font-[500]">
{t("zoom-out")}
</span>
</div>
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu.Root>
</div>
)}
{isPublicTemplate && (
<div
data-tut="reactourThird"
onClick={() => embedWidgetsData()}
className="border-none font-[650] text-[14px] op-link op-link-primary no-underline"
>
{t("sign-now")}
</div>
)}
</div>
)}
</div>
</div>
) : (
<div className="flex flex-wrap justify-between items-center w-full gap-y-1 ml-1">
<PrevNext
pageNumber={pageNumber}
allPages={allPages}
changePage={changePage}
/>
{isPlaceholder ? (
<>
<div className="flex mx-[100px] lg:mx-0 order-last lg:order-none">
{!isMailSend &&
signersdata.length > 0 &&
signersdata.length !== filterPrefill.length && (
<div>
{filterPrefill.length === 0 ? (
<span className="text-[13px] text-[#f5405e]">
{t("add")} {signersdata.length - filterPrefill.length}{" "}
{t("recipients")} {t("widgets-name.signature")}
</span>
) : (
<span className="text-[13px] text-[#f5405e]">
{t("add")} {signersdata.length - filterPrefill.length}{" "}
{t("more")}
{t("recipients")} {t("widgets-name.signature")}
</span>
)}
</div>
)}
</div>
<div className="flex">
{setIsEditTemplate && (
<button
onClick={() => setIsEditTemplate(true)}
className="outline-none border-none text-center mr-[3px]"
>
<i className="fa-light fa-gear fa-lg"></i>
</button>
)}
<button
onClick={() => window.history.go(-2)}
type="button"
className="op-btn op-btn-ghost op-btn-sm mr-[3px]"
>
{t("back")}
</button>
<button
disabled={isMailSend && true}
data-tut="headerArea"
className="op-btn op-btn-primary op-btn-sm mr-[3px]"
onClick={() => alertSendEmail()}
>
{completeBtnTitle
? completeBtnTitle
: isMailSend
? t("sent")
: t("send")}
</button>
</div>
</>
) : isPdfRequestFiles ? (
alreadySign ? (
<div className="flex flex-row">
<button
onClick={(e) =>
handleToPrint(e, pdfUrl, setIsDownloading, pdfDetails)
}
type="button"
className="op-btn op-btn-neutral op-btn-sm mr-[3px] shadow"
>
<i
className="fa-light fa-print py-[3px]"
aria-hidden="true"
></i>
<span className="hidden lg:block">{t("print")}</span>
</button>
{isCompleted && (
<button
type="button"
onClick={() =>
handleDownloadCertificate(pdfDetails, setIsDownloading)
}
className="op-btn op-btn-secondary op-btn-sm mr-[3px] shadow"
>
<i
className="fa-light fa-award py-[3px]"
aria-hidden="true"
></i>
<span className="hidden lg:block">{t("certificate")}</span>
</button>
)}
<button
type="button"
className="op-btn op-btn-primary op-btn-sm mr-[3px] shadow"
onClick={() => {
if (isCompleted) {
setIsDownloadModal(true);
} else {
handleDownloadPdf(pdfDetails, setIsDownloading);
}
}}
>
<i
className="fa-light fa-download py-[3px]"
aria-hidden="true"
></i>
<span className="hidden lg:block">{t("download")}</span>
</button>
</div>
) : (
<div className="flex" data-tut="reactourFifth">
{!templateId && (
<button
onClick={() => window.history.go(-2)}
type="button"
className="op-btn op-btn-ghost op-btn-sm mr-[3px]"
>
{t("back")}
</button>
)}
{currentSigner && (
<>
{templateId && (
<button
onClick={() =>
handleDownloadPdf(
pdfDetails,
pdfUrl,
setIsDownloading
)
}
type="button"
className="op-btn op-btn-ghost op-btn-sm mr-[3px]"
>
<span className="hidden lg:block">{t("download")}</span>
</button>
)}
<button
className="op-btn op-btn-secondary op-btn-sm mr-[3px] shadow"
onClick={() => handleDeclinePdfAlert()}
>
{t("decline")}
</button>
<button
type="button"
className="op-btn op-btn-primary op-btn-sm mr-[3px] shadow"
onClick={() => embedWidgetsData()}
>
{t("finish")}
</button>
{!templateId && (
<button
type="button"
className="op-btn op-btn-neutral op-btn-sm mr-[3px] shadow"
onClick={() =>
handleDownloadPdf(pdfDetails, setIsDownloading)
}
>
<i className="fa-light fa-arrow-down font-semibold lg:hidden"></i>
<span className="hidden lg:block">{t("download")}</span>
</button>
)}
</>
)}
</div>
)
) : isCompleted ? (
<div className="flex flex-row">
{isCompleted && (
<button
type="button"
onClick={() =>
handleDownloadCertificate(pdfDetails, setIsDownloading)
}
className="op-btn op-btn-secondary op-btn-sm gap-0 font-medium text-[12px] mr-[3px] shadow"
>
<i className="fa-light fa-award" aria-hidden="true"></i>
<span className="hidden lg:block ml-1">
{t("certificate")}
</span>
</button>
)}
<button
onClick={(e) =>
handleToPrint(e, pdfUrl, setIsDownloading, pdfDetails)
}
type="button"
className="op-btn op-btn-neutral op-btn-sm gap-0 font-medium text-[12px] mr-[3px] shadow"
>
<i className="fa-light fa-print" aria-hidden="true"></i>
<span className="hidden lg:block ml-1">{t("print")}</span>
</button>
<button
type="button"
className="op-btn op-btn-primary op-btn-sm gap-0 font-medium text-[12px] mr-[3px] shadow"
onClick={() => {
setIsDownloadModal(true);
}}
>
<i className="fa-light fa-download" aria-hidden="true"></i>
<span className="hidden lg:block ml-1">{t("download")}</span>
</button>
<button
type="button"
className="op-btn op-btn-info op-btn-sm gap-0 font-medium text-[12px] mr-[3px] shadow"
onClick={() => setIsEmail(true)}
>
<i className="fa-light fa-envelope" aria-hidden="true"></i>
<span className="hidden lg:block ml-1">{t("mail")}</span>
</button>
</div>
) : isPublicTemplate ? (
<div className="flex">
<button
type="button"
className="op-btn op-btn-primary op-btn-sm shadow"
onClick={() => embedWidgetsData()}
>
{t("sign-now")}
</button>
</div>
) : (
<div className="flex">
<button
onClick={() => window.history.go(-2)}
type="button"
className="op-btn op-btn-ghost op-btn-sm mr-[3px]"
>
{t("back")}
</button>
<button
type="button"
className="op-btn op-btn-primary op-btn-sm mr-[3px]"
onClick={() => embedWidgetsData()}
>
{t("finish")}
</button>
</div>
)}
</div>
)}
{isDownloading === "pdf" && (
<div className="fixed z-[200] inset-0 flex justify-center items-center bg-black bg-opacity-30">
<Loader />
</div>
)}
<ModalUi
isOpen={isDownloading === "certificate"}
title={
isDownloading === "certificate"
? t("generating-certificate")
: t("pdf-download")
}
handleClose={() => setIsDownloading("")}
>
<div className="p-3 md:p-5 text-[13px] md:text-base text-center text-base-content">
{isDownloading === "certificate"}{" "}
<p>{t("generate-certificate-alert")}</p>
</div>
</ModalUi>
</div>
);
}
export default Header;
@@ -1,749 +0,0 @@
import React, { useRef, useState } from "react";
import PrevNext from "./PrevNext";
import {
base64ToArrayBuffer,
decryptPdf,
deletePdfPage,
flattenPdf,
getFileAsArrayBuffer,
handleDownloadCertificate,
handleDownloadPdf,
handleRemoveWidgets,
handleToPrint,
reorderPdfPages
} from "../../constant/Utils";
import "../../styles/signature.css";
import { DropdownMenu } from "radix-ui";
import ModalUi from "../../primitives/ModalUi";
import Loader from "../../primitives/Loader";
import PageReorderModal from "./PageReorderModal";
import { useTranslation } from "react-i18next";
import { PDFDocument } from "pdf-lib";
import { maxFileSize } from "../../constant/const";
function Header(props) {
const { t } = useTranslation();
const isMobile = window.innerWidth < 767;
const [isDownloading, setIsDownloading] = useState("");
const [isDeletePage, setIsDeletePage] = useState(false);
const [isReorderModal, setIsReorderModal] = useState(false);
const mergePdfInputRef = useRef(null);
const enabledBackBtn = props?.disabledBackBtn === true ? false : true;
//function for show decline alert
const handleDeclinePdfAlert = async () => {
if (props?.handleDecline) {
props.handleDecline();
} else {
const currentDecline = { currnt: "Sure", isDeclined: true };
props?.setIsDecline(currentDecline);
}
};
const handleDetelePage = async () => {
props?.setIsUploadPdf && props?.setIsUploadPdf(true);
const pdfupdatedData = await deletePdfPage(
props?.pdfArrayBuffer,
props?.pageNumber
);
if (pdfupdatedData?.totalPages === 1) {
alert(t("delete-alert"));
} else {
props?.setPdfBase64Url(pdfupdatedData.base64);
props?.setPdfArrayBuffer(pdfupdatedData.arrayBuffer);
setIsDeletePage(false);
handleRemoveWidgets(
props?.setSignerPos,
props?.signerPos,
props?.pageNumber
);
}
};
// `removeFile` is used to remove file if exists
const removeFile = (e) => {
if (e) {
e.target.value = "";
}
};
const handleFileUpload = async (e) => {
const file = e.target.files[0];
if (!file) {
alert(t("please-select-pdf"));
return;
}
if (!file.type.includes("pdf")) {
alert(t("only-pdf-allowed"));
return;
}
const fileSize =
maxFileSize;
const pdfsize = file?.size;
const fileSizeBytes = fileSize * 1024 * 1024;
if (pdfsize > fileSizeBytes) {
alert(`${t("file-alert-1")} ${fileSize} MB`);
removeFile(e);
return;
}
try {
let uploadedPdfBytes = await file.arrayBuffer();
try {
uploadedPdfBytes = await flattenPdf(uploadedPdfBytes);
} catch (err) {
if (err?.message?.includes("is encrypted")) {
try {
const pdfFile = await decryptPdf(file, "");
const pdfArrayBuffer = await getFileAsArrayBuffer(pdfFile);
uploadedPdfBytes = await flattenPdf(pdfArrayBuffer);
} catch (err) {
if (err?.response?.status === 401) {
const password = prompt(
`PDF "${file.name}" is password-protected. Enter password:`
);
if (password) {
try {
const pdfFile = await decryptPdf(file, password);
const pdfArrayBuffer = await getFileAsArrayBuffer(pdfFile);
uploadedPdfBytes = await flattenPdf(pdfArrayBuffer);
// Upload the file to Parse Server
} catch (err) {
console.error("Incorrect password or decryption failed", err);
alert(t("incorrect-password-or-decryption-failed"));
}
} else {
alert(t("provide-password"));
}
} else {
console.log("Err ", err);
alert(t("error-uploading-pdf"));
}
}
} else {
alert(t("error-uploading-pdf"));
}
}
const uploadedPdfDoc = await PDFDocument.load(uploadedPdfBytes, {
ignoreEncryption: true
});
const basePdfDoc = await PDFDocument.load(props.pdfArrayBuffer);
// Copy pages from the uploaded PDF to the base PDF
const uploadedPdfPages = await basePdfDoc.copyPages(
uploadedPdfDoc,
uploadedPdfDoc.getPageIndices()
);
uploadedPdfPages.forEach((page) => basePdfDoc.addPage(page));
// Save the updated PDF
const pdfBase64 = await basePdfDoc.saveAsBase64({
useObjectStreams: false
});
const pdfBuffer = base64ToArrayBuffer(pdfBase64);
const pdfsize = pdfBuffer?.byteLength;
const fileSizeBytes = fileSize * 1024 * 1024;
if (pdfsize > fileSizeBytes) {
alert(`${t("file-alert-1")} ${fileSize} MB`);
removeFile(e);
return;
}
props.setPdfArrayBuffer(pdfBuffer);
props.setPdfBase64Url(pdfBase64);
props.setIsUploadPdf && props.setIsUploadPdf(true);
mergePdfInputRef.current.value = "";
} catch (error) {
mergePdfInputRef.current.value = "";
console.error("Error merging PDF:", error);
}
};
const handleReorderSave = async (order) => {
try {
const pdfupdatedData = await reorderPdfPages(props.pdfArrayBuffer, order);
if (pdfupdatedData) {
props.setPdfArrayBuffer(pdfupdatedData.arrayBuffer);
props.setPdfBase64Url(pdfupdatedData.base64);
props.setAllPages(pdfupdatedData.totalPages);
props.setPageNumber(1);
}
} catch (e) {
console.log("error in reorder pdf pages", e);
}
setIsReorderModal(false);
};
const handleDownloadDoc = async () => {
await handleDownloadPdf(
props?.pdfDetails,
setIsDownloading,
props.pdfBase64
);
};
const handleDownloadBtn = async () => {
if (
props?.isCompleted
) {
props?.setIsDownloadModal(true);
} else {
await handleDownloadDoc();
}
};
return (
<div className="flex py-[5px]">
{isMobile && props?.isShowHeader ? (
<div
id="navbar"
className="stickyHead touch-none"
style={{
width: window.innerWidth + "px"
}}
>
<div className="flex justify-between items-center py-[5px] pl-[10px] ">
<div onClick={() => window.history.go(-2)}>
<i
className="fa-light fa-arrow-left text-base-content"
aria-hidden="true"
></i>
</div>
<PrevNext
pageNumber={props?.pageNumber}
allPages={props?.allPages}
changePage={props?.changePage}
/>
{props?.isCompleted || props?.alreadySign ? (
<DropdownMenu.Root>
<DropdownMenu.Trigger asChild>
<div className="op-link op-link-primary no-underline text-[16px] font-semibold px-3">
<i
className="fa-light fa-ellipsis-v"
aria-hidden="true"
></i>
</div>
</DropdownMenu.Trigger>
<DropdownMenu.Portal>
<DropdownMenu.Content
className="DropdownMenuContent"
sideOffset={5}
>
<DropdownMenu.Item
className="DropdownMenuItem"
onClick={() => handleDownloadBtn()}
>
<div className="flex flex-row">
<i
className="fa-light fa-arrow-down mr-[3px]"
aria-hidden="true"
></i>
{t("download")}
</div>
</DropdownMenu.Item>
{
props?.isCompleted && (
<DropdownMenu.Item
className="DropdownMenuItem"
onClick={() =>
handleDownloadCertificate(
props?.pdfDetails,
setIsDownloading
)
}
>
<div className="border-none bg-[#fff]">
<i
className="fa-light fa-award mr-[3px]"
aria-hidden="true"
></i>
{t("certificate")}
</div>
</DropdownMenu.Item>
)
}
{props?.isSignYourself && (
<DropdownMenu.Item
className="DropdownMenuItem"
onClick={() => props?.setIsEmail(true)}
>
<div className="flex flex-row">
<i
className="fa-light fa-envelope mr-[3px]"
aria-hidden="true"
></i>
{t("mail")}
</div>
</DropdownMenu.Item>
)}
<DropdownMenu.Item
className="DropdownMenuItem"
onClick={(e) =>
handleToPrint(e, setIsDownloading, props?.pdfDetails)
}
>
<div className="flex flex-row">
<i
className="fa-light fa-print mr-[3px]"
aria-hidden="true"
></i>
{t("print")}
</div>
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu.Root>
) : (
<div className="flex justify-around items-center">
{/* current signer is checking user send request and check status of pdf sign than if current
user exist than show finish button else no
*/}
{props?.currentSigner && (
<div className="flex items-center" data-tut="reactourFifth">
{props?.decline && (
<div
onClick={() => handleDeclinePdfAlert()}
className="text-[red] border-none font-[650] text-[14px] mr-2"
>
{t("decline")}
</div>
)}
{props?.isPlaceholder ? (
<div
onClick={() => {
if (!props?.isMailSend) {
props?.handleSaveDoc();
}
}}
className={`${
props?.isMailSend ? "" : "op-link-primary"
} op-link no-underline font-[650] text-[14px]`}
data-tut="headerArea"
>
{props?.completeBtnTitle
? props?.completeBtnTitle
: t("send")}
</div>
) : (
<div
data-tut="reactourThird"
onClick={() => props?.embedWidgetsData()}
className="border-none font-[650] text-[14px] op-link op-link-primary no-underline"
>
{t("finish")}
</div>
)}
<input
type="file"
className="hidden"
accept="application/pdf"
ref={mergePdfInputRef}
onChange={handleFileUpload}
/>
<DropdownMenu.Root>
<DropdownMenu.Trigger asChild>
<div className="font-[650] text-[18px] px-3 text-base-content no-underline">
<i
className="fa-light fa-ellipsis-v"
aria-hidden="true"
></i>
</div>
</DropdownMenu.Trigger>
<DropdownMenu.Portal>
<DropdownMenu.Content
className="bg-white shadow-md rounded-md px-3 py-2"
sideOffset={5}
>
{props?.setIsEditTemplate && (
<DropdownMenu.Item
className="DropdownMenuItem"
onClick={() => props?.setIsEditTemplate(true)}
>
<div className="flex flex-row">
<i
className="fa-light fa-gear mr-[3px]"
aria-hidden="true"
></i>
<span className="font-[500]">{t("Edit")}</span>
</div>
</DropdownMenu.Item>
)}
<DropdownMenu.Item
className="DropdownMenuItem"
onClick={() => handleDownloadDoc()}
>
<div className="flex flex-row">
<i
className="fa-light fa-arrow-down mr-[3px]"
aria-hidden="true"
></i>
<span className="font-[500]">
{t("download")}
</span>
</div>
</DropdownMenu.Item>
{!props?.isDisablePdfEditTools && (
<>
<DropdownMenu.Item
className="DropdownMenuItem"
onClick={() => mergePdfInputRef.current.click()}
>
<div className="flex flex-row">
<i className="fa-light fa-plus text-gray-500 2xl:text-[30px] mr-[3px]"></i>
<span className="font-[500]">
{t("add-pages")}
</span>
</div>
</DropdownMenu.Item>
<DropdownMenu.Item
className="DropdownMenuItem"
onClick={() => setIsDeletePage(true)}
>
<div className="flex flex-row">
<i className="fa-light fa-trash text-gray-500 2xl:text-[30px] mr-[3px]"></i>
<span className="font-[500]">
{t("delete-page")}
</span>
</div>
</DropdownMenu.Item>
<DropdownMenu.Item
className="DropdownMenuItem"
onClick={() => setIsReorderModal(true)}
>
<div className="flex flex-row">
<i className="fa-light fa-list-ol text-gray-500 2xl:text-[30px] mr-[3px]"></i>
<span className="font-[500]">
{t("reorder-pages")}
</span>
</div>
</DropdownMenu.Item>
<DropdownMenu.Item
className="DropdownMenuItem"
onClick={() => props?.handleRotationFun(90)}
>
<div className="flex flex-row">
<i className="fa-light fa-rotate-right text-gray-500 2xl:text-[30px] mr-[3px]"></i>
<span className="font-[500]">
{t("rotate-right")}
</span>
</div>
</DropdownMenu.Item>
<DropdownMenu.Item
className="DropdownMenuItem"
onClick={() => props?.handleRotationFun(-90)}
>
<div className="flex flex-row">
<i className="fa-light fa-rotate-left text-gray-500 2xl:text-[30px] mr-[3px]"></i>
<span className="font-[500]">
{t("rotate-left")}
</span>
</div>
</DropdownMenu.Item>
</>
)}
<DropdownMenu.Item
className="DropdownMenuItem"
onClick={() => props?.clickOnZoomIn()}
>
<div className="flex flex-row">
<i className="fa-light fa-magnifying-glass-plus text-gray-500 2xl:text-[30px] mr-[3px]"></i>
<span className="font-[500]">{t("zoom-in")}</span>
</div>
</DropdownMenu.Item>
<DropdownMenu.Item
className="DropdownMenuItem"
onClick={() => props?.clickOnZoomOut()}
>
<div className="flex flex-row">
<i className="fa-light fa-magnifying-glass-minus text-gray-500 2xl:text-[30px] mr-[3px]"></i>
<span className="font-[500]">
{t("zoom-out")}
</span>
</div>
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu.Root>
</div>
)}
{props?.isPublicTemplate && (
<div
data-tut="reactourThird"
onClick={() => props?.embedWidgetsData()}
className="border-none font-[650] text-[14px] pr-2 op-link op-link-primary no-underline"
>
{t("sign-now")}
</div>
)}
</div>
)}
</div>
</div>
) : (
<div className="flex flex-wrap justify-between items-center w-full gap-y-1 ml-1">
<PrevNext
pageNumber={props?.pageNumber}
allPages={props?.allPages}
changePage={props?.changePage}
/>
{props?.isPlaceholder ? (
<>
<div className="flex mx-[100px] lg:mx-0 order-last lg:order-none"></div>
<div className="flex">
{props?.setIsEditTemplate && (
<button
onClick={() => props?.setIsEditTemplate(true)}
className="outline-none border-none text-center mr-[3px]"
>
<i className="fa-light fa-gear fa-lg text-base-content"></i>
</button>
)}
{enabledBackBtn && (
<button
onClick={() => window.history.go(-2)}
type="button"
className="op-btn op-btn-ghost text-base-content op-btn-sm mr-[3px]"
>
{t("back")}
</button>
)}
<button
disabled={props?.isMailSend && true}
data-tut="headerArea"
className="op-btn op-btn-primary op-btn-sm mr-[3px]"
onClick={() => props?.handleSaveDoc()}
>
{props?.completeBtnTitle
? props?.completeBtnTitle
: props?.isMailSend
? t("sent")
: t("send")}
</button>
</div>
</>
) : props?.isPdfRequestFiles || props?.isSelfSign ? (
props?.alreadySign || (props?.isSelfSign && props?.isCompleted) ? (
<div className="flex flex-row">
<button
onClick={(e) =>
handleToPrint(e, setIsDownloading, props?.pdfDetails)
}
type="button"
className="op-btn op-btn-neutral op-btn-sm mr-[3px] shadow"
>
<i
className="fa-light fa-print py-[3px]"
aria-hidden="true"
></i>
<span className="hidden lg:block">{t("print")}</span>
</button>
{
props?.isCompleted && (
<button
type="button"
onClick={() =>
handleDownloadCertificate(
props?.pdfDetails,
setIsDownloading
)
}
className="op-btn op-btn-secondary op-btn-sm mr-[3px] shadow"
>
<i
className="fa-light fa-award py-[3px]"
aria-hidden="true"
></i>
<span className="hidden lg:block">
{t("certificate")}
</span>
</button>
)
}
<button
type="button"
className="op-btn op-btn-primary op-btn-sm mr-[3px] shadow"
onClick={() => handleDownloadBtn()}
>
<i
className="fa-light fa-download py-[3px]"
aria-hidden="true"
></i>
<span className="hidden lg:block">{t("download")}</span>
</button>
</div>
) : (
<div className="flex" data-tut="reactourFifth">
{props?.currentSigner && (
<>
{props?.templateId && (
<button
onClick={() => handleDownloadDoc()}
type="button"
className="op-btn op-btn-ghost text-base-content op-btn-sm mr-[3px]"
>
<span className="hidden lg:block">{t("download")}</span>
</button>
)}
{!props?.isSelfSign && (
<button
className="op-btn op-btn-secondary op-btn-sm mr-[3px] shadow"
onClick={() => handleDeclinePdfAlert()}
>
{t("decline")}
</button>
)}
{!props?.templateId && (
<button
type="button"
className="op-btn op-btn-ghost text-base-content op-btn-sm mr-[3px]"
onClick={() => handleDownloadDoc()}
>
<i className="fa-light fa-arrow-down font-semibold lg:hidden"></i>
<span className="hidden lg:block">{t("download")}</span>
</button>
)}
<button
type="button"
className="op-btn op-btn-primary op-btn-sm mr-[3px] shadow"
onClick={() => props?.embedWidgetsData()}
>
{t("finish")}
</button>
</>
)}
</div>
)
) : props?.isCompleted ? (
<div className="flex flex-row">
{
props?.isCompleted && (
<button
type="button"
onClick={() =>
handleDownloadCertificate(
props?.pdfDetails,
setIsDownloading
)
}
className="op-btn op-btn-secondary op-btn-sm gap-0 font-medium text-[12px] mr-[3px] shadow"
>
<i className="fa-light fa-award" aria-hidden="true"></i>
<span className="hidden lg:block ml-1">
{t("certificate")}
</span>
</button>
)
}
<button
onClick={(e) =>
handleToPrint(e, setIsDownloading, props?.pdfDetails)
}
type="button"
className="op-btn op-btn-neutral op-btn-sm gap-0 font-medium text-[12px] mr-[3px] shadow"
>
<i className="fa-light fa-print" aria-hidden="true"></i>
<span className="hidden lg:block ml-1">{t("print")}</span>
</button>
<button
type="button"
className="op-btn op-btn-primary op-btn-sm gap-0 font-medium text-[12px] mr-[3px] shadow"
// onClick={() => props?.setIsDownloadModal(true)}
onClick={() => handleDownloadBtn()}
>
<i className="fa-light fa-download" aria-hidden="true"></i>
<span className="hidden lg:block ml-1">{t("download")}</span>
</button>
<button
type="button"
className="op-btn op-btn-info op-btn-sm gap-0 font-medium text-[12px] mr-[3px] shadow"
onClick={() => props?.setIsEmail(true)}
>
<i className="fa-light fa-envelope" aria-hidden="true"></i>
<span className="hidden lg:block ml-1">{t("mail")}</span>
</button>
</div>
) : props?.isPublicTemplate ? (
<div className="flex">
<button
type="button"
className="op-btn op-btn-primary op-btn-sm shadow"
onClick={() => props?.embedWidgetsData()}
>
{t("sign-now")}
</button>
</div>
) : (
<div className="flex">
<button
onClick={() => window.history.go(-2)}
type="button"
className="op-btn op-btn-ghost text-base-content op-btn-sm mr-[3px]"
>
{t("back")}
</button>
<button
type="button"
className="op-btn op-btn-primary op-btn-sm mr-[3px]"
onClick={() => props?.embedWidgetsData()}
>
{t("finish")}
</button>
</div>
)}
</div>
)}
{isDownloading === "pdf" && (
<div className="fixed z-[200] inset-0 flex justify-center items-center bg-black bg-opacity-30">
<Loader />
</div>
)}
<ModalUi
isOpen={
isDownloading === "certificate" || isDownloading === "certificate_err"
}
title={
isDownloading === "certificate" || isDownloading === "certificate_err"
? t("generating-certificate")
: t("pdf-download")
}
handleClose={() => setIsDownloading("")}
>
<div className="p-3 md:p-5 text-[13px] md:text-base text-center text-base-content">
{isDownloading === "certificate" ? (
<p>{t("generate-certificate-alert")}</p>
) : (
<p>{t("generate-certificate-err")}</p>
)}
</div>
</ModalUi>
<ModalUi
isOpen={isDeletePage}
title={t("delete-page")}
handleClose={() => setIsDeletePage(false)}
>
<div className="h-[100%] p-[20px]">
<p className="font-medium text-base-content">{t("delete-alert-2")}</p>
<p className="pt-3 text-base-content">{t("delete-note")}</p>
<div className="h-[1px] bg-[#9f9f9f] w-full my-[15px]"></div>
<button
onClick={() => handleDetelePage()}
type="button"
className="op-btn op-btn-primary"
>
{t("yes")}
</button>
<button
onClick={() => setIsDeletePage(false)}
type="button"
className="op-btn op-btn-ghost text-base-content"
>
{t("no")}
</button>
</div>
</ModalUi>
<PageReorderModal
isOpen={isReorderModal}
handleClose={() => setIsReorderModal(false)}
totalPages={props.allPages}
onSave={handleReorderSave}
/>
</div>
);
}
export default Header;
@@ -1,298 +0,0 @@
import { useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import {
base64ToArrayBuffer,
decryptPdf,
deletePdfPage,
flattenPdf,
getFileAsArrayBuffer,
handleRemoveWidgets,
reorderPdfPages
} from "../../constant/Utils";
import ModalUi from "../../primitives/ModalUi";
import { PDFDocument } from "pdf-lib";
import { maxFileSize } from "../../constant/const";
import PageReorderModal from "./PageReorderModal";
function PdfTools(props) {
const { t } = useTranslation();
const mergePdfInputRef = useRef(null);
const [isDeletePage, setIsDeletePage] = useState(false);
const [isReorderModal, setIsReorderModal] = useState(false);
const handleDetelePage = async () => {
props.setIsUploadPdf && props.setIsUploadPdf(true);
try {
const pdfupdatedData = await deletePdfPage(
props.pdfArrayBuffer,
props.pageNumber
);
if (pdfupdatedData?.totalPages === 1) {
alert(t("delete-alert"));
} else {
props.setPdfBase64Url(pdfupdatedData.base64);
props.setPdfArrayBuffer(pdfupdatedData.arrayBuffer);
setIsDeletePage(false);
handleRemoveWidgets(
props.setSignerPos,
props.signerPos,
props.pageNumber
);
props.setAllPages(pdfupdatedData.remainingPages || 1);
if (props.allPages === props.pageNumber) {
props.setPageNumber(props.pageNumber - 1);
} else if (props.allPages > 2) {
props.setPageNumber(props.pageNumber);
}
}
} catch (e) {
console.log("error in delete pdf page", e);
}
};
// `removeFile` is used to remove file if exists
const removeFile = (e) => {
if (e) {
e.target.value = "";
}
};
const handleFileUpload = async (e) => {
props.setIsTour && props.setIsTour(false);
const file = e.target.files[0];
if (!file) {
alert(t("please-select-pdf"));
return;
}
if (!file.type.includes("pdf")) {
alert(t("only-pdf-allowed"));
return;
}
const fileSize =
maxFileSize;
const pdfsize = file?.size;
const fileSizeBytes = fileSize * 1024 * 1024;
if (pdfsize > fileSizeBytes) {
alert(`${t("file-alert-1")} ${fileSize} MB`);
removeFile(e);
return;
}
try {
let uploadedPdfBytes = await file.arrayBuffer();
try {
uploadedPdfBytes = await flattenPdf(uploadedPdfBytes);
} catch (err) {
if (err?.message?.includes("is encrypted")) {
try {
const pdfFile = await decryptPdf(file, "");
const pdfArrayBuffer = await getFileAsArrayBuffer(pdfFile);
uploadedPdfBytes = await flattenPdf(pdfArrayBuffer);
} catch (err) {
if (err?.response?.status === 401) {
const password = prompt(
`PDF "${file.name}" is password-protected. Enter password:`
);
if (password) {
try {
const pdfFile = await decryptPdf(file, password);
const pdfArrayBuffer = await getFileAsArrayBuffer(pdfFile);
uploadedPdfBytes = await flattenPdf(pdfArrayBuffer);
// Upload the file to Parse Server
} catch (err) {
console.error("Incorrect password or decryption failed", err);
alert(t("incorrect-password-or-decryption-failed"));
}
} else {
alert(t("provide-password"));
}
} else {
console.log("Err ", err);
alert(t("error-uploading-pdf"));
}
}
} else {
alert(t("error-uploading-pdf"));
}
}
const uploadedPdfDoc = await PDFDocument.load(uploadedPdfBytes, {
ignoreEncryption: true
});
const basePdfDoc = await PDFDocument.load(props.pdfArrayBuffer);
// Copy pages from the uploaded PDF to the base PDF
const uploadedPdfPages = await basePdfDoc.copyPages(
uploadedPdfDoc,
uploadedPdfDoc.getPageIndices()
);
uploadedPdfPages.forEach((page) => basePdfDoc.addPage(page));
// Save the updated PDF
const pdfBase64 = await basePdfDoc.saveAsBase64({
useObjectStreams: false
});
const pdfBuffer = base64ToArrayBuffer(pdfBase64);
const pdfsize = pdfBuffer?.byteLength;
const fileSizeBytes = fileSize * 1024 * 1024;
if (pdfsize > fileSizeBytes) {
alert(`${t("file-alert-1")} ${fileSize} MB`);
removeFile(e);
return;
}
props.setPdfArrayBuffer(pdfBuffer);
props.setPdfBase64Url(pdfBase64);
props.setIsUploadPdf && props.setIsUploadPdf(true);
mergePdfInputRef.current.value = "";
} catch (error) {
mergePdfInputRef.current.value = "";
console.error("Error merging PDF:", error);
}
};
const handleReorderSave = async (order) => {
try {
const pdfupdatedData = await reorderPdfPages(props.pdfArrayBuffer, order);
if (pdfupdatedData) {
props.setPdfArrayBuffer(pdfupdatedData.arrayBuffer);
props.setPdfBase64Url(pdfupdatedData.base64);
props.setAllPages(pdfupdatedData.totalPages);
props.setPageNumber(1);
}
} catch (e) {
console.log("error in reorder pdf pages", e);
}
setIsReorderModal(false);
};
const handleDeletePage = () => {
setIsDeletePage(true);
props.setIsTour && props.setIsTour(false);
};
const handleReorderPages = () => {
setIsReorderModal(true);
props.setIsTour && props.setIsTour(false);
};
const handleZoomIn = () => {
props.clickOnZoomIn();
props.setIsTour && props.setIsTour(false);
};
const handleZoomOut = () => {
props.clickOnZoomOut();
props.setIsTour && props.setIsTour(false);
};
const handleRotate = () => {
props.handleRotationFun(90);
props.setIsTour && props.setIsTour(false);
};
const handleAntiRotate = () => {
props.handleRotationFun(-90);
props.setIsTour && props.setIsTour(false);
};
return (
<>
<span
data-tut="pdftools"
className="hidden h-max md:flex flex-col gap-1 text-center md:w-[5%] mt-[42px]"
>
{!props.isDisableEditTools && (
<>
<span
className="bg-gray-50 px-[4px] 2xl:py-[10px] cursor-pointer"
onClick={() => mergePdfInputRef.current.click()}
title={t("add-pages")}
>
<input
type="file"
className="hidden"
accept="application/pdf"
ref={mergePdfInputRef}
onChange={handleFileUpload}
/>
<i className="fa-light fa-plus text-gray-500 2xl:text-[25px]"></i>
</span>
<span
className="bg-gray-50 px-[4px] 2xl:py-[10px] cursor-pointer"
onClick={handleDeletePage}
title={t("delete-page")}
>
<i className="fa-light fa-trash text-gray-500 2xl:text-[25px]"></i>
</span>
<span
className="bg-gray-50 px-[4px] 2xl:py-[10px] cursor-pointer"
onClick={handleReorderPages}
title={t("reorder-pages")}
>
<i className="fa-light fa-list-ol text-gray-500 2xl:text-[25px]"></i>
</span>
</>
)}
<span
className="bg-gray-50 px-[4px] 2xl:py-[10px] cursor-pointer"
onClick={handleZoomIn}
title={t("zoom-in")}
>
<i className="fa-light fa-magnifying-glass-plus text-gray-500 2xl:text-[25px]"></i>
</span>
{!props.isDisableEditTools && (
<>
<span
className="bg-gray-50 px-[4px] 2xl:py-[10px] cursor-pointer"
onClick={handleRotate}
title={t("rotate-right")}
>
<i className="fa-light fa-rotate-right text-gray-500 2xl:text-[25px]"></i>
</span>
<span
className="bg-gray-50 px-[4px] 2xl:py-[10px] cursor-pointer"
title={t("rotate-left")}
onClick={handleAntiRotate}
>
<i className="fa-light fa-rotate-left text-gray-500 2xl:text-[25px]"></i>
</span>
</>
)}
<span
className="bg-gray-50 px-[4px] 2xl:py-[10px] cursor-pointer"
onClick={handleZoomOut}
title={t("zoom-out")}
>
<i className="fa-light fa-magnifying-glass-minus text-gray-500 2xl:text-[30px]"></i>
</span>
</span>
<ModalUi
isOpen={isDeletePage}
title={t("delete-page")}
handleClose={() => setIsDeletePage(false)}
>
<div className="h-[100%] p-[20px]">
<p className="font-medium text-base-content">{t("delete-alert-2")}</p>
<p className="pt-3 text-base-content">{t("delete-note")}</p>
<div className="h-[1px] bg-[#9f9f9f] w-full my-[15px]"></div>
<button
onClick={() => handleDetelePage()}
type="button"
className="op-btn op-btn-primary"
>
{t("yes")}
</button>
<button
onClick={() => setIsDeletePage(false)}
type="button"
className="op-btn op-btn-ghost text-base-content ml-1"
>
{t("no")}
</button>
</div>
</ModalUi>
<PageReorderModal
isOpen={isReorderModal}
handleClose={() => setIsReorderModal(false)}
totalPages={props.allPages}
onSave={handleReorderSave}
/>
</>
);
}
export default PdfTools;
@@ -0,0 +1,48 @@
import React from "react";
import { useTranslation } from "react-i18next";
function PdfZoom(props) {
const { t } = useTranslation();
return (
<span className="hidden md:flex flex-col gap-1 text-center md:w-[5%] mt-[43px] 2xl:mt-[63px]">
<span
className="bg-gray-50 px-[4px] 2xl:px-[15px] 2xl:py-[10px] cursor-pointer"
onClick={() => props.clickOnZoomIn()}
title={t("zoom-in")}
>
<i className="fa-light fa-magnifying-glass-plus text-gray-500 2xl:text-[30px]"></i>
</span>
{!props?.isDisableRotate && (
<>
<span
className="bg-gray-50 px-[4px] 2xl:px-[15px] 2xl:py-[10px] cursor-pointer"
onClick={() => props.handleRotationFun(90)}
title={t("rotate-right")}
>
<i className="fa-light fa-rotate-right text-gray-500 2xl:text-[30px]"></i>
</span>
<span
className="bg-gray-50 px-[4px] 2xl:px-[15px] 2xl:py-[10px] cursor-pointer"
title={t("rotate-left")}
onClick={() => props.handleRotationFun(-90)}
>
<i className="fa-light fa-rotate-left text-gray-500 2xl:text-[30px]"></i>
</span>
</>
)}
<span
className="bg-gray-50 px-[4px] 2xl:px-[15px] 2xl:py-[10px]"
onClick={() => props.clickOnZoomOut()}
style={{
cursor: props.zoomPercent > 0 ? "pointer" : "default"
}}
title={t("zoom-out")}
>
<i className="fa-light fa-magnifying-glass-minus text-gray-500 2xl:text-[30px]"></i>
</span>
</span>
);
}
export default PdfZoom;
@@ -0,0 +1,985 @@
import React, { useState, useEffect } from "react";
import BorderResize from "./BorderResize";
import PlaceholderBorder from "./PlaceholderBorder";
import { Rnd } from "react-rnd";
import {
defaultWidthHeight,
fontColorArr,
fontsizeArr,
getContainerScale,
handleCopyNextToWidget,
isTabAndMobile,
onChangeInput,
radioButtonWidget,
textInputWidget,
textWidget
} from "../../constant/Utils";
import PlaceholderType from "./PlaceholderType";
import moment from "moment";
import "../../styles/opensigndrive.css";
import ModalUi from "../../primitives/ModalUi";
import { useTranslation } from "react-i18next";
const selectFormat = (data) => {
switch (data) {
case "L":
return "MM/dd/yyyy";
case "DD-MM-YYYY":
return "dd-MM-yyyy";
case "DD/MM/YYYY":
return "dd/MM/yyyy";
case "LL":
return "MMMM dd, yyyy";
case "DD MMM, YYYY":
return "dd MMM, yyyy";
case "YYYY-MM-DD":
return "yyyy-MM-dd";
case "MM-DD-YYYY":
return "MM-dd-yyyy";
case "MM.DD.YYYY":
return "MM.dd.yyyy";
case "MMM DD, YYYY":
return "MMM dd, yyyy";
case "DD MMMM, YYYY":
return "dd MMMM, yyyy";
default:
return "MM/dd/yyyy";
}
};
const changeDateToMomentFormat = (format) => {
switch (format) {
case "MM/dd/yyyy":
return "L";
case "dd-MM-yyyy":
return "DD-MM-YYYY";
case "dd/MM/yyyy":
return "DD/MM/YYYY";
case "MMMM dd, yyyy":
return "LL";
case "dd MMM, yyyy":
return "DD MMM, YYYY";
case "yyyy-MM-dd":
return "YYYY-MM-DD";
case "MM-dd-yyyy":
return "MM-DD-YYYY";
case "MM.dd.yyyy":
return "MM.DD.YYYY";
case "MMM dd, yyyy":
return "MMM DD, YYYY";
case "dd MMMM, yyyy":
return "DD MMMM, YYYY";
default:
return "L";
}
};
//function to get default date
const getDefaultdate = (selectedDate, format = "dd-MM-yyyy") => {
let date;
if (format && format === "dd-MM-yyyy") {
const newdate = selectedDate
? selectedDate
: moment(new Date()).format(changeDateToMomentFormat(format));
const [day, month, year] = newdate.split("-");
date = new Date(`${year}-${month}-${day}`);
} else {
date = new Date(selectedDate);
}
const value = date;
return value;
};
//function to get default format
const getDefaultFormat = (dateFormat) => dateFormat || "MM/dd/yyyy";
function Placeholder(props) {
const { t } = useTranslation();
const [placeholderBorder, setPlaceholderBorder] = useState({ w: 0, h: 0 });
const [isDraggingEnabled, setDraggingEnabled] = useState(true);
const [isDateModal, setIsDateModal] = useState(false);
const [containerScale, setContainerScale] = useState();
const [selectDate, setSelectDate] = useState({
date:
props.pos.type === "date"
? moment(
getDefaultdate(
props?.pos?.options?.response,
props.pos?.options?.validation?.format
).getTime()
).format(
changeDateToMomentFormat(props.pos?.options?.validation?.format)
)
: "",
format:
props.pos.type === "date"
? getDefaultFormat(props.pos?.options?.validation?.format)
: ""
});
const [dateFormat, setDateFormat] = useState([]);
const [clickonWidget, setClickonWidget] = useState({});
const [startDate, setStartDate] = useState(
props.pos.type === "date" &&
getDefaultdate(
props?.pos?.options?.response,
props.pos?.options?.validation?.format
)
);
const [getCheckboxRenderWidth, setGetCheckboxRenderWidth] = useState({
width: null,
height: null
});
useEffect(() => {
const getPdfPageWidth = props.pdfOriginalWH.find(
(data) => data.pageNumber === props.pageNumber
);
setContainerScale(props.containerWH.width / getPdfPageWidth.width);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [props.containerWH.width, props.pdfOriginalWH]);
const dateFormatArr = [
"L",
"DD-MM-YYYY",
"YYYY-MM-DD",
"MM.DD.YYYY",
"MM-DD-YYYY",
"MMM DD, YYYY",
"LL",
"DD MMM, YYYY",
"DD MMMM, YYYY"
];
useEffect(() => {
const updateWidth = () => {
const rndElement = document.getElementById(props.pos.key);
if (rndElement) {
const { width, height } = rndElement.getBoundingClientRect();
setGetCheckboxRenderWidth({ width: width, height: height });
}
};
// Delay to ensure rendering is complete
const timer = setTimeout(updateWidth, 0);
return () => clearTimeout(timer);
}, [props.pos]);
useEffect(() => {
const closeMenuOnOutsideClick = (e) => {
if (!isDraggingEnabled && !e.target.closest("#changeIsDragging")) {
setDraggingEnabled(true);
}
};
document.addEventListener("click", closeMenuOnOutsideClick);
return () => {
// Cleanup the event listener when the component unmounts
document.removeEventListener("click", closeMenuOnOutsideClick);
};
}, [isDraggingEnabled]);
//function change format array list with selected date and format
const changeDateFormat = () => {
const updateDate = [];
dateFormatArr.map((data) => {
let date;
if (selectDate && selectDate.format === "dd-MM-yyyy") {
const [day, month, year] = selectDate.date.split("-");
date = new Date(`${year}-${month}-${day}`);
} else {
date = new Date(selectDate?.date);
}
const milliseconds = date.getTime();
const newDate = moment(milliseconds).format(data);
const dateObj = {
date: newDate,
format: selectFormat(data)
};
updateDate.push(dateObj);
});
setDateFormat(updateDate);
};
useEffect(() => {
if (props.isPlaceholder || props.isSignYourself) {
selectDate && changeDateFormat();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectDate]);
//`handleWidgetIdandPopup` is used to set current widget id and open relative popup
const handleWidgetIdandPopup = () => {
if (props.setSelectWidgetId) {
props.setSelectWidgetId(props.pos.key);
}
const widgetTypeExist = [
textInputWidget,
"checkbox",
"name",
"company",
"job title",
"date",
"email",
textWidget
].includes(props.pos.type);
if (widgetTypeExist) {
setDraggingEnabled(false);
}
if ((props.isNeedSign || props.isSignYourself) && !props.isDragging) {
if (props?.ispublicTemplate) {
props.handleUserDetails();
} else {
if (props.pos.type) {
if (
props.pos.type === "signature" ||
props.pos.type === "stamp" ||
props.pos.type === "image"
) {
props.setIsSignPad(true);
props.setSignKey(props.pos.key);
props.setIsStamp(props.pos.isStamp);
} else if (props.pos.type === "initials") {
props.setIsSignPad(true);
props.setSignKey(props.pos.key);
props.setIsStamp(props.pos.isStamp);
props.setIsInitial(true);
}
} else {
props.setIsSignPad(true);
props.setSignKey(props.pos.key);
props.setIsStamp(props.pos?.isStamp ? props.pos.isStamp : false);
}
}
} else if (
props.isPlaceholder &&
!props.isDragging &&
props.pos.type !== textWidget
) {
if (props.pos.key === props.selectWidgetId) {
props.handleLinkUser(props.data.Id);
props.setUniqueId(props.data.Id);
const checkIndex = props.xyPostion.findIndex(
(data) => data.Id === props.data.Id
);
props.setIsSelectId(checkIndex || 0);
}
} else if (!props.pos.type) {
if (
!props.pos.type &&
props.isNeedSign &&
props.data.signerObjId === props.signerObjId
) {
props.setIsSignPad(true);
props.setSignKey(props.pos.key);
props.setIsStamp(props.pos.isStamp);
} else if (
(props.isNeedSign && props.pos.type === "signature") ||
props.pos.type === "stamp"
) {
props.setIsSignPad(true);
props.setSignKey(props.pos.key);
props.setIsStamp(props.pos.isStamp);
} else if (props.isNeedSign && props.pos.type === "dropdown") {
props.setSignKey(props.pos.key);
}
}
};
const handleOnClickPlaceholder = () => {
props.setCurrWidgetsDetails && props.setCurrWidgetsDetails(props.pos);
if (!props.isNeedSign) {
props.setWidgetType(props.pos.type);
}
if (props.isNeedSign && props.data?.signerObjId === props.signerObjId) {
handleWidgetIdandPopup();
} else if (
props?.isNeedSign &&
props?.uniqueId &&
props.data?.Id === props?.uniqueId
) {
handleWidgetIdandPopup();
} else if (props.isPlaceholder || props.isSignYourself) {
handleWidgetIdandPopup();
}
};
//`handleOnClickSettingIcon` is used set current widget details and open setting of it
const handleOnClickSettingIcon = () => {
if (props.pos.type === radioButtonWidget) {
props.setIsRadio(true);
} else if (props.pos.type === "dropdown") {
props?.setShowDropdown(true);
} else if (props.pos.type === "checkbox") {
props?.setIsCheckbox(true);
}
//condition to handle setting icon for signyour-self flow for all type text widgets
else if (
[
textInputWidget,
textWidget,
"name",
"company",
"job title",
"email"
].includes(props.pos.type) &&
props.isSignYourself
) {
props.handleTextSettingModal(true);
} else {
props?.handleNameModal && props?.handleNameModal(true);
}
//condition for only placeholder and template flow
if (props.data && props?.pos?.type !== textWidget) {
props.setUniqueId(props?.data?.Id);
const checkIndex = props.xyPostion.findIndex(
(data) => data.Id === props.data.Id
);
props.setIsSelectId(checkIndex || 0);
}
//condition to handle in placeholder and template flow for text widget signerId for text widgets i have to set uniqueId in tempSignerId because
//it does not have any signer user and any signerobjId
else if (props.data && props.pos.type === textWidget) {
props.setTempSignerId(props.uniqueId);
props.setUniqueId(props?.data?.Id);
const checkIndex = props.xyPostion.findIndex(
(data) => data.Id === props.data.Id
);
props.setIsSelectId(checkIndex || 0);
}
props.setSignKey(props.pos.key);
props.setWidgetType(props.pos.type);
props.setCurrWidgetsDetails(props.pos);
};
//function to set required state value onclick on widget's copy icon
const handleCopyPlaceholder = (e) => {
e.stopPropagation();
//condition to handle text widget signer obj id and unique id
//when user click on copy icon of text widget in that condition text widget does not have any signerObjId
//in that case i have to save in tempSignerId as a unique id of previous select signer's unique id
//and on save or cancel button of copy all page popup i have set this temp signer Id in unique id
if (props.data && props?.pos?.type !== textWidget) {
props.setUniqueId(props?.data?.Id);
const checkIndex = props.xyPostion.findIndex(
(data) => data.Id === props.data.Id
);
props.setIsSelectId(checkIndex || 0);
} else if (props.data && props.pos.type === textWidget) {
props.setTempSignerId(props.uniqueId);
props.setUniqueId(props?.data?.Id);
const checkIndex = props.xyPostion.findIndex(
(data) => data.Id === props.data.Id
);
props.setIsSelectId(checkIndex || 0);
}
//checking widget's type and open widget copy modal for required widgets
if (
["signature", textWidget, "stamp", "initial"].includes(props.pos.type)
) {
props.setIsPageCopy(true);
props.setSignKey(props.pos.key);
} else {
//function to create new widget next to just widget
handleCopyNextToWidget(
props.pos,
props.pos.type,
props.xyPostion,
props.index,
props.setXyPostion,
props.data && props.data?.Id
);
}
};
//function to save date and format on local array onchange date and onclick format
const handleSaveDate = (data, isDateChange) => {
let updateDate = data.date;
//check if date change by user
if (isDateChange) {
//`changeDateToMomentFormat` is used to convert date as per required to moment package
updateDate = moment(data.date).format(
changeDateToMomentFormat(data.format)
);
}
//using moment package is used to change date as per the format provided in selectDate obj e.g. - MM/dd/yyyy -> 03/12/2024
//`getDefaultdate` is used to convert update date in new Date() format
const date = moment(
getDefaultdate(updateDate, data?.format).getTime()
).format(changeDateToMomentFormat(data?.format));
//`onChangeInput` is used to save data related to date in a placeholder field
onChangeInput(
date,
props.pos.key,
props.xyPostion,
props.index,
props.setXyPostion,
props.data && props.data.Id,
false,
data?.format,
null,
null,
props.fontSize || props.pos?.options?.fontSize || "12",
props.fontColor || props.pos?.options?.fontColor || "black"
);
setSelectDate({ date: date, format: data?.format });
};
const PlaceholderIcon = () => {
return (
props.isShowBorder && (
<>
{(props.isPlaceholder || props.isSignYourself) && (
<>
{/* condition to add setting icon for signyour-self flow for particular text widgets
and also it have diffrent position
*/}
{[
"checkbox",
textInputWidget,
textWidget,
"name",
"company",
"job title",
"email"
].includes(props.pos.type) && props.isSignYourself ? (
<i
onClick={(e) => {
e.stopPropagation();
handleOnClickSettingIcon();
}}
onTouchEnd={(e) => {
e.stopPropagation();
handleOnClickSettingIcon();
}}
className="fa-light fa-gear icon"
style={{
color: "#188ae2",
right: "29px",
top: "-19px"
}}
></i>
) : (
/* condition to add setting icon for placeholder & template flow for all widgets except signature and date */
((!props?.pos?.type && props.pos.isStamp) ||
(props?.pos?.type &&
!["date", "signature"].includes(props.pos.type) &&
!props.isSignYourself)) && (
<i
onClick={(e) => {
e.stopPropagation();
handleOnClickSettingIcon();
}}
onTouchEnd={(e) => {
e.stopPropagation();
handleOnClickSettingIcon();
}}
className="fa-light fa-gear icon"
style={{
color: "#188ae2",
right: props?.pos?.type === textWidget ? "32px" : "47px",
top: "-19px"
}}
></i>
)
)}
{/* condition for usericon for all widgets except text widgets and signyour-self flow */}
{props.pos.type !== textWidget && !props.isSignYourself && (
<i
data-tut="assignSigner"
className="fa-light fa-user icon"
onClick={(e) => {
e.stopPropagation();
props.handleLinkUser(props.data.Id);
props.setUniqueId(props.data.Id);
const checkIndex = props.xyPostion.findIndex(
(data) => data.Id === props.data.Id
);
props.setIsSelectId(checkIndex || 0);
}}
onTouchEnd={(e) => {
e.stopPropagation();
props.handleLinkUser(props.data.Id);
props.setUniqueId(props.data.Id);
const checkIndex = props.xyPostion.findIndex(
(data) => data.Id === props.data.Id
);
props.setIsSelectId(checkIndex || 0);
}}
style={{ color: "#188ae2", right: "32px", top: "-18px" }}
></i>
)}
</>
)}
{/* setting icon only for date widgets */}
{props.pos.type === "date" && selectDate && (
<i
onClick={(e) => {
props.setCurrWidgetsDetails(props.pos);
setIsDateModal(!isDateModal);
e.stopPropagation();
setClickonWidget(props.pos);
if (props.data) {
props.setSignKey(props.pos.key);
props.setUniqueId(props.data.Id);
const checkIndex = props.xyPostion.findIndex(
(data) => data.Id === props.data.Id
);
props.setIsSelectId(checkIndex || 0);
}
}}
onTouchEnd={(e) => {
props.setCurrWidgetsDetails(props.pos);
e.stopPropagation();
setIsDateModal(!isDateModal);
if (props.data) {
props.setSignKey(props.pos.key);
props.setUniqueId(props.data.Id);
const checkIndex = props.xyPostion.findIndex(
(data) => data.Id === props.data.Id
);
props.setIsSelectId(checkIndex || 0);
}
}}
style={{
zIndex: "99",
top: "-18px",
right: props.isPlaceholder ? "50px" : "30px",
color: "#188ae2",
fontSize: "14px"
}}
className="fa-light fa-gear icon"
></i>
)}
{/* copy icon for all widgets */}
<i
className="fa-light fa-copy icon"
onClick={(e) => handleCopyPlaceholder(e)}
onTouchEnd={(e) => handleCopyPlaceholder(e)}
style={{ color: "#188ae2", right: "12px", top: "-18px" }}
></i>
{/* delete icon for all widgets */}
<i
className="fa-light fa-circle-xmark icon"
onClick={(e) => {
e.stopPropagation();
//condition for template and placeholder flow
if (props.data) {
props.handleDeleteSign(props.pos.key, props.data.Id);
}
//condition for signyour-self flow
else {
props.handleDeleteSign(props.pos.key);
props.setIsStamp(false);
}
}}
//for mobile and tablet touch event
onTouchEnd={(e) => {
e.stopPropagation();
//condition for template and placeholder flow
if (props.data) {
props.handleDeleteSign(props.pos.key, props.data?.Id);
}
//condition for signyour-self flow
else {
props.handleDeleteSign(props.pos.key);
props.setIsStamp(false);
}
}}
style={{ color: "#188ae2", right: "-8px", top: "-18px" }}
></i>
</>
)
);
};
const xPos = (pos, signYourself) => {
const containerScale = getContainerScale(
props.pdfOriginalWH,
props.pageNumber,
props.containerWH
);
const resizePos = pos.xPosition;
if (signYourself) {
return resizePos * containerScale * props.scale;
} else {
//checking both condition mobile and desktop view
if (pos.isMobile && pos.scale) {
if (props.scale > 1) {
return resizePos * pos.scale * containerScale * props.scale;
} else {
return resizePos * pos.scale * containerScale;
}
} else {
// if (pos.scale === containerScale) {
// return resizePos * pos.scale * props.scale;
// } else {
return resizePos * containerScale * props.scale;
// }
}
}
};
const yPos = (pos, signYourself) => {
const containerScale = getContainerScale(
props.pdfOriginalWH,
props.pageNumber,
props.containerWH
);
const resizePos = pos.yPosition;
if (signYourself) {
return resizePos * containerScale * props.scale;
} else {
//checking both condition mobile and desktop view
if (pos.isMobile && pos.scale) {
if (props.scale > 1) {
return resizePos * pos.scale * containerScale * props.scale;
} else {
return resizePos * pos.scale * containerScale;
}
}
// else if (pos.scale === containerScale) {
// if (props.scale > 1) {
// return resizePos * pos.scale * props.scale;
// } else {
// return resizePos * pos.scale;
// }
// }
else {
return resizePos * containerScale * props.scale;
}
}
};
const getCursor = () => {
if (props.data && props.isNeedSign) {
if (props?.signerObjId && props.data?.signerObjId === props.signerObjId) {
return "pointer";
} else if (props?.uniqueId && props.data?.Id === props.uniqueId) {
return "pointer";
} else {
return "not-allowed";
}
} else {
return "all-scroll";
}
};
return (
<>
<Rnd
id={props.pos.key}
data-tut={props.pos.key === props.unSignedWidgetId ? "IsSigned" : ""}
key={props.pos.key}
lockAspectRatio={
![
textWidget,
"email",
"name",
"company",
"job title",
textInputWidget
].includes(props.pos.type) &&
(props.pos.Width
? props.pos.Width / props.pos.Height
: defaultWidthHeight(props.pos.type).width /
defaultWidthHeight(props.pos.type).height)
}
enableResizing={{
top: false,
right: false,
bottom: false,
left: false,
topRight: false,
bottomRight:
props.data && props.isNeedSign
? props.data?.signerObjId === props.signerObjId &&
props.pos.type !== "checkbox" &&
props.pos.type !== radioButtonWidget
? true
: false
: props.pos.type !== radioButtonWidget &&
props.pos.type !== "checkbox" &&
props.pos.key === props.selectWidgetId &&
true,
bottomLeft: false,
topLeft: false
}}
bounds="parent"
className="signYourselfBlock"
style={{
border: "1px solid #007bff",
borderRadius: "2px",
textAlign:
props.pos.type !== "name" &&
props.pos.type !== "company" &&
props.pos.type !== "job title" &&
"center",
cursor: getCursor(),
zIndex:
props.pos.type === "date"
? props.pos.key === props.selectWidgetId
? 99 + 1
: 99
: props?.pos?.zIndex
? props.pos.zIndex
: "5",
background: props.data ? props.data.blockColor : "rgb(203 233 237)"
}}
onDrag={() => {
setDraggingEnabled(true);
props.handleTabDrag && props.handleTabDrag(props.pos.key);
}}
size={{
width:
props.pos.type === radioButtonWidget ||
props.pos.type === "checkbox"
? "auto"
: props.posWidth(props.pos, props.isSignYourself),
height:
props.pos.type === radioButtonWidget ||
props.pos.type === "checkbox"
? "auto"
: props.posHeight(props.pos, props.isSignYourself)
}}
onResizeStart={() => {
setDraggingEnabled(true);
props.setIsResize && props.setIsResize(true);
}}
onResizeStop={(e, direction, ref) => {
props.setIsResize && props.setIsResize(false);
props.handleSignYourselfImageResize &&
props.handleSignYourselfImageResize(
ref,
props.pos.key,
props.xyPostion,
props.setXyPostion,
props.index,
containerScale,
props.scale,
props.data && props.data.Id,
props.isResize
);
}}
disableDragging={
props.isNeedSign
? true
: props.isPlaceholder && ![textWidget].includes(props.pos.type)
? false
: !isDraggingEnabled
}
onDragStop={(event, dragElement) =>
props.handleStop &&
props.handleStop(event, dragElement, props.data?.Id, props.pos?.key)
}
position={{
x: xPos(props.pos, props.isSignYourself),
y: yPos(props.pos, props.isSignYourself)
}}
onResize={(e, direction, ref) => {
setPlaceholderBorder({
w: ref.offsetWidth / (props.scale * containerScale),
h: ref.offsetHeight / (props.scale * containerScale)
});
}}
onClick={() => handleOnClickPlaceholder()}
>
{props.isShowBorder &&
props.pos.type !== radioButtonWidget &&
props.pos.type !== "checkbox" &&
props.pos.key === props.selectWidgetId ? (
<BorderResize right={-12} top={-11} />
) : props.data && props.isNeedSign && props.pos.type !== "checkbox" ? (
props.data?.signerObjId === props.signerObjId &&
props.pos.type !== radioButtonWidget &&
props.pos.type !== "checkbox" ? (
<BorderResize />
) : (
<></>
)
) : (
props.pos.type !== radioButtonWidget &&
props.pos.type !== "checkbox" &&
props.pos.key === props.selectWidgetId && <BorderResize />
)}
{props.isShowBorder && props.pos.key === props.selectWidgetId && (
<PlaceholderBorder
setDraggingEnabled={setDraggingEnabled}
pos={props.pos}
isPlaceholder={props.isPlaceholder}
getCheckboxRenderWidth={getCheckboxRenderWidth}
scale={props.scale}
containerScale={containerScale}
placeholderBorder={placeholderBorder}
/>
)}
{isTabAndMobile ? (
<div
className="flex items-stretch"
style={{
left: xPos(props.pos, props.isSignYourself),
top: yPos(props.pos, props.isSignYourself),
width:
props.pos.type === radioButtonWidget ||
props.pos.type === "checkbox"
? "auto"
: props.posWidth(props.pos, props.isSignYourself),
height:
props.pos.type === radioButtonWidget ||
props.pos.type === "checkbox"
? "auto"
: props.posHeight(props.pos, props.isSignYourself),
zIndex: "10"
}}
onTouchEnd={() => handleOnClickPlaceholder()}
onClick={() => handleOnClickPlaceholder()}
>
{props.pos.key === props.selectWidgetId && <PlaceholderIcon />}
<PlaceholderType
pos={props.pos}
xyPostion={props.xyPostion}
index={props.index}
setXyPostion={props.setXyPostion}
data={props.data}
setSignKey={props.setSignKey}
isShowDropdown={props?.isShowDropdown}
isPlaceholder={props.isPlaceholder}
isSignYourself={props.isSignYourself}
signerObjId={props.signerObjId}
handleUserName={props.handleUserName}
setDraggingEnabled={setDraggingEnabled}
pdfDetails={props?.pdfDetails && props?.pdfDetails[0]}
isNeedSign={props.isNeedSign}
setSelectDate={setSelectDate}
selectDate={selectDate}
setValidateAlert={props.setValidateAlert}
setStartDate={setStartDate}
startDate={startDate}
handleSaveDate={handleSaveDate}
xPos={props.xPos}
/>
</div>
) : (
<>
{props.pos.key === props.selectWidgetId && <PlaceholderIcon />}
<PlaceholderType
pos={props.pos}
xyPostion={props.xyPostion}
index={props.index}
setXyPostion={props.setXyPostion}
data={props.data}
setSignKey={props.setSignKey}
isShowDropdown={props?.isShowDropdown}
isPlaceholder={props.isPlaceholder}
isSignYourself={props.isSignYourself}
signerObjId={props.signerObjId}
handleUserName={props.handleUserName}
setDraggingEnabled={setDraggingEnabled}
pdfDetails={props?.pdfDetails && props?.pdfDetails[0]}
isNeedSign={props.isNeedSign}
setSelectDate={setSelectDate}
selectDate={selectDate}
setValidateAlert={props.setValidateAlert}
setStartDate={setStartDate}
startDate={startDate}
handleSaveDate={handleSaveDate}
xPos={props.xPos}
/>
</>
)}
</Rnd>
<ModalUi isOpen={isDateModal} title={t("widget-info")} showClose={false}>
<div className="h-[100%] p-[20px]">
<div className="flex flex-row items-center">
<span>{t("format")} : </span>
<div className="flex">
<select
className="ml-[7px] op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs"
defaultValue={""}
onChange={(e) => {
const selectedIndex = e.target.value;
e.stopPropagation();
setSelectDate(dateFormat[selectedIndex]);
}}
>
<option value="" disabled>
{t("select-date-format")}
</option>
{dateFormat.map((data, ind) => {
return (
<option className="text-[13px]" value={ind} key={ind}>
{data?.date ? data?.date : "nodata"}
</option>
);
})}
</select>
</div>
<span className="text-xs text-gray-400 ml-1">
{selectDate.format}
</span>
</div>
<div className="flex items-center mt-4 md:mt-2">
<span>{t("font-size")} :</span>
<select
className="ml-[3px] md:ml:[7px] op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs"
value={props.fontSize || clickonWidget.options?.fontSize || "12"}
onChange={(e) => props.setFontSize(e.target.value)}
>
{fontsizeArr.map((size, ind) => {
return (
<option className="text-[13px]" value={size} key={ind}>
{size}
</option>
);
})}
</select>
<div className="flex flex-row gap-1 items-center ml-2 md:ml-4 ">
<span>{t("color")}: </span>
<select
value={
props.fontColor || clickonWidget.options?.fontColor || "black"
}
onChange={(e) => props.setFontColor(e.target.value)}
className="ml-[4px] md:ml[7px] op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs"
>
{fontColorArr.map((color, ind) => {
return (
<option value={color} key={ind}>
{t(`color-type.${color}`)}
</option>
);
})}
</select>
<span
style={{
background:
props.fontColor || props.pos.options?.fontColor || "black"
}}
className="w-5 h-[19px] ml-1"
></span>
</div>
</div>
<div className="h-[1px] w-full my-[15px] bg-[#9f9f9f]"></div>
<button
type="button"
className="op-btn op-btn-primary"
onClick={() => {
setIsDateModal(false);
handleSaveDate(selectDate);
props.setFontColor("");
props.setFontSize("");
}}
>
{t("save")}
</button>
</div>
</ModalUi>
</>
);
}
export default Placeholder;
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,60 @@
import React from "react";
import { themeColor } from "../../constant/const";
import {
defaultWidthHeight,
isMobile,
radioButtonWidget,
resizeBorderExtraWidth,
textWidget
} from "../../constant/Utils";
function PlaceholderBorder(props) {
const getResizeBorderExtraWidth = resizeBorderExtraWidth();
const defaultWidth = defaultWidthHeight(props.pos.type).width;
const defaultHeight = defaultWidthHeight(props.pos.type).height;
const width = () => {
const getWidth =
props.placeholderBorder.w || props.pos.Width || defaultWidth;
return (
getWidth * props.scale * props.containerScale + getResizeBorderExtraWidth
);
};
const height = () => {
const getHeight =
props.placeholderBorder.h || props.pos.Height || defaultHeight;
return (
getHeight * props.scale * props.containerScale + getResizeBorderExtraWidth
);
};
const handleMinWidth = () => {
if (props.pos.type === "checkbox" || props.pos.type === radioButtonWidget) {
return props.getCheckboxRenderWidth.width + getResizeBorderExtraWidth;
} else {
return width();
}
};
const handleMinHeight = () => {
if (props.pos.type === "checkbox" || props.pos.type === radioButtonWidget) {
return props.getCheckboxRenderWidth.height + getResizeBorderExtraWidth;
} else {
return height();
}
};
return (
<div
onMouseEnter={() => !isMobile && props?.setDraggingEnabled(true)}
onTouchEnd={() =>
props.pos.type === textWidget && props?.setDraggingEnabled(true)
}
className="absolute inline-block w-[14px] h-[14px] border-[0.2px] overflow-hidden border-dashed"
style={{
borderColor: themeColor,
minWidth: handleMinWidth() || 0,
minHeight: handleMinHeight() || 0
}}
></div>
);
}
export default PlaceholderBorder;
@@ -1,6 +1,10 @@
import { useState } from "react";
import React, { useState } from "react";
import ModalUi from "../../primitives/ModalUi";
import { handleCopyNextToWidget, randomId } from "../../constant/Utils";
import {
handleCopyNextToWidget,
randomId,
textWidget
} from "../../constant/Utils";
import { useTranslation } from "react-i18next";
function PlaceholderCopy(props) {
@@ -15,89 +19,56 @@ function PlaceholderCopy(props) {
//function for get copy placeholder position
const getCopyPlaceholderPosition = (
type,
newPlaceholder,
currentPlaceholder,
newPageNumber,
existPlaceholderPosition
) => {
//remove existing page position when used option copy but first or copy but last
const filterPosition =
existPlaceholderPosition &&
existPlaceholderPosition.filter(
(data) => data.xPosition !== newPlaceholder.xPosition
(data) => data.xPosition !== currentPlaceholder.xPosition
);
//first option - copy all page but not first
//copy all page placeholder at requested position except first page
if (newPageNumber === 1 && type === 3) {
//when user choose this option and placed widget on first page then do not remove first page widget
//it should be copy all page included first page on same postion
if (props.pageNumber === 1) {
return {
pageNumber: newPageNumber,
pos: [...existPlaceholderPosition]
};
} //else when user copy widget and choose this option and not at first page then copy all page but not at first
else if (filterPosition && filterPosition.length > 0) {
if (filterPosition && filterPosition.length > 0) {
return {
pageNumber: newPageNumber,
pos: [...filterPosition]
};
}
}
//second option - copy all page but not last
//copy all page placeholder at requested position except last page
else if (newPageNumber === props.allPages && type === 2) {
if (existPlaceholderPosition) {
//when user choose this option and placed widget on last page then do not remove last page widget
//it should be copy all page included last page on same postion
if (props.pageNumber === props.allPages) {
return {
pageNumber: newPageNumber,
pos: [...existPlaceholderPosition]
};
} else {
return {
pageNumber: newPageNumber,
pos: [...filterPosition]
};
}
}
}
//this optin - copy all page with exiting widgets on same page
//copy all page placeholder at requested position with existing placeholder position
else if (existPlaceholderPosition) {
if (newPageNumber === props.pageNumber) {
return {
pageNumber: props.pageNumber,
pos: [...existPlaceholderPosition]
};
} else {
return {
pageNumber: newPageNumber,
pos: [...existPlaceholderPosition, newPlaceholder]
pos: [...filterPosition]
};
}
}
//copy all page placeholder at requested position with existing placeholder position
else if (existPlaceholderPosition) {
return {
pageNumber: newPageNumber,
pos: [...filterPosition, currentPlaceholder]
};
}
//copy all page placeholder at requested position
else {
if (newPageNumber === props.pageNumber) {
return {
pageNumber: props.pageNumber,
pos: [...existPlaceholderPosition]
};
} else {
return {
pageNumber: newPageNumber,
pos: [newPlaceholder]
};
}
return {
pageNumber: newPageNumber,
pos: [currentPlaceholder]
};
}
};
//function for copy placeholder as per select copy type
const copyPlaceholder = (type) => {
let newPlaceholderPosition = [];
let newPageNumber = 1;
const signerPosition = props.xyPosition;
const signerPosition = props.xyPostion;
const signerId = props.signerObjId ? props.signerObjId : props.Id;
//handle placeholder array and copy for multiple signers placeholder at requested location
if (signerId) {
//get current signers data
@@ -119,15 +90,10 @@ function PlaceholderCopy(props) {
const currentPlaceholder = placeholderPosition[0].pos.find(
(position) => position.key === props.signKey
);
for (let i = 0; i < props.allPages; i++) {
const newId = randomId();
const nameId = randomId(2)
const widgetName = `${currentPlaceholder?.options?.name}${nameId}`;
const newPlaceholder = {
...currentPlaceholder,
key: newId,
options: { ...currentPlaceholder?.options, name: widgetName }
};
currentPlaceholder.key = newId;
//get exist placeholder position for particular page
const existPlaceholder = filterSignerPosition[0].placeHolder.filter(
(data) => data.pageNumber === newPageNumber
@@ -138,11 +104,11 @@ function PlaceholderCopy(props) {
//function for get copy to requested location of placeholder position
const getPlaceholderObj = getCopyPlaceholderPosition(
type,
newPlaceholder,
currentPlaceholder,
newPageNumber,
existPlaceholderPosition
);
if (getPlaceholderObj?.pos) {
if (getPlaceholderObj) {
newPlaceholderPosition.push(getPlaceholderObj);
}
newPageNumber++;
@@ -172,13 +138,13 @@ function PlaceholderCopy(props) {
const signersData = signerPosition;
signersData.splice(0, signerPosition.length, ...updatedSignerPlaceholder);
props.setXyPosition(signersData);
props.setXyPostion(signersData);
}
//handle signyourself array and copy for single signers placeholder at requested location
else {
const xyPosition = props.xyPosition;
const xyPostion = props.xyPostion;
const placeholderPosition = xyPosition.filter(
const placeholderPosition = xyPostion.filter(
(data) => data.pageNumber === props.pageNumber
);
//get current placeholder position data which user want to copy
@@ -188,18 +154,18 @@ function PlaceholderCopy(props) {
for (let i = 0; i < props.allPages; i++) {
//get exist placeholder position for particular page
const existPlaceholder = xyPosition.filter(
const existPlaceholder = xyPostion.filter(
(data) => data.pageNumber === newPageNumber
);
const existPlaceholderPosition =
existPlaceholder[0] && existPlaceholder[0].pos;
const newId = randomId();
const newPlaceholder = { ...currentPlaceholder, key: newId };
currentPlaceholder.key = newId;
//function for get copy to requested location of placeholder position
const getPlaceholderObj = getCopyPlaceholderPosition(
type,
newPlaceholder,
currentPlaceholder,
newPageNumber,
existPlaceholderPosition
);
@@ -209,15 +175,14 @@ function PlaceholderCopy(props) {
newPageNumber++;
}
props.setXyPosition(newPlaceholderPosition);
props.setXyPostion(newPlaceholderPosition);
}
};
//function for getting selected type placeholder copy
const handleApplyCopy = () => {
const newId = randomId();
if (selectCopyType === 4) {
const signerPosition = props.xyPosition;
const signerPosition = props.xyPostion;
let currentXYposition;
const signerId = props.signerObjId ? props.signerObjId : props.Id;
if (signerId) {
@@ -233,29 +198,29 @@ function PlaceholderCopy(props) {
);
//function to create new widget next to just widget
handleCopyNextToWidget(
newId,
currentXYposition,
props.xyPosition,
props.widgetType,
props.xyPostion,
props.pageNumber,
props.setXyPosition,
props.setXyPostion,
props?.Id
);
} else {
const getIndex = props?.xyPosition.findIndex(
const getIndex = props?.xyPostion.findIndex(
(data) => data.pageNumber === props.pageNumber
);
const placeholderPosition = props?.xyPosition[getIndex];
const placeholderPosition = props?.xyPostion[getIndex];
//get current placeholder position data which user want to copy
currentXYposition = placeholderPosition.pos.find(
(pos) => pos.key === props.signKey
);
//function to create new widget next to just widget
handleCopyNextToWidget(
newId,
currentXYposition,
props.xyPosition,
props.widgetType,
props.xyPostion,
getIndex,
props.setXyPosition
props.setXyPostion
);
}
} else {
@@ -263,6 +228,11 @@ function PlaceholderCopy(props) {
}
};
const handleUniqueId = () => {
const signerId = props.signerObjId ? props.signerObjId : props.Id;
if (signerId && props.widgetType === textWidget) {
props.setUniqueId(props?.tempSignerId);
props.setTempSignerId("");
}
props.setIsPageCopy(false);
setSelectCopyType(1);
};
@@ -290,7 +260,7 @@ function PlaceholderCopy(props) {
);
})}
<div className="flex flex-row bg-[#9f9f9f] w-full my-[15px]"></div>
<div className="flex bg-[#9f9f9f] w-full my-[15px]"></div>
<button
onClick={() => {
handleApplyCopy();
@@ -304,7 +274,7 @@ function PlaceholderCopy(props) {
</button>
<button
type="button"
className="op-btn op-btn-ghost text-base-content ml-2"
className="op-btn op-btn-ghost"
onClick={() => handleUniqueId()}
>
{t("cancel")}

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