From b4884e94f6267b7d6d1446c62f4a381ff9fe4a0f Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Tue, 3 Sep 2024 00:37:31 +0300 Subject: [PATCH 01/30] add preferences --- app/javascript/template_builder/builder.vue | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/app/javascript/template_builder/builder.vue b/app/javascript/template_builder/builder.vue index 812b233b..4334aa8e 100644 --- a/app/javascript/template_builder/builder.vue +++ b/app/javascript/template_builder/builder.vue @@ -134,6 +134,17 @@ Save and Preview +
  • + + + Preferences + +
  • @@ -404,7 +415,7 @@ import Contenteditable from './contenteditable' import DocumentPreview from './preview' import DocumentControls from './controls' import MobileFields from './mobile_fields' -import { IconPlus, IconUsersPlus, IconDeviceFloppy, IconChevronDown, IconEye, IconWritingSign, IconInnerShadowTop, IconInfoCircle } from '@tabler/icons-vue' +import { IconPlus, IconUsersPlus, IconDeviceFloppy, IconChevronDown, IconEye, IconWritingSign, IconInnerShadowTop, IconInfoCircle, IconAdjustments } from '@tabler/icons-vue' import { v4 } from 'uuid' import { ref, computed } from 'vue' import { en as i18nEn } from './i18n' @@ -428,6 +439,7 @@ export default { Contenteditable, IconUsersPlus, IconChevronDown, + IconAdjustments, IconEye, IconDeviceFloppy }, @@ -770,6 +782,9 @@ export default { this.documentRefs = [] }, methods: { + closeDropdown () { + document.activeElement.blur() + }, t (key) { return this.i18n[key] || i18nEn[key] || key }, From 2598962c5b1cb864c2d6bab35ab0a8f97d957001 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Tue, 3 Sep 2024 18:07:12 +0300 Subject: [PATCH 02/30] configure image quality --- lib/templates/process_document.rb | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/templates/process_document.rb b/lib/templates/process_document.rb index 7d9653fa..7ee8739a 100644 --- a/lib/templates/process_document.rb +++ b/lib/templates/process_document.rb @@ -15,7 +15,7 @@ module Templates module_function - def call(attachment, data, extract_fields: false) + def call(attachment, data, extract_fields: false, image_quality: nil) if attachment.content_type == PDF_CONTENT_TYPE if extract_fields && data.size < MAX_FLATTEN_FILE_SIZE pdf = HexaPDF::Document.new(io: StringIO.new(data)) @@ -23,23 +23,23 @@ module Templates fields = Templates::FindAcroFields.call(pdf, attachment) end - generate_pdf_preview_images(attachment, data, pdf) + generate_pdf_preview_images(attachment, data, pdf, image_quality:) attachment.metadata['pdf']['fields'] = fields if fields elsif attachment.image? - generate_preview_image(attachment, data) + generate_preview_image(attachment, data, image_quality:) end attachment end - def generate_preview_image(attachment, data) + def generate_preview_image(attachment, data, image_quality: nil) ActiveStorage::Attachment.where(name: ATTACHMENT_NAME, record: attachment).destroy_all image = Vips::Image.new_from_buffer(data, '') image = image.autorot.resize(MAX_WIDTH / image.width.to_f) - io = StringIO.new(image.write_to_buffer(FORMAT, Q: Q, interlace: true)) + io = StringIO.new(image.write_to_buffer(FORMAT, Q: image_quality || Q, interlace: true)) ActiveStorage::Attachment.create!( blob: ActiveStorage::Blob.create_and_upload!( @@ -51,7 +51,7 @@ module Templates ) end - def generate_pdf_preview_images(attachment, data, pdf = nil) + def generate_pdf_preview_images(attachment, data, pdf = nil, image_quality: nil) ActiveStorage::Attachment.where(name: ATTACHMENT_NAME, record: attachment).destroy_all pdf ||= HexaPDF::Document.new(io: StringIO.new(data)) @@ -70,7 +70,7 @@ module Templates page = Vips::Image.new_from_buffer(data, '', dpi: DPI, page: page_number) page = page.resize(MAX_WIDTH / page.width.to_f) - io = StringIO.new(page.write_to_buffer(FORMAT, Q: Q, interlace: true)) + io = StringIO.new(page.write_to_buffer(FORMAT, Q: image_quality || Q, interlace: true)) ApplicationRecord.no_touching do ActiveStorage::Attachment.create!( @@ -117,11 +117,11 @@ module Templates end end - def generate_pdf_preview_from_file(attachment, file_path, page_number) + def generate_pdf_preview_from_file(attachment, file_path, page_number, image_quality: nil) io = StringIO.new command = [ - 'pdftocairo', '-jpeg', '-jpegopt', "progressive=y,quality=#{Q},optimize=y", + 'pdftocairo', '-jpeg', '-jpegopt', "progressive=y,quality=#{image_quality || Q},optimize=y", '-scale-to-x', MAX_WIDTH, '-scale-to-y', '-1', '-r', DPI, '-f', page_number + 1, '-l', page_number + 1, '-singlefile', Shellwords.escape(file_path), '-' From dc24df0d4c60b7c8d888dbdfac599abc68793632 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Tue, 3 Sep 2024 18:30:37 +0300 Subject: [PATCH 03/30] remove log --- lib/templates/process_document.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/templates/process_document.rb b/lib/templates/process_document.rb index 7ee8739a..1b821187 100644 --- a/lib/templates/process_document.rb +++ b/lib/templates/process_document.rb @@ -97,11 +97,9 @@ module Templates pdf.write(io, incremental: false, validate: false) io.string - rescue StandardError => e + rescue StandardError raise if Rails.env.development? - Rollbar.error(e) if defined?(Rollbar) - data end From 227a067c77ba159370c075b8f9b13b7a9b9f4d77 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Wed, 4 Sep 2024 00:29:38 +0300 Subject: [PATCH 04/30] fix readonly condition --- app/javascript/submission_form/area.vue | 3 ++- app/javascript/submission_form/areas.vue | 7 ++++++- app/javascript/submission_form/form.vue | 13 +++++++++++-- app/views/submit_form/show.html.erb | 2 ++ 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/app/javascript/submission_form/area.vue b/app/javascript/submission_form/area.vue index e7596862..1a67d904 100644 --- a/app/javascript/submission_form/area.vue +++ b/app/javascript/submission_form/area.vue @@ -226,7 +226,8 @@ export default { }, submitter: { type: Object, - required: true + required: false, + default: () => ({}) }, withSignatureId: { type: Boolean, diff --git a/app/javascript/submission_form/areas.vue b/app/javascript/submission_form/areas.vue index d9807ad5..65524fce 100644 --- a/app/javascript/submission_form/areas.vue +++ b/app/javascript/submission_form/areas.vue @@ -21,7 +21,7 @@ :values="values" :field="field" :area="area" - :submittable="true" + :submittable="submittable" :field-index="fieldIndex" :scroll-padding="scrollPadding" :submitter="submitter" @@ -58,6 +58,11 @@ export default { required: false, default: false }, + submittable: { + type: Boolean, + required: false, + default: true + }, submitter: { type: Object, required: true diff --git a/app/javascript/submission_form/form.vue b/app/javascript/submission_form/form.vue index c84c7eb1..f8ca5c4e 100644 --- a/app/javascript/submission_form/form.vue +++ b/app/javascript/submission_form/form.vue @@ -13,6 +13,12 @@ :scroll-padding="scrollPadding" @focus-step="[saveStep(), currentField.type !== 'checkbox' ? isFormVisible = true : '', goToStep($event, false, true)]" /> + f.readonly && f.conditions?.length && this.checkFieldConditions(f)) + }, stepFields () { return this.fields.filter((f) => !f.readonly).reduce((acc, f) => { const prevStep = acc[acc.length - 1] @@ -933,12 +942,12 @@ export default { return acc && isEmpty(this.values[c.field_uuid]) } else if (['not_empty', 'checked'].includes(c.action)) { return acc && !isEmpty(this.values[c.field_uuid]) - } else if (['equal', 'contains'].includes(c.action)) { + } else if (['equal', 'contains'].includes(c.action) && field) { const option = field.options.find((o) => o.uuid === c.value) const values = [this.values[c.field_uuid]].flat() return acc && values.includes(this.optionValue(option, field.options.indexOf(option))) - } else if (['not_equal', 'does_not_contain'].includes(c.action)) { + } else if (['not_equal', 'does_not_contain'].includes(c.action) && field) { const option = field.options.find((o) => o.uuid === c.value) const values = [this.values[c.field_uuid]].flat() diff --git a/app/views/submit_form/show.html.erb b/app/views/submit_form/show.html.erb index 325d93c5..d7a966ef 100644 --- a/app/views/submit_form/show.html.erb +++ b/app/views/submit_form/show.html.erb @@ -51,6 +51,8 @@ <% next if !field['readonly'] && field['submitter_uuid'] == @submitter.uuid %> <% next if field['redacted'] && field['submitter_uuid'] != @submitter.uuid %> <% next if value == '{{date}}' && field['submitter_uuid'] != @submitter.uuid %> + <% next if field['conditions'].present? && values[field['uuid']].blank? && field['submitter_uuid'] != @submitter.uuid %> + <% next if field['conditions'].present? && field['submitter_uuid'] == @submitter.uuid %> <% next if field.dig('preferences', 'formula').present? && field['submitter_uuid'] == @submitter.uuid %> <%= render 'submissions/value', area:, field:, attachments_index: @attachments_index, value:, locale: @submitter.account.locale, timezone: @submitter.account.timezone, submitter: submitters_index[field['submitter_uuid']], with_signature_id: @form_configs[:with_signature_id] %> <% end %> From 90db64cc0360e47ae39c9df56c92606ca53f973c Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Thu, 5 Sep 2024 15:03:43 +0300 Subject: [PATCH 05/30] fix file prefill --- lib/submissions/normalize_param_utils.rb | 10 ++++++---- lib/submitters/normalize_values.rb | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/submissions/normalize_param_utils.rb b/lib/submissions/normalize_param_utils.rb index 05e80a9f..07ded95c 100644 --- a/lib/submissions/normalize_param_utils.rb +++ b/lib/submissions/normalize_param_utils.rb @@ -48,13 +48,15 @@ module Submissions submitters.each do |submitter| submitter.values.each_value do |value| - attachment = attachments_index[value] + Array.wrap(value).each do |v| + attachment = attachments_index[v] - next unless attachment + next unless attachment - attachment.record = submitter + attachment.record = submitter - attachment.save! + attachment.save! + end end end end diff --git a/lib/submitters/normalize_values.rb b/lib/submitters/normalize_values.rb index b14a4270..3e65b0c7 100644 --- a/lib/submitters/normalize_values.rb +++ b/lib/submitters/normalize_values.rb @@ -137,7 +137,7 @@ module Submitters elsif type.in?(%w[signature initials]) && value.length < 60 find_or_create_blob_from_text(account, value, type) elsif (data = Base64.decode64(value.sub(BASE64_PREFIX_REGEXP, ''))) && - Marcel::MimeType.for(data).include?('image') + Marcel::MimeType.for(data).exclude?('octet-stream') find_or_create_blob_from_base64(account, data, type) else raise InvalidDefaultValue, "Invalid value, url, base64 or text < 60 chars is expected: #{value.first(200)}..." From 126a36dbf384de17cc4e91c29f513e1ce68616c0 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Fri, 6 Sep 2024 14:06:30 +0300 Subject: [PATCH 06/30] clear log --- lib/submissions/generate_result_attachments.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/submissions/generate_result_attachments.rb b/lib/submissions/generate_result_attachments.rb index 07504420..08428040 100644 --- a/lib/submissions/generate_result_attachments.rb +++ b/lib/submissions/generate_result_attachments.rb @@ -532,6 +532,8 @@ module Submissions begin pdf.acro_form.create_appearances(force: true) if pdf.acro_form && pdf.acro_form[:NeedAppearances] pdf.acro_form&.flatten + rescue HexaPDF::MissingGlyphError + nil rescue StandardError => e Rollbar.error(e) if defined?(Rollbar) end From 71b8cc717250961f17f32d4aa44f9094bd9cfc91 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Fri, 6 Sep 2024 19:30:49 +0300 Subject: [PATCH 07/30] add recipient preferences --- app/controllers/api/templates_controller.rb | 2 +- app/controllers/templates_controller.rb | 2 +- .../templates_recipients_controller.rb | 49 ++++++++++++++++ app/javascript/application.js | 18 +++++- app/javascript/elements/linked_input.js | 31 ++++++++++ .../elements/submitter_autocomplete.js | 1 + app/javascript/elements/toggle_attribute.js | 28 ++++++++++ app/javascript/elements/turbo_modal.js | 2 +- app/views/submissions/_detailed_form.html.erb | 12 +++- app/views/submissions/_email_form.html.erb | 4 +- app/views/submissions/_phone_form.html.erb | 12 +++- app/views/templates_preferences/show.html.erb | 56 +++++++++++++++++-- config/routes.rb | 1 + 13 files changed, 201 insertions(+), 17 deletions(-) create mode 100644 app/controllers/templates_recipients_controller.rb create mode 100644 app/javascript/elements/linked_input.js create mode 100644 app/javascript/elements/toggle_attribute.js diff --git a/app/controllers/api/templates_controller.rb b/app/controllers/api/templates_controller.rb index 7341b916..d24c58cc 100644 --- a/app/controllers/api/templates_controller.rb +++ b/app/controllers/api/templates_controller.rb @@ -97,7 +97,7 @@ module Api :name, :external_id, { - submitters: [%i[name uuid]], + submitters: [%i[name uuid is_requester linked_to_uuid email]], fields: [[:uuid, :submitter_uuid, :name, :type, :required, :readonly, :default_value, :title, :description, diff --git a/app/controllers/templates_controller.rb b/app/controllers/templates_controller.rb index f973831a..4118c48d 100644 --- a/app/controllers/templates_controller.rb +++ b/app/controllers/templates_controller.rb @@ -103,7 +103,7 @@ class TemplatesController < ApplicationController params.require(:template).permit( :name, { schema: [%i[attachment_uuid name]], - submitters: [%i[name uuid]], + submitters: [%i[name uuid is_requester linked_to_uuid email]], fields: [[:uuid, :submitter_uuid, :name, :type, :required, :readonly, :default_value, :title, :description, diff --git a/app/controllers/templates_recipients_controller.rb b/app/controllers/templates_recipients_controller.rb new file mode 100644 index 00000000..b07491be --- /dev/null +++ b/app/controllers/templates_recipients_controller.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +class TemplatesRecipientsController < ApplicationController + load_and_authorize_resource :template + + def create + authorize!(:update, @template) + + @template.submitters = + submitters_params.map { |s| s.reject { |_, v| v.is_a?(String) && v.blank? } } + + @template.save! + + render json: { submitters: @template.submitters } + end + + private + + def submitters_params + params.require(:template).permit( + submitters: [%i[name uuid is_requester linked_to_uuid email option]] + ).fetch(:submitters, {}).values.filter_map do |s| + next if s[:uuid].blank? + + if s[:is_requester] == '1' + s[:is_requester] = true + else + s.delete(:is_requester) + end + + option = s.delete(:option) + + if option.present? + case option + when 'is_requester' + s[:is_requester] = true + when 'not_set' + s.delete(:is_requester) + s.delete(:email) + s.delete(:linked_to_uuid) + when /\Alinked_to_(.*)\z/ + s[:linked_to_uuid] = ::Regexp.last_match(-1) + end + end + + s + end + end +end diff --git a/app/javascript/application.js b/app/javascript/application.js index 098ea339..090fef07 100644 --- a/app/javascript/application.js +++ b/app/javascript/application.js @@ -26,6 +26,8 @@ import EmailsTextarea from './elements/emails_textarea' import ToggleOnSubmit from './elements/toggle_on_submit' import PasswordInput from './elements/password_input' import SearchInput from './elements/search_input' +import ToggleAttribute from './elements/toggle_attribute' +import LinkedInput from './elements/linked_input' import * as TurboInstantClick from './lib/turbo_instant_click' @@ -89,9 +91,13 @@ safeRegisterElement('toggle-cookies', ToggleCookies) safeRegisterElement('toggle-on-submit', ToggleOnSubmit) safeRegisterElement('password-input', PasswordInput) safeRegisterElement('search-input', SearchInput) +safeRegisterElement('toggle-attribute', ToggleAttribute) +safeRegisterElement('linked-input', LinkedInput) safeRegisterElement('template-builder', class extends HTMLElement { connectedCallback () { + document.addEventListener('turbo:submit-end', this.onSubmit) + this.appElem = document.createElement('div') this.appElem.classList.add('md:h-screen') @@ -114,12 +120,22 @@ safeRegisterElement('template-builder', class extends HTMLElement { acceptFileTypes: this.dataset.acceptFileTypes }) - this.app.mount(this.appElem) + this.component = this.app.mount(this.appElem) this.appendChild(this.appElem) } + onSubmit = (e) => { + if (e.detail.success && e.detail?.formSubmission?.formElement?.id === 'submitters_form') { + e.detail.fetchResponse.response.json().then((data) => { + this.component.template.submitters = data.submitters + }) + } + } + disconnectedCallback () { + document.removeEventListener('turbo:submit-end', this.onSubmit) + this.app?.unmount() this.appElem?.remove() } diff --git a/app/javascript/elements/linked_input.js b/app/javascript/elements/linked_input.js new file mode 100644 index 00000000..0c0cfe7f --- /dev/null +++ b/app/javascript/elements/linked_input.js @@ -0,0 +1,31 @@ +export default class extends HTMLElement { + connectedCallback () { + if (this.target) { + this.input.value = this.target.value + + this.target.addEventListener('input', (e) => { + this.input.value = e.target.value + }) + + this.target.addEventListener('linked-input.update', (e) => { + this.input.value = e.target.value + }) + } + } + + get input () { + return this.querySelector('input') + } + + get target () { + if (this.dataset.targetId) { + const listItem = this.closest('[data-targets="dynamic-list.items"]') + + if (listItem) { + return listItem.querySelector(`#${this.dataset.targetId}`) + } else { + return document.getElementById(this.dataset.targetId) + } + } + } +} diff --git a/app/javascript/elements/submitter_autocomplete.js b/app/javascript/elements/submitter_autocomplete.js index 08db3808..5fc539bf 100644 --- a/app/javascript/elements/submitter_autocomplete.js +++ b/app/javascript/elements/submitter_autocomplete.js @@ -24,6 +24,7 @@ export default class extends HTMLElement { if (input && item[field]) { input.value = item[field] + input.dispatchEvent(new CustomEvent('linked-input.update', { bubbles: true })) } if (textarea && item[field]) { diff --git a/app/javascript/elements/toggle_attribute.js b/app/javascript/elements/toggle_attribute.js new file mode 100644 index 00000000..e9ee3075 --- /dev/null +++ b/app/javascript/elements/toggle_attribute.js @@ -0,0 +1,28 @@ +export default class extends HTMLElement { + connectedCallback () { + this.input.addEventListener('change', (event) => { + if (this.dataset.attribute) { + this.target[this.dataset.attribute] = event.target.checked + } + + if (this.dataset.className) { + this.target.classList.toggle(this.dataset.className, event.target.value !== this.dataset.value) + if (this.dataset.className === 'hidden' && this.target.tagName === 'INPUT') { + this.target.disabled = event.target.value !== this.dataset.value + } + } + + if (this.dataset.attribute === 'disabled') { + this.target.value = '' + } + }) + } + + get input () { + return this.querySelector('input[type="checkbox"]') || this.querySelector('select') + } + + get target () { + return document.getElementById(this.dataset.targetId) + } +} diff --git a/app/javascript/elements/turbo_modal.js b/app/javascript/elements/turbo_modal.js index 5009f60b..69c20549 100644 --- a/app/javascript/elements/turbo_modal.js +++ b/app/javascript/elements/turbo_modal.js @@ -22,7 +22,7 @@ export default actionable(class extends HTMLElement { } onSubmit = (e) => { - if (e.detail.success) { + if (e.detail.success && e.detail?.formSubmission?.formElement?.dataset?.closeOnSubmit !== 'false') { this.close() } } diff --git a/app/views/submissions/_detailed_form.html.erb b/app/views/submissions/_detailed_form.html.erb index f237becd..597dfb70 100644 --- a/app/views/submissions/_detailed_form.html.erb +++ b/app/views/submissions/_detailed_form.html.erb @@ -18,14 +18,20 @@ <% end %> - <%= tag.input type: 'text', name: 'submission[1][submitters][][name]', autocomplete: 'off', class: 'input input-sm input-bordered w-full', placeholder: 'Name', required: index.zero?, value: params[:selfsign] && index.zero? ? current_user.full_name : '', dir: 'auto' %> + "> + <%= tag.input type: 'text', name: 'submission[1][submitters][][name]', autocomplete: 'off', class: 'input input-sm input-bordered w-full', placeholder: 'Name', required: index.zero?, value: (params[:selfsign] && index.zero?) || item['is_requester'] ? current_user.full_name : '', dir: 'auto', id: "detailed_name_#{item['uuid']}" %> +
    - + "> + + - + "> + +
    diff --git a/app/views/submissions/_email_form.html.erb b/app/views/submissions/_email_form.html.erb index cb34187f..193e21dd 100644 --- a/app/views/submissions/_email_form.html.erb +++ b/app/views/submissions/_email_form.html.erb @@ -27,7 +27,9 @@ - <%= tag.input type: 'email', multiple: true, name: 'submission[1][submitters][][email]', autocomplete: 'off', class: 'input input-sm input-bordered w-full', placeholder: 'Email', required: index.zero?, value: params[:selfsign] && index.zero? ? current_user.email : '' %> + "> + <%= tag.input type: 'email', multiple: true, name: 'submission[1][submitters][][email]', autocomplete: 'off', class: 'input input-sm input-bordered w-full', placeholder: 'Email', required: index.zero?, value: item['email'].presence || ((params[:selfsign] && index.zero?) || item['is_requester'] ? current_user.email : ''), id: "email_#{item['uuid']}" %> + <% end %> diff --git a/app/views/submissions/_phone_form.html.erb b/app/views/submissions/_phone_form.html.erb index 1445cf47..25a2d11c 100644 --- a/app/views/submissions/_phone_form.html.erb +++ b/app/views/submissions/_phone_form.html.erb @@ -19,18 +19,24 @@ <% end %> - <%= tag.input type: 'tel', pattern: '^\+[0-9\s\-]+$', oninvalid: "this.value ? this.setCustomValidity('Use internatioanl format: +1xxx...') : ''", oninput: "this.setCustomValidity('')", name: 'submission[1][submitters][][phone]', autocomplete: 'off', class: 'input input-sm input-bordered w-full', placeholder: 'Phone', required: index.zero? %> + "> + <%= tag.input type: 'tel', pattern: '^\+[0-9\s\-]+$', oninvalid: "this.value ? this.setCustomValidity('Use internatioanl format: +1xxx...') : ''", oninput: "this.setCustomValidity('')", name: 'submission[1][submitters][][phone]', autocomplete: 'off', class: 'input input-sm input-bordered w-full', placeholder: 'Phone', required: index.zero?, id: "phone_phone_#{item['uuid']}" %> + <% if template.submitters.size > 1 %> - + "> + + <% end %> <% if template.submitters.size == 1 %>
    - + "> + +
    <% end %> diff --git a/app/views/templates_preferences/show.html.erb b/app/views/templates_preferences/show.html.erb index 2b897653..e3704ee3 100644 --- a/app/views/templates_preferences/show.html.erb +++ b/app/views/templates_preferences/show.html.erb @@ -1,6 +1,8 @@ -<%= render 'shared/turbo_modal_large', title: 'Preferences', close_after_submit: false do %> +<%= render 'shared/turbo_modal_large', title: 'Preferences' do %> <% show_api = Docuseal.multitenant? || current_account.testing? || !current_account.linked_account_account %> + <% show_recipients = @template.submitters.to_a.length > 1 %> <% options = [%w[General general]] %> + <% options << %w[Recipients recipients] if show_recipients %> <% options << ['API and Embedding', 'api'] if show_api %> <% if options.size > 1 %> @@ -8,7 +10,7 @@ <% options.each_with_index do |(label, value), index| %> <%= radio_button_tag 'option', value, value == 'general', class: 'peer hidden', data: { action: 'change:toggle-visible#trigger' } %> - @@ -17,7 +19,7 @@ <% end %>
    - <%= form_for @template, url: template_preferences_path(@template), method: :post, html: { autocomplete: 'off', class: 'mt-2' } do |f| %> + <%= form_for @template, url: template_preferences_path(@template), method: :post, html: { autocomplete: 'off', class: 'mt-2' }, data: { close_on_submit: false } do |f| %> <%= f.fields_for :preferences, Struct.new(:bcc_completed).new(@template.preferences['bcc_completed']) do |ff| %>
    @@ -46,7 +48,7 @@ Signature request email
    - <%= form_for @template, url: template_preferences_path(@template), method: :post, html: { autocomplete: 'off', class: 'mt-1' } do |f| %> + <%= form_for @template, url: template_preferences_path(@template), method: :post, html: { autocomplete: 'off', class: 'mt-1' }, data: { close_on_submit: false } do |f| %> <%= f.fields_for :preferences, Struct.new(:request_email_subject, :request_email_body).new(*(@template.preferences.values_at('request_email_subject', 'request_email_body').compact_blank.presence || AccountConfigs.find_or_initialize_for_key(current_account, AccountConfig::SUBMITTER_INVITATION_EMAIL_KEY).value.values_at('subject', 'body'))) do |ff| %>
    @@ -80,7 +82,7 @@ Documents copy email
    - <%= form_for @template, url: template_preferences_path(@template), method: :post, html: { autocomplete: 'off', class: 'mt-1' } do |f| %> + <%= form_for @template, url: template_preferences_path(@template), method: :post, html: { autocomplete: 'off', class: 'mt-1' }, data: { close_on_submit: false } do |f| %> <% configs = AccountConfigs.find_or_initialize_for_key(current_account, AccountConfig::SUBMITTER_DOCUMENTS_COPY_EMAIL_KEY).value %> <%= f.fields_for :preferences, Struct.new(:documents_copy_email_subject, :documents_copy_email_body, :documents_copy_email_enabled, :documents_copy_email_attach_audit).new(@template.preferences['documents_copy_email_subject'].presence || configs['subject'], @template.preferences['documents_copy_email_body'].presence || configs['body'], @template.preferences['documents_copy_email_enabled'], configs['attach_audit_log'] != false && @template.preferences['documents_copy_email_attach_audit'] != false) do |ff| %> @@ -127,7 +129,7 @@ Completed notification email
    - <%= form_for @template, url: template_preferences_path(@template), method: :post, html: { autocomplete: 'off', class: 'mt-1' } do |f| %> + <%= form_for @template, url: template_preferences_path(@template), method: :post, html: { autocomplete: 'off', class: 'mt-1' }, data: { close_on_submit: false } do |f| %> <% configs = AccountConfigs.find_or_initialize_for_key(current_account, AccountConfig::SUBMITTER_COMPLETED_EMAIL_KEY).value %> <%= f.fields_for :preferences, Struct.new(:completed_notification_email_subject, :completed_notification_email_body, :completed_notification_email_enabled, :completed_notification_email_attach_audit, :completed_notification_email_attach_documents).new(@template.preferences['completed_notification_email_subject'].presence || configs['subject'], @template.preferences['completed_notification_email_body'].presence || configs['body'], @template.preferences['completed_notification_email_enabled'], configs['attach_audit_log'] != false && @template.preferences['completed_notification_email_attach_audit'] != false, configs['attach_documents'] != false && @template.preferences['completed_notification_email_attach_documents'] != false) do |ff| %> @@ -176,6 +178,48 @@
    + <% if show_recipients %> + + <% end %> <% if show_api %> + [] + }, withSignatureId: { type: Boolean, required: false, @@ -739,6 +755,7 @@ export default { data () { return { isCompleted: false, + isInvited: false, isFormVisible: this.expand !== false, showFillAllRequiredFields: false, currentStep: 0, @@ -1119,7 +1136,7 @@ export default { const formData = new FormData(this.$refs.form) const isLastStep = this.currentStep === this.stepFields.length - 1 - if (isLastStep && !emptyRequiredField) { + if (isLastStep && !emptyRequiredField && !this.inviteSubmitters.length) { formData.append('completed', 'true') } diff --git a/app/javascript/submission_form/i18n.js b/app/javascript/submission_form/i18n.js index d5c65791..25411d6b 100644 --- a/app/javascript/submission_form/i18n.js +++ b/app/javascript/submission_form/i18n.js @@ -10,6 +10,8 @@ const en = { reviewed: 'Reviewed', other: 'Other', authored_by_me: 'Authored by me', + invite: 'Invite', + email: 'Email', approved_by: 'Approved by', reviewed_by: 'Reviewed by', authored_by: 'Authored by', @@ -85,6 +87,8 @@ const en = { } const es = { + invite: 'Invitar', + email: 'Correo electrónico', approved: 'Aprobado', reviewed: 'Revisado', other: 'Otro', @@ -170,6 +174,8 @@ const es = { } const it = { + invite: 'Invita', + email: 'Email', approved: 'Approvato', reviewed: 'Revisionato', other: 'Altro', @@ -255,6 +261,8 @@ const it = { } const de = { + invite: 'Einladen', + email: 'E-Mail', approved: 'Genehmigt', reviewed: 'Überprüft', other: 'Andere', @@ -340,6 +348,8 @@ const de = { } const fr = { + invite: 'Inviter', + email: 'Courriel', approved: 'Approuvé', reviewed: 'Révisé', other: 'Autre', @@ -425,6 +435,8 @@ const fr = { } const pl = { + invite: 'Zaproś', + email: 'E-mail', approved: 'Zaakceptowany', reviewed: 'Przejrzany', other: 'Inny', @@ -510,6 +522,8 @@ const pl = { } const uk = { + invite: 'Запросити', + email: 'Електронна пошта', approved: 'Затверджено', reviewed: 'Переглянуто', other: 'Інше', @@ -595,6 +609,8 @@ const uk = { } const cs = { + invite: 'Pozvat', + email: 'E-mail', approved: 'Schváleno', reviewed: 'Zkontrolováno', other: 'Jiné', @@ -680,6 +696,8 @@ const cs = { } const pt = { + invite: 'Convidar', + email: 'E-mail', approved: 'Aprovado', reviewed: 'Revisado', other: 'Outro', @@ -765,6 +783,8 @@ const pt = { } const he = { + invite: 'הזמן', + email: 'דוא"ל', approved: 'מאושר', reviewed: 'נסקר', other: 'אחר', @@ -851,6 +871,8 @@ const he = { } const nl = { + invite: 'Uitnodigen', + email: 'E-mail', approved: 'Goedgekeurd', reviewed: 'Beoordeeld', other: 'Anders', @@ -937,6 +959,8 @@ const nl = { } const ar = { + invite: 'دعوة', + email: 'البريد الإلكتروني', approved: 'موافق عليه', reviewed: 'تمت مراجعته', other: 'آخر', @@ -1022,6 +1046,8 @@ const ar = { } const ko = { + invite: '초대하기', + email: '이메일', approved: '승인됨', reviewed: '검토됨', other: '기타', diff --git a/app/javascript/submission_form/invite_form.vue b/app/javascript/submission_form/invite_form.vue new file mode 100644 index 00000000..c32cbd53 --- /dev/null +++ b/app/javascript/submission_form/invite_form.vue @@ -0,0 +1,117 @@ + + + diff --git a/app/models/submission_event.rb b/app/models/submission_event.rb index 8cfa11c1..1a4777b7 100644 --- a/app/models/submission_event.rb +++ b/app/models/submission_event.rb @@ -47,6 +47,7 @@ class SubmissionEvent < ApplicationRecord phone_verified: 'phone_verified', start_form: 'start_form', view_form: 'view_form', + invite_party: 'invite_party', complete_form: 'complete_form', decline_form: 'decline_form', api_complete_form: 'api_complete_form' diff --git a/app/views/start_form/completed.html.erb b/app/views/start_form/completed.html.erb index 36110eb2..e3727074 100644 --- a/app/views/start_form/completed.html.erb +++ b/app/views/start_form/completed.html.erb @@ -24,7 +24,7 @@ <%= button_to button_title(title: t('send_copy_to_email'), disabled_with: t('sending'), icon: svg_icon('mail_forward', class: 'w-6 h-6')), send_submission_email_index_path, params: { submitter_slug: @submitter.slug }, class: 'base-button w-full' %> <% end %> - <% if @template.submitters.to_a.size == 1 && %w[api embed].exclude?(@submitter.submission.source) && @submitter.account.account_configs.find_or_initialize_by(key: AccountConfig::ALLOW_TO_RESUBMIT).value != false %> + <% if Templates.filter_undefined_submitters(@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 %> <%= button_to button_title(title: t('resubmit'), disabled_with: t('resubmit'), icon: svg_icon('reload', class: 'w-6 h-6')), start_form_path(@template.slug), params: { submitter: { email: params[:email] }, resubmit: @submitter.slug }, method: :put, class: 'white-button w-full' %> diff --git a/app/views/submissions/_detailed_form.html.erb b/app/views/submissions/_detailed_form.html.erb index 597dfb70..a4b4b26f 100644 --- a/app/views/submissions/_detailed_form.html.erb +++ b/app/views/submissions/_detailed_form.html.erb @@ -1,17 +1,18 @@ <%= form_for '', url: template_submissions_path(template), html: { class: 'space-y-4', autocomplete: 'off' }, data: { turbo_frame: :_top } do |f| %> + <% submitters = template.submitters.reject { |e| e['invite_by_uuid'].present? } %>
    -
    - <% template.submitters.each_with_index do |item, index| %> +
    + <% submitters.each_with_index do |item, index| %> - <% if template.submitters.size > 1 %> + <% if submitters.size > 1 %> @@ -22,7 +23,7 @@ <%= tag.input type: 'text', name: 'submission[1][submitters][][name]', autocomplete: 'off', class: 'input input-sm input-bordered w-full', placeholder: 'Name', required: index.zero?, value: (params[:selfsign] && index.zero?) || item['is_requester'] ? current_user.full_name : '', dir: 'auto', id: "detailed_name_#{item['uuid']}" %> -
    +
    "> diff --git a/app/views/submissions/_email_form.html.erb b/app/views/submissions/_email_form.html.erb index 193e21dd..cd58298c 100644 --- a/app/views/submissions/_email_form.html.erb +++ b/app/views/submissions/_email_form.html.erb @@ -1,5 +1,6 @@ <%= form_for '', url: template_submissions_path(template), html: { class: 'space-y-4', autocomplete: 'off' }, data: { turbo_frame: :_top } do |f| %> - <% if template.submitters.size == 1 %> + <% submitters = template.submitters.reject { |e| e['invite_by_uuid'].present? } %> + <% if submitters.size == 1 %> @@ -20,7 +21,7 @@
    - <% template.submitters.each_with_index do |item, index| %> + <% submitters.each_with_index do |item, index| %>