mirror of
https://github.com/OpenSignLabs/OpenSign.git
synced 2026-09-03 16:28:56 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f2ada47302 | ||
|
|
b63d609a70 | ||
|
|
80ccd5d022 | ||
|
|
e948fdc60b | ||
|
|
4bdfe9d725 | ||
|
|
be0c4a2cbf | ||
|
|
b417754a0c | ||
|
|
6271fedcbf | ||
|
|
d3cb8d575c | ||
|
|
1e228c86c3 | ||
|
|
0c295961c3 | ||
|
|
5fbd1fc6f2 | ||
|
|
3f4007d564 | ||
|
|
b49dc6bc6a | ||
|
|
b325095d60 | ||
|
|
56a119a901 | ||
|
|
b9a309fa36 | ||
|
|
197c00dd79 | ||
|
|
ef81a0409b | ||
|
|
dd499ba0d0 | ||
|
|
a56b87a702 | ||
|
|
acb0f3083c | ||
|
|
606eb6a93b | ||
|
|
fe8ab56d47 | ||
|
|
4c5a3509cd | ||
|
|
f751a97f24 | ||
|
|
e4ecb5dbfd | ||
|
|
2297d6fc22 | ||
|
|
8922b61ecd | ||
|
|
db4b2d58d6 | ||
|
|
7017930709 | ||
|
|
810037e3c8 | ||
|
|
f8987a57d9 | ||
|
|
f3d20391fa | ||
|
|
6594ba03a3 | ||
|
|
9c2a388f66 | ||
|
|
8c49e1ac18 | ||
|
|
e1bf2ed94b | ||
|
|
797fcbd624 | ||
|
|
847657dc76 | ||
|
|
3a361cc21e | ||
|
|
1496c1b1da | ||
|
|
d87fa38976 | ||
|
|
df4630aa31 | ||
|
|
d8a0d9137e | ||
|
|
1b2528ac1d | ||
|
|
9b357acbdd | ||
|
|
ff4fa3a15f | ||
|
|
f104b0ca86 | ||
|
|
31affb53f1 | ||
|
|
338b76029f | ||
|
|
646e202b91 | ||
|
|
8a34917343 | ||
|
|
9b8c9403d1 | ||
|
|
40198e1bd8 | ||
|
|
0e6220e16c | ||
|
|
fc3a5ad1bf | ||
|
|
4919d6c3d3 | ||
|
|
91e89bb0e2 | ||
|
|
f942b2571a | ||
|
|
ae2c15660d | ||
|
|
721d52d539 | ||
|
|
b03e27cf70 | ||
|
|
4eae44c9b4 | ||
|
|
79234441db |
Generated
+17726
File diff suppressed because it is too large
Load Diff
+67
-43
@@ -1,66 +1,88 @@
|
||||
{
|
||||
"name": "open_sign",
|
||||
"version": "2.35.0",
|
||||
"version": "2.37.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.1",
|
||||
"@formkit/auto-animate": "^0.9.0",
|
||||
"@imgly/background-removal": "^1.7.0",
|
||||
"@lottiefiles/dotlottie-react": "^0.17.13",
|
||||
"@lottiefiles/dotlottie-react": "^0.19.0",
|
||||
"@mui/icons-material": "^5.15.10",
|
||||
"@mui/material": "^5.15.10",
|
||||
"@pdf-lib/fontkit": "^1.1.1",
|
||||
"@radix-ui/themes": "^3.3.0",
|
||||
"@reactour/tour": "^3.8.0",
|
||||
"@reduxjs/toolkit": "^2.11.2",
|
||||
"axios": "^1.13.4",
|
||||
"body-scroll-lock": "^4.0.0-beta.0",
|
||||
"@usewaypoint/block-avatar": "^0.0.3",
|
||||
"@usewaypoint/block-button": "^0.0.3",
|
||||
"@usewaypoint/block-columns-container": "^0.0.3",
|
||||
"@usewaypoint/block-container": "^0.0.2",
|
||||
"@usewaypoint/block-divider": "^0.0.4",
|
||||
"@usewaypoint/block-heading": "^0.0.3",
|
||||
"@usewaypoint/block-html": "^0.0.3",
|
||||
"@usewaypoint/block-image": "^0.0.5",
|
||||
"@usewaypoint/block-spacer": "^0.0.3",
|
||||
"@usewaypoint/block-text": "^0.0.7",
|
||||
"@usewaypoint/document-core": "^0.0.6",
|
||||
"@usewaypoint/email-builder": "^0.0.9",
|
||||
"axios": "^1.16.0",
|
||||
"date-fns-tz": "^3.2.0",
|
||||
"dompurify": "^3.4.1",
|
||||
"file-saver": "^2.0.5",
|
||||
"i18next": "^25.8.0",
|
||||
"i18next-browser-languagedetector": "^8.2.0",
|
||||
"i18next-http-backend": "^3.0.2",
|
||||
"highlight.js": "^11.11.1",
|
||||
"i18next": "^26.0.8",
|
||||
"i18next-browser-languagedetector": "^8.2.1",
|
||||
"i18next-http-backend": "^3.0.6",
|
||||
"jszip": "^3.10.1",
|
||||
"juice": "^11.1.1",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"moment": "^2.30.1",
|
||||
"parse": "^8.0.3",
|
||||
"parse": "^8.1.0",
|
||||
"pdf-lib": "^1.17.1",
|
||||
"pkijs": "^3.3.3",
|
||||
"pkijs": "^3.4.0",
|
||||
"prettier": "^3.8.3",
|
||||
"print-js": "^1.6.0",
|
||||
"prismjs": "^1.30.0",
|
||||
"process": "^0.11.10",
|
||||
"quill-html-edit-button": "^3.0.0",
|
||||
"radix-ui": "^1.4.3",
|
||||
"react": "^19.2.4",
|
||||
"react": "^19.2.5",
|
||||
"react-bootstrap": "^2.10.10",
|
||||
"react-colorful": "^5.6.1",
|
||||
"react-confetti": "^6.4.0",
|
||||
"react-datepicker": "^9.1.0",
|
||||
"react-dnd": "^16.0.1",
|
||||
"react-dnd-html5-backend": "^16.0.1",
|
||||
"react-dnd-multi-backend": "^9.0.0",
|
||||
"react-dom": "^19.2.4",
|
||||
"react-dom": "^19.2.5",
|
||||
"react-gtm-module": "^2.0.11",
|
||||
"react-i18next": "^16.5.4",
|
||||
"react-konva": "^19.2.1",
|
||||
"react-pdf": "^10.2.0",
|
||||
"react-i18next": "^17.0.6",
|
||||
"react-konva": "^19.2.3",
|
||||
"react-pdf": "^10.4.1",
|
||||
"react-quill-new": "^3.8.3",
|
||||
"react-redux": "^9.2.0",
|
||||
"react-rnd": "^10.5.2",
|
||||
"react-router": "^7.13.0",
|
||||
"react-rnd": "^10.5.3",
|
||||
"react-router": "^7.14.2",
|
||||
"react-scrollbars-custom": "^4.1.1",
|
||||
"react-select": "^5.10.2",
|
||||
"react-signature-canvas": "^1.1.0-alpha.2",
|
||||
"react-timezone-select": "^3.2.8",
|
||||
"react-to-print": "^3.2.0",
|
||||
"react-tooltip": "^5.30.0",
|
||||
"react-timezone-select": "^3.3.3",
|
||||
"react-to-print": "^3.3.0",
|
||||
"react-tooltip": "^5.30.1",
|
||||
"redux": "^5.0.1",
|
||||
"regex-parser": "^2.3.1",
|
||||
"serve": "^14.2.5",
|
||||
"web-vitals": "^5.1.0",
|
||||
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz"
|
||||
"web-vitals": "^5.2.0",
|
||||
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz",
|
||||
"zod": "^3.22.4",
|
||||
"zustand": "^4.5.1"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "npm run version && NODE_OPTIONS=\"--max-old-space-size=8192\" vite build",
|
||||
"start-dev": "vite",
|
||||
"dev": "vite",
|
||||
"preview": "vite preview",
|
||||
"start": "serve -s build",
|
||||
"start": "node server.cjs",
|
||||
"version": "curl -s https://api.github.com/repos/opensignlabs/opensign/releases/latest | grep '\"tag_name\":' | awk -F '\"' '{print $4}' > ./public/version.txt",
|
||||
"version-win": "powershell -Command \"Invoke-RestMethod -Uri 'https://api.github.com/repos/opensignlabs/opensign/releases/latest' | Select-Object -ExpandProperty tag_name | Out-File -FilePath ./public/version.txt\"",
|
||||
"build-win": "npm run version-win && vite build",
|
||||
@@ -94,39 +116,41 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.29.0",
|
||||
"@babel/preset-env": "^7.29.0",
|
||||
"@babel/preset-env": "^7.29.2",
|
||||
"@babel/preset-react": "^7.28.5",
|
||||
"@babel/runtime-corejs2": "^7.28.6",
|
||||
"@babel/runtime-corejs2": "^7.29.2",
|
||||
"@testing-library/jest-dom": "^6.9.1",
|
||||
"@testing-library/react": "^16.3.2",
|
||||
"@testing-library/user-event": "^14.6.1",
|
||||
"@types/react": "^19.2.10",
|
||||
"@vitejs/plugin-react": "^5.1.3",
|
||||
"@vitest/ui": "^4.0.18",
|
||||
"autoprefixer": "^10.4.24",
|
||||
"babel-loader": "^10.0.0",
|
||||
"@types/react": "^19.2.14",
|
||||
"@vitejs/plugin-react": "^6.0.1",
|
||||
"@vitest/ui": "^4.1.5",
|
||||
"autoprefixer": "^10.5.0",
|
||||
"babel-loader": "^9.2.1",
|
||||
"commitizen": "^4.3.1",
|
||||
"concurrently": "^9.2.1",
|
||||
"css-loader": "^7.1.3",
|
||||
"concurrently": "^10.0.3",
|
||||
"css-loader": "^7.1.4",
|
||||
"daisyui": "^4.12.24",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint": "^9.39.4",
|
||||
"eslint-plugin-prettier": "^5.5.5",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"jsdom": "^28.0.0",
|
||||
"lint-staged": "^16.2.7",
|
||||
"postcss": "^8.5.6",
|
||||
"prettier": "^3.8.1",
|
||||
"jsdom": "^29.0.2",
|
||||
"lint-staged": "^16.4.0",
|
||||
"postcss": "^8.5.12",
|
||||
"pretty-quick": "^4.2.2",
|
||||
"rollup-plugin-node-polyfills": "^0.2.1",
|
||||
"tailwindcss": "^3.4.19",
|
||||
"tmp": "^0.2.5",
|
||||
"vite": "^7.3.1",
|
||||
"vite-plugin-svgr": "^4.5.0",
|
||||
"vite-tsconfig-paths": "^6.0.5",
|
||||
"vitest": "^4.0.18"
|
||||
"tmp": "^0.2.7",
|
||||
"vite": ">=8.0.16",
|
||||
"vite-plugin-svgr": "^5.2.0",
|
||||
"vite-tsconfig-paths": "^6.1.1",
|
||||
"vitest": "^4.1.5"
|
||||
},
|
||||
"overrides": {
|
||||
"tmp": "$tmp"
|
||||
"tmp": "$tmp",
|
||||
"react": "$react",
|
||||
"react-dom": "$react-dom",
|
||||
"form-data": ">=4.0.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": "18 || 20 || 22"
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
"password-length": "Das Passwort sollte 8 Zeichen lang sein",
|
||||
"password-case": "Das Passwort sollte einen Großbuchstaben, einen Kleinbuchstaben und eine Zahl enthalten",
|
||||
"password-special-char": "Das Passwort sollte ein Sonderzeichen enthalten",
|
||||
"agreee": "Ich stimme den",
|
||||
"agree": "Ich stimme den",
|
||||
"term": "Nutzungsbedingungen zu",
|
||||
"subscribe-to-opensign": "Abonnieren Sie den OpenSign-Newsletter",
|
||||
"register": "Registrieren",
|
||||
@@ -237,11 +237,12 @@
|
||||
"p3": "Wenn Sie diese Option auswählen, werden die Signaturlinks gleichzeitig an alle Unterzeichner gesendet. Jeder Unterzeichner kann das Dokument nach Belieben unterzeichnen, unabhängig davon, ob andere Unterzeichner ihre Unterschriften abgeschlossen haben. Diese Methode ist schneller, erzwingt jedoch keine Reihenfolge unter den Teilnehmern.",
|
||||
"p4": "Wählen Sie die Option, die am besten zu den Anforderungen Ihrer Dokumentenverarbeitung passt."
|
||||
},
|
||||
"strict-order": "Strikte Reihenfolge erzwingen",
|
||||
"strict-order-help": "Wenn aktiviert, kann kein Unterzeichner unterschreiben oder genehmigen, bis alle vorherigen Unterzeichner/Genehmiger ihre Aktion abgeschlossen haben.",
|
||||
"no": "Nein",
|
||||
"auto-reminder": "Automatische Erinnerung",
|
||||
"remind-once": "Einmal erinnern alle (Tage)",
|
||||
"next": "Weiter",
|
||||
"select-folder": "Ordner auswählen",
|
||||
"OpenSign-drive": "{{appName}} Drive",
|
||||
"drive-document-status": {
|
||||
"Completed": "Abgeschlossen",
|
||||
@@ -261,6 +262,7 @@
|
||||
"signers": "Unterzeichner",
|
||||
"signers-help": "Beginnen Sie mit der Eingabe eines Kontaktnamens, um vorgeschlagene Unterzeichner aus Ihren gespeicherten Kontakten zu sehen, oder fügen Sie neue hinzu. Ordnen Sie die Reihenfolge der Unterzeichner, indem Sie sie in der gewünschten Reihenfolge hinzufügen. Verwenden Sie die '+'-Schaltfläche, um Unterzeichner hinzuzufügen, und 'x', um sie zu entfernen. Jeder Unterzeichner erhält eine E-Mail-Aufforderung, das Dokument in der aufgelisteten Reihenfolge zu unterzeichnen.",
|
||||
"bcc-help": "Beginnen Sie mit der Eingabe des Namens eines Kontakts, um Vorschläge aus Ihren gespeicherten Kontakten anzuzeigen oder neue hinzuzufügen. Verwenden Sie die Schaltfläche '+', um einen Benutzer hinzuzufügen, und das 'x', um ihn zu entfernen. Die E-Mail des ausgewählten Benutzers wird als Bcc (Blindkopie) hinzugefügt. Jeder Benutzer erhält eine E-Mail-Benachrichtigung, sobald das Dokument fertig ist.",
|
||||
"cc-help": "Beginnen Sie mit der Eingabe des Namens eines Kontakts, um Vorschläge aus Ihren gespeicherten Kontakten zu sehen, oder fügen Sie neue hinzu. Verwenden Sie die Schaltfläche „+“, um einen Benutzer hinzuzufügen, und „x“, um ihn zu entfernen. Die E-Mail des ausgewählten Benutzers wird als CC (Kopie) hinzugefügt. Jeder Benutzer erhält eine E-Mail-Benachrichtigung, sobald das Dokument abgeschlossen ist.",
|
||||
"add-signer": "Unterzeichner hinzufügen",
|
||||
"contact-not-found": "Kontakt nicht gefunden",
|
||||
"add-yourself": "Sich selbst hinzufügen",
|
||||
@@ -282,13 +284,15 @@
|
||||
"yes": "Ja",
|
||||
"copied": "Kopiert",
|
||||
"something-went-wrong-mssg": "Etwas ist schiefgelaufen, Das Aktualisieren dieser Seite kann das Problem lösen.",
|
||||
"attachments-docx-upload-failed": "Derzeit liegt ein Problem vor. Bitte versuchen Sie es erneut.",
|
||||
"attachments-docx-upload-limit": "Das Upload-Limit für große DOCX-Dateien ist erreicht. Bitte warten Sie 4 Minuten und versuchen Sie es erneut.",
|
||||
"token-generated": "Token erfolgreich generiert.",
|
||||
"webhook": "Webhook",
|
||||
"update-webhook": "Webhook aktualisieren",
|
||||
"add-webhook": "Webhook hinzufügen",
|
||||
"webhook-added": "Webhook erfolgreich hinzugefügt.",
|
||||
"team-disabled": "Team deaktiviert",
|
||||
"Team enabled": "Team aktiviert",
|
||||
"team-enabled": "Team aktiviert",
|
||||
"are-you-sure": "Sind Sie sicher, dass Sie",
|
||||
"disable": "deaktivieren",
|
||||
"enable": "aktivieren",
|
||||
@@ -296,7 +300,7 @@
|
||||
"edit-team": "Team bearbeiten",
|
||||
"name-of-team": "Name des Teams",
|
||||
"prev": "Zurück",
|
||||
"no-data-avaliable": "Keine Daten verfügbar",
|
||||
"no-data-available": "Keine Daten verfügbar",
|
||||
"add-team": "Team hinzufügen",
|
||||
"page-not-found": "Seite nicht gefunden",
|
||||
"users-from-teams": "Benutzer aus Teams",
|
||||
@@ -307,12 +311,37 @@
|
||||
"delete-user": "Benutzer löschen",
|
||||
"delete": "Löschen",
|
||||
"add-user": "Benutzer hinzufügen",
|
||||
"password-generateed": "Das Passwort wird nur einmal generiert; stellen Sie sicher, dass Sie es kopieren.",
|
||||
"password-generated": "Das Passwort wird nur einmal generiert; stellen Sie sicher, dass Sie es kopieren.",
|
||||
"Team status": "Teamstatus",
|
||||
"user-deactivated": "Benutzer deaktiviert.",
|
||||
"user-activated": "Benutzer aktiviert.",
|
||||
"public": "Öffentlich",
|
||||
"download": "Herunterladen",
|
||||
"sign-offline": "Offline unterzeichnen",
|
||||
"sign-offline-instructions": "Laden Sie das Dokument herunter, unterzeichnen Sie es mit einem externen Tool und laden Sie dann die unterschriebene Kopie hier hoch. Der Dokumenteigentümer wird Ihre Einreichung prüfen und entweder genehmigen oder ablehnen.",
|
||||
"sign-offline-step-1": "Das unsignierte PDF herunterladen.",
|
||||
"sign-offline-step-2": "Das PDF mit Ihrem bevorzugten Tool unterzeichnen.",
|
||||
"sign-offline-step-3": "Das unterschriebene PDF zur Genehmigung hochladen.",
|
||||
"sign-offline-upload-hint": "Das unterschriebene PDF zur Einreichung zur Genehmigung auswählen.",
|
||||
"sign-offline-submitted-title": "Zur Genehmigung eingereicht",
|
||||
"sign-offline-submitted-body": "Der Dokumenteigentümer wurde per E-Mail benachrichtigt, Ihre offline unterschriebene Kopie zu prüfen.",
|
||||
"offline-already-submitted-title": "Dokument offline eingereicht",
|
||||
"offline-already-submitted-body": "Sie haben dieses Dokument bereits zur Offline-Signaturprüfung eingereicht. Sobald es genehmigt oder abgelehnt wurde, werden Sie benachrichtigt.",
|
||||
"offline-action-approve-title": "Offline-Signatur genehmigen",
|
||||
"offline-action-approve-confirm": "Genehmigen",
|
||||
"offline-action-reject-title": "Offline-Signatur ablehnen",
|
||||
"offline-action-reject-hint": "Optional können Sie einen Grund angeben. Der Unterzeichner wird per E-Mail benachrichtigt.",
|
||||
"offline-action-reject-reason-placeholder": "Grund (optional)",
|
||||
"offline-action-reject-confirm": "Ablehnung bestätigen",
|
||||
"offline-action-review-title": "Offline-Signatur prüfen",
|
||||
"offline-action-review-hint": "Sehen Sie sich das vom Unterzeichner hochgeladene signierte PDF an, und genehmigen oder lehnen Sie es dann ab. Keine Aktion wird durchgeführt, bis Sie bestätigen.",
|
||||
"offline-action-submitted-at": "Eingereicht",
|
||||
"offline-action-no-preview": "Für diese Einreichung ist keine Vorschau verfügbar.",
|
||||
"offline-action-open-in-new-tab": "PDF in neuem Tab öffnen",
|
||||
"offline-action-invalid-link": "Dieser Prüflink ist ungültig oder abgelaufen.",
|
||||
"offline-action-success": "Aktion abgeschlossen.",
|
||||
"reject": "Ablehnen",
|
||||
"document": "Dokument",
|
||||
"view": "Ansehen",
|
||||
"make-template-public": "Vorlage öffentlich machen",
|
||||
"make-template-private": "Vorlage privat machen",
|
||||
@@ -337,7 +366,7 @@
|
||||
"quick-send-alert-1": "Alle Rollen in diesem Dokument sind derzeit mit Kontakten verknüpft. Um Kopien dieser Vorlage schnell an mehrere Unterzeichner zu senden, stellen Sie sicher, dass mindestens eine Rolle keinem Kontakt zugeordnet ist.",
|
||||
"quick-send-alert-2": "Bitte stellen Sie sicher, dass für alle Empfänger mindestens ein Signatur-Widget hinzugefügt wurde.",
|
||||
"quick-send-alert-3": "Bitte fügen Sie diesem template mindestens eine Rolle hinzu.",
|
||||
"quick-send-alert-4": "Limit für {{flow}} erreicht.",
|
||||
"quick-send-alert-4": "{{flow}} unterstützt bis zu {{count}} Unterzeichner pro Batch. Teilen Sie Ihre Liste auf und versuchen Sie es erneut.",
|
||||
"copy-link": "Link kopieren",
|
||||
"copy": "Kopieren",
|
||||
"revoke-document": "Dokument widerrufen",
|
||||
@@ -394,7 +423,10 @@
|
||||
"image": "Bild",
|
||||
"email": "E-Mail",
|
||||
"number": "Zahl",
|
||||
"draw": "Zeichnen"
|
||||
"draw": "Zeichnen",
|
||||
"attachments": "Anhänge",
|
||||
"attachments-add": "Klicken, um Anhänge hochzuladen",
|
||||
"attachments-count": "{{count}} Anhang/Anhänge"
|
||||
},
|
||||
"fields": "Felder",
|
||||
"recipients": "Empfänger",
|
||||
@@ -406,6 +438,14 @@
|
||||
"certificate": "Zertifikat",
|
||||
"decline": "Ablehnen",
|
||||
"finish": "Fertigstellen",
|
||||
"approve": "Genehmigen",
|
||||
"approve-mssg": " Sind Sie sicher, dass Sie das Dokument genehmigen möchten?",
|
||||
"signer-role-label": "Empfängerrolle",
|
||||
"signer-role-signer": "Unterzeichner",
|
||||
"signer-role-viewer": "Betrachter",
|
||||
"signer-role-approver": "Genehmiger",
|
||||
"signer-role-hint": "Unterzeichner muss unterschreiben. Genehmiger genehmigt ohne Unterschrift. Betrachter kann nur ansehen.",
|
||||
"signer-role-upgrade-msg": "Die Rollen Betrachter und Genehmiger sind nur in den kostenpflichtigen Tarifen verfügbar. Aktualisieren Sie Ihren Tarif, um diese Rollen zuzuweisen.",
|
||||
"done": "Fertig",
|
||||
"mail": "E-Mail",
|
||||
"sign-now": "Jetzt unterzeichnen",
|
||||
@@ -438,13 +478,15 @@
|
||||
"reset-password-alert-1": "Ein Link zum Zurücksetzen des Passworts wurde an Ihre E-Mail-Adresse gesendet.",
|
||||
"reset-password-alert-2": "Bitte richten Sie den E-Mail-Adapter ein.",
|
||||
"reset-password-alert-3": "Setzen Sie Ihr Passwort zurück",
|
||||
"faild-animation": "Animation konnte nicht geladen werden",
|
||||
"failed-animation": "Animation konnte nicht geladen werden",
|
||||
"apply": "Anwenden",
|
||||
"select-columns": "Spalten auswählen",
|
||||
"copy-type": {
|
||||
"All pages": "Alle Seiten",
|
||||
"All pages but last": "Alle Seiten außer der letzten",
|
||||
"All pages but first": "Alle Seiten außer der ersten",
|
||||
"Next to current widget": "Neben dem aktuellen Widget"
|
||||
"Next to current widget": "Neben dem aktuellen Widget",
|
||||
"Page range": "Seitenbereich"
|
||||
},
|
||||
"options": "Optionen",
|
||||
"minimum-check": "Minimale Anzahl",
|
||||
@@ -461,6 +503,19 @@
|
||||
"formula-error-braces": "Jede öffnende {{ muss eine passende }} haben.",
|
||||
"formula-error-operators": "Verwenden Sie bei Operatoren oder Klammern mindestens zwei Werte.",
|
||||
"formula-error-leading-operator": "Formeln dürfen nicht mit Operatoren beginnen.",
|
||||
"formula-error-trailing-operator": "Formeln dürfen nicht mit einem Operator enden.",
|
||||
"formula-error-adjacent-placeholders": "Zwei Platzhalter dürfen nicht ohne einen Operator dazwischen direkt nebeneinander stehen.",
|
||||
"formula-error-continuous-operators": "Zwei Operatoren dürfen nicht direkt hintereinander verwendet werden.",
|
||||
"formula-error-empty-parentheses": "Klammern dürfen nicht leer sein.",
|
||||
"formula-error-invalid-parentheses-content": "Klammern dürfen nicht mit einem Operator beginnen oder enden.",
|
||||
"formula-error-before-open-parenthesis": "Fehlender Operator vor '('. Fügen Sie vor der öffnenden Klammer einen Operator hinzu.",
|
||||
"formula-error-after-close-parenthesis": "Fehlender Operator nach ')'. Fügen Sie nach der schließenden Klammer einen Operator wie '+' oder '*' hinzu.",
|
||||
"formula-error-between-parentheses": "Fehlender Operator zwischen ')' und '('. Fügen Sie zwischen den beiden Gruppen einen Operator wie '*' hinzu.",
|
||||
"formula-error-division-by-zero": "Division durch Null ist nicht zulässig. Ersetzen Sie '0' durch eine gültige, von Null verschiedene Zahl oder Variable.",
|
||||
"formula-error-invalid-number-format": "Ungültiges Zahlenformat gefunden. Zahlen müssen in einem gültigen Format wie '1' oder '3.14' vorliegen, nicht '.5', '1.' oder '1.2.3'.",
|
||||
"formula-error-unclosed-parenthesis": "Nicht geschlossene '(' gefunden. Jede öffnende Klammer '(' muss eine passende schließende Klammer ')' haben.",
|
||||
"formula-error-unexpected-closing-parenthesis": "Unerwartete schließende Klammer ')' gefunden, ohne passende öffnende Klammer '('.",
|
||||
"formula-error-empty-placeholder": "Leerer Platzhalter '{{}}' ist nicht zulässig. Geben Sie einen Variablennamen innerhalb der Klammern an.",
|
||||
"decimal-places": "Dezimalstellen",
|
||||
"number-of-decimal-places": "Anzahl der Dezimalstellen",
|
||||
"decimal-places-helper": "Auf 0 setzen für ganze Zahlen",
|
||||
@@ -529,6 +584,7 @@
|
||||
"sign-url": "Signatur-URL",
|
||||
"mails-sent": "E-Mails gesendet",
|
||||
"mail-not-delivered": "E-Mail nicht zugestellt",
|
||||
"email-not-verified-head": "E-Mail nicht verifiziert",
|
||||
"document-alert": "Dokument-Warnung",
|
||||
"owner-subscription-expired": "Das Abonnement des Besitzers ist abgelaufen.",
|
||||
"subscription-expired": "Abonnement abgelaufen",
|
||||
@@ -604,6 +660,9 @@
|
||||
"document-signed-alert-7": "Dieses Dokument ist nicht mehr verfügbar.",
|
||||
"document-signed-alert-8": "Das Dokument wurde bereits zum Unterzeichnen versendet.",
|
||||
"yet-to-sign": "Noch zu unterzeichnen",
|
||||
"viewers": "Betrachter",
|
||||
"viewer-no-widgets": "Betrachter erhalten eine schreibgeschützte Kopie des Dokuments und können keine Widgets zugewiesen bekommen.",
|
||||
"viewer-confirm-widget-removal": "Wenn Sie diesen Empfänger auf Betrachter umstellen, werden alle ihm bereits zugewiesenen Widgets entfernt. Fortfahren?",
|
||||
"validation-alert-1": "Die Eingabe erfüllt nicht die Kriterien des regulären Ausdrucks.",
|
||||
"account-already-exists": "Konto existiert bereits!",
|
||||
"choose-password": "Passwort wählen",
|
||||
@@ -626,7 +685,7 @@
|
||||
"date": "Datum",
|
||||
"report-not-found": "Bericht nicht gefunden",
|
||||
"please-wait": "Bitte warten",
|
||||
"select-foler": "Ordner auswählen",
|
||||
"select-folder": "Ordner auswählen",
|
||||
"generated-on": "Erstellt am",
|
||||
"text-field": "Textfeld",
|
||||
"font-size": "Schriftgröße",
|
||||
@@ -685,13 +744,13 @@
|
||||
"public-template": "Dieses Video zeigt Ihnen, wie Sie Ihr personalisiertes öffentliches Profil einrichten, z. B. 'https://opensign.me/Ihr-Benutzername'. Sie lernen auch, wie Sie Ihren Slogan anpassen und Ihre Vorlagen für öffentliche Signaturen verfügbar machen.",
|
||||
"allowModify-widgets": "Sie können jedes dieser Felder per Drag & Drop in das Dokument ziehen, zusätzlich zu den Feldern, die der Dokumentersteller bereits für Sie festgelegt hat."
|
||||
},
|
||||
"enter-email-plaholder": "E-Mail-Adresse hinzufügen und Enter drücken",
|
||||
"enter-email-placeholder": "E-Mail-Adresse hinzufügen und Enter drücken",
|
||||
"success-email-alert": "E-Mail erfolgreich gesendet!",
|
||||
"expired-doc-title": "Abgelaufenes Dokument",
|
||||
"expired-on-mssg": "Dieses Dokument ist am {{expiredDate}} abgelaufen und nicht mehr verfügbar zum Unterzeichnen.",
|
||||
"signature-validate-alert": "Bitte bestätigen Sie, dass Sie mindestens {{minRequiredCount}} Kontrollkästchen ausgewählt haben.",
|
||||
"signature-validate-alert-2": "Stellen Sie sicher, dass dieses Feld korrekt ausgefüllt ist und alle Anforderungen erfüllt.",
|
||||
"remaing-users": "Verbleibende Benutzer",
|
||||
"remaining-users": "Verbleibende Benutzer",
|
||||
"Role": "Rolle",
|
||||
"additional-users": "Bitte kaufen Sie zusätzliche Benutzer hinzu.",
|
||||
"Quantity-of-users": "Anzahl der Benutzer",
|
||||
@@ -713,7 +772,7 @@
|
||||
"rotate-left": "Nach links drehen",
|
||||
"rotate-alert-mssg": "Alle Widgets auf dieser Seite gehen verloren. Sind Sie sicher, dass Sie fortfahren möchten?",
|
||||
"templateid": "Vorlagen-ID",
|
||||
"bulk-send-subcription-alert": "Bitte upgraden Sie auf den Professional- oder Team-Plan, um den Massenversand zu nutzen.",
|
||||
"bulk-send-subscription-alert": "Bitte upgraden Sie auf den Professional- oder Team-Plan, um den Massenversand zu nutzen.",
|
||||
"generate-test-token": "Test-Token generieren",
|
||||
"regenerate-test-token": "Test-Token neu generieren",
|
||||
"help-test-token": "Dieses Token kann verwendet werden, um die APIs unter https://sandbox.opensignlabs.com/api/v1 zu testen und unbegrenzt Dokumente zu signieren. Beachten Sie, dass die Sandbox-API Ihre Dokumente mit selbstsignierten Zertifikaten signiert, die möglicherweise von Adobe nicht als gültig erkannt werden. Nach Abschluss Ihrer Tests können Sie auf einen unserer kostenpflichtigen Pläne upgraden, um ein Produktionstoken zu generieren.",
|
||||
@@ -733,8 +792,9 @@
|
||||
"copied-code": "KOPIERT",
|
||||
"Installation": "Installation",
|
||||
"Usage": "Nutzung",
|
||||
"insufficient-credits": "Nicht genügend Signatur-Credits",
|
||||
"insufficient-credits-mssg": "Der Besitzer dieses Dokuments verfügt derzeit nicht über die notwendigen OpenSign-Credits, damit Sie unterzeichnen können. Bitte wenden Sie sich an den Besitzer, wenn Sie weitere Informationen benötigen.",
|
||||
"insufficient-credits": "Unzureichende Signaturguthaben. Bitte kontaktieren Sie {{ownerEmail}}",
|
||||
"insufficient-credits-title": "Nicht genügend Signatur-Credits",
|
||||
"insufficient-credits-mssg": "Der Besitzer dieses Dokuments verfügt derzeit nicht über die notwendigen {{appName}}-Credits, damit Sie unterzeichnen können. Bitte wenden Sie sich an den Besitzer, wenn Sie weitere Informationen benötigen.",
|
||||
"angular-npm-mssg-1": "Um OpenSign in Ihr Angular-Projekt zu integrieren, führen Sie einfach den folgenden Befehl aus:",
|
||||
"quota-mail-info-head": "Monatliches E-Mail-Limit",
|
||||
"quota-mail-info": "Um die Servicequalität aufrechtzuerhalten und Spam zu verhindern, erlaubt OpenSign bis zu 15 E-Mails pro Monat im kostenlosen Plan. Upgraden Sie jetzt für unbegrenzten E-Mail-Versand.",
|
||||
@@ -753,6 +813,13 @@
|
||||
"p3": "Wenn Sie diese Option deaktivieren, können Benutzer das Dokument direkt ohne zusätzliche Schritte unterzeichnen.",
|
||||
"p4": "Bitte wählen Sie die Option, die am besten zu Ihren Anforderungen für die Dokumentenunterzeichnung passt."
|
||||
},
|
||||
"allow-offline-sign": "Offline-Signatur zulassen",
|
||||
"allow-offline-sign-help": {
|
||||
"p1": "Legen Sie fest, ob Unterzeichner eine Kopie dieses Dokuments einreichen dürfen, die mit einem externen Werkzeug signiert wurde, damit der Eigentümer sie prüfen kann.",
|
||||
"p2": "Unterzeichner sehen die Option 'Offline unterzeichnen' und können eine extern signierte PDF zur Genehmigung hochladen.",
|
||||
"p3": "Unterzeichner können nur in der App signieren. Die Option 'Offline unterzeichnen' wird ausgeblendet.",
|
||||
"p4": "Diese Einstellung verwendet standardmäßig Ihre Kontoeinstellung und ist in kostenpflichtigen Tarifen verfügbar."
|
||||
},
|
||||
"advanced-options": "Erweiterte Optionen",
|
||||
"hide-advanced-options": "Erweiterte Optionen ausblenden",
|
||||
"download-files": "Dateien herunterladen",
|
||||
@@ -815,6 +882,7 @@
|
||||
"delete-folder-alert-1": "Löschen nicht möglich: Der Ordner muss leer sein.",
|
||||
"defaultSign-alert": "{{widgetsType}} Widgets sind nicht für die automatische Signatur verfügbar.",
|
||||
"embed-heading": "{{appName}} Vorlagen einbetten",
|
||||
"other-apps-by-opensign": "Andere Apps vom OpenSign-Team",
|
||||
"public-template-edit-title": "Öffentliche Vorlage bearbeiten",
|
||||
"public-template-edit-mssg": "Eine öffentliche Vorlage können Sie nicht direkt bearbeiten. Um Änderungen vorzunehmen, wird die Vorlage auf privat gesetzt und Sie müssen sie anschließend wieder öffentlich machen. Möchten Sie fortfahren?",
|
||||
"agree-p1": "Ich bestätige, dass ich die ",
|
||||
@@ -866,7 +934,6 @@
|
||||
"js-snippet-msg-1": "Die TemplateId erhalten Sie von der",
|
||||
"js-snippet-msg-2": "Vorlagen verwalten",
|
||||
"js-snippet-msg-3": "Seite.",
|
||||
"agrrement-alert": "Um fortzufahren, müssen Sie der Offenlegung über elektronische Aufzeichnungen und Unterschriften zustimmen.",
|
||||
"webhook-already-exists": "Die URL existiert bereits! Bitte versuchen Sie eine andere.",
|
||||
"webhook-must-be-secure": "Die Webhook-URL muss sicher sein und https:// verwenden.",
|
||||
"provide-test-token": "Bitte generieren Sie einen API-Test-Token.",
|
||||
@@ -886,6 +953,7 @@
|
||||
"p3": "Durch Auswahl dieser Option wird verhindert, dass Unterzeichner während des Unterzeichnungsprozesses Ergänzungen zum Dokument vornehmen."
|
||||
},
|
||||
"signature-setting": "Signatur-Einstellungen",
|
||||
"rotation": "Drehung",
|
||||
"activation-key": "Aktivierungsschlüssel",
|
||||
"paid-selfhost-help": "Bitte kontaktieren Sie {{appName}} für weitere Informationen unter ",
|
||||
"remaining-credits-selfhost-help": "Verwenden Sie Premium-Credits für API-Dokumentensignaturen, den Massenversand und die {{appName}} Integration auf Ihrer Website. Sie haben {{allowedcredits}} enthaltene Credits und {{addoncredits}} zusätzlich gekaufte Credits übrig.",
|
||||
@@ -914,6 +982,8 @@
|
||||
"visit-below-link": "Besuchen Sie den untenstehenden Link, um mehr zu erfahren -",
|
||||
"storage-help": "Durch die Aktivierung von BYOC können Sie Ihren eigenen S3-Speicher verbinden, sodass Ihre Dateien vollständig unter Ihrer Kontrolle bleiben und keine externen Kopien gespeichert werden. Wenn Ihnen Datenautonomie wichtig ist, erwägen Sie ein Upgrade auf Teams, um diese Funktion freizuschalten.",
|
||||
"daily-quota-reached": "Sie haben Ihr tägliches Kontingent erreicht. Für Unterstützung kontaktieren Sie bitte quotas@opensignlabs.com.",
|
||||
"email-not-verified-send": "Bitte verifizieren Sie Ihre E-Mail-Adresse, bevor Sie E-Mails senden. Gehen Sie zu Ihren <1>Profileinstellungen</1>, um die E-Mail-Verifizierung abzuschließen.",
|
||||
"email-not-verified-send-plain": "Bitte verifizieren Sie Ihre E-Mail-Adresse, bevor Sie E-Mails senden. Gehen Sie zu Ihren Profileinstellungen, um die E-Mail-Verifizierung abzuschließen.",
|
||||
"enabled-signature-type": "Aktivierte Signaturtypen",
|
||||
"enabled-signature-type-help": "Die Einstellung 'Aktivierte Signaturtypen' bestimmt, welche Signaturoptionen in Ihrer Organisation verfügbar sind. Wenn Sie beispielsweise die Option 'Zeichnen' deaktivieren, wird sie den Mitgliedern Ihrer Organisation im Signatur-Widget nicht angezeigt, während die anderen drei Optionen weiterhin zugänglich bleiben.",
|
||||
"indexing-public-profile": "Erlaube die Indexierung des öffentlichen Profils durch Suchmaschinen",
|
||||
@@ -941,6 +1011,7 @@
|
||||
"please-provide-templateid": "Bitte geben Sie templateid an",
|
||||
"this-template-is-not-public": "Dieses template ist nicht öffentlich",
|
||||
"invalid-templateid": "Ungültige templateid",
|
||||
"kiosk-no-active-role": "Diese Vorlage hat keine für die Kiosk-Signatur verfügbaren Unterzeichner- oder Genehmigerrollen.",
|
||||
"contact-billing-at-opensign": "Um weitere Plätze hinzuzufügen, kontaktieren Sie bitte OpenSign™ unter <1>billing@opensignlabs.com</1> für Unterstützung.",
|
||||
"title-length-alert": "Der Titel darf höchstens 250 Zeichen lang sein.",
|
||||
"note-length-alert": "Die Notiz darf höchstens 200 Zeichen lang sein.",
|
||||
@@ -1030,7 +1101,7 @@
|
||||
},
|
||||
"signer-already-present": "Unterzeichner bereits vorhanden",
|
||||
"kiosk-sign": "Kiosk-Unterschrift",
|
||||
"dont-have-access-to-template": "Das template wurde gelöscht oder Sie haben keinen Zugriff. Bitte kontaktieren Sie den Absender.",
|
||||
"do-not-have-access-to-template": "Das template wurde gelöscht oder Sie haben keinen Zugriff. Bitte kontaktieren Sie den Absender.",
|
||||
"kiosk-info": "Kiosk Modus ermöglicht es Ihnen, persönliche Unterschriften schnell und effizient zu erfassen. Ideal für Messen, Veranstaltungen oder Laufkundschaft, bei denen alle Unterzeichner physisch anwesend sind. ",
|
||||
"learn-more": "Mehr erfahren",
|
||||
"finish-mssg": "Sind Sie sicher, dass Sie das Dokument abschließen möchten?",
|
||||
@@ -1086,8 +1157,6 @@
|
||||
"search-contacts": "Kontakte durchsuchen…",
|
||||
"edit-draft": "Entwurf bearbeiten",
|
||||
"add-role-alert": "Bitte fügen Sie mindestens eine Rolle hinzu",
|
||||
"invalid-email-found": "Ungültige E-Mail gefunden: {{email}}",
|
||||
"duplicate-email-found": "Doppelte E-Mail gefunden: {{email}}",
|
||||
"vertical": "Vertikal",
|
||||
"horizontal": "Horizontal",
|
||||
"billing": "Abrechnung",
|
||||
@@ -1101,7 +1170,7 @@
|
||||
"access-denied": "Zugriff verweigert",
|
||||
"upgrade": "Upgrade",
|
||||
"do-not-access-app": "Sie haben keinen Zugriff auf diese Anwendung.",
|
||||
"dont-have-access": "Sie haben keinen Zugriff.",
|
||||
"do-not-have-access": "Sie haben keinen Zugriff.",
|
||||
"valid-email-alert": "Bitte geben Sie eine gültige E-Mail-Adresse ein.",
|
||||
"otp-not-validate": "OTP ist ungültig.",
|
||||
"domain-not-allowed": "Diese Domain ist nicht erlaubt",
|
||||
@@ -1118,10 +1187,12 @@
|
||||
"branding-help": "Branding ermöglicht White-Labeling für Ihre App",
|
||||
"custom-sub-domain": "Benutzerdefinierte Subdomain",
|
||||
"app-name": "App Name",
|
||||
"provide-domain-name": "Geben Sie Ihren Domainnamen an",
|
||||
"provide-domain-name": "Es sind nur Subdomains erlaubt (z. B. app.deinedomain.com). Primär-/Root-Domains wie deinedomain.com werden nicht unterstützt.",
|
||||
"provide-app-name-help": "Geben Sie einen eindeutigen App-Namen an. Er wird als Ihr Markenname in der Anwendung sowie in allen E-Mails angezeigt, überall dort, wo das Branding angewendet wird.",
|
||||
"upload-app-logo": "Laden Sie ein Logo im .png-, .jpg- oder .svg-Format hoch. Empfohlene Größe: {{size}} Pixel (Seitenverhältnis 5:2) für beste Anzeigequalität.",
|
||||
"upload-app-favicon": "Laden Sie ein quadratisches Favicon-Bild (.ico, .png oder .svg) hoch. Empfohlene Größe: {{size}} Pixel für beste Browserkompatibilität.",
|
||||
"provide-app-name": "Geben Sie Ihren App-Namen an",
|
||||
"logo": "Logo",
|
||||
"upload-app-logo": "Laden Sie Ihr App-Logo hoch",
|
||||
"prefill-unfilled-widget": "Die folgenden Pflichtfelder dürfen nicht leer sein: {{emptyWidget}}. Bitte füllen Sie diese aus, um fortzufahren.",
|
||||
"Dashboard": "Armaturenbrett",
|
||||
"Analytics": "Analytik",
|
||||
@@ -1289,11 +1360,11 @@
|
||||
"signer-form-data-for": "Formulardaten für Unterzeichner: {{signerName}} <{{signerEmail}}>",
|
||||
"widget-name": "Widget-Name",
|
||||
"widget-responses": "Widget-Antworten",
|
||||
"formdata-download-note": "Hinweis: Signaturen, Stempel, Initialen und Bilder werden nicht exportiert.",
|
||||
"formdata-download-note": "Hinweis: Signaturen, Stempel, Initialen und Bilder werden nicht exportiert. Daten von Offline-Unterzeichnern sind nicht verfügbar.",
|
||||
"export-all-formdata-error": "Sie können die Formulardaten nicht exportieren, da die Vorlage doppelte Widget-Namen enthält. Bitte erstellen Sie eine neue Vorlage, um Formulardaten exportieren zu können.",
|
||||
"export-formdata-error": "Sie können die Formulardaten nicht exportieren, da das Dokument doppelte Widget-Namen enthält. Bitte erstellen Sie ein neues Dokument ohne doppelte Widget-Namen, um Formulardaten exportieren zu können.",
|
||||
"duplicate-widget-name-error": "Dieser Widget-Name ist bereits einem anderen Unterzeichner zugewiesen. Bitte wählen Sie einen anderen Namen.",
|
||||
"duplicate-template-widget-error": "Sie können diese Vorlage nicht duplizieren, da sie doppelte Widget-Namen enthält.",
|
||||
"duplicate-template-widget-error": "Die Vorlage kann nicht dupliziert werden, weil einige Widget-Namen in verschiedenen Rollen doppelt vorkommen. Benennen Sie die doppelten Widgets um und versuchen Sie es erneut.",
|
||||
"credit-alert": "Kreditwarnung",
|
||||
"document-form-data": "Dokumenten-Formulardaten",
|
||||
"view-formdata-paid-title-error": "Die Funktion 'Formulardaten anzeigen' ermöglicht es Ihnen, die von Unterzeichnern ausgefüllten Widget-Daten auszudrucken und herunterzuladen.",
|
||||
@@ -1345,8 +1416,8 @@
|
||||
"file-selected": "Datei ausgewählt",
|
||||
"bulk-send-csv-helper": "Verwenden Sie Header für Unterzeichnerrollen (Rollen-E-Mail/Name/Telefon) und Vorabfüllwerte (Vorabfüllung: Feldname). Erforderliche Vorabfüllfelder müssen ausgefüllt sein.",
|
||||
"bulk-send-csv-helper-note": "Hinweis: Für Vorabfüllfelder vom Typ Datum in der CSV muss der Wert in folgendem Format angegeben werden: {{value}} (Beispiel: {{example}}).",
|
||||
"bulk-send-csv-summary": "{{total}} Zeilen importiert. {{invalid}} ungültige Zeilen wurden übersprungen.",
|
||||
"bulk-send-csv-missing-headers": "Erforderliche CSV-Header fehlen. Rollen: {{roles}}. Vorabfüllung: {{prefill}}.",
|
||||
"bulk-send-csv-summary": "{{total}} Zeilen aus der CSV importiert.",
|
||||
"bulk-send-csv-missing-headers": "Erforderliche CSV-Header fehlen. Rollen: {{roles}}.",
|
||||
"bulk-send-no-records": "Bitte fügen Sie vor dem Senden mindestens einen Empfänger hinzu.",
|
||||
"csv-only-support": "Es werden nur CSV-Dateien unterstützt.",
|
||||
"csv-no-records": "Keine Datensätze in der CSV gefunden.",
|
||||
@@ -1360,5 +1431,183 @@
|
||||
"enter-daily-email-quota": "Tägliches E-Mail-Kontingent eingeben",
|
||||
"enter-custom-regular-expression": "Benutzerdefinierten regulären Ausdruck eingeben",
|
||||
"enter-value": "{{value}} eingeben",
|
||||
"are-you-sure-want-to-send-document": "Sind Sie sicher, dass Sie das Dokument senden möchten?"
|
||||
}
|
||||
"are-you-sure-want-to-send-document": "Sind Sie sicher, dass Sie das Dokument senden möchten?",
|
||||
"favicon": "Favicon",
|
||||
"delete-two-factor-authentication": "Sind Sie sicher, dass Sie die Zwei-Faktor-Authentifizierung löschen möchten?",
|
||||
"paste-html-here": "HTML hier einfügen",
|
||||
"open-email-builder": "Sie können eine E-Mail-Vorlage mit der <1>E-Mail-Builder</1>-Plattform erstellen und den HTML-Code kopieren.",
|
||||
"contact-saved": "Kontakt erfolgreich gespeichert.",
|
||||
"remove-limit-of-user": "Sind Sie sicher, dass Sie das Limit dieses Benutzers aufheben möchten? Der Benutzer kann dann alle in Ihrer Organisation verfügbaren Premium-Credits nutzen, die unter allen Benutzern geteilt werden.",
|
||||
"current-expiry-date": "Aktuelles Ablaufdatum",
|
||||
"reminder-error": "„Einmal alle (Tage) erinnern“ darf die „Zeit zur Fertigstellung (Tage)“ nicht überschreiten.",
|
||||
"showing-to-of-records": "Zeige {{record}} bis {{recordPerPage}} von {{totalRows}} Einträgen",
|
||||
"records-per-page": "Einträge pro Seite",
|
||||
"email-not-found-in-row": "E-Mail in Zeile {{row}} nicht gefunden.",
|
||||
"invalid-email-found-in-row": "Ungültige E-Mail in Zeile {{row}} gefunden: {{email}}",
|
||||
"duplicate-email-found-in-row": "Doppelte E-Mail in Zeile {{row}} gefunden: {{email}}",
|
||||
"prefill": "Vorabfüllung",
|
||||
"recipients-&-data": "Empfänger & Daten",
|
||||
"response": "Antwort",
|
||||
"prefill-tab-help": "Dies sind die gemeinsamen Vorlagenfelder, die auf alle Dokumente in diesem Massenversand angewendet werden.",
|
||||
"recipient-tab-help-p1": "Im nächsten Schritt werden Sie:",
|
||||
"recipient-tab-help-p2": "Eine CSV mit Empfängerdetails hochladen oder",
|
||||
"recipient-tab-help-p3": "Empfängerinformationen manuell eingeben",
|
||||
"recipient-tab-help-p4": "Jeder Empfänger erhält sein eigenes Dokument, das aus dieser Vorlage generiert wird.",
|
||||
"response-tab-help": "Überprüfen Sie Ihre Antworten.",
|
||||
"summary": "Zusammenfassung",
|
||||
"prefill-fields": "Vorabfüllfelder",
|
||||
"documents": "Dokumente",
|
||||
"message": "Nachricht",
|
||||
"formula-help-p1": "Erfassen Sie numerische Eingaben und führen Sie dynamische Berechnungen mit Formeln durch.",
|
||||
"example": "Beispiel",
|
||||
"formula-help-p2": "Um eine Summe in number-3 anzuzeigen, verwenden Sie {{number-1}} + {{number-2}}.",
|
||||
"formula-help-p3": "Um einen Wert in number-4 zu berechnen, verwenden Sie ({{number-1}} * {{number-2}}) / {{number-3}}.",
|
||||
"formula-help-p4": "Jedes Zahlen-Widget muss einen eindeutigen Namen haben (z. B. number-1), da Formeln auf diesen Bezeichnern basieren.",
|
||||
"note-consume-premium-credits": "Hinweis: Diese Funktion verbraucht Premium-Credits.",
|
||||
"note-consume-premium-credits-on-document-send": "Hinweis: Diese Funktion verbraucht Premium-Credits. Sie werden jedes Mal verbraucht, wenn ein Dokument versendet wird – unabhängig davon, ob es unterschrieben wird oder nicht.",
|
||||
"webhook-authentication-help": "{{appName}} sichert jeden Webhook, indem eine HMAC-SHA256-Signatur mit Ihrem Webhook-Sicherheitsschlüssel und dem unveränderten Request-Payload generiert wird. Diese Signatur wird im Header x-webhook-signature gesendet. Ihr Server muss dieselbe Signatur mit dem empfangenen Payload und Ihrem geheimen Schlüssel generieren. Stimmen beide Signaturen überein, ist die Webhook-Anfrage authentisch und wurde nicht verändert.",
|
||||
"invalid-widget-found-in-row": "Ungültiger Wert in Zeile {{row}} für Widget {{widget}} erkannt.",
|
||||
"all-signer-status": "Alle Unterzeichner-Status",
|
||||
"viewed": "Angesehen",
|
||||
"signed": "Unterzeichnet",
|
||||
"switch-to-advanced": "Zum erweiterten Modus wechseln",
|
||||
"switch-to-basic": "Zum Basis-Modus wechseln",
|
||||
"use-name-as-sender": "Profilnamen als Absender verwenden",
|
||||
"use-name-as-sender-help": "Wenn aktiviert, verwendet {{appName}} Ihren Profilnamen als Absender für Signaturanfragen und Abschluss-E-Mails.",
|
||||
"acro-fields-detected": "Formularfelder erkannt",
|
||||
"acro-fields-detected-msg": "Es wurden {{count}} Formularfeld(er) im hochgeladenen PDF erkannt. Möchten Sie die entsprechenden Widgets automatisch einfügen?",
|
||||
"documents-count": "Anzahl der Dokumente",
|
||||
"templates-count": "Anzahl der Vorlagen",
|
||||
"emails-sent": "E-Mails gesendet",
|
||||
"storage-used": "Speicher verwendet",
|
||||
"docs-count-team-help": "Gesamtzahl der von allen Benutzern in Ihren Organisationen erstellten Dokumente.",
|
||||
"docs-count-help": "Gesamtzahl der von Ihnen erstellten Dokumente.",
|
||||
"templates-count-team-help": "Gesamtzahl der von allen Benutzern in Ihren Organisationen erstellten Vorlagen.",
|
||||
"templates-count-help": "Gesamtzahl der von Ihnen erstellten Vorlagen.",
|
||||
"emails-sent-team-help": "Gesamtzahl der in Ihren Organisationen versendeten E-Mail-Benachrichtigungen.",
|
||||
"emails-sent-help": "Gesamtzahl der von Ihnen versendeten E-Mail-Benachrichtigungen.",
|
||||
"storage-used-team-help": "Gesamter Speicherplatzverbrauch aller Benutzer in Ihren Organisationen.",
|
||||
"storage-used-help": "Gesamter Speicherplatzverbrauch durch Sie.",
|
||||
"cloud": "Cloud",
|
||||
"on-premises": "On-Premises",
|
||||
"plan-name-free": "OPENSIGN™ FREE",
|
||||
"plan-name-professional": "OPENSIGN™ PROFESSIONAL",
|
||||
"plan-name-teams": "OPENSIGN™ TEAMS",
|
||||
"plan-name-enterprise": "OPENSIGN™ ENTERPRISE",
|
||||
"plan-name-self-hosted": "OPENSIGN™ SELF-HOSTED",
|
||||
"plan-subtitle-free": "Kostenlose unbegrenzte E-Signaturen, für immer.",
|
||||
"plan-subtitle-professional": "Exklusiver Zugang zu erweiterten Funktionen.",
|
||||
"plan-subtitle-scalable": "Skalierbare Funktionen mit Priority-Support.",
|
||||
"subscribe": "Abonnieren",
|
||||
"contact-us": "Kontaktiere uns",
|
||||
"benefit-unlimited-digital-signatures": "Unbegrenzte digitale Signaturen",
|
||||
"benefit-sign-docs-yourself": "Dokumente selbst unterschreiben",
|
||||
"benefit-request-signatures": "Signaturen von anderen anfordern",
|
||||
"benefit-unlimited-templates": "Unbegrenzte Vorlagen",
|
||||
"benefit-14-field-types": "14 Feldtypen",
|
||||
"benefit-automatic-e-signatures": "Automatische E-Signaturen",
|
||||
"benefit-completion-certificates": "Abschlusszertifikate",
|
||||
"benefit-send-in-order": "In Reihenfolge senden",
|
||||
"benefit-organize-docs": "Dokumente in OpenSign™ Drive organisieren",
|
||||
"benefit-document-templates": "Dokumentvorlagen",
|
||||
"benefit-import-from-dropbox": "Import aus Dropbox",
|
||||
"benefit-contact-book": "Kontaktbuch",
|
||||
"benefit-document-expiry": "Unterstützung für Dokumentablauf",
|
||||
"benefit-decline-document": "Unterstützung für Dokumentablehnung",
|
||||
"benefit-email-notifications": "E-Mail-Benachrichtigungen",
|
||||
"benefit-public-profiles": "Öffentliche Profile",
|
||||
"benefit-and-much-more": "Und vieles mehr",
|
||||
"benefit-everything-free": "Alles in OpenSign™ Free",
|
||||
"benefit-field-validations": "Feldvalidierungen",
|
||||
"benefit-regex-validations": "Reguläre-Ausdrucks-Validierungen",
|
||||
"benefit-webhooks": "Webhooks",
|
||||
"benefit-zapier": "Zapier-Integration",
|
||||
"benefit-api-access": "API-Zugang",
|
||||
"benefit-api-signatures-100": "bis zu 100 API-Signaturen",
|
||||
"benefit-api-signatures-240": "bis zu 240 API-Signaturen",
|
||||
"benefit-custom-email-templates": "Benutzerdefinierte E-Mail-Vorlagen",
|
||||
"benefit-custom-smtp": "Eigenes Gmail- oder SMTP-Konto für den E-Mail-Versand verbinden",
|
||||
"benefit-auto-reminders": "Automatische Erinnerungen",
|
||||
"benefit-bulk-send-100": "Massenversand (bis zu 100 Dokumente)",
|
||||
"benefit-bulk-send-240": "Massenversand (bis zu 240 Dokumente)",
|
||||
"benefit-premium-profiles": "Premium öffentliche Profilbenutzernamen",
|
||||
"benefit-email-verification": "E-Mail-basierte Verifizierung zur Bestätigung der Unterzeichneridentität erzwingen",
|
||||
"benefit-embedded-signing": "Eingebettetes Signieren",
|
||||
"benefit-everything-professional": "Alles in OpenSign™ Professional",
|
||||
"benefit-teams-organizations": "Teams und Organisationen",
|
||||
"benefit-share-templates-teams": "Vorlagen mit Teams teilen",
|
||||
"benefit-share-templates-individuals": "Vorlagen mit Einzelpersonen teilen",
|
||||
"benefit-byoc": "BYOC - Dokumente in Ihrem eigenen Cloud-Speicher aufbewahren",
|
||||
"benefit-doc-id-removal": "DocumentId-Entfernung aus signierten Dokumenten",
|
||||
"benefit-request-payments": "Zahlungen anfordern (demnächst)",
|
||||
"benefit-mobile-app": "Mobile App (demnächst)",
|
||||
"benefit-api-signatures-500": "bis zu 500 API-Signaturen",
|
||||
"benefit-bulk-send-500": "Massenversand (bis zu 500 Dokumente)",
|
||||
"benefit-all-features": "Alle Funktionen",
|
||||
"benefit-custom-domain": "Benutzerdefinierte Domain",
|
||||
"benefit-custom-branding": "Benutzerdefiniertes Branding",
|
||||
"benefit-uptime-sla": "Uptime SLA",
|
||||
"benefit-sso-support": "SSO-Unterstützung",
|
||||
"benefit-priority-support": "Prioritätssupport",
|
||||
"benefit-3-users-1500-credits": "Enthält 3 Benutzer und 1500 Premium-Credits",
|
||||
"benefit-public-links": "Öffentliche Links",
|
||||
"benefit-kiosk-mode": "Kiosk-Modus",
|
||||
"benefit-bulk-send-1500": "Massenversand (bis zu 1500 Dokumente)",
|
||||
"benefit-connect-gmail-smtp": "Eigenes Gmail- oder SMTP-Konto verbinden",
|
||||
"benefit-analytics": "Analysen",
|
||||
"benefit-bulksend-api-pro-month-help": "Dieser Plan enthält 100 Premium-Credits pro Monat, mit denen Sie Dokumente per API versenden, öffentliche Signierlinks teilen, Massen-Signaturanfragen ausführen, den Kiosk-Modus für Vor-Ort-Unterschriften nutzen oder Signaturen aus direkt auf Ihrer Website eingebetteten Dokumenten sammeln können. Jedes Mal, wenn Sie ein Dokument initiieren oder versenden, wird ein Credit verwendet — und dieses einzelne Dokument kann bis zu 10 Unterzeichner enthalten.",
|
||||
"benefit-bulksend-api-pro-year-help": "Dieser Plan enthält 240 Premium-Credits pro Jahr, mit denen Sie Dokumente per API versenden, öffentliche Signierlinks teilen, Massen-Signaturanfragen ausführen, den Kiosk-Modus für Vor-Ort-Unterschriften nutzen oder Signaturen aus direkt auf Ihrer Website eingebetteten Dokumenten sammeln können. Jedes Mal, wenn Sie ein Dokument initiieren oder versenden, wird ein Credit verwendet — und dieses einzelne Dokument kann bis zu 10 Unterzeichner enthalten.",
|
||||
"benefit-bulksend-api-team-month-help": "Dieser Plan enthält 100 Premium-Credits pro Monat, mit denen Sie Dokumente per API versenden, öffentliche Signierlinks teilen, Massen-Signaturanfragen ausführen, den Kiosk-Modus für Vor-Ort-Unterschriften nutzen oder Signaturen aus direkt auf Ihrer Website eingebetteten Dokumenten sammeln können. Jedes Mal, wenn Sie ein Dokument initiieren oder versenden, wird ein Credit verwendet — und dieses einzelne Dokument kann bis zu 10 Unterzeichner enthalten.",
|
||||
"benefit-bulksend-api-team-year-help": "Dieser Plan enthält 500 Premium-Credits pro Jahr, mit denen Sie Dokumente per API versenden, öffentliche Signierlinks teilen, Massen-Signaturanfragen ausführen, den Kiosk-Modus für Vor-Ort-Unterschriften nutzen oder Signaturen aus direkt auf Ihrer Website eingebetteten Dokumenten sammeln können. Jedes Mal, wenn Sie ein Dokument initiieren oder versenden, wird ein Credit verwendet — und dieses einzelne Dokument kann bis zu 10 Unterzeichner enthalten.",
|
||||
"failed-pdf-for-prefill-processing": "PDF für die Vorbefüllung konnte nicht geladen werden.",
|
||||
"attach-all-role-to-signer": "Bitte weisen Sie dem Unterzeichner alle Rollen zu.",
|
||||
"upgrade-to-edit": "Zum Bearbeiten upgraden",
|
||||
"hide-live-chat-for-signers": "Live-Chat für Unterzeichner ausblenden",
|
||||
"hide-live-chat-for-signers-help": "Wenn aktiviert, werden Live-Chat-Widgets für Unterzeichner auf Signierseiten ausgeblendet.",
|
||||
"attachments-creator-hint": "Unterzeichner können Dateien hochladen, die am Ende des Dokuments angehängt werden.",
|
||||
"attachments-drop-hint": "Dateien hier ablegen oder klicken, um auszuwählen",
|
||||
"attachments-limits": "PDF, DOCX, PNG, JPG. Max. {{maxFiles}} Dateien, je {{maxSizeMB}} MB.",
|
||||
"attachments-invalid-type": "Dateityp wird nicht unterstützt: {{name}}",
|
||||
"attachments-max-files": "Maximal {{maxFiles}} Anhänge erlaubt.",
|
||||
"attachments-remove-confirm": "Das Entfernen löscht nur die Metadaten. Bereits angehängte Seiten bleiben im Dokument. Fortfahren?",
|
||||
"conditional-logic": "Bedingte Logik",
|
||||
"enabled": "Aktiviert",
|
||||
"disabled": "Deaktiviert",
|
||||
"logic-no-sources": "Keine anderen Felder dieses Unterzeichners können als Quelle verwendet werden.",
|
||||
"logic-action": "Aktion",
|
||||
"logic-when": "Wenn",
|
||||
"logic-all": "alle",
|
||||
"logic-any": "eine",
|
||||
"logic-of-following": "der folgenden Bedingungen erfüllt sind",
|
||||
"logic-add-rule": "Regel hinzufügen",
|
||||
"logic-pick-value": "Wert auswählen",
|
||||
"logic-value-placeholder": "Wert eingeben",
|
||||
"logic-action-show": "Dieses Feld anzeigen",
|
||||
"logic-action-hide": "Dieses Feld ausblenden",
|
||||
"logic-action-require": "Pflichtfeld machen",
|
||||
"logic-action-optional": "Optional machen",
|
||||
"logic-op-equals": "ist gleich",
|
||||
"logic-op-notEquals": "ist ungleich",
|
||||
"logic-op-checked": "ist angekreuzt",
|
||||
"logic-op-unchecked": "ist nicht angekreuzt",
|
||||
"widget-hidden-due-to-logic": "Dieses Widget ist aufgrund bedingter Logik ausgeblendet.",
|
||||
"remove": "Entfernen",
|
||||
"attachments-docx-not-supported": "Derzeit tritt ein Problem beim Hochladen von DOCX-Dateien auf. Bitte versuchen Sie, das Dokument in einem anderen unterstützten Format hochzuladen, oder versuchen Sie es später erneut.",
|
||||
"access-code": "Zugangscode",
|
||||
"access-code-set": "Zugangscode festgelegt",
|
||||
"access-code-add": "Zugangscode hinzufügen",
|
||||
"access-code-help": "Optionales Geheimnis, das nur dieser Unterzeichner kennt. Vor dem Anzeigen oder Unterschreiben des Dokuments wird die Eingabe verlangt. Geben Sie den Code dem Empfänger auf einem anderen Kanal weiter (Telefon, persönlich).",
|
||||
"access-code-placeholder": "Mindestens 6 Ziffern",
|
||||
"access-code-clear": "Löschen",
|
||||
"access-code-must-be-numeric": "Der Zugangscode darf nur Ziffern 0-9 enthalten.",
|
||||
"access-code-save": "Speichern",
|
||||
"access-code-required": "Der Absender hat diesem Empfänger einen Zugangscode zugewiesen. Geben Sie ihn ein, um fortzufahren.",
|
||||
"access-code-invalid": "Falscher Zugangscode. Bitte beim Absender nachfragen.",
|
||||
"access-code-too-short": "Der Zugangscode muss mindestens 6 Ziffern lang sein.",
|
||||
"access-code-verify": "Zugangscode überprüfen",
|
||||
"access-code-verified": "Zugangscode verifiziert.",
|
||||
"access-code-warning": "Dieser Zugangscode kann nicht geändert oder abgerufen werden, sobald das Dokument versendet wurde.",
|
||||
"copy-access-code": "Zugangscode kopieren",
|
||||
"access-code-upgrade-msg": "Zugangscodes sind nur in kostenpflichtigen Tarifen verfügbar. Aktualisieren Sie Ihren Tarif, um Zugangscodes für Empfänger hinzuzufügen.",
|
||||
"already-copied-to-clipboard": "Bereits in die Zwischenablage kopiert"
|
||||
}
|
||||
|
||||
@@ -68,14 +68,14 @@
|
||||
"loading": "Loading...",
|
||||
"of": "of",
|
||||
"sign-SSO": "Sign in with SSO",
|
||||
"login-page": "Login P+age",
|
||||
"login-page": "Login Page",
|
||||
"or": "OR",
|
||||
"signup-page": "Signup page",
|
||||
"password-match-length": "New password and confirmation password should match",
|
||||
"password-length": "Password should be 8 characters long",
|
||||
"password-case": "Password should contain uppercase letter, lowercase letter, digit",
|
||||
"password-special-char": " Password should contain special character",
|
||||
"agreee": "I agree to the",
|
||||
"agree": "I agree to the",
|
||||
"term": "Terms of Service",
|
||||
"subscribe-to-opensign": "Subscribe to OpenSign newsletter",
|
||||
"register": "Register",
|
||||
@@ -237,11 +237,12 @@
|
||||
"p3": "Selecting this option will send the signing links to all signers simultaneously. Every signer can sign the document at their convenience, regardless of whether other signers have completed their signatures. This method is faster but does not enforce any signing order among the participants.",
|
||||
"p4": "Select the option that best suits the needs of your document processing."
|
||||
},
|
||||
"strict-order": "Enforce strict order",
|
||||
"strict-order-help": "When enabled, no signer can sign or approve until every previous signer/approver has completed their action.",
|
||||
"no": "No",
|
||||
"auto-reminder": "Auto reminder",
|
||||
"remind-once": " Remind once in every (Days)",
|
||||
"next": "Next",
|
||||
"select-folder": "Select folder",
|
||||
"OpenSign-drive": "{{appName}} Drive",
|
||||
"drive-document-status": {
|
||||
"Completed": "Completed",
|
||||
@@ -261,6 +262,7 @@
|
||||
"signers": "Signers",
|
||||
"signers-help": "Begin typing a contact's name to see suggested signers from your saved contacts or add new ones. Arrange the signing order by adding signers in the desired sequence. Use the '+' button to include signers and the 'x' to remove them. Each signer will receive an email prompt to sign the document in the order listed.",
|
||||
"bcc-help": "Start typing a contact's name to see suggestions from your saved contacts or add new ones. Use the '+' button to add a user and the 'x' to remove them. The selected user's email will be added as a Bcc (blind carbon copy). Each user will receive an email notification once the document is completed.",
|
||||
"cc-help": "Start typing a contact's name to see suggestions from your saved contacts or add new ones. Use the '+' button to add a user and the 'x' to remove them. The selected user's email will be added as a cc (carbon copy). Each user will receive an email notification once the document is completed.",
|
||||
"add-signer": "Add Signer",
|
||||
"contact-not-found": "Contact not found",
|
||||
"add-yourself": "Add yourself",
|
||||
@@ -288,7 +290,7 @@
|
||||
"add-webhook": "Add webhook",
|
||||
"webhook-added": "Webhook added successfully.",
|
||||
"team-disabled": "Team disabled",
|
||||
"Team enabled": "Team enabled",
|
||||
"team-enabled": "Team enabled",
|
||||
"are-you-sure": "Are you sure you want to",
|
||||
"disable": "disable",
|
||||
"enable": "enable",
|
||||
@@ -296,7 +298,7 @@
|
||||
"edit-team": "Edit team",
|
||||
"name-of-team": "Name of team",
|
||||
"prev": "Prev",
|
||||
"no-data-avaliable": "No data available",
|
||||
"no-data-available": "No data available",
|
||||
"add-team": "Add team",
|
||||
"page-not-found": "Page not found",
|
||||
"users-from-teams": "users from Teams",
|
||||
@@ -307,12 +309,37 @@
|
||||
"delete-user": "Delete user",
|
||||
"delete": "Delete",
|
||||
"add-user": "Add user",
|
||||
"password-generateed": "Password will only be generated once; make sure to copy it.",
|
||||
"password-generated": "Password will only be generated once; make sure to copy it.",
|
||||
"Team status": "Team status",
|
||||
"user-deactivated": "User deactivated.",
|
||||
"user-activated": "User activated.",
|
||||
"public": "Public",
|
||||
"download": "Download",
|
||||
"sign-offline": "Sign Offline",
|
||||
"sign-offline-instructions": "Download the document, sign it using any external tool, then upload the signed copy here. The document owner will review your submission and either approve or reject it.",
|
||||
"sign-offline-step-1": "Download the unsigned PDF.",
|
||||
"sign-offline-step-2": "Sign the PDF using your preferred tool.",
|
||||
"sign-offline-step-3": "Upload the signed PDF for owner approval.",
|
||||
"sign-offline-upload-hint": "Select the signed PDF to submit for approval.",
|
||||
"sign-offline-submitted-title": "Submitted for approval",
|
||||
"sign-offline-submitted-body": "The document owner has been notified by email to review your offline-signed copy.",
|
||||
"offline-already-submitted-title": "Document submitted offline",
|
||||
"offline-already-submitted-body": "You have already submitted this document for offline signature review. The document owner will be notified once it is approved or rejected.",
|
||||
"offline-action-approve-title": "Approve offline signature",
|
||||
"offline-action-approve-confirm": "Approve",
|
||||
"offline-action-reject-title": "Reject offline signature",
|
||||
"offline-action-reject-hint": "Optionally provide a reason. The signer will be notified by email.",
|
||||
"offline-action-reject-reason-placeholder": "Reason (optional)",
|
||||
"offline-action-reject-confirm": "Confirm rejection",
|
||||
"offline-action-review-title": "Review offline signature",
|
||||
"offline-action-review-hint": "Preview the signed PDF uploaded by the signer, then approve or reject it. No action is taken until you confirm.",
|
||||
"offline-action-submitted-at": "Submitted",
|
||||
"offline-action-no-preview": "No preview available for this submission.",
|
||||
"offline-action-open-in-new-tab": "Open PDF in new tab",
|
||||
"offline-action-invalid-link": "This review link is invalid or has expired.",
|
||||
"offline-action-success": "Action completed.",
|
||||
"reject": "Reject",
|
||||
"document": "Document",
|
||||
"view": "View",
|
||||
"make-template-public": "Make template public",
|
||||
"make-template-private": "Make template private",
|
||||
@@ -337,7 +364,7 @@
|
||||
"quick-send-alert-1": "All roles in this document are currently linked to contacts. To quick send copies of this template to multiple signers, please ensure that at least one role is not linked to any contact.",
|
||||
"quick-send-alert-2": "Please ensure there's at least one signature widget added for all recipients.",
|
||||
"quick-send-alert-3": "Please add at least one role to this template.",
|
||||
"quick-send-alert-4": "Limit reached for {{flow}}.",
|
||||
"quick-send-alert-4": "{{flow}} supports up to {{count}} signers per batch. Split your list and try again.",
|
||||
"copy-link": "Copy link",
|
||||
"copy": "Copy",
|
||||
"revoke-document": "Revoke document",
|
||||
@@ -348,7 +375,7 @@
|
||||
"body": "Body",
|
||||
"add-contact": "Add contact",
|
||||
"edit-contact": "Edit contact",
|
||||
"add-signer-alert": "Contact already exist! Please select it from 'Signers' dropdown",
|
||||
"add-signer-alert": "Contact already exists! Please select it from 'Signers' dropdown",
|
||||
"record-delete-alert": "Record deleted successfully!",
|
||||
"record-revoke-alert": "Record revoked successfully!",
|
||||
"mail-sent-alert": "Mail sent successfully.",
|
||||
@@ -394,7 +421,10 @@
|
||||
"image": "image",
|
||||
"email": "email",
|
||||
"number": "number",
|
||||
"draw": "draw"
|
||||
"draw": "draw",
|
||||
"attachments": "attachments",
|
||||
"attachments-add": "Click to add attachments",
|
||||
"attachments-count": "{{count}} attachment(s)"
|
||||
},
|
||||
"fields": "Fields",
|
||||
"recipients": "Recipients",
|
||||
@@ -406,6 +436,14 @@
|
||||
"certificate": "Certificate",
|
||||
"decline": "Decline",
|
||||
"finish": "Finish",
|
||||
"approve": "Approve",
|
||||
"approve-mssg": " Are you sure you want to approve the document ?",
|
||||
"signer-role-label": "Recipient role",
|
||||
"signer-role-signer": "Signer",
|
||||
"signer-role-viewer": "Viewer",
|
||||
"signer-role-approver": "Approver",
|
||||
"signer-role-hint": "Signer must sign. Approver approves without signing. Viewer only views.",
|
||||
"signer-role-upgrade-msg": "Viewer and Approver roles are available on paid plans only. Upgrade your plan to assign these roles to recipients.",
|
||||
"done": "Done",
|
||||
"mail": "Mail",
|
||||
"sign-now": "Sign now",
|
||||
@@ -438,14 +476,15 @@
|
||||
"reset-password-alert-1": "Reset password link has been sent to your email id",
|
||||
"reset-password-alert-2": "Please setup email adapter ",
|
||||
"reset-password-alert-3": "Reset Your Password",
|
||||
"faild-animation": "Failed to load animation",
|
||||
"failed-animation": "Failed to load animation",
|
||||
"apply": "Apply",
|
||||
"select-columns": "Select columns",
|
||||
"copy-type": {
|
||||
"All pages": "All pages",
|
||||
"All pages but last": "All pages but last",
|
||||
"All pages but first": "All pages but first",
|
||||
"Next to current widget": "Next to current widget"
|
||||
"Next to current widget": "Next to current widget",
|
||||
"Page range": "Page range"
|
||||
},
|
||||
"options": "Options",
|
||||
"minimum-check": "Minimum check",
|
||||
@@ -462,6 +501,19 @@
|
||||
"formula-error-braces": "Each opening {{ must have a matching }}.",
|
||||
"formula-error-operators": "Add at least two values when using operators or parentheses.",
|
||||
"formula-error-leading-operator": "Formulas cannot start with operators.",
|
||||
"formula-error-trailing-operator": "Formulas cannot end with an operator.",
|
||||
"formula-error-adjacent-placeholders": "Two placeholders cannot be written together without an operator between them.",
|
||||
"formula-error-continuous-operators": "Two operators cannot be used together.",
|
||||
"formula-error-empty-parentheses": "Parentheses cannot be empty.",
|
||||
"formula-error-invalid-parentheses-content": "Parentheses cannot start or end with an operator.",
|
||||
"formula-error-before-open-parenthesis": "Missing operator before '('. Add an operator before the opening parenthesis.",
|
||||
"formula-error-after-close-parenthesis": "Missing operator after ')'. Add an operator like '+' or '*' after the closing parenthesis.",
|
||||
"formula-error-between-parentheses": "Missing operator between ')' and '('. Add an operator like '*' between the two groups.",
|
||||
"formula-error-division-by-zero": "Division by zero is not allowed. Replace '0' with a valid non-zero number or variable.",
|
||||
"formula-error-invalid-number-format": "Invalid number format found. Numbers must be in a valid format like '1', '3.14', not '.5', '1.', or '1.2.3'.",
|
||||
"formula-error-unclosed-parenthesis": "Unclosed '(' found. Every opening '(' must have a matching closing ')'.",
|
||||
"formula-error-unexpected-closing-parenthesis": "Unexpected closing parenthesis ')' found without a matching opening '('.",
|
||||
"formula-error-empty-placeholder": "Empty placeholder '{{}}' is not allowed. Provide a variable name inside the braces.",
|
||||
"decimal-places": "Decimal Places",
|
||||
"number-of-decimal-places": "Number of decimal places",
|
||||
"decimal-places-helper": "Set to 0 for whole numbers",
|
||||
@@ -530,6 +582,7 @@
|
||||
"sign-url": "Sign url",
|
||||
"mails-sent": "Mails Sent",
|
||||
"mail-not-delivered": "Mail not delivered",
|
||||
"email-not-verified-head": "Email not verified",
|
||||
"document-alert": "Document alert",
|
||||
"owner-subscription-expired": "Owner's subscription has expired.",
|
||||
"subscription-expired": "Subscription Expired",
|
||||
@@ -542,12 +595,12 @@
|
||||
"sign here": "Sign here",
|
||||
"guest-email-alert": "verification code is sent to your email",
|
||||
"get-otp-alert": "you will get a verification code via Email",
|
||||
"user-already-exist": "User already exist.",
|
||||
"user-already-exist": "User already exists.",
|
||||
"server-error": "Internal server error !",
|
||||
"fill-required-details!": "Please fill required details!",
|
||||
"sign-up-form": "Sign up form",
|
||||
"sign-up": "Sign up",
|
||||
"folder-already-exist!": "folder already exist!",
|
||||
"folder-already-exist!": "folder already exists!",
|
||||
"please-select-position!": "please select position!",
|
||||
"default-sign-alert": "Are you sure you want to auto-sign all requested {{widgetsType}} widgets?",
|
||||
"Your-Signature": "Your signature",
|
||||
@@ -556,7 +609,9 @@
|
||||
"pdf-download": "PDF download",
|
||||
"generate-certificate-alert": " Your completion certificate is being generated. Please wait momentarily. If the download doesn't start shortly, click the button again.",
|
||||
"generate-certificate-err": "We are currently experiencing an issue with certificate generation. Please try again later.",
|
||||
"folder-already-exist": "Folder already exist!",
|
||||
"attachments-docx-upload-failed": "We are currently facing a problem. Please try again.",
|
||||
"attachments-docx-size-limit": "Upload limit reached for large files. Please wait until {{time}} before retrying.",
|
||||
"folder-already-exist": "Folder already exists!",
|
||||
"folder-created-successfully": "Folder created successfully!",
|
||||
"fill-folder-name": "Please fill folder name",
|
||||
"already-exists-this-username": "User already exists with this username!",
|
||||
@@ -567,7 +622,7 @@
|
||||
"add-admin": "Add admin",
|
||||
"opensign-setup": "{{appName}} setup",
|
||||
"join-discord": "Join our discord server",
|
||||
"admin-already-exist": "Admin already exist.",
|
||||
"admin-already-exist": "Admin already exists.",
|
||||
"password-update-alert-1": "Password updated successfully.",
|
||||
"password-update-alert-2": "Your current password is missing or incorrect.",
|
||||
"password-update-alert-3": "Your current password is missing or incorrect.",
|
||||
@@ -605,6 +660,9 @@
|
||||
"document-signed-alert-7": "This Document is no longer available.",
|
||||
"document-signed-alert-8": "The document has already been dispatched for signing.",
|
||||
"yet-to-sign": "Yet to sign",
|
||||
"viewers": "Viewers",
|
||||
"viewer-no-widgets": "Viewers receive a read-only copy of the document and cannot have widgets assigned.",
|
||||
"viewer-confirm-widget-removal": "Switching this recipient to Viewer will remove all widgets already assigned to them. Continue?",
|
||||
"validation-alert-1": "The input does not meet the criteria set by the regular expression.",
|
||||
"account-already-exists": "Account already exists!",
|
||||
"choose-password": "Choose password",
|
||||
@@ -627,7 +685,7 @@
|
||||
"date": "Date",
|
||||
"report-not-found": "Report not found",
|
||||
"please-wait": "please wait",
|
||||
"select-foler": "Select folder",
|
||||
"select-folder": "Select folder",
|
||||
"generated-on": "Generated on",
|
||||
"text-field": "Text field",
|
||||
"font-size": "Font size",
|
||||
@@ -686,13 +744,13 @@
|
||||
"public-template": "This video demonstrates how to set up your personalized public profile, such as 'https://opensign.me/your-username'. You'll also learn how to customize your tagline and make your templates available for public signing.",
|
||||
"allowModify-widgets": "You can drag and drop any of these fields onto the document, in addition to the fields already designated for you by the document creator."
|
||||
},
|
||||
"enter-email-plaholder": "Add an email address and hit enter",
|
||||
"enter-email-placeholder": "Add an email address and hit enter",
|
||||
"success-email-alert": "Email sent successfully!",
|
||||
"expired-doc-title": "Expired document",
|
||||
"expired-on-mssg": "This document expired on {{expiredDate}} and is no longer available to sign.",
|
||||
"signature-validate-alert": "Please confirm that you have selected at least {{minRequiredCount}} checkboxes.",
|
||||
"signature-validate-alert-2": "Ensure this field is accurately filled and meets all requirements.",
|
||||
"remaing-users": "Remaining users",
|
||||
"remaining-users": "Remaining users",
|
||||
"Role": "Role",
|
||||
"additional-users": "Please purchase add-on users.",
|
||||
"Quantity-of-users": "Quantity of users",
|
||||
@@ -701,7 +759,7 @@
|
||||
"Proceed": "Proceed",
|
||||
"Select": "Select",
|
||||
"Add-seats": "Add seats",
|
||||
"user-name-exist": "user name already exist",
|
||||
"user-name-exist": "user name already exists",
|
||||
"format": "format",
|
||||
"select-date-format": "Select a date format",
|
||||
"quantity-of-credits": "Quantity of premium credits",
|
||||
@@ -714,7 +772,7 @@
|
||||
"rotate-left": "Rotate left",
|
||||
"rotate-alert-mssg": "All widgets on this page will be lost. Are you sure you want to proceed?",
|
||||
"templateid": "Template-Id",
|
||||
"bulk-send-subcription-alert": "Please upgrade to Professional or Team plan to use bulk send.",
|
||||
"bulk-send-subscription-alert": "Please upgrade to Professional or Team plan to use bulk send.",
|
||||
"generate-test-token": "Generate test token",
|
||||
"regenerate-test-token": "Regenerate test token",
|
||||
"help-test-token": "This token can be used to test the APIs at the https://sandbox.opensignlabs.com/api/v1 endpoint, allowing you to conduct unlimited document signatures. Please note that the sandbox API will sign your documents with self-signed certificates, which may not be recognized as valid by Adobe. Once you've completed your testing, you can upgrade to one of our paid plans to generate a production token.",
|
||||
@@ -734,8 +792,9 @@
|
||||
"copied-code": "COPIED",
|
||||
"Installation": "Installation",
|
||||
"Usage": "Usage",
|
||||
"insufficient-credits": "Insufficient signing credits",
|
||||
"insufficient-credits-mssg": "The owner of this document currently lacks the necessary OpenSign credits for you to sign. Please reach out to the owner if you require further details.",
|
||||
"insufficient-credits": "Insufficient signing credits. Please contact {{ownerEmail}}",
|
||||
"insufficient-credits-title": "Insufficient signing credits",
|
||||
"insufficient-credits-mssg": "The owner of this document currently lacks the necessary {{appName}} credits for you to sign. Please reach out to the owner if you require further details.",
|
||||
"angular-npm-mssg-1": "To integrate OpenSign into your Angular project, simply run the following command:",
|
||||
"quota-mail-info-head": "Monthly Email Limit",
|
||||
"quota-mail-info": "To maintain service quality and prevent spam, OpenSign allows up to 15 emails per month on the free plan. Upgrade now for unlimited email sending.",
|
||||
@@ -754,6 +813,13 @@
|
||||
"p3": "Selecting this option will disable OTP verification, allowing users to sign the document directly without additional steps.",
|
||||
"p4": "Please choose the option that best suits your document signing requirements."
|
||||
},
|
||||
"allow-offline-sign": "Allow offline signing",
|
||||
"allow-offline-sign-help": {
|
||||
"p1": "Choose whether signers can submit a copy of this document signed using an external tool for owner review.",
|
||||
"p2": "Signers will see a Sign Offline option and can upload an externally signed PDF for the document owner to approve.",
|
||||
"p3": "Signers can only sign within the app. The Sign Offline option will be hidden.",
|
||||
"p4": "This setting defaults to your account preference under Preferences and is available on paid plans."
|
||||
},
|
||||
"advanced-options": "Advanced options",
|
||||
"hide-advanced-options": "Hide advanced options",
|
||||
"download-files": "Download files",
|
||||
@@ -816,6 +882,7 @@
|
||||
"delete-folder-alert-1": "Unable to delete: The folder must be empty.",
|
||||
"defaultSign-alert": "{{widgetsType}} widgets are not available for auto sign.",
|
||||
"embed-heading": "Embed {{appName}} Templates",
|
||||
"other-apps-by-opensign": "Other apps by OpenSign team",
|
||||
"public-template-edit-title": "Edit Public Template",
|
||||
"public-template-edit-mssg": "You cannot edit a public template directly. To make changes, the template will be set to private, and you'll need to make it public again afterward. Do you want to continue?",
|
||||
"agree-p1": "I confirm that I have read and understood the ",
|
||||
@@ -867,7 +934,6 @@
|
||||
"js-snippet-msg-1": "You can get the TemplateId from the",
|
||||
"js-snippet-msg-2": "Manage Templates",
|
||||
"js-snippet-msg-3": "page.",
|
||||
"agrrement-alert": "To proceed, you must consent to the electronic record and signature disclosure.",
|
||||
"webhook-already-exists": "The URL already exists! Please try a different one.",
|
||||
"webhook-must-be-secure": "The webhook URL must be secure and use https://",
|
||||
"provide-test-token": "Please generate api test token",
|
||||
@@ -887,6 +953,7 @@
|
||||
"p3": "Selecting this option restricts signers from making any additions to the document during the signing process."
|
||||
},
|
||||
"signature-setting": "Signature settings",
|
||||
"rotation": "Rotation",
|
||||
"activation-key": "Activation key",
|
||||
"paid-selfhost-help": "Please contact to {{appName}} for more info at ",
|
||||
"remaining-credits-selfhost-help": "Use premium credits for API document signing, bulk sending {{appName}} integration on your website. You have {{allowedcredits}} included credits and {{addoncredits}} additional purchased credits remaining.",
|
||||
@@ -915,6 +982,8 @@
|
||||
"visit-below-link": "Visit below link to know more -",
|
||||
"storage-help": "Enabling BYOC lets you connect your own S3 storage so your files remain entirely under your control—no external copies retained. If data autonomy matters to you, consider upgrading to Teams to unlock this feature.",
|
||||
"daily-quota-reached": "You've reached your daily quota. For assistance, please contact quotas@opensignlabs.com.",
|
||||
"email-not-verified-send": "Please verify your email address before sending emails. Go to your <1>profile settings</1> to complete email verification.",
|
||||
"email-not-verified-send-plain": "Please verify your email address before sending emails. Go to your profile settings to complete email verification.",
|
||||
"enabled-signature-type": "Enabled Signature Types",
|
||||
"enabled-signature-type-help": "The 'Enabled Signature Types' setting determines which signature options are available across your organization. For example, if you disable the 'Draw' option, members of your organization will not see it in the signature widget, while the other three options will remain accessible.",
|
||||
"indexing-public-profile": "Allow indexing of public profile by search engines",
|
||||
@@ -942,6 +1011,7 @@
|
||||
"please-provide-templateid": "Please provide templateid",
|
||||
"this-template-is-not-public": "This template is not public",
|
||||
"invalid-templateid": "Invalid templateid",
|
||||
"kiosk-no-active-role": "This template has no signer or approver roles available for kiosk signing.",
|
||||
"contact-billing-at-opensign": "To add more seats, please contact OpenSign™ at <1>billing@opensignlabs.com</1> for assistance",
|
||||
"title-length-alert": "Title must be at most 250 characters long.",
|
||||
"note-length-alert": "Note must be at most 200 characters long.",
|
||||
@@ -1031,7 +1101,7 @@
|
||||
},
|
||||
"signer-already-present": "Signer already present",
|
||||
"kiosk-sign": "Kiosk Sign",
|
||||
"dont-have-access-to-template": "The template has been deleted or you don't have access. Please contact the sender.",
|
||||
"do-not-have-access-to-template": "The template has been deleted or you don't have access. Please contact the sender.",
|
||||
"kiosk-info": "Kiosk Mode lets you collect in-person signatures quickly and efficiently. Ideal for trade shows, events, or walk-in scenarios where all signers are physically present. ",
|
||||
"learn-more": "Learn more",
|
||||
"finish-mssg": " Are you sure you want to finish the document ?",
|
||||
@@ -1087,8 +1157,6 @@
|
||||
"search-contacts": "Search contacts…",
|
||||
"add-role-alert": "Please add at least one role",
|
||||
"edit-draft": "Edit draft",
|
||||
"invalid-email-found": "Invalid email found: {{email}}",
|
||||
"duplicate-email-found": "Duplicate email found: {{email}}",
|
||||
"vertical": "Vertical",
|
||||
"horizontal": "Horizontal",
|
||||
"billing": "Billing",
|
||||
@@ -1102,7 +1170,7 @@
|
||||
"access-denied": "Access denied",
|
||||
"upgrade": "Upgrade",
|
||||
"do-not-access-app": "You don't have access to this application.",
|
||||
"dont-have-access": "You don't have access.",
|
||||
"do-not-have-access": "You don't have access.",
|
||||
"valid-email-alert": "Please enter a valid email address.",
|
||||
"otp-not-validate": "OTP is not valid.",
|
||||
"domain-not-allowed": "This domain is not allowed",
|
||||
@@ -1119,10 +1187,12 @@
|
||||
"branding-help": "Branding provides white labelling to your app",
|
||||
"custom-sub-domain": "Custom sub-domain",
|
||||
"app-name": "App Name",
|
||||
"provide-domain-name": "provide your domain name",
|
||||
"provide-domain-name": "Only subdomains are allowed (e.g., app.yourdomain.com). Primary/root domains like yourdomain.com are not supported.",
|
||||
"provide-app-name-help": "Provide a unique app name. It will be displayed as your brand name within the application and in all emails wherever the branding is applied.",
|
||||
"upload-app-logo": "Upload a logo in .png, .jpg, or .svg format. Recommended size: {{size}} pixels (5:2 aspect ratio) for best display quality.",
|
||||
"upload-app-favicon": "Upload a square favicon image (.ico, .png, or .svg). Recommended size is {{size}} pixels for best browser compatibility.",
|
||||
"provide-app-name": "provide your app name",
|
||||
"logo": "Logo",
|
||||
"upload-app-logo": "upload your app logo",
|
||||
"prefill-unfilled-widget": "The following required field(s) cannot be left empty: {{emptyWidget}} Please fill them out to proceed.",
|
||||
"Dashboard": "Dashboard",
|
||||
"Analytics": "Analytics",
|
||||
@@ -1276,6 +1346,8 @@
|
||||
"disable-individual-user-smtp": "Disable individual user smtp settings",
|
||||
"enable-individual-user-smtp": "Enable individual user smtp settings",
|
||||
"user-smtp-general-help": "When disabled, users will not be able to configure their own SMTP settings. All emails will use the global SMTP configuration instead.",
|
||||
"hide-live-chat-for-signers": "Hide live chat widget for signers",
|
||||
"hide-live-chat-for-signers-help": "When enabled, the live chat widget will be hidden on all signing pages. A window variable 'opensign_hide_chat' is set to true, which can be used as a GTM trigger condition to prevent loading the widget for signers.",
|
||||
"global-smtp-settings": "Global SMTP Settings",
|
||||
"global-smtp-settings-help-p1": "These settings apply globally across your entire {{appName}} account. If an individual user has not configured their own email delivery settings, the system will automatically use the Global SMTP Settings defined here.",
|
||||
"global-smtp-settings-help-p2": "To ensure better inbox deliverability for all signature request emails, you can connect your preferred email provider. Choose one of the following options:",
|
||||
@@ -1290,11 +1362,11 @@
|
||||
"signer-form-data-for": "Signer Form data for: {{signerName}} <{{signerEmail}}>",
|
||||
"widget-name": "Widget Name",
|
||||
"widget-responses": "Widget Responses",
|
||||
"formdata-download-note": "Note: Signatures, stamps, initials and images wont be exported.",
|
||||
"formdata-download-note": "Note: Signatures, stamps, initials and images wont be exported. Offline signers’ data will not be available.",
|
||||
"export-all-formdata-error": "You can/t export form data because the template contains duplicate widget names. Please create a fresh template to be able to export form data.",
|
||||
"export-formdata-error": "You can't export form data because the document contains duplicate widget names. Please create a fresh document without duplicate widget names to be able to export form data.",
|
||||
"duplicate-widget-name-error": "This widget name is already assigned to another signer. Please choose a different name.",
|
||||
"duplicate-template-widget-error": "You can't duplicate this template because it contains duplicate widget names.",
|
||||
"duplicate-template-widget-error": "Unable to duplicate the template because some widget names are duplicated across different roles. Rename the duplicate widgets and try again.",
|
||||
"credit-alert": "Credit Alert",
|
||||
"document-form-data": "Document Form Data",
|
||||
"view-formdata-paid-title-error": "The View Form Data feature lets you print and download the widget data filled in by signers while signing.",
|
||||
@@ -1346,8 +1418,8 @@
|
||||
"file-selected": "file selected",
|
||||
"bulk-send-csv-helper": "Use headers for signer roles (Role Email/Name/Phone) and prefill values (Prefill: Field Name). Required prefill fields must be completed.",
|
||||
"bulk-send-csv-helper-note": "Note: For Date-type prefill fields in the CSV, the value must be provided in this format: {{value}} (example: {{example}}).",
|
||||
"bulk-send-csv-summary": "Imported {{total}} rows. {{invalid}} invalid rows were skipped.",
|
||||
"bulk-send-csv-missing-headers": "Missing required CSV headers. Roles: {{roles}}. Prefill: {{prefill}}.",
|
||||
"bulk-send-csv-summary": "Imported {{total}} rows from CSV.",
|
||||
"bulk-send-csv-missing-headers": "Missing required CSV headers. Roles: {{roles}}.",
|
||||
"bulk-send-no-records": "Please add at least one recipient before sending.",
|
||||
"csv-only-support": "Only CSV files are supported.",
|
||||
"csv-no-records": "No records found in the CSV.",
|
||||
@@ -1361,5 +1433,182 @@
|
||||
"enter-daily-email-quota": "Enter daily email quota",
|
||||
"enter-custom-regular-expression": "Enter custom regular expression",
|
||||
"enter-value": "Enter {{value}}",
|
||||
"are-you-sure-want-to-send-document": "Are you sure want to send document?"
|
||||
}
|
||||
"are-you-sure-want-to-send-document": "Are you sure want to send document?",
|
||||
"favicon": "Favicon",
|
||||
"delete-two-factor-authentication": "Are you sure you want to delete two-factor authentication?",
|
||||
"paste-html-here": "Paste HTML Here",
|
||||
"open-email-builder": "You can create email template using <1>email builder</1> platform and copy html code.",
|
||||
"contact-saved": "Contact saved successfully.",
|
||||
"remove-limit-of-user": "Are you sure you want to remove this user's limit? They will be able to consume all your organization's available premium credits that are shared across all users.",
|
||||
"current-expiry-date": "Current expiry date",
|
||||
"reminder-error": "Remind once in every (Days) must not exceed Time to complete (Days)",
|
||||
"showing-to-of-records": "Showing {{record}} to {{recordPerPage}} of {{totalRows}} records",
|
||||
"records-per-page": "Records per page",
|
||||
"email-not-found-in-row": "Email not found in row {{row}}",
|
||||
"invalid-email-found-in-row": "Invalid email found in row {{row}}: {{email}}",
|
||||
"duplicate-email-found-in-row": "Duplicate email found in row {{row}}: {{email}}",
|
||||
"prefill": "Prefill",
|
||||
"recipients-&-data": "Recipients & Data",
|
||||
"response": "Response",
|
||||
"prefill-tab-help": "These are the common template fields that will apply to all documents in this bulk send.",
|
||||
"recipient-tab-help-p1": "In this next step, you'll",
|
||||
"recipient-tab-help-p2": "Upload a CSV with recipient details or",
|
||||
"recipient-tab-help-p3": "Manually enter recipient information",
|
||||
"recipient-tab-help-p4": "Each recipient will receive their own document generated from this template.",
|
||||
"response-tab-help": "Review your responses.",
|
||||
"summary": "Summary",
|
||||
"prefill-fields": "Prefill fields",
|
||||
"documents": "Documents",
|
||||
"message": "Message",
|
||||
"formula-help-p1": "Collect numeric input and perform dynamic calculations using formulas.",
|
||||
"example": "Example",
|
||||
"formula-help-p2": "To display a total in number-3, use {{number-1}} + {{number-2}}.",
|
||||
"formula-help-p3": "To calculate a value in number-4, use ({{number-1}} * {{number-2}}) / {{number-3}}.",
|
||||
"formula-help-p4": "Each number widget must have a unique name (e.g., number-1), as formulas rely on these identifiers.",
|
||||
"note-consume-premium-credits": "Note: This feature consumes premium credits.",
|
||||
"note-consume-premium-credits-on-document-send": "Note: This feature consumes premium credits. It is consumed any time a document is sent out, regardless of whether it is signed or not.",
|
||||
"webhook-authentication-help": "{{appName}} secures every webhook by generating an HMAC-SHA256 signature using your webhook security key and the raw request payload. This signature is sent in the x-webhook-signature header. Your server must generate the same signature using the received payload and your secret key. If both signatures match, the webhook request is authentic and has not been modified.",
|
||||
"invalid-widget-found-in-row": "Invalid value detected in row {{row}} for widget {{widget}}.",
|
||||
"all-signer-status": "All signer status",
|
||||
"viewed": "Viewed",
|
||||
"signed": "Signed",
|
||||
"switch-to-advanced": "Switch to advanced",
|
||||
"switch-to-basic": "Switch to basic",
|
||||
"use-name-as-sender": "Use profile name as sender",
|
||||
"use-name-as-sender-help": "When enabled, {{appName}} uses your profile name as sender for signature request and completion mail.",
|
||||
"acro-fields-detected": "Form Fields Detected",
|
||||
"acro-fields-detected-msg": "We detected {{count}} form field(s) in the uploaded PDF. Would you like to automatically insert corresponding widgets?",
|
||||
"documents-count": "Documents count",
|
||||
"templates-count": "Templates count",
|
||||
"emails-sent": "Emails sent",
|
||||
"storage-used": "Storage used",
|
||||
"docs-count-team-help": "Total number of documents created by all users in your organizations.",
|
||||
"docs-count-help": "Total number of documents created by you.",
|
||||
"templates-count-team-help": "Total number of templates created by all users in your organizations.",
|
||||
"templates-count-help": "Total number of templates created by you.",
|
||||
"emails-sent-team-help": "Total number of email notifications sent across your organizations.",
|
||||
"emails-sent-help": "Total number of email notifications sent by you.",
|
||||
"storage-used-team-help": "Total storage space consumed by all users across your organizations.",
|
||||
"storage-used-help": "Total storage space consumed by you.",
|
||||
"cloud": "Cloud",
|
||||
"on-premises": "On-premises",
|
||||
"plan-name-free": "OPENSIGN™ FREE",
|
||||
"plan-name-professional": "OPENSIGN™ PROFESSIONAL",
|
||||
"plan-name-teams": "OPENSIGN™ TEAMS",
|
||||
"plan-name-enterprise": "OPENSIGN™ ENTERPRISE",
|
||||
"plan-name-self-hosted": "OPENSIGN™ SELF-HOSTED",
|
||||
"plan-subtitle-free": "Free Unlimited E-signatures, Forever.",
|
||||
"plan-subtitle-professional": "Exclusive Access to advanced features.",
|
||||
"plan-subtitle-scalable": "Scalable Features with priority support.",
|
||||
"subscribe": "Subscribe",
|
||||
"contact-us": "Contact us",
|
||||
"benefit-unlimited-digital-signatures": "Unlimited digital signatures",
|
||||
"benefit-sign-docs-yourself": "Sign documents yourself",
|
||||
"benefit-request-signatures": "Request signatures from others",
|
||||
"benefit-unlimited-templates": "Unlimited templates",
|
||||
"benefit-14-field-types": "14 field types",
|
||||
"benefit-automatic-e-signatures": "Automatic e-signatures",
|
||||
"benefit-completion-certificates": "Completion certificates",
|
||||
"benefit-send-in-order": "Send in order",
|
||||
"benefit-organize-docs": "Organize docs in OpenSign™ Drive",
|
||||
"benefit-document-templates": "Document templates",
|
||||
"benefit-import-from-dropbox": "Import from Dropbox",
|
||||
"benefit-contact-book": "Contact book",
|
||||
"benefit-document-expiry": "Document expiry support",
|
||||
"benefit-decline-document": "Decline document support",
|
||||
"benefit-email-notifications": "Email notifications",
|
||||
"benefit-public-profiles": "Public profiles",
|
||||
"benefit-and-much-more": "And much more",
|
||||
"benefit-everything-free": "Everything in OpenSign™ free",
|
||||
"benefit-field-validations": "Field validations",
|
||||
"benefit-regex-validations": "Regular expression validations",
|
||||
"benefit-webhooks": "Webhooks",
|
||||
"benefit-zapier": "Zapier integration",
|
||||
"benefit-api-access": "API Access",
|
||||
"benefit-api-signatures-100": "upto 100 API signatures",
|
||||
"benefit-api-signatures-240": "upto 240 API signatures",
|
||||
"benefit-custom-email-templates": "Custom email templates",
|
||||
"benefit-custom-smtp": "Connect your own Gmail or SMTP account for sending emails",
|
||||
"benefit-auto-reminders": "Auto reminders",
|
||||
"benefit-bulk-send-100": "Bulk send (upto 100 docs)",
|
||||
"benefit-bulk-send-240": "Bulk send (upto 240 docs)",
|
||||
"benefit-premium-profiles": "Premium Public profile usernames",
|
||||
"benefit-email-verification": "Enforce email-based verification to confirm signer identity",
|
||||
"benefit-embedded-signing": "Embedded signing",
|
||||
"benefit-everything-professional": "Everything in OpenSign™ professional",
|
||||
"benefit-teams-organizations": "Teams and Organizations",
|
||||
"benefit-share-templates-teams": "Share Templates with teams",
|
||||
"benefit-share-templates-individuals": "Share Templates with individuals",
|
||||
"benefit-byoc": "BYOC - Store your documents in your own cloud storage",
|
||||
"benefit-doc-id-removal": "DocumentId removal from signed docs",
|
||||
"benefit-request-payments": "Request Payments (coming soon)",
|
||||
"benefit-mobile-app": "Mobile app (coming soon)",
|
||||
"benefit-api-signatures-500": "upto 500 API signatures",
|
||||
"benefit-bulk-send-500": "Bulk send (upto 500 docs)",
|
||||
"benefit-all-features": "All features",
|
||||
"benefit-custom-domain": "Custom domain",
|
||||
"benefit-custom-branding": "Custom branding",
|
||||
"benefit-uptime-sla": "Uptime SLA",
|
||||
"benefit-sso-support": "SSO support",
|
||||
"benefit-priority-support": "Priority support",
|
||||
"benefit-3-users-1500-credits": "Includes 3 users and 1500 Premium Credit",
|
||||
"benefit-public-links": "Public links",
|
||||
"benefit-kiosk-mode": "Kiosk Mode",
|
||||
"benefit-bulk-send-1500": "Bulk send (upto 1500 docs)",
|
||||
"benefit-connect-gmail-smtp": "Connect your own Gmail or SMTP",
|
||||
"benefit-analytics": "Analytics",
|
||||
"benefit-bulksend-api-pro-month-help": "This plan comes with 100 premium credits per month that let you send documents via API, share public signing links, run bulk signature requests, use Kiosk mode for in-person signing, or collect signatures from documents embedded directly on your website. Each time you initiate or send a document, one credit is used — and that single document can include up to 10 signers.",
|
||||
"benefit-bulksend-api-pro-year-help": "This plan comes with 240 premium credits per year that let you send documents via API, share public signing links, run bulk signature requests, use Kiosk mode for in-person signing, or collect signatures from documents embedded directly on your website. Each time you initiate or send a document, one credit is used — and that single document can include up to 10 signers.",
|
||||
"benefit-bulksend-api-team-month-help": "This plan comes with 100 premium credits per month that let you send documents via API, share public signing links, run bulk signature requests, use Kiosk mode for in-person signing, or collect signatures from documents embedded directly on your website. Each time you initiate or send a document, one credit is used — and that single document can include up to 10 signers.",
|
||||
"benefit-bulksend-api-team-year-help": "This plan comes with 500 premium credits per year that let you send documents via API, share public signing links, run bulk signature requests, use Kiosk mode for in-person signing, or collect signatures from documents embedded directly on your website. Each time you initiate or send a document, one credit is used — and that single document can include up to 10 signers.",
|
||||
"failed-pdf-for-prefill-processing": "Failed to load PDF for prefill processing.",
|
||||
"attach-all-role-to-signer": "Please attach all roles to the signer.",
|
||||
"upgrade-to-edit": "Upgrade to edit",
|
||||
"attachments-creator-hint": "Signers can upload files that will be appended to the end of the document.",
|
||||
"attachments-drop-hint": "Drop files here or click to choose",
|
||||
"attachments-limits": "PDF, DOCX, PNG, JPG. Up to {{maxFiles}} files, {{maxSizeMB}} MB each.",
|
||||
"attachments-invalid-type": "Unsupported file type: {{name}}",
|
||||
"attachments-max-files": "A maximum of {{maxFiles}} attachments are allowed.",
|
||||
"attachments-remove-confirm": "Removing only deletes the metadata. Pages already appended will remain in the document. Continue?",
|
||||
"conditional-logic": "Conditional logic",
|
||||
"enabled": "Enabled",
|
||||
"disabled": "Disabled",
|
||||
"logic-no-sources": "No other fields on this signer can be used as a source.",
|
||||
"logic-action": "Action",
|
||||
"logic-when": "When",
|
||||
"logic-all": "all",
|
||||
"logic-any": "any",
|
||||
"logic-of-following": "of the following are true",
|
||||
"logic-add-rule": "Add rule",
|
||||
"logic-pick-value": "Select a value",
|
||||
"logic-value-placeholder": "Enter value",
|
||||
"logic-action-show": "Show this field",
|
||||
"logic-action-hide": "Hide this field",
|
||||
"logic-action-require": "Make required",
|
||||
"logic-action-optional": "Make optional",
|
||||
"logic-op-equals": "equals",
|
||||
"logic-op-notEquals": "does not equal",
|
||||
"logic-op-checked": "is checked",
|
||||
"logic-op-unchecked": "is not checked",
|
||||
"widget-hidden-due-to-logic": "This widget is hidden due to conditional logic.",
|
||||
"remove": "Remove",
|
||||
"attachement-note":"Note: This widget allows signers to upload a file during the signing process. The uploaded file will be appended as additional pages at the end of the existing PDF",
|
||||
"attachments-docx-not-supported":"We are currently experiencing an issue with DOCX file uploads. Please try uploading the document in a different supported format, or try again later.",
|
||||
"access-code": "Access code",
|
||||
"access-code-set": "Access code set",
|
||||
"access-code-add": "Add access code",
|
||||
"access-code-help": "Optional secret only this signer knows. They will be required to enter it before viewing or signing the document. Share it with the recipient out-of-band (phone, in person).",
|
||||
"access-code-placeholder": "At least 6 digits",
|
||||
"access-code-clear": "Clear",
|
||||
"access-code-save": "Save",
|
||||
"access-code-required": "This recipient was given an access code by the sender. Enter it to continue.",
|
||||
"access-code-invalid": "Incorrect access code. Please check with the sender.",
|
||||
"access-code-too-short": "Access code must be at least 6 digits.",
|
||||
"access-code-must-be-numeric": "Access code must contain only digits 0-9.",
|
||||
"access-code-verify": "Verify access code",
|
||||
"access-code-verified": "Access code verified.",
|
||||
"access-code-warning": "This access code cannot be changed or retrieved once the document is sent out.",
|
||||
"copy-access-code": "Copy access code",
|
||||
"access-code-upgrade-msg": "Access codes are available on paid plans only. Upgrade your plan to add access codes for recipients.",
|
||||
"already-copied-to-clipboard": "Already copied to clipboard"
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
"password-length": "La contraseña debe tener 8 caracteres",
|
||||
"password-case": "La contraseña debe incluir mayúsculas, minúsculas y números",
|
||||
"password-special-char": " La contraseña debe incluir caracteres especiales",
|
||||
"agreee": "Estoy de acuerdo con los",
|
||||
"agree": "Estoy de acuerdo con los",
|
||||
"term": "Términos de Servicio",
|
||||
"subscribe-to-opensign": "Suscribirse al boletín de OpenSign",
|
||||
"register": "Registrarse",
|
||||
@@ -237,11 +237,12 @@
|
||||
"p3": "Seleccionando esta opción se enviarán los enlaces para la firma a todos los firmantes simultáneamente. Cada firmante puede firmar el documento cuando le convenga, independientemente de si otros firmantes han completado sus firmas. Este método es más rápido, pero no impone ningún orden de firma entre los participantes.",
|
||||
"p4": "Selecciona la opción que mejor se adapte a las necesidades de tu tramitación de documentos."
|
||||
},
|
||||
"strict-order": "Forzar orden estricto",
|
||||
"strict-order-help": "Cuando está habilitado, ningún firmante puede firmar o aprobar hasta que todos los firmantes/aprobadores anteriores hayan completado su acción.",
|
||||
"no": "No",
|
||||
"auto-reminder": "Recordatorio automático",
|
||||
"remind-once": " Recordar una vez cada (Días)",
|
||||
"next": "Siguiente",
|
||||
"select-folder": "Seleccionar carpeta",
|
||||
"OpenSign-drive": "{{appName}} Drive",
|
||||
"drive-document-status": {
|
||||
"Completed": "Completado",
|
||||
@@ -261,6 +262,7 @@
|
||||
"signers": "Firmantes",
|
||||
"signers-help": "Empieza a escribir el nombre de un contacto para ver los firmantes sugeridos entre tus contactos guardados o añade otros nuevos. Organiza el orden de firma añadiendo firmantes en la secuencia deseada. Utiliza el botón «+» para añadir nuevos firmantes y la «X» para quitar todos. Cada firmante recibirá una solicitud por correo electrónico para que firme el documento según el orden indicado.",
|
||||
"bcc-help": "Empieza a escribir el nombre de un contacto para ver sugerencias de tus contactos guardados o agregar nuevos. Usa el botón '+' para añadir un usuario y el botón 'x' para eliminarlo. El correo electrónico del usuario seleccionado se añadirá como Bcc (copia oculta). Cada usuario recibirá una notificación por correo electrónico una vez que el documento esté completado.",
|
||||
"cc-help": "Empieza a escribir el nombre de un contacto para ver sugerencias de tus contactos guardados o añadir nuevos. Usa el botón '+' para agregar un usuario y la 'x' para eliminarlo. El correo del usuario seleccionado se añadirá como CC (copia). Cada usuario recibirá una notificación por correo una vez que el documento se complete.",
|
||||
"add-signer": "Añadir firmante",
|
||||
"contact-not-found": "Contacto no encontrado",
|
||||
"add-yourself": "Añadirte a ti mismo",
|
||||
@@ -282,13 +284,15 @@
|
||||
"yes": "Sí",
|
||||
"copied": "Copiado",
|
||||
"something-went-wrong-mssg": "Un problème est survenu, Actualiser cette page peut résoudre le problème.",
|
||||
"attachments-docx-upload-failed": "Actualmente tenemos un problema. Inténtalo de nuevo.",
|
||||
"attachments-docx-upload-limit": "Se alcanzó el límite de carga para archivos DOCX grandes. Espera 4 minutos antes de intentarlo de nuevo.",
|
||||
"token-generated": "Token generado exitosamente.",
|
||||
"webhook": "Webhook",
|
||||
"update-webhook": "Actualizar webhook",
|
||||
"add-webhook": "Agregar webhook",
|
||||
"webhook-added": "Webhook agregado exitosamente.",
|
||||
"team-disabled": "Equipo deshabilitado",
|
||||
"Team enabled": "Equipo habilitado",
|
||||
"team-enabled": "Equipo habilitado",
|
||||
"are-you-sure": "¿En definitiva quieres ",
|
||||
"disable": "deshabilitar",
|
||||
"enable": "habilitar",
|
||||
@@ -296,7 +300,7 @@
|
||||
"edit-team": "Editar equipo",
|
||||
"name-of-team": "Nombre del equipo",
|
||||
"prev": "Anterior",
|
||||
"no-data-avaliable": "No hay datos disponibles",
|
||||
"no-data-available": "No hay datos disponibles",
|
||||
"add-team": "Agregar equipo",
|
||||
"page-not-found": "Página no encontrada",
|
||||
"users-from-teams": "usuarios de equipos",
|
||||
@@ -307,12 +311,37 @@
|
||||
"delete-user": "Eliminar usuario",
|
||||
"delete": "Eliminar",
|
||||
"add-user": "Agregar usuario",
|
||||
"password-generateed": "La contraseña solo será generada una vez; asegúrate de copiarla.",
|
||||
"password-generated": "La contraseña solo será generada una vez; asegúrate de copiarla.",
|
||||
"Team status": "Estado del equipo",
|
||||
"user-deactivated": "Usuario desactivado.",
|
||||
"user-activated": "Usuario activado.",
|
||||
"public": "Público",
|
||||
"download": "Descargar",
|
||||
"sign-offline": "Firmar sin conexión",
|
||||
"sign-offline-instructions": "Descargue el documento, fírmelo con una herramienta externa y luego suba la copia firmada aquí. El propietario del documento revisará su envío y lo aprobará o rechazará.",
|
||||
"sign-offline-step-1": "Descargar el PDF sin firmar.",
|
||||
"sign-offline-step-2": "Firmar el PDF con su herramienta preferida.",
|
||||
"sign-offline-step-3": "Subir el PDF firmado para aprobación del propietario.",
|
||||
"sign-offline-upload-hint": "Seleccione el PDF firmado para enviarlo para su aprobación.",
|
||||
"sign-offline-submitted-title": "Enviado para aprobación",
|
||||
"sign-offline-submitted-body": "El propietario del documento ha sido notificado por correo electrónico para revisar su copia firmada sin conexión.",
|
||||
"offline-already-submitted-title": "Documento enviado sin conexión",
|
||||
"offline-already-submitted-body": "Ya ha enviado este documento para la revisión de la firma sin conexión. Se le notificará una vez que sea aprobado o rechazado.",
|
||||
"offline-action-approve-title": "Aprobar firma sin conexión",
|
||||
"offline-action-approve-confirm": "Aprobar",
|
||||
"offline-action-reject-title": "Rechazar firma sin conexión",
|
||||
"offline-action-reject-hint": "Opcionalmente, proporcione un motivo. El firmante será notificado por correo electrónico.",
|
||||
"offline-action-reject-reason-placeholder": "Motivo (opcional)",
|
||||
"offline-action-reject-confirm": "Confirmar rechazo",
|
||||
"offline-action-review-title": "Revisar firma sin conexión",
|
||||
"offline-action-review-hint": "Previsualice el PDF firmado subido por el firmante y luego apruébelo o recházelo. No se realiza ninguna acción hasta que confirme.",
|
||||
"offline-action-submitted-at": "Enviado",
|
||||
"offline-action-no-preview": "No hay vista previa disponible para este envío.",
|
||||
"offline-action-open-in-new-tab": "Abrir PDF en nueva pestaña",
|
||||
"offline-action-invalid-link": "Este enlace de revisión no es válido o ha expirado.",
|
||||
"offline-action-success": "Acción completada.",
|
||||
"reject": "Rechazar",
|
||||
"document": "Documento",
|
||||
"view": "Ver",
|
||||
"make-template-public": "Convertir la plantilla en pública",
|
||||
"make-template-private": "Convertir la plantilla en privada",
|
||||
@@ -337,7 +366,7 @@
|
||||
"quick-send-alert-1": "Todos los roles de este documento están actualmente vinculados a contactos. Para enviar rápidamente copias de esta plantilla a varios firmantes, por favor, asegúrate de que al menos un rol no esté vinculado a ningún contacto.",
|
||||
"quick-send-alert-2": "Por favor, asegúrate de que hay al menos un widget de firma añadido para cada destinatario.",
|
||||
"quick-send-alert-3": "Veuillez ajouter au moins un rôle à ce template.",
|
||||
"quick-send-alert-4": "Se alcanzó el límite para {{flow}}.",
|
||||
"quick-send-alert-4": "{{flow}} admite hasta {{count}} firmantes por lote. Divida su lista e inténtelo nuevamente.",
|
||||
"copy-link": "Copiar enlace",
|
||||
"copy": "Copiar",
|
||||
"revoke-document": "Revocar documento",
|
||||
@@ -394,7 +423,10 @@
|
||||
"image": "imagen",
|
||||
"email": "correo",
|
||||
"number": "número",
|
||||
"draw": "Dibujar"
|
||||
"draw": "Dibujar",
|
||||
"attachments": "adjuntos",
|
||||
"attachments-add": "Clic para subir archivos adjuntos",
|
||||
"attachments-count": "{{count}} adjunto(s)"
|
||||
},
|
||||
"fields": "Campos",
|
||||
"recipients": "Destinatarios",
|
||||
@@ -406,6 +438,14 @@
|
||||
"certificate": "Certificado",
|
||||
"decline": "Rechazar",
|
||||
"finish": "Finalizar",
|
||||
"approve": "Aprobar",
|
||||
"approve-mssg": " ¿Está seguro de que desea aprobar el documento?",
|
||||
"signer-role-label": "Rol del destinatario",
|
||||
"signer-role-signer": "Firmante",
|
||||
"signer-role-viewer": "Visualizador",
|
||||
"signer-role-approver": "Aprobador",
|
||||
"signer-role-hint": "El firmante debe firmar. El aprobador aprueba sin firmar. El visualizador solo visualiza.",
|
||||
"signer-role-upgrade-msg": "Los roles de Visualizador y Aprobador solo están disponibles en los planes de pago. Actualice su plan para asignar estos roles a los destinatarios.",
|
||||
"done": "Hecho",
|
||||
"mail": "Correo",
|
||||
"sign-now": "Firmar ahora",
|
||||
@@ -438,14 +478,15 @@
|
||||
"reset-password-alert-1": "El enlace para restablecer la contraseña ha sido enviado a tu correo electrónico",
|
||||
"reset-password-alert-2": "Por favor, configura un adaptador de correo ",
|
||||
"reset-password-alert-3": "Restablece tu contraseña",
|
||||
"faild-animation": "Error al cargar la animación",
|
||||
"failed-animation": "Error al cargar la animación",
|
||||
"apply": "Aplicar",
|
||||
"select-columns": "Seleccionar columnas",
|
||||
"copy-type": {
|
||||
"All pages": "Todas las páginas",
|
||||
"All pages but last": "Todas las páginas menos la última",
|
||||
"All pages but first": "Todas las páginas menos la primera",
|
||||
"Next to current widget": "Junto al widget actual"
|
||||
"Next to current widget": "Junto al widget actual",
|
||||
"Page range": "Rango de páginas"
|
||||
},
|
||||
"options": "Opciones",
|
||||
"minimum-check": "Chequeo mínimo",
|
||||
@@ -462,6 +503,19 @@
|
||||
"formula-error-braces": "Cada {{ de apertura debe tener una }} correspondiente.",
|
||||
"formula-error-operators": "Cuando uses operadores o paréntesis, agrega al menos dos valores.",
|
||||
"formula-error-leading-operator": "Las fórmulas no pueden comenzar con operadores.",
|
||||
"formula-error-trailing-operator": "Las fórmulas no pueden terminar con un operador.",
|
||||
"formula-error-adjacent-placeholders": "No se pueden escribir dos marcadores de posición seguidos sin un operador entre ellos.",
|
||||
"formula-error-continuous-operators": "No se pueden usar dos operadores seguidos.",
|
||||
"formula-error-empty-parentheses": "Los paréntesis no pueden estar vacíos.",
|
||||
"formula-error-invalid-parentheses-content": "Los paréntesis no pueden empezar ni terminar con un operador.",
|
||||
"formula-error-before-open-parenthesis": "Falta un operador antes de '('. Añada un operador antes del paréntesis de apertura.",
|
||||
"formula-error-after-close-parenthesis": "Falta un operador después de ')'. Añada un operador como '+' o '*' después del paréntesis de cierre.",
|
||||
"formula-error-between-parentheses": "Falta un operador entre ')' y '('. Añada un operador como '*' entre los dos grupos.",
|
||||
"formula-error-division-by-zero": "No se permite la división entre cero. Reemplace '0' por un número o variable válido distinto de cero.",
|
||||
"formula-error-invalid-number-format": "Se encontró un formato de número no válido. Los números deben estar en un formato válido como '1' o '3.14', no '.5', '1.' ni '1.2.3'.",
|
||||
"formula-error-unclosed-parenthesis": "Se encontró un '(' sin cerrar. Cada paréntesis de apertura '(' debe tener un paréntesis de cierre ')' correspondiente.",
|
||||
"formula-error-unexpected-closing-parenthesis": "Se encontró un paréntesis de cierre ')' inesperado sin un paréntesis de apertura '(' correspondiente.",
|
||||
"formula-error-empty-placeholder": "No se permite un marcador de posición vacío '{{}}'. Proporcione un nombre de variable dentro de las llaves.",
|
||||
"decimal-places": "Decimales",
|
||||
"number-of-decimal-places": "Número de decimales",
|
||||
"decimal-places-helper": "Establecer en 0 para números enteros",
|
||||
@@ -530,6 +584,7 @@
|
||||
"sign-url": "URL de firma",
|
||||
"mails-sent": "Correos enviados",
|
||||
"mail-not-delivered": "correo no entregado",
|
||||
"email-not-verified-head": "Correo no verificado",
|
||||
"document-alert": "Alerta de documento",
|
||||
"owner-subscription-expired": "La suscripción del propietario ha expirado.",
|
||||
"owner-doesnt-have-paid-plan": "El propietario no tiene un plan de pago.",
|
||||
@@ -605,6 +660,9 @@
|
||||
"document-signed-alert-7": "Este documento ya no está disponible.",
|
||||
"document-signed-alert-8": "Este documento ya ha sido enviado para ser firmado.",
|
||||
"yet-to-sign": "Todavía por firmar",
|
||||
"viewers": "Visualizadores",
|
||||
"viewer-no-widgets": "Los visualizadores reciben una copia de solo lectura del documento y no pueden tener widgets asignados.",
|
||||
"viewer-confirm-widget-removal": "Al cambiar este destinatario a Visualizador se eliminarán todos los widgets ya asignados. ¿Continuar?",
|
||||
"validation-alert-1": "Los datos ingresados no cumplen con los criterios establecidos por la expresión regular.",
|
||||
"account-already-exists": "¡La cuenta ya existe!",
|
||||
"choose-password": "Elegir contraseña",
|
||||
@@ -627,7 +685,7 @@
|
||||
"date": "Fecha",
|
||||
"report-not-found": "Reporte no encontrado",
|
||||
"please-wait": "por favor, espera",
|
||||
"select-foler": "Seleccionar carpeta",
|
||||
"select-folder": "Seleccionar carpeta",
|
||||
"generated-on": "Generado el",
|
||||
"text-field": "Campo de texto",
|
||||
"font-size": "Tamaño de la fuente",
|
||||
@@ -686,13 +744,13 @@
|
||||
"public-template": "Este video muestra cómo puedes configurar tu perfil público personalizado, como «https://opensign.me/tu-nombre-de-usuario». También aprenderás cómo personalizar tu eslogan y cómo hacer tus plantillas disponibles para su firmado público.",
|
||||
"allowModify-widgets": "Puede arrastrar y soltar cualquiera de estos campos en el documento, además de los campos ya designados para usted por el creador del documento"
|
||||
},
|
||||
"enter-email-plaholder": "Agrega una dirección de correo y presiona «Enter»",
|
||||
"enter-email-placeholder": "Agrega una dirección de correo y presiona «Enter»",
|
||||
"success-email-alert": "¡Correo enviado exitosamente!",
|
||||
"expired-doc-title": "Documento expirado",
|
||||
"expired-on-mssg": "Este documento expiro el {{expiredDate}} y ya no está disponible para ser firmado.",
|
||||
"signature-validate-alert": "Por favor, confirma que has seleccionado al menos {{minRequiredCount}} casillas.",
|
||||
"signature-validate-alert-2": "Asegúrate de que este campo esté correctamente rellenado y que cumpla con todos los requisitos.",
|
||||
"remaing-users": "Usuarios restantes",
|
||||
"remaining-users": "Usuarios restantes",
|
||||
"Role": "Rol",
|
||||
"additional-users": "Por favor, compra el complemento Usuarios.",
|
||||
"Quantity-of-users": "Cantidad de usuarios",
|
||||
@@ -714,7 +772,7 @@
|
||||
"rotate-left": "Rotar a la izquierda",
|
||||
"rotate-alert-mssg": "Todos los widgets de esta página se perderán. ¿En definitiva quieres proceder?",
|
||||
"templateid": "ID de plantilla",
|
||||
"bulk-send-subcription-alert": "Por favor, mejora al plan a Professional o Team para usar envío en masa.",
|
||||
"bulk-send-subscription-alert": "Por favor, mejora al plan a Professional o Team para usar envío en masa.",
|
||||
"generate-test-token": "Generar token de prueba",
|
||||
"regenerate-test-token": "Regenerar token de prueba",
|
||||
"help-test-token": "Este token puede ser usado para probar las API en el punto de acceso «https://sandbox.opensignlabs.com/api/v1», permitiéndote realizar firmas de documentos ilimitadas. Por favor, ten en cuenta que el API de pruebas firmará tus documentos con certificados autofirmados, que podrían no ser reconocidos por Adobe como válidos. Una vez hayas completado tus pruebas, puedes mejorar a uno de nuestros planes de pago para generar un token de producción.",
|
||||
@@ -734,8 +792,9 @@
|
||||
"copied-code": "COPIADO",
|
||||
"Installation": "Instalación",
|
||||
"Usage": "Uso",
|
||||
"insufficient-credits": "Créditos de firma insuficientes",
|
||||
"insufficient-credits-mssg": "El propietario de este documento actualmente carece de los créditos OpenSign necesarios para que puedas firmarlo. Por favor, contacta al propietario si necesitas más detalles.",
|
||||
"insufficient-credits": "Créditos de firma insuficientes. Por favor contacte con {{ownerEmail}}",
|
||||
"insufficient-credits-title": "Créditos de firma insuficientes",
|
||||
"insufficient-credits-mssg": "El propietario de este documento actualmente carece de los créditos {{appName}} necesarios para que puedas firmarlo. Por favor, contacta al propietario si necesitas más detalles.",
|
||||
"angular-npm-mssg-1": "Para integrar OpenSign a tu proyecto Angular, simplemente ejecuta los siguientes comandos:",
|
||||
"quota-mail-info-head": "Límite mensual de correos electrónicos",
|
||||
"quota-mail-info": "Para mantener la calidad del servicio y prevenir el spam, OpenSign permite hasta 15 correos electrónicos por mes en el plan gratuito. Mejora tu plan ahora para envíos ilimitados de correos electrónicos.",
|
||||
@@ -754,6 +813,13 @@
|
||||
"p3": "Seleccionar esta opción deshabilitará la verificación con OTP, lo cual permitirá a los usuarios firmar el documento directamente sin pasos adicionales.",
|
||||
"p4": "Por favor, selecciona la opción que mejor se adapte a tus requisitos para la firma de documentos."
|
||||
},
|
||||
"allow-offline-sign": "Permitir firma sin conexión",
|
||||
"allow-offline-sign-help": {
|
||||
"p1": "Decide si los firmantes pueden enviar una copia de este documento firmada con una herramienta externa para que el propietario la revise.",
|
||||
"p2": "Los firmantes verán una opción Firmar sin conexión y podrán subir un PDF firmado externamente para su aprobación.",
|
||||
"p3": "Los firmantes solo podrán firmar dentro de la aplicación. La opción Firmar sin conexión se ocultará.",
|
||||
"p4": "Esta configuración usa por defecto tu preferencia de cuenta y está disponible en planes de pago."
|
||||
},
|
||||
"advanced-options": "Opciones avanzadas",
|
||||
"hide-advanced-options": "Ocultar las opciones avanzadas",
|
||||
"download-files": "Descargar archivos",
|
||||
@@ -816,6 +882,7 @@
|
||||
"delete-folder-alert-1": "No se puede eliminar: la carpeta debe estar vacía",
|
||||
"defaultSign-alert": "Los widgets {{widgetsType}} no están disponibles para la firma automática",
|
||||
"embed-heading": "Incrustar plantillas de {{appName}}",
|
||||
"other-apps-by-opensign": "Otras aplicaciones del equipo de OpenSign",
|
||||
"public-template-edit-title": "Editar plantilla pública",
|
||||
"public-template-edit-mssg": "No puede editar una plantilla pública directamente. Para realizar cambios, la plantilla se configurará como privada y luego tendrás que volver a hacerla pública. ¿Quieres continuar?",
|
||||
"agree-p1": "Confirmo que he leído y comprendido el ",
|
||||
@@ -867,7 +934,6 @@
|
||||
"js-snippet-msg-1": "Puede obtener el TemplateId en la página Administrar plantillas",
|
||||
"js-snippet-msg-2": "Administrar plantillas",
|
||||
"js-snippet-msg-3": "página.",
|
||||
"agrrement-alert": "Para continuar, debe consentir la divulgación de registros y firmas electrónicas.",
|
||||
"webhook-already-exists": "¡La URL ya existe! Pruebe con uno diferente.",
|
||||
"webhook-must-be-secure": "La URL del webhook debe ser segura y utilizar https://",
|
||||
"provide-test-token": "Por favor genere un token de prueba de API",
|
||||
@@ -881,6 +947,7 @@
|
||||
"p3": "Seleccionar esta opción restringe a los firmantes de realizar cualquier adición al documento durante el proceso de firma."
|
||||
},
|
||||
"signature-setting": "Configuración de firmas",
|
||||
"rotation": "Rotación",
|
||||
"signyour-self-button": "Utilice este formulario para crear plantillas reutilizables para sus documentos.",
|
||||
"requestsign-button": "Utilice esta opción para solicitar firmas de otras personas y de usted mismo en conjunto.",
|
||||
"signyour-self-description": "Utilice este formulario para firmar el documento usted mismo sin agregar otros.",
|
||||
@@ -915,6 +982,8 @@
|
||||
"visit-below-link": "Visita el siguiente enlace para saber más -",
|
||||
"storage-help": "Habilitar BYOC te permite conectar tu propio almacenamiento S3 para que tus archivos permanezcan completamente bajo tu control, sin copias externas retenidas. Si la autonomía de los datos es importante para ti, considera actualizar a Teams para desbloquear esta función.",
|
||||
"daily-quota-reached": "Ha alcanzado su cuota diaria. Para obtener ayuda, comuníquese con quotas@opensignlabs.com.",
|
||||
"email-not-verified-send": "Por favor, verifica tu dirección de correo antes de enviar correos. Ve a tu <1>configuración de perfil</1> para completar la verificación del correo.",
|
||||
"email-not-verified-send-plain": "Por favor, verifica tu dirección de correo antes de enviar correos. Ve a la configuración de tu perfil para completar la verificación del correo.",
|
||||
"enabled-signature-type": "Tipos de firma habilitados",
|
||||
"enabled-signature-type-help": "La configuración de 'Tipos de firma habilitados' determina qué opciones de firma están disponibles en su organización. Por ejemplo, si desactiva la opción 'Dibujar', los miembros de su organización no la verán en el widget de firma, mientras que las otras tres opciones seguirán siendo accesibles.",
|
||||
"indexing-public-profile": "Permitir la indexación del perfil público por los motores de búsqueda",
|
||||
@@ -942,6 +1011,7 @@
|
||||
"please-provide-templateid": "Por favor, proporcione templateid",
|
||||
"this-template-is-not-public": "Esta template no es pública",
|
||||
"invalid-templateid": "templateid no válida",
|
||||
"kiosk-no-active-role": "Esta plantilla no tiene roles de firmante o aprobador disponibles para la firma en modo kiosko.",
|
||||
"contact-billing-at-opensign": "Para agregar más asientos, comuníquese con OpenSign™ a <1>billing@opensignlabs.com</1> para obtener ayuda.",
|
||||
"title-length-alert": "El título debe tener como máximo 250 caracteres.",
|
||||
"note-length-alert": "La nota debe tener como máximo 200 caracteres.",
|
||||
@@ -1031,7 +1101,7 @@
|
||||
},
|
||||
"signer-already-present": "Firmante ya presente",
|
||||
"kiosk-sign": "Firma en quiosco",
|
||||
"dont-have-access-to-template": "El template ha sido eliminado o no tiene acceso. Por favor, contacte al remitente.",
|
||||
"do-not-have-access-to-template": "El template ha sido eliminado o no tiene acceso. Por favor, contacte al remitente.",
|
||||
"kiosk-info": "El Modo Kiosco le permite recopilar firmas en persona de forma rápida y eficiente. Ideal para ferias, eventos o situaciones con personas que firman en el lugar. ",
|
||||
"learn-more": "Más información",
|
||||
"finish-mssg": "¿Está seguro de que desea finalizar el documento?",
|
||||
@@ -1085,8 +1155,6 @@
|
||||
"search-templates": "Buscar plantillas…",
|
||||
"search-documents": "Buscar documentos…",
|
||||
"search-contacts": "Buscar contactos…",
|
||||
"invalid-email-found": "Correo electrónico no válido encontrado: {{email}}",
|
||||
"duplicate-email-found": "Correo electrónico duplicado encontrado: {{email}}",
|
||||
"vertical": "Vertical",
|
||||
"add-role-alert": "Por favor, agregue al menos un rol",
|
||||
"edit-draft": "Editar borrador",
|
||||
@@ -1102,7 +1170,7 @@
|
||||
"access-denied": "Acceso denegado",
|
||||
"upgrade": "Actualizar",
|
||||
"do-not-access-app": "No tienes acceso a esta aplicación.",
|
||||
"dont-have-access": "No tienes acceso.",
|
||||
"do-not-have-access": "No tienes acceso.",
|
||||
"valid-email-alert": "Por favor ingresa una dirección de correo electrónico válida.",
|
||||
"otp-not-validate": "OTP no es válido.",
|
||||
"domain-not-allowed": "Este dominio no está permitido",
|
||||
@@ -1119,10 +1187,12 @@
|
||||
"branding-help": "El branding permite el white labelling de su aplicación",
|
||||
"custom-sub-domain": "Subdominio personalizado",
|
||||
"app-name": "Nombre de la aplicación",
|
||||
"provide-domain-name": "Proporcione su nombre de dominio",
|
||||
"provide-domain-name": "Solo se permiten subdominios (p. ej., app.tudominio.com). No se admiten dominios principales/raíz como tudominio.com.",
|
||||
"provide-app-name-help": "Indique un nombre único para la aplicación. Se mostrará como el nombre de su marca dentro de la aplicación y en todos los correos electrónicos donde se aplique la marca.",
|
||||
"upload-app-logo": "Sube un logotipo en formato .png, .jpg o .svg. Tamaño recomendado: {{size}} píxeles (relación de aspecto 5:2) para la mejor calidad de visualización.",
|
||||
"upload-app-favicon": "Sube una imagen cuadrada de favicon (.ico, .png o .svg). El tamaño recomendado es {{size}} píxeles para la mejor compatibilidad con el navegador.",
|
||||
"provide-app-name": "Proporcione el nombre de su aplicación",
|
||||
"logo": "Logo",
|
||||
"upload-app-logo": "Suba el logotipo de su aplicación",
|
||||
"prefill-unfilled-widget": "Los siguientes campos obligatorios no pueden quedar vacíos: {{emptyWidget}}. Por favor, complételos para continuar.",
|
||||
"Dashboard": "Panel",
|
||||
"Analytics": "Analítica",
|
||||
@@ -1290,11 +1360,11 @@
|
||||
"signer-form-data-for": "Datos del formulario para el firmante: {{signerName}} <{{signerEmail}}>",
|
||||
"widget-name": "Nombre del widget",
|
||||
"widget-responses": "Respuestas del widget",
|
||||
"formdata-download-note": "Nota: Las firmas, sellos, iniciales e imágenes no se exportarán.",
|
||||
"formdata-download-note": "Nota: Las firmas, sellos, iniciales e imágenes no se exportarán. Los datos de los firmantes sin conexión no estarán disponibles.",
|
||||
"export-all-formdata-error": "No puede exportar los datos del formulario porque la plantilla contiene nombres de widgets duplicados. Cree una nueva plantilla para poder exportar los datos.",
|
||||
"export-formdata-error": "No puede exportar los datos del formulario porque el documento contiene nombres de widgets duplicados. Cree un documento nuevo sin nombres duplicados para poder exportar los datos.",
|
||||
"duplicate-widget-name-error": "Este nombre de widget ya está asignado a otro firmante. Por favor elija un nombre diferente.",
|
||||
"duplicate-template-widget-error": "No puede duplicar esta plantilla porque contiene nombres de widgets duplicados.",
|
||||
"duplicate-template-widget-error": "No se puede duplicar la plantilla porque algunos nombres de widgets están duplicados en diferentes roles. Cambie el nombre de los widgets duplicados e inténtelo de nuevo.",
|
||||
"credit-alert": "Alerta de crédito",
|
||||
"document-form-data": "Datos del formulario del documento",
|
||||
"view-formdata-paid-title-error": "La función 'Ver datos del formulario' le permite imprimir y descargar los datos de los widgets completados por los firmantes durante la firma.",
|
||||
@@ -1346,8 +1416,8 @@
|
||||
"file-selected": "archivo seleccionado",
|
||||
"bulk-send-csv-helper": "Use encabezados para los roles de firmantes (Correo/Nombre/Teléfono del rol) y valores de prellenado (Prellenado: Nombre del campo). Los campos de prellenado obligatorios deben completarse.",
|
||||
"bulk-send-csv-helper-note": "Nota: Para los campos de prellenado de tipo fecha en el CSV, el valor debe proporcionarse en el siguiente formato: {{value}} (ejemplo: {{example}}).",
|
||||
"bulk-send-csv-summary": "{{total}} filas importadas. {{invalid}} filas no válidas fueron omitidas.",
|
||||
"bulk-send-csv-missing-headers": "Faltan encabezados obligatorios del CSV. Roles: {{roles}}. Prellenado: {{prefill}}.",
|
||||
"bulk-send-csv-summary": "Se importaron {{total}} filas del CSV.",
|
||||
"bulk-send-csv-missing-headers": "Faltan encabezados obligatorios del CSV. Roles: {{roles}}.",
|
||||
"bulk-send-no-records": "Agregue al menos un destinatario antes de enviar.",
|
||||
"csv-only-support": "Solo se admiten archivos CSV.",
|
||||
"csv-no-records": "No se encontraron registros en el CSV.",
|
||||
@@ -1361,5 +1431,184 @@
|
||||
"enter-daily-email-quota": "Ingrese la cuota diaria de correo electrónico",
|
||||
"enter-custom-regular-expression": "Ingrese una expresión regular personalizada",
|
||||
"enter-value": "Ingrese {{value}}",
|
||||
"are-you-sure-want-to-send-document": "¿Está seguro de que desea enviar el documento?"
|
||||
}
|
||||
"are-you-sure-want-to-send-document": "¿Está seguro de que desea enviar el documento?",
|
||||
"favicon": "Favicon",
|
||||
"delete-two-factor-authentication": "¿Está seguro de que desea eliminar la autenticación de dos factores?",
|
||||
"paste-html-here": "Pegar HTML aquí",
|
||||
"open-email-builder": "Puede crear una plantilla de correo electrónico utilizando la plataforma <1>email builder</1> y copiar el código HTML.",
|
||||
"contact-saved": "Contacto guardado correctamente.",
|
||||
"remove-limit-of-user": "¿Está seguro de que desea eliminar el límite de este usuario? Podrá consumir todos los créditos premium disponibles de su organización que se comparten entre todos los usuarios.",
|
||||
"current-expiry-date": "Fecha de vencimiento actual",
|
||||
"reminder-error": "“Recordar una vez cada (días)” no debe exceder el “Tiempo para completar (días)”.",
|
||||
"showing-to-of-records": "Mostrando {{record}} a {{recordPerPage}} de {{totalRows}} registros",
|
||||
"records-per-page": "Registros por página",
|
||||
"email-not-found-in-row": "No se encontró correo electrónico en la fila {{row}}.",
|
||||
"invalid-email-found-in-row": "Correo electrónico no válido encontrado en la fila {{row}}: {{email}}",
|
||||
"duplicate-email-found-in-row": "Correo electrónico duplicado encontrado en la fila {{row}}: {{email}}",
|
||||
"prefill": "Prellenado",
|
||||
"recipients-&-data": "Destinatarios y datos",
|
||||
"response": "Respuesta",
|
||||
"prefill-tab-help": "Estos son los campos comunes de la plantilla que se aplicarán a todos los documentos en este envío masivo.",
|
||||
"recipient-tab-help-p1": "En este siguiente paso, usted:",
|
||||
"recipient-tab-help-p2": "Subirá un CSV con los detalles de los destinatarios o",
|
||||
"recipient-tab-help-p3": "Ingresará manualmente la información de los destinatarios",
|
||||
"recipient-tab-help-p4": "Cada destinatario recibirá su propio documento generado a partir de esta plantilla.",
|
||||
"response-tab-help": "Revise sus respuestas.",
|
||||
"summary": "Resumen",
|
||||
"prefill-fields": "Campos de prellenado",
|
||||
"documents": "Documentos",
|
||||
"message": "Mensaje",
|
||||
"formula-help-p1": "Recopile entradas numéricas y realice cálculos dinámicos usando fórmulas.",
|
||||
"example": "Ejemplo",
|
||||
"formula-help-p2": "Para mostrar un total en number-3, use {{number-1}} + {{number-2}}.",
|
||||
"formula-help-p3": "Para calcular un valor en number-4, use ({{number-1}} * {{number-2}}) / {{number-3}}.",
|
||||
"formula-help-p4": "Cada widget numérico debe tener un nombre único (por ejemplo, number-1), ya que las fórmulas dependen de estos identificadores.",
|
||||
"note-consume-premium-credits": "Nota: Esta función consume créditos premium.",
|
||||
"note-consume-premium-credits-on-document-send": "Nota: Esta función consume créditos premium. Se consumen cada vez que se envía un documento, independientemente de si se firma o no.",
|
||||
"webhook-authentication-help": "{{appName}} protege cada webhook generando una firma HMAC-SHA256 utilizando su clave de seguridad del webhook y el payload original de la solicitud. Esta firma se envía en el encabezado x-webhook-signature. Su servidor debe generar la misma firma utilizando el payload recibido y su clave secreta. Si ambas firmas coinciden, la solicitud del webhook es auténtica y no ha sido modificada.",
|
||||
"invalid-widget-found-in-row": "Se detectó un valor no válido en la fila {{row}} para el widget {{widget}}.",
|
||||
"all-signer-status": "Estado de todos los firmantes",
|
||||
"viewed": "Visto",
|
||||
"signed": "Firmado",
|
||||
"switch-to-advanced": "Cambiar a avanzado",
|
||||
"switch-to-basic": "Cambiar a básico",
|
||||
"use-name-as-sender": "Usar el nombre del perfil como remitente",
|
||||
"use-name-as-sender-help": "Cuando está activado, {{appName}} usa el nombre de tu perfil como remitente para las solicitudes de firma y el correo de finalización.",
|
||||
"acro-fields-detected": "Campos de formulario detectados",
|
||||
"acro-fields-detected-msg": "Se detectaron {{count}} campo(s) de formulario en el PDF cargado. ¿Desea insertar automáticamente los widgets correspondientes?",
|
||||
"documents-count": "Cantidad de documentos",
|
||||
"templates-count": "Cantidad de plantillas",
|
||||
"emails-sent": "Correos enviados",
|
||||
"storage-used": "Almacenamiento usado",
|
||||
"docs-count-team-help": "Número total de documentos creados por todos los usuarios en tus organizaciones.",
|
||||
"docs-count-help": "Número total de documentos creados por ti.",
|
||||
"templates-count-team-help": "Número total de plantillas creadas por todos los usuarios en tus organizaciones.",
|
||||
"templates-count-help": "Número total de plantillas creadas por ti.",
|
||||
"emails-sent-team-help": "Número total de notificaciones por correo enviadas en tus organizaciones.",
|
||||
"emails-sent-help": "Número total de notificaciones por correo enviadas por ti.",
|
||||
"storage-used-team-help": "Espacio total de almacenamiento consumido por todos los usuarios en tus organizaciones.",
|
||||
"storage-used-help": "Espacio total de almacenamiento consumido por ti.",
|
||||
"cloud": "Nube",
|
||||
"on-premises": "En las instalaciones",
|
||||
"plan-name-free": "OPENSIGN™ FREE",
|
||||
"plan-name-professional": "OPENSIGN™ PROFESSIONAL",
|
||||
"plan-name-teams": "OPENSIGN™ TEAMS",
|
||||
"plan-name-enterprise": "OPENSIGN™ ENTERPRISE",
|
||||
"plan-name-self-hosted": "OPENSIGN™ SELF-HOSTED",
|
||||
"plan-subtitle-free": "Firmas electrónicas ilimitadas gratuitas, para siempre.",
|
||||
"plan-subtitle-professional": "Acceso exclusivo a funciones avanzadas.",
|
||||
"plan-subtitle-scalable": "Funciones escalables con soporte prioritario.",
|
||||
"subscribe": "Suscribirse",
|
||||
"contact-us": "Contáctenos",
|
||||
"benefit-unlimited-digital-signatures": "Firmas digitales ilimitadas",
|
||||
"benefit-sign-docs-yourself": "Firmar documentos usted mismo",
|
||||
"benefit-request-signatures": "Solicitar firmas de otros",
|
||||
"benefit-unlimited-templates": "Plantillas ilimitadas",
|
||||
"benefit-14-field-types": "14 tipos de campo",
|
||||
"benefit-automatic-e-signatures": "Firmas electrónicas automáticas",
|
||||
"benefit-completion-certificates": "Certificados de finalización",
|
||||
"benefit-send-in-order": "Enviar en orden",
|
||||
"benefit-organize-docs": "Organizar documentos en OpenSign™ Drive",
|
||||
"benefit-document-templates": "Plantillas de documentos",
|
||||
"benefit-import-from-dropbox": "Importar desde Dropbox",
|
||||
"benefit-contact-book": "Libreta de contactos",
|
||||
"benefit-document-expiry": "Compatibilidad con vencimiento de documentos",
|
||||
"benefit-decline-document": "Compatibilidad con rechazo de documentos",
|
||||
"benefit-email-notifications": "Notificaciones por correo electrónico",
|
||||
"benefit-public-profiles": "Perfiles públicos",
|
||||
"benefit-and-much-more": "Y mucho más",
|
||||
"benefit-everything-free": "Todo en OpenSign™ Free",
|
||||
"benefit-field-validations": "Validaciones de campo",
|
||||
"benefit-regex-validations": "Validaciones de expresiones regulares",
|
||||
"benefit-webhooks": "Webhooks",
|
||||
"benefit-zapier": "Integración con Zapier",
|
||||
"benefit-api-access": "Acceso a API",
|
||||
"benefit-api-signatures-100": "hasta 100 firmas de API",
|
||||
"benefit-api-signatures-240": "hasta 240 firmas de API",
|
||||
"benefit-custom-email-templates": "Plantillas de correo electrónico personalizadas",
|
||||
"benefit-custom-smtp": "Conecte su propia cuenta de Gmail o SMTP para enviar correos",
|
||||
"benefit-auto-reminders": "Recordatorios automáticos",
|
||||
"benefit-bulk-send-100": "Envío masivo (hasta 100 documentos)",
|
||||
"benefit-bulk-send-240": "Envío masivo (hasta 240 documentos)",
|
||||
"benefit-premium-profiles": "Nombres de usuario de perfil público premium",
|
||||
"benefit-email-verification": "Aplicar verificación basada en correo electrónico para confirmar la identidad del firmante",
|
||||
"benefit-embedded-signing": "Firma incrustada",
|
||||
"benefit-everything-professional": "Todo en OpenSign™ Professional",
|
||||
"benefit-teams-organizations": "Equipos y Organizaciones",
|
||||
"benefit-share-templates-teams": "Compartir plantillas con equipos",
|
||||
"benefit-share-templates-individuals": "Compartir plantillas con individuos",
|
||||
"benefit-byoc": "BYOC - Almacena tus documentos en tu propio almacenamiento en la nube",
|
||||
"benefit-doc-id-removal": "Eliminación de DocumentId de documentos firmados",
|
||||
"benefit-request-payments": "Solicitar pagos (próximamente)",
|
||||
"benefit-mobile-app": "Aplicación móvil (próximamente)",
|
||||
"benefit-api-signatures-500": "hasta 500 firmas de API",
|
||||
"benefit-bulk-send-500": "Envío masivo (hasta 500 documentos)",
|
||||
"benefit-all-features": "Todas las funciones",
|
||||
"benefit-custom-domain": "Dominio personalizado",
|
||||
"benefit-custom-branding": "Marca personalizada",
|
||||
"benefit-uptime-sla": "SLA de tiempo de actividad",
|
||||
"benefit-sso-support": "Soporte SSO",
|
||||
"benefit-priority-support": "Soporte prioritario",
|
||||
"benefit-3-users-1500-credits": "Incluye 3 usuarios y 1500 créditos premium",
|
||||
"benefit-public-links": "Enlaces públicos",
|
||||
"benefit-kiosk-mode": "Modo quiosco",
|
||||
"benefit-bulk-send-1500": "Envío masivo (hasta 1500 documentos)",
|
||||
"benefit-connect-gmail-smtp": "Conecte su propio Gmail o SMTP",
|
||||
"benefit-analytics": "Análisis",
|
||||
"benefit-bulksend-api-pro-month-help": "Este plan incluye 100 créditos premium al mes que te permiten enviar documentos mediante la API, compartir enlaces públicos de firma, ejecutar solicitudes de firma masiva, usar el modo Kiosco para firmas presenciales o recopilar firmas de documentos incrustados directamente en tu sitio web. Cada vez que inicias o envías un documento, se usa un crédito — y ese mismo documento puede incluir hasta 10 firmantes.",
|
||||
"benefit-bulksend-api-pro-year-help": "Este plan incluye 240 créditos premium al año que te permiten enviar documentos mediante la API, compartir enlaces públicos de firma, ejecutar solicitudes de firma masiva, usar el modo Kiosco para firmas presenciales o recopilar firmas de documentos incrustados directamente en tu sitio web. Cada vez que inicias o envías un documento, se usa un crédito — y ese mismo documento puede incluir hasta 10 firmantes.",
|
||||
"benefit-bulksend-api-team-month-help": "Este plan incluye 100 créditos premium al mes que te permiten enviar documentos mediante la API, compartir enlaces públicos de firma, ejecutar solicitudes de firma masiva, usar el modo Kiosco para firmas presenciales o recopilar firmas de documentos incrustados directamente en tu sitio web. Cada vez que inicias o envías un documento, se usa un crédito — y ese mismo documento puede incluir hasta 10 firmantes.",
|
||||
"benefit-bulksend-api-team-year-help": "Este plan incluye 500 créditos premium al año que te permiten enviar documentos mediante la API, compartir enlaces públicos de firma, ejecutar solicitudes de firma masiva, usar el modo Kiosco para firmas presenciales o recopilar firmas de documentos incrustados directamente en tu sitio web. Cada vez que inicias o envías un documento, se usa un crédito — y ese mismo documento puede incluir hasta 10 firmantes.",
|
||||
"failed-pdf-for-prefill-processing": "No se pudo cargar el PDF para el procesamiento de prellenado.",
|
||||
"attach-all-role-to-signer": "Por favor, asigna todos los roles al firmante.",
|
||||
"upgrade-to-edit": "Actualiza para editar",
|
||||
"hide-live-chat-for-signers": "Ocultar chat en vivo para firmantes",
|
||||
"hide-live-chat-for-signers-help": "Cuando está activado, los widgets de chat en vivo se ocultarán para los firmantes en las páginas de firma.",
|
||||
"attachments-creator-hint": "Los firmantes pueden subir archivos que se añadirán al final del documento.",
|
||||
"attachments-drop-hint": "Arrastra archivos aquí o haz clic para seleccionar",
|
||||
"attachments-limits": "PDF, DOCX, PNG, JPG. Hasta {{maxFiles}} archivos, {{maxSizeMB}} MB cada uno.",
|
||||
"attachments-invalid-type": "Tipo de archivo no admitido: {{name}}",
|
||||
"attachments-max-files": "Se permiten hasta {{maxFiles}} adjuntos.",
|
||||
"attachments-remove-confirm": "Eliminar solo borra los metadatos. Las páginas ya añadidas permanecerán en el documento. ¿Continuar?",
|
||||
"conditional-logic": "Lógica condicional",
|
||||
"enabled": "Habilitado",
|
||||
"disabled": "Deshabilitado",
|
||||
"logic-no-sources": "No hay otros campos de este firmante que puedan usarse como origen.",
|
||||
"logic-action": "Acción",
|
||||
"logic-when": "Cuando",
|
||||
"logic-all": "todas",
|
||||
"logic-any": "alguna",
|
||||
"logic-of-following": "de las siguientes condiciones se cumplan",
|
||||
"logic-add-rule": "Añadir regla",
|
||||
"logic-pick-value": "Seleccionar un valor",
|
||||
"logic-value-placeholder": "Introducir valor",
|
||||
"logic-action-show": "Mostrar este campo",
|
||||
"logic-action-hide": "Ocultar este campo",
|
||||
"logic-action-require": "Hacer obligatorio",
|
||||
"logic-action-optional": "Hacer opcional",
|
||||
"logic-op-equals": "es igual a",
|
||||
"logic-op-notEquals": "no es igual a",
|
||||
"logic-op-checked": "está marcado",
|
||||
"logic-op-unchecked": "no está marcado",
|
||||
"widget-hidden-due-to-logic": "Este widget está oculto debido a la lógica condicional.",
|
||||
"remove": "Eliminar",
|
||||
"attachement-note": "Nota: Este widget permite a los firmantes cargar un archivo durante el proceso de firma. El archivo cargado se añadirá como páginas adicionales al final del PDF existente.",
|
||||
"attachments-docx-not-supported": "Actualmente estamos experimentando un problema con las cargas de archivos DOCX. Intente cargar el documento en otro formato compatible o vuelva a intentarlo más tarde.",
|
||||
"access-code": "Código de acceso",
|
||||
"access-code-set": "Código de acceso establecido",
|
||||
"access-code-add": "Añadir código de acceso",
|
||||
"access-code-help": "Secreto opcional que solo conoce este firmante. Deberá introducirlo antes de ver o firmar el documento. Compártalo con el destinatario por otro canal (teléfono, en persona).",
|
||||
"access-code-placeholder": "Al menos 6 dígitos",
|
||||
"access-code-clear": "Borrar",
|
||||
"access-code-must-be-numeric": "El código de acceso debe contener solo dígitos 0-9.",
|
||||
"access-code-save": "Guardar",
|
||||
"access-code-required": "El remitente asignó un código de acceso a este destinatario. Introdúzcalo para continuar.",
|
||||
"access-code-invalid": "Código de acceso incorrecto. Consulte con el remitente.",
|
||||
"access-code-too-short": "El código de acceso debe tener al menos 6 dígitos.",
|
||||
"access-code-verify": "Verificar código de acceso",
|
||||
"access-code-verified": "Código de acceso verificado.",
|
||||
"access-code-warning": "Este código de acceso no se puede cambiar ni recuperar una vez que se envía el documento.",
|
||||
"copy-access-code": "Copiar código de acceso",
|
||||
"access-code-upgrade-msg": "Los códigos de acceso solo están disponibles en planes de pago. Actualice su plan para agregar códigos de acceso a los destinatarios.",
|
||||
"already-copied-to-clipboard": "Ya copiado al portapapeles"
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
"password-length": "Le mot de passe doit être de plus de 8 caractères",
|
||||
"password-case": "Le mot de passe doit contenir une lettre majuscule, minuscule et un chiffre ",
|
||||
"password-special-char": "Le mot de passe doit contenir un caractère spécial",
|
||||
"agreee": "Je suis d'accord avec le",
|
||||
"agree": "Je suis d'accord avec le",
|
||||
"term": "Conditions d'utilisation",
|
||||
"subscribe-to-opensign": "Abonnez-vous à la newsletter OpenSign",
|
||||
"register": "S'enregistrer ",
|
||||
@@ -237,11 +237,12 @@
|
||||
"p3": "La sélection de cette option enverra les liens de signature à tous les signataires simultanément. Chaque signataire peut signer le document à sa convenance, que d'autres signataires aient ou non complété leur signature. Cette méthode est plus rapide mais n'impose aucun ordre de signature entre les participants.",
|
||||
"p4": "Sélectionnez l'option qui correspond le mieux aux besoins de votre traitement de documents."
|
||||
},
|
||||
"strict-order": "Imposer l'ordre strict",
|
||||
"strict-order-help": "Lorsqu'il est activé, aucun signataire ne peut signer ou approuver tant que tous les signataires/approbateurs précédents n'ont pas terminé leur action.",
|
||||
"no": "Non",
|
||||
"auto-reminder": "Rappel automatique",
|
||||
"remind-once": "Rappeler une fois tous les (jours)",
|
||||
"next": "Suivant",
|
||||
"select-folder": "Sélectionner le dossier",
|
||||
"OpenSign-drive": "Lecteur {{appName}}",
|
||||
"drive-document-status": {
|
||||
"Completed": "Complété",
|
||||
@@ -261,6 +262,7 @@
|
||||
"signers": "Signataires",
|
||||
"signers-help": "Commencez à saisir le nom d'un contact pour voir les signataires suggérés par vos contacts enregistrés ou en ajouter de nouveaux. Organisez l'ordre de signature en ajoutant des signataires dans l'ordre souhaité. Utilisez le bouton « + » pour inclure les signataires et le « x » pour les supprimer. Chaque signataire recevra un e-mail invité à signer le document dans l'ordre indiqué.",
|
||||
"bcc-help": "Commencez à taper le nom d'un contact pour voir les suggestions parmi vos contacts enregistrés ou en ajouter de nouveaux. Utilisez le bouton '+' pour ajouter un utilisateur et le bouton 'x' pour le supprimer. L'adresse e-mail de l'utilisateur sélectionné sera ajoutée en Bcc (copie carbone invisible). Chaque utilisateur recevra une notification par e-mail une fois le document terminé.",
|
||||
"cc-help": "Commencez à saisir le nom d'un contact pour voir des suggestions à partir de vos contacts enregistrés ou en ajouter de nouveaux. Utilisez le bouton « + » pour ajouter un utilisateur et le « x » pour le supprimer. L'e-mail de l'utilisateur sélectionné sera ajouté en CC (copie carbone). Chaque utilisateur recevra une notification par e-mail une fois le document terminé.",
|
||||
"add-signer": "Ajouter un signataire",
|
||||
"contact-not-found": "Contact introuvable",
|
||||
"add-yourself": "Ajoutez-vous",
|
||||
@@ -296,7 +298,7 @@
|
||||
"edit-team": "Modifier l'équipe",
|
||||
"name-of-team": "Nom de l'équipe",
|
||||
"prev": "précédent",
|
||||
"no-data-avaliable": "pas de données disponibles",
|
||||
"no-data-available": "pas de données disponibles",
|
||||
"add-team": "Ajouter une équipe",
|
||||
"page-not-found": "Page non trouvée",
|
||||
"users-from-teams": "utilisateurs de votre Équipe",
|
||||
@@ -307,12 +309,37 @@
|
||||
"delete-user": "Supprimer l'utilisateur",
|
||||
"delete": "Supprimer",
|
||||
"add-user": "Ajouter un utilisateur",
|
||||
"password-generateed": "Le mot de passe ne sera généré qu'une seule fois ; assurez-vous de le copier.",
|
||||
"password-generated": "Le mot de passe ne sera généré qu'une seule fois ; assurez-vous de le copier.",
|
||||
"Team status": "Statut de l'équipe",
|
||||
"user-deactivated": "Utilisateur désactivé.",
|
||||
"user-activated": "Activé par l'utilisateur.",
|
||||
"public": "Public",
|
||||
"download": "Télécharger",
|
||||
"sign-offline": "Signer hors ligne",
|
||||
"sign-offline-instructions": "Téléchargez le document, signez-le avec un outil externe, puis téléversez la copie signée ici. Le propriétaire du document examinera votre soumission et l'approuvera ou la rejettera.",
|
||||
"sign-offline-step-1": "Télécharger le PDF non signé.",
|
||||
"sign-offline-step-2": "Signer le PDF avec votre outil préféré.",
|
||||
"sign-offline-step-3": "Téléverser le PDF signé pour approbation du propriétaire.",
|
||||
"sign-offline-upload-hint": "Sélectionner le PDF signé pour le soumettre à l'approbation.",
|
||||
"sign-offline-submitted-title": "Soumis pour approbation",
|
||||
"sign-offline-submitted-body": "Le propriétaire du document a été notifié par e-mail pour examiner votre copie signée hors ligne.",
|
||||
"offline-already-submitted-title": "Document soumis hors ligne",
|
||||
"offline-already-submitted-body": "Vous avez déjà soumis ce document pour la vérification de la signature hors ligne. Vous serez notifié une fois qu'il sera approuvé ou rejeté.",
|
||||
"offline-action-approve-title": "Approuver la signature hors ligne",
|
||||
"offline-action-approve-confirm": "Approuver",
|
||||
"offline-action-reject-title": "Rejeter la signature hors ligne",
|
||||
"offline-action-reject-hint": "Fournissez éventuellement un motif. Le signataire sera notifié par e-mail.",
|
||||
"offline-action-reject-reason-placeholder": "Motif (facultatif)",
|
||||
"offline-action-reject-confirm": "Confirmer le rejet",
|
||||
"offline-action-review-title": "Examiner la signature hors ligne",
|
||||
"offline-action-review-hint": "Prévisualisez le PDF signé téléversé par le signataire, puis approuvez-le ou rejetez-le. Aucune action n'est prise avant confirmation.",
|
||||
"offline-action-submitted-at": "Soumis",
|
||||
"offline-action-no-preview": "Aucun aperçu disponible pour cette soumission.",
|
||||
"offline-action-open-in-new-tab": "Ouvrir le PDF dans un nouvel onglet",
|
||||
"offline-action-invalid-link": "Ce lien de révision est invalide ou a expiré.",
|
||||
"offline-action-success": "Action effectuée.",
|
||||
"reject": "Rejeter",
|
||||
"document": "Document",
|
||||
"view": "voir",
|
||||
"make-template-public": "Rendre le modèle public",
|
||||
"make-template-private": "Rendre le modèle privé",
|
||||
@@ -337,7 +364,7 @@
|
||||
"quick-send-alert-1": "Tous les rôles dans ce document sont actuellement liés à des contacts. Pour envoyer rapidement des copies de ce modèle à plusieurs signataires, veuillez vous assurer qu'au moins un rôle n'est lié à aucun contact.",
|
||||
"quick-send-alert-2": "Veuillez vous assurer qu'au moins un widget de signature est ajouté pour tous les destinataires.",
|
||||
"quick-send-alert-3": "Veuillez ajouter au moins un rôle à ce template.",
|
||||
"quick-send-alert-4": "Limite atteint pour {{flow}}.",
|
||||
"quick-send-alert-4": "{{flow}} prend en charge jusqu'à {{count}} signataires par lot. Divisez votre liste et réessayez.",
|
||||
"copy-link": "Copier le lien",
|
||||
"copy": "Copier",
|
||||
"revoke-document": "Révoquer le document",
|
||||
@@ -394,7 +421,10 @@
|
||||
"image": "image",
|
||||
"email": "e-mail",
|
||||
"number": "nombre",
|
||||
"draw": "Dessiner"
|
||||
"draw": "Dessiner",
|
||||
"attachments": "pièces jointes",
|
||||
"attachments-add": "Cliquer pour ajouter des pièces jointes",
|
||||
"attachments-count": "{{count}} pièce(s) jointe(s)"
|
||||
},
|
||||
"fields": "Des champs",
|
||||
"recipients": "Destinataires",
|
||||
@@ -406,6 +436,14 @@
|
||||
"certificate": "Certificat",
|
||||
"decline": "refusé",
|
||||
"finish": "terminé",
|
||||
"approve": "Approuver",
|
||||
"approve-mssg": " Êtes-vous sûr de vouloir approuver le document ?",
|
||||
"signer-role-label": "Rôle du destinataire",
|
||||
"signer-role-signer": "Signataire",
|
||||
"signer-role-viewer": "Lecteur",
|
||||
"signer-role-approver": "Approbateur",
|
||||
"signer-role-hint": "Le signataire doit signer. L'approbateur approuve sans signer. Le lecteur peut uniquement consulter.",
|
||||
"signer-role-upgrade-msg": "Les rôles Lecteur et Approbateur sont uniquement disponibles dans les forfaits payants. Mettez à niveau votre forfait pour attribuer ces rôles aux destinataires.",
|
||||
"done": "Terminé",
|
||||
"mail": "Mail",
|
||||
"sign-now": "Signez maintenant",
|
||||
@@ -438,13 +476,15 @@
|
||||
"reset-password-alert-1": "Le lien de réinitialisation du mot de passe a été envoyé à votre identifiant de messagerie",
|
||||
"reset-password-alert-2": "Veuillez configurer l'adaptateur de messagerie",
|
||||
"reset-password-alert-3": "Réinitialisez votre mot de passe",
|
||||
"faild-animation": "Échec du chargement de l'animation",
|
||||
"failed-animation": "Échec du chargement de l'animation",
|
||||
"apply": "Appliquer",
|
||||
"select-columns": "Sélectionner les colonnes",
|
||||
"copy-type": {
|
||||
"All pages": "Toutes les pages",
|
||||
"All pages but last": "Toutes les pages sauf la dernière",
|
||||
"All pages but first": "Toutes les pages sauf la première",
|
||||
"Next to current widget": "À côté du widget actuel"
|
||||
"Next to current widget": "À côté du widget actuel",
|
||||
"Page range": "Plage de pages"
|
||||
},
|
||||
"options": "Possibilités",
|
||||
"minimum-check": "Vérification minimale",
|
||||
@@ -461,6 +501,19 @@
|
||||
"formula-error-braces": "Chaque {{ ouvrante doit avoir une }} correspondante.",
|
||||
"formula-error-operators": "Lorsque vous utilisez des opérateurs ou des parenthèses, ajoutez au moins deux valeurs.",
|
||||
"formula-error-leading-operator": "Les formules ne peuvent pas commencer par des opérateurs.",
|
||||
"formula-error-trailing-operator": "Les formules ne peuvent pas se terminer par un opérateur.",
|
||||
"formula-error-adjacent-placeholders": "Deux espaces réservés ne peuvent pas être écrits l’un à côté de l’autre sans opérateur entre eux.",
|
||||
"formula-error-continuous-operators": "Deux opérateurs ne peuvent pas être utilisés à la suite.",
|
||||
"formula-error-empty-parentheses": "Les parenthèses ne peuvent pas être vides.",
|
||||
"formula-error-invalid-parentheses-content": "Les parenthèses ne peuvent pas commencer ou se terminer par un opérateur.",
|
||||
"formula-error-before-open-parenthesis": "Opérateur manquant avant '('. Ajoutez un opérateur avant la parenthèse ouvrante.",
|
||||
"formula-error-after-close-parenthesis": "Opérateur manquant après ')'. Ajoutez un opérateur comme '+' ou '*' après la parenthèse fermante.",
|
||||
"formula-error-between-parentheses": "Opérateur manquant entre ')' et '('. Ajoutez un opérateur comme '*' entre les deux groupes.",
|
||||
"formula-error-division-by-zero": "La division par zéro n'est pas autorisée. Remplacez '0' par un nombre ou une variable valide non nul.",
|
||||
"formula-error-invalid-number-format": "Format de nombre invalide détecté. Les nombres doivent être dans un format valide comme '1' ou '3.14', et non '.5', '1.' ou '1.2.3'.",
|
||||
"formula-error-unclosed-parenthesis": "Parenthèse '(' non fermée détectée. Chaque parenthèse ouvrante '(' doit avoir une parenthèse fermante ')' correspondante.",
|
||||
"formula-error-unexpected-closing-parenthesis": "Parenthèse fermante ')' inattendue détectée sans parenthèse ouvrante '(' correspondante.",
|
||||
"formula-error-empty-placeholder": "Un espace réservé vide '{{}}' n'est pas autorisé. Indiquez un nom de variable à l'intérieur des accolades.",
|
||||
"decimal-places": "Décimales",
|
||||
"number-of-decimal-places": "Nombre de décimales",
|
||||
"decimal-places-helper": "Définir sur 0 pour les nombres entiers",
|
||||
@@ -529,6 +582,7 @@
|
||||
"sign-url": "Signer l'URL",
|
||||
"mails-sent": "Mails envoyés avec succès !",
|
||||
"mail-not-delivered": "Courrier non distribué",
|
||||
"email-not-verified-head": "E-mail non vérifié",
|
||||
"document-alert": "Alerte document",
|
||||
"owner-subscription-expired": "L'abonnement du propriétaire a expiré.",
|
||||
"owner-doesnt-have-paid-plan": "Le propriétaire n'a pas de plan payant.",
|
||||
@@ -604,6 +658,9 @@
|
||||
"document-signed-alert-7": "Ce document n'est plus disponible.",
|
||||
"document-signed-alert-8": "Le document a déjà été envoyé pour signature.",
|
||||
"yet-to-sign": "Reste à signer",
|
||||
"viewers": "Lecteurs",
|
||||
"viewer-no-widgets": "Les lecteurs reçoivent une copie en lecture seule du document et ne peuvent pas avoir de widgets assignés.",
|
||||
"viewer-confirm-widget-removal": "Le passage de ce destinataire en Lecteur supprimera tous les widgets déjà assignés. Continuer ?",
|
||||
"validation-alert-1": "L'entrée ne répond pas aux critères définis par l'expression régulière.",
|
||||
"account-already-exists": "Le compte existe déjà!",
|
||||
"choose-password": "Choisissez un mot de passe",
|
||||
@@ -626,7 +683,7 @@
|
||||
"date": "Date",
|
||||
"report-not-found": "Rapport introuvable",
|
||||
"please-wait": "S'il vous plaît, attendez",
|
||||
"select-foler": "Sélectionner le dossier",
|
||||
"select-folder": "Sélectionner le dossier",
|
||||
"generated-on": "Généré le",
|
||||
"text-field": "Champ de texte",
|
||||
"font-size": "Taille de police",
|
||||
@@ -685,14 +742,14 @@
|
||||
"public-template": "Cette vidéo montre comment configurer votre profil public personnalisé, tel que 'https://opensign.me/your-username'. Vous apprendrez également comment personnaliser votre slogan et rendre vos modèles disponibles pour la signature publique.",
|
||||
"allowModify-widgets": "Vous pouvez faire glisser et déposer n'importe lequel de ces champs sur le document, en plus des champs déjà désignés pour vous par le créateur du document."
|
||||
},
|
||||
"enter-email-plaholder": "Ajoutez une adresse email et appuyez sur Entrée",
|
||||
"enter-email-placeholder": "Ajoutez une adresse email et appuyez sur Entrée",
|
||||
"success-email-alert": "E-mail envoyé avec succès!",
|
||||
"expired-doc-title": "Document expiré",
|
||||
"expired-on-mssg": "Ce document a expiré le {{expiredDate}} et n'est plus disponible pour signature.",
|
||||
"signature-validate-alert": "Veuillez confirmer que vous avez coché au moins {{minRequiredCount}} cases.",
|
||||
"signature-validate-alert-2": "Assurez-vous que ce champ est rempli avec précision et répond à toutes les exigences.",
|
||||
"user-name-exist": "le nom d'utilisateur existe déjà",
|
||||
"remaing-users": "Utilisateurs restants",
|
||||
"remaining-users": "Utilisateurs restants",
|
||||
"Role": "Rôle",
|
||||
"additional-users": "Veuillez acheter des utilisateurs supplémentaires.",
|
||||
"Quantity-of-users": "Quantité d'utilisateurs",
|
||||
@@ -713,7 +770,7 @@
|
||||
"rotate-left": "Faire pivoter à gauche",
|
||||
"rotate-alert-mssg": "Tous les widgets de cette page seront perdus. Êtes-vous sûr de vouloir continuer ?",
|
||||
"templateid": "ID de modèle",
|
||||
"bulk-send-subcription-alert": "Veuillez passer au forfait Professionnel ou Équipe pour utiliser Quicksend.",
|
||||
"bulk-send-subscription-alert": "Veuillez passer au forfait Professionnel ou Équipe pour utiliser Quicksend.",
|
||||
"generate-test-token": "Générer jeton de test",
|
||||
"regenerate-test-token": "Régénérer le jeton de test",
|
||||
"help-test-token": "Ce jeton peut être utilisé pour tester les API au niveau du point de terminaison https://sandbox.opensignlabs.com/api/v1, vous permettant ainsi d'effectuer un nombre illimité de signatures de documents. Veuillez noter que l'API sandbox signera vos documents avec des certificats auto-signés, qui peuvent ne pas être reconnus comme valides par Adobe. Une fois vos tests terminés, vous pouvez passer à l'un de nos forfaits payants pour générer un jeton de production.",
|
||||
@@ -733,8 +790,9 @@
|
||||
"copied-code": "COPIÉ",
|
||||
"Installation": "Installation",
|
||||
"Usage": "Usage",
|
||||
"insufficient-credits": "Crédits de signature insuffisants",
|
||||
"insufficient-credits-mssg": "Le propriétaire de ce document ne dispose actuellement pas des crédits OpenSign nécessaires pour que vous puissiez le signer. Veuillez contacter le propriétaire si vous avez besoin de plus de détails.",
|
||||
"insufficient-credits": "Crédits de signature insuffisants. Veuillez contacter {{ownerEmail}}",
|
||||
"insufficient-credits-title": "Crédits de signature insuffisants",
|
||||
"insufficient-credits-mssg": "Le propriétaire de ce document ne dispose actuellement pas des crédits {{appName}} nécessaires pour que vous puissiez le signer. Veuillez contacter le propriétaire si vous avez besoin de plus de détails.",
|
||||
"angular-npm-mssg-1": "Pour intégrer OpenSign dans votre projet Angular, exécutez simplement la commande suivante :",
|
||||
"quota-mail-info-head": "Limite mensuelle d'e-mails",
|
||||
"quota-mail-info": "Pour maintenir la qualité du service et prévenir le spam, OpenSign permet jusqu'à 15 e-mails par mois avec le plan gratuit. Passez à l'offre supérieure pour un envoi illimité d'e-mails.",
|
||||
@@ -753,6 +811,13 @@
|
||||
"p3": "La sélection de cette option désactivera la vérification OTP, permettant aux utilisateurs de signer le document directement sans étapes supplémentaires.",
|
||||
"p4": "Veuillez choisir l'option qui correspond le mieux à vos exigences en matière de signature de documents."
|
||||
},
|
||||
"allow-offline-sign": "Autoriser la signature hors ligne",
|
||||
"allow-offline-sign-help": {
|
||||
"p1": "Choisissez si les signataires peuvent soumettre une copie de ce document signée avec un outil externe pour examen par le propriétaire.",
|
||||
"p2": "Les signataires verront une option Signer hors ligne et pourront téléverser un PDF signé en externe pour validation.",
|
||||
"p3": "Les signataires ne peuvent signer que dans l'application. L'option Signer hors ligne sera masquée.",
|
||||
"p4": "Ce paramètre prend par défaut votre préférence de compte et est disponible sur les forfaits payants."
|
||||
},
|
||||
"advanced-options": "Options avancées",
|
||||
"hide-advanced-options": "Masquer les options avancées",
|
||||
"download-files": "Télécharger des fichiers",
|
||||
@@ -762,7 +827,7 @@
|
||||
"server-down": "Impossible de se connecter au serveur {{appName}}. Si vous hébergez vous-même {{appName}}, veuillez vous assurer que toutes les étapes de la documentation ont été correctement suivies. Si vous exécutez {{appName}} localement, vous y accédez peut-être via un numéro de port incorrect.",
|
||||
"admin-exists": "L'administrateur existe déjà. Veuillez vous connecter à l'application en utilisant les informations d'identification de l'administrateur afin de gérer les utilisateurs.",
|
||||
"enable-tour": "Activer la visite",
|
||||
"istourenabled-otp-help": {
|
||||
"istourenabled-help": {
|
||||
"p1": "Cela permettra une visite guidée pour les signataires, fournissant des instructions pendant le processus de signature.",
|
||||
"p2": "Cela désactivera la visite guidée, garantissant ainsi une expérience de signature plus rapide et ininterrompue.",
|
||||
"p3": "Conseil: si vos signataires connaissent {{appName}} et l'utilisent fréquemment, vous souhaiterez peut-être désactiver la visite guidée pour une expérience plus fluide. Choisissez l'option qui correspond le mieux à vos besoins en matière de signature de documents."
|
||||
@@ -815,6 +880,7 @@
|
||||
"delete-folder-alert-1": "Impossible de supprimer : le dossier doit être vide.",
|
||||
"defaultSign-alert": "Les widgets {{widgetsType}} ne sont pas disponibles pour la signature automatique.",
|
||||
"embed-heading": "Intégrer des modèles {{appName}}",
|
||||
"other-apps-by-opensign": "Autres applications par l'équipe OpenSign",
|
||||
"public-template-edit-title": "Modifier le modèle public",
|
||||
"public-template-edit-mssg": "Vous ne pouvez pas modifier directement un modèle public. Pour apporter des modifications, le modèle sera défini comme privé et vous devrez ensuite le rendre à nouveau public. Voulez-vous continuer ?",
|
||||
"agree-p1": "Je confirme avoir lu et compris les ",
|
||||
@@ -866,7 +932,6 @@
|
||||
"term-cond-p30": " ou par email",
|
||||
"js-snippet-msg": "Pour intégrer des modèles {{appName}} dans vos sites Web ou pages de destination HTML, vous pouvez utiliser le code ci-dessous:",
|
||||
"js-snippet-msg-1": "Vous pouvez obtenir le TemplateId à partir de la page Gérer les modèles.",
|
||||
"agrrement-alert": "Pour procéder, vous devez consentir à la divulgation des enregistrements et signatures électroniques.",
|
||||
"webhook-already-exists": "L'URL existe déjà ! Veuillez en essayer un autre.",
|
||||
"webhook-must-be-secure": "L'URL du webhook doit être sécurisée et utiliser https://",
|
||||
"provide-test-token": "Veuillez générer un jeton de test API",
|
||||
@@ -880,6 +945,7 @@
|
||||
"p3": "Sélectionner cette option empêche les signataires d'apporter des ajouts au document pendant le processus de signature."
|
||||
},
|
||||
"signature-setting": "Paramètres de signature",
|
||||
"rotation": "Rotation",
|
||||
"change-signer": "Changer de signataire",
|
||||
"signyour-self-button": "Utilisez cette option pour signer le document vous-même sans en ajouter d'autres.",
|
||||
"requestsign-button": "Utilisez cette option pour demander les signatures des autres et de vous-même.",
|
||||
@@ -914,6 +980,8 @@
|
||||
"visit-below-link": "Visitez le lien ci-dessous pour en savoir plus -",
|
||||
"storage-help": "Activer BYOC vous permet de connecter votre propre stockage S3 afin que vos fichiers restent entièrement sous votre contrôle, sans copie externe conservée. Si l'autonomie des données est importante pour vous, envisagez de passer à l'offre Teams pour débloquer cette fonctionnalité.",
|
||||
"daily-quota-reached": "Vous avez atteint votre quota quotidien. Pour obtenir de l'aide, veuillez contacter quotas@opensignlabs.com.",
|
||||
"email-not-verified-send": "Veuillez vérifier votre adresse e-mail avant d'envoyer des e-mails. Accédez à vos <1>paramètres de profil</1> pour terminer la vérification de l'e-mail.",
|
||||
"email-not-verified-send-plain": "Veuillez vérifier votre adresse e-mail avant d'envoyer des e-mails. Accédez à vos paramètres de profil pour terminer la vérification de l'e-mail.",
|
||||
"enabled-signature-type": "Types de signature activés",
|
||||
"enabled-signature-type-help": "Le paramètre 'Types de signature activés' détermine quelles options de signature sont disponibles dans votre organisation. Par exemple, si vous désactivez l'option 'Dessiner', les membres de votre organisation ne la verront pas dans le widget de signature, tandis que les trois autres options resteront accessibles.",
|
||||
"indexing-public-profile": "Autoriser l'indexation du profil public par les moteurs de recherche",
|
||||
@@ -941,6 +1009,7 @@
|
||||
"please-provide-templateid": "Veuillez fournir templateid",
|
||||
"this-template-is-not-public": "Ce template n'est pas public",
|
||||
"invalid-templateid": "templateid invalide",
|
||||
"kiosk-no-active-role": "Ce modèle n'a aucun rôle de signataire ou d'approbateur disponible pour la signature en mode kiosque.",
|
||||
"contact-billing-at-opensign": "Pour ajouter plus de places, veuillez contacter OpenSign™ à l'adresse <1>billing@opensignlabs.com</1> pour obtenir de l'aide.",
|
||||
"title-length-alert": "Le titre doit comporter au maximum 250 caractères.",
|
||||
"note-length-alert": "La note doit comporter au maximum 200 caractères.",
|
||||
@@ -1030,7 +1099,7 @@
|
||||
},
|
||||
"signer-already-present": "Signataire déjà présent",
|
||||
"kiosk-sign": "Signature sur kiosque",
|
||||
"dont-have-access-to-template": "Le template a été supprimé ou vous n'y avez pas accès. Veuillez contacter l'expéditeur.",
|
||||
"do-not-have-access-to-template": "Le template a été supprimé ou vous n'y avez pas accès. Veuillez contacter l'expéditeur.",
|
||||
"kiosk-info": "Le Mode Kiosque vous permet de recueillir des signatures en personne rapidement et efficacement. Idéal pour les salons, événements ou situations où tous les signataires sont physiquement présents. ",
|
||||
"learn-more": "En savoir plus",
|
||||
"finish-mssg": "Êtes-vous sûr de vouloir terminer le document ?",
|
||||
@@ -1086,8 +1155,6 @@
|
||||
"search-contacts": "Rechercher des contacts…",
|
||||
"add-role-alert": "Veuillez ajouter au moins un rôle",
|
||||
"edit-draft": "Modifier le brouillon",
|
||||
"invalid-email-found": "Adresse e-mail invalide trouvée : {{email}}",
|
||||
"duplicate-email-found": "Adresse e-mail en double trouvée : {{email}}",
|
||||
"vertical": "Vertical",
|
||||
"horizontal": "Horizontal",
|
||||
"billing": "Facturation",
|
||||
@@ -1101,7 +1168,7 @@
|
||||
"access-denied": "Accès refusé",
|
||||
"upgrade": "Mettre à jour",
|
||||
"do-not-access-app": "Vous n'avez pas accès à cette application.",
|
||||
"dont-have-access": "Vous n'avez pas accès.",
|
||||
"do-not-have-access": "Vous n'avez pas accès.",
|
||||
"valid-email-alert": "Veuillez saisir une adresse e-mail valide.",
|
||||
"otp-not-validate": "OTP non valide.",
|
||||
"domain-not-allowed": "Ce domaine n'est pas autorisé",
|
||||
@@ -1118,10 +1185,12 @@
|
||||
"branding-help": "Le branding permet le white labelling de votre application",
|
||||
"custom-sub-domain": "Sous-domaine personnalisé",
|
||||
"app-name": "Nom de l'application",
|
||||
"provide-domain-name": "Fournissez votre nom de domaine",
|
||||
"provide-domain-name": "Seuls les sous-domaines sont autorisés (p. ex., app.votredomaine.com). Les domaines principaux/racine comme votredomaine.com ne sont pas pris en charge.",
|
||||
"provide-app-name-help": "Indiquez un nom d'application unique. Il sera affiché comme le nom de votre marque dans l'application et dans tous les e-mails partout où l'image de marque est appliquée.",
|
||||
"upload-app-logo": "Téléversez un logo au format .png, .jpg ou .svg. Taille recommandée : {{size}} pixels (ratio 5:2) pour une meilleure qualité d'affichage.",
|
||||
"upload-app-favicon": "Téléversez une image de favicon carrée (.ico, .png ou .svg). La taille recommandée est de {{size}} pixels pour une meilleure compatibilité avec les navigateurs.",
|
||||
"provide-app-name": "Fournissez le nom de votre application",
|
||||
"logo": "Logo",
|
||||
"upload-app-logo": "Téléchargez le logo de votre application",
|
||||
"prefill-unfilled-widget": "Les champs obligatoires suivants ne peuvent pas être vides : {{emptyWidget}}. Veuillez les remplir pour continuer.",
|
||||
"Dashboard": "Tableau de bord",
|
||||
"Analytics": "Analytique",
|
||||
@@ -1289,11 +1358,11 @@
|
||||
"signer-form-data-for": "Données du formulaire pour le signataire : {{signerName}} <{{signerEmail}}>",
|
||||
"widget-name": "Nom du widget",
|
||||
"widget-responses": "Réponses du widget",
|
||||
"formdata-download-note": "Remarque : Les signatures, tampons, initiales et images ne seront pas exportés.",
|
||||
"formdata-download-note": "Remarque : Les signatures, tampons, initiales et images ne seront pas exportés. Les données des signataires hors ligne ne seront pas disponibles.",
|
||||
"export-all-formdata-error": "Vous ne pouvez pas exporter les données du formulaire car le modèle contient des noms de widgets en double. Veuillez créer un nouveau modèle pour pouvoir exporter les données.",
|
||||
"export-formdata-error": "Vous ne pouvez pas exporter les données du formulaire car le document contient des noms de widgets en double. Veuillez créer un nouveau document sans doublons pour pouvoir exporter les données.",
|
||||
"duplicate-widget-name-error": "Ce nom de widget est déjà attribué à un autre signataire. Veuillez choisir un autre nom.",
|
||||
"duplicate-template-widget-error": "Vous ne pouvez pas dupliquer ce modèle car il contient des noms de widgets en double.",
|
||||
"duplicate-template-widget-error": "Impossible de dupliquer le modèle, car certains noms de widgets sont dupliqués dans différents rôles. Renommez les widgets en double, puis réessayez.",
|
||||
"credit-alert": "Alerte de crédit",
|
||||
"document-form-data": "Données du formulaire du document",
|
||||
"view-formdata-paid-title-error": "La fonction 'Afficher les données du formulaire' vous permet d'imprimer et de télécharger les données des widgets remplies par les signataires lors de la signature.",
|
||||
@@ -1345,8 +1414,8 @@
|
||||
"file-selected": "fichier sélectionné",
|
||||
"bulk-send-csv-helper": "Utilisez des en-têtes pour les rôles des signataires (E-mail/Nom/Téléphone du rôle) et les valeurs de préremplissage (Préremplissage : Nom du champ). Les champs de préremplissage requis doivent être complétés.",
|
||||
"bulk-send-csv-helper-note": "Remarque : Pour les champs de préremplissage de type date dans le CSV, la valeur doit être fournie dans le format suivant : {{value}} (exemple : {{example}}).",
|
||||
"bulk-send-csv-summary": "{{total}} lignes importées. {{invalid}} lignes non valides ont été ignorées.",
|
||||
"bulk-send-csv-missing-headers": "En-têtes CSV requis manquants. Rôles : {{roles}}. Préremplissage : {{prefill}}.",
|
||||
"bulk-send-csv-summary": "{{total}} lignes importées depuis le CSV.",
|
||||
"bulk-send-csv-missing-headers": "En-têtes CSV requis manquants. Rôles : {{roles}}.",
|
||||
"bulk-send-no-records": "Veuillez ajouter au moins un destinataire avant l'envoi.",
|
||||
"csv-only-support": "Seuls les fichiers CSV sont pris en charge.",
|
||||
"csv-no-records": "Aucun enregistrement trouvé dans le CSV.",
|
||||
@@ -1360,5 +1429,187 @@
|
||||
"enter-daily-email-quota": "Saisissez le quota quotidien d'e-mails",
|
||||
"enter-custom-regular-expression": "Saisissez une expression régulière personnalisée",
|
||||
"enter-value": "Saisissez {{value}}",
|
||||
"are-you-sure-want-to-send-document": "Êtes-vous sûr de vouloir envoyer le document ?"
|
||||
}
|
||||
"are-you-sure-want-to-send-document": "Êtes-vous sûr de vouloir envoyer le document ?",
|
||||
"favicon": "Favicon",
|
||||
"delete-two-factor-authentication": "Êtes-vous sûr de vouloir supprimer l'authentification à deux facteurs ?",
|
||||
"paste-html-here": "Collez le HTML ici",
|
||||
"open-email-builder": "Vous pouvez créer un modèle d'e-mail à l'aide de la plateforme <1>email builder</1> et copier le code HTML.",
|
||||
"contact-saved": "Contact enregistré avec succès.",
|
||||
"remove-limit-of-user": "Êtes-vous sûr de vouloir supprimer la limite de cet utilisateur ? Il pourra alors utiliser tous les crédits premium disponibles de votre organisation, partagés entre tous les utilisateurs.",
|
||||
"current-expiry-date": "Date d'expiration actuelle",
|
||||
"reminder-error": "« Rappeler une fois tous les (jours) » ne doit pas dépasser le « Temps pour terminer (jours) ».",
|
||||
"showing-to-of-records": "Affichage de {{record}} à {{recordPerPage}} sur {{totalRows}} enregistrements",
|
||||
"records-per-page": "Enregistrements par page",
|
||||
"email-not-found-in-row": "Aucun e-mail trouvé à la ligne {{row}}.",
|
||||
"invalid-email-found-in-row": "E-mail invalide trouvé à la ligne {{row}} : {{email}}",
|
||||
"duplicate-email-found-in-row": "E-mail en double trouvé à la ligne {{row}} : {{email}}",
|
||||
"prefill": "Préremplissage",
|
||||
"recipients-&-data": "Destinataires et données",
|
||||
"response": "Réponse",
|
||||
"prefill-tab-help": "Ce sont les champs communs du modèle qui s'appliqueront à tous les documents de cet envoi groupé.",
|
||||
"recipient-tab-help-p1": "Dans cette étape suivante, vous allez :",
|
||||
"recipient-tab-help-p2": "Téléverser un CSV avec les détails des destinataires ou",
|
||||
"recipient-tab-help-p3": "Saisir manuellement les informations des destinataires",
|
||||
"recipient-tab-help-p4": "Chaque destinataire recevra son propre document généré à partir de ce modèle.",
|
||||
"response-tab-help": "Vérifiez vos réponses.",
|
||||
"summary": "Résumé",
|
||||
"prefill-fields": "Champs de préremplissage",
|
||||
"documents": "Documents",
|
||||
"message": "Message",
|
||||
"formula-help-p1": "Collectez des entrées numériques et effectuez des calculs dynamiques à l'aide de formules.",
|
||||
"example": "Exemple",
|
||||
"formula-help-p2": "Pour afficher un total dans number-3, utilisez {{number-1}} + {{number-2}}.",
|
||||
"formula-help-p3": "Pour calculer une valeur dans number-4, utilisez ({{number-1}} * {{number-2}}) / {{number-3}}.",
|
||||
"formula-help-p4": "Chaque widget numérique doit avoir un nom unique (par ex. number-1), car les formules dépendent de ces identifiants.",
|
||||
"note-consume-premium-credits": "Remarque : Cette fonctionnalité consomme des crédits premium.",
|
||||
"note-consume-premium-credits-on-document-send": "Remarque : Cette fonctionnalité consomme des crédits premium. Les crédits sont consommés chaque fois qu'un document est envoyé, qu'il soit signé ou non.",
|
||||
"webhook-authentication-help": "{{appName}} sécurise chaque webhook en générant une signature HMAC-SHA256 à l'aide de votre clé de sécurité webhook et du payload brut de la requête. Cette signature est envoyée dans l'en-tête x-webhook-signature. Votre serveur doit générer la même signature en utilisant le payload reçu et votre clé secrète. Si les deux signatures correspondent, la requête webhook est authentique et n'a pas été modifiée.",
|
||||
"invalid-widget-found-in-row": "Valeur invalide détectée à la ligne {{row}} pour le widget {{widget}}.",
|
||||
"all-signer-status": "Statut de tous les signataires",
|
||||
"viewed": "Consulté",
|
||||
"signed": "Signé",
|
||||
"switch-to-advanced": "Passer en mode avancé",
|
||||
"switch-to-basic": "Passer en mode basique",
|
||||
"use-name-as-sender": "Utiliser le nom du profil comme expéditeur",
|
||||
"use-name-as-sender-help": "Lorsque cette option est activée, {{appName}} utilise le nom de votre profil comme expéditeur pour les demandes de signature et l'e-mail de finalisation.",
|
||||
"acro-fields-detected": "Champs de formulaire détectés",
|
||||
"acro-fields-detected-msg": "Nous avons détecté {{count}} champ(s) de formulaire dans le PDF téléchargé. Souhaitez-vous insérer automatiquement les widgets correspondants ?",
|
||||
"documents-count": "Nombre de documents",
|
||||
"templates-count": "Nombre de modèles",
|
||||
"emails-sent": "E-mails envoyés",
|
||||
"storage-used": "Stockage utilisé",
|
||||
"docs-count-team-help": "Nombre total de documents créés par tous les utilisateurs de vos organisations.",
|
||||
"docs-count-help": "Nombre total de documents que vous avez créés.",
|
||||
"templates-count-team-help": "Nombre total de modèles créés par tous les utilisateurs de vos organisations.",
|
||||
"templates-count-help": "Nombre total de modèles que vous avez créés.",
|
||||
"emails-sent-team-help": "Nombre total de notifications e-mail envoyées dans vos organisations.",
|
||||
"emails-sent-help": "Nombre total de notifications e-mail envoyées par vous.",
|
||||
"storage-used-team-help": "Espace de stockage total consommé par tous les utilisateurs de vos organisations.",
|
||||
"storage-used-help": "Espace de stockage total consommé par vous.",
|
||||
"cloud": "Cloud",
|
||||
"on-premises": "Sur site",
|
||||
"plan-name-free": "OPENSIGN™ FREE",
|
||||
"plan-name-professional": "OPENSIGN™ PROFESSIONAL",
|
||||
"plan-name-teams": "OPENSIGN™ TEAMS",
|
||||
"plan-name-enterprise": "OPENSIGN™ ENTERPRISE",
|
||||
"plan-name-self-hosted": "OPENSIGN™ SELF-HOSTED",
|
||||
"plan-subtitle-free": "Signatures électroniques illimitées gratuites, pour toujours.",
|
||||
"plan-subtitle-professional": "Accès exclusif aux fonctionnalités avancées.",
|
||||
"plan-subtitle-scalable": "Fonctionnalités évolutives avec support prioritaire.",
|
||||
"subscribe": "S'abonner",
|
||||
"contact-us": "Nous contacter",
|
||||
"benefit-unlimited-digital-signatures": "Signatures numériques illimitées",
|
||||
"benefit-sign-docs-yourself": "Signer des documents vous-même",
|
||||
"benefit-request-signatures": "Demander des signatures à d'autres",
|
||||
"benefit-unlimited-templates": "Modèles illimités",
|
||||
"benefit-14-field-types": "14 types de champ",
|
||||
"benefit-automatic-e-signatures": "Signatures électroniques automatiques",
|
||||
"benefit-completion-certificates": "Certificats d'achèvement",
|
||||
"benefit-send-in-order": "Envoyer dans l'ordre",
|
||||
"benefit-organize-docs": "Organiser les documents dans OpenSign™ Drive",
|
||||
"benefit-document-templates": "Modèles de documents",
|
||||
"benefit-import-from-dropbox": "Importer depuis Dropbox",
|
||||
"benefit-contact-book": "Carnet de contacts",
|
||||
"benefit-document-expiry": "Prise en charge de l'expiration des documents",
|
||||
"benefit-decline-document": "Prise en charge du refus de documents",
|
||||
"benefit-email-notifications": "Notifications par e-mail",
|
||||
"benefit-public-profiles": "Profils publics",
|
||||
"benefit-and-much-more": "Et bien plus encore",
|
||||
"benefit-everything-free": "Tout dans OpenSign™ Free",
|
||||
"benefit-field-validations": "Validations de champ",
|
||||
"benefit-regex-validations": "Validations par expression régulière",
|
||||
"benefit-webhooks": "Webhooks",
|
||||
"benefit-zapier": "Intégration Zapier",
|
||||
"benefit-api-access": "Accès API",
|
||||
"benefit-api-signatures-100": "jusqu'à 100 signatures API",
|
||||
"benefit-api-signatures-240": "jusqu'à 240 signatures API",
|
||||
"benefit-custom-email-templates": "Modèles d'e-mail personnalisés",
|
||||
"benefit-custom-smtp": "Connectez votre propre compte Gmail ou SMTP pour envoyer des e-mails",
|
||||
"benefit-auto-reminders": "Rappels automatiques",
|
||||
"benefit-bulk-send-100": "Envoi en masse (jusqu'à 100 documents)",
|
||||
"benefit-bulk-send-240": "Envoi en masse (jusqu'à 240 documents)",
|
||||
"benefit-premium-profiles": "Noms d'utilisateur de profil public premium",
|
||||
"benefit-email-verification": "Appliquer la vérification par e-mail pour confirmer l'identité du signataire",
|
||||
"benefit-embedded-signing": "Signature intégrée",
|
||||
"benefit-everything-professional": "Tout dans OpenSign™ Professional",
|
||||
"benefit-teams-organizations": "Équipes et Organisations",
|
||||
"benefit-share-templates-teams": "Partager des modèles avec des équipes",
|
||||
"benefit-share-templates-individuals": "Partager des modèles avec des individus",
|
||||
"benefit-byoc": "BYOC - Stockez vos documents dans votre propre stockage cloud",
|
||||
"benefit-doc-id-removal": "Suppression de l'ID de document des documents signés",
|
||||
"benefit-request-payments": "Demander des paiements (bientôt disponible)",
|
||||
"benefit-mobile-app": "Application mobile (bientôt disponible)",
|
||||
"benefit-api-signatures-500": "jusqu'à 500 signatures API",
|
||||
"benefit-bulk-send-500": "Envoi en masse (jusqu'à 500 documents)",
|
||||
"benefit-all-features": "Toutes les fonctionnalités",
|
||||
"benefit-custom-domain": "Domaine personnalisé",
|
||||
"benefit-custom-branding": "Identité visuelle personnalisée",
|
||||
"benefit-uptime-sla": "SLA de disponibilité",
|
||||
"benefit-sso-support": "Support SSO",
|
||||
"benefit-priority-support": "Support prioritaire",
|
||||
"benefit-3-users-1500-credits": "Comprend 3 utilisateurs et 1500 crédits premium",
|
||||
"benefit-public-links": "Liens publics",
|
||||
"benefit-kiosk-mode": "Mode kiosque",
|
||||
"benefit-bulk-send-1500": "Envoi en masse (jusqu'à 1500 documents)",
|
||||
"benefit-connect-gmail-smtp": "Connectez votre propre Gmail ou SMTP",
|
||||
"benefit-analytics": "Analyses",
|
||||
"something-went-wrong-mssg": "Une erreur s'est produite, actualiser cette page peut résoudre le problème.",
|
||||
"benefit-bulksend-api-pro-month-help": "Ce forfait inclut 100 crédits premium par mois qui vous permettent d'envoyer des documents via l'API, de partager des liens publics de signature, de lancer des demandes de signature en masse, d'utiliser le mode Kiosque pour les signatures en personne ou de collecter des signatures à partir de documents intégrés directement à votre site web. À chaque fois que vous initiez ou envoyez un document, un crédit est utilisé — et ce document peut inclure jusqu'à 10 signataires.",
|
||||
"benefit-bulksend-api-pro-year-help": "Ce forfait inclut 240 crédits premium par an qui vous permettent d'envoyer des documents via l'API, de partager des liens publics de signature, de lancer des demandes de signature en masse, d'utiliser le mode Kiosque pour les signatures en personne ou de collecter des signatures à partir de documents intégrés directement à votre site web. À chaque fois que vous initiez ou envoyez un document, un crédit est utilisé — et ce document peut inclure jusqu'à 10 signataires.",
|
||||
"benefit-bulksend-api-team-month-help": "Ce forfait inclut 100 crédits premium par mois qui vous permettent d'envoyer des documents via l'API, de partager des liens publics de signature, de lancer des demandes de signature en masse, d'utiliser le mode Kiosque pour les signatures en personne ou de collecter des signatures à partir de documents intégrés directement à votre site web. À chaque fois que vous initiez ou envoyez un document, un crédit est utilisé — et ce document peut inclure jusqu'à 10 signataires.",
|
||||
"benefit-bulksend-api-team-year-help": "Ce forfait inclut 500 crédits premium par an qui vous permettent d'envoyer des documents via l'API, de partager des liens publics de signature, de lancer des demandes de signature en masse, d'utiliser le mode Kiosque pour les signatures en personne ou de collecter des signatures à partir de documents intégrés directement à votre site web. À chaque fois que vous initiez ou envoyez un document, un crédit est utilisé — et ce document peut inclure jusqu'à 10 signataires.",
|
||||
"failed-pdf-for-prefill-processing": "Impossible de charger le PDF pour le traitement du préremplissage.",
|
||||
"attachments-docx-upload-failed": "Nous rencontrons actuellement un problème. Veuillez réessayer.",
|
||||
"attachments-docx-upload-limit": "La limite de téléchargement pour les fichiers DOCX volumineux a été atteinte. Veuillez attendre 4 minutes avant de réessayer.",
|
||||
"attach-all-role-to-signer": "Veuillez attribuer tous les rôles au signataire.",
|
||||
"upgrade-to-edit": "Mettre à niveau pour modifier",
|
||||
"hide-live-chat-for-signers": "Masquer le chat en direct pour les signataires",
|
||||
"hide-live-chat-for-signers-help": "Lorsqu'activé, les widgets de chat en direct seront masqués pour les signataires sur les pages de signature.",
|
||||
"attachments-creator-hint": "Les signataires peuvent téléverser des fichiers qui seront ajoutés à la fin du document.",
|
||||
"attachments-drop-hint": "Déposez les fichiers ici ou cliquez pour choisir",
|
||||
"attachments-limits": "PDF, DOCX, PNG, JPG. Jusqu'à {{maxFiles}} fichiers, {{maxSizeMB}} Mo chacun.",
|
||||
"attachments-invalid-type": "Type de fichier non pris en charge : {{name}}",
|
||||
"attachments-max-files": "Maximum {{maxFiles}} pièces jointes autorisées.",
|
||||
"attachments-remove-confirm": "La suppression efface uniquement les métadonnées. Les pages déjà ajoutées resteront dans le document. Continuer ?",
|
||||
"conditional-logic": "Logique conditionnelle",
|
||||
"enabled": "Activé",
|
||||
"disabled": "Désactivé",
|
||||
"logic-no-sources": "Aucun autre champ de ce signataire ne peut servir de source.",
|
||||
"logic-action": "Action",
|
||||
"logic-when": "Lorsque",
|
||||
"logic-all": "toutes",
|
||||
"logic-any": "au moins une",
|
||||
"logic-of-following": "des conditions suivantes sont vraies",
|
||||
"logic-add-rule": "Ajouter une règle",
|
||||
"logic-pick-value": "Choisir une valeur",
|
||||
"logic-value-placeholder": "Saisir une valeur",
|
||||
"logic-action-show": "Afficher ce champ",
|
||||
"logic-action-hide": "Masquer ce champ",
|
||||
"logic-action-require": "Rendre obligatoire",
|
||||
"logic-action-optional": "Rendre facultatif",
|
||||
"logic-op-equals": "est égal à",
|
||||
"logic-op-notEquals": "n'est pas égal à",
|
||||
"logic-op-checked": "est coché",
|
||||
"logic-op-unchecked": "n'est pas coché",
|
||||
"widget-hidden-due-to-logic": "Ce widget est masqué en raison de la logique conditionnelle.",
|
||||
"remove": "Supprimer",
|
||||
"attachement-note": "Remarque : Ce widget permet aux signataires de télécharger un fichier pendant le processus de signature. Le fichier téléchargé sera ajouté en tant que pages supplémentaires à la fin du PDF existant.",
|
||||
"attachments-docx-not-supported": "Nous rencontrons actuellement un problème avec le téléchargement des fichiers DOCX. Veuillez essayer de télécharger le document dans un autre format pris en charge ou réessayer plus tard.",
|
||||
"access-code": "Code d'accès",
|
||||
"access-code-set": "Code d'accès défini",
|
||||
"access-code-add": "Ajouter un code d'accès",
|
||||
"access-code-help": "Secret optionnel connu uniquement de ce signataire. Il devra le saisir avant de voir ou de signer le document. Communiquez-le au destinataire par un autre canal (téléphone, en personne).",
|
||||
"access-code-placeholder": "Au moins 6 chiffres",
|
||||
"access-code-clear": "Effacer",
|
||||
"access-code-must-be-numeric": "Le code d'accès ne doit contenir que des chiffres 0-9.",
|
||||
"access-code-save": "Enregistrer",
|
||||
"access-code-required": "L'expéditeur a attribué un code d'accès à ce destinataire. Saisissez-le pour continuer.",
|
||||
"access-code-invalid": "Code d'accès incorrect. Veuillez vérifier auprès de l'expéditeur.",
|
||||
"access-code-too-short": "Le code d'accès doit comporter au moins 6 chiffres.",
|
||||
"access-code-verify": "Vérifier le code d'accès",
|
||||
"access-code-verified": "Code d'accès vérifié.",
|
||||
"access-code-warning": "Ce code d'accès ne peut pas être modifié ou récupéré une fois le document envoyé.",
|
||||
"copy-access-code": "Copier le code d'accès",
|
||||
"access-code-upgrade-msg": "Les codes d'accès sont disponibles uniquement dans les forfaits payants. Améliorez votre forfait pour ajouter des codes d'accès aux destinataires.",
|
||||
"already-copied-to-clipboard": "Déjà copié dans le presse-papiers"
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
"password-length": "पासवर्ड 8 वर्ण लंबा होना चाहिए",
|
||||
"password-case": "पासवर्ड में अपरकेस अक्षर, लोअरकेस अक्षर, अंक होना चाहिए",
|
||||
"password-special-char": " पासवर्ड में विशेष वर्ण होना चाहिए",
|
||||
"agreee": "मैं सहमत हूँ",
|
||||
"agree": "मैं सहमत हूँ",
|
||||
"term": "सेवा की शर्तें",
|
||||
"subscribe-to-opensign": "OpenSign न्यूज़लेटर की सदस्यता लें",
|
||||
"register": "पंजीकृत करें",
|
||||
@@ -237,11 +237,12 @@
|
||||
"p3": "इस विकल्प का चयन करने पर हस्ताक्षर लिंक सभी हस्ताक्षरकर्ताओं को एक साथ भेजे जाएंगे। प्रत्येक हस्ताक्षरकर्ता अपनी सुविधानुसार दस्तावेज़ पर हस्ताक्षर कर सकता है, भले ही अन्य हस्ताक्षरकर्ताओं ने अपने हस्ताक्षर पूरे कर लिए हों या नहीं। यह विधि तेज़ है लेकिन प्रतिभागियों के बीच किसी भी हस्ताक्षर क्रम को लागू नहीं करती है।",
|
||||
"p4": "वह विकल्प चुनें जो आपके दस्तावेज़ प्रसंस्करण की आवश्यकताओं के लिए सबसे उपयुक्त हो।"
|
||||
},
|
||||
"strict-order": "सख्त क्रम लागू करें",
|
||||
"strict-order-help": "जब सक्षम होता है, तो कोई भी हस्ताक्षरकर्ता तब तक हस्ताक्षर या अनुमोदन नहीं कर सकता जब तक कि पिछले सभी हस्ताक्षरकर्ता/अनुमोदक अपनी कार्रवाई पूरी न कर लें।",
|
||||
"no": "नहीं",
|
||||
"auto-reminder": "स्वतः अनुस्मारक",
|
||||
"remind-once": " प्रत्येक (दिन) में एक बार याद दिलाएं",
|
||||
"next": "अगला",
|
||||
"select-folder": "फ़ोल्डर चुनें",
|
||||
"OpenSign-drive": "{{appName}} ड्राइव",
|
||||
"drive-document-status": {
|
||||
"Completed": "पूरा हुआ",
|
||||
@@ -261,6 +262,7 @@
|
||||
"signers": "हस्ताक्षरकर्ता",
|
||||
"signers-help": "अपने सहेजे गए संपर्कों से सुझाए गए हस्ताक्षरकर्ताओं को देखने के लिए किसी संपर्क का नाम टाइप करना शुरू करें या नए जोड़ें। वांछित क्रम में हस्ताक्षरकर्ताओं को जोड़कर हस्ताक्षर क्रम की व्यवस्था करें। हस्ताक्षरकर्ताओं को शामिल करने के लिए '+' बटन और उन्हें हटाने के लिए 'x' का उपयोग करें। प्रत्येक हस्ताक्षरकर्ता को सूचीबद्ध क्रम में दस्तावेज़ पर हस्ताक्षर करने के लिए एक ईमेल संकेत प्राप्त होगा।",
|
||||
"bcc-help": "अपने सहेजे गए संपर्कों से सुझाव देखने के लिए किसी संपर्क का नाम टाइप करना शुरू करें या नए जोड़ें। किसी उपयोगकर्ता को जोड़ने के लिए '+' बटन और उसे हटाने के लिए 'x' का उपयोग करें। चयनित उपयोगकर्ता का ईमेल Bcc (ब्लाइंड कार्बन कॉपी) के रूप में जोड़ा जाएगा। दस्तावेज़ पूरा होने के बाद प्रत्येक उपयोगकर्ता को एक ईमेल सूचना प्राप्त होगी।",
|
||||
"cc-help": "सुझाव देखने के लिए अपने सेव किए गए कॉन्टैक्ट्स में से किसी कॉन्टैक्ट का नाम टाइप करना शुरू करें या नए जोड़ें। यूज़र जोड़ने के लिए '+' बटन और हटाने के लिए 'x' का उपयोग करें। चुने गए यूज़र का ईमेल CC (कार्बन कॉपी) के रूप में जोड़ा जाएगा। दस्तावेज़ पूर्ण होने पर प्रत्येक यूज़र को एक ईमेल सूचना मिलेगी।",
|
||||
"add-signer": "हस्ताक्षरकर्ता जोड़ें",
|
||||
"contact-not-found": "संपर्क नहीं मिला",
|
||||
"add-yourself": "स्वयं को जोड़ें",
|
||||
@@ -282,13 +284,15 @@
|
||||
"yes": "हाँ",
|
||||
"copied": "कॉपी किया गया",
|
||||
"something-went-wrong-mssg": "कुछ गलत हो गया, इस पृष्ठ को रीफ्रेश करने से यह समस्या हल हो सकती है।",
|
||||
"attachments-docx-upload-failed": "फिलहाल एक समस्या आ रही है। कृपया पुनः प्रयास करें।",
|
||||
"attachments-docx-upload-limit": "बड़ी DOCX फ़ाइलों के लिए अपलोड सीमा को पार कर लिया गया है। कृपया 4 मिनट तक प्रतीक्षा करें और फिर प्रयास करें।",
|
||||
"token-generated": "टोकन सफलतापूर्वक उत्पन्न हुआ।",
|
||||
"webhook": "वेबहूक",
|
||||
"update-webhook": "वेबहूक अपडेट करें",
|
||||
"add-webhook": "वेबहूक जोड़ें",
|
||||
"webhook-added": "वेबहूक सफलतापूर्वक जोड़ा गया।",
|
||||
"team-disabled": "टीम अक्षम",
|
||||
"Team enabled": "टीम सक्षम",
|
||||
"team-enabled": "टीम सक्षम",
|
||||
"are-you-sure": "क्या आप वाकई चाहते हैं",
|
||||
"disable": "अक्षम करें",
|
||||
"enable": "सक्षम करें",
|
||||
@@ -296,7 +300,7 @@
|
||||
"edit-team": "टीम संपादित करें",
|
||||
"name-of-team": "टीम का नाम",
|
||||
"prev": "पिछला",
|
||||
"no-data-avaliable": "कोई डेटा उपलब्ध नहीं है",
|
||||
"no-data-available": "कोई डेटा उपलब्ध नहीं है",
|
||||
"add-team": "टीम जोड़ें",
|
||||
"page-not-found": "पृष्ठ नहीं मिला",
|
||||
"users-from-teams": "टीमों से उपयोगकर्ता",
|
||||
@@ -307,12 +311,37 @@
|
||||
"delete-user": "उपयोगकर्ता हटाएं",
|
||||
"delete": "हटाएं",
|
||||
"add-user": "उपयोगकर्ता जोड़ें",
|
||||
"password-generateed": "पासवर्ड केवल एक बार उत्पन्न होगा; इसे कॉपी करना सुनिश्चित करें।",
|
||||
"password-generated": "पासवर्ड केवल एक बार उत्पन्न होगा; इसे कॉपी करना सुनिश्चित करें।",
|
||||
"Team status": "टीम स्थिति",
|
||||
"user-deactivated": "उपयोगकर्ता निष्क्रिय।",
|
||||
"user-activated": "उपयोगकर्ता सक्रिय।",
|
||||
"public": "सार्वजनिक",
|
||||
"download": "डाउनलोड करें",
|
||||
"sign-offline": "ऑफ़लाइन हस्ताक्षर करें",
|
||||
"sign-offline-instructions": "दस्तावेज़ डाउनलोड करें, इसे किसी बाहरी टूल से हस्ताक्षरित करें, फिर यहाँ हस्ताक्षरित प्रति अपलोड करें। दस्तावेज़ का मालिक आपकी सबमिशन की समीक्षा करके उसे स्वीकृत या अस्वीकार करेगा।",
|
||||
"sign-offline-step-1": "बिना हस्ताक्षर का PDF डाउनलोड करें।",
|
||||
"sign-offline-step-2": "अपने पसंदीदा टूल से PDF हस्ताक्षरित करें।",
|
||||
"sign-offline-step-3": "मालिक की स्वीकृति के लिए हस्ताक्षरित PDF अपलोड करें।",
|
||||
"sign-offline-upload-hint": "स्वीकृति के लिए सबमिट करने हेतु हस्ताक्षरित PDF चुनें।",
|
||||
"sign-offline-submitted-title": "स्वीकृति के लिए सबमिट किया गया",
|
||||
"sign-offline-submitted-body": "दस्तावेज़ के मालिक को आपकी ऑफ़लाइन हस्ताक्षरित प्रति की समीक्षा करने के लिए ईमेल द्वारा सूचित किया गया है।",
|
||||
"offline-already-submitted-title": "दस्तावेज़ ऑफ़लाइन सबमिट किया गया",
|
||||
"offline-already-submitted-body": "आपने यह दस्तावेज़ पहले ही ऑफ़लाइन हस्ताक्षर समीक्षा के लिए सबमिट कर दिया है। स्वीकृति या अस्वीकृति होने पर आपको सूचित किया जाएगा।",
|
||||
"offline-action-approve-title": "ऑफ़लाइन हस्ताक्षर स्वीकृत करें",
|
||||
"offline-action-approve-confirm": "स्वीकृत करें",
|
||||
"offline-action-reject-title": "ऑफ़लाइन हस्ताक्षर अस्वीकार करें",
|
||||
"offline-action-reject-hint": "वैकल्पिक रूप से कारण बताएं। हस्ताक्षरकर्ता को ईमेल द्वारा सूचित किया जाएगा।",
|
||||
"offline-action-reject-reason-placeholder": "कारण (वैकल्पिक)",
|
||||
"offline-action-reject-confirm": "अस्वीकृति की पुष्टि करें",
|
||||
"offline-action-review-title": "ऑफ़लाइन हस्ताक्षर की समीक्षा करें",
|
||||
"offline-action-review-hint": "हस्ताक्षरकर्ता द्वारा अपलोड किए गए हस्ताक्षरित PDF का पूर्वावलोकन करें, फिर उसे स्वीकृत या अस्वीकार करें। पुष्टि करने तक कोई कार्रवाई नहीं की जाएगी।",
|
||||
"offline-action-submitted-at": "सबमिट किया गया",
|
||||
"offline-action-no-preview": "इस सबमिशन के लिए कोई पूर्वावलोकन उपलब्ध नहीं है।",
|
||||
"offline-action-open-in-new-tab": "PDF नए टैब में खोलें",
|
||||
"offline-action-invalid-link": "यह समीक्षा लिंक अमान्य है या समाप्त हो गई है।",
|
||||
"offline-action-success": "कार्रवाई पूर्ण हुई।",
|
||||
"reject": "अस्वीकार करें",
|
||||
"document": "दस्तावेज़",
|
||||
"view": "देखें",
|
||||
"make-template-public": "टेम्पलेट को सार्वजनिक करें",
|
||||
"make-template-private": "टेम्पलेट को निजी बनाएं",
|
||||
@@ -337,7 +366,7 @@
|
||||
"quick-send-alert-1": "इस दस्तावेज़ में सभी भूमिकाएँ वर्तमान में संपर्कों से जुड़ी हुई हैं। इस टेम्पलेट की प्रतियां कई हस्ताक्षरकर्ताओं को त्वरित रूप से भेजने के लिए, कृपया सुनिश्चित करें कि कम से कम एक भूमिका किसी भी संपर्क से जुड़ी नहीं है।",
|
||||
"quick-send-alert-2": "कृपया सुनिश्चित करें कि सभी प्राप्तकर्ताओं के लिए कम से कम एक हस्ताक्षर विजेट जोड़ा गया है।",
|
||||
"quick-send-alert-3": "कृपया इस टेम्पलेट में कम से कम एक भूमिका जोड़ें।",
|
||||
"quick-send-alert-4": "{{flow}} के लिए सीमा पूरी हो गई है।",
|
||||
"quick-send-alert-4": "{{flow}} प्रति बैच अधिकतम {{count}} साइनरों का समर्थन करता है। अपनी सूची विभाजित करें और पुनः प्रयास करें।",
|
||||
"copy-link": "लिंक कॉपी करें",
|
||||
"copy": "कॉपी करें",
|
||||
"revoke-document": "दस्तावेज़ रद्द करें",
|
||||
@@ -394,7 +423,10 @@
|
||||
"image": "छवि",
|
||||
"email": "ईमेल",
|
||||
"number": "संख्या",
|
||||
"draw": "आरेखित करें"
|
||||
"draw": "आरेखित करें",
|
||||
"attachments": "संलग्नक",
|
||||
"attachments-add": "संलग्नक अपलोड करने के लिए क्लिक करें",
|
||||
"attachments-count": "{{count}} संलग्नक"
|
||||
},
|
||||
"fields": "क्षेत्र",
|
||||
"recipients": "प्राप्तकर्ता",
|
||||
@@ -406,6 +438,14 @@
|
||||
"certificate": "प्रमाण पत्र",
|
||||
"decline": "अस्वीकार करें",
|
||||
"finish": "समाप्त करें",
|
||||
"approve": "स्वीकृत करें",
|
||||
"approve-mssg": " क्या आप वाकई दस्तावेज़ को स्वीकृत करना चाहते हैं?",
|
||||
"signer-role-label": "प्राप्तकर्ता की भूमिका",
|
||||
"signer-role-signer": "हस्ताक्षरकर्ता",
|
||||
"signer-role-viewer": "दर्शक",
|
||||
"signer-role-approver": "अनुमोदक",
|
||||
"signer-role-hint": "हस्ताक्षरकर्ता को हस्ताक्षर करना होगा। अनुमोदक बिना हस्ताक्षर किए अनुमोदन करता है। दर्शक केवल देखता है।",
|
||||
"signer-role-upgrade-msg": "दर्शक और अनुमोदक भूमिकाएँ केवल सशुल्क योजनाओं में उपलब्ध हैं। प्राप्तकर्ताओं को ये भूमिकाएँ सौंपने के लिए अपनी योजना अपग्रेड करें।",
|
||||
"done": "हो गया",
|
||||
"mail": "मेल",
|
||||
"sign-now": "अभी हस्ताक्षर करें",
|
||||
@@ -438,13 +478,15 @@
|
||||
"reset-password-alert-1": "पासवर्ड रीसेट लिंक आपके ईमेल आईडी पर भेज दिया गया है",
|
||||
"reset-password-alert-2": "कृपया ईमेल एडॉप्टर सेटअप करें ",
|
||||
"reset-password-alert-3": "अपना पासवर्ड रीसेट करें",
|
||||
"faild-animation": "एनिमेशन लोड करने में विफल",
|
||||
"failed-animation": "एनिमेशन लोड करने में विफल",
|
||||
"apply": "लागू करें",
|
||||
"select-columns": "स्तंभ चुनें",
|
||||
"copy-type": {
|
||||
"All pages": "सभी पृष्ठ",
|
||||
"All pages but last": "अंतिम को छोड़कर सभी पृष्ठ",
|
||||
"All pages but first": "पहले को छोड़कर सभी पृष्ठ",
|
||||
"Next to current widget": "वर्तमान विजेट के आगे"
|
||||
"Next to current widget": "वर्तमान विजेट के आगे",
|
||||
"Page range": "पृष्ठ सीमा"
|
||||
},
|
||||
"options": "विकल्प",
|
||||
"minimum-check": "न्यूनतम जाँच",
|
||||
@@ -461,6 +503,19 @@
|
||||
"formula-error-braces": "हर खुलने वाली {{ के लिए एक मिलान करने वाली }} होना चाहिए।",
|
||||
"formula-error-operators": "ऑपरेटर या कोष्ठक का उपयोग करते समय कम से कम दो मान जोड़ें।",
|
||||
"formula-error-leading-operator": "सूत्र ऑपरेटर से शुरू नहीं हो सकते।",
|
||||
"formula-error-trailing-operator": "सूत्र किसी ऑपरेटर पर समाप्त नहीं हो सकते।",
|
||||
"formula-error-adjacent-placeholders": "दो प्लेसहोल्डर बिना बीच में ऑपरेटर के साथ-साथ नहीं लिखे जा सकते।",
|
||||
"formula-error-continuous-operators": "दो ऑपरेटर लगातार साथ में उपयोग नहीं किए जा सकते।",
|
||||
"formula-error-empty-parentheses": "कोष्ठक (parentheses) खाली नहीं हो सकते।",
|
||||
"formula-error-invalid-parentheses-content": "कोष्ठक किसी ऑपरेटर से शुरू या समाप्त नहीं हो सकते।",
|
||||
"formula-error-before-open-parenthesis": "'(' से पहले ऑपरेटर गायब है। ओपनिंग कोष्ठक से पहले एक ऑपरेटर जोड़ें।",
|
||||
"formula-error-after-close-parenthesis": "')' के बाद ऑपरेटर गायब है। क्लोज़िंग कोष्ठक के बाद '+' या '*' जैसा ऑपरेटर जोड़ें।",
|
||||
"formula-error-between-parentheses": "')' और '(' के बीच ऑपरेटर गायब है। दोनों समूहों के बीच '*' जैसा ऑपरेटर जोड़ें।",
|
||||
"formula-error-division-by-zero": "शून्य से भाग देना अनुमति नहीं है। '0' को किसी मान्य गैर-शून्य संख्या या वेरिएबल से बदलें।",
|
||||
"formula-error-invalid-number-format": "अमान्य संख्या फ़ॉर्मेट मिला। संख्याएँ '1', '3.14' जैसे मान्य फ़ॉर्मेट में होनी चाहिए, '.5', '1.' या '1.2.3' नहीं।",
|
||||
"formula-error-unclosed-parenthesis": "बिना बंद किया हुआ '(' मिला। हर ओपनिंग '(' के लिए एक मैचिंग क्लोज़िंग ')' होना चाहिए।",
|
||||
"formula-error-unexpected-closing-parenthesis": "बिना मैचिंग ओपनिंग '(' के अप्रत्याशित क्लोज़िंग ')' मिला।",
|
||||
"formula-error-empty-placeholder": "खाली प्लेसहोल्डर '{{}}' अनुमति नहीं है। ब्रेसेज़ के अंदर वेरिएबल का नाम दें।",
|
||||
"decimal-places": "दशमलव स्थान",
|
||||
"number-of-decimal-places": "दशमलव स्थानों की संख्या",
|
||||
"decimal-places-helper": "पूर्णांक के लिए 0 पर सेट करें",
|
||||
@@ -529,6 +584,7 @@
|
||||
"sign-url": "यूआरएल पर हस्ताक्षर करें",
|
||||
"mails-sent": "मेल भेजे गए",
|
||||
"mail-not-delivered": "मेल वितरित नहीं किया गया",
|
||||
"email-not-verified-head": "ईमेल सत्यापित नहीं है",
|
||||
"document-alert": "दस्तावेज़ चेतावनी",
|
||||
"owner-subscription-expired": "मालिक की सदस्यता समाप्त हो गई है।",
|
||||
"subscription-expired": "सदस्यता समाप्त",
|
||||
@@ -604,6 +660,9 @@
|
||||
"document-signed-alert-7": "यह दस्तावेज़ अब उपलब्ध नहीं है।",
|
||||
"document-signed-alert-8": "दस्तावेज़ पर हस्ताक्षर के लिए पहले ही भेज दिया गया है।",
|
||||
"yet-to-sign": "अभी हस्ताक्षर करना बाकी है",
|
||||
"viewers": "दर्शक",
|
||||
"viewer-no-widgets": "दर्शकों को दस्तावेज़ की केवल-पढ़ने योग्य प्रति मिलती है और उन्हें विजेट असाइन नहीं किए जा सकते।",
|
||||
"viewer-confirm-widget-removal": "इस प्राप्तकर्ता को दर्शक में बदलने पर उन्हें पहले से असाइन किए गए सभी विजेट हटा दिए जाएंगे। जारी रखें?",
|
||||
"validation-alert-1": "इनपुट नियमित अभिव्यक्ति द्वारा निर्धारित मानदंडों को पूरा नहीं करता है।",
|
||||
"account-already-exists": "खाता पहले से मौजूद है!",
|
||||
"choose-password": "पासवर्ड चुनें",
|
||||
@@ -626,7 +685,7 @@
|
||||
"date": "दिनांक",
|
||||
"report-not-found": "रिपोर्ट नहीं मिली",
|
||||
"please-wait": "कृपया प्रतीक्षा करें",
|
||||
"select-foler": "फ़ोल्डर चुनें",
|
||||
"select-folder": "फ़ोल्डर चुनें",
|
||||
"generated-on": "पर उत्पन्न",
|
||||
"text-field": "पाठ क्षेत्र",
|
||||
"font-size": "फ़ॉन्ट आकार",
|
||||
@@ -685,13 +744,13 @@
|
||||
"public-template": "यह वीडियो दर्शाता है कि आप अपना व्यक्तिगत सार्वजनिक प्रोफ़ाइल कैसे सेट अप कर सकते हैं, जैसे 'https://opensign.me/your-username'। आप यह भी सीखेंगे कि अपनी टैगलाइन को कैसे अनुकूलित करें और अपने टेम्पलेट्स को सार्वजनिक हस्ताक्षर के लिए उपलब्ध कराएं।",
|
||||
"allowModify-widgets": "आप इन फ़ील्ड में से किसी को भी दस्तावेज़ पर खींच और छोड़ सकते हैं, इसके अतिरिक्त दस्तावेज़ निर्माता द्वारा आपके लिए पहले से निर्दिष्ट फ़ील्ड के। "
|
||||
},
|
||||
"enter-email-plaholder": "एक ईमेल पता जोड़ें और एंटर दबाएं",
|
||||
"enter-email-placeholder": "एक ईमेल पता जोड़ें और एंटर दबाएं",
|
||||
"success-email-alert": "ईमेल सफलतापूर्वक भेजा गया!",
|
||||
"expired-doc-title": "समाप्त दस्तावेज़",
|
||||
"expired-on-mssg": "यह दस्तावेज़ {{expiredDate}} को समाप्त हो गया है और अब हस्ताक्षर के लिए उपलब्ध नहीं है।",
|
||||
"signature-validate-alert": "कृपया पुष्टि करें कि आपने कम से कम {{minRequiredCount}} चेकबॉक्स चुने हैं।",
|
||||
"signature-validate-alert-2": "सुनिश्चित करें कि यह फ़ील्ड सटीक रूप से भरा गया है और सभी आवश्यकताओं को पूरा करता है।",
|
||||
"remaing-users": "शेष उपयोगकर्ता",
|
||||
"remaining-users": "शेष उपयोगकर्ता",
|
||||
"Role": "भूमिका",
|
||||
"additional-users": "कृपया ऐड-ऑन उपयोगकर्ता खरीदें।",
|
||||
"Quantity-of-users": "उपयोगकर्ताओं की मात्रा",
|
||||
@@ -713,7 +772,7 @@
|
||||
"rotate-left": "बाईं ओर घुमाएँ",
|
||||
"rotate-alert-mssg": "इस पृष्ठ पर सभी विजेट खो जाएंगे। क्या आप वाकई आगे बढ़ना चाहते हैं?",
|
||||
"templateid": "टेम्पलेट-आईडी",
|
||||
"bulk-send-subcription-alert": "थोक भेजने का उपयोग करने के लिए कृपया व्यावसायिक या टीम योजना में अपग्रेड करें।",
|
||||
"bulk-send-subscription-alert": "थोक भेजने का उपयोग करने के लिए कृपया व्यावसायिक या टीम योजना में अपग्रेड करें।",
|
||||
"generate-test-token": "परीक्षण टोकन उत्पन्न करें",
|
||||
"regenerate-test-token": "परीक्षण टोकन पुनः उत्पन्न करें",
|
||||
"help-test-token": "इस टोकन का उपयोग https://sandbox.opensignlabs.com/api/v1 समापन बिंदु पर एपीआई का परीक्षण करने के लिए किया जा सकता है, जिससे आप असीमित दस्तावेज़ हस्ताक्षर कर सकते हैं। कृपया ध्यान दें कि सैंडबॉक्स एपीआई आपके दस्तावेज़ों पर स्व-हस्ताक्षरित प्रमाणपत्रों के साथ हस्ताक्षर करेगा, जिन्हें एडोब द्वारा मान्य के रूप में मान्यता नहीं दी जा सकती है। एक बार जब आप अपना परीक्षण पूरा कर लेते हैं, तो आप उत्पादन टोकन उत्पन्न करने के लिए हमारी किसी भुगतान योजना में अपग्रेड कर सकते हैं।",
|
||||
@@ -733,8 +792,9 @@
|
||||
"copied-code": "कॉपी किया गया",
|
||||
"Installation": "इंस्टॉलेशन",
|
||||
"Usage": "उपयोग",
|
||||
"insufficient-credits": "अपर्याप्त हस्ताक्षर क्रेडिट",
|
||||
"insufficient-credits-mssg": "इस दस्तावेज़ के स्वामी के पास वर्तमान में आपके हस्ताक्षर करने के लिए आवश्यक OpenSign क्रेडिट की कमी है। यदि आपको अधिक विवरण चाहिए तो कृपया स्वामी से संपर्क करें।",
|
||||
"insufficient-credits": "हस्ताक्षर क्रेडिट अपर्याप्त हैं। कृपया {{ownerEmail}} से संपर्क करें",
|
||||
"insufficient-credits-title": "अपर्याप्त हस्ताक्षर क्रेडिट",
|
||||
"insufficient-credits-mssg": "इस दस्तावेज़ के स्वामी के पास वर्तमान में आपके हस्ताक्षर करने के लिए आवश्यक {{appName}} क्रेडिट की कमी है। यदि आपको अधिक विवरण चाहिए तो कृपया स्वामी से संपर्क करें।",
|
||||
"angular-npm-mssg-1": "OpenSign को अपने Angular प्रोजेक्ट में एकीकृत करने के लिए, बस निम्नलिखित कमांड चलाएँ:",
|
||||
"quota-mail-info-head": "मासिक ईमेल सीमा",
|
||||
"quota-mail-info": "सेवा की गुणवत्ता बनाए रखने और स्पैम को रोकने के लिए, OpenSign निःशुल्क योजना पर प्रति माह 15 ईमेल तक की अनुमति देता है। असीमित ईमेल भेजने के लिए अभी अपग्रेड करें।",
|
||||
@@ -753,6 +813,13 @@
|
||||
"p3": "इस विकल्प का चयन करने पर ओटीपी सत्यापन अक्षम हो जाएगा, जिससे उपयोगकर्ता अतिरिक्त चरणों के बिना सीधे दस्तावेज़ पर हस्ताक्षर कर सकेंगे।",
|
||||
"p4": "कृपया वह विकल्प चुनें जो आपके दस्तावेज़ हस्ताक्षर आवश्यकताओं के लिए सबसे उपयुक्त हो।"
|
||||
},
|
||||
"allow-offline-sign": "ऑफ़लाइन हस्ताक्षर की अनुमति दें",
|
||||
"allow-offline-sign-help": {
|
||||
"p1": "चुनें कि क्या हस्ताक्षरकर्ता बाहरी टूल से हस्ताक्षरित इस दस्तावेज़ की एक प्रति स्वामी की समीक्षा के लिए जमा कर सकते हैं।",
|
||||
"p2": "हस्ताक्षरकर्ताओं को ऑफ़लाइन हस्ताक्षर विकल्प दिखाई देगा और वे बाहरी रूप से हस्ताक्षरित PDF स्वीकृति के लिए अपलोड कर सकते हैं।",
|
||||
"p3": "हस्ताक्षरकर्ता केवल एप के भीतर ही हस्ताक्षर कर सकते हैं। ऑफ़लाइन हस्ताक्षर विकल्प छिपा दिया जाएगा।",
|
||||
"p4": "यह सेटिंग डिफ़ॉल्ट रूप से आपकी खाता प्राथमिकता का उपयोग करती है और भुगतान योजनाओं पर उपलब्ध है।"
|
||||
},
|
||||
"advanced-options": "उन्नत विकल्प",
|
||||
"hide-advanced-options": "उन्नत विकल्प छिपाएँ",
|
||||
"download-files": "फ़ाइलें डाउनलोड करें",
|
||||
@@ -815,6 +882,7 @@
|
||||
"delete-folder-alert-1": "हटाने में असमर्थ: फ़ोल्डर खाली होना चाहिए।",
|
||||
"defaultSign-alert": "{{widgetsType}} विजेट स्वतः हस्ताक्षर के लिए उपलब्ध नहीं हैं।",
|
||||
"embed-heading": "{{appName}} टेम्पलेट एम्बेड करें",
|
||||
"other-apps-by-opensign": "OpenSign टीम के अन्य ऐप्स",
|
||||
"public-template-edit-title": "सार्वजनिक टेम्पलेट संपादित करें",
|
||||
"public-template-edit-mssg": "आप सीधे सार्वजनिक टेम्पलेट संपादित नहीं कर सकते। परिवर्तन करने के लिए, टेम्पलेट को निजी पर सेट किया जाएगा, और आपको बाद में इसे फिर से सार्वजनिक करना होगा। क्या आप जारी रखना चाहते हैं?",
|
||||
"agree-p1": "मैं पुष्टि करता हूं कि मैंने पढ़ लिया है और समझ लिया है ",
|
||||
@@ -866,7 +934,6 @@
|
||||
"js-snippet-msg-1": "आप टेम्पलेट आईडी इससे प्राप्त कर सकते हैं",
|
||||
"js-snippet-msg-2": "टेम्पलेट प्रबंधित करें",
|
||||
"js-snippet-msg-3": "पृष्ठ।",
|
||||
"agrrement-alert": "आगे बढ़ने के लिए, आपको इलेक्ट्रॉनिक रिकॉर्ड और हस्ताक्षर प्रकटीकरण के लिए सहमति देनी होगी।",
|
||||
"webhook-already-exists": "URL पहले से मौजूद है! कृपया कोई दूसरा प्रयास करें।",
|
||||
"webhook-must-be-secure": "वेबहूक URL सुरक्षित होना चाहिए और https:// का उपयोग करना चाहिए",
|
||||
"provide-test-token": "कृपया एपीआई परीक्षण टोकन उत्पन्न करें",
|
||||
@@ -886,6 +953,7 @@
|
||||
"p3": "इस विकल्प का चयन करने से हस्ताक्षरकर्ताओं को हस्ताक्षर प्रक्रिया के दौरान दस्तावेज़ में कोई भी সংযোজন करने से प्रतिबंधित किया जाता है।"
|
||||
},
|
||||
"signature-setting": "हस्ताक्षर सेटिंग्स",
|
||||
"rotation": "घुमाव",
|
||||
"activation-key": "सक्रियण कुंजी",
|
||||
"paid-selfhost-help": "अधिक जानकारी के लिए कृपया {{appName}} से संपर्क करें ",
|
||||
"remaining-credits-selfhost-help": "अपनी वेबसाइट पर एपीआई दस्तावेज़ हस्ताक्षर, थोक भेजने {{appName}} एकीकरण के लिए प्रीमियम क्रेडिट का उपयोग करें। आपके पास {{allowedcredits}} शामिल क्रेडिट और {{addoncredits}} अतिरिक्त खरीदे गए क्रेडिट शेष हैं।",
|
||||
@@ -914,6 +982,8 @@
|
||||
"visit-below-link": "अधिक जानने के लिए नीचे दिए गए लिंक पर जाएं -",
|
||||
"storage-help": "BYOC सक्षम करने से आप अपना स्वयं का S3 संग्रहण कनेक्ट कर सकते हैं ताकि आपकी फ़ाइलें पूरी तरह से आपके नियंत्रण में रहें - कोई बाहरी प्रतियां बरकरार न रहें। यदि डेटा स्वायत्तता आपके लिए मायने रखती है, तो इस सुविधा को अनलॉक करने के लिए टीमों में अपग्रेड करने पर विचार करें।",
|
||||
"daily-quota-reached": "आपने अपना दैनिक कोटा पूरा कर लिया है। सहायता के लिए, कृपया quotas@opensignlabs.com पर संपर्क करें।",
|
||||
"email-not-verified-send": "ईमेल भेजने से पहले कृपया अपना ईमेल पता सत्यापित करें। ईमेल सत्यापन पूरा करने के लिए अपनी <1>प्रोफ़ाइल सेटिंग्स</1> में जाएँ।",
|
||||
"email-not-verified-send-plain": "ईमेल भेजने से पहले कृपया अपना ईमेल पता सत्यापित करें। ईमेल सत्यापन पूरा करने के लिए अपनी प्रोफ़ाइल सेटिंग्स में जाएँ।",
|
||||
"enabled-signature-type": "सक्षम हस्ताक्षर प्रकार",
|
||||
"enabled-signature-type-help": "'सक्षम हस्ताक्षर प्रकार' सेटिंग यह निर्धारित करती है कि आपके संगठन में कौन से हस्ताक्षर विकल्प उपलब्ध हैं। उदाहरण के लिए, यदि आप 'ड्रा' विकल्प को अक्षम करते हैं, तो आपके संगठन के सदस्य इसे हस्ताक्षर विजेट में नहीं देख पाएंगे, जबकि अन्य तीन विकल्प पहुंच योग्य रहेंगे।",
|
||||
"indexing-public-profile": "खोज इंजन द्वारा सार्वजनिक प्रोफ़ाइल को अनुक्रमित करने की अनुमति दें",
|
||||
@@ -941,6 +1011,7 @@
|
||||
"please-provide-templateid": "कृपया टेम्पलेट आईडी प्रदान करें",
|
||||
"this-template-is-not-public": "यह टेम्पलेट सार्वजनिक नहीं है",
|
||||
"invalid-templateid": "अमान्य टेम्पलेट आईडी",
|
||||
"kiosk-no-active-role": "इस टेम्पलेट में कियोस्क हस्ताक्षर के लिए कोई हस्ताक्षरकर्ता या अनुमोदक भूमिका उपलब्ध नहीं है।",
|
||||
"contact-billing-at-opensign": "अधिक सीटें जोड़ने के लिए, कृपया सहायता के लिए <1>billing@opensignlabs.com</1> पर OpenSign™ से संपर्क करें",
|
||||
"title-length-alert": "शीर्षक अधिकतम 250 वर्ण लंबा होना चाहिए।",
|
||||
"note-length-alert": "टिप्पणी अधिकतम 200 वर्ण लंबी होनी चाहिए।",
|
||||
@@ -1030,7 +1101,7 @@
|
||||
},
|
||||
"signer-already-present": "हस्ताक्षरकर्ता पहले से मौजूद है",
|
||||
"kiosk-sign": "कियोस्क साइन",
|
||||
"dont-have-access-to-template": "टेम्पलेट हटा दिया गया है या आपके पास पहुंच नहीं है। कृपया प्रेषक से संपर्क करें।",
|
||||
"do-not-have-access-to-template": "टेम्पलेट हटा दिया गया है या आपके पास पहुंच नहीं है। कृपया प्रेषक से संपर्क करें।",
|
||||
"kiosk-info": "कियोस्क मोड आपको व्यक्तिगत रूप से हस्ताक्षर शीघ्रता और कुशलता से एकत्र करने देता है। ट्रेड शो, कार्यक्रमों या वॉक-इन परिदृश्यों के लिए आदर्श जहां सभी हस्ताक्षरकर्ता शारीरिक रूप से मौजूद हों। ",
|
||||
"learn-more": "और जानें",
|
||||
"finish-mssg": " क्या आप वाकई दस्तावेज़ समाप्त करना चाहते हैं?",
|
||||
@@ -1086,8 +1157,6 @@
|
||||
"search-contacts": "संपर्क खोजें…",
|
||||
"add-role-alert": "कृपया कम से कम एक भूमिका जोड़ें",
|
||||
"edit-draft": "ड्राफ्ट संपादित करें",
|
||||
"invalid-email-found": "अमान्य ईमेल पाया गया: {{email}}",
|
||||
"duplicate-email-found": "डुप्लिकेट ईमेल पाया गया: {{email}}",
|
||||
"vertical": "वर्टिकल",
|
||||
"horizontal": "हॉरिज़ॉन्टल",
|
||||
"billing": "बिलिंग",
|
||||
@@ -1101,7 +1170,7 @@
|
||||
"access-denied": "पहुंच अस्वीकृत",
|
||||
"upgrade": "अपग्रेड",
|
||||
"do-not-access-app": "आपको इस एप्लिकेशन तक पहुंच नहीं है।",
|
||||
"dont-have-access": "आपको पहुंच नहीं है।",
|
||||
"do-not-have-access": "आपको पहुंच नहीं है।",
|
||||
"valid-email-alert": "कृपया एक मान्य ईमेल पता दर्ज करें।",
|
||||
"otp-not-validate": "ओटीपी मान्य नहीं है।",
|
||||
"domain-not-allowed": "यह डोमेन अनुमति नहीं है",
|
||||
@@ -1118,10 +1187,12 @@
|
||||
"branding-help": "ब्रांडिंग आपकी ऐप में वाइट लेबलिंग की सुविधा देती है",
|
||||
"custom-sub-domain": "कस्टम सब-डोमेन",
|
||||
"app-name": "ऐप का नाम",
|
||||
"provide-domain-name": "अपना डोमेन नाम दर्ज करें",
|
||||
"provide-domain-name": "केवल सबडोमेन की अनुमति है (उदा., app.yourdomain.com)। yourdomain.com जैसे प्राथमिक/रूट डोमेन समर्थित नहीं हैं।",
|
||||
"provide-app-name-help": "एक अनोखा ऐप नाम प्रदान करें। यह एप्लिकेशन के भीतर आपके ब्रांड नाम के रूप में और सभी ईमेल में, जहाँ भी ब्रांडिंग लागू हो, प्रदर्शित होगा।",
|
||||
"upload-app-logo": ".png, .jpg या .svg फ़ॉर्मेट में लोगो अपलोड करें। सर्वोत्तम डिस्प्ले गुणवत्ता के लिए अनुशंसित आकार: {{size}} पिक्सेल (5:2 आस्पेक्ट रेशियो)।",
|
||||
"upload-app-favicon": "एक चौकोर फ़ेविकॉन इमेज (.ico, .png या .svg) अपलोड करें। सर्वोत्तम ब्राउज़र अनुकूलता के लिए अनुशंसित आकार: {{size}} पिक्सेल।",
|
||||
"provide-app-name": "अपना ऐप नाम दर्ज करें",
|
||||
"logo": "लोगो",
|
||||
"upload-app-logo": "अपने ऐप का लोगो अपलोड करें",
|
||||
"prefill-unfilled-widget": "निम्न अनिवार्य फ़ील्ड खाली नहीं छोड़े जा सकते: {{emptyWidget}}। कृपया आगे बढ़ने के लिए इन्हें भरें।",
|
||||
"Dashboard": "डैशबोर्ड",
|
||||
"Analytics": "विश्लेषिकी",
|
||||
@@ -1289,11 +1360,11 @@
|
||||
"signer-form-data-for": "हस्ताक्षरकर्ता के लिए फ़ॉर्म डेटा: {{signerName}} <{{signerEmail}}>",
|
||||
"widget-name": "विजेट नाम",
|
||||
"widget-responses": "विजेट प्रतिक्रियाएँ",
|
||||
"formdata-download-note": "नोट: हस्ताक्षर, स्टैम्प, प्रारंभिक अक्षर और छवियाँ निर्यात नहीं की जाएँगी।",
|
||||
"formdata-download-note": "नोट: हस्ताक्षर, स्टैम्प, प्रारंभिक अक्षर और छवियाँ निर्यात नहीं की जाएँगी। ऑफ़लाइन हस्ताक्षरकर्ताओं का डेटा उपलब्ध नहीं होगा।",
|
||||
"export-all-formdata-error": "आप फ़ॉर्म डेटा निर्यात नहीं कर सकते क्योंकि टेम्पलेट में डुप्लिकेट विजेट नाम हैं। कृपया नया टेम्पलेट बनाएं ताकि आप फ़ॉर्म डेटा निर्यात कर सकें।",
|
||||
"export-formdata-error": "आप फ़ॉर्म डेटा निर्यात नहीं कर सकते क्योंकि दस्तावेज़ में डुप्लिकेट विजेट नाम हैं। कृपया नया दस्तावेज़ बनाएं जिसमें डुप्लिकेट विजेट नाम न हों।",
|
||||
"duplicate-widget-name-error": "यह विजेट नाम पहले से ही किसी अन्य साइनर को सौंपा गया है। कृपया कोई दूसरा नाम चुनें।",
|
||||
"duplicate-template-widget-error": "आप इस टेम्पलेट को डुप्लिकेट नहीं कर सकते क्योंकि इसमें डुप्लिकेट विजेट नाम हैं।",
|
||||
"duplicate-template-widget-error": "टेम्पलेट को डुप्लिकेट नहीं किया जा सकता क्योंकि कुछ विजेट नाम अलग-अलग भूमिकाओं में डुप्लिकेट हैं। डुप्लिकेट विजेट्स का नाम बदलें और फिर से प्रयास करें।",
|
||||
"credit-alert": "क्रेडिट अलर्ट",
|
||||
"document-form-data": "दस्तावेज़ फ़ॉर्म डेटा",
|
||||
"view-formdata-paid-title-error": "'फ़ॉर्म डेटा देखें' सुविधा आपको हस्ताक्षर करते समय साइनरों द्वारा भरे गए विजेट डेटा को प्रिंट और डाउनलोड करने की सुविधा देती है.",
|
||||
@@ -1345,8 +1416,8 @@
|
||||
"file-selected": "फ़ाइल चयनित",
|
||||
"bulk-send-csv-helper": "साइनर भूमिकाओं (भूमिका ईमेल/नाम/फ़ोन) और प्रीफ़िल मानों (प्रीफ़िल: फ़ील्ड नाम) के लिए हेडर का उपयोग करें। आवश्यक प्रीफ़िल फ़ील्ड भरना अनिवार्य है।",
|
||||
"bulk-send-csv-helper-note": "नोट: CSV में तिथि-प्रकार के प्रीफ़िल फ़ील्ड्स के लिए मान इस प्रारूप में प्रदान किया जाना चाहिए: {{value}} (उदाहरण: {{example}})।",
|
||||
"bulk-send-csv-summary": "{{total}} पंक्तियाँ आयात की गईं। {{invalid}} अमान्य पंक्तियाँ छोड़ दी गईं।",
|
||||
"bulk-send-csv-missing-headers": "आवश्यक CSV हेडर अनुपलब्ध हैं। भूमिकाएँ: {{roles}}। प्रीफ़िल: {{prefill}}।",
|
||||
"bulk-send-csv-summary": "CSV से {{total}} पंक्तियाँ आयात की गईं।",
|
||||
"bulk-send-csv-missing-headers": "आवश्यक CSV हेडर अनुपलब्ध हैं। भूमिकाएँ: {{roles}}।",
|
||||
"bulk-send-no-records": "भेजने से पहले कृपया कम से कम एक प्राप्तकर्ता जोड़ें।",
|
||||
"csv-only-support": "केवल CSV फ़ाइलें समर्थित हैं।",
|
||||
"csv-no-records": "CSV में कोई रिकॉर्ड नहीं मिला।",
|
||||
@@ -1360,5 +1431,184 @@
|
||||
"enter-daily-email-quota": "दैनिक ईमेल कोटा दर्ज करें",
|
||||
"enter-custom-regular-expression": "कस्टम रेगुलर एक्सप्रेशन दर्ज करें",
|
||||
"enter-value": "{{value}} दर्ज करें",
|
||||
"are-you-sure-want-to-send-document": "क्या आप वाकई दस्तावेज़ भेजना चाहते हैं?"
|
||||
}
|
||||
"are-you-sure-want-to-send-document": "क्या आप वाकई दस्तावेज़ भेजना चाहते हैं?",
|
||||
"favicon": "फ़ेविकॉन",
|
||||
"delete-two-factor-authentication": "क्या आप वाकई दो-कारक प्रमाणीकरण हटाना चाहते हैं?",
|
||||
"paste-html-here": "यहाँ HTML पेस्ट करें",
|
||||
"open-email-builder": "आप <1>email builder</1> प्लेटफ़ॉर्म का उपयोग करके ईमेल टेम्पलेट बना सकते हैं और HTML कोड कॉपी कर सकते हैं।",
|
||||
"contact-saved": "संपर्क सफलतापूर्वक सहेजा गया।",
|
||||
"remove-limit-of-user": "क्या आप वाकई इस उपयोगकर्ता की सीमा हटाना चाहते हैं? इसके बाद यह उपयोगकर्ता आपकी संस्था के सभी उपलब्ध प्रीमियम क्रेडिट का उपयोग कर सकेगा, जो सभी उपयोगकर्ताओं के बीच साझा किए जाते हैं।",
|
||||
"current-expiry-date": "वर्तमान समाप्ति तिथि",
|
||||
"reminder-error": "“हर (दिन) में एक बार याद दिलाएं” का मान “पूरा करने का समय (दिन)” से अधिक नहीं होना चाहिए।",
|
||||
"showing-to-of-records": "{{totalRows}} रिकॉर्ड में से {{record}} से {{recordPerPage}} तक दिखाया जा रहा है",
|
||||
"records-per-page": "प्रति पृष्ठ रिकॉर्ड",
|
||||
"email-not-found-in-row": "पंक्ति {{row}} में ईमेल नहीं मिला।",
|
||||
"invalid-email-found-in-row": "पंक्ति {{row}} में अमान्य ईमेल मिला: {{email}}",
|
||||
"duplicate-email-found-in-row": "पंक्ति {{row}} में डुप्लिकेट ईमेल मिला: {{email}}",
|
||||
"prefill": "प्रीफ़िल",
|
||||
"recipients-&-data": "प्राप्तकर्ता और डेटा",
|
||||
"response": "प्रतिक्रिया",
|
||||
"prefill-tab-help": "ये सामान्य टेम्पलेट फ़ील्ड हैं जो इस बल्क सेंड के सभी दस्तावेज़ों पर लागू होंगे।",
|
||||
"recipient-tab-help-p1": "इस अगले चरण में, आप:",
|
||||
"recipient-tab-help-p2": "प्राप्तकर्ता विवरण के साथ CSV अपलोड करेंगे या",
|
||||
"recipient-tab-help-p3": "प्राप्तकर्ता जानकारी मैन्युअल रूप से दर्ज करेंगे",
|
||||
"recipient-tab-help-p4": "प्रत्येक प्राप्तकर्ता को इस टेम्पलेट से जनरेट किया गया अपना दस्तावेज़ प्राप्त होगा।",
|
||||
"response-tab-help": "अपनी प्रतिक्रियाओं की समीक्षा करें।",
|
||||
"summary": "सारांश",
|
||||
"prefill-fields": "प्रीफ़िल फ़ील्ड",
|
||||
"documents": "दस्तावेज़",
|
||||
"message": "संदेश",
|
||||
"formula-help-p1": "संख्यात्मक इनपुट एकत्र करें और सूत्रों का उपयोग करके डायनामिक गणनाएँ करें।",
|
||||
"example": "उदाहरण",
|
||||
"formula-help-p2": "number-3 में कुल दिखाने के लिए {{number-1}} + {{number-2}} का उपयोग करें।",
|
||||
"formula-help-p3": "number-4 में मान निकालने के लिए ({{number-1}} * {{number-2}}) / {{number-3}} का उपयोग करें।",
|
||||
"formula-help-p4": "प्रत्येक नंबर विजेट का एक अद्वितीय नाम होना चाहिए (जैसे number-1), क्योंकि सूत्र इन पहचानकर्ताओं पर निर्भर करते हैं।",
|
||||
"note-consume-premium-credits": "नोट: यह सुविधा प्रीमियम क्रेडिट का उपयोग करती है।",
|
||||
"note-consume-premium-credits-on-document-send": "नोट: यह सुविधा प्रीमियम क्रेडिट का उपयोग करती है। दस्तावेज़ भेजे जाने पर हर बार क्रेडिट उपयोग किए जाते हैं, चाहे दस्तावेज़ पर हस्ताक्षर किए जाएँ या नहीं।",
|
||||
"webhook-authentication-help": "{{appName}} प्रत्येक वेबहुक को सुरक्षित करता है, आपके वेबहुक सुरक्षा कुंजी और मूल अनुरोध पेलोड का उपयोग करके HMAC-SHA256 हस्ताक्षर उत्पन्न करके। यह हस्ताक्षर x-webhook-signature हेडर में भेजा जाता है। आपके सर्वर को प्राप्त पेलोड और आपकी सीक्रेट कुंजी का उपयोग करके वही हस्ताक्षर उत्पन्न करना होगा। यदि दोनों हस्ताक्षर मेल खाते हैं, तो वेबहुक अनुरोध प्रमाणिक है और उसमें कोई परिवर्तन नहीं किया गया है।",
|
||||
"invalid-widget-found-in-row": "पंक्ति {{row}} में विजेट {{widget}} के लिए अमान्य मान पाया गया।",
|
||||
"all-signer-status": "सभी हस्ताक्षरकर्ताओं की स्थिति",
|
||||
"viewed": "देखा गया",
|
||||
"signed": "हस्ताक्षरित",
|
||||
"switch-to-advanced": "एडवांस्ड पर स्विच करें",
|
||||
"switch-to-basic": "बेसिक पर स्विच करें",
|
||||
"use-name-as-sender": "प्रेषक के रूप में प्रोफ़ाइल नाम का उपयोग करें",
|
||||
"use-name-as-sender-help": "सक्षम होने पर, {{appName}} हस्ताक्षर अनुरोध और पूर्णता ईमेल के लिए आपके प्रोफ़ाइल नाम को प्रेषक के रूप में उपयोग करता है।",
|
||||
"acro-fields-detected": "फॉर्म फ़ील्ड मिले",
|
||||
"acro-fields-detected-msg": "अपलोड किए गए PDF में {{count}} फॉर्म फ़ील्ड पाए गए। क्या आप स्वचालित रूप से संबंधित विजेट सम्मिलित करना चाहेंगे?",
|
||||
"documents-count": "दस्तावेज़ों की संख्या",
|
||||
"templates-count": "टेम्पलेट्स की संख्या",
|
||||
"emails-sent": "भेजे गए ईमेल",
|
||||
"storage-used": "उपयोग किया गया स्टोरेज",
|
||||
"docs-count-team-help": "आपकी संगठनों में सभी उपयोगकर्ताओं द्वारा बनाए गए दस्तावेज़ों की कुल संख्या।",
|
||||
"docs-count-help": "आपके द्वारा बनाए गए दस्तावेज़ों की कुल संख्या।",
|
||||
"templates-count-team-help": "आपकी संगठनों में सभी उपयोगकर्ताओं द्वारा बनाए गए टेम्पलेट्स की कुल संख्या।",
|
||||
"templates-count-help": "आपके द्वारा बनाए गए टेम्पलेट्स की कुल संख्या।",
|
||||
"emails-sent-team-help": "आपकी संगठनों में भेजी गई ईमेल सूचनाओं की कुल संख्या।",
|
||||
"emails-sent-help": "आपके द्वारा भेजी गई ईमेल सूचनाओं की कुल संख्या।",
|
||||
"storage-used-team-help": "आपकी संगठनों में सभी उपयोगकर्ताओं द्वारा उपयोग किया गया कुल स्टोरेज स्पेस।",
|
||||
"storage-used-help": "आपके द्वारा उपयोग किया गया कुल स्टोरेज स्पेस।",
|
||||
"cloud": "क्लाउड",
|
||||
"on-premises": "ऑन-प्रिमाइसेस",
|
||||
"plan-name-free": "OPENSIGN™ FREE",
|
||||
"plan-name-professional": "OPENSIGN™ PROFESSIONAL",
|
||||
"plan-name-teams": "OPENSIGN™ TEAMS",
|
||||
"plan-name-enterprise": "OPENSIGN™ ENTERPRISE",
|
||||
"plan-name-self-hosted": "OPENSIGN™ SELF-HOSTED",
|
||||
"plan-subtitle-free": "निःशुल्क असीमित ई-हस्ताक्षर, हमेशा के लिए।",
|
||||
"plan-subtitle-professional": "उन्नत सुविधाओं तक विशेष पहुँच।",
|
||||
"plan-subtitle-scalable": "प्राथमिकता समर्थन के साथ स्केलेबल सुविधाएँ।",
|
||||
"subscribe": "सदस्यता लें",
|
||||
"contact-us": "हमसे संपर्क करें",
|
||||
"benefit-unlimited-digital-signatures": "असीमित डिजिटल हस्ताक्षर",
|
||||
"benefit-sign-docs-yourself": "स्वयं दस्तावेज़ पर हस्ताक्षर करें",
|
||||
"benefit-request-signatures": "दूसरों से हस्ताक्षर का अनुरोध करें",
|
||||
"benefit-unlimited-templates": "असीमित टेम्पलेट",
|
||||
"benefit-14-field-types": "14 फ़ील्ड प्रकार",
|
||||
"benefit-automatic-e-signatures": "स्वचालित ई-हस्ताक्षर",
|
||||
"benefit-completion-certificates": "समापन प्रमाण पत्र",
|
||||
"benefit-send-in-order": "क्रम में भेजें",
|
||||
"benefit-organize-docs": "OpenSign™ Drive में दस्तावेज़ व्यवस्थित करें",
|
||||
"benefit-document-templates": "दस्तावेज़ टेम्पलेट",
|
||||
"benefit-import-from-dropbox": "Dropbox से आयात करें",
|
||||
"benefit-contact-book": "संपर्क पुस्तक",
|
||||
"benefit-document-expiry": "दस्तावेज़ समाप्ति समर्थन",
|
||||
"benefit-decline-document": "दस्तावेज़ अस्वीकृति समर्थन",
|
||||
"benefit-email-notifications": "ईमेल सूचनाएं",
|
||||
"benefit-public-profiles": "सार्वजनिक प्रोफ़ाइल",
|
||||
"benefit-and-much-more": "और बहुत कुछ",
|
||||
"benefit-everything-free": "OpenSign™ Free में सब कुछ",
|
||||
"benefit-field-validations": "फ़ील्ड सत्यापन",
|
||||
"benefit-regex-validations": "नियमित अभिव्यक्ति सत्यापन",
|
||||
"benefit-webhooks": "वेबहुक",
|
||||
"benefit-zapier": "Zapier एकीकरण",
|
||||
"benefit-api-access": "API एक्सेस",
|
||||
"benefit-api-signatures-100": "100 तक API हस्ताक्षर",
|
||||
"benefit-api-signatures-240": "240 तक API हस्ताक्षर",
|
||||
"benefit-custom-email-templates": "कस्टम ईमेल टेम्पलेट",
|
||||
"benefit-custom-smtp": "ईमेल भेजने के लिए अपना Gmail या SMTP खाता कनेक्ट करें",
|
||||
"benefit-auto-reminders": "स्वचालित अनुस्मारक",
|
||||
"benefit-bulk-send-100": "बल्क भेजें (100 दस्तावेज़ तक)",
|
||||
"benefit-bulk-send-240": "बल्क भेजें (240 दस्तावेज़ तक)",
|
||||
"benefit-premium-profiles": "प्रीमियम सार्वजनिक प्रोफ़ाइल उपयोगकर्ता नाम",
|
||||
"benefit-email-verification": "हस्ताक्षरकर्ता की पहचान की पुष्टि के लिए ईमेल-आधारित सत्यापन लागू करें",
|
||||
"benefit-embedded-signing": "एम्बेडेड हस्ताक्षर",
|
||||
"benefit-everything-professional": "OpenSign™ Professional में सब कुछ",
|
||||
"benefit-teams-organizations": "टीम और संगठन",
|
||||
"benefit-share-templates-teams": "टीमों के साथ टेम्पलेट साझा करें",
|
||||
"benefit-share-templates-individuals": "व्यक्तियों के साथ टेम्पलेट साझा करें",
|
||||
"benefit-byoc": "BYOC - अपने दस्तावेज़ अपने क्लाउड स्टोरेज में संग्रहीत करें",
|
||||
"benefit-doc-id-removal": "हस्ताक्षरित दस्तावेज़ों से DocumentId हटाना",
|
||||
"benefit-request-payments": "भुगतान का अनुरोध (जल्द आ रहा है)",
|
||||
"benefit-mobile-app": "मोबाइल ऐप (जल्द आ रहा है)",
|
||||
"benefit-api-signatures-500": "500 तक API हस्ताक्षर",
|
||||
"benefit-bulk-send-500": "बल्क भेजें (500 दस्तावेज़ तक)",
|
||||
"benefit-all-features": "सभी सुविधाएँ",
|
||||
"benefit-custom-domain": "कस्टम डोमेन",
|
||||
"benefit-custom-branding": "कस्टम ब्रांडिंग",
|
||||
"benefit-uptime-sla": "अपटाइम SLA",
|
||||
"benefit-sso-support": "SSO समर्थन",
|
||||
"benefit-priority-support": "प्राथमिकता समर्थन",
|
||||
"benefit-3-users-1500-credits": "3 उपयोगकर्ता और 1500 प्रीमियम क्रेडिट शामिल हैं",
|
||||
"benefit-public-links": "सार्वजनिक लिंक",
|
||||
"benefit-kiosk-mode": "कियोस्क मोड",
|
||||
"benefit-bulk-send-1500": "बल्क भेजें (1500 दस्तावेज़ तक)",
|
||||
"benefit-connect-gmail-smtp": "अपना Gmail या SMTP कनेक्ट करें",
|
||||
"benefit-analytics": "विश्लेषण",
|
||||
"benefit-bulksend-api-pro-month-help": "इस प्लान में प्रति माह 100 प्रीमियम क्रेडिट मिलते हैं, जिनसे आप API के जरिए दस्तावेज़ भेज सकते हैं, पब्लिक साइनिंग लिंक शेयर कर सकते हैं, बल्क सिग्नेचर रिक्वेस्ट चला सकते हैं, इन-पर्सन साइनिंग के लिए Kiosk मोड का उपयोग कर सकते हैं, या अपनी वेबसाइट पर सीधे एम्बेड किए गए दस्तावेज़ों से सिग्नेचर एकत्र कर सकते हैं। हर बार जब आप किसी दस्तावेज़ को शुरू (initiate) करते हैं या भेजते हैं, 1 क्रेडिट उपयोग होता है — और उस एक दस्तावेज़ में अधिकतम 10 साइनर हो सकते हैं।",
|
||||
"benefit-bulksend-api-pro-year-help": "इस प्लान में प्रति वर्ष 240 प्रीमियम क्रेडिट मिलते हैं, जिनसे आप API के जरिए दस्तावेज़ भेज सकते हैं, पब्लिक साइनिंग लिंक शेयर कर सकते हैं, बल्क सिग्नेचर रिक्वेस्ट चला सकते हैं, इन-पर्सन साइनिंग के लिए Kiosk मोड का उपयोग कर सकते हैं, या अपनी वेबसाइट पर सीधे एम्बेड किए गए दस्तावेज़ों से सिग्नेचर एकत्र कर सकते हैं। हर बार जब आप किसी दस्तावेज़ को शुरू (initiate) करते हैं या भेजते हैं, 1 क्रेडिट उपयोग होता है — और उस एक दस्तावेज़ में अधिकतम 10 साइनर हो सकते हैं।",
|
||||
"benefit-bulksend-api-team-month-help": "इस प्लान में प्रति माह 100 प्रीमियम क्रेडिट मिलते हैं, जिनसे आप API के जरिए दस्तावेज़ भेज सकते हैं, पब्लिक साइनिंग लिंक शेयर कर सकते हैं, बल्क सिग्नेचर रिक्वेस्ट चला सकते हैं, इन-पर्सन साइनिंग के लिए Kiosk मोड का उपयोग कर सकते हैं, या अपनी वेबसाइट पर सीधे एम्बेड किए गए दस्तावेज़ों से सिग्नेचर एकत्र कर सकते हैं। हर बार जब आप किसी दस्तावेज़ को शुरू (initiate) करते हैं या भेजते हैं, 1 क्रेडिट उपयोग होता है — और उस एक दस्तावेज़ में अधिकतम 10 साइनर हो सकते हैं।",
|
||||
"benefit-bulksend-api-team-year-help": "इस प्लान में प्रति वर्ष 500 प्रीमियम क्रेडिट मिलते हैं, जिनसे आप API के जरिए दस्तावेज़ भेज सकते हैं, पब्लिक साइनिंग लिंक शेयर कर सकते हैं, बल्क सिग्नेचर रिक्वेस्ट चला सकते हैं, इन-पर्सन साइनिंग के लिए Kiosk मोड का उपयोग कर सकते हैं, या अपनी वेबसाइट पर सीधे एम्बेड किए गए दस्तावेज़ों से सिग्नेचर एकत्र कर सकते हैं। हर बार जब आप किसी दस्तावेज़ को शुरू (initiate) करते हैं या भेजते हैं, 1 क्रेडिट उपयोग होता है — और उस एक दस्तावेज़ में अधिकतम 10 साइनर हो सकते हैं।",
|
||||
"failed-pdf-for-prefill-processing": "प्रीफिल प्रोसेसिंग के लिए PDF लोड करने में विफल।",
|
||||
"attach-all-role-to-signer": "कृपया सभी भूमिकाएँ साइनर से जोड़ें।",
|
||||
"upgrade-to-edit": "एडिट करने के लिए अपग्रेड करें",
|
||||
"hide-live-chat-for-signers": "साइनर के लिए लाइव चैट छुपाएं",
|
||||
"hide-live-chat-for-signers-help": "सक्षम होने पर, साइनिंग पेजों पर साइनर के लिए लाइव चैट विजेट छुपाए जाएंगे।",
|
||||
"attachments-creator-hint": "हस्ताक्षरकर्ता ऐसी फ़ाइलें अपलोड कर सकते हैं जो दस्तावेज़ के अंत में जोड़ी जाएँगी।",
|
||||
"attachments-drop-hint": "फ़ाइलें यहाँ छोड़ें या चुनने के लिए क्लिक करें",
|
||||
"attachments-limits": "PDF, DOCX, PNG, JPG. अधिकतम {{maxFiles}} फ़ाइलें, प्रत्येक {{maxSizeMB}} MB।",
|
||||
"attachments-invalid-type": "असमर्थित फ़ाइल प्रकार: {{name}}",
|
||||
"attachments-max-files": "अधिकतम {{maxFiles}} संलग्नक अनुमत हैं।",
|
||||
"attachments-remove-confirm": "हटाने से केवल मेटाडेटा हटेगा। पहले से जोड़े गए पृष्ठ दस्तावेज़ में बने रहेंगे। जारी रखें?",
|
||||
"conditional-logic": "सशर्त तर्क",
|
||||
"enabled": "सक्षम",
|
||||
"disabled": "अक्षम",
|
||||
"logic-no-sources": "इस हस्ताक्षरकर्ता का कोई अन्य फ़ील्ड स्रोत के रूप में उपयोग नहीं किया जा सकता।",
|
||||
"logic-action": "कार्रवाई",
|
||||
"logic-when": "जब",
|
||||
"logic-all": "सभी",
|
||||
"logic-any": "कोई भी",
|
||||
"logic-of-following": "निम्न शर्तें सही हों",
|
||||
"logic-add-rule": "नियम जोड़ें",
|
||||
"logic-pick-value": "मान चुनें",
|
||||
"logic-value-placeholder": "मान दर्ज करें",
|
||||
"logic-action-show": "इस फ़ील्ड को दिखाएँ",
|
||||
"logic-action-hide": "इस फ़ील्ड को छिपाएँ",
|
||||
"logic-action-require": "अनिवार्य करें",
|
||||
"logic-action-optional": "वैकल्पिक करें",
|
||||
"logic-op-equals": "बराबर है",
|
||||
"logic-op-notEquals": "बराबर नहीं है",
|
||||
"logic-op-checked": "चेक किया गया है",
|
||||
"logic-op-unchecked": "चेक नहीं किया गया है",
|
||||
"widget-hidden-due-to-logic": "यह विजेट सशर्त तर्क के कारण छिपा हुआ है।",
|
||||
"remove": "हटाएँ",
|
||||
"attachement-note": "नोट: यह विजेट हस्ताक्षरकर्ताओं को साइनिंग प्रक्रिया के दौरान फ़ाइल अपलोड करने की अनुमति देता है। अपलोड की गई फ़ाइल मौजूदा PDF के अंत में अतिरिक्त पेज के रूप में जोड़ दी जाएगी।",
|
||||
"attachments-docx-not-supported": "हम वर्तमान में DOCX फ़ाइल अपलोड में एक समस्या का सामना कर रहे हैं। कृपया दस्तावेज़ को किसी अन्य समर्थित प्रारूप में अपलोड करने का प्रयास करें, या बाद में पुनः प्रयास करें।",
|
||||
"access-code": "एक्सेस कोड",
|
||||
"access-code-set": "एक्सेस कोड सेट किया गया",
|
||||
"access-code-add": "एक्सेस कोड जोड़ें",
|
||||
"access-code-help": "वैकल्पिक गुप्त कोड जो केवल इस हस्ताक्षरकर्ता को पता है। दस्तावेज़ देखने या हस्ताक्षर करने से पहले उन्हें इसे दर्ज करना होगा। इसे प्राप्तकर्ता के साथ किसी अन्य माध्यम (फ़ोन, व्यक्तिगत रूप से) से साझा करें।",
|
||||
"access-code-placeholder": "कम से कम 6 अंक",
|
||||
"access-code-clear": "साफ़ करें",
|
||||
"access-code-must-be-numeric": "एक्सेस कोड में केवल 0-9 अंक होने चाहिए।",
|
||||
"access-code-save": "सहेजें",
|
||||
"access-code-required": "प्रेषक ने इस प्राप्तकर्ता को एक एक्सेस कोड दिया है। जारी रखने के लिए इसे दर्ज करें।",
|
||||
"access-code-invalid": "गलत एक्सेस कोड। कृपया प्रेषक से जांचें।",
|
||||
"access-code-too-short": "एक्सेस कोड कम से कम 6 अंक का होना चाहिए।",
|
||||
"access-code-verify": "एक्सेस कोड सत्यापित करें",
|
||||
"access-code-verified": "एक्सेस कोड सत्यापित हो गया।",
|
||||
"access-code-warning": "एक बार दस्तावेज़ भेज दिए जाने के बाद इस एक्सेस कोड को बदला या पुनः प्राप्त नहीं किया जा सकता।",
|
||||
"copy-access-code": "एक्सेस कोड कॉपी करें",
|
||||
"access-code-upgrade-msg": "एक्सेस कोड केवल भुगतान योजनाओं पर उपलब्ध हैं। प्राप्तकर्ताओं के लिए एक्सेस कोड जोड़ने के लिए अपनी योजना को अपग्रेड करें।",
|
||||
"already-copied-to-clipboard": "पहले से क्लिपबोर्ड में कॉपी किया गया है"
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
"password-length": "La password deve contenere almeno 8 caratteri",
|
||||
"password-case": "La password deve contenere una lettera maiuscola, una minuscola e un numero",
|
||||
"password-special-char": "La password deve contenere un carattere speciale",
|
||||
"agreee": "Accetto i",
|
||||
"agree": "Accetto i",
|
||||
"term": "Termini di servizio",
|
||||
"subscribe-to-opensign": "Iscriviti alla newsletter di OpenSign",
|
||||
"register": "Registrati",
|
||||
@@ -237,11 +237,12 @@
|
||||
"p3": "Selezionando questa opzione, i link di firma verranno inviati a tutti i firmatari contemporaneamente. Ogni firmatario può firmare il documento a sua discrezione, indipendentemente dal fatto che altri firmatari abbiano completato le firme. Questo metodo è più veloce ma non impone un ordine di firma tra i partecipanti.",
|
||||
"p4": "Seleziona l'opzione che meglio si adatta alle esigenze del tuo processo documentale."
|
||||
},
|
||||
"strict-order": "Imponi ordine rigoroso",
|
||||
"strict-order-help": "Se abilitato, nessun firmatario può firmare o approvare finché tutti i firmatari/approvatori precedenti non hanno completato la loro azione.",
|
||||
"no": "No",
|
||||
"auto-reminder": "Promemoria automatico",
|
||||
"remind-once": "Ricorda una volta ogni (giorni)",
|
||||
"next": "Avanti",
|
||||
"select-folder": "Seleziona cartella",
|
||||
"OpenSign-drive": "{{appName}} Drive",
|
||||
"drive-document-status": {
|
||||
"Completed": "Completato",
|
||||
@@ -261,6 +262,7 @@
|
||||
"signers": "Firmatari",
|
||||
"signers-help": "Inizia a digitare il nome di un contatto per vedere i firmatari suggeriti dai tuoi contatti salvati o aggiungerne di nuovi. Organizza l'ordine di firma aggiungendo firmatari nella sequenza desiderata. Usa il pulsante '+' per includere firmatari e 'x' per rimuoverli. Ogni firmatario riceverà una notifica via email per firmare il documento nell'ordine elencato.",
|
||||
"bcc-help": "it-Start it-typing it-a it-contact's it-name it-to it-see it-suggestions it-from it-your it-saved it-contacts it-or it-add it-new it-ones. it-Use it-the '+' it-button it-to it-add it-a it-user it-and it-the 'x' it-to it-remove it-them. it-The it-selected it-user's it-email it-will it-be it-added it-as it-a it-Bcc (it-blind it-carbon it-copy). it-Each it-user it-will it-receive it-an it-email it-notification it-once it-the it-document it-is it-completed.",
|
||||
"cc-help": "Inizia a digitare il nome di un contatto per vedere i suggerimenti dai tuoi contatti salvati oppure aggiungerne di nuovi. Usa il pulsante '+' per aggiungere un utente e la 'x' per rimuoverlo. L'email dell'utente selezionato verrà aggiunta come CC (copia conoscenza). Ogni utente riceverà una notifica via email una volta completato il documento.",
|
||||
"add-signer": "Aggiungi firmatario",
|
||||
"contact-not-found": "Contatto non trovato",
|
||||
"add-yourself": "Aggiungi te stesso",
|
||||
@@ -282,13 +284,15 @@
|
||||
"yes": "Sì",
|
||||
"copied": "Copiato",
|
||||
"something-went-wrong-mssg": "Si è verificato un errore, L'aggiornamento della pagina potrebbe risolvere il problema.",
|
||||
"attachments-docx-upload-failed": "Al momento si è verificato un problema. Riprova.",
|
||||
"attachments-docx-upload-limit": "È stato raggiunto il limite di caricamento per file DOCX grandi. Attendi 4 minuti prima di riprovare.",
|
||||
"token-generated": "Token generato con successo.",
|
||||
"webhook": "Webhook",
|
||||
"update-webhook": "Aggiorna Webhook",
|
||||
"add-webhook": "Aggiungi Webhook",
|
||||
"webhook-added": "Webhook aggiunto con successo.",
|
||||
"team-disabled": "Team disabilitato",
|
||||
"Team enabled": "Team abilitato",
|
||||
"team-enabled": "Team abilitato",
|
||||
"are-you-sure": "Sei sicuro di voler",
|
||||
"disable": "disabilitare",
|
||||
"enable": "abilitare",
|
||||
@@ -296,7 +300,7 @@
|
||||
"edit-team": "Modifica Team",
|
||||
"name-of-team": "Nome del Team",
|
||||
"prev": "Precedente",
|
||||
"no-data-avaliable": "Nessun dato disponibile",
|
||||
"no-data-available": "Nessun dato disponibile",
|
||||
"add-team": "Aggiungi Team",
|
||||
"page-not-found": "Pagina non trovata",
|
||||
"users-from-teams": "utenti dai Team",
|
||||
@@ -307,12 +311,37 @@
|
||||
"delete-user": "Elimina utente",
|
||||
"delete": "Elimina",
|
||||
"add-user": "Aggiungi utente",
|
||||
"password-generateed": "La password verrà generata solo una volta; assicurati di copiarla.",
|
||||
"password-generated": "La password verrà generata solo una volta; assicurati di copiarla.",
|
||||
"Team status": "Stato del Team",
|
||||
"user-deactivated": "Utente disattivato.",
|
||||
"user-activated": "Utente attivato.",
|
||||
"public": "Pubblico",
|
||||
"download": "Scarica",
|
||||
"sign-offline": "Firma offline",
|
||||
"sign-offline-instructions": "Scarica il documento, firmalo con uno strumento esterno, quindi carica qui la copia firmata. Il proprietario del documento esaminerà l'invio e lo approverà o rifiuterà.",
|
||||
"sign-offline-step-1": "Scarica il PDF non firmato.",
|
||||
"sign-offline-step-2": "Firma il PDF con il tuo strumento preferito.",
|
||||
"sign-offline-step-3": "Carica il PDF firmato per l'approvazione del proprietario.",
|
||||
"sign-offline-upload-hint": "Seleziona il PDF firmato da inviare per l'approvazione.",
|
||||
"sign-offline-submitted-title": "Inviato per approvazione",
|
||||
"sign-offline-submitted-body": "Il proprietario del documento è stato notificato via e-mail per esaminare la copia firmata offline.",
|
||||
"offline-already-submitted-title": "Documento inviato offline",
|
||||
"offline-already-submitted-body": "Hai già inviato questo documento per la revisione della firma offline. Riceverai una notifica una volta approvato o rifiutato.",
|
||||
"offline-action-approve-title": "Approva firma offline",
|
||||
"offline-action-approve-confirm": "Approva",
|
||||
"offline-action-reject-title": "Rifiuta firma offline",
|
||||
"offline-action-reject-hint": "Facoltativamente, fornisci un motivo. Il firmatario verrà notificato via e-mail.",
|
||||
"offline-action-reject-reason-placeholder": "Motivo (facoltativo)",
|
||||
"offline-action-reject-confirm": "Conferma rifiuto",
|
||||
"offline-action-review-title": "Esamina firma offline",
|
||||
"offline-action-review-hint": "Visualizza in anteprima il PDF firmato caricato dal firmatario, quindi approvalo o rifiutalo. Nessuna azione viene intrapresa fino alla conferma.",
|
||||
"offline-action-submitted-at": "Inviato",
|
||||
"offline-action-no-preview": "Nessuna anteprima disponibile per questo invio.",
|
||||
"offline-action-open-in-new-tab": "Apri PDF in una nuova scheda",
|
||||
"offline-action-invalid-link": "Questo link di revisione non è valido o è scaduto.",
|
||||
"offline-action-success": "Azione completata.",
|
||||
"reject": "Rifiuta",
|
||||
"document": "Documento",
|
||||
"view": "Visualizza",
|
||||
"make-template-public": "Rendi il modello pubblico",
|
||||
"make-template-private": "Rendi il modello privato",
|
||||
@@ -337,7 +366,7 @@
|
||||
"quick-send-alert-1": "Tutti i ruoli in questo documento sono attualmente collegati a contatti. Per inviare rapidamente copie di questo modello a più firmatari, assicurati che almeno un ruolo non sia collegato a nessun contatto.",
|
||||
"quick-send-alert-2": "Assicurati che ci sia almeno un widget firma aggiunto per tutti i destinatari.",
|
||||
"quick-send-alert-3": "Si prega di aggiungere almeno un ruolo a questo template.",
|
||||
"quick-send-alert-4": "Limite raggiunto per {{flow}}.",
|
||||
"quick-send-alert-4": "{{flow}} supporta fino a {{count}} firmatari per batch. Dividi il tuo elenco e riprova.",
|
||||
"copy-link": "Copia link",
|
||||
"copy": "Copia",
|
||||
"revoke-document": "Revoca documento",
|
||||
@@ -394,7 +423,10 @@
|
||||
"image": "immagine",
|
||||
"email": "email",
|
||||
"number": "numero",
|
||||
"draw": "Disegno"
|
||||
"draw": "Disegno",
|
||||
"attachments": "allegati",
|
||||
"attachments-add": "Clicca per caricare allegati",
|
||||
"attachments-count": "{{count}} allegato/i"
|
||||
},
|
||||
"fields": "Campi",
|
||||
"recipients": "Destinatari",
|
||||
@@ -406,6 +438,14 @@
|
||||
"certificate": "Certificato",
|
||||
"decline": "Rifiuta",
|
||||
"finish": "Completa",
|
||||
"approve": "Approva",
|
||||
"approve-mssg": " Sei sicuro di voler approvare il documento?",
|
||||
"signer-role-label": "Ruolo del destinatario",
|
||||
"signer-role-signer": "Firmatario",
|
||||
"signer-role-viewer": "Visualizzatore",
|
||||
"signer-role-approver": "Approvatore",
|
||||
"signer-role-hint": "Il firmatario deve firmare. L'approvatore approva senza firmare. Il visualizzatore può solo visualizzare.",
|
||||
"signer-role-upgrade-msg": "I ruoli Visualizzatore e Approvatore sono disponibili solo nei piani a pagamento. Esegui l'upgrade del tuo piano per assegnare questi ruoli ai destinatari.",
|
||||
"done": "Fatto",
|
||||
"mail": "Email",
|
||||
"sign-now": "Firma ora",
|
||||
@@ -438,13 +478,15 @@
|
||||
"reset-password-alert-1": "Il link per reimpostare la password è stato inviato al tuo indirizzo email",
|
||||
"reset-password-alert-2": "Si prega di configurare l'adattatore email",
|
||||
"reset-password-alert-3": "Reimposta la tua password",
|
||||
"faild-animation": "Caricamento animazione non riuscito",
|
||||
"failed-animation": "Caricamento animazione non riuscito",
|
||||
"apply": "Applica",
|
||||
"select-columns": "Seleziona colonne",
|
||||
"copy-type": {
|
||||
"All pages": "Tutte le pagine",
|
||||
"All pages but last": "Tutte le pagine tranne l'ultima",
|
||||
"All pages but first": "Tutte le pagine tranne la prima",
|
||||
"Next to current widget": "Accanto al widget corrente"
|
||||
"Next to current widget": "Accanto al widget corrente",
|
||||
"Page range": "Intervallo di pagine"
|
||||
},
|
||||
"options": "Opzioni",
|
||||
"minimum-check": "Controllo minimo",
|
||||
@@ -461,6 +503,19 @@
|
||||
"formula-error-braces": "Ogni {{ di apertura deve avere una }} corrispondente.",
|
||||
"formula-error-operators": "Quando usi operatori o parentesi, aggiungi almeno due valori.",
|
||||
"formula-error-leading-operator": "Le formule non possono iniziare con operatori.",
|
||||
"formula-error-trailing-operator": "Le formule non possono terminare con un operatore.",
|
||||
"formula-error-adjacent-placeholders": "Due segnaposto non possono essere scritti insieme senza un operatore tra di loro.",
|
||||
"formula-error-continuous-operators": "Non è possibile usare due operatori consecutivi.",
|
||||
"formula-error-empty-parentheses": "Le parentesi non possono essere vuote.",
|
||||
"formula-error-invalid-parentheses-content": "Le parentesi non possono iniziare o terminare con un operatore.",
|
||||
"formula-error-before-open-parenthesis": "Operatore mancante prima di '('. Aggiungi un operatore prima della parentesi di apertura.",
|
||||
"formula-error-after-close-parenthesis": "Operatore mancante dopo ')'. Aggiungi un operatore come '+' o '*' dopo la parentesi di chiusura.",
|
||||
"formula-error-between-parentheses": "Operatore mancante tra ')' e '('. Aggiungi un operatore come '*' tra i due gruppi.",
|
||||
"formula-error-division-by-zero": "La divisione per zero non è consentita. Sostituisci '0' con un numero o una variabile valida diversa da zero.",
|
||||
"formula-error-invalid-number-format": "Trovato un formato numerico non valido. I numeri devono essere in un formato valido come '1' o '3.14', non '.5', '1.' o '1.2.3'.",
|
||||
"formula-error-unclosed-parenthesis": "Trovata una '(' non chiusa. Ogni parentesi di apertura '(' deve avere una parentesi di chiusura ')' corrispondente.",
|
||||
"formula-error-unexpected-closing-parenthesis": "Trovata una parentesi di chiusura ')' inattesa senza una parentesi di apertura '(' corrispondente.",
|
||||
"formula-error-empty-placeholder": "Il segnaposto vuoto '{{}}' non è consentito. Fornisci un nome variabile all'interno delle parentesi graffe.",
|
||||
"decimal-places": "Cifre decimali",
|
||||
"number-of-decimal-places": "Numero di cifre decimali",
|
||||
"decimal-places-helper": "Imposta su 0 per numeri interi",
|
||||
@@ -529,6 +584,7 @@
|
||||
"sign-url": "URL Firma",
|
||||
"mails-sent": "Mail inviate",
|
||||
"mail-not-delivered": "Mail non consegnata",
|
||||
"email-not-verified-head": "Email non verificata",
|
||||
"document-alert": "Avviso Documento",
|
||||
"owner-subscription-expired": "L'abbonamento del proprietario è scaduto.",
|
||||
"owner-doesnt-have-paid-plan": "Il proprietario non ha un piano a pagamento.",
|
||||
@@ -604,6 +660,9 @@
|
||||
"document-signed-alert-7": "Questo documento non è più disponibile.",
|
||||
"document-signed-alert-8": "Il documento è già stato inviato per la firma.",
|
||||
"yet-to-sign": "Ancora da firmare",
|
||||
"viewers": "Visualizzatori",
|
||||
"viewer-no-widgets": "I visualizzatori ricevono una copia in sola lettura del documento e non possono avere widget assegnati.",
|
||||
"viewer-confirm-widget-removal": "Cambiando questo destinatario in Visualizzatore verranno rimossi tutti i widget già assegnati. Continuare?",
|
||||
"validation-alert-1": "L'input non soddisfa i criteri impostati dall'espressione regolare.",
|
||||
"account-already-exists": "L'account esiste già!",
|
||||
"choose-password": "Scegli Password",
|
||||
@@ -626,7 +685,7 @@
|
||||
"date": "Data",
|
||||
"report-not-found": "Report non trovato",
|
||||
"please-wait": "attendere prego",
|
||||
"select-foler": "Seleziona cartella",
|
||||
"select-folder": "Seleziona cartella",
|
||||
"generated-on": "Generato il",
|
||||
"text-field": "Campo di testo",
|
||||
"font-size": "Dimensione carattere",
|
||||
@@ -685,13 +744,13 @@
|
||||
"public-template": "Questo video dimostra come configurare il tuo profilo pubblico personalizzato, come 'https://opensign.me/tuo-username'. Imparerai anche come personalizzare il tuo slogan e rendere i tuoi modelli disponibili per la firma pubblica.",
|
||||
"allowModify-widgets": "È possibile trascinare e rilasciare uno qualsiasi di questi campi nel documento, oltre ai campi già designati dal creatore del documento."
|
||||
},
|
||||
"enter-email-plaholder": "Aggiungi un indirizzo email e premi invio",
|
||||
"enter-email-placeholder": "Aggiungi un indirizzo email e premi invio",
|
||||
"success-email-alert": "Email inviata con successo!",
|
||||
"expired-doc-title": "Documento Scaduto",
|
||||
"expired-on-mssg": "Questo documento è scaduto il {{expiredDate}} e non è più disponibile per la firma.",
|
||||
"signature-validate-alert": "Conferma di aver selezionato almeno {{minRequiredCount}} caselle di controllo.",
|
||||
"signature-validate-alert-2": "Assicurati che questo campo sia compilato correttamente e soddisfi tutti i requisiti.",
|
||||
"remaing-users": "Utenti rimanenti",
|
||||
"remaining-users": "Utenti rimanenti",
|
||||
"Role": "Ruolo",
|
||||
"additional-users": "Acquista utenti aggiuntivi.",
|
||||
"Quantity-of-users": "Quantità di utenti",
|
||||
@@ -713,7 +772,7 @@
|
||||
"rotate-left": "Ruota a sinistra",
|
||||
"rotate-alert-mssg": "Tutti i widget su questa pagina andranno persi. Sei sicuro di voler procedere?",
|
||||
"templateid": "ID Modello",
|
||||
"bulk-send-subcription-alert": "Esegui l'upgrade al piano Professionale o Team per utilizzare l'invio in massa.",
|
||||
"bulk-send-subscription-alert": "Esegui l'upgrade al piano Professionale o Team per utilizzare l'invio in massa.",
|
||||
"generate-test-token": "Genera Token di Test",
|
||||
"regenerate-test-token": "Rigenera Token di Test",
|
||||
"help-test-token": "Questo token può essere utilizzato per testare le API all'endpoint https://sandbox.opensignlabs.com/api/v1, consentendoti di firmare documenti illimitati. Nota che le API sandbox firmeranno i tuoi documenti con certificati autofirmati, che potrebbero non essere riconosciuti come validi da Adobe. Una volta completati i test, puoi eseguire l'upgrade a uno dei nostri piani a pagamento per generare un token di produzione.",
|
||||
@@ -733,8 +792,9 @@
|
||||
"copied-code": "COPIATO",
|
||||
"Installation": "Installazione",
|
||||
"Usage": "Utilizzo",
|
||||
"insufficient-credits": "Crediti di firma insufficienti",
|
||||
"insufficient-credits-mssg": "Il proprietario di questo documento non dispone attualmente dei crediti OpenSign necessari affinché tu possa firmare. Contatta il proprietario per ulteriori dettagli.",
|
||||
"insufficient-credits": "Crediti di firma insufficienti. Si prega di contattare {{ownerEmail}}",
|
||||
"insufficient-credits-title": "Crediti di firma insufficienti",
|
||||
"insufficient-credits-mssg": "Il proprietario di questo documento non dispone attualmente dei crediti {{appName}} necessari affinché tu possa firmare. Contatta il proprietario per ulteriori dettagli.",
|
||||
"angular-npm-mssg-1": "Per integrare OpenSign nel tuo progetto Angular, esegui semplicemente il seguente comando:",
|
||||
"quota-mail-info-head": "Limite mensile di email",
|
||||
"quota-mail-info": "Per mantenere la qualità del servizio e prevenire lo spam, OpenSign consente fino a 15 email al mese con il piano gratuito. Esegui l'upgrade ora per l'invio illimitato di email.",
|
||||
@@ -753,6 +813,13 @@
|
||||
"p3": "Selezionando questa opzione, disabiliterai la verifica OTP, consentendo agli utenti di firmare il documento direttamente senza passaggi aggiuntivi.",
|
||||
"p4": "Scegli l'opzione che meglio si adatta alle tue esigenze di firma dei documenti."
|
||||
},
|
||||
"allow-offline-sign": "Consenti firma offline",
|
||||
"allow-offline-sign-help": {
|
||||
"p1": "Scegli se i firmatari possono inviare una copia di questo documento firmata tramite uno strumento esterno per la revisione del proprietario.",
|
||||
"p2": "I firmatari vedranno l'opzione Firma offline e potranno caricare un PDF firmato esternamente per l'approvazione.",
|
||||
"p3": "I firmatari possono firmare solo all'interno dell'app. L'opzione Firma offline sarà nascosta.",
|
||||
"p4": "Questa impostazione utilizza come predefinita la tua preferenza account ed è disponibile sui piani a pagamento."
|
||||
},
|
||||
"advanced-options": "Opzioni avanzate",
|
||||
"hide-advanced-options": "Nascondi opzioni avanzate",
|
||||
"download-files": "Scarica file",
|
||||
@@ -815,6 +882,7 @@
|
||||
"delete-folder-alert-1": "Impossibile eliminare: la cartella deve essere vuota.",
|
||||
"defaultSign-alert": "I widget {{widgetsType}} non sono disponibili per la firma automatica.",
|
||||
"embed-heading": "Incorpora modelli {{appName}}",
|
||||
"other-apps-by-opensign": "Altre app del team OpenSign",
|
||||
"public-template-edit-title": "Modifica Modello Pubblico",
|
||||
"public-template-edit-mssg": "Non puoi modificare direttamente un modello pubblico. Per apportare modifiche, il modello sarà impostato come privato e dovrai renderlo pubblico di nuovo successivamente. Vuoi continuare?",
|
||||
"agree-p1": "Confermo di aver letto e compreso il",
|
||||
@@ -866,7 +934,6 @@
|
||||
"js-snippet-msg-1": "Puoi ottenere il TemplateId dalla",
|
||||
"js-snippet-msg-2": "pagina di gestione modelli",
|
||||
"js-snippet-msg-3": "pagina.",
|
||||
"agrrement-alert": "Per procedere, devi acconsentire alla divulgazione delle registrazioni elettroniche e delle firme.",
|
||||
"webhook-already-exists": "L'URL esiste già! Prova con uno diverso.",
|
||||
"webhook-must-be-secure": "L'URL del webhook deve essere sicuro e utilizzare https://",
|
||||
"provide-test-token": "Si prega di generare il token di test API",
|
||||
@@ -886,6 +953,7 @@
|
||||
"p3": "Selezionando questa opzione, i firmatari non potranno apportare alcuna aggiunta al documento durante il processo di firma."
|
||||
},
|
||||
"signature-setting": "Impostazioni Firma",
|
||||
"rotation": "Rotazione",
|
||||
"activation-key": "Chiave di attivazione",
|
||||
"paid-selfhost-help": "Si prega di contattare {{appName}} per ulteriori informazioni all'indirizzo ",
|
||||
"remaining-credits-selfhost-help": "Utilizza i crediti premium per la firma di documenti tramite API, l'invio in massa e l'integrazione di {{appName}} sul tuo sito web. Hai {{allowedcredits}} crediti inclusi e {{addoncredits}} crediti aggiuntivi acquistati rimanenti.",
|
||||
@@ -914,6 +982,8 @@
|
||||
"visit-below-link": "Visita il link qui sotto per saperne di più -",
|
||||
"storage-help": "Abilitare BYOC ti consente di collegare il tuo archivio S3 in modo che i tuoi file rimangano completamente sotto il tuo controllo, senza copie esterne conservate. Se l'autonomia dei dati è importante per te, considera l'upgrade a Teams per sbloccare questa funzionalità.",
|
||||
"daily-quota-reached": "Hai raggiunto la tua quota giornaliera. Per assistenza, contatta quotas@opensignlabs.com.",
|
||||
"email-not-verified-send": "Verifica il tuo indirizzo email prima di inviare email. Vai alle <1>impostazioni del profilo</1> per completare la verifica dell’email.",
|
||||
"email-not-verified-send-plain": "Verifica il tuo indirizzo email prima di inviare email. Vai alle impostazioni del profilo per completare la verifica dell’email.",
|
||||
"enabled-signature-type": "Tipi di firma abilitati",
|
||||
"enabled-signature-type-help": "L'impostazione 'Tipi di firma abilitati' determina quali opzioni di firma sono disponibili nella tua organizzazione. Ad esempio, se disabiliti l'opzione 'Disegna', i membri della tua organizzazione non la vedranno nel widget della firma, mentre le altre tre opzioni resteranno accessibili.",
|
||||
"indexing-public-profile": "Consenti l'indicizzazione del profilo pubblico dai motori di ricerca",
|
||||
@@ -941,6 +1011,7 @@
|
||||
"please-provide-templateid": "Si prega di fornire templateid",
|
||||
"this-template-is-not-public": "Questo template non è pubblico",
|
||||
"invalid-templateid": "templateid non valido",
|
||||
"kiosk-no-active-role": "Questo modello non ha ruoli di firmatario o approvatore disponibili per la firma in modalità chiosco.",
|
||||
"contact-billing-at-opensign": " Per aggiungere altri posti, contattare OpenSign™ all'indirizzo <1>billing@opensignlabs.com</1> per assistenza.",
|
||||
"title-length-alert": "Il titolo può contenere al massimo 250 caratteri.",
|
||||
"note-length-alert": "La nota può contenere al massimo 200 caratteri.",
|
||||
@@ -1030,7 +1101,7 @@
|
||||
},
|
||||
"signer-already-present": "Firmatario già presente",
|
||||
"kiosk-sign": "Firma su chiosco",
|
||||
"dont-have-access-to-template": "Il template è stato eliminato o non hai accesso. Si prega di contattare il mittente.",
|
||||
"do-not-have-access-to-template": "Il template è stato eliminato o non hai accesso. Si prega di contattare il mittente.",
|
||||
"kiosk-info": "La Modalità Kiosk consente di raccogliere firme in presenza in modo rapido ed efficiente. Ideale per fiere, eventi o situazioni con firmatari fisicamente presenti. ",
|
||||
"learn-more": "Scopri di più",
|
||||
"finish-mssg": "Sei sicuro di voler completare il documento?",
|
||||
@@ -1086,8 +1157,6 @@
|
||||
"search-contacts": "Cerca contatti…",
|
||||
"add-role-alert": "Si prega di aggiungere almeno un ruolo",
|
||||
"edit-draft": "Modifica bozza",
|
||||
"invalid-email-found": "Email non valida trovata: {{email}}",
|
||||
"duplicate-email-found": "Email duplicata trovata: {{email}}",
|
||||
"vertical": "Verticale",
|
||||
"horizontal": "Orizzontale",
|
||||
"billing": "Fatturazione",
|
||||
@@ -1101,7 +1170,7 @@
|
||||
"access-denied": "Accesso negato",
|
||||
"upgrade": "Aggiorna",
|
||||
"do-not-access-app": "Non hai accesso a questa applicazione.",
|
||||
"dont-have-access": "Non hai accesso.",
|
||||
"do-not-have-access": "Non hai accesso.",
|
||||
"valid-email-alert": "Inserisci un indirizzo email valido.",
|
||||
"otp-not-validate": "OTP non valido.",
|
||||
"domain-not-allowed": "Questo dominio non è consentito",
|
||||
@@ -1118,10 +1187,12 @@
|
||||
"branding-help": "Il branding consente il white labelling della tua app",
|
||||
"custom-sub-domain": "Sottodominio personalizzato",
|
||||
"app-name": "Nome dell'app",
|
||||
"provide-domain-name": "Fornisci il tuo nome di dominio",
|
||||
"provide-domain-name": "Sono consentiti solo i sottodomini (ad es., app.tuodominio.com). I domini principali/root come tuodominio.com non sono supportati.",
|
||||
"provide-app-name-help": "Fornisci un nome univoco per l'app. Verrà mostrato come nome del tuo brand nell'applicazione e in tutte le e-mail ovunque venga applicato il branding.",
|
||||
"upload-app-logo": "Carica un logo in formato .png, .jpg o .svg. Dimensione consigliata: {{size}} pixel (rapporto 5:2) per la migliore qualità di visualizzazione.",
|
||||
"upload-app-favicon": "Carica un'immagine favicon quadrata (.ico, .png o .svg). La dimensione consigliata è {{size}} pixel per la migliore compatibilità del browser.",
|
||||
"provide-app-name": "Fornisci il nome della tua app",
|
||||
"logo": "Logo",
|
||||
"upload-app-logo": "Carica il logo della tua app",
|
||||
"prefill-unfilled-widget": "I seguenti campi obbligatori non possono essere lasciati vuoti: {{emptyWidget}}. Per favore compilali per procedere.",
|
||||
"Dashboard": "Dashboard",
|
||||
"Analytics": "Analitica",
|
||||
@@ -1289,11 +1360,11 @@
|
||||
"signer-form-data-for": "Dati del modulo per il firmatario: {{signerName}} <{{signerEmail}}>",
|
||||
"widget-name": "Nome del widget",
|
||||
"widget-responses": "Risposte del widget",
|
||||
"formdata-download-note": "Nota: Firme, timbri, iniziali e immagini non verranno esportati.",
|
||||
"formdata-download-note": "Nota: Firme, timbri, iniziali e immagini non verranno esportati. I dati dei firmatari offline non saranno disponibili.",
|
||||
"export-all-formdata-error": "Non è possibile esportare i dati del modulo perché il modello contiene nomi di widget duplicati. Creare un nuovo modello per poter esportare i dati.",
|
||||
"export-formdata-error": "Non è possibile esportare i dati del modulo perché il documento contiene nomi di widget duplicati. Creare un nuovo documento senza nomi duplicati per poter esportare i dati.",
|
||||
"duplicate-widget-name-error": "Questo nome di widget è già assegnato a un altro firmatario. Scegli un nome diverso.",
|
||||
"duplicate-template-widget-error": "Non è possibile duplicare questo modello perché contiene nomi di widget duplicati.",
|
||||
"duplicate-template-widget-error": "Impossibile duplicare il modello perché alcuni nomi di widget sono duplicati in ruoli diversi. Rinomina i widget duplicati e riprova.",
|
||||
"credit-alert": "Avviso di credito",
|
||||
"document-form-data": "Dati del modulo del documento",
|
||||
"view-formdata-paid-title-error": "La funzione 'Visualizza dati del modulo' consente di stampare e scaricare i dati dei widget compilati dai firmatari durante la firma.",
|
||||
@@ -1345,8 +1416,8 @@
|
||||
"file-selected": "file selezionato",
|
||||
"bulk-send-csv-helper": "Utilizza le intestazioni per i ruoli dei firmatari (Email/Nome/Telefono del ruolo) e i valori di precompilazione (Precompilazione: Nome campo). I campi di precompilazione obbligatori devono essere compilati.",
|
||||
"bulk-send-csv-helper-note": "Nota: Per i campi di precompilazione di tipo data nel CSV, il valore deve essere fornito in questo formato: {{value}} (esempio: {{example}}).",
|
||||
"bulk-send-csv-summary": "{{total}} righe importate. {{invalid}} righe non valide sono state ignorate.",
|
||||
"bulk-send-csv-missing-headers": "Intestazioni CSV obbligatorie mancanti. Ruoli: {{roles}}. Precompilazione: {{prefill}}.",
|
||||
"bulk-send-csv-summary": "{{total}} righe importate dal CSV.",
|
||||
"bulk-send-csv-missing-headers": "Intestazioni CSV obbligatorie mancanti. Ruoli: {{roles}}.",
|
||||
"bulk-send-no-records": "Aggiungi almeno un destinatario prima dell'invio.",
|
||||
"csv-only-support": "Sono supportati solo file CSV.",
|
||||
"csv-no-records": "Nessun record trovato nel CSV.",
|
||||
@@ -1360,5 +1431,184 @@
|
||||
"enter-daily-email-quota": "Inserisci la quota giornaliera di e-mail",
|
||||
"enter-custom-regular-expression": "Inserisci un'espressione regolare personalizzata",
|
||||
"enter-value": "Inserisci {{value}}",
|
||||
"are-you-sure-want-to-send-document": "Sei sicuro di voler inviare il documento?"
|
||||
}
|
||||
"are-you-sure-want-to-send-document": "Sei sicuro di voler inviare il documento?",
|
||||
"favicon": "Favicon",
|
||||
"delete-two-factor-authentication": "Sei sicuro di voler eliminare l'autenticazione a due fattori?",
|
||||
"paste-html-here": "Incolla HTML qui",
|
||||
"open-email-builder": "Puoi creare un modello di e-mail utilizzando la piattaforma <1>email builder</1> e copiare il codice HTML.",
|
||||
"contact-saved": "Contatto salvato con successo.",
|
||||
"remove-limit-of-user": "Sei sicuro di voler rimuovere il limite di questo utente? Potrà consumare tutti i crediti premium disponibili della tua organizzazione, condivisi tra tutti gli utenti.",
|
||||
"current-expiry-date": "Data di scadenza attuale",
|
||||
"reminder-error": "“Ricorda una volta ogni (giorni)” non deve superare il “Tempo per completare (giorni)”.",
|
||||
"showing-to-of-records": "Visualizzazione da {{record}} a {{recordPerPage}} di {{totalRows}} record",
|
||||
"records-per-page": "Record per pagina",
|
||||
"email-not-found-in-row": "Email non trovata nella riga {{row}}.",
|
||||
"invalid-email-found-in-row": "Email non valida trovata nella riga {{row}}: {{email}}",
|
||||
"duplicate-email-found-in-row": "Email duplicata trovata nella riga {{row}}: {{email}}",
|
||||
"prefill": "Precompilazione",
|
||||
"recipients-&-data": "Destinatari e dati",
|
||||
"response": "Risposta",
|
||||
"prefill-tab-help": "Questi sono i campi comuni del modello che verranno applicati a tutti i documenti in questo invio massivo.",
|
||||
"recipient-tab-help-p1": "Nel passaggio successivo, dovrai:",
|
||||
"recipient-tab-help-p2": "Caricare un CSV con i dettagli dei destinatari oppure",
|
||||
"recipient-tab-help-p3": "Inserire manualmente le informazioni dei destinatari",
|
||||
"recipient-tab-help-p4": "Ogni destinatario riceverà il proprio documento generato da questo modello.",
|
||||
"response-tab-help": "Rivedi le tue risposte.",
|
||||
"summary": "Riepilogo",
|
||||
"prefill-fields": "Campi di precompilazione",
|
||||
"documents": "Documenti",
|
||||
"message": "Messaggio",
|
||||
"formula-help-p1": "Raccogli input numerici ed esegui calcoli dinamici utilizzando formule.",
|
||||
"example": "Esempio",
|
||||
"formula-help-p2": "Per visualizzare un totale in number-3, usa {{number-1}} + {{number-2}}.",
|
||||
"formula-help-p3": "Per calcolare un valore in number-4, usa ({{number-1}} * {{number-2}}) / {{number-3}}.",
|
||||
"formula-help-p4": "Ogni widget numerico deve avere un nome univoco (es. number-1), poiché le formule si basano su questi identificatori.",
|
||||
"note-consume-premium-credits": "Nota: Questa funzione consuma crediti premium.",
|
||||
"note-consume-premium-credits-on-document-send": "Nota: Questa funzione consuma crediti premium. I crediti vengono consumati ogni volta che un documento viene inviato, indipendentemente dal fatto che venga firmato o meno.",
|
||||
"webhook-authentication-help": "{{appName}} protegge ogni webhook generando una firma HMAC-SHA256 utilizzando la tua chiave di sicurezza webhook e il payload originale della richiesta. Questa firma viene inviata nell'intestazione x-webhook-signature. Il tuo server deve generare la stessa firma utilizzando il payload ricevuto e la tua chiave segreta. Se entrambe le firme coincidono, la richiesta webhook è autentica e non è stata modificata.",
|
||||
"invalid-widget-found-in-row": "Valore non valido rilevato nella riga {{row}} per il widget {{widget}}.",
|
||||
"all-signer-status": "Stato di tutti i firmatari",
|
||||
"viewed": "Visualizzato",
|
||||
"signed": "Firmato",
|
||||
"switch-to-advanced": "Passa alla modalità avanzata",
|
||||
"switch-to-basic": "Passa alla modalità base",
|
||||
"use-name-as-sender": "Usa il nome del profilo come mittente",
|
||||
"use-name-as-sender-help": "Quando abilitato, {{appName}} usa il nome del tuo profilo come mittente per le richieste di firma e l'email di completamento.",
|
||||
"acro-fields-detected": "Campi modulo rilevati",
|
||||
"acro-fields-detected-msg": "Sono stati rilevati {{count}} campo/i modulo nel PDF caricato. Vuoi inserire automaticamente i widget corrispondenti?",
|
||||
"documents-count": "Numero di documenti",
|
||||
"templates-count": "Numero di modelli",
|
||||
"emails-sent": "Email inviate",
|
||||
"storage-used": "Spazio utilizzato",
|
||||
"docs-count-team-help": "Numero totale di documenti creati da tutti gli utenti nelle tue organizzazioni.",
|
||||
"docs-count-help": "Numero totale di documenti creati da te.",
|
||||
"templates-count-team-help": "Numero totale di modelli creati da tutti gli utenti nelle tue organizzazioni.",
|
||||
"templates-count-help": "Numero totale di modelli creati da te.",
|
||||
"emails-sent-team-help": "Numero totale di notifiche email inviate nelle tue organizzazioni.",
|
||||
"emails-sent-help": "Numero totale di notifiche email inviate da te.",
|
||||
"storage-used-team-help": "Spazio di archiviazione totale consumato da tutti gli utenti nelle tue organizzazioni.",
|
||||
"storage-used-help": "Spazio di archiviazione totale consumato da te.",
|
||||
"cloud": "Cloud",
|
||||
"on-premises": "On-premise",
|
||||
"plan-name-free": "OPENSIGN™ FREE",
|
||||
"plan-name-professional": "OPENSIGN™ PROFESSIONAL",
|
||||
"plan-name-teams": "OPENSIGN™ TEAMS",
|
||||
"plan-name-enterprise": "OPENSIGN™ ENTERPRISE",
|
||||
"plan-name-self-hosted": "OPENSIGN™ SELF-HOSTED",
|
||||
"plan-subtitle-free": "Firme elettroniche illimitate gratuite, per sempre.",
|
||||
"plan-subtitle-professional": "Accesso esclusivo a funzionalità avanzate.",
|
||||
"plan-subtitle-scalable": "Funzionalità scalabili con supporto prioritario.",
|
||||
"subscribe": "Abbonati",
|
||||
"contact-us": "Contattaci",
|
||||
"benefit-unlimited-digital-signatures": "Firme digitali illimitate",
|
||||
"benefit-sign-docs-yourself": "Firma i documenti tu stesso",
|
||||
"benefit-request-signatures": "Richiedi firme ad altri",
|
||||
"benefit-unlimited-templates": "Modelli illimitati",
|
||||
"benefit-14-field-types": "14 tipi di campo",
|
||||
"benefit-automatic-e-signatures": "Firme elettroniche automatiche",
|
||||
"benefit-completion-certificates": "Certificati di completamento",
|
||||
"benefit-send-in-order": "Invia in ordine",
|
||||
"benefit-organize-docs": "Organizza i documenti in OpenSign™ Drive",
|
||||
"benefit-document-templates": "Modelli di documento",
|
||||
"benefit-import-from-dropbox": "Importa da Dropbox",
|
||||
"benefit-contact-book": "Rubrica contatti",
|
||||
"benefit-document-expiry": "Supporto per la scadenza dei documenti",
|
||||
"benefit-decline-document": "Supporto per il rifiuto dei documenti",
|
||||
"benefit-email-notifications": "Notifiche via e-mail",
|
||||
"benefit-public-profiles": "Profili pubblici",
|
||||
"benefit-and-much-more": "E molto altro",
|
||||
"benefit-everything-free": "Tutto in OpenSign™ Free",
|
||||
"benefit-field-validations": "Validazioni dei campi",
|
||||
"benefit-regex-validations": "Validazioni con espressioni regolari",
|
||||
"benefit-webhooks": "Webhook",
|
||||
"benefit-zapier": "Integrazione Zapier",
|
||||
"benefit-api-access": "Accesso API",
|
||||
"benefit-api-signatures-100": "fino a 100 firme API",
|
||||
"benefit-api-signatures-240": "fino a 240 firme API",
|
||||
"benefit-custom-email-templates": "Modelli e-mail personalizzati",
|
||||
"benefit-custom-smtp": "Collega il tuo account Gmail o SMTP per l'invio di e-mail",
|
||||
"benefit-auto-reminders": "Promemoria automatici",
|
||||
"benefit-bulk-send-100": "Invio in massa (fino a 100 documenti)",
|
||||
"benefit-bulk-send-240": "Invio in massa (fino a 240 documenti)",
|
||||
"benefit-premium-profiles": "Nomi utente del profilo pubblico premium",
|
||||
"benefit-email-verification": "Imponi la verifica via e-mail per confermare l'identità del firmatario",
|
||||
"benefit-embedded-signing": "Firma incorporata",
|
||||
"benefit-everything-professional": "Tutto in OpenSign™ Professional",
|
||||
"benefit-teams-organizations": "Team e Organizzazioni",
|
||||
"benefit-share-templates-teams": "Condividi modelli con i team",
|
||||
"benefit-share-templates-individuals": "Condividi modelli con individui",
|
||||
"benefit-byoc": "BYOC - Archivia i tuoi documenti nel tuo cloud storage",
|
||||
"benefit-doc-id-removal": "Rimozione del DocumentId dai documenti firmati",
|
||||
"benefit-request-payments": "Richiedi pagamenti (prossimamente)",
|
||||
"benefit-mobile-app": "App mobile (prossimamente)",
|
||||
"benefit-api-signatures-500": "fino a 500 firme API",
|
||||
"benefit-bulk-send-500": "Invio in massa (fino a 500 documenti)",
|
||||
"benefit-all-features": "Tutte le funzionalità",
|
||||
"benefit-custom-domain": "Dominio personalizzato",
|
||||
"benefit-custom-branding": "Branding personalizzato",
|
||||
"benefit-uptime-sla": "SLA di disponibilità",
|
||||
"benefit-sso-support": "Supporto SSO",
|
||||
"benefit-priority-support": "Supporto prioritario",
|
||||
"benefit-3-users-1500-credits": "Include 3 utenti e 1500 crediti premium",
|
||||
"benefit-public-links": "Link pubblici",
|
||||
"benefit-kiosk-mode": "Modalità chiosco",
|
||||
"benefit-bulk-send-1500": "Invio in massa (fino a 1500 documenti)",
|
||||
"benefit-connect-gmail-smtp": "Collega il tuo Gmail o SMTP",
|
||||
"benefit-analytics": "Analisi",
|
||||
"benefit-bulksend-api-pro-month-help": "Questo piano include 100 crediti premium al mese che ti permettono di inviare documenti tramite API, condividere link pubblici per la firma, eseguire richieste di firma in blocco, usare la modalità Kiosk per la firma di persona o raccogliere firme da documenti incorporati direttamente nel tuo sito web. Ogni volta che avvii o invii un documento, viene utilizzato 1 credito — e quel singolo documento può includere fino a 10 firmatari.",
|
||||
"benefit-bulksend-api-pro-year-help": "Questo piano include 240 crediti premium all'anno che ti permettono di inviare documenti tramite API, condividere link pubblici per la firma, eseguire richieste di firma in blocco, usare la modalità Kiosk per la firma di persona o raccogliere firme da documenti incorporati direttamente nel tuo sito web. Ogni volta che avvii o invii un documento, viene utilizzato 1 credito — e quel singolo documento può includere fino a 10 firmatari.",
|
||||
"benefit-bulksend-api-team-month-help": "Questo piano include 100 crediti premium al mese che ti permettono di inviare documenti tramite API, condividere link pubblici per la firma, eseguire richieste di firma in blocco, usare la modalità Kiosk per la firma di persona o raccogliere firme da documenti incorporati direttamente nel tuo sito web. Ogni volta che avvii o invii un documento, viene utilizzato 1 credito — e quel singolo documento può includere fino a 10 firmatari.",
|
||||
"benefit-bulksend-api-team-year-help": "Questo piano include 500 crediti premium all'anno che ti permettono di inviare documenti tramite API, condividere link pubblici per la firma, eseguire richieste di firma in blocco, usare la modalità Kiosk per la firma di persona o raccogliere firme da documenti incorporati direttamente nel tuo sito web. Ogni volta che avvii o invii un documento, viene utilizzato 1 credito — e quel singolo documento può includere fino a 10 firmatari.",
|
||||
"failed-pdf-for-prefill-processing": "Impossibile caricare il PDF per l'elaborazione del precompilamento.",
|
||||
"attach-all-role-to-signer": "Per favore, assegna tutti i ruoli al firmatario.",
|
||||
"upgrade-to-edit": "Esegui l'upgrade per modificare",
|
||||
"hide-live-chat-for-signers": "Nascondi chat dal vivo per i firmatari",
|
||||
"hide-live-chat-for-signers-help": "Se attivato, i widget di chat dal vivo saranno nascosti per i firmatari nelle pagine di firma.",
|
||||
"attachments-creator-hint": "I firmatari possono caricare file che verranno aggiunti alla fine del documento.",
|
||||
"attachments-drop-hint": "Trascina i file qui o clicca per selezionare",
|
||||
"attachments-limits": "PDF, DOCX, PNG, JPG. Fino a {{maxFiles}} file, {{maxSizeMB}} MB ciascuno.",
|
||||
"attachments-invalid-type": "Tipo di file non supportato: {{name}}",
|
||||
"attachments-max-files": "Massimo {{maxFiles}} allegati consentiti.",
|
||||
"attachments-remove-confirm": "La rimozione cancella solo i metadati. Le pagine già aggiunte resteranno nel documento. Continuare?",
|
||||
"conditional-logic": "Logica condizionale",
|
||||
"enabled": "Abilitato",
|
||||
"disabled": "Disabilitato",
|
||||
"logic-no-sources": "Nessun altro campo di questo firmatario può essere usato come origine.",
|
||||
"logic-action": "Azione",
|
||||
"logic-when": "Quando",
|
||||
"logic-all": "tutte",
|
||||
"logic-any": "una qualsiasi",
|
||||
"logic-of-following": "delle seguenti condizioni sono vere",
|
||||
"logic-add-rule": "Aggiungi regola",
|
||||
"logic-pick-value": "Seleziona un valore",
|
||||
"logic-value-placeholder": "Inserisci valore",
|
||||
"logic-action-show": "Mostra questo campo",
|
||||
"logic-action-hide": "Nascondi questo campo",
|
||||
"logic-action-require": "Rendi obbligatorio",
|
||||
"logic-action-optional": "Rendi facoltativo",
|
||||
"logic-op-equals": "è uguale a",
|
||||
"logic-op-notEquals": "non è uguale a",
|
||||
"logic-op-checked": "è selezionato",
|
||||
"logic-op-unchecked": "non è selezionato",
|
||||
"widget-hidden-due-to-logic": "Questo widget è nascosto a causa della logica condizionale.",
|
||||
"remove": "Rimuovi",
|
||||
"attachement-note": "Nota: Questo widget consente ai firmatari di caricare un file durante il processo di firma. Il file caricato verrà aggiunto come pagine aggiuntive alla fine del PDF esistente.",
|
||||
"attachments-docx-not-supported": "Al momento stiamo riscontrando un problema con il caricamento dei file DOCX. Prova a caricare il documento in un altro formato supportato oppure riprova più tardi.",
|
||||
"access-code": "Codice di accesso",
|
||||
"access-code-set": "Codice di accesso impostato",
|
||||
"access-code-add": "Aggiungi codice di accesso",
|
||||
"access-code-help": "Codice segreto facoltativo noto solo a questo firmatario. Dovrà inserirlo prima di visualizzare o firmare il documento. Comunicalo al destinatario tramite un canale alternativo (telefono, di persona).",
|
||||
"access-code-placeholder": "Almeno 6 cifre",
|
||||
"access-code-clear": "Cancella",
|
||||
"access-code-must-be-numeric": "Il codice di accesso deve contenere solo cifre 0-9.",
|
||||
"access-code-save": "Salva",
|
||||
"access-code-required": "Il mittente ha assegnato un codice di accesso a questo destinatario. Inseriscilo per continuare.",
|
||||
"access-code-invalid": "Codice di accesso errato. Verifica con il mittente.",
|
||||
"access-code-too-short": "Il codice di accesso deve contenere almeno 6 cifre.",
|
||||
"access-code-verify": "Verifica codice di accesso",
|
||||
"access-code-verified": "Codice di accesso verificato.",
|
||||
"access-code-warning": "Questo codice di accesso non può essere modificato o recuperato una volta inviato il documento.",
|
||||
"copy-access-code": "Copia codice di accesso",
|
||||
"access-code-upgrade-msg": "I codici di accesso sono disponibili solo nei piani a pagamento. Aggiorna il tuo piano per aggiungere codici di accesso per i destinatari.",
|
||||
"already-copied-to-clipboard": "Già copiato negli appunti"
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
"password-length": "비밀번호는 8자 이상이어야 합니다",
|
||||
"password-case": "비밀번호는 대문자, 소문자, 숫자를 포함해야 합니다",
|
||||
"password-special-char": "비밀번호는 특수 문자를 포함해야 합니다",
|
||||
"agreee": "저는 동의합니다",
|
||||
"agree": "저는 동의합니다",
|
||||
"term": "서비스 약관",
|
||||
"subscribe-to-opensign": "OpenSign 뉴스레터 구독",
|
||||
"register": "등록",
|
||||
@@ -237,11 +237,12 @@
|
||||
"p3": "이 옵션을 선택하면 서명 링크가 모든 서명자에게 동시에 전송됩니다. 각 서명자는 다른 서명자가 서명을 완료했는지 여부에 관계없이 편리한 시간에 문서에 서명할 수 있습니다. 이 방법은 더 빠르지만 참가자 간에 서명 순서를 강제하지 않습니다.",
|
||||
"p4": "문서 처리 요구에 가장 적합한 옵션을 선택하세요."
|
||||
},
|
||||
"strict-order": "엄격한 순서 적용",
|
||||
"strict-order-help": "활성화하면 이전의 모든 서명자/승인자가 작업을 완료할 때까지 어떤 서명자도 서명하거나 승인할 수 없습니다.",
|
||||
"no": "아니요",
|
||||
"auto-reminder": "자동 알림",
|
||||
"remind-once": " 매 (일)마다 한 번 알림",
|
||||
"next": "다음",
|
||||
"select-folder": "선택 폴더",
|
||||
"OpenSign-drive": "{{appName}} 드라이브",
|
||||
"drive-document-status": {
|
||||
"Completed": "완료됨",
|
||||
@@ -261,6 +262,7 @@
|
||||
"signers": "서명자",
|
||||
"signers-help": "연락처 이름을 입력하기 시작하면 저장된 연락처에서 추천 서명자가 표시되거나 새 연락처를 추가할 수 있습니다. 원하는 순서대로 서명자를 추가하여 서명 순서를 정렬하세요. '+' 버튼을 눌러 서명자를 포함시키고 'x' 버튼을 눌러 제거하세요. 각 서명자는 지정된 순서대로 문서에 서명하라는 이메일 안내를 받게 됩니다.",
|
||||
"bcc-help": "연락처 이름을 입력하기 시작하면 저장된 연락처에서 제안된 항목이 표시되거나 새 연락처를 추가할 수 있습니다. '+' 버튼을 사용하여 사용자를 추가하고 'x' 버튼을 사용하여 제거하세요. 선택한 사용자의 이메일은 숨은 참조(Bcc)로 추가됩니다. 문서가 완료되면 각 사용자에게 이메일 알림이 전송됩니다.",
|
||||
"cc-help": "연락처 이름을 입력하기 시작하면 저장된 연락처에서 추천이 표시되며, 새 연락처를 추가할 수도 있습니다. 사용자를 추가하려면 '+' 버튼을, 제거하려면 'x'를 사용하세요. 선택한 사용자의 이메일이 CC(참조)로 추가됩니다. 문서가 완료되면 각 사용자에게 이메일 알림이 전송됩니다.",
|
||||
"add-signer": "서명자 추가",
|
||||
"contact-not-found": "연락처를 찾을 수 없습니다",
|
||||
"add-yourself": "본인 추가",
|
||||
@@ -282,13 +284,15 @@
|
||||
"yes": "네",
|
||||
"copied": "복사됨",
|
||||
"something-went-wrong-mssg": "문제가 발생했습니다. 새로고침 시 문제가 해결될 수 있습니다.",
|
||||
"attachments-docx-upload-failed": "현재 문제가 발생했습니다. 다시 시도해 주세요.",
|
||||
"attachments-docx-upload-limit": "큰 DOCX 파일에 대한 업로드 제한에 도달했습니다. 4분 후에 다시 시도해 주세요.",
|
||||
"token-generated": "토큰이 성공적으로 생성되었습니다.",
|
||||
"webhook": "웹훅",
|
||||
"update-webhook": "웹훅 업데이트",
|
||||
"add-webhook": "웹훅 추가",
|
||||
"webhook-added": "웹훅이 성공적으로 추가되었습니다.",
|
||||
"team-disabled": "팀 비활성화됨",
|
||||
"Team enabled": "팀 활성화됨",
|
||||
"team-enabled": "팀 활성화됨",
|
||||
"are-you-sure": "정말로 하시겠습니까?",
|
||||
"disable": "비활성화",
|
||||
"enable": "활성화",
|
||||
@@ -296,7 +300,7 @@
|
||||
"edit-team": "팀 편집",
|
||||
"name-of-team": "팀 이름",
|
||||
"prev": "이전",
|
||||
"no-data-avaliable": "데이터 없음",
|
||||
"no-data-available": "데이터 없음",
|
||||
"add-team": "팀 추가",
|
||||
"page-not-found": "페이지를 찾을 수 없습니다",
|
||||
"users-from-teams": "팀의 사용자",
|
||||
@@ -307,12 +311,37 @@
|
||||
"delete-user": "사용자 삭제",
|
||||
"delete": "삭제",
|
||||
"add-user": "사용자 추가",
|
||||
"password-generateed": "비밀번호는 한 번만 생성되므로 복사해 두세요.",
|
||||
"password-generated": "비밀번호는 한 번만 생성되므로 복사해 두세요.",
|
||||
"Team status": "팀 상태",
|
||||
"user-deactivated": "유저 비활성화됨.",
|
||||
"user-activated": "유저 활성화됨.",
|
||||
"public": "공개",
|
||||
"download": "다운로드",
|
||||
"sign-offline": "오프라인 서명",
|
||||
"sign-offline-instructions": "문서를 다운로드하고 외부 도구로 서명한 후 서명된 사본을 여기에 업로드하세요. 문서 소유자가 제출물을 검토하여 승인하거나 거부합니다.",
|
||||
"sign-offline-step-1": "서명되지 않은 PDF를 다운로드합니다.",
|
||||
"sign-offline-step-2": "선호하는 도구를 사용하여 PDF에 서명합니다.",
|
||||
"sign-offline-step-3": "소유자 승인을 위해 서명된 PDF를 업로드합니다.",
|
||||
"sign-offline-upload-hint": "승인을 위해 제출할 서명된 PDF를 선택하세요.",
|
||||
"sign-offline-submitted-title": "승인을 위해 제출됨",
|
||||
"sign-offline-submitted-body": "문서 소유자에게 오프라인 서명 사본 검토를 위한 이메일이 전송되었습니다.",
|
||||
"offline-already-submitted-title": "문서가 오프라인으로 제출되었습니다",
|
||||
"offline-already-submitted-body": "이 문서는 이미 오프라인 서명 검토를 위해 제출되었습니다. 승인 또는 거부되면 알림이 전송됩니다.",
|
||||
"offline-action-approve-title": "오프라인 서명 승인",
|
||||
"offline-action-approve-confirm": "승인",
|
||||
"offline-action-reject-title": "오프라인 서명 거부",
|
||||
"offline-action-reject-hint": "선택적으로 이유를 제공하세요. 서명자에게 이메일로 알림이 전송됩니다.",
|
||||
"offline-action-reject-reason-placeholder": "이유 (선택 사항)",
|
||||
"offline-action-reject-confirm": "거부 확인",
|
||||
"offline-action-review-title": "오프라인 서명 검토",
|
||||
"offline-action-review-hint": "서명자가 업로드한 서명된 PDF를 미리 보고 승인하거나 거부하세요. 확인하기 전까지는 아무런 조치가 취해지지 않습니다.",
|
||||
"offline-action-submitted-at": "제출됨",
|
||||
"offline-action-no-preview": "이 제출에 대한 미리 보기를 사용할 수 없습니다.",
|
||||
"offline-action-open-in-new-tab": "새 탭에서 PDF 열기",
|
||||
"offline-action-invalid-link": "이 검토 링크가 유효하지 않거나 만료되었습니다.",
|
||||
"offline-action-success": "작업이 완료되었습니다.",
|
||||
"reject": "거부",
|
||||
"document": "문서",
|
||||
"view": "보기",
|
||||
"make-template-public": "템플릿 공개하기",
|
||||
"make-template-private": "템플릿 비공개하기",
|
||||
@@ -337,7 +366,7 @@
|
||||
"quick-send-alert-1": "이 문서의 모든 역할이 현재 연락처에 연결되어 있습니다. 이 템플릿의 사본을 여러 서명자에게 빠르게 보내려면, 최소한 하나의 역할이 어떤 연락처에도 연결되지 않도록 하십시오.",
|
||||
"quick-send-alert-2": "모든 수신자에게 최소 한 개의 서명 위젯이 추가되었는지 확인하십시오.",
|
||||
"quick-send-alert-3": "이 템플릿에 최소한 하나의 역할을 추가하십시오.",
|
||||
"quick-send-alert-4": "빠른 전송 한도에 도달했습니다.",
|
||||
"quick-send-alert-4": "{{flow}}은(는) 배치당 최대 {{count}}명의 서명자를 지원합니다. 목록을 나누어 다시 시도하세요.",
|
||||
"copy-link": "링크 복사",
|
||||
"copy": "복사",
|
||||
"revoke-document": "문서 취소",
|
||||
@@ -394,7 +423,10 @@
|
||||
"image": "이미지",
|
||||
"email": "이메일",
|
||||
"number": "숫자",
|
||||
"draw": "그리기"
|
||||
"draw": "그리기",
|
||||
"attachments": "첨부파일",
|
||||
"attachments-add": "클릭하여 첨부파일 업로드",
|
||||
"attachments-count": "{{count}}개 첨부파일"
|
||||
},
|
||||
"fields": "필드",
|
||||
"recipients": "수신자",
|
||||
@@ -406,6 +438,14 @@
|
||||
"certificate": "증명서",
|
||||
"decline": "거절",
|
||||
"finish": "완료",
|
||||
"approve": "승인",
|
||||
"approve-mssg": " 문서를 승인하시겠습니까?",
|
||||
"signer-role-label": "수신자 역할",
|
||||
"signer-role-signer": "서명자",
|
||||
"signer-role-viewer": "열람자",
|
||||
"signer-role-approver": "승인자",
|
||||
"signer-role-hint": "서명자는 서명해야 합니다. 승인자는 서명 없이 승인합니다. 열람자는 문서만 볼 수 있습니다.",
|
||||
"signer-role-upgrade-msg": "열람자 및 승인자 역할은 유료 요금제에서만 제공됩니다. 수신자에게 이러한 역할을 지정하려면 요금제를 업그레이드하세요.",
|
||||
"done": "완료",
|
||||
"mail": "메일",
|
||||
"sign-now": "지금 서명",
|
||||
@@ -438,14 +478,15 @@
|
||||
"reset-password-alert-1": "비밀번호 재설정 링크가 이메일로 전송되었습니다",
|
||||
"reset-password-alert-2": "이메일 어댑터를 설정하십시오 ",
|
||||
"reset-password-alert-3": "비밀번호 재설정",
|
||||
"faild-animation": "애니메이션 로드 실패",
|
||||
"failed-animation": "애니메이션 로드 실패",
|
||||
"apply": "적용",
|
||||
"select-columns": "열 선택",
|
||||
"copy-type": {
|
||||
"All pages": "모든 페이지",
|
||||
"All pages but last": "마지막 페이지 제외 모든 페이지",
|
||||
"All pages but first": "첫 페이지 제외 모든 페이지",
|
||||
"Next to current widget": "현재 위젯 옆"
|
||||
"Next to current widget": "현재 위젯 옆",
|
||||
"Page range": "페이지 범위"
|
||||
},
|
||||
"options": "옵션",
|
||||
"minimum-check": "최소 선택",
|
||||
@@ -462,6 +503,19 @@
|
||||
"formula-error-braces": "모든 열리는 중괄호 '{{' 는 반드시 닫히는 중괄호 '}}' 가 있어야 합니다.",
|
||||
"formula-error-operators": "연산자나 괄호를 사용할 때는 최소 두 개의 값을 추가하세요.",
|
||||
"formula-error-leading-operator": "수식은 연산자로 시작할 수 없습니다.",
|
||||
"formula-error-trailing-operator": "수식은 연산자로 끝날 수 없습니다.",
|
||||
"formula-error-adjacent-placeholders": "두 플레이스홀더는 사이에 연산자 없이 붙여 쓸 수 없습니다.",
|
||||
"formula-error-continuous-operators": "연산자를 두 개 연속으로 사용할 수 없습니다.",
|
||||
"formula-error-empty-parentheses": "괄호는 비워둘 수 없습니다.",
|
||||
"formula-error-invalid-parentheses-content": "괄호는 연산자로 시작하거나 끝날 수 없습니다.",
|
||||
"formula-error-before-open-parenthesis": "'(' 앞에 연산자가 없습니다. 여는 괄호 앞에 연산자를 추가하세요.",
|
||||
"formula-error-after-close-parenthesis": "')' 뒤에 연산자가 없습니다. 닫는 괄호 뒤에 '+' 또는 '*' 같은 연산자를 추가하세요.",
|
||||
"formula-error-between-parentheses": "')'와 '(' 사이에 연산자가 없습니다. 두 그룹 사이에 '*' 같은 연산자를 추가하세요.",
|
||||
"formula-error-division-by-zero": "0으로 나눌 수 없습니다. '0'을 유효한 0이 아닌 숫자 또는 변수로 바꾸세요.",
|
||||
"formula-error-invalid-number-format": "잘못된 숫자 형식이 발견되었습니다. 숫자는 '1', '3.14'처럼 올바른 형식이어야 하며, '.5', '1.', '1.2.3'은 허용되지 않습니다.",
|
||||
"formula-error-unclosed-parenthesis": "닫히지 않은 '('가 발견되었습니다. 모든 여는 괄호 '('는 대응하는 닫는 괄호 ')'가 있어야 합니다.",
|
||||
"formula-error-unexpected-closing-parenthesis": "대응하는 여는 괄호 '(' 없이 예상치 못한 닫는 괄호 ')'가 발견되었습니다.",
|
||||
"formula-error-empty-placeholder": "빈 플레이스홀더 '{{}}'는 허용되지 않습니다. 중괄호 안에 변수 이름을 입력하세요.",
|
||||
"decimal-places": "소수점 자리수",
|
||||
"number-of-decimal-places": "소수점 자리수",
|
||||
"decimal-places-helper": "정수를 원하면 0으로 설정하세요",
|
||||
@@ -530,6 +584,7 @@
|
||||
"sign-url": "서명 URL",
|
||||
"mails-sent": "메일 전송됨",
|
||||
"mail-not-delivered": "메일이 전달되지 않음",
|
||||
"email-not-verified-head": "이메일이 인증되지 않았습니다",
|
||||
"document-alert": "문서 알림",
|
||||
"owner-subscription-expired": "소유자의 구독이 만료되었습니다.",
|
||||
"subscription-expired": "구독 만료",
|
||||
@@ -605,6 +660,9 @@
|
||||
"document-signed-alert-7": "이 문서는 더 이상 사용할 수 없습니다.",
|
||||
"document-signed-alert-8": "문서가 이미 서명을 위해 발송되었습니다.",
|
||||
"yet-to-sign": "서명 대기 중",
|
||||
"viewers": "열람자",
|
||||
"viewer-no-widgets": "열람자는 문서의 읽기 전용 사본을 받으며 위젯을 배정받을 수 없습니다.",
|
||||
"viewer-confirm-widget-removal": "이 수신자를 열람자로 변경하면 이미 배정된 모든 위젯이 제거됩니다. 계속하시겠습니까?",
|
||||
"validation-alert-1": "입력이 정규식으로 설정된 기준을 충족하지 않습니다.",
|
||||
"account-already-exists": "계정이 이미 존재합니다!",
|
||||
"choose-password": "비밀번호 선택",
|
||||
@@ -627,7 +685,7 @@
|
||||
"date": "날짜",
|
||||
"report-not-found": "보고서를 찾을 수 없습니다",
|
||||
"please-wait": "잠시만 기다려주세요",
|
||||
"select-foler": "폴더 선택",
|
||||
"select-folder": "폴더 선택",
|
||||
"generated-on": "생성 날짜",
|
||||
"text-field": "텍스트 필드",
|
||||
"font-size": "글꼴 크기",
|
||||
@@ -686,13 +744,13 @@
|
||||
"public-template": "이 비디오는 'https://opensign.me/your-username'과 같은 개인화된 공개 프로필을 설정하는 방법을 보여줍니다. 또한 태그라인을 사용자 지정하고 템플릿을 공개 서명에 사용할 수 있도록 하는 방법도 배웁니다.",
|
||||
"allowModify-widgets": "문서 작성자가 이미 지정한 필드 외에 이러한 필드 중 하나를 문서에 끌어다 놓을 수 있습니다."
|
||||
},
|
||||
"enter-email-plaholder": "이메일 주소를 추가하고 Enter 키를 누르세요",
|
||||
"enter-email-placeholder": "이메일 주소를 추가하고 Enter 키를 누르세요",
|
||||
"success-email-alert": "이메일이 성공적으로 전송되었습니다!",
|
||||
"expired-doc-title": "만료된 문서",
|
||||
"expired-on-mssg": "이 문서는 {{expiredDate}}에 만료되어 더 이상 서명할 수 없습니다.",
|
||||
"signature-validate-alert": "최소 {{minRequiredCount}}개의 확인란을 선택했는지 확인하세요.",
|
||||
"signature-validate-alert-2": "이 필드가 정확하게 채워지고 모든 요구 사항을 충족하는지 확인하세요.",
|
||||
"remaing-users": "남은 사용자",
|
||||
"remaining-users": "남은 사용자",
|
||||
"Role": "역할",
|
||||
"additional-users": "추가 사용자를 구매하세요.",
|
||||
"Quantity-of-users": "사용자 수",
|
||||
@@ -714,7 +772,7 @@
|
||||
"rotate-left": "왼쪽으로 회전",
|
||||
"rotate-alert-mssg": "이 페이지의 모든 위젯이 손실됩니다. 계속하시겠습니까?",
|
||||
"templateid": "템플릿 ID",
|
||||
"bulk-send-subcription-alert": "대량 전송을 사용하려면 프로페셔널 또는 팀 플랜으로 업그레이드하세요.",
|
||||
"bulk-send-subscription-alert": "대량 전송을 사용하려면 프로페셔널 또는 팀 플랜으로 업그레이드하세요.",
|
||||
"generate-test-token": "테스트 토큰 생성",
|
||||
"regenerate-test-token": "테스트 토큰 재성",
|
||||
"help-test-token": "이 토큰은 https://sandbox.opensignlabs.com/api/v1 엔드포인트에서 API를 테스트하는 데 사용할 수 있으며 무제한 문서 서명을 수행할 수 있습니다. 샌드박스 API는 자체 서명된 인증서로 문서에 서명하므로 Adobe에서 유효한 것으로 인식하지 못할 수 있습니다. 테스트를 완료한 후 유료 플랜 중 하나로 업그레이드하여 프로덕션 토큰을 생성할 수 있습니다.",
|
||||
@@ -734,8 +792,9 @@
|
||||
"copied-code": "복사됨",
|
||||
"Installation": "설치",
|
||||
"Usage": "사용법",
|
||||
"insufficient-credits": "서명 크레딧 부족",
|
||||
"insufficient-credits-mssg": "이 문서의 소유자는 현재 서명하는 데 필요한 OpenSign 크레딧이 부족합니다. 자세한 내용이 필요한 경우 소유자에게 문의하세요.",
|
||||
"insufficient-credits": "서명 크레딧이 부족합니다. {{ownerEmail}}에 문의하세요",
|
||||
"insufficient-credits-title": "서명 크레딧 부족",
|
||||
"insufficient-credits-mssg": "이 문서의 소유자는 현재 서명하는 데 필요한 {{appName}} 크레딧이 부족합니다. 자세한 내용이 필요한 경우 소유자에게 문의하세요.",
|
||||
"angular-npm-mssg-1": "Angular 프로젝트에 OpenSign을 통합하려면 다음 명령을 실행하기만 하면 됩니다.",
|
||||
"quota-mail-info-head": "월간 이메일 한도",
|
||||
"quota-mail-info": "서비스 품질을 유지하고 스팸을 방지하기 위해 OpenSign은 무료 플랜에서 월 최대 15개의 이메일을 허용합니다. 지금 업그레이드하여 무제한 이메일 전송을 이용하세요.",
|
||||
@@ -754,6 +813,13 @@
|
||||
"p3": "이 옵션을 선택하면 OTP 인증이 비활성화되어 사용자가 추가 단계 없이 직접 문서에 서명할 수 있습니다.",
|
||||
"p4": "문서 서명 요구 사항에 가장 적합한 옵션을 선택하세요."
|
||||
},
|
||||
"allow-offline-sign": "오프라인 서명 허용",
|
||||
"allow-offline-sign-help": {
|
||||
"p1": "서명자가 외부 도구로 서명한 문서 사본을 소유자 검토를 위해 제출할 수 있는지 선택하세요.",
|
||||
"p2": "서명자에게 오프라인 서명 옵션이 표시되며 외부에서 서명된 PDF를 소유자 승인용으로 업로드할 수 있습니다.",
|
||||
"p3": "서명자는 앱 내에서만 서명할 수 있으며 오프라인 서명 옵션은 숨겨집니다.",
|
||||
"p4": "이 설정은 계정 기본 설정을 따르며 유료 요금제에서 사용할 수 있습니다."
|
||||
},
|
||||
"advanced-options": "고급 옵션",
|
||||
"hide-advanced-options": "고급 옵션 숨기기",
|
||||
"download-files": "파일 다운로드",
|
||||
@@ -816,6 +882,7 @@
|
||||
"delete-folder-alert-1": "삭제할 수 없음: 폴더가 비어 있어야 합니다.",
|
||||
"defaultSign-alert": "{{widgetsType}} 위젯은 자동 서명에 사용할 수 없습니다.",
|
||||
"embed-heading": "{{appName}} 템플릿 포함",
|
||||
"other-apps-by-opensign": "OpenSign 팀의 다른 앱",
|
||||
"public-template-edit-title": "공개 템플릿 편집",
|
||||
"public-template-edit-mssg": "공개 템플릿을 직접 편집할 수 없습니다. 변경하려면 템플릿이 비공개로 설정되며 나중에 다시 공개해야 합니다. 계속하시겠습니까?",
|
||||
"agree-p1": "나는 ",
|
||||
@@ -867,7 +934,6 @@
|
||||
"js-snippet-msg-1": "템플릿 ID는 다음에서 얻을 수 있습니다.",
|
||||
"js-snippet-msg-2": "템플릿 관리",
|
||||
"js-snippet-msg-3": "페이지.",
|
||||
"agrrement-alert": "계속하려면 전자 기록 및 서명 공개에 동의해야 합니다.",
|
||||
"webhook-already-exists": "URL이 이미 존재합니다! 다른 URL을 시도하세요.",
|
||||
"webhook-must-be-secure": "웹훅 URL은 안전해야 하며 https://를 사용해야 합니다.",
|
||||
"provide-test-token": "API 테스트 토큰을 생성하세요.",
|
||||
@@ -887,6 +953,7 @@
|
||||
"p3": "이 옵션을 선택하면 서명자가 서명 과정 중에 문서에 추가 작업을 할 수 없습니다."
|
||||
},
|
||||
"signature-setting": "서명 설정",
|
||||
"rotation": "회전",
|
||||
"activation-key": "활성화 키",
|
||||
"paid-selfhost-help": "자세한 내용은 {{appName}}에 문의하세요. ",
|
||||
"remaining-credits-selfhost-help": "API 문서 서명, 대량 전송, 웹사이트에 {{appName}} 통합에 프리미엄 크레딧을 사용하세요. 포함된 크레딧 {{allowedcredits}}개와 추가 구매한 크레딧 {{addoncredits}}개가 남아 있습니다.",
|
||||
@@ -915,6 +982,8 @@
|
||||
"visit-below-link": "자세한 내용은 아래 링크를 방문하세요 -",
|
||||
"storage-help": "BYOC를 활성화하면 자체 S3 저장소를 연결하여 파일이 외부 사본 없이 완전히 제어할 수 있습니다. 데이터 자율성이 중요하다면 팀으로 업그레이드하여 이 기능을 잠금 해제하는 것을 고려해보세요.",
|
||||
"daily-quota-reached": "일일 할당량에 도달했습니다. 도움이 필요하시면 quotas@opensignlabs.com으로 문의하세요.",
|
||||
"email-not-verified-send": "이메일을 보내기 전에 이메일 주소를 인증해 주세요. 이메일 인증을 완료하려면 <1>프로필 설정</1>으로 이동하세요.",
|
||||
"email-not-verified-send-plain": "이메일을 보내기 전에 이메일 주소를 인증해 주세요. 이메일 인증을 완료하려면 프로필 설정으로 이동하세요.",
|
||||
"enabled-signature-type": "활성화된 서명 유형",
|
||||
"enabled-signature-type-help": "'활성화된 서명 유형' 설정은 조직 전체에서 사용할 수 있는 서명 옵션을 결정합니다. 예를 들어 '그리기' 옵션을 비활성화하면 조직 구성원은 서명 위젯에서 해당 옵션을 볼 수 없지만 다른 세 가지 옵션은 계속 사용할 수 있습니다.",
|
||||
"indexing-public-profile": "검색 엔진에 의한 공개 프로필 색인 허용",
|
||||
@@ -942,6 +1011,7 @@
|
||||
"please-provide-templateid": "템플릿 ID를 입력하세요.",
|
||||
"this-template-is-not-public": "이 템플릿은 공개되지 않았습니다.",
|
||||
"invalid-templateid": "잘못된 템플릿 ID입니다.",
|
||||
"kiosk-no-active-role": "이 템플릿에는 키오스크 서명에 사용할 수 있는 서명자 또는 승인자 역할이 없습니다.",
|
||||
"contact-billing-at-opensign": "더 많은 좌석을 추가하려면 <1>billing@opensignlabs.com</1>으로 OpenSign™에 문의하여 도움을 받으세요.",
|
||||
"title-length-alert": "제목은 최대 250자여야 합니다.",
|
||||
"note-length-alert": "메모는 최대 200자여야 합니다.",
|
||||
@@ -1031,7 +1101,7 @@
|
||||
},
|
||||
"signer-already-present": "서명자가 이미 존재합니다",
|
||||
"kiosk-sign": "키오스크 서명",
|
||||
"dont-have-access-to-template": "템플릿이 삭제되었거나 접근 권한이 없습니다. 발신자에게 문의하십시오.",
|
||||
"do-not-have-access-to-template": "템플릿이 삭제되었거나 접근 권한이 없습니다. 발신자에게 문의하십시오.",
|
||||
"kiosk-info": "키오스크 모드를 사용하면 직접 서명을 빠르고 효율적으로 수집할 수 있습니다. 모든 서명자가 물리적으로 참석하는 무역 박람회, 이벤트 또는 방문 시나리오에 이상적입니다.",
|
||||
"learn-more": "더 알아보기",
|
||||
"finish-mssg": " 문서를 완료하시겠습니까?",
|
||||
@@ -1087,8 +1157,6 @@
|
||||
"search-contacts": "연락처 검색…",
|
||||
"add-role-alert": "하나 이상의 역할을 추가하십시오",
|
||||
"edit-draft": "초안 편집",
|
||||
"invalid-email-found": "잘못된 이메일 발견: {{email}}",
|
||||
"duplicate-email-found": "중복 이메일 발견: {{email}}",
|
||||
"vertical": "세로",
|
||||
"horizontal": "가로",
|
||||
"billing": "결제",
|
||||
@@ -1102,7 +1170,7 @@
|
||||
"access-denied": "접근 거부",
|
||||
"upgrade": "업그레이드",
|
||||
"do-not-access-app": "이 애플리케이션에 접근할 수 없습니다.",
|
||||
"dont-have-access": "접근 권한이 없습니다.",
|
||||
"do-not-have-access": "접근 권한이 없습니다.",
|
||||
"valid-email-alert": "유효한 이메일 주소를 입력하십시오.",
|
||||
"otp-not-validate": "OTP가 유효하지 않습니다.",
|
||||
"domain-not-allowed": "이 도메인은 허용되지 않습니다",
|
||||
@@ -1119,10 +1187,12 @@
|
||||
"branding-help": "브랜딩은 앱에 화이트 라벨링을 제공합니다",
|
||||
"custom-sub-domain": "사용자 지정 하위 도메인",
|
||||
"app-name": "앱 이름",
|
||||
"provide-domain-name": "도메인 이름을 입력하세요",
|
||||
"provide-domain-name": "서브도메인만 허용됩니다(예: app.yourdomain.com). yourdomain.com과 같은 기본/루트 도메인은 지원되지 않습니다.",
|
||||
"provide-app-name-help": "고유한 앱 이름을 입력하세요. 앱 내에서는 브랜드 이름으로, 브랜딩이 적용되는 모든 이메일에서도 표시됩니다.",
|
||||
"upload-app-logo": ".png, .jpg 또는 .svg 형식의 로고를 업로드하세요. 최상의 표시 품질을 위해 권장 크기: {{size}}픽셀(가로세로 비율 5:2).",
|
||||
"upload-app-favicon": "정사각형 파비콘 이미지(.ico, .png 또는 .svg)를 업로드하세요. 브라우저 호환성을 위해 권장 크기: {{size}}픽셀.",
|
||||
"provide-app-name": "앱 이름을 입력하세요",
|
||||
"logo": "로고",
|
||||
"upload-app-logo": "앱 로고 업로드",
|
||||
"prefill-unfilled-widget": "다음 필수 필드를 비워 둘 수 없습니다: {{emptyWidget}} 계속하려면 채워주세요.",
|
||||
"Dashboard": "대시보드",
|
||||
"Analytics": "분석",
|
||||
@@ -1290,11 +1360,11 @@
|
||||
"signer-form-data-for": "서명자 양식 데이터: {{signerName}} <{{signerEmail}}>",
|
||||
"widget-name": "위젯 이름",
|
||||
"widget-responses": "위젯 응답",
|
||||
"formdata-download-note": "참고: 서명, 스탬프, 이니셜 및 이미지는 내보내지지 않습니다.",
|
||||
"formdata-download-note": "참고: 서명, 스탬프, 이니셜 및 이미지는 내보내지지 않습니다. 오프라인 서명자의 데이터는 사용할 수 없습니다.",
|
||||
"export-all-formdata-error": "템플릿에 중복된 위젯 이름이 포함되어 있어 양식 데이터를 내보낼 수 없습니다. 양식 데이터를 내보내려면 새 템플릿을 만드십시오.",
|
||||
"export-formdata-error": "문서에 중복된 위젯 이름이 포함되어 있어 양식 데이터를 내보낼 수 없습니다. 양식 데이터를 내보내려면 중복된 위젯 이름이 없는 새 문서를 만드십시오.",
|
||||
"duplicate-widget-name-error": "이 위젯 이름은 이미 다른 서명자에게 할당되었습니다. 다른 이름을 선택하십시오.",
|
||||
"duplicate-template-widget-error": "이 템플릿에는 중복된 위젯 이름이 포함되어 있어 복제할 수 없습니다.",
|
||||
"duplicate-template-widget-error": "일부 위젯 이름이 다른 역할에서 중복되어 템플릿을 복제할 수 없습니다. 중복된 위젯의 이름을 변경한 후 다시 시도하세요.",
|
||||
"credit-alert": "크레딧 알림",
|
||||
"document-form-data": "문서 양식 데이터",
|
||||
"view-formdata-paid-title-error": "양식 데이터 보기 기능을 사용하면 서명자가 서명하는 동안 채운 위젯 데이터를 인쇄하고 다운로드할 수 있습니다.",
|
||||
@@ -1346,8 +1416,8 @@
|
||||
"files-selected": "파일이 선택되었습니다",
|
||||
"bulk-send-csv-helper": "서명자 역할(역할 이메일/이름/전화번호)과 사전 입력 값(사전 입력: 필드 이름)에 헤더를 사용하세요. 필수 사전 입력 필드는 반드시 입력해야 합니다.",
|
||||
"bulk-send-csv-helper-note": "참고: CSV의 날짜 유형 사전 입력 필드의 값은 다음 형식으로 제공되어야 합니다: {{value}} (예: {{example}}).",
|
||||
"bulk-send-csv-summary": "총 {{total}}개의 행을 가져왔습니다. {{invalid}}개의 잘못된 행은 건너뛰었습니다.",
|
||||
"bulk-send-csv-missing-headers": "필수 CSV 헤더가 누락되었습니다. 역할: {{roles}}. 사전 입력: {{prefill}}.",
|
||||
"bulk-send-csv-summary": "CSV에서 {{total}}개의 행을 가져왔습니다.",
|
||||
"bulk-send-csv-missing-headers": "필수 CSV 헤더가 누락되었습니다. 역할: {{roles}}.",
|
||||
"bulk-send-no-records": "전송하기 전에 최소 한 명의 수신자를 추가하세요.",
|
||||
"csv-only-support": "CSV 파일만 지원됩니다.",
|
||||
"csv-no-records": "CSV에서 레코드를 찾을 수 없습니다.",
|
||||
@@ -1361,5 +1431,184 @@
|
||||
"enter-daily-email-quota": "일일 이메일 할당량을 입력하세요",
|
||||
"enter-custom-regular-expression": "사용자 정의 정규식을 입력하세요",
|
||||
"enter-value": "{{value}}을(를) 입력하세요",
|
||||
"are-you-sure-want-to-send-document": "문서를 보내시겠습니까?"
|
||||
}
|
||||
"are-you-sure-want-to-send-document": "문서를 보내시겠습니까?",
|
||||
"favicon": "Favicon",
|
||||
"delete-two-factor-authentication": "이중 인증을 삭제하시겠습니까?",
|
||||
"paste-html-here": "여기에 HTML 붙여넣기",
|
||||
"open-email-builder": "<1>이메일 빌더</1> 플랫폼을 사용하여 이메일 템플릿을 생성하고 HTML 코드를 복사할 수 있습니다.",
|
||||
"contact-saved": "연락처가 성공적으로 저장되었습니다.",
|
||||
"remove-limit-of-user": "이 사용자의 제한을 제거하시겠습니까? 제거하면 이 사용자는 모든 사용자에게 공유된 조직의 사용 가능한 모든 프리미엄 크레딧을 사용할 수 있습니다.",
|
||||
"current-expiry-date": "현재 만료일",
|
||||
"reminder-error": "“(일)마다 한 번 알림”은 “완료까지의 시간(일)”을 초과할 수 없습니다.",
|
||||
"showing-to-of-records": "총 {{totalRows}}개 중 {{record}}부터 {{recordPerPage}}까지 표시 중",
|
||||
"records-per-page": "페이지당 항목 수",
|
||||
"email-not-found-in-row": "{{row}}행에서 이메일을 찾을 수 없습니다.",
|
||||
"invalid-email-found-in-row": "{{row}}행에서 잘못된 이메일이 발견되었습니다: {{email}}",
|
||||
"duplicate-email-found-in-row": "{{row}}행에서 중복된 이메일이 발견되었습니다: {{email}}",
|
||||
"prefill": "미리 채우기",
|
||||
"recipients-&-data": "수신자 및 데이터",
|
||||
"response": "응답",
|
||||
"prefill-tab-help": "이 필드는 이 대량 전송의 모든 문서에 적용되는 공통 템플릿 필드입니다.",
|
||||
"recipient-tab-help-p1": "다음 단계에서는:",
|
||||
"recipient-tab-help-p2": "수신자 정보가 포함된 CSV를 업로드하거나",
|
||||
"recipient-tab-help-p3": "수신자 정보를 수동으로 입력합니다",
|
||||
"recipient-tab-help-p4": "각 수신자는 이 템플릿에서 생성된 개별 문서를 받게 됩니다.",
|
||||
"response-tab-help": "응답을 검토하세요.",
|
||||
"summary": "요약",
|
||||
"prefill-fields": "미리 채우기 필드",
|
||||
"documents": "문서",
|
||||
"message": "메시지",
|
||||
"formula-help-p1": "숫자 입력을 수집하고 수식을 사용하여 동적 계산을 수행하세요.",
|
||||
"example": "예시",
|
||||
"formula-help-p2": "number-3에 합계를 표시하려면 {{number-1}} + {{number-2}}를 사용하세요.",
|
||||
"formula-help-p3": "number-4의 값을 계산하려면 ({{number-1}} * {{number-2}}) / {{number-3}}를 사용하세요.",
|
||||
"formula-help-p4": "각 숫자 위젯은 고유한 이름(예: number-1)을 가져야 하며, 수식은 이러한 식별자를 기반으로 합니다.",
|
||||
"note-consume-premium-credits": "참고: 이 기능은 프리미엄 크레딧을 사용합니다.",
|
||||
"note-consume-premium-credits-on-document-send": "참고: 이 기능은 프리미엄 크레딧을 사용합니다. 문서가 서명되었는지 여부와 관계없이 문서를 보낼 때마다 크레딧이 차감됩니다.",
|
||||
"webhook-authentication-help": "{{appName}}은(는) 웹훅 보안 키와 원본 요청 페이로드를 사용하여 HMAC-SHA256 서명을 생성함으로써 모든 웹훅을 보호합니다. 이 서명은 x-webhook-signature 헤더에 전송됩니다. 귀하의 서버는 수신된 페이로드와 비밀 키를 사용하여 동일한 서명을 생성해야 합니다. 두 서명이 일치하면 해당 웹훅 요청은 인증되었으며 수정되지 않았음을 의미합니다.",
|
||||
"invalid-widget-found-in-row": "{{row}}행에서 위젯 {{widget}}에 대한 유효하지 않은 값이 감지되었습니다.",
|
||||
"all-signer-status": "전체 서명자 상태",
|
||||
"viewed": "열람함",
|
||||
"signed": "서명 완료",
|
||||
"switch-to-advanced": "고급 모드로 전환",
|
||||
"switch-to-basic": "기본 모드로 전환",
|
||||
"use-name-as-sender": "프로필 이름을 발신자로 사용",
|
||||
"use-name-as-sender-help": "활성화하면 {{appName}}에서 서명 요청 및 완료 이메일의 발신자로 프로필 이름을 사용합니다.",
|
||||
"acro-fields-detected": "양식 필드 감지됨",
|
||||
"acro-fields-detected-msg": "업로드된 PDF에서 {{count}}개의 양식 필드가 감지되었습니다. 해당 위젯을 자동으로 삽입하시겠습니까?",
|
||||
"documents-count": "문서 수",
|
||||
"templates-count": "템플릿 수",
|
||||
"emails-sent": "전송된 이메일",
|
||||
"storage-used": "사용한 저장공간",
|
||||
"docs-count-team-help": "조직 내 모든 사용자가 생성한 문서의 총 개수입니다.",
|
||||
"docs-count-help": "내가 생성한 문서의 총 개수입니다.",
|
||||
"templates-count-team-help": "조직 내 모든 사용자가 생성한 템플릿의 총 개수입니다.",
|
||||
"templates-count-help": "내가 생성한 템플릿의 총 개수입니다.",
|
||||
"emails-sent-team-help": "조직 전체에서 전송된 이메일 알림의 총 개수입니다.",
|
||||
"emails-sent-help": "내가 전송한 이메일 알림의 총 개수입니다.",
|
||||
"storage-used-team-help": "조직 전체의 모든 사용자가 사용한 총 저장공간입니다.",
|
||||
"storage-used-help": "내가 사용한 총 저장공간입니다.",
|
||||
"cloud": "클라우드",
|
||||
"on-premises": "온프레미스",
|
||||
"plan-name-free": "OPENSIGN™ FREE",
|
||||
"plan-name-professional": "OPENSIGN™ PROFESSIONAL",
|
||||
"plan-name-teams": "OPENSIGN™ TEAMS",
|
||||
"plan-name-enterprise": "OPENSIGN™ ENTERPRISE",
|
||||
"plan-name-self-hosted": "OPENSIGN™ SELF-HOSTED",
|
||||
"plan-subtitle-free": "무료 무제한 전자 서명, 영원히.",
|
||||
"plan-subtitle-professional": "고급 기능에 대한 독점 액세스.",
|
||||
"plan-subtitle-scalable": "우선 지원을 제공하는 확장 가능한 기능.",
|
||||
"subscribe": "구독",
|
||||
"contact-us": "문의하기",
|
||||
"benefit-unlimited-digital-signatures": "무제한 디지털 서명",
|
||||
"benefit-sign-docs-yourself": "직접 문서에 서명",
|
||||
"benefit-request-signatures": "타인에게 서명 요청",
|
||||
"benefit-unlimited-templates": "무제한 템플릿",
|
||||
"benefit-14-field-types": "14가지 필드 유형",
|
||||
"benefit-automatic-e-signatures": "자동 전자 서명",
|
||||
"benefit-completion-certificates": "완료 인증서",
|
||||
"benefit-send-in-order": "순서대로 전송",
|
||||
"benefit-organize-docs": "OpenSign™ Drive에서 문서 정리",
|
||||
"benefit-document-templates": "문서 템플릿",
|
||||
"benefit-import-from-dropbox": "Dropbox에서 가져오기",
|
||||
"benefit-contact-book": "연락처 목록",
|
||||
"benefit-document-expiry": "문서 만료 지원",
|
||||
"benefit-decline-document": "문서 거절 지원",
|
||||
"benefit-email-notifications": "이메일 알림",
|
||||
"benefit-public-profiles": "공개 프로필",
|
||||
"benefit-and-much-more": "그 외 더 많은 기능",
|
||||
"benefit-everything-free": "OpenSign™ Free의 모든 기능",
|
||||
"benefit-field-validations": "필드 유효성 검사",
|
||||
"benefit-regex-validations": "정규식 유효성 검사",
|
||||
"benefit-webhooks": "웹훅",
|
||||
"benefit-zapier": "Zapier 통합",
|
||||
"benefit-api-access": "API 액세스",
|
||||
"benefit-api-signatures-100": "최대 100개 API 서명",
|
||||
"benefit-api-signatures-240": "최대 240개 API 서명",
|
||||
"benefit-custom-email-templates": "맞춤형 이메일 템플릿",
|
||||
"benefit-custom-smtp": "이메일 발송을 위한 Gmail 또는 SMTP 계정 연결",
|
||||
"benefit-auto-reminders": "자동 알림",
|
||||
"benefit-bulk-send-100": "일괄 전송 (최대 100개 문서)",
|
||||
"benefit-bulk-send-240": "일괄 전송 (최대 240개 문서)",
|
||||
"benefit-premium-profiles": "프리미엄 공개 프로필 사용자 이름",
|
||||
"benefit-email-verification": "서명자 신원 확인을 위한 이메일 인증 적용",
|
||||
"benefit-embedded-signing": "임베디드 서명",
|
||||
"benefit-everything-professional": "OpenSign™ Professional의 모든 기능",
|
||||
"benefit-teams-organizations": "팀 및 조직",
|
||||
"benefit-share-templates-teams": "팀과 템플릿 공유",
|
||||
"benefit-share-templates-individuals": "개인과 템플릿 공유",
|
||||
"benefit-byoc": "BYOC - 자체 클라우드 스토리지에 문서 저장",
|
||||
"benefit-doc-id-removal": "서명된 문서에서 DocumentId 제거",
|
||||
"benefit-request-payments": "결제 요청 (출시 예정)",
|
||||
"benefit-mobile-app": "모바일 앱 (출시 예정)",
|
||||
"benefit-api-signatures-500": "최대 500개 API 서명",
|
||||
"benefit-bulk-send-500": "일괄 전송 (최대 500개 문서)",
|
||||
"benefit-all-features": "모든 기능",
|
||||
"benefit-custom-domain": "사용자 지정 도메인",
|
||||
"benefit-custom-branding": "사용자 지정 브랜딩",
|
||||
"benefit-uptime-sla": "가동 시간 SLA",
|
||||
"benefit-sso-support": "SSO 지원",
|
||||
"benefit-priority-support": "우선 지원",
|
||||
"benefit-3-users-1500-credits": "3명의 사용자와 1500 프리미엄 크레딧 포함",
|
||||
"benefit-public-links": "공개 링크",
|
||||
"benefit-kiosk-mode": "키오스크 모드",
|
||||
"benefit-bulk-send-1500": "일괄 전송 (최대 1500개 문서)",
|
||||
"benefit-connect-gmail-smtp": "Gmail 또는 SMTP 연결",
|
||||
"benefit-analytics": "분석",
|
||||
"benefit-bulksend-api-pro-month-help": "이 플랜에는 월 100개의 프리미엄 크레딧이 포함되어 API로 문서를 전송하고, 공개 서명 링크를 공유하고, 대량 서명 요청을 실행하고, 대면 서명을 위한 Kiosk 모드를 사용하거나, 웹사이트에 직접 임베드된 문서에서 서명을 수집할 수 있습니다. 문서를 시작하거나 전송할 때마다 크레딧 1개가 사용되며 — 해당 문서 1건에는 최대 10명의 서명자를 포함할 수 있습니다.",
|
||||
"benefit-bulksend-api-pro-year-help": "이 플랜에는 연 240개의 프리미엄 크레딧이 포함되어 API로 문서를 전송하고, 공개 서명 링크를 공유하고, 대량 서명 요청을 실행하고, 대면 서명을 위한 Kiosk 모드를 사용하거나, 웹사이트에 직접 임베드된 문서에서 서명을 수집할 수 있습니다. 문서를 시작하거나 전송할 때마다 크레딧 1개가 사용되며 — 해당 문서 1건에는 최대 10명의 서명자를 포함할 수 있습니다.",
|
||||
"benefit-bulksend-api-team-month-help": "이 플랜에는 월 100개의 프리미엄 크레딧이 포함되어 API로 문서를 전송하고, 공개 서명 링크를 공유하고, 대량 서명 요청을 실행하고, 대면 서명을 위한 Kiosk 모드를 사용하거나, 웹사이트에 직접 임베드된 문서에서 서명을 수집할 수 있습니다. 문서를 시작하거나 전송할 때마다 크레딧 1개가 사용되며 — 해당 문서 1건에는 최대 10명의 서명자를 포함할 수 있습니다.",
|
||||
"benefit-bulksend-api-team-year-help": "이 플랜에는 연 500개의 프리미엄 크레딧이 포함되어 API로 문서를 전송하고, 공개 서명 링크를 공유하고, 대량 서명 요청을 실행하고, 대면 서명을 위한 Kiosk 모드를 사용하거나, 웹사이트에 직접 임베드된 문서에서 서명을 수집할 수 있습니다. 문서를 시작하거나 전송할 때마다 크레딧 1개가 사용되며 — 해당 문서 1건에는 최대 10명의 서명자를 포함할 수 있습니다.",
|
||||
"failed-pdf-for-prefill-processing": "프리필 처리를 위해 PDF를 불러오지 못했습니다.",
|
||||
"attach-all-role-to-signer": "서명자에게 모든 역할을 연결해 주세요.",
|
||||
"upgrade-to-edit": "편집하려면 업그레이드하세요",
|
||||
"hide-live-chat-for-signers": "서명자에게 라이브 채팅 숨기기",
|
||||
"hide-live-chat-for-signers-help": "활성화하면 서명 페이지에서 서명자에게 라이브 채팅 위젯이 숨겨집니다.",
|
||||
"attachments-creator-hint": "서명자는 문서 끝에 추가될 파일을 업로드할 수 있습니다.",
|
||||
"attachments-drop-hint": "파일을 여기에 놓거나 클릭하여 선택하세요",
|
||||
"attachments-limits": "PDF, DOCX, PNG, JPG. 최대 {{maxFiles}}개 파일, 각 {{maxSizeMB}} MB.",
|
||||
"attachments-invalid-type": "지원되지 않는 파일 형식: {{name}}",
|
||||
"attachments-max-files": "최대 {{maxFiles}}개의 첨부파일이 허용됩니다.",
|
||||
"attachments-remove-confirm": "제거하면 메타데이터만 삭제됩니다. 이미 추가된 페이지는 문서에 남아 있습니다. 계속하시겠습니까?",
|
||||
"conditional-logic": "조건부 로직",
|
||||
"enabled": "활성화됨",
|
||||
"disabled": "비활성화됨",
|
||||
"logic-no-sources": "이 서명자의 다른 필드를 소스로 사용할 수 없습니다.",
|
||||
"logic-action": "동작",
|
||||
"logic-when": "조건",
|
||||
"logic-all": "모두",
|
||||
"logic-any": "하나라도",
|
||||
"logic-of-following": "다음 조건이 참일 때",
|
||||
"logic-add-rule": "규칙 추가",
|
||||
"logic-pick-value": "값 선택",
|
||||
"logic-value-placeholder": "값 입력",
|
||||
"logic-action-show": "이 필드 표시",
|
||||
"logic-action-hide": "이 필드 숨기기",
|
||||
"logic-action-require": "필수로 만들기",
|
||||
"logic-action-optional": "선택 사항으로 만들기",
|
||||
"logic-op-equals": "같음",
|
||||
"logic-op-notEquals": "같지 않음",
|
||||
"logic-op-checked": "체크됨",
|
||||
"logic-op-unchecked": "체크되지 않음",
|
||||
"widget-hidden-due-to-logic": "이 위젯은 조건부 논리로 인해 숨겨져 있습니다.",
|
||||
"remove": "제거",
|
||||
"attachement-note": "참고: 이 위젯을 사용하면 서명자가 서명 과정 중에 파일을 업로드할 수 있습니다. 업로드된 파일은 기존 PDF의 마지막에 추가 페이지로 첨부됩니다.",
|
||||
"attachments-docx-not-supported": "현재 DOCX 파일 업로드에 문제가 발생하고 있습니다. 다른 지원되는 형식으로 문서를 업로드하시거나 잠시 후 다시 시도해 주세요.",
|
||||
"access-code": "접속 코드",
|
||||
"access-code-set": "접속 코드가 설정됨",
|
||||
"access-code-add": "접속 코드 추가",
|
||||
"access-code-help": "이 서명자만 아는 선택적 비밀번호입니다. 문서를 보거나 서명하기 전에 반드시 입력해야 합니다. 전화 또는 직접 만나서 등 별도의 경로로 수신자에게 전달하세요.",
|
||||
"access-code-placeholder": "6자리 이상 숫자",
|
||||
"access-code-clear": "지우기",
|
||||
"access-code-must-be-numeric": "접속 코드는 0-9 숫자만 포함해야 합니다.",
|
||||
"access-code-save": "저장",
|
||||
"access-code-required": "발신자가 이 수신자에게 접속 코드를 지정했습니다. 계속하려면 입력하세요.",
|
||||
"access-code-invalid": "잘못된 접속 코드입니다. 발신자에게 문의하세요.",
|
||||
"access-code-too-short": "접속 코드는 6자리 이상 숫자여야 합니다.",
|
||||
"access-code-verify": "접속 코드 확인",
|
||||
"access-code-verified": "접속 코드가 확인되었습니다.",
|
||||
"access-code-warning": "문서가 발송된 후에는 이 접속 코드를 변경하거나 검색할 수 없습니다.",
|
||||
"copy-access-code": "접속 코드 복사",
|
||||
"access-code-upgrade-msg": "접속 코드는 유료 요금제에서만 사용할 수 있습니다. 수신자에게 접속 코드를 추가하려면 요금제를 업그레이드하세요.",
|
||||
"already-copied-to-clipboard": "이미 클립보드에 복사됨"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"rewrites": [
|
||||
{ "source": "/:path((?!\\.well-known/).*)", "destination": "/index.html" }
|
||||
]
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,160 @@
|
||||
#!/usr/bin/env node
|
||||
/* eslint-env node */
|
||||
/* eslint-disable no-console */
|
||||
// Tiny static file server for the production build.
|
||||
// - Serves real files from ./build (including dotfile dirs like .well-known)
|
||||
// - Falls back to /index.html only when the requested path does not exist
|
||||
// (SPA client-side routing).
|
||||
const http = require("node:http");
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
|
||||
const root = path.join(__dirname, "build");
|
||||
const port = Number(process.env.PORT) || 3000;
|
||||
const host = process.env.HOST || "0.0.0.0";
|
||||
|
||||
const mime = {
|
||||
".html": "text/html; charset=utf-8",
|
||||
".js": "application/javascript; charset=utf-8",
|
||||
".mjs": "application/javascript; charset=utf-8",
|
||||
".css": "text/css; charset=utf-8",
|
||||
".json": "application/json; charset=utf-8",
|
||||
".map": "application/json; charset=utf-8",
|
||||
".svg": "image/svg+xml",
|
||||
".png": "image/png",
|
||||
".jpg": "image/jpeg",
|
||||
".jpeg": "image/jpeg",
|
||||
".gif": "image/gif",
|
||||
".ico": "image/x-icon",
|
||||
".webp": "image/webp",
|
||||
".woff": "font/woff",
|
||||
".woff2": "font/woff2",
|
||||
".ttf": "font/ttf",
|
||||
".otf": "font/otf",
|
||||
".txt": "text/plain; charset=utf-8",
|
||||
".pdf": "application/pdf",
|
||||
".csv": "text/csv; charset=utf-8",
|
||||
".wasm": "application/wasm",
|
||||
".xml": "application/xml; charset=utf-8"
|
||||
};
|
||||
|
||||
function contentType(filePath) {
|
||||
return mime[path.extname(filePath).toLowerCase()] || "application/octet-stream";
|
||||
}
|
||||
|
||||
|
||||
function safeJoin(reqPath) {
|
||||
let decoded;
|
||||
try {
|
||||
decoded = decodeURIComponent(reqPath.split("?")[0].split("#")[0]);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
const resolved = path.normalize(path.join(root, decoded));
|
||||
if (resolved !== root && !resolved.startsWith(root + path.sep)) return null;
|
||||
return resolved;
|
||||
}
|
||||
|
||||
function cacheControl(filePath) {
|
||||
const ext = path.extname(filePath).toLowerCase();
|
||||
const relativePath = path.relative(root, filePath);
|
||||
const isBuildAsset =
|
||||
relativePath === "assets" ||
|
||||
relativePath.startsWith(`assets${path.sep}`);
|
||||
|
||||
if (ext === ".html" || ext === "") {
|
||||
return "no-cache";
|
||||
}
|
||||
|
||||
return isBuildAsset
|
||||
? "public, max-age=31536000, immutable"
|
||||
: "no-cache";
|
||||
}
|
||||
|
||||
function streamFile(req, res, filePath, stats) {
|
||||
const headers = {
|
||||
"Content-Type": contentType(filePath),
|
||||
"Content-Length": stats.size,
|
||||
"Last-Modified": stats.mtime.toUTCString(),
|
||||
"Cache-Control": cacheControl(filePath)
|
||||
};
|
||||
if (req.method === "HEAD") {
|
||||
res.writeHead(200, headers);
|
||||
return res.end();
|
||||
}
|
||||
const stream = fs.createReadStream(filePath);
|
||||
stream.on("error", (err) => {
|
||||
console.error("Stream error:", err);
|
||||
if (!res.headersSent) {
|
||||
res.writeHead(500, { "Content-Type": "text/plain" });
|
||||
res.end("Internal Server Error");
|
||||
} else {
|
||||
res.destroy();
|
||||
}
|
||||
});
|
||||
res.on("close", () => {
|
||||
if (!stream.destroyed) stream.destroy();
|
||||
});
|
||||
res.on("error", (err) => {
|
||||
console.error("Response error:", err);
|
||||
if (!stream.destroyed) stream.destroy();
|
||||
});
|
||||
res.writeHead(200, headers);
|
||||
stream.pipe(res);
|
||||
}
|
||||
|
||||
function sendIndex(req, res) {
|
||||
const indexPath = path.join(root, "index.html");
|
||||
fs.stat(indexPath, (err, stats) => {
|
||||
if (err || !stats || !stats.isFile()) {
|
||||
res.writeHead(500, { "Content-Type": "text/plain" });
|
||||
return res.end("index.html not found");
|
||||
}
|
||||
streamFile(req, res, indexPath, stats);
|
||||
});
|
||||
}
|
||||
|
||||
const server = http.createServer((req, res) => {
|
||||
if (req.method === "OPTIONS") {
|
||||
const filePath = safeJoin(req.url || "/");
|
||||
res.writeHead(204);
|
||||
return res.end();
|
||||
}
|
||||
|
||||
if (req.method !== "GET" && req.method !== "HEAD") {
|
||||
res.writeHead(405, { Allow: "GET, HEAD, OPTIONS" });
|
||||
return res.end();
|
||||
}
|
||||
|
||||
const reqUrl = req.url || "/";
|
||||
const filePath = safeJoin(reqUrl);
|
||||
if (!filePath) {
|
||||
res.writeHead(400);
|
||||
return res.end("Bad Request");
|
||||
}
|
||||
|
||||
fs.stat(filePath, (err, stats) => {
|
||||
if (err) {
|
||||
// No file at this path → SPA fallback to index.html
|
||||
return sendIndex(req, res);
|
||||
}
|
||||
if (stats.isFile()) {
|
||||
return streamFile(req, res, filePath, stats);
|
||||
}
|
||||
if (stats.isDirectory()) {
|
||||
const indexInDir = path.join(filePath, "index.html");
|
||||
return fs.stat(indexInDir, (dirErr, dirStats) => {
|
||||
if (!dirErr && dirStats && dirStats.isFile()) {
|
||||
return streamFile(req, res, indexInDir, dirStats);
|
||||
}
|
||||
return sendIndex(req, res);
|
||||
});
|
||||
}
|
||||
// Path exists but is neither file nor directory → SPA fallback
|
||||
return sendIndex(req, res);
|
||||
});
|
||||
});
|
||||
|
||||
server.listen(port, host, () => {
|
||||
console.log(`Serving ${root} on http://${host}:${port}`);
|
||||
});
|
||||
@@ -35,6 +35,8 @@ const UpdateExistUserAdmin = lazyWithRetry(
|
||||
const Preferences = lazyWithRetry(() => import("./pages/Preferences"));
|
||||
const Login = lazyWithRetry(() => import("./pages/Login"));
|
||||
const VerifyDocument = lazyWithRetry(() => import("./pages/VerifyDocument"));
|
||||
const EmailBuilder = lazyWithRetry(() => import("./pages/EmailBuilder"));
|
||||
|
||||
pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/legacy/build/pdf.worker.min.mjs`;
|
||||
const AppLoader = () => {
|
||||
return (
|
||||
@@ -137,6 +139,7 @@ function App() {
|
||||
/>
|
||||
</Route>
|
||||
<Route path="/success" element={<DocSuccessPage />} />
|
||||
<Route path="/emailbuilder" element={<EmailBuilder />} />
|
||||
<Route path="*" element={<PageNotFound />} />
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
|
||||
@@ -218,7 +218,7 @@ const AddUser = (props) => {
|
||||
></i>
|
||||
</div>
|
||||
<div className="text-[12px] ml-2 mb-0 text-[red] select-none">
|
||||
{t("password-generateed")}
|
||||
{t("password-generated")}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
|
||||
@@ -15,27 +15,31 @@ const DatePicker = ({
|
||||
minDate,
|
||||
maxDate,
|
||||
onChange,
|
||||
handleClear
|
||||
handleClear,
|
||||
label,
|
||||
showLabel = true,
|
||||
showClear = true,
|
||||
dateClassName = ""
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const CustomInput = forwardRef(({ value, onClick }, ref) => (
|
||||
<div
|
||||
className="border-gray-400 rounded-[50px] border-[1px] px-3 text-xs py-2 focus:outline-none hover:border-base-content "
|
||||
className="w-full border-gray-400 rounded-[50px] border-[1px] px-3 text-xs py-2 focus:outline-none hover:border-base-content flex items-center justify-between"
|
||||
onClick={onClick}
|
||||
ref={ref}
|
||||
>
|
||||
{value}
|
||||
<i
|
||||
className={`${value ? "ml-[15px]" : "w-20"} fa-light fa-calendar `}
|
||||
></i>
|
||||
<span className={`${dateClassName} truncate`}>{value}</span>
|
||||
<i className={`fa-light fa-calendar`}></i>
|
||||
</div>
|
||||
));
|
||||
CustomInput.displayName = "CustomInput";
|
||||
|
||||
return (
|
||||
<div>
|
||||
<span>{t("default-date")}: </span>
|
||||
<>
|
||||
{showLabel && (
|
||||
<span className="flex-shrink-0">{label || t("default-date")}: </span>
|
||||
)}
|
||||
<ReactDatePicker
|
||||
renderCustomHeader={({ date, changeYear, changeMonth }) => (
|
||||
<div className="flex justify-start md:ml-2">
|
||||
@@ -65,6 +69,7 @@ const DatePicker = ({
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
wrapperClassName="w-full"
|
||||
closeOnScroll={true}
|
||||
selected={getDefaultDate(selectDate?.date, selectDate?.format)}
|
||||
popperPlacement="top-end"
|
||||
@@ -75,13 +80,15 @@ const DatePicker = ({
|
||||
}
|
||||
portalId="root-portal"
|
||||
/>
|
||||
<span
|
||||
onClick={() => handleClear()}
|
||||
className="underline text-blue-500 cursor-pointer ml-2"
|
||||
>
|
||||
{t("clear")}
|
||||
</span>
|
||||
</div>
|
||||
{showClear && handleClear && (
|
||||
<span
|
||||
onClick={() => handleClear()}
|
||||
className="underline text-blue-500 cursor-pointer ml-2"
|
||||
>
|
||||
{t("clear")}
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import DOMPurify from "dompurify";
|
||||
import juice from "juice";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
|
||||
const EmailBodyEditor = ({
|
||||
value,
|
||||
onChange,
|
||||
smallscreen = false,
|
||||
bodyName,
|
||||
isReset = false,
|
||||
isTemplateLoaded
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [inputHtml, setInputHtml] = useState("");
|
||||
const [cleanPreview, setCleanPreview] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
initProcessContent();
|
||||
}, [isTemplateLoaded]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isReset) {
|
||||
initProcessContent(value);
|
||||
}
|
||||
}, [isReset]);
|
||||
|
||||
const initProcessContent = () => {
|
||||
// 1. Sanitize immediately to strip scripts/malicious tags
|
||||
const sanitized = DOMPurify.sanitize(value, {
|
||||
USE_PROFILES: { html: true }, // Ensures basic HTML structure
|
||||
ADD_ATTR: ["target"] // Allow links to open in new tabs
|
||||
});
|
||||
|
||||
// 2. Inline CSS for email compatibility
|
||||
// Note: Juice is fast, but for huge files, you might debounce this
|
||||
try {
|
||||
const inlined = juice(sanitized);
|
||||
setInputHtml(inlined);
|
||||
onChange?.(inlined);
|
||||
setCleanPreview(inlined);
|
||||
} catch (err) {
|
||||
onChange?.(sanitized);
|
||||
setInputHtml(sanitized); // Fallback if juice fails
|
||||
setCleanPreview(sanitized);
|
||||
}
|
||||
};
|
||||
|
||||
const processContent = (value) => {
|
||||
// 1. Sanitize immediately to strip scripts/malicious tags
|
||||
const sanitized = DOMPurify.sanitize(value, {
|
||||
USE_PROFILES: { html: true }, // Ensures basic HTML structure
|
||||
ADD_ATTR: ["target"] // Allow links to open in new tabs
|
||||
});
|
||||
|
||||
// 2. Inline CSS for email compatibility
|
||||
// Note: Juice is fast, but for huge files, you might debounce this
|
||||
try {
|
||||
const inlined = juice(sanitized);
|
||||
setCleanPreview(inlined);
|
||||
onChange?.(inlined);
|
||||
} catch (err) {
|
||||
setCleanPreview(sanitized); // Fallback if juice fails
|
||||
onChange?.(sanitized);
|
||||
}
|
||||
};
|
||||
const handleChange = (e) => {
|
||||
const value = e.target.value;
|
||||
setInputHtml(value);
|
||||
processContent(value);
|
||||
};
|
||||
|
||||
const screen = smallscreen ? "flex-col" : "flex-col md:flex-row ";
|
||||
const template =
|
||||
bodyName === "request"
|
||||
? "#sample/requestemail"
|
||||
: bodyName === "completion"
|
||||
? "#sample/completionemail"
|
||||
: "#";
|
||||
return (
|
||||
<>
|
||||
<p className="text-sm ">
|
||||
<Trans i18nKey={"open-email-builder"}>
|
||||
{"You can create email template using "}
|
||||
<a
|
||||
href={`/emailbuilder${template}`}
|
||||
target="_blank"
|
||||
referrerPolicy="no-referrer"
|
||||
className="op-link op-link-primary font-medium"
|
||||
>
|
||||
email builder
|
||||
</a>
|
||||
{" platform and copy html code."}
|
||||
</Trans>
|
||||
</p>
|
||||
<div className={`flex ${screen} gap-5`}>
|
||||
{/* Editor Pane */}
|
||||
<div className="flex flex-1 flex-col mt-2">
|
||||
<label>{t("paste-html-here")}:</label>
|
||||
<div className="flex-1 mt-1 p-3 text-xs op-textarea op-textarea-bordered min-h-[70vh]">
|
||||
<textarea
|
||||
className="w-full min-h-[70vh] focus:outline-none"
|
||||
value={inputHtml}
|
||||
onChange={(e) => handleChange(e)}
|
||||
placeholder="<html><body><h1>Hello!</h1></body></html>"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Live Preview Pane */}
|
||||
<div className="flex flex-1 flex-col mt-2">
|
||||
<label>{t("preview")}</label>
|
||||
<div className="flex-1 mt-1 bg-white border-[1px] op-textarea border-[#ccc] min-h-[70vh]">
|
||||
{cleanPreview && (
|
||||
<iframe
|
||||
title="Safe Preview"
|
||||
srcDoc={cleanPreview}
|
||||
// SECURITY: sandbox prevents scripts from running even if they slip through
|
||||
sandbox="allow-popups allow-popups-to-escape-sandbox"
|
||||
className="w-full min-h-[70vh]"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default EmailBodyEditor;
|
||||
@@ -159,7 +159,7 @@ const Header = ({ isConsole, setIsLoggingOut }) => {
|
||||
</div>
|
||||
<div id="profile-menu" className="flex-none gap-2">
|
||||
<div>
|
||||
<FullScreenButton />
|
||||
<FullScreenButton />
|
||||
</div>
|
||||
{width >= 768 && (
|
||||
<div
|
||||
|
||||
@@ -4,42 +4,47 @@ import {
|
||||
useMemo
|
||||
} from "react";
|
||||
import axios from "axios";
|
||||
import SuggestionInput from "../shared/fields/SuggestionInput";
|
||||
import Loader from "../../primitives/Loader";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
emailRegex,
|
||||
} from "../../constant/const";
|
||||
import {
|
||||
convertPdfArrayBuffer,
|
||||
getSignedUrl,
|
||||
isBase64,
|
||||
getBase64FromUrl,
|
||||
generateId,
|
||||
} from "../../constant/Utils";
|
||||
import {
|
||||
formatCSVDate,
|
||||
handleEmbedPrefillToDoc,
|
||||
normalizeKey
|
||||
isWidgetResponseCompatible,
|
||||
formatCSVDate,
|
||||
normalizeKey,
|
||||
loadPdfOnce,
|
||||
hasSignatureWidget,
|
||||
} from "../../utils";
|
||||
import RenderWidgets from "./components/RenderWidgets";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { setBulkLoader } from "../../redux/reducers/widgetSlice";
|
||||
import {
|
||||
setBulkLoader,
|
||||
} from "../../redux/reducers/widgetSlice";
|
||||
import Table from "./components/Table";
|
||||
import PrefillWidgets from "./components/PrefillWidgets";
|
||||
import WizardHeader from "./components/WizardHeader";
|
||||
import ResponseTab from "./components/ResponseTab";
|
||||
import { steps } from "../../json/BulkSendSteps";
|
||||
|
||||
const EXCLUDED_PREFILL_TYPES = new Set(["image", "draw"]);
|
||||
const EXCLUDED_WIDGET_TYPES = new Set([
|
||||
"image",
|
||||
"signature",
|
||||
"stamp",
|
||||
"initials"
|
||||
"initials",
|
||||
]);
|
||||
const ALL_EXCLUDED_TYPES = new Set([
|
||||
...EXCLUDED_PREFILL_TYPES,
|
||||
...EXCLUDED_WIDGET_TYPES
|
||||
]);
|
||||
|
||||
const requiredAsteriskCls = (isRequired = false) => {
|
||||
return isRequired ? "after:content-['_*'] after:text-red-500" : "";
|
||||
};
|
||||
const BulkSendUi = (props) => {
|
||||
const { t } = useTranslation();
|
||||
const appName =
|
||||
@@ -54,6 +59,11 @@ const BulkSendUi = (props) => {
|
||||
const [isVacantRoles, setIsVacantRoles] = useState(false);
|
||||
const [fields, setFields] = useState([]);
|
||||
const [headers, setHeaders] = useState([]);
|
||||
const [prefillWidgets, setPrefillWidgets] = useState([]);
|
||||
const [step, setStep] = useState(0);
|
||||
const [stepsList, setStepsList] = useState(steps(t));
|
||||
const [stepKey, setStepKey] = useState(steps(t)?.[0]?.key || "");
|
||||
const [message, setMessage] = useState({ status: "", message: "" });
|
||||
|
||||
const tokenHeader = useMemo(() => {
|
||||
const headers = {
|
||||
@@ -70,10 +80,18 @@ const BulkSendUi = (props) => {
|
||||
const initializeWidgetsWithValues = async () => {
|
||||
if (props?.Placeholders?.length > 0) {
|
||||
const roles = props?.Placeholders?.filter((p) => !p.signerObjId) || [];
|
||||
const emails = [];
|
||||
const reservedEmails = (
|
||||
props?.Placeholders?.filter((p) => !!p?.signerObjId) || []
|
||||
)
|
||||
.map(
|
||||
(role) =>
|
||||
normalizeKey(role?.email) || normalizeKey(role?.signerPtr?.Email)
|
||||
)
|
||||
.filter(Boolean);
|
||||
|
||||
const users = await Promise.all(
|
||||
roles.map(async (role) => {
|
||||
const isPrefill = role?.Role?.toLowerCase() === "prefill";
|
||||
roles?.map(async (role) => {
|
||||
const isPrefill = role.Role?.toLowerCase() === "prefill";
|
||||
const uniqueNames = new Set();
|
||||
const pages = role?.placeHolder ?? [];
|
||||
const widgets = await Promise.all(
|
||||
@@ -85,6 +103,7 @@ const BulkSendUi = (props) => {
|
||||
// exclude certain widget types for non-prefill
|
||||
if (EXCLUDED_WIDGET_TYPES.has(widget.type) && !isPrefill)
|
||||
return false;
|
||||
if (widget?.options?.formula) return false;
|
||||
// unique by widget name
|
||||
if (uniqueNames.has(widgetName)) return false;
|
||||
uniqueNames.add(widgetName);
|
||||
@@ -126,6 +145,7 @@ const BulkSendUi = (props) => {
|
||||
|
||||
return {
|
||||
...widget,
|
||||
options: { ...widget.options, response: response },
|
||||
label: label,
|
||||
response,
|
||||
pageNumber: page.pageNumber
|
||||
@@ -133,10 +153,6 @@ const BulkSendUi = (props) => {
|
||||
})
|
||||
)
|
||||
);
|
||||
const signerEmail =
|
||||
normalizeKey(role?.email) || role?.signerPtr?.Email;
|
||||
if (signerEmail) emails.push(signerEmail);
|
||||
|
||||
return {
|
||||
fieldId: role.Id,
|
||||
email: "",
|
||||
@@ -146,15 +162,26 @@ const BulkSendUi = (props) => {
|
||||
};
|
||||
})
|
||||
);
|
||||
setFields(users);
|
||||
setEmails(emails);
|
||||
const prefills = users.find((r) => r?.label?.toLowerCase() === "prefill");
|
||||
const signers = users.filter(
|
||||
(r) => r?.label?.toLowerCase() !== "prefill"
|
||||
);
|
||||
setFields(signers);
|
||||
if (prefills && prefills?.widgets?.length) {
|
||||
setPrefillWidgets(prefills?.widgets);
|
||||
} else {
|
||||
setStepsList((s) => s.filter((step) => step.key !== "prefill"));
|
||||
setStepKey("recipients");
|
||||
setStep(0);
|
||||
}
|
||||
setEmails(reservedEmails);
|
||||
|
||||
// Build headers with required ordering:
|
||||
// 1) all role emails first
|
||||
// 2) then all widgets
|
||||
const emailHeaders = [];
|
||||
const widgetHeaders = [];
|
||||
for (const role of users) {
|
||||
for (const role of signers) {
|
||||
const isPrefill = role?.label?.toLowerCase() === "prefill";
|
||||
|
||||
if (!isPrefill) {
|
||||
@@ -179,7 +206,7 @@ const BulkSendUi = (props) => {
|
||||
}
|
||||
}
|
||||
setHeaders([...emailHeaders, ...widgetHeaders]);
|
||||
setForms((prev) => [...prev, { Id: 1, fields: users }]);
|
||||
setForms((prev) => [...prev, { Id: generateId(8), fields: signers }]);
|
||||
const signer = props.item?.Signers?.filter((x) => x?.objectId);
|
||||
setSigners(signer);
|
||||
}
|
||||
@@ -193,12 +220,12 @@ const BulkSendUi = (props) => {
|
||||
const checkSignatureAndRoles = (placeholders = []) => {
|
||||
const filtered = placeholders.filter((x) => x?.Role !== "prefill");
|
||||
|
||||
const isSignExist = filtered.every((p) =>
|
||||
p?.placeHolder?.some((h) => h?.pos?.some((p) => p?.type === "signature"))
|
||||
);
|
||||
|
||||
// Viewer/approver roles do not require a signature widget; only
|
||||
// signer-role placeholders must have one.
|
||||
const isSignExist =
|
||||
filtered?.length > 0 && filtered.every((p) => hasSignatureWidget(p));
|
||||
setIsSignatureExist(isSignExist);
|
||||
setIsVacantRoles(placeholders.some((x) => !x.signerObjId));
|
||||
setIsVacantRoles(filtered?.some((x) => !x.signerObjId));
|
||||
};
|
||||
|
||||
//function to check at least one signature field exist
|
||||
@@ -210,41 +237,84 @@ const BulkSendUi = (props) => {
|
||||
};
|
||||
|
||||
const handleInputChange = (formIndex, signer, role) => {
|
||||
const newForms = [...forms];
|
||||
const email = signer?.Email ? signer?.Email : signer || "";
|
||||
const fieldId = newForms[formIndex].fields.findIndex(
|
||||
(f) => f.label === role
|
||||
);
|
||||
newForms[formIndex].fields[fieldId].email = normalizeKey(email);
|
||||
newForms[formIndex].fields[fieldId].signer = signer?.objectId ? signer : "";
|
||||
setForms(newForms);
|
||||
setForms((prev) => {
|
||||
const next = [...prev];
|
||||
const form = next[formIndex];
|
||||
if (!form) return prev;
|
||||
|
||||
const fields = [...(form.fields ?? [])];
|
||||
const fieldId = fields.findIndex((f) => f.label === role);
|
||||
if (fieldId < 0) return prev;
|
||||
|
||||
const email = signer?.Email ? signer.Email : signer || "";
|
||||
|
||||
fields[fieldId] = {
|
||||
...fields[fieldId],
|
||||
email: normalizeKey(email),
|
||||
signer: signer?.objectId ? signer : ""
|
||||
};
|
||||
|
||||
next[formIndex] = { ...form, fields };
|
||||
return next;
|
||||
});
|
||||
};
|
||||
|
||||
const handleWidgetDetails = (
|
||||
value = "",
|
||||
formIndex,
|
||||
RoleIndex = 0,
|
||||
roleIndex = 0,
|
||||
widgetIndex
|
||||
) => {
|
||||
const newForms = [...forms];
|
||||
newForms[formIndex].fields[RoleIndex].widgets[widgetIndex].response = value;
|
||||
setForms(newForms);
|
||||
setForms((prev) => {
|
||||
const next = [...prev];
|
||||
const form = next[formIndex];
|
||||
if (!form) return prev;
|
||||
|
||||
const fields = [...(form.fields ?? [])];
|
||||
const field = fields[roleIndex];
|
||||
if (!field) return prev;
|
||||
|
||||
const widgets = [...(field.widgets ?? [])];
|
||||
const w = widgets[widgetIndex];
|
||||
if (!w) return prev;
|
||||
|
||||
widgets[widgetIndex] = {
|
||||
...w,
|
||||
options: { ...w?.options, response: value },
|
||||
response: value
|
||||
};
|
||||
fields[roleIndex] = { ...field, widgets };
|
||||
next[formIndex] = { ...form, fields };
|
||||
|
||||
return next;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
function validateEmails(data) {
|
||||
for (const item of data) {
|
||||
let email = "";
|
||||
const filterFields = item.fields.filter((p) => p.label !== "prefill");
|
||||
for (const [rowIndex, item] of data.entries()) {
|
||||
const rowEmails = new Set();
|
||||
const rowNumber = rowIndex + 1;
|
||||
const filterFields = item?.fields || [];
|
||||
for (const field of filterFields) {
|
||||
if (!emailRegex.test(field.email)) {
|
||||
alert(t("invalid-email-found", { email: field.email }));
|
||||
// skip prefill
|
||||
if (field?.label === "prefill") continue;
|
||||
const normalizedEmail = normalizeKey(field?.email);
|
||||
const params = { email: field.email, row: rowNumber };
|
||||
if (!normalizedEmail) {
|
||||
alert(t("email-not-found-in-row", { ...params }));
|
||||
return false;
|
||||
} else if (email === field.email || emails?.includes(field.email)) {
|
||||
alert(t("duplicate-email-found", { email: field.email }));
|
||||
} else if (!emailRegex.test(normalizedEmail)) {
|
||||
alert(t("invalid-email-found-in-row", { ...params }));
|
||||
return false;
|
||||
} else if (
|
||||
rowEmails.has(normalizedEmail) ||
|
||||
emails?.includes(normalizedEmail)
|
||||
) {
|
||||
alert(t("duplicate-email-found-in-row", { ...params }));
|
||||
return false;
|
||||
} else {
|
||||
email = field.email;
|
||||
rowEmails.add(normalizedEmail);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -266,7 +336,13 @@ const BulkSendUi = (props) => {
|
||||
const name = widgetDetails?.options?.name;
|
||||
if (!name || !responseByName.has(name)) return widgetDetails;
|
||||
|
||||
const newResponse = responseByName.get(name);
|
||||
const newResponse = responseByName?.get(name) || "";
|
||||
const isValidResponse = isWidgetResponseCompatible(
|
||||
widgetDetails,
|
||||
newResponse
|
||||
);
|
||||
|
||||
if (!isValidResponse) return widgetDetails;
|
||||
|
||||
return {
|
||||
...widgetDetails,
|
||||
@@ -327,6 +403,49 @@ const BulkSendUi = (props) => {
|
||||
if (validateEmails(forms)) {
|
||||
// Create a copy of Placeholders array from props.item
|
||||
let Placeholders = [...props.Placeholders];
|
||||
|
||||
let pdfUrl = props.item?.URL || props.item?.SignedUrl;
|
||||
const prefillExist = Placeholders?.some(
|
||||
(data) => data.Role === "prefill"
|
||||
);
|
||||
if (prefillExist) {
|
||||
const updatedPrefills = updateWidgetValues(
|
||||
prefillWidgets,
|
||||
Placeholders
|
||||
);
|
||||
const prefillDetails = updatedPrefills?.find(
|
||||
(data) => data.Role === "prefill"
|
||||
);
|
||||
const pdfSignedUrl = await getSignedUrl(
|
||||
pdfUrl,
|
||||
"", //docId
|
||||
props.item.objectId, // templateId
|
||||
);
|
||||
const pdfArrayBuffer = await loadPdfOnce(pdfSignedUrl);
|
||||
if (pdfArrayBuffer === "Error") {
|
||||
const error = t("something-went-wrong-mssg");
|
||||
alert(error);
|
||||
dispatch(setBulkLoader(false));
|
||||
return;
|
||||
}
|
||||
pdfUrl = await handleEmbedPrefillToDoc(
|
||||
prefillDetails,
|
||||
1, // scale
|
||||
pdfArrayBuffer,
|
||||
[], // prefillImg,
|
||||
props.item?.ExtUserPtr?.UserId?.objectId // userId
|
||||
);
|
||||
|
||||
if (pdfUrl?.error) {
|
||||
const error = pdfUrl?.error?.includes("not compatible")
|
||||
? t("pdf-uncompatible", { appName: appName })
|
||||
: pdfUrl?.error;
|
||||
alert(error);
|
||||
dispatch(setBulkLoader(false));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize an empty array to store updated documents
|
||||
let Documents = [];
|
||||
let error = "";
|
||||
@@ -371,39 +490,7 @@ const BulkSendUi = (props) => {
|
||||
widgetValues,
|
||||
updatedPlaceholders
|
||||
);
|
||||
const url = props.item?.URL || props.item?.SignedUrl;
|
||||
let signedUrl = await getSignedUrl(
|
||||
url,
|
||||
"", //docId
|
||||
props.item.objectId, // templateId
|
||||
);
|
||||
const prefillField = form?.fields?.find((f) => f.label === "prefill");
|
||||
const prefills = prefillField?.widgets ?? [];
|
||||
if (prefills?.length > 0) {
|
||||
const prefillDetails = updateWidgetPlaceholder?.find(
|
||||
(x) => x.Role === "prefill"
|
||||
);
|
||||
const pdfArrayBuffer = await convertPdfArrayBuffer(signedUrl);
|
||||
if (pdfArrayBuffer === "Error") {
|
||||
error = t("something-went-wrong-mssg");
|
||||
dispatch(setBulkLoader(false));
|
||||
break;
|
||||
}
|
||||
signedUrl = await handleEmbedPrefillToDoc(
|
||||
prefillDetails,
|
||||
1, // scale
|
||||
pdfArrayBuffer,
|
||||
[], // prefillImg,
|
||||
props.item?.ExtUserPtr?.UserId?.objectId // userId
|
||||
);
|
||||
if (signedUrl?.error) {
|
||||
error = signedUrl.error.includes("not compatible")
|
||||
? t("pdf-uncompatible", { appName: appName })
|
||||
: signedUrl?.error;
|
||||
dispatch(setBulkLoader(false));
|
||||
break;
|
||||
}
|
||||
}
|
||||
let signedUrl = pdfUrl;
|
||||
const placeholders = removeWidgetValues(
|
||||
widgetValues,
|
||||
updateWidgetPlaceholder
|
||||
@@ -450,13 +537,20 @@ const BulkSendUi = (props) => {
|
||||
try {
|
||||
const res = await axios.post(functionsUrl, params, { headers: headers });
|
||||
if (res.data && res.data.result) {
|
||||
props.handleClose("success", Documents?.length);
|
||||
setStep((s) => Math.min(2, s + 1));
|
||||
setStepKey("response");
|
||||
setMessage({
|
||||
status: "success",
|
||||
message: "Documents sent successfully."
|
||||
});
|
||||
// props.handleClose("success", Documents?.length);
|
||||
}
|
||||
} catch (err) {
|
||||
const message =
|
||||
err?.response?.data?.error || err?.message || "something went wrong.";
|
||||
console.error("Error sending documents:", message);
|
||||
props.handleClose("error", 0, message);
|
||||
setMessage({ status: "failed", message });
|
||||
// props.handleClose("error", 0, message);
|
||||
} finally {
|
||||
dispatch(setBulkLoader(false));
|
||||
}
|
||||
@@ -473,115 +567,66 @@ const BulkSendUi = (props) => {
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
{/* {isBulkLoader && (
|
||||
<div className="absolute z-[999] h-full w-full flex justify-center items-center bg-black bg-opacity-30">
|
||||
<Loader />
|
||||
</div>
|
||||
)} */}
|
||||
{props.Placeholders?.length > 0 ? (
|
||||
isSignatureExist ? (
|
||||
isVacantRoles ? (
|
||||
<>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="min-h-max max-h-[250px] overflow-auto">
|
||||
{
|
||||
forms?.length > 0 && (
|
||||
<div className="mx-4">
|
||||
<table className="op-table border-collapse w-full">
|
||||
<thead className="text-[13px] text-center">
|
||||
<tr className="border-y-[1px]">
|
||||
{/* Roles + widgets */}
|
||||
{headers?.map((header) => (
|
||||
<th
|
||||
key={header.label}
|
||||
className={`${requiredAsteriskCls(header?.isRequired)} p-2`}
|
||||
>
|
||||
{header.label}
|
||||
</th>
|
||||
))}
|
||||
{forms?.length > 1 && (
|
||||
<th className="p-2">{t("action")}</th>
|
||||
)}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="text-[12px] text-base-content">
|
||||
{forms.map((form, formIndex) => {
|
||||
const fields = form?.fields ?? [];
|
||||
const emailFields = fields.filter(
|
||||
(f) => f.label !== "prefill"
|
||||
);
|
||||
const widgets = fields.flatMap(
|
||||
(f, fieldIndex) =>
|
||||
(f.widgets ?? []).map(
|
||||
(widget, widgetIndex) => ({
|
||||
widget,
|
||||
fieldIndex, // keep the fieldIndex that produced this widget
|
||||
widgetIndex
|
||||
})
|
||||
)
|
||||
);
|
||||
<div className="border-t mt-3" />
|
||||
{/* Wizard header */}
|
||||
<div className="px-6 py-3">
|
||||
<WizardHeader
|
||||
steps={stepsList}
|
||||
step={step}
|
||||
// onStepClick={(i) => i <= step && setStep(i)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
return (
|
||||
<tr key={form.Id}>
|
||||
{/* Email cell (label + SuggestionInput) */}
|
||||
{emailFields?.map((field, fieldIndex) => (
|
||||
<td key={field.fieldId} className="p-2">
|
||||
<div className="flex flex-col min-w-max">
|
||||
<SuggestionInput
|
||||
required
|
||||
type="email"
|
||||
value={field.email ?? ""}
|
||||
index={fieldIndex}
|
||||
onChange={(signer) =>
|
||||
handleInputChange(
|
||||
formIndex,
|
||||
signer,
|
||||
field.label
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
))}
|
||||
|
||||
{widgets.map(
|
||||
({ widget, fieldIndex, widgetIndex }) => (
|
||||
<td key={widget.key} className="p-2">
|
||||
<RenderWidgets
|
||||
widget={widget}
|
||||
formIndex={formIndex}
|
||||
prefillIndex={widgetIndex}
|
||||
handleWidgetDetails={(value) =>
|
||||
handleWidgetDetails(
|
||||
value,
|
||||
formIndex,
|
||||
fieldIndex,
|
||||
widgetIndex
|
||||
)
|
||||
}
|
||||
/>
|
||||
</td>
|
||||
)
|
||||
)}
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<div className="flex flex-row flex-wrap pb-3 pt-2 px-3 gap-3 justify-center">
|
||||
<button
|
||||
type="submit"
|
||||
className="op-btn op-btn-accent w-[150px] focus:outline-none"
|
||||
>
|
||||
<i className="fa-light fa-paper-plane"></i>
|
||||
<span>{t("send")}</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{stepKey === "prefill" && (
|
||||
<PrefillWidgets
|
||||
prefills={prefillWidgets}
|
||||
setPrefills={setPrefillWidgets}
|
||||
onNext={() => {
|
||||
setStep((s) => Math.min(2, s + 1));
|
||||
setStepKey("recipients");
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{stepKey === "recipients" && (
|
||||
<>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="min-h-max max-h-[250px] overflow-auto">
|
||||
{
|
||||
forms?.length > 0 && (
|
||||
<div className="mx-4">
|
||||
<Table
|
||||
headers={headers}
|
||||
rowData={forms}
|
||||
handleInputChange={handleInputChange}
|
||||
handleWidgetDetails={handleWidgetDetails}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<div className="flex flex-row flex-wrap pb-3 pt-2 px-3 gap-3 justify-center">
|
||||
<button
|
||||
type="submit"
|
||||
className="op-btn op-btn-accent w-[150px] focus:outline-none"
|
||||
>
|
||||
<i className="fa-light fa-paper-plane"></i>
|
||||
<span>{t("send")}</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</>
|
||||
)}
|
||||
{stepKey === "response" && (
|
||||
<ResponseTab
|
||||
prefillCount={prefillWidgets?.length}
|
||||
documentCount={forms?.length}
|
||||
message={message}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<div className="text-black p-3 bg-white w-full text-sm md:text-base flex justify-center items-center">
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
import RenderWidgets from "./RenderWidgets";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const PrefillWidgets = ({ prefills = [], setPrefills, onNext }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleWidgetDetails = (value, widgetLabel) => {
|
||||
setPrefills((prev) => {
|
||||
const widgets = [...(prev ?? [])];
|
||||
const index = widgets.findIndex((w) => w?.label === widgetLabel);
|
||||
if (index === -1) return prev;
|
||||
widgets[index] = {
|
||||
...widgets[index],
|
||||
options: { ...widgets[index].options, response: value },
|
||||
response: value
|
||||
};
|
||||
return widgets;
|
||||
});
|
||||
};
|
||||
|
||||
const handleNext = (e) => {
|
||||
e?.preventDefault();
|
||||
e?.stopPropagation();
|
||||
onNext?.();
|
||||
};
|
||||
return (
|
||||
<>
|
||||
{prefills?.length > 0 && (
|
||||
<form
|
||||
onSubmit={handleNext}
|
||||
className="m-3 md:m-6 text-base-content flex flex-col relative"
|
||||
>
|
||||
<div className="py-3 px-[10px] op-card border-[1px] border-gray-400">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-10 gap-y-4 w-full">
|
||||
{[...prefills]
|
||||
.sort((a, b) =>
|
||||
a.pageNumber !== b.pageNumber
|
||||
? a.pageNumber - b.pageNumber
|
||||
: (a.yPosition ?? 0) - (b.yPosition ?? 0)
|
||||
)
|
||||
.map((widget) => (
|
||||
<RenderWidgets
|
||||
key={widget.key}
|
||||
showLabel
|
||||
widget={widget}
|
||||
handleWidgetDetails={(value) =>
|
||||
handleWidgetDetails(value, widget.label)
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row flex-wrap mt-3 gap-3 justify-center">
|
||||
<button
|
||||
type="submit"
|
||||
className="op-btn op-btn-primary w-[150px] focus:outline-none"
|
||||
>
|
||||
{t("next")}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default PrefillWidgets;
|
||||
@@ -1,4 +1,4 @@
|
||||
import { forwardRef, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { forwardRef, memo, useEffect, useMemo, useRef, useState } from "react";
|
||||
import DatePicker from "react-datepicker";
|
||||
import SignatureCanvas from "react-signature-canvas";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -6,6 +6,7 @@ import moment from "moment";
|
||||
import {
|
||||
changeDateToMomentFormat,
|
||||
compressedFileSize,
|
||||
generateId,
|
||||
getMonth,
|
||||
getYear,
|
||||
months,
|
||||
@@ -16,7 +17,6 @@ import {
|
||||
} from "../../../constant/Utils";
|
||||
import PenColorComponent from "../../pdf/tab/PenColorComponent";
|
||||
import { getDatePickerDate, toHtmlPattern } from "../../../utils";
|
||||
import CellsInput from "../../shared/fields/CellsInput";
|
||||
import { emailRegex } from "../../../constant/const";
|
||||
|
||||
const inputOpt = new Set(["text", "email", "number"]);
|
||||
@@ -32,7 +32,12 @@ const inputValidation = (pattern, type) => {
|
||||
return inputTypes[type] || "";
|
||||
};
|
||||
const canavasTheme = `opensigncss:bg-white opensigndark:bg-[#121212] opensigndark:border-[#f6f3f4]/20 opensigncss:border-gray-300 opensigndark:hover:border-white opensigncss:hover:border-black border-[1px]`;
|
||||
const DateWidget = ({ widget, isRequired, onChange }) => {
|
||||
|
||||
const widgetLabelCss = (isRequired = false) => {
|
||||
return `${isRequired ? "after:content-['_*'] after:text-red-500" : ""} block text-xs font-semibold`;
|
||||
};
|
||||
|
||||
const DateWidget = ({ widget, isRequired, onChange, showLabel }) => {
|
||||
const format = widget?.options?.validation?.format || "MM/dd/yyyy";
|
||||
|
||||
const PrefillDateInput = forwardRef(({ value, onClick }, ref) => (
|
||||
@@ -78,6 +83,11 @@ const DateWidget = ({ widget, isRequired, onChange }) => {
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
{showLabel && (
|
||||
<div className={`${widgetLabelCss(isRequired)} mb-[0.5rem]`}>
|
||||
{widget?.options?.name}
|
||||
</div>
|
||||
)}
|
||||
<div className="min-w-max">
|
||||
<DatePicker
|
||||
portalId="datepicker-portal-root"
|
||||
@@ -110,6 +120,7 @@ const DateWidget = ({ widget, isRequired, onChange }) => {
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
wrapperClassName="w-full"
|
||||
closeOnScroll={true}
|
||||
selected={handleDate(widget)}
|
||||
onChange={(date) => handleOnDateChange(date, widget)}
|
||||
@@ -121,21 +132,24 @@ const DateWidget = ({ widget, isRequired, onChange }) => {
|
||||
</div>
|
||||
);
|
||||
};
|
||||
const TextWidget = ({ widget, isRequired, onChange }) => {
|
||||
const TextWidget = ({
|
||||
widget,
|
||||
isRequired,
|
||||
onChange,
|
||||
showLabel,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [value, setValue] = useState([]);
|
||||
const inputType = widget?.options?.validation?.type || "";
|
||||
const serverRegex = widget?.options?.validation?.pattern;
|
||||
const isPredfineType = inputType ? inputOpt?.has(inputType) : "";
|
||||
const regExpression = inputValidation(serverRegex, inputType);
|
||||
const pattern = useMemo(() => toHtmlPattern(regExpression), [regExpression]);
|
||||
|
||||
useEffect(() => {
|
||||
const [value, setValue] = useState(() => {
|
||||
const response =
|
||||
widget?.options?.response ?? widget?.options?.defaultValue ?? "";
|
||||
const isNumber = inputType === "number" && response;
|
||||
setValue(isNumber ? Number(response) : response);
|
||||
}, []);
|
||||
return inputType === "number" && response ? Number(response) : response;
|
||||
});
|
||||
|
||||
const handleInputChange = (e) => {
|
||||
const text = e.target.value || "";
|
||||
setValue(text);
|
||||
@@ -143,6 +157,11 @@ const TextWidget = ({ widget, isRequired, onChange }) => {
|
||||
};
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
{showLabel && (
|
||||
<div className={`${widgetLabelCss(isRequired)} mb-[0.5rem]`}>
|
||||
{widget?.options?.name}
|
||||
</div>
|
||||
)}
|
||||
<div className="min-w-max">
|
||||
<input
|
||||
type={isPredfineType ? inputType : "text"}
|
||||
@@ -152,7 +171,9 @@ const TextWidget = ({ widget, isRequired, onChange }) => {
|
||||
widget?.options?.hint ||
|
||||
t("enter-value", { value: widget?.options?.name })
|
||||
}
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
className={[
|
||||
"op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs",
|
||||
].join(" ")}
|
||||
onChange={(e) => handleInputChange(e)}
|
||||
pattern={pattern || undefined} // if no pattern, browser won't do pattern validation
|
||||
onInvalid={(e) => {
|
||||
@@ -173,17 +194,19 @@ const TextWidget = ({ widget, isRequired, onChange }) => {
|
||||
</div>
|
||||
);
|
||||
};
|
||||
const CheckboxWidget = ({ widget, isRequired, onChange }) => {
|
||||
const CheckboxWidget = ({ widget, isRequired, onChange, showLabel }) => {
|
||||
const { t } = useTranslation();
|
||||
const [selectedCheckbox, setSelectedCheckbox] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedCheckbox(
|
||||
widget?.options?.response ?? widget?.options?.defaultValue ?? []
|
||||
);
|
||||
}, []);
|
||||
const [selectedCheckbox, setSelectedCheckbox] = useState(
|
||||
() => widget?.options?.response || widget?.options?.defaultValue || []
|
||||
);
|
||||
const groupName = `checkbox-group-${widget.key}`;
|
||||
const noneSelected = (selectedCheckbox?.length ?? 0) === 0;
|
||||
|
||||
const handleCheckboxValue = (isChecked, ind) => {
|
||||
// ✅ clear hidden input error immediately
|
||||
const hidden = document.querySelector(`input[name="${groupName}"]`);
|
||||
hidden?.setCustomValidity("");
|
||||
|
||||
const prevArr = Array.isArray(selectedCheckbox) ? selectedCheckbox : [];
|
||||
|
||||
const updateResponse = isChecked
|
||||
@@ -195,10 +218,15 @@ const CheckboxWidget = ({ widget, isRequired, onChange }) => {
|
||||
setSelectedCheckbox(updateResponse);
|
||||
onChange(updateResponse);
|
||||
};
|
||||
const noneSelected = selectedCheckbox?.length === 0;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-col gap-y-1 min-w-max">
|
||||
{showLabel && (
|
||||
<div className={`${widgetLabelCss(isRequired)} mb-[0.5rem]`}>
|
||||
{widget?.options?.name}
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-col gap-y-1 min-w-max relative">
|
||||
{widget.options?.values?.map((value, ind) => (
|
||||
<div
|
||||
key={ind}
|
||||
@@ -209,14 +237,7 @@ const CheckboxWidget = ({ widget, isRequired, onChange }) => {
|
||||
className="mt-[2px] op-checkbox op-checkbox-xs"
|
||||
type="checkbox"
|
||||
checked={selectedCheckbox.includes(ind)}
|
||||
required={isRequired && noneSelected && ind === 0} // ✅ key line
|
||||
onInvalid={(e) =>
|
||||
e.target.setCustomValidity(t("select-at-least-option"))
|
||||
}
|
||||
onChange={(e) => {
|
||||
e.target.setCustomValidity(""); // ✅ clear message as soon as user changes
|
||||
handleCheckboxValue(e.target.checked, ind);
|
||||
}}
|
||||
onChange={(e) => handleCheckboxValue(e.target.checked, ind)}
|
||||
/>
|
||||
<label
|
||||
htmlFor={`checkbox-${widget.key + ind}`}
|
||||
@@ -226,27 +247,47 @@ const CheckboxWidget = ({ widget, isRequired, onChange }) => {
|
||||
</label>
|
||||
</div>
|
||||
))}
|
||||
{/* ✅ the validator */}
|
||||
{isRequired && (
|
||||
<input
|
||||
tabIndex={-1}
|
||||
aria-hidden="true"
|
||||
className="absolute opacity-0 pointer-events-none w-0.5 h-0.5 left-[0.46rem] top-4"
|
||||
name={groupName}
|
||||
value={noneSelected ? "" : "selected"} // empty => invalid, non-empty => valid
|
||||
required
|
||||
onInvalid={(e) =>
|
||||
e.target.setCustomValidity(t("select-at-least-option"))
|
||||
}
|
||||
onChange={(e) => e.target.setCustomValidity("")}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
const DropdownWidget = ({ widget, isRequired, onChange }) => {
|
||||
const DropdownWidget = ({ widget, isRequired, onChange, showLabel }) => {
|
||||
const { t } = useTranslation();
|
||||
const [selected, setSelected] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
setSelected(
|
||||
widget?.options?.response ?? widget?.options?.defaultValue ?? ""
|
||||
);
|
||||
}, []);
|
||||
const [selected, setSelected] = useState(
|
||||
() =>
|
||||
widget?.options?.response?.trim() ||
|
||||
widget?.options?.defaultValue?.trim() ||
|
||||
""
|
||||
);
|
||||
|
||||
const handleDropdownChange = (value) => {
|
||||
setSelected(value);
|
||||
onChange(value);
|
||||
const select = value?.trim();
|
||||
setSelected(select);
|
||||
onChange(select);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
{showLabel && (
|
||||
<div className={`${widgetLabelCss(isRequired)} mb-[0.5rem]`}>
|
||||
{widget?.options?.name}
|
||||
</div>
|
||||
)}
|
||||
<div className="min-w-max">
|
||||
<select
|
||||
className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-base-content w-full"
|
||||
@@ -260,7 +301,7 @@ const DropdownWidget = ({ widget, isRequired, onChange }) => {
|
||||
{t("choose-one")}
|
||||
</option>
|
||||
{widget?.options?.values?.map((option, ind) => (
|
||||
<option key={ind} value={option}>
|
||||
<option key={ind} value={option?.trim()}>
|
||||
{option}
|
||||
</option>
|
||||
))}
|
||||
@@ -269,22 +310,28 @@ const DropdownWidget = ({ widget, isRequired, onChange }) => {
|
||||
</div>
|
||||
);
|
||||
};
|
||||
const RadioButtonWidget = ({ widget, isRequired, onChange, formIndex }) => {
|
||||
const [selected, setSelected] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
setSelected(
|
||||
widget?.options?.response || widget?.options?.defaultValue || ""
|
||||
);
|
||||
}, []);
|
||||
const RadioButtonWidget = ({ widget, isRequired, onChange, showLabel }) => {
|
||||
const [selected, setSelected] = useState(
|
||||
() =>
|
||||
widget?.options?.response?.trim() ||
|
||||
widget?.options?.defaultValue?.trim() ||
|
||||
""
|
||||
);
|
||||
const id = generateId(4);
|
||||
|
||||
const handleRadioChange = (value) => {
|
||||
setSelected(value);
|
||||
onChange(value);
|
||||
const select = value?.trim();
|
||||
setSelected(select);
|
||||
onChange(select);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
{showLabel && (
|
||||
<div className={`${widgetLabelCss(isRequired)} mb-[0.5rem]`}>
|
||||
{widget?.options?.name}
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-col gap-y-1 min-w-max">
|
||||
{widget.options?.values?.map((data, ind) => (
|
||||
<div
|
||||
@@ -294,10 +341,10 @@ const RadioButtonWidget = ({ widget, isRequired, onChange, formIndex }) => {
|
||||
<input
|
||||
id={`radio-${widget.key + ind}`}
|
||||
className="mt-[2px] op-radio op-radio-xs"
|
||||
name={`radio-group-${widget.key}-${formIndex}`} // ✅ same name => browser treats all radios as ONE group
|
||||
name={`radio-group-${widget.key}-${id}`} // ✅ same name => browser treats all radios as ONE group
|
||||
type="radio"
|
||||
required={isRequired && ind === 0} // ✅ set required ONCE (on first radio) to make the whole group mandatory
|
||||
checked={selected === data}
|
||||
checked={selected === data?.trim()}
|
||||
onChange={() => handleRadioChange(data)}
|
||||
/>
|
||||
<label
|
||||
@@ -312,7 +359,7 @@ const RadioButtonWidget = ({ widget, isRequired, onChange, formIndex }) => {
|
||||
</div>
|
||||
);
|
||||
};
|
||||
const ImageWidget = ({ widget, isRequired, onChange }) => {
|
||||
const ImageWidget = ({ widget, isRequired, onChange, showLabel }) => {
|
||||
const { t } = useTranslation();
|
||||
const imageRef = useRef(null);
|
||||
const [img, setImg] = useState({ src: "", imgType: "" });
|
||||
@@ -341,26 +388,23 @@ const ImageWidget = ({ widget, isRequired, onChange }) => {
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
{showLabel && (
|
||||
<div className={`${widgetLabelCss(isRequired)} mb-[0.5rem]`}>
|
||||
{widget?.options?.name}
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-col prefillCanvas">
|
||||
{defaultImg?.src || img?.src ? (
|
||||
<>
|
||||
<div
|
||||
className={`cursor-pointer rounded-box ${canavasTheme} flex flex-col w-full h-full justify-center items-center`}
|
||||
>
|
||||
<img
|
||||
alt={`image_${widget?.options?.name}`}
|
||||
src={img.src || defaultImg?.src}
|
||||
draggable="false"
|
||||
className="object-contain h-full w-full aspect-[5/2]"
|
||||
/>
|
||||
</div>
|
||||
<span
|
||||
onClick={handleClearImage}
|
||||
className="flex justify-start text-blue-500 underline cursor-pointer ml-1"
|
||||
>
|
||||
{t("clear")}
|
||||
</span>
|
||||
</>
|
||||
<div
|
||||
className={`cursor-pointer rounded-box ${canavasTheme} flex flex-col w-full h-full justify-center items-center`}
|
||||
>
|
||||
<img
|
||||
alt={`image_${widget?.options?.name}`}
|
||||
src={img.src || defaultImg?.src}
|
||||
draggable="false"
|
||||
className="object-contain h-full w-full aspect-[5/2]"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
className={`cursor-pointer rounded-box ${canavasTheme} overflow-hidden w-full h-full aspect-[5/2] flex flex-col justify-center items-center`}
|
||||
@@ -379,10 +423,18 @@ const ImageWidget = ({ widget, isRequired, onChange }) => {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{(defaultImg?.src || img?.src) && (
|
||||
<span
|
||||
onClick={handleClearImage}
|
||||
className="flex justify-start text-blue-500 underline cursor-pointer ml-1"
|
||||
>
|
||||
{t("clear")}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
const DrawWidget = ({ widget, isRequired, onChange }) => {
|
||||
const DrawWidget = ({ widget, isRequired, onChange, showLabel }) => {
|
||||
const { t } = useTranslation();
|
||||
const canvasRef = useRef(null);
|
||||
const sigRequiredRef = useRef(null);
|
||||
@@ -419,7 +471,12 @@ const DrawWidget = ({ widget, isRequired, onChange }) => {
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<div className="relative">
|
||||
{showLabel && (
|
||||
<div className={`${widgetLabelCss(isRequired)} mb-[0.5rem]`}>
|
||||
{widget?.options?.name}
|
||||
</div>
|
||||
)}
|
||||
<div className="relative w-fit">
|
||||
{image ? (
|
||||
<div className={`prefillCanvas ${canavasTheme} rounded-[10px]`}>
|
||||
<img
|
||||
@@ -469,48 +526,42 @@ const DrawWidget = ({ widget, isRequired, onChange }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const CellsWidget = ({ widget, isRequired, onChange }) => {
|
||||
const CellsWidget = ({ widget, isRequired, onChange, showLabel }) => {
|
||||
const { t } = useTranslation();
|
||||
const count = widget?.options?.cellCount || 1;
|
||||
const [word, setWord] = useState("");
|
||||
const inputType = widget?.options?.validation?.type || "";
|
||||
const serverRegex = widget?.options?.validation?.pattern;
|
||||
const hint = widget?.options?.hint;
|
||||
const regExpression = inputValidation(serverRegex, inputType);
|
||||
const pattern = useMemo(() => toHtmlPattern(regExpression), [regExpression]);
|
||||
|
||||
useEffect(() => {
|
||||
const response =
|
||||
widget?.options?.response ?? widget?.options?.defaultValue ?? "";
|
||||
const isNumber = inputType === "number" && response;
|
||||
setWord(isNumber ? Number(response) : response);
|
||||
setWord(String(response || ""));
|
||||
}, []);
|
||||
|
||||
const handleChange = (words) => {
|
||||
const value = words;
|
||||
const handleChange = (e) => {
|
||||
const value = e.target.value;
|
||||
setWord(value);
|
||||
onChange(value);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
{showLabel && (
|
||||
<div className={`${widgetLabelCss(isRequired)} mb-[0.5rem]`}>
|
||||
{widget?.options?.name}
|
||||
</div>
|
||||
)}
|
||||
<div className="relative">
|
||||
<CellsInput
|
||||
hint={
|
||||
widget?.options?.hint ||
|
||||
t("enter-value", { value: widget?.options?.name })
|
||||
}
|
||||
count={count}
|
||||
value={word}
|
||||
onChange={handleChange}
|
||||
only="all"
|
||||
autoFocus
|
||||
/>
|
||||
<input
|
||||
id={`widget-${widget?.options?.name}-${widget.key}`}
|
||||
value={word}
|
||||
required={isRequired}
|
||||
tabIndex={-1}
|
||||
onChange={() => {}}
|
||||
type="text"
|
||||
placeholder={
|
||||
hint || t("enter-value", { value: widget?.options?.name })
|
||||
}
|
||||
value={word ?? ""}
|
||||
onChange={(e) => handleChange(e)}
|
||||
pattern={pattern || undefined} // if no pattern, browser won't do pattern validation
|
||||
onInvalid={(e) => {
|
||||
const el = e.currentTarget;
|
||||
@@ -524,7 +575,8 @@ const CellsWidget = ({ widget, isRequired, onChange }) => {
|
||||
onInput={(e) => {
|
||||
e.currentTarget.setCustomValidity("");
|
||||
}}
|
||||
className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 opacity-0 w-[1px] h-[1px] pointer-events-none"
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none text-base-content hover:border-base-content w-full text-xs"
|
||||
maxLength={count}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -533,25 +585,21 @@ const CellsWidget = ({ widget, isRequired, onChange }) => {
|
||||
|
||||
const RenderWidgets = ({
|
||||
widget,
|
||||
formIndex,
|
||||
prefillIndex,
|
||||
handleWidgetDetails
|
||||
handleWidgetDetails,
|
||||
showLabel = false,
|
||||
}) => {
|
||||
const label = widget?.label?.toLowerCase() ?? "";
|
||||
const isPrefill = label.includes(PREFILL_PREFIX);
|
||||
const commonProps = {
|
||||
widget,
|
||||
isRequired: widget.options?.status === "required" && isPrefill,
|
||||
formIndex
|
||||
};
|
||||
|
||||
const onChange = (value) => {
|
||||
handleWidgetDetails(value, formIndex, prefillIndex);
|
||||
showLabel: showLabel,
|
||||
onChange: handleWidgetDetails,
|
||||
};
|
||||
|
||||
switch (widget.type) {
|
||||
case "date":
|
||||
return <DateWidget {...commonProps} onChange={onChange} />;
|
||||
return <DateWidget {...commonProps} />;
|
||||
case textWidget:
|
||||
case textInputWidget:
|
||||
case "name":
|
||||
@@ -559,24 +607,24 @@ const RenderWidgets = ({
|
||||
case "company":
|
||||
case "job title":
|
||||
case "number":
|
||||
return <TextWidget {...commonProps} onChange={onChange} />;
|
||||
return <TextWidget {...commonProps} />;
|
||||
case "checkbox":
|
||||
return <CheckboxWidget {...commonProps} onChange={onChange} />;
|
||||
return <CheckboxWidget {...commonProps} />;
|
||||
case radioButtonWidget:
|
||||
return <RadioButtonWidget {...commonProps} onChange={onChange} />;
|
||||
return <RadioButtonWidget {...commonProps} />;
|
||||
case "dropdown":
|
||||
return <DropdownWidget {...commonProps} onChange={onChange} />;
|
||||
return <DropdownWidget {...commonProps} />;
|
||||
case "image":
|
||||
case "stamp":
|
||||
return <ImageWidget {...commonProps} onChange={onChange} />;
|
||||
return <ImageWidget {...commonProps} />;
|
||||
case "initials":
|
||||
case "draw":
|
||||
return <DrawWidget {...commonProps} onChange={onChange} />;
|
||||
return <DrawWidget {...commonProps} />;
|
||||
case "cells":
|
||||
return <CellsWidget {...commonProps} onChange={onChange} />;
|
||||
return <CellsWidget {...commonProps} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export default RenderWidgets;
|
||||
export default memo(RenderWidgets);
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
function ResponseTab({
|
||||
prefillCount = 0,
|
||||
documentCount = 0,
|
||||
message = { status: "", message: "" }
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const { status, message: msg } = message;
|
||||
return (
|
||||
<div className="rounded-box border p-4 m-3 md:m-6 ">
|
||||
<div className="font-medium">{t("summary")}</div>
|
||||
<ul className="mt-2 list-disc pl-5 text-base-content/80">
|
||||
<li>
|
||||
{t("prefill-fields")}: {prefillCount}
|
||||
</li>
|
||||
<li>
|
||||
{t("documents")}: {documentCount}
|
||||
</li>
|
||||
<li>
|
||||
{t("status")}: {status}
|
||||
</li>
|
||||
{msg && (
|
||||
<li>
|
||||
{t("message")}: {msg}
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ResponseTab;
|
||||
@@ -0,0 +1,104 @@
|
||||
import {
|
||||
useState
|
||||
} from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import SuggestionInput from "../../shared/fields/SuggestionInput";
|
||||
import RenderWidgets from "./RenderWidgets";
|
||||
|
||||
const requiredAsteriskCls = (isRequired = false) => {
|
||||
return isRequired ? "after:content-['_*'] after:text-red-500" : "";
|
||||
};
|
||||
const Table = ({
|
||||
headers = [],
|
||||
rowData = [],
|
||||
handleInputChange,
|
||||
handleWidgetDetails,
|
||||
initialPerPage = 25,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [itemsPerPage, setItemsPerPage] = useState(initialPerPage);
|
||||
const startIndex = (currentPage - 1) * itemsPerPage;
|
||||
const endIndex = startIndex + itemsPerPage;
|
||||
const currentData = rowData.slice(startIndex, endIndex);
|
||||
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
|
||||
{/* Table */}
|
||||
<div
|
||||
className="overflow-x-auto border border-gray-300 rounded-lg"
|
||||
>
|
||||
<table className="op-table border-collapse w-full">
|
||||
<thead className="text-[13px] text-center">
|
||||
<tr className="border-y-[1px]">
|
||||
{headers?.map((header) => (
|
||||
<th
|
||||
key={header.label}
|
||||
className={`${requiredAsteriskCls(header?.isRequired)} p-2`}
|
||||
>
|
||||
{header.label}
|
||||
</th>
|
||||
))}
|
||||
{rowData.length > 1 && <th className="p-2">{t("action")}</th>}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{currentData.length > 0 &&
|
||||
currentData.map((form, formIndex) => {
|
||||
const globalIndex = startIndex + formIndex;
|
||||
const fields = form?.fields ?? [];
|
||||
|
||||
const emailFields = fields.filter((f) => f.label !== "prefill");
|
||||
|
||||
const widgets = fields.flatMap((f, fieldIndex) =>
|
||||
(f.widgets ?? []).map((widget, widgetIndex) => ({
|
||||
widget,
|
||||
fieldIndex, // keep the fieldIndex that produced this widget
|
||||
widgetIndex
|
||||
}))
|
||||
);
|
||||
return (
|
||||
<tr key={form.Id} id={`table-row-${formIndex}`}>
|
||||
{emailFields.map((field, fieldIndex) => (
|
||||
<td key={field.fieldId} className="p-2">
|
||||
<SuggestionInput
|
||||
required
|
||||
type="email"
|
||||
value={field.email ?? ""}
|
||||
index={fieldIndex}
|
||||
onChange={(signer) =>
|
||||
handleInputChange(globalIndex, signer, field.label)
|
||||
}
|
||||
/>
|
||||
</td>
|
||||
))}
|
||||
|
||||
{widgets.map(({ widget, fieldIndex, widgetIndex }) => (
|
||||
<td key={`${form.Id}-${widget.key}`} className="p-2">
|
||||
<RenderWidgets
|
||||
widget={widget}
|
||||
handleWidgetDetails={(value) =>
|
||||
handleWidgetDetails(
|
||||
value,
|
||||
globalIndex, // formIndex,
|
||||
fieldIndex,
|
||||
widgetIndex
|
||||
)
|
||||
}
|
||||
/>
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Table;
|
||||
@@ -0,0 +1,74 @@
|
||||
/* ---------------- Wizard Header ---------------- */
|
||||
|
||||
import React from "react";
|
||||
|
||||
function WizardHeader({ steps, step, onStepClick }) {
|
||||
return (
|
||||
<div className="flex flex-col md:flex-row flex-wrap items-center justify-center gap-0 md:gap-2">
|
||||
{steps.map((s, i) => {
|
||||
const isActive = i === step;
|
||||
const isDone = i < step;
|
||||
|
||||
return (
|
||||
<React.Fragment key={s.key}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onStepClick?.(i)}
|
||||
className={[
|
||||
"inline-flex items-center gap-2 rounded-md px-3 py-2 text-xs lg:text-sm font-medium border focus:outline-none",
|
||||
isActive
|
||||
? "op-bg-primary text-primary-content border-primary"
|
||||
: isDone
|
||||
? "bg-base-100 text-base-content border-base-300 hover:bg-base-200"
|
||||
: "bg-base-100 text-base-content/70 border-base-300"
|
||||
].join(" ")}
|
||||
title={s.label}
|
||||
>
|
||||
<span
|
||||
className={[
|
||||
"inline-flex h-5 w-5 items-center justify-center rounded-full text-xs",
|
||||
isActive
|
||||
? "op-bg-primary-content/15 text-primary-content"
|
||||
: isDone
|
||||
? "bg-base-200 text-base-content"
|
||||
: "bg-base-200 text-base-content/70"
|
||||
].join(" ")}
|
||||
>
|
||||
{i + 1}
|
||||
</span>
|
||||
|
||||
<span className="whitespace-nowrap">{s.label}</span>
|
||||
|
||||
{/* info icon like screenshot */}
|
||||
<a data-tooltip-id={`${s.key}-tooltip`} className="ml-1">
|
||||
<span
|
||||
className={[
|
||||
"inline-flex h-[1.10rem] w-[1.10rem] items-center justify-center border-[1px] rounded-full",
|
||||
!isActive
|
||||
? "border-[#33bbff] text-[#33bbff]"
|
||||
: "border border-base-300 text-base-300"
|
||||
].join(" ")}
|
||||
aria-hidden="true"
|
||||
>
|
||||
<i className="fa-light fa-question rounded-full text-[13px]"></i>
|
||||
</span>
|
||||
</a>
|
||||
<span className="text-left">{s.help}</span>
|
||||
</button>
|
||||
|
||||
{i < steps.length - 1 && (
|
||||
<span
|
||||
className="mx-1 select-none text-base-content/40 text-center text-2xl md:mb-2 rotate-90 md:rotate-0"
|
||||
aria-hidden="true"
|
||||
>
|
||||
›
|
||||
</span>
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default WizardHeader;
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Box, Typography } from '@mui/material';
|
||||
|
||||
import { TEditorBlock } from '../../../documents/editor/core';
|
||||
import { setDocument, useDocument, useSelectedBlockId } from '../../../documents/editor/EditorContext';
|
||||
|
||||
import AvatarSidebarPanel from './input-panels/AvatarSidebarPanel';
|
||||
import ButtonSidebarPanel from './input-panels/ButtonSidebarPanel';
|
||||
import ColumnsContainerSidebarPanel from './input-panels/ColumnsContainerSidebarPanel';
|
||||
import ContainerSidebarPanel from './input-panels/ContainerSidebarPanel';
|
||||
import DividerSidebarPanel from './input-panels/DividerSidebarPanel';
|
||||
import EmailLayoutSidebarPanel from './input-panels/EmailLayoutSidebarPanel';
|
||||
import HeadingSidebarPanel from './input-panels/HeadingSidebarPanel';
|
||||
import HtmlSidebarPanel from './input-panels/HtmlSidebarPanel';
|
||||
import ImageSidebarPanel from './input-panels/ImageSidebarPanel';
|
||||
import SpacerSidebarPanel from './input-panels/SpacerSidebarPanel';
|
||||
import TextSidebarPanel from './input-panels/TextSidebarPanel';
|
||||
|
||||
function renderMessage(val: string) {
|
||||
return (
|
||||
<Box sx={{ m: 3, p: 1, border: '1px dashed', borderColor: 'divider' }}>
|
||||
<Typography color="text.secondary">{val}</Typography>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default function ConfigurationPanel() {
|
||||
const document = useDocument();
|
||||
const selectedBlockId = useSelectedBlockId();
|
||||
|
||||
if (!selectedBlockId) {
|
||||
return renderMessage('Click on a block to inspect.');
|
||||
}
|
||||
const block = document[selectedBlockId];
|
||||
if (!block) {
|
||||
return renderMessage(`Block with id ${selectedBlockId} was not found. Click on a block to reset.`);
|
||||
}
|
||||
|
||||
const setBlock = (conf: TEditorBlock) => setDocument({ [selectedBlockId]: conf });
|
||||
const { data, type } = block;
|
||||
switch (type) {
|
||||
case 'Avatar':
|
||||
return <AvatarSidebarPanel key={selectedBlockId} data={data} setData={(data) => setBlock({ type, data })} />;
|
||||
case 'Button':
|
||||
return <ButtonSidebarPanel key={selectedBlockId} data={data} setData={(data) => setBlock({ type, data })} />;
|
||||
case 'ColumnsContainer':
|
||||
return (
|
||||
<ColumnsContainerSidebarPanel key={selectedBlockId} data={data} setData={(data) => setBlock({ type, data })} />
|
||||
);
|
||||
case 'Container':
|
||||
return <ContainerSidebarPanel key={selectedBlockId} data={data} setData={(data) => setBlock({ type, data })} />;
|
||||
case 'Divider':
|
||||
return <DividerSidebarPanel key={selectedBlockId} data={data} setData={(data) => setBlock({ type, data })} />;
|
||||
case 'Heading':
|
||||
return <HeadingSidebarPanel key={selectedBlockId} data={data} setData={(data) => setBlock({ type, data })} />;
|
||||
case 'Html':
|
||||
return <HtmlSidebarPanel key={selectedBlockId} data={data} setData={(data) => setBlock({ type, data })} />;
|
||||
case 'Image':
|
||||
return <ImageSidebarPanel key={selectedBlockId} data={data} setData={(data) => setBlock({ type, data })} />;
|
||||
case 'EmailLayout':
|
||||
return <EmailLayoutSidebarPanel key={selectedBlockId} data={data} setData={(data) => setBlock({ type, data })} />;
|
||||
case 'Spacer':
|
||||
return <SpacerSidebarPanel key={selectedBlockId} data={data} setData={(data) => setBlock({ type, data })} />;
|
||||
case 'Text':
|
||||
return <TextSidebarPanel key={selectedBlockId} data={data} setData={(data) => setBlock({ type, data })} />;
|
||||
default:
|
||||
return <pre>{JSON.stringify(block, null, ' ')}</pre>;
|
||||
}
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
import React, { useState } from "react";
|
||||
import { ZodError } from "zod";
|
||||
|
||||
import { AspectRatioOutlined } from "@mui/icons-material";
|
||||
import { ToggleButton } from "@mui/material";
|
||||
import {
|
||||
AvatarProps,
|
||||
AvatarPropsDefaults,
|
||||
AvatarPropsSchema
|
||||
} from "@usewaypoint/block-avatar";
|
||||
|
||||
import BaseSidebarPanel from "./helpers/BaseSidebarPanel";
|
||||
import RadioGroupInput from "./helpers/inputs/RadioGroupInput";
|
||||
import SliderInput from "./helpers/inputs/SliderInput";
|
||||
import TextInput from "./helpers/inputs/TextInput";
|
||||
import MultiStylePropertyPanel from "./helpers/style-inputs/MultiStylePropertyPanel";
|
||||
|
||||
type AvatarSidebarPanelProps = {
|
||||
data: AvatarProps;
|
||||
setData: (v: AvatarProps) => void;
|
||||
};
|
||||
export default function AvatarSidebarPanel({
|
||||
data,
|
||||
setData
|
||||
}: AvatarSidebarPanelProps) {
|
||||
const [, setErrors] = useState<ZodError | null>(null);
|
||||
const updateData = (d: unknown) => {
|
||||
const res = AvatarPropsSchema.safeParse(d);
|
||||
if (res.success) {
|
||||
setData(res.data);
|
||||
setErrors(null);
|
||||
} else {
|
||||
setErrors(res.error);
|
||||
}
|
||||
};
|
||||
|
||||
const size = data.props?.size ?? AvatarPropsDefaults.size;
|
||||
const imageUrl = data.props?.imageUrl ?? AvatarPropsDefaults.imageUrl;
|
||||
const alt = data.props?.alt ?? AvatarPropsDefaults.alt;
|
||||
const shape = data.props?.shape ?? AvatarPropsDefaults.shape;
|
||||
|
||||
return (
|
||||
<BaseSidebarPanel title="Avatar block">
|
||||
<SliderInput
|
||||
label="Size"
|
||||
iconLabel={<AspectRatioOutlined sx={{ color: "text.secondary" }} />}
|
||||
units="px"
|
||||
step={3}
|
||||
min={32}
|
||||
max={256}
|
||||
defaultValue={size}
|
||||
onChange={(size) => {
|
||||
updateData({ ...data, props: { ...data.props, size } });
|
||||
}}
|
||||
/>
|
||||
<RadioGroupInput
|
||||
label="Shape"
|
||||
defaultValue={shape}
|
||||
onChange={(shape) => {
|
||||
updateData({ ...data, props: { ...data.props, shape } });
|
||||
}}
|
||||
>
|
||||
<ToggleButton value="circle">Circle</ToggleButton>
|
||||
<ToggleButton value="square">Square</ToggleButton>
|
||||
<ToggleButton value="rounded">Rounded</ToggleButton>
|
||||
</RadioGroupInput>
|
||||
<TextInput
|
||||
label="Image URL"
|
||||
defaultValue={imageUrl}
|
||||
onChange={(imageUrl) => {
|
||||
updateData({ ...data, props: { ...data.props, imageUrl } });
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label="Alt text"
|
||||
defaultValue={alt}
|
||||
onChange={(alt) => {
|
||||
updateData({ ...data, props: { ...data.props, alt } });
|
||||
}}
|
||||
/>
|
||||
|
||||
<MultiStylePropertyPanel
|
||||
names={["textAlign", "padding"]}
|
||||
value={data.style}
|
||||
onChange={(style) => updateData({ ...data, style })}
|
||||
/>
|
||||
</BaseSidebarPanel>
|
||||
);
|
||||
}
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
import React, { useState } from "react";
|
||||
import { ZodError } from "zod";
|
||||
|
||||
import { ToggleButton } from "@mui/material";
|
||||
import {
|
||||
ButtonProps,
|
||||
ButtonPropsDefaults,
|
||||
ButtonPropsSchema
|
||||
} from "@usewaypoint/block-button";
|
||||
|
||||
import BaseSidebarPanel from "./helpers/BaseSidebarPanel";
|
||||
import ColorInput from "./helpers/inputs/ColorInput";
|
||||
import RadioGroupInput from "./helpers/inputs/RadioGroupInput";
|
||||
import TextInput from "./helpers/inputs/TextInput";
|
||||
import MultiStylePropertyPanel from "./helpers/style-inputs/MultiStylePropertyPanel";
|
||||
|
||||
type ButtonSidebarPanelProps = {
|
||||
data: ButtonProps;
|
||||
setData: (v: ButtonProps) => void;
|
||||
};
|
||||
export default function ButtonSidebarPanel({
|
||||
data,
|
||||
setData
|
||||
}: ButtonSidebarPanelProps) {
|
||||
const [, setErrors] = useState<ZodError | null>(null);
|
||||
|
||||
const updateData = (d: unknown) => {
|
||||
const res = ButtonPropsSchema.safeParse(d);
|
||||
if (res.success) {
|
||||
setData(res.data);
|
||||
setErrors(null);
|
||||
} else {
|
||||
setErrors(res.error);
|
||||
}
|
||||
};
|
||||
|
||||
const text = data.props?.text ?? ButtonPropsDefaults.text;
|
||||
const url = data.props?.url ?? ButtonPropsDefaults.url;
|
||||
const fullWidth = data.props?.fullWidth ?? ButtonPropsDefaults.fullWidth;
|
||||
const size = data.props?.size ?? ButtonPropsDefaults.size;
|
||||
const buttonStyle =
|
||||
data.props?.buttonStyle ?? ButtonPropsDefaults.buttonStyle;
|
||||
const buttonTextColor =
|
||||
data.props?.buttonTextColor ?? ButtonPropsDefaults.buttonTextColor;
|
||||
const buttonBackgroundColor =
|
||||
data.props?.buttonBackgroundColor ??
|
||||
ButtonPropsDefaults.buttonBackgroundColor;
|
||||
|
||||
return (
|
||||
<BaseSidebarPanel title="Button block">
|
||||
<TextInput
|
||||
label="Text"
|
||||
defaultValue={text}
|
||||
onChange={(text) =>
|
||||
updateData({ ...data, props: { ...data.props, text } })
|
||||
}
|
||||
/>
|
||||
<TextInput
|
||||
label="Url"
|
||||
defaultValue={url}
|
||||
onChange={(url) =>
|
||||
updateData({ ...data, props: { ...data.props, url } })
|
||||
}
|
||||
/>
|
||||
<RadioGroupInput
|
||||
label="Width"
|
||||
defaultValue={fullWidth ? "FULL_WIDTH" : "AUTO"}
|
||||
onChange={(v) =>
|
||||
updateData({
|
||||
...data,
|
||||
props: { ...data.props, fullWidth: v === "FULL_WIDTH" }
|
||||
})
|
||||
}
|
||||
>
|
||||
<ToggleButton value="FULL_WIDTH">Full</ToggleButton>
|
||||
<ToggleButton value="AUTO">Auto</ToggleButton>
|
||||
</RadioGroupInput>
|
||||
<RadioGroupInput
|
||||
label="Size"
|
||||
defaultValue={size}
|
||||
onChange={(size) =>
|
||||
updateData({ ...data, props: { ...data.props, size } })
|
||||
}
|
||||
>
|
||||
<ToggleButton value="x-small">Xs</ToggleButton>
|
||||
<ToggleButton value="small">Sm</ToggleButton>
|
||||
<ToggleButton value="medium">Md</ToggleButton>
|
||||
<ToggleButton value="large">Lg</ToggleButton>
|
||||
</RadioGroupInput>
|
||||
<RadioGroupInput
|
||||
label="Style"
|
||||
defaultValue={buttonStyle}
|
||||
onChange={(buttonStyle) =>
|
||||
updateData({ ...data, props: { ...data.props, buttonStyle } })
|
||||
}
|
||||
>
|
||||
<ToggleButton value="rectangle">Rectangle</ToggleButton>
|
||||
<ToggleButton value="rounded">Rounded</ToggleButton>
|
||||
<ToggleButton value="pill">Pill</ToggleButton>
|
||||
</RadioGroupInput>
|
||||
<ColorInput
|
||||
label="Text color"
|
||||
defaultValue={buttonTextColor}
|
||||
onChange={(buttonTextColor) =>
|
||||
updateData({ ...data, props: { ...data.props, buttonTextColor } })
|
||||
}
|
||||
/>
|
||||
<ColorInput
|
||||
label="Button color"
|
||||
defaultValue={buttonBackgroundColor}
|
||||
onChange={(buttonBackgroundColor) =>
|
||||
updateData({
|
||||
...data,
|
||||
props: { ...data.props, buttonBackgroundColor }
|
||||
})
|
||||
}
|
||||
/>
|
||||
<MultiStylePropertyPanel
|
||||
names={[
|
||||
"backgroundColor",
|
||||
"fontFamily",
|
||||
"fontSize",
|
||||
"fontWeight",
|
||||
"textAlign",
|
||||
"padding"
|
||||
]}
|
||||
value={data.style}
|
||||
onChange={(style) => updateData({ ...data, style })}
|
||||
/>
|
||||
</BaseSidebarPanel>
|
||||
);
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
import React, { useState } from "react";
|
||||
import { ZodError } from "zod";
|
||||
|
||||
import {
|
||||
SpaceBarOutlined,
|
||||
VerticalAlignBottomOutlined,
|
||||
VerticalAlignCenterOutlined,
|
||||
VerticalAlignTopOutlined
|
||||
} from "@mui/icons-material";
|
||||
import { ToggleButton } from "@mui/material";
|
||||
|
||||
import ColumnsContainerPropsSchema, {
|
||||
ColumnsContainerProps
|
||||
} from "../../../../documents/blocks/ColumnsContainer/ColumnsContainerPropsSchema";
|
||||
|
||||
import BaseSidebarPanel from "./helpers/BaseSidebarPanel";
|
||||
import ColumnWidthsInput from "./helpers/inputs/ColumnWidthsInput";
|
||||
import RadioGroupInput from "./helpers/inputs/RadioGroupInput";
|
||||
import SliderInput from "./helpers/inputs/SliderInput";
|
||||
import MultiStylePropertyPanel from "./helpers/style-inputs/MultiStylePropertyPanel";
|
||||
|
||||
type ColumnsContainerPanelProps = {
|
||||
data: ColumnsContainerProps;
|
||||
setData: (v: ColumnsContainerProps) => void;
|
||||
};
|
||||
export default function ColumnsContainerPanel({
|
||||
data,
|
||||
setData
|
||||
}: ColumnsContainerPanelProps) {
|
||||
const [, setErrors] = useState<ZodError | null>(null);
|
||||
const updateData = (d: unknown) => {
|
||||
const res = ColumnsContainerPropsSchema.safeParse(d);
|
||||
if (res.success) {
|
||||
setData(res.data);
|
||||
setErrors(null);
|
||||
} else {
|
||||
setErrors(res.error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<BaseSidebarPanel title="Columns block">
|
||||
<RadioGroupInput
|
||||
label="Number of columns"
|
||||
defaultValue={data.props?.columnsCount === 2 ? "2" : "3"}
|
||||
onChange={(v) => {
|
||||
updateData({
|
||||
...data,
|
||||
props: { ...data.props, columnsCount: v === "2" ? 2 : 3 }
|
||||
});
|
||||
}}
|
||||
>
|
||||
<ToggleButton value="2">2</ToggleButton>
|
||||
<ToggleButton value="3">3</ToggleButton>
|
||||
</RadioGroupInput>
|
||||
<ColumnWidthsInput
|
||||
defaultValue={data.props?.fixedWidths}
|
||||
onChange={(fixedWidths) => {
|
||||
updateData({ ...data, props: { ...data.props, fixedWidths } });
|
||||
}}
|
||||
/>
|
||||
<SliderInput
|
||||
label="Columns gap"
|
||||
iconLabel={<SpaceBarOutlined sx={{ color: "text.secondary" }} />}
|
||||
units="px"
|
||||
step={4}
|
||||
marks
|
||||
min={0}
|
||||
max={80}
|
||||
defaultValue={data.props?.columnsGap ?? 0}
|
||||
onChange={(columnsGap) =>
|
||||
updateData({ ...data, props: { ...data.props, columnsGap } })
|
||||
}
|
||||
/>
|
||||
<RadioGroupInput
|
||||
label="Alignment"
|
||||
defaultValue={data.props?.contentAlignment ?? "middle"}
|
||||
onChange={(contentAlignment) => {
|
||||
updateData({ ...data, props: { ...data.props, contentAlignment } });
|
||||
}}
|
||||
>
|
||||
<ToggleButton value="top">
|
||||
<VerticalAlignTopOutlined fontSize="small" />
|
||||
</ToggleButton>
|
||||
<ToggleButton value="middle">
|
||||
<VerticalAlignCenterOutlined fontSize="small" />
|
||||
</ToggleButton>
|
||||
<ToggleButton value="bottom">
|
||||
<VerticalAlignBottomOutlined fontSize="small" />
|
||||
</ToggleButton>
|
||||
</RadioGroupInput>
|
||||
|
||||
<MultiStylePropertyPanel
|
||||
names={["backgroundColor", "padding"]}
|
||||
value={data.style}
|
||||
onChange={(style) => updateData({ ...data, style })}
|
||||
/>
|
||||
</BaseSidebarPanel>
|
||||
);
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
import React, { useState } from "react";
|
||||
import type { ZodError } from "zod";
|
||||
|
||||
import ContainerPropsSchema, {
|
||||
ContainerProps
|
||||
} from "../../../../documents/blocks/Container/ContainerPropsSchema";
|
||||
|
||||
import BaseSidebarPanel from "./helpers/BaseSidebarPanel";
|
||||
import MultiStylePropertyPanel from "./helpers/style-inputs/MultiStylePropertyPanel";
|
||||
|
||||
type ContainerSidebarPanelProps = {
|
||||
data: ContainerProps;
|
||||
setData: (v: ContainerProps) => void;
|
||||
};
|
||||
|
||||
export default function ContainerSidebarPanel({
|
||||
data,
|
||||
setData
|
||||
}: ContainerSidebarPanelProps) {
|
||||
const [, setErrors] = useState<ZodError | null>(null);
|
||||
const updateData = (d: unknown) => {
|
||||
const res = ContainerPropsSchema.safeParse(d);
|
||||
if (res.success) {
|
||||
setData(res.data);
|
||||
setErrors(null);
|
||||
} else {
|
||||
setErrors(res.error);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<BaseSidebarPanel title="Container block">
|
||||
<MultiStylePropertyPanel
|
||||
names={["backgroundColor", "borderColor", "borderRadius", "padding"]}
|
||||
value={data.style}
|
||||
onChange={(style) => updateData({ ...data, style })}
|
||||
/>
|
||||
</BaseSidebarPanel>
|
||||
);
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
import React, { useState } from "react";
|
||||
import { ZodError } from "zod";
|
||||
|
||||
import { HeightOutlined } from "@mui/icons-material";
|
||||
import {
|
||||
DividerProps,
|
||||
DividerPropsDefaults,
|
||||
DividerPropsSchema
|
||||
} from "@usewaypoint/block-divider";
|
||||
|
||||
import BaseSidebarPanel from "./helpers/BaseSidebarPanel";
|
||||
import ColorInput from "./helpers/inputs/ColorInput";
|
||||
import SliderInput from "./helpers/inputs/SliderInput";
|
||||
import MultiStylePropertyPanel from "./helpers/style-inputs/MultiStylePropertyPanel";
|
||||
|
||||
type DividerSidebarPanelProps = {
|
||||
data: DividerProps;
|
||||
setData: (v: DividerProps) => void;
|
||||
};
|
||||
export default function DividerSidebarPanel({
|
||||
data,
|
||||
setData
|
||||
}: DividerSidebarPanelProps) {
|
||||
const [, setErrors] = useState<ZodError | null>(null);
|
||||
const updateData = (d: unknown) => {
|
||||
const res = DividerPropsSchema.safeParse(d);
|
||||
if (res.success) {
|
||||
setData(res.data);
|
||||
setErrors(null);
|
||||
} else {
|
||||
setErrors(res.error);
|
||||
}
|
||||
};
|
||||
|
||||
const lineColor = data.props?.lineColor ?? DividerPropsDefaults.lineColor;
|
||||
const lineHeight = data.props?.lineHeight ?? DividerPropsDefaults.lineHeight;
|
||||
|
||||
return (
|
||||
<BaseSidebarPanel title="Divider block">
|
||||
<ColorInput
|
||||
label="Color"
|
||||
defaultValue={lineColor}
|
||||
onChange={(lineColor) =>
|
||||
updateData({ ...data, props: { ...data.props, lineColor } })
|
||||
}
|
||||
/>
|
||||
<SliderInput
|
||||
label="Height"
|
||||
iconLabel={<HeightOutlined sx={{ color: "text.secondary" }} />}
|
||||
units="px"
|
||||
step={1}
|
||||
min={1}
|
||||
max={24}
|
||||
defaultValue={lineHeight}
|
||||
onChange={(lineHeight) =>
|
||||
updateData({ ...data, props: { ...data.props, lineHeight } })
|
||||
}
|
||||
/>
|
||||
<MultiStylePropertyPanel
|
||||
names={["backgroundColor", "padding"]}
|
||||
value={data.style}
|
||||
onChange={(style) => updateData({ ...data, style })}
|
||||
/>
|
||||
</BaseSidebarPanel>
|
||||
);
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
import React, { useState } from "react";
|
||||
import { ZodError } from "zod";
|
||||
|
||||
import { WidthWide, RoundedCornerOutlined } from "@mui/icons-material";
|
||||
import EmailLayoutPropsSchema, {
|
||||
EmailLayoutProps
|
||||
} from "../../../../documents/blocks/EmailLayout/EmailLayoutPropsSchema";
|
||||
|
||||
import BaseSidebarPanel from "./helpers/BaseSidebarPanel";
|
||||
import ColorInput, { NullableColorInput } from "./helpers/inputs/ColorInput";
|
||||
import { NullableFontFamily } from "./helpers/inputs/FontFamily";
|
||||
import SliderInput from "./helpers/inputs/SliderInput";
|
||||
|
||||
type EmailLayoutSidebarFieldsProps = {
|
||||
data: EmailLayoutProps;
|
||||
setData: (v: EmailLayoutProps) => void;
|
||||
};
|
||||
export default function EmailLayoutSidebarFields({
|
||||
data,
|
||||
setData
|
||||
}: EmailLayoutSidebarFieldsProps) {
|
||||
const [, setErrors] = useState<ZodError | null>(null);
|
||||
|
||||
const updateData = (d: unknown) => {
|
||||
const res = EmailLayoutPropsSchema.safeParse(d);
|
||||
if (res.success) {
|
||||
setData(res.data);
|
||||
setErrors(null);
|
||||
} else {
|
||||
setErrors(res.error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<BaseSidebarPanel title="Global">
|
||||
<ColorInput
|
||||
label="Backdrop color"
|
||||
defaultValue={data.backdropColor ?? "#F5F5F5"}
|
||||
onChange={(backdropColor) => updateData({ ...data, backdropColor })}
|
||||
/>
|
||||
<ColorInput
|
||||
label="Canvas color"
|
||||
defaultValue={data.canvasColor ?? "#FFFFFF"}
|
||||
onChange={(canvasColor) => updateData({ ...data, canvasColor })}
|
||||
/>
|
||||
<NullableColorInput
|
||||
label="Canvas border color"
|
||||
defaultValue={data.borderColor ?? null}
|
||||
onChange={(borderColor) => updateData({ ...data, borderColor })}
|
||||
/>
|
||||
<SliderInput
|
||||
iconLabel={<WidthWide />}
|
||||
units="px"
|
||||
step={10}
|
||||
min={320}
|
||||
max={1200}
|
||||
label="Canvas width"
|
||||
defaultValue={data.canvasWidth ?? 600}
|
||||
onChange={(canvasWidth) => updateData({ ...data, canvasWidth })}
|
||||
/>
|
||||
<SliderInput
|
||||
iconLabel={<RoundedCornerOutlined />}
|
||||
units="px"
|
||||
step={4}
|
||||
marks
|
||||
min={0}
|
||||
max={48}
|
||||
label="Canvas border radius"
|
||||
defaultValue={data.borderRadius ?? 0}
|
||||
onChange={(borderRadius) => updateData({ ...data, borderRadius })}
|
||||
/>
|
||||
<NullableFontFamily
|
||||
label="Font family"
|
||||
defaultValue="MODERN_SANS"
|
||||
onChange={(fontFamily) => updateData({ ...data, fontFamily })}
|
||||
/>
|
||||
<ColorInput
|
||||
label="Text color"
|
||||
defaultValue={data.textColor ?? "#262626"}
|
||||
onChange={(textColor) => updateData({ ...data, textColor })}
|
||||
/>
|
||||
</BaseSidebarPanel>
|
||||
);
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
import React, { useState } from "react";
|
||||
import { ZodError } from "zod";
|
||||
|
||||
import { ToggleButton } from "@mui/material";
|
||||
import {
|
||||
HeadingProps,
|
||||
HeadingPropsDefaults,
|
||||
HeadingPropsSchema
|
||||
} from "@usewaypoint/block-heading";
|
||||
|
||||
import BaseSidebarPanel from "./helpers/BaseSidebarPanel";
|
||||
import RadioGroupInput from "./helpers/inputs/RadioGroupInput";
|
||||
import TextInput from "./helpers/inputs/TextInput";
|
||||
import MultiStylePropertyPanel from "./helpers/style-inputs/MultiStylePropertyPanel";
|
||||
|
||||
type HeadingSidebarPanelProps = {
|
||||
data: HeadingProps;
|
||||
setData: (v: HeadingProps) => void;
|
||||
};
|
||||
export default function HeadingSidebarPanel({
|
||||
data,
|
||||
setData
|
||||
}: HeadingSidebarPanelProps) {
|
||||
const [, setErrors] = useState<ZodError | null>(null);
|
||||
|
||||
const updateData = (d: unknown) => {
|
||||
const res = HeadingPropsSchema.safeParse(d);
|
||||
if (res.success) {
|
||||
setData(res.data);
|
||||
setErrors(null);
|
||||
} else {
|
||||
setErrors(res.error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<BaseSidebarPanel title="Heading block">
|
||||
<TextInput
|
||||
label="Content"
|
||||
rows={3}
|
||||
defaultValue={data.props?.text ?? HeadingPropsDefaults.text}
|
||||
onChange={(text) => {
|
||||
updateData({ ...data, props: { ...data.props, text } });
|
||||
}}
|
||||
/>
|
||||
<RadioGroupInput
|
||||
label="Level"
|
||||
defaultValue={data.props?.level ?? HeadingPropsDefaults.level}
|
||||
onChange={(level) => {
|
||||
updateData({ ...data, props: { ...data.props, level } });
|
||||
}}
|
||||
>
|
||||
<ToggleButton value="h1">H1</ToggleButton>
|
||||
<ToggleButton value="h2">H2</ToggleButton>
|
||||
<ToggleButton value="h3">H3</ToggleButton>
|
||||
</RadioGroupInput>
|
||||
<MultiStylePropertyPanel
|
||||
names={[
|
||||
"color",
|
||||
"backgroundColor",
|
||||
"fontFamily",
|
||||
"fontWeight",
|
||||
"textAlign",
|
||||
"padding"
|
||||
]}
|
||||
value={data.style}
|
||||
onChange={(style) => updateData({ ...data, style })}
|
||||
/>
|
||||
</BaseSidebarPanel>
|
||||
);
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
import React, { useState } from "react";
|
||||
import { ZodError } from "zod";
|
||||
|
||||
import { HtmlProps, HtmlPropsSchema } from "@usewaypoint/block-html";
|
||||
|
||||
import BaseSidebarPanel from "./helpers/BaseSidebarPanel";
|
||||
import TextInput from "./helpers/inputs/TextInput";
|
||||
import MultiStylePropertyPanel from "./helpers/style-inputs/MultiStylePropertyPanel";
|
||||
|
||||
type HtmlSidebarPanelProps = {
|
||||
data: HtmlProps;
|
||||
setData: (v: HtmlProps) => void;
|
||||
};
|
||||
export default function HtmlSidebarPanel({
|
||||
data,
|
||||
setData
|
||||
}: HtmlSidebarPanelProps) {
|
||||
const [, setErrors] = useState<ZodError | null>(null);
|
||||
|
||||
const updateData = (d: unknown) => {
|
||||
const res = HtmlPropsSchema.safeParse(d);
|
||||
if (res.success) {
|
||||
setData(res.data);
|
||||
setErrors(null);
|
||||
} else {
|
||||
setErrors(res.error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<BaseSidebarPanel title="Html block">
|
||||
<TextInput
|
||||
label="Content"
|
||||
rows={5}
|
||||
defaultValue={data.props?.contents ?? ""}
|
||||
onChange={(contents) =>
|
||||
updateData({ ...data, props: { ...data.props, contents } })
|
||||
}
|
||||
/>
|
||||
<MultiStylePropertyPanel
|
||||
names={[
|
||||
"color",
|
||||
"backgroundColor",
|
||||
"fontFamily",
|
||||
"fontSize",
|
||||
"textAlign",
|
||||
"padding"
|
||||
]}
|
||||
value={data.style}
|
||||
onChange={(style) => updateData({ ...data, style })}
|
||||
/>
|
||||
</BaseSidebarPanel>
|
||||
);
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
import React, { useState } from "react";
|
||||
import { ZodError } from "zod";
|
||||
|
||||
import {
|
||||
VerticalAlignBottomOutlined,
|
||||
VerticalAlignCenterOutlined,
|
||||
VerticalAlignTopOutlined
|
||||
} from "@mui/icons-material";
|
||||
import { Stack, ToggleButton } from "@mui/material";
|
||||
import { ImageProps, ImagePropsSchema } from "@usewaypoint/block-image";
|
||||
|
||||
import BaseSidebarPanel from "./helpers/BaseSidebarPanel";
|
||||
import RadioGroupInput from "./helpers/inputs/RadioGroupInput";
|
||||
import TextDimensionInput from "./helpers/inputs/TextDimensionInput";
|
||||
import TextInput from "./helpers/inputs/TextInput";
|
||||
import MultiStylePropertyPanel from "./helpers/style-inputs/MultiStylePropertyPanel";
|
||||
|
||||
type ImageSidebarPanelProps = {
|
||||
data: ImageProps;
|
||||
setData: (v: ImageProps) => void;
|
||||
};
|
||||
export default function ImageSidebarPanel({
|
||||
data,
|
||||
setData
|
||||
}: ImageSidebarPanelProps) {
|
||||
const [, setErrors] = useState<ZodError | null>(null);
|
||||
|
||||
const updateData = (d: unknown) => {
|
||||
const res = ImagePropsSchema.safeParse(d);
|
||||
if (res.success) {
|
||||
setData(res.data);
|
||||
setErrors(null);
|
||||
} else {
|
||||
setErrors(res.error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<BaseSidebarPanel title="Image block">
|
||||
<TextInput
|
||||
label="Source URL"
|
||||
defaultValue={data.props?.url ?? ""}
|
||||
onChange={(v) => {
|
||||
const url = v.trim().length === 0 ? null : v.trim();
|
||||
updateData({ ...data, props: { ...data.props, url } });
|
||||
}}
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
label="Alt text"
|
||||
defaultValue={data.props?.alt ?? ""}
|
||||
onChange={(alt) =>
|
||||
updateData({ ...data, props: { ...data.props, alt } })
|
||||
}
|
||||
/>
|
||||
<TextInput
|
||||
label="Click through URL"
|
||||
defaultValue={data.props?.linkHref ?? ""}
|
||||
onChange={(v) => {
|
||||
const linkHref = v.trim().length === 0 ? null : v.trim();
|
||||
updateData({ ...data, props: { ...data.props, linkHref } });
|
||||
}}
|
||||
/>
|
||||
<Stack direction="row" spacing={2}>
|
||||
<TextDimensionInput
|
||||
label="Width"
|
||||
defaultValue={data.props?.width}
|
||||
onChange={(width) =>
|
||||
updateData({ ...data, props: { ...data.props, width } })
|
||||
}
|
||||
/>
|
||||
<TextDimensionInput
|
||||
label="Height"
|
||||
defaultValue={data.props?.height}
|
||||
onChange={(height) =>
|
||||
updateData({ ...data, props: { ...data.props, height } })
|
||||
}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
<RadioGroupInput
|
||||
label="Alignment"
|
||||
defaultValue={data.props?.contentAlignment ?? "middle"}
|
||||
onChange={(contentAlignment) =>
|
||||
updateData({ ...data, props: { ...data.props, contentAlignment } })
|
||||
}
|
||||
>
|
||||
<ToggleButton value="top">
|
||||
<VerticalAlignTopOutlined fontSize="small" />
|
||||
</ToggleButton>
|
||||
<ToggleButton value="middle">
|
||||
<VerticalAlignCenterOutlined fontSize="small" />
|
||||
</ToggleButton>
|
||||
<ToggleButton value="bottom">
|
||||
<VerticalAlignBottomOutlined fontSize="small" />
|
||||
</ToggleButton>
|
||||
</RadioGroupInput>
|
||||
|
||||
<MultiStylePropertyPanel
|
||||
names={["backgroundColor", "textAlign", "padding"]}
|
||||
value={data.style}
|
||||
onChange={(style) => updateData({ ...data, style })}
|
||||
/>
|
||||
</BaseSidebarPanel>
|
||||
);
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
import React, { useState } from "react";
|
||||
import { ZodError } from "zod";
|
||||
|
||||
import { HeightOutlined } from "@mui/icons-material";
|
||||
import {
|
||||
SpacerProps,
|
||||
SpacerPropsDefaults,
|
||||
SpacerPropsSchema
|
||||
} from "@usewaypoint/block-spacer";
|
||||
|
||||
import BaseSidebarPanel from "./helpers/BaseSidebarPanel";
|
||||
import SliderInput from "./helpers/inputs/SliderInput";
|
||||
|
||||
type SpacerSidebarPanelProps = {
|
||||
data: SpacerProps;
|
||||
setData: (v: SpacerProps) => void;
|
||||
};
|
||||
export default function SpacerSidebarPanel({
|
||||
data,
|
||||
setData
|
||||
}: SpacerSidebarPanelProps) {
|
||||
const [, setErrors] = useState<ZodError | null>(null);
|
||||
|
||||
const updateData = (d: unknown) => {
|
||||
const res = SpacerPropsSchema.safeParse(d);
|
||||
if (res.success) {
|
||||
setData(res.data);
|
||||
setErrors(null);
|
||||
} else {
|
||||
setErrors(res.error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<BaseSidebarPanel title="Spacer block">
|
||||
<SliderInput
|
||||
label="Height"
|
||||
iconLabel={<HeightOutlined sx={{ color: "text.secondary" }} />}
|
||||
units="px"
|
||||
step={4}
|
||||
min={4}
|
||||
max={128}
|
||||
defaultValue={data.props?.height ?? SpacerPropsDefaults.height}
|
||||
onChange={(height) =>
|
||||
updateData({ ...data, props: { ...data.props, height } })
|
||||
}
|
||||
/>
|
||||
</BaseSidebarPanel>
|
||||
);
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
import React, { useState } from "react";
|
||||
import { ZodError } from "zod";
|
||||
|
||||
import { TextProps, TextPropsSchema } from "@usewaypoint/block-text";
|
||||
|
||||
import BaseSidebarPanel from "./helpers/BaseSidebarPanel";
|
||||
import BooleanInput from "./helpers/inputs/BooleanInput";
|
||||
import TextInput from "./helpers/inputs/TextInput";
|
||||
import MultiStylePropertyPanel from "./helpers/style-inputs/MultiStylePropertyPanel";
|
||||
|
||||
type TextSidebarPanelProps = {
|
||||
data: TextProps;
|
||||
setData: (v: TextProps) => void;
|
||||
};
|
||||
export default function TextSidebarPanel({
|
||||
data,
|
||||
setData
|
||||
}: TextSidebarPanelProps) {
|
||||
const [, setErrors] = useState<ZodError | null>(null);
|
||||
|
||||
const updateData = (d: unknown) => {
|
||||
const res = TextPropsSchema.safeParse(d);
|
||||
if (res.success) {
|
||||
setData(res.data);
|
||||
setErrors(null);
|
||||
} else {
|
||||
setErrors(res.error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<BaseSidebarPanel title="Text block">
|
||||
<TextInput
|
||||
label="Content"
|
||||
rows={5}
|
||||
defaultValue={data.props?.text ?? ""}
|
||||
onChange={(text) =>
|
||||
updateData({ ...data, props: { ...data.props, text } })
|
||||
}
|
||||
/>
|
||||
<BooleanInput
|
||||
label="Markdown (GitHub flavored)"
|
||||
defaultValue={data.props?.markdown ?? false}
|
||||
onChange={(markdown) =>
|
||||
updateData({ ...data, props: { ...data.props, markdown } })
|
||||
}
|
||||
/>
|
||||
|
||||
<MultiStylePropertyPanel
|
||||
names={[
|
||||
"color",
|
||||
"backgroundColor",
|
||||
"fontFamily",
|
||||
"fontSize",
|
||||
"fontWeight",
|
||||
"textAlign",
|
||||
"padding"
|
||||
]}
|
||||
value={data.style}
|
||||
onChange={(style) => updateData({ ...data, style })}
|
||||
/>
|
||||
</BaseSidebarPanel>
|
||||
);
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
import React from "react";
|
||||
|
||||
import { Box, Stack, Typography } from "@mui/material";
|
||||
|
||||
type SidebarPanelProps = {
|
||||
title: string;
|
||||
children: React.ReactNode;
|
||||
};
|
||||
export default function BaseSidebarPanel({
|
||||
title,
|
||||
children
|
||||
}: SidebarPanelProps) {
|
||||
return (
|
||||
<Box p={2}>
|
||||
<Typography
|
||||
variant="overline"
|
||||
color="text.secondary"
|
||||
sx={{ display: "block", mb: 2 }}
|
||||
>
|
||||
{title}
|
||||
</Typography>
|
||||
<Stack spacing={5} mb={3}>
|
||||
{children}
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { FormControlLabel, Switch } from '@mui/material';
|
||||
|
||||
type Props = {
|
||||
label: string;
|
||||
defaultValue: boolean;
|
||||
onChange: (value: boolean) => void;
|
||||
};
|
||||
|
||||
export default function BooleanInput({ label, defaultValue, onChange }: Props) {
|
||||
const [value, setValue] = useState(defaultValue);
|
||||
return (
|
||||
<FormControlLabel
|
||||
label={label}
|
||||
control={
|
||||
<Switch
|
||||
checked={value}
|
||||
onChange={(_, checked: boolean) => {
|
||||
setValue(checked);
|
||||
onChange(checked);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
import React, { useState } from "react";
|
||||
|
||||
import { AddOutlined, CloseOutlined } from "@mui/icons-material";
|
||||
import { ButtonBase, InputLabel, Menu, Stack } from "@mui/material";
|
||||
|
||||
import Picker from "./Picker";
|
||||
|
||||
const BUTTON_SX = {
|
||||
border: "1px solid",
|
||||
borderColor: "cadet.400",
|
||||
width: 32,
|
||||
height: 32,
|
||||
borderRadius: "4px",
|
||||
bgcolor: "#FFFFFF"
|
||||
};
|
||||
|
||||
type Props =
|
||||
| {
|
||||
nullable: true;
|
||||
label: string;
|
||||
onChange: (value: string | null) => void;
|
||||
defaultValue: string | null;
|
||||
}
|
||||
| {
|
||||
nullable: false;
|
||||
label: string;
|
||||
onChange: (value: string) => void;
|
||||
defaultValue: string;
|
||||
};
|
||||
export default function ColorInput({
|
||||
label,
|
||||
defaultValue,
|
||||
onChange,
|
||||
nullable
|
||||
}: Props) {
|
||||
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
||||
const [value, setValue] = useState(defaultValue);
|
||||
const handleClickOpen = (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
|
||||
const renderResetButton = () => {
|
||||
if (!nullable) {
|
||||
return null;
|
||||
}
|
||||
if (typeof value !== "string" || value.trim().length === 0) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<ButtonBase
|
||||
onClick={() => {
|
||||
setValue(null);
|
||||
onChange(null);
|
||||
}}
|
||||
>
|
||||
<CloseOutlined fontSize="small" sx={{ color: "grey.600" }} />
|
||||
</ButtonBase>
|
||||
);
|
||||
};
|
||||
|
||||
const renderOpenButton = () => {
|
||||
if (value) {
|
||||
return (
|
||||
<ButtonBase
|
||||
onClick={handleClickOpen}
|
||||
sx={{ ...BUTTON_SX, bgcolor: value }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<ButtonBase onClick={handleClickOpen} sx={{ ...BUTTON_SX }}>
|
||||
<AddOutlined fontSize="small" />
|
||||
</ButtonBase>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack alignItems="flex-start">
|
||||
<InputLabel sx={{ mb: 0.5 }}>{label}</InputLabel>
|
||||
<Stack direction="row" spacing={1}>
|
||||
{renderOpenButton()}
|
||||
{renderResetButton()}
|
||||
</Stack>
|
||||
<Menu
|
||||
anchorEl={anchorEl}
|
||||
open={Boolean(anchorEl)}
|
||||
onClose={() => setAnchorEl(null)}
|
||||
MenuListProps={{
|
||||
sx: { height: "auto", padding: 0 }
|
||||
}}
|
||||
>
|
||||
<Picker
|
||||
value={value || ""}
|
||||
onChange={(v) => {
|
||||
setValue(v);
|
||||
onChange(v);
|
||||
}}
|
||||
/>
|
||||
</Menu>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
import React, { useState } from "react";
|
||||
import { HexColorInput, HexColorPicker } from "react-colorful";
|
||||
|
||||
import { Box, Stack, SxProps } from "@mui/material";
|
||||
|
||||
import Swatch from "./Swatch";
|
||||
|
||||
const DEFAULT_PRESET_COLORS = [
|
||||
"#E11D48",
|
||||
"#DB2777",
|
||||
"#C026D3",
|
||||
"#9333EA",
|
||||
"#7C3AED",
|
||||
"#4F46E5",
|
||||
"#2563EB",
|
||||
"#0284C7",
|
||||
"#0891B2",
|
||||
"#0D9488",
|
||||
"#059669",
|
||||
"#16A34A",
|
||||
"#65A30D",
|
||||
"#CA8A04",
|
||||
"#D97706",
|
||||
"#EA580C",
|
||||
"#DC2626",
|
||||
"#FFFFFF",
|
||||
"#FAFAFA",
|
||||
"#F5F5F5",
|
||||
"#E5E5E5",
|
||||
"#D4D4D4",
|
||||
"#A3A3A3",
|
||||
"#737373",
|
||||
"#525252",
|
||||
"#404040",
|
||||
"#262626",
|
||||
"#171717",
|
||||
"#0A0A0A",
|
||||
"#000000"
|
||||
];
|
||||
|
||||
const SX: SxProps = {
|
||||
p: 1,
|
||||
".react-colorful__pointer ": {
|
||||
width: 16,
|
||||
height: 16
|
||||
},
|
||||
".react-colorful__saturation": {
|
||||
mb: 1,
|
||||
borderRadius: "4px"
|
||||
},
|
||||
".react-colorful__last-control": {
|
||||
borderRadius: "4px"
|
||||
},
|
||||
".react-colorful__hue-pointer": {
|
||||
width: "4px",
|
||||
borderRadius: "4px",
|
||||
height: 24,
|
||||
cursor: "col-resize"
|
||||
},
|
||||
".react-colorful__saturation-pointer": {
|
||||
cursor: "all-scroll"
|
||||
},
|
||||
input: {
|
||||
padding: 1,
|
||||
border: "1px solid",
|
||||
borderColor: "grey.300",
|
||||
borderRadius: "4px",
|
||||
width: "100%"
|
||||
}
|
||||
};
|
||||
|
||||
type Props = {
|
||||
value: string;
|
||||
onChange: (v: string) => void;
|
||||
};
|
||||
export default function Picker({ value, onChange }: Props) {
|
||||
const [internalValue, setInternalValue] = useState(value);
|
||||
const handleChange = (v: string) => {
|
||||
setInternalValue(v);
|
||||
if (/^#[0-9a-fA-F]{6}$/.test(v)) {
|
||||
onChange(v);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack spacing={1} sx={SX}>
|
||||
<HexColorPicker color={value} onChange={handleChange} />
|
||||
<Swatch
|
||||
paletteColors={DEFAULT_PRESET_COLORS}
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<Box pt={1}>
|
||||
<HexColorInput prefixed color={internalValue} onChange={handleChange} />
|
||||
</Box>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
import React from "react";
|
||||
|
||||
import { Box, Button, SxProps } from "@mui/material";
|
||||
|
||||
type Props = {
|
||||
paletteColors: string[];
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
};
|
||||
|
||||
const TILE_BUTTON: SxProps = {
|
||||
width: 24,
|
||||
height: 24
|
||||
};
|
||||
export default function Swatch({ paletteColors, value, onChange }: Props) {
|
||||
const renderButton = (colorValue: string) => {
|
||||
return (
|
||||
<Button
|
||||
key={colorValue}
|
||||
onClick={() => onChange(colorValue)}
|
||||
sx={{
|
||||
...TILE_BUTTON,
|
||||
backgroundColor: colorValue,
|
||||
border: "1px solid",
|
||||
borderColor: value === colorValue ? "black" : "grey.200",
|
||||
minWidth: 24,
|
||||
display: "inline-flex",
|
||||
"&:hover": {
|
||||
backgroundColor: colorValue,
|
||||
borderColor: "grey.500"
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<Box
|
||||
width="100%"
|
||||
sx={{
|
||||
display: "grid",
|
||||
gap: 1,
|
||||
gridTemplateColumns: "1fr 1fr 1fr 1fr 1fr 1fr"
|
||||
}}
|
||||
>
|
||||
{paletteColors.map((c) => renderButton(c))}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
import React from "react";
|
||||
|
||||
import BaseColorInput from "./BaseColorInput";
|
||||
|
||||
type Props = {
|
||||
label: string;
|
||||
onChange: (value: string) => void;
|
||||
defaultValue: string;
|
||||
};
|
||||
export default function ColorInput(props: Props) {
|
||||
return <BaseColorInput {...props} nullable={false} />;
|
||||
}
|
||||
|
||||
type NullableProps = {
|
||||
label: string;
|
||||
onChange: (value: null | string) => void;
|
||||
defaultValue: null | string;
|
||||
};
|
||||
export function NullableColorInput(props: NullableProps) {
|
||||
return <BaseColorInput {...props} nullable />;
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { Stack } from '@mui/material';
|
||||
|
||||
import TextDimensionInput from './TextDimensionInput';
|
||||
|
||||
export const DEFAULT_2_COLUMNS = [6] as [number];
|
||||
export const DEFAULT_3_COLUMNS = [4, 8] as [number, number];
|
||||
|
||||
type TWidthValue = number | null | undefined;
|
||||
type FixedWidths = [
|
||||
//
|
||||
number | null | undefined,
|
||||
number | null | undefined,
|
||||
number | null | undefined,
|
||||
];
|
||||
type ColumnsLayoutInputProps = {
|
||||
defaultValue: FixedWidths | null | undefined;
|
||||
onChange: (v: FixedWidths | null | undefined) => void;
|
||||
};
|
||||
export default function ColumnWidthsInput({ defaultValue, onChange }: ColumnsLayoutInputProps) {
|
||||
const [currentValue, setCurrentValue] = useState<[TWidthValue, TWidthValue, TWidthValue]>(() => {
|
||||
if (defaultValue) {
|
||||
return defaultValue;
|
||||
}
|
||||
return [null, null, null];
|
||||
});
|
||||
|
||||
const setIndexValue = (index: 0 | 1 | 2, value: number | null | undefined) => {
|
||||
const nValue: FixedWidths = [...currentValue];
|
||||
nValue[index] = value;
|
||||
setCurrentValue(nValue);
|
||||
onChange(nValue);
|
||||
};
|
||||
|
||||
const columnsCountValue = 3;
|
||||
let column3 = null;
|
||||
if (columnsCountValue === 3) {
|
||||
column3 = (
|
||||
<TextDimensionInput
|
||||
label="Column 3"
|
||||
defaultValue={currentValue?.[2]}
|
||||
onChange={(v) => {
|
||||
setIndexValue(2, v);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Stack direction="row" spacing={1}>
|
||||
<TextDimensionInput
|
||||
label="Column 1"
|
||||
defaultValue={currentValue?.[0]}
|
||||
onChange={(v) => {
|
||||
setIndexValue(0, v);
|
||||
}}
|
||||
/>
|
||||
<TextDimensionInput
|
||||
label="Column 2"
|
||||
defaultValue={currentValue?.[1]}
|
||||
onChange={(v) => {
|
||||
setIndexValue(1, v);
|
||||
}}
|
||||
/>
|
||||
{column3}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { MenuItem, TextField } from '@mui/material';
|
||||
|
||||
import { FONT_FAMILIES } from '../../../../../../documents/blocks/helpers/fontFamily';
|
||||
|
||||
const OPTIONS = FONT_FAMILIES.map((option) => (
|
||||
<MenuItem key={option.key} value={option.key} sx={{ fontFamily: option.value }}>
|
||||
{option.label}
|
||||
</MenuItem>
|
||||
));
|
||||
|
||||
type NullableProps = {
|
||||
label: string;
|
||||
onChange: (value: null | string) => void;
|
||||
defaultValue: null | string;
|
||||
};
|
||||
export function NullableFontFamily({ label, onChange, defaultValue }: NullableProps) {
|
||||
const [value, setValue] = useState(defaultValue ?? 'inherit');
|
||||
return (
|
||||
<TextField
|
||||
select
|
||||
variant="standard"
|
||||
label={label}
|
||||
value={value}
|
||||
onChange={(ev) => {
|
||||
const v = ev.target.value;
|
||||
setValue(v);
|
||||
onChange(v === null ? null : v);
|
||||
}}
|
||||
>
|
||||
<MenuItem value="inherit">Match email settings</MenuItem>
|
||||
{OPTIONS}
|
||||
</TextField>
|
||||
);
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { TextFieldsOutlined } from '@mui/icons-material';
|
||||
import { InputLabel, Stack } from '@mui/material';
|
||||
|
||||
import RawSliderInput from './raw/RawSliderInput';
|
||||
|
||||
type Props = {
|
||||
label: string;
|
||||
defaultValue: number;
|
||||
onChange: (v: number) => void;
|
||||
};
|
||||
export default function FontSizeInput({ label, defaultValue, onChange }: Props) {
|
||||
const [value, setValue] = useState(defaultValue);
|
||||
const handleChange = (value: number) => {
|
||||
setValue(value);
|
||||
onChange(value);
|
||||
};
|
||||
return (
|
||||
<Stack spacing={1} alignItems="flex-start">
|
||||
<InputLabel shrink>{label}</InputLabel>
|
||||
<RawSliderInput
|
||||
iconLabel={<TextFieldsOutlined sx={{ fontSize: 16 }} />}
|
||||
value={value}
|
||||
setValue={handleChange}
|
||||
units="px"
|
||||
step={1}
|
||||
min={10}
|
||||
max={48}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { ToggleButton } from '@mui/material';
|
||||
|
||||
import RadioGroupInput from './RadioGroupInput';
|
||||
|
||||
type Props = {
|
||||
label: string;
|
||||
defaultValue: string;
|
||||
onChange: (value: string) => void;
|
||||
};
|
||||
export default function FontWeightInput({ label, defaultValue, onChange }: Props) {
|
||||
const [value, setValue] = useState(defaultValue);
|
||||
return (
|
||||
<RadioGroupInput
|
||||
label={label}
|
||||
defaultValue={value}
|
||||
onChange={(fontWeight) => {
|
||||
setValue(fontWeight);
|
||||
onChange(fontWeight);
|
||||
}}
|
||||
>
|
||||
<ToggleButton value="normal">Regular</ToggleButton>
|
||||
<ToggleButton value="bold">Bold</ToggleButton>
|
||||
</RadioGroupInput>
|
||||
);
|
||||
}
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import {
|
||||
AlignHorizontalLeftOutlined,
|
||||
AlignHorizontalRightOutlined,
|
||||
AlignVerticalBottomOutlined,
|
||||
AlignVerticalTopOutlined,
|
||||
} from '@mui/icons-material';
|
||||
import { InputLabel, Stack } from '@mui/material';
|
||||
|
||||
import RawSliderInput from './raw/RawSliderInput';
|
||||
|
||||
type TPaddingValue = {
|
||||
top: number;
|
||||
bottom: number;
|
||||
right: number;
|
||||
left: number;
|
||||
};
|
||||
type Props = {
|
||||
label: string;
|
||||
defaultValue: TPaddingValue | null;
|
||||
onChange: (value: TPaddingValue) => void;
|
||||
};
|
||||
export default function PaddingInput({ label, defaultValue, onChange }: Props) {
|
||||
const [value, setValue] = useState(() => {
|
||||
if (defaultValue) {
|
||||
return defaultValue;
|
||||
}
|
||||
return {
|
||||
top: 0,
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
};
|
||||
});
|
||||
|
||||
function handleChange(internalName: keyof TPaddingValue, nValue: number) {
|
||||
const v = {
|
||||
...value,
|
||||
[internalName]: nValue,
|
||||
};
|
||||
setValue(v);
|
||||
onChange(v);
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack spacing={2} alignItems="flex-start" pb={1}>
|
||||
<InputLabel shrink>{label}</InputLabel>
|
||||
|
||||
<RawSliderInput
|
||||
iconLabel={<AlignVerticalTopOutlined sx={{ fontSize: 16 }} />}
|
||||
value={value.top}
|
||||
setValue={(num) => handleChange('top', num)}
|
||||
units="px"
|
||||
step={4}
|
||||
min={0}
|
||||
max={80}
|
||||
marks
|
||||
/>
|
||||
|
||||
<RawSliderInput
|
||||
iconLabel={<AlignVerticalBottomOutlined sx={{ fontSize: 16 }} />}
|
||||
value={value.bottom}
|
||||
setValue={(num) => handleChange('bottom', num)}
|
||||
units="px"
|
||||
step={4}
|
||||
min={0}
|
||||
max={80}
|
||||
marks
|
||||
/>
|
||||
|
||||
<RawSliderInput
|
||||
iconLabel={<AlignHorizontalLeftOutlined sx={{ fontSize: 16 }} />}
|
||||
value={value.left}
|
||||
setValue={(num) => handleChange('left', num)}
|
||||
units="px"
|
||||
step={4}
|
||||
min={0}
|
||||
max={80}
|
||||
marks
|
||||
/>
|
||||
|
||||
<RawSliderInput
|
||||
iconLabel={<AlignHorizontalRightOutlined sx={{ fontSize: 16 }} />}
|
||||
value={value.right}
|
||||
setValue={(num) => handleChange('right', num)}
|
||||
units="px"
|
||||
step={4}
|
||||
min={0}
|
||||
max={80}
|
||||
marks
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { InputLabel, Stack, ToggleButtonGroup } from '@mui/material';
|
||||
|
||||
type Props = {
|
||||
label: string | JSX.Element;
|
||||
children: JSX.Element | JSX.Element[];
|
||||
defaultValue: string;
|
||||
onChange: (v: string) => void;
|
||||
};
|
||||
export default function RadioGroupInput({ label, children, defaultValue, onChange }: Props) {
|
||||
const [value, setValue] = useState(defaultValue);
|
||||
return (
|
||||
<Stack alignItems="flex-start">
|
||||
<InputLabel shrink>{label}</InputLabel>
|
||||
<ToggleButtonGroup
|
||||
exclusive
|
||||
fullWidth
|
||||
value={value}
|
||||
size="small"
|
||||
onChange={(_, v: unknown) => {
|
||||
if (typeof v !== 'string') {
|
||||
throw new Error('RadioGroupInput can only receive string values');
|
||||
}
|
||||
setValue(v);
|
||||
onChange(v);
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</ToggleButtonGroup>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { InputLabel, Stack } from '@mui/material';
|
||||
|
||||
import RawSliderInput from './raw/RawSliderInput';
|
||||
|
||||
type SliderInputProps = {
|
||||
label: string;
|
||||
iconLabel: JSX.Element;
|
||||
|
||||
step?: number;
|
||||
marks?: boolean;
|
||||
units: string;
|
||||
min?: number;
|
||||
max?: number;
|
||||
|
||||
defaultValue: number;
|
||||
onChange: (v: number) => void;
|
||||
};
|
||||
|
||||
export default function SliderInput({ label, defaultValue, onChange, ...props }: SliderInputProps) {
|
||||
const [value, setValue] = useState(defaultValue);
|
||||
return (
|
||||
<Stack spacing={1} alignItems="flex-start">
|
||||
<InputLabel shrink>{label}</InputLabel>
|
||||
<RawSliderInput
|
||||
value={value}
|
||||
setValue={(value: number) => {
|
||||
setValue(value);
|
||||
onChange(value);
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { FormatAlignCenterOutlined, FormatAlignLeftOutlined, FormatAlignRightOutlined } from '@mui/icons-material';
|
||||
import { ToggleButton } from '@mui/material';
|
||||
|
||||
import RadioGroupInput from './RadioGroupInput';
|
||||
|
||||
type Props = {
|
||||
label: string;
|
||||
defaultValue: string | null;
|
||||
onChange: (value: string | null) => void;
|
||||
};
|
||||
export default function TextAlignInput({ label, defaultValue, onChange }: Props) {
|
||||
const [value, setValue] = useState(defaultValue ?? 'left');
|
||||
|
||||
return (
|
||||
<RadioGroupInput
|
||||
label={label}
|
||||
defaultValue={value}
|
||||
onChange={(value) => {
|
||||
setValue(value);
|
||||
onChange(value);
|
||||
}}
|
||||
>
|
||||
<ToggleButton value="left">
|
||||
<FormatAlignLeftOutlined fontSize="small" />
|
||||
</ToggleButton>
|
||||
<ToggleButton value="center">
|
||||
<FormatAlignCenterOutlined fontSize="small" />
|
||||
</ToggleButton>
|
||||
<ToggleButton value="right">
|
||||
<FormatAlignRightOutlined fontSize="small" />
|
||||
</ToggleButton>
|
||||
</RadioGroupInput>
|
||||
);
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
import React from 'react';
|
||||
|
||||
import { TextField, Typography } from '@mui/material';
|
||||
|
||||
type TextDimensionInputProps = {
|
||||
label: string;
|
||||
defaultValue: number | null | undefined;
|
||||
onChange: (v: number | null) => void;
|
||||
};
|
||||
export default function TextDimensionInput({ label, defaultValue, onChange }: TextDimensionInputProps) {
|
||||
const handleChange: React.ChangeEventHandler<HTMLInputElement> = (ev) => {
|
||||
const value = parseInt(ev.target.value);
|
||||
onChange(isNaN(value) ? null : value);
|
||||
};
|
||||
return (
|
||||
<TextField
|
||||
fullWidth
|
||||
onChange={handleChange}
|
||||
defaultValue={defaultValue}
|
||||
label={label}
|
||||
variant="standard"
|
||||
placeholder="auto"
|
||||
size="small"
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
px
|
||||
</Typography>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { InputProps, TextField } from '@mui/material';
|
||||
|
||||
type Props = {
|
||||
label: string;
|
||||
rows?: number;
|
||||
placeholder?: string;
|
||||
helperText?: string | JSX.Element;
|
||||
InputProps?: InputProps;
|
||||
defaultValue: string;
|
||||
onChange: (v: string) => void;
|
||||
};
|
||||
export default function TextInput({ helperText, label, placeholder, rows, InputProps, defaultValue, onChange }: Props) {
|
||||
const [value, setValue] = useState(defaultValue);
|
||||
const isMultiline = typeof rows === 'number' && rows > 1;
|
||||
return (
|
||||
<TextField
|
||||
fullWidth
|
||||
multiline={isMultiline}
|
||||
minRows={rows}
|
||||
variant={isMultiline ? 'outlined' : 'standard'}
|
||||
label={label}
|
||||
placeholder={placeholder}
|
||||
helperText={helperText}
|
||||
InputProps={InputProps}
|
||||
value={value}
|
||||
onChange={(ev) => {
|
||||
const v = ev.target.value;
|
||||
setValue(v);
|
||||
onChange(v);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
import React from "react";
|
||||
|
||||
import { Box, Slider, Stack, Typography } from "@mui/material";
|
||||
|
||||
type SliderInputProps = {
|
||||
iconLabel: JSX.Element;
|
||||
|
||||
step?: number;
|
||||
marks?: boolean;
|
||||
units: string;
|
||||
min?: number;
|
||||
max?: number;
|
||||
|
||||
value: number;
|
||||
setValue: (v: number) => void;
|
||||
};
|
||||
|
||||
export default function RawSliderInput({
|
||||
iconLabel,
|
||||
value,
|
||||
setValue,
|
||||
units,
|
||||
...props
|
||||
}: SliderInputProps) {
|
||||
return (
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
spacing={2}
|
||||
justifyContent="space-between"
|
||||
width="100%"
|
||||
>
|
||||
<Box sx={{ minWidth: 24, lineHeight: 1, flexShrink: 0 }}>{iconLabel}</Box>
|
||||
<Slider
|
||||
{...props}
|
||||
value={value}
|
||||
onChange={(_, value: unknown) => {
|
||||
if (typeof value !== "number") {
|
||||
throw new Error(
|
||||
"RawSliderInput values can only receive numeric values"
|
||||
);
|
||||
}
|
||||
setValue(value);
|
||||
}}
|
||||
/>
|
||||
<Box sx={{ minWidth: 32, textAlign: "right", flexShrink: 0 }}>
|
||||
<Typography
|
||||
variant="body2"
|
||||
color="text.secondary"
|
||||
sx={{ lineHeight: 1 }}
|
||||
>
|
||||
{value}
|
||||
{units}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
import React from "react";
|
||||
|
||||
import { TStyle } from "../../../../../../documents/blocks/helpers/TStyle";
|
||||
|
||||
import SingleStylePropertyPanel from "./SingleStylePropertyPanel";
|
||||
|
||||
type MultiStylePropertyPanelProps = {
|
||||
names: (keyof TStyle)[];
|
||||
value: TStyle | undefined | null;
|
||||
onChange: (style: TStyle) => void;
|
||||
};
|
||||
export default function MultiStylePropertyPanel({
|
||||
names,
|
||||
value,
|
||||
onChange
|
||||
}: MultiStylePropertyPanelProps) {
|
||||
return (
|
||||
<>
|
||||
{names.map((name) => (
|
||||
<SingleStylePropertyPanel
|
||||
key={name}
|
||||
name={name}
|
||||
value={value || {}}
|
||||
onChange={onChange}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
import React from "react";
|
||||
|
||||
import { RoundedCornerOutlined } from "@mui/icons-material";
|
||||
|
||||
import { TStyle } from "../../../../../../documents/blocks/helpers/TStyle";
|
||||
import { NullableColorInput } from "../inputs/ColorInput";
|
||||
import { NullableFontFamily } from "../inputs/FontFamily";
|
||||
import FontSizeInput from "../inputs/FontSizeInput";
|
||||
import FontWeightInput from "../inputs/FontWeightInput";
|
||||
import PaddingInput from "../inputs/PaddingInput";
|
||||
import SliderInput from "../inputs/SliderInput";
|
||||
import TextAlignInput from "../inputs/TextAlignInput";
|
||||
|
||||
type StylePropertyPanelProps = {
|
||||
name: keyof TStyle;
|
||||
value: TStyle;
|
||||
onChange: (style: TStyle) => void;
|
||||
};
|
||||
export default function SingleStylePropertyPanel({
|
||||
name,
|
||||
value,
|
||||
onChange
|
||||
}: StylePropertyPanelProps) {
|
||||
const defaultValue = value[name] ?? null;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const handleChange = (v: any) => {
|
||||
onChange({ ...value, [name]: v });
|
||||
};
|
||||
|
||||
switch (name) {
|
||||
case "backgroundColor":
|
||||
return (
|
||||
<NullableColorInput
|
||||
label="Background color"
|
||||
defaultValue={defaultValue}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
);
|
||||
case "borderColor":
|
||||
return (
|
||||
<NullableColorInput
|
||||
label="Border color"
|
||||
defaultValue={defaultValue}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
);
|
||||
case "borderRadius":
|
||||
return (
|
||||
<SliderInput
|
||||
iconLabel={<RoundedCornerOutlined />}
|
||||
units="px"
|
||||
step={4}
|
||||
marks
|
||||
min={0}
|
||||
max={48}
|
||||
label="Border radius"
|
||||
defaultValue={defaultValue}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
);
|
||||
case "color":
|
||||
return (
|
||||
<NullableColorInput
|
||||
label="Text color"
|
||||
defaultValue={defaultValue}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
);
|
||||
case "fontFamily":
|
||||
return (
|
||||
<NullableFontFamily
|
||||
label="Font family"
|
||||
defaultValue={defaultValue}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
);
|
||||
case "fontSize":
|
||||
return (
|
||||
<FontSizeInput
|
||||
label="Font size"
|
||||
defaultValue={defaultValue}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
);
|
||||
case "fontWeight":
|
||||
return (
|
||||
<FontWeightInput
|
||||
label="Font weight"
|
||||
defaultValue={defaultValue}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
);
|
||||
case "textAlign":
|
||||
return (
|
||||
<TextAlignInput
|
||||
label="Alignment"
|
||||
defaultValue={defaultValue}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
);
|
||||
case "padding":
|
||||
return (
|
||||
<PaddingInput
|
||||
label="Padding"
|
||||
defaultValue={defaultValue}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import React from "react";
|
||||
|
||||
import { setDocument, useDocument } from "../../documents/editor/EditorContext";
|
||||
|
||||
import EmailLayoutSidebarPanel from "./ConfigurationPanel/input-panels/EmailLayoutSidebarPanel";
|
||||
|
||||
export default function StylesPanel() {
|
||||
const block = useDocument().root;
|
||||
if (!block) {
|
||||
return <p>Block not found</p>;
|
||||
}
|
||||
|
||||
const { data, type } = block;
|
||||
if (type !== "EmailLayout") {
|
||||
throw new Error('Expected "root" element to be of type EmailLayout');
|
||||
}
|
||||
|
||||
return (
|
||||
<EmailLayoutSidebarPanel
|
||||
key="root"
|
||||
data={data}
|
||||
setData={(data) => setDocument({ root: { type, data } })}
|
||||
/>
|
||||
);
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
import React from "react";
|
||||
|
||||
import { AppRegistrationOutlined, LastPageOutlined } from "@mui/icons-material";
|
||||
import { IconButton } from "@mui/material";
|
||||
|
||||
import {
|
||||
toggleInspectorDrawerOpen,
|
||||
useInspectorDrawerOpen
|
||||
} from "../../documents/editor/EditorContext";
|
||||
|
||||
export default function ToggleInspectorPanelButton() {
|
||||
const inspectorDrawerOpen = useInspectorDrawerOpen();
|
||||
|
||||
const handleClick = () => {
|
||||
toggleInspectorDrawerOpen();
|
||||
};
|
||||
if (inspectorDrawerOpen) {
|
||||
return (
|
||||
<IconButton onClick={handleClick}>
|
||||
<LastPageOutlined fontSize="small" />
|
||||
</IconButton>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<IconButton onClick={handleClick}>
|
||||
<AppRegistrationOutlined fontSize="small" />
|
||||
</IconButton>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import React from "react";
|
||||
|
||||
import { Box, Drawer, Tab, Tabs } from "@mui/material";
|
||||
|
||||
import {
|
||||
setSidebarTab,
|
||||
useInspectorDrawerOpen,
|
||||
useSelectedSidebarTab
|
||||
} from "../../documents/editor/EditorContext";
|
||||
|
||||
import ConfigurationPanel from "./ConfigurationPanel";
|
||||
import StylesPanel from "./StylesPanel";
|
||||
|
||||
export const INSPECTOR_DRAWER_WIDTH = 320;
|
||||
|
||||
export default function InspectorDrawer() {
|
||||
const selectedSidebarTab = useSelectedSidebarTab();
|
||||
const inspectorDrawerOpen = useInspectorDrawerOpen();
|
||||
|
||||
const renderCurrentSidebarPanel = () => {
|
||||
switch (selectedSidebarTab) {
|
||||
case "block-configuration":
|
||||
return <ConfigurationPanel />;
|
||||
case "styles":
|
||||
return <StylesPanel />;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
variant="persistent"
|
||||
anchor="right"
|
||||
open={inspectorDrawerOpen}
|
||||
sx={{
|
||||
width: inspectorDrawerOpen ? INSPECTOR_DRAWER_WIDTH : 0
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: INSPECTOR_DRAWER_WIDTH,
|
||||
height: 49,
|
||||
borderBottom: 1,
|
||||
borderColor: "divider"
|
||||
}}
|
||||
>
|
||||
<Box px={2}>
|
||||
<Tabs
|
||||
value={selectedSidebarTab}
|
||||
onChange={(_, v) => setSidebarTab(v)}
|
||||
>
|
||||
<Tab value="styles" label="Styles" />
|
||||
<Tab value="block-configuration" label="Inspect" />
|
||||
</Tabs>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
width: INSPECTOR_DRAWER_WIDTH,
|
||||
height: "calc(100% - 49px)",
|
||||
overflow: "auto"
|
||||
}}
|
||||
>
|
||||
{renderCurrentSidebarPanel()}
|
||||
</Box>
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import React from "react";
|
||||
|
||||
import { Button } from "@mui/material";
|
||||
|
||||
import { resetDocument } from "../../documents/editor/EditorContext";
|
||||
import getConfiguration from "../../getConfiguration";
|
||||
|
||||
export default function SidebarButton({
|
||||
href,
|
||||
children
|
||||
}: {
|
||||
href: string;
|
||||
children: JSX.Element | string;
|
||||
}) {
|
||||
const handleClick = () => {
|
||||
resetDocument(getConfiguration(href));
|
||||
};
|
||||
return (
|
||||
<Button size="small" href={href} onClick={handleClick}>
|
||||
{children}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
import React from "react";
|
||||
|
||||
import { FirstPageOutlined, MenuOutlined } from "@mui/icons-material";
|
||||
import { IconButton } from "@mui/material";
|
||||
|
||||
import {
|
||||
toggleSamplesDrawerOpen,
|
||||
useSamplesDrawerOpen
|
||||
} from "../../documents/editor/EditorContext";
|
||||
|
||||
function useIcon() {
|
||||
const samplesDrawerOpen = useSamplesDrawerOpen();
|
||||
if (samplesDrawerOpen) {
|
||||
return <FirstPageOutlined fontSize="small" />;
|
||||
}
|
||||
return <MenuOutlined fontSize="small" />;
|
||||
}
|
||||
|
||||
export default function ToggleSamplesPanelButton() {
|
||||
const icon = useIcon();
|
||||
return <IconButton onClick={toggleSamplesDrawerOpen}>{icon}</IconButton>;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import React from "react";
|
||||
import { Divider, Drawer, Stack, Typography } from "@mui/material";
|
||||
import { useSamplesDrawerOpen } from "../../documents/editor/EditorContext";
|
||||
import SidebarButton from "./SidebarButton";
|
||||
|
||||
export const SAMPLES_DRAWER_WIDTH = 240;
|
||||
|
||||
export default function SamplesDrawer() {
|
||||
const samplesDrawerOpen = useSamplesDrawerOpen();
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
variant="persistent"
|
||||
anchor="left"
|
||||
open={samplesDrawerOpen}
|
||||
sx={{ width: samplesDrawerOpen ? SAMPLES_DRAWER_WIDTH : 0 }}
|
||||
>
|
||||
<Stack
|
||||
spacing={3}
|
||||
py={1}
|
||||
px={2}
|
||||
width={SAMPLES_DRAWER_WIDTH}
|
||||
justifyContent="space-between"
|
||||
height="100%"
|
||||
>
|
||||
<Stack
|
||||
spacing={2}
|
||||
sx={{
|
||||
"& .MuiButtonBase-root": {
|
||||
width: "100%",
|
||||
justifyContent: "flex-start"
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Typography variant="h6" component="h1" sx={{ p: 0.75 }}>
|
||||
EmailBuilder
|
||||
</Typography>
|
||||
|
||||
<Stack alignItems="flex-start">
|
||||
<SidebarButton href="#">Empty</SidebarButton>
|
||||
<SidebarButton href="#sample/requestemail">
|
||||
Request Email
|
||||
</SidebarButton>
|
||||
<SidebarButton href="#sample/completionemail">
|
||||
Completion Email
|
||||
</SidebarButton>
|
||||
</Stack>
|
||||
|
||||
<Divider />
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import React, { useMemo } from "react";
|
||||
|
||||
import { FileDownloadOutlined } from "@mui/icons-material";
|
||||
import { IconButton, Tooltip } from "@mui/material";
|
||||
|
||||
import { useDocument } from "../../../documents/editor/EditorContext";
|
||||
// import { renderToStaticMarkup } from "@usewaypoint/email-builder";
|
||||
import renderEmailHtml from "../helper/renderEmailHtml";
|
||||
|
||||
export default function DownloadJson() {
|
||||
const doc = useDocument();
|
||||
// const code = useMemo(
|
||||
// () => renderToStaticMarkup(doc, { rootBlockId: "root" }),
|
||||
// [doc]
|
||||
// );
|
||||
|
||||
const code = useMemo(() => renderEmailHtml(doc, "root"), [doc]);
|
||||
|
||||
const href = useMemo(() => {
|
||||
const blob = new Blob([code], { type: "text/html;charset=utf-8" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
return url;
|
||||
}, [doc]);
|
||||
return (
|
||||
<Tooltip title="Download html of template in txt file">
|
||||
<IconButton href={href} download="emailTemplate.txt">
|
||||
<FileDownloadOutlined fontSize="small" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import React, { useMemo } from "react";
|
||||
|
||||
// import { renderToStaticMarkup } from "@usewaypoint/email-builder";
|
||||
|
||||
import { useDocument } from "../../documents/editor/EditorContext";
|
||||
|
||||
import HighlightedCodePanel from "./helper/HighlightedCodePanel";
|
||||
import renderEmailHtml from "./helper/renderEmailHtml";
|
||||
|
||||
export default function HtmlPanel() {
|
||||
const document = useDocument();
|
||||
// const code = useMemo(
|
||||
// () => renderToStaticMarkup(document, { rootBlockId: "root" }),
|
||||
// [document]
|
||||
// );
|
||||
const code = useMemo(() => renderEmailHtml(document, "root"), [document]);
|
||||
return <HighlightedCodePanel type="html" value={code} />;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import React, { useMemo } from "react";
|
||||
|
||||
import { useDocument } from "../../documents/editor/EditorContext";
|
||||
|
||||
import HighlightedCodePanel from "./helper/HighlightedCodePanel";
|
||||
|
||||
export default function JsonPanel() {
|
||||
const document = useDocument();
|
||||
const code = useMemo(() => JSON.stringify(document, null, " "), [document]);
|
||||
return <HighlightedCodePanel type="json" value={code} />;
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import React from "react";
|
||||
// DataObjectOutlined,
|
||||
import {
|
||||
CodeOutlined,
|
||||
EditOutlined,
|
||||
PreviewOutlined
|
||||
} from "@mui/icons-material";
|
||||
import { Tab, Tabs, Tooltip } from "@mui/material";
|
||||
|
||||
import {
|
||||
setSelectedMainTab,
|
||||
useSelectedMainTab
|
||||
} from "../../documents/editor/EditorContext";
|
||||
|
||||
export default function MainTabsGroup() {
|
||||
const selectedMainTab = useSelectedMainTab();
|
||||
const handleChange = (_: unknown, v: unknown) => {
|
||||
switch (v) {
|
||||
case "json":
|
||||
case "preview":
|
||||
case "editor":
|
||||
case "html":
|
||||
setSelectedMainTab(v);
|
||||
return;
|
||||
default:
|
||||
setSelectedMainTab("editor");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Tabs value={selectedMainTab} onChange={handleChange}>
|
||||
<Tab
|
||||
value="editor"
|
||||
label={
|
||||
<Tooltip title="Edit">
|
||||
<EditOutlined fontSize="small" />
|
||||
</Tooltip>
|
||||
}
|
||||
/>
|
||||
<Tab
|
||||
value="preview"
|
||||
label={
|
||||
<Tooltip title="Preview">
|
||||
<PreviewOutlined fontSize="small" />
|
||||
</Tooltip>
|
||||
}
|
||||
/>
|
||||
<Tab
|
||||
value="html"
|
||||
label={
|
||||
<Tooltip title="HTML output">
|
||||
<CodeOutlined fontSize="small" />
|
||||
</Tooltip>
|
||||
}
|
||||
/>
|
||||
{/* <Tab
|
||||
value="json"
|
||||
label={
|
||||
<Tooltip title="JSON output">
|
||||
<DataObjectOutlined fontSize="small" />
|
||||
</Tooltip>
|
||||
}
|
||||
/> */}
|
||||
</Tabs>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
import React, { useMemo, useState } from "react";
|
||||
import { ContentCopyOutlined } from "@mui/icons-material";
|
||||
import { IconButton, Snackbar, Tooltip } from "@mui/material";
|
||||
|
||||
import { useDocument } from "../../documents/editor/EditorContext";
|
||||
// import { renderToStaticMarkup } from "@usewaypoint/email-builder";
|
||||
import renderEmailHtml from "./helper/renderEmailHtml";
|
||||
|
||||
export default function ShareButton() {
|
||||
const document = useDocument();
|
||||
// const code = useMemo(
|
||||
// () => renderToStaticMarkup(document, { rootBlockId: "root" }),
|
||||
// [document]
|
||||
// );
|
||||
const code = useMemo(() => renderEmailHtml(document, "root"), [document]);
|
||||
const [message, setMessage] = useState<string | null>(null);
|
||||
|
||||
const onClick = async () => {
|
||||
await copyToClipboard(code);
|
||||
setMessage("Your current template is copied");
|
||||
setTimeout(() => {
|
||||
setMessage(null);
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
async function copyToClipboard(text: string): Promise<boolean> {
|
||||
// 1) Try modern Clipboard API
|
||||
try {
|
||||
if (navigator.clipboard?.writeText) {
|
||||
await navigator.clipboard.writeText(text);
|
||||
return true;
|
||||
}
|
||||
} catch {
|
||||
// Fall through to legacy approach
|
||||
}
|
||||
|
||||
// 2) Fallback: execCommand('copy')
|
||||
try {
|
||||
const textarea = document.createElement("textarea");
|
||||
textarea.value = text;
|
||||
|
||||
// Prevent scrolling to bottom on iOS
|
||||
textarea.style.position = "fixed";
|
||||
textarea.style.top = "0";
|
||||
textarea.style.left = "0";
|
||||
textarea.style.opacity = "0";
|
||||
textarea.style.pointerEvents = "none";
|
||||
|
||||
document.body.appendChild(textarea);
|
||||
textarea.focus();
|
||||
textarea.select();
|
||||
|
||||
const ok = document.execCommand("copy");
|
||||
document.body.removeChild(textarea);
|
||||
|
||||
return ok;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const onClose = () => {
|
||||
setMessage(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* <IconButton onClick={onClick}>
|
||||
<Tooltip title="Share current template">
|
||||
<IosShareOutlined fontSize="small" />
|
||||
</Tooltip>
|
||||
</IconButton> */}
|
||||
<IconButton onClick={onClick} sx={{ color: "text.primary" }}>
|
||||
<Tooltip title="copy" placement="left-start">
|
||||
<ContentCopyOutlined fontSize="small" />
|
||||
</Tooltip>
|
||||
</IconButton>
|
||||
<Snackbar
|
||||
anchorOrigin={{ vertical: "top", horizontal: "center" }}
|
||||
open={message !== null}
|
||||
onClose={onClose}
|
||||
message={message}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
import { html, json } from "./highlighters";
|
||||
|
||||
type TextEditorPanelProps = {
|
||||
type: "json" | "html" | "javascript";
|
||||
value: string;
|
||||
};
|
||||
export default function HighlightedCodePanel({
|
||||
type,
|
||||
value
|
||||
}: TextEditorPanelProps) {
|
||||
const [code, setCode] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
switch (type) {
|
||||
case "html":
|
||||
html(value).then(setCode);
|
||||
return;
|
||||
case "json":
|
||||
json(value).then(setCode);
|
||||
return;
|
||||
}
|
||||
}, [setCode, value, type]);
|
||||
|
||||
if (code === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<pre
|
||||
style={{ margin: 0, padding: 16 }}
|
||||
dangerouslySetInnerHTML={{ __html: code }}
|
||||
onClick={(ev) => {
|
||||
const s = window.getSelection();
|
||||
if (s === null) {
|
||||
return;
|
||||
}
|
||||
s.selectAllChildren(ev.currentTarget);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import hljs from "highlight.js";
|
||||
import jsonHighlighter from "highlight.js/lib/languages/json";
|
||||
import xmlHighlighter from "highlight.js/lib/languages/xml";
|
||||
import prettierPluginBabel from "prettier/plugins/babel";
|
||||
import prettierPluginEstree from "prettier/plugins/estree";
|
||||
import prettierPluginHtml from "prettier/plugins/html";
|
||||
import { format } from "prettier/standalone";
|
||||
|
||||
hljs.registerLanguage("json", jsonHighlighter);
|
||||
hljs.registerLanguage("html", xmlHighlighter);
|
||||
|
||||
export async function html(value: string): Promise<string> {
|
||||
const prettyValue = await format(value, {
|
||||
parser: "html",
|
||||
plugins: [prettierPluginHtml]
|
||||
});
|
||||
return hljs.highlight(prettyValue, { language: "html" }).value;
|
||||
}
|
||||
|
||||
export async function json(value: string): Promise<string> {
|
||||
const prettyValue = await format(value, {
|
||||
parser: "json",
|
||||
printWidth: 0,
|
||||
trailingComma: "all",
|
||||
plugins: [prettierPluginBabel, prettierPluginEstree]
|
||||
});
|
||||
return hljs.highlight(prettyValue, { language: "javascript" }).value;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import {
|
||||
renderToStaticMarkup
|
||||
} from "@usewaypoint/email-builder";
|
||||
import { TEditorConfiguration } from "../../../documents/editor/core";
|
||||
|
||||
function overrideCanvasSize(
|
||||
html: string,
|
||||
document: TEditorConfiguration,
|
||||
rootBlockId: string
|
||||
) {
|
||||
const rootBlock = document[rootBlockId];
|
||||
if (!rootBlock || rootBlock.type !== "EmailLayout") {
|
||||
return html;
|
||||
}
|
||||
|
||||
const data = rootBlock.data as { canvasWidth?: number | null };
|
||||
|
||||
const canvasWidth =
|
||||
typeof data?.canvasWidth === "number" && data.canvasWidth > 0
|
||||
? data.canvasWidth
|
||||
: 600;
|
||||
|
||||
let updatedHtml = html.replace(
|
||||
/max-width:\s*600px/gi,
|
||||
`max-width:${canvasWidth}px`
|
||||
);
|
||||
|
||||
return updatedHtml;
|
||||
}
|
||||
|
||||
export default function renderEmailHtml(
|
||||
document: TEditorConfiguration,
|
||||
rootBlockId = "root"
|
||||
) {
|
||||
const html = renderToStaticMarkup(document, { rootBlockId });
|
||||
return overrideCanvasSize(html, document, rootBlockId);
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
import React, { useMemo } from "react";
|
||||
|
||||
import { MonitorOutlined, PhoneIphoneOutlined } from "@mui/icons-material";
|
||||
import {
|
||||
Box,
|
||||
Stack,
|
||||
SxProps,
|
||||
ToggleButton,
|
||||
ToggleButtonGroup,
|
||||
Tooltip
|
||||
} from "@mui/material";
|
||||
import { Reader } from "@usewaypoint/email-builder";
|
||||
|
||||
import EditorBlock from "../../documents/editor/EditorBlock";
|
||||
import {
|
||||
setSelectedScreenSize,
|
||||
useDocument,
|
||||
useSelectedMainTab,
|
||||
useSelectedScreenSize
|
||||
} from "../../documents/editor/EditorContext";
|
||||
import ToggleInspectorPanelButton from "../InspectorDrawer/ToggleInspectorPanelButton";
|
||||
import ToggleSamplesPanelButton from "../SamplesDrawer/ToggleSamplesPanelButton";
|
||||
|
||||
import DownloadJson from "./DownloadJson";
|
||||
import HtmlPanel from "./HtmlPanel";
|
||||
import JsonPanel from "./JsonPanel";
|
||||
import MainTabsGroup from "./MainTabsGroup";
|
||||
import ShareButton from "./ShareButton";
|
||||
import renderEmailHtml from "./helper/renderEmailHtml";
|
||||
|
||||
export default function TemplatePanel() {
|
||||
const document = useDocument();
|
||||
const selectedMainTab = useSelectedMainTab();
|
||||
const selectedScreenSize = useSelectedScreenSize();
|
||||
const previewHtml = useMemo(
|
||||
() => renderEmailHtml(document, "root"),
|
||||
[document]
|
||||
);
|
||||
|
||||
let mainBoxSx: SxProps = { height: "100%" };
|
||||
if (selectedScreenSize === "mobile") {
|
||||
mainBoxSx = {
|
||||
...mainBoxSx,
|
||||
margin: "32px auto",
|
||||
width: 370,
|
||||
height: 800,
|
||||
boxShadow:
|
||||
"rgba(33, 36, 67, 0.04) 0px 10px 20px, rgba(33, 36, 67, 0.04) 0px 2px 6px, rgba(33, 36, 67, 0.04) 0px 0px 1px"
|
||||
};
|
||||
}
|
||||
|
||||
const handleScreenSizeChange = (_: unknown, value: unknown) => {
|
||||
switch (value) {
|
||||
case "mobile":
|
||||
case "desktop":
|
||||
setSelectedScreenSize(value);
|
||||
return;
|
||||
default:
|
||||
setSelectedScreenSize("desktop");
|
||||
}
|
||||
};
|
||||
|
||||
const renderMainPanel = () => {
|
||||
switch (selectedMainTab) {
|
||||
case "editor":
|
||||
return (
|
||||
<Box sx={mainBoxSx}>
|
||||
<EditorBlock id="root" />
|
||||
</Box>
|
||||
);
|
||||
case "preview":
|
||||
return (
|
||||
<Box sx={mainBoxSx}>
|
||||
<iframe
|
||||
title="Email preview"
|
||||
srcDoc={previewHtml}
|
||||
sandbox="allow-popups allow-popups-to-escape-sandbox"
|
||||
style={{ width: "100%", height: "100%", border: "none" }}
|
||||
/>
|
||||
{/* <Reader document={document} rootBlockId="root" /> */}
|
||||
</Box>
|
||||
);
|
||||
case "html":
|
||||
return <HtmlPanel />;
|
||||
case "json":
|
||||
return <JsonPanel />;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack
|
||||
sx={{
|
||||
height: 49,
|
||||
borderBottom: 1,
|
||||
borderColor: "divider",
|
||||
backgroundColor: "white",
|
||||
position: "sticky",
|
||||
top: 0,
|
||||
zIndex: "appBar",
|
||||
px: 1
|
||||
}}
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
>
|
||||
<ToggleSamplesPanelButton />
|
||||
<Stack
|
||||
px={2}
|
||||
direction="row"
|
||||
gap={2}
|
||||
width="100%"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
>
|
||||
<Stack direction="row" spacing={2}>
|
||||
<MainTabsGroup />
|
||||
</Stack>
|
||||
<Stack direction="row" spacing={2}>
|
||||
<DownloadJson />
|
||||
<ToggleButtonGroup
|
||||
value={selectedScreenSize}
|
||||
exclusive
|
||||
size="small"
|
||||
onChange={handleScreenSizeChange}
|
||||
>
|
||||
<ToggleButton value="desktop">
|
||||
<Tooltip title="Desktop view">
|
||||
<MonitorOutlined fontSize="small" />
|
||||
</Tooltip>
|
||||
</ToggleButton>
|
||||
<ToggleButton value="mobile">
|
||||
<Tooltip title="Mobile view">
|
||||
<PhoneIphoneOutlined fontSize="small" />
|
||||
</Tooltip>
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
<ShareButton />
|
||||
</Stack>
|
||||
</Stack>
|
||||
<ToggleInspectorPanelButton />
|
||||
</Stack>
|
||||
<Box
|
||||
sx={{ height: "calc(100vh - 49px)", overflow: "auto", minWidth: 370 }}
|
||||
>
|
||||
{renderMainPanel()}
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import {
|
||||
useState
|
||||
} from "react";
|
||||
import { Stack, useTheme } from "@mui/material";
|
||||
import {
|
||||
useInspectorDrawerOpen,
|
||||
useSamplesDrawerOpen
|
||||
} from "../documents/editor/EditorContext";
|
||||
import InspectorDrawer, { INSPECTOR_DRAWER_WIDTH } from "./InspectorDrawer";
|
||||
import SamplesDrawer, { SAMPLES_DRAWER_WIDTH } from "./SamplesDrawer";
|
||||
import TemplatePanel from "./TemplatePanel";
|
||||
|
||||
function useDrawerTransition(
|
||||
cssProperty: "margin-left" | "margin-right",
|
||||
open: boolean
|
||||
) {
|
||||
const { transitions } = useTheme();
|
||||
return transitions.create(cssProperty, {
|
||||
easing: !open ? transitions.easing.sharp : transitions.easing.easeOut,
|
||||
duration: !open
|
||||
? transitions.duration.leavingScreen
|
||||
: transitions.duration.enteringScreen
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
export default function EmailBuilderApp() {
|
||||
const inspectorDrawerOpen = useInspectorDrawerOpen();
|
||||
const samplesDrawerOpen = useSamplesDrawerOpen();
|
||||
|
||||
const marginLeftTransition = useDrawerTransition(
|
||||
"margin-left",
|
||||
samplesDrawerOpen
|
||||
);
|
||||
const marginRightTransition = useDrawerTransition(
|
||||
"margin-right",
|
||||
inspectorDrawerOpen
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<InspectorDrawer />
|
||||
<SamplesDrawer />
|
||||
<Stack
|
||||
sx={{
|
||||
marginRight: inspectorDrawerOpen ? `${INSPECTOR_DRAWER_WIDTH}px` : 0,
|
||||
marginLeft: samplesDrawerOpen ? `${SAMPLES_DRAWER_WIDTH}px` : 0,
|
||||
transition: [marginLeftTransition, marginRightTransition].join(", ")
|
||||
}}
|
||||
>
|
||||
<TemplatePanel />
|
||||
</Stack>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { CssBaseline, ThemeProvider } from "@mui/material";
|
||||
|
||||
import App from "./App";
|
||||
import theme from "./EmailBuildertheme";
|
||||
|
||||
const EmailBuilder = () => {
|
||||
return (
|
||||
<ThemeProvider theme={theme}>
|
||||
<CssBaseline />
|
||||
<App />
|
||||
</ThemeProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default EmailBuilder;
|
||||
@@ -0,0 +1,520 @@
|
||||
import { alpha, createTheme, darken, lighten } from "@mui/material/styles";
|
||||
|
||||
const BRAND_NAVY = "#212443";
|
||||
const BRAND_BLUE = "#0079CC";
|
||||
const BRAND_GREEN = "#1F8466";
|
||||
const BRAND_RED = "#E81212";
|
||||
const BRAND_YELLOW = "#F6DC9F";
|
||||
const BRAND_PURPLE = "#6C0E7C";
|
||||
const BRAND_BROWN = "#CC996C";
|
||||
const STANDARD_FONT_FAMILY =
|
||||
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"';
|
||||
const MONOSPACE_FONT_FAMILY =
|
||||
'ui-monospace, Menlo, Monaco, "Cascadia Mono", "Segoe UI Mono", "Roboto Mono", "Oxygen Mono", "Ubuntu Monospace", "Source Code Pro", "Fira Mono", "Droid Sans Mono", "Courier New", monospace';
|
||||
|
||||
const BASE_THEME = createTheme({
|
||||
palette: {
|
||||
background: {
|
||||
default: "#f2f5f7"
|
||||
},
|
||||
text: {
|
||||
primary: "#1F1F21",
|
||||
secondary: "#4F4F4F"
|
||||
}
|
||||
},
|
||||
typography: {
|
||||
fontFamily: STANDARD_FONT_FAMILY
|
||||
}
|
||||
});
|
||||
|
||||
const THEME = createTheme(BASE_THEME, {
|
||||
palette: {
|
||||
brand: {
|
||||
navy: BRAND_NAVY,
|
||||
blue: BRAND_BLUE,
|
||||
red: BRAND_RED,
|
||||
green: BRAND_GREEN,
|
||||
yellow: BRAND_YELLOW,
|
||||
purple: BRAND_PURPLE,
|
||||
brown: BRAND_BROWN
|
||||
},
|
||||
success: {
|
||||
main: BRAND_GREEN,
|
||||
light: lighten(BRAND_GREEN, 0.15),
|
||||
dark: darken(BRAND_GREEN, 0.15)
|
||||
},
|
||||
error: {
|
||||
main: BRAND_RED,
|
||||
light: lighten(BRAND_RED, 0.15),
|
||||
dark: darken(BRAND_RED, 0.15)
|
||||
},
|
||||
cadet: {
|
||||
100: "#F9FAFB",
|
||||
200: "#F2F5F7",
|
||||
300: "#DCE4EA",
|
||||
400: "#A8BBCA",
|
||||
500: "#6A8BA4"
|
||||
},
|
||||
highlight: {
|
||||
100: lighten(BRAND_YELLOW, 0.8),
|
||||
200: lighten(BRAND_YELLOW, 0.6),
|
||||
300: lighten(BRAND_YELLOW, 0.4),
|
||||
400: lighten(BRAND_YELLOW, 0.2),
|
||||
500: BRAND_YELLOW
|
||||
},
|
||||
info: {
|
||||
main: BRAND_BLUE
|
||||
},
|
||||
primary: {
|
||||
main: BRAND_BLUE
|
||||
}
|
||||
},
|
||||
components: {
|
||||
MuiCssBaseline: {
|
||||
styleOverrides: `
|
||||
address {
|
||||
font-style: normal;
|
||||
}
|
||||
fieldset {
|
||||
border: none;
|
||||
padding: 0;
|
||||
}
|
||||
pre {
|
||||
font-family: ${MONOSPACE_FONT_FAMILY}
|
||||
white-space: pre-wrap;
|
||||
font-size: 12px;
|
||||
}
|
||||
`
|
||||
},
|
||||
MuiAlert: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
fontSize: BASE_THEME.typography.pxToRem(14)
|
||||
},
|
||||
action: {
|
||||
paddingTop: 0,
|
||||
marginRight: 0
|
||||
},
|
||||
filledSuccess: {
|
||||
backgroundColor: BRAND_GREEN
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiStepLabel: {
|
||||
styleOverrides: {
|
||||
label: {
|
||||
fontWeight: BASE_THEME.typography.fontWeightMedium
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiDialog: {
|
||||
defaultProps: {
|
||||
fullWidth: true
|
||||
}
|
||||
},
|
||||
MuiDialogContent: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
paddingTop: BASE_THEME.spacing(1),
|
||||
paddingBottom: BASE_THEME.spacing(2)
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiDialogTitle: {
|
||||
defaultProps: {
|
||||
variant: "h4"
|
||||
},
|
||||
styleOverrides: {
|
||||
root: {
|
||||
paddingTop: BASE_THEME.spacing(3),
|
||||
paddingBottom: BASE_THEME.spacing(1)
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiDialogActions: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
borderTop: "1px solid",
|
||||
borderTopColor: BASE_THEME.palette.divider,
|
||||
marginTop: BASE_THEME.spacing(2.5),
|
||||
padding: `${BASE_THEME.spacing(1.5)} ${BASE_THEME.spacing(3)}`
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiTableCell: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
...BASE_THEME.typography.body2,
|
||||
borderColor: BASE_THEME.palette.grey[200]
|
||||
},
|
||||
head: {
|
||||
...BASE_THEME.typography.overline,
|
||||
fontWeight: BASE_THEME.typography.fontWeightMedium,
|
||||
letterSpacing: "0.075em",
|
||||
color: BASE_THEME.palette.text.secondary
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiTableRow: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
"&:last-child td": {
|
||||
borderBottom: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiAvatar: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
textTransform: "uppercase",
|
||||
fontSize: BASE_THEME.typography.pxToRem(14)
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiChip: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
"&.MuiChip-filledError, &.MuiChip-filledSuccess": {
|
||||
fill: BASE_THEME.palette.primary.contrastText
|
||||
}
|
||||
},
|
||||
sizeSmall: {
|
||||
borderRadius: BASE_THEME.spacing(0.5),
|
||||
fontSize: 12
|
||||
},
|
||||
iconSmall: {
|
||||
fontSize: 14,
|
||||
marginLeft: BASE_THEME.spacing(1)
|
||||
},
|
||||
colorSecondary: {
|
||||
borderColor: BASE_THEME.palette.grey[400],
|
||||
color: BASE_THEME.palette.text.secondary
|
||||
},
|
||||
label: {
|
||||
fontWeight: BASE_THEME.typography.fontWeightMedium
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiDrawer: {
|
||||
defaultProps: {
|
||||
PaperProps: {
|
||||
elevation: 2
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiTooltip: {
|
||||
styleOverrides: {
|
||||
tooltip: {
|
||||
fontSize: BASE_THEME.typography.pxToRem(12),
|
||||
backgroundColor: alpha(BASE_THEME.palette.text.primary, 0.9)
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiSlider: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
height: 1
|
||||
},
|
||||
track: {
|
||||
height: 1,
|
||||
border: "none"
|
||||
},
|
||||
rail: {
|
||||
height: 1,
|
||||
backgroundColor: BASE_THEME.palette.grey[500]
|
||||
},
|
||||
mark: {
|
||||
backgroundColor: BASE_THEME.palette.grey[500]
|
||||
},
|
||||
markActive: {
|
||||
height: 0
|
||||
},
|
||||
thumb: {
|
||||
height: 16,
|
||||
width: 16,
|
||||
cursor: "col-resize",
|
||||
"&:hover, &.Mui-active, &.Mui-focusVisible": {
|
||||
boxShadow: `0 0 0 4px ${alpha(BRAND_BLUE, 0.2)}`
|
||||
},
|
||||
"&:before": {
|
||||
display: "none"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiPaper: {
|
||||
defaultProps: {
|
||||
elevation: 2,
|
||||
square: true
|
||||
}
|
||||
},
|
||||
MuiButtonBase: {
|
||||
defaultProps: {
|
||||
disableTouchRipple: true,
|
||||
focusRipple: true
|
||||
},
|
||||
styleOverrides: {
|
||||
root: {
|
||||
"&.MuiButton-containedSecondary.Mui-disabled": {
|
||||
backgroundColor: BASE_THEME.palette.grey[100]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiButtonGroup: {
|
||||
defaultProps: {
|
||||
disableElevation: true
|
||||
}
|
||||
},
|
||||
MuiIconButton: {
|
||||
styleOverrides: {
|
||||
edgeStart: {
|
||||
marginLeft: BASE_THEME.spacing(-1)
|
||||
},
|
||||
colorSecondary: {
|
||||
color: BASE_THEME.palette.grey[500]
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiButton: {
|
||||
defaultProps: {
|
||||
disableElevation: true
|
||||
},
|
||||
styleOverrides: {
|
||||
textPrimary: {
|
||||
color: BASE_THEME.palette.text.primary
|
||||
},
|
||||
textSecondary: {
|
||||
color: BASE_THEME.palette.text.secondary
|
||||
},
|
||||
outlinedPrimary: {
|
||||
borderColor: BASE_THEME.palette.grey[300],
|
||||
color: BASE_THEME.palette.text.primary,
|
||||
"&:hover, &:active, &:focus": {
|
||||
borderColor: BASE_THEME.palette.grey[500],
|
||||
color: BASE_THEME.palette.text.primary
|
||||
}
|
||||
},
|
||||
containedSecondary: {
|
||||
backgroundColor: BASE_THEME.palette.common.white,
|
||||
border: `1px solid ${BASE_THEME.palette.grey[300]}`,
|
||||
color: BASE_THEME.palette.text.primary,
|
||||
"&:hover, &:active, &:focus": {
|
||||
backgroundColor: BASE_THEME.palette.common.white,
|
||||
borderColor: BASE_THEME.palette.grey[500],
|
||||
color: BASE_THEME.palette.text.primary
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiToggleButton: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
paddingLeft: BASE_THEME.spacing(1.5),
|
||||
paddingRight: BASE_THEME.spacing(1.5)
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiInputBase: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
"&:not(.Mui-disabled, .Mui-error):before": {
|
||||
borderBottom: `1px solid ${BASE_THEME.palette.grey[400]}`
|
||||
},
|
||||
"&:hover:not(.Mui-disabled, .Mui-error):before": {
|
||||
borderBottom: `1px solid ${BASE_THEME.palette.grey[500]} !important`
|
||||
},
|
||||
"&:after": {
|
||||
borderBottom: `1px solid ${BASE_THEME.palette.text.primary} !important`
|
||||
},
|
||||
"&.MuiOutlinedInput-root:not(.Mui-error)": {
|
||||
"& fieldset": {
|
||||
borderColor: BASE_THEME.palette.grey[300],
|
||||
transition: "border-color 0.2s"
|
||||
}
|
||||
},
|
||||
"&.MuiOutlinedInput-root:not(.Mui-disabled, .Mui-error)": {
|
||||
"&:hover fieldset": {
|
||||
borderColor: BASE_THEME.palette.grey[400]
|
||||
},
|
||||
"&.Mui-focused fieldset": {
|
||||
borderColor: BASE_THEME.palette.text.secondary,
|
||||
borderWidth: 1
|
||||
}
|
||||
}
|
||||
},
|
||||
input: {
|
||||
fontSize: BASE_THEME.typography.pxToRem(14),
|
||||
"&.Mui-disabled": {
|
||||
WebkitTextFillColor: "inherit",
|
||||
color: BASE_THEME.palette.text.secondary
|
||||
}
|
||||
},
|
||||
inputSizeSmall: {}
|
||||
}
|
||||
},
|
||||
MuiOutlinedInput: {
|
||||
styleOverrides: {
|
||||
notchedOutline: {
|
||||
"& legend": {
|
||||
fontSize: "0.85em",
|
||||
maxWidth: "100%"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiInputAdornment: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
"& .MuiTypography-root": {
|
||||
fontSize: BASE_THEME.typography.pxToRem(14),
|
||||
color: BASE_THEME.palette.text.secondary
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiInputLabel: {
|
||||
defaultProps: {
|
||||
shrink: true
|
||||
},
|
||||
styleOverrides: {
|
||||
shrink: {
|
||||
transform: "scale(0.85)",
|
||||
fontWeight: BASE_THEME.typography.fontWeightMedium,
|
||||
"&.Mui-focused": {
|
||||
color: BASE_THEME.palette.text.primary
|
||||
},
|
||||
"&.MuiInputLabel-standard": {
|
||||
transform: "translate(0, -4px) scale(0.85)",
|
||||
color: "#4F4F4F"
|
||||
},
|
||||
"&.MuiInputLabel-outlined": {
|
||||
transform: "translate(15px, -8px) scale(0.85)"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiTabs: {
|
||||
defaultProps: {
|
||||
variant: "scrollable"
|
||||
},
|
||||
styleOverrides: {
|
||||
indicator: {
|
||||
height: 1,
|
||||
backgroundColor: BASE_THEME.palette.text.primary
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiTab: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
textTransform: "none",
|
||||
minWidth: BASE_THEME.spacing(2),
|
||||
paddingLeft: BASE_THEME.spacing(1.5),
|
||||
paddingRight: BASE_THEME.spacing(1.5),
|
||||
fontSize: BASE_THEME.typography.pxToRem(14),
|
||||
fontFamily: BASE_THEME.typography.fontFamily,
|
||||
lineHeight: 1.5,
|
||||
fontWeight: BASE_THEME.typography.fontWeightMedium,
|
||||
transition: "color 0.2s",
|
||||
"&.Mui-selected": {
|
||||
color: BASE_THEME.palette.text.primary
|
||||
},
|
||||
"&:hover": {
|
||||
color: BASE_THEME.palette.text.primary
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiCard: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
borderRadius: 0
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiCardHeader: {
|
||||
styleOverrides: {
|
||||
title: {
|
||||
fontSize: BASE_THEME.typography.pxToRem(18),
|
||||
fontWeight: BASE_THEME.typography.fontWeightMedium
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
typography: {
|
||||
fontFamily: BASE_THEME.typography.fontFamily,
|
||||
h1: {
|
||||
fontFamily: BASE_THEME.typography.fontFamily,
|
||||
fontSize: BASE_THEME.typography.pxToRem(40),
|
||||
lineHeight: 1.2,
|
||||
letterSpacing: "-0.02em",
|
||||
fontWeight: BASE_THEME.typography.fontWeightMedium
|
||||
},
|
||||
h2: {
|
||||
fontFamily: BASE_THEME.typography.fontFamily,
|
||||
fontSize: BASE_THEME.typography.pxToRem(32),
|
||||
lineHeight: 1.2,
|
||||
letterSpacing: "-0.02em",
|
||||
fontWeight: BASE_THEME.typography.fontWeightMedium
|
||||
},
|
||||
h3: {
|
||||
fontFamily: BASE_THEME.typography.fontFamily,
|
||||
fontSize: BASE_THEME.typography.pxToRem(24),
|
||||
lineHeight: 1.5,
|
||||
letterSpacing: "-0.01em",
|
||||
fontWeight: BASE_THEME.typography.fontWeightMedium
|
||||
},
|
||||
h4: {
|
||||
fontFamily: BASE_THEME.typography.fontFamily,
|
||||
fontSize: BASE_THEME.typography.pxToRem(20),
|
||||
lineHeight: 1.5,
|
||||
letterSpacing: "-0.01em",
|
||||
fontWeight: BASE_THEME.typography.fontWeightMedium
|
||||
},
|
||||
h5: {
|
||||
fontFamily: BASE_THEME.typography.fontFamily,
|
||||
fontSize: BASE_THEME.typography.pxToRem(18),
|
||||
lineHeight: 1.5,
|
||||
letterSpacing: "-0.01em",
|
||||
fontWeight: BASE_THEME.typography.fontWeightMedium
|
||||
},
|
||||
h6: {
|
||||
fontFamily: BASE_THEME.typography.fontFamily,
|
||||
fontSize: BASE_THEME.typography.pxToRem(16),
|
||||
lineHeight: 1.5,
|
||||
letterSpacing: "-0.005em",
|
||||
fontWeight: BASE_THEME.typography.fontWeightMedium
|
||||
},
|
||||
body1: {
|
||||
fontSize: BASE_THEME.typography.pxToRem(14)
|
||||
},
|
||||
body2: {
|
||||
fontSize: BASE_THEME.typography.pxToRem(12)
|
||||
},
|
||||
overline: {
|
||||
fontWeight: BASE_THEME.typography.fontWeightMedium,
|
||||
letterSpacing: "0.05em"
|
||||
},
|
||||
button: {
|
||||
textTransform: "none",
|
||||
fontWeight: BASE_THEME.typography.fontWeightMedium,
|
||||
lineHeight: 1.5
|
||||
},
|
||||
caption: {
|
||||
letterSpacing: 0,
|
||||
lineHeight: 1.5
|
||||
}
|
||||
},
|
||||
shadows: [
|
||||
"none",
|
||||
"0px 4px 15px rgba(33, 36, 67, 0.04), 0px 0px 2px rgba(33, 36, 67, 0.04), 0px 0px 1px rgba(33, 36, 67, 0.04)",
|
||||
"0px 10px 20px rgba(33, 36, 67, 0.04), 0px 2px 6px rgba(33, 36, 67, 0.04), 0px 0px 1px rgba(33, 36, 67, 0.04)",
|
||||
"0px 16px 24px rgba(33, 36, 67, 0.05), 0px 2px 6px rgba(33, 36, 67, 0.05), 0px 0px 1px rgba(33, 36, 67, 0.05)",
|
||||
"0px 24px 32px rgba(33, 36, 67, 0.06), 0px 16px 24px rgba(33, 36, 67, 0.06), 0px 4px 8px rgba(33, 36, 67, 0.06)",
|
||||
...Array(20).fill("none")
|
||||
]
|
||||
});
|
||||
|
||||
export default THEME;
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
import React from "react";
|
||||
|
||||
import { ColumnsContainer as BaseColumnsContainer } from "@usewaypoint/block-columns-container";
|
||||
|
||||
import { useCurrentBlockId } from "../../editor/EditorBlock";
|
||||
import { setDocument, setSelectedBlockId } from "../../editor/EditorContext";
|
||||
import EditorChildrenIds, {
|
||||
EditorChildrenChange
|
||||
} from "../helpers/EditorChildrenIds";
|
||||
|
||||
import ColumnsContainerPropsSchema, {
|
||||
ColumnsContainerProps
|
||||
} from "./ColumnsContainerPropsSchema";
|
||||
|
||||
const EMPTY_COLUMNS = [
|
||||
{ childrenIds: [] },
|
||||
{ childrenIds: [] },
|
||||
{ childrenIds: [] }
|
||||
];
|
||||
|
||||
export default function ColumnsContainerEditor({
|
||||
style,
|
||||
props
|
||||
}: ColumnsContainerProps) {
|
||||
const currentBlockId = useCurrentBlockId();
|
||||
|
||||
const { columns, ...restProps } = props ?? {};
|
||||
const columnsValue = columns ?? EMPTY_COLUMNS;
|
||||
|
||||
const updateColumn = (
|
||||
columnIndex: 0 | 1 | 2,
|
||||
{ block, blockId, childrenIds }: EditorChildrenChange
|
||||
) => {
|
||||
const nColumns = [...columnsValue];
|
||||
nColumns[columnIndex] = { childrenIds };
|
||||
setDocument({
|
||||
[blockId]: block,
|
||||
[currentBlockId]: {
|
||||
type: "ColumnsContainer",
|
||||
data: ColumnsContainerPropsSchema.parse({
|
||||
style,
|
||||
props: {
|
||||
...restProps,
|
||||
columns: nColumns
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
setSelectedBlockId(blockId);
|
||||
};
|
||||
|
||||
return (
|
||||
<BaseColumnsContainer
|
||||
props={restProps}
|
||||
style={style}
|
||||
columns={[
|
||||
<EditorChildrenIds
|
||||
childrenIds={columns?.[0]?.childrenIds}
|
||||
onChange={(change) => updateColumn(0, change)}
|
||||
/>,
|
||||
<EditorChildrenIds
|
||||
childrenIds={columns?.[1]?.childrenIds}
|
||||
onChange={(change) => updateColumn(1, change)}
|
||||
/>,
|
||||
<EditorChildrenIds
|
||||
childrenIds={columns?.[2]?.childrenIds}
|
||||
onChange={(change) => updateColumn(2, change)}
|
||||
/>
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { ColumnsContainerPropsSchema as BaseColumnsContainerPropsSchema } from "@usewaypoint/block-columns-container";
|
||||
|
||||
const BasePropsShape = BaseColumnsContainerPropsSchema.shape.props
|
||||
.unwrap()
|
||||
.unwrap().shape;
|
||||
|
||||
const ColumnsContainerPropsSchema = z.object({
|
||||
style: BaseColumnsContainerPropsSchema.shape.style,
|
||||
props: z
|
||||
.object({
|
||||
...BasePropsShape,
|
||||
columns: z.tuple([
|
||||
z.object({ childrenIds: z.array(z.string()) }),
|
||||
z.object({ childrenIds: z.array(z.string()) }),
|
||||
z.object({ childrenIds: z.array(z.string()) })
|
||||
])
|
||||
})
|
||||
.optional()
|
||||
.nullable()
|
||||
});
|
||||
|
||||
export type ColumnsContainerProps = z.infer<typeof ColumnsContainerPropsSchema>;
|
||||
export default ColumnsContainerPropsSchema;
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
import React from "react";
|
||||
|
||||
import { Container as BaseContainer } from "@usewaypoint/block-container";
|
||||
|
||||
import { useCurrentBlockId } from "../../editor/EditorBlock";
|
||||
import {
|
||||
setDocument,
|
||||
setSelectedBlockId,
|
||||
useDocument
|
||||
} from "../../editor/EditorContext";
|
||||
import EditorChildrenIds from "../helpers/EditorChildrenIds";
|
||||
|
||||
import { ContainerProps } from "./ContainerPropsSchema";
|
||||
|
||||
export default function ContainerEditor({ style, props }: ContainerProps) {
|
||||
const childrenIds = props?.childrenIds ?? [];
|
||||
|
||||
const document = useDocument();
|
||||
const currentBlockId = useCurrentBlockId();
|
||||
|
||||
return (
|
||||
<BaseContainer style={style}>
|
||||
<EditorChildrenIds
|
||||
childrenIds={childrenIds}
|
||||
onChange={({ block, blockId, childrenIds }) => {
|
||||
setDocument({
|
||||
[blockId]: block,
|
||||
[currentBlockId]: {
|
||||
type: "Container",
|
||||
data: {
|
||||
...document[currentBlockId].data,
|
||||
props: { childrenIds: childrenIds }
|
||||
}
|
||||
}
|
||||
});
|
||||
setSelectedBlockId(blockId);
|
||||
}}
|
||||
/>
|
||||
</BaseContainer>
|
||||
);
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { ContainerPropsSchema as BaseContainerPropsSchema } from "@usewaypoint/block-container";
|
||||
|
||||
const ContainerPropsSchema = z.object({
|
||||
style: BaseContainerPropsSchema.shape.style,
|
||||
props: z
|
||||
.object({
|
||||
childrenIds: z.array(z.string()).optional().nullable()
|
||||
})
|
||||
.optional()
|
||||
.nullable()
|
||||
});
|
||||
|
||||
export default ContainerPropsSchema;
|
||||
|
||||
export type ContainerProps = z.infer<typeof ContainerPropsSchema>;
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
import React from "react";
|
||||
|
||||
import { useCurrentBlockId } from "../../editor/EditorBlock";
|
||||
import {
|
||||
setDocument,
|
||||
setSelectedBlockId,
|
||||
useDocument
|
||||
} from "../../editor/EditorContext";
|
||||
import EditorChildrenIds from "../helpers/EditorChildrenIds";
|
||||
|
||||
import { EmailLayoutProps } from "./EmailLayoutPropsSchema";
|
||||
|
||||
function getFontFamily(fontFamily: EmailLayoutProps["fontFamily"]) {
|
||||
const f = fontFamily ?? "MODERN_SANS";
|
||||
switch (f) {
|
||||
case "MODERN_SANS":
|
||||
return '"Helvetica Neue", "Arial Nova", "Nimbus Sans", Arial, sans-serif';
|
||||
case "BOOK_SANS":
|
||||
return 'Optima, Candara, "Noto Sans", source-sans-pro, sans-serif';
|
||||
case "ORGANIC_SANS":
|
||||
return 'Seravek, "Gill Sans Nova", Ubuntu, Calibri, "DejaVu Sans", source-sans-pro, sans-serif';
|
||||
case "GEOMETRIC_SANS":
|
||||
return 'Avenir, "Avenir Next LT Pro", Montserrat, Corbel, "URW Gothic", source-sans-pro, sans-serif';
|
||||
case "HEAVY_SANS":
|
||||
return 'Bahnschrift, "DIN Alternate", "Franklin Gothic Medium", "Nimbus Sans Narrow", sans-serif-condensed, sans-serif';
|
||||
case "ROUNDED_SANS":
|
||||
return 'ui-rounded, "Hiragino Maru Gothic ProN", Quicksand, Comfortaa, Manjari, "Arial Rounded MT Bold", Calibri, source-sans-pro, sans-serif';
|
||||
case "MODERN_SERIF":
|
||||
return 'Charter, "Bitstream Charter", "Sitka Text", Cambria, serif';
|
||||
case "BOOK_SERIF":
|
||||
return '"Iowan Old Style", "Palatino Linotype", "URW Palladio L", P052, serif';
|
||||
case "MONOSPACE":
|
||||
return '"Nimbus Mono PS", "Courier New", "Cutive Mono", monospace';
|
||||
}
|
||||
}
|
||||
|
||||
export default function EmailLayoutEditor(props: EmailLayoutProps) {
|
||||
const childrenIds = props.childrenIds ?? [];
|
||||
const canvasWidth = props.canvasWidth ?? 600;
|
||||
const document = useDocument();
|
||||
const currentBlockId = useCurrentBlockId();
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={() => {
|
||||
setSelectedBlockId(null);
|
||||
}}
|
||||
style={{
|
||||
backgroundColor: props.backdropColor ?? "#F5F5F5",
|
||||
color: props.textColor ?? "#262626",
|
||||
fontFamily: getFontFamily(props.fontFamily),
|
||||
fontSize: "16px",
|
||||
fontWeight: "400",
|
||||
letterSpacing: "0.15008px",
|
||||
lineHeight: "1.5",
|
||||
margin: "0",
|
||||
padding: "32px 0",
|
||||
width: "100%",
|
||||
minHeight: "100%"
|
||||
}}
|
||||
>
|
||||
<table
|
||||
align="center"
|
||||
width="100%"
|
||||
style={{
|
||||
margin: "0 auto",
|
||||
// maxWidth: "600px",
|
||||
maxWidth: `${canvasWidth}px`,
|
||||
backgroundColor: props.canvasColor ?? "#FFFFFF",
|
||||
borderRadius: props.borderRadius ?? undefined,
|
||||
border: (() => {
|
||||
const v = props.borderColor;
|
||||
if (!v) {
|
||||
return undefined;
|
||||
}
|
||||
return `1px solid ${v}`;
|
||||
})()
|
||||
}}
|
||||
role="presentation"
|
||||
cellSpacing="0"
|
||||
cellPadding="0"
|
||||
border={0}
|
||||
>
|
||||
<tbody>
|
||||
<tr style={{ width: "100%" }}>
|
||||
<td>
|
||||
<EditorChildrenIds
|
||||
childrenIds={childrenIds}
|
||||
onChange={({ block, blockId, childrenIds }) => {
|
||||
setDocument({
|
||||
[blockId]: block,
|
||||
[currentBlockId]: {
|
||||
type: "EmailLayout",
|
||||
data: {
|
||||
...document[currentBlockId].data,
|
||||
childrenIds: childrenIds
|
||||
}
|
||||
}
|
||||
});
|
||||
setSelectedBlockId(blockId);
|
||||
}}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
import { z } from "zod";
|
||||
|
||||
const COLOR_SCHEMA = z
|
||||
.string()
|
||||
.regex(/^#[0-9a-fA-F]{6}$/)
|
||||
.nullable()
|
||||
.optional();
|
||||
|
||||
const FONT_FAMILY_SCHEMA = z
|
||||
.enum([
|
||||
"MODERN_SANS",
|
||||
"BOOK_SANS",
|
||||
"ORGANIC_SANS",
|
||||
"GEOMETRIC_SANS",
|
||||
"HEAVY_SANS",
|
||||
"ROUNDED_SANS",
|
||||
"MODERN_SERIF",
|
||||
"BOOK_SERIF",
|
||||
"MONOSPACE"
|
||||
])
|
||||
.nullable()
|
||||
.optional();
|
||||
|
||||
const EmailLayoutPropsSchema = z.object({
|
||||
backdropColor: COLOR_SCHEMA,
|
||||
borderColor: COLOR_SCHEMA,
|
||||
borderRadius: z.number().optional().nullable(),
|
||||
canvasColor: COLOR_SCHEMA,
|
||||
canvasWidth: z.number().optional().nullable(),
|
||||
textColor: COLOR_SCHEMA,
|
||||
fontFamily: FONT_FAMILY_SCHEMA,
|
||||
childrenIds: z.array(z.string()).optional().nullable()
|
||||
});
|
||||
|
||||
export default EmailLayoutPropsSchema;
|
||||
|
||||
export type EmailLayoutProps = z.infer<typeof EmailLayoutPropsSchema>;
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
import React from "react";
|
||||
|
||||
import { Box, Button, SxProps, Typography } from "@mui/material";
|
||||
|
||||
type BlockMenuButtonProps = {
|
||||
label: string;
|
||||
icon: React.ReactNode;
|
||||
onClick: () => void;
|
||||
};
|
||||
|
||||
const BUTTON_SX: SxProps = { p: 1.5, display: "flex", flexDirection: "column" };
|
||||
const ICON_SX: SxProps = {
|
||||
mb: 0.75,
|
||||
width: "100%",
|
||||
bgcolor: "cadet.200",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
p: 1,
|
||||
border: "1px solid",
|
||||
borderColor: "cadet.300"
|
||||
};
|
||||
|
||||
export default function BlockTypeButton({
|
||||
label,
|
||||
icon,
|
||||
onClick
|
||||
}: BlockMenuButtonProps) {
|
||||
return (
|
||||
<Button
|
||||
sx={BUTTON_SX}
|
||||
onClick={(ev) => {
|
||||
ev.stopPropagation();
|
||||
onClick();
|
||||
}}
|
||||
>
|
||||
<Box sx={ICON_SX}>{icon}</Box>
|
||||
<Typography variant="body2">{label}</Typography>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
import React from "react";
|
||||
|
||||
import { Box, Menu } from "@mui/material";
|
||||
|
||||
import { TEditorBlock } from "../../../../editor/core";
|
||||
|
||||
import BlockButton from "./BlockButton";
|
||||
import { BUTTONS } from "./buttons";
|
||||
|
||||
type BlocksMenuProps = {
|
||||
anchorEl: HTMLElement | null;
|
||||
setAnchorEl: (v: HTMLElement | null) => void;
|
||||
onSelect: (block: TEditorBlock) => void;
|
||||
};
|
||||
export default function BlocksMenu({
|
||||
anchorEl,
|
||||
setAnchorEl,
|
||||
onSelect
|
||||
}: BlocksMenuProps) {
|
||||
const onClose = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
const onClick = (block: TEditorBlock) => {
|
||||
onSelect(block);
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
if (anchorEl === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Menu
|
||||
open
|
||||
anchorEl={anchorEl}
|
||||
onClose={onClose}
|
||||
anchorOrigin={{ vertical: "bottom", horizontal: "center" }}
|
||||
transformOrigin={{ vertical: "top", horizontal: "center" }}
|
||||
>
|
||||
<Box
|
||||
sx={{ p: 1, display: "grid", gridTemplateColumns: "1fr 1fr 1fr 1fr" }}
|
||||
>
|
||||
{BUTTONS.map((k, i) => (
|
||||
<BlockButton
|
||||
key={i}
|
||||
label={k.label}
|
||||
icon={k.icon}
|
||||
onClick={() => onClick(k.block())}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
import { AddOutlined } from "@mui/icons-material";
|
||||
import { Fade, IconButton } from "@mui/material";
|
||||
|
||||
type Props = {
|
||||
buttonElement: HTMLElement | null;
|
||||
onClick: () => void;
|
||||
};
|
||||
export default function DividerButton({ buttonElement, onClick }: Props) {
|
||||
const [visible, setVisible] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
function listener({ clientX, clientY }: MouseEvent) {
|
||||
if (!buttonElement) {
|
||||
return;
|
||||
}
|
||||
const rect = buttonElement.getBoundingClientRect();
|
||||
const rectY = rect.y;
|
||||
const bottomX = rect.x;
|
||||
const topX = bottomX + rect.width;
|
||||
|
||||
if (Math.abs(clientY - rectY) < 20) {
|
||||
if (bottomX < clientX && clientX < topX) {
|
||||
setVisible(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
setVisible(false);
|
||||
}
|
||||
window.addEventListener("mousemove", listener);
|
||||
return () => {
|
||||
window.removeEventListener("mousemove", listener);
|
||||
};
|
||||
}, [buttonElement, setVisible]);
|
||||
|
||||
return (
|
||||
<Fade in={visible}>
|
||||
<IconButton
|
||||
size="small"
|
||||
sx={{
|
||||
p: 0.12,
|
||||
position: "absolute",
|
||||
top: "-12px",
|
||||
left: "50%",
|
||||
transform: "translateX(-10px)",
|
||||
bgcolor: "brand.blue",
|
||||
color: "primary.contrastText",
|
||||
zIndex: "fab",
|
||||
"&:hover, &:active, &:focus": {
|
||||
bgcolor: "brand.blue",
|
||||
color: "primary.contrastText"
|
||||
}
|
||||
}}
|
||||
onClick={(ev) => {
|
||||
ev.stopPropagation();
|
||||
onClick();
|
||||
}}
|
||||
>
|
||||
<AddOutlined fontSize="small" />
|
||||
</IconButton>
|
||||
</Fade>
|
||||
);
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
import React from "react";
|
||||
|
||||
import { AddOutlined } from "@mui/icons-material";
|
||||
import { ButtonBase } from "@mui/material";
|
||||
|
||||
type Props = {
|
||||
onClick: () => void;
|
||||
};
|
||||
export default function PlaceholderButton({ onClick }: Props) {
|
||||
return (
|
||||
<ButtonBase
|
||||
onClick={(ev) => {
|
||||
ev.stopPropagation();
|
||||
onClick();
|
||||
}}
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignContent: "center",
|
||||
justifyContent: "center",
|
||||
height: 48,
|
||||
width: "100%",
|
||||
bgcolor: "rgba(0,0,0, 0.05)"
|
||||
}}
|
||||
>
|
||||
<AddOutlined
|
||||
sx={{
|
||||
p: 0.12,
|
||||
bgcolor: "brand.blue",
|
||||
borderRadius: 24,
|
||||
color: "primary.contrastText"
|
||||
}}
|
||||
fontSize="small"
|
||||
/>
|
||||
</ButtonBase>
|
||||
);
|
||||
}
|
||||
+161
@@ -0,0 +1,161 @@
|
||||
import React from "react";
|
||||
|
||||
import {
|
||||
AccountCircleOutlined,
|
||||
Crop32Outlined,
|
||||
HMobiledataOutlined,
|
||||
HorizontalRuleOutlined,
|
||||
HtmlOutlined,
|
||||
ImageOutlined,
|
||||
LibraryAddOutlined,
|
||||
NotesOutlined,
|
||||
SmartButtonOutlined,
|
||||
ViewColumnOutlined
|
||||
} from "@mui/icons-material";
|
||||
|
||||
import { TEditorBlock } from "../../../../editor/core";
|
||||
|
||||
type TButtonProps = {
|
||||
label: string;
|
||||
icon: JSX.Element;
|
||||
block: () => TEditorBlock;
|
||||
};
|
||||
export const BUTTONS: TButtonProps[] = [
|
||||
{
|
||||
label: "Heading",
|
||||
icon: <HMobiledataOutlined />,
|
||||
block: () => ({
|
||||
type: "Heading",
|
||||
data: {
|
||||
props: { text: "Hello friend" },
|
||||
style: {
|
||||
padding: { top: 16, bottom: 16, left: 24, right: 24 }
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
{
|
||||
label: "Text",
|
||||
icon: <NotesOutlined />,
|
||||
block: () => ({
|
||||
type: "Text",
|
||||
data: {
|
||||
props: { text: "My new text block" },
|
||||
style: {
|
||||
padding: { top: 16, bottom: 16, left: 24, right: 24 },
|
||||
fontWeight: "normal"
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
{
|
||||
label: "Button",
|
||||
icon: <SmartButtonOutlined />,
|
||||
block: () => ({
|
||||
type: "Button",
|
||||
data: {
|
||||
props: { text: "Button", url: window.location.origin },
|
||||
style: { padding: { top: 16, bottom: 16, left: 24, right: 24 } }
|
||||
}
|
||||
})
|
||||
},
|
||||
{
|
||||
label: "Image",
|
||||
icon: <ImageOutlined />,
|
||||
block: () => ({
|
||||
type: "Image",
|
||||
data: {
|
||||
props: {
|
||||
url: "https://ui-avatars.com/api/?size=128",
|
||||
alt: "Sample product",
|
||||
contentAlignment: "middle",
|
||||
linkHref: null
|
||||
},
|
||||
style: { padding: { top: 16, bottom: 16, left: 24, right: 24 } }
|
||||
}
|
||||
})
|
||||
},
|
||||
{
|
||||
label: "Avatar",
|
||||
icon: <AccountCircleOutlined />,
|
||||
block: () => ({
|
||||
type: "Avatar",
|
||||
data: {
|
||||
props: {
|
||||
imageUrl: "https://ui-avatars.com/api/?size=128",
|
||||
shape: "circle"
|
||||
},
|
||||
style: { padding: { top: 16, bottom: 16, left: 24, right: 24 } }
|
||||
}
|
||||
})
|
||||
},
|
||||
{
|
||||
label: "Divider",
|
||||
icon: <HorizontalRuleOutlined />,
|
||||
block: () => ({
|
||||
type: "Divider",
|
||||
data: {
|
||||
style: { padding: { top: 16, right: 0, bottom: 16, left: 0 } },
|
||||
props: {
|
||||
lineColor: "#CCCCCC"
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
{
|
||||
label: "Spacer",
|
||||
icon: <Crop32Outlined />,
|
||||
block: () => ({
|
||||
type: "Spacer",
|
||||
data: {}
|
||||
})
|
||||
},
|
||||
{
|
||||
label: "Html",
|
||||
icon: <HtmlOutlined />,
|
||||
block: () => ({
|
||||
type: "Html",
|
||||
data: {
|
||||
props: { contents: "<strong>Hello world</strong>" },
|
||||
style: {
|
||||
fontSize: 16,
|
||||
textAlign: null,
|
||||
padding: { top: 16, bottom: 16, left: 24, right: 24 }
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
{
|
||||
label: "Columns",
|
||||
icon: <ViewColumnOutlined />,
|
||||
block: () => ({
|
||||
type: "ColumnsContainer",
|
||||
data: {
|
||||
props: {
|
||||
columnsGap: 16,
|
||||
columnsCount: 3,
|
||||
columns: [
|
||||
{ childrenIds: [] },
|
||||
{ childrenIds: [] },
|
||||
{ childrenIds: [] }
|
||||
]
|
||||
},
|
||||
style: { padding: { top: 16, bottom: 16, left: 24, right: 24 } }
|
||||
}
|
||||
})
|
||||
},
|
||||
{
|
||||
label: "Container",
|
||||
icon: <LibraryAddOutlined />,
|
||||
block: () => ({
|
||||
type: "Container",
|
||||
data: {
|
||||
style: { padding: { top: 16, bottom: 16, left: 24, right: 24 } }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// { label: 'ProgressBar', icon: <ProgressBarOutlined />, block: () => ({}) },
|
||||
// { label: 'LoopContainer', icon: <ViewListOutlined />, block: () => ({}) },
|
||||
];
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
import React, { useState } from "react";
|
||||
|
||||
import { TEditorBlock } from "../../../../editor/core";
|
||||
|
||||
import BlocksMenu from "./BlocksMenu";
|
||||
import DividerButton from "./DividerButton";
|
||||
import PlaceholderButton from "./PlaceholderButton";
|
||||
|
||||
type Props = {
|
||||
placeholder?: boolean;
|
||||
onSelect: (block: TEditorBlock) => void;
|
||||
};
|
||||
export default function AddBlockButton({ onSelect, placeholder }: Props) {
|
||||
const [menuAnchorEl, setMenuAnchorEl] = useState<HTMLElement | null>(null);
|
||||
const [buttonElement, setButtonElement] = useState<HTMLElement | null>(null);
|
||||
|
||||
const handleButtonClick = () => {
|
||||
setMenuAnchorEl(buttonElement);
|
||||
};
|
||||
|
||||
const renderButton = () => {
|
||||
if (placeholder) {
|
||||
return <PlaceholderButton onClick={handleButtonClick} />;
|
||||
} else {
|
||||
return (
|
||||
<DividerButton
|
||||
buttonElement={buttonElement}
|
||||
onClick={handleButtonClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div ref={setButtonElement} style={{ position: "relative" }}>
|
||||
{renderButton()}
|
||||
</div>
|
||||
<BlocksMenu
|
||||
anchorEl={menuAnchorEl}
|
||||
setAnchorEl={setMenuAnchorEl}
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
import React, { Fragment } from "react";
|
||||
|
||||
import { TEditorBlock } from "../../../editor/core";
|
||||
import EditorBlock from "../../../editor/EditorBlock";
|
||||
|
||||
import AddBlockButton from "./AddBlockMenu";
|
||||
|
||||
export type EditorChildrenChange = {
|
||||
blockId: string;
|
||||
block: TEditorBlock;
|
||||
childrenIds: string[];
|
||||
};
|
||||
|
||||
function generateId() {
|
||||
return `block-${Date.now()}`;
|
||||
}
|
||||
|
||||
export type EditorChildrenIdsProps = {
|
||||
childrenIds: string[] | null | undefined;
|
||||
onChange: (val: EditorChildrenChange) => void;
|
||||
};
|
||||
export default function EditorChildrenIds({
|
||||
childrenIds,
|
||||
onChange
|
||||
}: EditorChildrenIdsProps) {
|
||||
const appendBlock = (block: TEditorBlock) => {
|
||||
const blockId = generateId();
|
||||
return onChange({
|
||||
blockId,
|
||||
block,
|
||||
childrenIds: [...(childrenIds || []), blockId]
|
||||
});
|
||||
};
|
||||
|
||||
const insertBlock = (block: TEditorBlock, index: number) => {
|
||||
const blockId = generateId();
|
||||
const newChildrenIds = [...(childrenIds || [])];
|
||||
newChildrenIds.splice(index, 0, blockId);
|
||||
return onChange({
|
||||
blockId,
|
||||
block,
|
||||
childrenIds: newChildrenIds
|
||||
});
|
||||
};
|
||||
|
||||
if (!childrenIds || childrenIds.length === 0) {
|
||||
return <AddBlockButton placeholder onSelect={appendBlock} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{childrenIds.map((childId, i) => (
|
||||
<Fragment key={childId}>
|
||||
<AddBlockButton onSelect={(block) => insertBlock(block, i)} />
|
||||
<EditorBlock id={childId} />
|
||||
</Fragment>
|
||||
))}
|
||||
<AddBlockButton onSelect={appendBlock} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
export type TStyle = {
|
||||
backgroundColor?: any;
|
||||
borderColor?: any;
|
||||
borderRadius?: any;
|
||||
color?: any;
|
||||
fontFamily?: any;
|
||||
fontSize?: any;
|
||||
fontWeight?: any;
|
||||
padding?: any;
|
||||
textAlign?: any;
|
||||
};
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
import React, { CSSProperties, useState } from "react";
|
||||
|
||||
import { Box } from "@mui/material";
|
||||
|
||||
import { useCurrentBlockId } from "../../../editor/EditorBlock";
|
||||
import {
|
||||
setSelectedBlockId,
|
||||
useSelectedBlockId
|
||||
} from "../../../editor/EditorContext";
|
||||
|
||||
import TuneMenu from "./TuneMenu";
|
||||
|
||||
type TEditorBlockWrapperProps = {
|
||||
children: JSX.Element;
|
||||
};
|
||||
|
||||
export default function EditorBlockWrapper({
|
||||
children
|
||||
}: TEditorBlockWrapperProps) {
|
||||
const selectedBlockId = useSelectedBlockId();
|
||||
const [mouseInside, setMouseInside] = useState(false);
|
||||
const blockId = useCurrentBlockId();
|
||||
|
||||
let outline: CSSProperties["outline"];
|
||||
if (selectedBlockId === blockId) {
|
||||
outline = "2px solid rgba(0,121,204, 1)";
|
||||
} else if (mouseInside) {
|
||||
outline = "2px solid rgba(0,121,204, 0.3)";
|
||||
}
|
||||
|
||||
const renderMenu = () => {
|
||||
if (selectedBlockId !== blockId) {
|
||||
return null;
|
||||
}
|
||||
return <TuneMenu blockId={blockId} />;
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
position: "relative",
|
||||
maxWidth: "100%",
|
||||
outlineOffset: "-1px",
|
||||
outline
|
||||
}}
|
||||
onMouseEnter={(ev) => {
|
||||
setMouseInside(true);
|
||||
ev.stopPropagation();
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
setMouseInside(false);
|
||||
}}
|
||||
onClick={(ev) => {
|
||||
setSelectedBlockId(blockId);
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
}}
|
||||
>
|
||||
{renderMenu()}
|
||||
{children}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
import React, { CSSProperties } from "react";
|
||||
|
||||
import { TStyle } from "../TStyle";
|
||||
|
||||
type TReaderBlockWrapperProps = {
|
||||
style: TStyle;
|
||||
children: JSX.Element;
|
||||
};
|
||||
|
||||
export default function ReaderBlockWrapper({
|
||||
style,
|
||||
children
|
||||
}: TReaderBlockWrapperProps) {
|
||||
const { padding, borderColor, ...restStyle } = style;
|
||||
const cssStyle: CSSProperties = {
|
||||
...restStyle
|
||||
};
|
||||
|
||||
if (padding) {
|
||||
const { top, bottom, left, right } = padding;
|
||||
cssStyle.padding = `${top}px ${right}px ${bottom}px ${left}px`;
|
||||
}
|
||||
|
||||
if (borderColor) {
|
||||
cssStyle.border = `1px solid ${borderColor}`;
|
||||
}
|
||||
|
||||
return <div style={{ maxWidth: "100%", ...cssStyle }}>{children}</div>;
|
||||
}
|
||||
+295
@@ -0,0 +1,295 @@
|
||||
import React from "react";
|
||||
|
||||
import {
|
||||
ArrowDownwardOutlined,
|
||||
ArrowUpwardOutlined,
|
||||
ContentCopyOutlined,
|
||||
DeleteOutlined
|
||||
} from "@mui/icons-material";
|
||||
import { IconButton, Paper, Stack, SxProps, Tooltip } from "@mui/material";
|
||||
|
||||
import { TEditorBlock, TEditorConfiguration } from "../../../editor/core";
|
||||
import {
|
||||
resetDocument,
|
||||
setSelectedBlockId,
|
||||
useDocument
|
||||
} from "../../../editor/EditorContext";
|
||||
import { ColumnsContainerProps } from "../../ColumnsContainer/ColumnsContainerPropsSchema";
|
||||
import cloneDocumentBlock from "../cloneDocumentBlock";
|
||||
|
||||
const sx: SxProps = {
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: -56,
|
||||
borderRadius: 64,
|
||||
paddingX: 0.5,
|
||||
paddingY: 1,
|
||||
zIndex: "fab"
|
||||
};
|
||||
|
||||
function findParentBlockId(blockId: string, document: TEditorConfiguration) {
|
||||
for (const [id, b] of Object.entries(document)) {
|
||||
if (id === blockId) {
|
||||
continue;
|
||||
}
|
||||
const block = b as TEditorBlock;
|
||||
switch (block.type) {
|
||||
case "EmailLayout":
|
||||
if (block.data.childrenIds?.includes(blockId)) {
|
||||
return id;
|
||||
}
|
||||
break;
|
||||
case "Container":
|
||||
if (block.data.props?.childrenIds?.includes(blockId)) {
|
||||
return id;
|
||||
}
|
||||
break;
|
||||
case "ColumnsContainer":
|
||||
if (
|
||||
block.data.props?.columns?.some((col) =>
|
||||
col.childrenIds?.includes(blockId)
|
||||
)
|
||||
) {
|
||||
return id;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
type Props = {
|
||||
blockId: string;
|
||||
};
|
||||
export default function TuneMenu({ blockId }: Props) {
|
||||
const document = useDocument();
|
||||
|
||||
const handleDuplicateClick = () => {
|
||||
const parentBlockId = findParentBlockId(blockId, document);
|
||||
|
||||
const { document: newDocument, blockId: newBlockId } = cloneDocumentBlock(
|
||||
document,
|
||||
blockId
|
||||
);
|
||||
|
||||
if (parentBlockId) {
|
||||
const parentBlock = newDocument[parentBlockId];
|
||||
switch (parentBlock.type) {
|
||||
case "EmailLayout": {
|
||||
if (!parentBlock.data.childrenIds) {
|
||||
parentBlock.data.childrenIds = [];
|
||||
}
|
||||
const index = parentBlock.data.childrenIds.indexOf(blockId);
|
||||
parentBlock.data.childrenIds.splice(index + 1, 0, newBlockId);
|
||||
break;
|
||||
}
|
||||
case "Container": {
|
||||
if (!parentBlock.data.props) {
|
||||
parentBlock.data.props = {};
|
||||
}
|
||||
if (!parentBlock.data.props.childrenIds) {
|
||||
parentBlock.data.props.childrenIds = [];
|
||||
}
|
||||
const index = parentBlock.data.props.childrenIds.indexOf(blockId);
|
||||
parentBlock.data.props.childrenIds.splice(index + 1, 0, newBlockId);
|
||||
break;
|
||||
}
|
||||
case "ColumnsContainer":
|
||||
if (!parentBlock.data.props) {
|
||||
parentBlock.data.props = {
|
||||
columns: [
|
||||
{ childrenIds: [] },
|
||||
{ childrenIds: [] },
|
||||
{ childrenIds: [] }
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
for (const column of parentBlock.data.props.columns) {
|
||||
if (column.childrenIds.includes(blockId)) {
|
||||
const index = column.childrenIds.indexOf(blockId);
|
||||
column.childrenIds.splice(index + 1, 0, newBlockId);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
resetDocument(newDocument);
|
||||
setSelectedBlockId(newBlockId);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteClick = () => {
|
||||
const filterChildrenIds = (childrenIds: string[] | null | undefined) => {
|
||||
if (!childrenIds) {
|
||||
return childrenIds;
|
||||
}
|
||||
return childrenIds.filter((f) => f !== blockId);
|
||||
};
|
||||
const nDocument: typeof document = { ...document };
|
||||
for (const [id, b] of Object.entries(nDocument)) {
|
||||
const block = b as TEditorBlock;
|
||||
if (id === blockId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (block.type) {
|
||||
case "EmailLayout":
|
||||
nDocument[id] = {
|
||||
...block,
|
||||
data: {
|
||||
...block.data,
|
||||
childrenIds: filterChildrenIds(block.data.childrenIds)
|
||||
}
|
||||
};
|
||||
break;
|
||||
case "Container":
|
||||
nDocument[id] = {
|
||||
...block,
|
||||
data: {
|
||||
...block.data,
|
||||
props: {
|
||||
...block.data.props,
|
||||
childrenIds: filterChildrenIds(block.data.props?.childrenIds)
|
||||
}
|
||||
}
|
||||
};
|
||||
break;
|
||||
case "ColumnsContainer":
|
||||
nDocument[id] = {
|
||||
type: "ColumnsContainer",
|
||||
data: {
|
||||
style: block.data.style,
|
||||
props: {
|
||||
...block.data.props,
|
||||
columns: block.data.props?.columns?.map((c) => ({
|
||||
childrenIds: filterChildrenIds(c.childrenIds)
|
||||
}))
|
||||
}
|
||||
} as ColumnsContainerProps
|
||||
};
|
||||
break;
|
||||
default:
|
||||
nDocument[id] = block;
|
||||
}
|
||||
}
|
||||
delete nDocument[blockId];
|
||||
resetDocument(nDocument);
|
||||
};
|
||||
|
||||
const handleMoveClick = (direction: "up" | "down") => {
|
||||
const moveChildrenIds = (ids: string[] | null | undefined) => {
|
||||
if (!ids) {
|
||||
return ids;
|
||||
}
|
||||
const index = ids.indexOf(blockId);
|
||||
if (index < 0) {
|
||||
return ids;
|
||||
}
|
||||
|
||||
const childrenIds = [...ids];
|
||||
if (direction === "up" && index > 0) {
|
||||
[childrenIds[index], childrenIds[index - 1]] = [
|
||||
childrenIds[index - 1],
|
||||
childrenIds[index]
|
||||
];
|
||||
} else if (direction === "down" && index < childrenIds.length - 1) {
|
||||
[childrenIds[index], childrenIds[index + 1]] = [
|
||||
childrenIds[index + 1],
|
||||
childrenIds[index]
|
||||
];
|
||||
}
|
||||
return childrenIds;
|
||||
};
|
||||
|
||||
const nDocument: typeof document = { ...document };
|
||||
for (const [id, b] of Object.entries(nDocument)) {
|
||||
const block = b as TEditorBlock;
|
||||
if (id === blockId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (block.type) {
|
||||
case "EmailLayout":
|
||||
nDocument[id] = {
|
||||
...block,
|
||||
data: {
|
||||
...block.data,
|
||||
childrenIds: moveChildrenIds(block.data.childrenIds)
|
||||
}
|
||||
};
|
||||
break;
|
||||
case "Container":
|
||||
nDocument[id] = {
|
||||
...block,
|
||||
data: {
|
||||
...block.data,
|
||||
props: {
|
||||
...block.data.props,
|
||||
childrenIds: moveChildrenIds(block.data.props?.childrenIds)
|
||||
}
|
||||
}
|
||||
};
|
||||
break;
|
||||
case "ColumnsContainer":
|
||||
nDocument[id] = {
|
||||
type: "ColumnsContainer",
|
||||
data: {
|
||||
style: block.data.style,
|
||||
props: {
|
||||
...block.data.props,
|
||||
columns: block.data.props?.columns?.map((c) => ({
|
||||
childrenIds: moveChildrenIds(c.childrenIds)
|
||||
}))
|
||||
}
|
||||
} as ColumnsContainerProps
|
||||
};
|
||||
break;
|
||||
default:
|
||||
nDocument[id] = block;
|
||||
}
|
||||
}
|
||||
|
||||
resetDocument(nDocument);
|
||||
setSelectedBlockId(blockId);
|
||||
};
|
||||
|
||||
return (
|
||||
<Paper sx={sx} onClick={(ev) => ev.stopPropagation()}>
|
||||
<Stack>
|
||||
<Tooltip title="Move up" placement="left-start">
|
||||
<IconButton
|
||||
onClick={() => handleMoveClick("up")}
|
||||
sx={{ color: "text.primary" }}
|
||||
>
|
||||
<ArrowUpwardOutlined fontSize="small" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title="Move down" placement="left-start">
|
||||
<IconButton
|
||||
onClick={() => handleMoveClick("down")}
|
||||
sx={{ color: "text.primary" }}
|
||||
>
|
||||
<ArrowDownwardOutlined fontSize="small" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title="Duplicate" placement="left-start">
|
||||
<IconButton
|
||||
onClick={handleDuplicateClick}
|
||||
sx={{ color: "text.primary" }}
|
||||
>
|
||||
<ContentCopyOutlined fontSize="small" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title="Delete" placement="left-start">
|
||||
<IconButton
|
||||
onClick={handleDeleteClick}
|
||||
sx={{ color: "text.primary" }}
|
||||
>
|
||||
<DeleteOutlined fontSize="small" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
import { TEditorBlock, TEditorConfiguration } from "../../editor/core";
|
||||
|
||||
type TResult = {
|
||||
document: TEditorConfiguration;
|
||||
blockId: string;
|
||||
};
|
||||
|
||||
function cloneChildrenIds(
|
||||
document: TEditorConfiguration,
|
||||
blockIds: string[]
|
||||
): string[] {
|
||||
return blockIds.map((blockId) => {
|
||||
const newBlock = cloneBlock(document, blockId);
|
||||
const newBlockId = `block-${Date.now()}-${Math.floor(Math.random() * 1000)}`;
|
||||
document[newBlockId] = newBlock;
|
||||
return newBlockId;
|
||||
});
|
||||
}
|
||||
|
||||
function cloneBlock(
|
||||
document: TEditorConfiguration,
|
||||
blockId: string
|
||||
): TEditorBlock {
|
||||
const clone = structuredClone(document[blockId]);
|
||||
switch (clone.type) {
|
||||
case "EmailLayout":
|
||||
throw new Error("Cloning EmailLayout blocks is not supported.");
|
||||
case "Avatar":
|
||||
case "Button":
|
||||
case "Divider":
|
||||
case "Heading":
|
||||
case "Html":
|
||||
case "Image":
|
||||
case "Spacer":
|
||||
case "Text":
|
||||
return clone;
|
||||
case "Container":
|
||||
if (clone.data?.props?.childrenIds) {
|
||||
clone.data.props.childrenIds = cloneChildrenIds(
|
||||
document,
|
||||
clone.data.props.childrenIds
|
||||
);
|
||||
}
|
||||
return clone;
|
||||
case "ColumnsContainer":
|
||||
if (clone.data?.props?.columns) {
|
||||
clone.data.props.columns[0].childrenIds = cloneChildrenIds(
|
||||
document,
|
||||
clone.data.props.columns[0].childrenIds
|
||||
);
|
||||
clone.data.props.columns[1].childrenIds = cloneChildrenIds(
|
||||
document,
|
||||
clone.data.props.columns[1].childrenIds
|
||||
);
|
||||
clone.data.props.columns[2].childrenIds = cloneChildrenIds(
|
||||
document,
|
||||
clone.data.props.columns[2].childrenIds
|
||||
);
|
||||
}
|
||||
return clone;
|
||||
}
|
||||
}
|
||||
|
||||
export default function cloneDocumentBlock(
|
||||
originalDocument: TEditorConfiguration,
|
||||
originalBlockId: string
|
||||
): TResult {
|
||||
const document = { ...originalDocument };
|
||||
const blockId = `block-${Date.now()}-${Math.floor(Math.random() * 1000)}`;
|
||||
document[blockId] = cloneBlock(document, originalBlockId);
|
||||
return {
|
||||
document,
|
||||
blockId
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
export const FONT_FAMILIES = [
|
||||
{
|
||||
key: "MODERN_SANS",
|
||||
label: "Modern sans",
|
||||
value: '"Helvetica Neue", "Arial Nova", "Nimbus Sans", Arial, sans-serif'
|
||||
},
|
||||
{
|
||||
key: "BOOK_SANS",
|
||||
label: "Book sans",
|
||||
value: 'Optima, Candara, "Noto Sans", source-sans-pro, sans-serif'
|
||||
},
|
||||
{
|
||||
key: "ORGANIC_SANS",
|
||||
label: "Organic sans",
|
||||
value:
|
||||
'Seravek, "Gill Sans Nova", Ubuntu, Calibri, "DejaVu Sans", source-sans-pro, sans-serif'
|
||||
},
|
||||
{
|
||||
key: "GEOMETRIC_SANS",
|
||||
label: "Geometric sans",
|
||||
value:
|
||||
'Avenir, "Avenir Next LT Pro", Montserrat, Corbel, "URW Gothic", source-sans-pro, sans-serif'
|
||||
},
|
||||
{
|
||||
key: "HEAVY_SANS",
|
||||
label: "Heavy sans",
|
||||
value:
|
||||
'Bahnschrift, "DIN Alternate", "Franklin Gothic Medium", "Nimbus Sans Narrow", sans-serif-condensed, sans-serif'
|
||||
},
|
||||
{
|
||||
key: "ROUNDED_SANS",
|
||||
label: "Rounded sans",
|
||||
value:
|
||||
'ui-rounded, "Hiragino Maru Gothic ProN", Quicksand, Comfortaa, Manjari, "Arial Rounded MT Bold", Calibri, source-sans-pro, sans-serif'
|
||||
},
|
||||
{
|
||||
key: "MODERN_SERIF",
|
||||
label: "Modern serif",
|
||||
value: 'Charter, "Bitstream Charter", "Sitka Text", Cambria, serif'
|
||||
},
|
||||
{
|
||||
key: "BOOK_SERIF",
|
||||
label: "Book serif",
|
||||
value:
|
||||
'"Iowan Old Style", "Palatino Linotype", "URW Palladio L", P052, serif'
|
||||
},
|
||||
{
|
||||
key: "MONOSPACE",
|
||||
label: "Monospace",
|
||||
value: '"Nimbus Mono PS", "Courier New", "Cutive Mono", monospace'
|
||||
}
|
||||
];
|
||||
|
||||
export const FONT_FAMILY_NAMES = [
|
||||
"MODERN_SANS",
|
||||
"BOOK_SANS",
|
||||
"ORGANIC_SANS",
|
||||
"GEOMETRIC_SANS",
|
||||
"HEAVY_SANS",
|
||||
"ROUNDED_SANS",
|
||||
"MODERN_SERIF",
|
||||
"BOOK_SERIF",
|
||||
"MONOSPACE"
|
||||
] as const;
|
||||
@@ -0,0 +1,28 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { FONT_FAMILY_NAMES } from "./fontFamily";
|
||||
|
||||
export function zColor() {
|
||||
return z.string().regex(/^#[0-9a-fA-F]{6}$/);
|
||||
}
|
||||
|
||||
export function zFontFamily() {
|
||||
return z.enum(FONT_FAMILY_NAMES);
|
||||
}
|
||||
|
||||
export function zFontWeight() {
|
||||
return z.enum(["bold", "normal"]);
|
||||
}
|
||||
|
||||
export function zTextAlign() {
|
||||
return z.enum(["left", "center", "right"]);
|
||||
}
|
||||
|
||||
export function zPadding() {
|
||||
return z.object({
|
||||
top: z.number(),
|
||||
bottom: z.number(),
|
||||
right: z.number(),
|
||||
left: z.number()
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import React, { createContext, useContext } from "react";
|
||||
|
||||
import { EditorBlock as CoreEditorBlock } from "./core";
|
||||
import { useDocument } from "./EditorContext";
|
||||
|
||||
const EditorBlockContext = createContext<string | null>(null);
|
||||
export const useCurrentBlockId = () => useContext(EditorBlockContext)!;
|
||||
|
||||
type EditorBlockProps = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param id - Block id
|
||||
* @returns EditorBlock component that loads data from the EditorDocumentContext
|
||||
*/
|
||||
export default function EditorBlock({ id }: EditorBlockProps) {
|
||||
const document = useDocument();
|
||||
const block = document[id];
|
||||
if (!block) {
|
||||
throw new Error("Could not find block");
|
||||
}
|
||||
return (
|
||||
<EditorBlockContext.Provider value={id}>
|
||||
<CoreEditorBlock {...block} />
|
||||
</EditorBlockContext.Provider>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
import { create } from "zustand";
|
||||
|
||||
import getConfiguration from "../../getConfiguration";
|
||||
|
||||
import { TEditorConfiguration } from "./core";
|
||||
|
||||
type TValue = {
|
||||
document: TEditorConfiguration;
|
||||
|
||||
selectedBlockId: string | null;
|
||||
selectedSidebarTab: "block-configuration" | "styles";
|
||||
selectedMainTab: "editor" | "preview" | "json" | "html";
|
||||
selectedScreenSize: "desktop" | "mobile";
|
||||
|
||||
inspectorDrawerOpen: boolean;
|
||||
samplesDrawerOpen: boolean;
|
||||
};
|
||||
|
||||
const editorStateStore = create<TValue>(() => ({
|
||||
document: getConfiguration(window.location.hash),
|
||||
selectedBlockId: null,
|
||||
selectedSidebarTab: "styles",
|
||||
selectedMainTab: "editor",
|
||||
selectedScreenSize: "desktop",
|
||||
|
||||
inspectorDrawerOpen: true,
|
||||
samplesDrawerOpen: true
|
||||
}));
|
||||
|
||||
export function useDocument() {
|
||||
return editorStateStore((s) => s.document);
|
||||
}
|
||||
|
||||
export function useSelectedBlockId() {
|
||||
return editorStateStore((s) => s.selectedBlockId);
|
||||
}
|
||||
|
||||
export function useSelectedScreenSize() {
|
||||
return editorStateStore((s) => s.selectedScreenSize);
|
||||
}
|
||||
|
||||
export function useSelectedMainTab() {
|
||||
return editorStateStore((s) => s.selectedMainTab);
|
||||
}
|
||||
|
||||
export function setSelectedMainTab(selectedMainTab: TValue["selectedMainTab"]) {
|
||||
return editorStateStore.setState({ selectedMainTab });
|
||||
}
|
||||
|
||||
export function useSelectedSidebarTab() {
|
||||
return editorStateStore((s) => s.selectedSidebarTab);
|
||||
}
|
||||
|
||||
export function useInspectorDrawerOpen() {
|
||||
return editorStateStore((s) => s.inspectorDrawerOpen);
|
||||
}
|
||||
|
||||
export function useSamplesDrawerOpen() {
|
||||
return editorStateStore((s) => s.samplesDrawerOpen);
|
||||
}
|
||||
|
||||
export function setSelectedBlockId(selectedBlockId: TValue["selectedBlockId"]) {
|
||||
const selectedSidebarTab =
|
||||
selectedBlockId === null ? "styles" : "block-configuration";
|
||||
const options: Partial<TValue> = {};
|
||||
if (selectedBlockId !== null) {
|
||||
options.inspectorDrawerOpen = true;
|
||||
}
|
||||
return editorStateStore.setState({
|
||||
selectedBlockId,
|
||||
selectedSidebarTab,
|
||||
...options
|
||||
});
|
||||
}
|
||||
|
||||
export function setSidebarTab(
|
||||
selectedSidebarTab: TValue["selectedSidebarTab"]
|
||||
) {
|
||||
return editorStateStore.setState({ selectedSidebarTab });
|
||||
}
|
||||
|
||||
export function resetDocument(document: TValue["document"]) {
|
||||
return editorStateStore.setState({
|
||||
document,
|
||||
selectedSidebarTab: "styles",
|
||||
selectedBlockId: null
|
||||
});
|
||||
}
|
||||
|
||||
export function setDocument(document: TValue["document"]) {
|
||||
const originalDocument = editorStateStore.getState().document;
|
||||
return editorStateStore.setState({
|
||||
document: {
|
||||
...originalDocument,
|
||||
...document
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function toggleInspectorDrawerOpen() {
|
||||
const inspectorDrawerOpen = !editorStateStore.getState().inspectorDrawerOpen;
|
||||
return editorStateStore.setState({ inspectorDrawerOpen });
|
||||
}
|
||||
|
||||
export function toggleSamplesDrawerOpen() {
|
||||
const samplesDrawerOpen = !editorStateStore.getState().samplesDrawerOpen;
|
||||
return editorStateStore.setState({ samplesDrawerOpen });
|
||||
}
|
||||
|
||||
export function setSelectedScreenSize(
|
||||
selectedScreenSize: TValue["selectedScreenSize"]
|
||||
) {
|
||||
return editorStateStore.setState({ selectedScreenSize });
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
import React from "react";
|
||||
import { z } from "zod";
|
||||
|
||||
import { Avatar, AvatarPropsSchema } from "@usewaypoint/block-avatar";
|
||||
import { Button, ButtonPropsSchema } from "@usewaypoint/block-button";
|
||||
import { Divider, DividerPropsSchema } from "@usewaypoint/block-divider";
|
||||
import { Heading, HeadingPropsSchema } from "@usewaypoint/block-heading";
|
||||
import { Html, HtmlPropsSchema } from "@usewaypoint/block-html";
|
||||
import { Image, ImagePropsSchema } from "@usewaypoint/block-image";
|
||||
import { Spacer, SpacerPropsSchema } from "@usewaypoint/block-spacer";
|
||||
import { Text, TextPropsSchema } from "@usewaypoint/block-text";
|
||||
import {
|
||||
buildBlockComponent,
|
||||
buildBlockConfigurationDictionary,
|
||||
buildBlockConfigurationSchema
|
||||
} from "@usewaypoint/document-core";
|
||||
|
||||
import ColumnsContainerEditor from "../blocks/ColumnsContainer/ColumnsContainerEditor";
|
||||
import ColumnsContainerPropsSchema from "../blocks/ColumnsContainer/ColumnsContainerPropsSchema";
|
||||
import ContainerEditor from "../blocks/Container/ContainerEditor";
|
||||
import ContainerPropsSchema from "../blocks/Container/ContainerPropsSchema";
|
||||
import EmailLayoutEditor from "../blocks/EmailLayout/EmailLayoutEditor";
|
||||
import EmailLayoutPropsSchema from "../blocks/EmailLayout/EmailLayoutPropsSchema";
|
||||
import EditorBlockWrapper from "../blocks/helpers/block-wrappers/EditorBlockWrapper";
|
||||
|
||||
const EDITOR_DICTIONARY = buildBlockConfigurationDictionary({
|
||||
Avatar: {
|
||||
schema: AvatarPropsSchema,
|
||||
Component: (props) => (
|
||||
<EditorBlockWrapper>
|
||||
<Avatar {...props} />
|
||||
</EditorBlockWrapper>
|
||||
)
|
||||
},
|
||||
Button: {
|
||||
schema: ButtonPropsSchema,
|
||||
Component: (props) => (
|
||||
<EditorBlockWrapper>
|
||||
<Button {...props} />
|
||||
</EditorBlockWrapper>
|
||||
)
|
||||
},
|
||||
Container: {
|
||||
schema: ContainerPropsSchema,
|
||||
Component: (props) => (
|
||||
<EditorBlockWrapper>
|
||||
<ContainerEditor {...props} />
|
||||
</EditorBlockWrapper>
|
||||
)
|
||||
},
|
||||
ColumnsContainer: {
|
||||
schema: ColumnsContainerPropsSchema,
|
||||
Component: (props) => (
|
||||
<EditorBlockWrapper>
|
||||
<ColumnsContainerEditor {...props} />
|
||||
</EditorBlockWrapper>
|
||||
)
|
||||
},
|
||||
Heading: {
|
||||
schema: HeadingPropsSchema,
|
||||
Component: (props) => (
|
||||
<EditorBlockWrapper>
|
||||
<Heading {...props} />
|
||||
</EditorBlockWrapper>
|
||||
)
|
||||
},
|
||||
Html: {
|
||||
schema: HtmlPropsSchema,
|
||||
Component: (props) => (
|
||||
<EditorBlockWrapper>
|
||||
<Html {...props} />
|
||||
</EditorBlockWrapper>
|
||||
)
|
||||
},
|
||||
Image: {
|
||||
schema: ImagePropsSchema,
|
||||
Component: (data) => {
|
||||
const props = {
|
||||
...data,
|
||||
props: {
|
||||
...data.props,
|
||||
url:
|
||||
data.props?.url ??
|
||||
"https://placehold.co/600x400@2x/F8F8F8/CCC?text=Your%20image"
|
||||
}
|
||||
};
|
||||
return (
|
||||
<EditorBlockWrapper>
|
||||
<Image {...props} />
|
||||
</EditorBlockWrapper>
|
||||
);
|
||||
}
|
||||
},
|
||||
Text: {
|
||||
schema: TextPropsSchema,
|
||||
Component: (props) => (
|
||||
<EditorBlockWrapper>
|
||||
<Text {...props} />
|
||||
</EditorBlockWrapper>
|
||||
)
|
||||
},
|
||||
EmailLayout: {
|
||||
schema: EmailLayoutPropsSchema,
|
||||
Component: (p) => <EmailLayoutEditor {...p} />
|
||||
},
|
||||
Spacer: {
|
||||
schema: SpacerPropsSchema,
|
||||
Component: (props) => (
|
||||
<EditorBlockWrapper>
|
||||
<Spacer {...props} />
|
||||
</EditorBlockWrapper>
|
||||
)
|
||||
},
|
||||
Divider: {
|
||||
schema: DividerPropsSchema,
|
||||
Component: (props) => (
|
||||
<EditorBlockWrapper>
|
||||
<Divider {...props} />
|
||||
</EditorBlockWrapper>
|
||||
)
|
||||
}
|
||||
});
|
||||
|
||||
export const EditorBlock = buildBlockComponent(EDITOR_DICTIONARY);
|
||||
export const EditorBlockSchema =
|
||||
buildBlockConfigurationSchema(EDITOR_DICTIONARY);
|
||||
export const EditorConfigurationSchema = z.record(
|
||||
z.string(),
|
||||
EditorBlockSchema
|
||||
);
|
||||
|
||||
export type TEditorBlock = z.infer<typeof EditorBlockSchema>;
|
||||
export type TEditorConfiguration = Record<string, TEditorBlock>;
|
||||
@@ -0,0 +1,34 @@
|
||||
import EMPTY_EMAIL_MESSAGE from "./sample/empty-email-message";
|
||||
import getRequestEmail from "./sample/request-email";
|
||||
import getCompletionEmail from "./sample/completion-email";
|
||||
|
||||
export default function getConfiguration(
|
||||
template: string,
|
||||
) {
|
||||
|
||||
if (template.startsWith("#sample/")) {
|
||||
const sampleName = template.replace("#sample/", "");
|
||||
switch (sampleName) {
|
||||
case "requestemail":
|
||||
return getRequestEmail(
|
||||
);
|
||||
case "completionemail":
|
||||
return getCompletionEmail(
|
||||
);
|
||||
default:
|
||||
return "requestemail";
|
||||
}
|
||||
}
|
||||
|
||||
if (template.startsWith("#code/")) {
|
||||
const encodedString = template.replace("#code/", "");
|
||||
const configurationString = decodeURIComponent(atob(encodedString));
|
||||
try {
|
||||
return JSON.parse(configurationString);
|
||||
} catch {
|
||||
console.error(`Couldn't load configuration from hash.`);
|
||||
}
|
||||
}
|
||||
|
||||
return EMPTY_EMAIL_MESSAGE;
|
||||
}
|
||||
+259
@@ -0,0 +1,259 @@
|
||||
import { TEditorConfiguration } from "../../documents/editor/core";
|
||||
|
||||
const getCompletionEmail = (
|
||||
): TEditorConfiguration => {
|
||||
const appName =
|
||||
"OpenSign™";
|
||||
|
||||
const logoBlock =
|
||||
{
|
||||
"block-1709571212684": {
|
||||
type: "Image",
|
||||
data: {
|
||||
style: {
|
||||
padding: { top: 24, bottom: 24, right: 24, left: 24 }
|
||||
},
|
||||
props: {
|
||||
width: null,
|
||||
height: 50,
|
||||
url: "https://qikinnovation.ams3.digitaloceanspaces.com/logo.png",
|
||||
alt: "logo",
|
||||
linkHref: null,
|
||||
contentAlignment: "middle"
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
const logoBlockId =
|
||||
["block-1709571212684"];
|
||||
|
||||
return {
|
||||
root: {
|
||||
type: "EmailLayout",
|
||||
data: {
|
||||
backdropColor: "#f5f5f5",
|
||||
canvasColor: "#FFFFFF",
|
||||
canvasWidth: 600,
|
||||
textColor: "#242424",
|
||||
fontFamily: "MODERN_SANS",
|
||||
childrenIds: [
|
||||
...logoBlockId,
|
||||
"block-1770633502472",
|
||||
"block-1770795667636",
|
||||
"block-1770795483071"
|
||||
]
|
||||
}
|
||||
},
|
||||
...logoBlock,
|
||||
"block-1770633502472": {
|
||||
type: "Text",
|
||||
data: {
|
||||
style: {
|
||||
color: "#FFFFFF",
|
||||
backgroundColor: "#47A3AD",
|
||||
fontSize: 20,
|
||||
fontWeight: "bold",
|
||||
padding: {
|
||||
top: 16,
|
||||
bottom: 16,
|
||||
right: 24,
|
||||
left: 24
|
||||
}
|
||||
},
|
||||
props: {
|
||||
markdown: false,
|
||||
text: "Document signed successfully\n\n"
|
||||
}
|
||||
}
|
||||
},
|
||||
"block-1770633797211": {
|
||||
type: "Text",
|
||||
data: {
|
||||
style: {
|
||||
fontSize: 15,
|
||||
fontWeight: "bold",
|
||||
padding: {
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
right: 24,
|
||||
left: 24
|
||||
}
|
||||
},
|
||||
props: {
|
||||
text: "Sender"
|
||||
}
|
||||
}
|
||||
},
|
||||
"block-1770633813576": {
|
||||
type: "Text",
|
||||
data: {
|
||||
style: {
|
||||
color: "#626363",
|
||||
fontSize: 15,
|
||||
fontWeight: "bold",
|
||||
padding: {
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
right: 24,
|
||||
left: 0
|
||||
}
|
||||
},
|
||||
props: {
|
||||
text: "{{receiver_email}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"block-1770633912944": {
|
||||
type: "Text",
|
||||
data: {
|
||||
style: {
|
||||
fontSize: 15,
|
||||
fontWeight: "bold",
|
||||
padding: {
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
right: 24,
|
||||
left: 24
|
||||
}
|
||||
},
|
||||
props: {
|
||||
text: "Organization"
|
||||
}
|
||||
}
|
||||
},
|
||||
"block-1770633915601": {
|
||||
type: "Text",
|
||||
data: {
|
||||
style: {
|
||||
color: "#626363",
|
||||
fontSize: 15,
|
||||
fontWeight: "bold",
|
||||
padding: {
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
right: 24,
|
||||
left: 0
|
||||
}
|
||||
},
|
||||
props: {
|
||||
text: "{{company_name}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"block-1770633918679": {
|
||||
type: "Text",
|
||||
data: {
|
||||
style: {
|
||||
fontSize: 15,
|
||||
fontWeight: "bold",
|
||||
padding: {
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
right: 24,
|
||||
left: 24
|
||||
}
|
||||
},
|
||||
props: {
|
||||
text: "Expires on"
|
||||
}
|
||||
}
|
||||
},
|
||||
"block-1770633921948": {
|
||||
type: "Text",
|
||||
data: {
|
||||
style: {
|
||||
color: "#626363",
|
||||
backgroundColor: null,
|
||||
fontSize: 15,
|
||||
fontWeight: "bold",
|
||||
padding: {
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
right: 24,
|
||||
left: 0
|
||||
}
|
||||
},
|
||||
props: {
|
||||
text: "{{expiry_date}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"block-1770633961786": {
|
||||
type: "Text",
|
||||
data: {
|
||||
style: {
|
||||
fontSize: 15,
|
||||
fontWeight: "bold",
|
||||
padding: {
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
right: 24,
|
||||
left: 24
|
||||
}
|
||||
},
|
||||
props: {
|
||||
text: "Note"
|
||||
}
|
||||
}
|
||||
},
|
||||
"block-1770633964531": {
|
||||
type: "Text",
|
||||
data: {
|
||||
style: {
|
||||
color: "#626363",
|
||||
fontSize: 15,
|
||||
fontWeight: "bold",
|
||||
padding: {
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
right: 24,
|
||||
left: 0
|
||||
}
|
||||
},
|
||||
props: {
|
||||
text: "{{note}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"block-1770795483071": {
|
||||
type: "Html",
|
||||
data: {
|
||||
style: {
|
||||
backgroundColor: "#f5f5f5",
|
||||
fontSize: 14,
|
||||
textAlign: null,
|
||||
padding: {
|
||||
top: 16,
|
||||
bottom: 16,
|
||||
right: 24,
|
||||
left: 24
|
||||
}
|
||||
},
|
||||
props: {
|
||||
contents: `This is an automated email from ${appName}. For any queries regarding this email, please contact the sender <a href="mailto:{{sender_mail}}" target="_blank">{{sender_mail}}</a> directly.`
|
||||
}
|
||||
}
|
||||
},
|
||||
"block-1770795667636": {
|
||||
type: "Html",
|
||||
data: {
|
||||
style: {
|
||||
fontSize: 14,
|
||||
textAlign: null,
|
||||
padding: {
|
||||
top: 32,
|
||||
bottom: 20,
|
||||
right: 20,
|
||||
left: 20
|
||||
}
|
||||
},
|
||||
props: {
|
||||
contents:
|
||||
'All parties have successfully signed the document "<b>{{document_title}}</b>". Kindly download the document from the attachment.'
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export default getCompletionEmail;
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
import { TEditorConfiguration } from "../../documents/editor/core";
|
||||
|
||||
const EMPTY_EMAIL_MESSAGE: TEditorConfiguration = {
|
||||
root: {
|
||||
type: "EmailLayout",
|
||||
data: {
|
||||
backdropColor: "#F5F5F5",
|
||||
canvasColor: "#FFFFFF",
|
||||
canvasWidth: 600,
|
||||
textColor: "#262626",
|
||||
fontFamily: "MODERN_SANS",
|
||||
childrenIds: []
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default EMPTY_EMAIL_MESSAGE;
|
||||
@@ -0,0 +1,323 @@
|
||||
import { TEditorConfiguration } from "../../documents/editor/core";
|
||||
|
||||
const getRequestEmail = (
|
||||
): TEditorConfiguration => {
|
||||
const appName =
|
||||
"OpenSign™";
|
||||
|
||||
const logoBlock =
|
||||
{
|
||||
"block-1709571212684": {
|
||||
type: "Image",
|
||||
data: {
|
||||
style: {
|
||||
padding: { top: 24, bottom: 24, right: 24, left: 24 }
|
||||
},
|
||||
props: {
|
||||
width: null,
|
||||
height: 50,
|
||||
url: "https://qikinnovation.ams3.digitaloceanspaces.com/logo.png",
|
||||
alt: "logo",
|
||||
linkHref: null,
|
||||
contentAlignment: "middle"
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
const logoBlockId =
|
||||
["block-1709571212684"];
|
||||
|
||||
return {
|
||||
root: {
|
||||
type: "EmailLayout",
|
||||
data: {
|
||||
backdropColor: "#f5f5f5",
|
||||
canvasColor: "#FFFFFF",
|
||||
canvasWidth: 600,
|
||||
textColor: "#242424",
|
||||
fontFamily: "MODERN_SANS",
|
||||
childrenIds: [
|
||||
...logoBlockId,
|
||||
"block-1770633502472",
|
||||
"block-1770633643816",
|
||||
"block-1770633750385",
|
||||
"block-1770633994542",
|
||||
"block-1770795931867"
|
||||
]
|
||||
}
|
||||
},
|
||||
...logoBlock,
|
||||
"block-1770633502472": {
|
||||
type: "Text",
|
||||
data: {
|
||||
style: {
|
||||
color: "#FFFFFF",
|
||||
backgroundColor: "#47A3AD",
|
||||
fontSize: 20,
|
||||
fontWeight: "bold",
|
||||
padding: {
|
||||
top: 16,
|
||||
bottom: 16,
|
||||
right: 24,
|
||||
left: 24
|
||||
}
|
||||
},
|
||||
props: {
|
||||
markdown: false,
|
||||
text: "Digital Signature Request"
|
||||
}
|
||||
}
|
||||
},
|
||||
"block-1770633643816": {
|
||||
type: "Html",
|
||||
data: {
|
||||
style: {
|
||||
fontSize: 14,
|
||||
textAlign: null,
|
||||
padding: {
|
||||
top: 20,
|
||||
bottom: 20,
|
||||
right: 20,
|
||||
left: 20
|
||||
}
|
||||
},
|
||||
props: {
|
||||
contents:
|
||||
"{{sender_name}} has requested you to review and sign <b>{{document_title}}</b>."
|
||||
}
|
||||
}
|
||||
},
|
||||
"block-1770633750385": {
|
||||
type: "ColumnsContainer",
|
||||
data: {
|
||||
style: {
|
||||
padding: {
|
||||
top: 20,
|
||||
bottom: 32,
|
||||
right: 20,
|
||||
left: 4
|
||||
}
|
||||
},
|
||||
props: {
|
||||
fixedWidths: [128, null, null],
|
||||
columnsCount: 2,
|
||||
columnsGap: 0,
|
||||
contentAlignment: "top",
|
||||
columns: [
|
||||
{
|
||||
childrenIds: [
|
||||
"block-1770633797211",
|
||||
"block-1770633912944",
|
||||
"block-1770633918679",
|
||||
"block-1770633961786"
|
||||
]
|
||||
},
|
||||
{
|
||||
childrenIds: [
|
||||
"block-1770633813576",
|
||||
"block-1770633915601",
|
||||
"block-1770633921948",
|
||||
"block-1770633964531"
|
||||
]
|
||||
},
|
||||
{
|
||||
childrenIds: []
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"block-1770633797211": {
|
||||
type: "Text",
|
||||
data: {
|
||||
style: {
|
||||
fontSize: 15,
|
||||
fontWeight: "bold",
|
||||
padding: {
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
right: 24,
|
||||
left: 24
|
||||
}
|
||||
},
|
||||
props: {
|
||||
text: "Sender"
|
||||
}
|
||||
}
|
||||
},
|
||||
"block-1770633813576": {
|
||||
type: "Text",
|
||||
data: {
|
||||
style: {
|
||||
color: "#626363",
|
||||
fontSize: 15,
|
||||
fontWeight: "bold",
|
||||
padding: {
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
right: 24,
|
||||
left: 0
|
||||
}
|
||||
},
|
||||
props: {
|
||||
text: "{{sender_mail}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"block-1770633912944": {
|
||||
type: "Text",
|
||||
data: {
|
||||
style: {
|
||||
fontSize: 15,
|
||||
fontWeight: "bold",
|
||||
padding: {
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
right: 24,
|
||||
left: 24
|
||||
}
|
||||
},
|
||||
props: {
|
||||
text: "Organization"
|
||||
}
|
||||
}
|
||||
},
|
||||
"block-1770633915601": {
|
||||
type: "Text",
|
||||
data: {
|
||||
style: {
|
||||
color: "#626363",
|
||||
fontSize: 15,
|
||||
fontWeight: "bold",
|
||||
padding: {
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
right: 24,
|
||||
left: 0
|
||||
}
|
||||
},
|
||||
props: {
|
||||
text: "{{company_name}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"block-1770633918679": {
|
||||
type: "Text",
|
||||
data: {
|
||||
style: {
|
||||
fontSize: 15,
|
||||
fontWeight: "bold",
|
||||
padding: {
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
right: 24,
|
||||
left: 24
|
||||
}
|
||||
},
|
||||
props: {
|
||||
text: "Expires on"
|
||||
}
|
||||
}
|
||||
},
|
||||
"block-1770633921948": {
|
||||
type: "Text",
|
||||
data: {
|
||||
style: {
|
||||
color: "#626363",
|
||||
backgroundColor: null,
|
||||
fontSize: 15,
|
||||
fontWeight: "bold",
|
||||
padding: {
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
right: 24,
|
||||
left: 0
|
||||
}
|
||||
},
|
||||
props: {
|
||||
text: "{{expiry_date}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"block-1770633961786": {
|
||||
type: "Text",
|
||||
data: {
|
||||
style: {
|
||||
fontSize: 15,
|
||||
fontWeight: "bold",
|
||||
padding: {
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
right: 24,
|
||||
left: 24
|
||||
}
|
||||
},
|
||||
props: {
|
||||
text: "Note"
|
||||
}
|
||||
}
|
||||
},
|
||||
"block-1770633964531": {
|
||||
type: "Text",
|
||||
data: {
|
||||
style: {
|
||||
color: "#626363",
|
||||
fontSize: 15,
|
||||
fontWeight: "bold",
|
||||
padding: {
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
right: 24,
|
||||
left: 0
|
||||
}
|
||||
},
|
||||
props: {
|
||||
text: "{{note}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"block-1770633994542": {
|
||||
type: "Button",
|
||||
data: {
|
||||
style: {
|
||||
fontSize: 14,
|
||||
textAlign: "left",
|
||||
padding: {
|
||||
top: 12,
|
||||
bottom: 20,
|
||||
right: 12,
|
||||
left: 52
|
||||
}
|
||||
},
|
||||
props: {
|
||||
buttonBackgroundColor: "#D46B0f",
|
||||
buttonStyle: "rectangle",
|
||||
fullWidth: false,
|
||||
size: "medium",
|
||||
text: "Sign Here",
|
||||
url: "{{signing_url}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"block-1770795931867": {
|
||||
type: "Html",
|
||||
data: {
|
||||
style: {
|
||||
backgroundColor: "#f5f5f5",
|
||||
fontSize: 14,
|
||||
textAlign: null,
|
||||
padding: {
|
||||
top: 16,
|
||||
bottom: 16,
|
||||
right: 24,
|
||||
left: 24
|
||||
}
|
||||
},
|
||||
props: {
|
||||
contents: `This is an automated email from ${appName}. For any queries regarding this email, please contact the sender <a href="mailto:{{sender_mail}}" target="_blank">{{sender_mail}}</a> directly.`
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
export default getRequestEmail;
|
||||
@@ -0,0 +1,41 @@
|
||||
import EmailBodyEditor from "../EmailBodyEditor";
|
||||
import { QuillEmailEditor } from "./quillemailbuilder";
|
||||
|
||||
const EmailEditor = ({
|
||||
type = "basic",
|
||||
values = {},
|
||||
onChange,
|
||||
placeholder = "add body of email",
|
||||
bodyName,
|
||||
isReset,
|
||||
isTemplateLoaded,
|
||||
smallscreen
|
||||
}) => {
|
||||
const currentValue = values[type] ?? "";
|
||||
const handleChange = (newValue) => {
|
||||
onChange?.(newValue, type);
|
||||
};
|
||||
|
||||
if (type === "advanced") {
|
||||
return (
|
||||
<EmailBodyEditor
|
||||
value={currentValue}
|
||||
onChange={handleChange}
|
||||
bodyName={bodyName}
|
||||
isReset={isReset}
|
||||
isTemplateLoaded={isTemplateLoaded}
|
||||
smallscreen={smallscreen}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<QuillEmailEditor
|
||||
value={currentValue}
|
||||
onChange={handleChange}
|
||||
placeholder={placeholder}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default EmailEditor;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user