Compare commits

..
438 changed files with 279859 additions and 63157 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"
]
}
]
}
+6 -7
View File
@@ -3,16 +3,18 @@
# Note that any data in staging will get cleared from time to time and you may have to sign up again and again
# Set it to the URL form where the app home page will be accessed
PUBLIC_URL=https://localhost:3001
PUBLIC_URL=http://localhost:3000
# Set it to true if you want to generate the Sourcemap for debugging
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'.
REACT_APP_SERVERURL=http://localhost:8080/app
# A 12 character long random app identifier. The value of this should be same as APP_ID which is a variable used by backend API.
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.
@@ -44,7 +46,7 @@ USE_LOCAL=true
MAILGUN_API_KEY=
MAILGUN_DOMAIN=mail.yourdomain.com
MAILGUN_SENDER=postmaster@mail.yourdomain.com
SMTP_ENABLE=true
SMTP_ENABLE=
SMTP_HOST=smtp.yourhost.com
SMTP_PORT=443
SMTP_USER_EMAIL=mailer@yourdomain.com
@@ -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,14 +1,7 @@
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:
+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).
---
---
-7
View File
@@ -1,7 +0,0 @@
{$HOST_URL} {
reverse_proxy client:3000
handle_path /api/* {
reverse_proxy server:8080
rewrite * {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/>
+2 -4
View File
@@ -1,10 +1,8 @@
build:
@echo "Building with HOST_URL=${HOST_URL}"
cp .env.local_dev .env
cd apps/OpenSign && cp ../../.env.local_dev .env && npm install && npm run build
HOST_URL=${HOST_URL} docker compose up --build --force-recreate
docker compose up --build --force-recreate
run:
@echo "Building with HOST_URL=${HOST_URL}"
cp .env.local_dev .env
docker compose up -d
docker compose up -d
+3 -28
View File
@@ -73,31 +73,9 @@ 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
```
Make sure that you have `Docker` and `git` installed before you run this command -
Please refer to the [Installation Guide](https://docs.opensignlabs.com/docs/self-host/docker/run-locally/) for detailed instructions on how to install OpenSign on your system.
Please refer to the [Installation Guide](INSTALLATION.md) for detailed instructions on how to install OpenSign on your system.
---
@@ -163,8 +141,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 +151,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
};
+25791
View File
File diff suppressed because it is too large Load Diff
+68 -79
View File
@@ -1,72 +1,66 @@
{
"name": "open_sign",
"version": "2.26.0",
"version": "0.1.0",
"private": true,
"dependencies": {
"@formkit/auto-animate": "^0.8.4",
"@imgly/background-removal": "^1.7.0",
"@lottiefiles/dotlottie-react": "^0.15.2",
"@pdf-lib/fontkit": "^1.1.1",
"@radix-ui/themes": "^3.2.1",
"@reduxjs/toolkit": "^2.8.2",
"axios": "^1.11.0",
"date-fns-tz": "^3.2.0",
"@formkit/auto-animate": "^0.8.2",
"@lottiefiles/dotlottie-react": "^0.8.6",
"@radix-ui/themes": "^3.1.1",
"@react-pdf/renderer": "^3.4.4",
"@reduxjs/toolkit": "^2.2.7",
"axios": "^1.7.2",
"file-saver": "^2.0.5",
"i18next": "^25.4.0",
"i18next-browser-languagedetector": "^8.2.0",
"i18next-http-backend": "^3.0.2",
"jszip": "^3.10.1",
"jwt-decode": "^4.0.0",
"moment": "^2.30.1",
"parse": "^6.1.1",
"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.3.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.3",
"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.8.2",
"react-konva": "^18.2.10",
"react-pdf": "^9.1.0",
"react-quill": "^2.0.0",
"react-redux": "^9.1.2",
"react-rnd": "^10.4.11",
"react-router-dom": "^6.26.0",
"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.0",
"react-signature-canvas": "^1.0.6",
"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.4",
"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.2",
"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": "concurrently \"npm run build-watch\" \"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 +88,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.12",
"@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.3",
"@babel/preset-react": "^7.24.7",
"@babel/runtime-corejs2": "^7.25.0",
"autoprefixer": "^10.4.19",
"babel-loader": "^9.1.3",
"clean-webpack-plugin": "^4.0.0",
"commitizen": "^4.3.0",
"concurrently": "^8.2.2",
"css-loader": "^7.1.2",
"daisyui": "^4.12.24",
"eslint": "^9.34.0",
"eslint-plugin-prettier": "^5.5.4",
"eslint-plugin-react": "^7.37.5",
"jsdom": "^26.1.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": "^8.57.0",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-react": "^7.34.3",
"lint-staged": "^15.2.8",
"mini-css-extract-plugin": "^2.9.0",
"postcss": "^8.4.41",
"prettier": "^3.3.3",
"pretty-quick": "^4.0.0",
"tailwindcss": "^3.4.7",
"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";
+212
View File
@@ -0,0 +1,212 @@
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";
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 = process.env.REACT_APP_SERVERURL
? process.env.REACT_APP_SERVERURL
: window.location.origin + "/api/app";
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;
-155
View File
@@ -1,155 +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 ValidateSession from "./primitives/ValidateSession";
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={
<ValidateSession>
<HomeLayout />
</ValidateSession>
}
>
<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

+283
View File
@@ -0,0 +1,283 @@
import React, { useState, useEffect } from "react";
import Parse from "parse";
import Loader from "../primitives/Loader";
const AddSigner = (props) => {
const [name, setName] = useState("");
const [phone, setPhone] = useState("");
const [email, setEmail] = useState("");
const [addYourself, setAddYourself] = useState(false);
const [isLoader, setIsLoader] = useState(false);
const [isUserExist, setIsUserExist] = useState(false);
useEffect(() => {
checkUserExist();
}, []);
// Load user details from localStorage when the component mounts
useEffect(() => {
const savedUserDetails = JSON.parse(
localStorage.getItem("UserInformation")
);
if (savedUserDetails && addYourself) {
setName(savedUserDetails.name);
setPhone(savedUserDetails?.phone || "");
setEmail(savedUserDetails.email);
}
}, [addYourself]);
const checkUserExist = async () => {
const user = Parse.User.current();
try {
const query = new Parse.Query("contracts_Contactbook");
query.equalTo("CreatedBy", user);
query.notEqualTo("IsDeleted", true);
query.equalTo("Email", user.getEmail());
const res = await query.first();
// console.log(res);
if (!res) {
setIsUserExist(true);
}
} catch (err) {
console.log("err", err);
}
};
// Define a function to handle form submission
const handleSubmit = async (e) => {
e.preventDefault();
e.stopPropagation();
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]
});
}
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("Contact already exist!");
setIsLoader(false);
}
} catch (err) {
console.log("err in fetch contact", err);
setIsLoader(false);
alert("something went wrong, please try again later");
}
} else {
setIsLoader(false);
alert("something went wrong, please try again later");
}
};
// Define a function to handle the "add yourself" checkbox
const handleAddYourselfChange = () => {
if (addYourself) {
setAddYourself(false);
setName("");
setPhone("");
setEmail("");
} else {
setAddYourself(true);
}
};
const handleReset = () => {
setAddYourself(false);
};
return (
<div className="h-full p-[20px]">
{isLoader && (
<div className="fixed inset-0 flex justify-center items-center bg-black bg-opacity-30">
<Loader />
</div>
)}
<div className="w-full mx-auto p-2 text-base-content">
{isUserExist && (
<div className="mb-3 flex items-center">
<input
type="checkbox"
id="addYourself"
checked={addYourself}
onChange={handleAddYourselfChange}
className="op-checkbox op-checkbox-sm"
/>
<label htmlFor="addYourself" className="ml-2 mb-0">
Add Yourself
</label>
</div>
)}
<form onSubmit={handleSubmit}>
<div className="mb-3">
<label htmlFor="name" className="block text-xs font-semibold">
Name
<span className="text-[red] text-[13px]"> *</span>
</label>
<input
type="text"
id="name"
value={name}
onChange={(e) => setName(e.target.value)}
required
disabled={addYourself}
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 font-semibold">
Email
<span className="text-[red] text-[13px]"> *</span>
</label>
<input
type="email"
id="email"
value={email}
onChange={(e) => setEmail(e.target.value?.toLowerCase())}
required
disabled={addYourself}
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">
Phone
</label>
<input
type="text"
id="phone"
value={phone}
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"
/>
</div>
<div className="mt-4 flex gap-x-2 justify-start">
<button type="submit" className="op-btn op-btn-primary">
Submit
</button>
<button
type="button"
onClick={() => handleReset()}
className="op-btn op-btn-secondary"
>
Reset
</button>
</div>
</form>
</div>
</div>
);
};
export default AddSigner;
-300
View File
@@ -1,300 +0,0 @@
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();
const [name, setName] = useState("");
const [phone, setPhone] = useState("");
const [email, setEmail] = useState("");
const [addYourself, setAddYourself] = useState(false);
const [isLoader, setIsLoader] = useState(false);
const [isUserExist, setIsUserExist] = useState(false);
useEffect(() => {
checkUserExist();
}, []);
// Load user details from localStorage when the component mounts
useEffect(() => {
const savedUserDetails = JSON.parse(
localStorage.getItem("UserInformation")
);
if (savedUserDetails && addYourself) {
setName(savedUserDetails.name);
setPhone(savedUserDetails?.phone || "");
setEmail(savedUserDetails.email?.toLowerCase()?.replace(/\s/g, ""));
}
}, [addYourself]);
const checkUserExist = async () => {
const user = Parse.User.current();
try {
const query = new Parse.Query("contracts_Contactbook");
query.equalTo("CreatedBy", user);
query.notEqualTo("IsDeleted", true);
query.equalTo("Email", user.getEmail());
const res = await query.first();
// console.log(res);
if (!res) {
setIsUserExist(true);
}
} catch (err) {
console.log("err", err);
}
};
// Define a function to handle form submission
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);
}
} 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"));
}
}
};
// Define a function to handle the "add yourself" checkbox
const handleAddYourselfChange = () => {
if (addYourself) {
setAddYourself(false);
setName("");
setPhone("");
setEmail("");
} else {
setAddYourself(true);
}
};
const handleReset = () => {
setAddYourself(false);
};
return (
<div className="h-full p-[20px]">
{isLoader && (
<div className="fixed inset-0 flex justify-center items-center bg-black bg-opacity-30">
<Loader />
</div>
)}
<div className="w-full mx-auto p-2 text-base-content">
<p className="mb-3 text-xs text-gray-500">{t("add-signer-note")}</p>
{isUserExist && (
<div className="mb-3 flex items-center">
<input
type="checkbox"
id="addYourself"
checked={addYourself}
onChange={handleAddYourselfChange}
className="op-checkbox op-checkbox-sm"
/>
<label htmlFor="addYourself" className="ml-2 mb-0">
{t("add-yourself")}
</label>
</div>
)}
<form onSubmit={handleSubmit}>
<div className="mb-3">
<label htmlFor="name" className="block text-xs font-semibold">
{t("name")}
<span className="text-[red] text-[13px]"> *</span>
</label>
<input
type="text"
id="name"
value={name}
onChange={(e) => setName(e.target.value)}
required
disabled={addYourself}
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 htmlFor="email" className="block text-xs font-semibold">
{t("email")}
<span className="text-[red] text-[13px]"> *</span>
</label>
<input
type="email"
id="email"
value={email}
onChange={(e) =>
setEmail(e.target.value?.toLowerCase()?.replace(/\s/g, ""))
}
required
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
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"
/>
</div>
<div className="mb-3">
<label htmlFor="phone" className="block text-xs font-semibold">
{t("phone")}
</label>
<input
type="text"
id="phone"
value={phone}
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">
<button type="submit" className="op-btn op-btn-primary">
{t("submit")}
</button>
<button
type="button"
onClick={() => handleReset()}
className="op-btn op-btn-secondary"
>
{t("reset")}
</button>
</div>
</form>
</div>
</div>
);
};
export default AddSigner;
+194
View File
@@ -0,0 +1,194 @@
import React, { useEffect, useState } from "react";
import Parse from "parse";
import Title from "./Title";
import Loader from "../primitives/Loader";
const AddTeam = (props) => {
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"
>
Name
<span className="text-[red] text-[13px]"> *</span>
</label>
<input
type="text"
name="name"
value={formdata.name}
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>
<div className="mb-3">
<label
htmlFor="phone"
className="block text-xs text-gray-700 font-semibold"
>
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">
Submit
</button>
<div
type="button"
onClick={() => handleReset()}
className="op-btn op-btn-secondary"
>
Reset
</div>
</div>
</form>
</div>
</div>
);
};
export default AddTeam;
+398
View File
@@ -0,0 +1,398 @@
import React, { useEffect, useState } from "react";
import Parse from "parse";
import Title from "./Title";
import Loader from "../primitives/Loader";
import { copytoData } from "../constant/Utils";
import { isEnableSubscription } from "../constant/const";
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 [formdata, setFormdata] = useState({
name: "",
phone: "",
email: "",
team: "",
password: "",
role: ""
});
const [isLoader, setIsLoader] = useState(false);
const [teamList, setTeamList] = useState([]);
const role = ["OrgAdmin", "Editor", "User"];
useEffect(() => {
getTeamList();
}, []);
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);
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];
setIsLoader(true);
const res = await checkUserExist();
if (res) {
props.setIsAlert({ type: "danger", msg: "User already exist." });
setIsLoader(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);
}
setIsLoader(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);
}
setIsLoader(false);
setFormdata({
name: "",
email: "",
phone: "",
team: "",
role: ""
});
}
}
} catch (err) {
console.log("err", err);
setIsLoader(false);
props.setIsAlert({ type: "danger", msg: "something went wrong." });
} 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 = (e) => {
setFormdata((prev) => ({ ...prev, [e.target.name]: e.target.value }));
};
const copytoclipboard = (text) => {
copytoData(text);
props.setIsAlert({ type: "success", msg: "Copied" });
setTimeout(() => props.setIsAlert({ type: "success", msg: "" }), 1500); // Reset copied state after 1.5 seconds
};
return (
<div className="shadow-md rounded-box my-[1px] p-3 bg-[#ffffff]">
<Title title={"Add User"} />
{isLoader && (
<div className="fixed inset-0 flex justify-center items-center bg-black bg-opacity-30 z-50 rounded-box">
<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"
>
Name
<span className="text-[red] text-[13px]"> *</span>
</label>
<input
type="text"
name="name"
value={formdata.name}
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>
<div className="mb-3">
<label
htmlFor="email"
className="block text-xs text-gray-700 font-semibold"
>
Email
<span className="text-[red] text-[13px]"> *</span>
</label>
<input
type="email"
name="email"
value={formdata.email}
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>
<div className="mb-3">
<label
htmlFor="email"
className="block text-xs text-gray-700 font-semibold"
>
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">
Password will only be generated once; make sure to copy it.
</div>
</div>
<div className="mb-3">
<label
htmlFor="phone"
className="block text-xs text-gray-700 font-semibold"
>
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"
>
Team<span className="text-[red] text-[13px]"> *</span>
</label>
<select
value={formdata.team}
onChange={(e) => handleChange(e)}
name="team"
className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content w-full text-xs"
required
>
<option defaultValue={""} value={""}>
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"
>
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"
required
>
<option defaultValue={""} value={""}>
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">
Submit
</button>
<div
type="button"
onClick={() => handleReset()}
className="op-btn op-btn-secondary"
>
Cancel
</div>
</div>
</form>
</div>
</div>
);
};
export default AddUser;
-285
View File
@@ -1,285 +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"
/>
</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"
/>
</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;
+283
View File
@@ -0,0 +1,283 @@
import React, { useState, useEffect, useRef } from "react";
import axios from "axios";
import SuggestionInput from "./shared/fields/SuggestionInput";
import Loader from "../primitives/Loader";
const BulkSendUi = (props) => {
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 allowedSigners = 50;
useEffect(() => {
signatureExist();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
//function to check atleast one signature field exist
const signatureExist = () => {
const getPlaceholder = props.item?.Placeholders;
const checkIsSignatureExistt = getPlaceholder?.every((placeholderObj) =>
placeholderObj?.placeHolder?.some((holder) =>
holder?.pos?.some((posItem) => posItem?.type === "signature")
)
);
setIsSignatureExist(checkIsSignatureExistt);
};
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 (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("Quick send reached limit.");
}
};
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
});
}
});
//console.log("Documents ", Documents);
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);
}
};
return (
<>
{isSubmit && (
<div className="absolute z-[999] h-full w-full flex justify-center items-center bg-black bg-opacity-30">
<Loader />
</div>
)}
{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>{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
onClick={handleAddForm}
className="op-btn op-btn-primary focus:outline-none"
>
<i className="fa-light fa-plus"></i> <span>Add new</span>
</button>
<button
type="submit"
className="op-btn op-btn-secondary focus:outline-none"
>
<i className="fa-light fa-paper-plane"></i>{" "}
<span>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">
All roles in this document are currently linked to contacts. To
&apos;quick send&apos; copies of this template to multiple
signers, please ensure that at least one role is not linked to
any contact.
</div>
)}
</>
) : (
<div className="text-black p-3 bg-white w-full text-sm md:text-base flex justify-center items-center">
Please ensure there&#39;s at least one signature widget added for
all recipients.
</div>
)
) : (
<div className="text-black p-3 bg-white w-full text-sm md:text-base flex justify-center items-center">
Please add at least one role to this template in order to &apos;quick
send&apos; copies of it to multiple signers.
</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>
);
}
@@ -2,10 +2,7 @@ import React, { useEffect, useState } from "react";
import Package from "../../package.json";
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("");
useEffect(() => {
@@ -40,6 +37,7 @@ const Footer = () => {
};
}, []);
const appName = "OpenSign™";
const openUrl = () => {
openInNewTab(
"https://github.com/OpenSignLabs/OpenSign/releases/tag/" + version
@@ -50,10 +48,9 @@ const Footer = () => {
<footer className="op-footer op-footer-center py-3 bg-base-300 text-base-content text-center text-[13px]">
<aside>
<p>
{t("all-right")} &copy; {new Date().getFullYear()} &nbsp;
All Rights Reserved &copy; {new Date().getFullYear()} &nbsp;
<span onClick={openUrl} className="hover:underline cursor-pointer">
{appName} ( {t("version")}:{" "}
{version ? version : `${Package.version} `})
{appName} ( version: {version ? version : `${Package.version} `})
</span>
</p>
</aside>
+229
View File
@@ -0,0 +1,229 @@
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,
checkIsSubscribedTeam,
getAppLogo,
openInNewTab
} from "../constant/Utils";
import { isEnableSubscription, isStaging } from "../constant/const";
const Header = ({ showSidebar }) => {
const navigate = useNavigate();
const { width } = useWindowSize();
let username = localStorage.getItem("username");
const image = localStorage.getItem("profileImg") || dp;
const [isOpen, setIsOpen] = useState(false);
const [isSubscribe, setIsSubscribe] = useState(true);
const [isPro, setIsPro] = useState(false);
const [isTeam, setIsTeam] = useState(false);
const [applogo, setAppLogo] = useState(
localStorage.getItem("appLogo") || " "
);
const toggleDropdown = () => {
setIsOpen(!isOpen);
};
useEffect(() => {
checkSubscription();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
async function checkSubscription() {
if (isEnableSubscription) {
const getIsSubscribe = await checkIsSubscribed();
const getIsTeam = await checkIsSubscribedTeam();
if (getIsSubscribe) {
const applogo = await getAppLogo();
if (applogo?.logo) {
setAppLogo(applogo?.logo);
} else {
setAppLogo(localStorage.getItem("appLogo") || "");
}
}
setIsPro(getIsSubscribe);
setIsTeam(getIsTeam);
setIsSubscribe(getIsSubscribe);
}
}
const closeDropdown = () => {
setIsOpen(false);
if (Parse?.User?.current()) {
try {
Parse.User.logOut();
} catch (err) {
console.log("Err", 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();
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/");
}
};
return (
<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={() => navigate("/subscription")}
>
Upgrade Now
</button>
</div>
)}
{!isTeam && isPro && (
<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">
PRO
</div>
)}
{isTeam && (
<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">
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> Docs
</span>
</li>
<li
onClick={() => {
setIsOpen(false);
navigate("/profile");
}}
>
<span>
<i className="fa-light fa-user"></i> Profile
</span>
</li>
<li
onClick={() => {
setIsOpen(false);
navigate("/changepassword");
}}
>
<span>
<i className="fa-light fa-lock"></i> Change Password
</span>
</li>
<li
onClick={() => {
handleConsoleRedirect();
}}
>
<span>
<i className="fa-light fa-id-card"></i> Console
</span>
</li>
<li onClick={closeDropdown}>
<span>
<i className="fa-light fa-arrow-right-from-bracket"></i> Log Out
</span>
</li>
</ul>
</div>
</div>
</div>
);
};
export default Header;
-268
View File
@@ -1,268 +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";
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);
}
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,208 @@
import React, { useState } from "react";
import Parse from "parse";
import Loader from "../primitives/Loader";
// import FacebookLogin from "react-facebook-login";
const LoginFacebook = ({
FBCred,
thirdpartyLoginfn,
thirdpartyLoader,
setThirdpartyLoader
}) => {
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("Internal server error !");
}
} else {
alert("Please 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">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">
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
})
}
required
/>
</div>
<div className="form-group">
<label htmlFor="Company" className="col-form-label">
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
})
}
required
/>
</div>
<div>
<button
type="button"
onClick={() => handleSubmitbtn()}
className="btn btn-info mr-[10px]"
>
Sign up
</button>
<button
type="button"
className="btn btn-secondary w-[90px]"
onClick={() => setIsModal(false)}
>
Cancel
</button>
</div>
</form>
</div>
</div>
</div>
</div>
)}
</div>
);
};
export default LoginFacebook;
+277
View File
@@ -0,0 +1,277 @@
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";
/*
* `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 [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 () => {
if (Parse.User.current()) {
await Parse.User.logOut();
}
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();
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 extUser = new Parse.Query("contracts_Users");
// extUser.equalTo("Email", details.Gmail);
// const extRes = await extUser.first();
const params = { email: details.Gmail };
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("googlesign", params);
// console.log("payload ", payload);
if (payload && payload.sessiontoken) {
// setThirdpartyLoader(true);
// 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.Gmail,
phone: extRes?.get("Phone") || "",
company: extRes.get("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("Internal server error !");
}
} else {
alert("Please fill required details!");
}
};
const handleCloseModal = () => {
setIsModal(false);
Parse.User.logOut();
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();
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="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">
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
})
}
required
/>
</div>
<div className="mb-3">
<label htmlFor="Company" className="block text-xs font-semibold">
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
})
}
required
/>
</div>
<div className="mb-3">
<label htmlFor="JobTitle" className="block text-xs font-semibold">
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
})
}
required
/>
</div>
<div className="flex flex-row gap-2">
<button
type="button"
className="op-btn op-btn-primary"
onClick={() => handleSubmitbtn()}
>
Sign up
</button>
<button
type="button"
className="op-btn op-btn-ghost"
onClick={handleCloseModal}
>
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}
@@ -1,39 +0,0 @@
import React from "react";
import ModalUi from "../primitives/ModalUi";
import { useTranslation } from "react-i18next";
function RotateAlert(props) {
const { t } = useTranslation();
return (
<ModalUi
isOpen={props.showRotateAlert}
title={t("Rotation-alert")}
handleClose={() => props.setShowRotateAlert({ status: false, degree: 0 })}
>
<div className="p-[20px] h-full">
<p>{t("rotate-alert-mssg")}</p>
<div className="h-[1px] w-full my-[15px] bg-[#9f9f9f]"></div>
<div className="flex gap-1">
<button
onClick={() => props.handleRemoveWidgets()}
type="button"
className="op-btn op-btn-primary"
>
{t("yes")}
</button>
<button
onClick={() =>
props.setShowRotateAlert({ status: false, degree: 0 })
}
type="button"
className="op-btn op-btn-ghost text-base-content shadow-md"
>
{t("no")}
</button>
</div>
</div>
</ModalUi>
);
}
export default RotateAlert;
@@ -1,54 +0,0 @@
import React from "react";
import { useTranslation } from "react-i18next";
import { NavLink } from "react-router";
const SocialMedia = () => {
const { t } = useTranslation();
return (
<React.Fragment>
<NavLink
to="https://github.com/opensignlabs/opensign"
target="_blank"
rel="noopener noreferrer"
>
<i aria-hidden="true" className="fa-brands fa-github"></i>
<span className="fa-sr-only">
OpenSign&apos;s {t("social-media.github")}
</span>
</NavLink>
<NavLink
to="https://www.linkedin.com/company/opensign%E2%84%A2/"
target="_blank"
rel="noopener noreferrer"
>
<i aria-hidden="true" className="fa-brands fa-linkedin"></i>
<span className="fa-sr-only">
OpenSign&apos;s {t("social-media.linked-in")}
</span>
</NavLink>
<NavLink
to="https://www.twitter.com/opensignlabs"
target="_blank"
rel="noopener noreferrer"
>
<i aria-hidden="true" className="fa-brands fa-square-x-twitter"></i>
<span className="fa-sr-only">
OpenSign&apos;s {t("social-media.twitter")}
</span>
</NavLink>
<NavLink
to="https://discord.com/invite/xe9TDuyAyj"
target="_blank"
rel="noopener noreferrer"
>
<i aria-hidden="true" className="fa-brands fa-discord"></i>
<span className="fa-sr-only">
OpenSign&apos;s {t("social-media.discord")}
</span>
</NavLink>
</React.Fragment>
);
};
export default SocialMedia;
@@ -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>
);
}
@@ -0,0 +1,44 @@
import React from "react";
import { useNavigate } from "react-router-dom";
import { openInNewTab } from "../../constant/Utils";
const DashboardButton = (props) => {
const navigate = useNavigate();
function openReport() {
if (props.Data && props.Data.Redirect_type) {
const Redirect_type = props.Data.Redirect_type;
const id = props.Data.Redirect_id;
if (Redirect_type === "Form") {
navigate(`/form/${id}`);
} else if (Redirect_type === "Report") {
navigate(`/report/${id}`);
} else if (Redirect_type === "Url") {
openInNewTab(id);
}
}
}
return (
<div
onClick={() => openReport()}
className={`${
props.Data && props.Data.Redirect_type
? "cursor-pointer"
: "cursor-default"
} w-full shadow-md px-3 py-2 op-card bg-base-100`}
>
<div className="flex flex-row items-center">
<span className="rounded-full bg-base-content bg-opacity-20 w-[60px] h-[60px] self-start flex justify-center items-center">
<i
className={`${
props.Icon ? props.Icon : "fa-light fa-info"
} text-[25px] lg:text-[30px]`}
></i>
</span>
<div className=" text-lg ml-3 text-base-content">{props.Label}</div>
</div>
</div>
);
};
export default DashboardButton;
@@ -1,60 +0,0 @@
import React from "react";
import { useNavigate } from "react-router";
import { openInNewTab } from "../../constant/Utils";
import { useTranslation } from "react-i18next";
const DashboardButton = (props) => {
const navigate = useNavigate();
const { t } = useTranslation();
function openReport() {
if (props.Data && props.Data.Redirect_type) {
const Redirect_type = props.Data.Redirect_type;
const id = props.Data.Redirect_id;
if (Redirect_type === "Form") {
navigate(`/form/${id}`);
} else if (Redirect_type === "Report") {
navigate(`/report/${id}`);
} else if (Redirect_type === "Url") {
openInNewTab(id);
}
}
}
return (
<div
onClick={() => openReport()}
className={`${
props.Data && props.Data.Redirect_type
? "cursor-pointer"
: "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">
{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>
);
};
export default DashboardButton;
@@ -2,13 +2,11 @@ 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";
const DashboardCard = (props) => {
const navigate = useNavigate();
const { t } = useTranslation();
const [parseBaseUrl] = useState(localStorage.getItem("baseUrl"));
const [parseAppId] = useState(localStorage.getItem("parseAppId"));
const [response, setresponse] = useState("");
@@ -133,7 +131,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 +146,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) {
@@ -313,7 +311,6 @@ const DashboardCard = (props) => {
}
}
}
return (
<div
onClick={() => openReport()}
@@ -331,11 +328,8 @@ const DashboardCard = (props) => {
} text-[25px] lg:text-[30px]`}
></i>
</span>
<div className="font-medium">
<div className="text-base lg:text-lg">
{t(`dashboard-card.${props.Label}`)}
</div>
<div className="text-base lg:text-lg"> {props.Label}</div>
<div className="text-2xl font-light">
{loading ? <div className="loader-01"></div> : setFormat(response)}
</div>
@@ -345,7 +339,7 @@ const DashboardCard = (props) => {
<Tooltip
id={props.Label}
iconColor={"white"}
message={t(`tour-mssg.${props.Label}`)}
message={props?.Data?.tourMessage}
/>
</div>
</div>
@@ -1,12 +1,10 @@
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";
import { useTranslation } from "react-i18next";
function DashboardReport(props) {
const { t } = useTranslation();
// console.log("props ", props)
const [List, setList] = useState([]);
const [isLoader, setIsLoader] = useState(true);
@@ -17,16 +15,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 +34,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 +54,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 +64,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 +92,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 +128,7 @@ function DashboardReport(props) {
) : (
<>
{reportName ? (
<DocumentsReport
<ReportTable
ReportName={reportName}
List={List}
setList={setList}
@@ -207,17 +137,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">Report Not Found</p>
</div>
</div>
)}
@@ -1,9 +1,7 @@
import React, { Suspense } from "react";
import { lazyWithRetry } from "../../utils";
import { useTranslation } from "react-i18next";
const DashboardButton = lazyWithRetry(() => import("./DashboardButton"));
const DashboardCard = lazyWithRetry(() => import("./DashboardCard"));
const DashboardReport = lazyWithRetry(() => import("./DashboardReport"));
import React, { Suspense, lazy } from "react";
const DashboardButton = lazy(() => import("./DashboardButton"));
const DashboardCard = lazy(() => import("./DashboardCard"));
const DashboardReport = lazy(() => import("./DashboardReport"));
const buttonList = [
{
label: "Sign yourself",
@@ -12,14 +10,13 @@ const buttonList = [
icon: "fa-light fa-pen-nib"
},
{
label: "Request signatures",
label: "Request signature",
redirectId: "8mZzFxbG1z",
redirectType: "Form",
icon: "fa-light fa-paper-plane"
}
];
const GetDashboard = (props) => {
const { t } = useTranslation();
const Button = ({ label, redirectId, redirectType, icon }) => (
<DashboardButton
Icon={icon}
@@ -40,7 +37,7 @@ const GetDashboard = (props) => {
<Suspense
fallback={
<div className="h-[150px] w-full flex justify-center items-center">
{t("loading")}
loading...
</div>
}
>
@@ -1,24 +1,21 @@
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";
function DriveBody(props) {
const { t } = useTranslation();
const [rename, setRename] = useState("");
const [renameValue, setRenameValue] = useState("");
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" },
@@ -45,7 +42,7 @@ function DriveBody(props) {
props.setFolderName((prev) => [...prev, folderData]);
props.setIsLoading({
isLoad: true,
message: t("loading-mssg")
message: "This might take some time"
});
props.setDocId(data.objectId);
props.setPdfData([]);
@@ -93,7 +90,7 @@ function DriveBody(props) {
console.log("Err ", err);
props.setIsAlert({
isShow: true,
alertMessage: t("something-went-wrong-mssg")
alertMessage: "something went wrong"
});
});
}
@@ -101,17 +98,16 @@ function DriveBody(props) {
//function for navigate user to microapp-signature component
const checkPdfStatus = async (data) => {
const signerExist = data?.Signers;
const isDecline = data?.IsDeclined;
const isPlaceholder = data?.Placeholders;
const signedUrl = data?.SignedUrl;
const isSignYourself = data?.IsSignyourself;
const signerExist = data.Signers && data.Signers;
const isDecline = data.IsDeclined && data.IsDeclined;
const isPlaceholder = data.Placeholders && data.Placeholders;
const signedUrl = data.SignedUrl;
//checking if document has completed and request signature flow
if (data?.IsCompleted && signerExist?.length > 0) {
navigate(`/recipientSignPdf/${data.objectId}`);
}
//checking if document has completed and signyour-self flow
else if ((!signerExist && !isPlaceholder) || isSignYourself) {
else if (!signerExist && !isPlaceholder) {
navigate(`/signaturePdf/${data.objectId}`);
}
//checking if document has declined by someone
@@ -138,10 +134,12 @@ function DriveBody(props) {
}
};
const handleMenuItemClick = async (selectType, data, deleteType) => {
const handleMenuItemClick = (selectType, data) => {
switch (selectType) {
case "Download": {
await handleDownloadPdf([data]);
const pdfName = data && data.Name;
const pdfUrl = data && data.SignedUrl ? data.SignedUrl : data.URL;
saveAs(pdfUrl, `${sanitizeFileName(pdfName)}_signed_by_OpenSign™.pdf`);
break;
}
case "Rename": {
@@ -150,7 +148,7 @@ function DriveBody(props) {
break;
}
case "Delete": {
setIsDeleteDoc({ status: true, deleteType });
setIsDeleteDoc(true);
setSelectDoc(data);
break;
}
@@ -164,7 +162,7 @@ function DriveBody(props) {
};
//function for delete document
const handleDeleteDocument = async (docData) => {
setIsDeleteDoc({});
setIsDeleteDoc(false);
const docId = docData.objectId;
const data = {
IsArchive: true
@@ -193,7 +191,7 @@ function DriveBody(props) {
console.log("Err ", err);
props.setIsAlert({
isShow: true,
alertMessage: t("something-went-wrong-mssg")
alertMessage: "something went wrong"
});
});
};
@@ -260,60 +258,22 @@ function DriveBody(props) {
setIsOpenMoveModal(false);
} else {
alert(t("folder-already-exist!"));
alert("folder already exist!");
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;
@@ -347,18 +307,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)}>
@@ -366,14 +323,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>
@@ -390,44 +347,41 @@ function DriveBody(props) {
<span className="text-[12px] font-medium">{data.Name}</span>
</td>
<td>{createddate}</td>
<td>{t("pdf")}</td>
<td>{t(`drive-document-status.${status}`)}</td>
<td>Pdf</td>
<td>{status}</td>
<td>
<i
onClick={(e) => {
e.stopPropagation();
handleMenuItemClick("Download", data);
}}
className="fa-light fa-download mr-[8px] op-text-primary cursor-pointer"
className="fa-light fa-download mr-[8px] op-text-primary"
aria-hidden="true"
></i>
</td>
</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}
onClick={() => {
if (!rename) {
handleOnclikFolder(data);
}
}}
onClick={() => handleOnclikFolder(data)}
className="cursor-pointer"
>
<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>
{rename === data.objectId ? (
<input
onFocus={() => {
inputRef.current.setSelectionRange(0, 0);
const input = inputRef.current;
if (input) {
input.select();
@@ -439,14 +393,16 @@ function DriveBody(props) {
onKeyDown={(e) => handleEnterPress(e, data)}
ref={inputRef}
defaultValue={renameValue}
// value={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]"
className="w-[100px] border-[1.5px] border-black rounded-sm 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"
@@ -458,41 +414,23 @@ 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>
</ContextMenu.Root>
</div>
) : (
<HoverCard.Root
open={rename || isMobile ? false : undefined}
openDelay={0}
closeDelay={100}
>
<HoverCard.Root openDelay={0} closeDelay={100}>
<HoverCard.Trigger asChild>
<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}
onClick={() => {
if (!rename) {
checkPdfStatus(data);
}
}}
onClick={() => checkPdfStatus(data)}
className="cursor-pointer"
>
<svg
@@ -507,22 +445,22 @@ function DriveBody(props) {
autoFocus={true}
type="text"
onFocus={() => {
inputRef.current.setSelectionRange(0, 0);
const input = inputRef.current;
if (input) {
input.select();
}
}}
onBlur={() => handledRenameDoc(data)}
onKeyDown={(e) => handleEnterPress(e, data, data.Type)}
onKeyDown={(e) => handleEnterPress(e, data)}
ref={inputRef}
defaultValue={renameValue}
// value={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]"
className="w-[100px] border-[1.5px] border-black rounded-sm text-[10px]"
/>
) : (
<span className="fileName select-none-cls">
{data.Name}
</span>
<span className="fileName">{data.Name}</span>
)}
</div>
</ContextMenu.Trigger>
@@ -550,6 +488,7 @@ function DriveBody(props) {
)
)}
</div>
<ContextMenu.Portal>
<ContextMenu.Content
className="ContextMenuContent"
@@ -564,9 +503,7 @@ function DriveBody(props) {
className="ContextMenuItem"
>
<i className={menu.icon}></i>
<span className="ml-[8px]">
{t(`context-menu.${menu.type}`)}
</span>
<span className="ml-[8px]">{menu.type}</span>
</ContextMenu.Item>
);
})}
@@ -577,31 +514,22 @@ function DriveBody(props) {
</HoverCard.Trigger>
<HoverCard.Portal>
<HoverCard.Content className="HoverCardContent" sideOffset={5}>
<strong className="text-[13px]">
{t("report-heading.Title")}:{" "}
</strong>
<strong className="text-[13px]">Title: </strong>
<span className="text-[12px] font-medium mb-0"> {data.Name}</span>
<br />
<strong className="text-[13px]">
{t("report-heading.Status")}:{" "}
</strong>
<span className="text-[12px] font-medium">
{t(`drive-document-status.${status}`)}
</span>
<strong className="text-[13px]">Status: </strong>
<span className="text-[12px] font-medium"> {status}</span>
<br />
<strong className="text-[13px]">
{t("report-heading.created-date")}:{" "}
</strong>
<strong className="text-[13px]">Created Date: </strong>
<span className="text-[12px] font-medium">{createddate}</span>
<br />
{signerExist && (
<>
<strong className="text-[13px]">
{t("report-heading.Signers")}:{" "}
</strong>
<strong className="text-[13px]">Signers: </strong>
{signersName()}
</>
)}
<HoverCard.Arrow className="HoverCardArrow" />
</HoverCard.Content>
</HoverCard.Portal>
@@ -617,29 +545,29 @@ function DriveBody(props) {
<Table striped bordered hover>
<thead>
<tr>
<th>{t("report-heading.Name")}</th>
<th>{t("report-heading.created-date")}</th>
<th>{t("report-heading.Type")}</th>
<th>{t("report-heading.Status")}</th>
<th>{t("action")}</th>
<th>Name</th>
<th>Created Date</th>
<th>Type</th>
<th>Status</th>
<th>Action</th>
</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>
)}
@@ -653,37 +581,26 @@ function DriveBody(props) {
/>
)}
<ModalUi
isOpen={isDeleteDoc.status}
title={t("delete-document")}
handleClose={() => setIsDeleteDoc({})}
isOpen={isDeleteDoc}
title={"Delete Document"}
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>Are you sure you want to delete this document?</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")}
Yes
</button>
<button
onClick={() => setIsDeleteDoc({})}
onClick={() => setIsDeleteDoc(false)}
type="button"
className="op-btn op-btn-neutral"
>
{t("no")}
No
</button>
</div>
</ModalUi>
@@ -1,41 +1,39 @@
import React from "react";
import ModalUi from "../../primitives/ModalUi";
import { useTranslation } from "react-i18next";
const AddRoleModal = (props) => {
const { t } = useTranslation();
return (
<ModalUi
title={t("add-role")}
title={"Add Role"}
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"
/>
<p className="text-[gray] text-[11px] mt-[5px] mb-[10px] ml-[10px]">
{t("role-ex")}..
e.g: Customer, Hr, Director, Manager, Student, etc...
</p>
<div>
<div className="h-[1px] w-full bg-[#9f9f9f] mb-[10px]"></div>
<button type="submit" className="op-btn op-btn-primary">
{t("add")}
Add
</button>
<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")}
Close
</button>
</div>
</form>
@@ -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,44 @@
import React from "react";
function DefaultSignature({ defaultSignImg, xyPostion, setDefaultSignAlert }) {
const confirmToaddDefaultSign = () => {
if (xyPostion.length > 0) {
setDefaultSignAlert({
isShow: true,
alertMessage:
"Are you sure you want to auto sign at requested all locations?"
});
} else {
setDefaultSignAlert({
isShow: true,
alertMessage: "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">
Signature
</div>
<div className="flex flex-col items-center mt-[10px] font-semibold">
<p className="text-base-content">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()}
>
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,19 +2,17 @@ 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 { useTranslation } from "react-i18next";
import { useLocation, useNavigate } from "react-router-dom";
function useQuery() {
return new URLSearchParams(useLocation().search);
}
function DraftDocument() {
const { t } = useTranslation();
const navigate = useNavigate();
const query = useQuery();
const docId = query.get("docId");
const [isLoading, setIsLoading] = useState({
isLoader: true,
message: t("loading-mssg")
message: "This might take some time"
});
useEffect(() => {
getDocumentDetails();
@@ -30,19 +28,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!"
});
}
};
@@ -58,7 +52,7 @@ function DraftDocument() {
navigate(`/recipientSignPdf/${data.objectId}`);
}
//checking if document has completed and signyour-self flow
else if ((!signerExist && !isPlaceholder) || data?.IsSignyourself) {
else if (!signerExist && !isPlaceholder) {
navigate(`/signaturePdf/${data.objectId}`);
}
//checking if document has declined by someone
@@ -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;
@@ -0,0 +1,381 @@
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";
function DropdownWidgetOption(props) {
const [dropdownOptionList, setDropdownOptionList] = useState([
"option-1",
"option-2"
]);
const [minCount, setMinCount] = useState(0);
const [maxCount, setMaxCount] = useState(0);
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 statusArr = ["required", "optional"];
const [defaultCheckbox, setDefaultCheckbox] = useState([]);
const resetState = () => {
setDropdownOptionList(["option-1", "option-2"]);
setDropdownName(props.type);
setIsReadOnly(false);
setIsHideLabel(false);
setMinCount(0);
setMaxCount(0);
setDefaultCheckbox([]);
setDefaultValue("");
};
useEffect(() => {
if (
props.currWidgetsDetails?.options?.name &&
props.currWidgetsDetails?.options?.values
) {
setDropdownName(props.currWidgetsDetails?.options?.name);
setDropdownOptionList(props.currWidgetsDetails?.options?.values);
setMinCount(
props.currWidgetsDetails?.options?.validation?.minRequiredCount
);
setMaxCount(
props.currWidgetsDetails?.options?.validation?.maxRequiredCount
);
setIsReadOnly(props.currWidgetsDetails?.options?.isReadOnly);
setIsHideLabel(props.currWidgetsDetails?.options?.isHideLabel);
setStatus(props.currWidgetsDetails?.options?.status || "required");
setDefaultValue(props.currWidgetsDetails?.options?.defaultValue || "");
setDefaultCheckbox(props.currWidgetsDetails?.options?.defaultValue || []);
} else {
setStatus("required");
resetState();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [props.currWidgetsDetails]);
const handleInputChange = (index, value) => {
setDropdownOptionList((prevInputs) => {
const newInputs = [...prevInputs];
newInputs[index] = value;
return newInputs;
});
};
//function add add checkbox option and add width of checkbox
const handleAddInput = () => {
const deleteOption = false;
const addOption = true;
setDropdownOptionList((prevInputs) => [...prevInputs, ""]);
props.handleSaveWidgetsOptions(
null,
null,
null,
null,
null,
addOption,
deleteOption
);
};
//function add add checkbox option and delete width of checkbox
const handleDeleteInput = (ind) => {
const deleteOption = true;
const addOption = false;
const getUpdatedOptions = dropdownOptionList.filter(
(data, index) => index !== ind
);
setDropdownOptionList(getUpdatedOptions);
props.handleSaveWidgetsOptions(
null,
null,
null,
null,
null,
addOption,
deleteOption
);
};
const handleSaveOption = () => {
const defaultData =
defaultCheckbox && defaultCheckbox.length > 0
? defaultCheckbox
: defaultValue;
props.handleSaveWidgetsOptions(
dropdownName,
dropdownOptionList,
minCount,
maxCount,
isReadOnly,
null,
null,
status,
defaultData,
isHideLabel
);
// 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);
if (getDefaultCheck) {
return true;
} else {
return false;
}
};
return (
<ModalUi isOpen={props.showDropdown} title={props.title} showClose={false}>
<div className="h-full p-[15px] text-base-content">
<form
onSubmit={(e) => {
e.preventDefault();
handleSaveOption();
}}
>
<div>
<label className="text-[13px] font-semibold">
Name<span className="text-[red] text-[13px]"> *</span>
</label>
<input
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"
/>
<label className="text-[13px] font-semibold mt-[5px]">
Options
</label>
<div className="flex flex-col">
{dropdownOptionList.map((option, index) => (
<div
key={index}
className="flex flex-row mb-[5px] items-center"
>
{props.type === "checkbox" && !props.isSignYourself && (
<input
type="checkbox"
checked={handleDefaultCheck(index)}
onChange={(e) => {
if (e.target.checked) {
const getDefaultCheck =
defaultCheckbox?.includes(index);
if (!getDefaultCheck) {
setDefaultCheckbox((prev) => [...prev, index]);
}
} else {
const removeOption = defaultCheckbox.filter(
(data) => data !== index
);
setDefaultCheckbox(removeOption);
}
}}
className="op-checkbox focus:outline-none hover:border-base-content mr-[5px]"
/>
)}
<input
required
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
type="text"
value={option}
onChange={(e) => handleInputChange(index, e.target.value)}
/>
<i
className="fa-light fa-rectangle-xmark text-[25px] ml-[10px] text-accent cursor-pointer"
onClick={() => handleDeleteInput(index)}
></i>
</div>
))}
<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`}
>
Minimun check
</label>
{!props.isSubscribe && isEnableSubscription && (
<Upgrade />
)}
<input
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`}
>
Maximum check
</label>
<input
required
defaultValue={0}
value={maxCount}
disabled={props.isSubscribe ? false : true}
onChange={(e) => {
const count = handleSetMinMax(e);
setMaxCount(count);
}}
className={`${
props.isSubscribe ? "" : "pointer-events-none"
} op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs`}
/>
</>
)}
</div>
)}
</div>
{["dropdown", radioButtonWidget].includes(props.type) && (
<>
<label className="text-[13px] font-semibold mt-[5px]">
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]">
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>
</>
)}
{["checkbox", radioButtonWidget].includes(props.type) && (
<div className="flex flex-row gap-5 my-2 items-center text-center">
{!props.isSignYourself && (
<div className="flex items-center">
<input
id="isreadonly"
type="checkbox"
checked={isReadOnly}
className="op-checkbox op-checkbox-sm"
onChange={(e) => setIsReadOnly(e.target.checked)}
/>
<label className="ml-1 mb-0" htmlFor="isreadonly">
Is read only
</label>
</div>
)}
<div className="flex items-center">
<input
id="ishidelabel"
type="checkbox"
checked={isHideLabel}
className="op-checkbox op-checkbox-sm"
onChange={(e) => setIsHideLabel(e.target.checked)}
/>
<label className="ml-1 mb-0" htmlFor="ishidelabel">
Hide labels
</label>
</div>
</div>
)}
</div>
<div
className={`${
props.type === "checkbox" && !props.isSignYourself
? "mb-[15px]"
: "my-[15px]"
} w-full h-[1px] bg-[#9f9f9f]`}
></div>
<button
disabled={dropdownOptionList.length === 0 && true}
type="submit"
className="op-btn op-btn-primary"
>
Save
</button>
{props.currWidgetsDetails?.options?.values?.length > 0 && (
<button
type="submit"
className="op-btn op-btn-ghost ml-1"
onClick={() => {
props.handleClose && props.handleClose();
resetState();
}}
>
Cancel
</button>
)}
</form>
</div>
</ModalUi>
);
}
export default DropdownWidgetOption;
@@ -1,446 +0,0 @@
import React, { useEffect, useState } from "react";
import ModalUi from "../../primitives/ModalUi";
import { radioButtonWidget } from "../../constant/Utils";
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"
]);
const [minCount, setMinCount] = useState(0);
const [maxCount, setMaxCount] = useState(0);
const [dropdownName, setDropdownName] = useState();
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 resetState = () => {
setDropdownOptionList(["Option-1", "Option-2"]);
setDropdownName(props.currWidgetsDetails?.options?.name || props.type);
setIsReadOnly(false);
setIsHideLabel(false);
setMinCount(0);
setMaxCount(0);
setDefaultCheckbox([]);
setDefaultValue("");
setLayout("vertical");
};
useEffect(() => {
if (
props.currWidgetsDetails?.options?.name &&
props.currWidgetsDetails?.options?.values?.length > 0
) {
setDropdownName(props.currWidgetsDetails?.options?.name);
setDropdownOptionList(props.currWidgetsDetails?.options?.values);
setMinCount(
props.currWidgetsDetails?.options?.validation?.minRequiredCount
);
setMaxCount(
props.currWidgetsDetails?.options?.validation?.maxRequiredCount
);
setIsReadOnly(props.currWidgetsDetails?.options?.isReadOnly);
setIsHideLabel(props.currWidgetsDetails?.options?.isHideLabel);
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();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [props.currWidgetsDetails]);
const handleInputChange = (index, value) => {
setDropdownOptionList((prevInputs) => {
const newInputs = [...prevInputs];
newInputs[index] = value;
return newInputs;
});
};
//function add add checkbox option and add width of checkbox
const handleAddInput = () => {
const deleteOption = false;
const addOption = true;
setDropdownOptionList((prevInputs) => [...prevInputs, ""]);
props.handleSaveWidgetsOptions(
null,
null,
null,
null,
null,
addOption,
deleteOption
);
};
//function add add checkbox option and delete width of checkbox
const handleDeleteInput = (ind) => {
const deleteOption = true;
const addOption = false;
const getUpdatedOptions = dropdownOptionList.filter(
(_, index) => index !== ind
);
setDropdownOptionList(getUpdatedOptions);
props.handleSaveWidgetsOptions(
null,
null,
null,
null,
null,
addOption,
deleteOption
);
};
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,
minCount,
maxCount,
isReadOnly,
null,
null,
status,
defaultData,
isHideLabel,
WidgetLayout
);
resetState();
};
const handleDefaultCheck = (index) => {
const getDefaultCheck = defaultCheckbox.includes(index);
if (getDefaultCheck) {
return true;
} else {
return false;
}
};
return (
<ModalUi isOpen={props.showDropdown} title={props.title} showClose={false}>
<div className="h-full p-[15px] text-base-content">
<form
onSubmit={(e) => {
e.preventDefault();
handleSaveOption();
}}
>
<div>
<label htmlFor="title" className="text-[13px] font-semibold">
{t("name")}
<span className="text-[red] text-[13px]"> *</span>
</label>
<input
id="title"
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
onInput={(e) => e.target.setCustomValidity("")}
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) => (
<div
key={index}
className="flex flex-row mb-[5px] items-center"
>
{props.type === "checkbox" && props.isShowAdvanceFeature && (
<input
type="checkbox"
checked={handleDefaultCheck(index)}
onChange={(e) => {
if (e.target.checked) {
const getDefaultCheck =
defaultCheckbox?.includes(index);
if (!getDefaultCheck) {
setDefaultCheckbox((prev) => [...prev, index]);
}
} else {
const removeOption = defaultCheckbox.filter(
(data) => data !== index
);
setDefaultCheckbox(removeOption);
}
}}
className="op-checkbox focus:outline-none hover:border-base-content mr-[5px]"
/>
)}
<input
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"
type="text"
value={option}
onChange={(e) => handleInputChange(index, e.target.value)}
/>
<i
className="fa-light fa-rectangle-xmark text-[25px] ml-[10px] text-accent cursor-pointer"
onClick={() => handleDeleteInput(index)}
></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>
</>
)}
{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 className="flex items-center mt-3 mb-3">
<span>{t("font-size")} :</span>
<select
className="ml-[7px] op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs"
value={
props.fontSize ||
props.currWidgetsDetails?.options?.fontSize ||
12
}
onChange={(e) => props.setFontSize(parseInt(e.target.value))}
>
{fontsizeArr.map((size, ind) => {
return (
<option className="text-[13px]" value={size} key={ind}>
{size}
</option>
);
})}
</select>
<div className="flex flex-row gap-1 items-center ml-4">
<span className="capitalize">{t("color")} : </span>
<select
value={
props.fontColor ||
props.currWidgetsDetails?.options?.fontColor ||
"black"
}
onChange={(e) => props.setFontColor(e.target.value)}
className="ml-[7px] op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs"
>
{fontColorArr.map((color, ind) => {
return (
<option value={color} key={ind}>
{t(`color-type.${color}`)}
</option>
);
})}
</select>
<span
style={{
background:
props.fontColor ||
props.currWidgetsDetails?.options?.fontColor ||
"black"
}}
className="w-5 h-[19px] ml-1"
></span>
</div>
</div>
{["checkbox", radioButtonWidget, "dropdown"].includes(
props.type
) && (
<div className="flex flex-row gap-5 my-2 items-center text-center">
{props.isShowAdvanceFeature && !isPrefillExist && (
<div className="flex items-center">
<input
id="isreadonly"
type="checkbox"
checked={isReadOnly}
className="op-checkbox op-checkbox-sm"
onChange={(e) => setIsReadOnly(e.target.checked)}
/>
<label
className="ml-2 mb-0 capitalize"
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)}
/>
<label
className="ml-2 mb-0 capitalize"
htmlFor="ishidelabel"
>
{t("hide-labels")}
</label>
</div>
)}
</div>
)}
{["checkbox", radioButtonWidget].includes(props.type) && (
<>
<div className="text-[13px] font-semibold mt-[5px] capitalize">
{t("layout")}
</div>
<div
className={`${props.type === "checkbox" ? "mb-[10px]" : ""} flex flex-row gap-[10px] mt-[0.5rem]`}
>
{layoutArr.map((data, ind) => (
<div
key={ind}
className="flex flex-row gap-[5px] items-center"
>
<input
className="op-radio op-radio-xs my-1"
type="radio"
name="layout"
checked={layout.toLowerCase() === data.toLowerCase()}
onChange={() => setLayout(data.toLowerCase())}
/>
<label className="text-[13px] font-500 mb-0">
{t(data)}
</label>
</div>
))}
</div>
</>
)}
</div>
<div
className={`${
props.type === "checkbox" && props.isShowAdvanceFeature
? "mb-[15px]"
: "my-[15px]"
} w-full h-[1px] bg-[#9f9f9f]`}
></div>
<button
disabled={dropdownOptionList.length === 0 && true}
type="submit"
className="op-btn op-btn-primary"
>
{t("save")}
</button>
{props.currWidgetsDetails?.options?.values?.length > 0 && (
<button
type="submit"
className="op-btn op-btn-ghost text-base-content ml-1"
onClick={() => {
props.handleClose && props.handleClose();
resetState();
}}
>
{t("cancel")}
</button>
)}
</form>
</div>
</ModalUi>
);
}
export default DropdownWidgetOption;
@@ -0,0 +1,189 @@
import React, { useState, useEffect } from "react";
import { checkIsSubscribed, getFileName } from "../../constant/Utils";
import Upgrade from "../../primitives/Upgrade";
import { isEnableSubscription } from "../../constant/const";
// import SelectFolder from "../../premitives/SelectFolder";
const EditTemplate = ({ template, onSuccess }) => {
// 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
});
const [isSubscribe, setIsSubscribe] = useState(false);
useEffect(() => {
fetchSubscription();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const fetchSubscription = async () => {
if (isEnableSubscription) {
const getIsSubscribe = await checkIsSubscribed();
setIsSubscribe(getIsSubscribe);
}
};
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;
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, ...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]">
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]">
Name
<span className="text-[13px] text-[red]"> *</span>
</label>
<input
type="text"
name="Name"
value={formData.Name}
onChange={(e) => handleStrInput(e)}
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]">
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]">
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]">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]">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]">No</div>
</div>
</div>
{isEnableSubscription && (
<div className="text-xs mt-2">
<span
className={
isSubscribe ? "font-semibold" : "font-semibold text-gray-300"
}
>
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">
Remind once in every (Days)
<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}
required
/>
</div>
)}
<div className="mt-[1rem] flex justify-start">
<button type="submit" className="op-btn op-btn-primary">
Submit
</button>
</div>
</form>
</div>
</div>
);
};
export default EditTemplate;
@@ -1,541 +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);
import { Quill } from "react-quill";
// 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",
@@ -91,6 +79,7 @@ export const formats = [
"blockquote",
"background",
"list",
"bullet",
"indent",
"link",
"image",
@@ -1,42 +1,35 @@
import React from "react";
import Tooltip from "../../primitives/Tooltip";
import ReactQuill from "react-quill-new";
import "../../styles/quill.css";
import ReactQuill from "react-quill";
import "react-quill/dist/quill.snow.css";
import EditorToolbar, { module1, formats } from "./EditorToolbar";
import { useTranslation } from "react-i18next";
export function EmailBody(props) {
const { t } = useTranslation();
return (
<form className="flex flex-col text-base-content text-lg font-normal">
<div className="m-2 md:m-10 p-3 md:p-10 shadow-md hover:shadow-lg border-[1px] border-indigo-800 rounded-md">
<label className="text-sm ml-2">
{t("subject")} <Tooltip message={t("email-subject")} />
Subject <Tooltip message={"email subject"} />
</label>
<input
required
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"
/>
<label className="text-sm ml-2 mt-3">
{t("body")} <Tooltip message={t("email-body")} />
Body <Tooltip message={"email body"} />
</label>
<div className="px-1 py-2 w-full focus:outline-none text-xs">
<div className="px-3 py-2 w-full focus:outline-none text-xs">
<EditorToolbar containerId="toolbar1" />
<ReactQuill
theme="snow"
value={props.requestBody}
placeholder={t("email-placeholder")}
// value={typedText}
placeholder="add body of email "
// readOnly={loading}
// onChangeSelection={handleTextSelection} // Listen for text selection
ref={props.editorRef}
modules={module1}
formats={formats}
@@ -1,53 +1,104 @@
import React, { useState } from "react";
import {
handleToPrint,
} from "../../constant/Utils";
import {
emailRegex,
} from "../../constant/const";
import axios from "axios";
import { handleDownloadPdf, 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,
setIsDownloadModal
extUserId,
activeMailAdapter
}) {
const { t } = useTranslation();
const [emailList, setEmailList] = useState([]);
const [emailValue, setEmailValue] = useState("");
const [isLoading, setIsLoading] = useState(false);
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);
console.log("sendmail ", sendmail);
if (sendmail?.status === "success") {
let sendMail;
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: pdfUrl,
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: "something went wrong"
});
}
}
if (sendMail && sendMail.data.result.status === "success") {
setSuccessEmail(true);
setIsEmail(false);
setTimeout(() => {
setSuccessEmail(false);
setIsEmail(false);
setEmailValue("");
setEmailList([]);
}, 1500);
setIsLoading(false);
}
else {
} else if (sendMail && sendMail.data.result.status === "error") {
setIsLoading(false);
setIsEmail(false);
setIsAlert({
isShow: true,
alertMessage: t("something-went-wrong-mssg")
alertMessage: "something went wrong"
});
setEmailValue("");
setEmailList([]);
} else {
setIsLoading(false);
setIsEmail(false);
setIsAlert({
isShow: true,
alertMessage: "something went wrong"
});
setEmailValue("");
setEmailList([]);
@@ -61,7 +112,7 @@ function EmailComponent({
};
//function for get email value
const handleEmailValue = (e) => {
const value = e.target.value?.toLowerCase()?.replace(/\s/g, "");
const value = e.target.value;
setEmailErr(false);
setEmailValue(value);
};
@@ -88,11 +139,6 @@ function EmailComponent({
}
}
};
const handleClose = () => {
setIsEmail(false);
setEmailValue("");
setEmailList([]);
};
return (
<div>
{/* isEmail */}
@@ -102,7 +148,7 @@ function EmailComponent({
<div className="absolute w-full h-full flex flex-col justify-center items-center z-[20] bg-[#e6f2f2]/70">
<Loader />
<span className="text-[12px] text-base-content">
{t("loader")}
This might take some time
</span>
</div>
)}
@@ -112,44 +158,41 @@ 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">
{t("successfully-signed")}
<span className="text-base-content font-semibold">
Successfully signed!
</span>
<div className="flex flex-row">
{!isAndroid && (
<button
onClick={(e) =>
handleToPrint(e, setIsDownloading, pdfDetails)
}
className="op-btn op-btn-neutral op-btn-sm text-xs md:text-[15px]"
onClick={(e) => handleToPrint(e, pdfUrl, setIsDownloading)}
className="op-btn op-btn-neutral op-btn-sm text-[15px]"
>
<i className="fa-light fa-print" aria-hidden="true"></i>
{t("print")}
Print
</button>
)}
<button
className="op-btn op-btn-primary op-btn-sm text-xs md:text-[15px] ml-2"
onClick={() => {
handleClose();
setIsDownloadModal(true);
}}
className="op-btn op-btn-primary op-btn-sm text-[15px] ml-2"
onClick={() =>
handleDownloadPdf(pdfDetails, pdfUrl, setIsDownloading)
}
>
<i className="fa-light fa-download" aria-hidden="true"></i>
{t("download")}
Download
</button>
</div>
</div>
<div className="h-full p-[20px]">
<p className="font-medium text-[15px] mb-[5px] text-base-content align-baseline">
{t("email-mssg")}
Recipients added here will get a copy of the signed document.
</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]">
@@ -169,14 +212,10 @@ 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")}
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
required
/>
)}
@@ -186,40 +225,54 @@ 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")}
placeholder="Add an email address and hit enter"
onBlur={() => emailValue && handleEnterPress("add")}
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
required
/>
</div>
)}
{emailErr && (
<p className="text-xs text-[red] ml-1.5 mt-0.5">
{t("email-error-1")}
please provide correct email address
</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">Note: </span>
<span className="text-[15px]">
You can only send to ten recipients at a time.
</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()}
>
Send
</button>
<button
type="button"
className="op-btn op-btn-ghost ml-2"
onClick={() => {
setIsEmail(false);
setEmailValue("");
setEmailList([]);
}}
>
Close
</button>
</div>
</ModalUi>
)}
@@ -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,477 @@
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";
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
}) {
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}
/>
{pdfUrl && 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={() =>
handleDownloadPdf(pdfDetails, pdfUrl, setIsDownloading)
}
>
<div className="flex flex-row">
<i
className="fa-light fa-arrow-down mr-[3px]"
aria-hidden="true"
></i>
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>
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>
Mail
</div>
</DropdownMenu.Item>
)}
<DropdownMenu.Item
className="DropdownMenuItem"
onClick={(e) =>
handleToPrint(e, pdfUrl, setIsDownloading)
}
>
<div className="flex flex-row">
<i
className="fa-light fa-print mr-[3px]"
aria-hidden="true"
></i>
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"
>
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 : "Send"}
</div>
) : (
<div
data-tut="reactourThird"
onClick={() => {
if (!pdfUrl) {
embedWidgetsData();
} else if (isPdfRequestFiles) {
embedWidgetsData();
}
}}
className="border-none font-[650] text-[14px] op-link op-link-primary no-underline"
>
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-full px-3 py-2"
sideOffset={5}
>
<DropdownMenu.Item
className="flex flex-row justify-center items-center text-[13px] focus:outline-none cursor-pointer"
onClick={() =>
handleDownloadPdf(
pdfDetails,
pdfUrl,
setIsDownloading
)
}
>
<i
className="fa-light fa-arrow-down mr-[3px]"
aria-hidden="true"
></i>
<span className="font-[500]">Download</span>
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu.Root>
</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]">
Add {signersdata.length - filterPrefill.length}{" "}
recipients signature
</span>
) : (
<span className="text-[13px] text-[#f5405e]">
Add {signersdata.length - filterPrefill.length} more
recipients 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]"
>
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
? "Sent"
: "Send"}
</button>
</div>
</>
) : isPdfRequestFiles ? (
alreadySign ? (
<div className="flex flex-row">
<button
onClick={(e) => handleToPrint(e, pdfUrl, setIsDownloading)}
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">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">Certificate</span>
</button>
)}
<button
type="button"
className="op-btn op-btn-primary op-btn-sm mr-[3px] shadow"
onClick={() =>
handleDownloadPdf(pdfDetails, pdfUrl, setIsDownloading)
}
>
<i
className="fa-light fa-download py-[3px]"
aria-hidden="true"
></i>
<span className="hidden lg:block">Download</span>
</button>
</div>
) : (
<div className="flex" data-tut="reactourFifth">
<button
onClick={() => window.history.go(-2)}
type="button"
className="op-btn op-btn-ghost op-btn-sm mr-[3px]"
>
Back
</button>
{currentSigner && (
<>
<button
className="op-btn op-btn-secondary op-btn-sm mr-[3px] shadow"
onClick={() => handleDeclinePdfAlert()}
>
Decline
</button>
<button
type="button"
className="op-btn op-btn-primary op-btn-sm mr-[3px] shadow"
onClick={() => embedWidgetsData()}
>
Finish
</button>
<button
type="button"
className="op-btn op-btn-neutral op-btn-sm mr-[3px] shadow"
onClick={() =>
handleDownloadPdf(pdfDetails, pdfUrl, setIsDownloading)
}
>
<i className="fa-light fa-arrow-down font-semibold lg:hidden"></i>
<span className="hidden lg:block">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">Certificate</span>
</button>
)}
<button
onClick={(e) => handleToPrint(e, pdfUrl, setIsDownloading)}
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">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={() =>
handleDownloadPdf(pdfDetails, pdfUrl, setIsDownloading)
}
>
<i className="fa-light fa-download" aria-hidden="true"></i>
<span className="hidden lg:block ml-1">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">Mail</span>
</button>
</div>
) : isPublicTemplate ? (
<div className="flex">
<button
type="button"
className="op-btn op-btn-primary op-btn-sm shadow"
onClick={() => embedWidgetsData()}
>
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]"
>
Back
</button>
<button
type="button"
className="op-btn op-btn-primary op-btn-sm mr-[3px]"
onClick={() => {
if (!pdfUrl) {
embedWidgetsData();
}
}}
>
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"
? "Generating certificate"
: "PDF Download"
}
handleClose={() => setIsDownloading("")}
>
<div className="p-3 md:p-5 text-[13px] md:text-base text-center text-base-content">
{isDownloading === "certificate"}{" "}
<p>
Your completion certificate is being generated. Please wait
momentarily. If the download doesn&apos;t start shortly, click the
button again.
</p>
</div>
</ModalUi>
</div>
);
}
export default Header;
@@ -1,728 +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 mb = Math.round(file?.size / Math.pow(1024, 2));
if (mb > maxFileSize) {
alert(`${t("file-alert-1")} ${maxFileSize} MB`);
removeFile(e);
return;
}
try {
let uploadedPdfBytes = await file.arrayBuffer();
try {
uploadedPdfBytes = await flattenPdf(uploadedPdfBytes);
} catch (err) {
if (err?.message?.includes("is encrypted")) {
try {
const pdfFile = await decryptPdf(file, "");
const pdfArrayBuffer = await getFileAsArrayBuffer(pdfFile);
uploadedPdfBytes = await flattenPdf(pdfArrayBuffer);
} catch (err) {
if (err?.response?.status === 401) {
const password = prompt(
`PDF "${file.name}" is password-protected. Enter password:`
);
if (password) {
try {
const pdfFile = await decryptPdf(file, password);
const pdfArrayBuffer = await getFileAsArrayBuffer(pdfFile);
uploadedPdfBytes = await flattenPdf(pdfArrayBuffer);
// Upload the file to Parse Server
} catch (err) {
console.error("Incorrect password or decryption failed", err);
alert(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);
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)}
>
<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,288 +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 mb = Math.round(file?.size / Math.pow(1024, 2));
if (mb > maxFileSize) {
alert(`${t("file-alert-1")} ${maxFileSize} MB`);
removeFile(e);
return;
}
try {
let uploadedPdfBytes = await file.arrayBuffer();
try {
uploadedPdfBytes = await flattenPdf(uploadedPdfBytes);
} catch (err) {
if (err?.message?.includes("is encrypted")) {
try {
const pdfFile = await decryptPdf(file, "");
const pdfArrayBuffer = await getFileAsArrayBuffer(pdfFile);
uploadedPdfBytes = await flattenPdf(pdfArrayBuffer);
} catch (err) {
if (err?.response?.status === 401) {
const password = prompt(
`PDF "${file.name}" is password-protected. Enter password:`
);
if (password) {
try {
const pdfFile = await decryptPdf(file, password);
const pdfArrayBuffer = await getFileAsArrayBuffer(pdfFile);
uploadedPdfBytes = await flattenPdf(pdfArrayBuffer);
// Upload the file to Parse Server
} catch (err) {
console.error("Incorrect password or decryption failed", err);
alert(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);
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,53 @@
import React from "react";
function PdfZoom(props) {
const onClickZoomIn = () => {
props.setScale(props.scale + 0.1 * props.scale);
props.setZoomPercent(props.zoomPercent + 10);
};
const onClickZoomOut = () => {
if (props.zoomPercent > 0) {
if (props.zoomPercent === 10) {
props.setScale(1);
} else {
props.setScale(props.scale - 0.1 * props.scale);
}
props.setZoomPercent(props.zoomPercent - 10);
}
};
const handleReset = () => {
props.setScale(1);
props.setZoomPercent(0);
};
return (
// md:mt-[41px] xl:mt-[63px]mt=[]
<span className="hidden md:flex flex-col gap-1 text-center md:w-[5%] mt-[50px] 2xl:mt-[63px]">
<span
className="bg-gray-50 px-[4px] 2xl:px-[15px] 2xl:py-[10px] cursor-pointer"
onClick={onClickZoomIn}
title="Zoom in"
>
<i className="fa-light fa-magnifying-glass-plus text-gray-500 2xl:text-[30px]"></i>
</span>
<span
className="bg-gray-50 px-[4px] 2xl:px-[15px] 2xl:py-[10px] cursor-pointer"
onClick={handleReset}
title="Reset"
>
<i className="fa-light fa-magnifying-glass-arrows-rotate text-gray-500 2xl:text-[30px]"></i>
</span>
<span
className="bg-gray-50 px-[4px] 2xl:px-[15px] 2xl:py-[10px]"
onClick={onClickZoomOut}
style={{
cursor: props.zoomPercent > 0 ? "pointer" : "default"
}}
title="Zoom out"
>
<i className="fa-light fa-magnifying-glass-minus text-gray-500 2xl:text-[30px]"></i>
</span>
</span>
);
}
export default PdfZoom;

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