diff --git a/app/javascript/submission_form/area.vue b/app/javascript/submission_form/area.vue index 1c7052289..62181fc5a 100644 --- a/app/javascript/submission_form/area.vue +++ b/app/javascript/submission_form/area.vue @@ -450,8 +450,18 @@ export default { } }, formatNumber (number, format) { + if (!number && number !== 0) { + return '' + } + if (format === 'comma') { return new Intl.NumberFormat('en-US').format(number) + } else if (format === 'usd') { + return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(number) + } else if (format === 'gbp') { + return new Intl.NumberFormat('en-GB', { style: 'currency', currency: 'GBP', minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(number) + } else if (format === 'eur') { + return new Intl.NumberFormat('fr-FR', { style: 'currency', currency: 'EUR', minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(number) } else if (format === 'dot') { return new Intl.NumberFormat('de-DE').format(number) } else if (format === 'space') { diff --git a/app/javascript/submission_form/areas.vue b/app/javascript/submission_form/areas.vue index 05fe7e2fa..d17a728cb 100644 --- a/app/javascript/submission_form/areas.vue +++ b/app/javascript/submission_form/areas.vue @@ -157,13 +157,15 @@ export default { const formContainer = root.getElementById('form_container') const container = root.body || root.querySelector('div') - const padding = 64 + const isAndroid = /android/i.test(navigator.userAgent) + const padding = isAndroid ? 128 : 64 + const scrollboxTop = isAndroid ? scrollbox.getBoundingClientRect().top : 0 const boxRect = scrollbox.children[0].getBoundingClientRect() const targetRect = target.getBoundingClientRect() const targetTopRelativeToBox = targetRect.top - boxRect.top - scrollbox.scrollTo({ top: targetTopRelativeToBox - container.offsetHeight + formContainer.offsetHeight + target.offsetHeight + padding, behavior: 'smooth' }) + scrollbox.scrollTo({ top: targetTopRelativeToBox + scrollboxTop - container.offsetHeight + formContainer.offsetHeight + target.offsetHeight + padding, behavior: 'smooth' }) }, setAreaRef (el) { if (el) { diff --git a/app/javascript/submission_form/form.vue b/app/javascript/submission_form/form.vue index c1d0d8a1c..cb41d182b 100644 --- a/app/javascript/submission_form/form.vue +++ b/app/javascript/submission_form/form.vue @@ -1113,9 +1113,9 @@ export default { this.minimizeForm() } - const isMobileSafariIos = 'ontouchstart' in window && navigator.maxTouchPoints > 0 && /AppleWebKit/i.test(navigator.userAgent) + const isMobile = 'ontouchstart' in window && navigator.maxTouchPoints > 0 && /AppleWebKit|android/i.test(navigator.userAgent) - if (isMobileSafariIos || /iPhone|iPad|iPod/i.test(navigator.userAgent)) { + if (isMobile || /iPhone|iPad|iPod/i.test(navigator.userAgent)) { this.$nextTick(() => { const root = this.$root.$el.parentNode.getRootNode() const scrollbox = root.getElementById('scrollbox') diff --git a/app/javascript/submission_form/phone_step.vue b/app/javascript/submission_form/phone_step.vue index c6c320e47..84875a86e 100644 --- a/app/javascript/submission_form/phone_step.vue +++ b/app/javascript/submission_form/phone_step.vue @@ -297,7 +297,7 @@ export default { const data = await resp.json() if (resp.status === 422) { - alert(this.t('number_phone_is_invalid').replace('{number}', this.fullInternationalPhoneValue)) + alert(data.error || this.t('number_phone_is_invalid').replace('{number}', this.fullInternationalPhoneValue)) } else if (resp.status === 429) { alert(data.error) } diff --git a/app/javascript/template_builder/area.vue b/app/javascript/template_builder/area.vue index afd6d62c9..ccb62ab9c 100644 --- a/app/javascript/template_builder/area.vue +++ b/app/javascript/template_builder/area.vue @@ -479,7 +479,7 @@ export default { methods: { buildDefaultName: Field.methods.buildDefaultName, closeDropdown () { - document.activeElement.blur() + this.$el.getRootNode().activeElement.blur() }, maybeToggleDefaultValue () { if (['text', 'number'].includes(this.field.type)) { @@ -521,6 +521,12 @@ export default { formatNumber (number, format) { if (format === 'comma') { return new Intl.NumberFormat('en-US').format(number) + } else if (format === 'usd') { + return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(number) + } else if (format === 'gbp') { + return new Intl.NumberFormat('en-GB', { style: 'currency', currency: 'GBP', minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(number) + } else if (format === 'eur') { + return new Intl.NumberFormat('fr-FR', { style: 'currency', currency: 'EUR', minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(number) } else if (format === 'dot') { return new Intl.NumberFormat('de-DE').format(number) } else if (format === 'space') { diff --git a/app/javascript/template_builder/builder.vue b/app/javascript/template_builder/builder.vue index 1e22e3b9b..70e9b0f60 100644 --- a/app/javascript/template_builder/builder.vue +++ b/app/javascript/template_builder/builder.vue @@ -222,8 +222,8 @@
({ value: option, uuid: v4() })) + } else { + field.options = [{ value: '', uuid: v4() }] + } } if (['stamp', 'heading'].includes(field.type)) { @@ -1510,29 +1514,29 @@ export default { onDocumentRemove (item) { if (window.confirm(this.t('are_you_sure_'))) { this.template.schema.splice(this.template.schema.indexOf(item), 1) - } - const removedFieldUuids = [] + const removedFieldUuids = [] - this.template.fields.forEach((field) => { - [...(field.areas || [])].forEach((area) => { - if (area.attachment_uuid === item.attachment_uuid) { - field.areas.splice(field.areas.indexOf(area), 1) + this.template.fields.forEach((field) => { + [...(field.areas || [])].forEach((area) => { + if (area.attachment_uuid === item.attachment_uuid) { + field.areas.splice(field.areas.indexOf(area), 1) - removedFieldUuids.push(field.uuid) - } + removedFieldUuids.push(field.uuid) + } + }) }) - }) - this.template.fields = - this.template.fields.filter((f) => !removedFieldUuids.includes(f.uuid) || f.areas?.length) + this.template.fields = + this.template.fields.filter((f) => !removedFieldUuids.includes(f.uuid) || f.areas?.length) - this.save() + this.save() + } }, onDocumentReplace (data) { const { replaceSchemaItem, schema, documents } = data - this.template.schema.splice(this.template.schema.indexOf(replaceSchemaItem), 1, schema[0]) + this.template.schema.splice(this.template.schema.indexOf(replaceSchemaItem), 1, { ...replaceSchemaItem, ...schema[0] }) this.template.documents.push(...documents) if (data.fields) { diff --git a/app/javascript/template_builder/field.vue b/app/javascript/template_builder/field.vue index 169f2ae36..b4941cf7d 100644 --- a/app/javascript/template_builder/field.vue +++ b/app/javascript/template_builder/field.vue @@ -192,14 +192,14 @@ class="w-full input input-primary input-xs text-sm bg-transparent" :placeholder="`${t('option')} ${index + 1}`" type="text" - :readonly="!editable" + :readonly="!editable || defaultField" required dir="auto" @focus="maybeFocusOnOptionArea(option)" @blur="save" >
+
+ + +
  • <% end %> + <%= render 'compliances' %> <%= render 'integrations' %> <% if can?(:manage, current_account) && Docuseal.multitenant? && true_user == current_user %>
    diff --git a/app/views/esign_settings/show.html.erb b/app/views/esign_settings/show.html.erb index 848dd7401..05169a4b5 100644 --- a/app/views/esign_settings/show.html.erb +++ b/app/views/esign_settings/show.html.erb @@ -173,7 +173,7 @@ <%= t('document_download_filename_format') %>
    - <%= f.select :value, [["#{I18n.t('document_name')}.pdf", '{document.name}'], ["#{I18n.t('document_name')} - name@domain.com.pdf", '{document.name} - {submission.submitters}'], ["#{I18n.t('document_name')} - name@domain.com - #{I18n.l(Time.current.beginning_of_year.in_time_zone(current_account.timezone), format: :short)}.pdf", '{document.name} - {submission.submitters} - {submission.completed_at}']], {}, class: 'base-select', onchange: 'this.form.requestSubmit()' %> + <%= f.select :value, [["#{I18n.t('document_name')}.pdf", '{document.name}'], ["#{I18n.t('document_name')} - #{I18n.t(:signed)}.pdf", '{document.name} - {submission.status}'], ["#{I18n.t('document_name')} - name@domain.com.pdf", '{document.name} - {submission.submitters}'], ["#{I18n.t('document_name')} - name@domain.com - #{I18n.l(Time.current.beginning_of_year.in_time_zone(current_account.timezone), format: :short)}.pdf", '{document.name} - {submission.submitters} - {submission.completed_at}']], {}, class: 'base-select', onchange: 'this.form.requestSubmit()' %>
    <% end %> diff --git a/app/views/submissions/show.html.erb b/app/views/submissions/show.html.erb index 336181ea0..083651cd0 100644 --- a/app/views/submissions/show.html.erb +++ b/app/views/submissions/show.html.erb @@ -222,6 +222,8 @@ <% elsif field['type'] == 'checkbox' %> <%= svg_icon('check', class: 'w-6 h-6') %> + <% elsif field['type'] == 'number' %> + <%= NumberUtils.format_number(value, field.dig('preferences', 'format')) %> <% elsif field['type'] == 'date' %> <%= TimeUtils.format_date_string(value, field.dig('preferences', 'format'), @submission.account.locale) %> <% else %> diff --git a/config/locales/i18n.yml b/config/locales/i18n.yml index 7101e1478..83a9d9e0e 100644 --- a/config/locales/i18n.yml +++ b/config/locales/i18n.yml @@ -24,6 +24,7 @@ en: &en pending_by_me: Pending by me partially_completed: Partially completed unarchive: Unarchive + signed: Signed first_party: 'First Party' remove_filter: Remove filter add: Add @@ -725,6 +726,7 @@ en: &en read: Read your data es: &es + signed: Firmado reply_to: Responder a partially_completed: Parcialmente completado pending_by_me: Pendiente por mi @@ -1432,6 +1434,7 @@ es: &es read: Leer tus datos it: &it + signed: Firmato reply_to: Rispondi a pending_by_me: In sospeso da me add: Aggiungi @@ -2138,6 +2141,7 @@ it: &it read: Leggi i tuoi dati fr: &fr + signed: Signé reply_to: Répondre à partially_completed: Partiellement complété pending_by_me: En attente par moi @@ -2846,6 +2850,7 @@ fr: &fr read: Lire vos données pt: &pt + signed: Assinado reply_to: Responder a partially_completed: Parcialmente concluído pending_by_me: Pendente por mim @@ -3553,6 +3558,7 @@ pt: &pt read: Ler seus dados de: &de + signed: Unterschrieben reply_to: Antworten auf partially_completed: Teilweise abgeschlossen pending_by_me: Ausstehend von mir diff --git a/lib/number_utils.rb b/lib/number_utils.rb index d0ab673cc..4cfa3879c 100644 --- a/lib/number_utils.rb +++ b/lib/number_utils.rb @@ -4,7 +4,16 @@ module NumberUtils FORMAT_LOCALES = { 'dot' => 'de', 'space' => 'fr', - 'comma' => 'en' + 'comma' => 'en', + 'usd' => 'en', + 'eur' => 'fr', + 'gbp' => 'en' + }.freeze + + CURRENCY_SYMBOLS = { + 'usd' => '$', + 'eur' => '€', + 'gbp' => '£' }.freeze module_function @@ -12,7 +21,9 @@ module NumberUtils def format_number(number, format) locale = FORMAT_LOCALES[format] - if locale + if CURRENCY_SYMBOLS[format] + ApplicationController.helpers.number_to_currency(number, locale:, precision: 2, unit: CURRENCY_SYMBOLS[format]) + elsif locale ApplicationController.helpers.number_with_delimiter(number, locale:) else number diff --git a/lib/submissions/generate_audit_trail.rb b/lib/submissions/generate_audit_trail.rb index 1611a68cd..f787c5fb5 100644 --- a/lib/submissions/generate_audit_trail.rb +++ b/lib/submissions/generate_audit_trail.rb @@ -175,7 +175,7 @@ def build_audit_trail(submission) padding: [15, 0, 8, 0], position: :float) - unless submission.source.in?(%w[embed api]) + if show_verify?(submission) column.formatted_text([{ link: verify_url, text: I18n.t('verify'), style: :link }], font_size: 9, padding: [15, 0, 10, 0], position: :float, text_align: :right) end @@ -425,6 +425,10 @@ def sign_reason def maybe_add_background(_canvas, _submission, _page_size); end + def show_verify?(submission) + !submission.source.in?(%w[embed api]) + end + def add_logo(column, _submission = nil) column.image(PdfIcons.logo_io, width: 40, height: 40, position: :float) diff --git a/lib/submitters.rb b/lib/submitters.rb index bfd168ad2..bcead9f2f 100644 --- a/lib/submitters.rb +++ b/lib/submitters.rb @@ -123,10 +123,22 @@ def build_document_filename(submitter, blob, filename_format) filename = ReplaceEmailVariables.call(filename_format, submitter:) filename = filename.gsub('{document.name}', blob.filename.base) + filename = filename.gsub(' - {submission.status}') do + if submitter.submission.submitters.all?(&:completed_at?) + status = + if submitter.submission.template_fields.any? { |f| f['type'] == 'signature' } + I18n.t(:signed) + else + I18n.t(:completed) + end + + " - #{status}" + end + end filename = filename.gsub( '{submission.completed_at}', - I18n.l(submitter.completed_at.beginning_of_year.in_time_zone(submitter.account.timezone), format: :short) + I18n.l(submitter.completed_at.in_time_zone(submitter.account.timezone), format: :short) ) "#{filename}.#{blob.filename.extension}"