mirror of
https://github.com/OpenSignLabs/OpenSign.git
synced 2026-09-05 01:07:39 +02:00
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
18a4c8523a | ||
|
|
f7b39409a8 | ||
|
|
6283524d2f | ||
|
|
216288fd09 | ||
|
|
3b1d8a48b7 |
@@ -3,6 +3,7 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- 'main'
|
- 'main'
|
||||||
|
- 'staging'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
docker:
|
docker:
|
||||||
|
|||||||
@@ -152,7 +152,8 @@
|
|||||||
"Duplicate Template": "Vorlage duplizieren",
|
"Duplicate Template": "Vorlage duplizieren",
|
||||||
"Duplicate": "Duplikat",
|
"Duplicate": "Duplikat",
|
||||||
"daily-mail-quota": "Tägliches E-Mail-Kontingent",
|
"daily-mail-quota": "Tägliches E-Mail-Kontingent",
|
||||||
"Save as template": "Als Vorlage speichern"
|
"Save as template": "Als Vorlage speichern",
|
||||||
|
"Fix & resend": "Korrigieren und erneut senden"
|
||||||
},
|
},
|
||||||
"report-heading": {
|
"report-heading": {
|
||||||
"Sr.No": "Nr.",
|
"Sr.No": "Nr.",
|
||||||
@@ -881,5 +882,10 @@
|
|||||||
"contact-billing-at-opensign": "Um weitere Plätze hinzuzufügen, kontaktieren Sie bitte OpenSign™ unter <1>billing@opensignlabs.com</1> für Unterstützung.",
|
"contact-billing-at-opensign": "Um weitere Plätze hinzuzufügen, kontaktieren Sie bitte OpenSign™ unter <1>billing@opensignlabs.com</1> für Unterstützung.",
|
||||||
"title-length-alert": "Der Titel darf höchstens 250 Zeichen lang sein.",
|
"title-length-alert": "Der Titel darf höchstens 250 Zeichen lang sein.",
|
||||||
"note-length-alert": "Die Notiz darf höchstens 200 Zeichen lang sein.",
|
"note-length-alert": "Die Notiz darf höchstens 200 Zeichen lang sein.",
|
||||||
"description-length-alert": "Die Beschreibung darf höchstens 500 Zeichen lang sein."
|
"description-length-alert": "Die Beschreibung darf höchstens 500 Zeichen lang sein.",
|
||||||
|
"fix-&-resend-document": "Dokument korrigieren und erneut senden",
|
||||||
|
"do-you-want-recreate-document?": "Dadurch wird ein Entwurf aus diesem Dokument mit allen vorhandenen Feldern erstellt. Sind Sie sicher, dass Sie dieses Dokument neu erstellen möchten?",
|
||||||
|
"start-editing": "Bearbeitung starten",
|
||||||
|
"unsaved-changes-discard-them?": "Sie haben ungespeicherte Änderungen. Verwerfen?",
|
||||||
|
"yes-discard": "Ja, verwerfen"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -152,7 +152,8 @@
|
|||||||
"Duplicate Template": "Duplicate template",
|
"Duplicate Template": "Duplicate template",
|
||||||
"Duplicate": "Duplicate",
|
"Duplicate": "Duplicate",
|
||||||
"daily-mail-quota": "Daily Email Quota",
|
"daily-mail-quota": "Daily Email Quota",
|
||||||
"Save as template": "Save as template"
|
"Save as template": "Save as template",
|
||||||
|
"Fix & resend": "Fix & Resend"
|
||||||
},
|
},
|
||||||
"report-heading": {
|
"report-heading": {
|
||||||
"Sr.No": "Sr.No",
|
"Sr.No": "Sr.No",
|
||||||
@@ -881,5 +882,10 @@
|
|||||||
"contact-billing-at-opensign": "To add more seats, please contact OpenSign™ at <1>billing@opensignlabs.com</1> for assistance",
|
"contact-billing-at-opensign": "To add more seats, please contact OpenSign™ at <1>billing@opensignlabs.com</1> for assistance",
|
||||||
"title-length-alert": "Title must be at most 250 characters long.",
|
"title-length-alert": "Title must be at most 250 characters long.",
|
||||||
"note-length-alert": "Note must be at most 200 characters long.",
|
"note-length-alert": "Note must be at most 200 characters long.",
|
||||||
"description-length-alert": "Description must be at most 500 characters long."
|
"description-length-alert": "Description must be at most 500 characters long.",
|
||||||
|
"fix-&-resend-document": "Fix & Resend Document",
|
||||||
|
"do-you-want-recreate-document?": "This will create a draft from this document with all fields intact. Are you sure you want to recreate this document?",
|
||||||
|
"start-editing": "Start Editing",
|
||||||
|
"unsaved-changes-discard-them?": "You have unsaved changes. Discard them?",
|
||||||
|
"yes-discard": "Yes, Discard"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -152,7 +152,8 @@
|
|||||||
"Duplicate Template": "Plantilla duplicada",
|
"Duplicate Template": "Plantilla duplicada",
|
||||||
"Duplicate": "Duplicada",
|
"Duplicate": "Duplicada",
|
||||||
"daily-mail-quota": "Cuota diaria de correos electrónicos",
|
"daily-mail-quota": "Cuota diaria de correos electrónicos",
|
||||||
"Save as template": "Guardar como plantilla"
|
"Save as template": "Guardar como plantilla",
|
||||||
|
"Fix & resend": "Corregir y reenviar"
|
||||||
},
|
},
|
||||||
"report-heading": {
|
"report-heading": {
|
||||||
"Sr.No": "Nº",
|
"Sr.No": "Nº",
|
||||||
@@ -881,5 +882,10 @@
|
|||||||
"contact-billing-at-opensign": "Para agregar más asientos, comuníquese con OpenSign™ a <1>billing@opensignlabs.com</1> para obtener ayuda.",
|
"contact-billing-at-opensign": "Para agregar más asientos, comuníquese con OpenSign™ a <1>billing@opensignlabs.com</1> para obtener ayuda.",
|
||||||
"title-length-alert": "El título debe tener como máximo 250 caracteres.",
|
"title-length-alert": "El título debe tener como máximo 250 caracteres.",
|
||||||
"note-length-alert": "La nota debe tener como máximo 200 caracteres.",
|
"note-length-alert": "La nota debe tener como máximo 200 caracteres.",
|
||||||
"description-length-alert": "La descripción debe tener como máximo 500 caracteres."
|
"description-length-alert": "La descripción debe tener como máximo 500 caracteres.",
|
||||||
|
"fix-&-resend-document": "Corregir y reenviar el documento",
|
||||||
|
"do-you-want-recreate-document?": "Esto creará un borrador a partir de este documento con todos los campos intactos. ¿Está seguro de que desea recrear este documento?",
|
||||||
|
"start-editing": "Comenzar a editar",
|
||||||
|
"unsaved-changes-discard-them?": "Tienes cambios sin guardar. ¿Deseas descartarlos?",
|
||||||
|
"yes-discard": "Sí, descartar"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -173,7 +173,8 @@
|
|||||||
"Duplicate Template": "dupliquer le modèle",
|
"Duplicate Template": "dupliquer le modèle",
|
||||||
"Duplicate": "Double",
|
"Duplicate": "Double",
|
||||||
"daily-mail-quota": "Quota d'e-mails quotidien",
|
"daily-mail-quota": "Quota d'e-mails quotidien",
|
||||||
"Save as template": "Enregistrer comme modèle"
|
"Save as template": "Enregistrer comme modèle",
|
||||||
|
"Fix & resend": "Corriger et renvoyer"
|
||||||
},
|
},
|
||||||
"report-help": {
|
"report-help": {
|
||||||
"Draft Documents": "Il s'agit de documents que vous avez commencés mais que vous n'avez pas finalisés pour envoi.",
|
"Draft Documents": "Il s'agit de documents que vous avez commencés mais que vous n'avez pas finalisés pour envoi.",
|
||||||
@@ -881,5 +882,10 @@
|
|||||||
"contact-billing-at-opensign": "Pour ajouter plus de places, veuillez contacter OpenSign™ à l'adresse <1>billing@opensignlabs.com</1> pour obtenir de l'aide.",
|
"contact-billing-at-opensign": "Pour ajouter plus de places, veuillez contacter OpenSign™ à l'adresse <1>billing@opensignlabs.com</1> pour obtenir de l'aide.",
|
||||||
"title-length-alert": "Le titre doit comporter au maximum 250 caractères.",
|
"title-length-alert": "Le titre doit comporter au maximum 250 caractères.",
|
||||||
"note-length-alert": "La note doit comporter au maximum 200 caractères.",
|
"note-length-alert": "La note doit comporter au maximum 200 caractères.",
|
||||||
"description-length-alert": "La description doit comporter au maximum 500 caractères"
|
"description-length-alert": "La description doit comporter au maximum 500 caractères",
|
||||||
|
"fix-&-resend-document": "Corriger et renvoyer le document",
|
||||||
|
"do-you-want-recreate-document?": "Cela créera un brouillon à partir de ce document avec tous les champs intacts. Êtes-vous sûr de vouloir recréer ce document ?",
|
||||||
|
"start-editing": "Commencer l'édition",
|
||||||
|
"unsaved-changes-discard-them?": "Vous avez des modifications non enregistrées. Les supprimer ?",
|
||||||
|
"yes-discard": "Oui, supprimer"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -152,7 +152,8 @@
|
|||||||
"Duplicate Template": "Duplica modello",
|
"Duplicate Template": "Duplica modello",
|
||||||
"Duplicate": "Duplica",
|
"Duplicate": "Duplica",
|
||||||
"daily-mail-quota": "Quota e-mail giornaliera",
|
"daily-mail-quota": "Quota e-mail giornaliera",
|
||||||
"Save as template": "Salva come modello"
|
"Save as template": "Salva come modello",
|
||||||
|
"Fix & resend": "Correggi e reinvia"
|
||||||
},
|
},
|
||||||
"report-heading": {
|
"report-heading": {
|
||||||
"Sr.No": "Nr.",
|
"Sr.No": "Nr.",
|
||||||
@@ -881,5 +882,10 @@
|
|||||||
"contact-billing-at-opensign": " Per aggiungere altri posti, contattare OpenSign™ all'indirizzo <1>billing@opensignlabs.com</1> per assistenza.",
|
"contact-billing-at-opensign": " Per aggiungere altri posti, contattare OpenSign™ all'indirizzo <1>billing@opensignlabs.com</1> per assistenza.",
|
||||||
"title-length-alert": "Il titolo può contenere al massimo 250 caratteri.",
|
"title-length-alert": "Il titolo può contenere al massimo 250 caratteri.",
|
||||||
"note-length-alert": "La nota può contenere al massimo 200 caratteri.",
|
"note-length-alert": "La nota può contenere al massimo 200 caratteri.",
|
||||||
"description-length-alert": " La descrizione può contenere al massimo 500 caratteri."
|
"description-length-alert": " La descrizione può contenere al massimo 500 caratteri.",
|
||||||
|
"fix-&-resend-document": "Correggi e reinvia il documento",
|
||||||
|
"do-you-want-recreate-document?": "Questo creerà una bozza da questo documento con tutti i campi intatti. Sei sicuro di voler ricreare questo documento?",
|
||||||
|
"start-editing": "Inizia a modificare",
|
||||||
|
"unsaved-changes-discard-them?": "Hai modifiche non salvate. Vuoi scartarle?",
|
||||||
|
"yes-discard": "Sì, scarta"
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 41 KiB |
@@ -24,7 +24,7 @@ const AddSigner = (props) => {
|
|||||||
if (savedUserDetails && addYourself) {
|
if (savedUserDetails && addYourself) {
|
||||||
setName(savedUserDetails.name);
|
setName(savedUserDetails.name);
|
||||||
setPhone(savedUserDetails?.phone || "");
|
setPhone(savedUserDetails?.phone || "");
|
||||||
setEmail(savedUserDetails.email);
|
setEmail(savedUserDetails.email?.toLowerCase()?.replace(/\s/g, ""));
|
||||||
}
|
}
|
||||||
}, [addYourself]);
|
}, [addYourself]);
|
||||||
|
|
||||||
|
|||||||
@@ -75,156 +75,46 @@ const AddUser = (props) => {
|
|||||||
setIsFormLoader(false);
|
setIsFormLoader(false);
|
||||||
} else {
|
} else {
|
||||||
if (localStorage.getItem("TenantId")) {
|
if (localStorage.getItem("TenantId")) {
|
||||||
|
const timezone = usertimezone;
|
||||||
try {
|
try {
|
||||||
const extUser = new Parse.Object("contracts_Users");
|
const params = {
|
||||||
extUser.set("Name", formdata.name);
|
name: formdata.name,
|
||||||
if (formdata.phone) {
|
email: formdata.email,
|
||||||
extUser.set("Phone", formdata.phone);
|
phone: formdata.phone,
|
||||||
}
|
password: formdata.password,
|
||||||
extUser.set("Email", formdata.email);
|
role: formdata.role,
|
||||||
extUser.set("UserRole", `contracts_${formdata.role}`);
|
team: formdata.team,
|
||||||
if (formdata?.team) {
|
timezone: timezone,
|
||||||
extUser.set("TeamIds", [
|
tenantId: localStorage.getItem("TenantId"),
|
||||||
{
|
organization: {
|
||||||
__type: "Pointer",
|
objectId: localUser?.OrganizationId?.objectId,
|
||||||
className: "contracts_Teams",
|
company: localUser?.Company
|
||||||
objectId: formdata.team
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
if (localUser && localUser.OrganizationId) {
|
|
||||||
extUser.set("OrganizationId", {
|
|
||||||
__type: "Pointer",
|
|
||||||
className: "contracts_Organizations",
|
|
||||||
objectId: localUser.OrganizationId.objectId
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (localUser && localUser.Company) {
|
|
||||||
extUser.set("Company", localUser.Company);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (localStorage.getItem("TenantId")) {
|
|
||||||
extUser.set("TenantId", {
|
|
||||||
__type: "Pointer",
|
|
||||||
className: "partners_Tenant",
|
|
||||||
objectId: localStorage.getItem("TenantId")
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const timezone = usertimezone;
|
|
||||||
if (timezone) {
|
|
||||||
extUser.set("Timezone", timezone);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const _users = Parse.Object.extend("User");
|
|
||||||
const _user = new _users();
|
|
||||||
_user.set("name", formdata.name);
|
|
||||||
_user.set("username", formdata.email);
|
|
||||||
_user.set("email", formdata.email);
|
|
||||||
_user.set("password", formdata.password);
|
|
||||||
if (formdata.phone) {
|
|
||||||
_user.set("phone", formdata.phone);
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
const user = await _user.save();
|
const res = await Parse.Cloud.run("adduser", params);
|
||||||
if (user) {
|
const parseData = JSON.parse(JSON.stringify(res));
|
||||||
const currentUser = Parse.User.current();
|
console.log("parseData ", parseData);
|
||||||
extUser.set(
|
if (props.closePopup) {
|
||||||
"CreatedBy",
|
props.closePopup();
|
||||||
Parse.User.createWithoutData(currentUser.id)
|
}
|
||||||
|
if (props.handleUserData) {
|
||||||
|
if (formdata?.team) {
|
||||||
|
const team = teamList.find((x) => x.objectId === formdata.team);
|
||||||
|
parseData.TeamIds = parseData.TeamIds.map((y) =>
|
||||||
|
y.objectId === team.objectId ? team : y
|
||||||
);
|
);
|
||||||
|
|
||||||
extUser.set("UserId", user);
|
|
||||||
const acl = new Parse.ACL();
|
|
||||||
acl.setPublicReadAccess(true);
|
|
||||||
acl.setPublicWriteAccess(true);
|
|
||||||
acl.setReadAccess(currentUser.id, true);
|
|
||||||
acl.setWriteAccess(currentUser.id, true);
|
|
||||||
|
|
||||||
extUser.setACL(acl);
|
|
||||||
|
|
||||||
const res = await extUser.save();
|
|
||||||
|
|
||||||
const parseData = JSON.parse(JSON.stringify(res));
|
|
||||||
|
|
||||||
if (props.closePopup) {
|
|
||||||
props.closePopup();
|
|
||||||
}
|
|
||||||
if (props.handleUserData) {
|
|
||||||
if (formdata?.team) {
|
|
||||||
const team = teamList.find(
|
|
||||||
(x) => x.objectId === formdata.team
|
|
||||||
);
|
|
||||||
parseData.TeamIds = parseData.TeamIds.map((y) =>
|
|
||||||
y.objectId === team.objectId ? team : y
|
|
||||||
);
|
|
||||||
}
|
|
||||||
props.handleUserData(parseData);
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsFormLoader(false);
|
|
||||||
setFormdata({
|
|
||||||
name: "",
|
|
||||||
email: "",
|
|
||||||
phone: "",
|
|
||||||
team: "",
|
|
||||||
role: ""
|
|
||||||
});
|
|
||||||
props.showAlert("success", t("user-created-successfully"));
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.log("err ", err);
|
|
||||||
if (err.code === 202) {
|
|
||||||
const params = { email: formdata.email };
|
|
||||||
const userRes = await Parse.Cloud.run("getUserId", params);
|
|
||||||
const currentUser = Parse.User.current();
|
|
||||||
extUser.set(
|
|
||||||
"CreatedBy",
|
|
||||||
Parse.User.createWithoutData(currentUser.id)
|
|
||||||
);
|
|
||||||
|
|
||||||
extUser.set("UserId", {
|
|
||||||
__type: "Pointer",
|
|
||||||
className: "_User",
|
|
||||||
objectId: userRes.id
|
|
||||||
});
|
|
||||||
const acl = new Parse.ACL();
|
|
||||||
acl.setPublicReadAccess(true);
|
|
||||||
acl.setPublicWriteAccess(true);
|
|
||||||
acl.setReadAccess(currentUser.id, true);
|
|
||||||
acl.setWriteAccess(currentUser.id, true);
|
|
||||||
|
|
||||||
extUser.setACL(acl);
|
|
||||||
const res = await extUser.save();
|
|
||||||
|
|
||||||
const parseData = JSON.parse(JSON.stringify(res));
|
|
||||||
if (props.closePopup) {
|
|
||||||
props.closePopup();
|
|
||||||
}
|
|
||||||
if (props.handleUserData) {
|
|
||||||
if (formdata?.team) {
|
|
||||||
const team = teamList.find(
|
|
||||||
(x) => x.objectId === formdata.team
|
|
||||||
);
|
|
||||||
parseData.TeamIds = parseData.TeamIds.map((y) =>
|
|
||||||
y.objectId === team.objectId ? team : y
|
|
||||||
);
|
|
||||||
}
|
|
||||||
props.handleUserData(parseData);
|
|
||||||
}
|
|
||||||
setIsFormLoader(false);
|
|
||||||
setFormdata({
|
|
||||||
name: "",
|
|
||||||
email: "",
|
|
||||||
phone: "",
|
|
||||||
team: "",
|
|
||||||
role: ""
|
|
||||||
});
|
|
||||||
props.showAlert("success", t("user-created-successfully"));
|
|
||||||
} else {
|
|
||||||
setIsFormLoader(false);
|
|
||||||
props.showAlert("danger", t("something-went-wrong-mssg"));
|
|
||||||
}
|
}
|
||||||
|
props.handleUserData(parseData);
|
||||||
}
|
}
|
||||||
|
setIsFormLoader(false);
|
||||||
|
setFormdata({
|
||||||
|
name: "",
|
||||||
|
email: "",
|
||||||
|
phone: "",
|
||||||
|
team: "",
|
||||||
|
role: ""
|
||||||
|
});
|
||||||
|
props.showAlert("success", t("user-created-successfully"));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log("err", err);
|
console.log("err", err);
|
||||||
setIsFormLoader(false);
|
setIsFormLoader(false);
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState, useRef } from "react";
|
||||||
import { getFileName } from "../../constant/Utils";
|
import {
|
||||||
|
base64ToArrayBuffer,
|
||||||
|
convertBase64ToFile,
|
||||||
|
generatePdfName,
|
||||||
|
getFileName
|
||||||
|
} from "../../constant/Utils";
|
||||||
import {
|
import {
|
||||||
maxDescriptionLength,
|
maxDescriptionLength,
|
||||||
maxNoteLength,
|
maxNoteLength,
|
||||||
@@ -8,10 +13,22 @@ import {
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Tooltip } from "react-tooltip";
|
import { Tooltip } from "react-tooltip";
|
||||||
import SignersInput from "../shared/fields/SignersInput";
|
import SignersInput from "../shared/fields/SignersInput";
|
||||||
|
import { PDFDocument } from "pdf-lib";
|
||||||
|
import ModalUi from "../../primitives/ModalUi";
|
||||||
|
import { SaveFileSize } from "../../constant/saveFileSize";
|
||||||
|
|
||||||
const EditTemplate = ({ template, onSuccess }) => {
|
const EditTemplate = ({
|
||||||
|
title,
|
||||||
|
handleClose,
|
||||||
|
pdfbase64,
|
||||||
|
template,
|
||||||
|
onSuccess,
|
||||||
|
setPdfArrayBuffer,
|
||||||
|
setPdfBase64Url
|
||||||
|
}) => {
|
||||||
const appName = "OpenSign™";
|
const appName = "OpenSign™";
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const inputFileRef = useRef(null);
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
Name: template?.Name || "",
|
Name: template?.Name || "",
|
||||||
Note: template?.Note || "",
|
Note: template?.Note || "",
|
||||||
@@ -32,6 +49,31 @@ const EditTemplate = ({ template, onSuccess }) => {
|
|||||||
AllowModifications: template?.AllowModifications || false,
|
AllowModifications: template?.AllowModifications || false,
|
||||||
TimeToCompleteDays: template?.TimeToCompleteDays || 15
|
TimeToCompleteDays: template?.TimeToCompleteDays || 15
|
||||||
});
|
});
|
||||||
|
const [isUpdate, setIsUpdate] = useState(false);
|
||||||
|
const [showConfirm, setShowConfirm] = useState(false);
|
||||||
|
const [uploadPdf, setUploadPdf] = useState({
|
||||||
|
name: "",
|
||||||
|
base64: "",
|
||||||
|
url: ""
|
||||||
|
});
|
||||||
|
const handleDrop = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const file = e.dataTransfer.files[0];
|
||||||
|
handleFile(file);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFile = (file) => {
|
||||||
|
if (file && file.type === "application/pdf") {
|
||||||
|
handleReplaceFileValdition(file);
|
||||||
|
// You can handle the file here
|
||||||
|
} else {
|
||||||
|
alert("Only pdf files are allowed.");
|
||||||
|
if (inputFileRef.current) inputFileRef.current.value = "";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const handleDragOver = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
};
|
||||||
|
|
||||||
// `isValidURL` is used to check valid webhook url
|
// `isValidURL` is used to check valid webhook url
|
||||||
function isValidURL(value) {
|
function isValidURL(value) {
|
||||||
@@ -44,9 +86,63 @@ const EditTemplate = ({ template, onSuccess }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleStrInput = (e) => {
|
const handleStrInput = (e) => {
|
||||||
|
setIsUpdate(true);
|
||||||
setFormData({ ...formData, [e.target.name]: e.target.value });
|
setFormData({ ...formData, [e.target.name]: e.target.value });
|
||||||
};
|
};
|
||||||
|
const getPdfMetadataHash = async (pdfBytes) => {
|
||||||
|
const pdfDoc = await PDFDocument.load(pdfBytes);
|
||||||
|
const pages = pdfDoc.getPages();
|
||||||
|
const metaString = pages
|
||||||
|
.map((page, index) => {
|
||||||
|
const { width, height } = page.getSize();
|
||||||
|
return `${index + 1}:${Math.round(width)}x${Math.round(height)}`;
|
||||||
|
})
|
||||||
|
.join("|");
|
||||||
|
const encoder = new TextEncoder();
|
||||||
|
const data = encoder.encode(metaString);
|
||||||
|
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
||||||
|
return Array.from(new Uint8Array(hashBuffer))
|
||||||
|
.map((b) => b.toString(16).padStart(2, "0"))
|
||||||
|
.join("");
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFileInput = async (e) => {
|
||||||
|
const file = e.target.files[0];
|
||||||
|
if (!file) return;
|
||||||
|
handleReplaceFileValdition(file);
|
||||||
|
};
|
||||||
|
const handleReplaceFileValdition = async (file) => {
|
||||||
|
try {
|
||||||
|
const basePdfBytes = base64ToArrayBuffer(pdfbase64);
|
||||||
|
const expectedHash = await getPdfMetadataHash(basePdfBytes);
|
||||||
|
const fileReader = new FileReader();
|
||||||
|
fileReader.onload = async (event) => {
|
||||||
|
const uploadedPdfBytes = event.target.result;
|
||||||
|
const uploadedHash = await getPdfMetadataHash(uploadedPdfBytes);
|
||||||
|
|
||||||
|
if (expectedHash === uploadedHash) {
|
||||||
|
const arrayBuffer = uploadedPdfBytes;
|
||||||
|
const uint8Array = new Uint8Array(arrayBuffer);
|
||||||
|
const binaryString = Array.from(uint8Array)
|
||||||
|
.map((b) => String.fromCharCode(b))
|
||||||
|
.join("");
|
||||||
|
const base64 = btoa(binaryString);
|
||||||
|
const pdfName = generatePdfName(16);
|
||||||
|
setIsUpdate(true);
|
||||||
|
setUploadPdf((prev) => ({ ...prev, name: pdfName, base64: base64 }));
|
||||||
|
// alert("✅ PDFs match (based on page number, width, height)");
|
||||||
|
} else {
|
||||||
|
alert("❌ PDF do NOT match based on page number, width, height");
|
||||||
|
if (inputFileRef.current) inputFileRef.current.value = "";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fileReader.readAsArrayBuffer(file);
|
||||||
|
} catch (err) {
|
||||||
|
alert("Error: " + err.message);
|
||||||
|
if (inputFileRef.current) inputFileRef.current.value = "";
|
||||||
|
}
|
||||||
|
};
|
||||||
// Define a function to handle form submission
|
// Define a function to handle form submission
|
||||||
const handleSubmit = async (e) => {
|
const handleSubmit = async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -67,6 +163,19 @@ const EditTemplate = ({ template, onSuccess }) => {
|
|||||||
alert(t("description-length-alert"));
|
alert(t("description-length-alert"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
let pdfUrl;
|
||||||
|
if (uploadPdf?.base64) {
|
||||||
|
pdfUrl = await convertBase64ToFile(uploadPdf.name, uploadPdf.base64);
|
||||||
|
setUploadPdf((prev) => ({ ...prev, url: pdfUrl }));
|
||||||
|
const pdfBuffer = base64ToArrayBuffer(uploadPdf.base64);
|
||||||
|
setPdfArrayBuffer && setPdfArrayBuffer(pdfBuffer);
|
||||||
|
setPdfBase64Url && setPdfBase64Url(uploadPdf.base64);
|
||||||
|
const tenantId =
|
||||||
|
localStorage.getItem("TenantId") ||
|
||||||
|
template?.ExtUserPtr?.TenantId?.objectId;
|
||||||
|
const buffer = atob(uploadPdf.base64);
|
||||||
|
SaveFileSize(buffer.length, pdfUrl, tenantId);
|
||||||
|
}
|
||||||
const isChecked = formData.SendinOrder === "true" ? true : false;
|
const isChecked = formData.SendinOrder === "true" ? true : false;
|
||||||
const isTourEnabled = formData?.IsTourEnabled === "false" ? false : true;
|
const isTourEnabled = formData?.IsTourEnabled === "false" ? false : true;
|
||||||
const AutoReminder = formData?.AutomaticReminders || false;
|
const AutoReminder = formData?.AutomaticReminders || false;
|
||||||
@@ -88,6 +197,7 @@ const EditTemplate = ({ template, onSuccess }) => {
|
|||||||
}
|
}
|
||||||
const data = {
|
const data = {
|
||||||
...formData,
|
...formData,
|
||||||
|
...(pdfUrl ? { URL: pdfUrl } : {}),
|
||||||
SendinOrder: isChecked,
|
SendinOrder: isChecked,
|
||||||
IsEnableOTP: IsEnableOTP,
|
IsEnableOTP: IsEnableOTP,
|
||||||
IsTourEnabled: isTourEnabled,
|
IsTourEnabled: isTourEnabled,
|
||||||
@@ -99,6 +209,7 @@ const EditTemplate = ({ template, onSuccess }) => {
|
|||||||
|
|
||||||
// `handleNotifySignChange` is trigger when user change radio of notify on signatures
|
// `handleNotifySignChange` is trigger when user change radio of notify on signatures
|
||||||
const handleNotifySignChange = (value) => {
|
const handleNotifySignChange = (value) => {
|
||||||
|
setIsUpdate(true);
|
||||||
setFormData((obj) => ({ ...obj, NotifyOnSignatures: value }));
|
setFormData((obj) => ({ ...obj, NotifyOnSignatures: value }));
|
||||||
};
|
};
|
||||||
const handleBcc = (data) => {
|
const handleBcc = (data) => {
|
||||||
@@ -108,226 +219,304 @@ const EditTemplate = ({ template, onSuccess }) => {
|
|||||||
Name: item?.label,
|
Name: item?.label,
|
||||||
Email: item?.email
|
Email: item?.email
|
||||||
}));
|
}));
|
||||||
|
setIsUpdate(true);
|
||||||
setFormData((prev) => ({ ...prev, Bcc: trimEmail }));
|
setFormData((prev) => ({ ...prev, Bcc: trimEmail }));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleEditTemplateClose = () => {
|
||||||
|
if (isUpdate) {
|
||||||
|
setShowConfirm(true);
|
||||||
|
} else {
|
||||||
|
handleClose();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const discardChanges = () => {
|
||||||
|
setShowConfirm(false);
|
||||||
|
handleClose();
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<div className="max-h-[300px] md:max-h-[400px] overflow-y-scroll p-[10px]">
|
<ModalUi
|
||||||
<div className="text-base-content">
|
isOpen
|
||||||
<form onSubmit={handleSubmit}>
|
title={isUpdate ? `${title} (unsaved)` : title}
|
||||||
<div className="mb-[0.35rem]">
|
handleClose={handleEditTemplateClose}
|
||||||
<label htmlFor="name" className="text-[13px]">
|
>
|
||||||
{t("report-heading.File")}
|
<ModalUi isOpen={showConfirm} showClose={false}>
|
||||||
</label>
|
<div className="p-[20px]">
|
||||||
<div className="op-input op-input-bordered op-input-sm focus:outline-none py-2 font-semibold w-full text-xs">
|
<p className="text-base font-normal text-base-content py-[5px] md:py-[6px] px-[5px]">
|
||||||
{getFileName(template.URL)}
|
{t("unsaved-changes-discard-them?")}
|
||||||
</div>
|
</p>
|
||||||
|
<div className="flex items-center mt-2.5 gap-2 md:gap-3 text-white">
|
||||||
|
<button
|
||||||
|
className="op-btn op-btn-primary px-6"
|
||||||
|
onClick={discardChanges}
|
||||||
|
>
|
||||||
|
{t("yes-discard")}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="op-btn op-btn-secondary px-4 md:px-6"
|
||||||
|
onClick={() => setShowConfirm(false)}
|
||||||
|
>
|
||||||
|
{t("cancel")}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-[0.35rem]">
|
</div>
|
||||||
<label htmlFor="name" className="text-[13px]">
|
</ModalUi>
|
||||||
{t("Title")}
|
<div className="max-h-[300px] md:max-h-[400px] overflow-y-scroll p-[10px]">
|
||||||
<span className="text-[13px] text-[red]"> *</span>
|
<div className="text-base-content">
|
||||||
</label>
|
<form onSubmit={handleSubmit}>
|
||||||
<input
|
<div className="mb-[0.35rem]">
|
||||||
type="text"
|
<label htmlFor="name" className="text-[13px]">
|
||||||
name="Name"
|
{t("report-heading.File")}
|
||||||
value={formData.Name}
|
</label>
|
||||||
onChange={(e) => handleStrInput(e)}
|
<div
|
||||||
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
|
className="border-[1.5px] border-dashed border-gray-300 rounded-lg px-4 py-6 text-center text-gray-500 bg-white cursor-pointer hover:border-base-content transition"
|
||||||
onInput={(e) => e.target.setCustomValidity("")}
|
onDrop={handleDrop}
|
||||||
required
|
onDragOver={handleDragOver}
|
||||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
onClick={() => inputFileRef?.current?.click()}
|
||||||
/>
|
>
|
||||||
</div>
|
<label
|
||||||
<div className="mb-[0.35rem]">
|
htmlFor="fileUpload"
|
||||||
<label htmlFor="Note" className="text-[13px]">
|
className="cursor-pointer text-center mb-0"
|
||||||
{t("report-heading.Note")}
|
>
|
||||||
</label>
|
Browse or drag & drop a new file to replace the existng one
|
||||||
<input
|
</label>
|
||||||
type="text"
|
|
||||||
name="Note"
|
|
||||||
id="Note"
|
|
||||||
value={formData.Note}
|
|
||||||
onChange={(e) => handleStrInput(e)}
|
|
||||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="mb-[0.35rem]">
|
|
||||||
<label htmlFor="Description" className="text-[13px]">
|
|
||||||
{t("description")}
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
name="Description"
|
|
||||||
id="Description"
|
|
||||||
value={formData.Description}
|
|
||||||
onChange={(e) => handleStrInput(e)}
|
|
||||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="mb-[0.35rem]">
|
|
||||||
<label className="text-[13px]">{t("send-in-order")}</label>
|
|
||||||
<div className="flex flex-col md:flex-row md:gap-4">
|
|
||||||
<div className="flex items-center gap-[8px] ml-[8px] mb-[5px]">
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
value={"true"}
|
|
||||||
className="op-radio op-radio-xs"
|
|
||||||
name="SendinOrder"
|
|
||||||
checked={formData.SendinOrder === "true"}
|
|
||||||
onChange={handleStrInput}
|
|
||||||
/>
|
|
||||||
<div className="text-[12px]">{t("yes")}</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-[8px] ml-[8px] mb-[5px]">
|
<input
|
||||||
<input
|
ref={inputFileRef}
|
||||||
type="radio"
|
type="file"
|
||||||
value={"false"}
|
className="hidden"
|
||||||
name="SendinOrder"
|
accept="application/pdf"
|
||||||
className="op-radio op-radio-xs"
|
onChange={(e) => handleFileInput(e)}
|
||||||
checked={formData.SendinOrder === "false"}
|
onInvalid={(e) =>
|
||||||
onChange={handleStrInput}
|
e.target.setCustomValidity(t("input-required"))
|
||||||
/>
|
}
|
||||||
<div className="text-[12px]">{t("no")}</div>
|
onInput={(e) => e.target.setCustomValidity("")}
|
||||||
|
/>
|
||||||
|
{uploadPdf?.name && (
|
||||||
|
<div
|
||||||
|
onClick={() => inputFileRef?.current?.click()}
|
||||||
|
className="mt-2 cursor-pointer op-input op-input-bordered op-input-sm focus:outline-none py-2 font-semibold w-full text-xs"
|
||||||
|
>
|
||||||
|
selected:{" "}
|
||||||
|
{uploadPdf?.url
|
||||||
|
? `${uploadPdf?.name}.pdf`
|
||||||
|
: getFileName(template.URL)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="mb-[0.35rem]">
|
||||||
|
<label htmlFor="name" className="text-[13px]">
|
||||||
|
{t("Title")}
|
||||||
|
<span className="text-[13px] text-[red]"> *</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="Name"
|
||||||
|
value={formData.Name}
|
||||||
|
onChange={(e) => handleStrInput(e)}
|
||||||
|
onInvalid={(e) =>
|
||||||
|
e.target.setCustomValidity(t("input-required"))
|
||||||
|
}
|
||||||
|
onInput={(e) => e.target.setCustomValidity("")}
|
||||||
|
required
|
||||||
|
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mb-[0.35rem]">
|
||||||
|
<label htmlFor="Note" className="text-[13px]">
|
||||||
|
{t("report-heading.Note")}
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="Note"
|
||||||
|
id="Note"
|
||||||
|
value={formData.Note}
|
||||||
|
onChange={(e) => handleStrInput(e)}
|
||||||
|
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mb-[0.35rem]">
|
||||||
|
<label htmlFor="Description" className="text-[13px]">
|
||||||
|
{t("description")}
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="Description"
|
||||||
|
id="Description"
|
||||||
|
value={formData.Description}
|
||||||
|
onChange={(e) => handleStrInput(e)}
|
||||||
|
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mb-[0.35rem]">
|
||||||
|
<label className="text-[13px]">{t("send-in-order")}</label>
|
||||||
|
<div className="flex flex-col md:flex-row md:gap-4">
|
||||||
|
<div className="flex items-center gap-[8px] ml-[8px] mb-[5px]">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
value={"true"}
|
||||||
|
className="op-radio op-radio-xs"
|
||||||
|
name="SendinOrder"
|
||||||
|
checked={formData.SendinOrder === "true"}
|
||||||
|
onChange={handleStrInput}
|
||||||
|
/>
|
||||||
|
<div className="text-[12px]">{t("yes")}</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-[8px] ml-[8px] mb-[5px]">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
value={"false"}
|
||||||
|
name="SendinOrder"
|
||||||
|
className="op-radio op-radio-xs"
|
||||||
|
checked={formData.SendinOrder === "false"}
|
||||||
|
onChange={handleStrInput}
|
||||||
|
/>
|
||||||
|
<div className="text-[12px]">{t("no")}</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="text-xs mt-3">
|
||||||
<div className="text-xs mt-3">
|
<label className="block">
|
||||||
<label className="block">
|
<span>
|
||||||
<span>
|
{t("enable-tour")}
|
||||||
{t("enable-tour")}
|
<a data-tooltip-id="istourenabled-tooltip" className="ml-1">
|
||||||
<a data-tooltip-id="istourenabled-tooltip" className="ml-1">
|
<sup>
|
||||||
|
<i className="fa-light fa-question rounded-full border-[#33bbff] text-[#33bbff] text-[13px] border-[1px] py-[1.5px] px-[4px]"></i>
|
||||||
|
</sup>
|
||||||
|
</a>{" "}
|
||||||
|
</span>
|
||||||
|
<Tooltip id="istourenabled-tooltip" className="z-50">
|
||||||
|
<div className="max-w-[200px] md:max-w-[450px]">
|
||||||
|
<p className="font-bold">{t("enable-tour")}</p>
|
||||||
|
<div className="p-[5px]">
|
||||||
|
<ol className="list-disc">
|
||||||
|
<li>
|
||||||
|
<span className="font-bold">{t("yes")}: </span>
|
||||||
|
<span>{t("istourenabled-help.p1")}</span>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span className="font-bold">{t("no")}: </span>
|
||||||
|
<span>{t("istourenabled-help.p2")}</span>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
<p>{t("istourenabled-help.p3", { appName: appName })}</p>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
</label>
|
||||||
|
<div className="flex flex-col md:flex-row md:gap-4">
|
||||||
|
<div className="flex items-center gap-2 ml-2 mb-1">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
value={"true"}
|
||||||
|
className="op-radio op-radio-xs"
|
||||||
|
name="IsTourEnabled"
|
||||||
|
checked={formData.IsTourEnabled === "true"}
|
||||||
|
onChange={handleStrInput}
|
||||||
|
/>
|
||||||
|
<div className="text-center">{t("yes")}</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2 ml-2 mb-1">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
value={"false"}
|
||||||
|
name="IsTourEnabled"
|
||||||
|
className="op-radio op-radio-xs"
|
||||||
|
checked={formData.IsTourEnabled === "false"}
|
||||||
|
onChange={handleStrInput}
|
||||||
|
/>
|
||||||
|
<div className="text-center">{t("no")}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="text-xs mt-3">
|
||||||
|
<label>
|
||||||
|
{t("notify-on-signatures")}
|
||||||
|
<a data-tooltip-id="nos-tooltip" className="ml-1">
|
||||||
<sup>
|
<sup>
|
||||||
<i className="fa-light fa-question rounded-full border-[#33bbff] text-[#33bbff] text-[13px] border-[1px] py-[1.5px] px-[4px]"></i>
|
<i className="fa-light fa-question rounded-full border-[#33bbff] text-[#33bbff] text-[13px] border-[1px] py-[1.5px] px-[4px]"></i>
|
||||||
</sup>
|
</sup>
|
||||||
</a>{" "}
|
</a>{" "}
|
||||||
</span>
|
<Tooltip id="nos-tooltip" className="z-[999]">
|
||||||
<Tooltip id="istourenabled-tooltip" className="z-50">
|
<div className="max-w-[200px] md:max-w-[450px] text-[11px]">
|
||||||
<div className="max-w-[200px] md:max-w-[450px]">
|
<p className="font-bold">{t("notify-on-signatures")}</p>
|
||||||
<p className="font-bold">{t("enable-tour")}</p>
|
<p>{t("notify-on-signatures-help.p1")}</p>
|
||||||
<div className="p-[5px]">
|
<p>{t("notify-on-signatures-help.note")}</p>
|
||||||
<ol className="list-disc">
|
|
||||||
<li>
|
|
||||||
<span className="font-bold">{t("yes")}: </span>
|
|
||||||
<span>{t("istourenabled-help.p1")}</span>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<span className="font-bold">{t("no")}: </span>
|
|
||||||
<span>{t("istourenabled-help.p2")}</span>
|
|
||||||
</li>
|
|
||||||
</ol>
|
|
||||||
</div>
|
</div>
|
||||||
<p>{t("istourenabled-help.p3", { appName: appName })}</p>
|
</Tooltip>
|
||||||
|
</label>
|
||||||
|
<div className="flex flex-col md:flex-row md:gap-4">
|
||||||
|
<div className={`flex items-center gap-2 ml-2 mb-1`}>
|
||||||
|
<input
|
||||||
|
className="mr-[2px] op-radio op-radio-xs"
|
||||||
|
type="radio"
|
||||||
|
onChange={() => handleNotifySignChange(true)}
|
||||||
|
checked={formData.NotifyOnSignatures === true}
|
||||||
|
/>
|
||||||
|
<div className="text-center">{t("yes")}</div>
|
||||||
|
</div>
|
||||||
|
<div className={`flex items-center gap-2 ml-2 mb-1`}>
|
||||||
|
<input
|
||||||
|
className="mr-[2px] op-radio op-radio-xs"
|
||||||
|
type="radio"
|
||||||
|
onChange={() => handleNotifySignChange(false)}
|
||||||
|
checked={formData.NotifyOnSignatures === false}
|
||||||
|
/>
|
||||||
|
<div className="text-center">{t("no")}</div>
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
|
||||||
</label>
|
|
||||||
<div className="flex flex-col md:flex-row md:gap-4">
|
|
||||||
<div className="flex items-center gap-2 ml-2 mb-1">
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
value={"true"}
|
|
||||||
className="op-radio op-radio-xs"
|
|
||||||
name="IsTourEnabled"
|
|
||||||
checked={formData.IsTourEnabled === "true"}
|
|
||||||
onChange={handleStrInput}
|
|
||||||
/>
|
|
||||||
<div className="text-center">{t("yes")}</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2 ml-2 mb-1">
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
value={"false"}
|
|
||||||
name="IsTourEnabled"
|
|
||||||
className="op-radio op-radio-xs"
|
|
||||||
checked={formData.IsTourEnabled === "false"}
|
|
||||||
onChange={handleStrInput}
|
|
||||||
/>
|
|
||||||
<div className="text-center">{t("no")}</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="text-xs mt-3">
|
||||||
<div className="text-xs mt-3">
|
<SignersInput
|
||||||
<label>
|
label={t("Bcc")}
|
||||||
{t("notify-on-signatures")}
|
initialData={template?.Bcc}
|
||||||
<a data-tooltip-id="nos-tooltip" className="ml-1">
|
onChange={handleBcc}
|
||||||
<sup>
|
helptextZindex={50}
|
||||||
<i className="fa-light fa-question rounded-full border-[#33bbff] text-[#33bbff] text-[13px] border-[1px] py-[1.5px] px-[4px]"></i>
|
helpText={t("bcc-help")}
|
||||||
</sup>
|
isCaptureAllData
|
||||||
</a>{" "}
|
/>
|
||||||
<Tooltip id="nos-tooltip" className="z-[999]">
|
|
||||||
<div className="max-w-[200px] md:max-w-[450px] text-[11px]">
|
|
||||||
<p className="font-bold">{t("notify-on-signatures")}</p>
|
|
||||||
<p>{t("notify-on-signatures-help.p1")}</p>
|
|
||||||
<p>{t("notify-on-signatures-help.note")}</p>
|
|
||||||
</div>
|
|
||||||
</Tooltip>
|
|
||||||
</label>
|
|
||||||
<div className="flex flex-col md:flex-row md:gap-4">
|
|
||||||
<div className={`flex items-center gap-2 ml-2 mb-1`}>
|
|
||||||
<input
|
|
||||||
className="mr-[2px] op-radio op-radio-xs"
|
|
||||||
type="radio"
|
|
||||||
onChange={() => handleNotifySignChange(true)}
|
|
||||||
checked={formData.NotifyOnSignatures === true}
|
|
||||||
/>
|
|
||||||
<div className="text-center">{t("yes")}</div>
|
|
||||||
</div>
|
|
||||||
<div className={`flex items-center gap-2 ml-2 mb-1`}>
|
|
||||||
<input
|
|
||||||
className="mr-[2px] op-radio op-radio-xs"
|
|
||||||
type="radio"
|
|
||||||
onChange={() => handleNotifySignChange(false)}
|
|
||||||
checked={formData.NotifyOnSignatures === false}
|
|
||||||
/>
|
|
||||||
<div className="text-center">{t("no")}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="text-xs mt-2">
|
||||||
<div className="text-xs mt-3">
|
<label className="block">Redirect Url</label>
|
||||||
<SignersInput
|
<input
|
||||||
label={t("Bcc")}
|
name="RedirectUrl"
|
||||||
initialData={template?.Bcc}
|
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||||
onChange={handleBcc}
|
value={formData.RedirectUrl}
|
||||||
helptextZindex={50}
|
onChange={handleStrInput}
|
||||||
helpText={t("bcc-help")}
|
onInvalid={(e) =>
|
||||||
isCaptureAllData
|
e.target.setCustomValidity(t("input-required"))
|
||||||
/>
|
}
|
||||||
</div>
|
onInput={(e) => e.target.setCustomValidity("")}
|
||||||
<div className="text-xs mt-2">
|
/>
|
||||||
<label className="block">Redirect Url</label>
|
</div>
|
||||||
<input
|
<div className="text-xs mt-2">
|
||||||
name="RedirectUrl"
|
<label className="block">
|
||||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
{t("time-to-complete")}
|
||||||
value={formData.RedirectUrl}
|
<span className="text-red-500 text-[13px]">*</span>
|
||||||
onChange={handleStrInput}
|
</label>
|
||||||
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
|
<input
|
||||||
onInput={(e) => e.target.setCustomValidity("")}
|
type="number"
|
||||||
/>
|
name="TimeToCompleteDays"
|
||||||
</div>
|
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||||
<div className="text-xs mt-2">
|
value={formData.TimeToCompleteDays}
|
||||||
<label className="block">
|
onChange={(e) => handleStrInput(e)}
|
||||||
{t("time-to-complete")}
|
onInvalid={(e) =>
|
||||||
<span className="text-red-500 text-[13px]">*</span>
|
e.target.setCustomValidity(t("input-required"))
|
||||||
</label>
|
}
|
||||||
<input
|
onInput={(e) => e.target.setCustomValidity("")}
|
||||||
type="number"
|
required
|
||||||
name="TimeToCompleteDays"
|
/>
|
||||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
</div>
|
||||||
value={formData.TimeToCompleteDays}
|
<div className="mt-[1rem] flex justify-start">
|
||||||
onChange={(e) => handleStrInput(e)}
|
<button type="submit" className="op-btn op-btn-primary">
|
||||||
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
|
{t("submit")}
|
||||||
onInput={(e) => e.target.setCustomValidity("")}
|
</button>
|
||||||
required
|
</div>
|
||||||
/>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-[1rem] flex justify-start">
|
|
||||||
<button type="submit" className="op-btn op-btn-primary">
|
|
||||||
{t("submit")}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</ModalUi>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ const DateFormatSelector = (props) => {
|
|||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
<div className="flex flex-col md:flex-row md:gap-4 mt-[0.75rem] text-[12px]">
|
<div className="flex flex-row gap-4 mt-[0.75rem] text-[12px]">
|
||||||
<div className="flex items-center gap-2 ml-2">
|
<div className="flex items-center gap-2 ml-2">
|
||||||
<input
|
<input
|
||||||
type="radio"
|
type="radio"
|
||||||
|
|||||||
@@ -219,7 +219,6 @@ export default function reportJson(id) {
|
|||||||
btnId: "44534",
|
btnId: "44534",
|
||||||
hoverLabel: "option",
|
hoverLabel: "option",
|
||||||
btnColor: "",
|
btnColor: "",
|
||||||
restrictBtn: true,
|
|
||||||
textColor: "black",
|
textColor: "black",
|
||||||
btnIcon: "fa-light fa-ellipsis-vertical fa-lg",
|
btnIcon: "fa-light fa-ellipsis-vertical fa-lg",
|
||||||
action: "option",
|
action: "option",
|
||||||
@@ -228,9 +227,18 @@ export default function reportJson(id) {
|
|||||||
btnId: "0940",
|
btnId: "0940",
|
||||||
btnLabel: "Save as template",
|
btnLabel: "Save as template",
|
||||||
hoverLabel: "Save as template",
|
hoverLabel: "Save as template",
|
||||||
|
restrictBtn: true,
|
||||||
btnIcon: "fa-light fa-envelope",
|
btnIcon: "fa-light fa-envelope",
|
||||||
redirectUrl: "",
|
redirectUrl: "",
|
||||||
action: "saveastemplate"
|
action: "saveastemplate"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
btnId: "8440",
|
||||||
|
btnLabel: "Fix & resend",
|
||||||
|
hoverLabel: "Fix & resend",
|
||||||
|
btnIcon: "fa-light fa-paper-plane",
|
||||||
|
redirectUrl: "",
|
||||||
|
action: "recreatedocument"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,8 +16,7 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { emailRegex } from "../constant/const";
|
import { emailRegex } from "../constant/const";
|
||||||
|
|
||||||
const AddAdmin = () => {
|
const AddAdmin = () => {
|
||||||
const appName =
|
const appName = "OpenSign™";
|
||||||
"OpenSign™";
|
|
||||||
const { t, i18n } = useTranslation();
|
const { t, i18n } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
@@ -96,7 +95,7 @@ const AddAdmin = () => {
|
|||||||
setState({ loading: true });
|
setState({ loading: true });
|
||||||
const userDetails = {
|
const userDetails = {
|
||||||
name: name,
|
name: name,
|
||||||
email: email,
|
email: email?.toLowerCase()?.replace(/\s/g, ""),
|
||||||
phone: phone,
|
phone: phone,
|
||||||
company: company,
|
company: company,
|
||||||
jobTitle: jobTitle
|
jobTitle: jobTitle
|
||||||
@@ -106,10 +105,10 @@ const AddAdmin = () => {
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const user = new Parse.User();
|
const user = new Parse.User();
|
||||||
user.set("name", name);
|
user.set("name", name);
|
||||||
user.set("email", email);
|
user.set("email", email?.toLowerCase()?.replace(/\s/g, ""));
|
||||||
user.set("password", password);
|
user.set("password", password);
|
||||||
user.set("phone", phone);
|
user.set("phone", phone);
|
||||||
user.set("username", email);
|
user.set("username", email?.toLowerCase()?.replace(/\s/g, ""));
|
||||||
const userRes = await user.save();
|
const userRes = await user.save();
|
||||||
if (userRes) {
|
if (userRes) {
|
||||||
const params = {
|
const params = {
|
||||||
@@ -117,7 +116,7 @@ const AddAdmin = () => {
|
|||||||
jobTitle: jobTitle,
|
jobTitle: jobTitle,
|
||||||
company: company,
|
company: company,
|
||||||
name: name,
|
name: name,
|
||||||
email: email,
|
email: email?.toLowerCase()?.replace(/\s/g, ""),
|
||||||
phone: phone,
|
phone: phone,
|
||||||
role: "contracts_Admin",
|
role: "contracts_Admin",
|
||||||
timezone: usertimezone
|
timezone: usertimezone
|
||||||
|
|||||||
@@ -344,7 +344,7 @@ const Preferences = () => {
|
|||||||
<Loader />
|
<Loader />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<h1 className="ml-4 mt-3 mb-2 font-semibold">
|
<h1 className="ml-4 mt-3 text-lg mb-2 font-semibold text-base-content">
|
||||||
{appName} {t("Preferences")}{" "}
|
{appName} {t("Preferences")}{" "}
|
||||||
<span>
|
<span>
|
||||||
<Tooltip message={`${appName} ${t("Preferences")}`} />
|
<Tooltip message={`${appName} ${t("Preferences")}`} />
|
||||||
@@ -362,7 +362,9 @@ const Preferences = () => {
|
|||||||
role="tab"
|
role="tab"
|
||||||
className={`${
|
className={`${
|
||||||
activeTab === ind ? "op-tab-active" : ""
|
activeTab === ind ? "op-tab-active" : ""
|
||||||
} op-tab text-xs md:text-base pb-2 md:pb-0 }`}
|
} op-tab text-xs md:text-base pb-2 md:pb-0 transition-all `}
|
||||||
|
aria-selected={activeTab === ind}
|
||||||
|
aria-controls={`panel-${tabData.title}`}
|
||||||
>
|
>
|
||||||
<i className={tabData.icon}></i>
|
<i className={tabData.icon}></i>
|
||||||
<span className="ml-1 md:ml-2">{tabData.title}</span>
|
<span className="ml-1 md:ml-2">{tabData.title}</span>
|
||||||
@@ -370,275 +372,330 @@ const Preferences = () => {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-center md:justify-start mt-3 md:mt-4 break-all">
|
<div
|
||||||
|
id="panel-general"
|
||||||
|
className="px-6 pt-4 pb-6"
|
||||||
|
aria-labelledby="tab-general"
|
||||||
|
role="tabpanel"
|
||||||
|
>
|
||||||
{activeTab === 0 ? (
|
{activeTab === 0 ? (
|
||||||
<div className="ml-4 mt-1 mb-2 flex flex-col">
|
<div className="grid grid-cols-1 md:grid-cols-12 md:gap-x-8">
|
||||||
<div className="mb-[0.75rem]">
|
{/* Left Column - Signature Settings */}
|
||||||
<label
|
<div className="md:col-span-5 flex flex-col">
|
||||||
className="mb-[0.7rem] text-[12px]"
|
{/* Signature Types Section */}
|
||||||
htmlFor="signaturetype"
|
<div className="mb-6">
|
||||||
>
|
<label
|
||||||
<span className="font-medium text-[14px]">
|
className="text-[14px] mb-[0.7rem] font-medium"
|
||||||
|
htmlFor="signaturetype"
|
||||||
|
>
|
||||||
{t("allowed-signature-types")}
|
{t("allowed-signature-types")}
|
||||||
</span>
|
<a
|
||||||
<a data-tooltip-id="signtypes-tooltip" className="ml-1">
|
data-tooltip-id="signtypes-tooltip"
|
||||||
<sup>
|
className="ml-1"
|
||||||
<i className="fa-light fa-question rounded-full border-[#33bbff] text-[#33bbff] text-[13px] border-[1px] py-[1.5px] px-[4px]"></i>
|
|
||||||
</sup>
|
|
||||||
</a>
|
|
||||||
<ReactTooltip
|
|
||||||
id="signtypes-tooltip"
|
|
||||||
className="z-[999]"
|
|
||||||
>
|
|
||||||
<div className="max-w-[200px] md:max-w-[450px]">
|
|
||||||
<p className="font-bold">
|
|
||||||
{t("allowed-signature-types")}
|
|
||||||
</p>
|
|
||||||
<p>{t("allowed-signature-types-help.p1")}</p>
|
|
||||||
<div className="p-[5px] ml-2">
|
|
||||||
<ol className="list-disc">
|
|
||||||
<li>
|
|
||||||
<span className="font-bold">Draw: </span>
|
|
||||||
<span>
|
|
||||||
{t("allowed-signature-types-help.l1")}
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<span className="font-bold">Type: </span>
|
|
||||||
<span>
|
|
||||||
{t("allowed-signature-types-help.l2")}
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<span className="font-bold">Upload: </span>
|
|
||||||
<span>
|
|
||||||
{t("allowed-signature-types-help.l3")}
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<span className="font-bold">Default: </span>
|
|
||||||
<span>
|
|
||||||
{t("allowed-signature-types-help.l4")}
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
</ol>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ReactTooltip>
|
|
||||||
</label>
|
|
||||||
<div className="ml-[7px] flex flex-col md:flex-row gap-[10px] mb-[0.7rem]">
|
|
||||||
{signatureType.map((type, i) => (
|
|
||||||
<div
|
|
||||||
key={i}
|
|
||||||
className="flex flex-row gap-[5px] items-center"
|
|
||||||
>
|
>
|
||||||
<input
|
<sup>
|
||||||
className="mr-[2px] op-checkbox op-checkbox-xs"
|
<i className="fa-light fa-question rounded-full border-[#33bbff] text-[#33bbff] text-[13px] border-[1px] py-[1.5px] px-[4px]"></i>
|
||||||
type="checkbox"
|
</sup>
|
||||||
name="signaturetype"
|
</a>
|
||||||
onChange={() => handleCheckboxChange(i)}
|
<ReactTooltip
|
||||||
checked={type.enabled}
|
id="signtypes-tooltip"
|
||||||
/>
|
className="z-[999]"
|
||||||
|
>
|
||||||
|
<div className="max-w-[200px] md:max-w-[450px]">
|
||||||
|
<p className="font-bold">
|
||||||
|
{t("allowed-signature-types")}
|
||||||
|
</p>
|
||||||
|
<p>{t("allowed-signature-types-help.p1")}</p>
|
||||||
|
<div className="p-[5px] ml-2">
|
||||||
|
<ol className="list-disc">
|
||||||
|
<li>
|
||||||
|
<span className="font-bold">Draw: </span>
|
||||||
|
<span>
|
||||||
|
{t("allowed-signature-types-help.l1")}
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span className="font-bold">Type: </span>
|
||||||
|
<span>
|
||||||
|
{t("allowed-signature-types-help.l2")}
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span className="font-bold">Upload: </span>
|
||||||
|
<span>
|
||||||
|
{t("allowed-signature-types-help.l3")}
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span className="font-bold">Default: </span>
|
||||||
|
<span>
|
||||||
|
{t("allowed-signature-types-help.l4")}
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ReactTooltip>
|
||||||
|
</label>
|
||||||
|
<div className="flex flex-col md:flex-row gap-3 mb-2">
|
||||||
|
{signatureType.map((type, i) => (
|
||||||
<div
|
<div
|
||||||
className="text-[13px] font-medium hover:underline underline-offset-2 cursor-default capitalize"
|
key={i}
|
||||||
title={`Enabling this allow signers to ${type.name} signature`}
|
className="flex flex-row gap-2 items-center"
|
||||||
>
|
>
|
||||||
{type?.name === "typed" ? "type" : type?.name}
|
<input
|
||||||
|
className="op-checkbox op-checkbox-xs"
|
||||||
|
type="checkbox"
|
||||||
|
id={`signature-type-${type.name}`}
|
||||||
|
name="signaturetype"
|
||||||
|
onChange={() => handleCheckboxChange(i)}
|
||||||
|
checked={type.enabled}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
htmlFor={`signature-type-${type.name}`}
|
||||||
|
className="text-sm font-medium text-base-content hover:underline underline-offset-2 cursor-pointer capitalize mb-0"
|
||||||
|
title={`Enabling this allows signers to ${type.name} signature`}
|
||||||
|
>
|
||||||
|
{type?.name === "typed" ? "type" : type?.name}
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
))}
|
||||||
))}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div className="mb-[0.75rem]">
|
{/* Notify on Signatures Section */}
|
||||||
<label className="mb-[0.7rem] text-[12px]">
|
<div className="mb-6">
|
||||||
<span className="text-[14px] font-medium">
|
<label className="text-[14px] mb-[0.7rem] font-medium">
|
||||||
{t("notify-on-signatures")}
|
{t("notify-on-signatures")}
|
||||||
</span>
|
<a data-tooltip-id="nos-tooltip" className="ml-1">
|
||||||
<a data-tooltip-id="nos-tooltip" className="ml-1">
|
<sup>
|
||||||
<sup>
|
<i className="fa-light fa-question rounded-full border-[#33bbff] text-[#33bbff] text-[13px] border-[1px] py-[1.5px] px-[4px]"></i>
|
||||||
<i className="fa-light fa-question rounded-full border-[#33bbff] text-[#33bbff] text-[13px] border-[1px] py-[1.5px] px-[4px]"></i>
|
</sup>
|
||||||
</sup>
|
</a>
|
||||||
</a>
|
<ReactTooltip id="nos-tooltip" className="z-[999]">
|
||||||
<ReactTooltip id="nos-tooltip" className="z-[999]">
|
<div className="max-w-[200px] md:max-w-[450px]">
|
||||||
<div className="max-w-[200px] md:max-w-[450px]">
|
<p className="font-bold">
|
||||||
<p className="font-bold">
|
{t("notify-on-signatures")}
|
||||||
{t("notify-on-signatures")}
|
</p>
|
||||||
</p>
|
<p>{t("notify-on-signatures-help.p1")}</p>
|
||||||
<p>{t("notify-on-signatures-help.p1")}</p>
|
<p>{t("notify-on-signatures-help.note")}</p>
|
||||||
<p>{t("notify-on-signatures-help.note")}</p>
|
</div>
|
||||||
|
</ReactTooltip>
|
||||||
|
</label>
|
||||||
|
<div className="flex flex-row gap-6">
|
||||||
|
<div className={"flex items-center gap-2"}>
|
||||||
|
<input
|
||||||
|
id="notify-yes"
|
||||||
|
className="op-radio op-radio-xs"
|
||||||
|
type="radio"
|
||||||
|
onChange={() => handleNotifySignChange(true)}
|
||||||
|
checked={isNotifyOnSignatures === true}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
htmlFor="notify-yes"
|
||||||
|
className="text-sm text-base-content cursor-pointer mb-0"
|
||||||
|
>
|
||||||
|
{t("yes")}
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</ReactTooltip>
|
<div className={"flex items-center gap-2"}>
|
||||||
</label>
|
<input
|
||||||
<div className="flex flex-col md:flex-row md:gap-4">
|
id="notify-no"
|
||||||
<div className={"flex items-center gap-2 ml-2 mb-1"}>
|
className="op-radio op-radio-xs"
|
||||||
<input
|
type="radio"
|
||||||
className="mr-[2px] op-radio op-radio-xs"
|
onChange={() => handleNotifySignChange(false)}
|
||||||
type="radio"
|
checked={isNotifyOnSignatures === false}
|
||||||
onChange={() => handleNotifySignChange(true)}
|
/>
|
||||||
checked={isNotifyOnSignatures === true}
|
<label
|
||||||
/>
|
htmlFor="notify-no"
|
||||||
<div className="text-[13px] cursor-default capitalize">
|
className="text-sm text-base-content cursor-pointer mb-0"
|
||||||
{t("yes")}
|
>
|
||||||
</div>
|
{t("no")}
|
||||||
</div>
|
</label>
|
||||||
<div className={"flex items-center gap-2 ml-2 mb-1"}>
|
|
||||||
<input
|
|
||||||
className="mr-[2px] op-radio op-radio-xs"
|
|
||||||
type="radio"
|
|
||||||
onChange={() => handleNotifySignChange(false)}
|
|
||||||
checked={isNotifyOnSignatures === false}
|
|
||||||
/>
|
|
||||||
<div className="text-[13px] cursor-default capitalize">
|
|
||||||
{t("no")}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div className="mb-[0.75rem]">
|
{/* Send in Order Section */}
|
||||||
<TimezoneSelector
|
<div className="mb-6">
|
||||||
timezone={timezone}
|
<label className="text-[14px] mb-[0.7rem] font-medium">
|
||||||
setTimezone={setTimezone}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="mb-[0.75rem]">
|
|
||||||
<DateFormatSelector
|
|
||||||
timezone={timezone}
|
|
||||||
dateFormat={dateFormat}
|
|
||||||
is12HourTime={is12HourTime}
|
|
||||||
setIs12HourTime={setIs12HourTime}
|
|
||||||
setDateFormat={setDateFormat}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="mb-[0.75rem] text-[12px]">
|
|
||||||
<label className="block mb-[0.7rem]">
|
|
||||||
<span className="text-[14px] font-medium">
|
|
||||||
{t("send-in-order")}
|
{t("send-in-order")}
|
||||||
</span>
|
<a
|
||||||
<a
|
data-tooltip-id="sendInOrder-tooltip"
|
||||||
data-tooltip-id="sendInOrder-tooltip"
|
className="ml-1"
|
||||||
className="ml-1"
|
>
|
||||||
>
|
<sup>
|
||||||
<sup>
|
<i className="fa-light fa-question rounded-full border-[#33bbff] text-[#33bbff] text-[13px] border-[1px] py-[1.5px] px-[4px]"></i>
|
||||||
<i className="fa-light fa-question rounded-full border-[#33bbff] text-[#33bbff] text-[13px] border-[1px] py-[1.5px] px-[4px]"></i>
|
</sup>
|
||||||
</sup>
|
</a>
|
||||||
</a>
|
<ReactTooltip
|
||||||
<ReactTooltip
|
id="sendInOrder-tooltip"
|
||||||
id="sendInOrder-tooltip"
|
className="z-[999]"
|
||||||
className="z-[999]"
|
>
|
||||||
>
|
<div className="max-w-[200px] md:max-w-[450px]">
|
||||||
<div className="max-w-[200px] md:max-w-[450px]">
|
<p className="font-bold">{t("send-in-order")}</p>
|
||||||
<p className="font-bold">{t("send-in-order")}</p>
|
<p>{t("send-in-order-help.p1")}</p>
|
||||||
<p>{t("send-in-order-help.p1")}</p>
|
<div className="p-[5px]">
|
||||||
<div className="p-[5px]">
|
<ol className="list-disc">
|
||||||
<ol className="list-disc">
|
<li>
|
||||||
<li>
|
<span className="font-bold">
|
||||||
<span className="font-bold">
|
{t("yes")}:{" "}
|
||||||
{t("yes")}:{" "}
|
</span>
|
||||||
</span>
|
<span>{t("send-in-order-help.p2")}</span>
|
||||||
<span>{t("send-in-order-help.p2")}</span>
|
</li>
|
||||||
</li>
|
<li>
|
||||||
<li>
|
<span className="font-bold">
|
||||||
<span className="font-bold">{t("no")}: </span>
|
{t("no")}:{" "}
|
||||||
<span>{t("send-in-order-help.p3")}</span>
|
</span>
|
||||||
</li>
|
<span>{t("send-in-order-help.p3")}</span>
|
||||||
</ol>
|
</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
<p>{t("send-in-order-help.p4")}</p>
|
||||||
</div>
|
</div>
|
||||||
<p>{t("send-in-order-help.p4")}</p>
|
</ReactTooltip>
|
||||||
|
</label>
|
||||||
|
<div className="flex flex-row gap-6">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<input
|
||||||
|
id="order-yes"
|
||||||
|
type="radio"
|
||||||
|
value={true}
|
||||||
|
className="op-radio op-radio-xs"
|
||||||
|
name="SendinOrder"
|
||||||
|
checked={sendinOrder}
|
||||||
|
onChange={handleSendinOrderInput}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
htmlFor="order-yes"
|
||||||
|
className="text-sm text-base-content cursor-pointer mb-0"
|
||||||
|
>
|
||||||
|
{t("yes")}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<input
|
||||||
|
id="order-no"
|
||||||
|
type="radio"
|
||||||
|
value={false}
|
||||||
|
name="SendinOrder"
|
||||||
|
className="op-radio op-radio-xs"
|
||||||
|
checked={!sendinOrder}
|
||||||
|
onChange={handleSendinOrderInput}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
htmlFor="order-no"
|
||||||
|
className="text-sm text-base-content cursor-pointer mb-0"
|
||||||
|
>
|
||||||
|
{t("no")}
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</ReactTooltip>
|
|
||||||
</label>
|
|
||||||
<div className="flex flex-col md:flex-row md:gap-4">
|
|
||||||
<div className="flex items-center gap-2 ml-2 mb-1">
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
value={true}
|
|
||||||
className="op-radio op-radio-xs"
|
|
||||||
name="SendinOrder"
|
|
||||||
checked={sendinOrder}
|
|
||||||
onChange={handleSendinOrderInput}
|
|
||||||
/>
|
|
||||||
<div className="text-center">{t("yes")}</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2 ml-2 mb-1">
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
value={false}
|
|
||||||
name="SendinOrder"
|
|
||||||
className="op-radio op-radio-xs"
|
|
||||||
checked={!sendinOrder}
|
|
||||||
onChange={handleSendinOrderInput}
|
|
||||||
/>
|
|
||||||
<div className="text-center">{t("no")}</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div className="mb-[0.75rem] text-[12px]">
|
{/* Enable Tour Section */}
|
||||||
<label className="block mb-[0.7rem]">
|
<div className="mb-6">
|
||||||
<span className="text-[14px] font-medium">
|
<label className="text-[14px] mb-[0.7rem] font-medium">
|
||||||
{t("enable-tour")}
|
{t("enable-tour")}
|
||||||
</span>
|
<a
|
||||||
<a
|
data-tooltip-id="istourenabled-tooltip"
|
||||||
data-tooltip-id="istourenabled-tooltip"
|
className="ml-1"
|
||||||
className="ml-1"
|
>
|
||||||
>
|
<sup>
|
||||||
<sup>
|
<i className="fa-light fa-question rounded-full border-[#33bbff] text-[#33bbff] text-[13px] border-[1px] py-[1.5px] px-[4px]"></i>
|
||||||
<i className="fa-light fa-question rounded-full border-[#33bbff] text-[#33bbff] text-[13px] border-[1px] py-[1.5px] px-[4px]"></i>
|
</sup>
|
||||||
</sup>
|
</a>
|
||||||
</a>
|
<ReactTooltip
|
||||||
<ReactTooltip
|
id="istourenabled-tooltip"
|
||||||
id="istourenabled-tooltip"
|
className="z-[999]"
|
||||||
className="z-[999]"
|
>
|
||||||
>
|
<div className="max-w-[200px] md:max-w-[450px]">
|
||||||
<div className="max-w-[200px] md:max-w-[450px]">
|
<p className="font-bold">{t("enable-tour")}</p>
|
||||||
<p className="font-bold">{t("enable-tour")}</p>
|
<div className="p-[5px]">
|
||||||
<div className="p-[5px]">
|
<ol className="list-disc">
|
||||||
<ol className="list-disc">
|
<li>
|
||||||
<li>
|
<span className="font-bold">
|
||||||
<span className="font-bold">
|
{t("yes")}:{" "}
|
||||||
{t("yes")}:{" "}
|
</span>
|
||||||
</span>
|
<span>{t("istourenabled-help.p1")}</span>
|
||||||
<span>{t("istourenabled-help.p1")}</span>
|
</li>
|
||||||
</li>
|
<li>
|
||||||
<li>
|
<span className="font-bold">
|
||||||
<span className="font-bold">{t("no")}: </span>
|
{t("no")}:{" "}
|
||||||
<span>{t("istourenabled-help.p2")}</span>
|
</span>
|
||||||
</li>
|
<span>{t("istourenabled-help.p2")}</span>
|
||||||
</ol>
|
</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
{t("istourenabled-help.p3", {
|
||||||
|
appName: appName
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<p>
|
</ReactTooltip>
|
||||||
{t("istourenabled-help.p3", {
|
</label>
|
||||||
appName: appName
|
<div className="flex flex-row gap-6">
|
||||||
})}
|
<div className="flex items-center gap-2">
|
||||||
</p>
|
<input
|
||||||
|
id="tour-yes"
|
||||||
|
type="radio"
|
||||||
|
value={true}
|
||||||
|
className="op-radio op-radio-xs"
|
||||||
|
name="IsTourEnabled"
|
||||||
|
checked={isTourEnabled}
|
||||||
|
onChange={handleTourInput}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
htmlFor="tour-yes"
|
||||||
|
className="text-sm text-base-content cursor-pointer mb-0"
|
||||||
|
>
|
||||||
|
{t("yes")}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<input
|
||||||
|
id="tour-no"
|
||||||
|
type="radio"
|
||||||
|
value={false}
|
||||||
|
name="IsTourEnabled"
|
||||||
|
className="op-radio op-radio-xs"
|
||||||
|
checked={!isTourEnabled}
|
||||||
|
onChange={handleTourInput}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
htmlFor="tour-no"
|
||||||
|
className="text-sm text-base-content cursor-pointer mb-0"
|
||||||
|
>
|
||||||
|
{t("no")}
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</ReactTooltip>
|
|
||||||
</label>
|
|
||||||
<div className="flex flex-col md:flex-row md:gap-4">
|
|
||||||
<div className="flex items-center gap-2 ml-2 mb-1">
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
value={true}
|
|
||||||
className="op-radio op-radio-xs"
|
|
||||||
name="IsTourEnabled"
|
|
||||||
checked={isTourEnabled}
|
|
||||||
onChange={handleTourInput}
|
|
||||||
/>
|
|
||||||
<div className="text-center">{t("yes")}</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2 ml-2 mb-1">
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
value={false}
|
|
||||||
name="IsTourEnabled"
|
|
||||||
className="op-radio op-radio-xs"
|
|
||||||
checked={!isTourEnabled}
|
|
||||||
onChange={handleTourInput}
|
|
||||||
/>
|
|
||||||
<div className="text-center">{t("no")}</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-[0.75rem]">
|
|
||||||
|
{/* Right Column - Timezone & Date Settings */}
|
||||||
|
<div className="md:col-span-7 flex flex-col">
|
||||||
|
<div className="mb-6">
|
||||||
|
<TimezoneSelector
|
||||||
|
timezone={timezone}
|
||||||
|
setTimezone={setTimezone}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mb-6">
|
||||||
|
<DateFormatSelector
|
||||||
|
timezone={timezone}
|
||||||
|
dateFormat={dateFormat}
|
||||||
|
is12HourTime={is12HourTime}
|
||||||
|
setIs12HourTime={setIs12HourTime}
|
||||||
|
setDateFormat={setDateFormat}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Save Button - Full Width */}
|
||||||
|
<div className="md:col-span-12 flex justify-start mt-2">
|
||||||
<button
|
<button
|
||||||
className="op-btn op-btn-primary w-[110px]"
|
className="op-btn op-btn-primary w-[110px]"
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
@@ -648,7 +705,7 @@ const Preferences = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col mx-4 mb-4">
|
<div className="flex flex-col mb-4">
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<h1 className="text-[14px] mb-[0.7rem] font-medium">
|
<h1 className="text-[14px] mb-[0.7rem] font-medium">
|
||||||
{t("request-email")}
|
{t("request-email")}
|
||||||
@@ -660,7 +717,7 @@ const Preferences = () => {
|
|||||||
>
|
>
|
||||||
<div className="text-lg font-normal">
|
<div className="text-lg font-normal">
|
||||||
<label className="text-sm">
|
<label className="text-sm">
|
||||||
{t("subject")}
|
{t("subject")}{" "}
|
||||||
<Tooltip
|
<Tooltip
|
||||||
id={"request-sub-tooltip"}
|
id={"request-sub-tooltip"}
|
||||||
message={`${t(
|
message={`${t(
|
||||||
@@ -682,7 +739,7 @@ const Preferences = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="text-lg font-normal py-2">
|
<div className="text-lg font-normal py-2">
|
||||||
<label className="text-sm mt-3">
|
<label className="text-sm mt-3">
|
||||||
{t("body")}
|
{t("body")}{" "}
|
||||||
<Tooltip
|
<Tooltip
|
||||||
id={"request-body-tooltip"}
|
id={"request-body-tooltip"}
|
||||||
message={`${t(
|
message={`${t(
|
||||||
@@ -730,7 +787,7 @@ const Preferences = () => {
|
|||||||
>
|
>
|
||||||
<div className="text-lg font-normal">
|
<div className="text-lg font-normal">
|
||||||
<label className="text-sm">
|
<label className="text-sm">
|
||||||
{t("subject")}
|
{t("subject")}{" "}
|
||||||
<Tooltip
|
<Tooltip
|
||||||
id={"complete-sub-tooltip"}
|
id={"complete-sub-tooltip"}
|
||||||
message={`${t(
|
message={`${t(
|
||||||
@@ -752,7 +809,7 @@ const Preferences = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="text-lg font-normal py-2">
|
<div className="text-lg font-normal py-2">
|
||||||
<label className="text-sm mt-3">
|
<label className="text-sm mt-3">
|
||||||
{t("body")}
|
{t("body")}{" "}
|
||||||
<Tooltip
|
<Tooltip
|
||||||
id={"complete-body-tooltip"}
|
id={"complete-body-tooltip"}
|
||||||
message={`${t(
|
message={`${t(
|
||||||
|
|||||||
@@ -733,20 +733,18 @@ const TemplatePlaceholder = () => {
|
|||||||
let isLabel = false;
|
let isLabel = false;
|
||||||
let unfilledTextWidgetId = "";
|
let unfilledTextWidgetId = "";
|
||||||
//condition is used to check text widget data is empty or have response
|
//condition is used to check text widget data is empty or have response
|
||||||
if (getPrefill) {
|
if (getPrefill && prefillPlaceholder) {
|
||||||
if (prefillPlaceholder) {
|
prefillPlaceholder.map((data) => {
|
||||||
prefillPlaceholder.map((data) => {
|
if (!isLabel) {
|
||||||
if (!isLabel) {
|
const unfilledTextWidgets = data.pos.find(
|
||||||
const unfilledTextWidgets = data.pos.find(
|
(position) => !position.options.response
|
||||||
(position) => !position.options.response
|
);
|
||||||
);
|
if (unfilledTextWidgets) {
|
||||||
if (unfilledTextWidgets) {
|
isLabel = true;
|
||||||
isLabel = true;
|
unfilledTextWidgetId = unfilledTextWidgets.key;
|
||||||
unfilledTextWidgetId = unfilledTextWidgets.key;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
if (getPrefill && isLabel) {
|
if (getPrefill && isLabel) {
|
||||||
setUnSignedWidgetId(unfilledTextWidgetId);
|
setUnSignedWidgetId(unfilledTextWidgetId);
|
||||||
@@ -1250,6 +1248,7 @@ const TemplatePlaceholder = () => {
|
|||||||
? { RedirectUrl: updateTemplate?.[0]?.RedirectUrl }
|
? { RedirectUrl: updateTemplate?.[0]?.RedirectUrl }
|
||||||
: {};
|
: {};
|
||||||
const data = {
|
const data = {
|
||||||
|
...(updateTemplate?.[0]?.URL ? { URL: updateTemplate?.[0]?.URL } : {}),
|
||||||
Name: updateTemplate?.[0]?.Name || "",
|
Name: updateTemplate?.[0]?.Name || "",
|
||||||
Note: updateTemplate?.[0]?.Note || "",
|
Note: updateTemplate?.[0]?.Note || "",
|
||||||
Description: updateTemplate?.[0]?.Description || "",
|
Description: updateTemplate?.[0]?.Description || "",
|
||||||
@@ -1964,16 +1963,17 @@ const TemplatePlaceholder = () => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<ModalUi
|
{isEditTemplate && (
|
||||||
title={t("edit-template")}
|
|
||||||
isOpen={isEditTemplate}
|
|
||||||
handleClose={handleEditTemplateModal}
|
|
||||||
>
|
|
||||||
<EditTemplate
|
<EditTemplate
|
||||||
|
title={t("edit-template")}
|
||||||
|
handleClose={handleEditTemplateModal}
|
||||||
|
pdfbase64={pdfBase64Url}
|
||||||
template={pdfDetails?.[0]}
|
template={pdfDetails?.[0]}
|
||||||
onSuccess={handleEditTemplateForm}
|
onSuccess={handleEditTemplateForm}
|
||||||
|
setPdfArrayBuffer={setPdfArrayBuffer}
|
||||||
|
setPdfBase64Url={setPdfBase64Url}
|
||||||
/>
|
/>
|
||||||
</ModalUi>
|
)}
|
||||||
<WidgetNameModal
|
<WidgetNameModal
|
||||||
signatureType={signatureType}
|
signatureType={signatureType}
|
||||||
widgetName={widgetName}
|
widgetName={widgetName}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React, { useState, useEffect, useRef } from "react";
|
import React, { useState, useEffect, useRef } from "react";
|
||||||
import pad from "../assets/images/pad.svg";
|
import pad from "../assets/images/pad.svg";
|
||||||
|
import recreatedoc from "../assets/images/recreatedoc.png";
|
||||||
import { Link, useLocation, useNavigate } from "react-router";
|
import { Link, useLocation, useNavigate } from "react-router";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import ModalUi from "./ModalUi";
|
import ModalUi from "./ModalUi";
|
||||||
@@ -19,7 +20,8 @@ import {
|
|||||||
getTenantDetails,
|
getTenantDetails,
|
||||||
handleSignatureType,
|
handleSignatureType,
|
||||||
replaceMailVaribles,
|
replaceMailVaribles,
|
||||||
signatureTypes
|
signatureTypes,
|
||||||
|
openInNewTab
|
||||||
} from "../constant/Utils";
|
} from "../constant/Utils";
|
||||||
import EditorToolbar, {
|
import EditorToolbar, {
|
||||||
module1,
|
module1,
|
||||||
@@ -412,6 +414,8 @@ const ReportTable = (props) => {
|
|||||||
setIsModal({ [`edit_${item.objectId}`]: true });
|
setIsModal({ [`edit_${item.objectId}`]: true });
|
||||||
} else if (act.action === "saveastemplate") {
|
} else if (act.action === "saveastemplate") {
|
||||||
setIsModal({ [`saveastemplate_${item.objectId}`]: true });
|
setIsModal({ [`saveastemplate_${item.objectId}`]: true });
|
||||||
|
} else if (act.action === "recreatedocument") {
|
||||||
|
setIsModal({ [`recreatedocument_${item.objectId}`]: true });
|
||||||
} else if (act.action) {
|
} else if (act.action) {
|
||||||
setIsModal({ [`extendexpiry_${item.objectId}`]: true });
|
setIsModal({ [`extendexpiry_${item.objectId}`]: true });
|
||||||
}
|
}
|
||||||
@@ -1352,6 +1356,24 @@ const ReportTable = (props) => {
|
|||||||
setUserDetails({});
|
setUserDetails({});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleRecreateDoc = async (item) => {
|
||||||
|
setActLoader({ [item.objectId]: true });
|
||||||
|
try {
|
||||||
|
const res = await Parse.Cloud.run("recreatedoc", {
|
||||||
|
docId: item.objectId
|
||||||
|
});
|
||||||
|
if (res) {
|
||||||
|
openInNewTab(`/placeHolderSign/${res.objectId}`, "_self");
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
handleCloseModal();
|
||||||
|
showAlert("danger", err.message);
|
||||||
|
// showAlert("danger", t("something-went-wrong-mssg"));
|
||||||
|
console.log("Err while create duplicate template", err);
|
||||||
|
} finally {
|
||||||
|
setActLoader({});
|
||||||
|
}
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
{Object.keys(actLoader)?.length > 0 && (
|
{Object.keys(actLoader)?.length > 0 && (
|
||||||
@@ -1826,6 +1848,45 @@ const ReportTable = (props) => {
|
|||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
{isModal["recreatedocument_" + item.objectId] && (
|
||||||
|
<ModalUi isOpen handleClose={handleCloseModal}>
|
||||||
|
{actLoader[item.objectId] && (
|
||||||
|
<div className="absolute h-full w-full flex justify-center items-center rounded-box bg-black/30">
|
||||||
|
<Loader />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<h3 className="text-base-content font-bold text-lg pt-[15px] px-[20px]">
|
||||||
|
{t("fix-&-resend-document")}
|
||||||
|
</h3>
|
||||||
|
<div className="p-[15px] md:p-[20px]">
|
||||||
|
<div className="text-lg font-normal text-center">
|
||||||
|
<img
|
||||||
|
src={recreatedoc}
|
||||||
|
alt="recreate-doc"
|
||||||
|
className="mx-auto w-[200px] h-auto"
|
||||||
|
/>
|
||||||
|
<p className="text-sm md:text-base md:px-2 mt-2">
|
||||||
|
{t("do-you-want-recreate-document?")}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<hr className="bg-[#ccc] mt-2.5" />
|
||||||
|
<div className="flex items-center justify-center mt-[14px] md:mt-[16px] gap-2 text-white">
|
||||||
|
<button
|
||||||
|
onClick={() => handleRecreateDoc(item)}
|
||||||
|
className="op-btn op-btn-primary focus:outline-none text-sm relative px-4"
|
||||||
|
>
|
||||||
|
{t("start-editing")}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={handleCloseModal}
|
||||||
|
className="op-btn op-btn-secondary focus:outline-none text-sm relative px-8"
|
||||||
|
>
|
||||||
|
{t("cancel")}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ModalUi>
|
||||||
|
)}
|
||||||
{isModal["saveastemplate_" + item.objectId] && (
|
{isModal["saveastemplate_" + item.objectId] && (
|
||||||
<ModalUi
|
<ModalUi
|
||||||
isOpen
|
isOpen
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
FROM node:22.14.0
|
FROM node:22.14.0
|
||||||
|
|
||||||
# install java
|
# install java
|
||||||
RUN wget https://download.oracle.com/java/23/latest/jdk-23_linux-x64_bin.deb \
|
RUN wget https://downloads.opensign.me/jdk-23_linux-x64_bin.deb \
|
||||||
&& dpkg -i jdk-23_linux-x64_bin.deb
|
&& dpkg -i jdk-23_linux-x64_bin.deb
|
||||||
|
|
||||||
# Set the working directory inside the container
|
# Set the working directory inside the container
|
||||||
|
|||||||
Binary file not shown.
@@ -2,6 +2,8 @@ import dotenv from 'dotenv';
|
|||||||
import { format, toZonedTime } from 'date-fns-tz';
|
import { format, toZonedTime } from 'date-fns-tz';
|
||||||
import { getSignedLocalUrl } from './cloud/parsefunction/getSignedUrl.js';
|
import { getSignedLocalUrl } from './cloud/parsefunction/getSignedUrl.js';
|
||||||
import { PDFDocument } from 'pdf-lib';
|
import { PDFDocument } from 'pdf-lib';
|
||||||
|
import crypto from 'node:crypto';
|
||||||
|
import axios from 'axios';
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|
||||||
export const cloudServerUrl = 'http://localhost:8080/app';
|
export const cloudServerUrl = 'http://localhost:8080/app';
|
||||||
@@ -148,6 +150,7 @@ export const useLocal = process.env.USE_LOCAL ? process.env.USE_LOCAL.toLowerCas
|
|||||||
export const smtpsecure = process.env.SMTP_PORT && process.env.SMTP_PORT !== '465' ? false : true;
|
export const smtpsecure = process.env.SMTP_PORT && process.env.SMTP_PORT !== '465' ? false : true;
|
||||||
export const smtpenable =
|
export const smtpenable =
|
||||||
process.env.SMTP_ENABLE && process.env.SMTP_ENABLE.toLowerCase() === 'true' ? true : false;
|
process.env.SMTP_ENABLE && process.env.SMTP_ENABLE.toLowerCase() === 'true' ? true : false;
|
||||||
|
export const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||||
|
|
||||||
// `generateId` is used to unique Id for fileAdapter
|
// `generateId` is used to unique Id for fileAdapter
|
||||||
export function generateId(length) {
|
export function generateId(length) {
|
||||||
@@ -297,3 +300,39 @@ export function formatDateTime(date, dateFormat, timeZone, is12Hour) {
|
|||||||
? format(zonedDate, `${selectFormat(dateFormat)}, ${timeFormat} 'GMT' XXX`, { timeZone })
|
? format(zonedDate, `${selectFormat(dateFormat)}, ${timeFormat} 'GMT' XXX`, { timeZone })
|
||||||
: formatTimeInTimezone(date, timeZone);
|
: formatTimeInTimezone(date, timeZone);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Utility: Convert base64 to buffer
|
||||||
|
export const base64ToBuffer = base64 => Buffer.from(base64, 'base64');
|
||||||
|
|
||||||
|
// Utility: Generate SHA-256 hash from PDF page metadata
|
||||||
|
const getPdfMetadataHash = async pdfBytes => {
|
||||||
|
const pdfDoc = await PDFDocument.load(pdfBytes);
|
||||||
|
const metaString = pdfDoc
|
||||||
|
.getPages()
|
||||||
|
.map((page, index) => {
|
||||||
|
const { width, height } = page.getSize();
|
||||||
|
return `${index + 1}:${Math.round(width)}x${Math.round(height)}`;
|
||||||
|
})
|
||||||
|
.join('|');
|
||||||
|
|
||||||
|
return crypto.createHash('sha256').update(metaString).digest('hex');
|
||||||
|
};
|
||||||
|
// Utility: Validate if uploaded file matches original template PDF
|
||||||
|
export const handleReplaceFileValidation = async (baseFileUrl, newFileBase64) => {
|
||||||
|
try {
|
||||||
|
const { data } = await axios.get(baseFileUrl, { responseType: 'arraybuffer' });
|
||||||
|
const basePdfBytes = Buffer.from(data);
|
||||||
|
const uploadedPdfBytes = base64ToBuffer(newFileBase64);
|
||||||
|
|
||||||
|
const baseHash = await getPdfMetadataHash(basePdfBytes);
|
||||||
|
const uploadedHash = await getPdfMetadataHash(uploadedPdfBytes);
|
||||||
|
|
||||||
|
if (baseHash === uploadedHash) {
|
||||||
|
return { base64: newFileBase64 };
|
||||||
|
}
|
||||||
|
return { error: 'PDFs do NOT match based on page number, width, and height' };
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Validation Error:', err.message);
|
||||||
|
return { error: err.message };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@@ -11,7 +11,11 @@ export const SSOAuth = {
|
|||||||
Authorization: `Bearer ${authData.access_token}`,
|
Authorization: `Bearer ${authData.access_token}`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (response.data && response.data.id && response.data.email === authData.id) {
|
if (
|
||||||
|
response.data &&
|
||||||
|
response.data.id &&
|
||||||
|
response.data.email?.toLowerCase()?.replace(/\s/g, '') === authData.id
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'SSO auth is invalid for this user.');
|
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'SSO auth is invalid for this user.');
|
||||||
|
|||||||
@@ -51,6 +51,8 @@ import editContact from './parsefunction/editContact.js';
|
|||||||
import forwardDoc from './parsefunction/ForwardDoc.js';
|
import forwardDoc from './parsefunction/ForwardDoc.js';
|
||||||
import saveAsTemplate from './parsefunction/saveAsTemplate.js';
|
import saveAsTemplate from './parsefunction/saveAsTemplate.js';
|
||||||
import updateTenant from './parsefunction/updateTenant.js';
|
import updateTenant from './parsefunction/updateTenant.js';
|
||||||
|
import recreateDocument from './parsefunction/recreateDocument.js';
|
||||||
|
import addUser from './parsefunction/addUser.js';
|
||||||
|
|
||||||
// This afterSave function triggers after an object is added or updated in the specified class, allowing for post-processing logic.
|
// This afterSave function triggers after an object is added or updated in the specified class, allowing for post-processing logic.
|
||||||
Parse.Cloud.afterSave('contracts_Document', DocumentAftersave);
|
Parse.Cloud.afterSave('contracts_Document', DocumentAftersave);
|
||||||
@@ -112,3 +114,5 @@ Parse.Cloud.define('editcontact', editContact);
|
|||||||
Parse.Cloud.define('forwarddoc', forwardDoc);
|
Parse.Cloud.define('forwarddoc', forwardDoc);
|
||||||
Parse.Cloud.define('saveastemplate', saveAsTemplate);
|
Parse.Cloud.define('saveastemplate', saveAsTemplate);
|
||||||
Parse.Cloud.define('updatetenant', updateTenant);
|
Parse.Cloud.define('updatetenant', updateTenant);
|
||||||
|
Parse.Cloud.define('recreatedoc', recreateDocument);
|
||||||
|
Parse.Cloud.define('adduser', addUser);
|
||||||
|
|||||||
@@ -0,0 +1,106 @@
|
|||||||
|
export default async function addUser(request) {
|
||||||
|
const { phone, name, password, organization, team, tenantId, timezone, role } = request.params;
|
||||||
|
const email = request.params?.email?.toLowerCase()?.replace(/\s/g, '');
|
||||||
|
if (!request.user) {
|
||||||
|
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Invalid session token.');
|
||||||
|
}
|
||||||
|
const currentUser = { __type: 'Pointer', className: '_User', objectId: request.user.id };
|
||||||
|
if (name && email && password && organization && team && role && tenantId) {
|
||||||
|
try {
|
||||||
|
const extUser = new Parse.Object('contracts_Users');
|
||||||
|
extUser.set('Name', name);
|
||||||
|
if (phone) {
|
||||||
|
extUser.set('Phone', phone);
|
||||||
|
}
|
||||||
|
extUser.set('Email', email);
|
||||||
|
extUser.set('UserRole', `contracts_${role}`);
|
||||||
|
if (team) {
|
||||||
|
extUser.set('TeamIds', [
|
||||||
|
{
|
||||||
|
__type: 'Pointer',
|
||||||
|
className: 'contracts_Teams',
|
||||||
|
objectId: team,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
if (organization.objectId) {
|
||||||
|
extUser.set('OrganizationId', {
|
||||||
|
__type: 'Pointer',
|
||||||
|
className: 'contracts_Organizations',
|
||||||
|
objectId: organization.objectId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (organization.company) {
|
||||||
|
extUser.set('Company', organization.company);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tenantId) {
|
||||||
|
extUser.set('TenantId', {
|
||||||
|
__type: 'Pointer',
|
||||||
|
className: 'partners_Tenant',
|
||||||
|
objectId: tenantId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (timezone) {
|
||||||
|
extUser.set('Timezone', timezone);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const _users = Parse.Object.extend('User');
|
||||||
|
const _user = new _users();
|
||||||
|
_user.set('name', name);
|
||||||
|
_user.set('username', email);
|
||||||
|
_user.set('email', email);
|
||||||
|
_user.set('password', password);
|
||||||
|
if (phone) {
|
||||||
|
_user.set('phone', phone);
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await _user.save();
|
||||||
|
if (user) {
|
||||||
|
extUser.set('CreatedBy', currentUser);
|
||||||
|
|
||||||
|
extUser.set('UserId', user);
|
||||||
|
const acl = new Parse.ACL();
|
||||||
|
acl.setPublicReadAccess(true);
|
||||||
|
acl.setPublicWriteAccess(true);
|
||||||
|
acl.setReadAccess(request.user.id, true);
|
||||||
|
acl.setWriteAccess(request.user.id, true);
|
||||||
|
extUser.setACL(acl);
|
||||||
|
const extUserRes = await extUser.save();
|
||||||
|
|
||||||
|
const parseData = JSON.parse(JSON.stringify(extUserRes));
|
||||||
|
return parseData;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.log('err ', err);
|
||||||
|
if (err.code === 202) {
|
||||||
|
const userQuery = new Parse.Query(Parse.User);
|
||||||
|
userQuery.equalTo('email', email);
|
||||||
|
const userRes = await userQuery.first({ useMasterKey: true });
|
||||||
|
userRes.setPassword(password);
|
||||||
|
await userRes.save(null, { useMasterKey: true });
|
||||||
|
extUser.set('CreatedBy', currentUser);
|
||||||
|
extUser.set('UserId', { __type: 'Pointer', className: '_User', objectId: userRes.id });
|
||||||
|
const acl = new Parse.ACL();
|
||||||
|
acl.setPublicReadAccess(true);
|
||||||
|
acl.setPublicWriteAccess(true);
|
||||||
|
acl.setReadAccess(request.user.id, true);
|
||||||
|
acl.setWriteAccess(request.user.id, true);
|
||||||
|
|
||||||
|
extUser.setACL(acl);
|
||||||
|
const res = await extUser.save();
|
||||||
|
|
||||||
|
const parseData = JSON.parse(JSON.stringify(res));
|
||||||
|
return parseData;
|
||||||
|
} else {
|
||||||
|
throw new Parse.Error(400, err?.message || 'something went wrong');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.log('err', err);
|
||||||
|
throw new Parse.Error(400, err?.message || 'something went wrong');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Parse.Error(400, 'Please provide all required fields.');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
import { exec } from 'child_process';
|
|
||||||
|
|
||||||
export default function DigitalSign(pdf, pfx, details) {
|
|
||||||
const signcmd = `java -jar PDFDigitalSigner.jar "${pdf}" "${pfx.name}" "${pfx.passphrase}" "${details.name}" "${details.location}" "${details.reason}"`;
|
|
||||||
// const signcmd = `java -jar PDFDigitalSigner.jar "${pdf}" keystore.pfx opensign "${details.name}" "${details.location}" "${details.reason}"`;
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
exec(signcmd, (error, stdout, stderr) => {
|
|
||||||
if (error) {
|
|
||||||
reject(`Error: ${error.message}`);
|
|
||||||
}
|
|
||||||
if (stderr) {
|
|
||||||
reject(`stderr: ${stderr}`);
|
|
||||||
}
|
|
||||||
// Resolve the promise with the output
|
|
||||||
resolve(stdout.trim());
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function retryAsync(fn, args = [], retries = 3, delay = 2000) {
|
|
||||||
for (let attempt = 1; attempt <= retries; attempt++) {
|
|
||||||
try {
|
|
||||||
const response = await fn(...args); // Try executing the async function
|
|
||||||
if (response) return { response, attempt }; // Stop retrying if response is received
|
|
||||||
} catch (error) {
|
|
||||||
if (attempt < retries) {
|
|
||||||
console.log(`Attempt ${attempt} failed. Retrying in ${delay / 1000} seconds...\n`);
|
|
||||||
await new Promise(resolve => setTimeout(resolve, delay)); // Wait before retrying
|
|
||||||
} else {
|
|
||||||
return { error, attempt };
|
|
||||||
// throw new Error(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -20,6 +20,16 @@ const masterKEY = process.env.MASTER_KEY;
|
|||||||
const eSignName = 'OpenSign';
|
const eSignName = 'OpenSign';
|
||||||
const eSigncontact = 'hello@opensignlabs.com';
|
const eSigncontact = 'hello@opensignlabs.com';
|
||||||
|
|
||||||
|
async function unlinkFile(path) {
|
||||||
|
if (fs.existsSync(path)) {
|
||||||
|
try {
|
||||||
|
fs.unlinkSync(path);
|
||||||
|
} catch (err) {
|
||||||
|
console.log('Err in unlink file: ', path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// `updateDoc` is used to create url in from pdfFile
|
// `updateDoc` is used to create url in from pdfFile
|
||||||
async function uploadFile(pdfName, filepath) {
|
async function uploadFile(pdfName, filepath) {
|
||||||
try {
|
try {
|
||||||
@@ -33,8 +43,8 @@ async function uploadFile(pdfName, filepath) {
|
|||||||
return { imageUrl: fileUrl };
|
return { imageUrl: fileUrl };
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log('Err ', err);
|
console.log('Err ', err);
|
||||||
// `fs.unlinkSync` is used to remove exported signed pdf file from exports folder
|
// below line of code is used to remove exported signed pdf file from exports folder
|
||||||
fs.unlinkSync(filepath);
|
unlinkFile(filepath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,10 +250,10 @@ async function sendCompletedMail(obj) {
|
|||||||
});
|
});
|
||||||
// console.log('res', res.data.result);
|
// console.log('res', res.data.result);
|
||||||
if (res.data?.result?.status !== 'success') {
|
if (res.data?.result?.status !== 'success') {
|
||||||
fs.unlinkSync(`./exports/signed_certificate_${doc.objectId}.pdf`);
|
unlinkFile(`./exports/signed_certificate_${doc.objectId}.pdf`);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
fs.unlinkSync(`./exports/signed_certificate_${doc.objectId}.pdf`);
|
unlinkFile(`./exports/signed_certificate_${doc.objectId}.pdf`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -288,7 +298,7 @@ async function sendMailsaveCertifcate(doc, pfx, isCustomMail, mailProvider, file
|
|||||||
sendCompletedMail({ isCustomMail, doc, mailProvider, filename });
|
sendCompletedMail({ isCustomMail, doc, mailProvider, filename });
|
||||||
}
|
}
|
||||||
saveFileUsage(CertificateBuffer.length, file.imageUrl, doc?.CreatedBy?.objectId);
|
saveFileUsage(CertificateBuffer.length, file.imageUrl, doc?.CreatedBy?.objectId);
|
||||||
fs.unlinkSync(pfx.name);
|
unlinkFile(pfx.name);
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -438,10 +448,10 @@ async function PDF(req) {
|
|||||||
const doc = { ..._resDoc, AuditTrail: updatedDoc.AuditTrail, SignedUrl: data.imageUrl };
|
const doc = { ..._resDoc, AuditTrail: updatedDoc.AuditTrail, SignedUrl: data.imageUrl };
|
||||||
sendMailsaveCertifcate(doc, pfx, isCustomMail, mailProvider, `signed_${name}`);
|
sendMailsaveCertifcate(doc, pfx, isCustomMail, mailProvider, `signed_${name}`);
|
||||||
} else {
|
} else {
|
||||||
fs.unlinkSync(pfxname);
|
unlinkFile(pfxname);
|
||||||
}
|
}
|
||||||
// `fs.unlinkSync` is used to remove exported signed pdf file from exports folder
|
// below code is used to remove exported signed pdf file from exports folder
|
||||||
fs.unlinkSync(signedFilePath);
|
unlinkFile(signedFilePath);
|
||||||
// console.log(`New Signed PDF created called: ${filePath}`);
|
// console.log(`New Signed PDF created called: ${filePath}`);
|
||||||
if (updatedDoc.message === 'success') {
|
if (updatedDoc.message === 'success') {
|
||||||
return { status: 'success', data: data.imageUrl };
|
return { status: 'success', data: data.imageUrl };
|
||||||
@@ -466,7 +476,7 @@ async function PDF(req) {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log('err in saving debugginglog', err);
|
console.log('err in saving debugginglog', err);
|
||||||
}
|
}
|
||||||
fs.unlinkSync(pfxname);
|
unlinkFile(pfxname);
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
export default async function recreateDocument(request) {
|
||||||
|
const { docId } = request.params;
|
||||||
|
if (!docId) {
|
||||||
|
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Missing docId parameter');
|
||||||
|
}
|
||||||
|
if (!request.user) {
|
||||||
|
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User not aunthenticated');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const docQuery = new Parse.Query('contracts_Document');
|
||||||
|
docQuery.equalTo('objectId', docId);
|
||||||
|
const doc = await docQuery.first({ useMasterKey: true });
|
||||||
|
if (!doc) {
|
||||||
|
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Document not found');
|
||||||
|
}
|
||||||
|
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') {
|
||||||
|
createDoc.set(key, false);
|
||||||
|
} else {
|
||||||
|
createDoc.set(key, value);
|
||||||
|
}
|
||||||
|
// console.log(`${key}: ${value}`);
|
||||||
|
});
|
||||||
|
const createDocRes = await createDoc.save(null, { useMasterKey: true });
|
||||||
|
// console.log('createDocRes', createDocRes);
|
||||||
|
const newDoc = JSON.parse(JSON.stringify(createDocRes));
|
||||||
|
return { objectId: newDoc.objectId, createdAt: newDoc.createdAt, updatedAt: newDoc.updatedAt };
|
||||||
|
} catch (err) {
|
||||||
|
console.log('err in recreate document', err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
Reference in New Issue
Block a user