From 73841f751c45a276e2539398cf594e2cb87f7cb3 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Sun, 4 Aug 2024 23:41:32 +0300 Subject: [PATCH 01/36] fix icons --- app/views/setup/index.html.erb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/setup/index.html.erb b/app/views/setup/index.html.erb index 3ae323f3..3414f1a7 100644 --- a/app/views/setup/index.html.erb +++ b/app/views/setup/index.html.erb @@ -38,10 +38,10 @@ <%= ff.password_field :password, required: true, placeholder: '************', class: 'base-input w-full pr-10', data: { target: 'password-input.passwordInput' } %> From 6b1c3bba198920ea9b7dcf201c0bd869f5c19dc3 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Mon, 5 Aug 2024 10:45:05 +0300 Subject: [PATCH 02/36] add testing footer --- .../generate_result_attachments.rb | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/lib/submissions/generate_result_attachments.rb b/lib/submissions/generate_result_attachments.rb index a2af8634..eae717a1 100644 --- a/lib/submissions/generate_result_attachments.rb +++ b/lib/submissions/generate_result_attachments.rb @@ -21,6 +21,8 @@ module Submissions A4_SIZE = [595, 842].freeze SUPPORTED_IMAGE_TYPES = ['image/png', 'image/jpeg'].freeze + TESTING_FOOTER = 'Testing Document - NOT LEGALLY BINDING' + MISSING_GLYPH_REPLACE = { '▪' => '-', '✔️' => 'V', @@ -100,18 +102,31 @@ module Submissions pdfs_index = build_pdfs_index(submitter, flatten: is_flatten) - if with_signature_id + if with_signature_id || submitter.account.testing? pdfs_index.each_value do |pdf| next if pdf.trailer.info[:DocumentID].present? - pdf.trailer.info[:DocumentID] = Digest::MD5.hexdigest(submitter.submission.slug).upcase + document_id = Digest::MD5.hexdigest(submitter.submission.slug).upcase + + pdf.trailer.info[:DocumentID] = document_id pdf.pages.each do |page| font_size = (([page.box.width, page.box.height].min / A4_SIZE[0].to_f) * 9).to_i cnv = page.canvas(type: :overlay) cnv.font(FONT_NAME, size: font_size) - cnv.text("Document ID: #{Digest::MD5.hexdigest(submitter.submission.slug).upcase}", - at: [2, 4]) + + text = + if submitter.account.testing? + if with_signature_id + "#{TESTING_FOOTER} | ID: #{document_id}" + else + TESTING_FOOTER + end + else + "Document ID: #{document_id}" + end + + cnv.text(text, at: [2, 4]) end end end From 478167ea4f89b34623d7aa4d1b2f2d7680a1b53a Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Mon, 5 Aug 2024 13:30:04 +0300 Subject: [PATCH 03/36] add webhook secret --- app/controllers/webhook_secret_controller.rb | 29 +++++++++++++++++++ ...send_form_completed_webhook_request_job.rb | 14 ++++++--- .../send_form_started_webhook_request_job.rb | 5 +++- .../send_form_viewed_webhook_request_job.rb | 5 +++- ...submission_archived_webhook_request_job.rb | 6 +++- ..._submission_created_webhook_request_job.rb | 6 +++- ...nd_template_created_webhook_request_job.rb | 6 +++- ...nd_template_updated_webhook_request_job.rb | 6 +++- app/models/encrypted_config.rb | 3 +- app/views/webhook_secret/index.html.erb | 19 ++++++++++++ app/views/webhook_settings/show.html.erb | 5 +++- config/routes.rb | 1 + 12 files changed, 93 insertions(+), 12 deletions(-) create mode 100644 app/controllers/webhook_secret_controller.rb create mode 100644 app/views/webhook_secret/index.html.erb diff --git a/app/controllers/webhook_secret_controller.rb b/app/controllers/webhook_secret_controller.rb new file mode 100644 index 00000000..12c1edae --- /dev/null +++ b/app/controllers/webhook_secret_controller.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +class WebhookSecretController < ApplicationController + before_action :load_encrypted_config + authorize_resource :encrypted_config, parent: false + + def index; end + + def create + @encrypted_config.assign_attributes(value: { + encrypted_config_params[:key] => encrypted_config_params[:value] + }.compact_blank) + + @encrypted_config.value.present? ? @encrypted_config.save! : @encrypted_config.delete + + redirect_back(fallback_location: settings_webhooks_path, notice: 'Webhook Secret has been saved.') + end + + private + + def load_encrypted_config + @encrypted_config = + current_account.encrypted_configs.find_or_initialize_by(key: EncryptedConfig::WEBHOOK_SECRET_KEY) + end + + def encrypted_config_params + params.require(:encrypted_config).permit(value: %i[key value]).fetch(:value, {}) + end +end diff --git a/app/jobs/send_form_completed_webhook_request_job.rb b/app/jobs/send_form_completed_webhook_request_job.rb index 1012c60a..62c6e1d5 100644 --- a/app/jobs/send_form_completed_webhook_request_job.rb +++ b/app/jobs/send_form_completed_webhook_request_job.rb @@ -14,7 +14,7 @@ class SendFormCompletedWebhookRequestJob attempt = params['attempt'].to_i - url = load_url(submitter, params) + url, secret = load_url_and_secret(submitter, params) return if url.blank? @@ -29,6 +29,7 @@ class SendFormCompletedWebhookRequestJob timestamp: Time.current, data: Submitters::SerializeForWebhook.call(submitter) }.to_json, + **secret.to_h, 'Content-Type' => 'application/json', 'User-Agent' => USER_AGENT) rescue Faraday::Error @@ -45,9 +46,11 @@ class SendFormCompletedWebhookRequestJob end end - def load_url(submitter, params) + def load_url_and_secret(submitter, params) if params['encrypted_config_id'] - url = EncryptedConfig.find(params['encrypted_config_id']).value + config = EncryptedConfig.find(params['encrypted_config_id']) + + url = config.value return if url.blank? @@ -55,7 +58,10 @@ class SendFormCompletedWebhookRequestJob return if preferences['form.completed'] == false - url + secret = EncryptedConfig.find_or_initialize_by(account_id: config.account_id, + key: EncryptedConfig::WEBHOOK_SECRET_KEY)&.value.to_h + + [url, secret] elsif params['webhook_url_id'] webhook_url = submitter.account.webhook_urls.find(params['webhook_url_id']) diff --git a/app/jobs/send_form_started_webhook_request_job.rb b/app/jobs/send_form_started_webhook_request_job.rb index 9bf8f6c8..747e9338 100644 --- a/app/jobs/send_form_started_webhook_request_job.rb +++ b/app/jobs/send_form_started_webhook_request_job.rb @@ -13,7 +13,8 @@ class SendFormStartedWebhookRequestJob submitter = Submitter.find(params['submitter_id']) attempt = params['attempt'].to_i - url = Accounts.load_webhook_url(submitter.submission.account) + config = Accounts.load_webhook_config(submitter.submission.account) + url = config&.value.presence return if url.blank? @@ -30,6 +31,8 @@ class SendFormStartedWebhookRequestJob timestamp: Time.current, data: Submitters::SerializeForWebhook.call(submitter) }.to_json, + **EncryptedConfig.find_or_initialize_by(account_id: config.account_id, + key: EncryptedConfig::WEBHOOK_SECRET_KEY)&.value.to_h, 'Content-Type' => 'application/json', 'User-Agent' => USER_AGENT) rescue Faraday::Error diff --git a/app/jobs/send_form_viewed_webhook_request_job.rb b/app/jobs/send_form_viewed_webhook_request_job.rb index 9cf6cc54..391064e3 100644 --- a/app/jobs/send_form_viewed_webhook_request_job.rb +++ b/app/jobs/send_form_viewed_webhook_request_job.rb @@ -13,7 +13,8 @@ class SendFormViewedWebhookRequestJob submitter = Submitter.find(params['submitter_id']) attempt = params['attempt'].to_i - url = Accounts.load_webhook_url(submitter.submission.account) + config = Accounts.load_webhook_config(submitter.submission.account) + url = config&.value.presence return if url.blank? @@ -30,6 +31,8 @@ class SendFormViewedWebhookRequestJob timestamp: Time.current, data: Submitters::SerializeForWebhook.call(submitter) }.to_json, + **EncryptedConfig.find_or_initialize_by(account_id: config.account_id, + key: EncryptedConfig::WEBHOOK_SECRET_KEY)&.value.to_h, 'Content-Type' => 'application/json', 'User-Agent' => USER_AGENT) rescue Faraday::Error diff --git a/app/jobs/send_submission_archived_webhook_request_job.rb b/app/jobs/send_submission_archived_webhook_request_job.rb index 76273cee..ff62419e 100644 --- a/app/jobs/send_submission_archived_webhook_request_job.rb +++ b/app/jobs/send_submission_archived_webhook_request_job.rb @@ -13,7 +13,9 @@ class SendSubmissionArchivedWebhookRequestJob submission = Submission.find(params['submission_id']) attempt = params['attempt'].to_i - url = Accounts.load_webhook_url(submission.account) + + config = Accounts.load_webhook_config(submission.account) + url = config&.value.presence return if url.blank? @@ -28,6 +30,8 @@ class SendSubmissionArchivedWebhookRequestJob timestamp: Time.current, data: submission.as_json(only: %i[id archived_at]) }.to_json, + **EncryptedConfig.find_or_initialize_by(account_id: config.account_id, + key: EncryptedConfig::WEBHOOK_SECRET_KEY)&.value.to_h, 'Content-Type' => 'application/json', 'User-Agent' => USER_AGENT) rescue Faraday::Error diff --git a/app/jobs/send_submission_created_webhook_request_job.rb b/app/jobs/send_submission_created_webhook_request_job.rb index e5ec7070..aeb3f18f 100644 --- a/app/jobs/send_submission_created_webhook_request_job.rb +++ b/app/jobs/send_submission_created_webhook_request_job.rb @@ -13,7 +13,9 @@ class SendSubmissionCreatedWebhookRequestJob submission = Submission.find(params['submission_id']) attempt = params['attempt'].to_i - url = Accounts.load_webhook_url(submission.account) + + config = Accounts.load_webhook_config(submission.account) + url = config&.value.presence return if url.blank? @@ -28,6 +30,8 @@ class SendSubmissionCreatedWebhookRequestJob timestamp: Time.current, data: Submissions::SerializeForApi.call(submission) }.to_json, + **EncryptedConfig.find_or_initialize_by(account_id: config.account_id, + key: EncryptedConfig::WEBHOOK_SECRET_KEY)&.value.to_h, 'Content-Type' => 'application/json', 'User-Agent' => USER_AGENT) rescue Faraday::Error diff --git a/app/jobs/send_template_created_webhook_request_job.rb b/app/jobs/send_template_created_webhook_request_job.rb index ed643450..e2d7ac71 100644 --- a/app/jobs/send_template_created_webhook_request_job.rb +++ b/app/jobs/send_template_created_webhook_request_job.rb @@ -13,7 +13,9 @@ class SendTemplateCreatedWebhookRequestJob template = Template.find(params['template_id']) attempt = params['attempt'].to_i - url = Accounts.load_webhook_url(template.account) + + config = Accounts.load_webhook_config(template.account) + url = config&.value.presence return if url.blank? @@ -28,6 +30,8 @@ class SendTemplateCreatedWebhookRequestJob timestamp: Time.current, data: Templates::SerializeForApi.call(template) }.to_json, + **EncryptedConfig.find_or_initialize_by(account_id: config.account_id, + key: EncryptedConfig::WEBHOOK_SECRET_KEY)&.value.to_h, 'Content-Type' => 'application/json', 'User-Agent' => USER_AGENT) rescue Faraday::Error diff --git a/app/jobs/send_template_updated_webhook_request_job.rb b/app/jobs/send_template_updated_webhook_request_job.rb index 638ba196..8d969eb5 100644 --- a/app/jobs/send_template_updated_webhook_request_job.rb +++ b/app/jobs/send_template_updated_webhook_request_job.rb @@ -13,7 +13,9 @@ class SendTemplateUpdatedWebhookRequestJob template = Template.find(params['template_id']) attempt = params['attempt'].to_i - url = Accounts.load_webhook_url(template.account) + + config = Accounts.load_webhook_config(template.account) + url = config&.value.presence return if url.blank? @@ -28,6 +30,8 @@ class SendTemplateUpdatedWebhookRequestJob timestamp: Time.current, data: Templates::SerializeForApi.call(template) }.to_json, + **EncryptedConfig.find_or_initialize_by(account_id: config.account_id, + key: EncryptedConfig::WEBHOOK_SECRET_KEY)&.value.to_h, 'Content-Type' => 'application/json', 'User-Agent' => USER_AGENT) rescue Faraday::Error diff --git a/app/models/encrypted_config.rb b/app/models/encrypted_config.rb index f3c36441..5d2422af 100644 --- a/app/models/encrypted_config.rb +++ b/app/models/encrypted_config.rb @@ -27,7 +27,8 @@ class EncryptedConfig < ApplicationRecord ESIGN_CERTS_KEY = 'esign_certs', TIMESTAMP_SERVER_URL_KEY = 'timestamp_server_url', APP_URL_KEY = 'app_url', - WEBHOOK_URL_KEY = 'webhook_url' + WEBHOOK_URL_KEY = 'webhook_url', + WEBHOOK_SECRET_KEY = 'webhook_secret' ].freeze belongs_to :account diff --git a/app/views/webhook_secret/index.html.erb b/app/views/webhook_secret/index.html.erb new file mode 100644 index 00000000..5c7ad9ed --- /dev/null +++ b/app/views/webhook_secret/index.html.erb @@ -0,0 +1,19 @@ +<%= render 'shared/turbo_modal', title: 'Webhook Secret' do %> + <%= form_for @encrypted_config, url: webhook_secret_index_path, method: :post, html: { class: 'space-y-4' }, data: { turbo_frame: :_top } do |f| %> +
+ <%= f.fields_for :value, Struct.new(:key, :value).new(*@encrypted_config.value.to_a.first) do |ff| %> +
+ <%= ff.label :key, class: 'label' %> + <%= ff.text_field :key, class: 'base-input', placeholder: 'X-Example-Header' %> +
+
+ <%= ff.label :value, class: 'label' %> + <%= ff.text_field :value, class: 'base-input' %> +
+ <% end %> +
+
+ <%= f.button button_title, class: 'base-button' %> +
+ <% end %> +<% end %> diff --git a/app/views/webhook_settings/show.html.erb b/app/views/webhook_settings/show.html.erb index c9b1e3c1..b05dbe0c 100644 --- a/app/views/webhook_settings/show.html.erb +++ b/app/views/webhook_settings/show.html.erb @@ -9,9 +9,12 @@
<%= form_for @encrypted_config, url: settings_webhooks_path, method: :post, html: { autocomplete: 'off' } do |f| %> <%= f.label :value, 'Webhook URL', class: 'text-sm font-semibold' %> -
+
<%= f.url_field :value, class: 'input font-mono input-bordered w-full', placeholder: 'https://example.com/hook' %> <%= f.button button_title(title: 'Save', disabled_with: 'Saving'), class: 'base-button w-full md:w-32' %> + + Add Secret +
<% end %> <% preference = current_account.account_configs.find_by(key: AccountConfig::WEBHOOK_PREFERENCES_KEY)&.value || {} %> diff --git a/config/routes.rb b/config/routes.rb index 7874959f..2de99d5f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -75,6 +75,7 @@ Rails.application.routes.draw do resources :submitters_autocomplete, only: %i[index] resources :template_folders_autocomplete, only: %i[index] resources :webhook_preferences, only: %i[create] + resources :webhook_secret, only: %i[index create] resource :templates_upload, only: %i[create] authenticated do resource :templates_upload, only: %i[show], path: 'new' From 91d44a3e3199a8b7f8c2323b7d70b05d11a1c825 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Mon, 5 Aug 2024 19:38:47 +0300 Subject: [PATCH 04/36] adjust date format --- app/javascript/application.js | 1 + app/javascript/template_builder/builder.vue | 26 ++++++++++++++++++--- app/javascript/template_builder/field.vue | 4 ++-- app/views/templates/edit.html.erb | 2 +- 4 files changed, 27 insertions(+), 6 deletions(-) diff --git a/app/javascript/application.js b/app/javascript/application.js index 2eaba9c4..55bc69b1 100644 --- a/app/javascript/application.js +++ b/app/javascript/application.js @@ -97,6 +97,7 @@ safeRegisterElement('template-builder', class extends HTMLElement { this.app = createApp(TemplateBuilder, { template: reactive(JSON.parse(this.dataset.template)), backgroundColor: '#faf7f5', + locale: this.dataset.locale, withPhone: this.dataset.withPhone === 'true', withLogo: this.dataset.withLogo !== 'false', editable: this.dataset.editable !== 'false', diff --git a/app/javascript/template_builder/builder.vue b/app/javascript/template_builder/builder.vue index 6d782b66..343f0673 100644 --- a/app/javascript/template_builder/builder.vue +++ b/app/javascript/template_builder/builder.vue @@ -435,6 +435,7 @@ export default { save: this.save, t: this.t, currencies: this.currencies, + locale: this.locale, baseFetch: this.baseFetch, fieldTypes: this.fieldTypes, backgroundColor: this.backgroundColor, @@ -468,6 +469,11 @@ export default { required: false, default: '' }, + locale: { + type: String, + required: false, + default: '' + }, editable: { type: Boolean, required: false, @@ -649,6 +655,20 @@ export default { computed: { selectedAreaRef: () => ref(), fieldsDragFieldRef: () => ref(), + defaultDateFormat () { + const isUsBrowser = Intl.DateTimeFormat().resolvedOptions().locale.endsWith('-US') + const isUsTimezone = new Intl.DateTimeFormat('en-US', { timeZoneName: 'short' }).format(new Date()).match(/\s(?:CST|CDT|PST|PDT|EST|EDT)$/) + + return this.localeDateFormats[this.locale] || ((isUsBrowser || isUsTimezone) ? 'MM/DD/YYYY' : 'DD/MM/YYYY') + }, + localeDateFormats () { + return { + 'de-DE': 'DD.MM.YYYY', + 'fr-FR': 'DD/MM/YYYY', + 'it-IT': 'DD/MM/YYYY', + 'es-ES': 'DD/MM/YYYY' + } + }, fieldAreasIndex () { const areas = {} @@ -768,7 +788,7 @@ export default { if (type === 'date') { field.preferences = { - format: Intl.DateTimeFormat().resolvedOptions().locale.endsWith('-US') || new Intl.DateTimeFormat('en-US', { timeZoneName: 'short' }).format(new Date()).match(/\s(?:CST|CDT|PST|PDT|EST|EDT)$/) ? 'MM/DD/YYYY' : 'DD/MM/YYYY' + format: this.defaultDateFormat } } @@ -801,7 +821,7 @@ export default { if (type === 'date') { field.preferences = { - format: Intl.DateTimeFormat().resolvedOptions().locale.endsWith('-US') || new Intl.DateTimeFormat('en-US', { timeZoneName: 'short' }).format(new Date()).match(/\s(?:CST|CDT|PST|PDT|EST|EDT)$/) ? 'MM/DD/YYYY' : 'DD/MM/YYYY' + format: this.defaultDateFormat } } @@ -1138,7 +1158,7 @@ export default { if (field.type === 'date') { field.preferences = { - format: Intl.DateTimeFormat().resolvedOptions().locale.endsWith('-US') || new Intl.DateTimeFormat('en-US', { timeZoneName: 'short' }).format(new Date()).match(/\s(?:CST|CDT|PST|PDT|EST|EDT)$/) ? 'MM/DD/YYYY' : 'DD/MM/YYYY' + format: this.defaultDateFormat } } } diff --git a/app/javascript/template_builder/field.vue b/app/javascript/template_builder/field.vue index 3cbde2da..94ba721c 100644 --- a/app/javascript/template_builder/field.vue +++ b/app/javascript/template_builder/field.vue @@ -281,7 +281,7 @@ export default { IconMathFunction, FieldType }, - inject: ['template', 'save', 'backgroundColor', 'selectedAreaRef', 't'], + inject: ['template', 'save', 'backgroundColor', 'selectedAreaRef', 't', 'locale'], props: { field: { type: Object, @@ -341,7 +341,7 @@ export default { if (this.field.type === 'date') { this.field.preferences.format ||= - (Intl.DateTimeFormat().resolvedOptions().locale.endsWith('-US') || new Intl.DateTimeFormat('en-US', { timeZoneName: 'short' }).format(new Date()).match(/\s(?:CST|CDT|PST|PDT|EST|EDT)$/) ? 'MM/DD/YYYY' : 'DD/MM/YYYY') + ({ 'de-DE': 'DD.MM.YYYY' }[this.locale] || ((Intl.DateTimeFormat().resolvedOptions().locale.endsWith('-US') || new Intl.DateTimeFormat('en-US', { timeZoneName: 'short' }).format(new Date()).match(/\s(?:CST|CDT|PST|PDT|EST|EDT)$/)) ? 'MM/DD/YYYY' : 'DD/MM/YYYY')) } }, methods: { diff --git a/app/views/templates/edit.html.erb b/app/views/templates/edit.html.erb index 32d18f89..c95e9f31 100644 --- a/app/views/templates/edit.html.erb +++ b/app/views/templates/edit.html.erb @@ -1 +1 @@ - + From 94d5ab4ace08dd4826e78da389aa3d2c9e51f2c8 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Tue, 6 Aug 2024 00:19:33 +0300 Subject: [PATCH 05/36] add reason --- app/controllers/account_configs_controller.rb | 3 +- app/javascript/form.js | 1 + app/javascript/submission_form/area.vue | 7 +- app/javascript/submission_form/areas.vue | 1 + app/javascript/submission_form/form.vue | 8 ++ app/javascript/submission_form/i18n.js | 104 ++++++++++++++++++ .../submission_form/signature_step.vue | 70 +++++++++++- app/models/account_config.rb | 1 + app/views/accounts/show.html.erb | 12 ++ app/views/submissions/_value.html.erb | 2 +- .../submit_form/_submission_form.html.erb | 2 +- app/views/submit_form/show.html.erb | 3 +- .../generate_result_attachments.rb | 6 +- lib/submitters/form_configs.rb | 3 + lib/submitters/submit_values.rb | 26 +++++ 15 files changed, 241 insertions(+), 8 deletions(-) diff --git a/app/controllers/account_configs_controller.rb b/app/controllers/account_configs_controller.rb index de3e1826..0b67f714 100644 --- a/app/controllers/account_configs_controller.rb +++ b/app/controllers/account_configs_controller.rb @@ -14,7 +14,8 @@ class AccountConfigsController < ApplicationController AccountConfig::DOWNLOAD_LINKS_AUTH_KEY, AccountConfig::FORCE_SSO_AUTH_KEY, AccountConfig::FLATTEN_RESULT_PDF_KEY, - AccountConfig::WITH_SIGNATURE_ID + AccountConfig::WITH_SIGNATURE_ID, + AccountConfig::REQUIRE_SIGNING_REASON_KEY ].freeze InvalidKey = Class.new(StandardError) diff --git a/app/javascript/form.js b/app/javascript/form.js index bc8d5e7e..3ee6f373 100644 --- a/app/javascript/form.js +++ b/app/javascript/form.js @@ -23,6 +23,7 @@ safeRegisterElement('submission-form', class extends HTMLElement { dryRun: this.dataset.dryRun === 'true', expand: ['true', 'false'].includes(this.dataset.expand) ? this.dataset.expand === 'true' : null, withSignatureId: this.dataset.withSignatureId === 'true', + requireSigningReason: this.dataset.requireSigningReason === 'true', withConfetti: this.dataset.withConfetti !== 'false', withDisclosure: this.dataset.withDisclosure === 'true', withTypedSignature: this.dataset.withTypedSignature !== 'false', diff --git a/app/javascript/submission_form/area.vue b/app/javascript/submission_form/area.vue index 814eb949..e7596862 100644 --- a/app/javascript/submission_form/area.vue +++ b/app/javascript/submission_form/area.vue @@ -75,7 +75,7 @@ ID: {{ signature.uuid }}
- {{ t('reason') }}: {{ t('digitally_signed_by') }} {{ submitter.name }} + {{ t('reason') }}: {{ values[field.preferences?.reason_field_uuid] || t('digitally_signed_by') }} {{ submitter.name }} @@ -258,6 +258,11 @@ export default { required: false, default: '' }, + values: { + type: Object, + required: false, + default: () => ({}) + }, isActive: { type: Boolean, required: false, diff --git a/app/javascript/submission_form/areas.vue b/app/javascript/submission_form/areas.vue index 6e68efd0..8c8cbcd3 100644 --- a/app/javascript/submission_form/areas.vue +++ b/app/javascript/submission_form/areas.vue @@ -18,6 +18,7 @@ + + +
{ if (this.$refs.canvas) { diff --git a/app/models/account_config.rb b/app/models/account_config.rb index fb154902..affb7cc6 100644 --- a/app/models/account_config.rb +++ b/app/models/account_config.rb @@ -39,6 +39,7 @@ class AccountConfig < ApplicationRecord FORCE_SSO_AUTH_KEY = 'force_sso_auth' FLATTEN_RESULT_PDF_KEY = 'flatten_result_pdf' WITH_SIGNATURE_ID = 'with_signature_id' + REQUIRE_SIGNING_REASON_KEY = 'require_signing_reason' DEFAULT_VALUES = { SUBMITTER_INVITATION_EMAIL_KEY => { diff --git a/app/views/accounts/show.html.erb b/app/views/accounts/show.html.erb index 0dc79e36..446c1993 100644 --- a/app/views/accounts/show.html.erb +++ b/app/views/accounts/show.html.erb @@ -63,6 +63,18 @@
<% end %> <% end %> + <% account_config = AccountConfig.find_or_initialize_by(account: current_account, key: AccountConfig::REQUIRE_SIGNING_REASON_KEY) %> + <% if can?(:manage, account_config) %> + <%= form_for account_config, url: account_configs_path, method: :post do |f| %> + <%= f.hidden_field :key %> +
+ + Require signing reason + + <%= f.check_box :value, class: 'toggle', checked: account_config.value, onchange: 'this.form.requestSubmit()' %> +
+ <% end %> + <% end %> <% account_config = AccountConfig.find_or_initialize_by(account: current_account, key: AccountConfig::ALLOW_TYPED_SIGNATURE) %> <% if can?(:manage, account_config) %> <%= form_for account_config, url: account_configs_path, method: :post do |f| %> diff --git a/app/views/submissions/_value.html.erb b/app/views/submissions/_value.html.erb index b8cb6517..2c70ae19 100644 --- a/app/views/submissions/_value.html.erb +++ b/app/views/submissions/_value.html.erb @@ -11,7 +11,7 @@ ID: <%= attachment.uuid %>
- <%= t('reason') %>: <%= t('digitally_signed_by') %> <%= submitter.name %> + <%= t('reason') %>: <%= submitter.values[field.dig('preferences', 'reason_field_uuid')].presence || t('digitally_signed_by') %> <%= submitter.name %> <% if submitter.email %> <<%= submitter.email %>> <% end %> diff --git a/app/views/submit_form/_submission_form.html.erb b/app/views/submit_form/_submission_form.html.erb index 7fbd6ccd..9a9012b6 100644 --- a/app/views/submit_form/_submission_form.html.erb +++ b/app/views/submit_form/_submission_form.html.erb @@ -1,3 +1,3 @@ <% data_attachments = attachments_index.values.select { |e| e.record_id == submitter.id }.to_json(only: %i[uuid created_at], methods: %i[url filename content_type]) %> <% data_fields = (submitter.submission.template_fields || submitter.submission.template.fields).select { |f| f['submitter_uuid'] == submitter.uuid }.to_json %> - + diff --git a/app/views/submit_form/show.html.erb b/app/views/submit_form/show.html.erb index 898c615b..cb70e844 100644 --- a/app/views/submit_form/show.html.erb +++ b/app/views/submit_form/show.html.erb @@ -2,6 +2,7 @@ <% content_for(:html_description, "#{@submitter.account.name} has invited you to fill and sign documents online effortlessly with a secure, fast, and user-friendly digital document signing solution.") %> <% fields_index = Templates.build_field_areas_index(@submitter.submission.template_fields || @submitter.submission.template.fields) %> <% values = @submitter.submission.submitters.reduce({}) { |acc, sub| acc.merge(sub.values) } %> +<% submitters_index = @submitter.submission.submitters.index_by(&:uuid) %> <% page_blob_struct = Struct.new(:url, :metadata, keyword_init: true) %>
@@ -28,7 +29,7 @@ <% next if field['redacted'] && field['submitter_uuid'] != @submitter.uuid %> <% next if value == '{{date}}' && 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: @submitter, with_signature_id: @form_configs[:with_signature_id] %> + <%= 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 %>
diff --git a/lib/submissions/generate_result_attachments.rb b/lib/submissions/generate_result_attachments.rb index eae717a1..dcce2608 100644 --- a/lib/submissions/generate_result_attachments.rb +++ b/lib/submissions/generate_result_attachments.rb @@ -184,7 +184,7 @@ module Submissions canvas.font(FONT_NAME, size: font_size) case field['type'] - when ->(type) { type == 'signature' && with_signature_id } + when ->(type) { type == 'signature' && (with_signature_id || field.dig('preferences', 'reason_field_uuid')) } attachment = submitter.attachments.find { |a| a.uuid == value } attachments_data_cache[attachment.uuid] ||= attachment.download @@ -207,8 +207,10 @@ module Submissions break if id_string.length < 8 end + reason_value = submitter.values[field.dig('preferences', 'reason_field_uuid')].presence + reason_string = - "#{I18n.t('reason')}: #{I18n.t('digitally_signed_by')} " \ + "#{I18n.t('reason')}: #{reason_value || I18n.t('digitally_signed_by')} " \ "#{submitter.name}#{submitter.email.present? ? " <#{submitter.email}>" : ''}\n" \ "#{I18n.l(attachment.created_at.in_time_zone(submitter.account.timezone), format: :long, locale: submitter.account.locale)} " \ diff --git a/lib/submitters/form_configs.rb b/lib/submitters/form_configs.rb index 19cf9dc9..6459e02d 100644 --- a/lib/submitters/form_configs.rb +++ b/lib/submitters/form_configs.rb @@ -7,6 +7,7 @@ module Submitters AccountConfig::FORM_WITH_CONFETTI_KEY, AccountConfig::FORM_PREFILL_SIGNATURE_KEY, AccountConfig::WITH_SIGNATURE_ID, + AccountConfig::REQUIRE_SIGNING_REASON_KEY, AccountConfig::ALLOW_TYPED_SIGNATURE].freeze module_function @@ -20,11 +21,13 @@ module Submitters with_confetti = find_safe_value(configs, AccountConfig::FORM_WITH_CONFETTI_KEY) != false prefill_signature = find_safe_value(configs, AccountConfig::FORM_PREFILL_SIGNATURE_KEY) != false with_signature_id = find_safe_value(configs, AccountConfig::WITH_SIGNATURE_ID) == true + require_signing_reason = find_safe_value(configs, AccountConfig::REQUIRE_SIGNING_REASON_KEY) == true attrs = { completed_button:, with_typed_signature:, with_confetti:, completed_message:, + require_signing_reason:, prefill_signature:, with_signature_id: } diff --git a/lib/submitters/submit_values.rb b/lib/submitters/submit_values.rb index 1392ba81..19c3bd3b 100644 --- a/lib/submitters/submit_values.rb +++ b/lib/submitters/submit_values.rb @@ -35,6 +35,7 @@ module Submitters assign_completed_attributes(submitter, request) if params[:completed] == 'true' ApplicationRecord.transaction do + maybe_set_signature_reason!(values, submitter, params) validate_values!(values, submitter, params, request) SubmissionEvents.create_with_tracking_data(submitter, 'complete_form', request) if params[:completed] == 'true' @@ -59,6 +60,31 @@ module Submitters submitter end + def maybe_set_signature_reason!(values, submitter, params) + return if params[:with_reason].blank? + + reason_field_uuid = params[:with_reason] + signature_field_uuid = values.except(reason_field_uuid).keys.first + + signature_field = submitter.submission.template_fields.find { |e| e['uuid'] == signature_field_uuid } + + signature_field['preferences'] ||= {} + signature_field['preferences']['reason_field_uuid'] = reason_field_uuid + + unless submitter.submission.template_fields.find { |e| e['uuid'] == reason_field_uuid } + reason_field = { 'type' => 'text', + 'uuid' => reason_field_uuid, + 'name' => I18n.t(:reason), + 'readonly' => true, + 'submitter_uuid' => submitter.uuid } + + submitter.submission.template_fields.insert(submitter.submission.template_fields.index(signature_field) + 1, + reason_field) + end + + submitter.submission.save! + end + def normalized_values(params) params.fetch(:values, {}).to_unsafe_h.transform_values do |v| if params[:cast_boolean] == 'true' From 507ca49112ddf8d80b1bdc1c3823cf3bfdde7338 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Wed, 7 Aug 2024 14:34:32 +0300 Subject: [PATCH 06/36] adjust ai link --- app/views/shared/_settings_nav.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/shared/_settings_nav.html.erb b/app/views/shared/_settings_nav.html.erb index 87279650..2995cf5c 100644 --- a/app/views/shared/_settings_nav.html.erb +++ b/app/views/shared/_settings_nav.html.erb @@ -118,7 +118,7 @@ <%= svg_icon('brand_discord', class: 'w-8 h-8') %>
- <%= content_for(:gpt_link) || capture do %> + <%= capture do %>