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 %>