fix: Update hint for smtp username

This commit is contained in:
prafull-opensignlabs
2025-03-17 06:37:12 +00:00
parent 22d27abb76
commit 827d80f50a
39 changed files with 1120 additions and 867 deletions
+1 -3
View File
@@ -1,5 +1,5 @@
# Use an official Node runtime as the base image
FROM node:18
FROM node:22.14.0
# Set the working directory inside the container
WORKDIR /usr/src/app
@@ -16,8 +16,6 @@ COPY apps/OpenSign/.husky .
# Define environment variables if needed
ENV NODE_ENV=production
ENV REACT_APP_DEPLOYMENT=free_selfhost
# build
RUN npm run build
@@ -79,6 +79,7 @@
"Storage": "Speicher",
"Signing certificate": "Signierzertifikat",
"Teams": "Teams",
"General": "Allgemein",
"Teams-Children": {
"Organizations": "Organisationen",
"OrgAdmins": "OrgAdmins"
@@ -149,7 +150,8 @@
"Copy Public URL": "Öffentliche URL kopieren",
"extend-expiry-date": "Ablaufdatum verlängern",
"Duplicate Template": "Vorlage duplizieren",
"Duplicate": "Duplikat"
"Duplicate": "Duplikat",
"daily-mail-quota": "Tägliches E-Mail-Kontingent"
},
"report-heading": {
"Sr.No": "Nr.",
@@ -169,7 +171,7 @@
"Name": "Name",
"Status": "Status",
"created-date": "Erstellungsdatum",
"Type": "Typ",
"Type": "Type",
"Logs": "Protokolle",
"Expiry-date": "Ablaufdatum"
},
@@ -604,6 +606,7 @@
"placeholder-sign-4": "Ziehen Sie ein Feld in das Dokument oder klicken Sie darauf, um es hinzuzufügen.",
"placeholder-sign-5": "Der PDF-Inhaltsbereich zeigt bereits die vorhandenen Platzhalter der Vorlage an. Diese Platzhalter entsprechen der Farbe des Empfängernamens, um sie leicht erkennbar zu machen.",
"placeholder-sign-6": "Mit einem Klick auf 'Weiter' wird das Dokument gespeichert. Im nächsten Schritt können Sie die E-Mails, die an die Empfänger versendet werden sollen, anpassen oder die Signaturlinks kopieren und diese selbst mit den Empfängern teilen.",
"report-1":"Klicken Sie auf die Schaltfläche „Hinzufügen“, um eine neue Vorlage zu erstellen. Vorlagen sind wiederverwendbare Dokumente, mit denen schnell neue Dokumente mit derselben Struktur und unterschiedlichen Unterzeichnern erstellt werden können. Eine HR-Vorlage für die Einarbeitung könnte beispielsweise vordefinierte Rollen wie „Personalleiter“ und „Neuer Mitarbeiter“ enthalten. Bei jeder Verwendung der Vorlage können Sie die Rolle „Neuer Mitarbeiter“ verschiedenen neuen Mitarbeitern zuweisen, während die Rolle „Personalleiter“ unverändert bleibt. So wird ein nahtloser Einarbeitungsprozess für jeden neuen Mitarbeiter ermöglicht.",
"redirect": "Klicken Sie auf die Schaltfläche 'Verwenden', um ein neues Dokument aus einer bestehenden Vorlage zu erstellen.",
"bulksend": "Um schnell mehrere Dokumente mithilfe einer vorhandenen Vorlage zu versenden, indem Sie einfach die E-Mail-Adressen der Empfänger erstellen, klicken Sie auf die Schaltfläche Massenversand",
"option": "Dieses Menü zeigt weitere Optionen wie Bearbeiten und Löschen. Verwenden Sie die Schaltfläche 'Bearbeiten', um Unterzeichnerrollen hinzuzufügen, Felder zu ändern und Ihre Vorlage zu aktualisieren. Änderungen gelten für alle zukünftigen Dokumente, die aus dieser Vorlage erstellt werden, wirken sich jedoch nicht auf vorhandene Dokumente aus. Verwenden Sie die Schaltfläche 'Löschen', um die Vorlage zu entfernen.",
@@ -848,5 +851,10 @@
"draft-template-info-p1": "Um Ihre Vorlage öffentlich zu machen, muss sie entweder eine einzelne Rolle enthalten oder, wenn sie mehrere Rollen umfasst, müssen alle zusätzlichen Rollen bereits den Unterzeichnern zugewiesen sein. Die nicht zugewiesene öffentliche Rolle muss leer bleiben und an erster Stelle stehen.",
"visit-below-link": "Besuchen Sie den untenstehenden Link, um mehr zu erfahren -",
"storage-help": "Durch die Aktivierung von BYOC können Sie Ihren eigenen S3-Speicher verbinden, sodass Ihre Dateien vollständig unter Ihrer Kontrolle bleiben und keine externen Kopien gespeichert werden. Wenn Ihnen Datenautonomie wichtig ist, erwägen Sie ein Upgrade auf Teams, um diese Funktion freizuschalten.",
"daily-quota-reached": "Youve reached your daily quota. For assistance, please contact quotas@opensignlabs.com."
"daily-quota-reached": "Sie haben Ihr tägliches Kontingent erreicht. Für Unterstützung kontaktieren Sie bitte quotas@opensignlabs.com.",
"enabled-signature-type": "Aktivierte Signaturtypen",
"enabled-signature-type-help": "Die Einstellung 'Aktivierte Signaturtypen' bestimmt, welche Signaturoptionen in Ihrer Organisation verfügbar sind. Wenn Sie beispielsweise die Option 'Zeichnen' deaktivieren, wird sie den Mitgliedern Ihrer Organisation im Signatur-Widget nicht angezeigt, während die anderen drei Optionen weiterhin zugänglich bleiben.",
"indexing-public-profile": "Erlaube die Indexierung des öffentlichen Profils durch Suchmaschinen",
"user-created-successfully": "Benutzer erfolgreich erstellt.",
"only-15-reminder-allowed": "Sie können bis zu 15 automatische Erinnerungen festlegen. Wenn zum Beispiel 'TimeToComplete' auf 15 Tage und 'RemindOnceInEvery' auf 1 Tag eingestellt ist, erreichen Sie das maximale Limit von 15 Erinnerungen. Passen Sie Ihre Einstellungen entsprechend an."
}
@@ -79,6 +79,7 @@
"Storage": "Storage",
"Signing certificate": "Signing certificate",
"Teams": "Teams",
"General": "General",
"Teams-Children": {
"Organizations": "Organizations",
"OrgAdmins": "OrgAdmins"
@@ -149,7 +150,8 @@
"Copy Public URL": "Copy public URL",
"extend-expiry-date": "Extend expiry date",
"Duplicate Template": "Duplicate template",
"Duplicate": "Duplicate"
"Duplicate": "Duplicate",
"daily-mail-quota": "Daily Email Quota"
},
"report-heading": {
"Sr.No": "Sr.No",
@@ -849,5 +851,10 @@
"draft-template-info-p1": "To make your template public, it must either contain a single role, or, if it includes multiple roles, all additional roles must already be assigned to signers. The unassigned public role should remain empty and must be placed in the first position.",
"visit-below-link": "Visit below link to know more -",
"storage-help": "Enabling BYOC lets you connect your own S3 storage so your files remain entirely under your control—no external copies retained. If data autonomy matters to you, consider upgrading to Teams to unlock this feature.",
"daily-quota-reached": "Youve reached your daily quota. For assistance, please contact quotas@opensignlabs.com."
"daily-quota-reached": "Youve reached your daily quota. For assistance, please contact quotas@opensignlabs.com.",
"enabled-signature-type": "Enabled Signature Types",
"enabled-signature-type-help": "The 'Enabled Signature Types' setting determines which signature options are available across your organization. For example, if you disable the 'Draw' option, members of your organization will not see it in the signature widget, while the other three options will remain accessible.",
"indexing-public-profile": "Allow indexing of public profile by search engines",
"user-created-successfully": "user created successfully.",
"only-15-reminder-allowed": "You can set up to 15 automatic reminders. For example, if 'TimeToComplete' is 15 days and 'RemindOnceInEvery' is 1 day, you'll reach the maximum limit of 15 reminders. Adjust your settings accordingly."
}
@@ -79,6 +79,7 @@
"Storage": "Almacenamiento",
"Signing certificate": "Certificado de firma",
"Teams": "Equipos",
"General": "General",
"Teams-Children": {
"Organizations": "Organizaciones",
"OrgAdmins": "OrgAdmins"
@@ -149,7 +150,8 @@
"Copy Public URL": "Copiar URL pública",
"extend-expiry-date": "Date d'expiration",
"Duplicate Template": "Plantilla duplicada",
"Duplicate": "Duplicada"
"Duplicate": "Duplicada",
"daily-mail-quota": "Cuota diaria de correos electrónicos"
},
"report-heading": {
"Sr.No": "Nº",
@@ -765,8 +767,6 @@
"term-cond-p3": "Recibirá y firmará documentos electrónicamente a través de {{appName}}.",
"term-cond-p4": "Su firma electrónica es jurídicamente vinculante y equivalente a una firma manuscrita.",
"term-cond-h2": "2. Consentimiento para el uso de registros y firmas electrónicas",
"js-snippet-msg-2": "Administrar plantillas",
"js-snippet-msg-3": "página.",
"term-cond-p5": "Al aceptar esta Divulgación:",
"term-cond-p6": "Usted acepta realizar transacciones electrónicas con el Remitente utilizando {{appName}} y comprende que este consentimiento es válido hasta que se retire.",
"term-cond-p7": "Usted acepta revisar, firmar y devolver documentos electrónicamente utilizando {{appName}}.",
@@ -802,6 +802,8 @@
"term-cond-p30": " o correo electrónico",
"js-snippet-msg": "Para integrar plantillas {{appName}} en sus sitios web HTML o páginas de destino, puede utilizar el siguiente código:",
"js-snippet-msg-1": "Puede obtener el TemplateId en la página Administrar plantillas",
"js-snippet-msg-2": "Administrar plantillas",
"js-snippet-msg-3": "página.",
"agrrement-alert": "Para continuar, debe consentir la divulgación de registros y firmas electrónicas.",
"webhook-already-exists": "¡La URL ya existe! Pruebe con uno diferente.",
"webhook-must-be-secure": "La URL del webhook debe ser segura y utilizar https://",
@@ -848,7 +850,11 @@
"agreement-note": "Nota: Aceptar esto no significa que esté firmando el documento de inmediato. Esto solo le permite revisar el documento electrónicamente. Tendrá la oportunidad de leerlo en su totalidad y decidir si desea firmarlo después.",
"draft-template-info-p1": "Para hacer que tu plantilla sea pública, debe contener un único rol o, si incluye múltiples roles, todos los roles adicionales deben estar ya asignados a firmantes. El rol público no asignado debe permanecer vacío y debe estar en la primera posición.",
"visit-below-link": "Visita el siguiente enlace para saber más -",
"upgrade-to-team-plan": "Actualizar a team plan",
"storage-help": "Habilitar BYOC te permite conectar tu propio almacenamiento S3 para que tus archivos permanezcan completamente bajo tu control, sin copias externas retenidas. Si la autonomía de los datos es importante para ti, considera actualizar a Teams para desbloquear esta función.",
"daily-quota-reached": "Youve reached your daily quota. For assistance, please contact quotas@opensignlabs.com."
"daily-quota-reached": "Ha alcanzado su cuota diaria. Para obtener ayuda, comuníquese con quotas@opensignlabs.com.",
"enabled-signature-type": "Tipos de firma habilitados",
"enabled-signature-type-help": "La configuración de 'Tipos de firma habilitados' determina qué opciones de firma están disponibles en su organización. Por ejemplo, si desactiva la opción 'Dibujar', los miembros de su organización no la verán en el widget de firma, mientras que las otras tres opciones seguirán siendo accesibles.",
"indexing-public-profile": "Permitir la indexación del perfil público por los motores de búsqueda",
"user-created-successfully": "Usuario creado con éxito.",
"only-15-reminder-allowed": "Puede configurar hasta 15 recordatorios automáticos. Por ejemplo, si 'TimeToComplete' es de 15 días y 'RemindOnceInEvery' es de 1 día, alcanzará el límite máximo de 15 recordatorios. Ajuste su configuración en consecuencia."
}
@@ -79,6 +79,7 @@
"Storage": "Stockage",
"Signing certificate": "Certificat de signature",
"Teams": "Équipes",
"General": "Général",
"Teams-Children": {
"Organizations": "Organisations",
"OrgAdmins": "OrgAdmins"
@@ -170,7 +171,8 @@
"Copy Public URL": "Copier l'URL publique",
"extend-expiry-date": "Prolonger la date d'expiration",
"Duplicate Template": "dupliquer le modèle",
"Duplicate": "Double"
"Duplicate": "Double",
"daily-mail-quota": "Quota d'e-mails quotidien"
},
"report-help": {
"Draft Documents": "Il s'agit de documents que vous avez commencés mais que vous n'avez pas finalisés pour envoi.",
@@ -450,6 +452,7 @@
"add-recipients": "Ajouter des destinataires",
"loading-mssg": "Cela pourrait prendre du temps",
"send-mail": "Envoyer un mail",
"signature-field-widget": "Au moins un champ de signature doit être ajouté pour chaque utilisateur. Vous n'avez pas ajouté de champs de signature pour {{signersName}}",
"placeholder-alert-1": "Veuillez vous assurer qu'au moins un widget de signature est ajouté pour tous les destinataires.",
"placeholder-alert-2": "Veuillez confirmer que vous avez rempli le champ de texte.",
"placeholder-alert-3": "Etes-vous sûr de vouloir envoyer ce document pour signature ? ",
@@ -557,7 +560,8 @@
"do-not-access-contact-admin": "Vous n'y avez pas accès, veuillez contacter l'administrateur.",
"filed-required-correctly": "Veuillez remplir correctement les informations requises.",
"admin-created": "Administrateur créé",
"invalid-masterkey": "Clé principale invalide", "master-key": "La clef maitresse",
"invalid-masterkey": "Clé principale invalide",
"master-key": "La clef maitresse",
"profile-update-alert": "Mise à jour du profil réussie.",
"date": "Date",
"report-not-found": "Rapport introuvable",
@@ -847,5 +851,10 @@
"draft-template-info-p1": "Pour rendre votre modèle public, il doit contenir un seul rôle ou, s'il inclut plusieurs rôles, tous les rôles supplémentaires doivent déjà être attribués aux signataires. Le rôle public non attribué doit rester vide et être placé en première position.",
"visit-below-link": "Visitez le lien ci-dessous pour en savoir plus -",
"storage-help": "Activer BYOC vous permet de connecter votre propre stockage S3 afin que vos fichiers restent entièrement sous votre contrôle, sans copie externe conservée. Si l'autonomie des données est importante pour vous, envisagez de passer à l'offre Teams pour débloquer cette fonctionnalité.",
"daily-quota-reached": "Youve reached your daily quota. For assistance, please contact quotas@opensignlabs.com."
"daily-quota-reached": "Vous avez atteint votre quota quotidien. Pour obtenir de l'aide, veuillez contacter quotas@opensignlabs.com.",
"enabled-signature-type": "Types de signature activés",
"enabled-signature-type-help": "Le paramètre 'Types de signature activés' détermine quelles options de signature sont disponibles dans votre organisation. Par exemple, si vous désactivez l'option 'Dessiner', les membres de votre organisation ne la verront pas dans le widget de signature, tandis que les trois autres options resteront accessibles.",
"indexing-public-profile": "Autoriser l'indexation du profil public par les moteurs de recherche",
"user-created-successfully": "Utilisateur créé avec succès.",
"only-15-reminder-allowed": "Vous pouvez définir jusqu'à 15 rappels automatiques. Par exemple, si 'TimeToComplete' est de 15 jours et 'RemindOnceInEvery' est de 1 jour, vous atteindrez la limite maximale de 15 rappels. Ajustez vos paramètres en conséquence."
}
@@ -79,6 +79,7 @@
"Storage": "Archiviazione",
"Signing certificate": "Certificato di firma",
"Teams": "Squadre",
"General": "Generale",
"Teams-Children": {
"Organizations": "Organizzazioni",
"OrgAdmins": "OrgAdmins"
@@ -149,7 +150,8 @@
"Copy Public URL": "Copia URL pubblico",
"extend-expiry-date": "Estendi data di scadenza",
"Duplicate Template": "Duplica modello",
"Duplicate": "Duplica"
"Duplicate": "Duplica",
"daily-mail-quota": "Quota e-mail giornaliera"
},
"report-heading": {
"Sr.No": "Nr.",
@@ -450,6 +452,7 @@
"add-recipients": "Aggiungi destinatari",
"loading-mssg": "Questo potrebbe richiedere del tempo",
"send-mail": "Invia Mail",
"signature-field-widget":"È necessario aggiungere almeno un campo firma per ogni utente. Non hai aggiunto campi firma per {{signersName}}",
"placeholder-alert-1": "Assicurati che sia stato aggiunto almeno un widget per la firma per tutti i destinatari.",
"placeholder-alert-2": "Conferma di aver compilato il campo di testo.",
"placeholder-alert-3": "Sei sicuro di voler inviare questo documento per le firme?",
@@ -848,5 +851,10 @@
"draft-template-info-p1": "Per rendere il tuo modello pubblico, deve contenere un solo ruolo oppure, se include più ruoli, tutti i ruoli aggiuntivi devono essere già assegnati ai firmatari. Il ruolo pubblico non assegnato deve rimanere vuoto e deve essere posizionato per primo.",
"visit-below-link": "Visita il link qui sotto per saperne di più -",
"storage-help": "Abilitare BYOC ti consente di collegare il tuo archivio S3 in modo che i tuoi file rimangano completamente sotto il tuo controllo, senza copie esterne conservate. Se l'autonomia dei dati è importante per te, considera l'upgrade a Teams per sbloccare questa funzionalità.",
"daily-quota-reached": "Youve reached your daily quota. For assistance, please contact quotas@opensignlabs.com."
"daily-quota-reached": "Hai raggiunto la tua quota giornaliera. Per assistenza, contatta quotas@opensignlabs.com.",
"enabled-signature-type": "Tipi di firma abilitati",
"enabled-signature-type-help": "L'impostazione 'Tipi di firma abilitati' determina quali opzioni di firma sono disponibili nella tua organizzazione. Ad esempio, se disabiliti l'opzione 'Disegna', i membri della tua organizzazione non la vedranno nel widget della firma, mentre le altre tre opzioni resteranno accessibili.",
"indexing-public-profile": "Consenti l'indicizzazione del profilo pubblico dai motori di ricerca",
"user-created-successfully": "Utente creato con successo.",
"only-15-reminder-allowed": "Puoi impostare fino a 15 promemoria automatici. Ad esempio, se 'TimeToComplete' è di 15 giorni e 'RemindOnceInEvery' è di 1 giorno, raggiungerai il limite massimo di 15 promemoria. Regola le tue impostazioni di conseguenza."
}
+152 -153
View File
@@ -76,167 +76,167 @@ const AddUser = (props) => {
setIsFormLoader(true);
const res = await checkUserExist();
if (res) {
props.setIsAlert({ type: "danger", msg: t("user-already-exist") });
props.showAlert("danger", t("user-already-exist"));
setIsFormLoader(false);
setTimeout(() => props.setIsAlert({ type: "success", msg: "" }), 1000);
} else {
try {
const extUser = new Parse.Object("contracts_Users");
extUser.set("Name", formdata.name);
if (formdata.phone) {
extUser.set("Phone", formdata.phone);
}
extUser.set("Email", formdata.email);
extUser.set("UserRole", `contracts_${formdata.role}`);
if (formdata?.team) {
extUser.set("TeamIds", [
{
__type: "Pointer",
className: "contracts_Teams",
objectId: formdata.team
}
]);
}
if (localUser && localUser.OrganizationId) {
extUser.set("OrganizationId", {
__type: "Pointer",
className: "contracts_Organizations",
objectId: localUser.OrganizationId.objectId
});
}
if (localUser && localUser.Company) {
extUser.set("Company", localUser.Company);
}
if (localStorage.getItem("TenantId")) {
extUser.set("TenantId", {
__type: "Pointer",
className: "partners_Tenant",
objectId: localStorage.getItem("TenantId")
});
}
const timezone = usertimezone;
if (timezone) {
extUser.set("Timezone", timezone);
}
if (localStorage.getItem("TenantId")) {
try {
const _users = Parse.Object.extend("User");
const _user = new _users();
_user.set("name", formdata.name);
_user.set("username", formdata.email);
_user.set("email", formdata.email);
_user.set("password", formdata.password);
const extUser = new Parse.Object("contracts_Users");
extUser.set("Name", formdata.name);
if (formdata.phone) {
_user.set("phone", formdata.phone);
extUser.set("Phone", formdata.phone);
}
extUser.set("Email", formdata.email);
extUser.set("UserRole", `contracts_${formdata.role}`);
if (formdata?.team) {
extUser.set("TeamIds", [
{
__type: "Pointer",
className: "contracts_Teams",
objectId: formdata.team
}
]);
}
if (localUser && localUser.OrganizationId) {
extUser.set("OrganizationId", {
__type: "Pointer",
className: "contracts_Organizations",
objectId: localUser.OrganizationId.objectId
});
}
if (localUser && localUser.Company) {
extUser.set("Company", localUser.Company);
}
const user = await _user.save();
if (user) {
const currentUser = Parse.User.current();
extUser.set(
"CreatedBy",
Parse.User.createWithoutData(currentUser.id)
);
extUser.set("UserId", user);
const acl = new Parse.ACL();
acl.setPublicReadAccess(true);
acl.setPublicWriteAccess(true);
acl.setReadAccess(currentUser.id, true);
acl.setWriteAccess(currentUser.id, true);
extUser.setACL(acl);
const res = await extUser.save();
const parseData = JSON.parse(JSON.stringify(res));
if (props.closePopup) {
props.closePopup();
}
if (props.handleUserData) {
if (formdata?.team) {
const team = teamList.find(
(x) => x.objectId === formdata.team
);
parseData.TeamIds = parseData.TeamIds.map((y) =>
y.objectId === team.objectId ? team : y
);
}
props.handleUserData(parseData);
}
setIsFormLoader(false);
setFormdata({
name: "",
email: "",
phone: "",
team: "",
role: ""
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();
if (user) {
const currentUser = Parse.User.current();
extUser.set(
"CreatedBy",
Parse.User.createWithoutData(currentUser.id)
);
extUser.set("UserId", user);
const acl = new Parse.ACL();
acl.setPublicReadAccess(true);
acl.setPublicWriteAccess(true);
acl.setReadAccess(currentUser.id, true);
acl.setWriteAccess(currentUser.id, true);
extUser.setACL(acl);
const res = await extUser.save();
const parseData = JSON.parse(JSON.stringify(res));
if (props.closePopup) {
props.closePopup();
}
if (props.handleUserData) {
if (formdata?.team) {
const team = teamList.find(
(x) => x.objectId === formdata.team
);
parseData.TeamIds = parseData.TeamIds.map((y) =>
y.objectId === team.objectId ? team : y
);
}
props.handleUserData(parseData);
}
setIsFormLoader(false);
setFormdata({
name: "",
email: "",
phone: "",
team: "",
role: ""
});
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"));
}
}
} 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: ""
});
}
console.log("err", err);
setIsFormLoader(false);
props.showAlert("danger", t("something-went-wrong-mssg"));
}
} catch (err) {
console.log("err", err);
setIsFormLoader(false);
props.setIsAlert({
type: "danger",
msg: t("something-went-wrong-mssg")
});
} finally {
setTimeout(
() => props.setIsAlert({ type: "success", msg: "" }),
1500
);
} else {
props.showAlert("danger", t("something-went-wrong-mssg"));
}
}
}
@@ -259,8 +259,7 @@ const AddUser = (props) => {
const copytoclipboard = (text) => {
copytoData(text);
props.setIsAlert({ type: "success", msg: t("copied") });
setTimeout(() => props.setIsAlert({ type: "success", msg: "" }), 1500); // Reset copied state after 1.5 seconds
props.showAlert("success", t("copied"));
};
return (
<div className="shadow-md rounded-box my-[1px] p-3 bg-base-100 relative">
@@ -64,6 +64,13 @@ const EditTemplate = ({
const IsEnableOTP = formData.IsEnableOTP === "true" ? true : false;
const allowModify = formData?.AllowModifications || false;
let reminderDate = {};
const remindOnceInEvery = formData?.RemindOnceInEvery;
const TimeToCompleteDays = parseInt(formData?.TimeToCompleteDays);
const reminderCount = TimeToCompleteDays / remindOnceInEvery;
if (AutoReminder && reminderCount > 15) {
alert(t("only-15-reminder-allowed"));
return;
}
if (AutoReminder) {
const RemindOnceInEvery = parseInt(formData?.RemindOnceInEvery);
const ReminderDate = new Date(template?.createdAt);
@@ -1,25 +1,19 @@
import React, { useState } from "react";
import axios from "axios";
import { handleToPrint } from "../../constant/Utils";
import { themeColor, emailRegex } from "../../constant/const";
import { emailRegex } from "../../constant/const";
import Loader from "../../primitives/Loader";
import ModalUi from "../../primitives/ModalUi";
import { useTranslation } from "react-i18next";
import Parse from "parse";
function EmailComponent({
isEmail,
pdfUrl,
setIsEmail,
setSuccessEmail,
pdfDetails,
sender,
setIsAlert,
extUserId,
setIsDownloadModal
}) {
const { t } = useTranslation();
const appName =
"OpenSign™";
const [emailList, setEmailList] = useState([]);
const [emailValue, setEmailValue] = useState("");
const [isLoading, setIsLoading] = useState(false);
@@ -28,73 +22,11 @@ function EmailComponent({
const isAndroid = /Android/i.test(navigator.userAgent);
//function for send email
const sendEmail = async () => {
const pdfName = pdfDetails[0]?.Name;
setIsLoading(true);
let sendMail;
const docId = pdfDetails?.[0]?.objectId || "";
let presignedUrl = pdfUrl;
try {
const axiosRes = await axios.post(
`${localStorage.getItem("baseUrl")}/functions/getsignedurl`,
{
url: pdfUrl,
docId: docId,
},
{
headers: {
"content-type": "Application/json",
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
}
}
);
presignedUrl = axiosRes.data.result;
} catch (err) {
console.log("err in getsignedurl", err);
}
for (let i = 0; i < emailList.length; i++) {
try {
let url = `${localStorage.getItem("baseUrl")}functions/sendmailv3`;
const headers = {
"Content-Type": "application/json",
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
sessionToken: localStorage.getItem("accesstoken")
};
const logo =
`<img src='https://qikinnovation.ams3.digitaloceanspaces.com/logo.png' height='50' style='padding:20px'/>`;
const opurl =
` <a href='www.opensignlabs.com' target=_blank>here</a>`;
let params = {
extUserId: extUserId,
pdfName: pdfName,
url: presignedUrl,
recipient: emailList[i],
subject: `${sender.name} has signed the doc - ${pdfName}`,
replyto:
pdfDetails?.[0]?.ExtUserPtr?.Email ||
"",
from:
sender.email,
html:
`<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8'/></head><body><div style='background-color:#f5f5f5;padding:20px'><div style='background-color:white'><div>` +
`${logo}</div><div style='padding:2px;font-family:system-ui;background-color:${themeColor}'><p style='font-size:20px;font-weight:400;color:white;padding-left:20px'>Document Copy</p></div><div>` +
`<p style='padding:20px;font-family:system-ui;font-size:14px'>A copy of the document <strong>${pdfName}</strong> is attached to this email. Kindly download the document from the attachment.</p>` +
`</div></div><div><p>This is an automated email from ${appName}. For any queries regarding this email, please contact the sender ${sender.email} directly. ` +
`If you think this email is inappropriate or spam, you may file a complaint with ${appName}${opurl}.</p></div></div></body></html>`
};
sendMail = await axios.post(url, params, { headers: headers });
} catch (error) {
console.log("error", error);
setIsLoading(false);
setIsEmail(false);
setIsAlert({
isShow: true,
alertMessage: t("something-went-wrong-mssg")
});
}
}
if (sendMail?.data?.result?.status === "success") {
const params = { docId: pdfDetails?.[0]?.objectId, recipients: emailList };
const sendmail = await Parse.Cloud.run("forwarddoc", params);
console.log("sendmail ", sendmail);
if (sendmail?.status === "success") {
setSuccessEmail(true);
setIsEmail(false);
setTimeout(() => {
@@ -76,6 +76,7 @@ const changeDateToMomentFormat = (format) => {
};
//function to get default format
const getDefaultFormat = (dateFormat) => dateFormat || "MM/dd/yyyy";
//function to convert formated date to new Date() format
const getDefaultDate = (dateStr, format) => {
//get valid date format for moment to convert formated date to new Date() format
@@ -90,6 +91,7 @@ const getDefaultDate = (dateStr, format) => {
return date;
}
};
function Placeholder(props) {
//'isTouchDevice' is used to detect whether a device has a touchscreen or is mouse-based
const isTouchDevice = navigator.maxTouchPoints > 0;
@@ -437,7 +439,9 @@ function Placeholder(props) {
//checking widget's type and open widget copy modal for required widgets
if (
["signature", textWidget, "stamp", "initials"].includes(props.pos.type)
["signature", textInputWidget, textWidget, "stamp", "initials"].includes(
props.pos.type
)
) {
props.setIsPageCopy(true);
props.setSignKey(props.pos.key);
@@ -1083,7 +1087,7 @@ function Placeholder(props) {
isSignYourself={props.isSignYourself}
isSelfSign={props.isSelfSign}
signerObjId={props.signerObjId}
handleUserName={props.handleUserName}
calculateFontsize={props.calculateFontsize}
pdfDetails={props?.pdfDetails && props?.pdfDetails[0]}
isNeedSign={props.isNeedSign}
setSelectDate={setSelectDate}
@@ -30,6 +30,7 @@ function PlaceholderType(props) {
const inputRef = useRef(null);
const [textValue, setTextValue] = useState();
const [selectedCheckbox, setSelectedCheckbox] = useState([]);
const [hint, setHint] = useState("");
const years = range(1950, getYear(new Date()) + 16, 1);
const fontSize = props.calculateFont(props.pos.options?.fontSize);
const fontColor = props.pos.options?.fontColor || "black";
@@ -150,6 +151,11 @@ function PlaceholderType(props) {
if (defaultData) {
setTextValue(defaultData);
}
if (props.pos?.options?.hint) {
setHint(props.pos?.options.hint);
} else {
setHint(props.pos?.type);
}
} else if ([textInputWidget].includes(props.pos?.type)) {
const defaultData = props.pos?.options?.defaultValue;
if (defaultData) {
@@ -158,7 +164,6 @@ function PlaceholderType(props) {
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [props.pos?.options?.defaultValue]);
const ExampleCustomInput = forwardRef(({ value, onClick }, ref) => (
<div
style={{
@@ -327,13 +332,20 @@ function PlaceholderType(props) {
/>
) : (
<div className={widgetCls}>
{props?.handleUserName &&
props?.handleUserName(
props?.data?.Id,
props?.data?.Role,
widgetTypeTraslation,
props.pos
)}
{props.pos.type && (
<div
style={{
fontSize: props.pos
? props.calculateFontsize(props.pos)
: "11px"
}}
className="font-medium"
>
{props.isNeedSign
? props.pos?.options?.hint || widgetTypeTraslation
: widgetTypeTraslation}
</div>
)}
</div>
);
case "stamp":
@@ -346,13 +358,20 @@ function PlaceholderType(props) {
/>
) : (
<div className={widgetCls}>
{props?.handleUserName &&
props?.handleUserName(
props?.data?.Id,
props?.data?.Role,
widgetTypeTraslation,
props.pos
)}
{props.pos.type && (
<div
style={{
fontSize: props.pos
? props.calculateFontsize(props.pos)
: "11px"
}}
className="font-medium"
>
{props.isNeedSign
? props.pos?.options?.hint || widgetTypeTraslation
: widgetTypeTraslation}
</div>
)}
</div>
);
case "checkbox":
@@ -533,13 +552,20 @@ function PlaceholderType(props) {
/>
) : (
<div className={widgetCls}>
{props?.handleUserName &&
props?.handleUserName(
props?.data?.Id,
props?.data?.Role,
widgetTypeTraslation,
props.pos
)}
{props.pos.type && (
<div
style={{
fontSize: props.pos
? props.calculateFontsize(props.pos)
: "11px"
}}
className="font-medium text-center"
>
{props.isNeedSign
? props.pos?.options?.hint || widgetTypeTraslation
: widgetTypeTraslation}
</div>
)}
</div>
);
case "name":
@@ -548,7 +574,7 @@ function PlaceholderType(props) {
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
<textarea
ref={inputRef}
placeholder={t("widgets-name.name")}
placeholder={hint}
rows={1}
onKeyDown={handleEnterPress}
value={textValue}
@@ -580,7 +606,7 @@ function PlaceholderType(props) {
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
<textarea
ref={inputRef}
placeholder={t("widgets-name.company")}
placeholder={hint}
rows={1}
onKeyDown={handleEnterPress}
value={textValue}
@@ -611,7 +637,7 @@ function PlaceholderType(props) {
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
<textarea
ref={inputRef}
placeholder={t("widgets-name.job title")}
placeholder={hint}
rows={1}
onKeyDown={handleEnterPress}
value={textValue}
@@ -712,13 +738,20 @@ function PlaceholderType(props) {
/>
) : (
<div className={widgetCls}>
{props?.handleUserName &&
props?.handleUserName(
props?.data?.Id,
props?.data?.Role,
widgetTypeTraslation,
props.pos
)}
{props.pos.type && (
<div
style={{
fontSize: props.pos
? props.calculateFontsize(props.pos)
: "11px"
}}
className="font-medium text-center"
>
{props.isNeedSign
? props.pos?.options?.hint || widgetTypeTraslation
: widgetTypeTraslation}
</div>
)}
</div>
);
case "email":
@@ -727,7 +760,7 @@ function PlaceholderType(props) {
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
<textarea
ref={inputRef}
placeholder={t("widgets-name.email")}
placeholder={hint}
rows={1}
onKeyDown={(e) => {
// Prevent new line on Enter key press
@@ -848,13 +881,20 @@ function PlaceholderType(props) {
) : (
<div className={widgetCls}>
{props.pos.isStamp ? <div>stamp</div> : <div>signature</div>}
{props?.handleUserName &&
props?.handleUserName(
props?.data?.Id,
props?.data?.Role,
null,
props.pos
)}
{props.pos.type && (
<div
style={{
fontSize: props.pos
? props.calculateFontsize(props.pos)
: "11px"
}}
className="font-medium"
>
{props.isNeedSign
? props.pos?.options?.hint || widgetTypeTraslation
: widgetTypeTraslation}
</div>
)}
</div>
);
}
+9 -101
View File
@@ -89,35 +89,6 @@ function RenderPdf(props) {
sign?.Id === data?.Id || sign?.objectId === data?.signerObjId
)
: [];
const handleAllUserName = (Id, Role, type, pos) => {
return (
<React.Fragment>
<div className="text-black text-[8px] font-bold">
{
props.pdfDetails[0].Signers?.find(
(signer) => signer.objectId === data.signerObjId
)?.Name
}
</div>
{type && (
<div
style={{ fontSize: pos ? calculateFontsize(pos) : "11px" }}
className="font-bold"
>
{type}
</div>
)}
{Role && (
<div
style={{ fontSize: pos ? calculateFontsize(pos) : "8px" }}
className="text-black font-medium"
>
{`(${Role})`}
</div>
)}
</React.Fragment>
);
};
return (
checkSign.length === 0 &&
data?.placeHolder?.map((placeData, key) => {
@@ -148,7 +119,6 @@ function RenderPdf(props) {
isSignYourself={false}
posWidth={posWidth}
posHeight={posHeight}
handleUserName={handleAllUserName}
isDragging={props.isDragging}
pdfDetails={props.pdfDetails}
setIsInitial={props.setIsInitial}
@@ -185,6 +155,7 @@ function RenderPdf(props) {
fontColor={props.fontColor}
setFontColor={props.setFontColor}
setRequestSignTour={props.setRequestSignTour}
calculateFontsize={calculateFontsize}
/>
</React.Fragment>
)
@@ -206,73 +177,6 @@ function RenderPdf(props) {
return `${width / 10}px`;
}
};
//function for render placeholder block over pdf document
const handleUserName = (Id, Role, type, pos) => {
if (Id) {
const checkSign = props.signersdata.find((sign) => sign.Id === Id);
if (checkSign?.Name) {
return (
<>
<div
style={{
fontSize: pos ? calculateFontsize(pos) : "8px"
}}
className="text-black font-medium"
>
{checkSign?.Name}
</div>
{type && (
<div
style={{
fontSize: pos ? calculateFontsize(pos) : "11px"
}}
className=" font-bold"
>
{type}
</div>
)}
{Role && (
<div
style={{
fontSize: pos ? calculateFontsize(pos) : "8px"
}}
className="text-black text-[8px] font-medium"
>
{`(${Role})`}
</div>
)}
</>
);
} else {
return (
<>
{type && (
<div
style={{ fontSize: pos ? calculateFontsize(pos) : "11px" }}
className=" font-bold"
>
{type}
</div>
)}
<div
style={{ fontSize: pos ? calculateFontsize(pos) : "8px" }}
className="text-black font-medium"
>
{Role}
</div>
</>
);
}
} else {
return (
<>
{type && <div className="text-[11px] font-bold">{type}</div>}
<div className="text-black text-[8px] font-medium">{Role}</div>
</>
);
}
};
const pdfDataBase64 = `data:application/pdf;base64,${props.pdfBase64Url}`;
//calculate render height of pdf in mobile view
const handlePageLoadSuccess = (page) => {
@@ -361,7 +265,6 @@ function RenderPdf(props) {
handleLinkUser={
props.handleLinkUser
}
handleUserName={handleUserName}
isSignYourself={false}
posWidth={posWidth}
posHeight={posHeight}
@@ -403,6 +306,9 @@ function RenderPdf(props) {
props.unSignedWidgetId
}
isFreeResize={true}
calculateFontsize={
calculateFontsize
}
/>
</React.Fragment>
);
@@ -448,7 +354,6 @@ function RenderPdf(props) {
setWidgetType={props.setWidgetType}
setSelectWidgetId={props.setSelectWidgetId}
selectWidgetId={props.selectWidgetId}
handleUserName={handleUserName}
setIsCheckbox={props.setIsCheckbox}
setValidateAlert={props.setValidateAlert}
setCurrWidgetsDetails={
@@ -468,6 +373,7 @@ function RenderPdf(props) {
setIsResize={props.setIsResize}
isFreeResize={false}
isOpenSignPad={true}
calculateFontsize={calculateFontsize}
/>
)
);
@@ -573,7 +479,6 @@ function RenderPdf(props) {
handleLinkUser={
props.handleLinkUser
}
handleUserName={handleUserName}
isSignYourself={false}
posWidth={posWidth}
posHeight={posHeight}
@@ -615,6 +520,9 @@ function RenderPdf(props) {
props.unSignedWidgetId
}
isFreeResize={true}
calculateFontsize={
calculateFontsize
}
/>
</React.Fragment>
);
@@ -665,7 +573,6 @@ function RenderPdf(props) {
setWidgetType={props.setWidgetType}
setSelectWidgetId={props.setSelectWidgetId}
selectWidgetId={props.selectWidgetId}
handleUserName={handleUserName}
setIsCheckbox={props.setIsCheckbox}
setValidateAlert={props.setValidateAlert}
setCurrWidgetsDetails={
@@ -686,6 +593,7 @@ function RenderPdf(props) {
setIsResize={props.setIsResize}
isFreeResize={false}
isOpenSignPad={true}
calculateFontsize={calculateFontsize}
/>
</React.Fragment>
);
@@ -31,7 +31,11 @@ const WidgetNameModal = (props) => {
name: props.defaultdata?.options?.name || "",
defaultValue: props.defaultdata?.options?.defaultValue || "",
status: props.defaultdata?.options?.status || "required",
hint: props.defaultdata?.options?.hint || "",
hint:
props.defaultdata?.options?.hint ||
(props.defaultdata?.type === textInputWidget
? "Enter text"
: `Enter ${props.defaultdata?.options?.name}`),
textvalidate:
props.defaultdata?.options?.validation?.type === "regex"
? props.defaultdata?.options?.validation?.pattern
@@ -272,12 +276,13 @@ const WidgetNameModal = (props) => {
</div>
</div>
)}
{props.defaultdata?.type === textInputWidget && (
{props.defaultdata?.type !== textWidget && (
<div className="mb-[0.75rem]">
<label htmlFor="hint" className="text-[13px]">
{t("hint")}
</label>
<input
maxLength={40}
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
name="hint"
value={formdata.hint}
@@ -295,7 +300,7 @@ const WidgetNameModal = (props) => {
].includes(props.defaultdata?.type) && (
<div className="flex flex-col md:flex-row md:items-center gap-3 mb-3">
<div className="flex items-center gap-2 ">
<span>{t("font-size")}:</span>
<span className="whitespace-nowrap">{t("font-size")}: </span>
<select
className="ml-[7px] w-[60%] op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs"
value={
+9 -3
View File
@@ -642,7 +642,13 @@ export const createDocument = async (
...Bcc,
...RedirectUrl
};
const remindOnceInEvery = Doc?.RemindOnceInEvery;
const TimeToCompleteDays = parseInt(Doc?.TimeToCompleteDays);
const reminderCount = TimeToCompleteDays / remindOnceInEvery;
const AutomaticReminders = Doc.autoreminder;
if (AutomaticReminders && reminderCount > 15) {
return { status: "error", id: "only-15-reminder-allowed" };
}
try {
const res = await axios.post(
`${localStorage.getItem("baseUrl")}classes/contracts_Document`,
@@ -660,7 +666,7 @@ export const createDocument = async (
}
} catch (err) {
console.log("axois err ", err);
return { status: "error", id: "Something Went Wrong!" };
return { status: "error", id: "something-went-wrong-mssg" };
}
}
};
@@ -985,7 +991,7 @@ export const addInitialData = (signerPos, setXyPosition, value, userId) => {
...item,
options: {
...item.options,
defaultValue: widgetData
defaultValue: item?.options?.defaultValue || widgetData
}
// Width:
// calculateInitialWidthHeight(item.type, widgetData).getWidth ||
+41 -12
View File
@@ -81,6 +81,15 @@ const Forms = (props) => {
const extUserData =
localStorage.getItem("Extand_Class") &&
JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
const sendinorder =
extUserData?.SendinOrder !== undefined && extUserData?.SendinOrder === false
? "false"
: "true";
const istourenabled =
extUserData?.IsTourEnabled !== undefined &&
extUserData?.IsTourEnabled === false
? "false"
: "true";
useEffect(() => {
handleReset();
return () => abortController.abort();
@@ -92,7 +101,12 @@ const Forms = (props) => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const initializeValues = async () => {
setFormData((obj) => ({ ...obj, NotifyOnSignatures: true }));
setFormData((obj) => ({
...obj,
NotifyOnSignatures: true,
SendinOrder: sendinorder,
IsTourEnabled: istourenabled
}));
};
function getFileAsArrayBuffer(file) {
@@ -374,14 +388,19 @@ const Forms = (props) => {
const isChecked = formData.SendinOrder === "false" ? false : true;
const isTourEnabled =
formData?.IsTourEnabled === "false" ? false : true;
const remindOnceInEvery = parseInt(formData.remindOnceInEvery);
const TimeToCompleteDays = parseInt(formData?.TimeToCompleteDays);
const AutomaticReminders = formData.autoreminder;
const reminderCount = TimeToCompleteDays / remindOnceInEvery;
if (AutomaticReminders && reminderCount > 15) {
alert(t("only-15-reminder-allowed"));
return;
}
object.set("SendinOrder", isChecked);
object.set("AutomaticReminders", formData.autoreminder);
object.set("RemindOnceInEvery", parseInt(formData.remindOnceInEvery));
object.set("AutomaticReminders", AutomaticReminders);
object.set("RemindOnceInEvery", remindOnceInEvery);
object.set("IsTourEnabled", isTourEnabled);
object.set(
"TimeToCompleteDays",
parseInt(formData?.TimeToCompleteDays)
);
object.set("TimeToCompleteDays", TimeToCompleteDays);
object.set("AllowModifications", false);
object.set("IsEnableOTP", false);
if (formData.NotifyOnSignatures !== undefined) {
@@ -437,14 +456,14 @@ const Forms = (props) => {
? "Note to myself"
: "Please review and sign this document",
TimeToCompleteDays: 15,
SendinOrder: "true",
SendinOrder: sendinorder,
password: "",
file: "",
NotifyOnSignatures: notifySign,
remindOnceInEvery: 5,
autoreminder: false,
IsEnableOTP: "false",
IsTourEnabled: "true",
IsTourEnabled: istourenabled,
RedirectUrl: "",
AllowModifications: false
});
@@ -454,7 +473,17 @@ const Forms = (props) => {
}
} catch (err) {
console.log("err ", err);
setIsAlert({ type: "danger", message: t("something-went-wrong-mssg") });
if (err.message === "only 15 reminder allowed") {
setIsAlert({
type: "danger",
message: t("only-15-reminder-allowed")
});
} else {
setIsAlert({
type: "danger",
message: t("something-went-wrong-mssg")
});
}
} finally {
setTimeout(() => setIsAlert({ type: "success", message: "" }), 1000);
setIsSubmit(false);
@@ -506,13 +535,13 @@ const Forms = (props) => {
? "Note to myself"
: "Please review and sign this document",
TimeToCompleteDays: 15,
SendinOrder: "true",
SendinOrder: sendinorder,
password: "",
file: "",
remindOnceInEvery: 5,
autoreminder: false,
IsEnableOTP: "false",
IsTourEnabled: "true",
IsTourEnabled: istourenabled,
NotifyOnSignatures: notifySign,
RedirectUrl: "",
AllowModifications: false
+27 -4
View File
@@ -68,6 +68,7 @@ import AddContact from "../primitives/AddContact";
function PlaceHolderSign() {
const { t } = useTranslation();
const copyUrlRef = useRef(null);
const appName =
"OpenSign™";
const editorRef = useRef();
@@ -1091,6 +1092,9 @@ function PlaceHolderSign() {
const copytoclipboard = (text) => {
copytoData(text);
if (copyUrlRef.current) {
copyUrlRef.current.textContent = text; // Update text safely
}
setCopied(true);
setTimeout(() => setCopied(false), 1500); // Reset copied state after 1.5 seconds
};
@@ -1311,13 +1315,13 @@ function PlaceHolderSign() {
setIsMailSend(true);
setIsLoading({ isLoad: false });
setIsUiLoading(false);
}
else if (sendMail?.data?.result?.status === "quota-reached") {
} else if (sendMail?.data?.result?.status === "quota-reached") {
setMailStatus("quotareached");
setIsSend(true);
setIsMailSend(true);
setIsUiLoading(false);
} else {
}
else {
setMailStatus("failed");
setIsSend(true);
setIsMailSend(true);
@@ -1569,6 +1573,15 @@ function PlaceHolderSign() {
isReadOnly: defaultdata?.isReadOnly || false
}
};
} else if (["signature"].includes(position.type)) {
return {
...position,
options: {
...position.options,
name: defaultdata.name,
hint: defaultdata?.hint || ""
}
};
} else {
return {
...position,
@@ -1577,6 +1590,7 @@ function PlaceHolderSign() {
name: defaultdata.name,
status: defaultdata.status,
defaultValue: defaultdata.defaultValue,
hint: defaultdata?.hint || "",
fontSize:
fontSize || currWidgetsDetails?.options?.fontSize || 12,
fontColor:
@@ -2086,6 +2100,11 @@ function PlaceHolderSign() {
<span className="h-[1px] w-[20%] bg-[#ccc]"></span>
</div>
<div className="my-3">{handleShareList()}</div>
<p
id="copyUrl"
ref={copyUrlRef}
className="hidden"
></p>
</>
)}
</div>
@@ -2127,7 +2146,11 @@ function PlaceHolderSign() {
</div>
) : (
<div className="mb-[10px]">
<p>{t("placeholder-alert-6")}</p>
{mailStatus === "dailyquotareached" ? (
<p>{t("daily-quota-reached")}</p>
) : (
<p>{t("placeholder-alert-6")}</p>
)}
{isCurrUser && (
<p className="mt-1">{t("placeholder-alert-5")}</p>
)}
+282 -136
View File
@@ -46,6 +46,8 @@ const Preferences = () => {
const [activeTab, setactiveTab] = useState(0);
const generaltab = { title: t("general"), icon: "fa-light fa-gears" };
const [tab, setTab] = useState([generaltab]);
const [sendinOrder, setSendinOrder] = useState(true);
const [isTourEnabled, setIsTourEnabled] = useState(false);
useEffect(() => {
fetchSignType();
@@ -90,6 +92,14 @@ const Preferences = () => {
const SignatureType = _getUser?.SignatureType || signatureTypes;
setSignatureType(SignatureType);
}
const sendinorder =
_getUser?.SendinOrder !== undefined ? _getUser?.SendinOrder : true;
setSendinOrder(sendinorder);
const istourenabled =
_getUser?.IsTourEnabled !== undefined
? _getUser?.IsTourEnabled
: true;
setIsTourEnabled(istourenabled);
}
} catch (err) {
console.log("err while getting user details", err);
@@ -142,6 +152,11 @@ const Preferences = () => {
params = { ...params, NotifyOnSignatures: isNotifyOnSignatures };
}
try {
params = {
...params,
SendinOrder: sendinOrder,
IsTourEnabled: isTourEnabled
};
const updateRes = await Parse.Cloud.run("updatepreferences", params);
if (updateRes) {
setIsAlert({ type: "success", msg: "Saved successfully." });
@@ -150,6 +165,8 @@ const Preferences = () => {
JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
if (extUser && extUser?.objectId) {
extUser.NotifyOnSignatures = isNotifyOnSignatures;
extUser.SendinOrder = sendinOrder;
extUser.IsTourEnabled = isTourEnabled;
const _extUser = JSON.parse(JSON.stringify(extUser));
localStorage.setItem("Extand_Class", JSON.stringify([_extUser]));
}
@@ -285,6 +302,9 @@ const Preferences = () => {
}
};
const handleTourInput = () => setIsTourEnabled(!isTourEnabled);
const handleSendinOrderInput = () => setSendinOrder(!sendinOrder);
return (
<React.Fragment>
<Title title={t("Preferences")} />
@@ -302,7 +322,7 @@ const Preferences = () => {
) : (
<div className="relative bg-base-100 text-base-content flex flex-col justify-center shadow-md rounded-box mb-3">
{isLoader && (
<div className="flex justify-center items-center absolute w-full h-full rounded-box bg-black/30">
<div className="flex z-[100] justify-center items-center absolute w-full h-full rounded-box bg-black/30">
<Loader />
</div>
)}
@@ -332,154 +352,280 @@ const Preferences = () => {
</div>
<div className="flex justify-center md:justify-start mt-3 md:mt-4 break-all">
{activeTab === 0 ? (
<>
<div className="ml-4 mt-1 mb-2 flex flex-col">
<div className="mb-[0.75rem]">
<label
htmlFor="signaturetype"
className="text-[14px] mb-[0.7rem] font-medium"
>
<div className="ml-4 mt-1 mb-2 flex flex-col">
<div className="mb-[0.75rem]">
<label
className="mb-[0.7rem] text-[12px]"
htmlFor="signaturetype"
>
<span className="font-medium text-[14px]">
{t("allowed-signature-types")}
<a
data-tooltip-id="signtypes-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>
<ReactTooltip
id="signtypes-tooltip"
className="z-[999]"
>
<div className="max-w-[200px] md:max-w-[450px] text-[11px]">
<p className="font-bold">
{t("allowed-signature-types")}
</p>
<p>{t("allowed-signature-types-help.p1")}</p>
<p 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>
</p>
</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
className="mr-[2px] op-checkbox op-checkbox-xs"
type="checkbox"
name="signaturetype"
onChange={() => handleCheckboxChange(i)}
checked={type.enabled}
/>
<div
className="text-[13px] font-medium hover:underline underline-offset-2 cursor-default capitalize"
title={`Enabling this allow signers to ${type.name} signature`}
>
{type?.name === "typed" ? "type" : type?.name}
</div>
</div>
))}
</div>
</div>
<div className="mb-[0.75rem]">
<label className="text-[14px] mb-[0.7rem] font-medium">
{t("notify-on-signatures")}
<a data-tooltip-id="nos-tooltip" className="ml-1">
<sup>
<i className="fa-light fa-question rounded-full border-[#33bbff] text-[#33bbff] text-[13px] border-[1px] py-[1.5px] px-[4px]"></i>
</sup>
</a>
<ReactTooltip 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>
</ReactTooltip>
</label>
<div className="flex flex-col md:flex-row md:gap-4">
</span>
<a data-tooltip-id="signtypes-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>
<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>
<p 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>
</p>
</div>
</ReactTooltip>
</label>
<div className="ml-[7px] flex flex-col md:flex-row gap-[10px] mb-[0.7rem]">
{signatureType.map((type, i) => (
<div
className={
"flex items-center gap-2 ml-2 mb-1"
}
key={i}
className="flex flex-row gap-[5px] items-center"
>
<input
className="mr-[2px] op-radio op-radio-xs"
type="radio"
onChange={() => handleNotifySignChange(true)}
checked={isNotifyOnSignatures === true}
className="mr-[2px] op-checkbox op-checkbox-xs"
type="checkbox"
name="signaturetype"
onChange={() => handleCheckboxChange(i)}
checked={type.enabled}
/>
<div className="text-[13px] font-medium cursor-default capitalize">
{t("yes")}
<div
className="text-[13px] font-medium hover:underline underline-offset-2 cursor-default capitalize"
title={`Enabling this allow signers to ${type.name} signature`}
>
{type?.name === "typed" ? "type" : type?.name}
</div>
</div>
<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(false)}
checked={isNotifyOnSignatures === false}
/>
<div className="text-[13px] font-medium cursor-default capitalize">
{t("no")}
</div>
</div>
))}
</div>
</div>
<div className="mb-[0.75rem]">
<label className="mb-[0.7rem] text-[12px]">
<span className="text-[14px] font-medium">
{t("notify-on-signatures")}
</span>
<a data-tooltip-id="nos-tooltip" className="ml-1">
<sup>
<i className="fa-light fa-question rounded-full border-[#33bbff] text-[#33bbff] text-[13px] border-[1px] py-[1.5px] px-[4px]"></i>
</sup>
</a>
<ReactTooltip id="nos-tooltip" className="z-[999]">
<div className="max-w-[200px] md:max-w-[450px]">
<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>
</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
className="mr-[2px] op-radio op-radio-xs"
type="radio"
onChange={() => handleNotifySignChange(true)}
checked={isNotifyOnSignatures === true}
/>
<div className="text-[13px] cursor-default capitalize">
{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={isNotifyOnSignatures === false}
/>
<div className="text-[13px] cursor-default capitalize">
{t("no")}
</div>
</div>
</div>
<div className="mb-[0.75rem]">
<TimezoneSelector
timezone={timezone}
setTimezone={setTimezone}
/>
</div>
<div className="mb-[0.75rem]">
<button
className="op-btn op-btn-primary"
onClick={handleSave}
</div>
<div className="mb-[0.75rem]">
<TimezoneSelector
timezone={timezone}
setTimezone={setTimezone}
/>
</div>
<div className="mb-[0.75rem] text-[12px]">
<label className="block mb-[0.7rem]">
<span className="text-[14px] font-medium">
{t("send-in-order")}
</span>
<a
data-tooltip-id="sendInOrder-tooltip"
className="ml-1"
>
{t("save")}
</button>
<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>
<ReactTooltip
id="sendInOrder-tooltip"
className="z-[999]"
>
<div className="max-w-[200px] md:max-w-[450px]">
<p className="font-bold">{t("send-in-order")}</p>
<p>{t("send-in-order-help.p1")}</p>
<p className="p-[5px]">
<ol className="list-disc">
<li>
<span className="font-bold">
{t("yes")}:{" "}
</span>
<span>{t("send-in-order-help.p2")}</span>
</li>
<li>
<span className="font-bold">{t("no")}: </span>
<span>{t("send-in-order-help.p3")}</span>
</li>
</ol>
</p>
<p>{t("send-in-order-help.p4")}</p>
</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 className="mb-[0.75rem] text-[12px]">
<label className="block mb-[0.7rem]">
<span className="text-[14px] font-medium">
{t("enable-tour")}
</span>
<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>
<ReactTooltip
id="istourenabled-tooltip"
className="z-[999]"
>
<div className="max-w-[200px] md:max-w-[450px]">
<p className="font-bold">{t("enable-tour")}</p>
<p 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>
</p>
<p>
{t("istourenabled-help.p3", {
appName: appName
})}
</p>
</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 className="mb-[0.75rem]">
<button
className="op-btn op-btn-primary w-[110px]"
onClick={handleSave}
>
{t("save")}
</button>
</div>
</div>
) : (
<div className="flex flex-col mx-4 mb-4">
<div className="flex flex-col">
+3 -7
View File
@@ -124,7 +124,6 @@ function SignYourSelf() {
const [pdfLoad, setPdfLoad] = useState(false);
const [isAlert, setIsAlert] = useState({ isShow: false, alertMessage: "" });
const [isDontShow, setIsDontShow] = useState(false);
const [extUserId, setExtUserId] = useState("");
const [isCompleted, setIsCompleted] = useState(false);
const [isCelebration, setIsCelebration] = useState(false);
const [pdfArrayBuffer, setPdfArrayBuffer] = useState("");
@@ -207,7 +206,6 @@ function SignYourSelf() {
if (documentData && documentData.length > 0) {
setPdfDetails(documentData);
setExtUserId(documentData[0]?.ExtUserPtr?.objectId);
const placeholders =
documentData[0]?.Placeholders?.length > 0
? documentData[0]?.Placeholders
@@ -1368,18 +1366,16 @@ function SignYourSelf() {
setCurrWidgetsDetails={setCurrWidgetsDetails}
/>
)}
{/*render email component to send email after finish signature on document */}
{/*render email component to send email after finish signature on document
<EmailComponent
isEmail={isEmail}
pdfUrl={pdfUrl}
setIsEmail={setIsEmail}
pdfDetails={pdfDetails}
setSuccessEmail={setSuccessEmail}
sender={jsonSender}
pdfDetails={pdfDetails}
setIsAlert={setIsAlert}
extUserId={extUserId}
setIsDownloadModal={setIsDownloadModal}
/>
*/}
{/* pdf header which contain funish back button */}
<Header
pageNumber={pageNumber}
+19 -1
View File
@@ -881,6 +881,14 @@ const TemplatePlaceholder = () => {
};
const handleSaveTemplate = async () => {
if (signersdata?.length) {
const remindOnceInEvery = parseInt(pdfDetails[0]?.RemindOnceInEvery);
const TimeToCompleteDays = parseInt(pdfDetails[0]?.TimeToCompleteDays);
const AutomaticReminders = pdfDetails[0]?.AutomaticReminders;
const reminderCount = TimeToCompleteDays / remindOnceInEvery;
if (AutomaticReminders && reminderCount > 15) {
alert(t("only-15-reminder-allowed"));
return;
}
setIsLoading({ isLoad: true, message: t("loading-mssg") });
setIsSendAlert(false);
let signers = [],
@@ -1086,7 +1094,7 @@ const TemplatePlaceholder = () => {
});
setIsCreateDoc(false);
} else {
setHandleError(t("something-went-wrong-mssg"));
setHandleError(t(res.id));
setIsCreateDoc(false);
}
};
@@ -1471,6 +1479,15 @@ const TemplatePlaceholder = () => {
"black"
}
};
} else if (["signature"].includes(position.type)) {
return {
...position,
options: {
...position.options,
name: defaultdata.name,
hint: defaultdata?.hint || ""
}
};
} else {
return {
...position,
@@ -1479,6 +1496,7 @@ const TemplatePlaceholder = () => {
name: defaultdata.name,
status: defaultdata.status,
defaultValue: defaultdata.defaultValue,
hint: defaultdata?.hint || "",
fontSize:
fontSize || currWidgetsDetails?.options?.fontSize || 12,
fontColor:
+89 -88
View File
@@ -10,7 +10,6 @@ import AddUser from "../components/AddUser";
import Title from "../components/Title";
import { useTranslation } from "react-i18next";
const heading = ["Sr.No", "Name", "Email", "Phone", "Role", "Team", "Active"];
// const actions = [];
const UserList = () => {
const { t } = useTranslation();
const [userList, setUserList] = useState([]);
@@ -114,9 +113,8 @@ const UserList = () => {
setUserList(_userRes);
} catch (err) {
console.log("Err in fetch userlist", err);
setIsAlert({ type: "danger", msg: t("something-went-wrong-mssg") });
showAlert("danger", t("something-went-wrong-mssg"));
} finally {
setTimeout(() => setIsAlert({ type: "success", msg: "" }), 1500);
setIsLoader(false);
}
}
@@ -173,17 +171,15 @@ const UserList = () => {
extUser.id = user.objectId;
extUser.set("IsDisabled", !IsDisabled);
await extUser.save();
setIsAlert({
type: !IsDisabled === true ? "danger" : "success",
msg:
!IsDisabled === true ? t("user-deactivated") : t("user-activated")
});
showAlert(
!IsDisabled === true ? "danger" : "success",
!IsDisabled === true ? t("user-deactivated") : t("user-activated")
);
} catch (err) {
setIsAlert({ type: "danger", msg: t("something-went-wrong-mssg") });
showAlert("danger", t("something-went-wrong-mssg"));
console.log("err in disable team", err);
} finally {
setIsActLoader({});
setTimeout(() => setIsAlert({ type: "success", msg: "" }), 1500);
}
}
};
@@ -191,6 +187,11 @@ const UserList = () => {
setIsActiveModal({ [user.objectId]: true });
};
// `showAlert` handle show/hide alert
const showAlert = (type, msg) => {
setIsAlert({ type, msg });
setTimeout(() => setIsAlert({ type: "success", msg: "" }), 1500);
};
return (
<div className="relative">
<Title title={isAdmin ? "Users" : "Page not found"} />
@@ -229,8 +230,8 @@ const UserList = () => {
</div>
</div>
</div>
<div className="overflow-x-auto w-full">
<table className="op-table border-collapse w-full">
<div className="w-full overflow-x-auto">
<table className="op-table border-collapse w-full mb-[50px]">
<thead className="text-[14px]">
<tr className="border-y-[1px]">
{heading?.map((item, index) => (
@@ -240,85 +241,85 @@ const UserList = () => {
))}
</tr>
</thead>
<tbody className="text-[12px]">
{userList?.length > 0 && (
<>
{currentList.map((item, index) => (
<tr className="border-y-[1px]" key={index}>
{heading.includes("Sr.No") && (
<th className="px-4 py-2">
{startIndex + index + 1}
</th>
)}
{userList?.length > 0 && (
<tbody className="text-[12px]">
{currentList.map((item, index) => (
<tr className="border-y-[1px]" key={index}>
{heading.includes("Sr.No") && (
<th className="px-4 py-2">
{startIndex + index + 1}
</th>
)}
<td className="px-4 py-2 font-semibold">
{item?.Name}{" "}
</td>
<td className="px-4 py-2 ">
{item?.Email || "-"}
</td>
<td className="px-4 py-2">
{item?.Phone || "-"}
</td>
<td className="px-4 py-2">
{item?.UserRole?.split("_").pop() || "-"}
</td>
<td className="px-4 py-2">
{formatRow(item.TeamIds)}
</td>
{item.UserRole !== "contracts_Admin" ? (
<td className="px-4 py-2 font-semibold">
{item?.Name}{" "}
</td>
<td className="px-4 py-2 ">
{item?.Email || "-"}
</td>
<td className="px-4 py-2">
{item?.Phone || "-"}
</td>
<td className="px-4 py-2">
{item?.UserRole?.split("_").pop() || "-"}
</td>
<td className="px-4 py-2">
{formatRow(item.TeamIds)}
</td>
{item.UserRole !== "contracts_Admin" && (
<td className="px-4 py-2 font-semibold">
<label className="cursor-pointer relative block items-center mb-0">
<input
type="checkbox"
className="op-toggle transition-all op-toggle-secondary"
checked={item?.IsDisabled !== true}
onChange={() => handleToggleBtn(item)}
/>
</label>
{isActiveModal[item.objectId] && (
<ModalUi
isOpen
title={t("user-status")}
handleClose={handleClose}
>
<div className="m-[20px]">
<div className="text-lg font-normal text-black">
{t("are-you-sure")}{" "}
{item?.IsDisabled
? t("activate")
: t("deactivate")}{" "}
{t("this-user")}?
</div>
<hr className="bg-[#ccc] mt-4 " />
<div className="flex items-center mt-3 gap-2 text-white">
<button
onClick={() =>
handleToggleSubmit(item)
}
className="op-btn op-btn-primary"
>
{t("yes")}
</button>
<button
onClick={handleClose}
className="op-btn op-btn-secondary"
>
{t("no")}
</button>
</div>
<label className="cursor-pointer relative block items-center mb-0">
<input
type="checkbox"
className="op-toggle transition-all op-toggle-secondary"
checked={item?.IsDisabled !== true}
onChange={() => handleToggleBtn(item)}
/>
</label>
{isActiveModal[item.objectId] && (
<ModalUi
isOpen
title={t("user-status")}
handleClose={handleClose}
>
<div className="m-[20px]">
<div className="text-lg font-normal text-black">
{t("are-you-sure")}{" "}
{item?.IsDisabled
? t("activate")
: t("deactivate")}{" "}
{t("this-user")}?
</div>
</ModalUi>
)}
</td>
)}
</tr>
))}
</>
)}
</tbody>
<hr className="bg-[#ccc] mt-4 " />
<div className="flex items-center mt-3 gap-2 text-white">
<button
onClick={() =>
handleToggleSubmit(item)
}
className="op-btn op-btn-primary"
>
{t("yes")}
</button>
<button
onClick={handleClose}
className="op-btn op-btn-secondary"
>
{t("no")}
</button>
</div>
</div>
</ModalUi>
)}
</td>
) : (
<td className="px-4 py-2 font-semibold"></td>
)}
</tr>
))}
</tbody>
)}
</table>
</div>
<div className="flex flex-row justify-between items-center text-xs font-medium">
<div className="flex flex-row justify-between items-center text-xs font-medium">
<div className="op-join flex flex-wrap items-center p-2">
{userList.length > recordperPage && (
<button
@@ -374,7 +375,7 @@ const UserList = () => {
handleClose={() => handleModal("form")}
>
<AddUser
setIsAlert={setIsAlert}
showAlert={showAlert}
handleUserData={handleUserData}
closePopup={() => handleModal("form")}
setFormHeader={setFormHeader}
@@ -1,4 +1,4 @@
import React, { useState, useEffect } from "react";
import React, { useState, useEffect, useRef } from "react";
import pad from "../assets/images/pad.svg";
import { useLocation, useNavigate } from "react-router";
import axios from "axios";
@@ -38,6 +38,7 @@ import * as XLSX from "xlsx";
import EditContactForm from "../components/EditContactForm";
const ReportTable = (props) => {
const copyUrlRef = useRef(null);
const appName =
"OpenSign™";
const drivename = appName === "OpenSign™" ? "OpenSign™" : "";
@@ -527,6 +528,9 @@ const ReportTable = (props) => {
const copytoclipboard = (share) => {
copytoData(share.url);
if (copyUrlRef.current) {
copyUrlRef.current.textContent = share.url; // Update text safely
}
setCopied({ ...copied, [share.email]: true });
};
//function to handle revoke/decline docment
@@ -2088,6 +2092,11 @@ const ReportTable = (props) => {
</div>
</div>
))}
<p
id="copyUrl"
ref={copyUrlRef}
className="hidden"
></p>
</div>
</ModalUi>
)}
+5 -1
View File
@@ -1,5 +1,9 @@
# Use an official Node runtime as the base image
FROM node:20
FROM node:22.14.0
# install java
RUN wget https://download.oracle.com/java/23/latest/jdk-23_linux-x64_bin.deb \
&& dpkg -i jdk-23_linux-x64_bin.deb
# Set the working directory inside the container
WORKDIR /usr/src/app
Binary file not shown.
-125
View File
@@ -139,131 +139,6 @@ export const updateMailCount = async (extUserId, plan, monthchange) => {
}
};
export function formatWidgetOptions(type, options) {
const colorsArr = ['red', 'black', 'blue', 'yellow'];
const fontSizes = [2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28];
const status = options?.required === true ? 'required' : 'optional' || 'required';
const defaultValue = options?.default || '';
const values = options?.values || [];
const color = options?.color ? options.color : 'black';
const fontColor = colorsArr.includes(color) ? color : 'black';
const size = options?.fontsize ? parseInt(options.fontsize) : 12;
const fontSize = fontSizes.includes(size) ? size : 12;
switch (type) {
case 'signature':
return { name: 'signature', status: 'required' };
case 'stamp':
return { status: status, name: 'stamp' };
case 'initials':
return { status: status, name: options.name || 'initials' };
case 'image':
return { status: status, name: options.name || 'image' };
case 'email':
return {
status: status,
name: options.name || 'email',
validation: { type: 'email' },
fontColor: fontColor,
fontSize: fontSize,
};
case 'name':
return {
status: status,
name: options.name || 'name',
fontColor: fontColor,
fontSize: fontSize,
};
case 'job title':
return {
status: status,
name: options.name || 'job title',
fontColor: fontColor,
fontSize: fontSize,
};
case 'company':
return {
status: status,
name: options.name || 'company',
fontColor: fontColor,
fontSize: fontSize,
};
case 'date': {
let today = new Date();
let dd = String(today.getDate()).padStart(2, '0');
let mm = String(today.getMonth() + 1).padStart(2, '0'); // January is 0!
let yyyy = today.getFullYear();
today = dd + '-' + mm + '-' + yyyy;
let dateFormat = options?.format;
dateFormat = dateFormat.replace(/m/g, 'M');
return {
status: status,
name: options.name || 'date',
response: defaultValue || today,
validation: { format: dateFormat || 'dd-MM-yyyy', type: 'date-format' },
fontColor: fontColor,
fontSize: fontSize,
};
}
case 'textbox':
return {
status: status,
name: 'textbox',
defaultValue: defaultValue,
hint: options.hint,
validation: { type: 'regex', pattern: options?.regularexpression || '/^[a-zA-Z0-9s]+$/' },
fontColor: fontColor,
fontSize: fontSize,
isReadOnly: options?.readonly || false,
};
case 'checkbox': {
const arr = options?.values;
let selectedvalues = [];
for (const obj of options.selectedvalues) {
const index = arr.indexOf(obj);
selectedvalues.push(index);
}
return {
status: status,
name: options.name || 'checkbox',
values: values,
isReadOnly: options?.readonly || false,
isHideLabel: options?.hidelabel || false,
validation: {
minRequiredCount: options?.validation?.minselections || 0,
maxRequiredCount: options?.validation?.maxselections || 0,
},
defaultValue: selectedvalues || [],
fontColor: fontColor,
fontSize: fontSize,
};
}
case 'radio button': {
return {
status: status,
name: options.name || 'radio',
values: values,
isReadOnly: options?.readonly || false,
isHideLabel: options?.hidelabel || false,
defaultValue: defaultValue,
fontColor: fontColor,
fontSize: fontSize,
};
}
case 'dropdown':
return {
status: status,
name: options.name || 'dropdown',
values: values,
defaultValue: defaultValue,
fontColor: fontColor,
fontSize: fontSize,
isReadOnly: options?.readonly || false,
};
default:
break;
}
}
export function sanitizeFileName(fileName) {
// Remove spaces and invalid characters
const file = fileName.replace(/[^a-zA-Z0-9._-]/g, '');
+2
View File
@@ -51,6 +51,7 @@ import generateCertificatebydocId from './parsefunction/generateCertificatebydoc
import fileUpload from './parsefunction/fileUpload.js';
import getUserListByOrg from './parsefunction/getUserListByOrg.js';
import editContact from './parsefunction/editContact.js';
import forwardDoc from './parsefunction/ForwardDoc.js';
// This afterSave function triggers after an object is added or updated in the specified class, allowing for post-processing logic.
@@ -112,3 +113,4 @@ Parse.Cloud.define('generatecertificate', generateCertificatebydocId);
Parse.Cloud.define('fileupload', fileUpload);
Parse.Cloud.define('getuserlistbyorg', getUserListByOrg);
Parse.Cloud.define('editcontact', editContact);
Parse.Cloud.define('forwarddoc', forwardDoc);
@@ -1,4 +1,13 @@
async function DocumentBeforesave(request) {
if (!request.original) {
const TimeToCompleteDays = request.object.get('TimeToCompleteDays') || 15;
const RemindOnceInEvery = request?.object?.get('RemindOnceInEvery') || 5;
const AutoReminder = request?.object?.get('AutomaticReminders') || false;
const reminderCount = TimeToCompleteDays / RemindOnceInEvery;
if (AutoReminder && reminderCount > 15) {
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'only 15 reminder allowed');
}
}
try {
// below code is used to update document when user sent document or self signed
const document = request.object;
@@ -0,0 +1,86 @@
import axios from 'axios';
import { appName, cloudServerUrl } from '../../Utils.js';
export default async function forwardDoc(request) {
try {
if (!request.user) {
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'unauthorized.');
}
const { docId, recipients } = request.params;
const isReceipents = recipients?.length > 0 && recipients?.length <= 10;
if (docId && isReceipents) {
const userPtr = { __type: 'Pointer', className: '_User', objectId: request.user.id };
const docQuery = new Parse.Query('contracts_Document');
docQuery
.equalTo('objectId', docId)
.equalTo('CreatedBy', userPtr)
.notEqualTo('IsArchive', true)
.notEqualTo('IsDeclined', true)
.include('Signers')
.include('ExtUserPtr')
.include('Placeholders.signerPtr')
.include('ExtUserPtr.TenantId');
const docRes = await docQuery.first({ useMasterKey: true });
if (!docRes) {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Document not found.');
}
const _docRes = docRes?.toJSON();
const docName = _docRes.Name;
const fileAdapterId = _docRes?.FileAdapterId || '';
const extUserId = _docRes?.ExtUserPtr?.objectId;
const from =
_docRes?.ExtUserPtr?.Email;
const replyTo =
_docRes?.ExtUserPtr?.Email;
const senderName =
_docRes?.ExtUserPtr?.Name;
try {
let mailRes;
for (let i = 0; i < recipients.length; i++) {
const logo =
`<img src='https://qikinnovation.ams3.digitaloceanspaces.com/logo.png' height='50' style='padding:20px'/>`;
const opurl =
` <a href='www.opensignlabs.com' target=_blank>here</a>`;
const themeColor = '#47a3ad';
let params = {
extUserId: extUserId,
pdfName: docName,
url: _docRes?.SignedUrl || '',
recipient: recipients[i],
subject: `${senderName} has signed the doc - ${docName}`,
replyto: replyTo || '',
from: from,
html:
`<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8'/></head><body><div style='background-color:#f5f5f5;padding:20px'><div style='background-color:white'><div>` +
`${logo}</div><div style='padding:2px;font-family:system-ui;background-color:${themeColor}'><p style='font-size:20px;font-weight:400;color:white;padding-left:20px'>Document Copy</p></div><div>` +
`<p style='padding:20px;font-family:system-ui;font-size:14px'>A copy of the document <strong>${docName}</strong> is attached to this email. Kindly download the document from the attachment.</p>` +
`</div></div><div><p>This is an automated email from ${appName}. For any queries regarding this email, please contact the sender ${replyTo} directly. ` +
`If you think this email is inappropriate or spam, you may file a complaint with ${appName}${opurl}.</p></div></div></body></html>`,
};
mailRes = await axios.post(`${cloudServerUrl}/functions/sendmailv3`, params, {
headers: {
'Content-Type': 'application/json',
'X-Parse-Application-Id': process.env.APP_ID,
'X-Parse-Master-Key': process.env.MASTER_KEY,
},
});
}
return mailRes.data?.result;
} catch (error) {
const msg =
error?.response?.data?.error ||
error?.response?.data ||
error?.message ||
'Something went wrong.';
throw new Parse.Error(400, msg);
}
} else {
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'please provide parameters.');
}
} catch (err) {
console.log('Err in forwardDoc', err);
throw err;
}
}
@@ -11,7 +11,7 @@ async function getDocument(docId) {
query.include('Placeholders');
query.notEqualTo('IsArchive', true);
const res = await query.first({ useMasterKey: true });
const _res = res.toJSON();
const _res = res?.toJSON();
return _res?.ExtUserPtr?.objectId;
} catch (err) {
console.log('err ', err);
@@ -1,4 +1,13 @@
async function TemplateBeforeSave(request) {
if (!request.original) {
const TimeToCompleteDays = request.object.get('TimeToCompleteDays') || 15;
const RemindOnceInEvery = request?.object?.get('RemindOnceInEvery') || 5;
const AutoReminder = request?.object?.get('AutomaticReminders') || false;
const reminderCount = TimeToCompleteDays / RemindOnceInEvery;
if (AutoReminder && reminderCount > 15) {
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'only 15 reminder allowed');
}
}
try {
if (!request.original) {
// below code is used to update template when user sent template or self signed
@@ -26,7 +26,7 @@ async function uploadFile(
return { imageUrl: fileUrl };
} catch (err) {
console.log('Err ', err);
// `unlinkCertificate` is used to remove exported signed pdf file from exports folder
// `unlinkCertificate` is used to remove exported certificate file from exports folder
unlinkCertificate(filepath);
}
}
@@ -0,0 +1,35 @@
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);
}
}
}
}
@@ -1,8 +1,5 @@
import fs from 'node:fs';
import axios from 'axios';
import { SignPdf } from '@signpdf/signpdf';
import { P12Signer } from '@signpdf/signer-p12';
import { pdflibAddPlaceholder } from '@signpdf/placeholder-pdf-lib';
import { PDFDocument } from 'pdf-lib';
import {
cloudServerUrl,
@@ -12,12 +9,16 @@ import {
appName,
} from '../../../Utils.js';
import GenerateCertificate from './GenerateCertificate.js';
import { Placeholder } from './Placeholder.js';
import DigitalSign, { retryAsync } from './DigitalSign.js';
const serverUrl = cloudServerUrl; // process.env.SERVER_URL;
const APPID = process.env.APP_ID;
const masterKEY = process.env.MASTER_KEY;
const eSignName = 'OpenSign';
const eSigncontact = 'hello@opensignlabs.com';
const logo =
"<img src='https://qikinnovation.ams3.digitaloceanspaces.com/logo.png' height='50' style='padding:20px'/>";
const opurl =
` <a href=www.opensignlabs.com target=_blank>here</a>`;
// `updateDoc` is used to create url in from pdfFile
async function uploadFile(
pdfName,
@@ -104,14 +105,10 @@ async function sendNotifyMail(doc, signUser, mailProvider) {
const signerName = signUser.Name;
const signerEmail = signUser.Email;
const viewDocUrl = `${process.env.PUBLIC_URL}/recipientSignPdf/${doc.objectId}`;
const logo =
`<div><img src='https://qikinnovation.ams3.digitaloceanspaces.com/logo.png' height='50' style='padding:20px' /></div>`;
const opurl =
` <a href=www.opensignlabs.com target=_blank>here</a>`;
const subject = `Document "${pdfName}" has been signed by ${signerName}`;
const body =
"<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8'/></head><body><div style='background-color:#f5f5f5;padding:20px'><div style='background-color:white'>" +
`${logo}<div style='padding:2px;font-family:system-ui;background-color:#47a3ad'><p style='font-size:20px;font-weight:400;color:white;padding-left:20px'>Document signed by ${signerName}</p>` +
`<div>${logo}</div><div style='padding:2px;font-family:system-ui;background-color:#47a3ad'><p style='font-size:20px;font-weight:400;color:white;padding-left:20px'>Document signed by ${signerName}</p>` +
`</div><div style='padding:20px;font-family:system-ui;font-size:14px'><p>Dear ${creatorName},</p><p>${pdfName} has been signed by ${signerName} "${signerEmail}" successfully</p>` +
`<p><a href=${viewDocUrl} target=_blank>View Document</a></p></div></div><div><p>This is an automated email from ${appName}. For any queries regarding this email, ` +
`please contact the sender ${creatorEmail} directly. If you think this email is inappropriate or spam, you may file a complaint with ${appName}${opurl}.</p></div></div></body></html>`;
@@ -154,17 +151,13 @@ async function sendCompletedMail(obj) {
signersMail = sender.Email;
}
const recipient = signersMail;
const logo =
`<div><img src='https://qikinnovation.ams3.digitaloceanspaces.com/logo.png' height='50' style='padding:20px'/></div>`;
const opurl =
` <a href=www.opensignlabs.com target=_blank>here</a>.</p></div></div></body></html>`;
let subject = `Document "${pdfName}" has been signed by all parties`;
let body =
"<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body><div style='background-color:#f5f5f5;padding:20px'><div style='background-color:white'>" +
`${logo}<div style='padding:2px;font-family:system-ui;background-color:#47a3ad'><p style='font-size:20px;font-weight:400;color:white;padding-left:20px'>Document signed successfully</p></div><div>` +
`<div>${logo}</div><div style='padding:2px;font-family:system-ui;background-color:#47a3ad'><p style='font-size:20px;font-weight:400;color:white;padding-left:20px'>Document signed successfully</p></div><div>` +
`<p style='padding:20px;font-family:system-ui;font-size:14px'>All parties have successfully signed the document <b>"${pdfName}"</b>. Kindly download the document from the attachment.</p>` +
`</div></div><div><p>This is an automated email from ${appName}. For any queries regarding this email, please contact the sender ${sender.Email} directly.` +
`If you think this email is inappropriate or spam, you may file a complaint with ${appName}${opurl}`;
`If you think this email is inappropriate or spam, you may file a complaint with ${appName}${opurl}.</p></div></div></body></html>`;
if (obj?.isCustomMail) {
const tenant = sender?.TenantId;
@@ -231,71 +224,102 @@ async function sendCompletedMail(obj) {
html: body,
mailProvider: obj.mailProvider,
bcc: Bcc,
certificatePath: `./exports/certificate_${doc.objectId}.pdf`,
certificatePath: `./exports/signed_certificate_${doc.objectId}.pdf`,
filename: obj?.filename,
};
const res = await axios.post(serverUrl + '/functions/sendmailv3', params, {
headers: {
'Content-Type': 'application/json',
'X-Parse-Application-Id': APPID,
'X-Parse-Master-Key': masterKEY,
},
});
try {
const res = await axios.post(serverUrl + '/functions/sendmailv3', params, {
headers: {
'Content-Type': 'application/json',
'X-Parse-Application-Id': APPID,
'X-Parse-Master-Key': masterKEY,
},
});
// console.log('res', res.data.result);
if (res.data?.result?.status !== 'success') {
fs.unlinkSync(`./exports/signed_certificate_${doc.objectId}.pdf`);
}
} catch (err) {
fs.unlinkSync(`./exports/signed_certificate_${doc.objectId}.pdf`);
}
}
// `sendMailsaveCertifcate` is used send completion mail and update complete status of document
async function sendMailsaveCertifcate(
doc,
P12Buffer,
pfx,
isCustomMail,
mailProvider,
filename,
) {
const certificate = await GenerateCertificate(doc);
const certificatePdf = await PDFDocument.load(certificate);
let passphrase = process.env.PASS_PHRASE;
if (doc?.ExtUserPtr?.TenantId?.PfxFile?.password) {
passphrase = doc?.ExtUserPtr?.TenantId?.PfxFile?.password;
}
const p12 = new P12Signer(P12Buffer, { passphrase: passphrase || null });
// `pdflibAddPlaceholder` is used to add code of only digitial sign in certificate
pdflibAddPlaceholder({
pdfDoc: certificatePdf,
reason: `Digitally signed by ${eSignName}.`,
location: 'n/a',
name: eSignName,
contactInfo: eSigncontact,
signatureLength: 15000,
});
const pdfWithPlaceholderBytes = await certificatePdf.save();
const CertificateBuffer = Buffer.from(pdfWithPlaceholderBytes);
//`new signPDF` create new instance of CertificateBuffer and p12Buffer
const certificateOBJ = new SignPdf();
// `signedCertificate` is used to sign certificate digitally
const signedCertificate = await certificateOBJ.sign(CertificateBuffer, p12);
const certificatePath = `./exports/certificate_${doc.objectId}.pdf`;
let certificatePath = `./exports/certificate_${doc.objectId}.pdf`;
//below is used to save signed certificate in exports folder
fs.writeFileSync(certificatePath, signedCertificate);
const file = await uploadFile(
'certificate.pdf',
certificatePath,
);
const body = { CertificateUrl: file.imageUrl };
await axios.put(serverUrl + '/classes/contracts_Document/' + doc.objectId, body, {
headers: {
'Content-Type': 'application/json',
'X-Parse-Application-Id': APPID,
'X-Parse-Master-Key': masterKEY,
},
});
// used in API only
if (doc.IsSendMail === false) {
console.log("don't send mail");
} else {
sendCompletedMail({ isCustomMail, doc, mailProvider, filename });
fs.writeFileSync(certificatePath, CertificateBuffer);
const details = { reason: `Digitally signed by ${eSignName}.`, location: 'n/a', name: eSignName };
const res = await retryAsync(DigitalSign, [certificatePath, pfx, details]);
// console.log('res', res);
// const res = await DigitalSign(certificatePath, pfx, details);
if (res) {
const CreatedBy = {
__type: 'Pointer',
className: '_User',
objectId: doc.CreatedBy.objectId,
};
let logbody = {
documentId: doc.objectId,
attempt: res.attempt,
createdBy: CreatedBy,
email: doc.ExtUserPtr.Email,
};
if (fs.existsSync(certificatePath)) {
fs.unlinkSync(certificatePath);
certificatePath = `./exports/signed_certificate_${doc.objectId}.pdf`;
const file = await uploadFile(
'certificate.pdf',
certificatePath,
);
const body = { CertificateUrl: file.imageUrl };
await axios.put(serverUrl + '/classes/contracts_Document/' + doc.objectId, body, {
headers: {
'Content-Type': 'application/json',
'X-Parse-Application-Id': APPID,
'X-Parse-Master-Key': masterKEY,
},
});
// used in API only
if (doc.IsSendMail === false) {
console.log("don't send mail");
} else {
sendCompletedMail({ isCustomMail, doc, mailProvider, filename });
}
saveFileUsage(CertificateBuffer.length, file.imageUrl, doc?.CreatedBy?.objectId);
fs.unlinkSync(pfx.name);
if (res.attempt > 1) {
logbody = { ...logbody, status: 'success' };
axios.post(`${cloudServerUrl}/classes/logs_SignRetry`, logbody, {
headers: { 'X-Parse-Application-Id': APPID, 'X-Parse-Master-Key': masterKEY },
});
}
} else {
fs.unlinkSync(certificatePath);
fs.unlinkSync(pfx.name);
const error = new Error('certificate: ' + res.error);
error.code = 400; // Set the error code (e.g., 400 for bad request)
logbody = { ...logbody, status: 'failure', stackTrace: 'certificate: ' + res.error };
axios.post(`${cloudServerUrl}/classes/logs_SignRetry`, logbody, {
headers: { 'X-Parse-Application-Id': APPID, 'X-Parse-Master-Key': masterKEY },
});
throw error;
}
}
saveFileUsage(CertificateBuffer.length, file.imageUrl, doc?.CreatedBy?.objectId);
}
/**
*
@@ -304,9 +328,11 @@ async function sendMailsaveCertifcate(
* @returns if success {status, data} else {status, message}
*/
async function PDF(req) {
const docId = req.params.docId;
const randomNumber = Math.floor(Math.random() * 5000);
const pfxname = `keystore_${randomNumber}.pfx`;
try {
const userIP = req.headers['x-real-ip']; // client IPaddress
const docId = req.params.docId;
const reqUserId = req.params.userId;
const isCustomMail = req.params.isCustomCompletionMail || false;
const mailProvider = req.params.mailProvider || '';
@@ -354,9 +380,9 @@ async function PDF(req) {
pfxFile = _resDoc?.ExtUserPtr?.TenantId?.PfxFile?.base64;
passphrase = _resDoc?.ExtUserPtr?.TenantId?.PfxFile?.password;
}
// const P12Buffer = fs.readFileSync();
const pfx = { name: pfxname, passphrase: passphrase };
const P12Buffer = Buffer.from(pfxFile, 'base64');
const p12Cert = new P12Signer(P12Buffer, { passphrase: passphrase || null });
fs.writeFileSync(pfxname, P12Buffer);
const UserPtr = { __type: 'Pointer', className: className, objectId: signUser.objectId };
const obj = { UserPtr: UserPtr, SignedUrl: '', Activity: 'Signed', ipAddress: userIP };
let updateAuditTrail;
@@ -375,12 +401,12 @@ async function PDF(req) {
} else {
isCompleted = true;
}
const randomNumber = Math.floor(Math.random() * 5000);
// below regex is used to replace all word with "_" except A to Z, a to z, numbers
const docName = _resDoc?.Name?.replace(/[^a-zA-Z0-9._-]/g, '_')?.toLowerCase();
const filename = docName?.length > 100 ? docName?.slice(0, 100) : docName;
const name = `signed_${filename}_${randomNumber}.pdf`;
const filePath = `./exports/${name}`;
const name = `${filename}_${randomNumber}.pdf`;
let filePath = `./exports/${name}`;
let signedFilePath = `./exports/signed_${name}`;
let pdfSize = PdfBuffer.length;
if (isCompleted) {
const signersName = _resDoc.Signers?.map(x => x.Name + ' <' + x.Email + '>');
@@ -394,34 +420,65 @@ async function PDF(req) {
form.updateFieldAppearances();
// Flattens the form, converting all form fields into non-editable, static content
form.flatten();
Placeholder({
pdfDoc: pdfDoc,
const pdfWithPlaceholderBytes = await pdfDoc.save();
PdfBuffer = Buffer.from(pdfWithPlaceholderBytes);
// `saveUrl` is used to save signed pdf in exports folder
fs.writeFileSync(filePath, PdfBuffer);
const details = {
reason: `Digitally signed by ${eSignName} for ${reason}`,
location: 'n/a',
name: eSignName,
contactInfo: eSigncontact,
signatureLength: 15000,
});
const pdfWithPlaceholderBytes = await pdfDoc.save();
PdfBuffer = Buffer.from(pdfWithPlaceholderBytes);
//`new signPDF` create new instance of pdfBuffer and p12Buffer
const OBJ = new SignPdf();
// `signedDocs` is used to signpdf digitally
const signedDocs = await OBJ.sign(PdfBuffer, p12Cert);
//`saveUrl` is used to save signed pdf in exports folder
fs.writeFileSync(filePath, signedDocs);
pdfSize = signedDocs.length;
};
const res = await retryAsync(DigitalSign, [filePath, pfx, details]);
// const res = await DigitalSign(filePath, pfx, details);
// console.log('res', res);
if (res) {
signedFilePath = `./exports/signed_${name}`;
const CreatedBy = {
__type: 'Pointer',
className: '_User',
objectId: _resDoc.CreatedBy.objectId,
};
let logbody = {
documentId: _resDoc.objectId,
attempt: res.attempt,
createdBy: CreatedBy,
email: _resDoc.ExtUserPtr.Email,
};
if (fs.existsSync(signedFilePath)) {
const signedPdf = fs.readFileSync(signedFilePath);
pdfSize = signedPdf.length;
console.log(`✅ New Signed PDF created called: ${signedFilePath} \n`);
fs.unlinkSync(filePath);
if (res.attempt > 1) {
logbody = { ...logbody, status: 'success' };
axios.post(`${cloudServerUrl}/classes/logs_SignRetry`, logbody, {
headers: { 'X-Parse-Application-Id': APPID, 'X-Parse-Master-Key': masterKEY },
});
}
} else {
fs.unlinkSync(filePath);
const error = new Error(res.error);
error.code = 400; // Set the error code (e.g., 400 for bad request)
logbody = { ...logbody, status: 'failure', stackTrace: res.error };
axios.post(`${cloudServerUrl}/classes/logs_SignRetry`, logbody, {
headers: { 'X-Parse-Application-Id': APPID, 'X-Parse-Master-Key': masterKEY },
});
throw error;
}
}
} else {
//`saveUrl` is used to save signed pdf in exports folder
fs.writeFileSync(filePath, PdfBuffer);
fs.writeFileSync(signedFilePath, PdfBuffer);
pdfSize = PdfBuffer.length;
console.log(`New Signed PDF created called: ${signedFilePath}`);
}
// `uploadFile` is used to upload pdf to aws s3 and get it's url
const data = await uploadFile(
name,
filePath,
`signed_${name}`,
signedFilePath,
);
if (data && data.imageUrl) {
@@ -441,15 +498,17 @@ async function PDF(req) {
const doc = { ..._resDoc, AuditTrail: updatedDoc.AuditTrail, SignedUrl: data.imageUrl };
sendMailsaveCertifcate(
doc,
P12Buffer,
pfx,
isCustomMail,
mailProvider,
name,
`signed_${name}`,
);
} else {
fs.unlinkSync(pfxname);
}
// `fs.unlinkSync` is used to remove exported signed pdf file from exports folder
fs.unlinkSync(filePath);
console.log(`New Signed PDF created called: ${filePath}`);
fs.unlinkSync(signedFilePath);
// console.log(`New Signed PDF created called: ${filePath}`);
if (updatedDoc.message === 'success') {
return { status: 'success', data: data.imageUrl };
} else {
@@ -465,6 +524,15 @@ async function PDF(req) {
}
} catch (err) {
console.log('Err in signpdf', err);
const body = { DebugginLog: err?.message };
try {
await axios.put(serverUrl + '/classes/contracts_Document/' + docId, body, {
headers: { 'X-Parse-Application-Id': APPID, 'X-Parse-Master-Key': masterKEY },
});
} catch (err) {
console.log('err in saving debugginglog', err);
}
fs.unlinkSync(pfxname);
throw err;
}
}
@@ -42,6 +42,12 @@ export default async function updatePreferences(request) {
newOrg.set('SignatureType', SignatureType);
}
}
if (request.params.SendinOrder !== undefined) {
newOrg.set('SendinOrder', request.params.SendinOrder);
}
if (request.params.IsTourEnabled !== undefined) {
newOrg.set('IsTourEnabled', request.params.IsTourEnabled);
}
const updateUserRes = await newOrg.save(null, { useMasterKey: true });
if (updateUserRes) {
const _updateUserRes = JSON.parse(JSON.stringify(updateUserRes));
Binary file not shown.
+4 -4
View File
@@ -47,7 +47,7 @@
"mongodb-runner": "^5.7.1",
"nodemon": "^3.1.9",
"nyc": "^17.1.0",
"prettier": "^3.5.3"
"prettier": "^3.5.0"
},
"engines": {
"node": "18 || 20 || 22"
@@ -16933,9 +16933,9 @@
}
},
"node_modules/prettier": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz",
"integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==",
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.0.tgz",
"integrity": "sha512-quyMrVt6svPS7CjQ9gKb3GLEX/rl3BCL2oa/QkNcXv4YNVBC9olt3s+H7ukto06q7B1Qz46PbrKLO34PR6vXcA==",
"dev": true,
"license": "MIT",
"bin": {
+1 -1
View File
@@ -57,7 +57,7 @@
"mongodb-runner": "^5.7.1",
"nodemon": "^3.1.9",
"nyc": "^17.1.0",
"prettier": "^3.5.3"
"prettier": "^3.5.0"
},
"overrides": {
"ws": "$ws",
Binary file not shown.
Binary file not shown.