mirror of
https://github.com/OpenSignLabs/OpenSign.git
synced 2026-09-04 00:38:57 +02:00
Compare commits
14
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1cc017bb05 | ||
|
|
531dd11185 | ||
|
|
1e80754750 | ||
|
|
0d286938c7 | ||
|
|
360c13df25 | ||
|
|
71b9bd6367 | ||
|
|
4a5b35e147 | ||
|
|
7930a8f439 | ||
|
|
7204183542 | ||
|
|
5561673fdc | ||
|
|
0df44062d6 | ||
|
|
8983440763 | ||
|
|
5a28c0ed06 | ||
|
|
cbca22c308 |
@@ -0,0 +1,213 @@
|
||||
# Tailwind Dark Mode Usage Guide
|
||||
|
||||
## Overview
|
||||
This guide shows how to use the new Tailwind utilities for better dark mode visibility in OpenSign.
|
||||
|
||||
## Button Styling
|
||||
|
||||
### VS Code-style Disabled Buttons
|
||||
```jsx
|
||||
// Option 1: Direct VS Code styling
|
||||
<button className="op-btn op-btn-primary op-btn-vscode-disabled" disabled>
|
||||
Disabled Button
|
||||
</button>
|
||||
|
||||
// Option 2: Themed disabled styling
|
||||
<button className="op-btn btn-themed-disabled">
|
||||
Themed Button
|
||||
</button>
|
||||
|
||||
// Option 3: Conditional styling
|
||||
<button
|
||||
className={`op-btn op-btn-primary ${isDisabled ? 'op-btn-vscode-disabled' : ''}`}
|
||||
disabled={isDisabled}
|
||||
>
|
||||
Dynamic Button
|
||||
</button>
|
||||
```
|
||||
|
||||
## Icon Styling
|
||||
|
||||
### Theme-aware Icons
|
||||
```jsx
|
||||
// Better visibility in dark mode
|
||||
<i className="fa-light fa-folder icon-improved"></i>
|
||||
|
||||
// Muted but still visible
|
||||
<i className="fa-light fa-plus icon-muted"></i>
|
||||
|
||||
// Disabled state
|
||||
<i className="fa-light fa-trash icon-disabled"></i>
|
||||
```
|
||||
|
||||
### CSS Variable Approach
|
||||
```jsx
|
||||
// Using CSS variables
|
||||
<i className="fa-light fa-search icon-themed"></i>
|
||||
<i className="fa-light fa-settings icon-themed-muted"></i>
|
||||
|
||||
// Inline styles with CSS variables
|
||||
<i
|
||||
className="fa-light fa-plus"
|
||||
style={{ color: 'var(--icon-color)' }}
|
||||
/>
|
||||
```
|
||||
|
||||
### Legacy JavaScript Function (Still Supported)
|
||||
```jsx
|
||||
// Existing approach - still works
|
||||
<i
|
||||
className="fa-light fa-plus"
|
||||
style={{ color: getThemeIconColor() }}
|
||||
/>
|
||||
```
|
||||
|
||||
## Text Styling
|
||||
|
||||
### Improved Gray Text
|
||||
```jsx
|
||||
// These automatically improve in dark mode
|
||||
<span className="text-gray-500">More visible in dark mode</span>
|
||||
<span className="text-gray-400">Muted but readable</span>
|
||||
<span className="text-gray-600">Clear text</span>
|
||||
```
|
||||
|
||||
## Complete Examples
|
||||
|
||||
### Toolbar with Better Visibility
|
||||
```jsx
|
||||
const Toolbar = () => (
|
||||
<div className="flex space-x-2 p-2">
|
||||
<button className="p-2 hover:bg-gray-200 rounded">
|
||||
<i className="fa-light fa-plus icon-improved"></i>
|
||||
</button>
|
||||
<button className="p-2 hover:bg-gray-200 rounded" disabled>
|
||||
<i className="fa-light fa-trash icon-disabled"></i>
|
||||
</button>
|
||||
<button className="p-2 hover:bg-gray-200 rounded">
|
||||
<i className="fa-light fa-edit icon-improved"></i>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
```
|
||||
|
||||
### Form with Disabled States
|
||||
```jsx
|
||||
const Form = ({ isSubmitting }) => (
|
||||
<form>
|
||||
<input className="op-input" />
|
||||
<button
|
||||
className={`op-btn op-btn-primary ${isSubmitting ? 'op-btn-vscode-disabled' : ''}`}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
{isSubmitting ? 'Submitting...' : 'Submit'}
|
||||
</button>
|
||||
</form>
|
||||
);
|
||||
```
|
||||
|
||||
## React-Tour and Tooltip Dark Mode Support
|
||||
|
||||
### React-Tour Modals
|
||||
The react-tour modals now automatically support dark mode with VS Code-inspired styling:
|
||||
|
||||
```jsx
|
||||
// These components automatically get dark mode styling
|
||||
<Tour
|
||||
onRequestClose={closeTour}
|
||||
steps={tourConfig}
|
||||
isOpen={isOpen}
|
||||
rounded={5}
|
||||
/>
|
||||
```
|
||||
|
||||
### ReactTooltip Components
|
||||
All ReactTooltip instances now support dark mode:
|
||||
|
||||
```jsx
|
||||
// Automatically styled for dark mode
|
||||
<ReactTooltip id="my-tooltip" className="z-[999]">
|
||||
<div className="max-w-[200px]">
|
||||
<p>Tooltip content</p>
|
||||
</div>
|
||||
</ReactTooltip>
|
||||
```
|
||||
|
||||
### HoverCard Balloon UI
|
||||
The balloon tooltips in OpenSign Drive now properly support dark mode:
|
||||
|
||||
```jsx
|
||||
// These automatically get dark styling in dark mode
|
||||
<HoverCard>
|
||||
<HoverCardContent>
|
||||
Document information
|
||||
</HoverCardContent>
|
||||
</HoverCard>
|
||||
```
|
||||
|
||||
## Dark Mode Features Added
|
||||
|
||||
### 1. **React-Tour Modal Styling**
|
||||
- Background: `#1F2937` (VS Code modal background)
|
||||
- Text: `#E5E7EB` (soft white for readability)
|
||||
- Borders: `#374151` (subtle borders)
|
||||
- Buttons: VS Code-style primary/secondary buttons
|
||||
|
||||
### 2. **ReactTooltip Styling**
|
||||
- Background: `#1F2937` with proper contrast
|
||||
- Border: `#374151` for definition
|
||||
- Box shadow: Enhanced for dark backgrounds
|
||||
- Text: `#E5E7EB` for optimal readability
|
||||
|
||||
### 3. **HoverCard Balloon UI**
|
||||
- Background: `#1F2937` (matches VS Code)
|
||||
- Text: `#E5E7EB` for readability
|
||||
- Arrow: Automatically matches background color
|
||||
- Enhanced shadows for dark backgrounds
|
||||
|
||||
### 4. **React-Datepicker Support**
|
||||
- Calendar background: `#1F2937`
|
||||
- Selected dates: VS Code blue (`#007ACC`)
|
||||
- Hover states: Proper contrast ratios
|
||||
- Navigation arrows: Themed appropriately
|
||||
|
||||
## CSS Classes Reference
|
||||
|
||||
| Class | Purpose | Dark Mode Color |
|
||||
|-------|---------|----------------|
|
||||
| `icon-improved` | Better icon visibility | `#CCCCCC` |
|
||||
| `icon-muted` | Muted but visible icons | `#999999` |
|
||||
| `icon-disabled` | Disabled icon state | `#858585` |
|
||||
| `op-btn-vscode-disabled` | VS Code disabled button | Background: `#3C3C3C` |
|
||||
| `btn-themed-disabled` | Themed disabled button | Uses CSS variables |
|
||||
| `icon-themed` | Variable-based icon color | `var(--icon-color)` |
|
||||
| `.reactour__helper` | `#1F2937` background |
|
||||
| `.react-tooltip` | `#1F2937` background |
|
||||
| `.HoverCardContent` | `#1F2937` background |
|
||||
| `.react-datepicker` | `#1F2937` background |
|
||||
|
||||
## Migration Guide
|
||||
|
||||
### From JavaScript Function to Tailwind
|
||||
```jsx
|
||||
// Before
|
||||
<i style={{ color: getThemeIconColor() }} className="fa-light fa-plus" />
|
||||
|
||||
// After
|
||||
<i className="fa-light fa-plus icon-improved" />
|
||||
```
|
||||
|
||||
### From Hardcoded Colors to Theme-aware
|
||||
```jsx
|
||||
// Before
|
||||
<i className="fa-light fa-plus text-gray-500" />
|
||||
|
||||
// After (automatic improvement)
|
||||
<i className="fa-light fa-plus text-gray-500" />
|
||||
// OR explicitly
|
||||
<i className="fa-light fa-plus icon-improved" />
|
||||
```
|
||||
|
||||
## Migration Notes
|
||||
|
||||
All existing tooltip and tour components will automatically inherit the new dark mode styling when the theme is set to `opensigndark`. No code changes required for existing implementations.
|
||||
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* Tailwind Dark Mode Usage Examples for OpenSign
|
||||
*
|
||||
* This file demonstrates how to use the new Tailwind utilities
|
||||
* for better dark mode visibility of buttons and icons.
|
||||
*/
|
||||
|
||||
// Example 1: VS Code-style disabled buttons
|
||||
const DisabledButtonExamples = () => {
|
||||
return (
|
||||
<div>
|
||||
{/* Option A: Using the VS Code disabled style */}
|
||||
<button className="op-btn op-btn-primary op-btn-vscode-disabled" disabled>
|
||||
VS Code Style Disabled Button
|
||||
</button>
|
||||
|
||||
{/* Option B: Using themed disabled style */}
|
||||
<button className="op-btn btn-themed-disabled">
|
||||
Themed Disabled Button
|
||||
</button>
|
||||
|
||||
{/* Option C: Conditional styling */}
|
||||
<button
|
||||
className={`op-btn op-btn-primary ${
|
||||
isDisabled ? "op-btn-vscode-disabled" : ""
|
||||
}`}
|
||||
disabled={isDisabled}
|
||||
>
|
||||
Conditional Button
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Example 2: Icon visibility improvements
|
||||
const IconExamples = () => {
|
||||
return (
|
||||
<div>
|
||||
{/* Theme-aware icons with better visibility */}
|
||||
<i className="fa-light fa-folder icon-improved"></i>
|
||||
<i className="fa-light fa-plus icon-muted"></i>
|
||||
<i className="fa-light fa-trash icon-disabled"></i>
|
||||
|
||||
{/* Using CSS variables */}
|
||||
<i className="fa-light fa-search icon-themed"></i>
|
||||
<i className="fa-light fa-settings icon-themed-muted"></i>
|
||||
|
||||
{/* Gray text that automatically improves in dark mode */}
|
||||
<span className="text-gray-500">
|
||||
This text is now more visible in dark mode
|
||||
</span>
|
||||
<span className="text-gray-400">Muted but still readable</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Example 3: Using CSS variables in inline styles
|
||||
const InlineStyleExamples = () => {
|
||||
return (
|
||||
<div>
|
||||
{/* Using CSS variables directly */}
|
||||
<i className="fa-light fa-plus" style={{ color: "var(--icon-color)" }} />
|
||||
|
||||
{/* Using the existing JavaScript function */}
|
||||
<i className="fa-light fa-minus" style={{ color: getThemeIconColor() }} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Example 4: Toolbar with improved icons
|
||||
const ToolbarExample = () => {
|
||||
return (
|
||||
<div className="flex space-x-2 p-2">
|
||||
<button className="p-2 hover:bg-gray-200 rounded">
|
||||
<i className="fa-light fa-plus icon-improved"></i>
|
||||
</button>
|
||||
<button className="p-2 hover:bg-gray-200 rounded" disabled>
|
||||
<i className="fa-light fa-trash icon-disabled"></i>
|
||||
</button>
|
||||
<button className="p-2 hover:bg-gray-200 rounded">
|
||||
<i className="fa-light fa-edit icon-improved"></i>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export {
|
||||
DisabledButtonExamples,
|
||||
IconExamples,
|
||||
InlineStyleExamples,
|
||||
ToolbarExample
|
||||
};
|
||||
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* Tailwind Dark Mode Usage Examples for OpenSign
|
||||
*
|
||||
* This file demonstrates how to use the new Tailwind utilities
|
||||
* for better dark mode visibility of buttons and icons.
|
||||
*/
|
||||
|
||||
// Example 1: VS Code-style disabled buttons
|
||||
const DisabledButtonExamples = () => {
|
||||
return (
|
||||
<div>
|
||||
{/* Option A: Using the VS Code disabled style */}
|
||||
<button className="op-btn op-btn-primary op-btn-vscode-disabled" disabled>
|
||||
VS Code Style Disabled Button
|
||||
</button>
|
||||
|
||||
{/* Option B: Using themed disabled style */}
|
||||
<button className="op-btn btn-themed-disabled">
|
||||
Themed Disabled Button
|
||||
</button>
|
||||
|
||||
{/* Option C: Conditional styling */}
|
||||
<button
|
||||
className={`op-btn op-btn-primary ${isDisabled ? 'op-btn-vscode-disabled' : ''}`}
|
||||
disabled={isDisabled}
|
||||
>
|
||||
Conditional Button
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Example 2: Icon visibility improvements
|
||||
const IconExamples = () => {
|
||||
return (
|
||||
<div>
|
||||
{/* Theme-aware icons with better visibility */}
|
||||
<i className="fa-light fa-folder icon-improved"></i>
|
||||
<i className="fa-light fa-plus icon-muted"></i>
|
||||
<i className="fa-light fa-trash icon-disabled"></i>
|
||||
|
||||
{/* Using CSS variables */}
|
||||
<i className="fa-light fa-search icon-themed"></i>
|
||||
<i className="fa-light fa-settings icon-themed-muted"></i>
|
||||
|
||||
{/* Gray text that automatically improves in dark mode */}
|
||||
<span className="text-gray-500">This text is now more visible in dark mode</span>
|
||||
<span className="text-gray-400">Muted but still readable</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Example 3: Using CSS variables in inline styles
|
||||
const InlineStyleExamples = () => {
|
||||
return (
|
||||
<div>
|
||||
{/* Using CSS variables directly */}
|
||||
<i
|
||||
className="fa-light fa-plus"
|
||||
style={{ color: 'var(--icon-color)' }}
|
||||
/>
|
||||
|
||||
{/* Using the existing JavaScript function */}
|
||||
<i
|
||||
className="fa-light fa-minus"
|
||||
style={{ color: getThemeIconColor() }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Example 4: Toolbar with improved icons
|
||||
const ToolbarExample = () => {
|
||||
return (
|
||||
<div className="flex space-x-2 p-2">
|
||||
<button className="p-2 hover:bg-gray-200 rounded">
|
||||
<i className="fa-light fa-plus icon-improved"></i>
|
||||
</button>
|
||||
<button className="p-2 hover:bg-gray-200 rounded" disabled>
|
||||
<i className="fa-light fa-trash icon-disabled"></i>
|
||||
</button>
|
||||
<button className="p-2 hover:bg-gray-200 rounded">
|
||||
<i className="fa-light fa-edit icon-improved"></i>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export {
|
||||
DisabledButtonExamples,
|
||||
IconExamples,
|
||||
InlineStyleExamples,
|
||||
ToolbarExample
|
||||
};
|
||||
Generated
+311
-265
File diff suppressed because it is too large
Load Diff
+22
-21
@@ -5,15 +5,15 @@
|
||||
"dependencies": {
|
||||
"@formkit/auto-animate": "^0.8.2",
|
||||
"@imgly/background-removal": "^1.6.0",
|
||||
"@lottiefiles/dotlottie-react": "^0.14.0",
|
||||
"@lottiefiles/dotlottie-react": "^0.14.2",
|
||||
"@pdf-lib/fontkit": "^1.1.1",
|
||||
"@radix-ui/themes": "^3.2.1",
|
||||
"@reduxjs/toolkit": "^2.8.2",
|
||||
"axios": "^1.9.0",
|
||||
"axios": "^1.10.0",
|
||||
"date-fns-tz": "^3.2.0",
|
||||
"file-saver": "^2.0.5",
|
||||
"i18next": "^23.16.8",
|
||||
"i18next-browser-languagedetector": "^8.1.0",
|
||||
"i18next": "^25.3.0",
|
||||
"i18next-browser-languagedetector": "^8.2.0",
|
||||
"i18next-http-backend": "^3.0.2",
|
||||
"jszip": "^3.10.1",
|
||||
"jwt-decode": "^4.0.0",
|
||||
@@ -23,6 +23,7 @@
|
||||
"pkijs": "^3.0.8",
|
||||
"print-js": "^1.6.0",
|
||||
"prismjs": "^1.30.0",
|
||||
"quill-html-edit-button": "^3.0.0",
|
||||
"radix-ui": "^1.4.2",
|
||||
"react": "^18.3.1",
|
||||
"react-bootstrap": "^2.10.10",
|
||||
@@ -35,24 +36,24 @@
|
||||
"react-dom": "^18.3.1",
|
||||
"react-gtm-module": "^2.0.11",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-i18next": "^15.5.2",
|
||||
"react-i18next": "^15.5.3",
|
||||
"react-konva": "^18.2.10",
|
||||
"react-pdf": "^9.2.1",
|
||||
"react-quill-new": "^3.4.6",
|
||||
"react-redux": "^9.2.0",
|
||||
"react-rnd": "^10.5.2",
|
||||
"react-router": "^7.6.1",
|
||||
"react-router": "^7.6.3",
|
||||
"react-scrollbars-custom": "^4.1.1",
|
||||
"react-select": "^5.10.1",
|
||||
"react-signature-canvas": "^1.1.0-alpha.2",
|
||||
"react-timezone-select": "^3.2.8",
|
||||
"react-tooltip": "^5.28.1",
|
||||
"react-tooltip": "^5.29.1",
|
||||
"reactour": "^1.19.4",
|
||||
"redux": "^5.0.1",
|
||||
"regex-parser": "^2.3.1",
|
||||
"serve": "^14.2.4",
|
||||
"styled-components": "^5.3.11",
|
||||
"web-vitals": "^5.0.2",
|
||||
"web-vitals": "^5.0.3",
|
||||
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz"
|
||||
},
|
||||
"scripts": {
|
||||
@@ -93,38 +94,38 @@
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.27.4",
|
||||
"@babel/core": "^7.27.7",
|
||||
"@babel/preset-env": "^7.27.2",
|
||||
"@babel/preset-react": "^7.27.1",
|
||||
"@babel/runtime-corejs2": "^7.27.4",
|
||||
"@babel/runtime-corejs2": "^7.27.6",
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
"@testing-library/react": "^16.3.0",
|
||||
"@testing-library/user-event": "^14.6.1",
|
||||
"@types/react": "^18.3.23",
|
||||
"@vitejs/plugin-react": "^4.5.1",
|
||||
"@vitejs/plugin-react-swc": "^3.10.1",
|
||||
"@vitest/ui": "^3.2.0",
|
||||
"@vitejs/plugin-react": "^4.6.0",
|
||||
"@vitejs/plugin-react-swc": "^3.10.2",
|
||||
"@vitest/ui": "^3.2.4",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"babel-loader": "^10.0.0",
|
||||
"commitizen": "^4.3.1",
|
||||
"concurrently": "^9.1.2",
|
||||
"concurrently": "^9.2.0",
|
||||
"css-loader": "^7.1.2",
|
||||
"daisyui": "^4.12.24",
|
||||
"dotenv": "^16.5.0",
|
||||
"eslint": "^9.28.0",
|
||||
"eslint-plugin-prettier": "^5.4.1",
|
||||
"dotenv": "^16.6.1",
|
||||
"eslint": "^9.30.0",
|
||||
"eslint-plugin-prettier": "^5.5.1",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"jsdom": "^26.1.0",
|
||||
"lint-staged": "^16.1.0",
|
||||
"postcss": "^8.5.4",
|
||||
"prettier": "^3.5.3",
|
||||
"lint-staged": "^16.1.2",
|
||||
"postcss": "^8.5.6",
|
||||
"prettier": "^3.6.2",
|
||||
"pretty-quick": "^4.2.2",
|
||||
"rollup-plugin-node-polyfills": "^0.2.1",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"vite": "^6.3.5",
|
||||
"vite-plugin-svgr": "^4.3.0",
|
||||
"vite-tsconfig-paths": "^5.1.4",
|
||||
"vitest": "^3.2.0"
|
||||
"vitest": "^3.2.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": "18 || 20 || 22"
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"create-account": "Konto erstellen",
|
||||
"login": "Anmelden",
|
||||
"language": "Sprache",
|
||||
"dark-mode": "Dunkelmodus",
|
||||
"name": "Name",
|
||||
"phone": "Telefon",
|
||||
"phone-optional": "optional",
|
||||
@@ -361,6 +362,7 @@
|
||||
"date": "Datum",
|
||||
"text": "Text",
|
||||
"text input": "Texteingabe",
|
||||
"cells": "Zellen",
|
||||
"checkbox": "Checkbox",
|
||||
"dropdown": "Dropdown",
|
||||
"radio button": "Radiobutton",
|
||||
@@ -415,10 +417,12 @@
|
||||
"options": "Optionen",
|
||||
"minimun-check": "Minimale Anzahl",
|
||||
"maximum-check": "Maximale Anzahl",
|
||||
"cell-count": "Zellzahl",
|
||||
"default-value": "Standardwert",
|
||||
"select": "Auswählen",
|
||||
"read-only": "Nur lesen",
|
||||
"read-only": "Ist schreibgeschützt",
|
||||
"hide-labels": "Labels ausblenden",
|
||||
"layout": "Layout",
|
||||
"checkbox": "Checkbox",
|
||||
"alert": "Warnung",
|
||||
"zoom-in": "Vergrößern",
|
||||
@@ -750,12 +754,15 @@
|
||||
"delete-page": "Seite löschen",
|
||||
"merge-pdf": "PDFs zusammenführen",
|
||||
"add-pages": "Seiten hinzufügen",
|
||||
"reorder-pages": "Seiten neu anordnen",
|
||||
"delete-alert": "Einzelne Seite kann nicht gelöscht werden.",
|
||||
"delete-alert-2": "Sind Sie sicher, dass Sie diese Seite löschen möchten?",
|
||||
"delete-note": "Hinweis: Sobald Sie diese Seite löschen, kann dies nicht rückgängig gemacht werden.",
|
||||
"Rotation-alert": "Seite drehen",
|
||||
"bulk-import": "Massenimport",
|
||||
"contacts-file": "Kontaktdatei (xlsx, csv)",
|
||||
"import-guideline": "Laden Sie eine CSV- oder Excel-Datei mit den Spalten Name, Email und optional Phone hoch. Es werden nur die ersten 100 Kontakte importiert.",
|
||||
"download-sample": "Beispieldatei herunterladen",
|
||||
"100-records-only": "Derzeit können Sie nur bis zu 100 Datensätze importieren.",
|
||||
"csv-excel-support-only": "Laden Sie eine Datei in einem der folgenden Formate hoch: CSV, XLSX oder XLS.",
|
||||
"contact-imported": "{{imported}} Kontakte wurden importiert. {{failed}} Kontakte konnten nicht importiert werden.",
|
||||
@@ -982,6 +989,7 @@
|
||||
"review": "Überprüfen",
|
||||
"next-field": "Nächstes Feld",
|
||||
"required-mssg": "{{leftRequiredWidget}} von {{totalWidget}} Feldern übrig",
|
||||
"verify-document": "Dokument verifizieren",
|
||||
"verify-document-signature": "Dokumentensignatur überprüfen",
|
||||
"select-pdf-document": "PDF-Dokument auswählen",
|
||||
"selected-file": "Ausgewählte Datei",
|
||||
@@ -1023,7 +1031,15 @@
|
||||
"could-not-parse-signer-info": "Signaturinformationen konnten nicht analysiert werden",
|
||||
"not-calculated": "Nicht berechnet",
|
||||
"not-found-in-signature": "Nicht in Signatur gefunden",
|
||||
"readonly-textinput-error": "Schreibgeschütztes Text-Widget muss einen Standardwert haben oder optional sein.",
|
||||
"readonly-dropdown-error": "Schreibgeschütztes Dropdown-Widget muss einen Standardwert haben oder optional sein.",
|
||||
"readonly-radiobtn-error": "Schreibgeschütztes Optionsfeld-Widget muss einen Standardwert haben oder optional sein."
|
||||
"readonly-error": "Das schreibgeschützte {{widgetName}}-Widget muss einen Standardwert haben oder kann optional gemacht werden.",
|
||||
"choose-one":"Wählen Sie eine aus",
|
||||
"search-templates": "Vorlagen durchsuchen…",
|
||||
"search-documents": "Dokumente suchen…",
|
||||
"search-contacts": "Kontakte durchsuchen…",
|
||||
"invalid-email-found": "Ungültige E-Mail gefunden: {{email}}",
|
||||
"duplicate-email-found": "Doppelte E-Mail gefunden: {{email}}",
|
||||
"vertical": "Vertikal",
|
||||
"horizontal": "Horizontal",
|
||||
"billing": "Abrechnung",
|
||||
"console": "Konsole"
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"create-account": "Create account",
|
||||
"login": "Login",
|
||||
"language": "Language",
|
||||
"dark-mode": "Dark mode",
|
||||
"name": "Name",
|
||||
"phone": "Phone",
|
||||
"phone-optional": "optional",
|
||||
@@ -199,7 +200,7 @@
|
||||
},
|
||||
"file-type": "pdf, png, jpg, jpeg",
|
||||
"docx": "docx",
|
||||
"file-selected": "file selected",
|
||||
"file-selected": "file(s) selected",
|
||||
"template-title": "Template title",
|
||||
"document-title": "Document title",
|
||||
"description": "Description",
|
||||
@@ -361,6 +362,7 @@
|
||||
"date": "date",
|
||||
"text": "text",
|
||||
"text input": "text input",
|
||||
"cells": "cells",
|
||||
"checkbox": "checkbox",
|
||||
"dropdown": "dropdown",
|
||||
"radio button": "radio button",
|
||||
@@ -415,10 +417,12 @@
|
||||
"options": "Options",
|
||||
"minimun-check": "Minimun check",
|
||||
"maximum-check": "Maximum check",
|
||||
"cell-count": "Cell count",
|
||||
"default-value": "Default value",
|
||||
"select": "Select",
|
||||
"read-only": "Is read only",
|
||||
"read-only": "read only",
|
||||
"hide-labels": "Hide labels",
|
||||
"layout": "Layout",
|
||||
"checkbox": "Checkbox",
|
||||
"alert": "Alert",
|
||||
"zoom-in": "Zoom in",
|
||||
@@ -526,7 +530,7 @@
|
||||
"new-password": "New password",
|
||||
"confirm-password": "Confirm password",
|
||||
"file-alert-1": "The selected file size is too large. Please select a file less than",
|
||||
"file-alert-2": "Please select file.",
|
||||
"file-alert-2": "Please select file(s).",
|
||||
"file-alert-3": "Please wait while the document is being uploaded.",
|
||||
"enter-pdf-password": "Enter Pdf password",
|
||||
"correct-password": "Please provide correct password",
|
||||
@@ -750,12 +754,15 @@
|
||||
"delete-page": "Delete page",
|
||||
"merge-pdf": "Merge pdf",
|
||||
"add-pages": "Add pages",
|
||||
"reorder-pages": "Reorder pages",
|
||||
"delete-alert": "Can not delete single page",
|
||||
"delete-alert-2": "Are you sure you want to delete this page?",
|
||||
"delete-note": "Note: Once you delete this page, you cannot undo.",
|
||||
"Rotation-alert": "Rotate page",
|
||||
"bulk-import": "Bulk import",
|
||||
"contacts-file": "Contacts file (xlsx, csv)",
|
||||
"import-guideline": "Upload a CSV or Excel file with columns Name, Email and optional Phone. Only the first 100 records will be imported.",
|
||||
"download-sample": "Download sample file",
|
||||
"100-records-only": "Currently you can only import up to 100 records.",
|
||||
"csv-excel-support-only": "Upload a file in one of the following formats: CSV, XLSX or XLS.",
|
||||
"contact-imported": "{{imported}} contacts were imported. {{failed}} contacts failed to import.",
|
||||
@@ -885,7 +892,7 @@
|
||||
"you-will-receive-email-shortly": "✅ That's it! You'll receive a confirmation email shortly.",
|
||||
"please-provide-templateid": "Please provide templateid",
|
||||
"this-template-is-not-public": "This template is not public",
|
||||
"invalid-templateid": "Invaldi templateid",
|
||||
"invalid-templateid": "Invalid templateid",
|
||||
"contact-billing-at-opensign": "To add more seats, please contact OpenSign™ at <1>billing@opensignlabs.com</1> for assistance",
|
||||
"title-length-alert": "Title must be at most 250 characters long.",
|
||||
"note-length-alert": "Note must be at most 200 characters long.",
|
||||
@@ -982,6 +989,7 @@
|
||||
"review":"Review",
|
||||
"next-field":"Next Field",
|
||||
"required-mssg":"{{leftRequiredWidget}} of {{totalWidget}} fields left",
|
||||
"verify-document": "Verify document",
|
||||
"verify-document-signature": "Verify Document Signature",
|
||||
"select-pdf-document": "Select PDF Document",
|
||||
"selected-file": "Selected file",
|
||||
@@ -1023,7 +1031,15 @@
|
||||
"could-not-parse-signer-info": "Could not parse signer info",
|
||||
"not-calculated": "Not calculated",
|
||||
"not-found-in-signature": "Not found in signature",
|
||||
"readonly-textinput-error": "Read-only text widget must have a default value or you can make it optional.",
|
||||
"readonly-dropdown-error": "Read-only dropdown widget must have a default value or you can make it optional.",
|
||||
"readonly-radiobtn-error": "Read-only radio button widget must have a default value or you can make it optional."
|
||||
"readonly-error": "Read-only {{widgetName}} widget must have a default value or you can make it optional.",
|
||||
"choose-one":"Choose One",
|
||||
"search-templates": "Search templates…",
|
||||
"search-documents": "Search documents…",
|
||||
"search-contacts": "Search contacts…",
|
||||
"invalid-email-found": "Invalid email found: {{email}}",
|
||||
"duplicate-email-found": "Duplicate email found: {{email}}",
|
||||
"vertical": "Vertical",
|
||||
"horizontal": "Horizontal",
|
||||
"billing": "Billing",
|
||||
"console": "Console"
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
"create-account": "Crear cuenta",
|
||||
"login": "Iniciar sesión",
|
||||
"language": "Idioma",
|
||||
"dark-mode": "Modo oscuro",
|
||||
"name": "Nombre",
|
||||
"phone": "Teléfono",
|
||||
"phone-optional": "opcional",
|
||||
@@ -362,6 +363,7 @@
|
||||
"date": "fecha",
|
||||
"text": "texto",
|
||||
"text input": "entrada de texto",
|
||||
"cells": "células",
|
||||
"checkbox": "casilla",
|
||||
"dropdown": "desplegable",
|
||||
"radio button": "botón de radio",
|
||||
@@ -416,10 +418,12 @@
|
||||
"options": "Opciones",
|
||||
"minimun-check": "Chequeo mínimo",
|
||||
"maximum-check": "Chequeo máximo",
|
||||
"cell-count": "recuento de células",
|
||||
"default-value": "Valor por defecto",
|
||||
"select": "Seleccionar",
|
||||
"read-only": "Es de solo lectura",
|
||||
"hide-labels": "Esconder etiquetas",
|
||||
"layout": "Diseño",
|
||||
"checkbox": "Casilla",
|
||||
"alert": "Alerta",
|
||||
"zoom-in": "Acercar",
|
||||
@@ -750,12 +754,15 @@
|
||||
"delete-page": "eliminar página",
|
||||
"merge-pdf": "fusionar pdf",
|
||||
"add-pages": "Agregar páginas",
|
||||
"reorder-pages": "Reordenar páginas",
|
||||
"delete-alert": "No se puede eliminar una sola página",
|
||||
"delete-alert-2": "¿Está seguro de que desea eliminar esta página?",
|
||||
"delete-note": "Nota: una vez que elimines esta página, no podrás deshacerla",
|
||||
"Rotation-alert": "Girar página",
|
||||
"bulk-import": "Importación masiva",
|
||||
"contacts-file": "Archivo de contactos (xlsx, csv)",
|
||||
"import-guideline": "Sube un archivo CSV o Excel con las columnas Name, Email y opcionalmente Phone. Solo se importarán los primeros 100 contactos.",
|
||||
"download-sample": "Descargar archivo de ejemplo",
|
||||
"100-records-only": "Currently you can only import up to 100 records.",
|
||||
"csv-excel-support-only": "Cargue un archivo en uno de los siguientes formatos: CSV, XLSX o XLS.",
|
||||
"contact-imported": "Se importaron {{imported}} contactos. {{failed}} contactos no pudieron importarse.",
|
||||
@@ -982,6 +989,7 @@
|
||||
"review": "Revisar",
|
||||
"next-field": "Siguiente campo",
|
||||
"required-mssg": "{{leftRequiredWidget}} de {{totalWidget}} campos restantes",
|
||||
"verify-document": "Verificar documento",
|
||||
"verify-document-signature": "Verificar firma del documento",
|
||||
"select-pdf-document": "Seleccionar documento PDF",
|
||||
"selected-file": "Archivo seleccionado",
|
||||
@@ -1023,7 +1031,15 @@
|
||||
"could-not-parse-signer-info": "No se pudo analizar la información del firmante",
|
||||
"not-calculated": "No calculado",
|
||||
"not-found-in-signature": "No encontrado en la firma",
|
||||
"readonly-textinput-error": "El widget de texto de solo lectura debe tener un valor predeterminado o puede hacerse opcional.",
|
||||
"readonly-dropdown-error": "El widget desplegable de solo lectura debe tener un valor predeterminado o puede hacerse opcional.",
|
||||
"readonly-radiobtn-error": "El widget de botón de opción de solo lectura debe tener un valor predeterminado o puede hacerse opcional."
|
||||
"readonly-error": "El widget de solo lectura {{widgetName}} debe tener un valor predeterminado o puede hacerlo opcional.",
|
||||
"choose-one":"Elige uno",
|
||||
"search-templates": "Buscar plantillas…",
|
||||
"search-documents": "Buscar documentos…",
|
||||
"search-contacts": "Buscar contactos…",
|
||||
"invalid-email-found": "Correo electrónico no válido encontrado: {{email}}",
|
||||
"duplicate-email-found": "Correo electrónico duplicado encontrado: {{email}}",
|
||||
"vertical": "Vertical",
|
||||
"horizontal": "Horizontal",
|
||||
"billing": "Facturación",
|
||||
"console": "Consola"
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"create-account": "Créer un compte",
|
||||
"login": "Se Connecter",
|
||||
"language": "Langue",
|
||||
"dark-mode": "Mode sombre",
|
||||
"name": "Nom et Prénom",
|
||||
"phone": "Téléphone",
|
||||
"phone-optional": "(facultatif)",
|
||||
@@ -361,6 +362,7 @@
|
||||
"date": "date",
|
||||
"text": "texte",
|
||||
"text input": "saisie de texte",
|
||||
"cells": "cellules",
|
||||
"checkbox": "case à cocher",
|
||||
"dropdown": "dérouler",
|
||||
"radio button": "bouton radio",
|
||||
@@ -415,10 +417,12 @@
|
||||
"options": "Possibilités",
|
||||
"minimun-check": "Vérification minimale",
|
||||
"maximum-check": "Contrôle maximum",
|
||||
"cell-count": "numération cellulaire",
|
||||
"default-value": "Valeur par défaut",
|
||||
"select": "Sélectionner",
|
||||
"read-only": "Est en lecture seule",
|
||||
"hide-labels": "Masquer les étiquettes",
|
||||
"layout": "Disposition",
|
||||
"checkbox": "Case à cocher",
|
||||
"alert": "Alerte",
|
||||
"zoom-in": "Agrandir",
|
||||
@@ -750,12 +754,15 @@
|
||||
"delete-page": "supprimer la page",
|
||||
"merge-pdf": "Fusionner le pdf",
|
||||
"add-pages": "Ajouter des pages",
|
||||
"reorder-pages": "Réorganiser les pages",
|
||||
"delete-alert": "Impossible de supprimer une seule page",
|
||||
"delete-alert-2": "Etes-vous sûr de vouloir supprimer cette page ?",
|
||||
"delete-note": "Remarque : une fois cette page supprimée, vous ne pouvez plus l'annuler",
|
||||
"Rotation-alert": "Faire pivoter la page",
|
||||
"bulk-import": "Importation en masse",
|
||||
"contacts-file": "Fichier de contacts (xlsx, csv)",
|
||||
"import-guideline": "Téléchargez un fichier CSV ou Excel avec les colonnes Name, Email et éventuellement Phone. Seuls les 100 premiers contacts seront importés.",
|
||||
"download-sample": "Télécharger un fichier d'exemple",
|
||||
"100-records-only": "Actuellement, vous ne pouvez importer que 100 enregistrements.",
|
||||
"csv-excel-support-only": "Veuillez télécharger un fichier dans l'un des formats suivants : CSV, XLSX ou XLS.",
|
||||
"contact-imported": "{{imported}} contacts ont été importés. {{failed}} contacts n'ont pas pu être importés.",
|
||||
@@ -982,6 +989,7 @@
|
||||
"review": "Revoir",
|
||||
"next-field": "Champ suivant",
|
||||
"required-mssg":"{{leftRequiredWidget}} champs sur {{totalWidget}} restants",
|
||||
"verify-document": "Vérifier le document",
|
||||
"verify-document-signature": "Vérifier la signature du document",
|
||||
"select-pdf-document": "Sélectionner le document PDF",
|
||||
"selected-file": "Fichier sélectionné",
|
||||
@@ -1023,7 +1031,15 @@
|
||||
"could-not-parse-signer-info": "Impossible d'analyser les informations sur le signataire",
|
||||
"not-calculated": "Non calculé",
|
||||
"not-found-in-signature": "Introuvable dans la signature",
|
||||
"readonly-textinput-error": "Le widget de texte en lecture seule doit avoir une valeur par défaut ou être rendu optionnel.",
|
||||
"readonly-dropdown-error": "Le widget déroulant en lecture seule doit avoir une valeur par défaut ou être rendu optionnel.",
|
||||
"readonly-radiobtn-error": "Le widget bouton radio en lecture seule doit avoir une valeur par défaut ou être rendu optionnel."
|
||||
"readonly-error": "Le widget en lecture seule {{widgetName}} doit avoir une valeur par défaut ou vous pouvez le rendre facultatif.",
|
||||
"choose-one":"Choisissez-en un",
|
||||
"search-templates": "Rechercher des modèles…",
|
||||
"search-documents": "Rechercher des documents…",
|
||||
"search-contacts": "Rechercher des contacts…",
|
||||
"invalid-email-found": "Adresse e-mail invalide trouvée : {{email}}",
|
||||
"duplicate-email-found": "Adresse e-mail en double trouvée : {{email}}",
|
||||
"vertical": "Vertical",
|
||||
"horizontal": "Horizontal",
|
||||
"billing": "Facturation",
|
||||
"console": "Console"
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"create-account": "खाता बनाएं",
|
||||
"login": "लॉग इन करें",
|
||||
"language": "भाषा",
|
||||
"dark-mode": "डार्क मोड",
|
||||
"name": "नाम",
|
||||
"phone": "फ़ोन",
|
||||
"phone-optional": "वैकल्पिक",
|
||||
@@ -354,13 +355,14 @@
|
||||
"widgets-name": {
|
||||
"signature": "हस्ताक्षर",
|
||||
"stamp": "मोहर",
|
||||
"initials": "हस्ताक्षर",
|
||||
"initials": "प्रारंभिक अक्षर",
|
||||
"name": "नाम",
|
||||
"job title": "पद",
|
||||
"company": "कंपनी",
|
||||
"date": "दिनांक",
|
||||
"text": "पाठ",
|
||||
"text input": "पाठ इनपुट",
|
||||
"cells": "सेल्स",
|
||||
"checkbox": "चेकबॉक्स",
|
||||
"dropdown": "ड्रॉपडाउन",
|
||||
"radio button": "रेडियो बटन",
|
||||
@@ -387,7 +389,7 @@
|
||||
"Email-verified-alert-2": "ईमेल पहले से ही सत्यापित है।",
|
||||
"upload-stamp-image": "मोहर छवि अपलोड करें",
|
||||
"draw": "आरेखित करें",
|
||||
"type": "प्रकार",
|
||||
"type": "टाइप",
|
||||
"color-type": {
|
||||
"red": "लाल",
|
||||
"blue": "नीला",
|
||||
@@ -415,10 +417,12 @@
|
||||
"options": "विकल्प",
|
||||
"minimun-check": "न्यूनतम जाँच",
|
||||
"maximum-check": "अधिकतम जाँच",
|
||||
"cell-count": "सेल्स संख्या",
|
||||
"default-value": "डिफ़ॉल्ट मान",
|
||||
"select": "चुनें",
|
||||
"read-only": "केवल पढ़ने के लिए है",
|
||||
"read-only": "सिर्फ पढ़ने के लिए है",
|
||||
"hide-labels": "लेबल छिपाएँ",
|
||||
"layout": "लेआउट",
|
||||
"checkbox": "चेकबॉक्स",
|
||||
"alert": "चेतावनी",
|
||||
"zoom-in": "ज़ूम इन करें",
|
||||
@@ -750,12 +754,15 @@
|
||||
"delete-page": "पृष्ठ हटाएं",
|
||||
"merge-pdf": "पीडीएफ मर्ज करें",
|
||||
"add-pages": "पृष्ठ जोड़ें",
|
||||
"reorder-pages": "पृष्ठ पुनः व्यवस्थित करें",
|
||||
"delete-alert": "एकल पृष्ठ नहीं हटाया जा सकता",
|
||||
"delete-alert-2": "क्या आप वाकई इस पृष्ठ को हटाना चाहते हैं?",
|
||||
"delete-note": "ध्यान दें: एक बार जब आप इस पृष्ठ को हटा देते हैं, तो आप इसे पूर्ववत नहीं कर सकते।",
|
||||
"Rotation-alert": "पृष्ठ घुमाएँ",
|
||||
"bulk-import": "थोक आयात",
|
||||
"contacts-file": "संपर्क फ़ाइल (xlsx, csv)",
|
||||
"import-guideline": "नाम, ईमेल और वैकल्पिक फोन कॉलम वाली CSV या Excel फ़ाइल अपलोड करें। केवल पहले 100 संपर्क आयात किए जाएंगे.",
|
||||
"download-sample": "उदाहरण फ़ाइल डाउनलोड करें",
|
||||
"100-records-only": "वर्तमान में आप केवल 100 रिकॉर्ड तक आयात कर सकते हैं।",
|
||||
"csv-excel-support-only": "निम्नलिखित प्रारूपों में से किसी एक में फ़ाइल अपलोड करें: CSV, XLSX या XLS।",
|
||||
"contact-imported": "{{imported}} संपर्क आयात किए गए। {{failed}} संपर्क आयात करने में विफल रहे।",
|
||||
@@ -982,6 +989,7 @@
|
||||
"review": "समीक्षा",
|
||||
"next-field": "अगला फ़ील्ड",
|
||||
"required-mssg": "{{totalWidget}} में से {{leftRequiredWidget}} फ़ील्ड शेष हैं",
|
||||
"verify-document": "दस्तावेज़ सत्यापित करें",
|
||||
"verify-document-signature": "दस्तावेज़ हस्ताक्षर सत्यापित करें",
|
||||
"select-pdf-document": "पीडीएफ दस्तावेज़ चुनें",
|
||||
"selected-file": "चयनित फ़ाइल",
|
||||
@@ -1023,7 +1031,15 @@
|
||||
"could-not-parse-signer-info": "हस्ताक्षरकर्ता जानकारी पार्स नहीं की जा सकी",
|
||||
"not-calculated": "गणना नहीं की गई",
|
||||
"not-found-in-signature": "हस्ताक्षर में नहीं मिला",
|
||||
"readonly-textinput-error": "रीड-ओनली टेक्स्ट विजेट में एक डिफ़ॉल्ट मान होना चाहिए या आप इसे वैकल्पिक बना सकते हैं।",
|
||||
"readonly-dropdown-error": "रीड-ओनली ड्रॉपडाउन विजेट में एक डिफ़ॉल्ट मान होना चाहिए या आप इसे वैकल्पिक बना सकते हैं।",
|
||||
"readonly-radiobtn-error": "रीड-ओनली रेडियो बटन विजेट में एक डिफ़ॉल्ट मान होना चाहिए या आप इसे वैकल्पिक बना सकते हैं।"
|
||||
"readonly-error": "रीड-ओनली {{widgetName}} विजेट में एक डिफ़ॉल्ट मान होना चाहिए या आप इसे वैकल्पिक बना सकते हैं।",
|
||||
"choose-one":"एक का चयन",
|
||||
"search-templates": "टेम्पलेट खोजें…",
|
||||
"search-documents": "दस्तावेज़ खोजें…",
|
||||
"search-contacts": "संपर्क खोजें…",
|
||||
"invalid-email-found": "अमान्य ईमेल पाया गया: {{email}}",
|
||||
"duplicate-email-found": "डुप्लिकेट ईमेल पाया गया: {{email}}",
|
||||
"vertical": "वर्टिकल",
|
||||
"horizontal": "हॉरिज़ॉन्टल",
|
||||
"billing": "बिलिंग",
|
||||
"console": "कंसोल"
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"create-account": "Crea Account",
|
||||
"login": "Accedi",
|
||||
"language": "Lingua",
|
||||
"dark-mode": "Modalità scura",
|
||||
"name": "Nome",
|
||||
"phone": "Telefono",
|
||||
"phone-optional": "facoltativo",
|
||||
@@ -361,6 +362,7 @@
|
||||
"date": "data",
|
||||
"text": "testo",
|
||||
"text input": "campo di testo",
|
||||
"cells": "cellule",
|
||||
"checkbox": "casella di controllo",
|
||||
"dropdown": "menu a tendina",
|
||||
"radio button": "pulsante di opzione",
|
||||
@@ -415,10 +417,12 @@
|
||||
"options": "Opzioni",
|
||||
"minimun-check": "Controllo minimo",
|
||||
"maximum-check": "Controllo massimo",
|
||||
"cell-count": "conteggio delle cellule",
|
||||
"default-value": "Valore predefinita",
|
||||
"select": "Seleziona",
|
||||
"read-only": "È solo lettura",
|
||||
"read-only": "È di sola lettura",
|
||||
"hide-labels": "Nascondi etichette",
|
||||
"layout": "Layout",
|
||||
"checkbox": "Casella di controllo",
|
||||
"alert": "Avviso",
|
||||
"zoom-in": "Ingrandisci",
|
||||
@@ -750,12 +754,15 @@
|
||||
"delete-page": "Elimina pagina",
|
||||
"merge-pdf": "Unisci PDF",
|
||||
"add-pages": "Aggiungi pagine",
|
||||
"reorder-pages": "Riordina pagine",
|
||||
"delete-alert": "Non è possibile eliminare una singola pagina",
|
||||
"delete-alert-2": "Sei sicuro di voler eliminare questa pagina?",
|
||||
"delete-note": "Nota: Una volta eliminata questa pagina, non potrai annullare l'operazione.",
|
||||
"Rotation-alert": "Ruota pagina",
|
||||
"bulk-import": "Importazione massiva",
|
||||
"contacts-file": "File contatti (xlsx, csv)",
|
||||
"import-guideline": "Carica un file CSV o Excel con le colonne Name, Email e opzionale Phone. Verranno importati solo i primi 100 contatti.",
|
||||
"download-sample": "Scarica file di esempio",
|
||||
"100-records-only": "Attualmente puoi importare solo fino a 100 record.",
|
||||
"csv-excel-support-only": "Carica un file nei seguenti formati: CSV, XLSX o XLS.",
|
||||
"contact-imported": "{{imported}} contatti importati. {{failed}} contatti non sono stati importati.",
|
||||
@@ -982,6 +989,7 @@
|
||||
"review": "Rivedere",
|
||||
"next-field": "Campo successivo",
|
||||
"required-mssg":"{{leftRequiredWidget}} di {{totalWidget}} campi rimanenti",
|
||||
"verify-document": "Verifica documento",
|
||||
"verify-document-signature": "Verifica firma documento",
|
||||
"select-pdf-document": "Seleziona documento PDF",
|
||||
"selected-file": "File selezionato",
|
||||
@@ -1023,7 +1031,15 @@
|
||||
"could-not-parse-signer-info": "Impossibile analizzare le informazioni del firmatario",
|
||||
"not-calculated": "Non calcolato",
|
||||
"not-found-in-signature": "Non trovato nella firma",
|
||||
"readonly-textinput-error": "Il widget di testo di sola lettura deve avere un valore predefinito oppure può essere reso opzionale.",
|
||||
"readonly-dropdown-error": "Il widget a discesa di sola lettura deve avere un valore predefinito oppure può essere reso opzionale.",
|
||||
"readonly-radiobtn-error": "Il widget pulsante di opzione di sola lettura deve avere un valore predefinito oppure può essere reso opzionale."
|
||||
"readonly-error": "Il widget {{widgetName}} di sola lettura deve avere un valore predefinito oppure può essere reso facoltativo.",
|
||||
"choose-one":"Scegline uno",
|
||||
"search-templates": "Cerca modelli…",
|
||||
"search-documents": "Cerca documenti…",
|
||||
"search-contacts": "Cerca contatti…",
|
||||
"invalid-email-found": "Email non valida trovata: {{email}}",
|
||||
"duplicate-email-found": "Email duplicata trovata: {{email}}",
|
||||
"vertical": "Verticale",
|
||||
"horizontal": "Orizzontale",
|
||||
"billing": "Fatturazione",
|
||||
"console": "Console"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
Name,Email,Phone
|
||||
John Doe,john@example.com,1234567890
|
||||
Jane Smith,jane@example.com,9876543210
|
||||
Foo Bar,foo@example.com,5555555555
|
||||
|
Binary file not shown.
|
After Width: | Height: | Size: 95 KiB |
@@ -142,7 +142,7 @@ function App() {
|
||||
/>
|
||||
{/* signyouself route with no rowlevel data using docId from url */}
|
||||
<Route path="/signaturePdf/:docId" element={<SignYourSelf />} />
|
||||
{/* draft document route to handle and navigate route page accordiing to document status */}
|
||||
{/* draft document route to handle and navigate route page according to document status */}
|
||||
<Route path="/draftDocument" element={<DraftDocument />} />
|
||||
{/* recipient placeholder set route with no rowlevel data using docId from url*/}
|
||||
<Route
|
||||
|
||||
@@ -15,12 +15,14 @@ const BulkSendUi = (props) => {
|
||||
const [isSignatureExist, setIsSignatureExist] = useState();
|
||||
const [isDisableBulkSend, setIsDisableBulkSend] = useState(false);
|
||||
const [isLoader, setIsLoader] = useState(false);
|
||||
const [signers, setSigners] = useState([]);
|
||||
const [emails, setEmails] = useState([]);
|
||||
useEffect(() => {
|
||||
signatureExist();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
//function to check atleast one signature field exist
|
||||
//function to check at least one signature field exist
|
||||
const signatureExist = async () => {
|
||||
setIsDisableBulkSend(false);
|
||||
const getPlaceholder = props?.Placeholders;
|
||||
@@ -47,7 +49,14 @@ const BulkSendUi = (props) => {
|
||||
(() => {
|
||||
if (props?.Placeholders?.length > 0) {
|
||||
let users = [];
|
||||
let emails = [];
|
||||
props?.Placeholders?.forEach((element) => {
|
||||
const signerEmail = element?.email || element?.signerPtr?.Email;
|
||||
|
||||
// only add when there's a non-empty signerEmail
|
||||
if (signerEmail) {
|
||||
emails = [...emails, signerEmail];
|
||||
}
|
||||
if (!element.signerObjId) {
|
||||
users = [
|
||||
...users,
|
||||
@@ -60,7 +69,10 @@ const BulkSendUi = (props) => {
|
||||
];
|
||||
}
|
||||
});
|
||||
setEmails(emails);
|
||||
setForms((prevForms) => [...prevForms, { Id: 1, fields: users }]);
|
||||
const signer = props.item?.Signers?.filter((x) => x?.objectId);
|
||||
setSigners(signer);
|
||||
}
|
||||
})();
|
||||
// eslint-disable-next-line
|
||||
@@ -82,10 +94,16 @@ const BulkSendUi = (props) => {
|
||||
|
||||
function validateEmails(data) {
|
||||
for (const item of data) {
|
||||
let email = "";
|
||||
for (const field of item.fields) {
|
||||
if (!emailRegex.test(field.email)) {
|
||||
alert(`Invalid email found: ${field.email}`);
|
||||
alert(t("invalid-email-found", { email: field.email }));
|
||||
return false;
|
||||
} else if (email === field.email || emails?.includes(field.email)) {
|
||||
alert(t("duplicate-email-found", { email: field.email }));
|
||||
return false;
|
||||
} else {
|
||||
email = field.email;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -143,15 +161,14 @@ const BulkSendUi = (props) => {
|
||||
Documents.push({
|
||||
...props.item,
|
||||
Placeholders: updatedPlaceholders,
|
||||
Signers: props.item.Signers
|
||||
? [...props.item.Signers, ...existSigner]
|
||||
: [...existSigner]
|
||||
Signers: signers ? [...signers, ...existSigner] : [...existSigner]
|
||||
});
|
||||
} else {
|
||||
Documents.push({
|
||||
...props.item,
|
||||
Placeholders: updatedPlaceholders,
|
||||
SignatureType: props.signatureType
|
||||
SignatureType: props.signatureType,
|
||||
Signers: signers
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import dp from "../assets/images/dp.png";
|
||||
import FullScreenButton from "./FullScreenButton";
|
||||
import ThemeToggle from "./ThemeToggle";
|
||||
import { useNavigate } from "react-router";
|
||||
import Parse from "parse";
|
||||
import { useWindowSize } from "../hook/useWindowSize";
|
||||
@@ -85,9 +86,37 @@ const Header = ({ showSidebar, setIsMenu, isConsole }) => {
|
||||
};
|
||||
}, [isOpen]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const updateLogoForTheme = () => {
|
||||
const isDarkMode =
|
||||
document.documentElement.getAttribute("data-theme") === "opensigndark";
|
||||
const logo = isDarkMode
|
||||
? "/static/js/assets/images/logo-dark.png" // Path to the dark mode logo
|
||||
: appInfo.applogo; // Use current logo for light mode
|
||||
if (applogo !== logo) {
|
||||
setAppLogo(logo);
|
||||
}
|
||||
};
|
||||
|
||||
// Set the logo immediately based on the current theme
|
||||
updateLogoForTheme();
|
||||
|
||||
const observer = new MutationObserver(() => {
|
||||
updateLogoForTheme();
|
||||
});
|
||||
|
||||
observer.observe(document.documentElement, {
|
||||
attributes: true,
|
||||
attributeFilter: ["data-theme"]
|
||||
});
|
||||
|
||||
return () => observer.disconnect();
|
||||
}, [applogo]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="op-navbar bg-base-100 shadow">
|
||||
<div className="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"
|
||||
@@ -147,7 +176,7 @@ const Header = ({ showSidebar, setIsMenu, isConsole }) => {
|
||||
</div>
|
||||
<ul
|
||||
tabIndex={0}
|
||||
className={`mt-3 z-[1] p-2 shadow op-dropdown-open op-menu op-menu-sm op-dropdown-content text-base-content bg-base-100 rounded-box w-52 ${
|
||||
className={`mt-3 z-[1] p-2 shadow op-dropdown-open op-menu op-menu-sm op-dropdown-content text-base-content bg-base-100 rounded-box w-56 ${
|
||||
isOpen ? "" : "hidden"
|
||||
}`}
|
||||
>
|
||||
@@ -194,6 +223,16 @@ const Header = ({ showSidebar, setIsMenu, isConsole }) => {
|
||||
{t("verify-document")}
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span>
|
||||
<i className="fa-light fa-moon"></i>
|
||||
{t("dark-mode")}
|
||||
<span className="text-[10px] font-semibold bg-base-300 text-base-content px-1 rounded-md">
|
||||
BETA
|
||||
</span>
|
||||
<ThemeToggle />
|
||||
</span>
|
||||
</li>
|
||||
</>
|
||||
)}
|
||||
<li onClick={closeDropdown}>
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
const ThemeToggle = () => {
|
||||
const [isDark, setIsDark] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const storedTheme = localStorage.getItem("theme");
|
||||
if (storedTheme === "dark") {
|
||||
setIsDark(true);
|
||||
document.documentElement.setAttribute("data-theme", "opensigndark");
|
||||
} else {
|
||||
document.documentElement.setAttribute("data-theme", "opensigncss");
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleChange = () => {
|
||||
const newTheme = !isDark;
|
||||
setIsDark(newTheme);
|
||||
if (newTheme) {
|
||||
document.documentElement.setAttribute("data-theme", "opensigndark");
|
||||
localStorage.setItem("theme", "dark");
|
||||
} else {
|
||||
document.documentElement.setAttribute("data-theme", "opensigncss");
|
||||
localStorage.setItem("theme", "light");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<input
|
||||
id="dark-mode-toggle"
|
||||
type="checkbox"
|
||||
className="op-toggle checked:[--tglbg:#3368ff] transition-all checked:bg-white"
|
||||
checked={isDark}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ThemeToggle;
|
||||
@@ -29,7 +29,7 @@ const DashboardButton = (props) => {
|
||||
: "cursor-default"
|
||||
} w-full shadow-md px-3 py-2 op-card bg-base-100`}
|
||||
>
|
||||
<div className="flex flex-row items-center">
|
||||
<div className="flex flex-row items-center text-base-content">
|
||||
<div className="flex flex-row items-center">
|
||||
<span className="rounded-full bg-base-content bg-opacity-20 w-[60px] h-[60px] self-start flex justify-center items-center">
|
||||
<i
|
||||
@@ -39,7 +39,7 @@ const DashboardButton = (props) => {
|
||||
></i>
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-lg ml-3 text-base-content">
|
||||
<div className="text-lg ml-3">
|
||||
{t(`sidebar.${props.Label}`)}
|
||||
{props.Label === "Sign yourself" && (
|
||||
<div className="text-gray-500 text-xs mt-1">
|
||||
|
||||
@@ -148,7 +148,7 @@ const DashboardCard = (props) => {
|
||||
);
|
||||
let arr = [];
|
||||
for (const obj of listData) {
|
||||
const isSigner = obj.Signers.some(
|
||||
const isSigner = obj.Signers?.some(
|
||||
(item) => item.UserId.objectId === currentUser.id
|
||||
);
|
||||
if (isSigner) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useEffect, useState, useRef } from "react";
|
||||
import Parse from "parse";
|
||||
import ReportTable from "../../primitives/GetReportDisplay";
|
||||
import reportJson from "../../json/ReportJson";
|
||||
@@ -17,10 +17,16 @@ function DashboardReport(props) {
|
||||
const [isMoreDocs, setIsMoreDocs] = useState(true);
|
||||
const abortController = new AbortController();
|
||||
const docPerPage = 5;
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [mobileSearchOpen, setMobileSearchOpen] = useState(false);
|
||||
const [isSearchResult, setIsSearchResult] = useState(false);
|
||||
const debounceTimer = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
setReportName("");
|
||||
getReportData(props.Record.reportId);
|
||||
setSearchTerm("");
|
||||
setMobileSearchOpen(false);
|
||||
getReportData(props.Record.reportId, 0, 20, "");
|
||||
|
||||
// Function returned from useEffect is called on unmount
|
||||
return () => {
|
||||
@@ -36,12 +42,69 @@ function DashboardReport(props) {
|
||||
// below useEffect call when isNextRecord state is true and fetch next record
|
||||
useEffect(() => {
|
||||
if (isNextRecord) {
|
||||
getReportData(props.Record.reportId, List.length, 20);
|
||||
getReportData(props.Record.reportId, List.length, 20, searchTerm);
|
||||
}
|
||||
// eslint-disable-next-line
|
||||
}, [isNextRecord]);
|
||||
|
||||
const getReportData = async (id, skipUserRecord = 0, limit = 20) => {
|
||||
const handleSearchChange = async (e) => {
|
||||
const term = e.target.value.toLowerCase();
|
||||
setSearchTerm(term);
|
||||
if (debounceTimer.current) {
|
||||
clearTimeout(debounceTimer.current);
|
||||
}
|
||||
debounceTimer.current = setTimeout(async () => {
|
||||
try {
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
|
||||
sessiontoken: localStorage.getItem("accesstoken")
|
||||
};
|
||||
const url = `${localStorage.getItem("baseUrl")}functions/getReport`;
|
||||
const res = await axios.post(
|
||||
url,
|
||||
{
|
||||
reportId: props.Record.reportId,
|
||||
searchTerm: term,
|
||||
skip: 0,
|
||||
limit: docPerPage
|
||||
},
|
||||
{ headers }
|
||||
);
|
||||
const data = res.data?.result || [];
|
||||
if (!data.error) {
|
||||
setList(data);
|
||||
setIsMoreDocs(data.length >= docPerPage);
|
||||
setIsNextRecord(false);
|
||||
setIsSearchResult(true);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Search error:", err);
|
||||
}
|
||||
}, 300);
|
||||
setIsSearchResult(false);
|
||||
};
|
||||
|
||||
const handleSearchPaste = (e) => {
|
||||
setTimeout(() => {
|
||||
handleSearchChange({ target: { value: e.target.value } });
|
||||
}, 0);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (debounceTimer.current) {
|
||||
clearTimeout(debounceTimer.current);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
const getReportData = async (
|
||||
id,
|
||||
skipUserRecord = 0,
|
||||
limit = 20,
|
||||
term = searchTerm
|
||||
) => {
|
||||
setIsLoader(true);
|
||||
const json = reportJson(id);
|
||||
if (json) {
|
||||
@@ -59,6 +122,9 @@ function DashboardReport(props) {
|
||||
const skipRecord = id === "5Go51Q7T8r" ? 0 : skipUserRecord;
|
||||
const limitRecord = id === "5Go51Q7T8r" ? 200 : limit;
|
||||
const params = { reportId: id, skip: skipRecord, limit: limitRecord };
|
||||
if (term) {
|
||||
params.searchTerm = term;
|
||||
}
|
||||
const url = `${localStorage.getItem("baseUrl")}functions/getReport`;
|
||||
const res = await axios.post(url, params, {
|
||||
headers: headers,
|
||||
@@ -68,7 +134,7 @@ function DashboardReport(props) {
|
||||
const listData = res.data?.result.filter((x) => x.Signers.length > 0);
|
||||
let arr = [];
|
||||
for (const obj of listData) {
|
||||
const isSigner = obj.Signers.some(
|
||||
const isSigner = obj.Signers?.some(
|
||||
(item) => item.UserId.objectId === currentUser
|
||||
);
|
||||
if (isSigner) {
|
||||
@@ -141,11 +207,17 @@ function DashboardReport(props) {
|
||||
setIsNextRecord={setIsNextRecord}
|
||||
isMoreDocs={isMoreDocs}
|
||||
docPerPage={docPerPage}
|
||||
mobileSearchOpen={mobileSearchOpen}
|
||||
setMobileSearchOpen={setMobileSearchOpen}
|
||||
searchTerm={searchTerm}
|
||||
handleSearchChange={handleSearchChange}
|
||||
handleSearchPaste={handleSearchPaste}
|
||||
isSearchResult={isSearchResult}
|
||||
/>
|
||||
) : (
|
||||
<div className="flex items-center justify-center h-[100px] w-full bg-white rounded">
|
||||
<div className="text-center">
|
||||
<p className="text-xl text-black">{t("report-not-found")}</p>
|
||||
<p className="text-xl text-base-content">{t("report-not-found")}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -405,7 +405,7 @@ function DriveBody(props) {
|
||||
</tr>
|
||||
)
|
||||
) : listType === "list" && data.Type === "Folder" ? (
|
||||
<div key={ind} className="relative w-[100px] h-[100px] mx-2 my-3">
|
||||
<div className="relative w-[100px] h-[100px] mx-2 my-3">
|
||||
<ContextMenu.Root>
|
||||
<ContextMenu.Trigger className="flex flex-col justify-center items-center select-none-cls">
|
||||
{/* folder */}
|
||||
@@ -447,7 +447,6 @@ function DriveBody(props) {
|
||||
)}
|
||||
</div>
|
||||
</ContextMenu.Trigger>
|
||||
|
||||
<ContextMenu.Portal>
|
||||
<ContextMenu.Content
|
||||
className="ContextMenuContent"
|
||||
@@ -641,7 +640,7 @@ function DriveBody(props) {
|
||||
) : (
|
||||
<div className="flex flex-row flex-wrap items-center mt-1 pb-[20px] mx-[5px]">
|
||||
{props.pdfData.map((data, ind) => {
|
||||
return <div key={ind}>{handleFolderData(data, ind, "list")}</div>;
|
||||
return <React.Fragment key={ind}>{handleFolderData(data, ind, "list")}</React.Fragment>;
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -10,7 +10,7 @@ const AddRoleModal = (props) => {
|
||||
isOpen={props.isModalRole}
|
||||
handleClose={props.handleCloseRoleModal}
|
||||
>
|
||||
<div className="h-full py-[10px] px-[20px]">
|
||||
<div className="text-base-content h-full py-[10px] px-[20px]">
|
||||
<form className="flex flex-col" onSubmit={props.handleAddRole}>
|
||||
<input
|
||||
value={props.roleName}
|
||||
|
||||
@@ -38,7 +38,7 @@ function AgreementSign(props) {
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
<div className="text-[11px] md:text-base">
|
||||
<div className="text-[11px] md:text-base text-base-content">
|
||||
<span>{t("agree-p1")}</span>
|
||||
<span
|
||||
className="font-bold text-blue-600 cursor-pointer"
|
||||
@@ -67,7 +67,7 @@ function AgreementSign(props) {
|
||||
{t("agrre-button")}
|
||||
</button>
|
||||
</div>
|
||||
<div className="mt-2">
|
||||
<div className="mt-2 text-base-content">
|
||||
<span className="text-[11px]">{t("agreement-note")}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -12,12 +12,9 @@ function BorderResize(props) {
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
width: getHeight() || "14px",
|
||||
height: getHeight() || "14px"
|
||||
}}
|
||||
style={{ width: getHeight() || "14px", height: getHeight() || "14px" }}
|
||||
className={`${props.right ? `-right-[12px]` : "-right-[2px]"} ${
|
||||
props.top ? `-bottom-[12px]` : "-bottom-[2px] "
|
||||
props.top ? `-bottom-[12px]` : "-bottom-[2px]"
|
||||
} absolute inline-block hover:cursor-sw-resize border-r-[3px] border-b-[3px] border-[#188ae2]`}
|
||||
></div>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import ModalUi from "../../primitives/ModalUi";
|
||||
import { fontsizeArr, fontColorArr } from "../../constant/Utils";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function CellsSettingModal({
|
||||
isOpen,
|
||||
handleClose,
|
||||
defaultData,
|
||||
handleSave
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [name, setName] = useState("");
|
||||
const [cellCount, setCellCount] = useState(5);
|
||||
const [fontSize, setFontSize] = useState(12);
|
||||
const [fontColor, setFontColor] = useState("black");
|
||||
|
||||
useEffect(() => {
|
||||
if (defaultData) {
|
||||
setName(defaultData.options?.name || "Cells");
|
||||
setCellCount(defaultData.options?.cellCount || 5);
|
||||
setFontSize(defaultData.options?.fontSize || 12);
|
||||
setFontColor(defaultData.options?.fontColor || "black");
|
||||
}
|
||||
}, [defaultData]);
|
||||
|
||||
const onSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
handleSave &&
|
||||
handleSave({
|
||||
name,
|
||||
cellCount: parseInt(cellCount, 10),
|
||||
fontSize,
|
||||
fontColor
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<ModalUi isOpen={isOpen} handleClose={handleClose} title={t("widget-info")}>
|
||||
<form onSubmit={onSubmit} className="p-[20px] text-base-content flex flex-col gap-3">
|
||||
<div>
|
||||
<label htmlFor="name" className="text-[13px]">
|
||||
{t("name")} <span className="text-[red]">*</span>
|
||||
</label>
|
||||
<input
|
||||
required
|
||||
name="name"
|
||||
className="op-input op-input-bordered op-input-sm w-full text-xs focus:outline-none hover:border-base-content"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="cellCount" className="text-[13px]">
|
||||
{t("cell-count")}
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
min="1"
|
||||
name="cellCount"
|
||||
className="op-input op-input-bordered op-input-sm w-full text-xs focus:outline-none hover:border-base-content"
|
||||
value={cellCount}
|
||||
onChange={(e) => setCellCount(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col md:flex-row md:items-center gap-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="whitespace-nowrap">{t("font-size")}: </span>
|
||||
<select
|
||||
className="ml-[7px] w-[60%] op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs"
|
||||
value={fontSize}
|
||||
onChange={(e) => setFontSize(parseInt(e.target.value))}
|
||||
>
|
||||
{fontsizeArr.map((size, ind) => (
|
||||
<option className="text-[13px]" value={size} key={ind}>
|
||||
{size}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<span>{t("color")}: </span>
|
||||
<select
|
||||
className="ml-[33px] md:ml-4 w-[65%] md:w-full op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs"
|
||||
value={fontColor}
|
||||
onChange={(e) => setFontColor(e.target.value)}
|
||||
>
|
||||
{fontColorArr.map((color, ind) => (
|
||||
<option value={color} key={ind}>
|
||||
{t(`color-type.${color}`)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<span className="w-5 h-[19px] ml-1" style={{ background: fontColor }} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="h-[1px] w-full bg-[#b7b3b3] my-[16px]"></div>
|
||||
<button type="submit" className="op-btn op-btn-primary">
|
||||
{t("save")}
|
||||
</button>
|
||||
</form>
|
||||
</ModalUi>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
|
||||
const Cell = ({
|
||||
isEnabled,
|
||||
count,
|
||||
h,
|
||||
value,
|
||||
editable,
|
||||
onChange,
|
||||
onKeyDown,
|
||||
onBlur,
|
||||
inputRef,
|
||||
index,
|
||||
fontSize,
|
||||
fontColor,
|
||||
hint
|
||||
}) => (
|
||||
<div
|
||||
className={`${isEnabled ? "bg-white border-gray-400" : "select-none-cls pointer-events-none border-gray-500"} flex items-center justify-center border-[1px]`}
|
||||
style={{ flex: `0 0 ${100 / count}%`, height: h }}
|
||||
>
|
||||
<input
|
||||
disabled={!isEnabled}
|
||||
maxLength={1}
|
||||
value={value}
|
||||
readOnly={!editable}
|
||||
ref={inputRef}
|
||||
onChange={editable ? (e) => onChange && onChange(e, index) : undefined}
|
||||
onKeyDown={editable ? (e) => onKeyDown && onKeyDown(e, index) : undefined}
|
||||
// trigger validation when leaving a cell
|
||||
onBlur={editable ? (e) => onBlur && onBlur(e, index) : undefined}
|
||||
className={`${isEnabled ? "placeholder-gray-300" : "placeholder-gray-500"} w-full text-center focus:outline-none bg-transparent`}
|
||||
placeholder={hint}
|
||||
style={{ fontFamily: "Arial, sans-serif", fontSize, color: fontColor }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default function CellsWidget({
|
||||
isEnabled,
|
||||
count = 8,
|
||||
height = 40,
|
||||
value = "",
|
||||
editable = false,
|
||||
onChange,
|
||||
onKeyDown,
|
||||
onBlur,
|
||||
onCellCountChange,
|
||||
inputRefs,
|
||||
resizable = false,
|
||||
fontSize = "12px",
|
||||
fontColor = "black",
|
||||
hint = ""
|
||||
}) {
|
||||
const [cellCount, setCellCount] = useState(count);
|
||||
|
||||
// keep internal state in sync with prop updates
|
||||
useEffect(() => setCellCount(count), [count]);
|
||||
|
||||
const startX = useRef(0);
|
||||
const startCount = useRef(cellCount);
|
||||
|
||||
const capture = (downEv, onMove, onUp = () => {}) => {
|
||||
const id = downEv.pointerId;
|
||||
const move = (ev) => id === ev.pointerId && onMove(ev);
|
||||
const up = (ev) => {
|
||||
if (id !== ev.pointerId) return;
|
||||
onUp(ev);
|
||||
downEv.target.releasePointerCapture(id);
|
||||
window.removeEventListener("pointermove", move);
|
||||
window.removeEventListener("pointerup", up);
|
||||
};
|
||||
window.addEventListener("pointermove", move);
|
||||
window.addEventListener("pointerup", up);
|
||||
downEv.target.setPointerCapture(id);
|
||||
};
|
||||
|
||||
const onTopHandlePointerDown = (ev) => {
|
||||
// Prevent triggering the widget drag logic
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
startX.current = ev.clientX;
|
||||
startCount.current = cellCount;
|
||||
capture(ev, (moveEv) => {
|
||||
const dx = moveEv.clientX - startX.current;
|
||||
const delta = Math.floor(-dx / 5);
|
||||
let newCount = Math.max(1, startCount.current + delta);
|
||||
if (newCount !== cellCount) {
|
||||
setCellCount(newCount);
|
||||
onCellCountChange?.(newCount);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const cells = Array.from({ length: cellCount }).map((_, i) => value[i] || "");
|
||||
const hints = Array.from({ length: cellCount }).map((_, i) => hint[i] || "");
|
||||
|
||||
return (
|
||||
<div
|
||||
className="relative flex w-full h-full overflow-visible"
|
||||
style={{ height }}
|
||||
>
|
||||
{resizable && (
|
||||
<div
|
||||
className="cell-size-handle absolute left-1/2 -translate-x-1/2 -bottom-4 rotate-180 cursor-ew-resize touch-none"
|
||||
onPointerDown={onTopHandlePointerDown}
|
||||
>
|
||||
<svg
|
||||
className="w-4 h-4 text-blue-600"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="m9.69 18.933.003.001C9.89 19.02 10 19 10 19s.11.02.308-.066l.002-.001.006-.003.018-.008a5.741 5.741 0 0 0 .281-.14c.186-.096.446-.24.757-.433.62-.384 1.445-.966 2.274-1.765C15.302 14.988 17 12.493 17 9A7 7 0 1 0 3 9c0 3.492 1.698 5.988 3.355 7.584a13.731 13.731 0 0 0 2.273 1.765 11.842 11.842 0 0 0 .976.544l.062.029.018.008.006.003ZM10 11.25a2.25 2.25 0 1 0 0-4.5 2.25 2.25 0 0 0 0 4.5Z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
)}
|
||||
{cells.map((val, i) => (
|
||||
<Cell
|
||||
key={i}
|
||||
isEnabled={isEnabled}
|
||||
count={cellCount}
|
||||
h={height}
|
||||
value={val}
|
||||
editable={editable}
|
||||
onChange={onChange}
|
||||
onKeyDown={onKeyDown}
|
||||
onBlur={onBlur}
|
||||
inputRef={inputRefs ? (el) => (inputRefs.current[i] = el) : undefined}
|
||||
index={i}
|
||||
fontSize={fontSize}
|
||||
fontColor={fontColor}
|
||||
hint={hints[i]}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -7,8 +7,8 @@ import { fontColorArr, fontsizeArr } from "../../constant/Utils";
|
||||
function DropdownWidgetOption(props) {
|
||||
const { t } = useTranslation();
|
||||
const [dropdownOptionList, setDropdownOptionList] = useState([
|
||||
"option-1",
|
||||
"option-2"
|
||||
"Option-1",
|
||||
"Option-2"
|
||||
]);
|
||||
const [minCount, setMinCount] = useState(0);
|
||||
const [maxCount, setMaxCount] = useState(0);
|
||||
@@ -17,11 +17,13 @@ function DropdownWidgetOption(props) {
|
||||
const [isHideLabel, setIsHideLabel] = useState(false);
|
||||
const [status, setStatus] = useState("required");
|
||||
const [defaultValue, setDefaultValue] = useState("");
|
||||
const statusArr = ["required", "optional"];
|
||||
const [defaultCheckbox, setDefaultCheckbox] = useState([]);
|
||||
const [layout, setLayout] = useState("vertical");
|
||||
const statusArr = ["required", "optional"];
|
||||
const layoutArr = ["vertical", "horizontal"];
|
||||
|
||||
const resetState = () => {
|
||||
setDropdownOptionList(["option-1", "option-2"]);
|
||||
setDropdownOptionList(["Option-1", "Option-2"]);
|
||||
setDropdownName(props.currWidgetsDetails?.options?.name || props.type);
|
||||
setIsReadOnly(false);
|
||||
setIsHideLabel(false);
|
||||
@@ -29,6 +31,7 @@ function DropdownWidgetOption(props) {
|
||||
setMaxCount(0);
|
||||
setDefaultCheckbox([]);
|
||||
setDefaultValue("");
|
||||
setLayout("vertical");
|
||||
};
|
||||
useEffect(() => {
|
||||
if (
|
||||
@@ -48,6 +51,7 @@ function DropdownWidgetOption(props) {
|
||||
setStatus(props.currWidgetsDetails?.options?.status || "required");
|
||||
setDefaultValue(props.currWidgetsDetails?.options?.defaultValue || "");
|
||||
setDefaultCheckbox(props.currWidgetsDetails?.options?.defaultValue || []);
|
||||
setLayout(props.currWidgetsDetails?.options?.layout || "vertical");
|
||||
} else {
|
||||
setStatus("required");
|
||||
resetState();
|
||||
@@ -107,14 +111,22 @@ function DropdownWidgetOption(props) {
|
||||
props?.type === "dropdown" || props?.type === radioButtonWidget;
|
||||
const readOnlyWithoutValue =
|
||||
isReadOnly && !defaultValue && status !== "optional";
|
||||
const isCheckbox = props?.type === "checkbox";
|
||||
const WidgetLayout = ["checkbox", radioButtonWidget].includes(props.type)
|
||||
? layout
|
||||
: null;
|
||||
|
||||
// If it’s a dropdown and it’s read-only without a value (nor marked optional), stop here.
|
||||
if (isDropdownOrRadio && readOnlyWithoutValue) {
|
||||
alert(
|
||||
props?.type === "dropdown"
|
||||
? t("readonly-dropdown-error")
|
||||
: t("readonly-radiobtn-error")
|
||||
);
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -129,7 +141,8 @@ function DropdownWidgetOption(props) {
|
||||
null,
|
||||
status,
|
||||
defaultData,
|
||||
isHideLabel
|
||||
isHideLabel,
|
||||
WidgetLayout
|
||||
);
|
||||
resetState();
|
||||
};
|
||||
@@ -153,17 +166,18 @@ function DropdownWidgetOption(props) {
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<label className="text-[13px] font-semibold">
|
||||
<label htmlFor="title" className="text-[13px] font-semibold">
|
||||
{t("name")}
|
||||
<span className="text-[red] text-[13px]"> *</span>
|
||||
</label>
|
||||
<input
|
||||
id="title"
|
||||
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
|
||||
onInput={(e) => e.target.setCustomValidity("")}
|
||||
required
|
||||
value={dropdownName}
|
||||
onChange={(e) => setDropdownName(e.target.value)}
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
required
|
||||
/>
|
||||
|
||||
<label className="text-[13px] font-semibold mt-[5px]">
|
||||
@@ -245,27 +259,25 @@ function DropdownWidgetOption(props) {
|
||||
</>
|
||||
)}
|
||||
{props.type !== "checkbox" && (
|
||||
<>
|
||||
<div className="flex flex-row gap-[10px] mt-[0.5rem]">
|
||||
{statusArr.map((data, ind) => {
|
||||
return (
|
||||
<div
|
||||
key={ind}
|
||||
className="flex flex-row gap-[5px] items-center"
|
||||
>
|
||||
<input
|
||||
className="op-radio op-radio-xs my-1"
|
||||
type="radio"
|
||||
name="status"
|
||||
onChange={() => setStatus(data.toLowerCase())}
|
||||
checked={status.toLowerCase() === data.toLowerCase()}
|
||||
/>
|
||||
<div className="text-[13px] font-500">{data}</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</>
|
||||
<div className="flex 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>
|
||||
@@ -286,8 +298,8 @@ function DropdownWidgetOption(props) {
|
||||
);
|
||||
})}
|
||||
</select>
|
||||
<div className="flex flex-row gap-1 items-center ml-4 ">
|
||||
<span>{t("color")} : </span>
|
||||
<div className="flex flex-row gap-1 items-center ml-4">
|
||||
<span className="capitalize">{t("color")} : </span>
|
||||
<select
|
||||
value={
|
||||
props.fontColor ||
|
||||
@@ -329,7 +341,10 @@ function DropdownWidgetOption(props) {
|
||||
className="op-checkbox op-checkbox-sm"
|
||||
onChange={(e) => setIsReadOnly(e.target.checked)}
|
||||
/>
|
||||
<label className="ml-1 mb-0" htmlFor="isreadonly">
|
||||
<label
|
||||
className="ml-2 mb-0 capitalize"
|
||||
htmlFor="isreadonly"
|
||||
>
|
||||
{t("read-only")}
|
||||
</label>
|
||||
</div>
|
||||
@@ -344,15 +359,45 @@ function DropdownWidgetOption(props) {
|
||||
onChange={(e) => setIsHideLabel(e.target.checked)}
|
||||
/>
|
||||
|
||||
<label className="ml-1 mb-0" htmlFor="ishidelabel">
|
||||
<label
|
||||
className="ml-2 mb-0 capitalize"
|
||||
htmlFor="ishidelabel"
|
||||
>
|
||||
{t("hide-labels")}
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{["checkbox", radioButtonWidget].includes(props.type) && (
|
||||
<>
|
||||
<div className="text-[13px] font-semibold mt-[5px] capitalize">
|
||||
{t("layout")}
|
||||
</div>
|
||||
<div
|
||||
className={`${props.type === "checkbox" ? "mb-[10px]" : ""} flex flex-row gap-[10px] mt-[0.5rem]`}
|
||||
>
|
||||
{layoutArr.map((data, ind) => (
|
||||
<div
|
||||
key={ind}
|
||||
className="flex flex-row gap-[5px] items-center"
|
||||
>
|
||||
<input
|
||||
className="op-radio op-radio-xs my-1"
|
||||
type="radio"
|
||||
name="layout"
|
||||
checked={layout.toLowerCase() === data.toLowerCase()}
|
||||
onChange={() => setLayout(data.toLowerCase())}
|
||||
/>
|
||||
<label className="text-[13px] font-500 mb-0">
|
||||
{t(data)}
|
||||
</label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={`${
|
||||
props.type === "checkbox" && props.isShowAdvanceFeature
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import React from "react";
|
||||
import { Quill } from "react-quill-new";
|
||||
// Use a third-party plugin so editors can toggle raw HTML view.
|
||||
import htmlEditButton from "quill-html-edit-button";
|
||||
|
||||
// Register the module which adds a "<>" button for HTML editing.
|
||||
Quill.register("modules/htmlEditButton", htmlEditButton);
|
||||
|
||||
// Custom Undo button icon component for Quill editor. You can import it directly
|
||||
// from 'quill/assets/icons/undo.svg' but I found that a number of loaders do not
|
||||
@@ -56,7 +61,9 @@ export const module1 = {
|
||||
container: "#toolbar1",
|
||||
handlers: { undo: undoChange, redo: redoChange }
|
||||
},
|
||||
history: { delay: 500, maxStack: 100, userOnly: true }
|
||||
history: { delay: 500, maxStack: 100, userOnly: true },
|
||||
// Enable the "<>" button registered above
|
||||
htmlEditButton: {}
|
||||
};
|
||||
|
||||
// Modules object for setting up the Quill editor
|
||||
@@ -65,7 +72,9 @@ export const module2 = {
|
||||
container: "#toolbar2",
|
||||
handlers: { undo: undoChange, redo: redoChange }
|
||||
},
|
||||
history: { delay: 500, maxStack: 100, userOnly: true }
|
||||
history: { delay: 500, maxStack: 100, userOnly: true },
|
||||
// Enable the "<>" button registered above
|
||||
htmlEditButton: {}
|
||||
};
|
||||
|
||||
// Formats objects for setting up the Quill editor
|
||||
|
||||
@@ -186,7 +186,7 @@ function EmailComponent({
|
||||
<input
|
||||
type="email"
|
||||
value={emailValue}
|
||||
className="p-[10px] pb-[20px] rounded-md w-full text-[15px] outline-none bg-transparent border-[1px] op-border-primary"
|
||||
className="p-[10px] pb-[20px] text-base-content rounded-md w-full text-[15px] outline-none bg-transparent border-[1px] op-border-primary"
|
||||
onChange={handleEmailValue}
|
||||
onKeyDown={handleEnterPress}
|
||||
placeholder={t("enter-email-plaholder")}
|
||||
@@ -204,19 +204,6 @@ function EmailComponent({
|
||||
{t("email-error-1")}
|
||||
</p>
|
||||
)}
|
||||
{/* <button
|
||||
className={`${
|
||||
emailValue ? "cursor-pointer" : "cursor-default"
|
||||
} op-btn op-btn-primary op-btn-sm m-2 shadow-md`}
|
||||
onClick={() => emailValue && handleEnterPress("add")}
|
||||
>
|
||||
<i className="fa-light fa-plus" aria-hidden="true"></i>
|
||||
</button>
|
||||
<div className="bg-[#e3e2e1] mt-[10px] p-[5px] rounded">
|
||||
<span className="font-bold">{t("report-heading.Note")}: </span>
|
||||
<span className="text-[15px]">{t("email-error-2")}</span>
|
||||
</div>
|
||||
<hr className="w-full my-[15px] bg-base-content" /> */}
|
||||
<div className="mt-2">
|
||||
<button
|
||||
type="button"
|
||||
@@ -227,7 +214,7 @@ function EmailComponent({
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-ghost ml-2"
|
||||
className="op-btn op-btn-ghost text-base-content ml-2"
|
||||
onClick={() => handleClose()}
|
||||
>
|
||||
{t("close")}
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
import React, { useEffect, useState, useRef } from "react";
|
||||
import ModalUi from "../../primitives/ModalUi";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function PageReorderModal({
|
||||
isOpen,
|
||||
handleClose,
|
||||
totalPages = 0,
|
||||
onSave
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [order, setOrder] = useState([]);
|
||||
// Keeps track of the page order relative to the original PDF
|
||||
const orderRef = useRef([]);
|
||||
// Captures the order when the modal opens
|
||||
const initialOrderRef = useRef([]);
|
||||
|
||||
// Initialize orderRef when total pages change (e.g. after upload)
|
||||
useEffect(() => {
|
||||
if (orderRef.current.length !== totalPages) {
|
||||
orderRef.current = Array.from({ length: totalPages }, (_, i) => i + 1);
|
||||
}
|
||||
}, [totalPages]);
|
||||
|
||||
// When modal opens, display the last saved order and store it as initial
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
setOrder(orderRef.current);
|
||||
initialOrderRef.current = [...orderRef.current];
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
const move = (index, dir) => {
|
||||
const swapIndex = index + dir;
|
||||
if (swapIndex < 0 || swapIndex >= order.length) return;
|
||||
const newOrder = [...order];
|
||||
[newOrder[index], newOrder[swapIndex]] = [newOrder[swapIndex], newOrder[index]];
|
||||
setOrder(newOrder);
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
const saveOrder = order.map((num) =>
|
||||
initialOrderRef.current.indexOf(num) + 1
|
||||
);
|
||||
// Persist the new display order for next time
|
||||
orderRef.current = [...order];
|
||||
onSave && onSave(saveOrder);
|
||||
};
|
||||
|
||||
const isUnchanged =
|
||||
order.length === initialOrderRef.current.length &&
|
||||
order.every((n, i) => n === initialOrderRef.current[i]);
|
||||
|
||||
return (
|
||||
<ModalUi isOpen={isOpen} handleClose={handleClose} title={t("reorder-pages")}>
|
||||
<div className="p-[20px] flex flex-col gap-2 text-base-content">
|
||||
{order.map((num, i) => (
|
||||
<div key={num} className="flex items-center justify-between">
|
||||
<span>
|
||||
{t("page")} {num}
|
||||
</span>
|
||||
<div className="flex gap-1">
|
||||
<button
|
||||
className="op-btn op-btn-xs op-btn-ghost"
|
||||
disabled={i === 0}
|
||||
onClick={() => move(i, -1)}
|
||||
>
|
||||
<i className="fa-light fa-arrow-up"></i>
|
||||
</button>
|
||||
<button
|
||||
className="op-btn op-btn-xs op-btn-ghost"
|
||||
disabled={i === order.length - 1}
|
||||
onClick={() => move(i, 1)}
|
||||
>
|
||||
<i className="fa-light fa-arrow-down"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<div className="h-[1px] bg-[#9f9f9f] w-full my-[15px]"></div>
|
||||
<button onClick={handleSave} type="button" className="op-btn op-btn-primary" disabled={isUnchanged}>
|
||||
{t("save")}
|
||||
</button>
|
||||
<button
|
||||
onClick={handleClose}
|
||||
type="button"
|
||||
className="op-btn op-btn-ghost ml-1"
|
||||
>
|
||||
{t("close")}
|
||||
</button>
|
||||
</div>
|
||||
</ModalUi>
|
||||
);
|
||||
}
|
||||
@@ -2,16 +2,21 @@ import React, { useRef, useState } from "react";
|
||||
import PrevNext from "./PrevNext";
|
||||
import {
|
||||
base64ToArrayBuffer,
|
||||
decryptPdf,
|
||||
deletePdfPage,
|
||||
flattenPdf,
|
||||
getFileAsArrayBuffer,
|
||||
handleDownloadCertificate,
|
||||
handleDownloadPdf,
|
||||
handleRemoveWidgets,
|
||||
handleToPrint
|
||||
handleToPrint,
|
||||
reorderPdfPages
|
||||
} from "../../constant/Utils";
|
||||
import "../../styles/signature.css";
|
||||
import { DropdownMenu } from "radix-ui";
|
||||
import ModalUi from "../../primitives/ModalUi";
|
||||
import Loader from "../../primitives/Loader";
|
||||
import PageReorderModal from "./PageReorderModal";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { PDFDocument } from "pdf-lib";
|
||||
import { maxFileSize } from "../../constant/const";
|
||||
@@ -24,6 +29,7 @@ function Header(props) {
|
||||
const isMobile = window.innerWidth < 767;
|
||||
const [isDownloading, setIsDownloading] = useState("");
|
||||
const [isDeletePage, setIsDeletePage] = useState(false);
|
||||
const [isReorderModal, setIsReorderModal] = useState(false);
|
||||
const mergePdfInputRef = useRef(null);
|
||||
const enabledBackBtn = props?.disabledBackBtn === true ? false : true;
|
||||
//function for show decline alert
|
||||
@@ -80,7 +86,42 @@ function Header(props) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const uploadedPdfBytes = await file.arrayBuffer();
|
||||
let uploadedPdfBytes = await file.arrayBuffer();
|
||||
try {
|
||||
uploadedPdfBytes = await flattenPdf(uploadedPdfBytes);
|
||||
} catch (err) {
|
||||
if (err?.message?.includes("is encrypted")) {
|
||||
try {
|
||||
const pdfFile = await decryptPdf(file, "");
|
||||
const pdfArrayBuffer = await getFileAsArrayBuffer(pdfFile);
|
||||
uploadedPdfBytes = await flattenPdf(pdfArrayBuffer);
|
||||
} catch (err) {
|
||||
if (err?.response?.status === 401) {
|
||||
const password = prompt(
|
||||
`PDF "${file.name}" is password-protected. Enter password:`
|
||||
);
|
||||
if (password) {
|
||||
try {
|
||||
const pdfFile = await decryptPdf(file, password);
|
||||
const pdfArrayBuffer = await getFileAsArrayBuffer(pdfFile);
|
||||
uploadedPdfBytes = await flattenPdf(pdfArrayBuffer);
|
||||
// Upload the file to Parse Server
|
||||
} catch (err) {
|
||||
console.error("Incorrect password or decryption failed", err);
|
||||
alert("Incorrect password or decryption failed.");
|
||||
}
|
||||
} else {
|
||||
alert("Please provided Password.");
|
||||
}
|
||||
} else {
|
||||
console.log("Err ", err);
|
||||
alert("error while uploading pdf.");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
alert("error while uploading pdf.");
|
||||
}
|
||||
}
|
||||
const uploadedPdfDoc = await PDFDocument.load(uploadedPdfBytes, {
|
||||
ignoreEncryption: true
|
||||
});
|
||||
@@ -106,12 +147,27 @@ function Header(props) {
|
||||
console.error("Error merging PDF:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleReorderSave = async (order) => {
|
||||
try {
|
||||
const pdfupdatedData = await reorderPdfPages(props.pdfArrayBuffer, order);
|
||||
if (pdfupdatedData) {
|
||||
props.setPdfArrayBuffer(pdfupdatedData.arrayBuffer);
|
||||
props.setPdfBase64Url(pdfupdatedData.base64);
|
||||
props.setAllPages(pdfupdatedData.totalPages);
|
||||
props.setPageNumber(1);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("error in reorder pdf pages", e);
|
||||
}
|
||||
setIsReorderModal(false);
|
||||
};
|
||||
return (
|
||||
<div className="flex py-[5px]">
|
||||
{isMobile && props?.isShowHeader ? (
|
||||
<div
|
||||
id="navbar"
|
||||
className="stickyHead"
|
||||
className="stickyHead touch-none"
|
||||
style={{
|
||||
width: window.innerWidth + "px"
|
||||
}}
|
||||
@@ -334,6 +390,17 @@ function Header(props) {
|
||||
</span>
|
||||
</div>
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item
|
||||
className="DropdownMenuItem"
|
||||
onClick={() => setIsReorderModal(true)}
|
||||
>
|
||||
<div className="flex flex-row">
|
||||
<i className="fa-light fa-list-ol text-gray-500 2xl:text-[30px] mr-[3px]"></i>
|
||||
<span className="font-[500]">
|
||||
{t("reorder-pages")}
|
||||
</span>
|
||||
</div>
|
||||
</DropdownMenu.Item>
|
||||
|
||||
<DropdownMenu.Item
|
||||
className="DropdownMenuItem"
|
||||
@@ -696,6 +763,12 @@ function Header(props) {
|
||||
</button>
|
||||
</div>
|
||||
</ModalUi>
|
||||
<PageReorderModal
|
||||
isOpen={isReorderModal}
|
||||
handleClose={() => setIsReorderModal(false)}
|
||||
totalPages={props.allPages}
|
||||
onSave={handleReorderSave}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,18 +1,24 @@
|
||||
import React, { useRef, useState } from "react";
|
||||
import { useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
base64ToArrayBuffer,
|
||||
decryptPdf,
|
||||
deletePdfPage,
|
||||
handleRemoveWidgets
|
||||
flattenPdf,
|
||||
getFileAsArrayBuffer,
|
||||
handleRemoveWidgets,
|
||||
reorderPdfPages
|
||||
} from "../../constant/Utils";
|
||||
import ModalUi from "../../primitives/ModalUi";
|
||||
import { PDFDocument } from "pdf-lib";
|
||||
import { maxFileSize } from "../../constant/const";
|
||||
import PageReorderModal from "./PageReorderModal";
|
||||
|
||||
function PdfZoom(props) {
|
||||
const { t } = useTranslation();
|
||||
const mergePdfInputRef = useRef(null);
|
||||
const [isDeletePage, setIsDeletePage] = useState(false);
|
||||
const [isReorderModal, setIsReorderModal] = useState(false);
|
||||
const handleDetelePage = async () => {
|
||||
props.setIsUploadPdf && props.setIsUploadPdf(true);
|
||||
try {
|
||||
@@ -67,7 +73,42 @@ function PdfZoom(props) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const uploadedPdfBytes = await file.arrayBuffer();
|
||||
let uploadedPdfBytes = await file.arrayBuffer();
|
||||
try {
|
||||
uploadedPdfBytes = await flattenPdf(uploadedPdfBytes);
|
||||
} catch (err) {
|
||||
if (err?.message?.includes("is encrypted")) {
|
||||
try {
|
||||
const pdfFile = await decryptPdf(file, "");
|
||||
const pdfArrayBuffer = await getFileAsArrayBuffer(pdfFile);
|
||||
uploadedPdfBytes = await flattenPdf(pdfArrayBuffer);
|
||||
} catch (err) {
|
||||
if (err?.response?.status === 401) {
|
||||
const password = prompt(
|
||||
`PDF "${file.name}" is password-protected. Enter password:`
|
||||
);
|
||||
if (password) {
|
||||
try {
|
||||
const pdfFile = await decryptPdf(file, password);
|
||||
const pdfArrayBuffer = await getFileAsArrayBuffer(pdfFile);
|
||||
uploadedPdfBytes = await flattenPdf(pdfArrayBuffer);
|
||||
// Upload the file to Parse Server
|
||||
} catch (err) {
|
||||
console.error("Incorrect password or decryption failed", err);
|
||||
alert("Incorrect password or decryption failed.");
|
||||
}
|
||||
} else {
|
||||
alert("Please provided Password.");
|
||||
}
|
||||
} else {
|
||||
console.log("Err ", err);
|
||||
alert("error while uploading pdf.");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
alert("error while uploading pdf.");
|
||||
}
|
||||
}
|
||||
const uploadedPdfDoc = await PDFDocument.load(uploadedPdfBytes, {
|
||||
ignoreEncryption: true
|
||||
});
|
||||
@@ -94,6 +135,21 @@ function PdfZoom(props) {
|
||||
}
|
||||
};
|
||||
|
||||
const handleReorderSave = async (order) => {
|
||||
try {
|
||||
const pdfupdatedData = await reorderPdfPages(props.pdfArrayBuffer, order);
|
||||
if (pdfupdatedData) {
|
||||
props.setPdfArrayBuffer(pdfupdatedData.arrayBuffer);
|
||||
props.setPdfBase64Url(pdfupdatedData.base64);
|
||||
props.setAllPages(pdfupdatedData.totalPages);
|
||||
props.setPageNumber(1);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("error in reorder pdf pages", e);
|
||||
}
|
||||
setIsReorderModal(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<span className="hidden md:flex flex-col gap-1 text-center md:w-[5%] mt-[42px]">
|
||||
@@ -120,6 +176,13 @@ function PdfZoom(props) {
|
||||
>
|
||||
<i className="fa-light fa-trash text-gray-500 2xl:text-[25px]"></i>
|
||||
</span>
|
||||
<span
|
||||
className="bg-gray-50 px-[4px] 2xl:py-[10px] cursor-pointer"
|
||||
onClick={() => setIsReorderModal(true)}
|
||||
title={t("reorder-pages")}
|
||||
>
|
||||
<i className="fa-light fa-list-ol text-gray-500 2xl:text-[25px]"></i>
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
<span
|
||||
@@ -185,6 +248,12 @@ function PdfZoom(props) {
|
||||
</button>
|
||||
</div>
|
||||
</ModalUi>
|
||||
<PageReorderModal
|
||||
isOpen={isReorderModal}
|
||||
handleClose={() => setIsReorderModal(false)}
|
||||
totalPages={props.allPages}
|
||||
onSave={handleReorderSave}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
import BorderResize from "./BorderResize";
|
||||
import { Rnd } from "react-rnd";
|
||||
import {
|
||||
@@ -13,7 +13,9 @@ import {
|
||||
onChangeInput,
|
||||
radioButtonWidget,
|
||||
textInputWidget,
|
||||
textWidget
|
||||
cellsWidget,
|
||||
textWidget,
|
||||
selectFormat
|
||||
} from "../../constant/Utils";
|
||||
import PlaceholderType from "./PlaceholderType";
|
||||
import moment from "moment";
|
||||
@@ -24,37 +26,6 @@ import { useDispatch } from "react-redux";
|
||||
import { setIsShowModal } from "../../redux/reducers/widgetSlice";
|
||||
import { themeColor } from "../../constant/const";
|
||||
|
||||
const selectFormat = (data) => {
|
||||
switch (data) {
|
||||
case "L":
|
||||
return "MM/dd/yyyy";
|
||||
case "MM/DD/YYYY":
|
||||
return "MM/dd/yyyy";
|
||||
case "DD-MM-YYYY":
|
||||
return "dd-MM-yyyy";
|
||||
case "DD/MM/YYYY":
|
||||
return "dd/MM/yyyy";
|
||||
case "LL":
|
||||
return "MMMM dd, yyyy";
|
||||
case "DD MMM, YYYY":
|
||||
return "dd MMM, yyyy";
|
||||
case "YYYY-MM-DD":
|
||||
return "yyyy-MM-dd";
|
||||
case "MM-DD-YYYY":
|
||||
return "MM-dd-yyyy";
|
||||
case "MM.DD.YYYY":
|
||||
return "MM.dd.yyyy";
|
||||
case "MMM DD, YYYY":
|
||||
return "MMM dd, yyyy";
|
||||
case "MMMM DD, YYYY":
|
||||
return "MMMM dd, yyyy";
|
||||
case "DD MMMM, YYYY":
|
||||
return "dd MMMM, yyyy";
|
||||
default:
|
||||
return "MM/dd/yyyy";
|
||||
}
|
||||
};
|
||||
|
||||
//function to get default format
|
||||
const getDefaultFormat = (dateFormat) => dateFormat || "MM/dd/yyyy";
|
||||
|
||||
@@ -80,11 +51,12 @@ function Placeholder(props) {
|
||||
props.pos?.options?.defaultValue || props.pos?.options?.response;
|
||||
const [isDateModal, setIsDateModal] = useState(false);
|
||||
const [containerScale, setContainerScale] = useState();
|
||||
const holdTimeout = useRef(null);
|
||||
const startTime = useRef(null); // Track when the user starts holdings
|
||||
const [selectDate, setSelectDate] = useState({});
|
||||
const [dateFormat, setDateFormat] = useState([]);
|
||||
const [clickonWidget, setClickonWidget] = useState({});
|
||||
const [isDateReadOnly, setIsDateReadOnly] = useState(
|
||||
props?.pos?.options?.isReadOnly || false
|
||||
);
|
||||
const startDate = props?.pos?.options?.response
|
||||
? getDefaultDate(
|
||||
props?.pos?.options?.response,
|
||||
@@ -108,7 +80,9 @@ function Placeholder(props) {
|
||||
"MMM DD, YYYY",
|
||||
"LL",
|
||||
"DD MMM, YYYY",
|
||||
"DD MMMM, YYYY"
|
||||
"DD MMMM, YYYY",
|
||||
"DD.MM.YYYY",
|
||||
"DD/MM/YYYY"
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
@@ -130,15 +104,18 @@ function Placeholder(props) {
|
||||
if (selectDate && selectDate.format === "dd-MM-yyyy") {
|
||||
const [day, month, year] = selectDate.date.split("-");
|
||||
date = new Date(`${year}-${month}-${day}`);
|
||||
} else if (selectDate && selectDate.format === "dd.MM.yyyy") {
|
||||
const [day, month, year] = selectDate.date.split(".");
|
||||
date = new Date(`${year}.${month}.${day}`);
|
||||
} else if (selectDate && selectDate.format === "dd/MM/yyyy") {
|
||||
const [day, month, year] = selectDate.date.split("/");
|
||||
date = new Date(`${year}/${month}/${day}`);
|
||||
} else {
|
||||
date = new Date(selectDate?.date);
|
||||
}
|
||||
const milliseconds = date.getTime();
|
||||
const newDate = moment(milliseconds).format(data);
|
||||
const dateObj = {
|
||||
date: newDate,
|
||||
format: selectFormat(data)
|
||||
};
|
||||
const dateObj = { date: newDate, format: selectFormat(data) };
|
||||
updateDate.push(dateObj);
|
||||
});
|
||||
setDateFormat(updateDate);
|
||||
@@ -303,6 +280,13 @@ function Placeholder(props) {
|
||||
} else if (props.pos.type === "checkbox") {
|
||||
props?.setIsCheckbox(true);
|
||||
}
|
||||
// cells widget settings in sign yourself flow
|
||||
else if (
|
||||
props.pos.type === cellsWidget &&
|
||||
(props.isSignYourself || props.isSelfSign)
|
||||
) {
|
||||
props.handleCellSettingModal && props.handleCellSettingModal();
|
||||
}
|
||||
//condition to handle setting icon for signyour-self flow for all type text widgets
|
||||
else if (
|
||||
[
|
||||
@@ -382,9 +366,12 @@ function Placeholder(props) {
|
||||
|
||||
//function to save date and format on local array onchange date and onclick format
|
||||
const handleSaveDate = (data, isDateChange) => {
|
||||
const isSpecialDateFormat =
|
||||
data?.format &&
|
||||
["dd-MM-yyyy", "dd.MM.yyyy", "dd/MM/yyyy"].includes(data?.format);
|
||||
let updateDate = data.date;
|
||||
let date;
|
||||
if (data?.format === "dd-MM-yyyy") {
|
||||
if (isSpecialDateFormat) {
|
||||
date = isDateChange
|
||||
? moment(updateDate).format(changeDateToMomentFormat(data.format))
|
||||
: updateDate;
|
||||
@@ -406,11 +393,54 @@ function Placeholder(props) {
|
||||
false,
|
||||
data?.format,
|
||||
props.fontSize || props.pos?.options?.fontSize || 12,
|
||||
props.fontColor || props.pos?.options?.fontColor || "black"
|
||||
props.fontColor || props.pos?.options?.fontColor || "black",
|
||||
isDateReadOnly || false
|
||||
);
|
||||
setSelectDate({ date: date, format: data?.format });
|
||||
};
|
||||
|
||||
const setCellCount = (key, newCount) => {
|
||||
props.setXyPosition((prev) => {
|
||||
const isSignerList = prev.some((d) => d.signerPtr);
|
||||
if (isSignerList) {
|
||||
const signerId = props.data?.Id || props.uniqueId;
|
||||
const filterSignerPos = prev.filter((d) => d.Id === signerId);
|
||||
if (filterSignerPos.length > 0) {
|
||||
const getPlaceHolder = filterSignerPos[0].placeHolder;
|
||||
const updatedPlaceHolder = getPlaceHolder.map((ph) => {
|
||||
if (ph.pageNumber !== props.pageNumber) return ph;
|
||||
const newPos = ph.pos.map((p) =>
|
||||
p.key === key
|
||||
? { ...p, options: { ...p.options, cellCount: newCount } }
|
||||
: p
|
||||
);
|
||||
return { ...ph, pos: newPos };
|
||||
});
|
||||
return prev.map((obj) =>
|
||||
obj.Id === signerId
|
||||
? { ...obj, placeHolder: updatedPlaceHolder }
|
||||
: obj
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const updatePos = prev[props.index].pos.map((p) =>
|
||||
p.key === key
|
||||
? { ...p, options: { ...p.options, cellCount: newCount } }
|
||||
: p
|
||||
);
|
||||
return prev.map((obj, ind) =>
|
||||
ind === props.index ? { ...obj, pos: updatePos } : obj
|
||||
);
|
||||
}
|
||||
return prev;
|
||||
});
|
||||
};
|
||||
const PlaceholderIcon = () => {
|
||||
const isSettingForCells =
|
||||
props?.isAlllowModify && !props?.assignedWidgetId.includes(props.pos.key)
|
||||
? []
|
||||
: [cellsWidget];
|
||||
|
||||
// 1- If props.isShowBorder is true, display border's icon for all widgets. OR
|
||||
// 2- Use the combination of props?.isAlllowModify and !props?.assignedWidgetId.includes(props.pos.key) to determine when to show border's icon:
|
||||
// 1- When isAlllowModify is true, show border's icon.
|
||||
@@ -434,10 +464,15 @@ function Placeholder(props) {
|
||||
"name",
|
||||
"company",
|
||||
"job title",
|
||||
"email"
|
||||
"email",
|
||||
...isSettingForCells
|
||||
].includes(props.pos.type) &&
|
||||
(props.isSignYourself || props.isSelfSign) ? (
|
||||
<i
|
||||
onPointerDown={(e) => {
|
||||
e.stopPropagation();
|
||||
handleOnClickSettingIcon();
|
||||
}}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleOnClickSettingIcon();
|
||||
@@ -447,7 +482,14 @@ function Placeholder(props) {
|
||||
handleOnClickSettingIcon();
|
||||
}}
|
||||
className="fa-light fa-gear icon"
|
||||
style={{ color: "#188ae2", right: "29px", top: "-19px" }}
|
||||
style={{
|
||||
color: "#188ae2",
|
||||
right: "29px",
|
||||
top: "-19px",
|
||||
cursor: "pointer",
|
||||
zIndex: 99,
|
||||
pointerEvents: "auto"
|
||||
}}
|
||||
></i>
|
||||
) : (
|
||||
/* condition to add setting icon for placeholder & template flow for all widgets except signature and date */
|
||||
@@ -457,6 +499,10 @@ function Placeholder(props) {
|
||||
!props.isSignYourself &&
|
||||
!props.isSelfSign)) && (
|
||||
<i
|
||||
onPointerDown={(e) => {
|
||||
e.stopPropagation();
|
||||
handleOnClickSettingIcon();
|
||||
}}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleOnClickSettingIcon();
|
||||
@@ -469,7 +515,10 @@ function Placeholder(props) {
|
||||
style={{
|
||||
color: "#188ae2",
|
||||
right: props?.pos?.type === textWidget ? "32px" : "51px",
|
||||
top: "-19px"
|
||||
top: "-19px",
|
||||
cursor: "pointer",
|
||||
zIndex: 99,
|
||||
pointerEvents: "auto"
|
||||
}}
|
||||
></i>
|
||||
)
|
||||
@@ -507,6 +556,7 @@ function Placeholder(props) {
|
||||
{/* setting icon only for date widgets */}
|
||||
{props.pos.type === "date" && selectDate && (
|
||||
<i
|
||||
onPointerDown={(e) => e.stopPropagation()}
|
||||
onClick={(e) => {
|
||||
props.setCurrWidgetsDetails(props.pos);
|
||||
setIsDateModal(!isDateModal);
|
||||
@@ -539,7 +589,9 @@ function Placeholder(props) {
|
||||
top: "-18px",
|
||||
right: props.isPlaceholder ? "50px" : "30px",
|
||||
color: "#188ae2",
|
||||
fontSize: "14px"
|
||||
fontSize: "14px",
|
||||
cursor: "pointer",
|
||||
pointerEvents: "auto"
|
||||
}}
|
||||
className="fa-light fa-gear icon"
|
||||
></i>
|
||||
@@ -666,7 +718,7 @@ function Placeholder(props) {
|
||||
return "not-allowed";
|
||||
}
|
||||
} else {
|
||||
return "all-scroll";
|
||||
return "move";
|
||||
}
|
||||
};
|
||||
|
||||
@@ -686,21 +738,6 @@ function Placeholder(props) {
|
||||
return "rgba(203, 233, 237, 0.69)";
|
||||
}
|
||||
};
|
||||
const handleTouchEnd = () => {
|
||||
if (!props.isNeedSign || props.isAlllowModify) {
|
||||
const holdDuration = Date.now() - startTime.current; // Calculate hold time
|
||||
clearTimeout(holdTimeout.current); // Cancel timeout if touch ended early
|
||||
|
||||
if (holdDuration < 1000) {
|
||||
try {
|
||||
navigator.vibrate([]); // Cancel any ongoing vibration
|
||||
} catch (e) {
|
||||
console.log("error in navigator.vibrate", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!props.isNeedSign || props.isAlllowModify) handleOnClickPlaceholder();
|
||||
};
|
||||
|
||||
const fontSize = calculateFont(props.pos.options?.fontSize);
|
||||
const fontColor = props.pos.options?.fontColor || "black";
|
||||
@@ -751,20 +788,16 @@ function Placeholder(props) {
|
||||
id={props.pos.key}
|
||||
data-tut={props.pos.key === props.unSignedWidgetId ? "IsSigned" : ""}
|
||||
key={props.pos.key}
|
||||
cancel=".cell-size-handle, .icon"
|
||||
lockAspectRatio={
|
||||
!props.isFreeResize &&
|
||||
![
|
||||
textWidget,
|
||||
"email",
|
||||
"name",
|
||||
"company",
|
||||
"job title",
|
||||
textInputWidget
|
||||
].includes(props.pos.type) &&
|
||||
(props.pos.Width
|
||||
? props.pos.Width / props.pos.Height
|
||||
: defaultWidthHeight(props.pos.type).width /
|
||||
defaultWidthHeight(props.pos.type).height)
|
||||
props?.isAlllowModify &&
|
||||
!props?.assignedWidgetId.includes(props.pos.key)
|
||||
? false
|
||||
: !props.isFreeResize &&
|
||||
(props.pos.Width
|
||||
? props.pos.Width / props.pos.Height
|
||||
: defaultWidthHeight(props.pos.type).width /
|
||||
defaultWidthHeight(props.pos.type).height)
|
||||
}
|
||||
enableResizing={{
|
||||
top: false,
|
||||
@@ -804,8 +837,16 @@ function Placeholder(props) {
|
||||
props.isNeedSign && props.data?.Id !== props?.uniqueId && "0.4",
|
||||
background: handleBackground()
|
||||
}}
|
||||
onDrag={() => {
|
||||
onDrag={(_, d) => {
|
||||
props.handleTabDrag && props.handleTabDrag(props.pos.key);
|
||||
props.showGuidelines &&
|
||||
props.showGuidelines(
|
||||
true,
|
||||
d.x,
|
||||
d.y,
|
||||
d.node.offsetWidth,
|
||||
d.node.offsetHeight
|
||||
);
|
||||
}}
|
||||
size={{
|
||||
width:
|
||||
@@ -820,12 +861,32 @@ function Placeholder(props) {
|
||||
: props.posHeight(props.pos, props.isSignYourself)
|
||||
}}
|
||||
minHeight={
|
||||
props.pos.type !== "checkbox" &&
|
||||
calculateFont(props.pos.options?.fontSize, true)
|
||||
props.pos.type === cellsWidget
|
||||
? calculateFont(props.pos.options?.fontSize, true)
|
||||
: props.pos.type !== "checkbox" &&
|
||||
calculateFont(props.pos.options?.fontSize, true)
|
||||
}
|
||||
maxHeight="auto"
|
||||
onResizeStart={() => {
|
||||
onResizeStart={(e, dir, ref) => {
|
||||
props.setIsResize && props.setIsResize(true);
|
||||
props.showGuidelines &&
|
||||
props.showGuidelines(
|
||||
true,
|
||||
xPos(props.pos, props.isSignYourself),
|
||||
yPos(props.pos, props.isSignYourself),
|
||||
ref.offsetWidth,
|
||||
ref.offsetHeight
|
||||
);
|
||||
}}
|
||||
onResize={(e, dir, ref, delta, position) => {
|
||||
props.showGuidelines &&
|
||||
props.showGuidelines(
|
||||
true,
|
||||
position.x,
|
||||
position.y,
|
||||
ref.offsetWidth,
|
||||
ref.offsetHeight
|
||||
);
|
||||
}}
|
||||
onResizeStop={(e, direction, ref) => {
|
||||
setTimeout(() => {
|
||||
@@ -843,6 +904,7 @@ function Placeholder(props) {
|
||||
props.data && props.data.Id,
|
||||
props.isResize
|
||||
);
|
||||
props.showGuidelines && props.showGuidelines(false);
|
||||
}}
|
||||
onDragStop={(event, dragElement) => {
|
||||
props.handleStop &&
|
||||
@@ -852,6 +914,9 @@ function Placeholder(props) {
|
||||
props.data?.Id,
|
||||
props.pos?.key
|
||||
);
|
||||
props.isDragging &&
|
||||
props.showGuidelines &&
|
||||
props.showGuidelines(false);
|
||||
}}
|
||||
position={{
|
||||
x: xPos(props.pos, props.isSignYourself),
|
||||
@@ -888,24 +953,24 @@ function Placeholder(props) {
|
||||
![radioButtonWidget, "checkbox"].includes(props.pos.type) &&
|
||||
props.pos.key === props?.currWidgetsDetails?.key && <BorderResize />
|
||||
)}
|
||||
|
||||
{/* 1- Show a ouline if props.pos.key === props?.currWidgetsDetails?.key, indicating the current user's selected widget.
|
||||
2- If props.isShowBorder is true, display ouline for all widgets.
|
||||
3- Use the combination of props?.isAlllowModify and !props?.assignedWidgetId.includes(props.pos.key) to determine when to show ouline:
|
||||
3.1- When isAlllowModify is true, show ouline.
|
||||
3.2- Do not display ouline for widgets already assigned (props.assignedWidgetId.includes(props.pos.key) is true).
|
||||
{/* 1- Show a border if props.pos.key === props?.currWidgetsDetails?.key, indicating the current user's selected widget.
|
||||
2- If props.isShowBorder is true, display border for all widgets.
|
||||
3- Use the combination of props?.isAlllowModify and !props?.assignedWidgetId.includes(props.pos.key) to determine when to show border:
|
||||
3.1- When isAlllowModify is true, show border.
|
||||
3.2- Do not display border for widgets already assigned (props.assignedWidgetId.includes(props.pos.key) is true).
|
||||
*/}
|
||||
{props.pos.key === props?.currWidgetsDetails?.key &&
|
||||
(props.isShowBorder ||
|
||||
(props?.isAlllowModify &&
|
||||
!props?.assignedWidgetId.includes(props.pos.key))) && (
|
||||
<div
|
||||
style={{ borderColor: themeColor }}
|
||||
className="w-[calc(100%+21px)] h-[calc(100%+21px)] cursor-move absolute inline-block border-[1px] border-dashed"
|
||||
></div>
|
||||
)}
|
||||
<div
|
||||
className={`${
|
||||
props.pos.key === props?.currWidgetsDetails?.key &&
|
||||
(props.isShowBorder ||
|
||||
(props?.isAlllowModify &&
|
||||
!props?.assignedWidgetId.includes(props.pos.key)))
|
||||
? "outline-[0.3px] outline-dashed outline-offset-[10px]"
|
||||
: ""
|
||||
} flex items-stretch justify-center`}
|
||||
className="flex items-stretch justify-center"
|
||||
style={{
|
||||
outlineColor: themeColor,
|
||||
left: xPos(props.pos, props.isSignYourself),
|
||||
top: yPos(props.pos, props.isSignYourself),
|
||||
width: "100%",
|
||||
@@ -938,18 +1003,19 @@ function Placeholder(props) {
|
||||
handleSaveDate={handleSaveDate}
|
||||
xPos={props.xPos}
|
||||
calculateFont={calculateFont}
|
||||
setCellCount={setCellCount}
|
||||
/>
|
||||
</div>
|
||||
</Rnd>
|
||||
)}
|
||||
|
||||
<ModalUi isOpen={isDateModal} title={t("widget-info")} showClose={false}>
|
||||
<div className="h-[100%] p-[20px]">
|
||||
<div className="flex flex-row items-center">
|
||||
<span>{t("format")} : </span>
|
||||
<div className="flex">
|
||||
<div className="text-base-content h-[100%] p-[20px]">
|
||||
<div className="flex flex-col md:flex-row md:items-center gap-y-3">
|
||||
<div className="flex flex-row items-center gap-x-1">
|
||||
<span className="capitalize">{t("format")} :</span>
|
||||
<select
|
||||
className="ml-[7px] op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs"
|
||||
className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs"
|
||||
defaultValue={""}
|
||||
onChange={(e) => {
|
||||
const selectedIndex = e.target.value;
|
||||
@@ -973,23 +1039,23 @@ function Placeholder(props) {
|
||||
{selectDate.format}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center mt-4 md:mt-2">
|
||||
<span>{t("font-size")} :</span>
|
||||
<select
|
||||
className="ml-[3px] md:ml:[7px] op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs"
|
||||
value={props.fontSize || clickonWidget.options?.fontSize || 12}
|
||||
onChange={(e) => props.setFontSize(parseInt(e.target.value))}
|
||||
>
|
||||
{fontsizeArr.map((size, ind) => {
|
||||
return (
|
||||
<div className="flex flex-col md:flex-row gap-y-2 md:gap-y-0 gap-x-2 mt-3">
|
||||
<div className="flex flex-row items-center">
|
||||
<span className="capitalize">{t("font-size")} :</span>
|
||||
<select
|
||||
className="ml-[3px] md:ml:[7px] op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs"
|
||||
value={props.fontSize || clickonWidget.options?.fontSize || 12}
|
||||
onChange={(e) => props.setFontSize(parseInt(e.target.value))}
|
||||
>
|
||||
{fontsizeArr.map((size, ind) => (
|
||||
<option className="text-[13px]" value={size} key={ind}>
|
||||
{size}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</select>
|
||||
<div className="flex flex-row gap-1 items-center ml-2 md:ml-4 ">
|
||||
<span>{t("color")}: </span>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="flex flex-row gap-1 items-center">
|
||||
<span className="capitalize">{t("color")} :</span>
|
||||
<select
|
||||
value={
|
||||
props.fontColor || clickonWidget.options?.fontColor || "black"
|
||||
@@ -997,13 +1063,11 @@ function Placeholder(props) {
|
||||
onChange={(e) => props.setFontColor(e.target.value)}
|
||||
className="ml-[4px] md:ml[7px] op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs"
|
||||
>
|
||||
{fontColorArr.map((color, ind) => {
|
||||
return (
|
||||
<option value={color} key={ind}>
|
||||
{t(`color-type.${color}`)}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
{fontColorArr.map((color, ind) => (
|
||||
<option value={color} key={ind}>
|
||||
{t(`color-type.${color}`)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<span
|
||||
style={{
|
||||
@@ -1014,7 +1078,26 @@ function Placeholder(props) {
|
||||
></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{props?.isPlaceholder && (
|
||||
<div className="flex items-center mt-3">
|
||||
<input
|
||||
id="isReadOnly"
|
||||
name="isReadOnly"
|
||||
type="checkbox"
|
||||
checked={
|
||||
isDateReadOnly || props.pos.options?.isReadOnly || false
|
||||
}
|
||||
className="op-checkbox op-checkbox-xs"
|
||||
onChange={() => setIsDateReadOnly(!isDateReadOnly)}
|
||||
/>
|
||||
<label
|
||||
className="ml-1.5 mb-0 capitalize text-[13px]"
|
||||
htmlFor="isreadonly"
|
||||
>
|
||||
{t("read-only")}
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
<div className="h-[1px] w-full my-[15px] bg-[#9f9f9f]"></div>
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import React, { useEffect, useState, forwardRef, useRef } from "react";
|
||||
import React, { useEffect, useState, forwardRef } from "react";
|
||||
import {
|
||||
getMonth,
|
||||
getYear,
|
||||
radioButtonWidget,
|
||||
textInputWidget,
|
||||
cellsWidget,
|
||||
textWidget,
|
||||
months,
|
||||
years,
|
||||
@@ -14,8 +15,9 @@ import DatePicker from "react-datepicker";
|
||||
import "react-datepicker/dist/react-datepicker.css";
|
||||
import "../../styles/signature.css";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import CellsWidget from "./CellsWidget";
|
||||
const textWidgetCls =
|
||||
"w-full h-full md:min-w-full md:min-h-full z-[999] text-[12px] rounded-[2px] border-[1px] border-[#007bff] overflow-hidden resize-none outline-none text-base-content item-center whitespace-pre-wrap bg-white";
|
||||
"w-full h-full md:min-w-full md:min-h-full z-[999] text-[12px] rounded-[2px] border-[1px] border-[#007bff] overflow-hidden resize-none outline-none text-base-content item-center whitespace-pre-wrap";
|
||||
const selectWidgetCls =
|
||||
"w-full h-full absolute left-0 top-0 border-[1px] border-[#007bff] rounded-[2px] focus:outline-none text-base-content";
|
||||
const widgetCls =
|
||||
@@ -27,10 +29,13 @@ function PlaceholderType(props) {
|
||||
props.isSignYourself ||
|
||||
((props.isSelfSign || props.isNeedSign) &&
|
||||
props.data?.signerObjId === props.signerObjId);
|
||||
const isReadOnly =
|
||||
props.pos.options?.isReadOnly ||
|
||||
props.data?.signerObjId !== props.signerObjId;
|
||||
// prefer the latest response value over any default value
|
||||
const widgetData =
|
||||
props.pos?.options?.defaultValue || props.pos?.options?.response;
|
||||
props.pos?.options?.response ?? props.pos?.options?.defaultValue ?? "";
|
||||
const widgetTypeTranslation = t(`widgets-name.${props?.pos?.type}`);
|
||||
const inputRef = useRef(null);
|
||||
const [widgetValue, setwidgetValue] = useState();
|
||||
const [selectedCheckbox, setSelectedCheckbox] = useState([]);
|
||||
const [hint, setHint] = useState("");
|
||||
@@ -56,16 +61,13 @@ function PlaceholderType(props) {
|
||||
[]
|
||||
);
|
||||
} else {
|
||||
if (widgetData) {
|
||||
setwidgetValue(widgetData);
|
||||
}
|
||||
// keep displayed value in sync with the stored response
|
||||
setwidgetValue(widgetData);
|
||||
}
|
||||
if (props.pos?.options?.hint) {
|
||||
setHint(props.pos?.options.hint);
|
||||
} else if (props.pos?.options?.validation?.type) {
|
||||
checkRegularExpress(props.pos?.options?.validation?.type, setHint);
|
||||
} else {
|
||||
setHint(props.pos?.type);
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@@ -77,7 +79,8 @@ function PlaceholderType(props) {
|
||||
color: fontColor,
|
||||
fontFamily: "Arial, sans-serif"
|
||||
}}
|
||||
className={`${selectWidgetCls} overflow-hidden`}
|
||||
className={`${isReadOnly ? `select-none` : ``} ${selectWidgetCls} overflow-hidden`}
|
||||
disabled={isReadOnly}
|
||||
onClick={onClick}
|
||||
ref={ref}
|
||||
>
|
||||
@@ -148,68 +151,59 @@ function PlaceholderType(props) {
|
||||
</div>
|
||||
);
|
||||
case "checkbox":
|
||||
const checkBoxLayout = props.pos.options?.layout || "vertical";
|
||||
const isMultipleCheckbox =
|
||||
props.pos.options?.values?.length > 0 ? true : false;
|
||||
const checkBoxWrapperClass = `flex items-start ${
|
||||
checkBoxLayout === "horizontal"
|
||||
? `flex-row flex-wrap ${isMultipleCheckbox ? "gap-x-2" : ""}`
|
||||
: `flex-col ${isMultipleCheckbox ? "gap-y-[3px]" : ""}`
|
||||
}`; // Using gap-y-1 for consistency, adjust if needed
|
||||
|
||||
return (
|
||||
<div style={{ zIndex: props.isSignYourself && "99" }}>
|
||||
{props.pos.options?.values?.map((data, ind) => {
|
||||
return (
|
||||
<div key={ind} className="select-none-cls pointer-events-none">
|
||||
<label
|
||||
htmlFor={`checkbox-${props.pos.key + ind}`}
|
||||
style={{ fontSize: fontSize, color: fontColor }}
|
||||
className={`mb-0 flex items-center gap-1 ${
|
||||
ind > 0 ? "mt-[3px]" : "mt-[0px]"
|
||||
}`}
|
||||
>
|
||||
<input
|
||||
id={`checkbox-${props.pos.key + ind}`}
|
||||
style={{
|
||||
width: fontSize,
|
||||
height: fontSize
|
||||
}}
|
||||
className="op-checkbox rounded-[1px]"
|
||||
disabled={
|
||||
props.isNeedSign &&
|
||||
(props.pos.options?.isReadOnly ||
|
||||
props.data?.signerObjId !== props.signerObjId)
|
||||
}
|
||||
type="checkbox"
|
||||
readOnly
|
||||
checked={!!selectCheckbox(ind, selectedCheckbox)}
|
||||
/>
|
||||
{!props.pos.options?.isHideLabel && (
|
||||
<span className="leading-none">{data}</span>
|
||||
)}
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<div
|
||||
className={checkBoxWrapperClass}
|
||||
style={{ zIndex: props.isSignYourself && "99" }}
|
||||
>
|
||||
{props.pos.options?.values?.map((data, ind) => (
|
||||
<div key={ind} className="select-none-cls pointer-events-none">
|
||||
<label
|
||||
htmlFor={`checkbox-${props.pos.key + ind}`}
|
||||
style={{ fontSize: fontSize, color: fontColor }}
|
||||
className={`mb-0 flex items-center gap-1`}
|
||||
>
|
||||
<input
|
||||
id={`checkbox-${props.pos.key + ind}`}
|
||||
style={{ width: fontSize, height: fontSize }}
|
||||
className="op-checkbox rounded-[1px]"
|
||||
disabled={props.isNeedSign && isReadOnly}
|
||||
type="checkbox"
|
||||
readOnly
|
||||
checked={!!selectCheckbox(ind, selectedCheckbox)}
|
||||
/>
|
||||
{!props.pos.options?.isHideLabel && (
|
||||
<span className="leading-none">{data}</span>
|
||||
)}
|
||||
</label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
case textInputWidget:
|
||||
return props.isSignYourself || iswidgetEnable ? (
|
||||
<textarea
|
||||
ref={inputRef}
|
||||
placeholder={hint || t("widgets-name.text")}
|
||||
rows={1}
|
||||
value={widgetValue}
|
||||
className={`${
|
||||
props.pos.options?.isReadOnly ||
|
||||
props.data?.signerObjId !== props.signerObjId
|
||||
? "select-none"
|
||||
: textWidgetCls
|
||||
}`}
|
||||
className={`${textWidgetCls} ${isReadOnly ? "select-none" : ""}`}
|
||||
style={{
|
||||
fontSize: fontSize,
|
||||
color: fontColor,
|
||||
background: props.data?.blockColor,
|
||||
background: isReadOnly ? props.data?.blockColor : "white",
|
||||
pointerEvents: "none"
|
||||
}}
|
||||
readOnly
|
||||
disabled={
|
||||
props.isNeedSign &&
|
||||
(props.pos.options?.isReadOnly ||
|
||||
props.data?.signerObjId !== props.signerObjId)
|
||||
}
|
||||
disabled={props.isNeedSign && isReadOnly}
|
||||
cols="50"
|
||||
/>
|
||||
) : (
|
||||
@@ -217,13 +211,39 @@ function PlaceholderType(props) {
|
||||
<span>{hint || widgetTypeTranslation}</span>
|
||||
</div>
|
||||
);
|
||||
case cellsWidget: {
|
||||
const count = props.pos.options?.cellCount || 5;
|
||||
const cells = (widgetValue || "").split("");
|
||||
const height = "100%";
|
||||
const fontSize = props.calculateFont(props.pos.options?.fontSize);
|
||||
const fontColor = props.pos.options?.fontColor || "black";
|
||||
const handleCellResize = (newCount) => {
|
||||
if (props.setCellCount) props.setCellCount(props.pos.key, newCount);
|
||||
};
|
||||
const isEditable =
|
||||
props.isPlaceholder || props.isSignYourself || props.isSelfSign;
|
||||
return (
|
||||
<CellsWidget
|
||||
isEnabled={iswidgetEnable}
|
||||
count={count}
|
||||
height={height}
|
||||
value={cells.join("")}
|
||||
editable={isEditable}
|
||||
resizable={isEditable}
|
||||
fontSize={fontSize}
|
||||
fontColor={fontColor}
|
||||
hint={hint}
|
||||
onCellCountChange={handleCellResize}
|
||||
/>
|
||||
);
|
||||
}
|
||||
case "dropdown":
|
||||
return (
|
||||
<div
|
||||
style={textWidgetStyle}
|
||||
className="select-none-cls flex justify-between items-center"
|
||||
>
|
||||
{widgetData || hint || widgetTypeTranslation}
|
||||
{widgetData || t("choose-one")}
|
||||
<i className="fa-light fa-circle-chevron-down mr-1 "></i>
|
||||
</div>
|
||||
);
|
||||
@@ -255,17 +275,18 @@ function PlaceholderType(props) {
|
||||
return iswidgetEnable ? (
|
||||
<textarea
|
||||
readOnly
|
||||
ref={inputRef}
|
||||
placeholder={hint || widgetTypeTranslation}
|
||||
rows={1}
|
||||
value={widgetValue}
|
||||
className={textWidgetCls}
|
||||
className={`${textWidgetCls} ${isReadOnly ? "select-none" : ""}`}
|
||||
style={{
|
||||
fontSize: fontSize,
|
||||
color: fontColor,
|
||||
background: isReadOnly ? props.data?.blockColor : "white",
|
||||
pointerEvents: "none"
|
||||
}}
|
||||
cols="50"
|
||||
disabled={props.isNeedSign && isReadOnly}
|
||||
/>
|
||||
) : (
|
||||
<div className="flex h-full select-none-cls" style={textWidgetStyle}>
|
||||
@@ -276,17 +297,18 @@ function PlaceholderType(props) {
|
||||
return iswidgetEnable ? (
|
||||
<textarea
|
||||
readOnly
|
||||
ref={inputRef}
|
||||
placeholder={hint || widgetTypeTranslation}
|
||||
rows={1}
|
||||
value={widgetValue}
|
||||
className={textWidgetCls}
|
||||
className={`${textWidgetCls} ${isReadOnly ? "select-none" : ""}`}
|
||||
style={{
|
||||
fontSize: fontSize,
|
||||
color: fontColor,
|
||||
background: isReadOnly ? props.data?.blockColor : "white",
|
||||
pointerEvents: "none"
|
||||
}}
|
||||
cols="50"
|
||||
disabled={props.isNeedSign && isReadOnly}
|
||||
/>
|
||||
) : (
|
||||
<div style={textWidgetStyle} className="select-none-cls">
|
||||
@@ -297,17 +319,18 @@ function PlaceholderType(props) {
|
||||
return iswidgetEnable ? (
|
||||
<textarea
|
||||
readOnly
|
||||
ref={inputRef}
|
||||
placeholder={hint || widgetTypeTranslation}
|
||||
rows={1}
|
||||
value={widgetValue}
|
||||
className={textWidgetCls}
|
||||
className={`${textWidgetCls} ${isReadOnly ? "select-none" : ""}`}
|
||||
style={{
|
||||
fontSize: fontSize,
|
||||
color: fontColor,
|
||||
background: isReadOnly ? props.data?.blockColor : "white",
|
||||
pointerEvents: "none"
|
||||
}}
|
||||
cols="50"
|
||||
disabled={props.isNeedSign && isReadOnly}
|
||||
/>
|
||||
) : (
|
||||
<div style={textWidgetStyle} className="select-none-cls">
|
||||
@@ -401,19 +424,18 @@ function PlaceholderType(props) {
|
||||
return iswidgetEnable ? (
|
||||
<textarea
|
||||
readOnly
|
||||
ref={inputRef}
|
||||
placeholder={hint || widgetTypeTranslation}
|
||||
rows={1}
|
||||
value={widgetValue}
|
||||
className={textWidgetCls}
|
||||
className={`${textWidgetCls} ${isReadOnly ? "select-none" : ""}`}
|
||||
style={{
|
||||
fontSize: fontSize,
|
||||
color: fontColor,
|
||||
fontFamily: "Arial, sans-serif",
|
||||
background: isReadOnly ? props.data?.blockColor : "white",
|
||||
pointerEvents: "none"
|
||||
}}
|
||||
disabled
|
||||
cols="1"
|
||||
disabled={props.isNeedSign && isReadOnly}
|
||||
/>
|
||||
) : (
|
||||
<div style={textWidgetStyle} className="select-none-cls">
|
||||
@@ -421,46 +443,39 @@ function PlaceholderType(props) {
|
||||
</div>
|
||||
);
|
||||
case radioButtonWidget:
|
||||
const radioLayout = props.pos.options?.layout || "vertical";
|
||||
const isOnlyOneBtn = props.pos.options?.values?.length > 0 ? true : false;
|
||||
const radioWrapperClass = `flex items-start ${
|
||||
radioLayout === "horizontal"
|
||||
? `flex-row flex-wrap ${isOnlyOneBtn ? "gap-x-[10px]" : ""}`
|
||||
: `flex-col ${isOnlyOneBtn ? "gap-y-[5px]" : ""}`
|
||||
}`; // Using gap-y-1 for consistency, adjust if needed
|
||||
return (
|
||||
<div>
|
||||
{props.pos.options?.values.map((data, ind) => {
|
||||
return (
|
||||
<div key={ind} className="select-none-cls pointer-events-none">
|
||||
<label
|
||||
htmlFor={`radio-${props.pos.key + ind}`}
|
||||
style={{
|
||||
fontSize: fontSize,
|
||||
color: fontColor,
|
||||
marginTop: ind > 0 ? "5px" : "0px"
|
||||
}}
|
||||
className="text-xs mb-0 flex items-center gap-1 "
|
||||
>
|
||||
<input
|
||||
readOnly
|
||||
id={`radio-${props.pos.key + ind}`}
|
||||
style={{
|
||||
width: fontSize,
|
||||
height: fontSize,
|
||||
lineHeight: 2
|
||||
}}
|
||||
className={`op-radio rounded-full border- border-black appearance-none bg-white inline-block align-middle relative ${
|
||||
handleRadioCheck(data) ? "checked-radio" : ""
|
||||
}`}
|
||||
type="radio"
|
||||
disabled={
|
||||
props.isNeedSign &&
|
||||
(props.pos.options?.isReadOnly ||
|
||||
props.data?.signerObjId !== props.signerObjId)
|
||||
}
|
||||
checked={handleRadioCheck(data)}
|
||||
/>
|
||||
{!props.pos.options?.isHideLabel && (
|
||||
<span className="leading-none">{data}</span>
|
||||
)}
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<div className={radioWrapperClass}>
|
||||
{props.pos.options?.values.map((data, ind) => (
|
||||
<div key={ind} className="select-none-cls pointer-events-none">
|
||||
<label
|
||||
htmlFor={`radio-${props.pos.key + ind}`}
|
||||
style={{ fontSize: fontSize, color: fontColor }}
|
||||
className="text-xs mb-0 flex items-center gap-1"
|
||||
>
|
||||
<input
|
||||
readOnly
|
||||
id={`radio-${props.pos.key + ind}`}
|
||||
style={{ width: fontSize, height: fontSize, lineHeight: 2 }}
|
||||
className={`op-radio rounded-full border-black appearance-none bg-white inline-block align-middle relative ${
|
||||
handleRadioCheck(data) ? "checked-radio" : ""
|
||||
}`}
|
||||
type="radio"
|
||||
disabled={props.isNeedSign && isReadOnly}
|
||||
checked={handleRadioCheck(data)}
|
||||
/>
|
||||
{!props.pos.options?.isHideLabel && (
|
||||
<span className="leading-none">{data}</span>
|
||||
)}
|
||||
</label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
case textWidget:
|
||||
@@ -474,7 +489,8 @@ function PlaceholderType(props) {
|
||||
style={{
|
||||
fontFamily: "Arial, sans-serif",
|
||||
fontSize: fontSize,
|
||||
color: fontColor
|
||||
color: fontColor,
|
||||
background: "white"
|
||||
}}
|
||||
cols="50"
|
||||
/>
|
||||
|
||||
@@ -31,7 +31,7 @@ const RecipientList = (props) => {
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
//handle draggable element drop and also used in mobile view on up and down key to chnage sequence of recipient's list
|
||||
//handle draggable element drop and also used in mobile view on up and down key to change sequence of recipient's list
|
||||
const handleChangeSequence = (e, ind, isUp, isDown, obj) => {
|
||||
e.preventDefault();
|
||||
let draggedItemId;
|
||||
@@ -82,7 +82,7 @@ const RecipientList = (props) => {
|
||||
};
|
||||
return (
|
||||
<>
|
||||
{props.signersdata.length > 0 &&
|
||||
{props.signersdata?.length > 0 &&
|
||||
props.signersdata.map((obj, ind) => {
|
||||
return (
|
||||
<div
|
||||
|
||||
@@ -3,7 +3,12 @@ import { useTranslation } from "react-i18next";
|
||||
import { Document, Page } from "react-pdf";
|
||||
import { useSelector } from "react-redux";
|
||||
import { PDFDocument } from "pdf-lib";
|
||||
import { base64ToArrayBuffer } from "../../constant/Utils";
|
||||
import {
|
||||
base64ToArrayBuffer,
|
||||
decryptPdf,
|
||||
flattenPdf,
|
||||
getFileAsArrayBuffer
|
||||
} from "../../constant/Utils";
|
||||
import { maxFileSize } from "../../constant/const";
|
||||
|
||||
function RenderAllPdfPage(props) {
|
||||
@@ -87,7 +92,42 @@ function RenderAllPdfPage(props) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const uploadedPdfBytes = await file.arrayBuffer();
|
||||
let uploadedPdfBytes = await file.arrayBuffer();
|
||||
try {
|
||||
uploadedPdfBytes = await flattenPdf(uploadedPdfBytes);
|
||||
} catch (err) {
|
||||
if (err?.message?.includes("is encrypted")) {
|
||||
try {
|
||||
const pdfFile = await decryptPdf(file, "");
|
||||
const pdfArrayBuffer = await getFileAsArrayBuffer(pdfFile);
|
||||
uploadedPdfBytes = await flattenPdf(pdfArrayBuffer);
|
||||
} catch (err) {
|
||||
if (err?.response?.status === 401) {
|
||||
const password = prompt(
|
||||
`PDF "${file.name}" is password-protected. Enter password:`
|
||||
);
|
||||
if (password) {
|
||||
try {
|
||||
const pdfFile = await decryptPdf(file, password);
|
||||
const pdfArrayBuffer = await getFileAsArrayBuffer(pdfFile);
|
||||
uploadedPdfBytes = await flattenPdf(pdfArrayBuffer);
|
||||
// Upload the file to Parse Server
|
||||
} catch (err) {
|
||||
console.error("Incorrect password or decryption failed", err);
|
||||
alert("Incorrect password or decryption failed.");
|
||||
}
|
||||
} else {
|
||||
alert("Please provided Password.");
|
||||
}
|
||||
} else {
|
||||
console.log("Err ", err);
|
||||
alert("error while uploading pdf.");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
alert("error while uploading pdf.");
|
||||
}
|
||||
}
|
||||
const uploadedPdfDoc = await PDFDocument.load(uploadedPdfBytes, {
|
||||
ignoreEncryption: true
|
||||
});
|
||||
@@ -169,8 +209,8 @@ function RenderAllPdfPage(props) {
|
||||
onChange={handleFileUpload}
|
||||
/>
|
||||
<i className="fa-light fa-plus text-gray-500"></i>
|
||||
<span className="text-xs lg:text-sm text-base-content ">
|
||||
Add pages
|
||||
<span className="text-xs lg:text-sm text-base-content">
|
||||
{t("add-pages")}
|
||||
</span>
|
||||
</button>
|
||||
)}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState } from "react";
|
||||
import React, { useState, useRef } from "react";
|
||||
import RSC from "react-scrollbars-custom";
|
||||
import { Document, Page } from "react-pdf";
|
||||
import {
|
||||
@@ -11,13 +11,45 @@ import {
|
||||
import Placeholder from "./Placeholder";
|
||||
import Alert from "../../primitives/Alert";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import usePdfPinchZoom from "../../hook/usePdfPinchZoom";
|
||||
|
||||
function RenderPdf(props) {
|
||||
const { t } = useTranslation();
|
||||
const [scaledHeight, setScaledHeight] = useState();
|
||||
const [guideline, setGuideline] = useState({
|
||||
show: false,
|
||||
x1: 0,
|
||||
x2: 0,
|
||||
y1: 0,
|
||||
y2: 0
|
||||
});
|
||||
//check isGuestSigner is present in local if yes than handle login flow header in mobile view
|
||||
const isGuestSigner = localStorage.getItem("isGuestSigner");
|
||||
|
||||
const pdfContainerRef = useRef(null);
|
||||
|
||||
// enable pinch to zoom only on actual pdf wrapper
|
||||
usePdfPinchZoom(
|
||||
pdfContainerRef,
|
||||
props.scale,
|
||||
props.setScale,
|
||||
props.setZoomPercent
|
||||
);
|
||||
|
||||
const handleGuideline = (isShow, x = 0, y = 0, width = 0, height = 0) => {
|
||||
if (isShow) {
|
||||
setGuideline({
|
||||
show: true,
|
||||
x1: x,
|
||||
x2: x + width,
|
||||
y1: y,
|
||||
y2: y + height
|
||||
});
|
||||
} else {
|
||||
setGuideline({ show: false, x1: 0, x2: 0, y1: 0, y2: 0 });
|
||||
}
|
||||
};
|
||||
|
||||
// handle signature block width and height according to screen
|
||||
const posWidth = (pos, signYourself) => {
|
||||
const containerScale = getContainerScale(
|
||||
@@ -79,7 +111,7 @@ function RenderPdf(props) {
|
||||
}
|
||||
};
|
||||
|
||||
//function for render placeholder block over pdf document
|
||||
// function for render placeholder block over pdf document (all signing flow)
|
||||
const checkSignedSigners = (data) => {
|
||||
let checkSign = [];
|
||||
//condition to handle quick send flow and using normal request sign flow
|
||||
@@ -91,73 +123,73 @@ function RenderPdf(props) {
|
||||
: [];
|
||||
return (
|
||||
checkSign.length === 0 &&
|
||||
data?.placeHolder?.map((placeData, key) => {
|
||||
return (
|
||||
<React.Fragment key={key}>
|
||||
{placeData.pageNumber === props.pageNumber &&
|
||||
placeData.pos.map((pos, ind) => {
|
||||
return (
|
||||
pos && (
|
||||
<React.Fragment key={ind}>
|
||||
<Placeholder
|
||||
pos={pos}
|
||||
handleSignYourselfImageResize={handleImageResize}
|
||||
index={props.pageNumber}
|
||||
xyPosition={props.signerPos}
|
||||
setXyPosition={props.setSignerPos}
|
||||
data={data}
|
||||
setIsResize={props.setIsResize}
|
||||
isShowBorder={props.isSelfSign}
|
||||
isAlllowModify={props.isAlllowModify}
|
||||
signerObjId={props.signerObjectId}
|
||||
isShowDropdown={true}
|
||||
isNeedSign={props.pdfRequest}
|
||||
isSelfSign={true}
|
||||
isSignYourself={false}
|
||||
posWidth={posWidth}
|
||||
posHeight={posHeight}
|
||||
isDragging={props.isDragging}
|
||||
pdfDetails={props.pdfDetails}
|
||||
unSignedWidgetId={props.unSignedWidgetId}
|
||||
setCurrWidgetsDetails={props.setCurrWidgetsDetails}
|
||||
uniqueId={props.uniqueId}
|
||||
scale={props.scale}
|
||||
containerWH={props.containerWH}
|
||||
pdfOriginalWH={props.pdfOriginalWH}
|
||||
pageNumber={props.pageNumber}
|
||||
ispublicTemplate={props.ispublicTemplate}
|
||||
handleUserDetails={props.handleUserDetails}
|
||||
isResize={props.isResize}
|
||||
setIsAgreeTour={props.setIsAgreeTour}
|
||||
isAgree={props.isAgree}
|
||||
handleTabDrag={props.handleTabDrag}
|
||||
handleStop={props.handleStop}
|
||||
setUniqueId={props.setUniqueId}
|
||||
setIsSelectId={props.setIsSelectId}
|
||||
handleDeleteSign={props.handleDeleteSign}
|
||||
setIsPageCopy={props.setIsPageCopy}
|
||||
handleTextSettingModal={props.handleTextSettingModal}
|
||||
setIsCheckbox={props.setIsCheckbox}
|
||||
isFreeResize={false}
|
||||
isOpenSignPad={true}
|
||||
assignedWidgetId={props.assignedWidgetId}
|
||||
isApplyAll={true}
|
||||
setFontSize={props.setFontSize}
|
||||
fontSize={props.fontSize}
|
||||
fontColor={props.fontColor}
|
||||
setFontColor={props.setFontColor}
|
||||
setRequestSignTour={props.setRequestSignTour}
|
||||
calculateFontsize={calculateFontsize}
|
||||
currWidgetsDetails={props?.currWidgetsDetails}
|
||||
setTempSignerId={props.setTempSignerId}
|
||||
/>
|
||||
</React.Fragment>
|
||||
)
|
||||
);
|
||||
})}
|
||||
</React.Fragment>
|
||||
);
|
||||
})
|
||||
data?.placeHolder?.map((placeData, key) => (
|
||||
<React.Fragment key={key}>
|
||||
{placeData.pageNumber === props.pageNumber &&
|
||||
placeData.pos.map(
|
||||
(pos, ind) =>
|
||||
pos && (
|
||||
<React.Fragment key={ind}>
|
||||
<Placeholder
|
||||
pos={pos}
|
||||
handleSignYourselfImageResize={handleImageResize}
|
||||
index={props.pageNumber}
|
||||
xyPosition={props.signerPos}
|
||||
setXyPosition={props.setSignerPos}
|
||||
data={data}
|
||||
setIsResize={props.setIsResize}
|
||||
isShowBorder={props.isSelfSign}
|
||||
isAlllowModify={props.isAlllowModify}
|
||||
signerObjId={props.signerObjectId}
|
||||
isShowDropdown={true}
|
||||
isNeedSign={props.pdfRequest}
|
||||
isSelfSign={true}
|
||||
isSignYourself={false}
|
||||
posWidth={posWidth}
|
||||
posHeight={posHeight}
|
||||
showGuidelines={handleGuideline}
|
||||
isDragging={props.isDragging}
|
||||
pdfDetails={props.pdfDetails}
|
||||
unSignedWidgetId={props.unSignedWidgetId}
|
||||
setCurrWidgetsDetails={props.setCurrWidgetsDetails}
|
||||
uniqueId={props.uniqueId}
|
||||
scale={props.scale}
|
||||
containerWH={props.containerWH}
|
||||
pdfOriginalWH={props.pdfOriginalWH}
|
||||
pageNumber={props.pageNumber}
|
||||
ispublicTemplate={props.ispublicTemplate}
|
||||
handleUserDetails={props.handleUserDetails}
|
||||
isResize={props.isResize}
|
||||
setIsAgreeTour={props.setIsAgreeTour}
|
||||
isAgree={props.isAgree}
|
||||
handleTabDrag={props.handleTabDrag}
|
||||
handleStop={props.handleStop}
|
||||
setUniqueId={props.setUniqueId}
|
||||
setIsSelectId={props.setIsSelectId}
|
||||
handleDeleteSign={props.handleDeleteSign}
|
||||
setIsPageCopy={props.setIsPageCopy}
|
||||
handleTextSettingModal={props.handleTextSettingModal}
|
||||
handleCellSettingModal={props.handleCellSettingModal}
|
||||
setIsCheckbox={props.setIsCheckbox}
|
||||
isFreeResize={props.isSelfSign ? true : false}
|
||||
isOpenSignPad={true}
|
||||
assignedWidgetId={props.assignedWidgetId}
|
||||
isApplyAll={true}
|
||||
setCellCount={props.setCellCount}
|
||||
setFontSize={props.setFontSize}
|
||||
fontSize={props.fontSize}
|
||||
fontColor={props.fontColor}
|
||||
setFontColor={props.setFontColor}
|
||||
setRequestSignTour={props.setRequestSignTour}
|
||||
calculateFontsize={calculateFontsize}
|
||||
currWidgetsDetails={props?.currWidgetsDetails}
|
||||
setTempSignerId={props.setTempSignerId}
|
||||
/>
|
||||
</React.Fragment>
|
||||
)
|
||||
)}
|
||||
</React.Fragment>
|
||||
))
|
||||
);
|
||||
};
|
||||
|
||||
@@ -172,148 +204,145 @@ function RenderPdf(props) {
|
||||
}
|
||||
};
|
||||
const pdfDataBase64 = `data:application/pdf;base64,${props.pdfBase64Url}`;
|
||||
//calculate render height of pdf in mobile view
|
||||
// calculate render height of pdf in mobile view
|
||||
const handlePageLoadSuccess = (page) => {
|
||||
const containerWidth = props.divRef.current.offsetWidth; // Get container width
|
||||
const viewport = page.getViewport({ scale: 1 });
|
||||
const scale = containerWidth / viewport.width; // Scale to fit container width
|
||||
const scaleHeight = viewport.height * scale;
|
||||
setScaledHeight(scaleHeight);
|
||||
if (isMobile) {
|
||||
const containerWidth = props.divRef.current.offsetWidth; // Get container width
|
||||
const viewport = page.getViewport({ scale: 1 });
|
||||
const scale = containerWidth / viewport.width; // Scale to fit container width
|
||||
const scaleHeight = viewport.height * scale;
|
||||
setScaledHeight(scaleHeight);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<>
|
||||
{props.successEmail && (
|
||||
<Alert type={"success"}>{t("success-email-alert")}</Alert>
|
||||
)}
|
||||
{isMobile ? (
|
||||
<RSC
|
||||
<RSC
|
||||
style={{
|
||||
position: "relative",
|
||||
boxShadow: "rgba(17, 12, 46, 0.15) 0px 48px 100px 0px",
|
||||
height: isMobile
|
||||
? isGuestSigner
|
||||
? window.innerHeight - 49 // 49 is height of header
|
||||
: scaledHeight
|
||||
: `${window.innerHeight}px`,
|
||||
zIndex: 0
|
||||
}}
|
||||
noScrollY={isMobile ? props.scale === 1 : false}
|
||||
noScrollX={props.scale === 1}
|
||||
>
|
||||
<div
|
||||
data-tut={isMobile ? "reactourForth" : undefined}
|
||||
className={
|
||||
isMobile
|
||||
? `${isGuestSigner ? "30px" : ""} border-[0.1px] border-[#ebe8e8] overflow-x-auto relative`
|
||||
: "relative"
|
||||
}
|
||||
style={{
|
||||
position: "relative",
|
||||
boxShadow: "rgba(17, 12, 46, 0.15) 0px 48px 100px 0px",
|
||||
//49 is height of header
|
||||
height: isGuestSigner ? window.innerHeight - 49 : scaledHeight,
|
||||
zIndex: 0
|
||||
width:
|
||||
props.containerWH?.width && props.containerWH?.width * props.scale
|
||||
}}
|
||||
noScrollY={props.scale === 1 ? true : false}
|
||||
noScrollX={props.scale === 1 ? true : false}
|
||||
ref={(node) => {
|
||||
pdfContainerRef.current = node;
|
||||
props.drop && props.drop(node);
|
||||
}}
|
||||
id="container"
|
||||
>
|
||||
<div
|
||||
data-tut="reactourForth"
|
||||
className={`${
|
||||
isGuestSigner ? "30px" : ""
|
||||
} border-[0.1px] border-[#ebe8e8] overflow-x-auto`}
|
||||
style={{
|
||||
width:
|
||||
props.containerWH?.width &&
|
||||
props.containerWH?.width * props.scale
|
||||
}}
|
||||
ref={props.drop}
|
||||
id="container"
|
||||
>
|
||||
{props.containerWH?.width &&
|
||||
props.pdfOriginalWH.length > 0 &&
|
||||
(props.pdfRequest || props.isSelfSign
|
||||
? props.signerPos?.map((data, key) => {
|
||||
return (
|
||||
{props.pdfLoad !== false &&
|
||||
props.containerWH?.width &&
|
||||
props.pdfOriginalWH.length > 0 && (
|
||||
<>
|
||||
{props.pdfRequest || props.isSelfSign
|
||||
? // request sign, guest sign,
|
||||
props.signerPos?.map((data, key) => (
|
||||
<React.Fragment key={key}>
|
||||
{checkSignedSigners(data)}
|
||||
</React.Fragment>
|
||||
);
|
||||
})
|
||||
: props.placeholder // placeholder mobile
|
||||
? props.signerPos?.map((data, ind) => {
|
||||
return (
|
||||
))
|
||||
: props.placeholder // placeholdersign document, draft document, create template, draft template
|
||||
? props.signerPos?.map((data, ind) => (
|
||||
<React.Fragment key={ind}>
|
||||
{data?.placeHolder &&
|
||||
data?.placeHolder.map((placeData, index) => {
|
||||
return (
|
||||
<React.Fragment key={index}>
|
||||
{placeData.pageNumber === props.pageNumber &&
|
||||
placeData.pos.map((pos) => {
|
||||
return (
|
||||
<React.Fragment key={pos.key}>
|
||||
<Placeholder
|
||||
pos={pos}
|
||||
setIsPageCopy={props.setIsPageCopy}
|
||||
handleDeleteSign={
|
||||
props.handleDeleteSign
|
||||
}
|
||||
handleTabDrag={props.handleTabDrag}
|
||||
handleStop={props.handleStop}
|
||||
handleSignYourselfImageResize={
|
||||
handleImageResize
|
||||
}
|
||||
index={props.pageNumber}
|
||||
xyPosition={props.signerPos}
|
||||
setXyPosition={props.setSignerPos}
|
||||
data={data}
|
||||
setIsResize={props.setIsResize}
|
||||
setShowDropdown={
|
||||
props.setShowDropdown
|
||||
}
|
||||
isShowBorder={true}
|
||||
isPlaceholder={true}
|
||||
setUniqueId={props.setUniqueId}
|
||||
handleLinkUser={
|
||||
props.handleLinkUser
|
||||
}
|
||||
isSignYourself={false}
|
||||
posWidth={posWidth}
|
||||
posHeight={posHeight}
|
||||
isDragging={props.isDragging}
|
||||
setIsValidate={props.setIsValidate}
|
||||
setIsRadio={props.setIsRadio}
|
||||
setIsCheckbox={props.setIsCheckbox}
|
||||
setCurrWidgetsDetails={
|
||||
props.setCurrWidgetsDetails
|
||||
}
|
||||
handleNameModal={
|
||||
props.handleNameModal
|
||||
}
|
||||
setTempSignerId={
|
||||
props.setTempSignerId
|
||||
}
|
||||
uniqueId={props.uniqueId}
|
||||
handleTextSettingModal={
|
||||
props.handleTextSettingModal
|
||||
}
|
||||
scale={props.scale}
|
||||
containerWH={props.containerWH}
|
||||
pdfOriginalWH={props.pdfOriginalWH}
|
||||
pageNumber={props.pageNumber}
|
||||
setIsSelectId={props.setIsSelectId}
|
||||
fontSize={props.fontSize}
|
||||
setFontSize={props.setFontSize}
|
||||
fontColor={props.fontColor}
|
||||
setFontColor={props.setFontColor}
|
||||
isResize={props.isResize}
|
||||
unSignedWidgetId={
|
||||
props.unSignedWidgetId
|
||||
}
|
||||
isFreeResize={true}
|
||||
calculateFontsize={
|
||||
calculateFontsize
|
||||
}
|
||||
currWidgetsDetails={
|
||||
props?.currWidgetsDetails
|
||||
}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
data?.placeHolder.map((placeData, index) => (
|
||||
<React.Fragment key={index}>
|
||||
{placeData.pageNumber === props.pageNumber &&
|
||||
placeData.pos.map((pos) => (
|
||||
<React.Fragment key={pos.key}>
|
||||
<Placeholder
|
||||
pos={pos}
|
||||
setIsPageCopy={props.setIsPageCopy}
|
||||
handleDeleteSign={
|
||||
props.handleDeleteSign
|
||||
}
|
||||
handleTabDrag={props.handleTabDrag}
|
||||
handleStop={props.handleStop}
|
||||
handleSignYourselfImageResize={
|
||||
handleImageResize
|
||||
}
|
||||
index={props.pageNumber}
|
||||
xyPosition={props.signerPos}
|
||||
setXyPosition={props.setSignerPos}
|
||||
data={data}
|
||||
setIsResize={props.setIsResize}
|
||||
setShowDropdown={props.setShowDropdown}
|
||||
isShowBorder={true}
|
||||
isPlaceholder={true}
|
||||
setUniqueId={props.setUniqueId}
|
||||
handleLinkUser={props.handleLinkUser}
|
||||
isSignYourself={false}
|
||||
posWidth={posWidth}
|
||||
posHeight={posHeight}
|
||||
showGuidelines={handleGuideline}
|
||||
isDragging={props.isDragging}
|
||||
setIsValidate={props.setIsValidate}
|
||||
setIsRadio={props.setIsRadio}
|
||||
setIsCheckbox={props.setIsCheckbox}
|
||||
setCurrWidgetsDetails={
|
||||
props.setCurrWidgetsDetails
|
||||
}
|
||||
handleNameModal={props.handleNameModal}
|
||||
setTempSignerId={props.setTempSignerId}
|
||||
uniqueId={props.uniqueId}
|
||||
handleTextSettingModal={
|
||||
props.handleTextSettingModal
|
||||
}
|
||||
handleCellSettingModal={
|
||||
props.handleCellSettingModal
|
||||
}
|
||||
scale={props.scale}
|
||||
containerWH={props.containerWH}
|
||||
pdfOriginalWH={props.pdfOriginalWH}
|
||||
pageNumber={props.pageNumber}
|
||||
setIsSelectId={props.setIsSelectId}
|
||||
fontSize={props.fontSize}
|
||||
setFontSize={props.setFontSize}
|
||||
setCellCount={props.setCellCount}
|
||||
fontColor={props.fontColor}
|
||||
setFontColor={props.setFontColor}
|
||||
isResize={props.isResize}
|
||||
unSignedWidgetId={
|
||||
props.unSignedWidgetId
|
||||
}
|
||||
isFreeResize={true}
|
||||
calculateFontsize={calculateFontsize}
|
||||
currWidgetsDetails={
|
||||
props?.currWidgetsDetails
|
||||
}
|
||||
/>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</React.Fragment>
|
||||
);
|
||||
})
|
||||
: !props.pdfDetails?.[0]?.IsCompleted &&
|
||||
props.xyPosition?.map((data, ind) => {
|
||||
return (
|
||||
))
|
||||
: !props.pdfDetails?.[0]?.IsCompleted && // signyourself flow
|
||||
props.xyPosition?.map((data, ind) => (
|
||||
<React.Fragment key={ind}>
|
||||
{data.pageNumber === props.pageNumber &&
|
||||
data.pos.map((pos, id) => {
|
||||
return (
|
||||
data.pos.map(
|
||||
(pos, id) =>
|
||||
pos && (
|
||||
<Placeholder
|
||||
key={id}
|
||||
@@ -333,6 +362,7 @@ function RenderPdf(props) {
|
||||
isSignYourself={true}
|
||||
posWidth={posWidth}
|
||||
posHeight={posHeight}
|
||||
showGuidelines={handleGuideline}
|
||||
pdfDetails={props.pdfDetails[0]}
|
||||
isDragging={props.isDragging}
|
||||
setIsCheckbox={props.setIsCheckbox}
|
||||
@@ -342,6 +372,9 @@ function RenderPdf(props) {
|
||||
handleTextSettingModal={
|
||||
props.handleTextSettingModal
|
||||
}
|
||||
handleCellSettingModal={
|
||||
props.handleCellSettingModal
|
||||
}
|
||||
scale={props.scale}
|
||||
pdfOriginalWH={props.pdfOriginalWH}
|
||||
pageNumber={props.pageNumber}
|
||||
@@ -351,7 +384,7 @@ function RenderPdf(props) {
|
||||
setFontColor={props.setFontColor}
|
||||
isResize={props.isResize}
|
||||
setIsResize={props.setIsResize}
|
||||
isFreeResize={false}
|
||||
isFreeResize={true}
|
||||
isOpenSignPad={true}
|
||||
calculateFontsize={calculateFontsize}
|
||||
currWidgetsDetails={
|
||||
@@ -359,247 +392,67 @@ function RenderPdf(props) {
|
||||
}
|
||||
/>
|
||||
)
|
||||
);
|
||||
})}
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
}))}
|
||||
{/* Mobile */}
|
||||
<Document
|
||||
error={<p className="mx-2">{t("failed-to-load-refresh-page")}</p>}
|
||||
onLoadError={() => props.setPdfLoad(false)}
|
||||
loading={t("loading-doc")}
|
||||
onLoadSuccess={props.pageDetails}
|
||||
onClick={() =>
|
||||
props.setCurrWidgetsDetails && props.setCurrWidgetsDetails({})
|
||||
}
|
||||
file={pdfDataBase64}
|
||||
>
|
||||
<Page
|
||||
onLoadSuccess={handlePageLoadSuccess}
|
||||
scale={props.scale || 1}
|
||||
key={props.index}
|
||||
pageNumber={props.pageNumber}
|
||||
width={props.containerWH.width}
|
||||
renderAnnotationLayer={false}
|
||||
renderTextLayer={false}
|
||||
onGetAnnotationsError={(error) => {
|
||||
console.log("annotation error", error);
|
||||
}}
|
||||
className="select-none touch-callout-none"
|
||||
/>
|
||||
</Document>
|
||||
</div>
|
||||
</RSC>
|
||||
) : (
|
||||
<RSC
|
||||
style={{
|
||||
position: "relative",
|
||||
boxShadow: "rgba(17, 12, 46, 0.15) 0px 48px 100px 0px",
|
||||
height: window.innerHeight + "px",
|
||||
zIndex: 0
|
||||
}}
|
||||
noScrollY={false}
|
||||
noScrollX={props.scale === 1 ? true : false}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
width:
|
||||
props.containerWH?.width &&
|
||||
props.containerWH?.width * props.scale
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
<Document
|
||||
error={<p className="mx-2">{t("failed-to-load-refresh-page")}</p>}
|
||||
onLoadError={(e) => {
|
||||
console.log("PDF load error", e);
|
||||
props.setPdfLoad(false);
|
||||
}}
|
||||
ref={props.drop}
|
||||
id="container"
|
||||
loading={t("loading-doc")}
|
||||
onLoadSuccess={(pdf) => {
|
||||
props.setPdfLoad(true);
|
||||
props.pageDetails(pdf);
|
||||
}}
|
||||
onClick={() =>
|
||||
props.setCurrWidgetsDetails && props.setCurrWidgetsDetails({})
|
||||
}
|
||||
file={pdfDataBase64}
|
||||
>
|
||||
{props.pdfLoad &&
|
||||
props.containerWH?.width &&
|
||||
props.pdfOriginalWH.length > 0 &&
|
||||
(props.pdfRequest || props.isSelfSign //pdf request sign flow
|
||||
? props.signerPos?.map((data, key) => {
|
||||
return (
|
||||
<React.Fragment key={key}>
|
||||
{checkSignedSigners(data)}
|
||||
</React.Fragment>
|
||||
);
|
||||
})
|
||||
: props.placeholder //placeholder and template flow
|
||||
? props.signerPos.map((data, ind) => {
|
||||
return (
|
||||
<React.Fragment key={ind}>
|
||||
{data?.placeHolder &&
|
||||
data?.placeHolder.map((placeData, index) => {
|
||||
return (
|
||||
<React.Fragment key={index}>
|
||||
{placeData.pageNumber === props.pageNumber &&
|
||||
placeData.pos.map((pos) => {
|
||||
return (
|
||||
<React.Fragment key={pos.key}>
|
||||
<Placeholder
|
||||
pos={pos}
|
||||
setIsPageCopy={props.setIsPageCopy}
|
||||
handleDeleteSign={
|
||||
props.handleDeleteSign
|
||||
}
|
||||
handleTabDrag={props.handleTabDrag}
|
||||
handleStop={props.handleStop}
|
||||
handleSignYourselfImageResize={
|
||||
handleImageResize
|
||||
}
|
||||
index={props.pageNumber}
|
||||
xyPosition={props.signerPos}
|
||||
setXyPosition={props.setSignerPos}
|
||||
data={data}
|
||||
setIsResize={props.setIsResize}
|
||||
setShowDropdown={
|
||||
props.setShowDropdown
|
||||
}
|
||||
isShowBorder={true}
|
||||
isPlaceholder={true}
|
||||
setUniqueId={props.setUniqueId}
|
||||
handleLinkUser={
|
||||
props.handleLinkUser
|
||||
}
|
||||
isSignYourself={false}
|
||||
posWidth={posWidth}
|
||||
posHeight={posHeight}
|
||||
isDragging={props.isDragging}
|
||||
setIsValidate={props.setIsValidate}
|
||||
setIsRadio={props.setIsRadio}
|
||||
setIsCheckbox={props.setIsCheckbox}
|
||||
setCurrWidgetsDetails={
|
||||
props.setCurrWidgetsDetails
|
||||
}
|
||||
handleNameModal={
|
||||
props.handleNameModal
|
||||
}
|
||||
setTempSignerId={
|
||||
props.setTempSignerId
|
||||
}
|
||||
uniqueId={props.uniqueId}
|
||||
handleTextSettingModal={
|
||||
props.handleTextSettingModal
|
||||
}
|
||||
scale={props.scale}
|
||||
containerWH={props.containerWH}
|
||||
pdfOriginalWH={props.pdfOriginalWH}
|
||||
pageNumber={props.pageNumber}
|
||||
setIsSelectId={props.setIsSelectId}
|
||||
fontSize={props.fontSize}
|
||||
setFontSize={props.setFontSize}
|
||||
fontColor={props.fontColor}
|
||||
setFontColor={props.setFontColor}
|
||||
isResize={props.isResize}
|
||||
unSignedWidgetId={
|
||||
props.unSignedWidgetId
|
||||
}
|
||||
isFreeResize={true}
|
||||
calculateFontsize={
|
||||
calculateFontsize
|
||||
}
|
||||
currWidgetsDetails={
|
||||
props?.currWidgetsDetails
|
||||
}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</React.Fragment>
|
||||
);
|
||||
})
|
||||
: !props.pdfDetails?.[0]?.IsCompleted &&
|
||||
props.xyPosition?.map((data, ind) => {
|
||||
// signyourself flow
|
||||
return (
|
||||
<React.Fragment key={ind}>
|
||||
{data.pageNumber === props.pageNumber &&
|
||||
data.pos.map((pos) => {
|
||||
return (
|
||||
<React.Fragment key={pos.key}>
|
||||
<Placeholder
|
||||
pos={pos}
|
||||
setIsPageCopy={props.setIsPageCopy}
|
||||
handleDeleteSign={props.handleDeleteSign}
|
||||
handleTabDrag={props.handleTabDrag}
|
||||
handleStop={(event, dragElement) =>
|
||||
props.handleStop(
|
||||
event,
|
||||
dragElement,
|
||||
pos.type
|
||||
)
|
||||
}
|
||||
handleSignYourselfImageResize={
|
||||
handleSignYourselfImageResize
|
||||
}
|
||||
index={props.index}
|
||||
xyPosition={props.xyPosition}
|
||||
setXyPosition={props.setXyPosition}
|
||||
isShowBorder={true}
|
||||
isSignYourself={true}
|
||||
posWidth={posWidth}
|
||||
posHeight={posHeight}
|
||||
pdfDetails={props.pdfDetails[0]}
|
||||
isDragging={props.isDragging}
|
||||
setIsCheckbox={props.setIsCheckbox}
|
||||
setCurrWidgetsDetails={
|
||||
props.setCurrWidgetsDetails
|
||||
}
|
||||
handleTextSettingModal={
|
||||
props.handleTextSettingModal
|
||||
}
|
||||
scale={props.scale}
|
||||
containerWH={props.containerWH}
|
||||
pdfOriginalWH={props.pdfOriginalWH}
|
||||
pageNumber={props.pageNumber}
|
||||
fontSize={props.fontSize}
|
||||
setFontSize={props.setFontSize}
|
||||
fontColor={props.fontColor}
|
||||
setFontColor={props.setFontColor}
|
||||
isResize={props.isResize}
|
||||
setIsResize={props.setIsResize}
|
||||
isFreeResize={false}
|
||||
isOpenSignPad={true}
|
||||
calculateFontsize={calculateFontsize}
|
||||
currWidgetsDetails={
|
||||
props?.currWidgetsDetails
|
||||
}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</React.Fragment>
|
||||
);
|
||||
}))}
|
||||
{/* large device */}
|
||||
{/* this component for render pdf document is in middle of the component */}
|
||||
<Document
|
||||
error={<p className="mx-2">{t("failed-to-load-refresh-page")}</p>}
|
||||
onLoadError={() => props.setPdfLoad(false)}
|
||||
loading={t("loading-doc")}
|
||||
onLoadSuccess={props.pageDetails}
|
||||
onClick={() =>
|
||||
props.setCurrWidgetsDetails && props.setCurrWidgetsDetails({})
|
||||
}
|
||||
file={pdfDataBase64}
|
||||
>
|
||||
<Page
|
||||
key={props.index}
|
||||
width={props.containerWH.width}
|
||||
scale={props.scale || 1}
|
||||
className={"-z-[1]"} // when user zoom-in in tablet widgets move backward that's why pass -z-[1]
|
||||
pageNumber={props.pageNumber}
|
||||
renderAnnotationLayer={false}
|
||||
renderTextLayer={false}
|
||||
onGetAnnotationsError={(error) => {
|
||||
console.log("annotation error", error);
|
||||
}}
|
||||
<Page
|
||||
key={props.index}
|
||||
onLoadSuccess={handlePageLoadSuccess}
|
||||
width={props.containerWH.width}
|
||||
scale={props.scale || 1}
|
||||
className={isMobile ? "select-none touch-callout-none" : "-z-[1]"}
|
||||
pageNumber={props.pageNumber}
|
||||
renderAnnotationLayer={false}
|
||||
renderTextLayer={false}
|
||||
onGetAnnotationsError={(error) => {
|
||||
console.log("annotation error", error);
|
||||
}}
|
||||
/>
|
||||
</Document>
|
||||
{guideline.show && (
|
||||
<>
|
||||
{/* top guide */}
|
||||
<div
|
||||
className="absolute pointer-events-none z-[1000] left-0 w-full border-t-[1px] border-dashed border-[#3b82f6]"
|
||||
style={{ top: guideline.y1 }}
|
||||
/>
|
||||
</Document>
|
||||
</div>
|
||||
</RSC>
|
||||
)}
|
||||
{/* bottom guide */}
|
||||
<div
|
||||
className="absolute pointer-events-none z-[1000] left-0 w-full border-t-[1px] border-dashed border-[#3b82f6]"
|
||||
style={{ top: guideline.y2 }}
|
||||
/>
|
||||
{/* left guide */}
|
||||
<div
|
||||
className="absolute pointer-events-none z-[1000] top-0 h-full border-l-[1px] border-dashed border-[#3b82f6]"
|
||||
style={{ left: guideline.x1 }}
|
||||
/>
|
||||
{/* right guide */}
|
||||
<div
|
||||
className="absolute pointer-events-none z-[1000] top-0 h-full border-l-[1px] border-dashed border-[#3b82f6]"
|
||||
style={{ left: guideline.x2 }}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</RSC>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -24,14 +24,14 @@ function SelectLanguage(props) {
|
||||
<div
|
||||
className={`${
|
||||
!props.isProfile && " mt-[9px] pb-2 md:pb-0 "
|
||||
} flex justify-center items-center `}
|
||||
} flex justify-center items-center text-base-content`}
|
||||
>
|
||||
<select
|
||||
value={lang}
|
||||
onChange={handleChangeLang}
|
||||
className={`${
|
||||
!props.isProfile ? " md:w-[15%] w-[50%]" : "w-[180px]"
|
||||
} op-select op-select-bordered bg-white op-select-sm `}
|
||||
} op-select op-select-bordered op-select-sm `}
|
||||
>
|
||||
<option disabled>select</option>
|
||||
{languages.map((item) => {
|
||||
|
||||
@@ -13,7 +13,7 @@ function TextFontSetting(props) {
|
||||
title={t("text-field")}
|
||||
handleClose={() => props.setIsTextSetting(false)}
|
||||
>
|
||||
<div className="h-full p-[20px]">
|
||||
<div className="h-full p-[20px] text-base-content">
|
||||
<div className="flex flex-col md:flex-row md:items-center gap-3">
|
||||
{/* Font Size Selector */}
|
||||
<div className="flex items-center gap-2">
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
isMobile,
|
||||
radioButtonWidget,
|
||||
textInputWidget,
|
||||
cellsWidget,
|
||||
textWidget,
|
||||
widgets
|
||||
} from "../../constant/Utils";
|
||||
@@ -37,6 +38,10 @@ function WidgetComponent(props) {
|
||||
type: "BOX",
|
||||
item: { type: "BOX", id: 7, text: textInputWidget }
|
||||
});
|
||||
const [, cells] = useDrag({
|
||||
type: "BOX",
|
||||
item: { type: "BOX", id: 17, text: cellsWidget }
|
||||
});
|
||||
const [, initials] = useDrag({
|
||||
type: "BOX",
|
||||
item: { type: "BOX", id: 8, text: "initials" }
|
||||
@@ -89,6 +94,7 @@ function WidgetComponent(props) {
|
||||
date,
|
||||
text,
|
||||
textInput,
|
||||
cells,
|
||||
checkbox,
|
||||
dropdown,
|
||||
radioButton,
|
||||
@@ -132,7 +138,9 @@ function WidgetComponent(props) {
|
||||
);
|
||||
const filterWidgets = widget.filter(
|
||||
(data) =>
|
||||
!["dropdown", radioButtonWidget, textInputWidget].includes(data.type)
|
||||
!["dropdown", radioButtonWidget, textInputWidget].includes(
|
||||
data.type
|
||||
)
|
||||
);
|
||||
const textWidgetData = widget.filter((data) => data.type !== textWidget);
|
||||
const updateWidgets = props.isSignYourself
|
||||
|
||||
@@ -5,6 +5,7 @@ import RegexParser from "regex-parser";
|
||||
import {
|
||||
signatureTypes,
|
||||
textInputWidget,
|
||||
cellsWidget,
|
||||
textWidget
|
||||
} from "../../constant/Utils";
|
||||
import { fontColorArr, fontsizeArr } from "../../constant/Utils";
|
||||
@@ -19,7 +20,8 @@ const WidgetNameModal = (props) => {
|
||||
status: "required",
|
||||
hint: "",
|
||||
textvalidate: "",
|
||||
isReadOnly: false
|
||||
isReadOnly: false,
|
||||
cellCount: 5
|
||||
});
|
||||
const [isValid, setIsValid] = useState(true);
|
||||
const statusArr = ["Required", "Optional"];
|
||||
@@ -51,12 +53,14 @@ const WidgetNameModal = (props) => {
|
||||
props.defaultdata?.options?.validation?.type === "regex"
|
||||
? props.defaultdata?.options?.validation?.pattern
|
||||
: props.defaultdata?.options?.validation?.type || "",
|
||||
isReadOnly: props.defaultdata?.options?.isReadOnly || false
|
||||
isReadOnly: props.defaultdata?.options?.isReadOnly || false,
|
||||
cellCount: props.defaultdata?.options?.cellCount || 5
|
||||
});
|
||||
} else {
|
||||
setFormdata({
|
||||
...formdata,
|
||||
name: props.defaultdata?.options?.name || ""
|
||||
name: props.defaultdata?.options?.name || "",
|
||||
cellCount: props.defaultdata?.options?.cellCount || 5
|
||||
});
|
||||
}
|
||||
|
||||
@@ -83,16 +87,17 @@ const WidgetNameModal = (props) => {
|
||||
props.handleData(data, props.defaultdata?.type);
|
||||
}
|
||||
} else {
|
||||
const isTextInput = props.defaultdata?.type === textInputWidget;
|
||||
const isTextInput = [textInputWidget, cellsWidget].includes(
|
||||
props.defaultdata?.type
|
||||
);
|
||||
const { isReadOnly, defaultValue, status } = formdata;
|
||||
// If it’s a text‐input widget, enforce that read-only fields have
|
||||
// either a defaultValue or an "optional" status.
|
||||
if (isTextInput) {
|
||||
const readOnlyWithoutValue =
|
||||
isReadOnly && !defaultValue && status !== "optional";
|
||||
|
||||
if (readOnlyWithoutValue) {
|
||||
alert(t("readonly-textinput-error"));
|
||||
alert(t("readonly-error", { widgetName: props.defaultdata?.type }));
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -104,7 +109,8 @@ const WidgetNameModal = (props) => {
|
||||
defaultValue: "",
|
||||
status: "required",
|
||||
hint: "",
|
||||
textvalidate: ""
|
||||
textvalidate: "",
|
||||
cellCount: 5
|
||||
});
|
||||
setSignatureType(signTypes);
|
||||
}
|
||||
@@ -117,6 +123,23 @@ const WidgetNameModal = (props) => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleChangeValidateInput = (e) => {
|
||||
if (e) {
|
||||
if (e.target.value === "ssn") {
|
||||
setFormdata({
|
||||
...formdata,
|
||||
[e.target.name]: e.target.value,
|
||||
hint: "xxx-xx-xxxx",
|
||||
cellCount: 11
|
||||
});
|
||||
} else {
|
||||
setFormdata({ ...formdata, [e.target.name]: e.target.value });
|
||||
}
|
||||
} else {
|
||||
setFormdata({ ...formdata, textvalidate: "" });
|
||||
}
|
||||
};
|
||||
|
||||
const handledefaultChange = (e) => {
|
||||
if (formdata.textvalidate) {
|
||||
const regexObject = RegexParser(handleValidation(formdata.textvalidate));
|
||||
@@ -125,7 +148,11 @@ const WidgetNameModal = (props) => {
|
||||
} else {
|
||||
setIsValid(true);
|
||||
}
|
||||
setFormdata({ ...formdata, [e.target.name]: e.target.value });
|
||||
const val =
|
||||
props.defaultdata?.type === cellsWidget
|
||||
? e.target.value.slice(0, formdata.cellCount)
|
||||
: e.target.value;
|
||||
setFormdata({ ...formdata, [e.target.name]: val });
|
||||
};
|
||||
|
||||
function handleValidation(type) {
|
||||
@@ -135,7 +162,10 @@ const WidgetNameModal = (props) => {
|
||||
case "number":
|
||||
return "/^\\d+$/";
|
||||
case "text":
|
||||
return "/^[a-zA-Zs]+$/";
|
||||
//allow space in text regex
|
||||
return "/^[a-zA-Z ]+$/";
|
||||
case "ssn":
|
||||
return "/^(?!000|666|9\\d{2})\\d{3}-(?!00)\\d{2}-(?!0000)\\d{4}$/";
|
||||
default:
|
||||
return type;
|
||||
}
|
||||
@@ -163,7 +193,7 @@ const WidgetNameModal = (props) => {
|
||||
<form
|
||||
onSubmit={handleSubmit}
|
||||
className={`${
|
||||
props.defaultdata?.type === textInputWidget
|
||||
[textInputWidget, cellsWidget].includes(props.defaultdata?.type)
|
||||
? "pt-0"
|
||||
: ["signature", "initials"].includes(props.defaultdata?.type)
|
||||
? "pt-2"
|
||||
@@ -187,7 +217,21 @@ const WidgetNameModal = (props) => {
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{props.defaultdata?.type === textInputWidget && (
|
||||
{props.defaultdata?.type === cellsWidget && (
|
||||
<div className="mb-[0.75rem] text-[13px]">
|
||||
<label htmlFor="cellCount">{t("cell-count")}</label>
|
||||
<input
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
type="number"
|
||||
min="1"
|
||||
name="cellCount"
|
||||
value={formdata.cellCount}
|
||||
onChange={(e) => handleChange(e)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{[textInputWidget, cellsWidget].includes(props.defaultdata?.type) && (
|
||||
<>
|
||||
<div className="mb-[0.75rem]">
|
||||
<label htmlFor="name" className="text-[13px]">
|
||||
@@ -199,6 +243,11 @@ const WidgetNameModal = (props) => {
|
||||
value={formdata.defaultValue}
|
||||
onChange={(e) => handledefaultChange(e)}
|
||||
autoComplete="off"
|
||||
maxLength={
|
||||
props.defaultdata?.type === cellsWidget
|
||||
? formdata.cellCount
|
||||
: undefined
|
||||
}
|
||||
onBlur={() => {
|
||||
if (isValid === false) {
|
||||
setFormdata({ ...formdata, defaultValue: "" });
|
||||
@@ -250,7 +299,9 @@ const WidgetNameModal = (props) => {
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{[textInputWidget].includes(props.defaultdata?.type) && (
|
||||
{[textInputWidget, cellsWidget].includes(
|
||||
props.defaultdata?.type
|
||||
) && (
|
||||
<div className="flex items-center">
|
||||
<input
|
||||
id="isReadOnly"
|
||||
@@ -265,7 +316,10 @@ const WidgetNameModal = (props) => {
|
||||
}))
|
||||
}
|
||||
/>
|
||||
<label className="ml-1 mb-0" htmlFor="isreadonly">
|
||||
<label
|
||||
className="ml-1.5 mb-0 capitalize text-[13px]"
|
||||
htmlFor="isreadonly"
|
||||
>
|
||||
{t("read-only")}
|
||||
</label>
|
||||
</div>
|
||||
@@ -317,6 +371,7 @@ const WidgetNameModal = (props) => {
|
||||
{[
|
||||
textInputWidget,
|
||||
textWidget,
|
||||
cellsWidget,
|
||||
"name",
|
||||
"company",
|
||||
"job title",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -195,7 +195,7 @@ const SelectFolder = ({ required, onSuccess, folderCls, isReset }) => {
|
||||
? selectFolder.Name
|
||||
: t("OpenSign-drive", { appName: drivename })}
|
||||
</p>
|
||||
<div className="text-black text-sm">
|
||||
<div className="text-sm">
|
||||
<i
|
||||
className="fa-light fa-pencil cursor-pointer"
|
||||
title={t("select-folder")}
|
||||
|
||||
@@ -97,7 +97,7 @@ const SelectSigners = (props) => {
|
||||
const contactRes = axiosRes?.data?.result || [];
|
||||
if (contactRes) {
|
||||
const res = JSON.parse(JSON.stringify(contactRes));
|
||||
//compareArrays is a function where compare between two array (total signersList and dcument signers list)
|
||||
//compareArrays is a function where compare between two array (total signersList and document signers list)
|
||||
//and filter signers from total signer's list which already present in document's signers list
|
||||
// const compareArrays = (res, signerObj) => {
|
||||
// return res.filter(
|
||||
|
||||
@@ -122,7 +122,7 @@ const SignersInput = (props) => {
|
||||
);
|
||||
if (contactRes) {
|
||||
const res = JSON.parse(JSON.stringify(contactRes));
|
||||
//compareArrays is a function where compare between two array (total signersList and dcument signers list)
|
||||
//compareArrays is a function where compare between two array (total signersList and document signers list)
|
||||
//and filter signers from total signer's list which already present in document's signers list
|
||||
const compareArrays = (res, signerObj) => {
|
||||
return res.filter(
|
||||
|
||||
@@ -18,16 +18,16 @@ const Menu = ({ item, isOpen, closeSidebar }) => {
|
||||
className={({ isActive }) =>
|
||||
`${
|
||||
isActive ? " bg-base-300 text-base-content" : ""
|
||||
} flex items-center justify-start text-left p-3 lg:p-4 text-base-content hover:text-base-content focus:bg-base-300 hover:bg-base-300 hover:no-underline focus:outline-none`
|
||||
} flex gap-x-5 items-center justify-start text-left p-3 text-base-content hover:text-base-content focus:bg-base-300 hover:bg-base-300 hover:no-underline focus:outline-none`
|
||||
}
|
||||
onClick={closeSidebar}
|
||||
tabIndex={isOpen ? 0 : -1}
|
||||
role="menuitem"
|
||||
>
|
||||
<span className="w-[20px] h-[20px] flex justify-center">
|
||||
<i className={`${item.icon} text-[18px]`} aria-hidden="true"></i>
|
||||
<i className={`${item.icon} text-[20px]`} aria-hidden="true"></i>
|
||||
</span>
|
||||
<span className="ml-3 lg:ml-4">
|
||||
<span className="flex items-center mb-0.5">
|
||||
{t(`sidebar.${item.title}`, { appName: drivename })}
|
||||
</span>
|
||||
</NavLink>
|
||||
|
||||
@@ -12,16 +12,16 @@ const Submenu = ({ item, closeSidebar, toggleSubmenu, submenuOpen }) => {
|
||||
<li role="none" className="my-0.5">
|
||||
<button
|
||||
onClick={() => toggleSubmenu(item.title)}
|
||||
className="flex items-center justify-start text-left p-3 lg:p-4 text-base-content hover:text-base-content focus:bg-base-300 hover:bg-base-300 hover:no-underline focus:outline-none "
|
||||
className="flex gap-x-5 items-center justify-start text-left p-3 text-base-content hover:text-base-content focus:bg-base-300 hover:bg-base-300 hover:no-underline focus:outline-none"
|
||||
aria-expanded={submenuOpen}
|
||||
aria-haspopup="true"
|
||||
aria-controls={`submenu-${title}`}
|
||||
>
|
||||
<span className="w-[20px] h-[20px] flex justify-center">
|
||||
<i className={`${icon} text-[18px]`}></i>
|
||||
<i className={`${icon} text-[20px]`}></i>
|
||||
</span>
|
||||
<div className="flex justify-between items-center w-full">
|
||||
<span className="ml-3 lg:ml-4 text-start">
|
||||
<span className="flex items-center mb-0.5">
|
||||
{t(`sidebar.${item.title}`, { appName })}
|
||||
</span>
|
||||
<i
|
||||
@@ -46,20 +46,20 @@ const Submenu = ({ item, closeSidebar, toggleSubmenu, submenuOpen }) => {
|
||||
}
|
||||
className={({ isActive }) =>
|
||||
`${
|
||||
isActive ? "bg-base-300 text-base-content" : ""
|
||||
} flex items-center justify-start text-left pl-6 md:pl-8 py-2 text-sm cursor-pointer text-base-content hover:text-base-content focus:bg-base-300 hover:bg-base-300 hover:no-underline focus:outline-none`
|
||||
isActive ? "bg-base-300 text-base-content " : ""
|
||||
} pl-4 flex items-center gap-x-5 py-2 text-sm cursor-pointer text-base-content hover:text-base-content focus:bg-base-300 hover:bg-base-300 hover:no-underline focus:outline-none`
|
||||
}
|
||||
onClick={closeSidebar}
|
||||
role="menuitem"
|
||||
tabIndex={submenuOpen ? 0 : -1}
|
||||
>
|
||||
<span className="w-[15px] h-[15px] flex justify-center">
|
||||
<span className="w-[18px] h-[18px] flex justify-center">
|
||||
<i
|
||||
className={`${childItem.icon} text-[18px]`}
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</span>
|
||||
<span className="ml-3 lg:ml-4">
|
||||
<span className="mb-0.5">
|
||||
{t(`sidebar.${item.title}-Children.${childItem.title}`, {
|
||||
appName: drivename
|
||||
})}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,6 +3,11 @@ export const templateCls = "contracts_Template";
|
||||
export const documentCls = "contracts_Document";
|
||||
export const themeColor = "#47a3ad";
|
||||
export const iconColor = "#686968";
|
||||
// Dynamic icon color function for better dark mode visibility
|
||||
export const getThemeIconColor = () => {
|
||||
const theme = document.documentElement.getAttribute("data-theme");
|
||||
return theme === "opensigndark" ? "#CCCCCC" : "#686968";
|
||||
};
|
||||
export const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
|
||||
export const maxFileSize = 10; // 10MB
|
||||
export const maxTitleLength = 250; // 250 characters
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
type Size = { width: number; height: number; };
|
||||
|
||||
/**
|
||||
* useSize
|
||||
* @param ref React ref object pointing to a DOM element
|
||||
* @returns current size of that element: { width, height }
|
||||
*/
|
||||
export function useElSize(ref: React.RefObject<HTMLElement>): Size {
|
||||
const [size, setSize] = useState<Size>({ width: 0, height: 0 });
|
||||
|
||||
useEffect(() => {
|
||||
const el = ref.current;
|
||||
if (!el) return;
|
||||
|
||||
// Initialize size
|
||||
setSize({
|
||||
width: el.offsetWidth,
|
||||
height: el.offsetHeight,
|
||||
});
|
||||
|
||||
// Watch for resize
|
||||
const observer = new ResizeObserver(entries => {
|
||||
for (let entry of entries) {
|
||||
const { width, height } = entry.contentRect;
|
||||
setSize({ width, height });
|
||||
}
|
||||
});
|
||||
observer.observe(el);
|
||||
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
};
|
||||
}, [ref]);
|
||||
|
||||
return size;
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
export default function usePdfPinchZoom(ref, scale, setScale, setZoomPercent) {
|
||||
const initialDistanceRef = useRef(null);
|
||||
const startScaleRef = useRef(scale);
|
||||
const currentScaleRef = useRef(scale);
|
||||
const pinchScaleRef = useRef(null); // stores relative scale during a gesture
|
||||
const frameRef = useRef(null);
|
||||
|
||||
// keep a reference to the latest scale value
|
||||
useEffect(() => {
|
||||
const element = ref.current;
|
||||
currentScaleRef.current = scale;
|
||||
// once React commits the new scale after a pinch we clear the transform
|
||||
if (element && !pinchScaleRef.current) {
|
||||
element.style.transform = "";
|
||||
}
|
||||
}, [scale, ref]);
|
||||
|
||||
useEffect(() => {
|
||||
const element = ref.current;
|
||||
if (!element) return;
|
||||
|
||||
const getDistance = (touches) => {
|
||||
const [touch1, touch2] = touches;
|
||||
const dx = touch1.clientX - touch2.clientX;
|
||||
const dy = touch1.clientY - touch2.clientY;
|
||||
return Math.hypot(dx, dy);
|
||||
};
|
||||
|
||||
const handleTouchStart = (e) => {
|
||||
if (e.touches.length === 2) {
|
||||
initialDistanceRef.current = getDistance(e.touches);
|
||||
startScaleRef.current = currentScaleRef.current;
|
||||
element.style.transformOrigin = "0 0";
|
||||
}
|
||||
};
|
||||
|
||||
const handleTouchMove = (e) => {
|
||||
if (e.touches.length === 2 && initialDistanceRef.current) {
|
||||
e.preventDefault();
|
||||
const newDistance = getDistance(e.touches);
|
||||
const pinchScale = newDistance / initialDistanceRef.current;
|
||||
const newScale = Math.max(1, startScaleRef.current * pinchScale);
|
||||
// store relative scale so it can be applied on top of current width
|
||||
pinchScaleRef.current = newScale / startScaleRef.current;
|
||||
|
||||
if (!frameRef.current) {
|
||||
frameRef.current = requestAnimationFrame(() => {
|
||||
element.style.transform = `scale(${pinchScaleRef.current})`;
|
||||
frameRef.current = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleTouchEnd = () => {
|
||||
if (pinchScaleRef.current) {
|
||||
let relativeScale = pinchScaleRef.current;
|
||||
const inlineTransform = element.style.transform;
|
||||
if (inlineTransform && inlineTransform.startsWith("scale(")) {
|
||||
const value = parseFloat(inlineTransform.slice(6, -1));
|
||||
if (!isNaN(value)) {
|
||||
relativeScale = value;
|
||||
}
|
||||
}
|
||||
const finalScale = Math.max(1, startScaleRef.current * relativeScale);
|
||||
const percent = parseFloat(((finalScale - 1) * 100).toFixed(2));
|
||||
const unchanged =
|
||||
Math.abs(finalScale - currentScaleRef.current) < 0.001;
|
||||
|
||||
pinchScaleRef.current = null;
|
||||
if (frameRef.current) {
|
||||
cancelAnimationFrame(frameRef.current);
|
||||
frameRef.current = null;
|
||||
}
|
||||
|
||||
if (unchanged) {
|
||||
// if scale didn't actually change, reset transform right away
|
||||
element.style.transform = "";
|
||||
} else {
|
||||
// keep transform applied until state update reflects the new scale
|
||||
element.style.transform = `scale(${relativeScale})`;
|
||||
}
|
||||
|
||||
setScale(finalScale);
|
||||
setZoomPercent(percent);
|
||||
|
||||
// ensure refs reflect the latest value before next gesture starts
|
||||
currentScaleRef.current = finalScale;
|
||||
startScaleRef.current = finalScale;
|
||||
}
|
||||
initialDistanceRef.current = null;
|
||||
};
|
||||
|
||||
element.addEventListener("touchstart", handleTouchStart, {
|
||||
passive: false
|
||||
});
|
||||
element.addEventListener("touchmove", handleTouchMove, { passive: false });
|
||||
element.addEventListener("touchend", handleTouchEnd);
|
||||
element.addEventListener("touchcancel", handleTouchEnd);
|
||||
|
||||
return () => {
|
||||
element.removeEventListener("touchstart", handleTouchStart);
|
||||
element.removeEventListener("touchmove", handleTouchMove);
|
||||
element.removeEventListener("touchend", handleTouchEnd);
|
||||
element.removeEventListener("touchcancel", handleTouchEnd);
|
||||
};
|
||||
}, [ref, setScale, setZoomPercent]);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useEffect } from "react";
|
||||
|
||||
/**`useScript` hook is generated scripte for google sign in button */
|
||||
/**`useScript` hook is generated script for google sign in button */
|
||||
export const useScript = (url, onload) => {
|
||||
useEffect(() => {
|
||||
const script = document.createElement("script");
|
||||
@@ -15,4 +15,3 @@ export const useScript = (url, onload) => {
|
||||
};
|
||||
}, [url, onload]);
|
||||
};
|
||||
|
||||
|
||||
@@ -98,4 +98,164 @@ body {
|
||||
*::-webkit-scrollbar-thumb {
|
||||
background-color: gray;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
/* Note: Dark mode styling is now handled via Tailwind utilities in tailwind.config.js */
|
||||
/* You can use classes like: icon-improved, icon-muted, icon-disabled, op-btn-vscode-disabled */
|
||||
|
||||
/* React-tour and ReactTooltip dark mode styling */
|
||||
[data-theme="opensigndark"] {
|
||||
/* React-tour modal styling */
|
||||
.reactour__helper {
|
||||
background-color: #1F2937 !important;
|
||||
color: #E5E7EB !important;
|
||||
border: 1px solid #374151 !important;
|
||||
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.5) !important;
|
||||
}
|
||||
|
||||
.reactour__close {
|
||||
color: #E5E7EB !important;
|
||||
background-color: #374151 !important;
|
||||
border: 1px solid #4B5563 !important;
|
||||
}
|
||||
|
||||
.reactour__close:hover {
|
||||
background-color: #4B5563 !important;
|
||||
color: #FFFFFF !important;
|
||||
}
|
||||
|
||||
/* React-tour navigation buttons */
|
||||
.reactour__controls {
|
||||
background-color: #1F2937 !important;
|
||||
border-top: 1px solid #374151 !important;
|
||||
}
|
||||
|
||||
.reactour__controls button {
|
||||
background-color: #374151 !important;
|
||||
color: #E5E7EB !important;
|
||||
border: 1px solid #4B5563 !important;
|
||||
}
|
||||
|
||||
.reactour__controls button:hover {
|
||||
background-color: #4B5563 !important;
|
||||
color: #FFFFFF !important;
|
||||
}
|
||||
|
||||
.reactour__controls button:disabled {
|
||||
background-color: #3C3C3C !important;
|
||||
color: #858585 !important;
|
||||
border-color: #565656 !important;
|
||||
cursor: not-allowed !important;
|
||||
}
|
||||
|
||||
/* ReactTooltip styling */
|
||||
.react-tooltip {
|
||||
background-color: #1F2937 !important;
|
||||
color: #E5E7EB !important;
|
||||
border: 1px solid #374151 !important;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4) !important;
|
||||
}
|
||||
|
||||
.react-tooltip.type-dark {
|
||||
background-color: #1F2937 !important;
|
||||
color: #E5E7EB !important;
|
||||
}
|
||||
|
||||
.react-tooltip.place-top:after,
|
||||
.react-tooltip.place-bottom:after,
|
||||
.react-tooltip.place-left:after,
|
||||
.react-tooltip.place-right:after {
|
||||
border-color: #1F2937 !important;
|
||||
}
|
||||
|
||||
/* Tour content improvements */
|
||||
.reactour__badge {
|
||||
background-color: #007ACC !important;
|
||||
color: #FFFFFF !important;
|
||||
}
|
||||
|
||||
.reactour__helper h1,
|
||||
.reactour__helper h2,
|
||||
.reactour__helper h3,
|
||||
.reactour__helper h4,
|
||||
.reactour__helper h5,
|
||||
.reactour__helper h6 {
|
||||
color: #FFFFFF !important;
|
||||
}
|
||||
|
||||
.reactour__helper p {
|
||||
color: #E5E7EB !important;
|
||||
}
|
||||
|
||||
/* VS Code-style tour buttons */
|
||||
.reactour__controls .op-btn {
|
||||
background-color: #007ACC !important;
|
||||
color: #FFFFFF !important;
|
||||
border: 1px solid #007ACC !important;
|
||||
}
|
||||
|
||||
.reactour__controls .op-btn:hover {
|
||||
background-color: #0086D1 !important;
|
||||
border-color: #0086D1 !important;
|
||||
}
|
||||
|
||||
.reactour__controls .op-btn-secondary {
|
||||
background-color: #374151 !important;
|
||||
color: #E5E7EB !important;
|
||||
border: 1px solid #4B5563 !important;
|
||||
}
|
||||
|
||||
.reactour__controls .op-btn-secondary:hover {
|
||||
background-color: #4B5563 !important;
|
||||
color: #FFFFFF !important;
|
||||
}
|
||||
|
||||
/* React-datepicker dark mode styling */
|
||||
.react-datepicker {
|
||||
background-color: #1F2937 !important;
|
||||
border: 1px solid #374151 !important;
|
||||
color: #E5E7EB !important;
|
||||
}
|
||||
|
||||
.react-datepicker__header {
|
||||
background-color: #374151 !important;
|
||||
border-bottom: 1px solid #4B5563 !important;
|
||||
color: #E5E7EB !important;
|
||||
}
|
||||
|
||||
.react-datepicker__current-month,
|
||||
.react-datepicker__day-name {
|
||||
color: #E5E7EB !important;
|
||||
}
|
||||
|
||||
.react-datepicker__day {
|
||||
color: #E5E7EB !important;
|
||||
}
|
||||
|
||||
.react-datepicker__day:hover {
|
||||
background-color: #4B5563 !important;
|
||||
color: #FFFFFF !important;
|
||||
}
|
||||
|
||||
.react-datepicker__day--selected {
|
||||
background-color: #007ACC !important;
|
||||
color: #FFFFFF !important;
|
||||
}
|
||||
|
||||
.react-datepicker__day--keyboard-selected {
|
||||
background-color: #374151 !important;
|
||||
color: #E5E7EB !important;
|
||||
}
|
||||
|
||||
.react-datepicker__day--outside-month {
|
||||
color: #6B7280 !important;
|
||||
}
|
||||
|
||||
.react-datepicker__navigation {
|
||||
color: #E5E7EB !important;
|
||||
}
|
||||
|
||||
.react-datepicker__navigation:hover {
|
||||
background-color: #4B5563 !important;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import "./index.css";
|
||||
import "./styles/dark-theme-improvements.css";
|
||||
import App from "./App";
|
||||
import { Provider } from "react-redux";
|
||||
import { store } from "./redux/store";
|
||||
@@ -24,6 +25,11 @@ const serverUrl = serverUrl_fn();
|
||||
Parse.initialize(appId);
|
||||
Parse.serverURL = serverUrl;
|
||||
|
||||
const savedTheme = localStorage.getItem("theme");
|
||||
if (savedTheme === "dark") {
|
||||
document.documentElement.setAttribute("data-theme", "opensigndark");
|
||||
}
|
||||
|
||||
const HTML5toTouch = {
|
||||
backends: [
|
||||
{
|
||||
|
||||
@@ -186,6 +186,14 @@ export default function reportJson(id) {
|
||||
btnIcon: "fa-light fa-envelope",
|
||||
redirectUrl: "",
|
||||
action: "saveastemplate"
|
||||
},
|
||||
{
|
||||
btnId: "8440",
|
||||
btnLabel: "Fix & resend",
|
||||
hoverLabel: "Fix & resend",
|
||||
btnIcon: "fa-light fa-paper-plane",
|
||||
redirectUrl: "",
|
||||
action: "recreatedocument"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+202
-232
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useNavigate, useParams } from "react-router";
|
||||
import { formJson } from "../json/FormJson";
|
||||
import Parse from "parse";
|
||||
@@ -12,9 +12,12 @@ import {
|
||||
flattenPdf,
|
||||
generatePdfName,
|
||||
generateTitleFromFilename,
|
||||
getFileName,
|
||||
getSecureUrl,
|
||||
toDataUrl
|
||||
toDataUrl,
|
||||
decryptPdf,
|
||||
base64ToFile,
|
||||
getFileAsArrayBuffer,
|
||||
removeTrailingSegment
|
||||
} from "../constant/Utils";
|
||||
import { PDFDocument } from "pdf-lib";
|
||||
import axios from "axios";
|
||||
@@ -68,6 +71,7 @@ const Forms = (props) => {
|
||||
AllowModifications: false
|
||||
});
|
||||
const [fileupload, setFileUpload] = useState("");
|
||||
const [selectedFiles, setSelectedFiles] = useState([]);
|
||||
const [fileload, setfileload] = useState(false);
|
||||
const [percentage, setpercentage] = useState(0);
|
||||
const [isReset, setIsReset] = useState(false);
|
||||
@@ -114,15 +118,6 @@ const Forms = (props) => {
|
||||
}));
|
||||
};
|
||||
|
||||
function getFileAsArrayBuffer(file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => resolve(e.target.result);
|
||||
reader.onerror = (e) => reject(e.target.error);
|
||||
reader.readAsArrayBuffer(file);
|
||||
});
|
||||
}
|
||||
|
||||
// `removeFile` is used to reset progress, percentage and remove file if exists
|
||||
const removeFile = (e) => {
|
||||
setfileload(false);
|
||||
@@ -134,219 +129,212 @@ const Forms = (props) => {
|
||||
const handleFileInput = async (e) => {
|
||||
setpercentage(0);
|
||||
try {
|
||||
let files = e.target.files;
|
||||
setFormData((prev) => ({ ...prev, file: e.target.files[0] }));
|
||||
if (typeof files[0] !== "undefined") {
|
||||
const mb = Math.round(files[0].size / Math.pow(1024, 2));
|
||||
if (mb > maxFileSize) {
|
||||
alert(`${t("file-alert-1")} ${maxFileSize} MB`);
|
||||
setFileUpload("");
|
||||
removeFile(e);
|
||||
return;
|
||||
} else {
|
||||
if (files?.[0]?.type === "application/pdf") {
|
||||
const size = files?.[0]?.size;
|
||||
const name = generatePdfName(16);
|
||||
const pdfName = `${name?.split(".")[0]}.pdf`;
|
||||
setfileload(true);
|
||||
try {
|
||||
const res = await getFileAsArrayBuffer(files[0]);
|
||||
const flatPdf = await flattenPdf(res);
|
||||
const parseFile = new Parse.File(
|
||||
pdfName,
|
||||
[...flatPdf],
|
||||
"application/pdf"
|
||||
);
|
||||
const files = Array.from(e.target.files);
|
||||
const filesNameArr = files.map((f) => f.name);
|
||||
setSelectedFiles(filesNameArr);
|
||||
if (!files.length) {
|
||||
alert(t("file-alert-2"));
|
||||
return;
|
||||
}
|
||||
// setFormData((prev) => ({ ...prev, file: files[0] }));
|
||||
const totalMb = Math.round(
|
||||
files.reduce((sum, f) => sum + f.size, 0) / Math.pow(1024, 2)
|
||||
);
|
||||
if (totalMb > maxFileSize) {
|
||||
alert(`${t("file-alert-1")} ${maxFileSize} MB`);
|
||||
setFileUpload("");
|
||||
setSelectedFiles([]);
|
||||
removeFile(e);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await parseFile.save({
|
||||
progress: (progressValue, loaded, total, { type }) => {
|
||||
if (type === "upload" && progressValue !== null) {
|
||||
const percentCompleted = Math.round(
|
||||
(loaded * 100) / total
|
||||
);
|
||||
setpercentage(percentCompleted);
|
||||
}
|
||||
}
|
||||
});
|
||||
// The response object will contain information about the uploaded file
|
||||
// You can access the URL of the uploaded file using response.url()
|
||||
if (response.url()) {
|
||||
const fileRes = await getSecureUrl(response.url());
|
||||
if (fileRes.url) {
|
||||
setFileUpload(fileRes.url);
|
||||
setfileload(false);
|
||||
const tenantId = localStorage.getItem("TenantId");
|
||||
const title = generateTitleFromFilename(files?.[0]?.name);
|
||||
setFormData((obj) => ({ ...obj, Name: title }));
|
||||
SaveFileSize(size, fileRes.url, tenantId);
|
||||
return fileRes.url;
|
||||
} else {
|
||||
removeFile(e);
|
||||
}
|
||||
} else {
|
||||
removeFile(e);
|
||||
}
|
||||
} catch (error) {
|
||||
removeFile(e);
|
||||
console.error("Error uploading file:", error);
|
||||
}
|
||||
const pdfBuffers = [];
|
||||
for (const file of files) {
|
||||
setFormData((prev) => ({ ...prev, file: file }));
|
||||
if (file.type === "application/pdf") {
|
||||
try {
|
||||
const buffer = await getFileAsArrayBuffer(file);
|
||||
const flat = await flattenPdf(buffer);
|
||||
pdfBuffers.push(flat);
|
||||
} catch (err) {
|
||||
if (err?.message?.includes("is encrypted")) {
|
||||
try {
|
||||
setIsDecrypting(true);
|
||||
const pdfFile = await decryptPdf(file, "");
|
||||
setIsDecrypting(false);
|
||||
setfileload(true);
|
||||
const res = await getFileAsArrayBuffer(pdfFile);
|
||||
const flatPdf = await flattenPdf(res);
|
||||
// Upload the file to Parse Server
|
||||
pdfBuffers.push(flatPdf);
|
||||
} catch (err) {
|
||||
if (err?.message?.includes("is encrypted")) {
|
||||
try {
|
||||
setIsDecrypting(true);
|
||||
const size = files?.[0].size;
|
||||
const name = generatePdfName(16);
|
||||
const url = "https://ai.nxglabs.in/decryptpdf"; //
|
||||
let formData = new FormData();
|
||||
formData.append("file", files[0]);
|
||||
formData.append("password", "");
|
||||
const config = {
|
||||
headers: { "content-type": "multipart/form-data" },
|
||||
responseType: "blob"
|
||||
};
|
||||
const response = await axios.post(url, formData, config);
|
||||
const pdfBlob = new Blob([response.data], {
|
||||
type: "application/pdf"
|
||||
});
|
||||
const pdfFile = new File([pdfBlob], name, {
|
||||
type: "application/pdf"
|
||||
});
|
||||
setIsDecrypting(false);
|
||||
setfileload(true);
|
||||
removeFile(e);
|
||||
if (err?.response?.status === 401) {
|
||||
// setIsPassword(true);
|
||||
const password = prompt(
|
||||
`PDF "${file.name}" is password-protected. Enter password:`
|
||||
);
|
||||
|
||||
if (password) {
|
||||
try {
|
||||
const pdfFile = await decryptPdf(file, password);
|
||||
setIsDecrypting(false);
|
||||
setfileload(true);
|
||||
const res = await getFileAsArrayBuffer(pdfFile);
|
||||
const flatPdf = await flattenPdf(res);
|
||||
// Upload the file to Parse Server
|
||||
const parseFile = new Parse.File(
|
||||
name,
|
||||
[...flatPdf],
|
||||
"application/pdf"
|
||||
pdfBuffers.push(flatPdf);
|
||||
} catch (err) {
|
||||
console.error(
|
||||
"Incorrect password or decryption failed",
|
||||
err
|
||||
);
|
||||
setSelectedFiles(
|
||||
filesNameArr.filter((f) => f !== file.name)
|
||||
);
|
||||
|
||||
await parseFile.save({
|
||||
progress: (progressValue, loaded, total, { type }) => {
|
||||
if (type === "upload" && progressValue !== null) {
|
||||
const percentCompleted = Math.round(
|
||||
(loaded * 100) / total
|
||||
);
|
||||
setpercentage(percentCompleted);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Retrieve the URL of the uploaded file
|
||||
if (parseFile.url()) {
|
||||
const fileRes = await getSecureUrl(parseFile.url());
|
||||
if (fileRes.url) {
|
||||
setFileUpload(fileRes.url);
|
||||
removeFile();
|
||||
const title = generateTitleFromFilename(
|
||||
files?.[0]?.name
|
||||
);
|
||||
setFormData((obj) => ({ ...obj, Name: title }));
|
||||
const tenantId = localStorage.getItem("TenantId");
|
||||
SaveFileSize(size, fileRes.url, tenantId);
|
||||
return fileRes.url;
|
||||
} else {
|
||||
removeFile(e);
|
||||
}
|
||||
} else {
|
||||
removeFile(e);
|
||||
}
|
||||
} catch (err) {
|
||||
removeFile();
|
||||
if (err?.response?.status === 401) {
|
||||
setIsPassword(true);
|
||||
} else {
|
||||
console.log("Error uploading file: ", err?.response);
|
||||
setIsDecrypting(false);
|
||||
e.target.value = "";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log("err ", err);
|
||||
setFileUpload("");
|
||||
removeFile(e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const isImage = files?.[0]?.type.includes("image/");
|
||||
if (isImage) {
|
||||
const image = await toDataUrl(files[0]);
|
||||
const pdfDoc = await PDFDocument.create();
|
||||
let embedImg;
|
||||
if (files?.[0]?.type === "image/png") {
|
||||
embedImg = await pdfDoc.embedPng(image);
|
||||
} else {
|
||||
embedImg = await pdfDoc.embedJpg(image);
|
||||
}
|
||||
|
||||
// Get image dimensions
|
||||
const imageWidth = embedImg.width;
|
||||
const imageHeight = embedImg.height;
|
||||
const page = pdfDoc.addPage([imageWidth, imageHeight]);
|
||||
page.drawImage(embedImg, {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: imageWidth,
|
||||
height: imageHeight
|
||||
});
|
||||
const size = files?.[0]?.size;
|
||||
const name = generatePdfName(16);
|
||||
const getFile = await pdfDoc.save({
|
||||
useObjectStreams: false
|
||||
});
|
||||
setfileload(true);
|
||||
const pdfName = `${name?.split(".")[0]}.pdf`;
|
||||
const parseFile = new Parse.File(
|
||||
pdfName,
|
||||
[...getFile],
|
||||
"application/pdf"
|
||||
);
|
||||
|
||||
try {
|
||||
const response = await parseFile.save({
|
||||
progress: (progressValue, loaded, total, { type }) => {
|
||||
if (type === "upload" && progressValue !== null) {
|
||||
const percentCompleted = Math.round(
|
||||
(loaded * 100) / total
|
||||
);
|
||||
setpercentage(percentCompleted);
|
||||
}
|
||||
}
|
||||
});
|
||||
// The response object will contain information about the uploaded file
|
||||
// You can access the URL of the uploaded file using response.url()
|
||||
if (response.url()) {
|
||||
const fileRes = await getSecureUrl(response.url());
|
||||
if (fileRes.url) {
|
||||
setFileUpload(fileRes.url);
|
||||
setfileload(false);
|
||||
const tenantId = localStorage.getItem("TenantId");
|
||||
const title = generateTitleFromFilename(files?.[0]?.name);
|
||||
setFormData((obj) => ({ ...obj, Name: title }));
|
||||
SaveFileSize(size, fileRes.url, tenantId);
|
||||
return fileRes.url;
|
||||
} else {
|
||||
removeFile(e);
|
||||
alert(`Incorrect password for file: ${file.name}`);
|
||||
}
|
||||
} else {
|
||||
console.error("password not provided");
|
||||
setSelectedFiles(
|
||||
filesNameArr.filter((f) => f !== file.name)
|
||||
);
|
||||
setIsDecrypting(false);
|
||||
setfileload(false);
|
||||
removeFile(e);
|
||||
}
|
||||
} catch (error) {
|
||||
} else {
|
||||
console.log("Error uploading file: ", err?.response);
|
||||
setIsDecrypting(false);
|
||||
e.target.value = "";
|
||||
removeFile(e);
|
||||
console.error("Error uploading file:", error);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log("err ", err);
|
||||
removeFile(e);
|
||||
}
|
||||
}
|
||||
} else if (file.type.includes("image/")) {
|
||||
const image = await toDataUrl(file);
|
||||
const pdfDoc = await PDFDocument.create();
|
||||
const embed =
|
||||
file.type === "image/png"
|
||||
? await pdfDoc.embedPng(image)
|
||||
: await pdfDoc.embedJpg(image);
|
||||
const page = pdfDoc.addPage([embed.width, embed.height]);
|
||||
page.drawImage(embed, {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: embed.width,
|
||||
height: embed.height
|
||||
});
|
||||
const bytes = await pdfDoc.save({ useObjectStreams: false });
|
||||
pdfBuffers.push(bytes);
|
||||
} else if (
|
||||
file.type ===
|
||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.document" ||
|
||||
file.name.toLowerCase().endsWith(".docx")
|
||||
) {
|
||||
try {
|
||||
const baseApi = localStorage.getItem("baseUrl") || "";
|
||||
const url = removeTrailingSegment(baseApi) + "/docxtopdf";
|
||||
let fd = new FormData();
|
||||
fd.append("file", file);
|
||||
setfileload(true);
|
||||
setpercentage(0);
|
||||
const config = {
|
||||
headers: {
|
||||
"content-type": "multipart/form-data",
|
||||
sessiontoken: Parse.User.current().getSessionToken()
|
||||
},
|
||||
signal: abortController.signal,
|
||||
onUploadProgress: (progressEvent) => {
|
||||
if (progressEvent.total) {
|
||||
const percentCompleted = Math.round(
|
||||
(progressEvent.loaded * 100) / progressEvent.total
|
||||
);
|
||||
setpercentage(percentCompleted);
|
||||
}
|
||||
}
|
||||
};
|
||||
const res = await axios.post(url, fd, config);
|
||||
if (res.data?.url) {
|
||||
const pdfRes = await axios.get(res.data.url, {
|
||||
responseType: "arraybuffer"
|
||||
});
|
||||
pdfBuffers.push(pdfRes.data);
|
||||
}
|
||||
setfileload(false);
|
||||
} catch (err) {
|
||||
setfileload(false);
|
||||
removeFile(e);
|
||||
console.log("err in docx to pdf ", err);
|
||||
const error = isOpenSignDomain
|
||||
? `${t("docx-error")} ${t("docx-error-contact")}`
|
||||
: t("docx-error");
|
||||
alert(error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
}
|
||||
|
||||
if (!pdfBuffers.length) {
|
||||
alert(t("file-alert-2"));
|
||||
return false;
|
||||
setSelectedFiles([]);
|
||||
return;
|
||||
}
|
||||
|
||||
setfileload(true);
|
||||
const merged = await PDFDocument.create();
|
||||
for (const bytes of pdfBuffers) {
|
||||
const doc = await PDFDocument.load(bytes, { ignoreEncryption: true });
|
||||
const pages = await merged.copyPages(doc, doc.getPageIndices());
|
||||
pages.forEach((p) => merged.addPage(p));
|
||||
}
|
||||
|
||||
const pdfBytes = await merged.save({ useObjectStreams: false });
|
||||
const name = generatePdfName(16);
|
||||
const pdfName = `${name}.pdf`;
|
||||
let uploadedUrl = "";
|
||||
const parseFile = new Parse.File(
|
||||
pdfName,
|
||||
[...pdfBytes],
|
||||
"application/pdf"
|
||||
);
|
||||
const response = await parseFile.save({
|
||||
progress: (progressValue, loaded, total, { type }) => {
|
||||
if (type === "upload" && progressValue !== null) {
|
||||
const percentCompleted = Math.round((loaded * 100) / total);
|
||||
setpercentage(percentCompleted);
|
||||
}
|
||||
}
|
||||
});
|
||||
if (response.url()) {
|
||||
const fileRes = await getSecureUrl(response.url());
|
||||
if (fileRes.url) {
|
||||
uploadedUrl = fileRes.url;
|
||||
}
|
||||
}
|
||||
if (uploadedUrl) {
|
||||
const tenantId = localStorage.getItem("TenantId");
|
||||
SaveFileSize(pdfBytes.byteLength, uploadedUrl, tenantId);
|
||||
setFileUpload(uploadedUrl);
|
||||
setfileload(false);
|
||||
const title = generateTitleFromFilename(filesNameArr?.[0]);
|
||||
setFormData((obj) => ({ ...obj, Name: title }));
|
||||
removeFile(e);
|
||||
} else {
|
||||
setfileload(false);
|
||||
removeFile(e);
|
||||
setSelectedFiles([]);
|
||||
}
|
||||
} catch (error) {
|
||||
alert(error.message);
|
||||
return false;
|
||||
setSelectedFiles([]);
|
||||
}
|
||||
};
|
||||
// `isValidURL` is used to check valid webhook url
|
||||
@@ -446,9 +434,6 @@ const Forms = (props) => {
|
||||
className: "contracts_Users",
|
||||
objectId: ExtCls[0].objectId
|
||||
});
|
||||
if (extUserData?.TenantId?.ActiveFileAdapter) {
|
||||
object.set("FileAdapterId", extUserData?.TenantId?.ActiveFileAdapter);
|
||||
}
|
||||
const res = await object.save();
|
||||
if (res) {
|
||||
setSigners([]);
|
||||
@@ -478,6 +463,7 @@ const Forms = (props) => {
|
||||
AllowModifications: false
|
||||
});
|
||||
setFileUpload("");
|
||||
setSelectedFiles([]);
|
||||
setpercentage(0);
|
||||
navigate(`/${props?.redirectRoute}/${res.id}`);
|
||||
}
|
||||
@@ -559,6 +545,7 @@ const Forms = (props) => {
|
||||
setFormData(obj);
|
||||
removeFile();
|
||||
setFileUpload("");
|
||||
setSelectedFiles([]);
|
||||
setTimeout(() => setIsReset(false), 50);
|
||||
};
|
||||
const handleCancel = () => {
|
||||
@@ -571,24 +558,7 @@ const Forms = (props) => {
|
||||
try {
|
||||
const size = formData?.file?.size;
|
||||
const name = generatePdfName(16);
|
||||
const url = "https://ai.nxglabs.in/decryptpdf"; //
|
||||
let Data = new FormData();
|
||||
Data.append("file", formData?.file);
|
||||
Data.append("password", formData.password);
|
||||
const config = {
|
||||
headers: {
|
||||
"content-type": "multipart/form-data"
|
||||
// sessiontoken: Parse.User.current().getSessionToken()
|
||||
},
|
||||
responseType: "blob"
|
||||
};
|
||||
const response = await axios.post(url, Data, config);
|
||||
const pdfBlob = new Blob([response.data], {
|
||||
type: "application/pdf"
|
||||
});
|
||||
const pdfFile = new File([pdfBlob], name, {
|
||||
type: "application/pdf"
|
||||
});
|
||||
const pdfFile = await decryptPdf(formData?.file, formData?.password);
|
||||
setIsDecrypting(false);
|
||||
const res = await getFileAsArrayBuffer(pdfFile);
|
||||
const flatPdf = await flattenPdf(res);
|
||||
@@ -749,19 +719,20 @@ const Forms = (props) => {
|
||||
)}
|
||||
<div className="text-xs">
|
||||
<label className="block">
|
||||
{`${`${t("report-heading.File")} (${t("file-type")}`}${
|
||||
")"
|
||||
}`}
|
||||
{`${`${t("report-heading.File")} (${t("file-type")}`}${", docx)"}`}
|
||||
<span className="text-red-500 text-[13px]">*</span>
|
||||
</label>
|
||||
{fileupload.length > 0 ? (
|
||||
<div className="flex gap-1 justify-center items-center">
|
||||
<div className="flex justify-between items-center op-input op-input-bordered op-input-sm w-full h-full text-[13px]">
|
||||
<div className="break-all cursor-default">
|
||||
{t("file-selected")}: {getFileName(fileupload)}
|
||||
{t("file-selected")}: {selectedFiles.join(", ")}
|
||||
</div>
|
||||
<div
|
||||
onClick={() => setFileUpload("")}
|
||||
onClick={() => {
|
||||
setFileUpload("");
|
||||
setSelectedFiles([]);
|
||||
}}
|
||||
className="cursor-pointer px-[10px] text-[20px] font-bold text-red-500"
|
||||
>
|
||||
<i className="fa-light fa-xmark"></i>
|
||||
@@ -772,12 +743,11 @@ const Forms = (props) => {
|
||||
<div className="flex gap-1 justify-center items-center">
|
||||
<input
|
||||
type="file"
|
||||
multiple
|
||||
className="op-file-input op-file-input-bordered op-file-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
onChange={(e) => handleFileInput(e)}
|
||||
ref={inputFileRef}
|
||||
accept={
|
||||
"application/pdf,image/png,image/jpeg"
|
||||
}
|
||||
accept="application/pdf,application/vnd.openxmlformats-officedocument.wordprocessingml.document,image/png,image/jpeg"
|
||||
onInvalid={(e) =>
|
||||
e.target.setCustomValidity(t("input-required"))
|
||||
}
|
||||
|
||||
@@ -324,63 +324,65 @@ const ManageSign = () => {
|
||||
hidden
|
||||
/>
|
||||
<div className="relative">
|
||||
<div>
|
||||
{image ? (
|
||||
<div className="signatureCanvas relative border-[2px] border-[#888] rounded-box overflow-hidden">
|
||||
<img
|
||||
alt="signature"
|
||||
src={image}
|
||||
className="w-full h-full object-contain"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<SignatureCanvas
|
||||
ref={canvasRef}
|
||||
penColor={penColor}
|
||||
canvasProps={{
|
||||
width: "456px",
|
||||
height: "180px",
|
||||
className:
|
||||
"signatureCanvas border-[2px] border-[#888] rounded-box"
|
||||
}}
|
||||
// backgroundColor="rgb(255, 255, 255)"
|
||||
onEnd={() =>
|
||||
handleSignatureChange(canvasRef.current.toDataURL())
|
||||
}
|
||||
dotSize={1}
|
||||
{isLoader && (
|
||||
<div className="absolute bg-black bg-opacity-30 z-50 w-full h-full flex justify-center items-center">
|
||||
<Loader />
|
||||
</div>
|
||||
)}
|
||||
{image ? (
|
||||
<div className="signatureCanvas relative border-[2px] border-[#888] rounded-box overflow-hidden">
|
||||
<img
|
||||
alt="signature"
|
||||
src={image}
|
||||
className="w-full h-full object-contain"
|
||||
/>
|
||||
)}
|
||||
<div className="penContainerDefault flex flex-row justify-between">
|
||||
<div>
|
||||
{!image && (
|
||||
<div className="flex flex-row gap-1.5 m-[5px]">
|
||||
{allColor.map((data, key) => {
|
||||
return (
|
||||
<i
|
||||
key={key}
|
||||
onClick={() => setPenColor(allColor[key])}
|
||||
className={`border-b-[2px] ${key === 0 && penColor === "blue" ? "border-blue-600" : key === 1 && penColor === "red" ? "border-red-500" : key === 2 && penColor === "black" ? "border-black" : "border-white"} text-[${data}] text-[16px] fa-light fa-pen-nib`}
|
||||
></i>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<SignatureCanvas
|
||||
ref={canvasRef}
|
||||
penColor={penColor}
|
||||
canvasProps={{
|
||||
width: "456px",
|
||||
height: "180px",
|
||||
className:
|
||||
"signatureCanvas border-[2px] border-[#888] rounded-box"
|
||||
}}
|
||||
onEnd={() =>
|
||||
handleSignatureChange(canvasRef.current.toDataURL())
|
||||
}
|
||||
dotSize={1}
|
||||
/>
|
||||
)}
|
||||
<div className="penContainerDefault flex flex-row justify-between">
|
||||
<div>
|
||||
{!image && (
|
||||
<div className="flex flex-row gap-1.5 m-[5px]">
|
||||
{allColor.map((data, key) => {
|
||||
return (
|
||||
<i
|
||||
key={key}
|
||||
onClick={() => setPenColor(allColor[key])}
|
||||
className={`border-b-[2px] ${key === 0 && penColor === "blue" ? "border-blue-600" : key === 1 && penColor === "red" ? "border-red-500" : key === 2 && penColor === "black" ? "border-black" : "border-white"} text-[${data}] text-[16px] fa-light fa-pen-nib`}
|
||||
></i>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-row gap-2 text-sm md:text-base mr-1">
|
||||
<div
|
||||
type="button"
|
||||
className="op-link"
|
||||
onClick={() => handleUploadBtn()}
|
||||
>
|
||||
{t("upload")}
|
||||
</div>
|
||||
<div className="flex flex-row gap-2 text-sm md:text-base mr-1">
|
||||
<div
|
||||
type="button"
|
||||
className="op-link"
|
||||
onClick={() => handleUploadBtn()}
|
||||
>
|
||||
{t("upload")}
|
||||
</div>
|
||||
<div
|
||||
type="button"
|
||||
className="op-link"
|
||||
onClick={() => handleClear()}
|
||||
>
|
||||
{t("clear")}
|
||||
</div>
|
||||
<div
|
||||
type="button"
|
||||
className="op-link"
|
||||
onClick={() => handleClear()}
|
||||
>
|
||||
{t("clear")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -420,7 +422,6 @@ const ManageSign = () => {
|
||||
canvasProps={{
|
||||
className: "intialSignature rounded-box"
|
||||
}}
|
||||
// backgroundColor="rgb(255, 255, 255)"
|
||||
onEnd={() => handleInitialsChange()}
|
||||
dotSize={1}
|
||||
/>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useEffect, useState, useRef } from "react";
|
||||
import "../styles/opensigndrive.css";
|
||||
import {
|
||||
iconColor,
|
||||
getThemeIconColor,
|
||||
} from "../constant/const";
|
||||
import {
|
||||
getDrive
|
||||
@@ -539,6 +539,12 @@ function Opensigndrive() {
|
||||
}
|
||||
}, 300);
|
||||
};
|
||||
|
||||
const handleSearchPaste = (e) => {
|
||||
setTimeout(() => {
|
||||
handleSearchChange({ target: { value: e.target.value } });
|
||||
}, 0);
|
||||
};
|
||||
// Cleanup on unmount
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
@@ -657,7 +663,8 @@ function Opensigndrive() {
|
||||
type="search"
|
||||
value={searchTerm}
|
||||
onChange={handleSearchChange}
|
||||
placeholder="Search documents…"
|
||||
placeholder={t("search-documents")}
|
||||
onPaste={handleSearchPaste}
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-64 text-xs"
|
||||
/>
|
||||
</div>
|
||||
@@ -668,7 +675,7 @@ function Opensigndrive() {
|
||||
onClick={() => setMobileSearchOpen((open) => !open)}
|
||||
>
|
||||
<i
|
||||
style={{ color: `${iconColor}` }}
|
||||
style={{ color: `${getThemeIconColor()}` }}
|
||||
className="fa-solid fa-magnifying-glass"
|
||||
></i>
|
||||
</button>
|
||||
@@ -681,7 +688,7 @@ function Opensigndrive() {
|
||||
<i
|
||||
className="fa-light fa-plus-square text-[24px]"
|
||||
aria-hidden="true"
|
||||
style={{ color: `${iconColor}` }}
|
||||
style={{ color: `${getThemeIconColor()}` }}
|
||||
></i>
|
||||
</div>
|
||||
<div
|
||||
@@ -730,9 +737,9 @@ function Opensigndrive() {
|
||||
<i
|
||||
className="fa-light fa-sort-amount-asc mr-[5px] text-[19px]"
|
||||
aria-hidden="true"
|
||||
style={{ color: `${iconColor}` }}
|
||||
style={{ color: `${getThemeIconColor()}` }}
|
||||
></i>
|
||||
<span style={{ fontSize: "15px", color: `${iconColor}` }}>
|
||||
<span style={{ fontSize: "15px", color: `${getThemeIconColor()}` }}>
|
||||
{selectedSort}
|
||||
</span>
|
||||
</div>
|
||||
@@ -793,7 +800,7 @@ function Opensigndrive() {
|
||||
>
|
||||
<i
|
||||
className={`${isList ? "fa-light fa-th-large" : "fa-light fa-list"} text-[20px]`}
|
||||
style={{ color: `${iconColor}` }}
|
||||
style={{ color: `${getThemeIconColor()}` }}
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</div>
|
||||
@@ -809,7 +816,7 @@ function Opensigndrive() {
|
||||
<i
|
||||
className="fa-light fa-ellipsis-vertical fa-lg"
|
||||
aria-hidden="true"
|
||||
style={{ color: `${iconColor}` }}
|
||||
style={{ color: `${getThemeIconColor()}` }}
|
||||
></i>
|
||||
</div>
|
||||
<div
|
||||
@@ -854,7 +861,8 @@ function Opensigndrive() {
|
||||
type="search"
|
||||
value={searchTerm}
|
||||
onChange={handleSearchChange}
|
||||
placeholder="Search documents…"
|
||||
placeholder={t("search-documents")}
|
||||
onPaste={handleSearchPaste}
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -23,7 +23,6 @@ import {
|
||||
pdfNewWidthFun,
|
||||
signPdfFun,
|
||||
addDefaultSignatureImg,
|
||||
radioButtonWidget,
|
||||
replaceMailVaribles,
|
||||
convertPdfArrayBuffer,
|
||||
contractUsers,
|
||||
@@ -47,7 +46,8 @@ import {
|
||||
mailTemplate,
|
||||
updateDateWidgetsRes,
|
||||
widgetDataValue,
|
||||
getOriginalWH
|
||||
getOriginalWH,
|
||||
handleCheckResponse,
|
||||
} from "../constant/Utils";
|
||||
import Header from "../components/pdf/PdfHeader";
|
||||
import RenderPdf from "../components/pdf/RenderPdf";
|
||||
@@ -277,6 +277,15 @@ function PdfRequestFiles(
|
||||
let currUserId;
|
||||
//getting document details
|
||||
const documentData = await contractDocument(docId);
|
||||
// Filter out 'prefill' roles from the Placeholder array
|
||||
const filteredPlaceholder = documentData[0].Placeholders.filter(
|
||||
(data) => data.Role !== "prefill"
|
||||
);
|
||||
// Reassign the updated Placeholder back to the documentData array
|
||||
documentData[0] = {
|
||||
...documentData[0],
|
||||
Placeholders: filteredPlaceholder
|
||||
};
|
||||
if (documentData && documentData.length > 0) {
|
||||
const userSignatureType =
|
||||
documentData[0]?.ExtUserPtr?.SignatureType || signatureTypes;
|
||||
@@ -624,6 +633,7 @@ function PdfRequestFiles(
|
||||
} catch (err) {
|
||||
console.log("err in get email verification ", err);
|
||||
setHandleError(t("something-went-wrong-mssg"));
|
||||
setIsUiLoading(false);
|
||||
}
|
||||
}
|
||||
//check if isEmailVerified then go on next step
|
||||
@@ -633,153 +643,13 @@ function PdfRequestFiles(
|
||||
(data) => data.signerObjId === signerObjectId
|
||||
);
|
||||
if (checkUser && checkUser.length > 0) {
|
||||
let checkboxExist,
|
||||
requiredRadio,
|
||||
showAlert = false,
|
||||
widgetKey,
|
||||
radioExist,
|
||||
requiredCheckbox,
|
||||
TourPageNumber; // `pageNumber` is used to check on which page user did not fill widget's data then change current pageNumber and show tour message on that page
|
||||
|
||||
for (let i = 0; i < checkUser[0].placeHolder.length; i++) {
|
||||
for (let j = 0; j < checkUser[0].placeHolder[i].pos.length; j++) {
|
||||
//get current page
|
||||
const updatePage = checkUser[0].placeHolder[i]?.pageNumber;
|
||||
//checking checbox type widget
|
||||
checkboxExist =
|
||||
checkUser[0].placeHolder[i].pos[j].type === "checkbox";
|
||||
//checking radio button type widget
|
||||
radioExist =
|
||||
checkUser[0].placeHolder[i].pos[j].type === radioButtonWidget;
|
||||
//condition to check checkbox widget exist or not
|
||||
if (checkboxExist) {
|
||||
//get all required type checkbox
|
||||
requiredCheckbox = checkUser[0].placeHolder[i].pos.filter(
|
||||
(position) =>
|
||||
!position.options?.isReadOnly &&
|
||||
position.type === "checkbox"
|
||||
);
|
||||
//if required type checkbox data exit then check user checked all checkbox or some checkbox remain to check
|
||||
//also validate to minimum and maximum required checkbox
|
||||
if (requiredCheckbox && requiredCheckbox.length > 0) {
|
||||
for (let i = 0; i < requiredCheckbox.length; i++) {
|
||||
//get minimum required count if exit
|
||||
const minCount =
|
||||
requiredCheckbox[i].options?.validation?.minRequiredCount;
|
||||
const parseMin = minCount && parseInt(minCount);
|
||||
//get maximum required count if exit
|
||||
const maxCount =
|
||||
requiredCheckbox[i].options?.validation?.maxRequiredCount;
|
||||
const parseMax = maxCount && parseInt(maxCount);
|
||||
//in `response` variable is used to get how many checkbox checked by user
|
||||
const response =
|
||||
requiredCheckbox[i].options?.response?.length;
|
||||
//in `defaultValue` variable is used to get how many checkbox checked by default
|
||||
const defaultValue =
|
||||
requiredCheckbox[i].options?.defaultValue?.length;
|
||||
//condition to check parseMin and parseMax greater than 0 then consider it as a required check box
|
||||
if (
|
||||
parseMin > 0 &&
|
||||
parseMax > 0 &&
|
||||
!response &&
|
||||
!defaultValue &&
|
||||
!showAlert
|
||||
) {
|
||||
showAlert = true;
|
||||
widgetKey = requiredCheckbox[i].key;
|
||||
TourPageNumber = updatePage;
|
||||
setminRequiredCount(parseMin);
|
||||
}
|
||||
//else condition to validate minimum required checkbox
|
||||
else if (
|
||||
parseMin > 0 &&
|
||||
(parseMin > response || !response)
|
||||
) {
|
||||
if (!showAlert) {
|
||||
showAlert = true;
|
||||
widgetKey = requiredCheckbox[i].key;
|
||||
TourPageNumber = updatePage;
|
||||
setminRequiredCount(parseMin);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//condition to check radio widget exist or not
|
||||
else if (radioExist) {
|
||||
//get all required type radio button
|
||||
requiredRadio = checkUser[0].placeHolder[i].pos.filter(
|
||||
(position) =>
|
||||
!position.options?.isReadOnly &&
|
||||
position.type === radioButtonWidget
|
||||
);
|
||||
//if required type radio data exit then check user checked all radio button or some radio remain to check
|
||||
if (requiredRadio && requiredRadio?.length > 0) {
|
||||
let checkSigned;
|
||||
for (let i = 0; i < requiredRadio?.length; i++) {
|
||||
checkSigned = requiredRadio[i]?.options?.response;
|
||||
if (!checkSigned) {
|
||||
let checkDefaultSigned =
|
||||
requiredRadio[i]?.options?.defaultValue;
|
||||
if (!checkDefaultSigned && !showAlert) {
|
||||
showAlert = true;
|
||||
widgetKey = requiredRadio[i].key;
|
||||
TourPageNumber = updatePage;
|
||||
setminRequiredCount(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//else condition to check all type widget data fill or not except checkbox and radio button
|
||||
else {
|
||||
//get all required type widgets except checkbox and radio
|
||||
const requiredWidgets = checkUser[0].placeHolder[i].pos.filter(
|
||||
(position) =>
|
||||
position.options?.status === "required" &&
|
||||
position.type !== radioButtonWidget &&
|
||||
position.type !== "checkbox"
|
||||
);
|
||||
if (requiredWidgets && requiredWidgets?.length > 0) {
|
||||
let checkSigned;
|
||||
for (let i = 0; i < requiredWidgets?.length; i++) {
|
||||
checkSigned = requiredWidgets[i]?.options?.response;
|
||||
if (!checkSigned) {
|
||||
const checkSignUrl = requiredWidgets[i]?.pos?.SignUrl;
|
||||
if (!checkSignUrl) {
|
||||
let checkDefaultSigned =
|
||||
requiredWidgets[i]?.options?.defaultValue;
|
||||
if (!checkDefaultSigned && !showAlert) {
|
||||
showAlert = true;
|
||||
widgetKey = requiredWidgets[i].key;
|
||||
TourPageNumber = updatePage;
|
||||
setminRequiredCount(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//when showAlert is true then break the loop and show alert to fill required data in widgets
|
||||
if (showAlert) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (checkboxExist && requiredCheckbox && showAlert) {
|
||||
setUnSignedWidgetId(widgetKey);
|
||||
setPageNumber(TourPageNumber);
|
||||
setWidgetsTour(true);
|
||||
} else if (radioExist && showAlert) {
|
||||
setUnSignedWidgetId(widgetKey);
|
||||
setPageNumber(TourPageNumber);
|
||||
setWidgetsTour(true);
|
||||
} else if (showAlert) {
|
||||
setUnSignedWidgetId(widgetKey);
|
||||
setPageNumber(TourPageNumber);
|
||||
const status = handleCheckResponse(checkUser,setminRequiredCount)
|
||||
if (status?.showAlert) {
|
||||
setUnSignedWidgetId(status?.widgetKey);
|
||||
setPageNumber(status?.tourPageNumber);
|
||||
setWidgetsTour(true);
|
||||
setIsUiLoading(false);
|
||||
} else {
|
||||
setIsUiLoading(true);
|
||||
// `widgets` is Used to return widgets details with page number of current user
|
||||
const widgets = checkUser?.[0]?.placeHolder;
|
||||
let pdfArrBuffer;
|
||||
@@ -823,13 +693,11 @@ function PdfRequestFiles(
|
||||
}
|
||||
//embed all widgets in document
|
||||
const pdfBytes = await multiSignEmbed(
|
||||
pdfOriginalWH,
|
||||
widgets,
|
||||
pdfDoc,
|
||||
isSignYourSelfFlow,
|
||||
scale
|
||||
);
|
||||
// console.log("pdfte", pdfBytes);
|
||||
//get ExistUserPtr object id of user class to get tenantDetails
|
||||
if (!pdfBytes?.error) {
|
||||
const objectId = pdfDetails?.[0]?.ExtUserPtr?.UserId?.objectId;
|
||||
@@ -855,16 +723,19 @@ function PdfRequestFiles(
|
||||
isSuccessRoute,
|
||||
contactId
|
||||
);
|
||||
const index = pdfDetails?.[0]?.Signers.findIndex(
|
||||
(x) => x.objectId === signerObjectId
|
||||
);
|
||||
const index =
|
||||
updatedDoc.updatedPdfDetails?.[0]?.Signers.findIndex(
|
||||
(x) => x.objectId === contactId
|
||||
);
|
||||
const newIndex = index + 1;
|
||||
const usermail = {
|
||||
Email: pdfDetails?.[0]?.Placeholders[newIndex]?.email || ""
|
||||
Email:
|
||||
updatedDoc.updatedPdfDetails?.[0]?.Placeholders[newIndex]
|
||||
?.email || ""
|
||||
};
|
||||
const user = usermail?.Email
|
||||
? usermail
|
||||
: pdfDetails?.[0]?.Signers[newIndex];
|
||||
: updatedDoc.updatedPdfDetails?.[0]?.Signers[newIndex];
|
||||
if (
|
||||
sendmail !== "false" &&
|
||||
sendInOrder
|
||||
@@ -1023,6 +894,7 @@ function PdfRequestFiles(
|
||||
isShow: true,
|
||||
alertMessage: t("something-went-wrong-mssg")
|
||||
});
|
||||
setIsUiLoading(false);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("err in embedsign", err);
|
||||
@@ -1034,8 +906,8 @@ function PdfRequestFiles(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleSignPdf = async () => {
|
||||
setIsUiLoading(true);
|
||||
await embedWidgetsData();
|
||||
};
|
||||
|
||||
@@ -1059,15 +931,15 @@ function PdfRequestFiles(
|
||||
let filterSignerPos = [];
|
||||
if (signerObjId) {
|
||||
//get current signerObjId placeholder details
|
||||
filterSignerPos = updateSignPos.filter(
|
||||
filterSignerPos = updateSignPos?.filter(
|
||||
(data) => data.Id === signerObjId
|
||||
);
|
||||
}
|
||||
|
||||
if (filterSignerPos.length > 0) {
|
||||
const getPlaceHolder = filterSignerPos[0].placeHolder;
|
||||
const getPlaceHolder = filterSignerPos[0]?.placeHolder;
|
||||
//get position of current pagenumber
|
||||
const getPageNumer = getPlaceHolder.filter(
|
||||
const getPageNumer = getPlaceHolder?.filter(
|
||||
(data) => data.pageNumber === pageNumber
|
||||
);
|
||||
if (getPageNumer.length > 0) {
|
||||
@@ -1111,9 +983,9 @@ function PdfRequestFiles(
|
||||
setIsTextSetting(value);
|
||||
};
|
||||
const handleSaveFontSize = () => {
|
||||
const filterSignerPos = signerPos.filter((data) => data.Id === uniqueId);
|
||||
const filterSignerPos = signerPos?.filter((data) => data.Id === uniqueId);
|
||||
if (filterSignerPos) {
|
||||
const placehoder = filterSignerPos[0].placeHolder;
|
||||
const placehoder = filterSignerPos[0]?.placeHolder;
|
||||
const getPageNumer = placehoder.filter(
|
||||
(data) => data.pageNumber === pageNumber
|
||||
);
|
||||
@@ -1229,7 +1101,7 @@ function PdfRequestFiles(
|
||||
const addDefaultSignature = () => {
|
||||
const type = defaultSignAlert?.type;
|
||||
//get current signers placeholder position data
|
||||
const currentSignerPosition = signerPos.filter(
|
||||
const currentSignerPosition = signerPos?.filter(
|
||||
(data) => data.signerObjId === signerObjectId
|
||||
);
|
||||
const defaultSign = type === "signature" ? defaultSignImg : myInitial;
|
||||
@@ -1254,7 +1126,8 @@ function PdfRequestFiles(
|
||||
setRequestSignTour(true);
|
||||
if (isDontShow) {
|
||||
const isEnableOTP = pdfDetails?.[0]?.IsEnableOTP || false;
|
||||
if (!isEnableOTP) {
|
||||
const sessionToken = localStorage.getItem("accesstoken");
|
||||
if (!isEnableOTP && !sessionToken) {
|
||||
try {
|
||||
await axios.post(
|
||||
`${localStorage.getItem("baseUrl")}functions/updatecontacttour`,
|
||||
@@ -1619,11 +1492,11 @@ function PdfRequestFiles(
|
||||
}
|
||||
if (uniqueId) {
|
||||
let filterSignerPos, currentPagePosition;
|
||||
filterSignerPos = signerPos.find((data) => data.Id === uniqueId);
|
||||
filterSignerPos = signerPos?.find((data) => data.Id === uniqueId);
|
||||
const getPlaceHolder = filterSignerPos?.placeHolder;
|
||||
if (getPlaceHolder) {
|
||||
//checking exist placeholder on same page
|
||||
currentPagePosition = getPlaceHolder.find(
|
||||
currentPagePosition = getPlaceHolder?.find(
|
||||
(data) => data.pageNumber === pageNumber
|
||||
);
|
||||
}
|
||||
@@ -1651,12 +1524,6 @@ function PdfRequestFiles(
|
||||
);
|
||||
setSignerPos(updatesignerPos);
|
||||
}
|
||||
|
||||
// if (dragTypeValue === "dropdown") {
|
||||
// setShowDropdown(true);
|
||||
// } else if (dragTypeValue === "checkbox") {
|
||||
// setIsCheckbox(true);
|
||||
// } else
|
||||
if (
|
||||
[textWidget, "name", "company", "job title", "email"].includes(
|
||||
dragTypeValue
|
||||
@@ -1672,7 +1539,7 @@ function PdfRequestFiles(
|
||||
//function for delete signature block
|
||||
const handleDeleteSign = (key, Id) => {
|
||||
const updateData = [];
|
||||
const filterSignerPos = signerPos.filter((data) => data.Id === Id);
|
||||
const filterSignerPos = signerPos?.filter((data) => data.Id === Id);
|
||||
if (filterSignerPos.length > 0) {
|
||||
const getPlaceHolder = filterSignerPos[0].placeHolder;
|
||||
const getPageNumer = getPlaceHolder.filter(
|
||||
@@ -1700,7 +1567,7 @@ function PdfRequestFiles(
|
||||
});
|
||||
setSignerPos(newUpdateSigner);
|
||||
} else {
|
||||
const getRemainPage = filterSignerPos[0].placeHolder.filter(
|
||||
const getRemainPage = filterSignerPos[0]?.placeHolder?.filter(
|
||||
(data) => data.pageNumber !== pageNumber
|
||||
);
|
||||
//condition to check placeholder length is greater than 1 do not need to remove whole placeholder
|
||||
@@ -1713,11 +1580,11 @@ function PdfRequestFiles(
|
||||
return obj;
|
||||
});
|
||||
let signerupdate = [];
|
||||
signerupdate = signerPos.filter((data) => data.Id !== Id);
|
||||
signerupdate = signerPos?.filter((data) => data.Id !== Id);
|
||||
signerupdate.push(newUpdatePos[0]);
|
||||
setSignerPos(signerupdate);
|
||||
} else {
|
||||
const updatedData = signerPos.map((item) => {
|
||||
const updatedData = signerPos?.map((item) => {
|
||||
if (item.Id === Id) {
|
||||
// Create a copy of the item object and delete the placeHolder field
|
||||
const updatedItem = { ...item };
|
||||
@@ -1735,7 +1602,7 @@ function PdfRequestFiles(
|
||||
//function to get first widget and page number to assign currect signer and tour message
|
||||
const showFirstWidget = () => {
|
||||
if (!requestSignTour) {
|
||||
const getCurrentUserPlaceholder = signerPos.find(
|
||||
const getCurrentUserPlaceholder = signerPos?.find(
|
||||
(x) => x.Id === uniqueId
|
||||
);
|
||||
const placeholder = getCurrentUserPlaceholder.placeHolder;
|
||||
@@ -1756,7 +1623,6 @@ function PdfRequestFiles(
|
||||
setShowSignPagenumber(sortedPagenumber);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<DndProvider backend={HTML5Backend}>
|
||||
<Title
|
||||
@@ -1828,6 +1694,7 @@ function PdfRequestFiles(
|
||||
{!requestSignTour &&
|
||||
isAgree &&
|
||||
signerObjectId &&
|
||||
!alreadySign &&
|
||||
requestSignTourFunction()}
|
||||
<Tour
|
||||
showNumber={false}
|
||||
@@ -2267,6 +2134,7 @@ function PdfRequestFiles(
|
||||
setUniqueId={setUniqueId}
|
||||
tempSignerId={tempSignerId}
|
||||
signatureTypes={signatureType}
|
||||
allowCellResize={pdfDetails[0]?.AllowModifications ?? false}
|
||||
/>
|
||||
)}
|
||||
<DownloadPdfZip
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
multiSignEmbed,
|
||||
addWidgetOptions,
|
||||
textInputWidget,
|
||||
cellsWidget,
|
||||
textWidget,
|
||||
radioButtonWidget,
|
||||
color,
|
||||
@@ -862,7 +863,6 @@ function PlaceHolderSign() {
|
||||
try {
|
||||
//pdfOriginalWH contained all pdf's pages width,height & pagenumber in array format
|
||||
const pdfBase64 = await multiSignEmbed(
|
||||
pdfOriginalWH,
|
||||
placeholder,
|
||||
pdfDoc,
|
||||
isSignYourSelfFlow,
|
||||
@@ -1417,7 +1417,8 @@ function PlaceHolderSign() {
|
||||
deleteOption,
|
||||
status,
|
||||
defaultValue,
|
||||
isHideLabel
|
||||
isHideLabel,
|
||||
layout
|
||||
) => {
|
||||
const filterSignerPos = signerPos.filter((data) => data.Id === uniqueId);
|
||||
if (filterSignerPos.length > 0) {
|
||||
@@ -1453,6 +1454,7 @@ function PlaceHolderSign() {
|
||||
name: dropdownName,
|
||||
values: dropdownOptions,
|
||||
status: status,
|
||||
layout: layout,
|
||||
isReadOnly: isReadOnly || false,
|
||||
isHideLabel: isHideLabel || false,
|
||||
defaultValue: defaultValue,
|
||||
@@ -1492,6 +1494,7 @@ function PlaceHolderSign() {
|
||||
maxRequiredCount: maxCount
|
||||
},
|
||||
defaultValue: defaultValue,
|
||||
layout: layout,
|
||||
isReadOnly: isReadOnly || false,
|
||||
isHideLabel: isHideLabel || false,
|
||||
fontSize:
|
||||
@@ -1590,6 +1593,30 @@ function PlaceHolderSign() {
|
||||
isReadOnly: defaultdata?.isReadOnly || false
|
||||
}
|
||||
};
|
||||
} else if (position.type === cellsWidget) {
|
||||
return {
|
||||
...position,
|
||||
options: {
|
||||
...position.options,
|
||||
name: defaultdata?.name || "Cells",
|
||||
status: defaultdata?.status || "required",
|
||||
hint: defaultdata?.hint || "",
|
||||
cellCount: parseInt(defaultdata?.cellCount || 5),
|
||||
defaultValue: (defaultdata?.defaultValue || "").slice(
|
||||
0,
|
||||
parseInt(defaultdata?.cellCount || 5)
|
||||
),
|
||||
validation:
|
||||
{},
|
||||
fontSize:
|
||||
fontSize || currWidgetsDetails?.options?.fontSize || 12,
|
||||
fontColor:
|
||||
fontColor ||
|
||||
currWidgetsDetails?.options?.fontColor ||
|
||||
"black",
|
||||
isReadOnly: defaultdata?.isReadOnly || false
|
||||
}
|
||||
};
|
||||
} else if (["signature"].includes(position.type)) {
|
||||
return {
|
||||
...position,
|
||||
@@ -1717,14 +1744,14 @@ function PlaceHolderSign() {
|
||||
objectId: data.objectId
|
||||
};
|
||||
const updatePlaceHolder = signerPos.map((x) => {
|
||||
if (x.Id === id) {
|
||||
if (x.Id === id || x.signerObjId === id) {
|
||||
return { ...x, signerPtr: signerPtr, signerObjId: data.objectId };
|
||||
}
|
||||
return { ...x };
|
||||
});
|
||||
setSignerPos(updatePlaceHolder);
|
||||
const updateSigner = signersdata.map((x) => {
|
||||
if (x.Id === id) {
|
||||
if (x.Id === id || x.objectId === id) {
|
||||
return { ...x, ...data, className: "contracts_Contactbook" };
|
||||
}
|
||||
return { ...x };
|
||||
@@ -1739,7 +1766,9 @@ function PlaceHolderSign() {
|
||||
}
|
||||
}
|
||||
setSignersData(updateSigner);
|
||||
const index = signersdata.findIndex((x) => x.Id === id);
|
||||
const index = signersdata.findIndex(
|
||||
(x) => x.Id === id || x.objectId === id
|
||||
);
|
||||
setIsSelectId(index);
|
||||
}
|
||||
if (isNewContact.status) {
|
||||
@@ -1903,7 +1932,9 @@ function PlaceHolderSign() {
|
||||
};
|
||||
//`handleInputChange` function to get signers list from dropdown
|
||||
const handleInputChange = (item, id) => {
|
||||
const signerExist = forms.some((x) => x.value === item.value);
|
||||
const signerExist = signersdata?.some(
|
||||
(x) => x.objectId && x.objectId === item.value
|
||||
);
|
||||
if (signerExist) {
|
||||
alert(t("already-exist-signer"));
|
||||
} else {
|
||||
@@ -2150,7 +2181,7 @@ function PlaceHolderSign() {
|
||||
navigate("/report/1MwEuxLEkF");
|
||||
}}
|
||||
>
|
||||
<div className="h-[100%] p-[20px]">
|
||||
<div className="h-[100%] p-[20px] text-base-content">
|
||||
{mailStatus === "success" ? (
|
||||
<div className="text-center mb-[10px]">
|
||||
<LottieWithLoader />
|
||||
@@ -2602,7 +2633,7 @@ function PlaceHolderSign() {
|
||||
title={t("document-alert")}
|
||||
showClose={false}
|
||||
>
|
||||
<div className="h-[100%] p-[20px]">
|
||||
<div className="h-[100%] p-[20px] text-base-content">
|
||||
<p>{isAlreadyPlace.message}</p>
|
||||
<div className="h-[1px] w-full my-[15px] bg-[#9f9f9f]"></div>
|
||||
<button
|
||||
|
||||
@@ -56,6 +56,10 @@ const Preferences = () => {
|
||||
const [dateFormat, setDateFormat] = useState("MM/DD/YYYY");
|
||||
const [is12HourTime, setIs12HourTime] = useState(false);
|
||||
const [isLTVEnabled, setIsLTVEnabled] = useState(false);
|
||||
const [isDefaultMail, setIsDefaultMail] = useState({
|
||||
requestMail: false,
|
||||
completionMail: false
|
||||
});
|
||||
useEffect(() => {
|
||||
fetchSignType();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@@ -227,6 +231,7 @@ const Preferences = () => {
|
||||
setRequestSubject(
|
||||
`{{sender_name}} has requested you to sign {{document_title}}`
|
||||
);
|
||||
setIsDefaultMail((prev) => ({ ...prev, requestMail: true }));
|
||||
}
|
||||
setDefaultReqHtml({
|
||||
body: defaultRequestBody,
|
||||
@@ -241,6 +246,7 @@ const Preferences = () => {
|
||||
setCompletionSubject(
|
||||
`Document {{document_title}} has been signed by all parties`
|
||||
);
|
||||
setIsDefaultMail((prev) => ({ ...prev, completionMail: true }));
|
||||
}
|
||||
setDefaultCompHtml({
|
||||
body: defaultCompletionBody,
|
||||
@@ -301,7 +307,7 @@ const Preferences = () => {
|
||||
JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
|
||||
if (extUser && extUser?.objectId) {
|
||||
extUser.TenantId.RequestBody = updateRes?.RequestBody;
|
||||
extUser.TenantId.RequestBody = updateRes?.RequestSubject;
|
||||
extUser.TenantId.RequestSubject = updateRes?.RequestSubject;
|
||||
const _extUser = JSON.parse(JSON.stringify(extUser));
|
||||
localStorage.setItem("Extand_Class", JSON.stringify([_extUser]));
|
||||
}
|
||||
@@ -318,13 +324,45 @@ const Preferences = () => {
|
||||
};
|
||||
|
||||
//function to use reset form
|
||||
const handleReset = (request, completion) => {
|
||||
if (request) {
|
||||
const handleReset = async (request, completion) => {
|
||||
let extUser =
|
||||
localStorage.getItem("Extand_Class") &&
|
||||
JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
|
||||
handleModifyMail(request);
|
||||
if (request && !isDefaultMail?.requestMail) {
|
||||
setRequestBody(defaultReqHtml?.body);
|
||||
setRequestSubject(defaultReqHtml?.subject);
|
||||
} else if (completion) {
|
||||
try {
|
||||
await Parse.Cloud.run("updatetenant", {
|
||||
tenantId: tenantId,
|
||||
details: { RequestBody: "", RequestSubject: "" }
|
||||
});
|
||||
if (extUser && extUser?.objectId) {
|
||||
extUser.TenantId.RequestBody = "";
|
||||
extUser.TenantId.RequestSubject = "";
|
||||
const _extUser = JSON.parse(JSON.stringify(extUser));
|
||||
localStorage.setItem("Extand_Class", JSON.stringify([_extUser]));
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("Err in reseting request mail", err);
|
||||
}
|
||||
} else if (completion && !isDefaultMail?.completionMail) {
|
||||
setCompletionSubject(defaultCompHtml?.subject);
|
||||
SetCompletionBody(defaultCompHtml?.body);
|
||||
try {
|
||||
await Parse.Cloud.run("updatetenant", {
|
||||
tenantId: tenantId,
|
||||
details: { CompletionBody: "", CompletionSubject: "" }
|
||||
});
|
||||
if (extUser && extUser?.objectId) {
|
||||
extUser.TenantId.CompletionBody = "";
|
||||
extUser.TenantId.CompletionSubject = "";
|
||||
const _extUser = JSON.parse(JSON.stringify(extUser));
|
||||
localStorage.setItem("Extand_Class", JSON.stringify([_extUser]));
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("Err in reseting completion mail", err);
|
||||
}
|
||||
}
|
||||
};
|
||||
//function for handle ontext change and save again text in delta
|
||||
@@ -344,6 +382,11 @@ const Preferences = () => {
|
||||
const handleTourInput = () => setIsTourEnabled(!isTourEnabled);
|
||||
const handleSendinOrderInput = () => setSendinOrder(!sendinOrder);
|
||||
const tabName = (ind) => tab.find((t, i) => i === ind)?.name;
|
||||
const handleModifyMail = (mode) => {
|
||||
mode === "request"
|
||||
? setIsDefaultMail((p) => ({ ...p, requestMail: !p?.requestMail }))
|
||||
: setIsDefaultMail((p) => ({ ...p, completionMail: !p?.completionMail }));
|
||||
};
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Title title={t("Preferences")} />
|
||||
@@ -737,6 +780,18 @@ const Preferences = () => {
|
||||
{t("request-email")}
|
||||
</h1>
|
||||
<div className="relative mt-2 mb-4">
|
||||
{
|
||||
isDefaultMail?.requestMail && (
|
||||
<div className="absolute backdrop-blur-[2px] flex w-full h-full justify-center items-center bg-black/10 rounded-box select-none z-20">
|
||||
<button
|
||||
onClick={() => handleModifyMail("request")}
|
||||
className="op-btn op-btn-primary shadow-lg"
|
||||
>
|
||||
Modify
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<form
|
||||
onSubmit={handleSaveRequestEmail}
|
||||
className="p-3 border-[1px] border-base-content rounded-box"
|
||||
@@ -801,6 +856,18 @@ const Preferences = () => {
|
||||
{t("completion-email")}
|
||||
</h1>
|
||||
<div className="relative my-2">
|
||||
{
|
||||
isDefaultMail?.completionMail && (
|
||||
<div className="absolute backdrop-blur-[2px] flex w-full h-full justify-center items-center bg-black/10 rounded-box select-none z-20">
|
||||
<button
|
||||
onClick={() => handleModifyMail("completion")}
|
||||
className="op-btn op-btn-primary shadow-lg"
|
||||
>
|
||||
Modify
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<form
|
||||
onSubmit={handleSaveCompletionEmail}
|
||||
className="p-3 border-[1px] border-base-content rounded-box"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useEffect, useState, useRef } from "react";
|
||||
import ReportTable from "../primitives/GetReportDisplay";
|
||||
import Parse from "parse";
|
||||
import axios from "axios";
|
||||
@@ -27,12 +27,18 @@ const Report = () => {
|
||||
const [isImport, setIsImport] = useState(false);
|
||||
const abortController = new AbortController();
|
||||
const docPerPage = 10;
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [mobileSearchOpen, setMobileSearchOpen] = useState(false);
|
||||
const [isSearchResult, setIsSearchResult] = useState(false);
|
||||
const debounceTimer = useRef(null);
|
||||
|
||||
// below useEffect is call when id param change
|
||||
useEffect(() => {
|
||||
setReportName("");
|
||||
setList([]);
|
||||
getReportData();
|
||||
setSearchTerm("");
|
||||
setMobileSearchOpen(false);
|
||||
getReportData(0, docPerPage, "");
|
||||
|
||||
// Function returned from useEffect is called on unmount
|
||||
return () => {
|
||||
@@ -48,7 +54,7 @@ const Report = () => {
|
||||
// below useEffect call when isNextRecord state is true and fetch next record
|
||||
useEffect(() => {
|
||||
if (isNextRecord) {
|
||||
getReportData(List.length, 20);
|
||||
getReportData(List.length, 20, searchTerm);
|
||||
}
|
||||
// eslint-disable-next-line
|
||||
}, [isNextRecord]);
|
||||
@@ -56,7 +62,58 @@ const Report = () => {
|
||||
const handleDontShow = (isChecked) => {
|
||||
setIsDontShow(isChecked);
|
||||
};
|
||||
const getReportData = async (skipUserRecord = 0, limit = 20) => {
|
||||
|
||||
const handleSearchChange = async (e) => {
|
||||
const term = e.target.value.toLowerCase();
|
||||
setSearchTerm(term);
|
||||
if (debounceTimer.current) {
|
||||
clearTimeout(debounceTimer.current);
|
||||
}
|
||||
debounceTimer.current = setTimeout(async () => {
|
||||
try {
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
|
||||
sessiontoken: localStorage.getItem("accesstoken")
|
||||
};
|
||||
const url = `${localStorage.getItem("baseUrl")}functions/getReport`;
|
||||
const res = await axios.post(
|
||||
url,
|
||||
{ reportId: id, searchTerm: term, skip: 0, limit: docPerPage },
|
||||
{ headers }
|
||||
);
|
||||
const data = res.data?.result || [];
|
||||
if (!data.error) {
|
||||
setList(data);
|
||||
setIsMoreDocs(data.length >= docPerPage);
|
||||
setIsNextRecord(false);
|
||||
setIsSearchResult(true);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Search error:", err);
|
||||
}
|
||||
}, 300);
|
||||
setIsSearchResult(false);
|
||||
};
|
||||
|
||||
const handleSearchPaste = (e) => {
|
||||
setTimeout(() => {
|
||||
handleSearchChange({ target: { value: e.target.value } });
|
||||
}, 0);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (debounceTimer.current) {
|
||||
clearTimeout(debounceTimer.current);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
const getReportData = async (
|
||||
skipUserRecord = 0,
|
||||
limit = 20,
|
||||
term = searchTerm
|
||||
) => {
|
||||
// setIsLoader(true);
|
||||
const json = reportJson(id);
|
||||
if (json) {
|
||||
@@ -77,6 +134,9 @@ const Report = () => {
|
||||
const skipRecord = id === "4Hhwbp482K" ? 0 : skipUserRecord;
|
||||
const limitRecord = id === "4Hhwbp482K" ? 200 : limit;
|
||||
const params = { reportId: id, skip: skipRecord, limit: limitRecord };
|
||||
if (term) {
|
||||
params.searchTerm = term;
|
||||
}
|
||||
const url = `${localStorage.getItem("baseUrl")}functions/getReport`;
|
||||
const res = await axios.post(url, params, {
|
||||
headers: headers,
|
||||
@@ -120,7 +180,7 @@ const Report = () => {
|
||||
const listData = res.data?.result.filter((x) => x.Signers.length > 0);
|
||||
let arr = [];
|
||||
for (const obj of listData) {
|
||||
const isSigner = obj.Signers.some(
|
||||
const isSigner = obj?.Signers?.some(
|
||||
(item) => item.UserId.objectId === currentUser
|
||||
);
|
||||
if (isSigner) {
|
||||
@@ -197,6 +257,12 @@ const Report = () => {
|
||||
report_help={reporthelp}
|
||||
tourData={tourData}
|
||||
isDontShow={isDontShow}
|
||||
mobileSearchOpen={mobileSearchOpen}
|
||||
setMobileSearchOpen={setMobileSearchOpen}
|
||||
searchTerm={searchTerm}
|
||||
handleSearchChange={handleSearchChange}
|
||||
handleSearchPaste={handleSearchPaste}
|
||||
isSearchResult={isSearchResult}
|
||||
isImport={isImport}
|
||||
/>
|
||||
) : (
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
randomId,
|
||||
getDate,
|
||||
textWidget,
|
||||
cellsWidget,
|
||||
convertPdfArrayBuffer,
|
||||
textInputWidget,
|
||||
fetchImageBase64,
|
||||
@@ -68,6 +69,8 @@ import {
|
||||
resetWidgetState
|
||||
} from "../redux/reducers/widgetSlice.js";
|
||||
import WidgetsValueModal from "../components/pdf/WidgetsValueModal";
|
||||
import WidgetNameModal from "../components/pdf/WidgetNameModal";
|
||||
import CellsSettingModal from "../components/pdf/CellsSettingModal";
|
||||
//For signYourself inProgress section signer can add sign and complete doc sign.
|
||||
function SignYourSelf() {
|
||||
const { t } = useTranslation();
|
||||
@@ -118,6 +121,10 @@ function SignYourSelf() {
|
||||
const [isTextSetting, setIsTextSetting] = useState(false);
|
||||
const [currWidgetsDetails, setCurrWidgetsDetails] = useState({});
|
||||
const [isCheckbox, setIsCheckbox] = useState(false);
|
||||
const [isNameModal, setIsNameModal] = useState(false);
|
||||
const [isCellsSetting, setIsCellsSetting] = useState(false);
|
||||
const openNameModal = () => setIsNameModal(true);
|
||||
const openCellsSettingModal = () => setIsCellsSetting(true);
|
||||
const [pdfLoad, setPdfLoad] = useState(false);
|
||||
const [isAlert, setIsAlert] = useState({ isShow: false, alertMessage: "" });
|
||||
const [isDontShow, setIsDontShow] = useState(false);
|
||||
@@ -356,9 +363,13 @@ function SignYourSelf() {
|
||||
);
|
||||
const dragTypeValue = item?.text ? item.text : monitor.type;
|
||||
const widgetValue = getWidgetValue(dragTypeValue);
|
||||
const widgetTypeExist = ["name", "company", "job title", "email"].includes(
|
||||
dragTypeValue
|
||||
);
|
||||
const widgetTypeExist = [
|
||||
"name",
|
||||
"company",
|
||||
"job title",
|
||||
"email",
|
||||
cellsWidget
|
||||
].includes(dragTypeValue);
|
||||
const containerScale = getContainerScale(
|
||||
pdfOriginalWH,
|
||||
pageNumber,
|
||||
@@ -439,6 +450,7 @@ function SignYourSelf() {
|
||||
[
|
||||
textInputWidget,
|
||||
textWidget,
|
||||
cellsWidget,
|
||||
"name",
|
||||
"company",
|
||||
"job title",
|
||||
@@ -636,7 +648,6 @@ function SignYourSelf() {
|
||||
}
|
||||
//embed all widgets in document
|
||||
const pdfBytes = await multiSignEmbed(
|
||||
pdfOriginalWH,
|
||||
xyPosition,
|
||||
pdfDoc,
|
||||
isSignYourSelfFlow,
|
||||
@@ -931,7 +942,8 @@ function SignYourSelf() {
|
||||
deleteOption,
|
||||
status,
|
||||
defaultValue,
|
||||
isHideLabel
|
||||
isHideLabel,
|
||||
layout
|
||||
) => {
|
||||
const getPageNumer = xyPosition.filter(
|
||||
(data) => data.pageNumber === pageNumber
|
||||
@@ -939,6 +951,8 @@ function SignYourSelf() {
|
||||
if (getPageNumer.length > 0) {
|
||||
const getXYdata = getPageNumer[0].pos;
|
||||
const getPosData = getXYdata;
|
||||
const widgetLayout =
|
||||
currWidgetsDetails?.type === "checkbox" ? { layout: layout } : {};
|
||||
const addSignPos = getPosData.map((position) => {
|
||||
if (position.key === currWidgetsDetails?.key) {
|
||||
if (addOption) {
|
||||
@@ -962,6 +976,7 @@ function SignYourSelf() {
|
||||
...position.options,
|
||||
name: dropdownName,
|
||||
values: dropdownOptions,
|
||||
...widgetLayout,
|
||||
isReadOnly: isReadOnly,
|
||||
isHideLabel: isHideLabel || false,
|
||||
fontSize:
|
||||
@@ -1028,6 +1043,92 @@ function SignYourSelf() {
|
||||
handleTextSettingModal(false);
|
||||
}
|
||||
};
|
||||
|
||||
const setCellCount = (key, newCount) => {
|
||||
setXyPosition((prev) => {
|
||||
const getPageNumer = prev.filter((data) => data.pageNumber === pageNumber);
|
||||
if (getPageNumer.length > 0) {
|
||||
const updatePos = getPageNumer[0].pos.map((p) =>
|
||||
p.key === key ? { ...p, options: { ...p.options, cellCount: newCount } } : p
|
||||
);
|
||||
return prev.map((obj, ind) => (ind === index ? { ...obj, pos: updatePos } : obj));
|
||||
}
|
||||
return prev;
|
||||
});
|
||||
};
|
||||
|
||||
const handleWidgetdefaultdata = (defaultdata) => {
|
||||
const newFontSize =
|
||||
defaultdata?.fontSize !== undefined ? defaultdata.fontSize : fontSize;
|
||||
const newFontColor =
|
||||
defaultdata?.fontColor !== undefined ? defaultdata.fontColor : fontColor;
|
||||
|
||||
const getPageNumer = xyPosition.filter(
|
||||
(data) => data.pageNumber === pageNumber
|
||||
);
|
||||
if (getPageNumer.length > 0) {
|
||||
const updatePos = getPageNumer[0].pos.map((position) => {
|
||||
if (position.key === currWidgetsDetails?.key) {
|
||||
if (position.type === cellsWidget) {
|
||||
const count = parseInt(
|
||||
defaultdata?.cellCount ?? position.options?.cellCount ?? 5,
|
||||
10
|
||||
);
|
||||
return {
|
||||
...position,
|
||||
options: {
|
||||
...position.options,
|
||||
name: defaultdata?.name || position.options?.name || "Cells",
|
||||
cellCount: count,
|
||||
defaultValue: (defaultdata?.defaultValue || "").slice(0, count),
|
||||
fontSize: newFontSize || position.options?.fontSize || 12,
|
||||
fontColor: newFontColor || position.options?.fontColor || "black"
|
||||
}
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
...position,
|
||||
options: {
|
||||
...position.options,
|
||||
name: defaultdata?.name || position.options?.name,
|
||||
fontSize: newFontSize || position.options?.fontSize || 12,
|
||||
fontColor:
|
||||
newFontColor || position.options?.fontColor || "black"
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
return position;
|
||||
});
|
||||
const updateXYposition = xyPosition.map((obj, ind) =>
|
||||
ind === index ? { ...obj, pos: updatePos } : obj
|
||||
);
|
||||
setXyPosition(updateXYposition);
|
||||
}
|
||||
setFontSize();
|
||||
setFontColor();
|
||||
setCurrWidgetsDetails({});
|
||||
setIsNameModal(false);
|
||||
};
|
||||
|
||||
const handleNameModal = () => {
|
||||
setIsNameModal(false);
|
||||
setCurrWidgetsDetails({});
|
||||
setIsCheckbox(false);
|
||||
};
|
||||
|
||||
const handleCellsSettingSave = (data) => {
|
||||
// ensure font and color are updated before applying widget changes
|
||||
if (data?.fontSize !== undefined) setFontSize(data.fontSize);
|
||||
if (data?.fontColor !== undefined) setFontColor(data.fontColor);
|
||||
handleWidgetdefaultdata(data);
|
||||
setIsCellsSetting(false);
|
||||
};
|
||||
|
||||
const handleCellsSettingClose = () => {
|
||||
setIsCellsSetting(false);
|
||||
setCurrWidgetsDetails({});
|
||||
};
|
||||
const clickOnZoomIn = () => {
|
||||
onClickZoomIn(scale, zoomPercent, setScale, setZoomPercent);
|
||||
};
|
||||
@@ -1183,9 +1284,8 @@ function SignYourSelf() {
|
||||
title={t("document-signed")}
|
||||
handleClose={() => setShowAlreadySignDoc({ status: false })}
|
||||
>
|
||||
<div className="p-[20px] h-full">
|
||||
<div className="p-[20px] h-full text-base-content">
|
||||
<p>{showAlreadySignDoc.mssg}</p>
|
||||
|
||||
<div className="h-[1px] w-full my-[15px] bg-[#9f9f9f]"></div>
|
||||
<button
|
||||
className="op-btn op-btn-ghost shadow-md"
|
||||
@@ -1283,12 +1383,15 @@ function SignYourSelf() {
|
||||
setIsPageCopy={setIsPageCopy}
|
||||
setIsCheckbox={setIsCheckbox}
|
||||
setCurrWidgetsDetails={setCurrWidgetsDetails}
|
||||
handleNameModal={openNameModal}
|
||||
handleCellSettingModal={openCellsSettingModal}
|
||||
handleTextSettingModal={handleTextSettingModal}
|
||||
setScale={setScale}
|
||||
scale={scale}
|
||||
pdfBase64Url={pdfBase64Url}
|
||||
fontSize={fontSize}
|
||||
setFontSize={setFontSize}
|
||||
setCellCount={setCellCount}
|
||||
fontColor={fontColor}
|
||||
setFontColor={setFontColor}
|
||||
isResize={isResize}
|
||||
@@ -1330,6 +1433,7 @@ function SignYourSelf() {
|
||||
xyPosition={xyPosition} //placeholder details
|
||||
pageNumber={pageNumber} //current page number
|
||||
setXyPosition={setXyPosition} //placeholder details state
|
||||
setCellCount={setCellCount}
|
||||
setPageNumber={setPageNumber}
|
||||
setCurrWidgetsDetails={setCurrWidgetsDetails}
|
||||
currWidgetsDetails={currWidgetsDetails}
|
||||
@@ -1338,6 +1442,23 @@ function SignYourSelf() {
|
||||
signatureTypes={signatureTypes}
|
||||
/>
|
||||
)}
|
||||
<WidgetNameModal
|
||||
widgetName={currWidgetsDetails?.options?.name}
|
||||
defaultdata={currWidgetsDetails}
|
||||
isOpen={isNameModal}
|
||||
handleClose={handleNameModal}
|
||||
handleData={handleWidgetdefaultdata}
|
||||
fontSize={fontSize}
|
||||
setFontSize={setFontSize}
|
||||
fontColor={fontColor}
|
||||
setFontColor={setFontColor}
|
||||
/>
|
||||
<CellsSettingModal
|
||||
isOpen={isCellsSetting}
|
||||
defaultData={currWidgetsDetails}
|
||||
handleClose={handleCellsSettingClose}
|
||||
handleSave={handleCellsSettingSave}
|
||||
/>
|
||||
<RotateAlert
|
||||
showRotateAlert={showRotateAlert.status}
|
||||
setShowRotateAlert={setShowRotateAlert}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState, useRef } from "react";
|
||||
import { useEffect, useState, useRef } from "react";
|
||||
import RenderAllPdfPage from "../components/pdf/RenderAllPdfPage";
|
||||
import { useParams, useNavigate } from "react-router";
|
||||
import axios from "axios";
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
defaultWidthHeight,
|
||||
addWidgetOptions,
|
||||
textInputWidget,
|
||||
cellsWidget,
|
||||
radioButtonWidget,
|
||||
getContainerScale,
|
||||
convertBase64ToFile,
|
||||
@@ -832,7 +833,6 @@ const TemplatePlaceholder = () => {
|
||||
try {
|
||||
//pdfOriginalWH contained all pdf's pages width,height & pagenumber in array format
|
||||
const pdfBase64 = await multiSignEmbed(
|
||||
pdfOriginalWH,
|
||||
placeholder,
|
||||
pdfDoc,
|
||||
isSignYourSelfFlow,
|
||||
@@ -1274,7 +1274,8 @@ const TemplatePlaceholder = () => {
|
||||
deleteOption,
|
||||
status,
|
||||
defaultValue,
|
||||
isHideLabel
|
||||
isHideLabel,
|
||||
layout
|
||||
) => {
|
||||
const filterSignerPos = signerPos.filter((data) => data.Id === uniqueId);
|
||||
if (filterSignerPos.length > 0) {
|
||||
@@ -1313,6 +1314,7 @@ const TemplatePlaceholder = () => {
|
||||
name: dropdownName,
|
||||
values: dropdownOptions,
|
||||
status: status,
|
||||
layout: layout,
|
||||
defaultValue: defaultValue,
|
||||
isReadOnly: isReadOnly || false,
|
||||
isHideLabel: isHideLabel || false,
|
||||
@@ -1351,6 +1353,7 @@ const TemplatePlaceholder = () => {
|
||||
minRequiredCount: minCount,
|
||||
maxRequiredCount: maxCount
|
||||
},
|
||||
layout: layout,
|
||||
isReadOnly: isReadOnly || false,
|
||||
defaultValue: defaultValue,
|
||||
isHideLabel: isHideLabel || false,
|
||||
@@ -1451,6 +1454,36 @@ const TemplatePlaceholder = () => {
|
||||
"black"
|
||||
}
|
||||
};
|
||||
} else if (position.type === cellsWidget) {
|
||||
return {
|
||||
...position,
|
||||
options: {
|
||||
...position.options,
|
||||
name: defaultdata?.name || "Cells",
|
||||
status: defaultdata?.status || "required",
|
||||
hint: defaultdata?.hint || "",
|
||||
cellCount: parseInt(defaultdata?.cellCount || 5),
|
||||
defaultValue: (defaultdata?.defaultValue || "").slice(
|
||||
0,
|
||||
parseInt(defaultdata?.cellCount || 5)
|
||||
),
|
||||
validation:
|
||||
isSubscribe && inputype
|
||||
? {
|
||||
type: inputype,
|
||||
pattern:
|
||||
inputype === "regex" ? defaultdata.textvalidate : ""
|
||||
}
|
||||
: {},
|
||||
isReadOnly: defaultdata?.isReadOnly || false,
|
||||
fontSize:
|
||||
fontSize || currWidgetsDetails?.options?.fontSize || 12,
|
||||
fontColor:
|
||||
fontColor ||
|
||||
currWidgetsDetails?.options?.fontColor ||
|
||||
"black"
|
||||
}
|
||||
};
|
||||
} else if (["signature"].includes(position.type)) {
|
||||
return {
|
||||
...position,
|
||||
@@ -1510,6 +1543,22 @@ const TemplatePlaceholder = () => {
|
||||
setIsRadio(false);
|
||||
setIsCheckbox(false);
|
||||
};
|
||||
const setCellCount = (key, newCount) => {
|
||||
const updated = signerPos.map((signer) => {
|
||||
if (signer.Id !== uniqueId) return signer;
|
||||
const placeHolder = signer.placeHolder.map((ph) => {
|
||||
if (ph.pageNumber !== pageNumber) return ph;
|
||||
const pos = ph.pos.map((p) =>
|
||||
p.key === key
|
||||
? { ...p, options: { ...p.options, cellCount: newCount } }
|
||||
: p
|
||||
);
|
||||
return { ...ph, pos };
|
||||
});
|
||||
return { ...signer, placeHolder };
|
||||
});
|
||||
setSignerPos(updated);
|
||||
};
|
||||
|
||||
const clickOnZoomIn = () => {
|
||||
onClickZoomIn(scale, zoomPercent, setScale, setZoomPercent);
|
||||
@@ -1671,7 +1720,7 @@ const TemplatePlaceholder = () => {
|
||||
navigate("/report/6TeaPr321t");
|
||||
}}
|
||||
>
|
||||
<div className="h-full p-[20px] mb-2">
|
||||
<div className="h-full p-[20px] mb-2 text-base-content">
|
||||
<p>{t("template-created-alert")}</p>
|
||||
<div className="h-[1px] w-full my-[15px] bg-[#9f9f9f]"></div>
|
||||
<div className="flex gap-1 flex-col md:flex-row">
|
||||
@@ -1828,6 +1877,7 @@ const TemplatePlaceholder = () => {
|
||||
pdfBase64Url={pdfBase64Url}
|
||||
fontSize={fontSize}
|
||||
setFontSize={setFontSize}
|
||||
setCellCount={setCellCount}
|
||||
fontColor={fontColor}
|
||||
setFontColor={setFontColor}
|
||||
isResize={isResize}
|
||||
|
||||
@@ -284,7 +284,7 @@ const UserList = () => {
|
||||
handleClose={handleClose}
|
||||
>
|
||||
<div className="m-[20px]">
|
||||
<div className="text-lg font-normal text-black">
|
||||
<div className="text-lg font-normal text-base-content">
|
||||
{t("are-you-sure")}{" "}
|
||||
{item?.IsDisabled
|
||||
? t("activate")
|
||||
|
||||
@@ -286,7 +286,7 @@ function UserProfile() {
|
||||
style={{ width: `${percentage}%` }}
|
||||
></div>
|
||||
</div>
|
||||
<span className="text-black text-sm">{percentage}%</span>
|
||||
<span className="text-base-contentk text-sm">{percentage}%</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="text-base font-semibold pt-4">
|
||||
|
||||
@@ -91,7 +91,7 @@ function DownloadPdfZip(props) {
|
||||
title={t("download-files")}
|
||||
handleClose={() => props.setIsDownloadModal(false)}
|
||||
>
|
||||
<div className="p-[20px] h-full">
|
||||
<div className="p-[20px] h-full text-base-content">
|
||||
{downloadType.map((data, ind) => {
|
||||
return (
|
||||
<label
|
||||
|
||||
@@ -7,6 +7,7 @@ import ModalUi from "./ModalUi";
|
||||
import AddSigner from "../components/AddSigner";
|
||||
import {
|
||||
emailRegex,
|
||||
iconColor,
|
||||
} from "../constant/const";
|
||||
import Alert from "./Alert";
|
||||
import Tooltip from "./Tooltip";
|
||||
@@ -38,9 +39,12 @@ import { useTranslation } from "react-i18next";
|
||||
import DownloadPdfZip from "./DownloadPdfZip";
|
||||
import * as XLSX from "xlsx";
|
||||
import EditContactForm from "../components/EditContactForm";
|
||||
import { useElSize } from "../hook/useElSize";
|
||||
|
||||
const ReportTable = (props) => {
|
||||
const copyUrlRef = useRef(null);
|
||||
const titleRef = useRef(null);
|
||||
const titleElement = useElSize(titleRef);
|
||||
const appName =
|
||||
"OpenSign™";
|
||||
const drivename = appName === "OpenSign™" ? "OpenSign™" : "";
|
||||
@@ -87,11 +91,18 @@ const ReportTable = (props) => {
|
||||
const [contact, setContact] = useState({ Name: "", Email: "", Phone: "" });
|
||||
const [isSuccess, setIsSuccess] = useState({});
|
||||
const [templateId, setTemplateId] = useState("");
|
||||
const [sortOrder, setSortOrder] = useState("asc");
|
||||
const isTemplateReport = props.ReportName === "Templates";
|
||||
const recordsPerPage = 5;
|
||||
const startIndex = (currentPage - 1) * props.docPerPage;
|
||||
const { isMoreDocs, setIsNextRecord } = props;
|
||||
|
||||
useEffect(() => {
|
||||
if (props.isSearchResult) {
|
||||
setCurrentPage(1);
|
||||
}
|
||||
}, [props.isSearchResult]);
|
||||
|
||||
const getPaginationRange = () => {
|
||||
const totalPageNumbers = 7; // Adjust this value to show more/less page numbers
|
||||
const pages = [];
|
||||
@@ -427,7 +438,22 @@ const ReportTable = (props) => {
|
||||
// Get current list
|
||||
const indexOfLastDoc = currentPage * props.docPerPage;
|
||||
const indexOfFirstDoc = indexOfLastDoc - props.docPerPage;
|
||||
const currentList = props.List?.slice(indexOfFirstDoc, indexOfLastDoc);
|
||||
const sortedList = React.useMemo(() => {
|
||||
if (props.ReportName === "Contactbook") {
|
||||
const contacts = [...props.List];
|
||||
contacts.sort((a, b) => {
|
||||
const nameA = a?.Name?.toLowerCase() || "";
|
||||
const nameB = b?.Name?.toLowerCase() || "";
|
||||
if (nameA < nameB) return sortOrder === "asc" ? -1 : 1;
|
||||
if (nameA > nameB) return sortOrder === "asc" ? 1 : -1;
|
||||
return 0;
|
||||
});
|
||||
return contacts;
|
||||
}
|
||||
return props.List;
|
||||
}, [props.List, sortOrder, props.ReportName]);
|
||||
|
||||
const currentList = sortedList?.slice(indexOfFirstDoc, indexOfLastDoc);
|
||||
|
||||
// Change page
|
||||
const paginateFront = () => {
|
||||
@@ -447,6 +473,10 @@ const ReportTable = (props) => {
|
||||
setIsContactform(!isContactform);
|
||||
};
|
||||
|
||||
const toggleSortOrder = () => {
|
||||
setSortOrder(sortOrder === "asc" ? "desc" : "asc");
|
||||
};
|
||||
|
||||
const handleUserData = (data) => {
|
||||
props.setList((prevData) => [data, ...prevData]);
|
||||
};
|
||||
@@ -907,7 +937,6 @@ const ReportTable = (props) => {
|
||||
setActLoader({});
|
||||
}
|
||||
};
|
||||
|
||||
const handleUpdateExpiry = async (e, item) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
@@ -999,7 +1028,7 @@ const ReportTable = (props) => {
|
||||
? "op-border-primary op-text-primary"
|
||||
: x.Activity === "VIEWED"
|
||||
? "border-green-400 text-green-400"
|
||||
: "border-black text-black"
|
||||
: "border-base-content text-base-content"
|
||||
} focus:outline-none border-2 w-[60px] h-[30px] text-[11px] rounded-full`}
|
||||
>
|
||||
{x?.Activity?.toUpperCase() || "-"}
|
||||
@@ -1314,7 +1343,6 @@ const ReportTable = (props) => {
|
||||
try {
|
||||
const params = { docId: doc?.objectId };
|
||||
const templateRes = await Parse.Cloud.run("saveastemplate", params);
|
||||
// console.log("templateRes ", templateRes);
|
||||
setTemplateId(templateRes?.id);
|
||||
setIsSuccess({ [doc.objectId]: true });
|
||||
} catch (err) {
|
||||
@@ -1394,6 +1422,12 @@ const ReportTable = (props) => {
|
||||
setActLoader({});
|
||||
}
|
||||
};
|
||||
|
||||
const restrictBtn = (item, act) => {
|
||||
return item.IsSignyourself && act.action === "recreatedocument"
|
||||
? true
|
||||
: false;
|
||||
};
|
||||
return (
|
||||
<div className="relative">
|
||||
{Object.keys(actLoader)?.length > 0 && (
|
||||
@@ -1416,7 +1450,10 @@ const ReportTable = (props) => {
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<div className="flex flex-row items-center justify-between my-2 mx-3 text-[20px] md:text-[23px]">
|
||||
<div
|
||||
ref={titleRef}
|
||||
className="flex flex-row items-center justify-between my-2 mx-3 text-[20px] md:text-[23px]"
|
||||
>
|
||||
<div className="font-light">
|
||||
{t(`report-name.${props.ReportName}`)}{" "}
|
||||
{props.report_help && (
|
||||
@@ -1428,26 +1465,68 @@ const ReportTable = (props) => {
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-row justify-center items-center gap-3">
|
||||
{props.isImport && (
|
||||
<div className="cursor-pointer" onClick={() => handleImportBtn()}>
|
||||
<i className="fa-light fa-upload op-text-secondary text-[23px] md:text-[30px]"></i>
|
||||
<div className="flex flex-row justify-center items-center gap-3 mb-2">
|
||||
{/* Search input for report bigger in width */}
|
||||
{titleElement?.width > 500 && (
|
||||
<div className="flex">
|
||||
<input
|
||||
type="search"
|
||||
value={props.searchTerm}
|
||||
onChange={props.handleSearchChange}
|
||||
placeholder={
|
||||
props.ReportName === "Contactbook"
|
||||
? t("search-contacts")
|
||||
: isTemplateReport
|
||||
? t("search-templates")
|
||||
: t("search-documents")
|
||||
}
|
||||
onPaste={props.handleSearchPaste}
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-64 text-xs"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{/* contact import */}
|
||||
{props.isImport && (
|
||||
<div
|
||||
className="cursor-pointer flex"
|
||||
onClick={() => handleImportBtn()}
|
||||
>
|
||||
<i className="fa-light fa-upload op-text-secondary text-[23px] md:text-[25px]"></i>
|
||||
</div>
|
||||
)}
|
||||
{/* add contact form */}
|
||||
{props.form && (
|
||||
<div
|
||||
className="cursor-pointer"
|
||||
className="cursor-pointer flex"
|
||||
onClick={() => handleContactFormModal()}
|
||||
>
|
||||
<i className="fa-light fa-square-plus text-accent text-[30px] md:text-[35px]"></i>
|
||||
<i className="fa-light fa-square-plus text-accent text-[30px] md:text-[32px]"></i>
|
||||
</div>
|
||||
)}
|
||||
{/* create template form */}
|
||||
{isTemplateReport && (
|
||||
<i
|
||||
<div
|
||||
data-tut="reactourFirst"
|
||||
className="cursor-pointer flex"
|
||||
onClick={() => navigate("/form/template")}
|
||||
className="cursor-pointer fa-light fa-square-plus text-accent text-[30px] md:text-[35px]"
|
||||
></i>
|
||||
>
|
||||
<i className="cursor-pointer fa-light fa-square-plus text-accent text-[30px] md:text-[32px]"></i>
|
||||
</div>
|
||||
)}
|
||||
{/* search icon/magnifer icon */}
|
||||
{titleElement?.width < 500 && (
|
||||
<button
|
||||
className="flex justify-center items-center focus:outline-none rounded-md text-[18px]"
|
||||
aria-label="Search"
|
||||
onClick={() =>
|
||||
props.setMobileSearchOpen(!props.mobileSearchOpen)
|
||||
}
|
||||
>
|
||||
<i
|
||||
style={{ color: `${iconColor}` }}
|
||||
className="fa-solid fa-magnifying-glass"
|
||||
></i>
|
||||
</button>
|
||||
)}
|
||||
<ModalUi
|
||||
isOpen={isModal?.export}
|
||||
@@ -1477,6 +1556,17 @@ const ReportTable = (props) => {
|
||||
required
|
||||
className="op-file-input op-file-input-bordered op-file-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
/>
|
||||
<p className="mt-1 ml-2 text-[11px] text-gray-600">
|
||||
{t("import-guideline")}{" "}
|
||||
<a
|
||||
href="/sample_contacts.csv"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-primary underline"
|
||||
>
|
||||
{t("download-sample")}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-md m-2">
|
||||
<div className="flex flex-col md:flex-row gap-1">
|
||||
@@ -1536,6 +1626,19 @@ const ReportTable = (props) => {
|
||||
</ModalUi>
|
||||
</div>
|
||||
</div>
|
||||
{/* Search input for report smalle in width */}
|
||||
{titleElement?.width < 500 && props.mobileSearchOpen && (
|
||||
<div className="top-full left-0 w-full bg-white px-3 pt-1 pb-3">
|
||||
<input
|
||||
type="search"
|
||||
value={props.searchTerm}
|
||||
onChange={props.handleSearchChange}
|
||||
placeholder={t("search-documents")}
|
||||
onPaste={props.handleSearchPaste}
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={`overflow-auto w-full border-b ${
|
||||
props.List?.length > 0
|
||||
@@ -1552,7 +1655,24 @@ const ReportTable = (props) => {
|
||||
<tr className="border-y-[1px]">
|
||||
{props.heading?.map((item, index) => (
|
||||
<React.Fragment key={index}>
|
||||
<th className="p-2">{t(`report-heading.${item}`)}</th>
|
||||
<th className="p-2">
|
||||
{t(`report-heading.${item}`)}
|
||||
{props.ReportName === "Contactbook" && item === "Name" && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={toggleSortOrder}
|
||||
className="ml-1"
|
||||
>
|
||||
<i
|
||||
className={
|
||||
sortOrder === "asc"
|
||||
? "fa-light fa-arrow-down-a-z"
|
||||
: "fa-light fa-arrow-up-a-z"
|
||||
}
|
||||
></i>
|
||||
</button>
|
||||
)}
|
||||
</th>
|
||||
</React.Fragment>
|
||||
))}
|
||||
{props.actions?.length > 0 && (
|
||||
@@ -1602,7 +1722,7 @@ const ReportTable = (props) => {
|
||||
handleClose={handleClose}
|
||||
>
|
||||
<div className="m-[20px]">
|
||||
<div className="text-lg font-normal text-black">
|
||||
<div className="text-lg font-normal text-base-content">
|
||||
{t("contact-delete-alert")}
|
||||
</div>
|
||||
<hr className="bg-[#ccc] mt-4 " />
|
||||
@@ -1745,7 +1865,7 @@ const ReportTable = (props) => {
|
||||
{/* template report */}
|
||||
{isOption[item.objectId] &&
|
||||
act.action === "option" && (
|
||||
<ul className="absolute -right-1 top-auto z-[70] w-52 op-dropdown-content op-menu shadow-black/20 shadow bg-base-100 text-base-content rounded-box">
|
||||
<ul className="absolute -right-1 top-auto z-[70] w-52 op-dropdown-content op-menu op-menu-sm shadow-black/20 shadow bg-base-100 text-base-content rounded-box">
|
||||
{act.subaction?.map((subact) => (
|
||||
<li
|
||||
key={subact.btnId}
|
||||
@@ -1836,34 +1956,40 @@ const ReportTable = (props) => {
|
||||
{/* doc report */}
|
||||
{isOption[item.objectId] &&
|
||||
act.action === "option" && (
|
||||
<ul className="absolute -right-1 top-auto z-[70] w-max op-dropdown-content op-menu shadow-black/20 shadow bg-base-100 text-base-content rounded-box">
|
||||
{act.subaction?.map((subact) => (
|
||||
<li
|
||||
key={subact.btnId}
|
||||
onClick={() =>
|
||||
handleActionBtn(
|
||||
subact,
|
||||
item
|
||||
)
|
||||
}
|
||||
title={t(
|
||||
`btnLabel.${subact.hoverLabel}`
|
||||
)}
|
||||
>
|
||||
<span>
|
||||
<i
|
||||
className={`${subact.btnIcon} mr-1.5`}
|
||||
></i>
|
||||
{subact.btnLabel && (
|
||||
<span className="text-[13px] capitalize font-medium">
|
||||
{t(
|
||||
`btnLabel.${subact.btnLabel}`
|
||||
<ul className="absolute -right-1 top-auto z-[70] w-max op-dropdown-content op-menu op-menu-sm shadow-black/20 shadow bg-base-100 text-base-content rounded-box">
|
||||
{act.subaction?.map(
|
||||
(subact) =>
|
||||
!restrictBtn(
|
||||
item,
|
||||
subact
|
||||
) && (
|
||||
<li
|
||||
key={subact.btnId}
|
||||
onClick={() =>
|
||||
handleActionBtn(
|
||||
subact,
|
||||
item
|
||||
)
|
||||
}
|
||||
title={t(
|
||||
`btnLabel.${subact.hoverLabel}`
|
||||
)}
|
||||
>
|
||||
<span>
|
||||
<i
|
||||
className={`${subact.btnIcon} mr-1.5`}
|
||||
></i>
|
||||
{subact.btnLabel && (
|
||||
<span className="text-[13px] capitalize font-medium">
|
||||
{t(
|
||||
`btnLabel.${subact.btnLabel}`
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</li>
|
||||
)
|
||||
)}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
@@ -1972,7 +2098,7 @@ const ReportTable = (props) => {
|
||||
</div>
|
||||
) : (
|
||||
<div className="m-[20px]">
|
||||
<div className="text-lg font-normal text-black">
|
||||
<div className="text-lg font-normal text-base-content">
|
||||
{t("save-as-template-?")}
|
||||
</div>
|
||||
<hr className="bg-[#ccc] mt-3" />
|
||||
@@ -2010,7 +2136,7 @@ const ReportTable = (props) => {
|
||||
</label>
|
||||
<input
|
||||
type="date"
|
||||
className="rounded-full mb-2 bg-base-300 w-full px-4 py-2 text-black border-2 hover:border-spacing-2"
|
||||
className="rounded-full mb-2 bg-base-300 w-full px-4 py-2 text-base-content border-2 hover:border-spacing-2"
|
||||
defaultValue={
|
||||
item?.ExpiryDate?.iso?.split("T")?.[0]
|
||||
}
|
||||
@@ -2120,7 +2246,7 @@ const ReportTable = (props) => {
|
||||
handleClose={handleClose}
|
||||
>
|
||||
<div className="m-[20px]">
|
||||
<div className="text-lg font-normal text-black">
|
||||
<div className="text-lg font-normal text-base-content">
|
||||
{t("delete-document-alert")}
|
||||
</div>
|
||||
<hr className="bg-[#ccc] mt-4" />
|
||||
@@ -2177,7 +2303,7 @@ const ReportTable = (props) => {
|
||||
{shareUrls.map((share, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="text-sm font-normal text-black flex my-2 justify-between items-center"
|
||||
className="text-sm font-normal text-base-content flex my-2 justify-between items-center"
|
||||
>
|
||||
<span className="w-[150px] mr-[5px] md:mr-0 md:w-[300px] whitespace-nowrap overflow-hidden text-ellipsis text-sm font-semibold">
|
||||
{share.email}
|
||||
@@ -2219,14 +2345,14 @@ const ReportTable = (props) => {
|
||||
handleClose={handleClose}
|
||||
>
|
||||
<div className="m-[20px]">
|
||||
<div className="text-sm md:text-lg font-normal text-black">
|
||||
<div className="text-sm md:text-lg font-normal text-base-content">
|
||||
{t("revoke-document-alert")}
|
||||
</div>
|
||||
<div className="mt-2">
|
||||
<textarea
|
||||
rows={3}
|
||||
placeholder="Reason (optional)"
|
||||
className="px-4 op-textarea op-textarea-bordered focus:outline-none hover:border-base-content w-full text-xs"
|
||||
className="px-4 op-textarea op-textarea-bordered text-base-content focus:outline-none hover:border-base-content w-full text-xs"
|
||||
value={reason}
|
||||
onChange={(e) => setReason(e.target.value)}
|
||||
></textarea>
|
||||
@@ -2337,7 +2463,7 @@ const ReportTable = (props) => {
|
||||
)}
|
||||
{Object?.keys(isNextStep) <= 0 && (
|
||||
<div className="flex justify-between items-center gap-2 my-2 px-3">
|
||||
<div className="text-black">
|
||||
<div className="text-base-content">
|
||||
{user?.signerPtr?.Name || "-"}{" "}
|
||||
{`<${
|
||||
user?.email
|
||||
|
||||
@@ -43,7 +43,7 @@ function CustomModal(props) {
|
||||
</h3>
|
||||
{!isExtendExpiry && (
|
||||
<div className="p-[10px] px-[20px]">
|
||||
<p className="text-[15px]">{props.bodyMssg && props.bodyMssg}</p>
|
||||
<p className="text-[15px] text-base-content">{props.bodyMssg && props.bodyMssg}</p>
|
||||
</div>
|
||||
)}
|
||||
{!isExtendExpiry && (
|
||||
@@ -68,7 +68,7 @@ function CustomModal(props) {
|
||||
)}
|
||||
{props.footerMessage && (
|
||||
<>
|
||||
<div className="mx-3">
|
||||
<div className="mx-3 text-base-content">
|
||||
<textarea
|
||||
rows={3}
|
||||
placeholder="Reason (optional)"
|
||||
@@ -108,7 +108,7 @@ function CustomModal(props) {
|
||||
</label>
|
||||
<input
|
||||
type="date"
|
||||
className="rounded-full bg-base-300 w-full px-4 py-2 text-black border-2 hover:border-spacing-2"
|
||||
className="rounded-full bg-base-300 w-full px-4 py-2 text-base-content border-2 hover:border-spacing-2"
|
||||
defaultValue={props?.doc?.ExpiryDate?.iso?.split("T")?.[0]}
|
||||
onChange={(e) => setExpiryDate(e.target.value)}
|
||||
/>
|
||||
|
||||
@@ -10,6 +10,10 @@
|
||||
background-position: right 0.7rem top 50%;
|
||||
background-size: 1rem auto;
|
||||
}
|
||||
[data-theme="opensigndark"] .validationlist{
|
||||
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAAZCAYAAADE6YVjAAAAAXNSR0IArs4c6QAAAQ1JREFUSEvtlDtKBTEARc/Fys8OXumndhl2ggt4jTuwsVJQK92DtY1Yuw3bp2jlDvxUcn2BDIQ4k2Sa1ziBNMmdHO4JGbGCoRUwmCCjLP8DXbYPgD3gVtLnkB/bm8AxsJD02Jfr1WX7EJjHD16BC0kf+QG2N4BzYDfu3Um6z3N/ILb3gbMs+AJcpo1igwDYybIh95Su9UFmwPVyrg+BCoBv4FTSexESNm1vRw1bGegNuAFOEkVdJABCi0VVVxewHS49aAve0/EDrGVrX8u1qz5AyBXfSaFRyhhs0IWqj7ECKjZohhTuqAlQ1ZU6yRo1A0ZBYqPw6I6AB0nPrX/J6p20HlTKTZBRFiddo3T9ArZOWBrGcf52AAAAAElFTkSuQmCC");
|
||||
}
|
||||
|
||||
|
||||
@media (max-width: 375px) {
|
||||
.validationlist {
|
||||
@@ -17,7 +21,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width:375px) and (max-width: 767px) {
|
||||
@media (min-width: 375px) and (max-width: 767px) {
|
||||
.validationlist {
|
||||
background-position: right 1rem top 50%;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
/* VS Code Dark Theme Improvements for OpenSign */
|
||||
|
||||
/* Better disabled button styling for dark mode */
|
||||
[data-theme="opensigndark"] {
|
||||
/* Primary button disabled state */
|
||||
.op-btn-primary:disabled {
|
||||
background-color: #3C3C3C !important;
|
||||
color: #CCCCCC !important;
|
||||
border-color: #565656 !important;
|
||||
opacity: 1 !important;
|
||||
cursor: not-allowed !important;
|
||||
}
|
||||
|
||||
.op-btn-primary:disabled:hover {
|
||||
background-color: #3C3C3C !important;
|
||||
color: #CCCCCC !important;
|
||||
border-color: #565656 !important;
|
||||
transform: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
/* Secondary button disabled state */
|
||||
.op-btn-secondary:disabled {
|
||||
background-color: #2A2A2A !important;
|
||||
color: #999999 !important;
|
||||
border-color: #444444 !important;
|
||||
opacity: 1 !important;
|
||||
cursor: not-allowed !important;
|
||||
}
|
||||
|
||||
/* Ghost button disabled state */
|
||||
.op-btn-ghost:disabled {
|
||||
background-color: transparent !important;
|
||||
color: #666666 !important;
|
||||
border-color: #444444 !important;
|
||||
opacity: 1 !important;
|
||||
cursor: not-allowed !important;
|
||||
}
|
||||
|
||||
/* Better icon visibility for various states */
|
||||
.icon-disabled,
|
||||
.fa-light.text-gray-400,
|
||||
.fa-light.text-gray-500 {
|
||||
color: #858585 !important;
|
||||
}
|
||||
|
||||
.icon-visible,
|
||||
.nav-icon,
|
||||
.folder-icon {
|
||||
color: #CCCCCC !important;
|
||||
}
|
||||
|
||||
/* Muted/inactive icons with better visibility */
|
||||
.muted-icon,
|
||||
.inactive-icon {
|
||||
color: #999999 !important;
|
||||
}
|
||||
|
||||
/* Hover states for better interactivity */
|
||||
.hover\\:bg-gray-200:hover {
|
||||
background-color: #2A2A2A !important;
|
||||
}
|
||||
|
||||
.hover\\:text-gray-600:hover {
|
||||
color: #E5E7EB !important;
|
||||
}
|
||||
|
||||
/* Form elements in disabled state */
|
||||
.op-input:disabled,
|
||||
.op-select:disabled,
|
||||
.op-textarea:disabled {
|
||||
background-color: #2A2A2A !important;
|
||||
color: #999999 !important;
|
||||
border-color: #444444 !important;
|
||||
cursor: not-allowed !important;
|
||||
}
|
||||
|
||||
/* Dropdown menu items */
|
||||
.dropdown-item {
|
||||
color: #E5E7EB !important;
|
||||
}
|
||||
|
||||
.dropdown-item:hover {
|
||||
background-color: #2A2A2A !important;
|
||||
color: #FFFFFF !important;
|
||||
}
|
||||
|
||||
/* Better text contrast for various elements */
|
||||
.text-gray-600 {
|
||||
color: #CCCCCC !important;
|
||||
}
|
||||
|
||||
.text-gray-500 {
|
||||
color: #999999 !important;
|
||||
}
|
||||
|
||||
.text-gray-400 {
|
||||
color: #858585 !important;
|
||||
}
|
||||
|
||||
/* Status indicators with better visibility */
|
||||
.status-badge {
|
||||
box-shadow: 0 2px 4px rgba(255, 255, 255, 0.1) !important;
|
||||
}
|
||||
|
||||
/* Tooltip improvements */
|
||||
.op-tooltip {
|
||||
background-color: #1F2937 !important;
|
||||
color: #E5E7EB !important;
|
||||
border-color: #4B5563 !important;
|
||||
}
|
||||
|
||||
/* Card and panel borders */
|
||||
.op-card,
|
||||
.border-gray-300 {
|
||||
border-color: #2C2C2C !important;
|
||||
}
|
||||
|
||||
/* Loading states */
|
||||
.opacity-50 {
|
||||
opacity: 0.7 !important;
|
||||
}
|
||||
|
||||
/* Focus states for better accessibility */
|
||||
.op-btn:focus-visible,
|
||||
.op-input:focus-visible,
|
||||
.op-select:focus-visible {
|
||||
outline: 2px solid #007ACC !important;
|
||||
outline-offset: 2px !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Ensure these styles don't affect light mode */
|
||||
[data-theme="opensigncss"] {
|
||||
/* Keep original colors for light mode */
|
||||
.icon-disabled {
|
||||
color: #9CA3AF;
|
||||
}
|
||||
|
||||
.icon-visible {
|
||||
color: #6B7280;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,26 @@
|
||||
/* Dark mode support for custom warning in Managesign */
|
||||
[data-theme="opensigndark"] .customwarning {
|
||||
background-color: #374151 !important;
|
||||
color: #E5E7EB !important;
|
||||
border-color: #4B5563 !important;
|
||||
}
|
||||
|
||||
[data-theme="opensigndark"] .customwarning::before {
|
||||
border-color: transparent transparent #4B5563 transparent !important;
|
||||
}
|
||||
|
||||
/* Dark mode support for signature management warning */
|
||||
[data-theme="opensigndark"] .signWarning {
|
||||
background-color: #374151 !important;
|
||||
color: #E5E7EB !important;
|
||||
}
|
||||
|
||||
/* Ensure the Managesign page background is consistent */
|
||||
[data-theme="opensigndark"] .managesign-container {
|
||||
background-color: #121212 !important;
|
||||
color: #F3F4F6 !important;
|
||||
}
|
||||
|
||||
.customwarning {
|
||||
position: absolute;
|
||||
padding: 8px;
|
||||
|
||||
@@ -204,6 +204,15 @@ a {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* Dark mode support for HoverCard */
|
||||
[data-theme="opensigndark"] .HoverCardContent {
|
||||
background-color: #1F2937;
|
||||
color: #E5E7EB;
|
||||
box-shadow:
|
||||
hsl(0 0% 0% / 50%) 0px 10px 38px -10px,
|
||||
hsl(0 0% 0% / 30%) 0px 10px 20px -15px;
|
||||
}
|
||||
|
||||
.HoverCardContent[data-side="top"] {
|
||||
animation-name: slideDownAndFade;
|
||||
}
|
||||
@@ -224,6 +233,11 @@ a {
|
||||
fill: white;
|
||||
}
|
||||
|
||||
/* Dark mode support for HoverCard arrow */
|
||||
[data-theme="opensigndark"] .HoverCardArrow {
|
||||
fill: #1F2937;
|
||||
}
|
||||
|
||||
@keyframes slideUpAndFade {
|
||||
0% {
|
||||
opacity: 0;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
.react-datepicker__input-container {
|
||||
position: initial !important;
|
||||
}
|
||||
.select-none-cls{
|
||||
.select-none-cls {
|
||||
-webkit-user-select: none;
|
||||
/* Disable text selection in WebKit browsers */
|
||||
-moz-user-select: none;
|
||||
@@ -33,10 +33,17 @@
|
||||
width: 440px;
|
||||
height: 167px;
|
||||
}
|
||||
.tabWidth{
|
||||
.tabWidth {
|
||||
border: 1px solid #f3f4f6;
|
||||
background-color: #f3f4f6;
|
||||
width: 440px;
|
||||
}
|
||||
|
||||
[data-theme="opensigndark"] .tabWidth {
|
||||
border: 1px solid #1f2937 !important;
|
||||
background-color: #1f2937 !important;
|
||||
}
|
||||
|
||||
.intialSignatureCanvas {
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
@@ -52,7 +59,6 @@
|
||||
background-color: #111111; /* blue-500 */
|
||||
}
|
||||
|
||||
|
||||
.intialSignature {
|
||||
border: 2px solid #888;
|
||||
background-color: rgb(255, 255, 255);
|
||||
@@ -60,6 +66,40 @@
|
||||
height: 183px;
|
||||
}
|
||||
|
||||
/* Dark mode support for initials box in /managesign */
|
||||
[data-theme="opensigndark"] .intialSignature {
|
||||
background-color: #1f2937 !important;
|
||||
border-color: #4b5563 !important;
|
||||
}
|
||||
|
||||
[data-theme="opensigndark"] .intialSignatureCanvas {
|
||||
background-color: #1f2937 !important;
|
||||
border-color: #4b5563 !important;
|
||||
}
|
||||
|
||||
/* Also support signature canvas for consistency */
|
||||
[data-theme="opensigndark"] .signatureCanvas {
|
||||
background-color: #1f2937 !important;
|
||||
border-color: #4b5563 !important;
|
||||
}
|
||||
|
||||
/* Dark mode support for initials box in /managesign */
|
||||
[data-theme="opensigndark"] .intialSignature {
|
||||
background-color: #1f2937 !important;
|
||||
border-color: #4b5563 !important;
|
||||
}
|
||||
|
||||
[data-theme="opensigndark"] .intialSignatureCanvas {
|
||||
background-color: #1f2937 !important;
|
||||
border-color: #4b5563 !important;
|
||||
}
|
||||
|
||||
/* Also support signature canvas for consistency */
|
||||
[data-theme="opensigndark"] .signatureCanvas {
|
||||
background-color: #1f2937 !important;
|
||||
border-color: #4b5563 !important;
|
||||
}
|
||||
|
||||
.penContainerDefault {
|
||||
width: 460px;
|
||||
}
|
||||
@@ -401,7 +441,7 @@ option {
|
||||
width: 300px;
|
||||
height: 120px;
|
||||
}
|
||||
.tabWidth{
|
||||
.tabWidth {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
@@ -427,7 +467,7 @@ option {
|
||||
width: 280px;
|
||||
height: 112px;
|
||||
}
|
||||
.tabWidth{
|
||||
.tabWidth {
|
||||
width: 280px;
|
||||
}
|
||||
|
||||
@@ -451,7 +491,7 @@ option {
|
||||
width: 230px;
|
||||
height: 92px;
|
||||
}
|
||||
.tabWidth{
|
||||
.tabWidth {
|
||||
width: 230px;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,11 +6,67 @@ module.exports = {
|
||||
},
|
||||
plugins: [
|
||||
require("daisyui"),
|
||||
function ({ addUtilities }) {
|
||||
function ({ addUtilities, theme }) {
|
||||
addUtilities({
|
||||
// Prevent iOS long-press popup
|
||||
".touch-callout-none": {
|
||||
"-webkit-touch-callout": "none"
|
||||
},
|
||||
// VS Code-style disabled button for all themes
|
||||
".op-btn-vscode-disabled": {
|
||||
"background-color": "#3C3C3C !important",
|
||||
color: "#CCCCCC !important",
|
||||
"border-color": "#565656 !important",
|
||||
cursor: "not-allowed !important",
|
||||
opacity: "1 !important",
|
||||
"&:hover": {
|
||||
"background-color": "#3C3C3C !important",
|
||||
color: "#CCCCCC !important",
|
||||
"border-color": "#565656 !important",
|
||||
transform: "none !important"
|
||||
}
|
||||
},
|
||||
// Dark mode icon improvements using DaisyUI theme detection
|
||||
'[data-theme="opensigndark"] .icon-improved': {
|
||||
color: "#CCCCCC !important"
|
||||
},
|
||||
'[data-theme="opensigndark"] .icon-muted': {
|
||||
color: "#999999 !important"
|
||||
},
|
||||
'[data-theme="opensigndark"] .icon-disabled': {
|
||||
color: "#858585 !important"
|
||||
},
|
||||
// Gray text improvements for dark mode
|
||||
'[data-theme="opensigndark"] .text-gray-500': {
|
||||
color: "#CCCCCC !important"
|
||||
},
|
||||
'[data-theme="opensigndark"] .text-gray-400': {
|
||||
color: "#999999 !important"
|
||||
},
|
||||
'[data-theme="opensigndark"] .text-gray-600': {
|
||||
color: "#CCCCCC !important"
|
||||
},
|
||||
// CSS variable utilities that work with arbitrary values
|
||||
".icon-themed": {
|
||||
color: "var(--icon-color)"
|
||||
},
|
||||
".icon-themed-muted": {
|
||||
color: "var(--icon-color-muted)"
|
||||
},
|
||||
".icon-themed-disabled": {
|
||||
color: "var(--icon-color-disabled)"
|
||||
},
|
||||
".btn-themed-disabled": {
|
||||
"background-color": "var(--btn-disabled-bg)",
|
||||
color: "var(--btn-disabled-color)",
|
||||
"border-color": "var(--btn-disabled-border)",
|
||||
cursor: "not-allowed",
|
||||
"&:hover": {
|
||||
"background-color": "var(--btn-disabled-bg)",
|
||||
color: "var(--btn-disabled-color)",
|
||||
"border-color": "var(--btn-disabled-border)",
|
||||
transform: "none"
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -18,7 +74,48 @@ module.exports = {
|
||||
daisyui: {
|
||||
// themes: true,
|
||||
themes: [
|
||||
"dark",
|
||||
{
|
||||
opensigndark: {
|
||||
primary: "#007ACC", // VS Code blue - CTA & highlight color
|
||||
"primary-content": "#FFFFFF",
|
||||
|
||||
secondary: "#1F2937", // Sidebar background (darker slate)
|
||||
"secondary-content": "#E5E7EB",
|
||||
|
||||
accent: "#4A9EFF", // Lighter VS Code blue for hover, minor CTA
|
||||
"accent-content": "#FFFFFF",
|
||||
|
||||
neutral: "#3C3C3C", // VS Code inactive/disabled element background
|
||||
"neutral-content": "#CCCCCC", // VS Code inactive text color
|
||||
|
||||
"base-100": "#121212", // App background
|
||||
"base-200": "#181818", // Slight elevation (cards)
|
||||
"base-300": "#1E1E1E", // Further elevated items (panels)
|
||||
"base-content": "#F3F4F6", // Main text color (soft white)
|
||||
|
||||
info: "#2563EB", // For info panels like "Out for signature"
|
||||
success: "#22C55E", // Optional: for completed docs or alerts
|
||||
warning: "#FBBF24",
|
||||
error: "#EF4444",
|
||||
|
||||
"--rounded-btn": "1.9rem",
|
||||
"--tab-border": "2px",
|
||||
"--tab-radius": "0.7rem",
|
||||
|
||||
// Custom CSS variables for icon and button states
|
||||
"--icon-color": "#CCCCCC",
|
||||
"--icon-color-muted": "#999999",
|
||||
"--icon-color-disabled": "#858585",
|
||||
"--btn-disabled-bg": "#3C3C3C",
|
||||
"--btn-disabled-color": "#CCCCCC",
|
||||
"--btn-disabled-border": "#565656",
|
||||
|
||||
// Optional polish
|
||||
"--navbar-padding": "0.8rem",
|
||||
"--border-color": "#2C2C2C", // Card/table separation
|
||||
"--tooltip-color": "#1F2937"
|
||||
}
|
||||
},
|
||||
{
|
||||
opensigncss: {
|
||||
primary: "#002864",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { defineConfig } from "vite";
|
||||
import { defineConfig, splitVendorChunkPlugin } from "vite";
|
||||
import react from "@vitejs/plugin-react";
|
||||
import svgr from "vite-plugin-svgr";
|
||||
import { resolve } from "path";
|
||||
@@ -12,7 +12,8 @@ export default defineConfig(({ command, mode }) => {
|
||||
return {
|
||||
plugins: [
|
||||
react(),
|
||||
svgr() // Transform SVGs into React components
|
||||
svgr(), // Transform SVGs into React components
|
||||
splitVendorChunkPlugin()
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
FROM node:22.14.0
|
||||
|
||||
|
||||
# Install LibreOffice for DOCX to PDF conversions
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y libreoffice \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Set the working directory inside the container
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import axios from 'axios';
|
||||
dotenv.config();
|
||||
|
||||
export const cloudServerUrl = 'http://localhost:8080/app';
|
||||
export const serverAppId = process.env.APP_ID || 'opensign';
|
||||
export const appName = 'OpenSign™';
|
||||
|
||||
export const MAX_NAME_LENGTH = 250;
|
||||
@@ -288,6 +289,8 @@ export const selectFormat = data => {
|
||||
return 'MMMM dd, yyyy';
|
||||
case 'DD MMMM, YYYY':
|
||||
return 'dd MMMM, yyyy';
|
||||
case 'DD.MM.YYYY':
|
||||
return 'dd.MM.yyyy';
|
||||
default:
|
||||
return 'MM/dd/yyyy';
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ import express from 'express';
|
||||
import cors from 'cors';
|
||||
import dotenv from 'dotenv';
|
||||
import uploadFile from './uploadFile.js';
|
||||
import docxtopdf, { upload as docxUpload } from './docxtopdf.js';
|
||||
import decryptpdf, { upload as decryptUpload } from './decryptpdf.js';
|
||||
|
||||
export const app = express();
|
||||
|
||||
@@ -11,4 +13,5 @@ app.use(express.json({ limit: '50mb' }));
|
||||
app.use(express.urlencoded({ limit: '50mb', extended: true }));
|
||||
|
||||
app.post('/file_upload', uploadFile);
|
||||
|
||||
app.post('/docxtopdf', docxUpload.single('file'), docxtopdf);
|
||||
app.post('/decryptpdf', decryptUpload.single('file'), decryptpdf);
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import fs from 'node:fs';
|
||||
import multer from 'multer';
|
||||
import Coherentpdf from 'coherentpdf';
|
||||
|
||||
const storage = multer.diskStorage({
|
||||
destination(req, file, cb) {
|
||||
cb(null, 'exports');
|
||||
},
|
||||
filename(req, file, cb) {
|
||||
cb(null, file.originalname);
|
||||
},
|
||||
});
|
||||
|
||||
export const upload = multer({ storage });
|
||||
|
||||
export default async function decryptpdf(req, res) {
|
||||
const inputPath = req.file.path;
|
||||
const password = req.body.password || '';
|
||||
try {
|
||||
const file = fs.readFileSync(inputPath);
|
||||
const pdf = await Coherentpdf.fromMemory(file, password);
|
||||
await Coherentpdf.decryptPdf(pdf, password);
|
||||
// Get decrypted buffer directly from memory (no file I/O)
|
||||
const buffer = await Coherentpdf.toMemory(pdf, false, false);
|
||||
res.set({
|
||||
'Content-Type': 'application/pdf',
|
||||
'Content-Disposition': 'inline; filename="decrypted.pdf"',
|
||||
'Content-Length': buffer.length,
|
||||
});
|
||||
res.send(buffer);
|
||||
fs.unlink(inputPath, () => {});
|
||||
} catch (err) {
|
||||
fs.unlink(inputPath, () => {});
|
||||
console.log('Error in decrypt file: ', err);
|
||||
let code = err?.code ? err.code : 400;
|
||||
let message = err?.[2]?.c ? err[2].c : 'Something went wrong.';
|
||||
if (err?.[2]?.c?.includes('Bad password') || err?.[2]?.c?.includes('decrypt_pdf_inner')) {
|
||||
code = 401;
|
||||
message = 'Incorrect password.';
|
||||
}
|
||||
return res.status(code).json({ error: message });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
import fs from 'node:fs';
|
||||
import axios from 'axios';
|
||||
import multer from 'multer';
|
||||
import libre from 'libreoffice-convert';
|
||||
import util from 'node:util';
|
||||
import { cloudServerUrl, getSecureUrl, serverAppId } from '../../Utils.js';
|
||||
|
||||
libre.convertAsync = util.promisify(libre.convert);
|
||||
|
||||
const storage = multer.diskStorage({
|
||||
destination(req, file, cb) {
|
||||
cb(null, 'exports');
|
||||
},
|
||||
filename(req, file, cb) {
|
||||
cb(null, file.originalname);
|
||||
},
|
||||
});
|
||||
|
||||
export const upload = multer({ storage });
|
||||
|
||||
function generatePdfName(length) {
|
||||
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
let result = '';
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export default async function docxtopdf(req, res) {
|
||||
const serverUrl = cloudServerUrl;
|
||||
const appId = serverAppId;
|
||||
const masterKey = process.env.MASTER_KEY;
|
||||
const inputPath = req.file.path;
|
||||
const name = generatePdfName(16);
|
||||
const fileName = `${name}.pdf`;
|
||||
const outputPath = './exports/output.pdf';
|
||||
|
||||
try {
|
||||
const userRes = await axios.get(serverUrl + '/users/me', {
|
||||
headers: {
|
||||
'X-Parse-Application-Id': appId,
|
||||
'X-Parse-Session-Token': req.headers['sessiontoken'],
|
||||
},
|
||||
});
|
||||
const userId = JSON.stringify({
|
||||
UserId: {
|
||||
__type: 'Pointer',
|
||||
className: '_User',
|
||||
objectId: userRes.data.objectId,
|
||||
},
|
||||
});
|
||||
const resUser = await axios.get(
|
||||
serverUrl + `/classes/contracts_Users?where=${userId}&limit=1&include=TenantId`,
|
||||
{
|
||||
headers: {
|
||||
'X-Parse-Application-Id': appId,
|
||||
'X-Parse-Master-Key': masterKey,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (resUser?.data?.results?.length > 0) {
|
||||
const tenantId = resUser.data.results[0].TenantId?.objectId;
|
||||
const ext = '.pdf';
|
||||
const outPath = `./exports/output${ext}`;
|
||||
const docxBuf = fs.readFileSync(inputPath);
|
||||
const pdfBuffer = await libre.convertAsync(docxBuf, ext, undefined);
|
||||
fs.writeFileSync(outPath, pdfBuffer);
|
||||
const file = fs.readFileSync(outPath);
|
||||
const size = fs.statSync(outPath).size;
|
||||
const PartnersTenant = JSON.stringify({
|
||||
PartnersTenant: {
|
||||
__type: 'Pointer',
|
||||
className: 'partners_Tenant',
|
||||
objectId: tenantId,
|
||||
},
|
||||
});
|
||||
const resTenantCredit = await axios.get(
|
||||
serverUrl + `/classes/partners_TenantCredits?where=${PartnersTenant}&limit=1`,
|
||||
{
|
||||
headers: {
|
||||
'X-Parse-Application-Id': appId,
|
||||
'X-Parse-Master-Key': masterKey,
|
||||
},
|
||||
}
|
||||
);
|
||||
if (resTenantCredit.data?.results?.length > 0) {
|
||||
const tenantCreditsId = resTenantCredit.data.results[0].objectId;
|
||||
const activeFileAdapter = resUser.data.results[0].TenantId?.ActiveFileAdapter;
|
||||
let fileUrl;
|
||||
if (activeFileAdapter) {
|
||||
const params = {
|
||||
fileBase64: file.toString('base64'),
|
||||
fileName,
|
||||
id: activeFileAdapter,
|
||||
};
|
||||
const url = serverUrl + '/functions/savetofileadapter';
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Parse-Application-Id': appId,
|
||||
'X-Parse-Session-Token': req.headers['sessiontoken'],
|
||||
};
|
||||
try {
|
||||
const savetos3 = await axios.post(url, params, { headers });
|
||||
fileUrl = savetos3?.data?.result?.url;
|
||||
} catch (err) {
|
||||
console.log('err in save to customfile', err);
|
||||
}
|
||||
} else {
|
||||
const parsefile = await axios.post(serverUrl + `/files/${fileName}`, file, {
|
||||
headers: {
|
||||
'X-Parse-Application-Id': appId,
|
||||
'X-Parse-Master-Key': masterKey,
|
||||
'Content-Type': 'application/pdf',
|
||||
},
|
||||
});
|
||||
const fileRes = getSecureUrl(parsefile.data.url);
|
||||
fileUrl = fileRes.url;
|
||||
}
|
||||
const usedStorage = resTenantCredit.data.results[0].usedStorage
|
||||
? resTenantCredit.data.results[0].usedStorage + size
|
||||
: size;
|
||||
await axios.put(
|
||||
serverUrl + `/classes/partners_TenantCredits/${tenantCreditsId}`,
|
||||
{ usedStorage },
|
||||
{
|
||||
headers: {
|
||||
'X-Parse-Application-Id': appId,
|
||||
'X-Parse-Master-Key': masterKey,
|
||||
},
|
||||
}
|
||||
);
|
||||
await axios.post(
|
||||
serverUrl + '/classes/partners_DataFiles',
|
||||
{
|
||||
FileSize: size,
|
||||
FileUrl: fileUrl,
|
||||
TenantPtr: {
|
||||
__type: 'Pointer',
|
||||
className: 'partners_Tenant',
|
||||
objectId: tenantId,
|
||||
},
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
'X-Parse-Application-Id': appId,
|
||||
'X-Parse-Master-Key': masterKey,
|
||||
},
|
||||
}
|
||||
);
|
||||
[inputPath, outPath].forEach(p => fs.existsSync(p) && fs.unlinkSync(p));
|
||||
return res.status(200).json({ message: 'success.', url: fileUrl });
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
[inputPath, outputPath].forEach(p => fs.existsSync(p) && fs.unlinkSync(p));
|
||||
const msg =
|
||||
err?.response?.data?.error || err?.response?.data || err?.message || 'Something went wrong.';
|
||||
console.log(`Error converting file: ${msg}`);
|
||||
|
||||
const message =
|
||||
'We are currently experiencing some issues with processing DOCX files. Please upload the PDF version or contact us on support@opensignlabs.com';
|
||||
return res.status(400).json({ error: message });
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import multer from 'multer';
|
||||
import multerS3 from 'multer-s3';
|
||||
import aws from 'aws-sdk';
|
||||
import dotenv from 'dotenv';
|
||||
import { cloudServerUrl, useLocal } from '../../Utils.js';
|
||||
import { cloudServerUrl, serverAppId, useLocal } from '../../Utils.js';
|
||||
dotenv.config();
|
||||
|
||||
function sanitizeFileName(fileName) {
|
||||
@@ -50,7 +50,7 @@ async function uploadFile(req, res) {
|
||||
const DO_SPACE = process.env.DO_SPACE;
|
||||
|
||||
const parseBaseUrl = cloudServerUrl; //process.env.SERVER_URL;
|
||||
const parseAppId = process.env.APP_ID;
|
||||
const parseAppId = serverAppId;
|
||||
let fileStorage;
|
||||
if (useLocal === 'true') {
|
||||
fileStorage = multer.diskStorage({
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import axios from 'axios';
|
||||
import { cloudServerUrl } from '../../Utils.js';
|
||||
import { cloudServerUrl, serverAppId } from '../../Utils.js';
|
||||
const serverUrl = cloudServerUrl; //process.env.SERVER_URL;
|
||||
const APPID = process.env.APP_ID;
|
||||
const APPID = serverAppId;
|
||||
const masterKEY = process.env.MASTER_KEY;
|
||||
async function addTeamAndOrg(extUser) {
|
||||
try {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import axios from 'axios';
|
||||
import { cloudServerUrl } from '../../Utils.js';
|
||||
import { cloudServerUrl, serverAppId } from '../../Utils.js';
|
||||
async function AuthLoginAsMail(request) {
|
||||
try {
|
||||
//function for login user using user objectId without touching user's password
|
||||
const serverUrl = cloudServerUrl; //process.env.SERVER_URL;
|
||||
const APPID = process.env.APP_ID;
|
||||
const APPID = serverAppId;
|
||||
const masterKEY = process.env.MASTER_KEY;
|
||||
|
||||
let otpN = request.params.otp;
|
||||
@@ -92,7 +92,7 @@ async function AuthLoginAsMail(request) {
|
||||
} catch (err) {
|
||||
console.log('err in Auth');
|
||||
console.log(err);
|
||||
return 'Result not found', err;
|
||||
return 'Result not found';
|
||||
}
|
||||
}
|
||||
export default AuthLoginAsMail;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import axios from 'axios';
|
||||
import { appName, cloudServerUrl } from '../../Utils.js';
|
||||
import { appName, cloudServerUrl, serverAppId } from '../../Utils.js';
|
||||
|
||||
export default async function forwardDoc(request) {
|
||||
try {
|
||||
@@ -58,7 +58,7 @@ export default async function forwardDoc(request) {
|
||||
mailRes = await axios.post(`${cloudServerUrl}/functions/sendmailv3`, params, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Parse-Application-Id': process.env.APP_ID,
|
||||
'X-Parse-Application-Id': serverAppId,
|
||||
'X-Parse-Master-Key': process.env.MASTER_KEY,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import axios from 'axios';
|
||||
import { cloudServerUrl } from '../../Utils.js';
|
||||
import { cloudServerUrl, serverAppId } from '../../Utils.js';
|
||||
|
||||
export default async function GetTemplate(request) {
|
||||
const serverUrl = cloudServerUrl; //process.env.SERVER_URL;
|
||||
@@ -10,7 +10,7 @@ export default async function GetTemplate(request) {
|
||||
if (sessiontoken) {
|
||||
const userRes = await axios.get(serverUrl + '/users/me', {
|
||||
headers: {
|
||||
'X-Parse-Application-Id': process.env.APP_ID,
|
||||
'X-Parse-Application-Id': serverAppId,
|
||||
'X-Parse-Session-Token': sessiontoken,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import axios from 'axios';
|
||||
import { cloudServerUrl } from '../../Utils.js';
|
||||
const appId = process.env.APP_ID;
|
||||
import { cloudServerUrl, serverAppId } from '../../Utils.js';
|
||||
const appId = serverAppId;
|
||||
const masterkey = process.env.MASTER_KEY;
|
||||
export default async function createBatchContact(req) {
|
||||
if (!req?.user) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import axios from 'axios';
|
||||
import { cloudServerUrl, mailTemplate, replaceMailVaribles } from '../../Utils.js';
|
||||
import { cloudServerUrl, mailTemplate, replaceMailVaribles, serverAppId } from '../../Utils.js';
|
||||
const serverUrl = cloudServerUrl; //process.env.SERVER_URL;
|
||||
const appId = process.env.APP_ID;
|
||||
const appId = serverAppId;
|
||||
async function deductcount(docsCount, extUserId) {
|
||||
try {
|
||||
const extCls = new Parse.Object('contracts_Users');
|
||||
@@ -15,8 +15,6 @@ async function deductcount(docsCount, extUserId) {
|
||||
async function sendMail(document, publicUrl) {
|
||||
//sessionToken
|
||||
const baseUrl = new URL(publicUrl);
|
||||
|
||||
// console.log("pdfDetails", pdfDetails);
|
||||
const timeToCompleteDays = document?.TimeToCompleteDays || 15;
|
||||
const ExpireDate = new Date(document.createdAt);
|
||||
ExpireDate.setDate(ExpireDate.getDate() + timeToCompleteDays);
|
||||
@@ -86,7 +84,7 @@ async function sendMail(document, publicUrl) {
|
||||
};
|
||||
let params = {
|
||||
extUserId: document.ExtUserPtr.objectId,
|
||||
recipient: objectId ? existSigner?.Email : signerMail[i].email,
|
||||
recipient: existSigner?.Email || signerMail[i].email,
|
||||
subject: replaceVar?.subject ? replaceVar?.subject : mailTemplate(mailparam).subject,
|
||||
from: document.ExtUserPtr.Email,
|
||||
replyto: senderEmail || '',
|
||||
@@ -168,9 +166,9 @@ async function batchQuery(userId, Documents, Ip, parseConfig, type, publicUrl) {
|
||||
})),
|
||||
ACL: Acl,
|
||||
SentToOthers: true,
|
||||
RemindOnceInEvery: x.RemindOnceInEvery || 5,
|
||||
RemindOnceInEvery: x.RemindOnceInEvery ? parseInt(x.RemindOnceInEvery) : 5,
|
||||
AutomaticReminders: x.AutomaticReminders || false,
|
||||
TimeToCompleteDays: x.TimeToCompleteDays || 15,
|
||||
TimeToCompleteDays: x.TimeToCompleteDays ? parseInt(x.TimeToCompleteDays) : 15,
|
||||
OriginIp: Ip,
|
||||
DocSentAt: { __type: 'Date', iso: isoDate },
|
||||
IsEnableOTP: x?.IsEnableOTP || false,
|
||||
@@ -231,6 +229,7 @@ export default async function createBatchDocs(request) {
|
||||
const sessionToken = request.headers?.sessiontoken;
|
||||
const type = request.headers?.type || 'quicksend';
|
||||
const Documents = JSON.parse(strDocuments);
|
||||
|
||||
const Ip = request?.headers?.['x-real-ip'] || '';
|
||||
// Access the host from the headers
|
||||
const publicUrl = request.headers.public_url;
|
||||
|
||||
@@ -65,7 +65,7 @@ export default async function generateCertificatebydocId(req) {
|
||||
const certificate = await GenerateCertificate(doc);
|
||||
const certificatePdf = await PDFDocument.load(certificate);
|
||||
const p12 = new P12Signer(P12Buffer, { passphrase: process.env.PASS_PHRASE || null });
|
||||
// `pdflibAddPlaceholder` is used to add code of only digitial sign in certificate
|
||||
// `pdflibAddPlaceholder` is used to add code of only digital sign in certificate
|
||||
pdflibAddPlaceholder({
|
||||
pdfDoc: certificatePdf,
|
||||
reason: `Digitally signed by ${eSignName}.`,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import axios from 'axios';
|
||||
import { cloudServerUrl } from '../../Utils.js';
|
||||
import { cloudServerUrl, serverAppId } from '../../Utils.js';
|
||||
export default async function getDocument(request) {
|
||||
const serverUrl = cloudServerUrl; //process.env.SERVER_URL;
|
||||
const docId = request.params.docId;
|
||||
@@ -30,7 +30,7 @@ export default async function getDocument(request) {
|
||||
try {
|
||||
const userRes = await axios.get(serverUrl + '/users/me', {
|
||||
headers: {
|
||||
'X-Parse-Application-Id': process.env.APP_ID,
|
||||
'X-Parse-Application-Id': serverAppId,
|
||||
'X-Parse-Session-Token': sessiontoken,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import axios from 'axios';
|
||||
import { cloudServerUrl } from '../../Utils.js';
|
||||
import { cloudServerUrl, serverAppId } from '../../Utils.js';
|
||||
export default async function getDrive(request) {
|
||||
const serverUrl = cloudServerUrl; //process.env.SERVER_URL;
|
||||
const appId = process.env.APP_ID;
|
||||
const appId = serverAppId;
|
||||
const limit = request.params.limit;
|
||||
const skip = request.params.skip;
|
||||
const docId = request.params.docId;
|
||||
|
||||
@@ -1,20 +1,27 @@
|
||||
import { cloudServerUrl } from '../../Utils.js';
|
||||
import { cloudServerUrl, serverAppId } from '../../Utils.js';
|
||||
import reportJson from './reportsJson.js';
|
||||
import axios from 'axios';
|
||||
|
||||
// Escape regex special characters. Copied from filterDocs.js
|
||||
function escapeRegExp(str) {
|
||||
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
}
|
||||
|
||||
export default async function getReport(request) {
|
||||
const reportId = request.params.reportId;
|
||||
const limit = request.params.limit;
|
||||
const skip = request.params.skip;
|
||||
const searchTerm = request.params.searchTerm || '';
|
||||
|
||||
const serverUrl = cloudServerUrl; //process.env.SERVER_URL;
|
||||
const appId = process.env.APP_ID;
|
||||
const appId = serverAppId;
|
||||
const masterKey = process.env.MASTER_KEY;
|
||||
const sessionToken = request.headers['sessiontoken'] || request.headers['x-parse-session-token'];
|
||||
try {
|
||||
const userRes = await axios.get(serverUrl + '/users/me', {
|
||||
headers: {
|
||||
'X-Parse-Application-Id': appId,
|
||||
'X-Parse-Session-Token': request.headers['sessiontoken'],
|
||||
'X-Parse-Session-Token': sessionToken,
|
||||
},
|
||||
});
|
||||
const userId = userRes.data && userRes.data.objectId;
|
||||
@@ -25,7 +32,7 @@ export default async function getReport(request) {
|
||||
const { params, keys } = json;
|
||||
const orderBy = '-updatedAt';
|
||||
const strKeys = keys.join();
|
||||
let strParams = JSON.stringify(params);
|
||||
let paramsObj = { ...params };
|
||||
if (reportId == '6TeaPr321t') {
|
||||
const extUserQuery = new Parse.Query('contracts_Users');
|
||||
extUserQuery.equalTo('Email', userRes.data.email);
|
||||
@@ -36,8 +43,8 @@ export default async function getReport(request) {
|
||||
if (_extUser?.TeamIds && _extUser.TeamIds?.length > 0) {
|
||||
let teamArr = [];
|
||||
_extUser?.TeamIds?.forEach(x => (teamArr = [...teamArr, ...x.Ancestors]));
|
||||
strParams = JSON.stringify({
|
||||
...params,
|
||||
paramsObj = {
|
||||
...paramsObj,
|
||||
$or: [
|
||||
{ SharedWith: { $in: teamArr } },
|
||||
{
|
||||
@@ -55,15 +62,23 @@ export default async function getReport(request) {
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
};
|
||||
} else {
|
||||
strParams = JSON.stringify({
|
||||
...params,
|
||||
paramsObj = {
|
||||
...paramsObj,
|
||||
CreatedBy: { __type: 'Pointer', className: '_User', objectId: userId },
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
if (searchTerm) {
|
||||
const escaped = escapeRegExp(searchTerm);
|
||||
paramsObj = {
|
||||
...paramsObj,
|
||||
Name: { $regex: `.*${escaped}.*`, $options: 'i' },
|
||||
};
|
||||
}
|
||||
const strParams = JSON.stringify(paramsObj);
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Parse-Application-Id': appId,
|
||||
|
||||
@@ -1,25 +1,5 @@
|
||||
// `saveRoleContact` is used to save user in contracts_Guest role and create contact
|
||||
const saveRoleContact = async contact => {
|
||||
try {
|
||||
const Role = new Parse.Query(Parse.Role);
|
||||
const guestRole = await Role.equalTo('name', 'contracts_Guest').first();
|
||||
if (guestRole) {
|
||||
// Check if the user is already in the role
|
||||
const relation = guestRole.relation('users');
|
||||
const usersInRoleQuery = relation.query();
|
||||
usersInRoleQuery.equalTo('objectId', contact.UserId.objectId);
|
||||
const usersInRole = await usersInRoleQuery.find();
|
||||
if (usersInRole.length > 0) {
|
||||
console.log('User already added to Guest role.');
|
||||
} else {
|
||||
relation.add({ __type: 'Pointer', className: '_User', id: contact.UserId.objectId });
|
||||
await guestRole.save(null, { useMasterKey: true });
|
||||
// console.log('User added to Guest role successfully.');
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('err in role save', err);
|
||||
}
|
||||
const contactQuery = new Parse.Object('contracts_Contactbook');
|
||||
contactQuery.set('Name', contact.Name);
|
||||
contactQuery.set('Email', contact.Email);
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
saveFileUsage,
|
||||
getSecureUrl,
|
||||
appName,
|
||||
serverAppId,
|
||||
} from '../../../Utils.js';
|
||||
import GenerateCertificate from './GenerateCertificate.js';
|
||||
import { pdflibAddPlaceholder } from '@signpdf/placeholder-pdf-lib';
|
||||
@@ -15,7 +16,7 @@ import { SignPdf } from '@signpdf/signpdf';
|
||||
import { P12Signer } from '@signpdf/signer-p12';
|
||||
|
||||
const serverUrl = cloudServerUrl; // process.env.SERVER_URL;
|
||||
const APPID = process.env.APP_ID;
|
||||
const APPID = serverAppId;
|
||||
const masterKEY = process.env.MASTER_KEY;
|
||||
const eSignName = 'OpenSign';
|
||||
const eSigncontact = 'hello@opensignlabs.com';
|
||||
|
||||
@@ -14,11 +14,14 @@ export default async function recreateDocument(request) {
|
||||
if (!doc) {
|
||||
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Document not found');
|
||||
}
|
||||
if (doc?.get('IsSignyourself')) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Signyourself Document not allowed');
|
||||
}
|
||||
const _docRes = doc?.toJSON();
|
||||
const { objectId, SignedUrl, AuditTrail, ACL, DeclineBy, DeclineReason, ...docRes } = _docRes;
|
||||
const createDoc = new Parse.Object('contracts_Document');
|
||||
Object.entries(docRes).forEach(([key, value]) => {
|
||||
if (key === 'IsDeclined') {
|
||||
if (key === 'IsDeclined' || key === 'IsCompleted') {
|
||||
createDoc.set(key, false);
|
||||
} else {
|
||||
createDoc.set(key, value);
|
||||
|
||||
@@ -55,7 +55,7 @@ export default function reportJson(id, userId) {
|
||||
'ExpiryDate',
|
||||
],
|
||||
};
|
||||
// In progess report
|
||||
// In progress report
|
||||
case '1MwEuxLEkF':
|
||||
return {
|
||||
reportName: 'In-progress documents',
|
||||
@@ -123,6 +123,8 @@ export default function reportJson(id, userId) {
|
||||
'TimeToCompleteDays',
|
||||
'IsSignyourself',
|
||||
'IsCompleted',
|
||||
'ExpiryDate',
|
||||
'IsSignyourself',
|
||||
],
|
||||
};
|
||||
// declined documents report
|
||||
|
||||
@@ -51,6 +51,18 @@ export default async function saveAsTemplate(request) {
|
||||
|
||||
if (_docRes?.Placeholders?.length > 0) {
|
||||
if (_docRes?.IsSignyourself) {
|
||||
//add required option for all widget when save as template using signyour-self draft document
|
||||
const updatedPlaceholder = _docRes?.Placeholders.map(pageItem => ({
|
||||
...pageItem,
|
||||
pos: pageItem.pos.map(p => ({
|
||||
...p,
|
||||
options: {
|
||||
...p.options,
|
||||
status: 'required',
|
||||
...(p?.options?.defaultValue ? { defaultValue: '' } : {}),
|
||||
},
|
||||
})),
|
||||
}));
|
||||
const placeHolders = {
|
||||
signerObjId: '',
|
||||
signerPtr: {},
|
||||
@@ -58,14 +70,32 @@ export default async function saveAsTemplate(request) {
|
||||
blockColor: '#93a3db',
|
||||
Role: 'Role 1',
|
||||
email: '',
|
||||
placeHolder: _docRes?.Placeholders,
|
||||
placeHolder: updatedPlaceholder,
|
||||
};
|
||||
templateCls.set('Placeholders', [placeHolders]);
|
||||
} else {
|
||||
const placeHolders = _docRes?.Placeholders?.map((x, i) => {
|
||||
const email = x.email ? { email: '' } : {};
|
||||
return { ...x, signerObjId: '', signerPtr: {}, Role: 'Role ' + (i + 1), ...email };
|
||||
});
|
||||
const placeHolders = _docRes?.Placeholders.map((signer, signerIndex) => ({
|
||||
// copy everything else, then overwrite these fields:
|
||||
...signer,
|
||||
signerObjId: '',
|
||||
signerPtr: {},
|
||||
Role: `Role ${signerIndex + 1}`,
|
||||
email: '',
|
||||
|
||||
// rebuild placeHolder/pages
|
||||
placeHolder: (signer.placeHolder || []).map(page => ({
|
||||
...page,
|
||||
pos: (page.pos || []).map(widget => {
|
||||
// if there is a defaultValue in options, zero it out
|
||||
if (widget.options && widget.options.defaultValue !== undefined) {
|
||||
return { ...widget, options: { ...widget.options, defaultValue: '' } }; // reset only the value
|
||||
}
|
||||
// otherwise, return the widget unchanged
|
||||
return widget;
|
||||
}),
|
||||
})),
|
||||
}));
|
||||
|
||||
templateCls.set('Placeholders', placeHolders);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,6 @@ const makeEmail = async (
|
||||
replyto,
|
||||
testPdf
|
||||
) => {
|
||||
const publicUrl = new URL(process.env.SERVER_URL);
|
||||
const htmlContent = html;
|
||||
const boundary = 'boundary_' + Date.now().toString(16);
|
||||
const bccHeader = bcc && bcc.length > 0 ? `BCC: ${bcc.join(',')}\n` : ''; // Construct BCC header if provided
|
||||
@@ -53,10 +52,16 @@ const makeEmail = async (
|
||||
const isSecure =
|
||||
new URL(url)?.protocol === 'https:' && new URL(url)?.hostname !== 'localhost';
|
||||
if (isSecure) {
|
||||
https.get(url, async function (response) {
|
||||
response.pipe(Pdf);
|
||||
response.on('end', () => resolve('success'));
|
||||
});
|
||||
https
|
||||
.get(url, async function (response) {
|
||||
response.pipe(Pdf);
|
||||
Pdf.on('finish', () => resolve('success'));
|
||||
Pdf.on('error', () => resolve('error'));
|
||||
})
|
||||
.on('error', e => {
|
||||
console.error(`error: ${e.message}`);
|
||||
resolve('error');
|
||||
});
|
||||
} else {
|
||||
const httpsAgent = new https.Agent({ rejectUnauthorized: false }); // Disable SSL validation
|
||||
axios
|
||||
|
||||
@@ -8,12 +8,15 @@ export default async function updateTenant(request) {
|
||||
if (!request.user) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'unauthorized');
|
||||
}
|
||||
const validKeys = ['CompletionBody', 'CompletionSubject', 'RequestBody', 'RequestSubject'];
|
||||
try {
|
||||
const tenant = new Parse.Object('partners_Tenant');
|
||||
tenant.id = tenantId;
|
||||
// Update tenant details
|
||||
Object.keys(details).forEach(key => {
|
||||
tenant.set(key, details?.[key]);
|
||||
if (validKeys.includes(key)) {
|
||||
tenant.set(key, details?.[key]);
|
||||
}
|
||||
});
|
||||
|
||||
const tenantRes = await tenant.save(null, { useMasterKey: true });
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import axios from 'axios';
|
||||
import { cloudServerUrl } from '../../Utils.js';
|
||||
import { cloudServerUrl, serverAppId } from '../../Utils.js';
|
||||
const serverUrl = cloudServerUrl; //process.env.SERVER_URL;
|
||||
const APPID = process.env.APP_ID;
|
||||
const APPID = serverAppId;
|
||||
const masterKEY = process.env.MASTER_KEY;
|
||||
|
||||
async function saveUser(userDetails) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user