Merge pull request #1696 from OpenSignLabs/updates-14659482113

v2.19.0
This commit is contained in:
Amol
2025-04-25 13:13:08 +05:30
committed by GitHub
29 changed files with 2074 additions and 1586 deletions
+268 -617
View File
File diff suppressed because it is too large Load Diff
+13 -13
View File
@@ -4,16 +4,16 @@
"private": true,
"dependencies": {
"@formkit/auto-animate": "^0.8.2",
"@lottiefiles/dotlottie-react": "^0.13.2",
"@lottiefiles/dotlottie-react": "^0.13.4",
"@pdf-lib/fontkit": "^1.1.1",
"@radix-ui/themes": "^3.1.6",
"@reduxjs/toolkit": "^2.5.1",
"@reduxjs/toolkit": "^2.7.0",
"axios": "^1.8.4",
"css-minimizer-webpack-plugin": "^7.0.2",
"date-fns-tz": "^3.2.0",
"file-saver": "^2.0.5",
"i18next": "^23.16.8",
"i18next-browser-languagedetector": "^8.0.4",
"i18next-browser-languagedetector": "^8.0.5",
"i18next-http-backend": "^3.0.2",
"jszip": "^3.10.1",
"jwt-decode": "^4.0.0",
@@ -35,20 +35,20 @@
"react-dom": "^18.2.0",
"react-gtm-module": "^2.0.11",
"react-helmet": "^6.1.0",
"react-i18next": "^15.4.1",
"react-i18next": "^15.5.0",
"react-konva": "^18.2.10",
"react-pdf": "^9.2.1",
"react-quill-new": "^3.4.6",
"react-redux": "^9.2.0",
"react-rnd": "^10.5.2",
"react-router": "^7.1.5",
"react-router": "^7.5.1",
"react-scripts": "^5.0.1",
"react-scrollbars-custom": "^4.1.1",
"react-select": "^5.10.1",
"react-signature-canvas": "^1.0.7",
"react-syntax-highlighter": "^15.6.1",
"react-timezone-select": "^3.2.8",
"react-tooltip": "^5.28.0",
"react-tooltip": "^5.28.1",
"react-web-share": "^2.0.2",
"reactour": "^1.19.4",
"redux": "^5.0.1",
@@ -105,19 +105,19 @@
"commitizen": "^4.3.1",
"concurrently": "^9.1.2",
"css-loader": "^7.1.2",
"daisyui": "^4.12.23",
"dotenv": "^16.4.7",
"daisyui": "^4.12.24",
"dotenv": "^16.5.0",
"dotenv-webpack": "^8.1.0",
"eslint": "^9.23.0",
"eslint-plugin-prettier": "^5.2.5",
"eslint-plugin-react": "^7.37.4",
"lint-staged": "^15.5.0",
"eslint": "^8.57.1",
"eslint-plugin-prettier": "^5.2.6",
"eslint-plugin-react": "^7.37.5",
"lint-staged": "^15.5.1",
"mini-css-extract-plugin": "^2.9.2",
"postcss": "^8.5.3",
"prettier": "^3.5.3",
"pretty-quick": "^4.1.1",
"tailwindcss": "^3.4.17",
"terser-webpack-plugin": "^5.3.11",
"terser-webpack-plugin": "^5.3.14",
"webpack-cli": "^5.1.4"
},
"overrides": {
@@ -1,4 +1,4 @@
{
{
"header-news": "Neue Funktion: Benutzer des Teams-Plans können jetzt ihre eigenen AWS S3-Buckets für die Dateispeicherung integrieren",
"header-news-btn": "Jetzt einrichten",
"create-account": "Konto erstellen",
@@ -341,7 +341,7 @@
"verify-email-1": "E-Mail verifizieren",
"resend": "Erneut senden",
"contact-details": "Kontaktdetails",
"verify-email": "Bitte verifizieren Sie Ihre E-Mail!",
"verify-email": "Bitte bestätigen Sie Ihre E-Mail, um fortzufahren. Dies ist ein einmaliger Schritt, um die mit Ihrem OpenSign-Konto verknüpfte E-Mail zu bestätigen.",
"send-otp": "OTP senden",
"otp-placeholder": "Verifizierungscode aus der E-Mail eingeben",
"loading-doc": "Dokument wird geladen...",
@@ -887,5 +887,83 @@
"do-you-want-recreate-document?": "Dadurch wird ein Entwurf aus diesem Dokument mit allen vorhandenen Feldern erstellt. Sind Sie sicher, dass Sie dieses Dokument neu erstellen möchten?",
"start-editing": "Bearbeitung starten",
"unsaved-changes-discard-them?": "Sie haben ungespeicherte Änderungen. Verwerfen?",
"yes-discard": "Ja, verwerfen"
"yes-discard": "Ja, verwerfen",
"LTV-enabled-signatures": "LTV-fähige Signaturen",
"BETA": "BETA",
"two-factor-authentication": "Zwei-Faktor-Authentifizierung",
"2fa-help-text": "Die Zwei-Faktor-Authentifizierung fügt Ihrem Konto eine zusätzliche Sicherheitsebene hinzu, indem sie mehr als nur ein Passwort zum Anmelden erfordert.",
"2fa-help-bullet1": "Erhöht die Sicherheit, indem sowohl Ihr Passwort als auch ein Bestätigungscode erforderlich sind.",
"2fa-help-bullet2": "Der Bestätigungscode wird von einer Authentifizierungs-App auf Ihrem Gerät generiert.",
"2fa-help-bullet3": "Schützt Ihr Konto, selbst wenn Ihr Passwort kompromittiert wurde.",
"setup-2fa": "2FA einrichten",
"setup-2fa-again": "2FA erneut einrichten",
"2fa-setup-intro": "Schützen Sie Ihr Konto mit der Zwei-Faktor-Authentifizierung. Wenn aktiviert, müssen Sie bei jeder Anmeldung einen Code aus Ihrer Authentifizierungs-App eingeben.",
"scan-qr-code": "QR-Code scannen",
"scan-qr-instructions": "Verwenden Sie eine Authentifizierungs-App wie Google Authenticator, Microsoft Authenticator oder Authy, um diesen QR-Code zu scannen.",
"manual-setup-instructions": "Können Sie den Code nicht scannen? Sie können Ihre Authentifizierungs-App manuell mit diesem geheimen Schlüssel einrichten:",
"secret-key": "Geheimer Schlüssel",
"copied-to-clipboard": "In die Zwischenablage kopiert",
"copy-to-clipboard": "In die Zwischenablage kopieren",
"recovery-codes": "Wiederherstellungscodes",
"recovery-codes-instructions": "Speichern Sie diese Wiederherstellungscodes an einem sicheren Ort. Wenn Sie den Zugriff auf Ihre Authentifizierungs-App verlieren, können Sie einen dieser Einmal-Codes verwenden, um sich anzumelden.",
"download-recovery-codes": "Wiederherstellungscodes herunterladen",
"verification-code": "Bestätigungscode",
"enter-code-from-authenticator-app": "Geben Sie den 6-stelligen Code aus Ihrer Authentifizierungs-App ein",
"verification-code-required": "Bestätigungscode ist erforderlich",
"verification-code-invalid": "Ungültiger Bestätigungscode. Bitte versuchen Sie es erneut.",
"2fa-enabled": "2FA aktiviert",
"2fa-enabled-successfully": "Zwei-Faktor-Authentifizierung erfolgreich aktiviert",
"2fa-setup-complete": "Einrichtung abgeschlossen!",
"2fa-setup-complete-instructions": "Ihr Konto ist jetzt mit der Zwei-Faktor-Authentifizierung geschützt. Sie müssen bei jeder Anmeldung einen Bestätigungscode eingeben.",
"two-factor-verification": "Zwei-Faktor-Verifizierung",
"enter-verification-code-instructions": "Geben Sie den 6-stelligen Bestätigungscode aus Ihrer Authentifizierungs-App ein, um fortzufahren.",
"recovery-code": "Wiederherstellungscode",
"enter-recovery-code-help": "Geben Sie einen Ihrer Wiederherstellungscodes ein",
"recovery-code-required": "Wiederherstellungscode ist erforderlich",
"use-verification-code-instead": "Stattdessen Bestätigungscode verwenden",
"use-recovery-code-instead": "Stattdessen Wiederherstellungscode verwenden",
"regenerate-2fa-remove-existing": "Sind Sie sicher, dass Sie die Zwei-Faktor-Authentifizierung neu generieren möchten? Diese Aktion entfernt Ihre bestehenden Authentifizierungseinstellungen.",
"use-passkey": "Mit Passkey anmelden",
"security-section": "Sicherheit",
"passkey-authentication": "Passkey-Authentifizierung",
"passkey-not-supported": "Ihr Browser oder Gerät unterstützt keine Passkey-Authentifizierung",
"passkey-description": "Passkeys bieten eine stärkere, phishing-resistente Alternative zu Passwörtern. Sie können Ihren Fingerabdruck, Ihre Gesichtserkennung oder Ihre Geräte-PIN verwenden, um sich sicher anzumelden.",
"passkey-tooltip": "Passkeys sind eine einfachere und sicherere Alternative zu Passwörtern. Sie verwenden biometrische Daten wie Fingerabdrücke oder Gesichtserkennung, die bereits auf Ihrem Gerät gespeichert sind.",
"security-section-help": "Verwalten Sie Sicherheitsoptionen, einschließlich Passkeys und Authentifizierungsmethoden, um Ihr Konto zu schützen.",
"passkey-register": "Passkey registrieren",
"passkey-register-another": "Weiteren Passkey registrieren",
"passkey-registered": "Passkey registriert",
"passkey-registering": "Passkey wird registriert...",
"passkey-registered-success": "Passkey erfolgreich registriert!",
"passkey-registration-failed": "Passkey-Registrierung fehlgeschlagen",
"passkey-auth-failed": "Passkey-Authentifizierung fehlgeschlagen",
"passkey-missing-user-info": "Benutzerinformationen erforderlich",
"passkeys-list": "Ihre Passkeys",
"default-passkey": "Ihr Passkey",
"passkey-rename": "Umbenennen",
"passkey-delete": "Löschen",
"passkey-rename-title": "Passkey umbenennen",
"passkey-delete-title": "Passkey löschen",
"passkey-delete-confirm": "Möchten Sie den Passkey \"{{name}}\" wirklich löschen?",
"passkey-name": "Passkey-Name",
"passkey-name-placeholder": "Geben Sie einen beschreibenden Namen für diesen Passkey ein",
"passkey-renamed-success": "Passkey erfolgreich umbenannt",
"passkey-deleted-success": "Passkey erfolgreich gelöscht",
"passkey-rename-failed": "Passkey konnte nicht umbenannt werden",
"passkey-delete-failed": "Passkey konnte nicht gelöscht werden",
"processing": "Wird verarbeitet...",
"today": "Heute",
"yesterday": "Gestern",
"days-ago": "Vor {{days}} Tagen",
"verify-with-passkey": "Mit Passkey verifizieren",
"verify-with-otp": "Mit OTP verifizieren",
"verify-identity": "Identität verifizieren",
"verify-account": "Verifizieren Sie Ihre Identität",
"verification": "Verifizierung",
"passkey-verification-failed": "Passkey-Verifizierung fehlgeschlagen",
"security-auth-help": {
"p1": "Administre la configuración de seguridad de su cuenta para mantener sus datos seguros. OpenSign admite métodos de autenticación avanzados para mejorar la protección de la cuenta.",
"2fa-auth-help": "Agregue una capa adicional de seguridad activando 2FA. Esto requiere ingresar un código de verificación desde una aplicación autenticadora después de su contraseña.",
"passkey-auth-help": "Use claves de acceso para iniciar sesión sin contraseña con verificación biométrica o basada en el dispositivo, proporcionando una seguridad sólida y comodidad."
}
}
@@ -1,5 +1,5 @@
{
"header-news": "New feature: Teams plan users can now integrate their own AWS S3 buckets for file storage",
"header-news": "New feature: Protect your account with Two-Factor Authentication (2FA) and enjoy the future of login with Passkeys — no passwords needed.",
"header-news-btn": "Setup now",
"create-account": "Create account",
"login": "Login",
@@ -341,7 +341,7 @@
"verify-email-1": "Verify email",
"resend": "Resend",
"contact-details": "Contact details",
"verify-email": "Please verify your email !",
"verify-email": "Please verify your email to continue. This is a one-time step to confirm the email associated with your OpenSign account.",
"send-otp": " Send OTP",
"otp-placeholder": "Enter verification code received over email",
"loading-doc": "Loading document..",
@@ -887,5 +887,83 @@
"do-you-want-recreate-document?": "This will create a draft from this document with all fields intact. Are you sure you want to recreate this document?",
"start-editing": "Start Editing",
"unsaved-changes-discard-them?": "You have unsaved changes. Discard them?",
"yes-discard": "Yes, Discard"
}
"yes-discard": "Yes, Discard",
"LTV-enabled-signatures": "LTV enabled signatures",
"BETA": "BETA",
"two-factor-authentication": "Two-Factor Authentication",
"2fa-help-text": "Two-factor authentication adds an extra layer of security to your account by requiring more than just a password to sign in.",
"2fa-help-bullet1": "Enhances security by requiring both your password and a verification code.",
"2fa-help-bullet2": "The verification code is generated by an authenticator app on your device.",
"2fa-help-bullet3": "Protects your account even if your password is compromised.",
"setup-2fa": "Setup 2FA",
"setup-2fa-again": "Setup 2FA again",
"2fa-setup-intro": "Protect your account with two-factor authentication. When enabled, you'll need to enter a code from your authenticator app whenever you sign in.",
"scan-qr-code": "Scan QR Code",
"scan-qr-instructions": "Use an authenticator app like Google Authenticator, Microsoft Authenticator, or Authy to scan this QR code.",
"manual-setup-instructions": "Can't scan the code? You can manually set up your authenticator app using this secret key:",
"secret-key": "Secret Key",
"copied-to-clipboard": "Copied to clipboard",
"copy-to-clipboard": "Copy to clipboard",
"recovery-codes": "Recovery Codes",
"recovery-codes-instructions": "Save these recovery codes in a secure location. If you lose access to your authenticator app, you can use one of these one-time codes to sign in.",
"download-recovery-codes": "Download Recovery Codes",
"verification-code": "Verification Code",
"enter-code-from-authenticator-app": "Enter the 6-digit code from your authenticator app",
"verification-code-required": "Verification code is required",
"verification-code-invalid": "Invalid verification code. Please try again.",
"2fa-enabled": "2FA enabled",
"2fa-enabled-successfully": "Two-factor authentication enabled successfully",
"2fa-setup-complete": "Setup Complete!",
"2fa-setup-complete-instructions": "Your account is now protected with two-factor authentication. You'll need to enter a verification code each time you sign in.",
"two-factor-verification": "Two-Factor Verification",
"enter-verification-code-instructions": "Enter the 6-digit verification code from your authenticator app to continue.",
"recovery-code": "Recovery Code",
"enter-recovery-code-help": "Enter one of your recovery codes",
"recovery-code-required": "Recovery code is required",
"use-verification-code-instead": "Use verification code instead",
"use-recovery-code-instead": "Use recovery code instead",
"regenerate-2fa-remove-existing": "Are you sure you want to regenerate two-factor authentication? This action will remove your existing authentication settings.",
"use-passkey": "Sign in with passkey",
"security-section": "Security",
"passkey-authentication": "Passkey Authentication",
"passkey-not-supported": "Your browser or device doesn't support passkey authentication",
"passkey-description": "Passkeys provide a stronger, phishing-resistant alternative to passwords. You can use your fingerprint, face recognition, or device PIN to sign in securely.",
"passkey-tooltip": "Passkeys are a simpler and more secure alternative to passwords. They use biometric data like fingerprints or facial recognition that's already stored on your device.",
"security-section-help": "Manage security options including passkeys and authentication methods to protect your account.",
"passkey-register": "Register passkey",
"passkey-register-another": "Register another passkey",
"passkey-registered": "Passkey registered",
"passkey-registering": "Registering passkey...",
"passkey-registered-success": "Passkey successfully registered!",
"passkey-registration-failed": "Failed to register passkey",
"passkey-auth-failed": "Passkey authentication failed",
"passkey-missing-user-info": "User information is required",
"passkeys-list": "Your Passkeys",
"default-passkey": "Your Passkey",
"passkey-rename": "Rename",
"passkey-delete": "Delete",
"passkey-rename-title": "Rename Passkey",
"passkey-delete-title": "Delete Passkey",
"passkey-delete-confirm": "Are you sure you want to delete passkey \"{{name}}\"?",
"passkey-name": "Passkey Name",
"passkey-name-placeholder": "Enter a descriptive name for this passkey",
"passkey-renamed-success": "Passkey renamed successfully",
"passkey-deleted-success": "Passkey deleted successfully",
"passkey-rename-failed": "Failed to rename passkey",
"passkey-delete-failed": "Failed to delete passkey",
"processing": "Processing...",
"today": "Today",
"yesterday": "Yesterday",
"days-ago": "{{days}} days ago",
"verify-with-passkey": "Verify with passkey",
"verify-with-otp": "Verify with OTP",
"verification": "Verification",
"verify-account": "Verify your account",
"verify-identity": "Verify identity",
"passkey-verification-failed": "Passkey verification failed. Please try again or use OTP.",
"security-auth-help": {
"p1":"Manage your account's security settings to keep your data safe. OpenSign supports advanced authentication methods to enhance account protection.",
"2fa-auth-help":" Add an extra layer of security by enabling 2FA. This requires you to enter a verification code from an authenticator app after your password.",
"passkey-auth-help":"Use passkeys for passwordless sign-in with biometric or device-based verification, providing both strong security and convenience."
}
}
@@ -342,7 +342,7 @@
"verify-email-1": "Verificar correo",
"resend": "Reenviar",
"contact-details": "Detalles del contacto",
"verify-email": "¡Por favor, verifica tu correo!",
"verify-email": "Por favor verifique su correo electrónico para continuar. Este es un paso único para confirmar el correo electrónico asociado a su cuenta de OpenSign.",
"send-otp": " Enviar OTP",
"otp-placeholder": "Ingresa el código de verificación enviado por correo",
"loading-doc": "Cargando documento...",
@@ -887,5 +887,83 @@
"do-you-want-recreate-document?": "Esto creará un borrador a partir de este documento con todos los campos intactos. ¿Está seguro de que desea recrear este documento?",
"start-editing": "Comenzar a editar",
"unsaved-changes-discard-them?": "Tienes cambios sin guardar. ¿Deseas descartarlos?",
"yes-discard": "Sí, descartar"
"yes-discard": "Sí, descartar",
"LTV-enabled-signatures": "Firmas con LTV habilitado",
"BETA": "BETA",
"two-factor-authentication": "Autenticación de dos factores",
"2fa-help-text": "La autenticación de dos factores agrega una capa adicional de seguridad a su cuenta al requerir más que solo una contraseña para iniciar sesión.",
"2fa-help-bullet1": "Mejora la seguridad al requerir tanto su contraseña como un código de verificación.",
"2fa-help-bullet2": "El código de verificación es generado por una aplicación de autenticación en su dispositivo.",
"2fa-help-bullet3": "Protege su cuenta incluso si su contraseña se ve comprometida.",
"setup-2fa": "Configurar 2FA",
"setup-2fa-again": "Configurar 2FA nuevamente",
"2fa-setup-intro": "Proteja su cuenta con la autenticación de dos factores. Cuando esté habilitada, deberá ingresar un código de su aplicación de autenticación cada vez que inicie sesión.",
"scan-qr-code": "Escanear código QR",
"scan-qr-instructions": "Use una aplicación de autenticación como Google Authenticator, Microsoft Authenticator o Authy para escanear este código QR.",
"manual-setup-instructions": "¿No puede escanear el código? Puede configurar manualmente su aplicación de autenticación utilizando esta clave secreta:",
"secret-key": "Clave secreta",
"copied-to-clipboard": "Copiado al portapapeles",
"copy-to-clipboard": "Copiar al portapapeles",
"recovery-codes": "Códigos de recuperación",
"recovery-codes-instructions": "Guarde estos códigos de recuperación en un lugar seguro. Si pierde el acceso a su aplicación de autenticación, puede usar uno de estos códigos de un solo uso para iniciar sesión.",
"download-recovery-codes": "Descargar códigos de recuperación",
"verification-code": "Código de verificación",
"enter-code-from-authenticator-app": "Ingrese el código de 6 dígitos de su aplicación de autenticación",
"verification-code-required": "Se requiere el código de verificación",
"verification-code-invalid": "Código de verificación inválido. Por favor, inténtelo de nuevo.",
"2fa-enabled": "2FA activado",
"2fa-enabled-successfully": "Autenticación de dos factores habilitada con éxito",
"2fa-setup-complete": "¡Configuración completa!",
"2fa-setup-complete-instructions": "Su cuenta ahora está protegida con autenticación de dos factores. Deberá ingresar un código de verificación cada vez que inicie sesión.",
"two-factor-verification": "Verificación de dos factores",
"enter-verification-code-instructions": "Ingrese el código de verificación de 6 dígitos de su aplicación de autenticación para continuar.",
"recovery-code": "Código de recuperación",
"enter-recovery-code-help": "Ingrese uno de sus códigos de recuperación",
"recovery-code-required": "Se requiere el código de recuperación",
"use-verification-code-instead": "Usar código de verificación en su lugar",
"use-recovery-code-instead": "Usar código de recuperación en su lugar",
"regenerate-2fa-remove-existing": "¿Está seguro de que desea regenerar la autenticación de dos factores? Esta acción eliminará su configuración de autenticación actual.",
"use-passkey": "Iniciar sesión con passkey",
"security-section": "Seguridad",
"passkey-authentication": "Autenticación con passkey",
"passkey-not-supported": "Tu navegador o dispositivo no admite la autenticación con passkey",
"passkey-description": "Las passkeys proporcionan una alternativa más segura y resistente al phishing que las contraseñas. Puedes usar tu huella digital, reconocimiento facial o PIN del dispositivo para iniciar sesión de forma segura.",
"passkey-tooltip": "Las passkeys son una alternativa más simple y segura a las contraseñas. Usan datos biométricos como huellas digitales o reconocimiento facial ya almacenados en tu dispositivo.",
"security-section-help": "Administra las opciones de seguridad, incluidas las passkeys y métodos de autenticación para proteger tu cuenta.",
"passkey-register": "Registrar passkey",
"passkey-register-another": "Registrar otra passkey",
"passkey-registered": "Passkey registrada",
"passkey-registering": "Registrando passkey...",
"passkey-registered-success": "¡Passkey registrada con éxito!",
"passkey-registration-failed": "Fallo al registrar la passkey",
"passkey-auth-failed": "Falló la autenticación con passkey",
"passkey-missing-user-info": "Se requiere información del usuario",
"passkeys-list": "Tus passkeys",
"default-passkey": "Tu passkey",
"passkey-rename": "Renombrar",
"passkey-delete": "Eliminar",
"passkey-rename-title": "Renombrar passkey",
"passkey-delete-title": "Eliminar passkey",
"passkey-delete-confirm": "¿Estás seguro de que deseas eliminar la passkey \"{{name}}\"?",
"passkey-name": "Nombre de la passkey",
"passkey-name-placeholder": "Introduce un nombre descriptivo para esta passkey",
"passkey-renamed-success": "Passkey renombrada con éxito",
"passkey-deleted-success": "Passkey eliminada con éxito",
"passkey-rename-failed": "No se pudo renombrar la passkey",
"passkey-delete-failed": "No se pudo eliminar la passkey",
"processing": "Procesando...",
"today": "Hoy",
"yesterday": "Ayer",
"days-ago": "Hace {{days}} días",
"verify-with-passkey": "Verificar con passkey",
"verify-with-otp": "Verificar con OTP",
"verify-identity": "Verificar identidad",
"verify-account": "Verifique su identidad",
"verification": "Verificación",
"passkey-verification-failed": "La verificación con passkey ha fallado",
"security-auth-help": {
"p1": "Administre la configuración de seguridad de su cuenta para mantener sus datos seguros. OpenSign admite métodos de autenticación avanzados para mejorar la protección de la cuenta.",
"2fa-auth-help": "Agregue una capa adicional de seguridad activando 2FA. Esto requiere ingresar un código de verificación desde una aplicación autenticadora después de su contraseña.",
"passkey-auth-help": "Use claves de acceso para iniciar sesión sin contraseña con verificación biométrica o basada en el dispositivo, proporcionando una seguridad sólida y comodidad."
}
}
@@ -341,7 +341,7 @@
"verify-email-1": "Vérifier l'e-mail",
"resend": "Renvoyer",
"contact-details": "Coordonnées",
"verify-email": "Veuillez vérifier votre email!",
"verify-email": "Veuillez vérifier votre e-mail pour continuer. C'est une étape unique pour confirmer l'adresse e-mail associée à votre compte OpenSign.",
"send-otp": "envoyer un code à usage unique",
"otp-placeholder": "Entrez le code de vérification reçu par e-mail",
"loading-doc": "Chargement du document..",
@@ -887,5 +887,83 @@
"do-you-want-recreate-document?": "Cela créera un brouillon à partir de ce document avec tous les champs intacts. Êtes-vous sûr de vouloir recréer ce document ?",
"start-editing": "Commencer l'édition",
"unsaved-changes-discard-them?": "Vous avez des modifications non enregistrées. Les supprimer ?",
"yes-discard": "Oui, supprimer"
"yes-discard": "Oui, supprimer",
"LTV-enabled-signatures": "Signatures avec LTV activée",
"BETA": "BETA",
"two-factor-authentication": "Authentification à deux facteurs",
"2fa-help-text": "L'authentification à deux facteurs ajoute une couche de sécurité supplémentaire à votre compte en exigeant plus qu'un simple mot de passe pour se connecter.",
"2fa-help-bullet1": "Renforce la sécurité en demandant à la fois votre mot de passe et un code de vérification.",
"2fa-help-bullet2": "Le code de vérification est généré par une application d'authentification sur votre appareil.",
"2fa-help-bullet3": "Protège votre compte même si votre mot de passe est compromis.",
"setup-2fa": "Configurer 2FA",
"setup-2fa-again": "Reconfigurer 2FA",
"2fa-setup-intro": "Protégez votre compte avec l'authentification à deux facteurs. Une fois activée, vous devrez saisir un code provenant de votre application d'authentification à chaque connexion.",
"scan-qr-code": "Scanner le code QR",
"scan-qr-instructions": "Utilisez une application d'authentification comme Google Authenticator, Microsoft Authenticator ou Authy pour scanner ce code QR.",
"manual-setup-instructions": "Vous ne pouvez pas scanner le code ? Configurez manuellement votre application d'authentification avec cette clé secrète :",
"secret-key": "Clé secrète",
"copied-to-clipboard": "Copié dans le presse-papiers",
"copy-to-clipboard": "Copier dans le presse-papiers",
"recovery-codes": "Codes de récupération",
"recovery-codes-instructions": "Enregistrez ces codes de récupération dans un endroit sûr. Si vous perdez l'accès à votre application d'authentification, vous pourrez utiliser l'un de ces codes à usage unique pour vous connecter.",
"download-recovery-codes": "Télécharger les codes de récupération",
"verification-code": "Code de vérification",
"enter-code-from-authenticator-app": "Saisissez le code à 6 chiffres de votre application d'authentification",
"verification-code-required": "Le code de vérification est requis",
"verification-code-invalid": "Code de vérification invalide. Veuillez réessayer.",
"2fa-enabled": "2FA activé",
"2fa-enabled-successfully": "Authentification à deux facteurs activée avec succès",
"2fa-setup-complete": "Configuration terminée !",
"2fa-setup-complete-instructions": "Votre compte est maintenant protégé par l'authentification à deux facteurs. Vous devrez entrer un code de vérification à chaque connexion.",
"two-factor-verification": "Vérification en deux étapes",
"enter-verification-code-instructions": "Saisissez le code de vérification à 6 chiffres de votre application d'authentification pour continuer.",
"recovery-code": "Code de récupération",
"enter-recovery-code-help": "Saisissez l'un de vos codes de récupération",
"recovery-code-required": "Le code de récupération est requis",
"use-verification-code-instead": "Utiliser le code de vérification à la place",
"use-recovery-code-instead": "Utiliser le code de récupération à la place",
"regenerate-2fa-remove-existing": "Êtes-vous sûr de vouloir régénérer l'authentification à deux facteurs ? Cette action supprimera vos paramètres d'authentification existants.",
"use-passkey": "Se connecter avec une clé d'accès",
"security-section": "Sécurité",
"passkey-authentication": "Authentification par clé d'accès",
"passkey-not-supported": "Votre navigateur ou appareil ne prend pas en charge l'authentification par clé d'accès",
"passkey-description": "Les clés d'accès offrent une alternative plus sécurisée et résistante au phishing que les mots de passe. Vous pouvez utiliser votre empreinte digitale, reconnaissance faciale ou code PIN de l'appareil pour vous connecter en toute sécurité.",
"passkey-tooltip": "Les clés d'accès sont une alternative plus simple et plus sécurisée aux mots de passe. Elles utilisent des données biométriques comme les empreintes digitales ou la reconnaissance faciale déjà stockées sur votre appareil.",
"security-section-help": "Gérez les options de sécurité, y compris les clés d'accès et les méthodes d'authentification pour protéger votre compte.",
"passkey-register": "Enregistrer une clé d'accès",
"passkey-register-another": "Enregistrer une autre clé d'accès",
"passkey-registered": "Clé d'accès enregistrée",
"passkey-registering": "Enregistrement de la clé d'accès...",
"passkey-registered-success": "Clé d'accès enregistrée avec succès !",
"passkey-registration-failed": "Échec de l'enregistrement de la clé d'accès",
"passkey-auth-failed": "Échec de l'authentification avec la clé d'accès",
"passkey-missing-user-info": "Informations utilisateur requises",
"passkeys-list": "Vos clés d'accès",
"default-passkey": "Votre clé d'accès",
"passkey-rename": "Renommer",
"passkey-delete": "Supprimer",
"passkey-rename-title": "Renommer la clé d'accès",
"passkey-delete-title": "Supprimer la clé d'accès",
"passkey-delete-confirm": "Êtes-vous sûr de vouloir supprimer la clé d'accès « {{name}} » ?",
"passkey-name": "Nom de la clé d'accès",
"passkey-name-placeholder": "Entrez un nom descriptif pour cette clé d'accès",
"passkey-renamed-success": "Clé d'accès renommée avec succès",
"passkey-deleted-success": "Clé d'accès supprimée avec succès",
"passkey-rename-failed": "Échec du renommage de la clé d'accès",
"passkey-delete-failed": "Échec de la suppression de la clé d'accès",
"processing": "Traitement en cours...",
"today": "Aujourd'hui",
"yesterday": "Hier",
"days-ago": "Il y a {{days}} jours",
"verify-with-passkey": "Vérifier avec une passkey",
"verify-with-otp": "Vérifier avec OTP",
"verify-identity": "Vérifier l'identité",
"verify-account": "Vérifiez votre identité",
"verification": "Vérification",
"passkey-verification-failed": "La vérification par passkey a échoué",
"security-auth-help": {
"p1": "Gérez les paramètres de sécurité de votre compte pour protéger vos données. OpenSign prend en charge des méthodes d'authentification avancées pour renforcer la protection de votre compte.",
"2fa-auth-help": "Ajoutez une couche de sécurité supplémentaire en activant 2FA. Cela vous demandera de saisir un code de vérification provenant dune application dauthentification après votre mot de passe.",
"passkey-auth-help": "Utilisez des passkeys pour une connexion sans mot de passe grâce à une vérification biométrique ou basée sur lappareil, offrant à la fois une sécurité renforcée et une grande commodité."
}
}
@@ -341,7 +341,7 @@
"verify-email-1": "Verifica email",
"resend": "Reinvia",
"contact-details": "Dettagli contatto",
"verify-email": "Verifica la tua email!",
"verify-email": "Si prega di verificare l'e-mail per continuare. Questo è un passaggio unico per confermare l'e-mail associata al tuo account OpenSign.",
"send-otp": "Invia OTP",
"otp-placeholder": "Inserisci il codice di verifica ricevuto via email",
"loading-doc": "Caricamento del documento...",
@@ -887,5 +887,83 @@
"do-you-want-recreate-document?": "Questo creerà una bozza da questo documento con tutti i campi intatti. Sei sicuro di voler ricreare questo documento?",
"start-editing": "Inizia a modificare",
"unsaved-changes-discard-them?": "Hai modifiche non salvate. Vuoi scartarle?",
"yes-discard": "Sì, scarta"
"yes-discard": "Sì, scarta",
"LTV-enabled-signatures": "Firme con LTV abilitato",
"BETA": "BETA",
"two-factor-authentication": "Autenticazione a due fattori",
"2fa-help-text": "L'autenticazione a due fattori aggiunge un ulteriore livello di sicurezza al tuo account richiedendo più di una semplice password per accedere.",
"2fa-help-bullet1": "Aumenta la sicurezza richiedendo sia la password che un codice di verifica.",
"2fa-help-bullet2": "Il codice di verifica viene generato da un'app di autenticazione sul tuo dispositivo.",
"2fa-help-bullet3": "Protegge il tuo account anche se la password è stata compromessa.",
"setup-2fa": "Configura 2FA",
"setup-2fa-again": "Configura nuovamente 2FA",
"2fa-setup-intro": "Proteggi il tuo account con l'autenticazione a due fattori. Quando è attiva, dovrai inserire un codice dall'app di autenticazione ogni volta che accedi.",
"scan-qr-code": "Scansiona il codice QR",
"scan-qr-instructions": "Usa un'app di autenticazione come Google Authenticator, Microsoft Authenticator o Authy per scansionare questo codice QR.",
"manual-setup-instructions": "Non riesci a scansionare il codice? Puoi configurare manualmente l'app usando questa chiave segreta:",
"secret-key": "Chiave segreta",
"copied-to-clipboard": "Copiato negli appunti",
"copy-to-clipboard": "Copia negli appunti",
"recovery-codes": "Codici di recupero",
"recovery-codes-instructions": "Salva questi codici di recupero in un luogo sicuro. Se perdi l'accesso all'app di autenticazione, puoi usarne uno per accedere.",
"download-recovery-codes": "Scarica i codici di recupero",
"verification-code": "Codice di verifica",
"enter-code-from-authenticator-app": "Inserisci il codice a 6 cifre dalla tua app di autenticazione",
"verification-code-required": "È richiesto il codice di verifica",
"verification-code-invalid": "Codice di verifica non valido. Riprova.",
"2fa-enabled": "2FA abilitato",
"2fa-enabled-successfully": "Autenticazione a due fattori attivata con successo",
"2fa-setup-complete": "Configurazione completata!",
"2fa-setup-complete-instructions": "Il tuo account è ora protetto da autenticazione a due fattori. Dovrai inserire un codice ogni volta che accedi.",
"two-factor-verification": "Verifica a due fattori",
"enter-verification-code-instructions": "Inserisci il codice di verifica a 6 cifre dalla tua app di autenticazione per continuare.",
"recovery-code": "Codice di recupero",
"enter-recovery-code-help": "Inserisci uno dei tuoi codici di recupero",
"recovery-code-required": "È richiesto il codice di recupero",
"use-verification-code-instead": "Usa il codice di verifica invece",
"use-recovery-code-instead": "Usa il codice di recupero invece",
"regenerate-2fa-remove-existing": "Sei sicuro di voler rigenerare l'autenticazione a due fattori? Questa azione rimuoverà le impostazioni di autenticazione esistenti.",
"use-passkey": "Accedi con passkey",
"security-section": "Sicurezza",
"passkey-authentication": "Autenticazione passkey",
"passkey-not-supported": "Il tuo browser o dispositivo non supporta l'autenticazione passkey",
"passkey-description": "Le passkey offrono unalternativa più sicura e resistente al phishing rispetto alle password. Puoi usare limpronta digitale, il riconoscimento facciale o il PIN del dispositivo per accedere in sicurezza.",
"passkey-tooltip": "Le passkey sono unalternativa più semplice e sicura alle password. Usano dati biometrici come impronte digitali o riconoscimento facciale già presenti sul tuo dispositivo.",
"security-section-help": "Gestisci le opzioni di sicurezza, incluse passkey e metodi di autenticazione, per proteggere il tuo account.",
"passkey-register": "Registra passkey",
"passkey-register-another": "Registra un'altra passkey",
"passkey-registered": "Passkey registrata",
"passkey-registering": "Registrazione passkey in corso...",
"passkey-registered-success": "Passkey registrata con successo!",
"passkey-registration-failed": "Registrazione della passkey non riuscita",
"passkey-auth-failed": "Autenticazione passkey fallita",
"passkey-missing-user-info": "Informazioni utente richieste",
"passkeys-list": "Le tue passkey",
"default-passkey": "La tua passkey",
"passkey-rename": "Rinomina",
"passkey-delete": "Elimina",
"passkey-rename-title": "Rinomina passkey",
"passkey-delete-title": "Elimina passkey",
"passkey-delete-confirm": "Sei sicuro di voler eliminare la passkey \"{{name}}\"?",
"passkey-name": "Nome della passkey",
"passkey-name-placeholder": "Inserisci un nome descrittivo per questa passkey",
"passkey-renamed-success": "Passkey rinominata con successo",
"passkey-deleted-success": "Passkey eliminata con successo",
"passkey-rename-failed": "Impossibile rinominare la passkey",
"passkey-delete-failed": "Impossibile eliminare la passkey",
"processing": "Elaborazione...",
"today": "Oggi",
"yesterday": "Ieri",
"days-ago": "{{days}} giorni fa",
"verify-with-passkey": "Verifica con passkey",
"verify-with-otp": "Verifica con OTP",
"verify-identity": "Verifica identità",
"verify-account": "Verifica la tua identità",
"verification": "Verifica",
"passkey-verification-failed": "Verifica con passkey fallita",
"security-auth-help": {
"p1": "Gestisci le impostazioni di sicurezza del tuo account per proteggere i tuoi dati. OpenSign supporta metodi di autenticazione avanzati per migliorare la protezione dellaccount.",
"2fa-auth-help": "Aggiungi un ulteriore livello di sicurezza abilitando 2FA. Ti verrà richiesto di inserire un codice di verifica da un'app di autenticazione dopo la password.",
"passkey-auth-help": "Usa le passkey per accedere senza password con verifica biometrica o basata sul dispositivo, garantendo sicurezza elevata e praticità."
}
}
+17 -16
View File
@@ -31,14 +31,15 @@ const Header = ({ showSidebar, setIsMenu, isConsole }) => {
initializeHead();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
async function initializeHead() {
const applogo = await getAppLogo();
if (applogo?.logo) {
setAppLogo(applogo?.logo);
} else {
const logo = localStorage.getItem("appLogo") || appInfo.applogo;
setAppLogo(logo);
}
const applogo = await getAppLogo();
if (applogo?.logo) {
setAppLogo(applogo?.logo);
} else {
const logo = localStorage.getItem("appLogo") || appInfo.applogo;
setAppLogo(logo);
}
}
const closeDropdown = async () => {
@@ -151,15 +152,15 @@ const Header = ({ showSidebar, setIsMenu, isConsole }) => {
>
{!isConsole && (
<>
<li
onClick={() =>
openInNewTab("https://docs.opensignlabs.com")
}
>
<span>
<i className="fa-light fa-book"></i> {t("docs")}
</span>
</li>
<li
onClick={() =>
openInNewTab("https://docs.opensignlabs.com")
}
>
<span>
<i className="fa-light fa-book"></i> {t("docs")}
</span>
</li>
<li
onClick={() => {
setIsOpen(false);
@@ -1,10 +1,10 @@
import React, { useEffect, useState } from "react";
import React, { useState } from "react";
import Parse from "parse";
import Alert from "../../../primitives/Alert";
import Loader from "../../../primitives/Loader";
import { useTranslation } from "react-i18next";
const CreateFolder = ({ parentFolderId, onSuccess, folderCls }) => {
const CreateFolder = ({ parentFolderId, onSuccess, folderCls, onBack }) => {
const folderPtr = {
__type: "Pointer",
className: folderCls,
@@ -12,41 +12,11 @@ const CreateFolder = ({ parentFolderId, onSuccess, folderCls }) => {
};
const { t } = useTranslation();
const [name, setName] = useState("");
const [folderList, setFolderList] = useState([]);
const [isAlert, setIsAlert] = useState(false);
const [isLoader, setIsLoader] = useState(false);
const [selectedParent, setSelectedParent] = useState();
const [alert, setAlert] = useState({ type: "info", message: "" });
useEffect(() => {
fetchFolder();
// eslint-disable-next-line
}, []);
const fetchFolder = async () => {
try {
const FolderQuery = new Parse.Query(folderCls);
if (parentFolderId) {
FolderQuery.equalTo("Folder", folderPtr);
FolderQuery.equalTo("Type", "Folder");
FolderQuery.notEqualTo("IsArchive", true);
FolderQuery.equalTo("CreatedBy", Parse.User.current());
} else {
FolderQuery.doesNotExist("Folder");
FolderQuery.equalTo("Type", "Folder");
FolderQuery.notEqualTo("IsArchive", true);
FolderQuery.equalTo("CreatedBy", Parse.User.current());
}
const res = await FolderQuery.find();
if (res) {
const result = JSON.parse(JSON.stringify(res));
if (result) {
setFolderList(result);
}
}
} catch (error) {
console.log("Err ", error);
}
const showToast = (type, msg) => {
setAlert({ type: type, message: msg });
setTimeout(() => setAlert({ type: type, message: "" }), 1000);
};
const handleCreateFolder = async (event) => {
event.preventDefault();
@@ -62,60 +32,32 @@ const CreateFolder = ({ parentFolderId, onSuccess, folderCls }) => {
}
const templExist = await exsitQuery.first();
if (templExist) {
setAlert({ type: "danger", message: t("folder-already-exist") });
setIsAlert(true);
setTimeout(() => {
setIsAlert(false);
}, 1000);
showToast("danger", t("folder-already-exist"));
} else {
const template = new Parse.Object(folderCls);
template.set("Name", name);
template.set("Type", "Folder");
if (selectedParent) {
template.set("Folder", {
__type: "Pointer",
className: folderCls,
objectId: selectedParent
});
} else if (parentFolderId) {
if (parentFolderId) {
template.set("Folder", folderPtr);
}
template.set("CreatedBy", Parse.User.createWithoutData(currentUser.id));
const res = await template.save();
if (res) {
handleLoader(false);
setAlert({
type: "success",
message: t("folder-created-successfully")
});
setIsAlert(true);
setTimeout(() => {
setIsAlert(false);
}, 1000);
if (onSuccess) {
onSuccess(res);
}
showToast("success", t("folder-created-successfully"));
onSuccess && onSuccess(res?.toJSON());
}
}
} else {
handleLoader(false);
setAlert({ type: "info", message: t("fill-folder-name") });
setIsAlert(true);
setTimeout(() => {
setIsAlert(false);
}, 1000);
showToast("info", t("fill-folder-name"));
}
};
const handleOptions = (e) => {
setSelectedParent(e.target.value);
};
const handleLoader = (status) => {
setIsLoader(status);
};
const handleLoader = (status) => setIsLoader(status);
return (
<div>
{isAlert && <Alert type={alert.type}>{alert.message}</Alert>}
{alert.message && <Alert type={alert.type}>{alert.message}</Alert>}
<div id="createFolder" className="relative">
{isLoader && (
<div className="absolute h-full w-full flex justify-center items-center">
@@ -139,31 +81,25 @@ const CreateFolder = ({ parentFolderId, onSuccess, folderCls }) => {
required
/>
</div>
<div className="text-xs mt-2">
<label className="block">{t("parent-folder")}</label>
<select
value={selectedParent}
onChange={handleOptions}
className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content w-full text-xs"
>
<option>select</option>
{folderList.length > 0 &&
folderList.map((x) => (
<option key={x.objectId} value={x.objectId}>
{x.Name}
</option>
))}
</select>
</div>
<div>
<div className="flex justify-between items-center py-[1rem] ">
<button
onClick={handleCreateFolder}
disabled={isLoader}
className="op-btn op-btn-primary op-btn-sm mt-3"
className="op-btn op-btn-primary op-btn-sm"
>
<i className="fa-light fa-plus"></i>
<span>{t("create")}</span>
</button>
{onBack && (
<div
className="op-btn op-btn-seconday op-btn-sm"
title={t("back")}
onClick={() => onBack()}
>
<i className="fa-light fa-arrow-left" aria-hidden="true"></i>
<span className="text-xs">{t("back")}</span>
</div>
)}
</div>
</div>
</div>
@@ -6,8 +6,7 @@ import { useTranslation } from "react-i18next";
const FolderModal = (props) => {
const { t } = useTranslation();
const appName =
"OpenSign™";
const appName = "OpenSign™";
const drivename = appName === "OpenSign™" ? "OpenSign™" : "";
const [clickFolder, setClickFolder] = useState("");
const [folderList, setFolderList] = useState([]);
@@ -105,49 +104,45 @@ const FolderModal = (props) => {
// `handleCancel` is call when user click on folder name from path/tab in popup
const removeTabListItem = async (e, i) => {
e.preventDefault();
// setEditable(false);
if (!isAdd) {
setIsLoader(true);
let folderPtr;
if (i) {
setFolderList([]);
let list = tabList.filter((itm, j) => {
if (j <= i) {
return itm;
}
});
let _len = list.length - 1;
folderPtr = {
__type: "Pointer",
className: props.folderCls,
objectId: list[_len].objectId
};
setTabList(list);
} else {
setClickFolder({});
setFolderList([]);
setTabList([]);
}
setIsLoader(true);
setIsAdd(false);
if (i !== undefined) {
setFolderList([]);
const list = tabList.filter((folder, j) => j <= i && folder);
const index = list.length - 1;
const folderPtr = {
__type: "Pointer",
className: props.folderCls,
objectId: list[index].objectId
};
setTabList(list);
fetchFolder(folderPtr);
} else {
setClickFolder({});
setFolderList([]);
setTabList([]);
fetchFolder();
}
};
// `handleCreate` is used to open folder creation form in popup
const handleCreate = () => {
setIsAdd(!isAdd);
};
const handleCreate = () => setIsAdd(true);
const handleBack = () => setIsAdd(false);
// `handleAddFolder` is call when user folder created successfully and it fetch folder list on the basis of folderPtr or without folderPtr
const handleAddFolder = (newFolder) => {
props.setPdfData((prev) => [...prev, newFolder?.toJSON()]);
props.setPdfData((prev) => [...prev, newFolder]);
if (clickFolder && clickFolder.ObjectId) {
fetchFolder({
__type: "Pointer",
className: props.folderCls,
objectId: clickFolder.ObjectId
objectId: newFolder.objectId // clickFolder.ObjectId
});
} else {
fetchFolder();
}
handleCreate();
setClickFolder({ ObjectId: newFolder.objectId, Name: newFolder.Name });
setTabList((prev) => [...prev, newFolder]);
handleBack();
};
return (
<div className="text-xs mt-2">
@@ -180,54 +175,57 @@ const FolderModal = (props) => {
))}
<hr className="bg-[#8a8a8a] mt-[0.750rem]" />
</div>
<div className="mt-2 mb-3">
<div className="max-h-[210px] overflow-auto">
{!isAdd && folderList.length > 0
? folderList.map((folder) => (
<div
key={folder.objectId}
className={`${
folder.Type === "Folder"
? "cursor-pointer"
: "cursor-default"
} border-b-[1px] border-[#8a8a8a] py-2 mb-0.5"`}
onClick={() =>
folder.Type === "Folder" && handleSelect(folder)
}
>
<div className="flex items-center gap-2">
{folder.Type === "Folder" ? (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
className="w-[1.4rem] h-[1.4rem] fill-current op-text-secondary"
>
<path d="M64 480H448c35.3 0 64-28.7 64-64V160c0-35.3-28.7-64-64-64H288c-10.1 0-19.6-4.7-25.6-12.8L243.2 57.6C231.1 41.5 212.1 32 192 32H64C28.7 32 0 60.7 0 96V416c0 35.3 28.7 64 64 64z" />
</svg>
) : (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 384 512"
className="w-[1.4rem] h-[1.4rem] fill-current op-text-primary"
>
<path d="M374.629 150.627L233.371 9.373C227.371 3.371 219.23 0 210.746 0H64C28.652 0 0 28.652 0 64V448C0 483.345 28.652 512 64 512H320C355.348 512 384 483.345 384 448V173.254C384 164.767 380.629 156.629 374.629 150.627ZM224 22.629L361.375 160H248C234.781 160 224 149.234 224 136V22.629ZM368 448C368 474.467 346.469 496 320 496H64C37.531 496 16 474.467 16 448V64C16 37.533 37.531 16 64 16H208V136C208 158.062 225.938 176 248 176H368V448ZM96 264C96 268.406 99.594 272 104 272H280C284.406 272 288 268.406 288 264S284.406 256 280 256H104C99.594 256 96 259.594 96 264ZM280 320H104C99.594 320 96 323.594 96 328S99.594 336 104 336H280C284.406 336 288 332.406 288 328S284.406 320 280 320ZM280 384H104C99.594 384 96 387.594 96 392S99.594 400 104 400H280C284.406 400 288 396.406 288 392S284.406 384 280 384Z" />
</svg>
)}
<span className="font-semibold">{folder.Name}</span>
<div className={`${!isAdd ? "mb-3" : ""} mt-2`}>
{!isAdd && (
<div className="max-h-[210px] overflow-auto">
{folderList.length > 0
? folderList.map((folder) => (
<div
key={folder.objectId}
className={`${
folder.Type === "Folder"
? "cursor-pointer"
: "cursor-default"
} border-b-[1px] border-[#8a8a8a] py-2 mb-0.5"`}
onClick={() =>
folder.Type === "Folder" && handleSelect(folder)
}
>
<div className="flex items-center gap-2">
{folder.Type === "Folder" ? (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
className="w-[1.4rem] h-[1.4rem] fill-current op-text-secondary"
>
<path d="M64 480H448c35.3 0 64-28.7 64-64V160c0-35.3-28.7-64-64-64H288c-10.1 0-19.6-4.7-25.6-12.8L243.2 57.6C231.1 41.5 212.1 32 192 32H64C28.7 32 0 60.7 0 96V416c0 35.3 28.7 64 64 64z" />
</svg>
) : (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 384 512"
className="w-[1.4rem] h-[1.4rem] fill-current op-text-primary"
>
<path d="M374.629 150.627L233.371 9.373C227.371 3.371 219.23 0 210.746 0H64C28.652 0 0 28.652 0 64V448C0 483.345 28.652 512 64 512H320C355.348 512 384 483.345 384 448V173.254C384 164.767 380.629 156.629 374.629 150.627ZM224 22.629L361.375 160H248C234.781 160 224 149.234 224 136V22.629ZM368 448C368 474.467 346.469 496 320 496H64C37.531 496 16 474.467 16 448V64C16 37.533 37.531 16 64 16H208V136C208 158.062 225.938 176 248 176H368V448ZM96 264C96 268.406 99.594 272 104 272H280C284.406 272 288 268.406 288 264S284.406 256 280 256H104C99.594 256 96 259.594 96 264ZM280 320H104C99.594 320 96 323.594 96 328S99.594 336 104 336H280C284.406 336 288 332.406 288 328S284.406 320 280 320ZM280 384H104C99.594 384 96 387.594 96 392S99.594 400 104 400H280C284.406 400 288 396.406 288 392S284.406 384 280 384Z" />
</svg>
)}
<span className="font-semibold">{folder.Name}</span>
</div>
</div>
</div>
))
: !isLoader && (
<div className="text-base-content text-center my-2">
{t("no-data")}
</div>
)}
</div>
))
: !isLoader && (
<div className="text-base-content text-center my-2">
{t("no-data")}
</div>
)}
</div>
)}
{isAdd && (
<CreateFolder
parentFolderId={clickFolder && clickFolder.ObjectId}
folderCls={props.folderCls}
onSuccess={handleAddFolder}
onBack={handleBack}
/>
)}
{isLoader && (
@@ -238,33 +236,26 @@ const FolderModal = (props) => {
</div>
</div>
<hr />
<div className="flex justify-between items-center py-[.75rem] px-[1.25rem]">
<div
className="op-btn op-btn-seconday op-btn-sm"
title={t("save-here")}
onClick={handleCreate}
>
{isAdd ? (
<>
<i className="fa-light fa-arrow-left" aria-hidden="true"></i>
<span className="text-xs">{t("back")}</span>
</>
) : (
<>
<i className="fa-light fa-square-plus" aria-hidden="true"></i>
<span className="">{t("add-folder")}</span>
</>
)}
{!isAdd && (
<div className="flex justify-between items-center py-[.75rem] px-[1.25rem]">
<div
className="op-btn op-btn-primary op-btn-sm"
title={t("save-here")}
onClick={handleSubmit}
>
<i className="fa-light fa-save" aria-hidden="true"></i>
{t("save-here")}
</div>
<div
className="op-btn op-btn-seconday op-btn-sm"
title={t("add-folder")}
onClick={handleCreate}
>
<i className="fa-light fa-square-plus" aria-hidden="true"></i>
<span className="">{t("add-folder")}</span>
</div>
</div>
<div
className="op-btn op-btn-primary op-btn-sm"
title={t("save-here")}
onClick={handleSubmit}
>
<i className="fa-light fa-save" aria-hidden="true"></i>
{t("save-here")}
</div>
</div>
)}
</ModalUi>
</div>
);
@@ -6,8 +6,7 @@ import Tooltip from "../../../primitives/Tooltip";
import { useTranslation } from "react-i18next";
const SelectFolder = ({ required, onSuccess, folderCls, isReset }) => {
const appName =
"OpenSign™";
const appName = "OpenSign™";
const drivename = appName === "OpenSign™" ? "OpenSign™" : "";
const { t } = useTranslation();
const [isOpen, SetIsOpen] = useState(false);
@@ -125,50 +124,46 @@ const SelectFolder = ({ required, onSuccess, folderCls, isReset }) => {
// `handleCancel` is call when user click on folder name from path/tab in popup
const removeTabListItem = async (e, i) => {
e.preventDefault();
// setEditable(false);
if (!isAdd) {
setIsLoader(true);
let folderPtr;
if (i) {
setFolderList([]);
let list = tabList.filter((itm, j) => {
if (j <= i) {
return itm;
}
});
let _len = list.length - 1;
folderPtr = {
__type: "Pointer",
className: folderCls,
objectId: list[_len].objectId
};
setTabList(list);
} else {
setClickFolder({});
setSelectedFolder({});
setFolderList([]);
setTabList([]);
}
setIsLoader(true);
setIsAdd(false);
if (i !== undefined) {
setFolderList([]);
const list = tabList.filter((folder, j) => j <= i && folder);
const index = list.length - 1;
const folderPtr = {
__type: "Pointer",
className: folderCls,
objectId: list[index].objectId
};
fetchFolder(folderPtr);
setTabList(list);
} else {
setClickFolder({});
setSelectedFolder({});
setFolderList([]);
setTabList([]);
fetchFolder();
}
};
// `handleCreate` is used to open folder creation form in popup
const handleCreate = () => {
setIsAdd(!isAdd);
};
const handleCreate = () => setIsAdd(true);
const handleBack = () => setIsAdd(false);
// `handleAddFolder` is call when user folder created successfully and it fetch folder list on the basis of folderPtr or without folderPtr
const handleAddFolder = () => {
const handleAddFolder = (newFolder) => {
setFolderList([]);
if (clickFolder && clickFolder.ObjectId) {
fetchFolder({
__type: "Pointer",
className: folderCls,
objectId: clickFolder.ObjectId
objectId: newFolder.objectId // clickFolder.ObjectId
});
} else {
fetchFolder();
}
handleCreate();
setClickFolder({ ObjectId: newFolder.objectId, Name: newFolder.Name });
setTabList((prev) => [...prev, newFolder]);
handleBack();
};
return (
<div className="text-xs mt-2 ">
@@ -246,53 +241,56 @@ const SelectFolder = ({ required, onSuccess, folderCls, isReset }) => {
<hr className="bg-[#8a8a8a] mt-[0.750rem]" />
</div>
<div className="mb-2">
<div className="max-h-[210px] overflow-auto">
{!isAdd && folderList.length > 0
? folderList.map((folder) => (
<div
key={folder.objectId}
className={`${
folder.Type === "Folder"
? "cursor-pointer"
: "cursor-default"
} border-b-[1px] border-[#8a8a8a] py-2 mb-0.5"`}
onClick={() =>
folder.Type === "Folder" && handleSelect(folder)
}
>
<div className="flex items-center gap-2">
{folder.Type === "Folder" ? (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
className="w-[1.4rem] h-[1.4rem] fill-current op-text-secondary"
>
<path d="M64 480H448c35.3 0 64-28.7 64-64V160c0-35.3-28.7-64-64-64H288c-10.1 0-19.6-4.7-25.6-12.8L243.2 57.6C231.1 41.5 212.1 32 192 32H64C28.7 32 0 60.7 0 96V416c0 35.3 28.7 64 64 64z" />
</svg>
) : (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 384 512"
className="w-[1.4rem] h-[1.4rem] fill-current op-text-primary"
>
<path d="M374.629 150.627L233.371 9.373C227.371 3.371 219.23 0 210.746 0H64C28.652 0 0 28.652 0 64V448C0 483.345 28.652 512 64 512H320C355.348 512 384 483.345 384 448V173.254C384 164.767 380.629 156.629 374.629 150.627ZM224 22.629L361.375 160H248C234.781 160 224 149.234 224 136V22.629ZM368 448C368 474.467 346.469 496 320 496H64C37.531 496 16 474.467 16 448V64C16 37.533 37.531 16 64 16H208V136C208 158.062 225.938 176 248 176H368V448ZM96 264C96 268.406 99.594 272 104 272H280C284.406 272 288 268.406 288 264S284.406 256 280 256H104C99.594 256 96 259.594 96 264ZM280 320H104C99.594 320 96 323.594 96 328S99.594 336 104 336H280C284.406 336 288 332.406 288 328S284.406 320 280 320ZM280 384H104C99.594 384 96 387.594 96 392S99.594 400 104 400H280C284.406 400 288 396.406 288 392S284.406 384 280 384Z" />
</svg>
)}
<span className="font-semibold">{folder.Name}</span>
{!isAdd && (
<div className="max-h-[210px] overflow-auto">
{folderList.length > 0
? folderList.map((folder) => (
<div
key={folder.objectId}
className={`${
folder.Type === "Folder"
? "cursor-pointer"
: "cursor-default"
} border-b-[1px] border-[#8a8a8a] py-2 mb-0.5"`}
onClick={() =>
folder.Type === "Folder" && handleSelect(folder)
}
>
<div className="flex items-center gap-2">
{folder.Type === "Folder" ? (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
className="w-[1.4rem] h-[1.4rem] fill-current op-text-secondary"
>
<path d="M64 480H448c35.3 0 64-28.7 64-64V160c0-35.3-28.7-64-64-64H288c-10.1 0-19.6-4.7-25.6-12.8L243.2 57.6C231.1 41.5 212.1 32 192 32H64C28.7 32 0 60.7 0 96V416c0 35.3 28.7 64 64 64z" />
</svg>
) : (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 384 512"
className="w-[1.4rem] h-[1.4rem] fill-current op-text-primary"
>
<path d="M374.629 150.627L233.371 9.373C227.371 3.371 219.23 0 210.746 0H64C28.652 0 0 28.652 0 64V448C0 483.345 28.652 512 64 512H320C355.348 512 384 483.345 384 448V173.254C384 164.767 380.629 156.629 374.629 150.627ZM224 22.629L361.375 160H248C234.781 160 224 149.234 224 136V22.629ZM368 448C368 474.467 346.469 496 320 496H64C37.531 496 16 474.467 16 448V64C16 37.533 37.531 16 64 16H208V136C208 158.062 225.938 176 248 176H368V448ZM96 264C96 268.406 99.594 272 104 272H280C284.406 272 288 268.406 288 264S284.406 256 280 256H104C99.594 256 96 259.594 96 264ZM280 320H104C99.594 320 96 323.594 96 328S99.594 336 104 336H280C284.406 336 288 332.406 288 328S284.406 320 280 320ZM280 384H104C99.594 384 96 387.594 96 392S99.594 400 104 400H280C284.406 400 288 396.406 288 392S284.406 384 280 384Z" />
</svg>
)}
<span className="font-semibold">{folder.Name}</span>
</div>
</div>
</div>
))
: !isLoader && (
<div className="text-base-content text-center my-2">
{t("no-data")}
</div>
)}
</div>
))
: !isLoader && (
<div className="text-base-content text-center my-2">
{t("no-data")}
</div>
)}
</div>
)}
{isAdd && (
<CreateFolder
parentFolderId={clickFolder && clickFolder.ObjectId}
folderCls={folderCls}
onSuccess={handleAddFolder}
onBack={handleBack}
/>
)}
{isLoader && (
@@ -303,33 +301,26 @@ const SelectFolder = ({ required, onSuccess, folderCls, isReset }) => {
</div>
</div>
<hr />
<div className="flex justify-between items-center py-[.75rem] px-[1.25rem]">
<div
className="op-btn op-btn-seconday op-btn-sm"
title={t("save-here")}
onClick={handleCreate}
>
{isAdd ? (
<>
<i className="fa-light fa-arrow-left" aria-hidden="true"></i>
<span className="text-xs">{t("back")}</span>
</>
) : (
<>
<i className="fa-light fa-square-plus" aria-hidden="true"></i>
<span className="">{t("add-folder")}</span>
</>
)}
{!isAdd && (
<div className="flex justify-between items-center py-[.75rem] px-[1.25rem]">
<div
className="op-btn op-btn-primary op-btn-sm"
title={t("save-here")}
onClick={handleSubmit}
>
<i className="fa-light fa-save" aria-hidden="true"></i>
{t("save-here")}
</div>
<div
className="op-btn op-btn-seconday op-btn-sm"
title={t("add-folder")}
onClick={handleCreate}
>
<i className="fa-light fa-square-plus" aria-hidden="true"></i>
<span className="">{t("add-folder")}</span>
</div>
</div>
<div
className="op-btn op-btn-primary op-btn-sm"
title={t("save-here")}
onClick={handleSubmit}
>
<i className="fa-light fa-save" aria-hidden="true"></i>
{t("save-here")}
</div>
</div>
)}
</ModalUi>
</div>
);
+82 -56
View File
@@ -10,6 +10,8 @@ import { useTranslation } from "react-i18next";
import SelectLanguage from "../components/pdf/SelectLanguage";
import LoaderWithMsg from "../primitives/LoaderWithMsg";
import Title from "../components/Title";
import ModalUi from "../primitives/ModalUi";
import Loader from "../primitives/Loader";
function GuestLogin() {
const { t, i18n } = useTranslation();
@@ -30,6 +32,7 @@ function GuestLogin() {
const [contactId, setContactId] = useState(contactBookId);
const [sendmail, setSendmail] = useState();
const [contact, setContact] = useState({ name: "", phone: "", email: "" });
const navigateToDoc = async (docId, contactId) => {
try {
const docDetails = await Parse.Cloud.run("getDocument", {
@@ -53,6 +56,7 @@ function GuestLogin() {
return false;
}
};
useEffect(() => {
handleServerUrl();
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -124,6 +128,7 @@ function GuestLogin() {
}
} catch (error) {
alert(t("something-went-wrong-mssg"));
setLoading(false);
}
};
@@ -188,11 +193,13 @@ function GuestLogin() {
}
} catch (error) {
console.log("err ", error);
setLoading(false);
}
} else {
alert(t("enter-otp-alert"));
}
};
const handleUserData = async (e) => {
e.preventDefault();
if (!emailRegex.test(contact.email?.toLowerCase()?.replace(/\s/g, ""))) {
@@ -221,6 +228,7 @@ function GuestLogin() {
}
}
};
const handleInputChange = (e) => {
if (e.target.name === "email") {
setContact((prev) => ({
@@ -231,9 +239,56 @@ function GuestLogin() {
setContact((prev) => ({ ...prev, [e.target.name]: e.target.value }));
}
};
return (
<div>
<Title title="Request Sign" />
{/* OTP Verification Modal */}
{EnterOTP && (
<ModalUi
isOpen
title={t("otp-verification")}
handleClose={() => setEnterOtp(false)}
>
{loading ? (
<div className="h-[150px] flex justify-center items-center">
<Loader />
</div>
) : (
<form onSubmit={(e) => VerifyOTP(e)}>
<div className="px-6 py-3 text-base-content">
<label className="mb-2">{t("enter-otp")}</label>
<input
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
required
type="tel"
pattern="[0-9]{4}"
className="w-full op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content text-xs"
placeholder={t("otp-placeholder")}
value={OTP}
onChange={(e) => setOTP(e.target.value)}
/>
</div>
<div className="px-6 mb-3">
<button type="submit" className="op-btn op-btn-primary">
{t("verify")}
</button>
<button
className="op-btn op-btn-secondary ml-2"
onClick={(e) => handleSendOTPBtn(e)}
>
{t("resend")}
</button>
</div>
</form>
)}
</ModalUi>
)}
{isLoading.isLoad ? (
<LoaderWithMsg isLoading={isLoading} />
) : (
@@ -249,63 +304,34 @@ function GuestLogin() {
)}
</div>
{contactId ? (
<>
{!EnterOTP ? (
<div className="w-full md:w-[50%] text-base-content">
<h1 className="text-2xl md:text-[30px]">{t("welcome")}</h1>
<legend className="text-[12px] text-[#878787] mt-2 mb-1">
{t("get-otp-alert")}
</legend>
<div className="p-[20px] outline outline-1 outline-slate-300/50 my-2 op-card shadow-md">
<input
type="email"
name="email"
value={email}
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full disabled:text-[#5c5c5c] text-xs"
disabled
/>
</div>
<div className="mt-3">
<button
className="op-btn op-btn-primary"
onClick={(e) => handleSendOTPBtn(e)}
disabled={loading}
>
{loading ? t("loading") : t("get-verification-code")}
</button>
</div>
</div>
) : (
<form
className="w-full md:w-[50%] text-base-content"
onSubmit={VerifyOTP}
<div className="w-full md:w-[50%] text-base-content">
<h1 className="text-2xl md:text-[30px]">{t("welcome")}</h1>
<legend className="text-[12px] text-[#878787] mt-2 mb-1">
{t("get-otp-alert")}
</legend>
<div className="p-[20px] outline outline-1 outline-slate-300/50 my-2 op-card shadow-md">
<input
type="email"
name="email"
value={email}
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full disabled:text-[#5c5c5c] text-xs"
disabled
/>
</div>
<div className="mt-3">
<button
className="op-btn op-btn-primary flex items-center"
onClick={(e) => {
e.preventDefault();
SendOtp();
}}
disabled={loading}
>
<h1 className="text-2xl md:text-[30px]">{t("welcome")}</h1>
<legend className="text-[12px] text-[#878787] mt-2">
{t("guest-email-alert")}
</legend>
<div className="p-[20px] pt-[15px] outline outline-1 outline-slate-300/50 op-card my-2 shadow-md">
<p className="text-sm">{t("enter-verification-code")}</p>
<input
type="number"
className="mt-2 op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
name="OTP"
value={OTP}
onChange={(e) => setOTP(e.target.value)}
/>
</div>
<div className="mt-2.5">
<button
className="op-btn op-btn-primary"
type="submit"
disabled={loading}
>
{loading ? t("loading") : t("verify")}
</button>
</div>
</form>
)}
</>
<i className="fa-light fa-message-sms mr-2"></i>
{loading ? t("loading") : t("get-verification-code")}
</button>
</div>
</div>
) : (
<div className="w-full md:w-[50%] text-base-content">
<h1 className="text-2xl md:text-[30px]">{t("welcome")}</h1>
+136 -129
View File
@@ -87,100 +87,47 @@ function Login() {
const handleSubmit = async (event) => {
localStorage.removeItem("accesstoken");
event.preventDefault();
if (!emailRegex.test(state.email)) {
alert("Please enter a valid email address.");
} else {
const { email, password } = state;
if (email && password) {
try {
setState({ ...state, loading: true });
localStorage.setItem("appLogo", appInfo.applogo);
// Pass the username and password to logIn function
const user = await Parse.User.logIn(email, password);
if (user) {
let _user = user.toJSON();
localStorage.setItem("UserInformation", JSON.stringify(_user));
localStorage.setItem("userEmail", email);
localStorage.setItem("accesstoken", _user.sessionToken);
localStorage.setItem("scriptId", true);
if (_user.ProfilePic) {
localStorage.setItem("profileImg", _user.ProfilePic);
} else {
localStorage.setItem("profileImg", "");
}
// Check extended class user role and tenentId
try {
const userSettings = appInfo.settings;
await Parse.Cloud.run("getUserDetails")
.then(async (extUser) => {
if (extUser) {
// console.log("extUser", extUser, extUser?.get("IsDisabled"));
const IsDisabled = extUser?.get("IsDisabled") || false;
if (!IsDisabled) {
const userRole = extUser?.get("UserRole");
const menu =
userRole &&
userSettings.find((menu) => menu.role === userRole);
if (menu) {
const _currentRole = userRole;
const redirectUrl =
location?.state?.from ||
`/${menu.pageType}/${menu.pageId}`;
let _role = _currentRole.replace("contracts_", "");
localStorage.setItem("_user_role", _role);
const checkLanguage = extUser?.get("Language");
if (checkLanguage) {
checkLanguage && i18n.changeLanguage(checkLanguage);
}
return;
}
const results = [extUser];
const extUser_str = JSON.stringify(results);
const { email, password } = state;
if (!email || !password) {
return;
}
localStorage.setItem("Extand_Class", extUser_str);
const extInfo = JSON.parse(JSON.stringify(extUser));
localStorage.setItem("userEmail", extInfo.Email);
localStorage.setItem("username", extInfo.Name);
if (extInfo?.TenantId) {
const tenant = {
Id: extInfo?.TenantId?.objectId || "",
Name: extInfo?.TenantId?.TenantName || ""
};
localStorage.setItem("TenantId", tenant?.Id);
dispatch(showTenant(tenant?.Name));
localStorage.setItem("TenantName", tenant?.Name);
}
localStorage.setItem("PageLanding", menu.pageId);
localStorage.setItem("defaultmenuid", menu.menuId);
localStorage.setItem("pageType", menu.pageType);
setState({ ...state, loading: false });
// Redirect to the appropriate URL after successful login
navigate(redirectUrl);
} else {
setState({ ...state, loading: false });
setIsModal(true);
}
} else {
showToast("danger", t("do-not-access-contact-admin"));
logOutUser();
}
} else {
showToast("danger", t("user-not-found"));
logOutUser();
}
})
.catch((error) => {
showToast("danger", t("something-went-wrong-mssg"));
console.error("Error while fetching Follow", error);
});
} catch (error) {
showToast("danger", `${error.message}`);
}
}
} catch (error) {
console.error("Error while logging in user", error);
showToast("danger", "Invalid username/password or region");
}
try {
setState({ ...state, loading: true });
localStorage.setItem("appLogo", appInfo.applogo);
const _user = await Parse.Cloud.run("loginuser", { email, password });
if (!_user) {
setState({ ...state, loading: false });
return;
}
// Get extended user data (including 2FA status) using cloud function
try {
await Parse.User.become(_user.sessionToken);
// Store basic user info needed for further steps
localStorage.setItem("UserInformation", JSON.stringify(_user));
localStorage.setItem("userEmail", email);
localStorage.setItem("accesstoken", _user.sessionToken);
localStorage.setItem("scriptId", true);
if (_user.ProfilePic) {
localStorage.setItem("profileImg", _user.ProfilePic);
} else {
localStorage.setItem("profileImg", "");
}
await continueLoginFlow();
} catch (error) {
console.error("Error checking 2FA status:", error);
showToast("danger", t("something-went-wrong-mssg"));
setState({ ...state, loading: false });
}
} catch (error) {
console.error("Error while logging in user", error);
showToast("danger", "Invalid username/password or region");
}
};
@@ -289,52 +236,51 @@ function Login() {
localStorage.setItem("profileImg", "");
}
const userSettings = appInfo.settings;
await Parse.Cloud.run("getUserDetails").then(async (extUser) => {
if (extUser) {
const IsDisabled = extUser?.get("IsDisabled") || false;
if (!IsDisabled) {
const userRole = extUser.get("UserRole");
const _currentRole = userRole;
const menu =
userRole && userSettings.find((menu) => menu.role === userRole);
if (menu) {
const _role = _currentRole.replace("contracts_", "");
localStorage.setItem("_user_role", _role);
const redirectUrl =
location?.state?.from || `/${menu.pageType}/${menu.pageId}`;
const results = [extUser];
const extendedInfo_stringify = JSON.stringify(results);
localStorage.setItem("Extand_Class", extendedInfo_stringify);
const extInfo = JSON.parse(JSON.stringify(extUser));
localStorage.setItem("userEmail", extInfo.Email);
localStorage.setItem("username", extInfo.Name);
if (extInfo?.TenantId) {
const tenant = {
Id: extInfo?.TenantId?.objectId || "",
Name: extInfo?.TenantId?.TenantName || ""
};
localStorage.setItem("TenantId", tenant?.Id);
dispatch(showTenant(tenant?.Name));
localStorage.setItem("TenantName", tenant?.Name);
}
localStorage.setItem("PageLanding", menu.pageId);
localStorage.setItem("defaultmenuid", menu.menuId);
localStorage.setItem("pageType", menu.pageType);
// Redirect to the appropriate URL after successful login
navigate(redirectUrl);
} else {
setState({ ...state, loading: false });
logOutUser();
const extUser = await Parse.Cloud.run("getUserDetails");
if (extUser) {
const IsDisabled = extUser?.get("IsDisabled") || false;
if (!IsDisabled) {
const userRole = extUser.get("UserRole");
const _currentRole = userRole;
const menu =
userRole && userSettings.find((menu) => menu.role === userRole);
if (menu) {
const _role = _currentRole.replace("contracts_", "");
localStorage.setItem("_user_role", _role);
const redirectUrl =
location?.state?.from || `/${menu.pageType}/${menu.pageId}`;
const results = [extUser];
const extendedInfo_stringify = JSON.stringify(results);
localStorage.setItem("Extand_Class", extendedInfo_stringify);
const extInfo = JSON.parse(JSON.stringify(extUser));
localStorage.setItem("userEmail", extInfo.Email);
localStorage.setItem("username", extInfo.Name);
if (extInfo?.TenantId) {
const tenant = {
Id: extInfo?.TenantId?.objectId || "",
Name: extInfo?.TenantId?.TenantName || ""
};
localStorage.setItem("TenantId", tenant?.Id);
dispatch(showTenant(tenant?.Name));
localStorage.setItem("TenantName", tenant?.Name);
}
localStorage.setItem("PageLanding", menu.pageId);
localStorage.setItem("defaultmenuid", menu.menuId);
localStorage.setItem("pageType", menu.pageType);
// Redirect to the appropriate URL after successful login
navigate(redirectUrl);
} else {
showToast("danger", t("do-not-access-contact-admin"));
setState({ ...state, loading: false });
logOutUser();
}
} else {
showToast("danger", t("user-not-found"));
showToast("danger", t("do-not-access-contact-admin"));
logOutUser();
}
});
} else {
showToast("danger", t("user-not-found"));
logOutUser();
}
} catch (error) {
showToast("danger", t("something-went-wrong-mssg"));
console.log("err", error);
@@ -417,6 +363,67 @@ function Login() {
localStorage.setItem("parseAppId", appid);
};
const continueLoginFlow = async () => {
try {
const userSettings = appInfo.settings;
const extUser = await Parse.Cloud.run("getUserDetails");
if (extUser) {
const IsDisabled = extUser?.get("IsDisabled") || false;
if (!IsDisabled) {
const userRole = extUser?.get("UserRole");
const menu =
userRole && userSettings?.find((menu) => menu.role === userRole);
if (menu) {
const _currentRole = userRole;
const redirectUrl =
location?.state?.from || `/${menu.pageType}/${menu.pageId}`;
let _role = _currentRole.replace("contracts_", "");
localStorage.setItem("_user_role", _role);
const checkLanguage = extUser?.get("Language");
if (checkLanguage) {
checkLanguage && i18n.changeLanguage(checkLanguage);
}
// Continue with storing user data and redirecting
const results = [extUser];
const extUser_str = JSON.stringify(results);
localStorage.setItem("Extand_Class", extUser_str);
const extInfo = JSON.parse(JSON.stringify(extUser));
localStorage.setItem("userEmail", extInfo.Email);
localStorage.setItem("username", extInfo.Name);
if (extInfo?.TenantId) {
const tenant = {
Id: extInfo?.TenantId?.objectId || "",
Name: extInfo?.TenantId?.TenantName || ""
};
localStorage.setItem("TenantId", tenant?.Id);
dispatch(showTenant(tenant?.Name));
localStorage.setItem("TenantName", tenant?.Name);
}
localStorage.setItem("PageLanding", menu.pageId);
localStorage.setItem("defaultmenuid", menu.menuId);
localStorage.setItem("pageType", menu.pageType);
setState({ ...state, loading: false });
navigate(redirectUrl);
} else {
setState({ ...state, loading: false });
setIsModal(true);
}
} else {
showToast("danger", t("do-not-access-contact-admin"));
logOutUser();
}
} else {
showToast("danger", t("user-not-found"));
logOutUser();
}
} catch (error) {
console.error("Error during login flow", error);
showToast("danger", error.message || t("something-went-wrong-mssg"));
}
};
return errMsg ? (
<div className="h-screen flex justify-center text-center items-center p-4 text-gray-500 text-base">
{errMsg}
@@ -437,7 +444,7 @@ function Login() {
<div
aria-labelledby="loginHeading"
role="region"
className="pb-1 md:pb-4 pt-10 md:px-10 lg:px-16 h-screen"
className="pb-1 md:pb-4 pt-10 md:px-10 lg:px-16 h-full"
>
<div className="md:p-4 lg:p-10 p-4 bg-base-100 text-base-content op-card">
<div className="w-[250px] h-[66px] inline-block overflow-hidden">
+128 -89
View File
@@ -58,6 +58,9 @@ function Opensigndrive() {
const [isDontShow, setIsDontShow] = useState(false);
const [tourData, setTourData] = useState();
const [showTourFirstTIme, setShowTourFirstTime] = useState(true);
const [searchTerm, setSearchTerm] = useState("");
const [mobileSearchOpen, setMobileSearchOpen] = useState(false);
const debounceTimer = useRef(null);
const orderName = {
Ascending: "Ascending",
Descending: "Descending",
@@ -209,9 +212,7 @@ function Opensigndrive() {
});
} finally {
setLoading(false);
setIsLoading({
isLoad: false
});
setIsLoading({ isLoad: false });
}
};
@@ -219,15 +220,16 @@ function Opensigndrive() {
const handleScroll = () => {
//get document of render openSign-drive component using id
const documentList = document.getElementById("renderList");
//documentList.clientHeight property returns the height of an element's content area, including padding but not including borders, margins, or scrollbars.
//documentList.scrollHeight property returns the entire height of an element,including the parts that are not visible due to overflow..
// documentList.scrollTop property show height of element, how much the content has been scrolled from the top.
const { scrollTop, clientHeight, scrollHeight } = documentList;
const scrolled = Math.ceil(scrollTop + clientHeight); // ceil return e.g 3.14 => 4
const totalHeight = Math.floor(scrollHeight); // floor return e.g 3.14 => 3
// clientHeight property returns the height of an element's content area, including padding but not including borders, margins, or scrollbars.
// scrollHeight property returns the entire height of an element,including the parts that are not visible due to overflow..
// scrollTop property show height of element, how much the content has been scrolled from the top.
// When the sum of scrollTop and clientHeight is equal to scrollHeight, it means that the user has scrolled to the bottom of the div.
if (
documentList &&
documentList.scrollTop + documentList.clientHeight >=
documentList.scrollHeight
) {
if (scrolled >= totalHeight) {
//disableLoading is used disable initial loader
const disableLoading = true;
// If the fetched data length is less than the limit, it means there's no more data to fetch
@@ -252,11 +254,7 @@ function Opensigndrive() {
const handleRoute = (index, folderData) => {
setSkip(0);
// after onclick on route filter route from that index
const updateFolderName = folderName.filter((x, i) => {
if (i <= index) {
return x;
}
});
const updateFolderName = folderName.filter((_, i) => i <= index);
setFolderName(updateFolderName);
//get route details after onclick path of folder name
const getCurrentId = folderData[index];
@@ -517,6 +515,34 @@ function Opensigndrive() {
setIsTour(true);
}
}
const handleSearchChange = async (e) => {
const name = e.target.value.toLowerCase();
setSearchTerm(name);
// Clear previous timer
if (debounceTimer.current) {
clearTimeout(debounceTimer.current);
}
// Start new debounce timer
debounceTimer.current = setTimeout(async () => {
try {
const res = await Parse.Cloud.run("filterdocs", { searchTerm: name });
setPdfData(JSON.parse(JSON.stringify(res)));
} catch (err) {
console.error("Search error:", err);
alert(`Error: ${err.message}`);
}
}, 300);
};
// Cleanup on unmount
useEffect(() => {
return () => {
if (debounceTimer.current) {
clearTimeout(debounceTimer.current);
}
};
}, []);
return (
<div className="bg-base-100 text-base-content rounded-box w-full shadow-md">
<Title title={`${drivename} Drive`} drive={true} />
@@ -524,22 +550,14 @@ function Opensigndrive() {
isOpen={isAlert.isShow}
title={t("alert")}
handleClose={() => {
setIsAlert({
isShow: false,
alertMessage: ""
});
setIsAlert({ isShow: false, alertMessage: "" });
}}
>
<div className="h-full p-[20px] pb-[15px]">
<p>{isAlert.alertMessage}</p>
<div className="h-[1px] bg-[#9f9f9f] w-full my-[15px]"></div>
<button
onClick={() =>
setIsAlert({
isShow: false,
alertMessage: ""
})
}
onClick={() => setIsAlert({ isShow: false, alertMessage: "" })}
type="button"
className="op-btn op-btn-neutral op-btn-sm"
>
@@ -608,7 +626,7 @@ function Opensigndrive() {
</div>
) : (
<>
<div className="flex flex-row justify-between items-center px-[15px] md:px-[25px] pt-[20px]">
<div className="flex flex-row justify-between items-center px-[15px] md:px-[25px] pt-2 md:pt-[20px]">
{tourData && (
<Tour
onRequestClose={closeTour}
@@ -627,24 +645,44 @@ function Opensigndrive() {
>
{handleFolderTab(folderName)}
</div>
<div className="flex flex-row items-center">
<div className="flex flex-row items-center justify-center md:gap-1">
{/* Desktop search input */}
<div className="hidden md:block p-2">
<input
type="search"
value={searchTerm}
onChange={handleSearchChange}
placeholder="Search documents…"
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-64 text-xs"
/>
</div>
{/* Mobile search toggle */}
<button
className="md:hidden p-2 flex justify-center items-center focus:outline-none rounded-md hover:bg-gray-200 text-[18px]"
aria-label="Search"
onClick={() => setMobileSearchOpen((open) => !open)}
>
<i
style={{ color: `${iconColor}` }}
className="fa-solid fa-magnifying-glass"
></i>
</button>
<div
id="folder-menu"
className={`${
isOptions ? "dropdown show dropDownStyle" : "dropdown"
} hidden md:block`}
} hidden md:block cursor-pointer hover:bg-gray-200 p-2 rounded-md`}
onClick={() => setIsOptions(!isOptions)}
>
<div className="sort" data-tut="reactourSecond">
<div data-tut="reactourSecond">
<i
className="fa-light fa-plus-square"
className="fa-light fa-plus-square text-[24px]"
aria-hidden="true"
style={{ fontSize: "25px", color: `${iconColor}` }}
style={{ color: `${iconColor}` }}
></i>
</div>
<div
className={`${isOptions ? "block" : "hidden"} ${dropdowncss}`}
// className={isOptions ? "dropdown-menu show" : "dropdown-menu"}
aria-labelledby="dropdownMenuButton"
aria-expanded={isOptions ? "true" : "false"}
>
@@ -683,11 +721,11 @@ function Opensigndrive() {
>
<div
data-tut="reactourThird"
className="sort "
className="cursor-pointer flex flex-row items-center justify-center p-2 hover:bg-gray-200 rounded-md"
data-toggle="dropdown"
>
<i
className="fa-light fa-sort-amount-asc mr-[5px] text-[14px]"
className="fa-light fa-sort-amount-asc mr-[5px] text-[19px]"
aria-hidden="true"
style={{ color: `${iconColor}` }}
></i>
@@ -702,69 +740,59 @@ function Opensigndrive() {
aria-labelledby="dropdownMenuButton"
aria-expanded={isShowSort ? "true" : "false"}
>
{sortingValue.map((value, ind) => {
return (
<span
key={ind}
onClick={() => {
setSelectedSort(value);
sortingData(value, null, pdfData);
}}
className="dropdown-item text-[10px] md:text-[13px]"
style={{
paddingLeft: selectedSort !== value && "31px"
}}
>
{selectedSort === value && (
<i
className="fa-light fa-check"
aria-hidden="true"
></i>
)}
<span className="ml-[5px]">
{t(`sort-order.${value}`)}
</span>
{sortingValue.map((value, ind) => (
<span
key={ind}
onClick={() => {
setSelectedSort(value);
sortingData(value, null, pdfData);
}}
className="dropdown-item text-[10px] md:text-[13px]"
style={{
paddingLeft: selectedSort !== value && "31px"
}}
>
{selectedSort === value && (
<i className="fa-light fa-check" aria-hidden="true"></i>
)}
<span className="ml-[5px]">
{t(`sort-order.${value}`)}
</span>
);
})}
</span>
))}
<hr className="hrStyle" />
{sortOrder.map((order, ind) => {
return (
<span
key={ind}
onClick={() => {
setSortingOrder(order);
sortingData(null, order, pdfData);
}}
className="dropdown-item text-[10px] md:text-[13px]"
style={{
paddingLeft: sortingOrder !== order && "31px"
}}
>
{sortingOrder === order && (
<i
className="fa-light fa-check"
aria-hidden="true"
></i>
)}
<span className="ml-[5px]">
{t(`sort-order.${order}`)}
</span>
{sortOrder.map((order, ind) => (
<span
key={ind}
onClick={() => {
setSortingOrder(order);
sortingData(null, order, pdfData);
}}
className="dropdown-item text-[10px] md:text-[13px]"
style={{
paddingLeft: sortingOrder !== order && "31px"
}}
>
{sortingOrder === order && (
<i className="fa-light fa-check" aria-hidden="true"></i>
)}
<span className="ml-[5px]">
{t(`sort-order.${order}`)}
</span>
);
})}
</span>
))}
</div>
</div>
<div
className="cursor-pointer p-2 hover:bg-gray-200 rounded-md flex justify-center items-center"
data-tut="reactourForth"
className="sort"
onClick={() => setIsList(!isList)}
>
<i
className={
className={`${
isList ? "fa-light fa-th-large" : "fa-light fa-list"
}
style={{ fontSize: "24px", color: `${iconColor}` }}
} text-[20px]`}
style={{ color: `${iconColor}` }}
aria-hidden="true"
></i>
</div>
@@ -776,7 +804,7 @@ function Opensigndrive() {
onClick={() => setIsOptions(!isOptions)}
>
<div
className="p-[19px] my-2 flex items-center justify-center cursor-pointer hover:bg-[var(--mauve-3)] rounded-[2px] shadow-[0_2px_4px_rgba(168,204,206,0.1)]"
className="p-3 flex items-center justify-center cursor-pointer rounded-md hover:bg-gray-200"
data-tut="reactourSecond"
>
<i
@@ -820,7 +848,18 @@ function Opensigndrive() {
</div>
</div>
</div>
{/* Mobile search overlay */}
{mobileSearchOpen && (
<div className="top-full left-0 w-full bg-white px-4 py-2 shadow-md md:hidden">
<input
type="search"
value={searchTerm}
onChange={handleSearchChange}
placeholder="Search documents…"
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
/>
</div>
)}
{pdfData && pdfData.length === 0 ? (
<div className="flex justify-center items-center w-full h-[50vh]">
<span className="text-base-content font-bold">
+9 -5
View File
@@ -50,7 +50,7 @@ const Preferences = () => {
const [isTourEnabled, setIsTourEnabled] = useState(false);
const [dateFormat, setDateFormat] = useState("MM/DD/YYYY");
const [is12HourTime, setIs12HourTime] = useState(false);
const [isLTVEnabled, setIsLTVEnabled] = useState(false);
useEffect(() => {
fetchSignType();
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -110,6 +110,9 @@ const Preferences = () => {
const is12Hr =
_getUser?.Is12HourTime !== undefined ? _getUser?.Is12HourTime : false;
setIs12HourTime(is12Hr);
const isLTVEnabled =
_getUser?.IsLTVEnabled !== undefined ? _getUser?.IsLTVEnabled : false;
setIsLTVEnabled(isLTVEnabled);
}
} catch (err) {
console.log("err while getting user details", err);
@@ -167,7 +170,8 @@ const Preferences = () => {
SendinOrder: sendinOrder,
IsTourEnabled: isTourEnabled,
DateFormat: dateFormat,
Is12HourTime: is12HourTime
Is12HourTime: is12HourTime,
IsLTVEnabled: isLTVEnabled
};
const updateRes = await Parse.Cloud.run("updatepreferences", params);
if (updateRes) {
@@ -673,7 +677,6 @@ const Preferences = () => {
</div>
</div>
</div>
{/* Right Column - Timezone & Date Settings */}
<div className="md:col-span-7 flex flex-col">
<div className="mb-6">
@@ -693,7 +696,6 @@ const Preferences = () => {
/>
</div>
</div>
{/* Save Button - Full Width */}
<div className="md:col-span-12 flex justify-start mt-2">
<button
@@ -704,7 +706,7 @@ const Preferences = () => {
</button>
</div>
</div>
) : (
) : activeTab === 1 ? (
<div className="flex flex-col mb-4">
<div className="flex flex-col">
<h1 className="text-[14px] mb-[0.7rem] font-medium">
@@ -849,6 +851,8 @@ const Preferences = () => {
</div>
</div>
</div>
) : (
<></>
)}
</div>
</div>
@@ -741,12 +741,13 @@ const ReportTable = (props) => {
company_name: doc?.ExtUserPtr?.Company || "",
signing_url: `<a href=${signPdf} target=_blank>Sign here</a>`
};
const subject =
doc?.RequestSubject ||
doc?.ExtUserPtr?.TenantId?.RequestSubject ||
`{{sender_name}} has requested you to sign "{{document_title}}"`;
const body =
doc?.RequestBody ||
doc?.ExtUserPtr?.TenantId?.RequestBody ||
`<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body><p>Hi {{receiver_name}},</p><br><p>We hope this email finds you well. {{sender_name}} has requested you to review and sign <b>"{{document_title}}"</b>.</p><p>Your signature is crucial to proceed with the next steps as it signifies your agreement and authorization.</p><br><p>{{signing_url}}</p><br><p>If you have any questions or need further clarification regarding the document or the signing process, please contact the sender.</p><br><p>Thanks</p><p> Team ${appName}</p><br></body> </html>`;
const res = replaceMailVaribles(subject, body, variables);
setMail((prev) => ({ ...prev, subject: res.subject, body: res.body }));
@@ -168,21 +168,6 @@
}
.sort {
padding: 8px 15px;
margin: 5px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.sort:hover {
box-shadow: 0 2px 4px rgba(168, 204, 206, 0.1);
background-color: var(--mauve-3);
border-radius: 2px;
}
.fileName {
display: inline-block;
width: 100px;
@@ -286,15 +271,3 @@ a {
transform: translateX(0);
}
}
@media (min-width: 310px) and (max-width: 550px) {
.sort {
padding: 2px;
}
}
@media screen and (max-width: 309px) {
.sort {
padding: 2px;
}
}
@@ -67,7 +67,7 @@ async function uploadFile(req, res) {
newFileName = sanitizeFileName(
newFileName + '_' + new Date().toISOString() + '.' + extension
);
console.log(newFileName);
// console.log(newFileName);
cb(null, newFileName);
},
});
@@ -96,7 +96,7 @@ async function uploadFile(req, res) {
newFileName = sanitizeFileName(
newFileName + '_' + new Date().toISOString() + '.' + extension
);
console.log(newFileName);
// console.log(newFileName);
cb(null, newFileName);
},
});
@@ -115,7 +115,7 @@ async function uploadFile(req, res) {
newFileName = sanitizeFileName(
newFileName + '_' + new Date().toISOString() + '.' + extension
);
console.log(newFileName);
// console.log(newFileName);
cb(null, newFileName);
},
});
@@ -149,7 +149,7 @@ async function uploadFile(req, res) {
//res.header("Access-Control-Allow-Headers", "Content-Type");
//res.setHeader("Access-Control-Allow-Origin", "*");
if (useLocal === 'true') {
console.log(req.file);
// console.log(req.file);
var fileUrl = `${parseBaseUrl}/files/${parseAppId}/${req.file.filename}`;
} else {
var fileUrl = req.file.location;
+4
View File
@@ -52,7 +52,9 @@ import forwardDoc from './parsefunction/ForwardDoc.js';
import saveAsTemplate from './parsefunction/saveAsTemplate.js';
import updateTenant from './parsefunction/updateTenant.js';
import recreateDocument from './parsefunction/recreateDocument.js';
import loginUser from './parsefunction/loginUser.js';
import addUser from './parsefunction/addUser.js';
import filterDocs from './parsefunction/filterDocs.js';
// This afterSave function triggers after an object is added or updated in the specified class, allowing for post-processing logic.
Parse.Cloud.afterSave('contracts_Document', DocumentAftersave);
@@ -115,4 +117,6 @@ Parse.Cloud.define('forwarddoc', forwardDoc);
Parse.Cloud.define('saveastemplate', saveAsTemplate);
Parse.Cloud.define('updatetenant', updateTenant);
Parse.Cloud.define('recreatedoc', recreateDocument);
Parse.Cloud.define('loginuser', loginUser);
Parse.Cloud.define('adduser', addUser);
Parse.Cloud.define('filterdocs', filterDocs);
@@ -0,0 +1,80 @@
/**
* Escapes special characters in a string so it can safely be used in a RegExp.
*/
function escapeRegExp(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
/**
* Fetches contracts_Document objects whose Name matches searchTerm
* and whose CreatedBy pointer is the current user.
*
* @param {Parse.User} user the logged-in Parse.User (e.g. request.user)
* @param {string} searchTerm substring (or full name) to match
* @param {object} [options]
* @param {number} [options.limit=100] max results
* @param {number} [options.skip=0] offset for pagination
* @param {boolean} [options.caseSensitive=false] regex case sensitivity
*/
async function fetchDocumentsByName(
user,
searchTerm,
{ limit = 300, skip = 0, caseSensitive = false } = {}
) {
const query = new Parse.Query('contracts_Document');
// 1) Filter by Name substring (case-insensitive by default)
if (searchTerm) {
const escaped = escapeRegExp(searchTerm);
const pattern = `.*${escaped}.*`;
query.matches('Name', pattern, caseSensitive ? undefined : 'i');
}
// 2) Filter by CreatedBy pointer
query.equalTo('CreatedBy', user);
// 3) Pagination & sorting
query.limit(limit);
query.skip(skip);
// query.ascending('Name'); //sort alphabetically:
query.include('ExtUserPtr');
query.include('ExtUserPtr.TenantId');
query.include('Signers');
query.notEqualTo('IsArchive', true);
query.descending('updatedAt');
query.exclude('AuditTrail');
query.notEqualTo('Type', 'Folder');
try {
return await query.find({ useMasterKey: true });
} catch (err) {
console.error('Error fetching contracts_Document by Name:', err);
// Wrap low-level or network errors in a script-level error
throw new Parse.Error(
Parse.Error.SCRIPT_FAILED,
'Unable to retrieve your documents at this time'
);
}
}
export default async function filterDocs(request) {
const { searchTerm = '', limit, skip, caseSensitive } = request.params;
if (!request.user) {
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User is not authenticated.');
}
if (typeof searchTerm !== 'string') {
throw new Parse.Error(Parse.Error.INVALID_PARAMETER, 'searchTerm must be a string');
}
try {
const docs = await fetchDocumentsByName(request.user, searchTerm, {
limit,
skip,
caseSensitive,
});
return docs;
} catch (error) {
// handle error
console.log('err while filtering doc', error);
throw error;
}
}
@@ -72,7 +72,7 @@ export default async function generateCertificatebydocId(req) {
location: 'n/a',
name: eSignName,
contactInfo: eSigncontact,
signatureLength: 15000,
signatureLength: 16000,
});
const pdfWithPlaceholderBytes = await certificatePdf.save();
const CertificateBuffer = Buffer.from(pdfWithPlaceholderBytes);
@@ -5,7 +5,6 @@ export default async function getDrive(request) {
const appId = process.env.APP_ID;
const limit = request.params.limit;
const skip = request.params.skip;
const classUrl = serverUrl + '/classes/contracts_Document';
const docId = request.params.docId;
try {
const userRes = await axios.get(serverUrl + '/users/me', {
@@ -0,0 +1,26 @@
import crypto from 'node:crypto';
export default async function loginUser(request) {
const username = request.params.email;
const password = request.params.password;
if (username && password) {
try {
// Pass the username and password to logIn function
const user = await Parse.User.logIn(username, password);
// console.log('user ', user);
if (user) {
const _user = user?.toJSON();
return {
..._user,
};
} else {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'user not found.');
}
} catch (err) {
console.log('err in login user', err);
throw err;
}
} else {
throw new Parse.Error(Parse.Error.PASSWORD_MISSING, 'username/password is missing.');
}
}
@@ -270,7 +270,7 @@ async function sendMailsaveCertifcate(doc, pfx, isCustomMail, mailProvider, file
location: 'n/a',
name: eSignName,
contactInfo: eSigncontact,
signatureLength: 15000,
signatureLength: 16000,
});
const pdfWithPlaceholderBytes = await certificatePdf.save();
const CertificateBuffer = Buffer.from(pdfWithPlaceholderBytes);
@@ -408,7 +408,7 @@ async function PDF(req) {
location: 'n/a',
name: eSignName,
contactInfo: eSigncontact,
signatureLength: 15000,
signatureLength: 16000,
});
const pdfWithPlaceholderBytes = await pdfDoc.save();
PdfBuffer = Buffer.from(pdfWithPlaceholderBytes);
@@ -96,6 +96,10 @@ export default function reportJson(id, userId) {
'Placeholders',
'SignedUrl',
'TemplateId',
'RequestBody',
'RequestSubject',
'ExtUserPtr.TenantId.RequestBody',
'ExtUserPtr.TenantId.RequestSubject',
],
};
// completed documents report
@@ -227,6 +231,10 @@ export default function reportJson(id, userId) {
'Placeholders',
'SignedUrl',
'TemplateId',
'RequestBody',
'RequestSubject',
'ExtUserPtr.TenantId.RequestBody',
'ExtUserPtr.TenantId.RequestSubject',
],
};
// Recent signature requests report show on dashboard
@@ -54,6 +54,9 @@ export default async function updatePreferences(request) {
if (request.params.Is12HourTime !== undefined) {
newOrg.set('Is12HourTime', request.params.Is12HourTime);
}
if (request.params.IsLTVEnabled !== undefined) {
newOrg.set('IsLTVEnabled', request.params.IsLTVEnabled);
}
const updateUserRes = await newOrg.save(null, { useMasterKey: true });
if (updateUserRes) {
const _updateUserRes = JSON.parse(JSON.stringify(updateUserRes));
@@ -0,0 +1,233 @@
/**
*
* @param {Parse} Parse
*/
exports.up = async Parse => {
const defaultotp = new Parse.Schema('defaultdata_Otp');
defaultotp.setCLP({
get: {},
find: {},
count: {},
create: {},
update: {},
delete: {},
addField: {},
});
await defaultotp.update();
const schema = new Parse.Schema('contracts_Signature');
schema.setCLP({
get: { '*': true },
find: { '*': true },
count: { '*': true },
create: { '*': true },
update: { '*': true },
delete: {},
addField: {},
});
await schema.update();
const docSchema = new Parse.Schema('contracts_Document');
docSchema.setCLP({
get: { '*': true },
find: { requiresAuthentication: true },
count: { '*': true },
create: { '*': true },
update: { '*': true },
delete: {},
addField: { requiresAuthentication: true },
});
await docSchema.update();
const templateSchema = new Parse.Schema('contracts_Template');
templateSchema.setCLP({
get: { '*': true },
find: {},
count: { '*': true },
create: { '*': true },
update: { '*': true },
delete: {},
addField: { requiresAuthentication: true },
});
await templateSchema.update();
const dataFiles = new Parse.Schema('partners_DataFiles');
dataFiles.setCLP({
get: {},
find: {},
count: {},
create: { '*': true },
update: {},
delete: {},
addField: { '*': true },
});
await dataFiles.update();
const pTenant = new Parse.Schema('partners_Tenant');
pTenant.setCLP({
get: {},
find: {},
count: {},
create: {},
update: {},
delete: {},
addField: {},
});
await pTenant.update();
const TenantCredits = new Parse.Schema('partners_TenantCredits');
TenantCredits.setCLP({
get: { '*': true },
find: { '*': true },
count: { '*': true },
create: { '*': true },
update: { '*': true },
delete: {},
addField: { '*': true },
});
await TenantCredits.update();
const _user = new Parse.Schema('_User');
_user.setCLP({
get: { '*': true },
find: { '*': true },
count: { '*': true },
create: { '*': true },
update: { '*': true },
delete: {},
addField: {},
});
await _user.update();
const Migrationdb = new Parse.Schema('Migrationdb');
Migrationdb.setCLP({
get: {},
find: {},
count: {},
create: {},
update: {},
delete: {},
addField: {},
});
await Migrationdb.update();
const contactbook = new Parse.Schema('contracts_Contactbook');
contactbook.setCLP({
get: { '*': true },
find: { '*': true },
count: { '*': true },
create: { '*': true },
update: { '*': true },
delete: {},
addField: {},
});
return contactbook.update();
};
/**
*
* @param {Parse} Parse
*/
exports.down = async Parse => {
const defaultotp = new Parse.Schema('defaultdata_Otp');
defaultotp.setCLP({
get: { '*': true },
find: { '*': true },
count: { '*': true },
create: { '*': true },
update: { '*': true },
delete: { '*': true },
addField: { '*': true },
});
await defaultotp.update();
const schema = new Parse.Schema('contracts_Signature');
schema.setCLP({
get: { '*': true },
find: { '*': true },
count: { '*': true },
create: { '*': true },
update: { '*': true },
delete: { '*': true },
addField: { '*': true },
});
await schema.update();
const docSchema = new Parse.Schema('contracts_Document');
docSchema.setCLP({
get: { '*': true },
find: { '*': true },
count: { '*': true },
create: { '*': true },
update: { '*': true },
delete: { '*': true },
addField: { '*': true },
});
await docSchema.update();
const templateSchema = new Parse.Schema('contracts_Template');
templateSchema.setCLP({
get: { '*': true },
find: { '*': true },
count: { '*': true },
create: { '*': true },
update: { '*': true },
delete: { '*': true },
addField: { '*': true },
});
await templateSchema.update();
const dataFiles = new Parse.Schema('partners_DataFiles');
dataFiles.setCLP({
get: { '*': true },
find: { '*': true },
count: { '*': true },
create: { '*': true },
update: { '*': true },
delete: { '*': true },
addField: { '*': true },
});
await dataFiles.update();
const pTenant = new Parse.Schema('partners_Tenant');
pTenant.setCLP({
get: { '*': true },
find: { '*': true },
count: { '*': true },
create: { '*': true },
update: { '*': true },
delete: { '*': true },
addField: { '*': true },
});
await pTenant.update();
const TenantCredits = new Parse.Schema('partners_TenantCredits');
TenantCredits.setCLP({
get: { '*': true },
find: { '*': true },
count: { '*': true },
create: { '*': true },
update: { '*': true },
delete: { '*': true },
addField: { '*': true },
});
await TenantCredits.update();
const _user = new Parse.Schema('_User');
_user.setCLP({
get: { '*': true },
find: { '*': true },
count: { '*': true },
create: { '*': true },
update: { '*': true },
delete: { '*': true },
addField: { '*': true },
});
await _user.update();
const Migrationdb = new Parse.Schema('Migrationdb');
Migrationdb.setCLP({
get: { '*': true },
find: { '*': true },
count: { '*': true },
create: { '*': true },
update: { '*': true },
delete: { '*': true },
addField: { '*': true },
});
await Migrationdb.update();
const contactbook = new Parse.Schema('contracts_Contactbook');
contactbook.setCLP({
get: { '*': true },
find: { '*': true },
count: { '*': true },
create: { '*': true },
update: { '*': true },
delete: { '*': true },
addField: { '*': true },
});
return contactbook.update();
};
+438 -315
View File
File diff suppressed because it is too large Load Diff
+12 -10
View File
@@ -18,43 +18,45 @@
"watch": "nodemon index.js"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.775.0",
"@aws-sdk/s3-request-presigner": "^3.775.0",
"@aws-sdk/client-s3": "^3.787.0",
"@aws-sdk/s3-request-presigner": "^3.787.0",
"@parse/fs-files-adapter": "^3.0.0",
"@parse/s3-files-adapter": "^4.1.0",
"@pdf-lib/fontkit": "^1.1.1",
"@signpdf/placeholder-pdf-lib": "^3.2.4",
"@signpdf/placeholder-pdf-lib": "^3.2.6",
"@signpdf/signer-p12": "^3.2.4",
"@signpdf/signpdf": "^3.2.5",
"aws-sdk": "^2.1692.0",
"axios": "^1.8.4",
"cors": "^2.8.5",
"date-fns-tz": "^3.2.0",
"dotenv": "^16.4.7",
"express": "^4.21.1",
"dotenv": "^16.5.0",
"express": "^4.21.2",
"form-data": "^4.0.2",
"generate-api-key": "^1.0.2",
"googleapis": "^148.0.0",
"mailgun.js": "^11.1.0",
"mongodb": "^6.15.0",
"mongodb": "^6.16.0",
"multer": "^1.4.5-lts.2",
"multer-s3": "^3.0.1",
"node-forge": "^1.3.1",
"nodemailer": "^6.10.0",
"nodemailer": "^6.10.1",
"parse": "^5.3.0",
"parse-dbtool": "^1.2.0",
"parse-server": "^7.4.0",
"parse-server-api-mail-adapter": "^4.1.0",
"pdf-lib": "^1.17.1",
"posthog-node": "^4.10.2",
"posthog-node": "^4.13.0",
"qrcode": "^1.5.4",
"speakeasy": "^2.0.0",
"ws": "^8.18.1"
},
"type": "module",
"devDependencies": {
"@babel/eslint-parser": "^7.27.0",
"eslint": "^9.23.0",
"eslint": "^9.25.1",
"jasmine": "^5.6.0",
"mongodb-runner": "^5.8.0",
"mongodb-runner": "^5.8.2",
"nodemon": "^3.1.9",
"nyc": "^17.1.0",
"prettier": "^3.5.3"