diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 8bbd5052..c2c317e5 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -1,9 +1,17 @@ name: Build Docker Images on: - push: - tags: - - "*.*.*" + workflow_dispatch: + inputs: + version: + description: Version + type: string + required: true + image: + description: QEMU image + type: string + required: false + default: tonistiigi/binfmt:latest jobs: build: @@ -16,23 +24,23 @@ jobs: with: submodules: recursive - - - name: Docker meta + - name: Docker meta id: meta uses: docker/metadata-action@v4 with: - images: | - docuseal/docuseal - tags: | - type=semver,pattern={{version}} + images: docuseal/docuseal + tags: type=semver,pattern={{version}} + - name: Set up QEMU uses: docker/setup-qemu-action@v3 + with: + image: ${{ inputs.image }} - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Create .version file - run: echo ${{ github.ref_name }} > .version + run: echo ${{ inputs.version }} > .version - name: Login to Docker Hub uses: docker/login-action@v3 diff --git a/Gemfile.lock b/Gemfile.lock index b55adaa1..df7255dd 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -319,7 +319,7 @@ GEM method_source (1.1.0) mini_magick (4.13.2) mini_mime (1.1.5) - mini_portile2 (2.8.8) + mini_portile2 (2.8.9) minitest (5.25.4) msgpack (1.7.5) multi_json (1.15.0) @@ -338,18 +338,18 @@ GEM net-smtp (0.5.0) net-protocol nio4r (2.7.4) - nokogiri (1.18.8) + nokogiri (1.18.9) mini_portile2 (~> 2.8.2) racc (~> 1.4) - nokogiri (1.18.8-aarch64-linux-gnu) + nokogiri (1.18.9-aarch64-linux-gnu) racc (~> 1.4) - nokogiri (1.18.8-aarch64-linux-musl) + nokogiri (1.18.9-aarch64-linux-musl) racc (~> 1.4) - nokogiri (1.18.8-arm64-darwin) + nokogiri (1.18.9-arm64-darwin) racc (~> 1.4) - nokogiri (1.18.8-x86_64-linux-gnu) + nokogiri (1.18.9-x86_64-linux-gnu) racc (~> 1.4) - nokogiri (1.18.8-x86_64-linux-musl) + nokogiri (1.18.9-x86_64-linux-musl) racc (~> 1.4) oj (3.16.8) bigdecimal (>= 3.0) @@ -541,7 +541,7 @@ GEM stringio (3.1.2) strip_attributes (1.14.1) activemodel (>= 3.0, < 9.0) - thor (1.3.2) + thor (1.4.0) timeout (0.4.3) trailblazer-option (0.1.2) turbo-rails (2.0.11) diff --git a/app/controllers/api/templates_controller.rb b/app/controllers/api/templates_controller.rb index 0ccc5b39..88a606f4 100644 --- a/app/controllers/api/templates_controller.rb +++ b/app/controllers/api/templates_controller.rb @@ -109,7 +109,7 @@ module Api submitters: [%i[name uuid is_requester invite_by_uuid optional_invite_by_uuid linked_to_uuid email]], fields: [[:uuid, :submitter_uuid, :name, :type, :required, :readonly, :default_value, - :title, :description, + :title, :description, :prefillable, { preferences: {}, conditions: [%i[field_uuid value action operation]], options: [%i[value uuid]], diff --git a/app/controllers/submissions_controller.rb b/app/controllers/submissions_controller.rb index 9d4de051..4f31322c 100644 --- a/app/controllers/submissions_controller.rb +++ b/app/controllers/submissions_controller.rb @@ -32,10 +32,7 @@ class SubmissionsController < ApplicationController def create save_template_message(@template, params) if params[:save_message] == '1' - if params[:is_custom_message] != '1' - params.delete(:subject) - params.delete(:body) - end + [params.delete(:subject), params.delete(:body)] if params[:is_custom_message] != '1' submissions = if params[:emails].present? @@ -46,11 +43,16 @@ class SubmissionsController < ApplicationController emails: params[:emails], params: params.merge('send_completed_email' => true)) else + submissions_attrs = submissions_params[:submission].to_h.values + + submissions_attrs, = + Submissions::NormalizeParamUtils.normalize_submissions_params!(submissions_attrs, @template) + Submissions.create_from_submitters(template: @template, user: current_user, source: :invite, submitters_order: params[:preserve_order] == '1' ? 'preserved' : 'random', - submissions_attrs: submissions_params[:submission].to_h.values, + submissions_attrs:, params: params.merge('send_completed_email' => true)) end @@ -62,9 +64,8 @@ class SubmissionsController < ApplicationController redirect_to template_path(@template), notice: I18n.t('new_recipients_have_been_added') rescue Submissions::CreateFromSubmitters::BaseError => e - render turbo_stream: turbo_stream.replace(:submitters_error, - partial: 'submissions/error', - locals: { error: e.message }), + render turbo_stream: turbo_stream.replace(:submitters_error, partial: 'submissions/error', + locals: { error: e.message }), status: :unprocessable_entity end @@ -95,7 +96,7 @@ class SubmissionsController < ApplicationController end def submissions_params - params.permit(submission: { submitters: [%i[uuid email phone name]] }) + params.permit(submission: { submitters: [:uuid, :email, :phone, :name, { values: {} }] }) end def load_template diff --git a/app/controllers/templates_controller.rb b/app/controllers/templates_controller.rb index fba51b90..73e68e85 100644 --- a/app/controllers/templates_controller.rb +++ b/app/controllers/templates_controller.rb @@ -120,7 +120,7 @@ class TemplatesController < ApplicationController submitters: [%i[name uuid is_requester linked_to_uuid invite_by_uuid optional_invite_by_uuid email]], fields: [[:uuid, :submitter_uuid, :name, :type, :required, :readonly, :default_value, - :title, :description, + :title, :description, :prefillable, { preferences: {}, conditions: [%i[field_uuid value action operation]], options: [%i[value uuid]], diff --git a/app/controllers/templates_preferences_controller.rb b/app/controllers/templates_preferences_controller.rb index 31ac7190..cbee0993 100644 --- a/app/controllers/templates_preferences_controller.rb +++ b/app/controllers/templates_preferences_controller.rb @@ -27,7 +27,7 @@ class TemplatesPreferencesController < ApplicationController completed_redirect_url validate_unique_submitters require_all_submitters submitters_order require_phone_2fa default_expire_at_duration shared_link_2fa - default_expire_at + default_expire_at request_email_enabled 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/controllers/templates_prefillable_fields_controller.rb b/app/controllers/templates_prefillable_fields_controller.rb new file mode 100644 index 00000000..6202ca5c --- /dev/null +++ b/app/controllers/templates_prefillable_fields_controller.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +class TemplatesPrefillableFieldsController < ApplicationController + PREFILLABLE_FIELD_TYPES = %w[text number cells date checkbox select radio phone].freeze + + load_and_authorize_resource :template + + def create + authorize!(:update, @template) + + field = @template.fields.find { |f| f['uuid'] == params[:field_uuid] } + + if params[:prefillable] == 'false' + field.delete('prefillable') + field.delete('readonly') + elsif params[:prefillable] == 'true' + field['prefillable'] = true + field['readonly'] = true + end + + @template.save! + + render turbo_stream: turbo_stream.replace(:prefillable_fields_list, partial: 'list', + locals: { template: @template }) + end +end diff --git a/app/javascript/application.js b/app/javascript/application.js index af363801..dda88f7a 100644 --- a/app/javascript/application.js +++ b/app/javascript/application.js @@ -37,6 +37,7 @@ import AppTour from './elements/app_tour' import DashboardDropzone from './elements/dashboard_dropzone' import RequiredCheckboxGroup from './elements/required_checkbox_group' import PageContainer from './elements/page_container' +import EmailEditor from './elements/email_editor' import * as TurboInstantClick from './lib/turbo_instant_click' @@ -109,6 +110,7 @@ safeRegisterElement('dashboard-dropzone', DashboardDropzone) safeRegisterElement('check-on-click', CheckOnClick) safeRegisterElement('required-checkbox-group', RequiredCheckboxGroup) safeRegisterElement('page-container', PageContainer) +safeRegisterElement('email-editor', EmailEditor) safeRegisterElement('template-builder', class extends HTMLElement { connectedCallback () { diff --git a/app/javascript/elements/email_editor.js b/app/javascript/elements/email_editor.js new file mode 100644 index 00000000..38f0d3b4 --- /dev/null +++ b/app/javascript/elements/email_editor.js @@ -0,0 +1,131 @@ +import { target, targetable } from '@github/catalyst/lib/targetable' + +let loaderPromise = null + +function loadCodeMirror () { + if (!loaderPromise) { + loaderPromise = Promise.all([ + import(/* webpackChunkName: "email-editor" */ '@codemirror/view'), + import(/* webpackChunkName: "email-editor" */ '@codemirror/commands'), + import(/* webpackChunkName: "email-editor" */ '@codemirror/language'), + import(/* webpackChunkName: "email-editor" */ '@codemirror/lang-html'), + import(/* webpackChunkName: "email-editor" */ '@specious/htmlflow') + ]).then(([view, commands, language, html, htmlflow]) => { + return { + minimalSetup: [ + commands.history(), + language.syntaxHighlighting(language.defaultHighlightStyle, { fallback: true }), + view.keymap.of([...commands.defaultKeymap, ...commands.historyKeymap]) + ], + EditorView: view.EditorView, + html: html.html, + htmlflow: htmlflow.default || htmlflow + } + }) + } + + return loaderPromise +} + +export default targetable(class extends HTMLElement { + static [target.static] = [ + 'codeViewTab', + 'previewViewTab', + 'editorContainer', + 'previewIframe' + ] + + connectedCallback () { + this.mount() + + if (this.input.value) { + this.showPreviewView() + } else { + this.showCodeView() + } + + this.previewViewTab.addEventListener('click', this.showPreviewView) + this.codeViewTab.addEventListener('click', this.showCodeView) + } + + showCodeView = () => { + this.editorView.dispatch({ + changes: { from: 0, to: this.editorView.state.doc.length, insert: this.input.value } + }) + + this.previewViewTab.classList.remove('tab-active', 'tab-bordered') + this.previewViewTab.classList.add('pb-[3px]') + this.codeViewTab.classList.remove('pb-[3px]') + this.codeViewTab.classList.add('tab-active', 'tab-bordered') + this.editorContainer.classList.remove('hidden') + this.previewIframe.classList.add('hidden') + } + + showPreviewView = () => { + this.previewIframe.srcdoc = this.input.value + + this.codeViewTab.classList.remove('tab-active', 'tab-bordered') + this.codeViewTab.classList.add('pb-[3px]') + this.previewViewTab.classList.remove('pb-[3px]') + this.previewViewTab.classList.add('tab-active', 'tab-bordered') + this.editorContainer.classList.add('hidden') + this.previewIframe.classList.remove('hidden') + } + + async mount () { + this.input = this.querySelector('input[type="hidden"]') + this.input.style.display = 'none' + + const { EditorView, minimalSetup, html, htmlflow } = await loadCodeMirror() + + this.editorView = new EditorView({ + doc: this.input.value, + parent: this.editorContainer, + extensions: [ + html(), + minimalSetup, + EditorView.lineWrapping, + EditorView.updateListener.of(update => { + if (update.docChanged) this.input.value = update.state.doc.toString() + }), + EditorView.theme({ + '&': { + backgroundColor: 'white', + color: 'black', + fontSize: '14px', + fontFamily: 'monospace' + }, + '&.cm-focused': { + outline: 'none' + }, + '&.cm-editor': { + borderRadius: '0.375rem', + border: 'none' + }, + '.cm-gutters': { + display: 'none' + } + }) + ] + }) + + this.previewIframe.srcdoc = this.editorView.state.doc.toString() + + this.previewIframe.onload = () => { + const previewIframeDoc = this.previewIframe.contentDocument + + if (previewIframeDoc.body) { + previewIframeDoc.body.contentEditable = true + } + + const contentDocument = this.previewIframe.contentDocument || this.previewIframe.contentWindow.document + + contentDocument.body.addEventListener('input', async () => { + const html = contentDocument.documentElement.outerHTML.replace(' contenteditable="true"', '') + const prettifiedHtml = await htmlflow(html) + + this.input.value = prettifiedHtml + }) + } + } +}) diff --git a/app/javascript/elements/toggle_visible.js b/app/javascript/elements/toggle_visible.js index d93fdaf5..a796b9c6 100644 --- a/app/javascript/elements/toggle_visible.js +++ b/app/javascript/elements/toggle_visible.js @@ -7,5 +7,9 @@ export default actionable(class extends HTMLElement { elementIds.forEach((elementId) => { document.getElementById(elementId).classList.toggle('hidden', (event.target.dataset.toggleId || event.target.value) !== elementId) }) + + if (this.dataset.focusId) { + document.getElementById(this.dataset.focusId)?.focus() + } } }) diff --git a/app/javascript/submission_form/dropzone.vue b/app/javascript/submission_form/dropzone.vue index 29ec6798..89e01b3b 100644 --- a/app/javascript/submission_form/dropzone.vue +++ b/app/javascript/submission_form/dropzone.vue @@ -112,11 +112,23 @@ export default { onSelectFiles (e) { e.preventDefault() - this.uploadFiles(this.$refs.input.files).then(() => { - if (this.$refs.input) { - this.$refs.input.value = '' + const files = Array.from(this.$refs.input.files).filter((f) => { + if (this.accept === 'image/*') { + return f.type.startsWith('image') + } else { + return true } }) + + if (this.accept === 'image/*' && !files.length) { + alert(this.t('please_upload_an_image_file')) + } else { + this.uploadFiles(files).then(() => { + if (this.$refs.input) { + this.$refs.input.value = '' + } + }) + } }, async uploadFiles (files) { this.isLoading = true diff --git a/app/javascript/template_builder/area.vue b/app/javascript/template_builder/area.vue index 9622b503..ad8ab7d5 100644 --- a/app/javascript/template_builder/area.vue +++ b/app/javascript/template_builder/area.vue @@ -141,6 +141,7 @@ :with-required="false" :with-areas="false" :with-signature-id="withSignatureId" + :with-prefillable="withPrefillable" @click-formula="isShowFormulaModal = true" @click-font="isShowFontModal = true" @click-description="isShowDescriptionModal = true" @@ -353,6 +354,11 @@ export default { required: false, default: null }, + withPrefillable: { + type: Boolean, + required: false, + default: false + }, defaultSubmitters: { type: Array, required: false, diff --git a/app/javascript/template_builder/builder.vue b/app/javascript/template_builder/builder.vue index ef0748f6..cd2339f4 100644 --- a/app/javascript/template_builder/builder.vue +++ b/app/javascript/template_builder/builder.vue @@ -331,6 +331,7 @@ :default-fields="[...defaultRequiredFields, ...defaultFields]" :allow-draw="!onlyDefinedFields || drawField" :with-signature-id="withSignatureId" + :with-prefillable="withPrefillable" :data-document-uuid="document.uuid" :default-submitters="defaultSubmitters" :drag-field-placeholder="fieldsDragFieldRef.value || dragField" @@ -438,6 +439,7 @@ :field-types="fieldTypes" :with-sticky-submitters="withStickySubmitters" :with-signature-id="withSignatureId" + :with-prefillable="withPrefillable" :only-defined-fields="onlyDefinedFields" :editable="editable" :show-tour-start-form="showTourStartForm" @@ -543,7 +545,6 @@ export default { withPayment: this.withPayment, isPaymentConnected: this.isPaymentConnected, withFormula: this.withFormula, - withSignatureId: this.withSignatureId, withConditions: this.withConditions, isInlineSize: this.isInlineSize, defaultDrawFieldType: this.defaultDrawFieldType, @@ -802,6 +803,13 @@ export default { language () { return this.locale.split('-')[0].toLowerCase() }, + withPrefillable () { + if (this.template.fields) { + return this.template.fields.some((f) => f.prefillable) + } else { + return false + } + }, isInlineSize () { return CSS.supports('container-type: size') }, diff --git a/app/javascript/template_builder/document.vue b/app/javascript/template_builder/document.vue index abba5052..e512fb2c 100644 --- a/app/javascript/template_builder/document.vue +++ b/app/javascript/template_builder/document.vue @@ -11,6 +11,7 @@ :areas="areasIndex[index]" :allow-draw="allowDraw" :with-signature-id="withSignatureId" + :with-prefillable="withPrefillable" :is-drag="isDrag" :with-field-placeholder="withFieldPlaceholder" :default-fields="defaultFields" @@ -72,6 +73,11 @@ export default { required: false, default: null }, + withPrefillable: { + type: Boolean, + required: false, + default: false + }, drawFieldType: { type: String, required: false, diff --git a/app/javascript/template_builder/field.vue b/app/javascript/template_builder/field.vue index f9e1c0e9..b12a33d9 100644 --- a/app/javascript/template_builder/field.vue +++ b/app/javascript/template_builder/field.vue @@ -126,6 +126,7 @@ :default-field="defaultField" :editable="editable" :with-signature-id="withSignatureId" + :with-prefillable="withPrefillable" :background-color="dropdownBgColor" @click-formula="isShowFormulaModal = true" @click-font="isShowFontModal = true" @@ -308,6 +309,11 @@ export default { required: false, default: null }, + withPrefillable: { + type: Boolean, + required: false, + default: false + }, withOptions: { type: Boolean, required: false, diff --git a/app/javascript/template_builder/field_settings.vue b/app/javascript/template_builder/field_settings.vue index b78c6b4e..22821f4c 100644 --- a/app/javascript/template_builder/field_settings.vue +++ b/app/javascript/template_builder/field_settings.vue @@ -406,6 +406,21 @@ {{ t('read_only') }} +
  • + +

  • - <% if can?(:manage, account_config) && (can?(:manage, :personalization_advanced) || !Docuseal.multitenant?) %> + <% if can?(:manage, account_config) && (can?(:manage, :personalization_advanced) || account_config.persisted?) %> <%= form_for account_config, url: account_configs_path, method: :post do |f| %> <%= f.hidden_field :key %>
    diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index df578657..60c6222e 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -18,7 +18,6 @@ <% end %> <%= stylesheet_pack_tag 'application', media: 'all' %> - <%= render 'shared/posthog' if ENV['POSTHOG_TOKEN'] %> <%= render 'shared/plausible' if !signed_in? && ENV['PLAUSIBLE_DOMAIN'] %> diff --git a/app/views/layouts/form.html.erb b/app/views/layouts/form.html.erb index 64b4a537..78163f73 100644 --- a/app/views/layouts/form.html.erb +++ b/app/views/layouts/form.html.erb @@ -12,7 +12,6 @@ <%= javascript_pack_tag 'form', defer: true %> <% end %> <%= stylesheet_pack_tag 'form', media: 'all' %> - <%= render 'shared/posthog' if ENV['POSTHOG_TOKEN'] %> <%= yield %> diff --git a/app/views/layouts/plain.html.erb b/app/views/layouts/plain.html.erb index 2510c1dd..e4a1b6f9 100644 --- a/app/views/layouts/plain.html.erb +++ b/app/views/layouts/plain.html.erb @@ -12,7 +12,6 @@ <%= javascript_pack_tag 'application', defer: true %> <% end %> <%= stylesheet_pack_tag 'application', media: 'all' %> - <%= render 'shared/posthog' if ENV['POSTHOG_TOKEN'] %> <% if flash.present? %><%= render 'shared/flash' %><% end %> diff --git a/app/views/personalization_settings/_documents_copy_email_form.html.erb b/app/views/personalization_settings/_documents_copy_email_form.html.erb index c5af6c49..e4f777d4 100644 --- a/app/views/personalization_settings/_documents_copy_email_form.html.erb +++ b/app/views/personalization_settings/_documents_copy_email_form.html.erb @@ -13,17 +13,7 @@ <%= ff.label :subject, t('subject'), class: 'label' %> <%= ff.text_field :subject, required: true, class: 'base-input', dir: 'auto' %>
    -
    -
    - <%= ff.label :body, t('body'), class: 'label' %> - - <%= svg_icon('info_circle', class: 'w-4 h-4') %> - -
    - - <%= ff.text_area :body, required: true, class: 'base-input w-full py-2', dir: 'auto' %> - -
    + <%= render 'personalization_settings/email_body_field', ff:, config: f.object %> <% if can?(:manage, :reply_to) || can?(:manage, :personalization_advanced) %>
    <%= ff.label :reply_to, t('reply_to'), class: 'label' %> diff --git a/app/views/personalization_settings/_email_body_field.html.erb b/app/views/personalization_settings/_email_body_field.html.erb new file mode 100644 index 00000000..264a4240 --- /dev/null +++ b/app/views/personalization_settings/_email_body_field.html.erb @@ -0,0 +1,11 @@ +
    +
    + <%= ff.label :body, t('body'), class: 'label' %> + + <%= svg_icon('info_circle', class: 'w-4 h-4') %> + +
    + + <%= ff.text_area :body, required: true, class: 'base-input w-full !rounded-2xl py-2', dir: 'auto' %> + +
    diff --git a/app/views/personalization_settings/_signature_request_email_form.html.erb b/app/views/personalization_settings/_signature_request_email_form.html.erb index ea529dfe..1b4588ea 100644 --- a/app/views/personalization_settings/_signature_request_email_form.html.erb +++ b/app/views/personalization_settings/_signature_request_email_form.html.erb @@ -13,17 +13,7 @@ <%= ff.label :subject, t('subject'), class: 'label' %> <%= ff.text_field :subject, required: true, class: 'base-input', dir: 'auto' %>
    -
    -
    - <%= ff.label :body, t('body'), class: 'label' %> - - <%= svg_icon('info_circle', class: 'w-4 h-4') %> - -
    - - <%= ff.text_area :body, required: true, class: 'base-input w-full py-2', dir: 'auto' %> - -
    + <%= render 'personalization_settings/email_body_field', ff:, config: f.object %> <% end %>
    <%= f.button button_title(title: t('save'), disabled_with: t('saving')), class: 'base-button' %> diff --git a/app/views/personalization_settings/_submitter_completed_email_form.html.erb b/app/views/personalization_settings/_submitter_completed_email_form.html.erb index 28c562dd..11803829 100644 --- a/app/views/personalization_settings/_submitter_completed_email_form.html.erb +++ b/app/views/personalization_settings/_submitter_completed_email_form.html.erb @@ -18,17 +18,7 @@
    <%= ff.text_field :subject, required: true, class: 'base-input', dir: 'auto' %> -
    -
    - <%= ff.label :body, t('body'), class: 'label' %> - - <%= svg_icon('info_circle', class: 'w-4 h-4') %> - -
    - - <%= ff.text_area :body, required: true, class: 'base-input w-full py-2', dir: 'auto' %> - -
    + <%= render 'personalization_settings/email_body_field', ff:, config: f.object %>
    <%= t('attach_documents') %> diff --git a/app/views/shared/_posthog.html.erb b/app/views/shared/_posthog.html.erb deleted file mode 100644 index bb563284..00000000 --- a/app/views/shared/_posthog.html.erb +++ /dev/null @@ -1,4 +0,0 @@ - diff --git a/app/views/submissions/_detailed_form.html.erb b/app/views/submissions/_detailed_form.html.erb index e5c48f28..dd4f9d99 100644 --- a/app/views/submissions/_detailed_form.html.erb +++ b/app/views/submissions/_detailed_form.html.erb @@ -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? } %> @@ -11,6 +12,7 @@
    <% submitters.each_with_index do |item, index| %> + <% prefillable_fields = local_assigns[:prefillable_fields].to_a.select { |f| f['submitter_uuid'] == item['uuid'] } %> <% if submitters.size > 1 %> <% end %> - - "> - <%= 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']}" %> - - -
    - - "> - " value="<%= item['email'].presence || ((params[:selfsign] && index.zero?) || item['is_requester'] ? current_user.email : '') %>" id="detailed_email_<%= item['uuid'] %>"> + <% if prefillable_fields.blank? %> + + "> + <%= 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']}" %> - - "> - <%= 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 %> +
    + + "> + " value="<%= item['email'].presence || ((params[:selfsign] && index.zero?) || item['is_requester'] ? current_user.email : '') %>" id="detailed_email_<%= item['uuid'] %>"> + + + <% has_phone_field = true %> + + "> + <%= 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 %> + + +
    + <% end %> + <% if prefillable_fields.present? %> + + "> + -
    + <% if local_assigns[:require_phone_2fa] == true || prefillable_fields.any? { |f| f['type'] == 'phone' } %> + <% has_phone_field = true %> + + "> + <%= 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 %> + + + <% end %> + <% prefillable_fields.each do |field| %> + <% if field['type'] == 'checkbox' %> + + <% 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 %>
    <% end %>
    - <% if params[:selfsign].blank? %> + <% if params[:selfsign].blank? && local_assigns[:prefillable_fields].blank? %> <%= svg_icon('user_plus', class: 'w-4 h-4 stroke-2') %> <%= t('add_new') %> @@ -51,7 +85,9 @@
    <%= 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 %>
    <%= f.button button_title(title: t('add_recipients')), class: 'base-button' %> diff --git a/app/views/submissions/_send_email.html.erb b/app/views/submissions/_send_email.html.erb index 785eac90..d7f357c0 100644 --- a/app/views/submissions/_send_email.html.erb +++ b/app/views/submissions/_send_email.html.erb @@ -4,7 +4,7 @@ <% can_send_emails = Accounts.can_send_emails?(current_account) %>
    <%= 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 %> <%= local_assigns[:resend_email] ? t('re_send_email') : t('send_email') %> <% end %>
    diff --git a/app/views/submissions/new.html.erb b/app/views/submissions/new.html.erb index b9874d75..ccea6b4b 100644 --- a/app/views/submissions/new.html.erb +++ b/app/views/submissions/new.html.erb @@ -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 %>
    <% options.each_with_index do |(label, value), index| %>
    - <%= 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' } %> @@ -14,7 +16,7 @@
    - <% unless require_phone_2fa %> + <% unless only_detailed %>
    <%= render 'email_form', template: @template %>
    @@ -22,8 +24,8 @@ <%= render 'phone_form', template: @template %>
    <% end %> -
    - <%= render 'detailed_form', template: @template, require_phone_2fa: %> +
    + <%= render 'detailed_form', template: @template, require_phone_2fa:, prefillable_fields: %>