mirror of
https://github.com/OpenSignLabs/OpenSign.git
synced 2026-08-17 21:25:54 +02:00
@@ -1,25 +0,0 @@
|
||||
# Use an official Node runtime as the base image
|
||||
FROM node:18
|
||||
|
||||
# Set the working directory inside the container
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
# Copy package.json and package-lock.json first to leverage Docker cache
|
||||
COPY ./package*.json ./
|
||||
|
||||
# Install application dependencies
|
||||
RUN npm install
|
||||
|
||||
# Copy the current directory contents into the container
|
||||
COPY ./ .
|
||||
COPY ./.husky .
|
||||
|
||||
# Make port 3000 available to the world outside this container
|
||||
EXPOSE 3000
|
||||
|
||||
# Define environment variables if needed
|
||||
# ENV NODE_ENV production
|
||||
|
||||
# Run the application
|
||||
ENTRYPOINT npm run start
|
||||
|
||||
@@ -13,6 +13,10 @@ RUN npm install
|
||||
# Copy the current directory contents into the container
|
||||
COPY apps/OpenSign/ .
|
||||
COPY apps/OpenSign/.husky .
|
||||
COPY apps/OpenSign/entrypoint.sh .
|
||||
|
||||
# make the entrypoint.sh file executable
|
||||
RUN chmod +x entrypoint.sh
|
||||
|
||||
# Define environment variables if needed
|
||||
ENV NODE_ENV=production
|
||||
@@ -20,8 +24,13 @@ ENV GENERATE_SOURCEMAP=false
|
||||
# build
|
||||
RUN npm run build
|
||||
|
||||
# Inject env.js loader into index.html
|
||||
RUN sed -i '/<head>/a\<script src="/env.js"></script>' build/index.html
|
||||
|
||||
# Make port 3000 available to the world outside this container
|
||||
EXPOSE 3000
|
||||
|
||||
ENTRYPOINT ["./entrypoint.sh"]
|
||||
|
||||
# Run the application
|
||||
CMD ["npm", "start"]
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
#!/bin/sh
|
||||
|
||||
ENV_FILE=./build/env.js
|
||||
DOTENV_FILE=./.env.prod # ✅ use .env.prod
|
||||
|
||||
echo "Generating runtime env file at $ENV_FILE..."
|
||||
|
||||
echo "window.RUNTIME_ENV = {" > $ENV_FILE
|
||||
|
||||
# List of keys to include
|
||||
RUNTIME_KEYS="REACT_APP_SERVERURL"
|
||||
|
||||
for key in $RUNTIME_KEYS; do
|
||||
# First check docker env (-e), fallback to .env file
|
||||
value=$(printenv "$key")
|
||||
|
||||
if [ -z "$value" ] && [ -f "$DOTENV_FILE" ]; then
|
||||
# fallback: read from .env
|
||||
value=$(grep "^$key=" "$DOTENV_FILE" | cut -d '=' -f2- | tr -d '\r\n' | sed 's/"/\\"/g')
|
||||
else
|
||||
value=$(echo "$value" | sed 's/"/\\"/g')
|
||||
fi
|
||||
|
||||
echo " $key: \"$value\"," >> $ENV_FILE
|
||||
done
|
||||
|
||||
echo "};" >> $ENV_FILE
|
||||
|
||||
exec "$@"
|
||||
@@ -19,6 +19,7 @@
|
||||
"jwt-decode": "^4.0.0",
|
||||
"moment": "^2.30.1",
|
||||
"parse": "^6.1.1",
|
||||
"pkijs": "^3.0.8",
|
||||
"pdf-lib": "^1.17.1",
|
||||
"print-js": "^1.6.0",
|
||||
"prismjs": "^1.30.0",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"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",
|
||||
"sandbox-news": "Dies ist eine Sandbox-Umgebung. Bitte nicht für produktive Zwecke verwenden.",
|
||||
"create-account": "Konto erstellen",
|
||||
"login": "Anmelden",
|
||||
"language": "Sprache",
|
||||
@@ -675,7 +676,7 @@
|
||||
"public-template-mssg-1": "Um OpenSign in Ihr React- oder Next.js-Projekt zu integrieren, führen Sie einfach den folgenden Befehl aus:",
|
||||
"public-template-mssg-2": "Stellen Sie sicher, dass npm oder yarn in Ihrem Projekt eingerichtet ist. Wenn Sie Yarn verwenden, können Sie npm install durch yarn add @opensign/react ersetzen.",
|
||||
"public-template-mssg-3": "Benötigen Sie weitere Details oder Beispiele?",
|
||||
"public-template-mssg-4": "Besuchen Sie die",
|
||||
"public-template-mssg-4": "Besuchen Sie die ",
|
||||
"public-template-mssg-5": " npm für die neuesten Updates, detaillierte Dokumentationen und Versionshistorie.",
|
||||
"public-template-mssg-6": "Bevor Sie diesen Code-Schnipsel verwenden können, müssen Sie diese Vorlage öffentlich machen.",
|
||||
"public-template-mssg-7": "Bevor Sie einen öffentlichen Link generieren können, müssen Sie diese Vorlage öffentlich machen.",
|
||||
@@ -980,5 +981,46 @@
|
||||
"finish-mssg": "Sind Sie sicher, dass Sie das Dokument abschließen möchten?",
|
||||
"review": "Überprüfen",
|
||||
"next-field": "Nächstes Feld",
|
||||
"required-mssg": "{{leftRequiredWidget}} von {{totalWidget}} Feldern übrig"
|
||||
"required-mssg": "{{leftRequiredWidget}} von {{totalWidget}} Feldern übrig",
|
||||
"verify-document-signature": "Dokumentensignatur überprüfen",
|
||||
"select-pdf-document": "PDF-Dokument auswählen",
|
||||
"selected-file": "Ausgewählte Datei",
|
||||
"verify-signature": "Signatur überprüfen",
|
||||
"verification-status": "Überprüfungsstatus",
|
||||
"verification-in-progress": "Überprüfung läuft...",
|
||||
"verification-results-will-appear-here": "Überprüfungsergebnisse werden hier angezeigt",
|
||||
"please-select-pdf": "Bitte wählen Sie eine gültige PDF-Datei aus",
|
||||
"please-select-file-to-verify": "Bitte wählen Sie eine Datei zur Überprüfung aus",
|
||||
"no-signature-found": "Keine Signatur im Dokument gefunden",
|
||||
"error-verifying-pdf": "Fehler beim Überprüfen der PDF",
|
||||
"signature-valid-basic": "Signatur ist gültig",
|
||||
"signature-invalid-basic": "Signatur ist ungültig",
|
||||
"all-signatures-verified-convincing": "Dokument überprüft: Alle Signaturen wurden erfolgreich validiert.",
|
||||
"some-signatures-invalid-basic": "Einige Signaturen sind ungültig",
|
||||
"no-signatures-processed": "Keine Signaturen verarbeitet",
|
||||
"unnamed-signature-field": "Unbenanntes Signaturfeld",
|
||||
"error-processing-signature": "Fehler beim Verarbeiten der Signatur",
|
||||
"signer-info-not-available": "Signaturinformationen nicht verfügbar",
|
||||
"cert-validity-not-checked": "Gültigkeit des Zertifikats nicht geprüft",
|
||||
"valid": "Gültig",
|
||||
"expired-or-not-yet-valid": "Abgelaufen oder noch nicht gültig",
|
||||
"valid-from": "Gültig von",
|
||||
"to": "bis",
|
||||
"signer": "Unterzeichner",
|
||||
"issuer": "Aussteller",
|
||||
"not-available": "Nicht verfügbar",
|
||||
"not-performed": "Nicht durchgeführt",
|
||||
"missing-acrofield-dict": "Fehlendes Acrofield-Wörterbuch",
|
||||
"signature-dictionary-not-found-or-invalid": "Signaturwörterbuch nicht gefunden oder ungültig",
|
||||
"missing-or-invalid-byterange": "Fehlender oder ungültiger ByteRange",
|
||||
"missing-or-invalid-contents": "Fehlender oder ungültiger Inhalt",
|
||||
"missing-signature-contents": "Fehlender Signaturinhalt",
|
||||
"invalid-signature-hex-format": "Ungültiges Signatur-Hex-Format",
|
||||
"unsupported-signature-format-not-signeddata": "Nicht unterstütztes Signaturformat - nicht SignedData",
|
||||
"signer-certificate-not-found": "Unterzeichnerzertifikat nicht gefunden",
|
||||
"no-certificates-in-signature": "Keine Zertifikate in der Signatur",
|
||||
"no-signer-info-in-pkcs7": "Keine Signaturinformationen in PKCS#7",
|
||||
"could-not-parse-signer-info": "Signaturinformationen konnten nicht analysiert werden",
|
||||
"not-calculated": "Nicht berechnet",
|
||||
"not-found-in-signature": "Nicht in Signatur gefunden"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"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",
|
||||
"sandbox-news": "This is a sandbox environment. Please do not use it for production purposes.",
|
||||
"create-account": "Create account",
|
||||
"login": "Login",
|
||||
"language": "Language",
|
||||
@@ -675,7 +676,7 @@
|
||||
"public-template-mssg-1": "To integrate OpenSign into your React or Next.js project, simply run the following command:",
|
||||
"public-template-mssg-2": "Ensure you have npm or yarn set up in your project. If you're using Yarn, you can replace npm install with yarn add @opensign/react.",
|
||||
"public-template-mssg-3": "Need more details or examples?",
|
||||
"public-template-mssg-4": "Visit the",
|
||||
"public-template-mssg-4": "Visit the ",
|
||||
"public-template-mssg-5": " npm for the latest updates, detailed documentation, and version history.",
|
||||
"public-template-mssg-6": "Before you can use this code snippet, you must make this template public.",
|
||||
"public-template-mssg-7": "Before you can generate a public link you must make this template public.",
|
||||
@@ -980,5 +981,46 @@
|
||||
"finish-mssg":" Are you sure you want to finish the document ?",
|
||||
"review":"Review",
|
||||
"next-field":"Next Field",
|
||||
"required-mssg":"{{leftRequiredWidget}} of {{totalWidget}} fields left"
|
||||
"required-mssg":"{{leftRequiredWidget}} of {{totalWidget}} fields left",
|
||||
"verify-document-signature": "Verify Document Signature",
|
||||
"select-pdf-document": "Select PDF Document",
|
||||
"selected-file": "Selected file",
|
||||
"verify-signature": "Verify Signature",
|
||||
"verification-status": "Verification Status",
|
||||
"verification-in-progress": "Verification in progress...",
|
||||
"verification-results-will-appear-here": "Verification results will appear here",
|
||||
"please-select-pdf": "Please select a valid PDF file",
|
||||
"please-select-file-to-verify": "Please select a file to verify",
|
||||
"no-signature-found": "No signature found in the document",
|
||||
"error-verifying-pdf": "Error verifying PDF",
|
||||
"signature-valid-basic": "Signature is valid",
|
||||
"signature-invalid-basic": "Signature is invalid",
|
||||
"all-signatures-verified-convincing": "Document Verified: All signatures have been successfully validated.",
|
||||
"some-signatures-invalid-basic": "Some signatures are invalid",
|
||||
"no-signatures-processed": "No signatures were processed",
|
||||
"unnamed-signature-field": "Unnamed Signature Field",
|
||||
"error-processing-signature": "Error processing signature",
|
||||
"signer-info-not-available": "Signer information not available",
|
||||
"cert-validity-not-checked": "Certificate validity not checked",
|
||||
"valid": "Valid",
|
||||
"expired-or-not-yet-valid": "Expired or not yet valid",
|
||||
"valid-from": "Valid from",
|
||||
"to": "to",
|
||||
"signer": "Signer",
|
||||
"issuer": "Issuer",
|
||||
"not-available": "Not available",
|
||||
"not-performed": "Not performed",
|
||||
"missing-acrofield-dict": "Missing acrofield dictionary",
|
||||
"signature-dictionary-not-found-or-invalid": "Signature dictionary not found or invalid",
|
||||
"missing-or-invalid-byterange": "Missing or invalid ByteRange",
|
||||
"missing-or-invalid-contents": "Missing or invalid Contents",
|
||||
"missing-signature-contents": "Missing signature contents",
|
||||
"invalid-signature-hex-format": "Invalid signature hex format",
|
||||
"unsupported-signature-format-not-signeddata": "Unsupported signature format - not SignedData",
|
||||
"signer-certificate-not-found": "Signer certificate not found",
|
||||
"no-certificates-in-signature": "No certificates in signature",
|
||||
"no-signer-info-in-pkcs7": "No signer info in PKCS#7",
|
||||
"could-not-parse-signer-info": "Could not parse signer info",
|
||||
"not-calculated": "Not calculated",
|
||||
"not-found-in-signature": "Not found in signature"
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"header-news": "Nueva característica: los usuarios del plan Teams ahora pueden integrar sus propios depósitos de AWS S3 para el almacenamiento de archivos",
|
||||
"header-news-btn": "Configurar ahora",
|
||||
"sandbox-news": "Este es un entorno sandbox. Por favor, no lo utilice con fines de producción.",
|
||||
"create-account": "Crear cuenta",
|
||||
"login": "Iniciar sesión",
|
||||
"language": "Idioma",
|
||||
@@ -675,7 +676,7 @@
|
||||
"public-template-mssg-1": "Para integrar OpenSign a tu proyecto React o Next.js, simplemente ejecuta los siguientes comandos:",
|
||||
"public-template-mssg-2": "Asegúrate de tener «npm» o «yarn» configurado en tu proyecto. Si estás usando «yarn», puedes reemplazar «npm install» con «yarn add @opensign/react».",
|
||||
"public-template-mssg-3": "¿Necesitas más detalles o ejemplos?",
|
||||
"public-template-mssg-4": "Visita la",
|
||||
"public-template-mssg-4": "Visita la ",
|
||||
"public-template-mssg-5": " «npm» para las últimas actualizaciones, documentación detallada e historial de versiones.",
|
||||
"public-template-mssg-6": "Antes de que puedas usar este fragmento de código, debes convertir esta plantilla en pública.",
|
||||
"public-template-mssg-7": "Antes de poder generar un enlace público, debes hacer que esta plantilla sea pública.",
|
||||
@@ -980,5 +981,46 @@
|
||||
"finish-mssg": "¿Está seguro de que desea finalizar el documento?",
|
||||
"review": "Revisar",
|
||||
"next-field": "Siguiente campo",
|
||||
"required-mssg": "{{leftRequiredWidget}} de {{totalWidget}} campos restantes"
|
||||
"required-mssg": "{{leftRequiredWidget}} de {{totalWidget}} campos restantes",
|
||||
"verify-document-signature": "Verificar firma del documento",
|
||||
"select-pdf-document": "Seleccionar documento PDF",
|
||||
"selected-file": "Archivo seleccionado",
|
||||
"verify-signature": "Verificar firma",
|
||||
"verification-status": "Estado de verificación",
|
||||
"verification-in-progress": "Verificación en curso...",
|
||||
"verification-results-will-appear-here": "Los resultados de la verificación aparecerán aquí",
|
||||
"please-select-pdf": "Por favor, seleccione un archivo PDF válido",
|
||||
"please-select-file-to-verify": "Por favor, seleccione un archivo para verificar",
|
||||
"no-signature-found": "No se encontró ninguna firma en el documento",
|
||||
"error-verifying-pdf": "Error al verificar el PDF",
|
||||
"signature-valid-basic": "La firma es válida",
|
||||
"signature-invalid-basic": "La firma no es válida",
|
||||
"all-signatures-verified-convincing": "Documento verificado: Todas las firmas han sido validadas exitosamente.",
|
||||
"some-signatures-invalid-basic": "Algunas firmas no son válidas",
|
||||
"no-signatures-processed": "No se procesaron firmas",
|
||||
"unnamed-signature-field": "Campo de firma sin nombre",
|
||||
"error-processing-signature": "Error al procesar la firma",
|
||||
"signer-info-not-available": "Información del firmante no disponible",
|
||||
"cert-validity-not-checked": "Validez del certificado no verificada",
|
||||
"valid": "Válido",
|
||||
"expired-or-not-yet-valid": "Caducado o aún no válido",
|
||||
"valid-from": "Válido desde",
|
||||
"to": "hasta",
|
||||
"signer": "Firmante",
|
||||
"issuer": "Emisor",
|
||||
"not-available": "No disponible",
|
||||
"not-performed": "No realizado",
|
||||
"missing-acrofield-dict": "Falta el diccionario Acrofield",
|
||||
"signature-dictionary-not-found-or-invalid": "Diccionario de firmas no encontrado o inválido",
|
||||
"missing-or-invalid-byterange": "ByteRange faltante o inválido",
|
||||
"missing-or-invalid-contents": "Contenido faltante o inválido",
|
||||
"missing-signature-contents": "Falta el contenido de la firma",
|
||||
"invalid-signature-hex-format": "Formato hexadecimal de firma inválido",
|
||||
"unsupported-signature-format-not-signeddata": "Formato de firma no compatible - no SignedData",
|
||||
"signer-certificate-not-found": "Certificado del firmante no encontrado",
|
||||
"no-certificates-in-signature": "No hay certificados en la firma",
|
||||
"no-signer-info-in-pkcs7": "No hay información del firmante en PKCS#7",
|
||||
"could-not-parse-signer-info": "No se pudo analizar la información del firmante",
|
||||
"not-calculated": "No calculado",
|
||||
"not-found-in-signature": "No encontrado en la firma"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"header-news": "Nouvelle fonctionnalité : les utilisateurs du forfait Teams peuvent désormais intégrer leurs propres compartiments AWS S3 pour le stockage de fichiers",
|
||||
"header-news-btn": "Configurer maintenant",
|
||||
"sandbox-news": "Ceci est un environnement sandbox. Veuillez ne pas l'utiliser à des fins de production.",
|
||||
"create-account": "Créer un compte",
|
||||
"login": "Se Connecter",
|
||||
"language": "Langue",
|
||||
@@ -675,7 +676,7 @@
|
||||
"public-template-mssg-1": "Pour intégrer OpenSign dans votre projet React ou Next.js, exécutez simplement la commande suivante :",
|
||||
"public-template-mssg-2": "Assurez-vous que npm ou Yarn est configuré dans votre projet. Si vous utilisez Yarn, vous pouvez remplacer npm install par Yarn Add @opensign/react.",
|
||||
"public-template-mssg-3": "Besoin de plus de détails ou d'exemples ?",
|
||||
"public-template-mssg-4": "Visitez le",
|
||||
"public-template-mssg-4": "Visitez le ",
|
||||
"public-template-mssg-5": "npm pour les dernières mises à jour, une documentation détaillée et l'historique des versions.",
|
||||
"public-template-mssg-6": "Avant de pouvoir utiliser cet extrait de code, vous devez rendre ce modèle public.",
|
||||
"public-template-mssg-7": "Avant de pouvoir générer un lien public, vous devez rendre ce modèle public.",
|
||||
@@ -980,5 +981,46 @@
|
||||
"finish-mssg": "Êtes-vous sûr de vouloir terminer le document ?",
|
||||
"review": "Revoir",
|
||||
"next-field": "Champ suivant",
|
||||
"required-mssg":"{{leftRequiredWidget}} champs sur {{totalWidget}} restants"
|
||||
"required-mssg":"{{leftRequiredWidget}} champs sur {{totalWidget}} restants",
|
||||
"verify-document-signature": "Vérifier la signature du document",
|
||||
"select-pdf-document": "Sélectionner le document PDF",
|
||||
"selected-file": "Fichier sélectionné",
|
||||
"verify-signature": "Vérifier la signature",
|
||||
"verification-status": "État de la vérification",
|
||||
"verification-in-progress": "Vérification en cours...",
|
||||
"verification-results-will-appear-here": "Les résultats de la vérification apparaîtront ici",
|
||||
"please-select-pdf": "Veuillez sélectionner un fichier PDF valide",
|
||||
"please-select-file-to-verify": "Veuillez sélectionner un fichier à vérifier",
|
||||
"no-signature-found": "Aucune signature trouvée dans le document",
|
||||
"error-verifying-pdf": "Erreur lors de la vérification du PDF",
|
||||
"signature-valid-basic": "La signature est valide",
|
||||
"signature-invalid-basic": "La signature est invalide",
|
||||
"all-signatures-verified-convincing": "Document vérifié : Toutes les signatures ont été validées avec succès.",
|
||||
"some-signatures-invalid-basic": "Certaines signatures sont invalides",
|
||||
"no-signatures-processed": "Aucune signature traitée",
|
||||
"unnamed-signature-field": "Champ de signature sans nom",
|
||||
"error-processing-signature": "Erreur lors du traitement de la signature",
|
||||
"signer-info-not-available": "Informations sur le signataire non disponibles",
|
||||
"cert-validity-not-checked": "Validité du certificat non vérifiée",
|
||||
"valid": "Valide",
|
||||
"expired-or-not-yet-valid": "Expiré ou pas encore valide",
|
||||
"valid-from": "Valide du",
|
||||
"to": "au",
|
||||
"signer": "Signataire",
|
||||
"issuer": "Émetteur",
|
||||
"not-available": "Non disponible",
|
||||
"not-performed": "Non effectué",
|
||||
"missing-acrofield-dict": "Dictionnaire Acrofield manquant",
|
||||
"signature-dictionary-not-found-or-invalid": "Dictionnaire de signatures introuvable ou invalide",
|
||||
"missing-or-invalid-byterange": "ByteRange manquant ou invalide",
|
||||
"missing-or-invalid-contents": "Contenu manquant ou invalide",
|
||||
"missing-signature-contents": "Contenu de la signature manquant",
|
||||
"invalid-signature-hex-format": "Format hexadécimal de signature invalide",
|
||||
"unsupported-signature-format-not-signeddata": "Format de signature non pris en charge - pas SignedData",
|
||||
"signer-certificate-not-found": "Certificat du signataire introuvable",
|
||||
"no-certificates-in-signature": "Aucun certificat dans la signature",
|
||||
"no-signer-info-in-pkcs7": "Aucune information sur le signataire dans PKCS#7",
|
||||
"could-not-parse-signer-info": "Impossible d'analyser les informations sur le signataire",
|
||||
"not-calculated": "Non calculé",
|
||||
"not-found-in-signature": "Introuvable dans la signature"
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"header-news": "Nuova funzionalità: Gli utenti del piano Teams possono ora integrare i propri bucket AWS S3 per l'archiviazione dei file",
|
||||
"header-news-btn": "Configura Ora",
|
||||
"sandbox-news": "Questo è un ambiente sandbox. Si prega di non utilizzarlo per scopi di produzione.",
|
||||
"create-account": "Crea Account",
|
||||
"login": "Accedi",
|
||||
"language": "Lingua",
|
||||
@@ -675,7 +676,7 @@
|
||||
"public-template-mssg-1": "Per integrare OpenSign nel tuo progetto React o Next.js, esegui semplicemente il seguente comando:",
|
||||
"public-template-mssg-2": "Assicurati di avere npm o yarn configurato nel tuo progetto. Se usi Yarn, puoi sostituire npm install con yarn add @opensign/react.",
|
||||
"public-template-mssg-3": "Hai bisogno di maggiori dettagli o esempi?",
|
||||
"public-template-mssg-4": "Visita il",
|
||||
"public-template-mssg-4": "Visita il ",
|
||||
"public-template-mssg-5": " npm per gli aggiornamenti più recenti, documentazione dettagliata e cronologia delle versioni.",
|
||||
"public-template-mssg-6": "Prima di poter utilizzare questo frammento di codice, devi rendere questo modello pubblico.",
|
||||
"public-template-mssg-7": "Prima di poter generare un link pubblico, devi rendere questo modello pubblico.",
|
||||
@@ -980,5 +981,46 @@
|
||||
"finish-mssg": "Sei sicuro di voler completare il documento?",
|
||||
"review": "Rivedere",
|
||||
"next-field": "Campo successivo",
|
||||
"required-mssg":"{{leftRequiredWidget}} di {{totalWidget}} campi rimanenti"
|
||||
"required-mssg":"{{leftRequiredWidget}} di {{totalWidget}} campi rimanenti",
|
||||
"verify-document-signature": "Verifica firma documento",
|
||||
"select-pdf-document": "Seleziona documento PDF",
|
||||
"selected-file": "File selezionato",
|
||||
"verify-signature": "Verifica firma",
|
||||
"verification-status": "Stato verifica",
|
||||
"verification-in-progress": "Verifica in corso...",
|
||||
"verification-results-will-appear-here": "I risultati della verifica appariranno qui",
|
||||
"please-select-pdf": "Seleziona un file PDF valido",
|
||||
"please-select-file-to-verify": "Seleziona un file da verificare",
|
||||
"no-signature-found": "Nessuna firma trovata nel documento",
|
||||
"error-verifying-pdf": "Errore durante la verifica del PDF",
|
||||
"signature-valid-basic": "La firma è valida",
|
||||
"signature-invalid-basic": "La firma non è valida",
|
||||
"all-signatures-verified-convincing": "Documento Verificato: Tutte le firme sono state validate con successo.",
|
||||
"some-signatures-invalid-basic": "Alcune firme non sono valide",
|
||||
"no-signatures-processed": "Nessuna firma elaborata",
|
||||
"unnamed-signature-field": "Campo firma senza nome",
|
||||
"error-processing-signature": "Errore durante l'elaborazione della firma",
|
||||
"signer-info-not-available": "Informazioni firmatario non disponibili",
|
||||
"cert-validity-not-checked": "Validità certificato non verificata",
|
||||
"valid": "Valido",
|
||||
"expired-or-not-yet-valid": "Scaduto o non ancora valido",
|
||||
"valid-from": "Valido dal",
|
||||
"to": "al",
|
||||
"signer": "Firmatario",
|
||||
"issuer": "Emittente",
|
||||
"not-available": "Non disponibile",
|
||||
"not-performed": "Non eseguito",
|
||||
"missing-acrofield-dict": "Dizionario Acrofield mancante",
|
||||
"signature-dictionary-not-found-or-invalid": "Dizionario firme non trovato o non valido",
|
||||
"missing-or-invalid-byterange": "ByteRange mancante o non valido",
|
||||
"missing-or-invalid-contents": "Contenuto mancante o non valido",
|
||||
"missing-signature-contents": "Contenuto firma mancante",
|
||||
"invalid-signature-hex-format": "Formato esadecimale firma non valido",
|
||||
"unsupported-signature-format-not-signeddata": "Formato firma non supportato - non SignedData",
|
||||
"signer-certificate-not-found": "Certificato firmatario non trovato",
|
||||
"no-certificates-in-signature": "Nessun certificato nella firma",
|
||||
"no-signer-info-in-pkcs7": "Nessuna informazione firmatario in PKCS#7",
|
||||
"could-not-parse-signer-info": "Impossibile analizzare le informazioni del firmatario",
|
||||
"not-calculated": "Non calcolato",
|
||||
"not-found-in-signature": "Non trovato nella firma"
|
||||
}
|
||||
|
||||
+10
-10
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect, lazy } from "react";
|
||||
import { useState, useEffect, lazy } from "react";
|
||||
import { Routes, Route, BrowserRouter } from "react-router";
|
||||
import { pdfjs } from "react-pdf";
|
||||
import Form from "./pages/Form";
|
||||
@@ -106,10 +106,10 @@ function App() {
|
||||
element={<LazyPage Page={GuestLogin} />}
|
||||
/>
|
||||
<Route path="/debugpdf" element={<LazyPage Page={DebugPdf} />} />
|
||||
<Route
|
||||
path="/forgetpassword"
|
||||
element={<LazyPage Page={ForgetPassword} />}
|
||||
/>
|
||||
<Route
|
||||
path="/forgetpassword"
|
||||
element={<LazyPage Page={ForgetPassword} />}
|
||||
/>
|
||||
<Route
|
||||
element={
|
||||
<ValidateSession>
|
||||
@@ -117,10 +117,10 @@ function App() {
|
||||
</ValidateSession>
|
||||
}
|
||||
>
|
||||
<Route
|
||||
path="/changepassword"
|
||||
element={<LazyPage Page={ChangePassword} />}
|
||||
/>
|
||||
<Route
|
||||
path="/changepassword"
|
||||
element={<LazyPage Page={ChangePassword} />}
|
||||
/>
|
||||
<Route path="/form/:id" element={<Form />} />
|
||||
<Route path="/report/:id" element={<Report />} />
|
||||
<Route path="/dashboard/:id" element={<Dashboard />} />
|
||||
@@ -165,7 +165,7 @@ function App() {
|
||||
path="/recipientSignPdf/:docId"
|
||||
element={<PdfRequestFiles />}
|
||||
/>
|
||||
<Route path="/users" element={<UserList />} />
|
||||
<Route path="/users" element={<UserList />} />
|
||||
<Route
|
||||
path="/preferences"
|
||||
element={<LazyPage Page={Preferences} />}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
import dp from "../assets/images/dp.png";
|
||||
import FullScreenButton from "./FullScreenButton";
|
||||
import { useNavigate } from "react-router";
|
||||
@@ -172,17 +172,17 @@ const Header = ({ showSidebar, setIsMenu, isConsole }) => {
|
||||
<i className="fa-light fa-user"></i> {t("profile")}
|
||||
</span>
|
||||
</li>
|
||||
<li
|
||||
onClick={() => {
|
||||
setIsOpen(false);
|
||||
navigate("/changepassword");
|
||||
}}
|
||||
>
|
||||
<span>
|
||||
<i className="fa-light fa-lock"></i>{" "}
|
||||
{t("change-password")}
|
||||
</span>
|
||||
</li>
|
||||
<li
|
||||
onClick={() => {
|
||||
setIsOpen(false);
|
||||
navigate("/changepassword");
|
||||
}}
|
||||
>
|
||||
<span>
|
||||
<i className="fa-light fa-lock"></i>{" "}
|
||||
{t("change-password")}
|
||||
</span>
|
||||
</li>
|
||||
</>
|
||||
)}
|
||||
<li onClick={closeDropdown}>
|
||||
|
||||
@@ -22,7 +22,7 @@ function DropdownWidgetOption(props) {
|
||||
|
||||
const resetState = () => {
|
||||
setDropdownOptionList(["option-1", "option-2"]);
|
||||
setDropdownName( props.currWidgetsDetails?.options?.name || props.type);
|
||||
setDropdownName(props.currWidgetsDetails?.options?.name || props.type);
|
||||
setIsReadOnly(false);
|
||||
setIsHideLabel(false);
|
||||
setMinCount(0);
|
||||
@@ -115,7 +115,7 @@ function DropdownWidgetOption(props) {
|
||||
defaultData,
|
||||
isHideLabel
|
||||
);
|
||||
resetState()
|
||||
resetState();
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, {
|
||||
import {
|
||||
useState,
|
||||
useRef,
|
||||
} from "react";
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import BorderResize from "./BorderResize";
|
||||
import PlaceholderBorder from "./PlaceholderBorder";
|
||||
import { Rnd } from "react-rnd";
|
||||
import {
|
||||
changeDateToMomentFormat,
|
||||
@@ -23,6 +22,7 @@ import ModalUi from "../../primitives/ModalUi";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { setIsShowModal } from "../../redux/reducers/widgetSlice";
|
||||
import { themeColor } from "../../constant/const";
|
||||
|
||||
const selectFormat = (data) => {
|
||||
switch (data) {
|
||||
@@ -78,7 +78,6 @@ function Placeholder(props) {
|
||||
const dispatch = useDispatch();
|
||||
const widgetData =
|
||||
props.pos?.options?.defaultValue || props.pos?.options?.response;
|
||||
const [placeholderBorder, setPlaceholderBorder] = useState({ w: 0, h: 0 });
|
||||
const [isDateModal, setIsDateModal] = useState(false);
|
||||
const [containerScale, setContainerScale] = useState();
|
||||
const holdTimeout = useRef(null);
|
||||
@@ -93,10 +92,6 @@ function Placeholder(props) {
|
||||
)
|
||||
: new Date();
|
||||
|
||||
const [getCheckboxRenderWidth, setGetCheckboxRenderWidth] = useState({
|
||||
width: null,
|
||||
height: null
|
||||
});
|
||||
useEffect(() => {
|
||||
const getPdfPageWidth = props.pdfOriginalWH.find(
|
||||
(data) => data.pageNumber === props.pageNumber
|
||||
@@ -127,18 +122,6 @@ function Placeholder(props) {
|
||||
}
|
||||
}, [widgetData]);
|
||||
|
||||
const handleGetDaynamicWH = () => {
|
||||
if (
|
||||
props?.pos?.type === "checkbox" ||
|
||||
props?.pos?.type === radioButtonWidget
|
||||
) {
|
||||
const rndElement = document.getElementById(props.pos.key);
|
||||
if (rndElement) {
|
||||
const { width, height } = rndElement.getBoundingClientRect();
|
||||
setGetCheckboxRenderWidth({ width: width, height: height });
|
||||
}
|
||||
}
|
||||
};
|
||||
//function change format array list with selected date and format
|
||||
const changeDateFormat = () => {
|
||||
const updateDate = [];
|
||||
@@ -309,7 +292,6 @@ function Placeholder(props) {
|
||||
//The else condition is used to handle the case when the user clicks on a widget and open signature pad to draw sign
|
||||
props.setCurrWidgetsDetails && props.setCurrWidgetsDetails(props.pos);
|
||||
handleWidgetIdandPopup();
|
||||
handleGetDaynamicWH();
|
||||
}
|
||||
};
|
||||
//`handleOnClickSettingIcon` is used set current widget details and open setting of it
|
||||
@@ -823,7 +805,6 @@ function Placeholder(props) {
|
||||
background: handleBackground()
|
||||
}}
|
||||
onDrag={() => {
|
||||
handleGetDaynamicWH();
|
||||
props.handleTabDrag && props.handleTabDrag(props.pos.key);
|
||||
}}
|
||||
size={{
|
||||
@@ -838,7 +819,10 @@ function Placeholder(props) {
|
||||
? "auto"
|
||||
: props.posHeight(props.pos, props.isSignYourself)
|
||||
}}
|
||||
minHeight={calculateFont(props.pos.options?.fontSize, true)}
|
||||
minHeight={
|
||||
props.pos.type !== "checkbox" &&
|
||||
calculateFont(props.pos.options?.fontSize, true)
|
||||
}
|
||||
maxHeight="auto"
|
||||
onResizeStart={() => {
|
||||
props.setIsResize && props.setIsResize(true);
|
||||
@@ -873,12 +857,6 @@ function Placeholder(props) {
|
||||
x: xPos(props.pos, props.isSignYourself),
|
||||
y: yPos(props.pos, props.isSignYourself)
|
||||
}}
|
||||
onResize={(e, direction, ref) => {
|
||||
setPlaceholderBorder({
|
||||
w: ref.offsetWidth / (props.scale * containerScale),
|
||||
h: ref.offsetHeight / (props.scale * containerScale)
|
||||
});
|
||||
}}
|
||||
disableDragging={handleDragging()}
|
||||
>
|
||||
{props.pos.key === props?.currWidgetsDetails?.key &&
|
||||
@@ -911,40 +889,27 @@ function Placeholder(props) {
|
||||
props.pos.key === props?.currWidgetsDetails?.key && <BorderResize />
|
||||
)}
|
||||
|
||||
{/* 1- Show a border if props.pos.key === props?.currWidgetsDetails?.key, indicating the current user's selected widget.
|
||||
2- If props.isShowBorder is true, display borders for all widgets.
|
||||
3- Use the combination of props?.isAlllowModify and !props?.assignedWidgetId.includes(props.pos.key) to determine when to show borders:
|
||||
1- When isAlllowModify is true, show borders.
|
||||
2- Do not display border for widgets already assigned (props.assignedWidgetId.includes(props.pos.key) is true).
|
||||
*/}
|
||||
{props.pos.key === props?.currWidgetsDetails?.key &&
|
||||
(props.isShowBorder ||
|
||||
(props?.isAlllowModify &&
|
||||
!props?.assignedWidgetId.includes(props.pos.key))) && (
|
||||
<PlaceholderBorder
|
||||
pos={props.pos}
|
||||
isPlaceholder={props.isPlaceholder}
|
||||
getCheckboxRenderWidth={getCheckboxRenderWidth}
|
||||
scale={props.scale}
|
||||
containerScale={containerScale}
|
||||
placeholderBorder={placeholderBorder}
|
||||
/>
|
||||
)}
|
||||
{/* 1- Show a ouline if props.pos.key === props?.currWidgetsDetails?.key, indicating the current user's selected widget.
|
||||
2- If props.isShowBorder is true, display ouline for all widgets.
|
||||
3- Use the combination of props?.isAlllowModify and !props?.assignedWidgetId.includes(props.pos.key) to determine when to show ouline:
|
||||
3.1- When isAlllowModify is true, show ouline.
|
||||
3.2- Do not display ouline for widgets already assigned (props.assignedWidgetId.includes(props.pos.key) is true).
|
||||
*/}
|
||||
<div
|
||||
className="flex items-stretch justify-center"
|
||||
className={`${
|
||||
props.pos.key === props?.currWidgetsDetails?.key &&
|
||||
(props.isShowBorder ||
|
||||
(props?.isAlllowModify &&
|
||||
!props?.assignedWidgetId.includes(props.pos.key)))
|
||||
? "outline-[0.3px] outline-dashed outline-offset-[10px]"
|
||||
: ""
|
||||
} flex items-stretch justify-center`}
|
||||
style={{
|
||||
outlineColor: themeColor,
|
||||
left: xPos(props.pos, props.isSignYourself),
|
||||
top: yPos(props.pos, props.isSignYourself),
|
||||
width:
|
||||
props.pos.type === radioButtonWidget ||
|
||||
props.pos.type === "checkbox"
|
||||
? "auto"
|
||||
: props.posWidth(props.pos, props.isSignYourself),
|
||||
height:
|
||||
props.pos.type === radioButtonWidget ||
|
||||
props.pos.type === "checkbox"
|
||||
? "auto"
|
||||
: props.posHeight(props.pos, props.isSignYourself),
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
zIndex: "10"
|
||||
}}
|
||||
onTouchEnd={() => handleOnClickPlaceholder()}
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
import React from "react";
|
||||
import { themeColor } from "../../constant/const";
|
||||
import {
|
||||
defaultWidthHeight,
|
||||
isMobile,
|
||||
radioButtonWidget,
|
||||
resizeBorderExtraWidth,
|
||||
textWidget
|
||||
} from "../../constant/Utils";
|
||||
function PlaceholderBorder(props) {
|
||||
const getResizeBorderExtraWidth = resizeBorderExtraWidth();
|
||||
const defaultWidth = defaultWidthHeight(props.pos.type).width;
|
||||
const defaultHeight = defaultWidthHeight(props.pos.type).height;
|
||||
const width = () => {
|
||||
const getWidth =
|
||||
props.placeholderBorder.w || props.pos.Width || defaultWidth;
|
||||
return (
|
||||
getWidth * props.scale * props.containerScale + getResizeBorderExtraWidth
|
||||
);
|
||||
};
|
||||
const height = () => {
|
||||
const getHeight =
|
||||
props.placeholderBorder.h || props.pos.Height || defaultHeight;
|
||||
|
||||
return (
|
||||
getHeight * props.scale * props.containerScale + getResizeBorderExtraWidth
|
||||
);
|
||||
};
|
||||
|
||||
const handleMinWidth = () => {
|
||||
if (props.pos.type === "checkbox" || props.pos.type === radioButtonWidget) {
|
||||
return props.getCheckboxRenderWidth.width + getResizeBorderExtraWidth;
|
||||
} else {
|
||||
return width();
|
||||
}
|
||||
};
|
||||
const handleMinHeight = () => {
|
||||
if (props.pos.type === "checkbox" || props.pos.type === radioButtonWidget) {
|
||||
return props.getCheckboxRenderWidth.height + getResizeBorderExtraWidth;
|
||||
} else {
|
||||
return height();
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div
|
||||
className="absolute inline-block w-[14px] h-[14px] border-[0.2px] overflow-hidden border-dashed"
|
||||
style={{
|
||||
borderColor: themeColor,
|
||||
minWidth: handleMinWidth() || 0,
|
||||
minHeight: handleMinHeight() || 0
|
||||
}}
|
||||
></div>
|
||||
);
|
||||
}
|
||||
|
||||
export default PlaceholderBorder;
|
||||
@@ -98,22 +98,6 @@ function PlaceholderType(props) {
|
||||
}
|
||||
};
|
||||
|
||||
//handle height on enter press in text area
|
||||
// const handleEnterPress = (e) => {
|
||||
// const height = 18;
|
||||
// if (e.key === "Enter") {
|
||||
// //function to save height of text area
|
||||
// onChangeHeightOfTextArea(
|
||||
// height,
|
||||
// props.pos.type,
|
||||
// props.pos.key,
|
||||
// props.xyPosition,
|
||||
// props.index,
|
||||
// props.setXyPosition,
|
||||
// props.data && props.data?.Id
|
||||
// );
|
||||
// }
|
||||
// };
|
||||
switch (type) {
|
||||
case "signature":
|
||||
return props.pos.SignUrl ? (
|
||||
@@ -168,34 +152,34 @@ function PlaceholderType(props) {
|
||||
<div style={{ zIndex: props.isSignYourself && "99" }}>
|
||||
{props.pos.options?.values?.map((data, ind) => {
|
||||
return (
|
||||
<div
|
||||
key={ind}
|
||||
className="select-none-cls flex items-center text-center gap-0.5 pointer-events-none"
|
||||
>
|
||||
<input
|
||||
id={`checkbox-${props.pos.key + ind}`}
|
||||
style={{ width: fontSize, height: fontSize }}
|
||||
className={`${
|
||||
ind === 0 ? "mt-0" : "mt-[5px]"
|
||||
} flex justify-center op-checkbox rounded-[1px] `}
|
||||
disabled={
|
||||
props.isNeedSign &&
|
||||
(props.pos.options?.isReadOnly ||
|
||||
props.data?.signerObjId !== props.signerObjId)
|
||||
}
|
||||
type="checkbox"
|
||||
readOnly
|
||||
checked={!!selectCheckbox(ind, selectedCheckbox)}
|
||||
/>
|
||||
{!props.pos.options?.isHideLabel && (
|
||||
<label
|
||||
htmlFor={`checkbox-${props.pos.key + ind}`}
|
||||
style={{ fontSize: fontSize, color: fontColor }}
|
||||
className="text-xs mb-0 text-center"
|
||||
>
|
||||
{data}
|
||||
</label>
|
||||
)}
|
||||
<div key={ind} className="select-none-cls pointer-events-none">
|
||||
<label
|
||||
htmlFor={`checkbox-${props.pos.key + ind}`}
|
||||
style={{ fontSize: fontSize, color: fontColor }}
|
||||
className={`mb-0 flex items-center gap-1 ${
|
||||
ind > 0 ? "mt-[3px]" : "mt-[0px]"
|
||||
}`}
|
||||
>
|
||||
<input
|
||||
id={`checkbox-${props.pos.key + ind}`}
|
||||
style={{
|
||||
width: fontSize,
|
||||
height: fontSize
|
||||
}}
|
||||
className="op-checkbox rounded-[1px]"
|
||||
disabled={
|
||||
props.isNeedSign &&
|
||||
(props.pos.options?.isReadOnly ||
|
||||
props.data?.signerObjId !== props.signerObjId)
|
||||
}
|
||||
type="checkbox"
|
||||
readOnly
|
||||
checked={!!selectCheckbox(ind, selectedCheckbox)}
|
||||
/>
|
||||
{!props.pos.options?.isHideLabel && (
|
||||
<span className="leading-none">{data}</span>
|
||||
)}
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
@@ -243,7 +227,6 @@ function PlaceholderType(props) {
|
||||
<i className="fa-light fa-circle-chevron-down mr-1 "></i>
|
||||
</div>
|
||||
);
|
||||
|
||||
case "initials":
|
||||
return props.pos.SignUrl ? (
|
||||
<img
|
||||
@@ -442,38 +425,39 @@ function PlaceholderType(props) {
|
||||
<div>
|
||||
{props.pos.options?.values.map((data, ind) => {
|
||||
return (
|
||||
<div
|
||||
key={ind}
|
||||
className="select-none-cls flex items-center text-center gap-0.5 pointer-events-none"
|
||||
>
|
||||
<input
|
||||
readOnly
|
||||
id={`radio-${props.pos.key + ind}`}
|
||||
<div key={ind} className="select-none-cls pointer-events-none">
|
||||
<label
|
||||
htmlFor={`radio-${props.pos.key + ind}`}
|
||||
style={{
|
||||
width: fontSize,
|
||||
height: fontSize,
|
||||
marginTop: ind > 0 ? "10px" : "0px"
|
||||
fontSize: fontSize,
|
||||
color: fontColor,
|
||||
marginTop: ind > 0 ? "5px" : "0px"
|
||||
}}
|
||||
className={`op-radio rounded-full border- border-black appearance-none bg-white inline-block align-middle relative ${
|
||||
handleRadioCheck(data) ? "checked-radio" : ""
|
||||
}`}
|
||||
type="radio"
|
||||
disabled={
|
||||
props.isNeedSign &&
|
||||
(props.pos.options?.isReadOnly ||
|
||||
props.data?.signerObjId !== props.signerObjId)
|
||||
}
|
||||
checked={handleRadioCheck(data)}
|
||||
/>
|
||||
{!props.pos.options?.isHideLabel && (
|
||||
<label
|
||||
htmlFor={`radio-${props.pos.key + ind}`}
|
||||
style={{ fontSize: fontSize, color: fontColor }}
|
||||
className="text-xs mb-0"
|
||||
>
|
||||
{data}
|
||||
</label>
|
||||
)}
|
||||
className="text-xs mb-0 flex items-center gap-1 "
|
||||
>
|
||||
<input
|
||||
readOnly
|
||||
id={`radio-${props.pos.key + ind}`}
|
||||
style={{
|
||||
width: fontSize,
|
||||
height: fontSize,
|
||||
lineHeight: 2
|
||||
}}
|
||||
className={`op-radio rounded-full border- border-black appearance-none bg-white inline-block align-middle relative ${
|
||||
handleRadioCheck(data) ? "checked-radio" : ""
|
||||
}`}
|
||||
type="radio"
|
||||
disabled={
|
||||
props.isNeedSign &&
|
||||
(props.pos.options?.isReadOnly ||
|
||||
props.data?.signerObjId !== props.signerObjId)
|
||||
}
|
||||
checked={handleRadioCheck(data)}
|
||||
/>
|
||||
{!props.pos.options?.isHideLabel && (
|
||||
<span className="leading-none">{data}</span>
|
||||
)}
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -9,7 +9,8 @@ function SelectLanguage(props) {
|
||||
{ value: "es", text: "Española" }, //spanish
|
||||
{ value: "fr", text: "Français" }, //french
|
||||
{ value: "it", text: "Italiano" }, //italian
|
||||
{ value: "de", text: "Deutsch" } //german
|
||||
{ value: "de", text: "Deutsch" }, //german
|
||||
{ value: "hi", text: "हिन्दी" } //hindi
|
||||
];
|
||||
const defaultLanguage = i18next.language || "en";
|
||||
const [lang, setLang] = useState(defaultLanguage);
|
||||
|
||||
@@ -231,7 +231,9 @@ function WidgetsValueModal(props) {
|
||||
setXyPosition,
|
||||
uniqueId,
|
||||
false,
|
||||
data?.format
|
||||
data?.format,
|
||||
currWidgetsDetails?.options?.fontSize || 12,
|
||||
currWidgetsDetails?.options?.fontColor || "black"
|
||||
);
|
||||
setSelectDate({ date: date, format: data?.format });
|
||||
};
|
||||
@@ -615,21 +617,19 @@ function WidgetsValueModal(props) {
|
||||
setImage();
|
||||
handleTab();
|
||||
};
|
||||
const autoSignAll = () => {
|
||||
return (
|
||||
<label className="cursor-pointer flex items-center text-sm">
|
||||
<input
|
||||
className="mr-2 md:mr-3 op-checkbox op-checkbox-xs md:op-checkbox-sm"
|
||||
type="checkbox"
|
||||
value={isAutoSign}
|
||||
onChange={(e) => {
|
||||
setIsAutoSign(e.target.checked);
|
||||
}}
|
||||
/>
|
||||
{t("auto-sign-mssg")}
|
||||
</label>
|
||||
);
|
||||
};
|
||||
const autoSignAll = (
|
||||
<label className="mb-0 cursor-pointer flex items-center text-sm">
|
||||
<input
|
||||
className="mr-2 md:mr-3 op-checkbox op-checkbox-xs md:op-checkbox-sm"
|
||||
type="checkbox"
|
||||
value={isAutoSign}
|
||||
onChange={(e) => {
|
||||
setIsAutoSign(e.target.checked);
|
||||
}}
|
||||
/>
|
||||
{t("auto-sign-mssg")}
|
||||
</label>
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const loadFont = async () => {
|
||||
@@ -857,7 +857,7 @@ function WidgetsValueModal(props) {
|
||||
]);
|
||||
|
||||
const savesigncheckbox = (
|
||||
<label className="cursor-pointer flex items-center mb-0 text-center text-[11px] md:text-base">
|
||||
<label className="cursor-pointer flex items-center mb-0 text-center text-sm">
|
||||
<input
|
||||
className="mr-2 md:mr-3 op-checkbox op-checkbox-xs md:op-checkbox-sm"
|
||||
type="checkbox"
|
||||
@@ -1059,7 +1059,14 @@ function WidgetsValueModal(props) {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="mt-4 h-full">
|
||||
<div
|
||||
className={`${
|
||||
currWidgetsDetails?.type === "stamp" ||
|
||||
currWidgetsDetails?.type === "image"
|
||||
? ""
|
||||
: "mt-3"
|
||||
} h-full`}
|
||||
>
|
||||
{isDefaultSign ? (
|
||||
<>
|
||||
{currWidgetsDetails?.type !== "initials" &&
|
||||
@@ -1085,7 +1092,7 @@ function WidgetsValueModal(props) {
|
||||
{/* Standalone autoSignAll for "My Signature/Initials" (isDefaultSign) if conditions met */}
|
||||
{setIsAutoSign && uniqueId && (
|
||||
<div className="flex justify-center my-2">
|
||||
{autoSignAll()}
|
||||
{autoSignAll}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
@@ -1114,7 +1121,7 @@ function WidgetsValueModal(props) {
|
||||
{/* Standalone autoSignAll for "My Signature/Initials" (isDefaultSign) if conditions met */}
|
||||
{setIsAutoSign && uniqueId && (
|
||||
<div className="flex justify-center my-2">
|
||||
{autoSignAll()}
|
||||
{autoSignAll}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
@@ -1163,9 +1170,8 @@ function WidgetsValueModal(props) {
|
||||
["image", "stamp"].includes(
|
||||
currWidgetsDetails?.type
|
||||
)))) && (
|
||||
<div className="flex justify-center items-center space-x-4 my-2">
|
||||
{setIsAutoSign && uniqueId && autoSignAll()}
|
||||
|
||||
<div className="flex justify-center items-center gap-x-2 my-2">
|
||||
{setIsAutoSign && uniqueId && autoSignAll}
|
||||
{image &&
|
||||
(isImageSelect ||
|
||||
["image", "stamp"].includes(
|
||||
@@ -1173,7 +1179,7 @@ function WidgetsValueModal(props) {
|
||||
)) && (
|
||||
<label
|
||||
htmlFor={`removeBgToggleModal-${currWidgetsDetails?.key}`}
|
||||
className="cursor-pointer flex items-center text-sm"
|
||||
className="mb-0 cursor-pointer flex items-center text-sm"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
@@ -1253,16 +1259,18 @@ function WidgetsValueModal(props) {
|
||||
<div className="flex flex-row justify-between mt-[10px]">
|
||||
<PenColorComponent />
|
||||
</div>
|
||||
<div className="flex flex-col mt-2">
|
||||
<div className="flex flex-row ml-1 mt-2 gap-x-3">
|
||||
{/* Standalone autoSignAll for "Type" tab if conditions met */}
|
||||
{setIsAutoSign && uniqueId && (
|
||||
<div className="flex justify-start my-1">
|
||||
{autoSignAll()}
|
||||
{autoSignAll}
|
||||
</div>
|
||||
)}
|
||||
{accesstoken && (
|
||||
<div className="flex justify-start my-1">
|
||||
{saveSignCheckbox?.isVisible && savesigncheckbox}
|
||||
</div>
|
||||
)}
|
||||
{accesstoken &&
|
||||
saveSignCheckbox?.isVisible &&
|
||||
savesigncheckbox}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
@@ -1287,16 +1295,18 @@ function WidgetsValueModal(props) {
|
||||
<div className="flex flex-row justify-between mt-[10px]">
|
||||
<PenColorComponent />
|
||||
</div>
|
||||
<div className="flex flex-col mt-2">
|
||||
<div className="flex flex-row ml-1 mt-1 gap-x-3">
|
||||
{/* Standalone autoSignAll for "Draw" tab if conditions met */}
|
||||
{setIsAutoSign && uniqueId && (
|
||||
<div className="flex justify-start my-1">
|
||||
{autoSignAll()}
|
||||
{autoSignAll}
|
||||
</div>
|
||||
)}
|
||||
{accesstoken && (
|
||||
<div className="flex justify-start my-1">
|
||||
{saveSignCheckbox?.isVisible && savesigncheckbox}
|
||||
</div>
|
||||
)}
|
||||
{accesstoken &&
|
||||
saveSignCheckbox?.isVisible &&
|
||||
savesigncheckbox}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
@@ -1324,50 +1334,45 @@ function WidgetsValueModal(props) {
|
||||
);
|
||||
case "checkbox":
|
||||
return (
|
||||
<div className="border-[1px] border-gray-300 rounded-[2px] p-1 px-3">
|
||||
<div className="border-[1px] border-gray-300 rounded-[2px] pt-1 px-2.5">
|
||||
{currWidgetsDetails?.options?.values?.map((data, ind) => {
|
||||
return (
|
||||
<div
|
||||
key={ind}
|
||||
className=" select-none-cls flex items-center text-center gap-0.5"
|
||||
>
|
||||
<input
|
||||
id={`checkbox-${currWidgetsDetails?.key + ind}`}
|
||||
className={`${
|
||||
ind === 0 ? "mt-0" : "mt-[5px]"
|
||||
} op-checkbox op-checkbox-sm rounded-[1px] `}
|
||||
type="checkbox"
|
||||
checked={!!selectCheckbox(ind, selectedCheckbox)}
|
||||
onChange={(e) => {
|
||||
if (e.target.checked) {
|
||||
const maxRequired =
|
||||
currWidgetsDetails?.options?.validation
|
||||
?.maxRequiredCount;
|
||||
const maxCountInt =
|
||||
maxRequired && parseInt(maxRequired);
|
||||
if (maxCountInt > 0) {
|
||||
if (
|
||||
selectedCheckbox &&
|
||||
selectedCheckbox?.length <= maxCountInt - 1
|
||||
) {
|
||||
<div key={ind} className=" select-none-cls">
|
||||
<label
|
||||
htmlFor={`checkbox-${currWidgetsDetails?.key + ind}`}
|
||||
className="text-xs flex items-center gap-1"
|
||||
>
|
||||
<input
|
||||
id={`checkbox-${currWidgetsDetails?.key + ind}`}
|
||||
className={`${
|
||||
ind === 0 ? "mt-0" : "mt-[5px]"
|
||||
} op-checkbox op-checkbox-xs rounded-[1px] mt-1`}
|
||||
type="checkbox"
|
||||
checked={!!selectCheckbox(ind, selectedCheckbox)}
|
||||
onChange={(e) => {
|
||||
if (e.target.checked) {
|
||||
const maxRequired =
|
||||
currWidgetsDetails?.options?.validation
|
||||
?.maxRequiredCount;
|
||||
const maxCountInt =
|
||||
maxRequired && parseInt(maxRequired);
|
||||
if (maxCountInt > 0) {
|
||||
if (
|
||||
selectedCheckbox &&
|
||||
selectedCheckbox?.length <= maxCountInt - 1
|
||||
) {
|
||||
handleCheckboxValue(e.target.checked, ind);
|
||||
}
|
||||
} else {
|
||||
handleCheckboxValue(e.target.checked, ind);
|
||||
}
|
||||
} else {
|
||||
handleCheckboxValue(e.target.checked, ind);
|
||||
}
|
||||
} else {
|
||||
handleCheckboxValue(e.target.checked, ind);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{!currWidgetsDetails?.options?.isHideLabel && (
|
||||
<label
|
||||
htmlFor={`checkbox-${currWidgetsDetails?.key + ind}`}
|
||||
className="text-xs mb-0 text-center"
|
||||
>
|
||||
{data}
|
||||
</label>
|
||||
)}
|
||||
}}
|
||||
/>
|
||||
{data}
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
@@ -1512,35 +1517,26 @@ function WidgetsValueModal(props) {
|
||||
);
|
||||
case radioButtonWidget:
|
||||
return (
|
||||
<div className="border-[1px] border-gray-300 rounded-[2px] p-1 px-3">
|
||||
<div className="border-[1px] border-gray-300 rounded-[2px] pt-1 px-2.5">
|
||||
{currWidgetsDetails?.options?.values.map((data, ind) => {
|
||||
return (
|
||||
<div
|
||||
key={ind}
|
||||
className="select-none-cls flex items-center text-center gap-0.5"
|
||||
>
|
||||
<input
|
||||
id={`radio-${currWidgetsDetails?.key + ind}`}
|
||||
style={{
|
||||
marginTop: ind > 0 ? "10px" : "0px"
|
||||
}}
|
||||
className={`flex justify-center op-radio`}
|
||||
type="radio"
|
||||
value={data}
|
||||
checked={handleRadioCheck(data)}
|
||||
onChange={(e) => {
|
||||
handleCheckRadio(e.target.value);
|
||||
}}
|
||||
/>
|
||||
{!currWidgetsDetails?.options?.isHideLabel && (
|
||||
<label
|
||||
htmlFor={`radio-${currWidgetsDetails?.key + ind}`}
|
||||
// style={{ fontSize: fontSize, color: fontColor }}
|
||||
className="text-xs mb-0"
|
||||
>
|
||||
{data}
|
||||
</label>
|
||||
)}
|
||||
<div key={ind} className="select-none-cls">
|
||||
<label
|
||||
htmlFor={`radio-${currWidgetsDetails?.key + ind}`}
|
||||
className="cursor-pointer flex items-center text-sm gap-1"
|
||||
>
|
||||
<input
|
||||
id={`radio-${currWidgetsDetails?.key + ind}`}
|
||||
className={`op-radio op-radio-xs mt-1`}
|
||||
type="radio"
|
||||
value={data}
|
||||
checked={handleRadioCheck(data)}
|
||||
onChange={(e) => {
|
||||
handleCheckRadio(e.target.value);
|
||||
}}
|
||||
/>
|
||||
<span>{data}</span>
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
@@ -1652,7 +1648,7 @@ function WidgetsValueModal(props) {
|
||||
const validateExpression = (regexValidation) => {
|
||||
if (widgetValue && regexValidation) {
|
||||
let regexObject = regexValidation;
|
||||
if (props.pos?.options?.validation?.type === "regex") {
|
||||
if (currWidgetsDetails?.options?.validation?.type === "regex") {
|
||||
regexObject = RegexParser(regexValidation);
|
||||
}
|
||||
let isValidate = regexObject.test(widgetValue);
|
||||
@@ -1675,7 +1671,8 @@ function WidgetsValueModal(props) {
|
||||
validateExpression(regexValidation);
|
||||
break;
|
||||
default:
|
||||
regexValidation = props.pos?.options?.validation?.pattern || "";
|
||||
regexValidation =
|
||||
currWidgetsDetails?.options?.validation?.pattern || "";
|
||||
validateExpression(regexValidation);
|
||||
break;
|
||||
}
|
||||
@@ -1795,11 +1792,11 @@ function WidgetsValueModal(props) {
|
||||
<ModalUi
|
||||
isOpen={true}
|
||||
handleClose={() => !isShowValidation && handleclose()}
|
||||
position="bottom"
|
||||
>
|
||||
<div className="h-[100%] p-[20px]">
|
||||
<div className="h-[100%] p-[18px]">
|
||||
{isFinish ? (
|
||||
<>
|
||||
{" "}
|
||||
<div className="p-1 mt-3">
|
||||
<span className="text-base">{t("finish-mssg")}</span>
|
||||
</div>
|
||||
@@ -1824,7 +1821,7 @@ function WidgetsValueModal(props) {
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="p-1 m-1">
|
||||
<div>
|
||||
<div className="relative inline-block">
|
||||
<span className="text-base">
|
||||
{currWidgetsDetails?.options?.name || widgetTypeTranslation}
|
||||
@@ -1835,7 +1832,7 @@ function WidgetsValueModal(props) {
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col justify-center m-2 mt-4">
|
||||
<div className="flex flex-col justify-center m-2 mt-3">
|
||||
<div className="flex justify-center">
|
||||
{getWidgetType(currWidgetsDetails?.type)}
|
||||
</div>
|
||||
@@ -1852,15 +1849,18 @@ function WidgetsValueModal(props) {
|
||||
) ? (
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-ghost mr-1 mt-[2px]"
|
||||
className="op-btn op-btn-ghost op-btn-sm mr-1"
|
||||
onClick={() => handleClear()}
|
||||
>
|
||||
{t("clear")}
|
||||
</button>
|
||||
) : (
|
||||
<div className="w-[80px]"></div>
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-ghost op-btn-sm mr-1 cursor-default"
|
||||
></button>
|
||||
)}
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex items-center gap-2">
|
||||
{!isSave && <HandleRequiredField />}
|
||||
{isSave ? (
|
||||
<button
|
||||
@@ -1888,7 +1888,7 @@ function WidgetsValueModal(props) {
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-primary op-btn-sm"
|
||||
className="op-btn op-btn-primary op-btn-sm text-xs md:text-sm"
|
||||
onClick={() => {
|
||||
handleClickOnNext();
|
||||
}}
|
||||
|
||||
@@ -2,9 +2,19 @@ import React, { useEffect, useState } from "react";
|
||||
import AsyncSelect from "react-select/async";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import axios from "axios";
|
||||
import { handleUnlinkSigner } from "../../../constant/Utils";
|
||||
|
||||
const SelectSigners = (props) => {
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
signerPos,
|
||||
setSignerPos,
|
||||
signersData,
|
||||
setSignersData,
|
||||
uniqueId,
|
||||
isRemove,
|
||||
handleAddUser
|
||||
} = props;
|
||||
const [userList, setUserList] = useState([]);
|
||||
const [selected, setSelected] = useState();
|
||||
const [userData, setUserData] = useState({});
|
||||
@@ -30,7 +40,7 @@ const SelectSigners = (props) => {
|
||||
//checking if user select no signer option from dropdown
|
||||
if (item) {
|
||||
//checking selected signer is already assign to the document or not
|
||||
const alreadyAssign = props.signersData.some(
|
||||
const alreadyAssign = signersData.some(
|
||||
(item2) => item2.objectId === item.value
|
||||
);
|
||||
if (alreadyAssign) {
|
||||
@@ -49,7 +59,7 @@ const SelectSigners = (props) => {
|
||||
};
|
||||
const handleAdd = () => {
|
||||
if (userData && userData.objectId) {
|
||||
props.details(userData);
|
||||
handleAddUser(userData);
|
||||
if (props.closePopup) {
|
||||
props.closePopup();
|
||||
}
|
||||
@@ -60,7 +70,13 @@ const SelectSigners = (props) => {
|
||||
};
|
||||
//function to use remove signer from assigned widgets in create template flow
|
||||
const handleRemove = () => {
|
||||
props.handleUnlinkSigner();
|
||||
handleUnlinkSigner(
|
||||
signerPos,
|
||||
setSignerPos,
|
||||
signersData,
|
||||
setSignersData,
|
||||
uniqueId
|
||||
);
|
||||
if (props.closePopup) {
|
||||
props.closePopup();
|
||||
}
|
||||
@@ -161,7 +177,7 @@ const SelectSigners = (props) => {
|
||||
<button className="op-btn op-btn-primary" onClick={() => handleAdd()}>
|
||||
{t("submit")}
|
||||
</button>
|
||||
{props.isExistSigner && props.handleUnlinkSigner && (
|
||||
{props.isExistSigner && isRemove && (
|
||||
<button
|
||||
className="op-btn op-btn-accent op-btn-outline"
|
||||
onClick={() => handleRemove()}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import axios from "axios";
|
||||
import moment from "moment";
|
||||
import React from "react";
|
||||
import { PDFDocument, rgb, degrees } from "pdf-lib";
|
||||
import Parse from "parse";
|
||||
import { appInfo } from "./appinfo";
|
||||
@@ -21,7 +20,9 @@ export const isTabAndMobile = window.innerWidth < 1023;
|
||||
export const textInputWidget = "text input";
|
||||
export const textWidget = "text";
|
||||
export const radioButtonWidget = "radio button";
|
||||
|
||||
export function getEnv() {
|
||||
return window?.RUNTIME_ENV || {};
|
||||
}
|
||||
|
||||
//function for create list of year for date widget
|
||||
export const range = (start, end, step) => {
|
||||
@@ -510,10 +511,6 @@ export const defaultWidthHeight = (type) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const resizeBorderExtraWidth = () => {
|
||||
return 20;
|
||||
};
|
||||
|
||||
export async function getBase64FromUrl(url, autosign) {
|
||||
const data = await fetch(url);
|
||||
const blob = await data.blob();
|
||||
@@ -1114,7 +1111,7 @@ export const addInitialData = (signerPos, setXyPosition, value, userId) => {
|
||||
};
|
||||
|
||||
//function for embed document id
|
||||
export const embedDocId = async (pdfDoc, documentId, allPages) => {
|
||||
export const embedDocId = async (pdfOriginalWH, pdfDoc, documentId) => {
|
||||
const appName =
|
||||
"OpenSign™";
|
||||
// `fontBytes` is used to embed custom font in pdf
|
||||
@@ -1123,19 +1120,21 @@ export const embedDocId = async (pdfDoc, documentId, allPages) => {
|
||||
);
|
||||
pdfDoc.registerFontkit(fontkit);
|
||||
const font = await pdfDoc.embedFont(fontBytes, { subset: true });
|
||||
for (let i = 0; i < allPages; i++) {
|
||||
//pdfOriginalWH contained all pdf's pages width and height
|
||||
for (let i = 0; i < pdfOriginalWH?.length; i++) {
|
||||
const fontSize = 10;
|
||||
const textContent =
|
||||
documentId && `${appName} DocumentId: ${documentId} `;
|
||||
const pages = pdfDoc.getPages();
|
||||
const page = pages[i];
|
||||
const getSize = pdfOriginalWH[i];
|
||||
try {
|
||||
const getObj = compensateRotation(
|
||||
page.getRotation().angle,
|
||||
10,
|
||||
5,
|
||||
1,
|
||||
page.getSize(),
|
||||
getSize,
|
||||
fontSize,
|
||||
rgb(0.5, 0.5, 0.5),
|
||||
font,
|
||||
@@ -1537,7 +1536,13 @@ const getWidgetsFontColor = (type) => {
|
||||
}
|
||||
};
|
||||
//function for embed multiple signature using pdf-lib
|
||||
export const multiSignEmbed = async (widgets, pdfDoc, signyourself, scale) => {
|
||||
export const multiSignEmbed = async (
|
||||
pdfOriginalWH,
|
||||
widgets,
|
||||
pdfDoc,
|
||||
signyourself,
|
||||
scale
|
||||
) => {
|
||||
// `fontBytes` is used to embed custom font in pdf
|
||||
const fontBytes = await fileasbytes(
|
||||
"https://cdn.opensignlabs.com/webfonts/times.ttf"
|
||||
@@ -1546,6 +1551,11 @@ export const multiSignEmbed = async (widgets, pdfDoc, signyourself, scale) => {
|
||||
const font = await pdfDoc.embedFont(fontBytes, { subset: true });
|
||||
let hasError = false;
|
||||
for (let item of widgets) {
|
||||
//pdfOriginalWH contained all pdf's pages width and height
|
||||
//'getSize' is used to get particular pdf's page width and height
|
||||
const getSize = pdfOriginalWH.find(
|
||||
(page) => page?.pageNumber === item?.pageNumber
|
||||
);
|
||||
if (hasError) break; // Stop the outer loop if an error occurred
|
||||
const typeExist = item.pos.some((data) => data?.type);
|
||||
let updateItem;
|
||||
@@ -1665,10 +1675,11 @@ export const multiSignEmbed = async (widgets, pdfDoc, signyourself, scale) => {
|
||||
].includes(position.type);
|
||||
if (position.type === "checkbox") {
|
||||
let checkboxGapFromTop, isCheck;
|
||||
let y = yPos(position);
|
||||
const optionsFontSize = fontSize || 13;
|
||||
const checkboxSize = fontSize;
|
||||
const checkboxTextGapFromLeft = fontSize + 5 || 22;
|
||||
let y = yPos(position) + 2;
|
||||
//calculate checkbox size to draw on pdf
|
||||
const checkboxSize = fontSize - 1;
|
||||
//calculate gap between checkbox and options
|
||||
const checkboxTextGapFromLeft = fontSize + 5;
|
||||
if (position?.options?.values.length > 0) {
|
||||
position?.options?.values.forEach((item, ind) => {
|
||||
const checkboxRandomId = "checkbox" + randomId();
|
||||
@@ -1680,13 +1691,11 @@ export const multiSignEmbed = async (widgets, pdfDoc, signyourself, scale) => {
|
||||
} else if (position?.options?.defaultValue) {
|
||||
isCheck = position?.options?.defaultValue?.includes(ind);
|
||||
}
|
||||
|
||||
const checkbox = form.createCheckBox(checkboxRandomId);
|
||||
|
||||
if (ind > 0) {
|
||||
y = y + checkboxGapFromTop;
|
||||
} else {
|
||||
checkboxGapFromTop = fontSize + 5 || 26;
|
||||
checkboxGapFromTop = fontSize + 3.2;
|
||||
}
|
||||
|
||||
if (!position?.options?.isHideLabel) {
|
||||
@@ -1694,10 +1703,10 @@ export const multiSignEmbed = async (widgets, pdfDoc, signyourself, scale) => {
|
||||
const optionsPosition = compensateRotation(
|
||||
page.getRotation().angle,
|
||||
xPos(position) + checkboxTextGapFromLeft,
|
||||
y,
|
||||
y - 3,
|
||||
1,
|
||||
page.getSize(),
|
||||
optionsFontSize,
|
||||
getSize,
|
||||
fontSize,
|
||||
updateColorInRgb,
|
||||
font,
|
||||
page
|
||||
@@ -1710,7 +1719,7 @@ export const multiSignEmbed = async (widgets, pdfDoc, signyourself, scale) => {
|
||||
width: checkboxSize,
|
||||
height: checkboxSize
|
||||
};
|
||||
checkboxObj = getWidgetPosition(page, checkboxObj, 1);
|
||||
checkboxObj = getWidgetPosition(page, checkboxObj, 1, getSize);
|
||||
checkbox.addToPage(page, checkboxObj);
|
||||
|
||||
//applied which checkbox should be checked
|
||||
@@ -1780,7 +1789,7 @@ export const multiSignEmbed = async (widgets, pdfDoc, signyourself, scale) => {
|
||||
: NewbreakTextIntoLines(textContent, fixedWidth);
|
||||
// Set initial y-coordinate for the first line
|
||||
let x = xPos(position);
|
||||
let y = yPos(position);
|
||||
let y = yPos(position) - 4;
|
||||
// Embed each line on the page
|
||||
for (const line of lines) {
|
||||
const textPosition = compensateRotation(
|
||||
@@ -1788,7 +1797,7 @@ export const multiSignEmbed = async (widgets, pdfDoc, signyourself, scale) => {
|
||||
x,
|
||||
y,
|
||||
1,
|
||||
page.getSize(),
|
||||
getSize,
|
||||
fontSize,
|
||||
updateColorInRgb,
|
||||
font,
|
||||
@@ -1822,7 +1831,12 @@ export const multiSignEmbed = async (widgets, pdfDoc, signyourself, scale) => {
|
||||
width: widgetWidth,
|
||||
height: widgetHeight
|
||||
};
|
||||
const dropdownOption = getWidgetPosition(page, dropdownObj, 1);
|
||||
const dropdownOption = getWidgetPosition(
|
||||
page,
|
||||
dropdownObj,
|
||||
1,
|
||||
getSize
|
||||
);
|
||||
const dropdownSelected = { ...dropdownOption, font: font };
|
||||
dropdown.defaultUpdateAppearances(font);
|
||||
dropdown.addToPage(page, dropdownSelected);
|
||||
@@ -1830,28 +1844,33 @@ export const multiSignEmbed = async (widgets, pdfDoc, signyourself, scale) => {
|
||||
} else if (position.type === radioButtonWidget) {
|
||||
const radioRandomId = "radio" + randomId();
|
||||
const radioGroup = form.createRadioGroup(radioRandomId);
|
||||
let radioOptionGapFromTop;
|
||||
const optionsFontSize = fontSize || 13;
|
||||
const radioTextGapFromLeft = fontSize + 5 || 20;
|
||||
//draw radio button on document and options if hide label is enable
|
||||
let radioButtonFromTop;
|
||||
//getting radio buttons options text font size
|
||||
const optionsFontSize = fontSize;
|
||||
//calculate value of gap between radio button and options
|
||||
const radioTextGapFromLeft = fontSize + 6;
|
||||
//getting radio button font size
|
||||
const radioSize = fontSize;
|
||||
//getting position of radio widget in y direction
|
||||
let y = yPos(position);
|
||||
//on the basic of option's length create radio button and message
|
||||
if (position?.options?.values.length > 0) {
|
||||
position?.options?.values.forEach((item, ind) => {
|
||||
if (ind > 0) {
|
||||
y = y + radioOptionGapFromTop;
|
||||
y = y + radioButtonFromTop;
|
||||
} else {
|
||||
radioOptionGapFromTop = fontSize + 10 || 25;
|
||||
radioButtonFromTop = fontSize + 6;
|
||||
}
|
||||
if (!position?.options?.isHideLabel) {
|
||||
// below line of code is used to embed label with radio button in pdf
|
||||
|
||||
const optionsPosition = compensateRotation(
|
||||
page.getRotation().angle,
|
||||
xPos(position) + radioTextGapFromLeft,
|
||||
y,
|
||||
y - 2,
|
||||
1,
|
||||
page.getSize(),
|
||||
optionsFontSize,
|
||||
getSize,
|
||||
fontSize,
|
||||
updateColorInRgb,
|
||||
font,
|
||||
page
|
||||
@@ -1860,13 +1879,13 @@ export const multiSignEmbed = async (widgets, pdfDoc, signyourself, scale) => {
|
||||
page.drawText(item, optionsPosition);
|
||||
}
|
||||
let radioObj = {
|
||||
x: xPos(position),
|
||||
x: xPos(position) + 2,
|
||||
y: y,
|
||||
width: radioSize,
|
||||
height: radioSize
|
||||
};
|
||||
|
||||
radioObj = getWidgetPosition(page, radioObj, 1);
|
||||
radioObj = getWidgetPosition(page, radioObj, 1, getSize);
|
||||
radioGroup.addOptionToPage(item, page, radioObj);
|
||||
});
|
||||
}
|
||||
@@ -1884,7 +1903,7 @@ export const multiSignEmbed = async (widgets, pdfDoc, signyourself, scale) => {
|
||||
height: widgetHeight
|
||||
};
|
||||
|
||||
const imageOptions = getWidgetPosition(page, signature, 1);
|
||||
const imageOptions = getWidgetPosition(page, signature, 1, getSize);
|
||||
page.drawImage(img, imageOptions);
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -2611,7 +2630,7 @@ function compensateRotation(
|
||||
}
|
||||
|
||||
// `getWidgetPosition` is used to calulcate position of image type widget like x, y, width, height for pdf-lib
|
||||
function getWidgetPosition(page, image, sizeRatio) {
|
||||
function getWidgetPosition(page, image, sizeRatio, getSize) {
|
||||
let pageWidth;
|
||||
// pageHeight;
|
||||
if ([90, 270].includes(page.getRotation().angle)) {
|
||||
@@ -2635,7 +2654,7 @@ function getWidgetPosition(page, image, sizeRatio) {
|
||||
imageX,
|
||||
imageYFromTop,
|
||||
1,
|
||||
page.getSize(),
|
||||
getSize,
|
||||
imageHeight
|
||||
);
|
||||
|
||||
@@ -3066,7 +3085,7 @@ export const mailTemplate = (param) => {
|
||||
"</td></tr><tr><td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Note</td><td></td><td style='color:#626363;font-weight:bold'>" +
|
||||
param.note +
|
||||
"</td></tr><tr><td></td><td></td></tr></table></div> <div style='margin-left:70px'><a target=_blank href=" +
|
||||
param.sigingUrl +
|
||||
param.signingUrl +
|
||||
"><button style='padding:12px;background-color:#d46b0f;color:white;border:0px;font-weight:bold;margin-top:30px'>Sign here</button></a></div><div style='display:flex;justify-content:center;margin-top:10px'></div></div></div><div><p> This is an automated email from " +
|
||||
appName +
|
||||
". For any queries regarding this email, please contact the sender " +
|
||||
@@ -3161,3 +3180,46 @@ export const checkRegularExpress = (validateType, setValidatePlaceholder) => {
|
||||
setValidatePlaceholder("please enter value");
|
||||
}
|
||||
};
|
||||
//function to use unlink signer from widgets
|
||||
export const handleUnlinkSigner = (
|
||||
signerPos,
|
||||
setSignerPos,
|
||||
signersdata,
|
||||
setSignersData,
|
||||
uniqueId
|
||||
) => {
|
||||
//remove existing signer's details from 'signerPos' array
|
||||
const updatePlaceHolder = signerPos.map((x) => {
|
||||
if (x.Id === uniqueId) {
|
||||
return { ...x, signerPtr: {}, signerObjId: "" };
|
||||
}
|
||||
return { ...x };
|
||||
});
|
||||
setSignerPos(updatePlaceHolder);
|
||||
//remove existing signer's details from 'signersdata' array and keep role and id
|
||||
const updateSigner = signersdata.map((item) => {
|
||||
if (item.Id == uniqueId) {
|
||||
return { Role: item.Role, Id: item.Id, blockColor: item.blockColor };
|
||||
}
|
||||
return item;
|
||||
});
|
||||
setSignersData(updateSigner);
|
||||
};
|
||||
//function is used to get pdf original width and height
|
||||
export const getOriginalWH = async (pdf) => {
|
||||
let pdfWHObj = [];
|
||||
//get total page number
|
||||
const totalPages = pdf?.numPages;
|
||||
//according to page number get all pdf's pages width and height
|
||||
for (let index = 0; index < totalPages; index++) {
|
||||
try {
|
||||
const getPage = await pdf.getPage(index + 1);
|
||||
const width = getPage?.view[2];
|
||||
const height = getPage?.view[3];
|
||||
pdfWHObj.push({ pageNumber: index + 1, width, height });
|
||||
} catch (e) {
|
||||
console.log(`Error getting page ${index + 1} of PDF: ${e.message}`);
|
||||
}
|
||||
}
|
||||
return pdfWHObj;
|
||||
};
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import logo from "../assets/images/logo.png";
|
||||
import { getEnv } from "./Utils";
|
||||
|
||||
export function serverUrl_fn() {
|
||||
let baseUrl = process.env.REACT_APP_SERVERURL
|
||||
? process.env.REACT_APP_SERVERURL
|
||||
: window.location.origin + "/api/app";
|
||||
const env = getEnv();
|
||||
const serverurl = env?.REACT_APP_SERVERURL
|
||||
? env.REACT_APP_SERVERURL // env.REACT_APP_SERVERURL is used for prod
|
||||
: process.env.REACT_APP_SERVERURL; // process.env.REACT_APP_SERVERURL is used for dev (locally)
|
||||
let baseUrl = serverurl ? serverurl : window.location.origin + "/api/app";
|
||||
return baseUrl;
|
||||
}
|
||||
export const appInfo = {
|
||||
|
||||
@@ -25,7 +25,7 @@ i18n
|
||||
interpolation: {
|
||||
escapeValue: false // Not needed for react as it escapes by default
|
||||
},
|
||||
whitelist: ["en", "es", "fr", "it", "de"] // List of allowed languages
|
||||
whitelist: ["en", "es", "fr", "it", "de", "hi"] // List of allowed languages
|
||||
});
|
||||
|
||||
export default i18n;
|
||||
|
||||
@@ -1,3 +1,13 @@
|
||||
const userssetting = [
|
||||
{
|
||||
icon: "fa-light fa-users fa-fw",
|
||||
title: "Users",
|
||||
target: "_self",
|
||||
pageType: "",
|
||||
description: "",
|
||||
objectId: "users"
|
||||
}
|
||||
];
|
||||
export const subSetting = [
|
||||
{
|
||||
icon: "fa-light fa-sliders",
|
||||
@@ -7,14 +17,7 @@ export const subSetting = [
|
||||
description: "",
|
||||
objectId: "preferences"
|
||||
},
|
||||
{
|
||||
icon: "fa-light fa-users fa-fw",
|
||||
title: "Users",
|
||||
target: "_self",
|
||||
pageType: "",
|
||||
description: "",
|
||||
objectId: "users"
|
||||
}
|
||||
...userssetting
|
||||
];
|
||||
|
||||
const sidebarList = [
|
||||
@@ -65,7 +68,7 @@ const sidebarList = [
|
||||
pageType: "report",
|
||||
description: "",
|
||||
objectId: "6TeaPr321t"
|
||||
},
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -68,6 +68,7 @@ function Login() {
|
||||
setState({ ...state, loading: false, alertType: type, alertMsg: msg });
|
||||
setTimeout(() => setState({ ...state, alertMsg: "" }), 2000);
|
||||
};
|
||||
|
||||
const checkUserExt = async () => {
|
||||
const app = await getAppLogo();
|
||||
if (app?.error === "invalid_json") {
|
||||
@@ -82,11 +83,11 @@ function Login() {
|
||||
} else {
|
||||
setImage(appInfo?.applogo || undefined);
|
||||
}
|
||||
dispatch(fetchAppInfo());
|
||||
if (localStorage.getItem("accesstoken")) {
|
||||
setState({ ...state, loading: true });
|
||||
GetLoginData();
|
||||
}
|
||||
dispatch(fetchAppInfo());
|
||||
};
|
||||
const handleChange = (event) => {
|
||||
let { name, value } = event.target;
|
||||
@@ -96,20 +97,15 @@ function Login() {
|
||||
setState({ ...state, [name]: value });
|
||||
};
|
||||
|
||||
const handleSubmit = async (event) => {
|
||||
localStorage.removeItem("accesstoken");
|
||||
event.preventDefault();
|
||||
const handleLogin = async (
|
||||
) => {
|
||||
const email = state?.email
|
||||
const password = state?.password
|
||||
|
||||
if (!emailRegex.test(state.email)) {
|
||||
alert("Please enter a valid email address.");
|
||||
return;
|
||||
}
|
||||
|
||||
const { email, password } = state;
|
||||
if (!email || !password) {
|
||||
return;
|
||||
}
|
||||
|
||||
localStorage.removeItem("accesstoken");
|
||||
try {
|
||||
setState({ ...state, loading: true });
|
||||
localStorage.setItem("appLogo", appInfo.applogo);
|
||||
@@ -132,6 +128,14 @@ function Login() {
|
||||
showToast("danger", "Invalid username/password or region");
|
||||
}
|
||||
};
|
||||
const handleLoginBtn = async (event) => {
|
||||
event.preventDefault();
|
||||
if (!emailRegex.test(state.email)) {
|
||||
alert("Please enter a valid email address.");
|
||||
return;
|
||||
}
|
||||
await handleLogin();
|
||||
};
|
||||
|
||||
const setThirdpartyLoader = (value) => {
|
||||
setState({ ...state, thirdpartyLoader: value });
|
||||
@@ -275,7 +279,6 @@ function Login() {
|
||||
const userInformation = JSON.parse(
|
||||
localStorage.getItem("UserInformation")
|
||||
);
|
||||
// console.log("payload ", payload);
|
||||
if (payload && payload.sessionToken) {
|
||||
const params = {
|
||||
userDetails: {
|
||||
@@ -289,7 +292,6 @@ function Login() {
|
||||
}
|
||||
};
|
||||
const userSignUp = await Parse.Cloud.run("usersignup", params);
|
||||
// console.log("userSignUp ", userSignUp);
|
||||
if (userSignUp && userSignUp.sessionToken) {
|
||||
const LocalUserDetails = {
|
||||
name: userInformation.name,
|
||||
@@ -430,7 +432,7 @@ function Login() {
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-2">
|
||||
<div>
|
||||
<form onSubmit={handleSubmit} aria-label="Login Form">
|
||||
<form onSubmit={handleLoginBtn} aria-label="Login Form">
|
||||
<h1 className="text-[30px] mt-6">{t("welcome")}</h1>
|
||||
<fieldset>
|
||||
<legend className="text-[12px] text-[#878787]">
|
||||
@@ -488,15 +490,14 @@ function Login() {
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="relative mt-1">
|
||||
<NavLink
|
||||
to="/forgetpassword"
|
||||
className="text-[13px] op-link op-link-primary underline-offset-1 focus:outline-none ml-1"
|
||||
>
|
||||
{t("forgot-password")}
|
||||
</NavLink>
|
||||
</div>
|
||||
<div className="relative mt-1">
|
||||
<NavLink
|
||||
to="/forgetpassword"
|
||||
className="text-[13px] op-link op-link-primary underline-offset-1 focus:outline-none ml-1"
|
||||
>
|
||||
{t("forgot-password")}
|
||||
</NavLink>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-2 text-center text-xs font-bold mt-2">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useRef, useEffect } from "react";
|
||||
import { useState, useRef, useEffect } from "react";
|
||||
import { PDFDocument } from "pdf-lib";
|
||||
import "../styles/signature.css";
|
||||
import Parse from "parse";
|
||||
@@ -46,7 +46,8 @@ import {
|
||||
textWidget,
|
||||
mailTemplate,
|
||||
updateDateWidgetsRes,
|
||||
widgetDataValue
|
||||
widgetDataValue,
|
||||
getOriginalWH
|
||||
} from "../constant/Utils";
|
||||
import Header from "../components/pdf/PdfHeader";
|
||||
import RenderPdf from "../components/pdf/RenderPdf";
|
||||
@@ -816,11 +817,13 @@ function PdfRequestFiles(
|
||||
//embed document's object id to all pages in pdf document
|
||||
if (!HeaderDocId) {
|
||||
if (!isDocId) {
|
||||
await embedDocId(pdfDoc, docId, allPages);
|
||||
//pdfOriginalWH contained all pdf's pages width,height & pagenumber in array format
|
||||
await embedDocId(pdfOriginalWH, pdfDoc, docId);
|
||||
}
|
||||
}
|
||||
//embed multi signature in pdf
|
||||
//embed all widgets in document
|
||||
const pdfBytes = await multiSignEmbed(
|
||||
pdfOriginalWH,
|
||||
widgets,
|
||||
pdfDoc,
|
||||
isSignYourSelfFlow,
|
||||
@@ -916,7 +919,7 @@ function PdfRequestFiles(
|
||||
const htmlReqBody =
|
||||
"<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body>" +
|
||||
replacedRequestBody +
|
||||
"</body> </html>";
|
||||
"</body></html>";
|
||||
|
||||
const variables = {
|
||||
document_title: documentName,
|
||||
@@ -929,7 +932,7 @@ function PdfRequestFiles(
|
||||
receiver_phone: user?.Phone || "",
|
||||
expiry_date: localExpireDate,
|
||||
company_name: orgName,
|
||||
signing_url: `<a href=${signPdf} target=_blank>Sign here</a>`
|
||||
signing_url: signPdf
|
||||
};
|
||||
replaceVar = replaceMailVaribles(
|
||||
requestSubject,
|
||||
@@ -944,7 +947,7 @@ function PdfRequestFiles(
|
||||
title: documentName,
|
||||
organization: orgName,
|
||||
localExpireDate: localExpireDate,
|
||||
sigingUrl: signPdf
|
||||
signingUrl: signPdf
|
||||
};
|
||||
let params = {
|
||||
replyto: senderEmail || "",
|
||||
@@ -1169,14 +1172,7 @@ function PdfRequestFiles(
|
||||
];
|
||||
//function for get pdf page details
|
||||
const pageDetails = async (pdf) => {
|
||||
let pdfWHObj = [];
|
||||
const totalPages = pdf.numPages; // Get the total number of pages
|
||||
for (let index = 0; index < totalPages; index++) {
|
||||
const getPage = await pdf.getPage(index + 1);
|
||||
const scale = 1;
|
||||
const { width, height } = getPage.getViewport({ scale });
|
||||
pdfWHObj.push({ pageNumber: index + 1, width, height });
|
||||
}
|
||||
const pdfWHObj = await getOriginalWH(pdf);
|
||||
setPdfOriginalWH(pdfWHObj);
|
||||
setPdfLoad(true);
|
||||
};
|
||||
|
||||
@@ -44,7 +44,8 @@ import {
|
||||
handleSignatureType,
|
||||
getBase64FromUrl,
|
||||
generatePdfName,
|
||||
mailTemplate
|
||||
mailTemplate,
|
||||
getOriginalWH
|
||||
} from "../constant/Utils";
|
||||
import RenderPdf from "../components/pdf/RenderPdf";
|
||||
import { useNavigate } from "react-router";
|
||||
@@ -671,14 +672,7 @@ function PlaceHolderSign() {
|
||||
|
||||
//function for get pdf page details
|
||||
const pageDetails = async (pdf) => {
|
||||
let pdfWHObj = [];
|
||||
const totalPages = pdf?.numPages;
|
||||
for (let index = 0; index < totalPages; index++) {
|
||||
const getPage = await pdf.getPage(index + 1);
|
||||
const scale = 1;
|
||||
const { width, height } = getPage.getViewport({ scale });
|
||||
pdfWHObj.push({ pageNumber: index + 1, width, height });
|
||||
}
|
||||
const pdfWHObj = await getOriginalWH(pdf);
|
||||
setPdfOriginalWH(pdfWHObj);
|
||||
setPdfLoad(true);
|
||||
};
|
||||
@@ -862,7 +856,9 @@ function PlaceHolderSign() {
|
||||
});
|
||||
const isSignYourSelfFlow = false;
|
||||
try {
|
||||
//pdfOriginalWH contained all pdf's pages width,height & pagenumber in array format
|
||||
const pdfBase64 = await multiSignEmbed(
|
||||
pdfOriginalWH,
|
||||
placeholder,
|
||||
pdfDoc,
|
||||
isSignYourSelfFlow,
|
||||
@@ -1220,7 +1216,7 @@ function PlaceHolderSign() {
|
||||
receiver_phone: signerMail[i]?.Phone || "",
|
||||
expiry_date: localExpireDate,
|
||||
company_name: orgName,
|
||||
signing_url: `<a href=${signPdf} target=_blank>Sign here</a>`
|
||||
signing_url: signPdf
|
||||
};
|
||||
replaceVar = replaceMailVaribles(
|
||||
requestSubject,
|
||||
@@ -1249,7 +1245,7 @@ function PlaceHolderSign() {
|
||||
receiver_phone: signerMail[i]?.Phone || "",
|
||||
expiry_date: localExpireDate,
|
||||
company_name: orgName,
|
||||
signing_url: `<a href=${signPdf} target=_blank>Sign here</a>`
|
||||
signing_url: signPdf
|
||||
};
|
||||
replaceVar = replaceMailVaribles(mailSubject, htmlReqBody, variables);
|
||||
}
|
||||
@@ -1260,7 +1256,7 @@ function PlaceHolderSign() {
|
||||
title: documentName,
|
||||
organization: orgName,
|
||||
localExpireDate: localExpireDate,
|
||||
sigingUrl: signPdf
|
||||
signingUrl: signPdf
|
||||
};
|
||||
let params = {
|
||||
extUserId: owner?.objectId,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useRef, useEffect } from "react";
|
||||
import { useState, useRef, useEffect } from "react";
|
||||
import { PDFDocument } from "pdf-lib";
|
||||
import "../styles/signature.css";
|
||||
import Parse from "parse";
|
||||
@@ -32,12 +32,12 @@ import {
|
||||
onClickZoomIn,
|
||||
onClickZoomOut,
|
||||
rotatePdfPage,
|
||||
signatureTypes,
|
||||
getBase64FromUrl,
|
||||
convertBase64ToFile,
|
||||
generatePdfName,
|
||||
handleRemoveWidgets,
|
||||
addWidgetSelfsignOptions
|
||||
addWidgetSelfsignOptions,
|
||||
getOriginalWH
|
||||
} from "../constant/Utils";
|
||||
import { useParams } from "react-router";
|
||||
import Tour from "../primitives/Tour";
|
||||
@@ -626,10 +626,12 @@ function SignYourSelf() {
|
||||
const HeaderDocId = extUserPtr?.HeaderDocId;
|
||||
//embed document's object id to all pages in pdf document
|
||||
if (!HeaderDocId) {
|
||||
await embedDocId(pdfDoc, documentId, allPages);
|
||||
//pdfOriginalWH contained all pdf's pages width,height & pagenumber in array format
|
||||
await embedDocId(pdfOriginalWH, pdfDoc, documentId);
|
||||
}
|
||||
//embed multi signature in pdf
|
||||
//embed all widgets in document
|
||||
const pdfBytes = await multiSignEmbed(
|
||||
pdfOriginalWH,
|
||||
xyPosition,
|
||||
pdfDoc,
|
||||
isSignYourSelfFlow,
|
||||
@@ -738,7 +740,6 @@ function SignYourSelf() {
|
||||
getDocumentDetails(false);
|
||||
}
|
||||
};
|
||||
|
||||
//function for save x and y position and show signature tab on that position
|
||||
const handleTabDrag = (key) => {
|
||||
setDragKey(key);
|
||||
@@ -789,14 +790,7 @@ function SignYourSelf() {
|
||||
};
|
||||
//function for get pdf page details
|
||||
const pageDetails = async (pdf) => {
|
||||
let pdfWHObj = [];
|
||||
const totalPages = pdf?.numPages;
|
||||
for (let index = 0; index < totalPages; index++) {
|
||||
const getPage = await pdf.getPage(index + 1);
|
||||
const scale = 1;
|
||||
const { width, height } = getPage.getViewport({ scale });
|
||||
pdfWHObj.push({ pageNumber: index + 1, width, height });
|
||||
}
|
||||
const pdfWHObj = await getOriginalWH(pdf);
|
||||
setPdfOriginalWH(pdfWHObj);
|
||||
setPdfLoad(true);
|
||||
};
|
||||
|
||||
@@ -38,7 +38,8 @@ import {
|
||||
convertPdfArrayBuffer,
|
||||
generatePdfName,
|
||||
textWidget,
|
||||
multiSignEmbed
|
||||
multiSignEmbed,
|
||||
getOriginalWH
|
||||
} from "../constant/Utils";
|
||||
import RenderPdf from "../components/pdf/RenderPdf";
|
||||
import "../styles/AddUser.css";
|
||||
@@ -534,14 +535,7 @@ const TemplatePlaceholder = () => {
|
||||
|
||||
//function for get pdf page details
|
||||
const pageDetails = async (pdf) => {
|
||||
let pdfWHObj = [];
|
||||
const totalPages = pdf?.numPages;
|
||||
for (let index = 0; index < totalPages; index++) {
|
||||
const getPage = await pdf.getPage(index + 1);
|
||||
const scale = 1;
|
||||
const { width, height } = getPage.getViewport({ scale });
|
||||
pdfWHObj.push({ pageNumber: index + 1, width, height });
|
||||
}
|
||||
const pdfWHObj = await getOriginalWH(pdf);
|
||||
setPdfOriginalWH(pdfWHObj);
|
||||
setPdfLoad(true);
|
||||
};
|
||||
@@ -832,7 +826,9 @@ const TemplatePlaceholder = () => {
|
||||
});
|
||||
const isSignYourSelfFlow = false;
|
||||
try {
|
||||
//pdfOriginalWH contained all pdf's pages width,height & pagenumber in array format
|
||||
const pdfBase64 = await multiSignEmbed(
|
||||
pdfOriginalWH,
|
||||
placeholder,
|
||||
pdfDoc,
|
||||
isSignYourSelfFlow,
|
||||
@@ -1149,25 +1145,6 @@ const TemplatePlaceholder = () => {
|
||||
const handleLinkUser = (id) => {
|
||||
setIsAddUser({ [id]: true });
|
||||
};
|
||||
//function to use unlink signer from widgets
|
||||
const handleUnlinkSigner = () => {
|
||||
//remove existing signer's details from 'signerPos' array
|
||||
const updatePlaceHolder = signerPos.map((x) => {
|
||||
if (x.Id === uniqueId) {
|
||||
return { ...x, signerPtr: {}, signerObjId: "" };
|
||||
}
|
||||
return { ...x };
|
||||
});
|
||||
setSignerPos(updatePlaceHolder);
|
||||
//remove existing signer's details from 'signersdata' array and keep role and id
|
||||
const updateSigner = signersdata.map((item) => {
|
||||
if (item.Id == uniqueId) {
|
||||
return { Role: item.Role, Id: item.Id, blockColor: item.blockColor };
|
||||
}
|
||||
return item;
|
||||
});
|
||||
setSignersData(updateSigner);
|
||||
};
|
||||
// `handleAddUser` is used to adduser
|
||||
const handleAddUser = (data) => {
|
||||
const signerPtr = {
|
||||
@@ -1950,7 +1927,9 @@ const TemplatePlaceholder = () => {
|
||||
closePopup={closePopup}
|
||||
signersData={signersdata}
|
||||
signerPos={signerPos}
|
||||
handleUnlinkSigner={handleUnlinkSigner}
|
||||
setSignerPos={setSignerPos}
|
||||
setSignersData={setSignersData}
|
||||
isRemove={true}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -423,9 +423,9 @@ function UserProfile() {
|
||||
onClick={() =>
|
||||
editmode ? handleCancel() : navigate("/changepassword")
|
||||
}
|
||||
className={`op-btn ${
|
||||
editmode ? "op-btn-ghost w-[100px]" : "op-btn-secondary"
|
||||
}`}
|
||||
className={
|
||||
`op-btn ${editmode ? "op-btn-ghost w-[100px]" : "op-btn-secondary"}`
|
||||
}
|
||||
>
|
||||
{editmode ? t("cancel") : t("change-password")}
|
||||
</button>
|
||||
|
||||
@@ -0,0 +1,864 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PDFDocument, PDFName, PDFSignature, PDFRef, PDFDict } from 'pdf-lib'; // Updated import
|
||||
import * as asn1js from 'asn1js';
|
||||
import { Certificate, ContentInfo, SignedData, IssuerAndSerialNumber } from 'pkijs';
|
||||
// import * as jsrsasign from 'jsrsasign'; // Removed for dynamic loading
|
||||
|
||||
const VerifyDocument = () => {
|
||||
const { t } = useTranslation();
|
||||
const [selectedFile, setSelectedFile] = useState(null);
|
||||
const [fileBuffer, setFileBuffer] = useState(null);
|
||||
const [verificationResult, setVerificationResult] = useState('');
|
||||
const [detailedResults, setDetailedResults] = useState([]);
|
||||
const [collapsedSections, setCollapsedSections] = useState({});
|
||||
// const [jsrsasignStatus, setJsrsasignStatus] = useState('loading'); // Removed
|
||||
|
||||
// OID to human-readable label mapping
|
||||
const oidMapping = {
|
||||
'2.5.4.6': 'Country',
|
||||
'2.5.4.10': 'Organization',
|
||||
'2.5.4.11': 'Organizational Unit',
|
||||
'2.5.4.17': 'Postal Code',
|
||||
'2.5.4.8': 'State',
|
||||
'2.5.4.7': 'Locality', // Alternative for City
|
||||
'2.5.4.9': 'City',
|
||||
'2.5.4.51': 'Address',
|
||||
'2.5.4.3': 'Common Name',
|
||||
'2.5.4.4': 'Surname',
|
||||
'2.5.4.5': 'Serial Number',
|
||||
'2.5.4.12': 'Title',
|
||||
'2.5.4.13': 'Description',
|
||||
'2.5.4.16': 'Postal Address',
|
||||
'2.5.4.18': 'Post Office Box',
|
||||
'2.5.4.20': 'Telephone Number',
|
||||
'1.2.840.113549.1.9.1': 'Email Address',
|
||||
// Common alternative OIDs
|
||||
'C': 'Country',
|
||||
'O': 'Organization',
|
||||
'OU': 'Organizational Unit',
|
||||
'CN': 'Common Name',
|
||||
'ST': 'State',
|
||||
'L': 'Locality',
|
||||
'STREET': 'Address',
|
||||
'emailAddress': 'Email Address',
|
||||
'serialNumber': 'Serial Number'
|
||||
};
|
||||
|
||||
// Function to parse certificate subject/issuer into structured data
|
||||
const parseCertificateInfo = (certString) => {
|
||||
if (!certString) return {};
|
||||
|
||||
const parsed = {};
|
||||
|
||||
// Find all OID patterns and their positions
|
||||
const oidPattern = /(\d+\.\d+\.\d+\.\d+|\w+)=/g;
|
||||
const matches = [];
|
||||
let match;
|
||||
|
||||
while ((match = oidPattern.exec(certString)) !== null) {
|
||||
matches.push({
|
||||
oid: match[1],
|
||||
startIndex: match.index,
|
||||
equalIndex: match.index + match[1].length
|
||||
});
|
||||
}
|
||||
|
||||
// Extract value for each OID
|
||||
for (let i = 0; i < matches.length; i++) {
|
||||
const currentMatch = matches[i];
|
||||
const nextMatch = matches[i + 1];
|
||||
|
||||
const valueStart = currentMatch.equalIndex + 1; // Skip the "=" character
|
||||
const valueEnd = nextMatch ? nextMatch.startIndex - 2 : certString.length; // -2 to remove ", " before next OID
|
||||
|
||||
const value = certString.substring(valueStart, valueEnd).trim();
|
||||
const label = oidMapping[currentMatch.oid] || currentMatch.oid;
|
||||
|
||||
parsed[label] = value;
|
||||
}
|
||||
|
||||
return parsed;
|
||||
};
|
||||
|
||||
// Function to determine if status should show success icon
|
||||
const isSuccessStatus = (status) => {
|
||||
const successTerms = ['valid', 'success', 'parsed', 'verified'];
|
||||
const errorTerms = ['error', 'invalid', 'failed', 'expired'];
|
||||
|
||||
const statusLower = status.toLowerCase();
|
||||
|
||||
// Check for explicit error terms first
|
||||
if (errorTerms.some(term => statusLower.includes(term))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for success terms
|
||||
return successTerms.some(term => statusLower.includes(term));
|
||||
};
|
||||
|
||||
// Function to determine if certificate validity should show success icon
|
||||
const isCertificateValid = (validityText) => {
|
||||
const validityLower = validityText.toLowerCase();
|
||||
|
||||
// If it contains "valid" and doesn't contain negative terms
|
||||
return validityLower.includes('valid') &&
|
||||
!validityLower.includes('expired') &&
|
||||
!validityLower.includes('not yet valid') &&
|
||||
!validityLower.includes('invalid');
|
||||
};
|
||||
|
||||
// Toggle collapsible sections
|
||||
const toggleSection = (signatureIndex, section) => {
|
||||
const key = `${signatureIndex}-${section}`;
|
||||
setCollapsedSections(prev => ({
|
||||
...prev,
|
||||
[key]: !prev[key]
|
||||
}));
|
||||
};
|
||||
|
||||
// useEffect for jsrsasign loading removed
|
||||
|
||||
const handleFileChange = (event) => {
|
||||
const file = event.target.files[0];
|
||||
if (file && file.type === 'application/pdf') {
|
||||
setSelectedFile(file);
|
||||
setVerificationResult('');
|
||||
setDetailedResults([]);
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
setFileBuffer(e.target.result);
|
||||
};
|
||||
reader.readAsArrayBuffer(file);
|
||||
} else {
|
||||
setSelectedFile(null);
|
||||
setFileBuffer(null);
|
||||
setDetailedResults([]);
|
||||
setVerificationResult(t('please-select-pdf'));
|
||||
}
|
||||
};
|
||||
|
||||
const parseSignature = async (pdfDoc) => {
|
||||
const signatureFields = pdfDoc.getForm().getFields().filter(field => field instanceof PDFSignature); // Updated filter logic
|
||||
if (!signatureFields.length) {
|
||||
return { error: t('no-signature-found') };
|
||||
}
|
||||
|
||||
const results = [];
|
||||
|
||||
for (const field of signatureFields) {
|
||||
try {
|
||||
if (!field.acroField || !field.acroField.dict) {
|
||||
results.push({
|
||||
name: field.getName() || t('unnamed-signature-field'),
|
||||
status: t('error-processing-signature'),
|
||||
errorDetails: t('missing-acrofield-dict'),
|
||||
signerInfo: t('signer-info-not-available'),
|
||||
certificateSubject: '',
|
||||
certificateIssuer: '',
|
||||
certificateValidity: t('cert-validity-not-checked'),
|
||||
isCertificateDateValid: false,
|
||||
calculatedDocumentHash: t('not-available'),
|
||||
messageDigestInSignature: t('not-available'),
|
||||
hashComparisonResult: t('not-performed'),
|
||||
authenticatedAttributesSignatureResult: t('not-performed'),
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// New logic to determine the actual signature dictionary
|
||||
const fieldDict = field.acroField.dict;
|
||||
const vEntry = fieldDict.get(PDFName.of('V'));
|
||||
let actualSignatureDict = null;
|
||||
|
||||
if (vEntry) {
|
||||
if (vEntry instanceof PDFRef) {
|
||||
const lookedUp = pdfDoc.context.lookup(vEntry);
|
||||
if (lookedUp instanceof PDFDict) {
|
||||
actualSignatureDict = lookedUp;
|
||||
}
|
||||
} else if (vEntry instanceof PDFDict) {
|
||||
actualSignatureDict = vEntry;
|
||||
}
|
||||
}
|
||||
|
||||
// Use actualSignatureDict if found, otherwise behavior might be problematic (as per existing logic)
|
||||
// If actualSignatureDict is null, subsequent checks for byteRangeObject etc. will fail,
|
||||
// leading to an error message for this signature, which is acceptable.
|
||||
const signatureDict = actualSignatureDict;
|
||||
|
||||
// Check if signatureDict is null (meaning actualSignatureDict was not resolved)
|
||||
// and push an error if it is, before trying to get ByteRange or Contents.
|
||||
if (!signatureDict) {
|
||||
results.push({
|
||||
name: field.getName() || t('unnamed-signature-field'),
|
||||
status: t('error-processing-signature'),
|
||||
errorDetails: t('signature-dictionary-not-found-or-invalid'), // New error message
|
||||
signerInfo: t('signer-info-not-available'),
|
||||
certificateSubject: '',
|
||||
certificateIssuer: '',
|
||||
certificateValidity: t('cert-validity-not-checked'),
|
||||
isCertificateDateValid: false,
|
||||
calculatedDocumentHash: t('not-available'),
|
||||
messageDigestInSignature: t('not-available'),
|
||||
hashComparisonResult: t('not-performed'),
|
||||
authenticatedAttributesSignatureResult: t('not-performed'),
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
const byteRangeObject = signatureDict.get(PDFName.of('ByteRange'));
|
||||
let byteRange; // Will be assigned after validation
|
||||
|
||||
// Comprehensive validation for byteRangeObject and its contents
|
||||
if (!byteRangeObject ||
|
||||
!byteRangeObject.array ||
|
||||
!Array.isArray(byteRangeObject.array) ||
|
||||
byteRangeObject.array.length === 0 ||
|
||||
byteRangeObject.array.length % 2 !== 0) {
|
||||
results.push({
|
||||
name: field.getName() || t('unnamed-signature-field'),
|
||||
status: t('error-processing-signature'),
|
||||
errorDetails: t('missing-or-invalid-byterange'),
|
||||
signerInfo: t('signer-info-not-available'),
|
||||
certificateSubject: '',
|
||||
certificateIssuer: '',
|
||||
certificateValidity: t('cert-validity-not-checked'),
|
||||
isCertificateDateValid: false,
|
||||
calculatedDocumentHash: t('not-available'),
|
||||
messageDigestInSignature: t('not-available'),
|
||||
hashComparisonResult: t('not-performed'),
|
||||
authenticatedAttributesSignatureResult: t('not-performed'),
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
const byteRangeNumbers = [];
|
||||
let byteRangeIsValid = true;
|
||||
for (const pdfObject of byteRangeObject.array) {
|
||||
if (!pdfObject || typeof pdfObject.asNumber !== 'function') {
|
||||
byteRangeIsValid = false;
|
||||
break;
|
||||
}
|
||||
const num = pdfObject.asNumber();
|
||||
if (!Number.isFinite(num)) { // Checks for NaN, Infinity, -Infinity
|
||||
byteRangeIsValid = false;
|
||||
break;
|
||||
}
|
||||
byteRangeNumbers.push(num);
|
||||
}
|
||||
|
||||
if (!byteRangeIsValid) {
|
||||
results.push({
|
||||
name: field.getName() || t('unnamed-signature-field'),
|
||||
status: t('error-processing-signature'),
|
||||
errorDetails: t('missing-or-invalid-byterange'), // Or a more specific error like "ByteRange contains non-numeric values"
|
||||
signerInfo: t('signer-info-not-available'),
|
||||
certificateSubject: '',
|
||||
certificateIssuer: '',
|
||||
certificateValidity: t('cert-validity-not-checked'),
|
||||
isCertificateDateValid: false,
|
||||
calculatedDocumentHash: t('not-available'),
|
||||
messageDigestInSignature: t('not-available'),
|
||||
hashComparisonResult: t('not-performed'),
|
||||
authenticatedAttributesSignatureResult: t('not-performed'),
|
||||
});
|
||||
continue;
|
||||
}
|
||||
byteRange = byteRangeNumbers; // Assign the validated numbers to byteRange
|
||||
|
||||
const contentsObject = signatureDict.get(PDFName.of('Contents'));
|
||||
if (!contentsObject || typeof contentsObject.asString !== 'function') {
|
||||
results.push({
|
||||
name: field.getName() || t('unnamed-signature-field'),
|
||||
status: t('error-processing-signature'),
|
||||
errorDetails: t('missing-or-invalid-contents'),
|
||||
signerInfo: t('signer-info-not-available'),
|
||||
certificateSubject: '',
|
||||
certificateIssuer: '',
|
||||
certificateValidity: t('cert-validity-not-checked'),
|
||||
isCertificateDateValid: false,
|
||||
calculatedDocumentHash: t('not-available'),
|
||||
messageDigestInSignature: t('not-available'),
|
||||
hashComparisonResult: t('not-performed'),
|
||||
authenticatedAttributesSignatureResult: t('not-performed'),
|
||||
});
|
||||
continue;
|
||||
}
|
||||
const contents = contentsObject.asString();
|
||||
|
||||
// The old basic check can be removed now as the more specific checks above cover these cases.
|
||||
// if (!byteRange || !contents) { ... }
|
||||
|
||||
// Calculate totalSignedLength for accurate buffer initialization
|
||||
let totalSignedLength = 0;
|
||||
for (let i = 1; i < byteRange.length; i += 2) {
|
||||
totalSignedLength += byteRange[i];
|
||||
}
|
||||
|
||||
if (totalSignedLength <= 0) {
|
||||
results.push({
|
||||
name: field.getName() || t('unnamed-signature-field'),
|
||||
status: t('error-processing-signature'),
|
||||
errorDetails: t('missing-or-invalid-byterange'), // totalSignedLength being non-positive implies invalid ByteRange
|
||||
signerInfo: t('signer-info-not-available'),
|
||||
certificateSubject: '',
|
||||
certificateIssuer: '',
|
||||
certificateValidity: t('cert-validity-not-checked'),
|
||||
isCertificateDateValid: false,
|
||||
calculatedDocumentHash: t('not-available'),
|
||||
messageDigestInSignature: t('not-available'),
|
||||
hashComparisonResult: t('not-performed'),
|
||||
authenticatedAttributesSignatureResult: t('not-performed'),
|
||||
});
|
||||
continue;
|
||||
}
|
||||
const pdfSignedDataBytes = new Uint8Array(totalSignedLength);
|
||||
let offset = 0;
|
||||
let reconstructionFailed = false;
|
||||
|
||||
for (let i = 0; i < byteRange.length; i += 2) {
|
||||
const start = byteRange[i];
|
||||
const length = byteRange[i+1];
|
||||
|
||||
if (start < 0 || length <= 0 || start + length > fileBuffer.byteLength) {
|
||||
reconstructionFailed = true;
|
||||
break;
|
||||
}
|
||||
pdfSignedDataBytes.set(new Uint8Array(fileBuffer.slice(start, start + length)), offset);
|
||||
offset += length;
|
||||
}
|
||||
|
||||
if (reconstructionFailed) {
|
||||
results.push({
|
||||
name: field.getName() || t('unnamed-signature-field'),
|
||||
status: t('error-processing-signature'),
|
||||
errorDetails: t('missing-or-invalid-byterange'), // Error during reconstruction due to invalid segment
|
||||
signerInfo: t('signer-info-not-available'),
|
||||
certificateSubject: '',
|
||||
certificateIssuer: '',
|
||||
certificateValidity: t('cert-validity-not-checked'),
|
||||
isCertificateDateValid: false,
|
||||
calculatedDocumentHash: t('not-available'),
|
||||
messageDigestInSignature: t('not-available'),
|
||||
hashComparisonResult: t('not-performed'),
|
||||
authenticatedAttributesSignatureResult: t('not-performed'),
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// Remove leading/trailing null bytes from hex if present from PDF content
|
||||
const pkcs7Hex = contents.trim();
|
||||
|
||||
if (!pkcs7Hex) {
|
||||
results.push({
|
||||
name: field.getName() || t('unnamed-signature-field'),
|
||||
status: t('signature-invalid-basic'),
|
||||
errorDetails: t('missing-signature-contents'), // New i18n key
|
||||
signerInfo: t('signer-info-not-available'),
|
||||
certificateSubject: '',
|
||||
certificateIssuer: '',
|
||||
certificateValidity: t('cert-validity-not-checked'),
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// Convert hex string to ArrayBuffer
|
||||
let cmsContentBuffer;
|
||||
try {
|
||||
cmsContentBuffer = new Uint8Array(pkcs7Hex.match(/.{1,2}/g).map(byte => parseInt(byte, 16))).buffer;
|
||||
} catch (hexError) {
|
||||
// console.error('Error converting hex string to ArrayBuffer:', hexError); // Removed
|
||||
results.push({
|
||||
name: field.getName() || t('unnamed-signature-field'),
|
||||
status: t('signature-invalid-basic'),
|
||||
errorDetails: t('invalid-signature-hex-format'), // New i18n key
|
||||
signerInfo: t('signer-info-not-available'),
|
||||
certificateSubject: '',
|
||||
certificateIssuer: '',
|
||||
certificateValidity: t('cert-validity-not-checked'),
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// Parse the CMS ContentInfo
|
||||
const asn1 = asn1js.fromBER(cmsContentBuffer);
|
||||
if (asn1.offset === -1) {
|
||||
// console.error('Error parsing ASN.1 from signature data'); // Removed
|
||||
results.push({
|
||||
name: field.getName() || t('unnamed-signature-field'),
|
||||
status: t('signature-invalid-basic'),
|
||||
errorDetails: 'ASN.1 parsing error from signature data.',
|
||||
signerInfo: t('signer-info-not-available'),
|
||||
certificateSubject: '',
|
||||
certificateIssuer: '',
|
||||
certificateValidity: t('cert-validity-not-checked'),
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
const cmsContentInfo = new ContentInfo({ schema: asn1.result });
|
||||
if (String(cmsContentInfo.contentType).trim() !== String(ContentInfo.SIGNED_DATA).trim()) {
|
||||
// console.error('Not a SignedData content type. Actual type:', cmsContentInfo.contentType, 'Expected:', ContentInfo.SIGNED_DATA); // Removed
|
||||
results.push({
|
||||
name: field.getName() || t('unnamed-signature-field'),
|
||||
status: t('signature-invalid-basic'),
|
||||
errorDetails: t('unsupported-signature-format-not-signeddata'), // New i18n key
|
||||
signerInfo: t('signer-info-not-available'),
|
||||
certificateSubject: '',
|
||||
certificateIssuer: '',
|
||||
certificateValidity: t('cert-validity-not-checked'),
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
const signedData = new SignedData({ schema: cmsContentInfo.content });
|
||||
|
||||
let signerInfoText = t('signer-info-not-available');
|
||||
let certSubject = '';
|
||||
let certIssuer = '';
|
||||
let certValidity = t('cert-validity-not-checked');
|
||||
let isValid = false;
|
||||
|
||||
if (signedData.signerInfos && signedData.signerInfos.length > 0) {
|
||||
const signerInfo = signedData.signerInfos[0];
|
||||
|
||||
if (signedData.certificates && signedData.certificates.length > 0) {
|
||||
let signerCertificate = null;
|
||||
for (const cert of signedData.certificates) {
|
||||
if (cert instanceof Certificate) {
|
||||
const issuerAndSerialNumber = signerInfo.sid;
|
||||
if (issuerAndSerialNumber instanceof IssuerAndSerialNumber) {
|
||||
let certMatch = true;
|
||||
if (cert.issuer.typesAndValues.length === issuerAndSerialNumber.issuer.typesAndValues.length) {
|
||||
for (let i = 0; i < cert.issuer.typesAndValues.length; i++) {
|
||||
if (cert.issuer.typesAndValues[i].type !== issuerAndSerialNumber.issuer.typesAndValues[i].type ||
|
||||
cert.issuer.typesAndValues[i].value.valueBlock.value !== issuerAndSerialNumber.issuer.typesAndValues[i].value.valueBlock.value) {
|
||||
certMatch = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
certMatch = false;
|
||||
}
|
||||
|
||||
if (certMatch && cert.serialNumber.valueBlock.valueHexView.join('') === issuerAndSerialNumber.serialNumber.valueBlock.valueHexView.join('')) {
|
||||
signerCertificate = cert;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (signerCertificate) {
|
||||
certSubject = signerCertificate.subject.typesAndValues.map(tv => `${tv.type}=${tv.value.valueBlock.value}`).join(', ');
|
||||
certIssuer = signerCertificate.issuer.typesAndValues.map(tv => `${tv.type}=${tv.value.valueBlock.value}`).join(', ');
|
||||
signerInfoText = `${t('signer')}: ${certSubject}, ${t('issuer')}: ${certIssuer}`;
|
||||
|
||||
const notBefore = signerCertificate.notBefore.value;
|
||||
const notAfter = signerCertificate.notAfter.value;
|
||||
const currentDate = new Date();
|
||||
certValidity = `${t('valid-from')} ${notBefore.toLocaleDateString()} ${t('to')} ${notAfter.toLocaleDateString()}`;
|
||||
if (currentDate < notBefore || currentDate > notAfter) {
|
||||
certValidity += ` (${t('expired-or-not-yet-valid')})`;
|
||||
isValid = false; // Explicitly false if expired
|
||||
} else {
|
||||
certValidity += ` (${t('valid')})`;
|
||||
isValid = true;
|
||||
}
|
||||
} else {
|
||||
signerInfoText = t('signer-certificate-not-found'); // New i18n key
|
||||
}
|
||||
} else {
|
||||
signerInfoText = t('no-certificates-in-signature'); // New i18n key
|
||||
}
|
||||
} else {
|
||||
signerInfoText = t('no-signer-info-in-pkcs7'); // Re-use existing key, or make new one
|
||||
}
|
||||
|
||||
results.push({
|
||||
name: field.getName() || t('unnamed-signature-field'),
|
||||
status: isValid ? t('signature-valid-basic') : t('signature-invalid-basic'),
|
||||
signerInfo: signerInfoText,
|
||||
certificateSubject: certSubject,
|
||||
certificateIssuer: certIssuer,
|
||||
certificateValidity: certValidity,
|
||||
errorDetails: !isValid && signerInfoText === t('signer-info-not-available') ? t('could-not-parse-signer-info') : undefined, // New i18n key
|
||||
});
|
||||
|
||||
} catch (e) {
|
||||
console.error('Error processing signature field with pkijs:', field.getName(), e);
|
||||
results.push({
|
||||
name: field.getName() || t('unnamed-signature-field'),
|
||||
status: t('error-processing-signature'),
|
||||
errorDetails: e.message,
|
||||
signerInfo: t('signer-info-not-available'),
|
||||
certificateSubject: '',
|
||||
certificateIssuer: '',
|
||||
certificateValidity: t('cert-validity-not-checked'),
|
||||
});
|
||||
}
|
||||
}
|
||||
return { results };
|
||||
};
|
||||
|
||||
|
||||
const handleVerifyDocument = async () => {
|
||||
// Removed jsrsasignStatus check
|
||||
|
||||
if (!fileBuffer) {
|
||||
setVerificationResult(t('please-select-file-to-verify'));
|
||||
setDetailedResults([]);
|
||||
return;
|
||||
}
|
||||
|
||||
setVerificationResult(t('verification-in-progress'));
|
||||
setDetailedResults([]);
|
||||
|
||||
try {
|
||||
// Removed window.KJUR and window.X509 check
|
||||
|
||||
const pdfDoc = await PDFDocument.load(fileBuffer, { ignoreEncryption: true });
|
||||
const signatureInfo = await parseSignature(pdfDoc);
|
||||
|
||||
if (signatureInfo.error) {
|
||||
setVerificationResult(signatureInfo.error);
|
||||
} else if (signatureInfo.results && signatureInfo.results.length > 0) {
|
||||
setDetailedResults(signatureInfo.results);
|
||||
// Overall status can be determined by checking if all signatures are valid
|
||||
const allValid = signatureInfo.results.every(res => res.status === t('signature-valid-basic'));
|
||||
setVerificationResult(allValid ? t('all-signatures-verified-convincing') : t('some-signatures-invalid-basic'));
|
||||
} else {
|
||||
setVerificationResult(t('no-signatures-processed')); // Should be caught by no-signature-found earlier
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error during PDF processing or signature verification:', e);
|
||||
setVerificationResult(`${t('error-verifying-pdf')}: ${e.message}`);
|
||||
setDetailedResults([]);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="container mx-auto p-6 bg-base-100 shadow-xl rounded-lg mt-10">
|
||||
<style>{`
|
||||
.checkmark__circle {
|
||||
stroke-dasharray: 166;
|
||||
stroke-dashoffset: 166;
|
||||
stroke-width: 2;
|
||||
stroke-miterlimit: 10;
|
||||
stroke: #7ac142; /* Green color */
|
||||
fill: none;
|
||||
animation: stroke 0.6s cubic-bezier(0.65, 0, 0.45, 1) forwards;
|
||||
}
|
||||
|
||||
.checkmark {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
border-radius: 50%;
|
||||
display: block;
|
||||
stroke-width: 2;
|
||||
stroke: #fff; /* White check path */
|
||||
stroke-miterlimit: 10;
|
||||
margin: 10px auto; /* Example margin */
|
||||
box-shadow: inset 0px 0px 0px #7ac142;
|
||||
animation: fill .4s ease-in-out .4s forwards, scale .3s ease-in-out .9s both;
|
||||
}
|
||||
|
||||
.checkmark__check {
|
||||
transform-origin: 50% 50%;
|
||||
stroke-dasharray: 48;
|
||||
stroke-dashoffset: 48;
|
||||
animation: stroke 0.3s cubic-bezier(0.65, 0, 0.45, 1) 0.8s forwards;
|
||||
}
|
||||
|
||||
@keyframes stroke {
|
||||
100% {
|
||||
stroke-dashoffset: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes scale {
|
||||
0%, 100% {
|
||||
transform: none;
|
||||
}
|
||||
50% {
|
||||
transform: scale3d(1.1, 1.1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fill {
|
||||
100% {
|
||||
box-shadow: inset 0px 0px 0px 30px #7ac142;
|
||||
}
|
||||
}
|
||||
`}</style>
|
||||
<h1 className="text-3xl font-bold mb-6 text-center text-base-content">
|
||||
{t('verify-document-signature')}
|
||||
</h1>
|
||||
|
||||
<div className="mb-6 p-6 border border-base-300 rounded-lg bg-base-200/30 shadow-sm">
|
||||
<label
|
||||
htmlFor="document-upload"
|
||||
className="block text-lg font-medium text-base-content mb-2"
|
||||
>
|
||||
{t('select-pdf-document')}
|
||||
</label>
|
||||
<input
|
||||
type="file"
|
||||
id="document-upload"
|
||||
accept=".pdf"
|
||||
onChange={handleFileChange}
|
||||
className="file-input file-input-bordered file-input-primary w-full max-w-xs"
|
||||
/>
|
||||
{selectedFile && (
|
||||
<p className="mt-2 text-sm text-base-content w-full truncate">
|
||||
{t('selected-file')}: {selectedFile.name}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="text-center mb-6">
|
||||
<button
|
||||
onClick={handleVerifyDocument}
|
||||
className="op-btn op-btn-primary op-btn-md"
|
||||
disabled={!selectedFile || verificationResult === t('verification-in-progress')}
|
||||
>
|
||||
{/* Removed jsrsasignStatus === 'loading' condition for spinner */}
|
||||
{verificationResult === t('verification-in-progress') ? (
|
||||
<span className="loading loading-spinner"></span>
|
||||
) : (
|
||||
t('verify-signature')
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{verificationResult && verificationResult !== t('verification-in-progress') && (
|
||||
<div className="mt-8 p-6 border border-base-300 rounded-lg bg-base-200 shadow-md min-h-[120px] flex flex-col items-center justify-center">
|
||||
<h2 className="text-2xl font-bold mb-4 text-base-content text-center">
|
||||
{t('verification-status')}
|
||||
</h2>
|
||||
{verificationResult === "Document Verified: All signatures have been successfully validated." && (
|
||||
<div className="flex flex-col items-center my-4">
|
||||
<svg className="checkmark" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 52 52">
|
||||
<circle className="checkmark__circle" cx="26" cy="26" r="25" fill="none"/>
|
||||
<path className="checkmark__check" fill="none" d="M14.1 27.2l7.1 7.2 16.7-16.8"/>
|
||||
</svg>
|
||||
</div>
|
||||
)}
|
||||
<p className="text-lg text-base-content mb-4 text-center">{verificationResult}</p>
|
||||
{detailedResults.length > 0 && (
|
||||
<div className="w-full space-y-6">
|
||||
{detailedResults.map((res, index) => {
|
||||
const signerInfo = parseCertificateInfo(res.certificateSubject);
|
||||
const issuerInfo = parseCertificateInfo(res.certificateIssuer);
|
||||
|
||||
return (
|
||||
<div key={index} className="bg-white border border-gray-200 rounded-xl shadow-lg overflow-hidden">
|
||||
{/* Header Section */}
|
||||
<div className="bg-gradient-to-r from-blue-600 to-indigo-600 text-white p-6">
|
||||
<div className="flex items-center space-x-3">
|
||||
<span className="text-2xl">🔏</span>
|
||||
<div>
|
||||
<h4 className="text-xl font-bold">Signature Details</h4>
|
||||
<p className="text-blue-100 text-sm">Digital Certificate Information</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Basic Info Section */}
|
||||
<div className="p-6 border-b border-gray-100">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<span className="text-sm font-medium text-gray-500 uppercase tracking-wide">Field Name</span>
|
||||
<p className="mt-1 text-lg font-semibold text-gray-900 font-mono">{res.name}</p>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-sm font-medium text-gray-500 uppercase tracking-wide">Overall Status</span>
|
||||
<div className="mt-1 flex items-center space-x-2">
|
||||
<span className={`text-lg ${isSuccessStatus(res.status) ? 'text-green-600' : 'text-red-600'}`}>
|
||||
{isSuccessStatus(res.status) ? '✅' : '❌'}
|
||||
</span>
|
||||
<span className="text-lg font-semibold text-gray-900">{res.status}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Signer Information Section */}
|
||||
{Object.keys(signerInfo).length > 0 && (
|
||||
<div className="border-b border-gray-100">
|
||||
<button
|
||||
onClick={() => toggleSection(index, 'signer')}
|
||||
className="w-full px-6 py-4 text-left hover:bg-gray-50 transition-colors duration-200 focus:outline-none focus:bg-gray-50"
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-3">
|
||||
<span className="text-xl">📇</span>
|
||||
<h5 className="text-lg font-semibold text-gray-900">Signer Information</h5>
|
||||
</div>
|
||||
<svg
|
||||
className={`w-5 h-5 text-gray-400 transition-transform duration-200 ${
|
||||
collapsedSections[`${index}-signer`] ? 'transform rotate-180' : ''
|
||||
}`}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
{!collapsedSections[`${index}-signer`] && (
|
||||
<div className="px-6 pb-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{Object.entries(signerInfo).map(([label, value]) => (
|
||||
<div key={label} className="bg-gray-50 rounded-lg p-4">
|
||||
<span className="text-xs font-medium text-gray-500 uppercase tracking-wide">{label}</span>
|
||||
<p className="mt-1 text-sm font-mono text-gray-900 break-all">{value}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Issuer Information Section */}
|
||||
{Object.keys(issuerInfo).length > 0 && (
|
||||
<div className="border-b border-gray-100">
|
||||
<button
|
||||
onClick={() => toggleSection(index, 'issuer')}
|
||||
className="w-full px-6 py-4 text-left hover:bg-gray-50 transition-colors duration-200 focus:outline-none focus:bg-gray-50"
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-3">
|
||||
<span className="text-xl">🏢</span>
|
||||
<h5 className="text-lg font-semibold text-gray-900">Issuer Details</h5>
|
||||
</div>
|
||||
<svg
|
||||
className={`w-5 h-5 text-gray-400 transition-transform duration-200 ${
|
||||
collapsedSections[`${index}-issuer`] ? 'transform rotate-180' : ''
|
||||
}`}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
{!collapsedSections[`${index}-issuer`] && (
|
||||
<div className="px-6 pb-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{Object.entries(issuerInfo).map(([label, value]) => (
|
||||
<div key={label} className="bg-gray-50 rounded-lg p-4">
|
||||
<span className="text-xs font-medium text-gray-500 uppercase tracking-wide">{label}</span>
|
||||
<p className="mt-1 text-sm font-mono text-gray-900 break-all">{value}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Certificate Validity Section */}
|
||||
{res.certificateValidity && (
|
||||
<div className="p-6 bg-gray-50">
|
||||
<div className="flex items-center space-x-3 mb-4">
|
||||
<span className="text-xl">🕒</span>
|
||||
<h5 className="text-lg font-semibold text-gray-900">Certificate Validity</h5>
|
||||
</div>
|
||||
<div className="bg-white rounded-lg p-4 border">
|
||||
<div className="flex items-center space-x-2">
|
||||
<span className={`text-lg ${isCertificateValid(res.certificateValidity) ? 'text-green-600' : 'text-red-600'}`}>
|
||||
{isCertificateValid(res.certificateValidity) ? '✅' : '❌'}
|
||||
</span>
|
||||
<span className="text-sm font-mono text-gray-900">{res.certificateValidity}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Technical Details Section (if any) */}
|
||||
{(res.calculatedDocumentHash || res.messageDigestInSignature || res.hashComparisonResult || res.authenticatedAttributesSignatureResult || res.errorDetails || res.certificateSubject || res.certificateIssuer) && (
|
||||
<div className="p-6 bg-gray-50 border-t">
|
||||
<details className="group">
|
||||
<summary className="flex items-center justify-between cursor-pointer text-sm font-medium text-gray-700 hover:text-gray-900">
|
||||
<span>🔧 Technical Details</span>
|
||||
<svg className="w-4 h-4 transition-transform group-open:rotate-180" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</summary>
|
||||
<div className="mt-4 space-y-3">
|
||||
{/* Raw Certificate Data */}
|
||||
{res.certificateSubject && (
|
||||
<div className="bg-white rounded p-3 border">
|
||||
<span className="text-xs font-medium text-gray-500 uppercase tracking-wide block mb-1">Raw Certificate Subject</span>
|
||||
<code className="text-xs text-gray-800 break-all bg-gray-100 p-2 rounded block">{res.certificateSubject}</code>
|
||||
</div>
|
||||
)}
|
||||
{res.certificateIssuer && (
|
||||
<div className="bg-white rounded p-3 border">
|
||||
<span className="text-xs font-medium text-gray-500 uppercase tracking-wide block mb-1">Raw Certificate Issuer</span>
|
||||
<code className="text-xs text-gray-800 break-all bg-gray-100 p-2 rounded block">{res.certificateIssuer}</code>
|
||||
</div>
|
||||
)}
|
||||
{res.calculatedDocumentHash && res.calculatedDocumentHash !== t('not-available') && res.calculatedDocumentHash !== t('not-calculated') && (
|
||||
<div className="bg-white rounded p-3 border">
|
||||
<span className="text-xs font-medium text-gray-500 uppercase tracking-wide block mb-1">Calculated Document Hash</span>
|
||||
<code className="text-xs text-gray-800 break-all bg-gray-100 p-2 rounded block">{res.calculatedDocumentHash}</code>
|
||||
</div>
|
||||
)}
|
||||
{res.messageDigestInSignature && res.messageDigestInSignature !== t('not-available') && res.messageDigestInSignature !== t('not-found-in-signature') && (
|
||||
<div className="bg-white rounded p-3 border">
|
||||
<span className="text-xs font-medium text-gray-500 uppercase tracking-wide block mb-1">Message Digest in Signature</span>
|
||||
<code className="text-xs text-gray-800 break-all bg-gray-100 p-2 rounded block">{res.messageDigestInSignature}</code>
|
||||
</div>
|
||||
)}
|
||||
{res.hashComparisonResult && res.hashComparisonResult !== t('not-performed') && (
|
||||
<div className="bg-white rounded p-3 border">
|
||||
<span className="text-xs font-medium text-gray-500 uppercase tracking-wide block mb-1">Hash Comparison</span>
|
||||
<span className="text-sm text-gray-800">{res.hashComparisonResult}</span>
|
||||
</div>
|
||||
)}
|
||||
{res.authenticatedAttributesSignatureResult && res.authenticatedAttributesSignatureResult !== t('not-performed') && (
|
||||
<div className="bg-white rounded p-3 border">
|
||||
<span className="text-xs font-medium text-gray-500 uppercase tracking-wide block mb-1">Attributes Signature Verification</span>
|
||||
<span className="text-sm text-gray-800">{res.authenticatedAttributesSignatureResult}</span>
|
||||
</div>
|
||||
)}
|
||||
{res.errorDetails && (
|
||||
<div className="bg-red-50 border border-red-200 rounded p-3">
|
||||
<span className="text-xs font-medium text-red-600 uppercase tracking-wide block mb-1">Error Details</span>
|
||||
<span className="text-sm text-red-800">{res.errorDetails}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{verificationResult === t('verification-in-progress') && (
|
||||
<div className="mt-8 p-4 border border-base-300 rounded-lg bg-base-200 min-h-[100px] flex justify-center items-center">
|
||||
<span className="loading loading-lg loading-dots"></span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!verificationResult && !selectedFile && (
|
||||
<div className="mt-8 p-4 border border-base-300 rounded-lg bg-base-200 min-h-[100px] flex justify-center items-center"> {/* Added flex for centering */}
|
||||
<p className="text-base-content/60 italic text-center">{t('verification-results-will-appear-here')}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default VerifyDocument;
|
||||
@@ -679,7 +679,7 @@ const ReportTable = (props) => {
|
||||
receiver_phone: userDetails?.Phone || "",
|
||||
expiry_date: localExpireDate,
|
||||
company_name: doc.ExtUserPtr.Company,
|
||||
signing_url: `<a href=${signPdf} target=_blank>Sign here</a>`
|
||||
signing_url: signPdf
|
||||
};
|
||||
const res = replaceMailVaribles(subject, "", variables);
|
||||
setMail((prev) => ({ ...prev, subject: res.subject }));
|
||||
@@ -710,7 +710,7 @@ const ReportTable = (props) => {
|
||||
receiver_phone: userDetails?.Phone || "",
|
||||
expiry_date: localExpireDate,
|
||||
company_name: doc.ExtUserPtr.Company,
|
||||
signing_url: `<a href=${signPdf} target=_blank>Sign here</a>`
|
||||
signing_url: signPdf
|
||||
};
|
||||
const res = replaceMailVaribles("", body, variables);
|
||||
|
||||
@@ -754,7 +754,7 @@ const ReportTable = (props) => {
|
||||
receiver_phone: user?.signerPtr?.Phone || "",
|
||||
expiry_date: localExpireDate,
|
||||
company_name: doc?.ExtUserPtr?.Company || "",
|
||||
signing_url: `<a href=${signPdf} target=_blank>Sign here</a>`
|
||||
signing_url: signPdf
|
||||
};
|
||||
const subject =
|
||||
doc?.RequestSubject ||
|
||||
|
||||
@@ -25,12 +25,10 @@ const LinkUserModal = (props) => {
|
||||
handleClose={props.closePopup}
|
||||
>
|
||||
<SelectSigners
|
||||
details={props.handleAddUser}
|
||||
{...props}
|
||||
closePopup={props.closePopup}
|
||||
signersData={props?.signersData}
|
||||
isContact={isContact}
|
||||
setIsContact={setIsContact}
|
||||
handleUnlinkSigner={props.handleUnlinkSigner}
|
||||
isExistSigner={isExistSigner}
|
||||
/>
|
||||
{isContact && (
|
||||
|
||||
@@ -8,13 +8,18 @@ const ModalUi = ({
|
||||
handleClose,
|
||||
showHeader = true,
|
||||
showClose = true,
|
||||
reduceWidth
|
||||
reduceWidth,
|
||||
position
|
||||
}) => {
|
||||
const width = reduceWidth;
|
||||
const isBottom = position === "bottom" ? "items-end pb-2" : "";
|
||||
return (
|
||||
<>
|
||||
{isOpen && (
|
||||
<dialog id="selectSignerModal" className="op-modal op-modal-open">
|
||||
<dialog
|
||||
id="selectSignerModal"
|
||||
className={`${isBottom} op-modal op-modal-open`}
|
||||
>
|
||||
<div
|
||||
className={`${
|
||||
width || "md:min-w-[500px]"
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
# Use an official Node runtime as the base image
|
||||
FROM node:20
|
||||
|
||||
# Set the working directory inside the container
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
# Copy package.json and package-lock.json first to leverage Docker cache
|
||||
COPY ./package*.json ./
|
||||
|
||||
# Install application dependencies
|
||||
RUN npm install
|
||||
|
||||
# If you have native dependencies, you'll need extra tools. Uncomment the following line if needed.
|
||||
# RUN apk add --no-cache make gcc g++ python3
|
||||
|
||||
# Copy the current directory contents into the container
|
||||
COPY ./ .
|
||||
|
||||
# Make port 8080 available to the world outside this container
|
||||
EXPOSE 8080
|
||||
|
||||
# Define environment variables if needed
|
||||
# ENV NODE_ENV production
|
||||
# ENV DATABASE_URL mongodb://db:27017
|
||||
|
||||
# Run the application
|
||||
ENTRYPOINT npm start
|
||||
@@ -252,7 +252,7 @@ export const mailTemplate = param => {
|
||||
"</td></tr><tr><td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Note</td><td></td><td style='color:#626363;font-weight:bold'>" +
|
||||
param.note +
|
||||
"</td></tr><tr><td></td><td></td></tr></table></div> <div style='margin-left:70px'><a target=_blank href=" +
|
||||
param.sigingUrl +
|
||||
param.signingUrl +
|
||||
"><button style='padding:12px;background-color:#d46b0f;color:white;border:0px;font-weight:bold;margin-top:30px'>Sign here</button></a></div><div style='display:flex;justify-content:center;margin-top:10px'></div></div></div><div><p> This is an automated email from " +
|
||||
AppName +
|
||||
'. For any queries regarding this email, please contact the sender ' +
|
||||
|
||||
@@ -4,9 +4,9 @@ export default async function Newsletter(request) {
|
||||
const email = request.params?.email?.toLowerCase()?.replace(/\s/g, '');
|
||||
const domain = request.params.domain;
|
||||
try {
|
||||
const envAppId = process.env.REACT_APP_APPID || 'opensign';
|
||||
const envAppId = 'opensign';
|
||||
const headers = { 'Content-Type': 'application/json', 'X-Parse-Application-Id': envAppId };
|
||||
const envProdServer = process.env.REACT_APP_SERVERURL || 'https://app.opensignlabs.com/api/app';
|
||||
const envProdServer = 'https://app.opensignlabs.com/api/app';
|
||||
const newsletter = await axios.post(
|
||||
`${envProdServer}/classes/Newsletter`,
|
||||
{ Name: name, Email: email, Domain: domain },
|
||||
|
||||
@@ -14,7 +14,7 @@ async function deductcount(docsCount, extUserId) {
|
||||
}
|
||||
async function sendMail(document, publicUrl) {
|
||||
//sessionToken
|
||||
const baseUrl = new URL(publicUrl); //process.env.PUBLIC_URL
|
||||
const baseUrl = new URL(publicUrl);
|
||||
|
||||
// console.log("pdfDetails", pdfDetails);
|
||||
const timeToCompleteDays = document?.TimeToCompleteDays || 15;
|
||||
@@ -71,7 +71,7 @@ async function sendMail(document, publicUrl) {
|
||||
receiver_phone: existSigner?.Phone || '',
|
||||
expiry_date: localExpireDate,
|
||||
company_name: orgName,
|
||||
signing_url: `<a href=${signPdf} target=_blank>Sign here</a>`,
|
||||
signing_url: signPdf,
|
||||
};
|
||||
replaceVar = replaceMailVaribles(mailSubject, htmlReqBody, variables);
|
||||
}
|
||||
@@ -82,7 +82,7 @@ async function sendMail(document, publicUrl) {
|
||||
title: document.Name,
|
||||
organization: orgName,
|
||||
localExpireDate: localExpireDate,
|
||||
sigingUrl: signPdf,
|
||||
signingUrl: signPdf,
|
||||
};
|
||||
let params = {
|
||||
extUserId: document.ExtUserPtr.objectId,
|
||||
|
||||
@@ -116,7 +116,7 @@ async function sendNotifyMail(doc, signUser, mailProvider, publicUrl) {
|
||||
const creatorEmail = doc.ExtUserPtr.Email;
|
||||
const signerName = signUser.Name;
|
||||
const signerEmail = signUser.Email;
|
||||
const viewDocUrl = `${publicUrl}/recipientSignPdf/${doc.objectId}`; // ` ${process.env.PUBLIC_URL}/recipientSignPdf/${doc.objectId}`;
|
||||
const viewDocUrl = `${publicUrl}/recipientSignPdf/${doc.objectId}`;
|
||||
const subject = `Document "${pdfName}" has been signed by ${signerName}`;
|
||||
const body =
|
||||
"<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8'/></head><body><div style='background-color:#f5f5f5;padding:20px'><div style='background-color:white'>" +
|
||||
|
||||
@@ -161,7 +161,7 @@ app.use(express.urlencoded({ limit: '50mb', extended: true }));
|
||||
app.use(function (req, res, next) {
|
||||
req.headers['x-real-ip'] = getUserIP(req);
|
||||
const publicUrl = 'https://' + req?.get('host');
|
||||
req.headers['public_url'] = publicUrl; // process.env.PUBLIC_URL
|
||||
req.headers['public_url'] = publicUrl;
|
||||
next();
|
||||
});
|
||||
function getUserIP(request) {
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "open_sign_server",
|
||||
"version": "1.4.0",
|
||||
"version": "2.21.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "open_sign_server",
|
||||
"version": "1.4.0",
|
||||
"version": "2.21.1",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.812.0",
|
||||
|
||||
Reference in New Issue
Block a user