From c4505e67f087013f169dca153e3a546593beb095 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Tue, 8 Apr 2025 15:24:17 +0300 Subject: [PATCH 01/34] require 2fa placeholder --- app/controllers/submit_form_controller.rb | 24 +++---- .../templates_preferences_controller.rb | 2 +- app/javascript/elements/fetch_form.js | 36 ++++++++++ app/javascript/form.js | 2 + app/views/submissions/_detailed_form.html.erb | 2 +- app/views/submissions/new.html.erb | 23 ++++--- .../_form_fields.html.erb | 0 app/views/templates_preferences/show.html.erb | 3 +- config/locales/i18n.yml | 65 +++++++++++++++++++ lib/api_path_consider_json_middleware.rb | 4 +- 10 files changed, 135 insertions(+), 26 deletions(-) create mode 100644 app/javascript/elements/fetch_form.js create mode 100644 app/views/templates_preferences/_form_fields.html.erb diff --git a/app/controllers/submit_form_controller.rb b/app/controllers/submit_form_controller.rb index fcd0fdc8..6cbe16f5 100644 --- a/app/controllers/submit_form_controller.rb +++ b/app/controllers/submit_form_controller.rb @@ -7,11 +7,11 @@ class SubmitFormController < ApplicationController skip_before_action :authenticate_user! skip_authorization_check + before_action :load_submitter, only: %i[show update completed] + CONFIG_KEYS = [].freeze def show - @submitter = Submitter.find_by!(slug: params[:slug]) - submission = @submitter.submission return redirect_to submit_form_completed_path(@submitter.slug) if @submitter.completed_at? @@ -50,26 +50,24 @@ class SubmitFormController < ApplicationController end def update - submitter = Submitter.find_by!(slug: params[:slug]) - - if submitter.completed_at? + if @submitter.completed_at? return render json: { error: I18n.t('form_has_been_completed_already') }, status: :unprocessable_entity end - if submitter.template.archived_at? || submitter.submission.archived_at? + if @submitter.template.archived_at? || @submitter.submission.archived_at? return render json: { error: I18n.t('form_has_been_archived') }, status: :unprocessable_entity end - if submitter.submission.expired? + if @submitter.submission.expired? return render json: { error: I18n.t('form_has_been_expired') }, status: :unprocessable_entity end - if submitter.declined_at? + if @submitter.declined_at? return render json: { error: I18n.t('form_has_been_declined') }, status: :unprocessable_entity end - Submitters::SubmitValues.call(submitter, params, request) + Submitters::SubmitValues.call(@submitter, params, request) head :ok rescue Submitters::SubmitValues::RequiredFieldError => e @@ -80,14 +78,16 @@ class SubmitFormController < ApplicationController render json: { error: e.message }, status: :unprocessable_entity end - def completed - @submitter = Submitter.completed.find_by!(slug: params[:submit_form_slug]) - end + def completed; end def success; end private + def load_submitter + @submitter = Submitter.find_by!(slug: params[:slug] || params[:submit_form_slug]) + end + def build_attachments_index(submission) ActiveStorage::Attachment.where(record: submission.submitters, name: :attachments) .preload(:blob).index_by(&:uuid) diff --git a/app/controllers/templates_preferences_controller.rb b/app/controllers/templates_preferences_controller.rb index a9221f7b..78399e6a 100644 --- a/app/controllers/templates_preferences_controller.rb +++ b/app/controllers/templates_preferences_controller.rb @@ -25,7 +25,7 @@ class TemplatesPreferencesController < ApplicationController documents_copy_email_attach_documents documents_copy_email_reply_to completed_notification_email_attach_documents completed_redirect_url - submitters_order + submitters_order require_phone_2fa completed_notification_email_subject completed_notification_email_body completed_notification_email_enabled completed_notification_email_attach_audit] + [completed_message: %i[title body], diff --git a/app/javascript/elements/fetch_form.js b/app/javascript/elements/fetch_form.js new file mode 100644 index 00000000..b3378a63 --- /dev/null +++ b/app/javascript/elements/fetch_form.js @@ -0,0 +1,36 @@ +export default class extends HTMLElement { + connectedCallback () { + this.form.addEventListener('submit', (e) => { + e.preventDefault() + + this.submit() + }) + + if (this.dataset.onload === 'true') { + this.form.querySelector('button').click() + } + } + + submit () { + fetch(this.form.action, { + method: this.form.method, + body: new FormData(this.form) + }).then(async (resp) => { + if (!resp.ok) { + try { + const data = JSON.parse(await resp.text()) + + if (data.error) { + alert(data.error) + } + } catch (err) { + console.error(err) + } + } + }) + } + + get form () { + return this.querySelector('form') + } +} diff --git a/app/javascript/form.js b/app/javascript/form.js index 2e07f033..cbd26d1a 100644 --- a/app/javascript/form.js +++ b/app/javascript/form.js @@ -3,11 +3,13 @@ import { createApp, reactive } from 'vue' import Form from './submission_form/form' import DownloadButton from './elements/download_button' import ToggleSubmit from './elements/toggle_submit' +import FetchForm from './elements/fetch_form' const safeRegisterElement = (name, element, options = {}) => !window.customElements.get(name) && window.customElements.define(name, element, options) safeRegisterElement('download-button', DownloadButton) safeRegisterElement('toggle-submit', ToggleSubmit) +safeRegisterElement('fetch-form', FetchForm) safeRegisterElement('submission-form', class extends HTMLElement { connectedCallback () { this.appElem = document.createElement('div') diff --git a/app/views/submissions/_detailed_form.html.erb b/app/views/submissions/_detailed_form.html.erb index 37a2a88a..847ac5a8 100644 --- a/app/views/submissions/_detailed_form.html.erb +++ b/app/views/submissions/_detailed_form.html.erb @@ -31,7 +31,7 @@ "> - " id="detailed_phone_<%= item['uuid'] %>"> + <%= tag.input type: 'tel', pattern: '^\+[0-9\s\-]+$', oninvalid: "this.value ? this.setCustomValidity('#{t('use_international_format_1xxx_')}') : ''", oninput: "this.setCustomValidity('')", name: 'submission[1][submitters][][phone]', autocomplete: 'off', class: 'base-input !h-10 mt-1.5 w-full', placeholder: local_assigns[:require_phone_2fa] == true ? t(:phone) : "#{t('phone')} (#{t('optional')})", id: "detailed_phone_#{item['uuid']}", required: local_assigns[:require_phone_2fa] == true %> diff --git a/app/views/submissions/new.html.erb b/app/views/submissions/new.html.erb index 5de57e76..a7bcc8f6 100644 --- a/app/views/submissions/new.html.erb +++ b/app/views/submissions/new.html.erb @@ -1,10 +1,11 @@ +<% require_phone_2fa = @template.preferences['require_phone_2fa'] == true %> <%= render 'shared/turbo_modal_large', title: params[:selfsign] ? t('add_recipients') : t('add_new_recipients') do %> - <% options = [[t('via_email'), 'email'], [t('via_phone'), 'phone'], [t('detailed'), 'detailed'], [t('upload_list'), 'list']].compact %> + <% options = [require_phone_2fa ? nil : [t('via_email'), 'email'], require_phone_2fa ? nil : [t('via_phone'), 'phone'], [t('detailed'), 'detailed'], [t('upload_list'), 'list']].compact %>
<% options.each_with_index do |(label, value), index| %>
- <%= radio_button_tag 'option', value, value == 'email', class: 'peer hidden', data: { action: 'change:toggle-visible#trigger' } %> + <%= radio_button_tag 'option', value, value == (require_phone_2fa ? 'detailed' : 'email'), class: 'peer hidden', data: { action: 'change:toggle-visible#trigger' } %> @@ -13,14 +14,16 @@
-
- <%= render 'email_form', template: @template %> -
- - <% end %> <% end %> + <% if can?(:manage, :personalization_advanced) %> + <%= form_for @template, url: template_preferences_path(@template), method: :post, html: { autocomplete: 'off', class: 'mt-2' }, data: { close_on_submit: false } do |f| %> +
+ + <%= t('ensure_unique_recipients') %> + + <%= f.fields_for :preferences, Struct.new(:validate_unique_submitters).new(@template.preferences['validate_unique_submitters']) do |ff| %> + <%= ff.check_box :validate_unique_submitters, { class: 'toggle', onchange: 'this.form.requestSubmit()' }, 'true', '' %> + <% end %> +
+ <% end %> + <% end %>
<%= button_tag button_title(title: t('save'), disabled_with: t('updating')), class: 'base-button', form: :submitters_form %>
diff --git a/config/locales/i18n.yml b/config/locales/i18n.yml index 277e3344..952ff3b9 100644 --- a/config/locales/i18n.yml +++ b/config/locales/i18n.yml @@ -22,6 +22,7 @@ en: &en thanks: Thanks bcc_recipients: BCC recipients always_enforce_signing_order: Always enforce the signing order + ensure_unique_recipients: Ensure unique recipients edit_per_party: Edit per party reply_to: Reply to pending_by_me: Pending by me @@ -795,6 +796,7 @@ en: &en read: Read your data es: &es + ensure_unique_recipients: Asegurar destinatarios únicos require_phone_2fa_to_open: Requiere 2FA por teléfono para abrir the_sender_has_requested_a_two_factor_authentication_via_one_time_password_sent_to_your_html: El remitente ha solicitado una autenticación de dos factores mediante una contraseña de un solo uso enviada a su número de teléfono %{phone}. send_verification_code: Enviar código de verificación @@ -1572,6 +1574,7 @@ es: &es read: Leer tus datos it: &it + ensure_unique_recipients: Assicurarsi destinatari unici require_phone_2fa_to_open: Richiedi l'autenticazione a due fattori tramite telefono per aprire the_sender_has_requested_a_two_factor_authentication_via_one_time_password_sent_to_your_html: Il mittente ha richiesto un'autenticazione a due fattori tramite una password monouso inviata al tuo numero di telefono %{phone}. send_verification_code: Invia codice di verifica @@ -2348,6 +2351,7 @@ it: &it read: Leggi i tuoi dati fr: &fr + ensure_unique_recipients: Assurer l'unicité des destinataires require_phone_2fa_to_open: Requiert une 2FA par téléphone pour ouvrir the_sender_has_requested_a_two_factor_authentication_via_one_time_password_sent_to_your_html: L'expéditeur a demandé une authentification à deux facteurs via un mot de passe à usage unique envoyé à votre numéro de téléphone %{phone}. send_verification_code: Envoyer le code de vérification @@ -3126,6 +3130,7 @@ fr: &fr read: Lire vos données pt: &pt + ensure_unique_recipients: Garantir destinatários únicos require_phone_2fa_to_open: Necessário autenticação de dois fatores via telefone para abrir the_sender_has_requested_a_two_factor_authentication_via_one_time_password_sent_to_your_html: O remetente solicitou uma autenticação de dois fatores via senha de uso único enviada para seu número %{phone}. send_verification_code: Enviar código de verificação @@ -3904,6 +3909,7 @@ pt: &pt read: Ler seus dados de: &de + ensure_unique_recipients: Stellen Sie einzigartige Empfänger sicher require_phone_2fa_to_open: Telefon-2FA zum Öffnen erforderlich the_sender_has_requested_a_two_factor_authentication_via_one_time_password_sent_to_your_html: Der Absender hat eine Zwei-Faktor-Authentifizierung per Einmalpasswort angefordert, das an Ihre %{phone}-Telefonnummer gesendet wurde. send_verification_code: Bestätigungscode senden diff --git a/lib/submissions/create_from_submitters.rb b/lib/submissions/create_from_submitters.rb index 6801aa13..a0c274fa 100644 --- a/lib/submissions/create_from_submitters.rb +++ b/lib/submissions/create_from_submitters.rb @@ -65,6 +65,12 @@ module Submissions raise BaseError, 'Defined more signing parties than in template' end + if template.preferences['validate_unique_submitters'] == true + submission_emails = submission.submitters.filter_map(&:email) + + raise BaseError, 'Recipient emails should differ' if submission_emails.uniq.size != submission_emails.size + end + next if submission.submitters.blank? maybe_add_invite_submitters(submission, template) From 6e81a3be3f7dac70c6e5f0bd7d332fbcda2847bc Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Thu, 10 Apr 2025 11:51:40 +0300 Subject: [PATCH 08/34] fix i18n --- config/locales/i18n.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/config/locales/i18n.yml b/config/locales/i18n.yml index 952ff3b9..f15660ba 100644 --- a/config/locales/i18n.yml +++ b/config/locales/i18n.yml @@ -54,7 +54,7 @@ en: &en 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.' sent_with_docuseal_pro_html: 'Sent with DocuSeal Pro' - show_send_with_docuseal_pro_attribution_in_emails_html: Show "Sent with DocuSeal Pro" attribution in emails + show_send_with_docuseal_pro_attribution_in_emails_html: Show "Sent with DocuSeal Pro" attribution in emails 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.' you_have_been_invited_to_sign_the_name: 'You have been invited to sign the "%{name}".' @@ -832,7 +832,7 @@ es: &es 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}.' sent_with_docuseal_pro_html: 'Enviado con DocuSeal Pro' - show_send_with_docuseal_pro_attribution_in_emails_html: Mostrar el mensaje "Enviado con DocuSeal Pro" en los correos electrónicos + show_send_with_docuseal_pro_attribution_in_emails_html: Mostrar el mensaje "Enviado con DocuSeal Pro" en los correos electrónicos 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 thanks: Gracias @@ -1609,7 +1609,7 @@ it: &it 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}.' sent_with_docuseal_pro_html: 'Inviato con DocuSeal Pro' - show_send_with_docuseal_pro_attribution_in_emails_html: Mostra la dicitura "Inviato con DocuSeal Pro" nelle email + show_send_with_docuseal_pro_attribution_in_emails_html: Mostra la dicitura "Inviato con DocuSeal Pro" nelle email 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 thanks: Grazie @@ -2387,7 +2387,7 @@ fr: &fr 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}.' sent_with_docuseal_pro_html: 'Envoyé avec DocuSeal Pro' - show_send_with_docuseal_pro_attribution_in_emails_html: Afficher "Envoyé avec DocuSeal Pro" dans les e-mails + show_send_with_docuseal_pro_attribution_in_emails_html: Afficher "Envoyé avec DocuSeal Pro" dans les e-mails 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 thanks: Merci @@ -3166,7 +3166,7 @@ pt: &pt 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}.' sent_with_docuseal_pro_html: 'Enviado com DocuSeal Pro' - show_send_with_docuseal_pro_attribution_in_emails_html: Mostrar "Enviado com DocuSeal Pro" nos e-mails + show_send_with_docuseal_pro_attribution_in_emails_html: Mostrar "Enviado com DocuSeal Pro" nos e-mails 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á thanks: Obrigado @@ -3945,7 +3945,7 @@ de: &de 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}.' sent_with_docuseal_pro_html: Gesendet mit DocuSeal Pro - show_send_with_docuseal_pro_attribution_in_emails_html: '"Gesendet mit DocuSeal Pro" in E-Mails anzeigen' + show_send_with_docuseal_pro_attribution_in_emails_html: '"Gesendet mit DocuSeal Pro" in E-Mails anzeigen' 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 thanks: Danke From 5e27cc36c6ef27facf9b71a90efa7d3fdcb2efd5 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Thu, 10 Apr 2025 13:22:10 +0300 Subject: [PATCH 09/34] fix default font --- lib/submissions/generate_result_attachments.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/submissions/generate_result_attachments.rb b/lib/submissions/generate_result_attachments.rb index 2fe766c0..34453141 100644 --- a/lib/submissions/generate_result_attachments.rb +++ b/lib/submissions/generate_result_attachments.rb @@ -221,7 +221,7 @@ module Submissions font_variant = nil unless font_name.in?(DEFAULT_FONTS) end - font = pdf.fonts.add(font_name, variant: font_variant) + font = pdf.fonts.add(font_name, variant: font_variant, custom_encoding: font_name.in?(DEFAULT_FONTS)) value = submitter.values[field['uuid']] value = field['default_value'] if field['type'] == 'heading' From 4e5157d1ee9633853065cd5f515567eb71734caa Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Fri, 24 Jan 2025 23:08:44 +0200 Subject: [PATCH 10/34] add text valign --- app/javascript/submission_form/area.vue | 9 ++++++--- app/javascript/template_builder/area.vue | 11 +++++++---- app/views/submissions/_value.html.erb | 15 +++++++++------ lib/submissions/generate_result_attachments.rb | 16 +++++++++++++--- 4 files changed, 35 insertions(+), 16 deletions(-) diff --git a/app/javascript/submission_form/area.vue b/app/javascript/submission_form/area.vue index ce212a4f..d18aae5f 100644 --- a/app/javascript/submission_form/area.vue +++ b/app/javascript/submission_form/area.vue @@ -183,7 +183,7 @@ v-else ref="textContainer" dir="auto" - class="flex items-center px-0.5 w-full" + class="flex px-0.5 w-full" :class="{ ...alignClasses, ...fontClasses }" >
+<% valign = field.dig('preferences', 'valign') %> <% color = field.dig('preferences', 'color') %> <% font = field.dig('preferences', 'font') %> <% font_type = field.dig('preferences', 'font_type') %> -width: <%= area['w'] * 100 %>%; height: <%= area['h'] * 100 %>%; left: <%= area['x'] * 100 %>%; top: <%= area['y'] * 100 %>%; <%= "font-size: clamp(4pt, 1.6vw, #{field['preferences']['font_size'].to_i * 1.23}pt); line-height: `clamp(6pt, 2.0vw, #{(field['preferences']['font_size'].to_i * 1.23) + 3}pt)`" if field.dig('preferences', 'font_size') %>"> +width: <%= area['w'] * 100 %>%; height: <%= area['h'] * 100 %>%; left: <%= area['x'] * 100 %>%; top: <%= area['y'] * 100 %>%; <%= "font-size: clamp(4pt, 1.6vw, #{field['preferences']['font_size'].to_i * 1.23}pt); line-height: `clamp(6pt, 2.0vw, #{(field['preferences']['font_size'].to_i * 1.23) + 3}pt)`" if field.dig('preferences', 'font_size') %>"> <% if field['type'] == 'signature' %>
@@ -61,17 +62,19 @@
<% elsif field['type'] == 'date' %> -
+
<% value = Time.current.in_time_zone(local_assigns[:timezone]).to_date.to_s if value == '{{date}}' %> - <%= TimeUtils.format_date_string(value, field.dig('preferences', 'format'), local_assigns[:locale]) %> +
<%= TimeUtils.format_date_string(value, field.dig('preferences', 'format'), local_assigns[:locale]) %>
<% elsif field['type'] == 'number' %> -
- <%= NumberUtils.format_number(value, field.dig('preferences', 'format')) %> +
+
<%= NumberUtils.format_number(value, field.dig('preferences', 'format')) %>
<% else %> -
<%= Array.wrap(value).join(', ') %>
+
+
<%= Array.wrap(value).join(', ') %>
+
<% end %> diff --git a/lib/submissions/generate_result_attachments.rb b/lib/submissions/generate_result_attachments.rb index 34453141..87c04402 100644 --- a/lib/submissions/generate_result_attachments.rb +++ b/lib/submissions/generate_result_attachments.rb @@ -229,8 +229,9 @@ module Submissions text_align = field.dig('preferences', 'align').to_s.to_sym.presence || (value.to_s.match?(RTL_REGEXP) ? :right : :left) - layouter = HexaPDF::Layout::TextLayouter.new(text_valign: :center, text_align:, - font:, font_size:) + text_valign = (field.dig('preferences', 'valign').to_s.presence || 'center').to_sym + + layouter = HexaPDF::Layout::TextLayouter.new(text_valign:, text_align:, font:, font_size:) next if Array.wrap(value).compact_blank.blank? @@ -515,10 +516,19 @@ module Submissions 0 end + align_y_diff = + if text_valign == :top + 0 + elsif text_valign == :bottom + height_diff + TEXT_TOP_MARGIN + else + height_diff / 2 + end + layouter.fit([text], field['type'].in?(%w[date number]) ? width : area['w'] * width, height_diff.positive? ? box_height : area['h'] * height) .draw(canvas, (area['x'] * width) - right_align_x_adjustment + TEXT_LEFT_MARGIN, - height - (area['y'] * height) + height_diff - TEXT_TOP_MARGIN) + height - (area['y'] * height) + align_y_diff - TEXT_TOP_MARGIN) end end end From d1cfc64adc8d482588908d86690306a65efa3850 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Mon, 14 Apr 2025 12:14:24 +0300 Subject: [PATCH 11/34] adjust api response --- lib/submitters/serialize_for_api.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/submitters/serialize_for_api.rb b/lib/submitters/serialize_for_api.rb index ef17189e..01f1246d 100644 --- a/lib/submitters/serialize_for_api.rb +++ b/lib/submitters/serialize_for_api.rb @@ -49,7 +49,8 @@ module Submitters def serialize_events(events) events.map do |event| - event.as_json(only: %i[id submitter_id event_type event_timestamp]).merge('data' => event.data.slice('reason')) + event.as_json(only: %i[id submitter_id event_type event_timestamp]) + .merge('data' => event.data.slice('reason', 'firstname', 'lastname', 'method', 'country')) end end end From 67be785e69d3dd15ba6ea75a17a07e2a696a3a21 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Tue, 15 Apr 2025 13:22:02 +0300 Subject: [PATCH 12/34] add slug filter --- app/controllers/api/submissions_controller.rb | 27 +++++++++++-------- app/controllers/api/submitters_controller.rb | 23 +++++++++------- app/controllers/api/templates_controller.rb | 1 + 3 files changed, 31 insertions(+), 20 deletions(-) diff --git a/app/controllers/api/submissions_controller.rb b/app/controllers/api/submissions_controller.rb index 89d361ec..17d961dd 100644 --- a/app/controllers/api/submissions_controller.rb +++ b/app/controllers/api/submissions_controller.rb @@ -11,17 +11,7 @@ module Api def index submissions = Submissions.search(@submissions, params[:q]) - submissions = submissions.where(template_id: params[:template_id]) if params[:template_id].present? - - if params[:template_folder].present? - submissions = submissions.joins(template: :folder).where(folder: { name: params[:template_folder] }) - end - - if params.key?(:archived) - submissions = params[:archived].in?(['true', true]) ? submissions.archived : submissions.active - end - - submissions = Submissions::Filter.call(submissions, current_user, params) + submissions = filter_submissions(submissions, params) submissions = paginate(submissions.preload(:created_by_user, :submitters, template: :folder, @@ -115,6 +105,21 @@ module Api private + def filter_submissions(submissions, params) + submissions = submissions.where(template_id: params[:template_id]) if params[:template_id].present? + submissions = submissions.where(slug: params[:slug]) if params[:slug].present? + + if params[:template_folder].present? + submissions = submissions.joins(template: :folder).where(folder: { name: params[:template_folder] }) + end + + if params.key?(:archived) + submissions = params[:archived].in?(['true', true]) ? submissions.archived : submissions.active + end + + Submissions::Filter.call(submissions, current_user, params) + end + def build_create_json(submissions) json = submissions.flat_map do |submission| submission.submitters.map do |s| diff --git a/app/controllers/api/submitters_controller.rb b/app/controllers/api/submitters_controller.rb index 1fd06d74..c2e6a07f 100644 --- a/app/controllers/api/submitters_controller.rb +++ b/app/controllers/api/submitters_controller.rb @@ -7,15 +7,7 @@ module Api def index submitters = Submitters.search(@submitters, params[:q]) - submitters = submitters.where(external_id: params[:application_key]) if params[:application_key].present? - submitters = submitters.where(external_id: params[:external_id]) if params[:external_id].present? - submitters = submitters.where(submission_id: params[:submission_id]) if params[:submission_id].present? - - if params[:template_id].present? - submitters = submitters.joins(:submission).where(submission: { template_id: params[:template_id] }) - end - - submitters = maybe_filder_by_completed_at(submitters, params) + submitters = filter_submitters(submitters, params) submitters = paginate( submitters.preload(:template, :submission, :submission_events, @@ -163,6 +155,19 @@ module Api submitter end + def filter_submitters(submitters, params) + submitters = submitters.where(external_id: params[:application_key]) if params[:application_key].present? + submitters = submitters.where(external_id: params[:external_id]) if params[:external_id].present? + submitters = submitters.where(slug: params[:slug]) if params[:slug].present? + submitters = submitters.where(submission_id: params[:submission_id]) if params[:submission_id].present? + + if params[:template_id].present? + submitters = submitters.joins(:submission).where(submission: { template_id: params[:template_id] }) + end + + maybe_filder_by_completed_at(submitters, params) + end + def assign_external_id(submitter, attrs) submitter.external_id = attrs[:application_key] if attrs.key?(:application_key) submitter.external_id = attrs[:external_id] if attrs.key?(:external_id) diff --git a/app/controllers/api/templates_controller.rb b/app/controllers/api/templates_controller.rb index a695e728..5e8da098 100644 --- a/app/controllers/api/templates_controller.rb +++ b/app/controllers/api/templates_controller.rb @@ -90,6 +90,7 @@ module Api templates = params[:archived].in?(['true', true]) ? templates.archived : templates.active templates = templates.where(external_id: params[:application_key]) if params[:application_key].present? templates = templates.where(external_id: params[:external_id]) if params[:external_id].present? + templates = templates.where(slug: params[:slug]) if params[:slug].present? templates = templates.joins(:folder).where(folder: { name: params[:folder] }) if params[:folder].present? templates From 7cada0af8be1375187647879913a5379092df03a Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Tue, 15 Apr 2025 19:58:31 +0300 Subject: [PATCH 13/34] older devises --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 29d4e173..b0c4ece8 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ ] }, "browserslist": [ - "last 3 years" + "last 5 years" ], "devDependencies": { "@babel/eslint-parser": "^7.21.8", From 2b5f772febfd21f0554e70afa029e643f0b74124 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Tue, 15 Apr 2025 21:40:22 +0300 Subject: [PATCH 14/34] update ai link --- app/views/icons/_sparkles.html.erb | 3 +++ app/views/shared/_navbar.html.erb | 8 ++++++++ lib/docuseal.rb | 2 +- 3 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 app/views/icons/_sparkles.html.erb diff --git a/app/views/icons/_sparkles.html.erb b/app/views/icons/_sparkles.html.erb new file mode 100644 index 00000000..7981c9fe --- /dev/null +++ b/app/views/icons/_sparkles.html.erb @@ -0,0 +1,3 @@ + + + diff --git a/app/views/shared/_navbar.html.erb b/app/views/shared/_navbar.html.erb index 794997ab..f23bcd0f 100644 --- a/app/views/shared/_navbar.html.erb +++ b/app/views/shared/_navbar.html.erb @@ -50,6 +50,14 @@ <% end %> <% end %> + <% if Docuseal.multitenant? || current_user.role == 'superadmin' %> +
  • + <%= link_to Docuseal::CHATGPT_URL, target: 'blank', class: 'flex items-center' do %> + <%= svg_icon('sparkles', class: 'w-5 h-5 flex-shrink-0 stroke-2') %> + <%= t('ask_ai') %> + <% end %> +
  • + <% end %> <% if (can?(:manage, EncryptedConfig) && current_user == true_user) || (current_user != true_user && current_account.testing?) %> <%= form_for '', url: testing_account_path, method: current_account.testing? ? :delete : :get, html: { class: 'w-full py-1' } do |f| %>