prefillable fields

This commit is contained in:
Pete Matsyburka
2025-07-27 10:27:12 +03:00
committed by GitHub
parent 2974e6a64f
commit b2b97a313a
21 changed files with 208 additions and 35 deletions
+51 -15
View File
@@ -1,3 +1,4 @@
<% has_phone_field = false %>
<%= 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? || e['optional_invite_by_uuid'].present? } %>
<dynamic-list class="space-y-4">
@@ -11,6 +12,7 @@
</div>
<div class="grid <%= 'md:grid-cols-2' if submitters.size > 1 %> gap-4">
<% submitters.each_with_index do |item, index| %>
<% prefillable_fields = local_assigns[:prefillable_fields].to_a.select { |f| f['submitter_uuid'] == item['uuid'] } %>
<submitter-item class="form-control">
<% if submitters.size > 1 %>
<label class="label pt-0 pb-1">
@@ -18,30 +20,62 @@
</label>
<% end %>
<input type="hidden" name="submission[1][submitters][][uuid]" value="<%= item['uuid'] %>">
<submitters-autocomplete data-field="name">
<linked-input data-target-id="<%= "detailed_name_#{item['linked_to_uuid']}" if item['linked_to_uuid'].present? %>">
<%= tag.input type: 'text', name: 'submission[1][submitters][][name]', autocomplete: 'off', class: 'base-input !h-10 w-full', placeholder: t('name'), required: index.zero? || template.preferences['require_all_submitters'], value: item['email'].present? ? current_account.submitters.accessible_by(current_ability).where.not(name: nil).order(id: :desc).find_by(email: item['email'])&.name : ((params[:selfsign] && index.zero?) || item['is_requester'] ? current_user.full_name : ''), dir: 'auto', id: "detailed_name_#{item['uuid']}" %>
</linked-input>
</submitters-autocomplete>
<div class="grid <%= 'md:grid-cols-2 gap-1' if submitters.size == 1 %>">
<% if prefillable_fields.blank? %>
<submitters-autocomplete data-field="name">
<linked-input data-target-id="<%= "detailed_name_#{item['linked_to_uuid']}" if item['linked_to_uuid'].present? %>">
<%= tag.input type: 'text', name: 'submission[1][submitters][][name]', autocomplete: 'off', class: 'base-input !h-10 w-full', placeholder: t('name'), required: index.zero? || template.preferences['require_all_submitters'], value: item['email'].present? ? current_account.submitters.accessible_by(current_ability).where.not(name: nil).order(id: :desc).find_by(email: item['email'])&.name : ((params[:selfsign] && index.zero?) || item['is_requester'] ? current_user.full_name : ''), dir: 'auto', id: "detailed_name_#{item['uuid']}" %>
</linked-input>
</submitters-autocomplete>
<div class="grid <%= 'md:grid-cols-2 gap-1' if submitters.size == 1 %>">
<submitters-autocomplete data-field="email">
<linked-input data-target-id="<%= "detailed_email_#{item['linked_to_uuid']}" if item['linked_to_uuid'].present? %>">
<input type="email" multiple name="submission[1][submitters][][email]" autocomplete="off" class="base-input !h-10 mt-1.5 w-full" placeholder="<%= "#{t('email')} (#{t('optional')})" %>" value="<%= item['email'].presence || ((params[:selfsign] && index.zero?) || item['is_requester'] ? current_user.email : '') %>" id="detailed_email_<%= item['uuid'] %>">
</linked-input>
</submitters-autocomplete>
<% has_phone_field = true %>
<submitters-autocomplete data-field="phone">
<linked-input data-target-id="<%= "detailed_phone_#{item['linked_to_uuid']}" if item['linked_to_uuid'].present? %>">
<%= 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 %>
</linked-input>
</submitters-autocomplete>
</div>
<% end %>
<% if prefillable_fields.present? %>
<submitters-autocomplete data-field="email">
<linked-input data-target-id="<%= "detailed_email_#{item['linked_to_uuid']}" if item['linked_to_uuid'].present? %>">
<input type="email" multiple name="submission[1][submitters][][email]" autocomplete="off" class="base-input !h-10 mt-1.5 w-full" placeholder="<%= "#{t('email')} (#{t('optional')})" %>" value="<%= item['email'].presence || ((params[:selfsign] && index.zero?) || item['is_requester'] ? current_user.email : '') %>" id="detailed_email_<%= item['uuid'] %>">
<input type="email" multiple name="submission[1][submitters][][email]" autocomplete="off" class="base-input !h-10 w-full" placeholder="<%= t('email') %>" value="<%= item['email'].presence || ((params[:selfsign] && index.zero?) || item['is_requester'] ? current_user.email : '') %>" id="detailed_email_<%= item['uuid'] %>" required>
</linked-input>
</submitters-autocomplete>
<submitters-autocomplete data-field="phone">
<linked-input data-target-id="<%= "detailed_phone_#{item['linked_to_uuid']}" if item['linked_to_uuid'].present? %>">
<%= 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 %>
</linked-input>
</submitters-autocomplete>
</div>
<% if local_assigns[:require_phone_2fa] == true || prefillable_fields.any? { |f| f['type'] == 'phone' } %>
<% has_phone_field = true %>
<submitters-autocomplete data-field="phone">
<linked-input data-target-id="<%= "detailed_phone_#{item['linked_to_uuid']}" if item['linked_to_uuid'].present? %>">
<%= 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: t(:phone), id: "detailed_phone_#{item['uuid']}", required: true %>
</linked-input>
</submitters-autocomplete>
<% end %>
<% prefillable_fields.each do |field| %>
<% if field['type'] == 'checkbox' %>
<label for="detailed_field_<%= field['uuid'] %>" class="flex items-center justify-between mt-1.5 pl-3 pr-2.5 h-10 border border-base-content/20 rounded-full cursor-pointer transition-colors bg-white">
<span class="text-base select-none px-1"> <%= field['title'].presence || field['name'] %></span>
<%= tag.input type: 'checkbox', name: "submission[1][submitters][][values][#{field['uuid']}]", id: "detailed_field_#{field['uuid']}", class: 'toggle toggle-sm', style: 'width: 38px; --handleoffset: 17px', checked: field['default_value'].present? && (field['default_value'] == true || field['default_value'].to_s == '1' || field['default_value'].to_s.downcase == 'true'), required: field['required'], value: 'true' %>
</label>
<% elsif field['type'] == 'select' || field['type'] == 'radio' %>
<%= select_tag "submission[1][submitters][][values][#{field['uuid']}]", options_for_select(field['options'].pluck('value'), field['default_value']), prompt: t(:select), id: "detailed_field_#{field['uuid']}", class: 'select select-sm base-input !h-10 mt-1.5 ', required: field['required'] %>
<% elsif field['type'] == 'date' %>
<%= tag.input type: field['type'], name: "submission[1][submitters][][values][#{field['uuid']}]", autocomplete: 'off', class: 'base-input !h-10 mt-1.5 w-full border rounded p-3', placeholder: (field['required'] ? field['title'].presence || field['name'] : "#{field['title'].presence || field['name']} (#{t('optional')})"), value: field['default_value'], id: "detailed_field_#{field['uuid']}", required: field['required'] %>
<% elsif field['type'] != 'phone' %>
<%= tag.input type: field['type'], name: "submission[1][submitters][][values][#{field['uuid']}]", autocomplete: 'off', class: 'base-input !h-10 mt-1.5 w-full border rounded p-3', placeholder: (field['required'] ? field['title'].presence || field['name'] : "#{field['title'].presence || field['name']} (#{t('optional')})"), value: field['default_value'], id: "detailed_field_#{field['uuid']}", required: field['required'] %>
<% end %>
<% end %>
<% end %>
</submitter-item>
<% end %>
</div>
</div>
</div>
</div>
<% if params[:selfsign].blank? %>
<% if params[:selfsign].blank? && local_assigns[:prefillable_fields].blank? %>
<a href="#" class="btn btn-primary btn-sm w-full flex items-center justify-center" data-action="click:dynamic-list#addItem">
<%= svg_icon('user_plus', class: 'w-4 h-4 stroke-2') %>
<span><%= t('add_new') %></span>
@@ -51,7 +85,9 @@
<div>
<%= render('submitters_order', f:, template:) if Accounts.can_send_emails?(current_account) %>
<%= render 'send_email', f:, template: %>
<%= render 'send_sms', f: %>
<% if has_phone_field %>
<%= render 'send_sms', f: %>
<% end %>
</div>
<div class="form-control">
<%= f.button button_title(title: t('add_recipients')), class: 'base-button' %>
+1 -1
View File
@@ -4,7 +4,7 @@
<% can_send_emails = Accounts.can_send_emails?(current_account) %>
<div class="flex justify-between items-center">
<%= f.label :send_email, for: uuid = SecureRandom.uuid, class: 'flex items-center cursor-pointer' do %>
<%= f.check_box :send_email, id: uuid, class: 'base-checkbox', disabled: !can_send_emails || local_assigns[:disable_email], checked: can_send_emails && !local_assigns.key?(:resend_email) && !local_assigns[:disable_email] %>
<%= f.check_box :send_email, id: uuid, class: 'base-checkbox', disabled: !can_send_emails || local_assigns[:disable_email], checked: can_send_emails && !local_assigns.key?(:resend_email) && !local_assigns[:disable_email] && template&.preferences&.dig('request_email_enabled') != false %>
<span class="label"><%= local_assigns[:resend_email] ? t('re_send_email') : t('send_email') %></span>
<% end %>
<div>
+7 -5
View File
@@ -1,11 +1,13 @@
<% require_phone_2fa = @template.preferences['require_phone_2fa'] == true %>
<% prefillable_fields = @template.fields.select { |f| f['prefillable'] } %>
<% only_detailed = require_phone_2fa || prefillable_fields.present? %>
<%= render 'shared/turbo_modal_large', title: params[:selfsign] ? t('add_recipients') : t('add_new_recipients') do %>
<% 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 = [only_detailed ? nil : [t('via_email'), 'email'], only_detailed ? nil : [t('via_phone'), 'phone'], [t('detailed'), 'detailed'], [t('upload_list'), 'list']].compact %>
<toggle-visible data-element-ids="<%= options.map(&:last).to_json %>" class="relative text-center px-2 mt-4 block">
<div class="flex justify-center">
<% options.each_with_index do |(label, value), index| %>
<div>
<%= radio_button_tag 'option', value, value == (require_phone_2fa ? 'detailed' : 'email'), class: 'peer hidden', data: { action: 'change:toggle-visible#trigger' } %>
<%= radio_button_tag 'option', value, value == (only_detailed ? 'detailed' : 'email'), class: 'peer hidden', data: { action: 'change:toggle-visible#trigger' } %>
<label for="option_<%= value %>" class="block bg-base-200 md:min-w-[112px] text-sm font-semibold whitespace-nowrap py-1.5 px-4 peer-checked:bg-base-300 <%= 'hidden sm:inline-block' if value == 'list' %> <%= 'rounded-l-3xl' if index.zero? %> <%= 'rounded-r-3xl sm:rounded-r-none' if value == 'detailed' %> <%= 'rounded-r-3xl' if index == options.size - 1 %>">
<%= label %>
</label>
@@ -14,7 +16,7 @@
</div>
</toggle-visible>
<div class="px-5 mb-5 mt-4">
<% unless require_phone_2fa %>
<% unless only_detailed %>
<div id="email">
<%= render 'email_form', template: @template %>
</div>
@@ -22,8 +24,8 @@
<%= render 'phone_form', template: @template %>
</div>
<% end %>
<div id="detailed" class="<%= 'hidden' unless require_phone_2fa %>">
<%= render 'detailed_form', template: @template, require_phone_2fa: %>
<div id="detailed" class="<%= 'hidden' unless only_detailed %>">
<%= render 'detailed_form', template: @template, require_phone_2fa:, prefillable_fields: %>
</div>
<div id="list" class="hidden">
<%= render 'list_form', template: @template %>
+10 -1
View File
@@ -59,6 +59,7 @@
<%= t('form_preferences') %>
</div>
<div class="collapse-content">
<%= render 'templates_prefillable_fields/form', template: @template %>
<%= form_for @template, url: template_preferences_path(@template), method: :post, html: { autocomplete: 'off', class: 'mt-1' }, data: { close_on_submit: false } do |f| %>
<toggle-on-submit data-element-id="form_saved_alert"></toggle-on-submit>
<% configs = AccountConfigs.find_or_initialize_for_key(current_account, AccountConfig::SUBMITTER_COMPLETED_EMAIL_KEY).value %>
@@ -68,7 +69,7 @@
<%= ff.url_field :completed_redirect_url, required: false, class: 'base-input', dir: 'auto' %>
</div>
<%= ff.fields_for :completed_message, ff.object.completed_message do |fff| %>
<div class="form-control mb-2 border-t mt-4">
<div class="form-control mb-2 mt-4">
<%= fff.label :body, t('completion_message'), class: 'label' %>
<autoresize-textarea>
<%= fff.text_area :body, required: false, class: 'base-input w-full py-2', dir: 'auto' %>
@@ -162,6 +163,14 @@
<% end %>
</div>
<% end %>
<%= f.fields_for :preferences, Struct.new(:request_email_enabled).new(@template.preferences['request_email_enabled']) do |ff| %>
<div class="flex items-center justify-between py-2.5 px-1 mb-2">
<span>
<%= 'Send signature request email' %>
</span>
<%= ff.check_box :request_email_enabled, { checked: ff.object.request_email_enabled != false, class: 'toggle', onchange: 'this.form.requestSubmit()' }, 'true', 'false' %>
</div>
<% end %>
<div class="form-control pt-2">
<%= f.button button_title(title: t('save'), disabled_with: t('saving')), class: 'base-button' %>
<div class="flex justify-center">
@@ -0,0 +1,16 @@
<% select_fields = template.fields.filter_map { |f| [f['name'], f['uuid']] if f['name'].present? && f['type'].in?(TemplatesPrefillableFieldsController::PREFILLABLE_FIELD_TYPES) } %>
<% if select_fields.present? %>
<div class="border-b pb-4 mb-4">
<%= form_for '', url: template_prefillable_fields_path(template), method: :post, data: { close_on_submit: false } do |f| %>
<div class="form-control">
<%= f.hidden_field :prefillable, value: 'true' %>
<%= f.label :field_uuid, t(:invite_form_fields), class: 'label' %>
<div class="join w-full">
<%= select_tag :field_uuid, options_for_select(select_fields), prompt: t(:select_field), class: 'base-select w-full join-item', dir: 'auto', required: true %>
<%= f.button button_title(title: t('add'), disabled_with: t('add')), class: 'base-button join-item !px-6' %>
</div>
</div>
<% end %>
<%= render partial: 'templates_prefillable_fields/list', locals: { template: } %>
</div>
<% end %>
@@ -0,0 +1,6 @@
<div id="prefillable_fields_list" class="flex gap-1 px-0.5 mt-2 flex-wrap">
<% template.fields.each do |f| %>
<% next unless f['prefillable'] %>
<%= button_to button_title(title: f['name'].presence || f['type'].capitalize, disabled_with: f['name'].presence || f['type'].capitalize, icon: svg_icon('x', class: 'w-4 h-4'), icon_disabled: svg_icon('loader', class: 'w-4 h-4 animate-spin')), template_prefillable_fields_path(template), params: { field_uuid: f['uuid'], prefillable: 'false' }, class: 'badge badge-lg badge-primary space-x-1 pr-3 pl-2', form: { data: { close_on_submit: false } } %>
<% end %>
</div>