mirror of
https://github.com/OpenSignLabs/OpenSign.git
synced 2026-09-04 16:58:05 +02:00
Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
be5b1b87f9 | ||
|
|
7204183542 | ||
|
|
5561673fdc | ||
|
|
0df44062d6 | ||
|
|
8983440763 | ||
|
|
5a28c0ed06 |
@@ -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>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
|
||||
const Cell = ({
|
||||
isEnabled,
|
||||
count,
|
||||
h,
|
||||
value,
|
||||
@@ -15,10 +16,11 @@ const Cell = ({
|
||||
hint
|
||||
}) => (
|
||||
<div
|
||||
className="flex items-center justify-center border border-gray-800 bg-white"
|
||||
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}
|
||||
@@ -27,7 +29,7 @@ const Cell = ({
|
||||
onKeyDown={editable ? (e) => onKeyDown && onKeyDown(e, index) : undefined}
|
||||
// trigger validation when leaving a cell
|
||||
onBlur={editable ? (e) => onBlur && onBlur(e, index) : undefined}
|
||||
className="w-full text-center focus:outline-none bg-transparent placeholder-gray-300"
|
||||
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 }}
|
||||
/>
|
||||
@@ -35,6 +37,7 @@ const Cell = ({
|
||||
);
|
||||
|
||||
export default function CellsWidget({
|
||||
isEnabled,
|
||||
count = 8,
|
||||
height = 40,
|
||||
value = "",
|
||||
@@ -120,6 +123,7 @@ export default function CellsWidget({
|
||||
{cells.map((val, i) => (
|
||||
<Cell
|
||||
key={i}
|
||||
isEnabled={isEnabled}
|
||||
count={cellCount}
|
||||
h={height}
|
||||
value={val}
|
||||
|
||||
@@ -740,7 +740,7 @@ function Placeholder(props) {
|
||||
return "not-allowed";
|
||||
}
|
||||
} else {
|
||||
return "all-scroll";
|
||||
return "move";
|
||||
}
|
||||
};
|
||||
|
||||
@@ -946,23 +946,24 @@ function Placeholder(props) {
|
||||
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%",
|
||||
|
||||
@@ -226,6 +226,7 @@ function PlaceholderType(props) {
|
||||
props.isPlaceholder || props.isSignYourself || props.isSelfSign;
|
||||
return (
|
||||
<CellsWidget
|
||||
isEnabled={iswidgetEnable}
|
||||
count={count}
|
||||
height={height}
|
||||
value={cells.join("")}
|
||||
|
||||
@@ -123,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));
|
||||
@@ -148,7 +165,7 @@ const WidgetNameModal = (props) => {
|
||||
//allow space in text regex
|
||||
return "/^[a-zA-Z ]+$/";
|
||||
case "ssn":
|
||||
return "^\\d{3}-\\d{2}-\\d{4}$";
|
||||
return "/^(?!000|666|9\\d{2})\\d{3}-(?!00)\\d{2}-(?!0000)\\d{4}$/";
|
||||
default:
|
||||
return type;
|
||||
}
|
||||
|
||||
@@ -694,17 +694,20 @@ function WidgetsValueModal(props) {
|
||||
}
|
||||
}
|
||||
if (isTab === "type") {
|
||||
//trim user name or typed name value to show in initial signature
|
||||
const trimmedName = typedSignature
|
||||
? typedSignature?.trim()
|
||||
: currentUserName?.trim();
|
||||
//get full name of user
|
||||
const fullUserName = typedSignature || currentUserName
|
||||
const firstCharacter = trimmedName?.charAt(0);
|
||||
const userName =
|
||||
currWidgetsDetails?.type === "initials"
|
||||
? firstCharacter
|
||||
: typedSignature;
|
||||
: fullUserName;
|
||||
const signatureValue = currWidgetsDetails?.typeSignature;
|
||||
setTypedSignature(signatureValue || userName || "");
|
||||
convertToImg(fontSelect, userName);
|
||||
convertToImg(fontSelect, signatureValue || userName || "");
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isTab]);
|
||||
@@ -1472,6 +1475,7 @@ function WidgetsValueModal(props) {
|
||||
case cellsWidget:
|
||||
return (
|
||||
<CellsWidget
|
||||
isEnabled={true}
|
||||
count={cellsValue.length}
|
||||
height="100%"
|
||||
value={cellsValue.join("")}
|
||||
@@ -1762,6 +1766,10 @@ function WidgetsValueModal(props) {
|
||||
regexValidation = /^[0-9\s]*$/;
|
||||
validateExpression(regexValidation);
|
||||
break;
|
||||
case "ssn":
|
||||
regexValidation = /^(?!000|666|9\d{2})\d{3}-(?!00)\d{2}-(?!0000)\d{4}$/;
|
||||
validateExpression(regexValidation);
|
||||
break;
|
||||
default:
|
||||
// Grab the current pattern (if it exists)
|
||||
const pattern = currWidgetsDetails?.options?.validation?.pattern;
|
||||
@@ -1872,6 +1880,11 @@ function WidgetsValueModal(props) {
|
||||
}
|
||||
};
|
||||
const handleclose = () => {
|
||||
// If validation is shown, clear the response and close the modal
|
||||
if (isShowValidation) {
|
||||
handleClear();
|
||||
setIsShowValidation(false);
|
||||
}
|
||||
dispatch(setIsShowModal({}));
|
||||
dispatch(setLastIndex(""));
|
||||
if (currWidgetsDetails?.type === textWidget && uniqueId) {
|
||||
@@ -1936,7 +1949,7 @@ function WidgetsValueModal(props) {
|
||||
<>
|
||||
<ModalUi
|
||||
isOpen={true}
|
||||
handleClose={() => !isShowValidation && handleclose()}
|
||||
handleClose={() => handleclose()}
|
||||
position="bottom"
|
||||
>
|
||||
<div className="h-[100%] p-[18px]">
|
||||
|
||||
@@ -98,6 +98,27 @@ export const getSecureUrl = async (url) => {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes a trailing path segment from a URL string, if present.
|
||||
*
|
||||
* @param {string} url - The original URL.
|
||||
* @param {string} segment - The segment to strip off (default: "app").
|
||||
* @returns {string} - The URL with the trailing segment removed, or unmodified if it didn’t match.
|
||||
*/
|
||||
export function removeTrailingSegment(url, segment = "app") {
|
||||
// Normalize a trailing slash (e.g. “/app/” → “/app”)
|
||||
const normalized = url.endsWith("/") ? url.slice(0, -1) : url;
|
||||
|
||||
const lastSlash = normalized.lastIndexOf("/");
|
||||
const lastPart = normalized.slice(lastSlash + 1);
|
||||
|
||||
if (lastPart === segment) {
|
||||
return normalized.slice(0, lastSlash);
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
export const color = [
|
||||
"#93a3db",
|
||||
"#e6c3db",
|
||||
@@ -3425,7 +3446,7 @@ export const handleCheckResponse = (checkUser, setminRequiredCount) => {
|
||||
export const decryptPdf = async (file, password) => {
|
||||
const name = generatePdfName(16);
|
||||
const baseApi = localStorage.getItem("baseUrl") || "";
|
||||
const url = baseApi.replace("/app", "") + "decryptpdf?ts=" + Date.now();
|
||||
const url = removeTrailingSegment(baseApi) + "/decryptpdf?ts=" + Date.now();
|
||||
let formData = new FormData();
|
||||
formData.append("file", file);
|
||||
formData.append("password", password);
|
||||
@@ -3468,6 +3489,15 @@ export function base64ToFile(base64String, filename) {
|
||||
return new File([u8arr], filename, { type: mime });
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the given File object and returns its contents as an ArrayBuffer.
|
||||
*
|
||||
* @param {File} file
|
||||
* The File instance to be read.
|
||||
* @returns {Promise<ArrayBuffer>}
|
||||
* A promise that resolves with the file’s binary data as an ArrayBuffer,
|
||||
* or rejects with an error if the read fails.
|
||||
*/
|
||||
export function getFileAsArrayBuffer(file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
|
||||
@@ -16,7 +16,8 @@ import {
|
||||
toDataUrl,
|
||||
decryptPdf,
|
||||
base64ToFile,
|
||||
getFileAsArrayBuffer
|
||||
getFileAsArrayBuffer,
|
||||
removeTrailingSegment
|
||||
} from "../constant/Utils";
|
||||
import { PDFDocument } from "pdf-lib";
|
||||
import axios from "axios";
|
||||
@@ -240,7 +241,7 @@ const Forms = (props) => {
|
||||
) {
|
||||
try {
|
||||
const baseApi = localStorage.getItem("baseUrl") || "";
|
||||
const url = baseApi.replace("/app", "") + "docxtopdf";
|
||||
const url = removeTrailingSegment(baseApi) + "/docxtopdf";
|
||||
let fd = new FormData();
|
||||
fd.append("file", file);
|
||||
setfileload(true);
|
||||
|
||||
@@ -1608,13 +1608,7 @@ function PlaceHolderSign() {
|
||||
parseInt(defaultdata?.cellCount || 5)
|
||||
),
|
||||
validation:
|
||||
isSubscribe && inputype
|
||||
? {
|
||||
type: inputype,
|
||||
pattern:
|
||||
inputype === "regex" ? defaultdata.textvalidate : ""
|
||||
}
|
||||
: {},
|
||||
{},
|
||||
fontSize:
|
||||
fontSize || currWidgetsDetails?.options?.fontSize || 12,
|
||||
fontColor:
|
||||
@@ -2636,7 +2630,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
|
||||
|
||||
Reference in New Issue
Block a user