diff --git a/.env.local_dev b/.env.local_dev index 7b4907b5db..dc3d04fd21 100644 --- a/.env.local_dev +++ b/.env.local_dev @@ -8,13 +8,11 @@ PUBLIC_URL=https://localhost:3001 GENERATE_SOURCEMAP=false # Set it to the URL from where APIs will be accessible, for local development it should be localhost:3000/api/app (use your local port number instead) # REACT_APP_SERVERURL=http://localhost:8080/app -# A 12 character long random app identifier. The value of this should be same as APP_ID which is a variable used by backend API. +# (DEPRECATED) This should not be changed if provided; it should be 'opensign'. REACT_APP_APPID=opensign # Backend ExpressJS config **************************************************************************************************************************************************************************************** -# A 12 character long random app identifier. The value of this should be same as REACT_APP_APPID which is a variable used by Frontend React App. -APP_ID=opensign # Name of the app. It will be visible in the verification emails sent out. appName=open_sign_server # A 12 character long random secret key that allows access to all the data. It is used in Parse dashboard config to view all the data in the database. @@ -111,3 +109,6 @@ CRUxFgQUDYlgGVxSxuOknhQc256x3++7BDwwMTAhMAkGBSsOAwIaBQAEFFjASdYl # Provide Pass pharse of above PFX or p12 document PASS_PHRASE=opensign + +# (DEPRECATED) This should not be changed if provided; it should be 'opensign'. +APP_ID=opensign diff --git a/README.md b/README.md index ac6c2e7e67..a51aad5fac 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ Welcome to OpenSign, the premier open source docusign alternative - document e-s The simplest way to install OpenSign on your own server is using official docker images by running the following command - ``` -export HOST_URL=https://opensign.yourdomain.com && curl --remote-name-all https://raw.githubusercontent.com/OpenSignLabs/OpenSign/docker_beta/docker-compose.yml https://raw.githubusercontent.com/OpenSignLabs/OpenSign/docker_beta/Caddyfile https://raw.githubusercontent.com/OpenSignLabs/OpenSign/docker_beta/.env.local_dev && mv .env.local_dev .env.prod && docker compose up --force-recreate +export HOST_URL=https://opensign.yourdomain.com && curl --remote-name-all https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/docker-compose.yml https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/Caddyfile https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/.env.local_dev && mv .env.local_dev .env.prod && docker compose up --force-recreate ``` Make sure that you have `Docker` and `git` installed before you run this command - diff --git a/apps/OpenSign/Dockerhubfile b/apps/OpenSign/Dockerhubfile index d470799737..395bad9d64 100644 --- a/apps/OpenSign/Dockerhubfile +++ b/apps/OpenSign/Dockerhubfile @@ -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,7 @@ COPY apps/OpenSign/.husky . # Define environment variables if needed ENV NODE_ENV=production -ENV REACT_APP_DEPLOYMENT=free_selfhost - +ENV GENERATE_SOURCEMAP=false # build RUN npm run build diff --git a/apps/OpenSign/public/locales/de/translation.json b/apps/OpenSign/public/locales/de/translation.json index c9c7f6397f..1ec359a6e0 100644 --- a/apps/OpenSign/public/locales/de/translation.json +++ b/apps/OpenSign/public/locales/de/translation.json @@ -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.", @@ -847,5 +850,11 @@ "agreement-note": "Hinweis: Durch Ihre Zustimmung unterzeichnen Sie das Dokument nicht sofort. Sie können das Dokument nur elektronisch einsehen. Sie haben die Möglichkeit, es vollständig zu lesen und anschließend zu entscheiden, ob Sie es unterzeichnen möchten.", "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." + "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": "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." } diff --git a/apps/OpenSign/public/locales/en/translation.json b/apps/OpenSign/public/locales/en/translation.json index 3c31ea9d23..de165cf02f 100644 --- a/apps/OpenSign/public/locales/en/translation.json +++ b/apps/OpenSign/public/locales/en/translation.json @@ -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", @@ -848,5 +850,11 @@ "agreement-note": "Note: Agreeing to this does not mean you are signing the document immediately. This only allows you to review the document electronically. You will have the opportunity to read it in full and decide whether to sign it afterward.", "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." + "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": "You’ve 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." } diff --git a/apps/OpenSign/public/locales/es/translation.json b/apps/OpenSign/public/locales/es/translation.json index 2037853aa7..f4f3ed50e7 100644 --- a/apps/OpenSign/public/locales/es/translation.json +++ b/apps/OpenSign/public/locales/es/translation.json @@ -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,6 +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." + "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": "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." } diff --git a/apps/OpenSign/public/locales/fr/translation.json b/apps/OpenSign/public/locales/fr/translation.json index 22ce1eda08..30302c1a98 100644 --- a/apps/OpenSign/public/locales/fr/translation.json +++ b/apps/OpenSign/public/locales/fr/translation.json @@ -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", @@ -846,5 +850,11 @@ "agreement-note": "Remarque : Accepter cela ne signifie pas que vous signez immédiatement le document. Cela vous permet uniquement de consulter le document électroniquement. Vous aurez l'opportunité de le lire entièrement et de décider ensuite si vous souhaitez le signer.", "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é." + "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": "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." } diff --git a/apps/OpenSign/public/locales/it/translation.json b/apps/OpenSign/public/locales/it/translation.json index d30629ff96..fdfe4f7f4b 100644 --- a/apps/OpenSign/public/locales/it/translation.json +++ b/apps/OpenSign/public/locales/it/translation.json @@ -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?", @@ -847,5 +850,11 @@ "agreement-note": "Nota: Accettare questo non significa che stai firmando immediatamente il documento. Questo ti consente solo di esaminare il documento elettronicamente. Avrai l'opportunità di leggerlo per intero e decidere successivamente se firmarlo.", "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à." + "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": "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." } diff --git a/apps/OpenSign/src/components/AddUser.js b/apps/OpenSign/src/components/AddUser.js index 4b48c9ba47..50106d63ff 100644 --- a/apps/OpenSign/src/components/AddUser.js +++ b/apps/OpenSign/src/components/AddUser.js @@ -2,13 +2,8 @@ import React, { useEffect, useState } from "react"; import Parse from "parse"; import Title from "./Title"; import Loader from "../primitives/Loader"; -import { - copytoData, - usertimezone -} from "../constant/Utils"; -import { - emailRegex, -} from "../constant/const"; +import { copytoData, usertimezone } from "../constant/Utils"; +import { emailRegex } from "../constant/const"; import { useTranslation } from "react-i18next"; function generatePassword(length) { const characters = @@ -46,9 +41,9 @@ const AddUser = (props) => { if (teamRes.length > 0) { const _teamRes = JSON.parse(JSON.stringify(teamRes)); setTeamList(_teamRes); - const allUserId = - _teamRes.find((x) => x.Name === "All Users")?.objectId || ""; - setFormdata((prev) => ({ ...prev, team: allUserId })); + const allUserId = + _teamRes.find((x) => x.Name === "All Users")?.objectId || ""; + setFormdata((prev) => ({ ...prev, team: allUserId })); } }; const checkUserExist = async () => { @@ -76,167 +71,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); + } + + 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) - ); + 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.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); + extUser.setACL(acl); - const res = await extUser.save(); + const res = await extUser.save(); - const parseData = JSON.parse(JSON.stringify(res)); + 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 - ); + 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); } - props.handleUserData(parseData); - } - setIsFormLoader(false); - setFormdata({ - name: "", - email: "", - phone: "", - team: "", - role: "" - }); - } - } catch (err) { - console.log("err ", err); - if (err.code === 202) { - const params = { email: formdata.email }; - const userRes = await Parse.Cloud.run("getUserId", params); - const currentUser = Parse.User.current(); - extUser.set( - "CreatedBy", - Parse.User.createWithoutData(currentUser.id) - ); + 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.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(); + 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 - ); + 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); } - 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")); } - setIsFormLoader(false); - setFormdata({ - name: "", - email: "", - phone: "", - team: "", - role: "" - }); } + } catch (err) { + 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 +254,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 (
@@ -270,126 +264,120 @@ const AddUser = (props) => {
)} -
-
-
- - handleChange(e)} - onInvalid={(e) => - e.target.setCustomValidity(t("input-required")) - } - onInput={(e) => e.target.setCustomValidity("")} - required - className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs" - /> -
-
- - handleChange(e)} - required - onInvalid={(e) => - e.target.setCustomValidity(t("input-required")) - } - onInput={(e) => e.target.setCustomValidity("")} - className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs" - /> -
-
- -
-
{formdata?.password}
- copytoclipboard(formdata?.password)} - className="fa-light fa-copy rounded-full hover:bg-base-300 p-[8px] cursor-pointer " - > -
-
- {t("password-generateed")} -
-
-
- - handleChange(e)} - className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs" - /> -
-
- - -
-
- -
handleReset()} - className="op-btn op-btn-secondary" - > - {t("cancel")} -
-
-
-
+
+
+
+ + handleChange(e)} + onInvalid={(e) => e.target.setCustomValidity(t("input-required"))} + onInput={(e) => e.target.setCustomValidity("")} + required + className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs" + /> +
+
+ + handleChange(e)} + required + onInvalid={(e) => e.target.setCustomValidity(t("input-required"))} + onInput={(e) => e.target.setCustomValidity("")} + className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs" + /> +
+
+ +
+
{formdata?.password}
+ copytoclipboard(formdata?.password)} + className="fa-light fa-copy rounded-full hover:bg-base-300 p-[8px] cursor-pointer " + > +
+
+ {t("password-generateed")} +
+
+
+ + handleChange(e)} + className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs" + /> +
+
+ + +
+
+ +
handleReset()} + className="op-btn op-btn-secondary" + > + {t("cancel")} +
+
+
+
); }; diff --git a/apps/OpenSign/src/components/pdf/EditTemplate.js b/apps/OpenSign/src/components/pdf/EditTemplate.js index 5c2c5f4c9a..e612e65247 100644 --- a/apps/OpenSign/src/components/pdf/EditTemplate.js +++ b/apps/OpenSign/src/components/pdf/EditTemplate.js @@ -1,19 +1,11 @@ -import React, { - useState, -} from "react"; -import { - getFileName -} from "../../constant/Utils"; +import React, { useState } from "react"; +import { getFileName } from "../../constant/Utils"; import { useTranslation } from "react-i18next"; import { Tooltip } from "react-tooltip"; import SignersInput from "../shared/fields/SignersInput"; -const EditTemplate = ({ - template, - onSuccess, -}) => { - const appName = - "OpenSign™"; +const EditTemplate = ({ template, onSuccess }) => { + const appName = "OpenSign™"; const { t } = useTranslation(); const [formData, setFormData] = useState({ Name: template?.Name || "", @@ -64,6 +56,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); @@ -247,11 +246,7 @@ const EditTemplate = ({
-
+
{t("yes")}
-
+
{ - 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 = - ``; - const opurl = - ` here`; - - 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: - `
` + - `${logo}

Document Copy

` + - `

A copy of the document ${pdfName} is attached to this email. Kindly download the document from the attachment.

` + - `

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}.

` - }; - 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(() => { diff --git a/apps/OpenSign/src/components/pdf/Placeholder.js b/apps/OpenSign/src/components/pdf/Placeholder.js index a8da8d33bf..6b5c425176 100644 --- a/apps/OpenSign/src/components/pdf/Placeholder.js +++ b/apps/OpenSign/src/components/pdf/Placeholder.js @@ -74,24 +74,23 @@ const changeDateToMomentFormat = (format) => { return "L"; } }; +//function to get default format +const getDefaultFormat = (dateFormat) => dateFormat || "MM/dd/yyyy"; -//function to get default date -const getDefaultdate = (selectedDate, format = "dd-MM-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 + const formats = changeDateToMomentFormat(format); + const parsedDate = moment(dateStr, formats); let date; - if (format && format === "dd-MM-yyyy") { - const newdate = selectedDate - ? selectedDate - : moment(new Date()).format(changeDateToMomentFormat(format)); - const [day, month, year] = newdate.split("-"); - date = new Date(`${year}-${month}-${day}`); + if (parsedDate.isValid()) { + date = new Date(parsedDate.toISOString()); + return date; } else { - date = new Date(selectedDate); + date = new Date(); + return date; } - const value = date; - return value; }; -//function to get default format -const getDefaultFormat = (dateFormat) => dateFormat || "MM/dd/yyyy"; function Placeholder(props) { //'isTouchDevice' is used to detect whether a device has a touchscreen or is mouse-based @@ -104,28 +103,16 @@ function Placeholder(props) { const holdTimeout = useRef(null); const startTime = useRef(null); // Track when the user starts holdings const [isDisableDragging, setIsDisableDragging] = useState(true); - const [selectDate, setSelectDate] = useState({ - date: - props.pos.type === "date" - ? moment( - getDefaultdate( - props?.pos?.options?.response, - props.pos?.options?.validation?.format - ).getTime() - ).format( - changeDateToMomentFormat(props.pos?.options?.validation?.format) - ) - : "", - format: - props.pos.type === "date" - ? getDefaultFormat(props.pos?.options?.validation?.format) - : "" - }); + const [selectDate, setSelectDate] = useState({}); const [dateFormat, setDateFormat] = useState([]); const [clickonWidget, setClickonWidget] = useState({}); const [startDate, setStartDate] = useState( - props.pos.type === "date" && - getDefaultdate(new Date(), props.pos?.options?.validation?.format) + props?.pos?.options?.response + ? getDefaultDate( + props?.pos?.options?.response, + props.pos?.options?.validation?.format + ) + : new Date() ); const [getCheckboxRenderWidth, setGetCheckboxRenderWidth] = useState({ width: null, @@ -164,7 +151,6 @@ function Placeholder(props) { return () => clearTimeout(timer); }, [props.pos]); - useEffect(() => { const onOutsideClick = () => { if (!isDraggingEnabled) { @@ -453,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); @@ -476,7 +464,7 @@ function Placeholder(props) { const isDateChange = true; const dateObj = { date: startDate, - format: selectDate.format + format: getDefaultFormat(props.pos?.options?.validation?.format) }; handleSaveDate(dateObj, isDateChange); //function to save date and format in local array } @@ -485,19 +473,18 @@ function Placeholder(props) { //function to save date and format on local array onchange date and onclick format const handleSaveDate = (data, isDateChange) => { let updateDate = data.date; - //check if date change by user - if (isDateChange) { - //`changeDateToMomentFormat` is used to convert date as per required to moment package - updateDate = moment(data.date).format( - changeDateToMomentFormat(data.format) + let date; + if (data?.format === "dd-MM-yyyy") { + date = isDateChange + ? moment(updateDate).format(changeDateToMomentFormat(data.format)) + : updateDate; + } else { + //using moment package is used to change date as per the format provided in selectDate obj e.g. - MM/dd/yyyy -> 03/12/2024 + const newDate = new Date(updateDate); + date = moment(newDate.getTime()).format( + changeDateToMomentFormat(data?.format) ); } - //using moment package is used to change date as per the format provided in selectDate obj e.g. - MM/dd/yyyy -> 03/12/2024 - //`getDefaultdate` is used to convert update date in new Date() format - const date = moment( - getDefaultdate(updateDate, data?.format).getTime() - ).format(changeDateToMomentFormat(data?.format)); - //`onChangeInput` is used to save data related to date in a placeholder field onChangeInput( date, @@ -1100,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} diff --git a/apps/OpenSign/src/components/pdf/PlaceholderType.js b/apps/OpenSign/src/components/pdf/PlaceholderType.js index 788c17d527..624b82f6a5 100644 --- a/apps/OpenSign/src/components/pdf/PlaceholderType.js +++ b/apps/OpenSign/src/components/pdf/PlaceholderType.js @@ -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) => (
) : (
- {props?.handleUserName && - props?.handleUserName( - props?.data?.Id, - props?.data?.Role, - widgetTypeTraslation, - props.pos - )} + {props.pos.type && ( +
+ {props.isNeedSign + ? props.pos?.options?.hint || widgetTypeTraslation + : widgetTypeTraslation} +
+ )}
); case "stamp": @@ -346,13 +358,20 @@ function PlaceholderType(props) { /> ) : (
- {props?.handleUserName && - props?.handleUserName( - props?.data?.Id, - props?.data?.Role, - widgetTypeTraslation, - props.pos - )} + {props.pos.type && ( +
+ {props.isNeedSign + ? props.pos?.options?.hint || widgetTypeTraslation + : widgetTypeTraslation} +
+ )}
); case "checkbox": @@ -533,13 +552,20 @@ function PlaceholderType(props) { /> ) : (
- {props?.handleUserName && - props?.handleUserName( - props?.data?.Id, - props?.data?.Role, - widgetTypeTraslation, - props.pos - )} + {props.pos.type && ( +
+ {props.isNeedSign + ? props.pos?.options?.hint || widgetTypeTraslation + : widgetTypeTraslation} +
+ )}
); case "name": @@ -548,7 +574,7 @@ function PlaceholderType(props) { (props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (