From c696890cf5afc8f789392d61909fd614cf4e403d Mon Sep 17 00:00:00 2001 From: Alex Turchyn Date: Tue, 14 Jan 2025 00:10:50 +0200 Subject: [PATCH 01/82] fix dropdowns in Firefox --- app/javascript/template_builder/area.vue | 2 +- app/javascript/template_builder/field.vue | 2 +- app/javascript/template_builder/field_submitter.vue | 2 +- app/javascript/template_builder/field_type.vue | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/javascript/template_builder/area.vue b/app/javascript/template_builder/area.vue index afd6d62c..fb968221 100644 --- a/app/javascript/template_builder/area.vue +++ b/app/javascript/template_builder/area.vue @@ -479,7 +479,7 @@ export default { methods: { buildDefaultName: Field.methods.buildDefaultName, closeDropdown () { - document.activeElement.blur() + this.$el.getRootNode().activeElement.blur() }, maybeToggleDefaultValue () { if (['text', 'number'].includes(this.field.type)) { diff --git a/app/javascript/template_builder/field.vue b/app/javascript/template_builder/field.vue index 169f2ae3..d581fc7a 100644 --- a/app/javascript/template_builder/field.vue +++ b/app/javascript/template_builder/field.vue @@ -392,7 +392,7 @@ export default { return this.sortedAreas[0] && this.$emit('scroll-to', this.sortedAreas[0]) }, closeDropdown () { - document.activeElement.blur() + this.$el.getRootNode().activeElement.blur() }, addOption () { this.field.options.push({ value: '', uuid: v4() }) diff --git a/app/javascript/template_builder/field_submitter.vue b/app/javascript/template_builder/field_submitter.vue index 68a6e193..b5949e55 100644 --- a/app/javascript/template_builder/field_submitter.vue +++ b/app/javascript/template_builder/field_submitter.vue @@ -362,7 +362,7 @@ export default { this.$emit('new-submitter', newSubmitter) }, closeDropdown () { - document.activeElement.blur() + this.$el.getRootNode().activeElement.blur() } } } diff --git a/app/javascript/template_builder/field_type.vue b/app/javascript/template_builder/field_type.vue index 8f17d256..4a72227d 100644 --- a/app/javascript/template_builder/field_type.vue +++ b/app/javascript/template_builder/field_type.vue @@ -170,7 +170,7 @@ export default { }, methods: { closeDropdown () { - document.activeElement.blur() + this.$el.getRootNode().activeElement.blur() } } } From e3f3895aac91a65da056814b266a0a84437d449e Mon Sep 17 00:00:00 2001 From: Alex Turchyn Date: Tue, 14 Jan 2025 23:07:39 +0200 Subject: [PATCH 02/82] fix scrolling to fields on Android devices --- app/javascript/submission_form/form.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/javascript/submission_form/form.vue b/app/javascript/submission_form/form.vue index c1d0d8a1..cb41d182 100644 --- a/app/javascript/submission_form/form.vue +++ b/app/javascript/submission_form/form.vue @@ -1113,9 +1113,9 @@ export default { this.minimizeForm() } - const isMobileSafariIos = 'ontouchstart' in window && navigator.maxTouchPoints > 0 && /AppleWebKit/i.test(navigator.userAgent) + const isMobile = 'ontouchstart' in window && navigator.maxTouchPoints > 0 && /AppleWebKit|android/i.test(navigator.userAgent) - if (isMobileSafariIos || /iPhone|iPad|iPod/i.test(navigator.userAgent)) { + if (isMobile || /iPhone|iPad|iPod/i.test(navigator.userAgent)) { this.$nextTick(() => { const root = this.$root.$el.parentNode.getRootNode() const scrollbox = root.getElementById('scrollbox') From 92b61992f2b0c1594cce1ccf17a699dbd6149a9d Mon Sep 17 00:00:00 2001 From: Alex Turchyn Date: Wed, 15 Jan 2025 01:19:17 +0200 Subject: [PATCH 03/82] fix vertical scrolling for mobile devices --- app/javascript/template_builder/builder.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/javascript/template_builder/builder.vue b/app/javascript/template_builder/builder.vue index 1e22e3b9..96670b9d 100644 --- a/app/javascript/template_builder/builder.vue +++ b/app/javascript/template_builder/builder.vue @@ -222,8 +222,8 @@
Date: Wed, 15 Jan 2025 13:57:30 +0200 Subject: [PATCH 04/82] Revert "fix scrolling to fields on Android devices" This reverts commit e3f3895aac91a65da056814b266a0a84437d449e. --- app/javascript/submission_form/form.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/javascript/submission_form/form.vue b/app/javascript/submission_form/form.vue index cb41d182..c1d0d8a1 100644 --- a/app/javascript/submission_form/form.vue +++ b/app/javascript/submission_form/form.vue @@ -1113,9 +1113,9 @@ export default { this.minimizeForm() } - const isMobile = 'ontouchstart' in window && navigator.maxTouchPoints > 0 && /AppleWebKit|android/i.test(navigator.userAgent) + const isMobileSafariIos = 'ontouchstart' in window && navigator.maxTouchPoints > 0 && /AppleWebKit/i.test(navigator.userAgent) - if (isMobile || /iPhone|iPad|iPod/i.test(navigator.userAgent)) { + if (isMobileSafariIos || /iPhone|iPad|iPod/i.test(navigator.userAgent)) { this.$nextTick(() => { const root = this.$root.$el.parentNode.getRootNode() const scrollbox = root.getElementById('scrollbox') From 9bd0ad70c4633544144abd3a1eff8225e7ba10e3 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Thu, 16 Jan 2025 00:40:22 +0200 Subject: [PATCH 05/82] add default field options --- app/javascript/template_builder/builder.vue | 6 +++++- app/javascript/template_builder/field.vue | 8 ++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/app/javascript/template_builder/builder.vue b/app/javascript/template_builder/builder.vue index 96670b9d..ebb0f191 100644 --- a/app/javascript/template_builder/builder.vue +++ b/app/javascript/template_builder/builder.vue @@ -1320,7 +1320,11 @@ export default { if (!this.fieldsDragFieldRef.value) { if (['select', 'multiple', 'radio'].includes(field.type)) { - field.options = [{ value: '', uuid: v4() }] + if (this.dragField?.options?.length) { + field.options = this.dragField.options.map(option => ({ value: option, uuid: v4() })) + } else { + field.options = [{ value: '', uuid: v4() }] + } } if (['stamp', 'heading'].includes(field.type)) { diff --git a/app/javascript/template_builder/field.vue b/app/javascript/template_builder/field.vue index d581fc7a..b4941cf7 100644 --- a/app/javascript/template_builder/field.vue +++ b/app/javascript/template_builder/field.vue @@ -192,14 +192,14 @@ class="w-full input input-primary input-xs text-sm bg-transparent" :placeholder="`${t('option')} ${index + 1}`" type="text" - :readonly="!editable" + :readonly="!editable || defaultField" required dir="auto" @focus="maybeFocusOnOptionArea(option)" @blur="save" >
<% elsif field['type'] == 'checkbox' %> <%= svg_icon('check', class: 'w-6 h-6') %> + <% elsif field['type'] == 'number' %> + <%= NumberUtils.format_number(value, field.dig('preferences', 'format')) %> <% elsif field['type'] == 'date' %> <%= TimeUtils.format_date_string(value, field.dig('preferences', 'format'), @submission.account.locale) %> <% else %> diff --git a/lib/number_utils.rb b/lib/number_utils.rb index d0ab673c..4cfa3879 100644 --- a/lib/number_utils.rb +++ b/lib/number_utils.rb @@ -4,7 +4,16 @@ module NumberUtils FORMAT_LOCALES = { 'dot' => 'de', 'space' => 'fr', - 'comma' => 'en' + 'comma' => 'en', + 'usd' => 'en', + 'eur' => 'fr', + 'gbp' => 'en' + }.freeze + + CURRENCY_SYMBOLS = { + 'usd' => '$', + 'eur' => '€', + 'gbp' => '£' }.freeze module_function @@ -12,7 +21,9 @@ module NumberUtils def format_number(number, format) locale = FORMAT_LOCALES[format] - if locale + if CURRENCY_SYMBOLS[format] + ApplicationController.helpers.number_to_currency(number, locale:, precision: 2, unit: CURRENCY_SYMBOLS[format]) + elsif locale ApplicationController.helpers.number_with_delimiter(number, locale:) else number From 794e3394252ce71561f6bc9c98a0322d4cc9187e Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Sat, 18 Jan 2025 16:23:01 +0200 Subject: [PATCH 14/82] add verification settings --- .../template_builder/field_settings.vue | 29 ++++++++++++++++++- app/javascript/template_builder/i18n.js | 6 ++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/app/javascript/template_builder/field_settings.vue b/app/javascript/template_builder/field_settings.vue index 0916f383..5f7cacc9 100644 --- a/app/javascript/template_builder/field_settings.vue +++ b/app/javascript/template_builder/field_settings.vue @@ -26,6 +26,33 @@ {{ t('format') }}
+
+ + +
  • -
  • +
  • this.names.indexOf(s.name))) @@ -328,6 +344,13 @@ export default { selectSubmitter (submitter) { this.$emit('update:model-value', submitter.uuid) }, + getOrdinalSuffix (num) { + if (num % 10 === 1 && num % 100 !== 11) return 'st' + if (num % 10 === 2 && num % 100 !== 12) return 'nd' + if (num % 10 === 3 && num % 100 !== 13) return 'rd' + + return 'th' + }, remove (submitter) { if (window.confirm(this.t('are_you_sure_'))) { this.$emit('remove', submitter) diff --git a/app/javascript/template_builder/fields.vue b/app/javascript/template_builder/fields.vue index 8bff615b..127fc7d6 100644 --- a/app/javascript/template_builder/fields.vue +++ b/app/javascript/template_builder/fields.vue @@ -5,7 +5,7 @@ class="roles-dropdown w-full rounded-lg" :style="withStickySubmitters ? { backgroundColor } : {}" :submitters="submitters" - :menu-style="{ backgroundColor: ['', null, 'transparent'].includes(backgroundColor) ? 'white' : backgroundColor }" + :menu-style="{ overflow: 'auto', display: 'flex', flexDirection: 'row', maxHeight: 'calc(100vh - 120px)', backgroundColor: ['', null, 'transparent'].includes(backgroundColor) ? 'white' : backgroundColor }" :editable="editable && !defaultSubmitters.length" @new-submitter="save" @remove="removeSubmitter" diff --git a/app/javascript/template_builder/i18n.js b/app/javascript/template_builder/i18n.js index d8fa6f2e..73a6c17f 100644 --- a/app/javascript/template_builder/i18n.js +++ b/app/javascript/template_builder/i18n.js @@ -1,4 +1,5 @@ const en = { + party: 'Party', method: 'Method', reorder_fields: 'Reorder fields', verify_id: 'Verify ID', @@ -160,6 +161,7 @@ const en = { } const es = { + party: 'Parte', method: 'Método', reorder_fields: 'Reordenar campos', verify_id: 'Verificar ID', @@ -321,6 +323,7 @@ const es = { } const it = { + party: 'Parte', method: 'Metodo', reorder_fields: 'Riordina i campi', verify_id: 'Verifica ID', @@ -482,6 +485,7 @@ const it = { } const pt = { + party: 'Parte', method: 'Método', reorder_fields: 'Reorganizar campos', verify_id: 'Verificar ID', @@ -643,6 +647,7 @@ const pt = { } const fr = { + party: 'Partie', method: 'Méthode', reorder_fields: 'Réorganiser les champs', verify_id: "Vérifier l'ID", @@ -804,6 +809,7 @@ const fr = { } const de = { + party: 'Partei', method: 'Verfahren', reorder_fields: 'Felder neu anordnen', verify_id: 'ID überprüfen', diff --git a/app/views/submissions/show.html.erb b/app/views/submissions/show.html.erb index 083651cd..71568920 100644 --- a/app/views/submissions/show.html.erb +++ b/app/views/submissions/show.html.erb @@ -118,7 +118,7 @@
    - + <%= (@submission.template_submitters || @submission.template.submitters).find { |e| e['uuid'] == submitter&.uuid }&.dig('name') || "#{(index + 1).ordinalize} Submitter" %> From cd05c1fd19be677ea6844e8a05006af294993c28 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Mon, 20 Jan 2025 23:05:15 +0200 Subject: [PATCH 20/82] search field by title --- app/javascript/template_builder/fields.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/template_builder/fields.vue b/app/javascript/template_builder/fields.vue index 127fc7d6..2eefac39 100644 --- a/app/javascript/template_builder/fields.vue +++ b/app/javascript/template_builder/fields.vue @@ -327,7 +327,7 @@ export default { }, filteredSubmitterDefaultFields () { if (this.defaultFieldsSearch) { - return this.submitterDefaultFields.filter((f) => f.name.toLowerCase().includes(this.defaultFieldsSearch.toLowerCase())) + return this.submitterDefaultFields.filter((f) => (f.title || f.name).toLowerCase().includes(this.defaultFieldsSearch.toLowerCase())) } else { return this.submitterDefaultFields } From 0cb25574472fc28e43d507df25ec9f399f76ac27 Mon Sep 17 00:00:00 2001 From: Alex Turchyn Date: Tue, 21 Jan 2025 13:05:12 +0200 Subject: [PATCH 21/82] add warning message to navbar --- app/views/shared/_navbar.html.erb | 1 + app/views/shared/_navbar_warning.html.erb | 0 config/locales/i18n.yml | 12 ++++++++++++ 3 files changed, 13 insertions(+) create mode 100644 app/views/shared/_navbar_warning.html.erb diff --git a/app/views/shared/_navbar.html.erb b/app/views/shared/_navbar.html.erb index bdb4a26b..0a65fd2e 100644 --- a/app/views/shared/_navbar.html.erb +++ b/app/views/shared/_navbar.html.erb @@ -1,3 +1,4 @@ +<%= render 'shared/navbar_warning' %>
    diff --git a/app/views/shared/_navbar_warning.html.erb b/app/views/shared/_navbar_warning.html.erb new file mode 100644 index 00000000..e69de29b diff --git a/config/locales/i18n.yml b/config/locales/i18n.yml index 83a9d9e0..1f84f29b 100644 --- a/config/locales/i18n.yml +++ b/config/locales/i18n.yml @@ -688,6 +688,8 @@ en: &en tell_us_more_about_your_experience: Tell us more about your experience extremely_dissatisfied: Extremely Dissatisfied extremely_satisfied: Extremely Satisfied + your_pro_plan_payment_is_overdue: Your Pro plan payment is overdue. + click_here_to_update_your_payment_details_and_clear_the_invoice_to_ensure_uninterrupted_service_html: Click here to update your payment details and clear the invoice to ensure uninterrupted service. submission_event_names: send_email_to_html: 'Email sent to %{submitter_name}' send_reminder_email_to_html: 'Reminder email sent to %{submitter_name}' @@ -1396,6 +1398,8 @@ es: &es tell_us_more_about_your_experience: Cuéntanos más sobre tu experiencia extremely_dissatisfied: Extremadamente insatisfecho extremely_satisfied: Extremadamente satisfecho + your_pro_plan_payment_is_overdue: El pago de tu plan Pro está atrasado. + click_here_to_update_your_payment_details_and_clear_the_invoice_to_ensure_uninterrupted_service_html: 'Haz clic aquí para actualizar tus datos de pago y liquidar la factura para garantizar un servicio ininterrumpido.' submission_event_names: send_email_to_html: 'Correo electrónico enviado a %{submitter_name}' send_reminder_email_to_html: 'Correo de recordatorio enviado a %{submitter_name}' @@ -2103,6 +2107,8 @@ it: &it tell_us_more_about_your_experience: Raccontaci di più sulla tua esperienza extremely_dissatisfied: Estremamente insoddisfatto extremely_satisfied: Estremamente soddisfatto + your_pro_plan_payment_is_overdue: Il pagamento del tuo piano Pro è in ritardo. + click_here_to_update_your_payment_details_and_clear_the_invoice_to_ensure_uninterrupted_service_html: 'Fai clic qui per aggiornare i tuoi dati di pagamento e saldare la fattura per garantire un servizio ininterrotto.' submission_event_names: send_email_to_html: 'E-mail inviato a %{submitter_name}' send_reminder_email_to_html: 'E-mail di promemoria inviato a %{submitter_name}' @@ -2812,6 +2818,8 @@ fr: &fr tell_us_more_about_your_experience: Parlez-nous davantage de votre expérience extremely_dissatisfied: Extrêmement insatisfait extremely_satisfied: Extrêmement satisfait + your_pro_plan_payment_is_overdue: Le paiement de votre plan Pro est en retard. + click_here_to_update_your_payment_details_and_clear_the_invoice_to_ensure_uninterrupted_service_html: 'Cliquez ici pour mettre à jour vos informations de paiement et régler la facture afin de garantir un service ininterrompu.' submission_event_names: send_email_to_html: 'E-mail envoyé à %{submitter_name}' send_reminder_email_to_html: 'E-mail de rappel envoyé à %{submitter_name}' @@ -3520,6 +3528,8 @@ pt: &pt tell_us_more_about_your_experience: Conte-nos mais sobre sua experiência extremely_dissatisfied: Extremamente insatisfeito extremely_satisfied: Extremamente satisfeito + your_pro_plan_payment_is_overdue: O pagamento do seu plano Pro está atrasado. + click_here_to_update_your_payment_details_and_clear_the_invoice_to_ensure_uninterrupted_service_html: 'Clique aqui para atualizar seus dados de pagamento e quitar a fatura para garantir um serviço ininterrupto.' submission_event_names: send_email_to_html: 'E-mail enviado para %{submitter_name}' send_reminder_email_to_html: 'E-mail de lembrete enviado para %{submitter_name}' @@ -4228,6 +4238,8 @@ de: &de tell_us_more_about_your_experience: Erzählen Sie uns mehr über Ihre Erfahrung extremely_dissatisfied: Extrem unzufrieden extremely_satisfied: Extrem zufrieden + your_pro_plan_payment_is_overdue: Ihre Zahlung für den Pro-Plan ist überfällig. + click_here_to_update_your_payment_details_and_clear_the_invoice_to_ensure_uninterrupted_service_html: 'Klicken Sie hier, um Ihre Zahlungsdaten zu aktualisieren und die Rechnung zu begleichen, um einen unterbrechungsfreien Service sicherzustellen.' submission_event_names: send_email_to_html: 'E-Mail gesendet an %{submitter_name}' send_reminder_email_to_html: 'Erinnerungs-E-Mail gesendet an %{submitter_name}' From d52c57df72c7b36c2b3d60376f27664203762ffb Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Wed, 22 Jan 2025 11:49:04 +0200 Subject: [PATCH 22/82] adjust i18n --- app/controllers/accounts_controller.rb | 2 +- config/locales/i18n.yml | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb index 454ac39b..daeb44cc 100644 --- a/app/controllers/accounts_controller.rb +++ b/app/controllers/accounts_controller.rb @@ -50,7 +50,7 @@ class AccountsController < ApplicationController # rubocop:disable Layout/LineLength render turbo_stream: turbo_stream.replace( :account_delete_button, - html: helpers.tag.p(I18n.t('your_account_removal_request_will_be_processed_within_2_weeks_please_contact_us_if_you_want_to_keep_your_account')) + html: helpers.tag.p(I18n.t('your_account_removal_request_will_be_processed_within_2_months_please_contact_us_if_you_want_to_keep_your_account')) ) # rubocop:enable Layout/LineLength end diff --git a/config/locales/i18n.yml b/config/locales/i18n.yml index 1f84f29b..e710474a 100644 --- a/config/locales/i18n.yml +++ b/config/locales/i18n.yml @@ -165,7 +165,7 @@ en: &en schedule_account_for_deletion_: Schedule account for deletion? account_information_has_been_updated: Account information has been updated. should_be_a_valid_url: should be a valid URL - your_account_removal_request_will_be_processed_within_2_weeks_please_contact_us_if_you_want_to_keep_your_account: Your account removal request will be processed within 2 weeks. Please contact us if you want to keep your account. + your_account_removal_request_will_be_processed_within_2_months_please_contact_us_if_you_want_to_keep_your_account: Your account removal request will be processed within 2 months. Please contact us if you want to keep your account. test_mode: Test mode copy: Copy copied: Copied @@ -875,7 +875,7 @@ es: &es schedule_account_for_deletion_: ¿Programar la eliminación de la cuenta? account_information_has_been_updated: La información de la cuenta ha sido actualizada. should_be_a_valid_url: debe ser una URL válida - your_account_removal_request_will_be_processed_within_2_weeks_please_contact_us_if_you_want_to_keep_your_account: Tu solicitud de eliminación de cuenta se procesará en un plazo de 2 semanas. Por favor contáctanos si deseas mantener tu cuenta. + your_account_removal_request_will_be_processed_within_2_months_please_contact_us_if_you_want_to_keep_your_account: Tu solicitud de eliminación de cuenta se procesará en un plazo de 2 meses. Por favor contáctanos si deseas mantener tu cuenta. test_mode: Modo de prueba copy: Copiar copied: Copiado @@ -1584,7 +1584,7 @@ it: &it schedule_account_for_deletion_: "Programmare l'eliminazione dell'account?" account_information_has_been_updated: "Le informazioni dell'account sono state aggiornate." should_be_a_valid_url: deve essere un URL valido - your_account_removal_request_will_be_processed_within_2_weeks_please_contact_us_if_you_want_to_keep_your_account: "La tua richiesta di rimozione dell'account sarà elaborata entro 2 settimane. Contattaci se desideri mantenere il tuo account." + your_account_removal_request_will_be_processed_within_2_months_please_contact_us_if_you_want_to_keep_your_account: "La tua richiesta di rimozione dell'account sarà elaborata entro 2 mesi. Contattaci se desideri mantenere il tuo account." test_mode: Modalità di test copy: Copia copied: Copiato @@ -2295,7 +2295,7 @@ fr: &fr schedule_account_for_deletion_: Programmer la suppression du compte? account_information_has_been_updated: Les informations du compte ont été mises à jour. should_be_a_valid_url: doit être une URL valide - your_account_removal_request_will_be_processed_within_2_weeks_please_contact_us_if_you_want_to_keep_your_account: Votre demande de suppression du compte sera traitée dans un délai de 2 semaines. Veuillez nous contacter si vous souhaitez conserver votre compte. + your_account_removal_request_will_be_processed_within_2_months_please_contact_us_if_you_want_to_keep_your_account: Votre demande de suppression du compte sera traitée dans un délai de 2 mois. Veuillez nous contacter si vous souhaitez conserver votre compte. test_mode: Mode test copy: Copier copied: Copié @@ -3005,7 +3005,7 @@ pt: &pt schedule_account_for_deletion_: Agendar exclusão da conta? account_information_has_been_updated: As informações da conta foram atualizadas. should_be_a_valid_url: deve ser um URL válido - your_account_removal_request_will_be_processed_within_2_weeks_please_contact_us_if_you_want_to_keep_your_account: Seu pedido de remoção da conta será processado em até 2 semanas. Entre em contato conosco se você quiser manter sua conta. + your_account_removal_request_will_be_processed_within_2_months_please_contact_us_if_you_want_to_keep_your_account: Seu pedido de remoção da conta será processado em até 2 meses. Entre em contato conosco se você quiser manter sua conta. test_mode: Modo de teste copy: Copiar copied: Copiado @@ -3715,7 +3715,7 @@ de: &de schedule_account_for_deletion_: Konto zur Löschung einplanen? account_information_has_been_updated: Die Kontoinformationen wurden aktualisiert. should_be_a_valid_url: sollte eine gültige URL sein - your_account_removal_request_will_be_processed_within_2_weeks_please_contact_us_if_you_want_to_keep_your_account: Deine Anfrage zur Kontolöschung wird innerhalb von 2 Wochen bearbeitet. Bitte kontaktiere uns, wenn du dein Konto behalten möchtest. + your_account_removal_request_will_be_processed_within_2_months_please_contact_us_if_you_want_to_keep_your_account: Deine Anfrage zur Kontolöschung wird innerhalb von 2 Monaten bearbeitet. Bitte kontaktiere uns, wenn du dein Konto behalten möchtest. test_mode: Testmodus copy: Kopieren copied: Kopiert From 4dc325a6bb624a1269878a59ac3642c4c76d41c3 Mon Sep 17 00:00:00 2001 From: Alex Turchyn Date: Wed, 22 Jan 2025 20:58:47 +0200 Subject: [PATCH 23/82] change open/read timeout for webhook requests --- ...send_form_completed_webhook_request_job.rb | 17 ++---------- .../send_form_declined_webhook_request_job.rb | 17 ++---------- .../send_form_started_webhook_request_job.rb | 17 ++---------- .../send_form_viewed_webhook_request_job.rb | 17 ++---------- ...submission_archived_webhook_request_job.rb | 17 ++---------- ...ubmission_completed_webhook_request_job.rb | 17 ++---------- ..._submission_created_webhook_request_job.rb | 17 ++---------- ...nd_template_created_webhook_request_job.rb | 17 ++---------- ...nd_template_updated_webhook_request_job.rb | 17 ++---------- lib/send_webhook_request.rb | 26 +++++++++++++++++++ 10 files changed, 44 insertions(+), 135 deletions(-) create mode 100644 lib/send_webhook_request.rb diff --git a/app/jobs/send_form_completed_webhook_request_job.rb b/app/jobs/send_form_completed_webhook_request_job.rb index 253ebf33..48951724 100644 --- a/app/jobs/send_form_completed_webhook_request_job.rb +++ b/app/jobs/send_form_completed_webhook_request_job.rb @@ -5,8 +5,6 @@ class SendFormCompletedWebhookRequestJob sidekiq_options queue: :webhooks - USER_AGENT = 'DocuSeal.com Webhook' - MAX_ATTEMPTS = 20 def perform(params = {}) @@ -21,19 +19,8 @@ class SendFormCompletedWebhookRequestJob ActiveStorage::Current.url_options = Docuseal.default_url_options - resp = begin - Faraday.post(webhook_url.url, - { - event_type: 'form.completed', - timestamp: Time.current, - data: Submitters::SerializeForWebhook.call(submitter) - }.to_json, - 'Content-Type' => 'application/json', - 'User-Agent' => USER_AGENT, - **webhook_url.secret.to_h) - rescue Faraday::Error - nil - end + resp = SendWebhookRequest.call(webhook_url, event_type: 'form.completed', + data: Submitters::SerializeForWebhook.call(submitter)) if (resp.nil? || resp.status.to_i >= 400) && attempt <= MAX_ATTEMPTS && (!Docuseal.multitenant? || submitter.account.account_configs.exists?(key: :plan)) diff --git a/app/jobs/send_form_declined_webhook_request_job.rb b/app/jobs/send_form_declined_webhook_request_job.rb index 86fdae2a..1c7a1e32 100644 --- a/app/jobs/send_form_declined_webhook_request_job.rb +++ b/app/jobs/send_form_declined_webhook_request_job.rb @@ -5,8 +5,6 @@ class SendFormDeclinedWebhookRequestJob sidekiq_options queue: :webhooks - USER_AGENT = 'DocuSeal.com Webhook' - MAX_ATTEMPTS = 10 def perform(params = {}) @@ -19,19 +17,8 @@ class SendFormDeclinedWebhookRequestJob ActiveStorage::Current.url_options = Docuseal.default_url_options - resp = begin - Faraday.post(webhook_url.url, - { - event_type: 'form.declined', - timestamp: Time.current, - data: Submitters::SerializeForWebhook.call(submitter) - }.to_json, - **webhook_url.secret.to_h, - 'Content-Type' => 'application/json', - 'User-Agent' => USER_AGENT) - rescue Faraday::Error - nil - end + resp = SendWebhookRequest.call(webhook_url, event_type: 'form.declined', + data: Submitters::SerializeForWebhook.call(submitter)) if (resp.nil? || resp.status.to_i >= 400) && attempt <= MAX_ATTEMPTS && (!Docuseal.multitenant? || submitter.account.account_configs.exists?(key: :plan)) diff --git a/app/jobs/send_form_started_webhook_request_job.rb b/app/jobs/send_form_started_webhook_request_job.rb index 3a6e0eae..44510f46 100644 --- a/app/jobs/send_form_started_webhook_request_job.rb +++ b/app/jobs/send_form_started_webhook_request_job.rb @@ -5,8 +5,6 @@ class SendFormStartedWebhookRequestJob sidekiq_options queue: :webhooks - USER_AGENT = 'DocuSeal.com Webhook' - MAX_ATTEMPTS = 10 def perform(params = {}) @@ -19,19 +17,8 @@ class SendFormStartedWebhookRequestJob ActiveStorage::Current.url_options = Docuseal.default_url_options - resp = begin - Faraday.post(webhook_url.url, - { - event_type: 'form.started', - timestamp: Time.current, - data: Submitters::SerializeForWebhook.call(submitter) - }.to_json, - **webhook_url.secret.to_h, - 'Content-Type' => 'application/json', - 'User-Agent' => USER_AGENT) - rescue Faraday::Error - nil - end + resp = SendWebhookRequest.call(webhook_url, event_type: 'form.started', + data: Submitters::SerializeForWebhook.call(submitter)) if (resp.nil? || resp.status.to_i >= 400) && attempt <= MAX_ATTEMPTS && (!Docuseal.multitenant? || submitter.account.account_configs.exists?(key: :plan)) diff --git a/app/jobs/send_form_viewed_webhook_request_job.rb b/app/jobs/send_form_viewed_webhook_request_job.rb index c70cf776..162743e4 100644 --- a/app/jobs/send_form_viewed_webhook_request_job.rb +++ b/app/jobs/send_form_viewed_webhook_request_job.rb @@ -5,8 +5,6 @@ class SendFormViewedWebhookRequestJob sidekiq_options queue: :webhooks - USER_AGENT = 'DocuSeal.com Webhook' - MAX_ATTEMPTS = 10 def perform(params = {}) @@ -19,19 +17,8 @@ class SendFormViewedWebhookRequestJob ActiveStorage::Current.url_options = Docuseal.default_url_options - resp = begin - Faraday.post(webhook_url.url, - { - event_type: 'form.viewed', - timestamp: Time.current, - data: Submitters::SerializeForWebhook.call(submitter) - }.to_json, - **webhook_url.secret.to_h, - 'Content-Type' => 'application/json', - 'User-Agent' => USER_AGENT) - rescue Faraday::Error - nil - end + resp = SendWebhookRequest.call(webhook_url, event_type: 'form.viewed', + data: Submitters::SerializeForWebhook.call(submitter)) if (resp.nil? || resp.status.to_i >= 400) && attempt <= MAX_ATTEMPTS && (!Docuseal.multitenant? || submitter.account.account_configs.exists?(key: :plan)) diff --git a/app/jobs/send_submission_archived_webhook_request_job.rb b/app/jobs/send_submission_archived_webhook_request_job.rb index 334d047b..82fc271f 100644 --- a/app/jobs/send_submission_archived_webhook_request_job.rb +++ b/app/jobs/send_submission_archived_webhook_request_job.rb @@ -5,8 +5,6 @@ class SendSubmissionArchivedWebhookRequestJob sidekiq_options queue: :webhooks - USER_AGENT = 'DocuSeal.com Webhook' - MAX_ATTEMPTS = 10 def perform(params = {}) @@ -17,19 +15,8 @@ class SendSubmissionArchivedWebhookRequestJob return if webhook_url.url.blank? || webhook_url.events.exclude?('submission.archived') - resp = begin - Faraday.post(webhook_url.url, - { - event_type: 'submission.archived', - timestamp: Time.current, - data: submission.as_json(only: %i[id archived_at]) - }.to_json, - **webhook_url.secret.to_h, - 'Content-Type' => 'application/json', - 'User-Agent' => USER_AGENT) - rescue Faraday::Error - nil - end + resp = SendWebhookRequest.call(webhook_url, event_type: 'submission.archived', + data: submission.as_json(only: %i[id archived_at])) if (resp.nil? || resp.status.to_i >= 400) && attempt <= MAX_ATTEMPTS && (!Docuseal.multitenant? || submission.account.account_configs.exists?(key: :plan)) diff --git a/app/jobs/send_submission_completed_webhook_request_job.rb b/app/jobs/send_submission_completed_webhook_request_job.rb index d6037d0e..375bfa75 100644 --- a/app/jobs/send_submission_completed_webhook_request_job.rb +++ b/app/jobs/send_submission_completed_webhook_request_job.rb @@ -5,8 +5,6 @@ class SendSubmissionCompletedWebhookRequestJob sidekiq_options queue: :webhooks - USER_AGENT = 'DocuSeal.com Webhook' - MAX_ATTEMPTS = 10 def perform(params = {}) @@ -17,19 +15,8 @@ class SendSubmissionCompletedWebhookRequestJob return if webhook_url.url.blank? || webhook_url.events.exclude?('submission.completed') - resp = begin - Faraday.post(webhook_url.url, - { - event_type: 'submission.completed', - timestamp: Time.current, - data: Submissions::SerializeForApi.call(submission) - }.to_json, - 'Content-Type' => 'application/json', - 'User-Agent' => USER_AGENT, - **webhook_url.secret.to_h) - rescue Faraday::Error - nil - end + resp = SendWebhookRequest.call(webhook_url, event_type: 'submission.completed', + data: Submissions::SerializeForApi.call(submission)) if (resp.nil? || resp.status.to_i >= 400) && attempt <= MAX_ATTEMPTS && (!Docuseal.multitenant? || submission.account.account_configs.exists?(key: :plan)) diff --git a/app/jobs/send_submission_created_webhook_request_job.rb b/app/jobs/send_submission_created_webhook_request_job.rb index a4dba4a6..d798e76a 100644 --- a/app/jobs/send_submission_created_webhook_request_job.rb +++ b/app/jobs/send_submission_created_webhook_request_job.rb @@ -5,8 +5,6 @@ class SendSubmissionCreatedWebhookRequestJob sidekiq_options queue: :webhooks - USER_AGENT = 'DocuSeal.com Webhook' - MAX_ATTEMPTS = 10 def perform(params = {}) @@ -17,19 +15,8 @@ class SendSubmissionCreatedWebhookRequestJob return if webhook_url.url.blank? || webhook_url.events.exclude?('submission.created') - resp = begin - Faraday.post(webhook_url.url, - { - event_type: 'submission.created', - timestamp: Time.current, - data: Submissions::SerializeForApi.call(submission) - }.to_json, - **webhook_url.secret.to_h, - 'Content-Type' => 'application/json', - 'User-Agent' => USER_AGENT) - rescue Faraday::Error - nil - end + resp = SendWebhookRequest.call(webhook_url, event_type: 'submission.created', + data: Submissions::SerializeForApi.call(submission)) if (resp.nil? || resp.status.to_i >= 400) && attempt <= MAX_ATTEMPTS && (!Docuseal.multitenant? || submission.account.account_configs.exists?(key: :plan)) diff --git a/app/jobs/send_template_created_webhook_request_job.rb b/app/jobs/send_template_created_webhook_request_job.rb index 6b2493dc..353ecb6d 100644 --- a/app/jobs/send_template_created_webhook_request_job.rb +++ b/app/jobs/send_template_created_webhook_request_job.rb @@ -5,8 +5,6 @@ class SendTemplateCreatedWebhookRequestJob sidekiq_options queue: :webhooks - USER_AGENT = 'DocuSeal.com Webhook' - MAX_ATTEMPTS = 10 def perform(params = {}) @@ -17,19 +15,8 @@ class SendTemplateCreatedWebhookRequestJob return if webhook_url.url.blank? || webhook_url.events.exclude?('template.created') - resp = begin - Faraday.post(webhook_url.url, - { - event_type: 'template.created', - timestamp: Time.current, - data: Templates::SerializeForApi.call(template) - }.to_json, - **webhook_url.secret.to_h, - 'Content-Type' => 'application/json', - 'User-Agent' => USER_AGENT) - rescue Faraday::Error - nil - end + resp = SendWebhookRequest.call(webhook_url, event_type: 'template.created', + data: Templates::SerializeForApi.call(template)) if (resp.nil? || resp.status.to_i >= 400) && attempt <= MAX_ATTEMPTS && (!Docuseal.multitenant? || template.account.account_configs.exists?(key: :plan)) diff --git a/app/jobs/send_template_updated_webhook_request_job.rb b/app/jobs/send_template_updated_webhook_request_job.rb index 68479f1b..30623e15 100644 --- a/app/jobs/send_template_updated_webhook_request_job.rb +++ b/app/jobs/send_template_updated_webhook_request_job.rb @@ -5,8 +5,6 @@ class SendTemplateUpdatedWebhookRequestJob sidekiq_options queue: :webhooks - USER_AGENT = 'DocuSeal.com Webhook' - MAX_ATTEMPTS = 10 def perform(params = {}) @@ -17,19 +15,8 @@ class SendTemplateUpdatedWebhookRequestJob return if webhook_url.url.blank? || webhook_url.events.exclude?('template.updated') - resp = begin - Faraday.post(webhook_url.url, - { - event_type: 'template.updated', - timestamp: Time.current, - data: Templates::SerializeForApi.call(template) - }.to_json, - **webhook_url.secret.to_h, - 'Content-Type' => 'application/json', - 'User-Agent' => USER_AGENT) - rescue Faraday::Error - nil - end + resp = SendWebhookRequest.call(webhook_url, event_type: 'template.updated', + data: Templates::SerializeForApi.call(template)) if (resp.nil? || resp.status.to_i >= 400) && attempt <= MAX_ATTEMPTS && (!Docuseal.multitenant? || template.account.account_configs.exists?(key: :plan)) diff --git a/lib/send_webhook_request.rb b/lib/send_webhook_request.rb new file mode 100644 index 00000000..d2e6ebf4 --- /dev/null +++ b/lib/send_webhook_request.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module SendWebhookRequest + USER_AGENT = 'DocuSeal.com Webhook' + + module_function + + def call(webhook_url, event_type:, data:) + Faraday.post(webhook_url.url) do |req| + req.headers['Content-Type'] = 'application/json' + req.headers['User-Agent'] = USER_AGENT + req.headers.merge!(webhook_url.secret.to_h) if webhook_url.secret.present? + + req.body = { + event_type: event_type, + timestamp: Time.current, + data: data + }.to_json + + req.options.read_timeout = 8 + req.options.open_timeout = 8 + end + rescue Faraday::Error + nil + end +end From ec8e35ed7aee392dcf66bda3b467c5680d3cc7b4 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Wed, 22 Jan 2025 21:26:26 +0200 Subject: [PATCH 24/82] fix email typo --- lib/submissions.rb | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/lib/submissions.rb b/lib/submissions.rb index 86988d09..3e1ccace 100644 --- a/lib/submissions.rb +++ b/lib/submissions.rb @@ -107,20 +107,24 @@ module Submissions def normalize_email(email) return if email.blank? - return email.downcase if email.to_s.include?(',') - return email.downcase if email.to_s.include?('.gob') - return email.downcase if email.to_s.include?('.om') - return email.downcase if email.to_s.include?('.mm') - return email.downcase if email.to_s.include?('.cm') - return email.downcase if email.to_s.include?('.et') - return email.downcase if email.to_s.include?('.mo') - return email.downcase if email.to_s.include?('.nz') - return email.downcase if email.to_s.include?('.za') - return email.downcase unless email.to_s.include?('.') + + return email.downcase if email.to_s.include?(',') || + email.to_s.match?(/\.(?:gob|om|mm|cm|et|mo|nz|za|ie)\z/) || + email.to_s.exclude?('.') fixed_email = EmailTypo.call(email.delete_prefix('<')) - Rails.logger.info("Fixed email #{email.split('@').last}") if fixed_email != email.downcase.delete_prefix('<').strip + return fixed_email if fixed_email == email + + domain = email.to_s.split('@').last.to_s.downcase + + if DidYouMean::Levenshtein.distance(domain, fixed_email.to_s.split('@').last) > 3 + Rails.logger.info("Skipped email fix #{domain}") + + return email.downcase + end + + Rails.logger.info("Fixed email #{domain}") if fixed_email != email.downcase.delete_prefix('<').strip fixed_email end From 80b06550c2f9a5a551b9c5a50a3308f6b68d5a65 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Thu, 23 Jan 2025 11:39:43 +0200 Subject: [PATCH 25/82] adjust template link --- app/controllers/start_form_controller.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/controllers/start_form_controller.rb b/app/controllers/start_form_controller.rb index 209c81b4..4512e018 100644 --- a/app/controllers/start_form_controller.rb +++ b/app/controllers/start_form_controller.rb @@ -64,6 +64,7 @@ class StartFormController < ApplicationController .or(template.submissions.where(expire_at: nil)).where(archived_at: nil)) .order(id: :desc) .where(declined_at: nil) + .where(external_id: nil) .then { |rel| params[:resubmit].present? ? rel.where(completed_at: nil) : rel } .find_or_initialize_by(**submitter_params.compact_blank) end From e0c6a4e08bd033ba09e29ecad8754212e325cfb1 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Thu, 23 Jan 2025 17:11:50 +0200 Subject: [PATCH 26/82] fix submission preview page --- app/controllers/submissions_preview_controller.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/controllers/submissions_preview_controller.rb b/app/controllers/submissions_preview_controller.rb index 0d61cb00..862b45d9 100644 --- a/app/controllers/submissions_preview_controller.rb +++ b/app/controllers/submissions_preview_controller.rb @@ -20,7 +20,10 @@ class SubmissionsPreviewController < ApplicationController @submission ||= Submission.find_by!(slug: params[:slug]) - if @submission.account.archived_at? || (!@submission.submitters.all?(&:completed_at?) && current_user.blank?) + raise ActionController::RoutingError if @submission.account.archived_at? + + if !@submission.submitters.all?(&:completed_at?) && !signature_valid && + (!current_user || !current_ability.can?(:read, @submission)) raise ActionController::RoutingError, I18n.t('not_found') end From 87b039e7091633dd560399e8d1b58aac42f67221 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Thu, 23 Jan 2025 22:21:01 +0200 Subject: [PATCH 27/82] fix submitter names --- .../template_builder/field_submitter.vue | 36 +++++++++---------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/app/javascript/template_builder/field_submitter.vue b/app/javascript/template_builder/field_submitter.vue index 695d90ff..e1015835 100644 --- a/app/javascript/template_builder/field_submitter.vue +++ b/app/javascript/template_builder/field_submitter.vue @@ -219,6 +219,14 @@ import { IconUserPlus, IconTrashX, IconPlus, IconChevronUp, IconChevronDown } fr import Contenteditable from './contenteditable' import { v4 } from 'uuid' +function getOrdinalSuffix (num) { + if (num % 10 === 1 && num % 100 !== 11) return 'st' + if (num % 10 === 2 && num % 100 !== 12) return 'nd' + if (num % 10 === 3 && num % 100 !== 13) return 'rd' + + return 'th' +} + export default { name: 'FieldSubmitter', components: { @@ -292,6 +300,14 @@ export default { ] }, names () { + const generatedNames = [] + + for (let i = 21; i < 101; i++) { + const suffix = getOrdinalSuffix(i) + + generatedNames.push(`${i}${suffix} ${this.t('party')}`) + } + return [ this.t('first_party'), this.t('second_party'), @@ -313,20 +329,9 @@ export default { this.t('eighteenth_party'), this.t('nineteenth_party'), this.t('twentieth_party'), - ...this.generatedNames + ...generatedNames ] }, - generatedNames () { - const names = [] - - for (let i = 21; i < 101; i++) { - const suffix = this.getOrdinalSuffix(i) - - names.push(`${i}${suffix} ${this.t('party')}`) - } - - return names - }, lastPartyIndex () { const index = Math.max(...this.submitters.map((s) => this.names.indexOf(s.name))) @@ -344,13 +349,6 @@ export default { selectSubmitter (submitter) { this.$emit('update:model-value', submitter.uuid) }, - getOrdinalSuffix (num) { - if (num % 10 === 1 && num % 100 !== 11) return 'st' - if (num % 10 === 2 && num % 100 !== 12) return 'nd' - if (num % 10 === 3 && num % 100 !== 13) return 'rd' - - return 'th' - }, remove (submitter) { if (window.confirm(this.t('are_you_sure_'))) { this.$emit('remove', submitter) From f6d59d731cda2c8aa54b87b9716cf42b6ebcc40d Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Fri, 24 Jan 2025 13:32:12 +0200 Subject: [PATCH 28/82] fix email typo --- lib/submissions.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/submissions.rb b/lib/submissions.rb index 3e1ccace..fc1f6810 100644 --- a/lib/submissions.rb +++ b/lib/submissions.rb @@ -117,8 +117,11 @@ module Submissions return fixed_email if fixed_email == email domain = email.to_s.split('@').last.to_s.downcase + fixed_domain = fixed_email.to_s.split('@').last - if DidYouMean::Levenshtein.distance(domain, fixed_email.to_s.split('@').last) > 3 + return email.downcase if domain == fixed_domain + + if DidYouMean::Levenshtein.distance(domain, fixed_domain) > 3 Rails.logger.info("Skipped email fix #{domain}") return email.downcase From 44e848a1f0ab0da7fa9e82a67195525c706206e5 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Fri, 24 Jan 2025 22:32:20 +0200 Subject: [PATCH 29/82] fix heading move --- app/javascript/template_builder/area.vue | 36 ++++++++++++--------- app/javascript/template_builder/builder.vue | 2 ++ 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/app/javascript/template_builder/area.vue b/app/javascript/template_builder/area.vue index ccb62ab9..1ada02dc 100644 --- a/app/javascript/template_builder/area.vue +++ b/app/javascript/template_builder/area.vue @@ -361,6 +361,7 @@ export default { isMoved: false, renderDropdown: false, isNameFocus: false, + isHeadingSelected: false, textOverflowChars: 0, dragFrom: { x: 0, y: 0 } } @@ -377,7 +378,7 @@ export default { } }, isValueInput () { - return (this.field.type === 'heading' && this.isSelected) || this.isContenteditable || (this.inputMode && ['text', 'number', 'date'].includes(this.field.type)) + return (this.field.type === 'heading' && this.isHeadingSelected) || this.isContenteditable || (this.inputMode && ['text', 'number', 'date'].includes(this.field.type)) }, modalContainerEl () { return this.$el.getRootNode().querySelector('#docuseal_modal_container') @@ -485,7 +486,7 @@ export default { if (['text', 'number'].includes(this.field.type)) { this.isContenteditable = true - this.$nextTick(() => this.focusValueInput()) + this.focusValueInput() } else if (this.field.type === 'checkbox') { this.field.readonly = !this.field.readonly this.field.default_value === true ? delete this.field.default_value : this.field.default_value = true @@ -507,16 +508,18 @@ export default { } }, focusValueInput (e) { - if (this.$refs.defaultValue !== document.activeElement) { - this.$refs.defaultValue.focus() - - if (this.$refs.defaultValue.innerText.length && this.$refs.defaultValue !== e?.target) { - window.getSelection().collapse( - this.$refs.defaultValue.firstChild, - this.$refs.defaultValue.innerText.length - ) + this.$nextTick(() => { + if (this.$refs.defaultValue && this.$refs.defaultValue !== document.activeElement) { + this.$refs.defaultValue.focus() + + if (this.$refs.defaultValue.innerText.length && this.$refs.defaultValue !== e?.target) { + window.getSelection().collapse( + this.$refs.defaultValue.firstChild, + this.$refs.defaultValue.innerText.length + ) + } } - } + }) }, formatNumber (number, format) { if (format === 'comma') { @@ -632,6 +635,7 @@ export default { const text = this.$refs.defaultValue.innerText.trim() this.isContenteditable = false + this.isHeadingSelected = false if (text) { if (this.field.type === 'number') { @@ -749,10 +753,6 @@ export default { this.selectedAreaRef.value = this.area - if (this.field.type === 'heading') { - this.$nextTick(() => this.focusValueInput()) - } - this.dragFrom = { x: rect.left - e.clientX, y: rect.top - e.clientY } this.$el.getRootNode().addEventListener('mousemove', this.mouseMove) @@ -787,6 +787,12 @@ export default { this.save() } + if (this.field.type === 'heading') { + this.isHeadingSelected = !this.isMoved + + this.focusValueInput() + } + this.isDragged = false this.isMoved = false diff --git a/app/javascript/template_builder/builder.vue b/app/javascript/template_builder/builder.vue index 70e9b0f6..6e8bae2a 100644 --- a/app/javascript/template_builder/builder.vue +++ b/app/javascript/template_builder/builder.vue @@ -1427,6 +1427,8 @@ export default { const documentRef = this.documentRefs.find((e) => e.document.uuid === area.attachment_uuid) const areaRef = documentRef.pageRefs[area.page].areaRefs.find((ref) => ref.area === this.selectedAreaRef.value) + areaRef.isHeadingSelected = true + areaRef.focusValueInput() }) } From 37de5e0d0dacc26c32444801ce862b1896a40ff8 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Sat, 25 Jan 2025 19:37:15 +0200 Subject: [PATCH 30/82] add resubmit from dashboard --- .../submitters_resubmit_controller.rb | 58 +++++++++++++++++++ app/views/submissions/show.html.erb | 5 ++ app/views/submit_form/completed.html.erb | 3 +- config/routes.rb | 1 + 4 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 app/controllers/submitters_resubmit_controller.rb diff --git a/app/controllers/submitters_resubmit_controller.rb b/app/controllers/submitters_resubmit_controller.rb new file mode 100644 index 00000000..fb893d98 --- /dev/null +++ b/app/controllers/submitters_resubmit_controller.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +class SubmittersResubmitController < ApplicationController + load_and_authorize_resource :submitter, parent: false + + def update + return redirect_to submit_form_path(slug: @submitter.slug) if @submitter.email != current_user.email + + submission = @submitter.template.submissions.new(created_by_user: current_user, + submitters_order: :preserved, + **@submitter.submission.slice(:template_fields, + :account_id, + :template_schema, + :template_submitters, + :preferences)) + + @submitter.submission.submitters.each do |submitter| + new_submitter = submission.submitters.new(submitter.slice(:uuid, :email, :phone, :name, + :preferences, :metadata, :account_id)) + + next unless submitter.uuid == @submitter.uuid + + assign_submitter_values(new_submitter, submitter) + + @new_submitter ||= new_submitter + end + + submission.save! + + redirect_to submit_form_path(slug: @new_submitter.slug) + end + + private + + def assign_submitter_values(new_submitter, submitter) + attachments_index = submitter.attachments.index_by(&:uuid) + + submitter.submission.template_fields.each do |field| + next if field['submitter_uuid'] != submitter.uuid + next if field['default_value'] == '{{date}}' + next if field['type'] == 'stamp' + next if field['type'] == 'signature' + next if field.dig('preferences', 'formula').present? + + value = submitter.values[field['uuid']] + + next if value.blank? + + if field['type'].in?(%w[image file initials]) + Array.wrap(value).each do |attachment_uuid| + new_submitter.attachments << attachments_index[attachment_uuid].dup + end + end + + new_submitter.values[field['uuid']] = value + end + end +end diff --git a/app/views/submissions/show.html.erb b/app/views/submissions/show.html.erb index 71568920..09d57646 100644 --- a/app/views/submissions/show.html.erb +++ b/app/views/submissions/show.html.erb @@ -190,6 +190,11 @@
    <% end %> + <% if signed_in? && submitter && submitter.completed_at? && submitter.email == current_user.email && submitter.completed_at > 1.month.ago && can?(:update, @submission) %> +
    + <%= button_to t('resubmit'), submitters_resubmit_path(submitter), method: :put, class: 'btn btn-sm btn-primary w-full', form: { target: '_blank' }, data: { turbo: false } %> +
    + <% end %>
    diff --git a/app/views/submit_form/completed.html.erb b/app/views/submit_form/completed.html.erb index 85c5a129..92114274 100644 --- a/app/views/submit_form/completed.html.erb +++ b/app/views/submit_form/completed.html.erb @@ -42,7 +42,8 @@ <% end %>
    - <% if Templates.filter_undefined_submitters(@submitter.submission.template).size == 1 && %w[api embed].exclude?(@submitter.submission.source) && @submitter.account.account_configs.find_or_initialize_by(key: AccountConfig::ALLOW_TO_RESUBMIT).value != false && !@submitter.template.archived_at? %> + <% undefined_submitters = Templates.filter_undefined_submitters(@submitter.submission.template) %> + <% if undefined_submitters.size == 1 && undefined_submitters.first['uuid'] == @submitter.uuid && %w[api embed].exclude?(@submitter.submission.source) && @submitter.account.account_configs.find_or_initialize_by(key: AccountConfig::ALLOW_TO_RESUBMIT).value != false && !@submitter.template.archived_at? %>
    <%= t('or') %>
    <%= button_to button_title(title: t('resubmit'), disabled_with: t('resubmit'), icon: svg_icon('reload', class: 'w-6 h-6')), start_form_path(@submitter.submission.template.slug), params: { submitter: { email: @submitter.email, phone: @submitter.phone, name: @submitter.name }, resubmit: @submitter.slug }, method: :put, class: 'white-button w-full' %> diff --git a/config/routes.rb b/config/routes.rb index dd568316..b2f84549 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -79,6 +79,7 @@ Rails.application.routes.draw do resource :testing_account, only: %i[show destroy] resources :testing_api_settings, only: %i[index] resources :submitters_autocomplete, only: %i[index] + resources :submitters_resubmit, only: %i[update] resources :template_folders_autocomplete, only: %i[index] resources :webhook_secret, only: %i[show update] resources :webhook_preferences, only: %i[update] From 5718acecff1c7a8228e41ad76ffbbdba7fe3ef5e Mon Sep 17 00:00:00 2001 From: Alex Turchyn Date: Mon, 27 Jan 2025 19:54:44 +0200 Subject: [PATCH 31/82] add translations --- config/locales/i18n.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/config/locales/i18n.yml b/config/locales/i18n.yml index e710474a..c3a91e8e 100644 --- a/config/locales/i18n.yml +++ b/config/locales/i18n.yml @@ -690,6 +690,9 @@ en: &en extremely_satisfied: Extremely Satisfied your_pro_plan_payment_is_overdue: Your Pro plan payment is overdue. click_here_to_update_your_payment_details_and_clear_the_invoice_to_ensure_uninterrupted_service_html: Click here to update your payment details and clear the invoice to ensure uninterrupted service. + overdue_payment: Overdue Payment + your_pro_plan_has_been_suspended_due_to_unpaid_invoices_you_can_update_your_payment_details_to_settle_the_invoice_and_continue_using_docuseal_or_cancel_your_subscription: Your Pro Plan has been suspended due to unpaid invoices. You can update your payment details to settle the invoice and continue using DocuSeal or cancel your subscription. + manage_subscription: Manage Subscription submission_event_names: send_email_to_html: 'Email sent to %{submitter_name}' send_reminder_email_to_html: 'Reminder email sent to %{submitter_name}' @@ -1400,6 +1403,9 @@ es: &es extremely_satisfied: Extremadamente satisfecho your_pro_plan_payment_is_overdue: El pago de tu plan Pro está atrasado. click_here_to_update_your_payment_details_and_clear_the_invoice_to_ensure_uninterrupted_service_html: 'Haz clic aquí para actualizar tus datos de pago y liquidar la factura para garantizar un servicio ininterrumpido.' + overdue_payment: Pago Atrasado + your_pro_plan_has_been_suspended_due_to_unpaid_invoices_you_can_update_your_payment_details_to_settle_the_invoice_and_continue_using_docuseal_or_cancel_your_subscription: Tu plan Pro ha sido suspendido debido a facturas impagas. Puedes actualizar tus datos de pago para liquidar la factura y seguir usando DocuSeal o cancelar tu suscripción. + manage_subscription: Gestionar Suscripción submission_event_names: send_email_to_html: 'Correo electrónico enviado a %{submitter_name}' send_reminder_email_to_html: 'Correo de recordatorio enviado a %{submitter_name}' @@ -2109,6 +2115,9 @@ it: &it extremely_satisfied: Estremamente soddisfatto your_pro_plan_payment_is_overdue: Il pagamento del tuo piano Pro è in ritardo. click_here_to_update_your_payment_details_and_clear_the_invoice_to_ensure_uninterrupted_service_html: 'Fai clic qui per aggiornare i tuoi dati di pagamento e saldare la fattura per garantire un servizio ininterrotto.' + overdue_payment: Pagamento Scaduto + your_pro_plan_has_been_suspended_due_to_unpaid_invoices_you_can_update_your_payment_details_to_settle_the_invoice_and_continue_using_docuseal_or_cancel_your_subscription: Il tuo piano Pro è stato sospeso a causa di fatture non pagate. Puoi aggiornare i tuoi dati di pagamento per saldare la fattura e continuare a utilizzare DocuSeal o annullare l'abbonamento. + manage_subscription: Gestisci Abbonamento submission_event_names: send_email_to_html: 'E-mail inviato a %{submitter_name}' send_reminder_email_to_html: 'E-mail di promemoria inviato a %{submitter_name}' @@ -2820,6 +2829,9 @@ fr: &fr extremely_satisfied: Extrêmement satisfait your_pro_plan_payment_is_overdue: Le paiement de votre plan Pro est en retard. click_here_to_update_your_payment_details_and_clear_the_invoice_to_ensure_uninterrupted_service_html: 'Cliquez ici pour mettre à jour vos informations de paiement et régler la facture afin de garantir un service ininterrompu.' + overdue_payment: Paiement En Retard + your_pro_plan_has_been_suspended_due_to_unpaid_invoices_you_can_update_your_payment_details_to_settle_the_invoice_and_continue_using_docuseal_or_cancel_your_subscription: Votre plan Pro a été suspendu en raison de factures impayées. Vous pouvez mettre à jour vos informations de paiement pour régler la facture et continuer à utiliser DocuSeal ou annuler votre abonnement. + manage_subscription: Gérer l'Abonnement submission_event_names: send_email_to_html: 'E-mail envoyé à %{submitter_name}' send_reminder_email_to_html: 'E-mail de rappel envoyé à %{submitter_name}' @@ -3530,6 +3542,9 @@ pt: &pt extremely_satisfied: Extremamente satisfeito your_pro_plan_payment_is_overdue: O pagamento do seu plano Pro está atrasado. click_here_to_update_your_payment_details_and_clear_the_invoice_to_ensure_uninterrupted_service_html: 'Clique aqui para atualizar seus dados de pagamento e quitar a fatura para garantir um serviço ininterrupto.' + overdue_payment: Pagamento Atrasado + your_pro_plan_has_been_suspended_due_to_unpaid_invoices_you_can_update_your_payment_details_to_settle_the_invoice_and_continue_using_docuseal_or_cancel_your_subscription: Seu plano Pro foi suspenso devido a faturas não pagas. Você pode atualizar seus dados de pagamento para quitar a fatura e continuar usando o DocuSeal ou cancelar sua assinatura. + manage_subscription: Gerenciar Assinatura submission_event_names: send_email_to_html: 'E-mail enviado para %{submitter_name}' send_reminder_email_to_html: 'E-mail de lembrete enviado para %{submitter_name}' @@ -4240,6 +4255,9 @@ de: &de extremely_satisfied: Extrem zufrieden your_pro_plan_payment_is_overdue: Ihre Zahlung für den Pro-Plan ist überfällig. click_here_to_update_your_payment_details_and_clear_the_invoice_to_ensure_uninterrupted_service_html: 'Klicken Sie hier, um Ihre Zahlungsdaten zu aktualisieren und die Rechnung zu begleichen, um einen unterbrechungsfreien Service sicherzustellen.' + overdue_payment: Überfällige Zahlung + your_pro_plan_has_been_suspended_due_to_unpaid_invoices_you_can_update_your_payment_details_to_settle_the_invoice_and_continue_using_docuseal_or_cancel_your_subscription: Ihr Pro-Plan wurde aufgrund unbezahlter Rechnungen ausgesetzt. Sie können Ihre Zahlungsdaten aktualisieren, um die Rechnung zu begleichen und DocuSeal weiterhin zu nutzen, oder Ihr Abonnement kündigen. + manage_subscription: Abonnement Verwalten submission_event_names: send_email_to_html: 'E-Mail gesendet an %{submitter_name}' send_reminder_email_to_html: 'Erinnerungs-E-Mail gesendet an %{submitter_name}' From bc0917a190a98b3737ca6e5a2b526d5361322786 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Mon, 27 Jan 2025 15:51:47 +0200 Subject: [PATCH 32/82] fix sign in date --- app/views/users/index.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/users/index.html.erb b/app/views/users/index.html.erb index 6d08ecaf..a37a59c0 100644 --- a/app/views/users/index.html.erb +++ b/app/views/users/index.html.erb @@ -60,7 +60,7 @@ - <%= user.last_sign_in_at ? l(user.last_sign_in_at.in_time_zone(current_account.timezone), format: :short, locale: current_account.locale) : '-' %> + <%= user.current_sign_in_at ? l(user.current_sign_in_at.in_time_zone(current_account.timezone), format: :short, locale: current_account.locale) : '-' %> <% if params[:status].blank? && can?(:update, user) && user.archived_at.blank? %> From 97c0dad7ffabedb92ce465705f0c7892a818ce5e Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Tue, 28 Jan 2025 14:13:06 +0200 Subject: [PATCH 33/82] add with fields search --- app/javascript/template_builder/builder.vue | 6 ++++++ app/javascript/template_builder/fields.vue | 11 ++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/app/javascript/template_builder/builder.vue b/app/javascript/template_builder/builder.vue index 6e8bae2a..5e6316dc 100644 --- a/app/javascript/template_builder/builder.vue +++ b/app/javascript/template_builder/builder.vue @@ -366,6 +366,7 @@ :with-help="withHelp" :default-submitters="defaultSubmitters" :draw-field-type="drawFieldType" + :with-fields-search="withFieldsSearch" :default-fields="[...defaultRequiredFields, ...defaultFields]" :template="template" :default-required-fields="defaultRequiredFields" @@ -623,6 +624,11 @@ export default { required: false, default: true }, + withFieldsSearch: { + type: Boolean, + required: false, + default: null + }, withFieldsList: { type: Boolean, required: false, diff --git a/app/javascript/template_builder/fields.vue b/app/javascript/template_builder/fields.vue index 2eefac39..cd896022 100644 --- a/app/javascript/template_builder/fields.vue +++ b/app/javascript/template_builder/fields.vue @@ -229,6 +229,11 @@ export default { type: Array, required: true }, + withFieldsSearch: { + type: Boolean, + required: false, + default: null + }, template: { type: Object, required: true @@ -297,7 +302,11 @@ export default { fieldNames: FieldType.computed.fieldNames, fieldIcons: FieldType.computed.fieldIcons, isShowFieldSearch () { - return this.submitterDefaultFields.length > 15 + if (this.withFieldsSearch === false) { + return false + } else { + return this.submitterDefaultFields.length > 15 + } }, defaultFieldsIndex () { return this.defaultFields.reduce((acc, field) => { From ab6cd0e89c7c17ed1ac43d44cf7cc20a66d94f42 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Tue, 28 Jan 2025 18:32:32 +0200 Subject: [PATCH 34/82] adjust send email job --- app/jobs/send_submitter_invitation_email_job.rb | 8 +++++++- lib/accounts.rb | 4 ++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/app/jobs/send_submitter_invitation_email_job.rb b/app/jobs/send_submitter_invitation_email_job.rb index 22616c09..31f4af2b 100644 --- a/app/jobs/send_submitter_invitation_email_job.rb +++ b/app/jobs/send_submitter_invitation_email_job.rb @@ -8,6 +8,12 @@ class SendSubmitterInvitationEmailJob return if submitter.submission.source == 'invite' && !Accounts.can_send_emails?(submitter.account, on_events: true) + unless Accounts.can_send_invitation_emails?(submitter.account) + Rollbar.warning("Skip email: #{submitter.account.id}") if defined?(Rollbar) + + return + end + mail = SubmitterMailer.invitation_email(submitter) Submitters::ValidateSending.call(submitter, mail) @@ -17,6 +23,6 @@ class SendSubmitterInvitationEmailJob SubmissionEvent.create!(submitter:, event_type: 'send_email') submitter.sent_at ||= Time.current - submitter.save + submitter.save! end end diff --git a/lib/accounts.rb b/lib/accounts.rb index 67427cf6..f3dd639b 100644 --- a/lib/accounts.rb +++ b/lib/accounts.rb @@ -153,6 +153,10 @@ module Accounts EncryptedConfig.exists?(key: EncryptedConfig::EMAIL_SMTP_KEY) end + def can_send_invitation_emails?(_account) + true + end + def normalize_timezone(timezone) tzinfo = TZInfo::Timezone.get(ActiveSupport::TimeZone::MAPPING[timezone] || timezone) From 071f187605247807dc31599738186333372c27e0 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Wed, 29 Jan 2025 00:56:14 +0200 Subject: [PATCH 35/82] fix template conditions --- lib/abilities/template_conditions.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/abilities/template_conditions.rb b/lib/abilities/template_conditions.rb index cadbc030..4a7ebf04 100644 --- a/lib/abilities/template_conditions.rb +++ b/lib/abilities/template_conditions.rb @@ -25,7 +25,7 @@ module Abilities account_ids = [user.account_id, TemplateSharing::ALL_ID] template.template_sharings.any? do |e| - e.account_id.in?(account_ids) && (ability.nil? || e.ability == ability) + e.account_id.in?(account_ids) && (ability.nil? || e.ability == 'manage' || e.ability == ability) end end end From 9ff41a0c4a9428d42c3a1a3608dde03e8fb3a003 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Wed, 29 Jan 2025 16:08:09 +0200 Subject: [PATCH 36/82] scroll on mobile field click --- app/javascript/submission_form/areas.vue | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/app/javascript/submission_form/areas.vue b/app/javascript/submission_form/areas.vue index d17a728c..67ac0bdd 100644 --- a/app/javascript/submission_form/areas.vue +++ b/app/javascript/submission_form/areas.vue @@ -31,7 +31,7 @@ :with-label="withLabel && !withFieldPlaceholder && step.length < 2" :is-value-set="step.some((f) => f.uuid in values)" :attachments-index="attachmentsIndex" - @click="$emit('focus-step', stepIndex)" + @click="[$emit('focus-step', stepIndex), maybeScrollOnClick(field, area)]" /> @@ -109,6 +109,14 @@ export default { areaRefs: [] } }, + computed: { + isMobileContainer () { + const root = this.$root.$el.parentNode.getRootNode() + const container = root.body || root.querySelector('div') + + return container.style.overflow === 'hidden' + } + }, beforeUpdate () { this.areaRefs = [] }, @@ -121,14 +129,16 @@ export default { this.scrollIntoArea(field.areas[0]) } }, + maybeScrollOnClick (field, area) { + if (['text', 'number', 'cells'].includes(field.type) && this.isMobileContainer) { + this.scrollIntoArea(area) + } + }, scrollIntoArea (area) { const areaRef = this.areaRefs.find((a) => a.area === area) if (areaRef) { - const root = this.$root.$el.parentNode.getRootNode() - const container = root.body || root.querySelector('div') - - if (container.style.overflow === 'hidden') { + if (this.isMobileContainer) { this.scrollInContainer(areaRef.$el) } else { const targetRect = areaRef.$refs.scrollToElem.getBoundingClientRect() From 922101b12c9eef3d884c469893c3744c73397b75 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Fri, 31 Jan 2025 12:03:52 +0200 Subject: [PATCH 37/82] fix dashboard toggle --- app/views/templates_dashboard/index.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/templates_dashboard/index.html.erb b/app/views/templates_dashboard/index.html.erb index 6819beaf..8f290c57 100644 --- a/app/views/templates_dashboard/index.html.erb +++ b/app/views/templates_dashboard/index.html.erb @@ -2,7 +2,7 @@ <% if Docuseal.demo? %><%= render 'shared/demo_alert' %><% end %>
    - <% if has_archived || @pagy.count > 0 %> + <% if has_archived || @pagy.count > 0 || @template_folders.present? %>
    <%= render 'dashboard/toggle_view', selected: 'templates' %>
    From bfb2e0043d36af62b083bbb16b72923ee66cb69c Mon Sep 17 00:00:00 2001 From: Alex Turchyn Date: Wed, 29 Jan 2025 16:03:06 +0200 Subject: [PATCH 38/82] update email attribution footer for Test Mode --- app/views/shared/_email_attribution.html.erb | 6 +++++- config/locales/i18n.yml | 6 ++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/app/views/shared/_email_attribution.html.erb b/app/views/shared/_email_attribution.html.erb index 5e30a0c4..43b6d5f0 100644 --- a/app/views/shared/_email_attribution.html.erb +++ b/app/views/shared/_email_attribution.html.erb @@ -2,5 +2,9 @@ ---

    - <%= t('sent_using_product_name_free_document_signing_html', product_url: "#{Docuseal::PRODUCT_EMAIL_URL}/start", product_name: Docuseal.product_name) %> + <% if @current_account&.testing? %> + <%= t('sent_using_product_name_via_developer_sandbox_html', product_url: "#{Docuseal::PRODUCT_EMAIL_URL}/start", product_name: Docuseal.product_name) %> + <% else %> + <%= t('sent_using_product_name_free_document_signing_html', product_url: "#{Docuseal::PRODUCT_EMAIL_URL}/start", product_name: Docuseal.product_name) %> + <% end %>

    diff --git a/config/locales/i18n.yml b/config/locales/i18n.yml index c3a91e8e..aa31f9e0 100644 --- a/config/locales/i18n.yml +++ b/config/locales/i18n.yml @@ -42,6 +42,7 @@ en: &en hello_name: Hello %{name} you_are_invited_to_product_name: You are invited to %{product_name} you_have_been_invited_to_account_name_product_name_please_sign_up_using_the_link_below_: 'You have been invited to %{account_name} %{product_name}. Please sign up using the link below:' + sent_using_product_name_via_developer_sandbox_html: 'Sent using %{product_name} via Developer Sandbox' sent_using_product_name_free_document_signing_html: 'Sent using %{product_name} free document signing.' sign_documents_with_trusted_certificate_provided_by_docu_seal_your_documents_and_data_are_never_shared_with_docu_seal_p_d_f_checksum_is_provided_to_generate_a_trusted_signature: Sign documents with trusted certificate provided by DocuSeal. Your documents and data are never shared with DocuSeal. PDF checksum is provided to generate a trusted signature. you_have_been_invited_to_submit_the_name_form: 'You have been invited to submit the "%{name}" form.' @@ -755,6 +756,7 @@ es: &es hello_name: Hola %{name} you_are_invited_to_product_name: Estás invitado a %{product_name} you_have_been_invited_to_account_name_product_name_please_sign_up_using_the_link_below_: 'Has sido invitado a %{account_name} %{product_name}. Por favor, regístrate usando el enlace a continuación:' + sent_using_product_name_via_developer_sandbox_html: 'Enviado usando %{product_name} a través de Developer Sandbox' sent_using_product_name_free_document_signing_html: 'Enviado usando la firma de documentos gratuita de %{product_name}.' sign_documents_with_trusted_certificate_provided_by_docu_seal_your_documents_and_data_are_never_shared_with_docu_seal_p_d_f_checksum_is_provided_to_generate_a_trusted_signature: Firme documentos con un certificado de confianza proporcionado por DocuSeal. Sus documentos y datos nunca se comparten con DocuSeal. Se proporciona un checksum de PDF para generar una firma de confianza. hi_there: Hola @@ -1467,6 +1469,7 @@ it: &it hello_name: Ciao %{name} you_are_invited_to_product_name: Sei stato invitato a %{product_name} you_have_been_invited_to_account_name_product_name_please_sign_up_using_the_link_below_: 'Sei stato invitato a %{account_name} %{product_name}. Registrati utilizzando il link qui sotto:' + sent_using_product_name_via_developer_sandbox_html: 'Inviato utilizzando %{product_name} tramite Developer Sandbox' sent_using_product_name_free_document_signing_html: 'Inviato utilizzando la firma di documenti gratuita di %{product_name}.' sign_documents_with_trusted_certificate_provided_by_docu_seal_your_documents_and_data_are_never_shared_with_docu_seal_p_d_f_checksum_is_provided_to_generate_a_trusted_signature: "Firma documenti con un certificato di fiducia fornito da DocuSeal. I tuoi documenti e i tuoi dati non vengono mai condivisi con DocuSeal. Il checksum PDF è fornito per generare una firma di fiducia." hi_there: Ciao @@ -2180,6 +2183,7 @@ fr: &fr hello_name: Bonjour %{name} you_are_invited_to_product_name: Vous êtes invité à %{product_name} you_have_been_invited_to_account_name_product_name_please_sign_up_using_the_link_below_: 'Vous avez été invité à %{account_name} %{product_name}. Veuillez vous inscrire en utilisant le lien ci-dessous:' + sent_using_product_name_via_developer_sandbox_html: 'Envoyé en utilisant %{product_name} via Developer Sandbox' sent_using_product_name_free_document_signing_html: 'Envoyé en utilisant la signature de documents gratuite de %{product_name}.' sign_documents_with_trusted_certificate_provided_by_docu_seal_your_documents_and_data_are_never_shared_with_docu_seal_p_d_f_checksum_is_provided_to_generate_a_trusted_signature: Signez des documents avec un certificat de confiance fourni par DocuSeal. Vos documents et données ne sont jamais partagés avec DocuSeal. Un checksum PDF est fourni pour générer une signature de confiance. hi_there: Bonjour @@ -2894,6 +2898,7 @@ pt: &pt hello_name: Olá %{name} you_are_invited_to_product_name: Você está convidado para %{product_name} you_have_been_invited_to_account_name_product_name_please_sign_up_using_the_link_below_: 'Você foi convidado para %{account_name} %{product_name}. Inscreva-se usando o link abaixo:' + sent_using_product_name_via_developer_sandbox_html: 'Enviado usando %{product_name} via Developer Sandbox' sent_using_product_name_free_document_signing_html: 'Enviado usando a assinatura gratuita de documentos de %{product_name}.' sign_documents_with_trusted_certificate_provided_by_docu_seal_your_documents_and_data_are_never_shared_with_docu_seal_p_d_f_checksum_is_provided_to_generate_a_trusted_signature: Assine documentos com certificado confiável fornecido pela DocuSeal. Seus documentos e dados nunca são compartilhados com a DocuSeal. O checksum do PDF é fornecido para gerar uma assinatura confiável. hi_there: Olá @@ -3607,6 +3612,7 @@ de: &de hello_name: Hallo %{name} you_are_invited_to_product_name: Sie sind eingeladen zu %{product_name} you_have_been_invited_to_account_name_product_name_please_sign_up_using_the_link_below_: 'Sie wurden zu %{account_name} %{product_name} eingeladen. Bitte registrieren Sie sich über den folgenden Link:' + sent_using_product_name_via_developer_sandbox_html: 'Gesendet über %{product_name} via Developer Sandbox' sent_using_product_name_free_document_signing_html: 'Gesendet mit der kostenlosen Dokumentensignierung von %{product_name}.' sign_documents_with_trusted_certificate_provided_by_docu_seal_your_documents_and_data_are_never_shared_with_docu_seal_p_d_f_checksum_is_provided_to_generate_a_trusted_signature: Unterzeichnen Sie Dokumente mit einem vertrauenswürdigen Zertifikat von DocuSeal. Ihre Dokumente und Daten werden niemals mit DocuSeal geteilt. Eine PDF-Prüfziffer wird bereitgestellt, um eine vertrauenswürdige Signatur zu generieren. hi_there: Hallo From 6d2a8ca3d345a9ad3d7f472e4429a86518460953 Mon Sep 17 00:00:00 2001 From: Alex Turchyn Date: Thu, 30 Jan 2025 14:40:05 +0200 Subject: [PATCH 39/82] update translations --- app/views/shared/_email_attribution.html.erb | 2 +- config/locales/i18n.yml | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/views/shared/_email_attribution.html.erb b/app/views/shared/_email_attribution.html.erb index 43b6d5f0..8cfd4988 100644 --- a/app/views/shared/_email_attribution.html.erb +++ b/app/views/shared/_email_attribution.html.erb @@ -3,7 +3,7 @@

    <% if @current_account&.testing? %> - <%= t('sent_using_product_name_via_developer_sandbox_html', product_url: "#{Docuseal::PRODUCT_EMAIL_URL}/start", product_name: Docuseal.product_name) %> + <%= t('sent_using_product_name_in_testing_mode_html', product_url: "#{Docuseal::PRODUCT_EMAIL_URL}/start", product_name: Docuseal.product_name) %> <% else %> <%= t('sent_using_product_name_free_document_signing_html', product_url: "#{Docuseal::PRODUCT_EMAIL_URL}/start", product_name: Docuseal.product_name) %> <% end %> diff --git a/config/locales/i18n.yml b/config/locales/i18n.yml index aa31f9e0..e222e091 100644 --- a/config/locales/i18n.yml +++ b/config/locales/i18n.yml @@ -42,7 +42,7 @@ en: &en hello_name: Hello %{name} you_are_invited_to_product_name: You are invited to %{product_name} you_have_been_invited_to_account_name_product_name_please_sign_up_using_the_link_below_: 'You have been invited to %{account_name} %{product_name}. Please sign up using the link below:' - sent_using_product_name_via_developer_sandbox_html: 'Sent using %{product_name} via Developer Sandbox' + sent_using_product_name_in_testing_mode_html: 'Sent using %{product_name} in Testing Mode' sent_using_product_name_free_document_signing_html: 'Sent using %{product_name} free document signing.' sign_documents_with_trusted_certificate_provided_by_docu_seal_your_documents_and_data_are_never_shared_with_docu_seal_p_d_f_checksum_is_provided_to_generate_a_trusted_signature: Sign documents with trusted certificate provided by DocuSeal. Your documents and data are never shared with DocuSeal. PDF checksum is provided to generate a trusted signature. you_have_been_invited_to_submit_the_name_form: 'You have been invited to submit the "%{name}" form.' @@ -756,7 +756,7 @@ es: &es hello_name: Hola %{name} you_are_invited_to_product_name: Estás invitado a %{product_name} you_have_been_invited_to_account_name_product_name_please_sign_up_using_the_link_below_: 'Has sido invitado a %{account_name} %{product_name}. Por favor, regístrate usando el enlace a continuación:' - sent_using_product_name_via_developer_sandbox_html: 'Enviado usando %{product_name} a través de Developer Sandbox' + sent_using_product_name_in_testing_mode_html: 'Enviado usando %{product_name} en Modo de Prueba' sent_using_product_name_free_document_signing_html: 'Enviado usando la firma de documentos gratuita de %{product_name}.' sign_documents_with_trusted_certificate_provided_by_docu_seal_your_documents_and_data_are_never_shared_with_docu_seal_p_d_f_checksum_is_provided_to_generate_a_trusted_signature: Firme documentos con un certificado de confianza proporcionado por DocuSeal. Sus documentos y datos nunca se comparten con DocuSeal. Se proporciona un checksum de PDF para generar una firma de confianza. hi_there: Hola @@ -1469,7 +1469,7 @@ it: &it hello_name: Ciao %{name} you_are_invited_to_product_name: Sei stato invitato a %{product_name} you_have_been_invited_to_account_name_product_name_please_sign_up_using_the_link_below_: 'Sei stato invitato a %{account_name} %{product_name}. Registrati utilizzando il link qui sotto:' - sent_using_product_name_via_developer_sandbox_html: 'Inviato utilizzando %{product_name} tramite Developer Sandbox' + sent_using_product_name_in_testing_mode_html: 'Inviato utilizzando %{product_name} in Modalità di Test' sent_using_product_name_free_document_signing_html: 'Inviato utilizzando la firma di documenti gratuita di %{product_name}.' sign_documents_with_trusted_certificate_provided_by_docu_seal_your_documents_and_data_are_never_shared_with_docu_seal_p_d_f_checksum_is_provided_to_generate_a_trusted_signature: "Firma documenti con un certificato di fiducia fornito da DocuSeal. I tuoi documenti e i tuoi dati non vengono mai condivisi con DocuSeal. Il checksum PDF è fornito per generare una firma di fiducia." hi_there: Ciao @@ -2183,7 +2183,7 @@ fr: &fr hello_name: Bonjour %{name} you_are_invited_to_product_name: Vous êtes invité à %{product_name} you_have_been_invited_to_account_name_product_name_please_sign_up_using_the_link_below_: 'Vous avez été invité à %{account_name} %{product_name}. Veuillez vous inscrire en utilisant le lien ci-dessous:' - sent_using_product_name_via_developer_sandbox_html: 'Envoyé en utilisant %{product_name} via Developer Sandbox' + sent_using_product_name_in_testing_mode_html: 'Envoyé en utilisant %{product_name} en Mode Test' sent_using_product_name_free_document_signing_html: 'Envoyé en utilisant la signature de documents gratuite de %{product_name}.' sign_documents_with_trusted_certificate_provided_by_docu_seal_your_documents_and_data_are_never_shared_with_docu_seal_p_d_f_checksum_is_provided_to_generate_a_trusted_signature: Signez des documents avec un certificat de confiance fourni par DocuSeal. Vos documents et données ne sont jamais partagés avec DocuSeal. Un checksum PDF est fourni pour générer une signature de confiance. hi_there: Bonjour @@ -2898,7 +2898,7 @@ pt: &pt hello_name: Olá %{name} you_are_invited_to_product_name: Você está convidado para %{product_name} you_have_been_invited_to_account_name_product_name_please_sign_up_using_the_link_below_: 'Você foi convidado para %{account_name} %{product_name}. Inscreva-se usando o link abaixo:' - sent_using_product_name_via_developer_sandbox_html: 'Enviado usando %{product_name} via Developer Sandbox' + sent_using_product_name_in_testing_mode_html: 'Enviado usando %{product_name} no Modo de Teste' sent_using_product_name_free_document_signing_html: 'Enviado usando a assinatura gratuita de documentos de %{product_name}.' sign_documents_with_trusted_certificate_provided_by_docu_seal_your_documents_and_data_are_never_shared_with_docu_seal_p_d_f_checksum_is_provided_to_generate_a_trusted_signature: Assine documentos com certificado confiável fornecido pela DocuSeal. Seus documentos e dados nunca são compartilhados com a DocuSeal. O checksum do PDF é fornecido para gerar uma assinatura confiável. hi_there: Olá @@ -3612,7 +3612,7 @@ de: &de hello_name: Hallo %{name} you_are_invited_to_product_name: Sie sind eingeladen zu %{product_name} you_have_been_invited_to_account_name_product_name_please_sign_up_using_the_link_below_: 'Sie wurden zu %{account_name} %{product_name} eingeladen. Bitte registrieren Sie sich über den folgenden Link:' - sent_using_product_name_via_developer_sandbox_html: 'Gesendet über %{product_name} via Developer Sandbox' + sent_using_product_name_in_testing_mode_html: 'Gesendet über %{product_name} im Testmodus' sent_using_product_name_free_document_signing_html: 'Gesendet mit der kostenlosen Dokumentensignierung von %{product_name}.' sign_documents_with_trusted_certificate_provided_by_docu_seal_your_documents_and_data_are_never_shared_with_docu_seal_p_d_f_checksum_is_provided_to_generate_a_trusted_signature: Unterzeichnen Sie Dokumente mit einem vertrauenswürdigen Zertifikat von DocuSeal. Ihre Dokumente und Daten werden niemals mit DocuSeal geteilt. Eine PDF-Prüfziffer wird bereitgestellt, um eine vertrauenswürdige Signatur zu generieren. hi_there: Hallo From 5bed6448dcea7c4051bdec2142e77d8fcef45b75 Mon Sep 17 00:00:00 2001 From: Alex Turchyn Date: Mon, 27 Jan 2025 13:30:34 +0200 Subject: [PATCH 40/82] make custom fields titles smaller on mobile devices --- .../submission_form/attachment_step.vue | 2 +- app/javascript/submission_form/date_step.vue | 14 ++++++++++---- app/javascript/submission_form/form.vue | 16 ++++++++++++---- app/javascript/submission_form/image_step.vue | 16 +++++++++++++--- app/javascript/submission_form/initials_step.vue | 7 ++++--- .../submission_form/multi_select_step.vue | 16 +++++++++++----- app/javascript/submission_form/number_step.vue | 15 +++++++++------ app/javascript/submission_form/payment_step.vue | 2 +- app/javascript/submission_form/phone_step.vue | 8 +++----- .../submission_form/signature_step.vue | 7 ++++--- app/javascript/submission_form/text_step.vue | 15 +++++++++------ .../submission_form/verification_step.vue | 2 +- 12 files changed, 78 insertions(+), 42 deletions(-) diff --git a/app/javascript/submission_form/attachment_step.vue b/app/javascript/submission_form/attachment_step.vue index f64dbb23..9f9f5f8b 100644 --- a/app/javascript/submission_form/attachment_step.vue +++ b/app/javascript/submission_form/attachment_step.vue @@ -49,7 +49,7 @@

    -
    +
    + > + +
    + > +