Merge from docusealco/wip
This commit is contained in:
+2
-2
@@ -60,10 +60,10 @@ RSpec/NestedGroups:
|
||||
Max: 6
|
||||
|
||||
RSpec/MultipleExpectations:
|
||||
Max: 20
|
||||
Max: 25
|
||||
|
||||
RSpec/ExampleLength:
|
||||
Max: 40
|
||||
Max: 50
|
||||
|
||||
RSpec/MultipleMemoizedHelpers:
|
||||
Max: 9
|
||||
|
||||
@@ -17,7 +17,8 @@ class AccountConfigsController < ApplicationController
|
||||
AccountConfig::FLATTEN_RESULT_PDF_KEY,
|
||||
AccountConfig::WITH_SIGNATURE_ID,
|
||||
AccountConfig::COMBINE_PDF_RESULT_KEY,
|
||||
AccountConfig::REQUIRE_SIGNING_REASON_KEY
|
||||
AccountConfig::REQUIRE_SIGNING_REASON_KEY,
|
||||
AccountConfig::DOCUMENT_FILENAME_FORMAT_KEY
|
||||
].freeze
|
||||
|
||||
InvalidKey = Class.new(StandardError)
|
||||
|
||||
@@ -15,6 +15,7 @@ class ApplicationController < ActionController::Base
|
||||
|
||||
helper_method :button_title,
|
||||
:current_account,
|
||||
:form_link_host,
|
||||
:svg_icon
|
||||
|
||||
impersonates :user, with: ->(uuid) { User.find_by(uuid:) }
|
||||
@@ -105,6 +106,10 @@ class ApplicationController < ActionController::Base
|
||||
render_to_string(partial: "icons/#{icon_name}", locals: { class: })
|
||||
end
|
||||
|
||||
def form_link_host
|
||||
Docuseal.default_url_options[:host]
|
||||
end
|
||||
|
||||
def maybe_redirect_com
|
||||
return if request.domain != 'docuseal.co'
|
||||
|
||||
|
||||
@@ -11,6 +11,8 @@ class EsignSettingsController < ApplicationController
|
||||
end
|
||||
end
|
||||
|
||||
prepend_before_action :maybe_redirect_com, only: %i[show]
|
||||
|
||||
before_action :load_encrypted_config
|
||||
authorize_resource :encrypted_config, parent: false, only: %i[new create]
|
||||
authorize_resource :encrypted_config, only: %i[update destroy show]
|
||||
|
||||
@@ -9,7 +9,14 @@ class SubmissionsArchivedController < ApplicationController
|
||||
.or(@submissions.where.not(templates: { archived_at: nil }))
|
||||
.preload(:created_by_user, template: :author)
|
||||
@submissions = Submissions.search(@submissions, params[:q], search_template: true)
|
||||
@submissions = Submissions::Filter.call(@submissions, current_user, params)
|
||||
|
||||
@pagy, @submissions = pagy(@submissions.preload(:submitters).order(id: :desc))
|
||||
@submissions = if params[:completed_at_from].present? || params[:completed_at_to].present?
|
||||
@submissions.order(Submitter.arel_table[:completed_at].maximum.desc)
|
||||
else
|
||||
@submissions.order(id: :desc)
|
||||
end
|
||||
|
||||
@pagy, @submissions = pagy(@submissions.preload(submitters: :start_form_submission_events))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,6 +6,8 @@ class SubmissionsController < ApplicationController
|
||||
|
||||
load_and_authorize_resource :submission, only: %i[show destroy]
|
||||
|
||||
prepend_before_action :maybe_redirect_com, only: %i[show]
|
||||
|
||||
def show
|
||||
@submission = Submissions.preload_with_pages(@submission)
|
||||
|
||||
|
||||
@@ -11,10 +11,17 @@ class SubmissionsDashboardController < ApplicationController
|
||||
.preload(:created_by_user, template: :author)
|
||||
|
||||
@submissions = Submissions.search(@submissions, params[:q], search_template: true)
|
||||
@submissions = Submissions::Filter.call(@submissions, current_user, params)
|
||||
|
||||
@submissions = @submissions.pending if params[:status] == 'pending'
|
||||
@submissions = @submissions.completed if params[:status] == 'completed'
|
||||
|
||||
@pagy, @submissions = pagy(@submissions.preload(submitters: :start_form_submission_events).order(id: :desc))
|
||||
@submissions = if params[:completed_at_from].present? || params[:completed_at_to].present?
|
||||
@submissions.order(Submitter.arel_table[:completed_at].maximum.desc)
|
||||
else
|
||||
@submissions.order(id: :desc)
|
||||
end
|
||||
|
||||
@pagy, @submissions = pagy(@submissions.preload(submitters: :start_form_submission_events))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -53,8 +53,15 @@ class SubmissionsDownloadController < ApplicationController
|
||||
end
|
||||
|
||||
def build_urls(submitter)
|
||||
filename_format = AccountConfig.find_or_initialize_by(account_id: submitter.account_id,
|
||||
key: AccountConfig::DOCUMENT_FILENAME_FORMAT_KEY)&.value
|
||||
|
||||
Submitters.select_attachments_for_download(submitter).map do |attachment|
|
||||
ActiveStorage::Blob.proxy_url(attachment.blob, expires_at: FILES_TTL.from_now.to_i)
|
||||
ActiveStorage::Blob.proxy_url(
|
||||
attachment.blob,
|
||||
expires_at: FILES_TTL.from_now.to_i,
|
||||
filename: Submitters.build_document_filename(submitter, attachment.blob, filename_format)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -65,6 +72,13 @@ class SubmissionsDownloadController < ApplicationController
|
||||
attachment = submitter.submission.combined_document_attachment
|
||||
attachment ||= Submissions::GenerateCombinedAttachment.call(submitter)
|
||||
|
||||
ActiveStorage::Blob.proxy_url(attachment.blob, expires_at: FILES_TTL.from_now.to_i)
|
||||
filename_format = AccountConfig.find_or_initialize_by(account_id: submitter.account_id,
|
||||
key: AccountConfig::DOCUMENT_FILENAME_FORMAT_KEY)&.value
|
||||
|
||||
ActiveStorage::Blob.proxy_url(
|
||||
attachment.blob,
|
||||
expires_at: FILES_TTL.from_now.to_i,
|
||||
filename: Submitters.build_document_filename(submitter, attachment.blob, filename_format)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class SubmissionsFiltersController < ApplicationController
|
||||
ALLOWED_NAMES = %w[
|
||||
author
|
||||
completed_at
|
||||
created_at
|
||||
].freeze
|
||||
|
||||
skip_authorization_check
|
||||
|
||||
def show
|
||||
return head :not_found unless ALLOWED_NAMES.include?(params[:name])
|
||||
|
||||
render params[:name]
|
||||
end
|
||||
end
|
||||
@@ -4,7 +4,7 @@ class SubmissionsPreviewController < ApplicationController
|
||||
skip_before_action :authenticate_user!
|
||||
skip_authorization_check
|
||||
|
||||
before_action :maybe_redirect_com, only: %i[show completed]
|
||||
prepend_before_action :maybe_redirect_com, only: %i[show completed]
|
||||
|
||||
TTL = 40.minutes
|
||||
|
||||
@@ -20,7 +20,7 @@ class SubmissionsPreviewController < ApplicationController
|
||||
|
||||
@submission ||= Submission.find_by!(slug: params[:slug])
|
||||
|
||||
if !@submission.submitters.all?(&:completed_at?) && current_user.blank?
|
||||
if @submission.account.archived_at? || (!@submission.submitters.all?(&:completed_at?) && current_user.blank?)
|
||||
raise ActionController::RoutingError, I18n.t('not_found')
|
||||
end
|
||||
|
||||
|
||||
@@ -7,8 +7,6 @@ class SubmitFormController < ApplicationController
|
||||
skip_before_action :authenticate_user!
|
||||
skip_authorization_check
|
||||
|
||||
before_action :maybe_redirect_com, only: %i[show completed]
|
||||
|
||||
CONFIG_KEYS = [].freeze
|
||||
|
||||
def show
|
||||
@@ -17,7 +15,9 @@ class SubmitFormController < ApplicationController
|
||||
submission = @submitter.submission
|
||||
|
||||
return redirect_to submit_form_completed_path(@submitter.slug) if @submitter.completed_at?
|
||||
return render :archived if submission.template.archived_at? || submission.archived_at?
|
||||
return render :archived if submission.template.archived_at? ||
|
||||
submission.archived_at? ||
|
||||
@submitter.account.archived_at?
|
||||
return render :expired if submission.expired?
|
||||
return render :declined if @submitter.declined_at?
|
||||
return render :awaiting if submission.template.preferences['submitters_order'] == 'preserved' &&
|
||||
@@ -27,8 +27,7 @@ class SubmitFormController < ApplicationController
|
||||
|
||||
Submitters::MaybeUpdateDefaultValues.call(@submitter, current_user)
|
||||
|
||||
@attachments_index = ActiveStorage::Attachment.where(record: submission.submitters, name: :attachments)
|
||||
.preload(:blob).index_by(&:uuid)
|
||||
@attachments_index = build_attachments_index(submission)
|
||||
|
||||
@form_configs = Submitters::FormConfigs.call(@submitter, CONFIG_KEYS)
|
||||
|
||||
@@ -80,4 +79,11 @@ class SubmitFormController < ApplicationController
|
||||
end
|
||||
|
||||
def success; end
|
||||
|
||||
private
|
||||
|
||||
def build_attachments_index(submission)
|
||||
ActiveStorage::Attachment.where(record: submission.submitters, name: :attachments)
|
||||
.preload(:blob).index_by(&:uuid)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,7 +6,7 @@ class SubmittersSendEmailController < ApplicationController
|
||||
def create
|
||||
if Docuseal.multitenant? && SubmissionEvent.exists?(submitter: @submitter,
|
||||
event_type: 'send_email',
|
||||
created_at: 24.hours.ago..Time.current)
|
||||
created_at: 10.hours.ago..Time.current)
|
||||
Rollbar.warning("Already sent: #{@submitter.id}") if defined?(Rollbar)
|
||||
|
||||
return redirect_back(fallback_location: submission_path(@submitter.submission),
|
||||
|
||||
@@ -7,8 +7,15 @@ class TemplatesArchivedSubmissionsController < ApplicationController
|
||||
def index
|
||||
@submissions = @submissions.where.not(archived_at: nil)
|
||||
@submissions = Submissions.search(@submissions, params[:q], search_values: true)
|
||||
@submissions = Submissions::Filter.call(@submissions, current_user, params)
|
||||
|
||||
@pagy, @submissions = pagy(@submissions.preload(:submitters).order(id: :desc))
|
||||
@submissions = if params[:completed_at_from].present? || params[:completed_at_to].present?
|
||||
@submissions.order(Submitter.arel_table[:completed_at].maximum.desc)
|
||||
else
|
||||
@submissions.order(id: :desc)
|
||||
end
|
||||
|
||||
@pagy, @submissions = pagy(@submissions.preload(submitters: :start_form_submission_events))
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
redirect_to root_path
|
||||
end
|
||||
|
||||
@@ -9,13 +9,20 @@ class TemplatesController < ApplicationController
|
||||
submissions = @template.submissions.accessible_by(current_ability)
|
||||
submissions = submissions.active if @template.archived_at.blank?
|
||||
submissions = Submissions.search(submissions, params[:q], search_values: true)
|
||||
submissions = Submissions::Filter.call(submissions, current_user, params)
|
||||
|
||||
@base_submissions = submissions
|
||||
|
||||
submissions = submissions.pending if params[:status] == 'pending'
|
||||
submissions = submissions.completed if params[:status] == 'completed'
|
||||
|
||||
@pagy, @submissions = pagy(submissions.preload(submitters: :start_form_submission_events).order(id: :desc))
|
||||
submissions = if params[:completed_at_from].present? || params[:completed_at_to].present?
|
||||
submissions.order(Submitter.arel_table[:completed_at].maximum.desc)
|
||||
else
|
||||
submissions.order(id: :desc)
|
||||
end
|
||||
|
||||
@pagy, @submissions = pagy(submissions.preload(submitters: :start_form_submission_events))
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
redirect_to root_path
|
||||
end
|
||||
|
||||
@@ -30,6 +30,7 @@ import ToggleAttribute from './elements/toggle_attribute'
|
||||
import LinkedInput from './elements/linked_input'
|
||||
import CheckboxGroup from './elements/checkbox_group'
|
||||
import MaskedInput from './elements/masked_input'
|
||||
import SetDateButton from './elements/set_date_button'
|
||||
|
||||
import * as TurboInstantClick from './lib/turbo_instant_click'
|
||||
|
||||
@@ -97,6 +98,7 @@ safeRegisterElement('toggle-attribute', ToggleAttribute)
|
||||
safeRegisterElement('linked-input', LinkedInput)
|
||||
safeRegisterElement('checkbox-group', CheckboxGroup)
|
||||
safeRegisterElement('masked-input', MaskedInput)
|
||||
safeRegisterElement('set-date-button', SetDateButton)
|
||||
|
||||
safeRegisterElement('template-builder', class extends HTMLElement {
|
||||
connectedCallback () {
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
export default class extends HTMLElement {
|
||||
connectedCallback () {
|
||||
this.button.addEventListener('click', () => {
|
||||
this.fromInput.value = this.dataset.fromValue || ''
|
||||
this.toInput.value = this.dataset.toValue || ''
|
||||
})
|
||||
}
|
||||
|
||||
get button () {
|
||||
return this.querySelector('button')
|
||||
}
|
||||
|
||||
get fromInput () {
|
||||
return document.getElementById(this.dataset.fromId)
|
||||
}
|
||||
|
||||
get toInput () {
|
||||
return document.getElementById(this.dataset.toId)
|
||||
}
|
||||
}
|
||||
@@ -37,6 +37,7 @@
|
||||
<AppearsOn :field="field" />
|
||||
<div class="text-center">
|
||||
<input
|
||||
:id="field.uuid"
|
||||
ref="input"
|
||||
v-model="value"
|
||||
class="base-input !text-2xl text-center w-full"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<div
|
||||
id="dropzone"
|
||||
class="flex h-32 w-full"
|
||||
@dragover.prevent
|
||||
@drop.prevent="onDropFiles"
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
:with-label="!isAnonymousChecboxes && showFieldNames"
|
||||
:current-step="currentStepFields"
|
||||
:scroll-padding="scrollPadding"
|
||||
@focus-step="[saveStep(), currentField.type !== 'checkbox' ? isFormVisible = true : '', goToStep($event, false, true)]"
|
||||
@focus-step="[saveStep(), goToStep($event, false, true), currentField.type !== 'checkbox' ? isFormVisible = true : '']"
|
||||
/>
|
||||
<FieldAreas
|
||||
:steps="readonlyConditionalFields.map((e) => [e])"
|
||||
@@ -1039,10 +1039,14 @@ export default {
|
||||
} else if (['not_empty', 'checked'].includes(c.action)) {
|
||||
return acc && !isEmpty(this.values[c.field_uuid])
|
||||
} else if (['equal', 'contains'].includes(c.action) && field) {
|
||||
const option = field.options.find((o) => o.uuid === c.value)
|
||||
const values = [this.values[c.field_uuid]].flat()
|
||||
if (field.options) {
|
||||
const option = field.options.find((o) => o.uuid === c.value)
|
||||
const values = [this.values[c.field_uuid]].flat()
|
||||
|
||||
return acc && values.includes(this.optionValue(option, field.options.indexOf(option)))
|
||||
return acc && values.includes(this.optionValue(option, field.options.indexOf(option)))
|
||||
} else {
|
||||
return acc && [this.values[c.field_uuid]].flat().includes(c.value)
|
||||
}
|
||||
} else if (['not_equal', 'does_not_contain'].includes(c.action) && field) {
|
||||
const option = field.options.find((o) => o.uuid === c.value)
|
||||
const values = [this.values[c.field_uuid]].flat()
|
||||
@@ -1243,9 +1247,13 @@ export default {
|
||||
if (response.status === 422 || response.status === 500) {
|
||||
const data = await response.json()
|
||||
|
||||
const i18nError = data.error ? this.t(data.error.replace(/\s+/g, '_').toLowerCase()) : ''
|
||||
if (data.error) {
|
||||
const i18nKey = data.error.replace(/\s+/g, '_').toLowerCase()
|
||||
|
||||
alert(i18nError !== data.error ? i18nError : (data.error || this.t('value_is_invalid')))
|
||||
alert(this.t(i18nKey) !== i18nKey ? this.t(i18nKey) : data.error)
|
||||
} else {
|
||||
alert(this.t('value_is_invalid'))
|
||||
}
|
||||
|
||||
return Promise.reject(new Error(data.error))
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ const en = {
|
||||
documents_have_been_signed: 'Documents have been signed!',
|
||||
create_a_free_account: 'Create a Free Account',
|
||||
powered_by: 'Powered by',
|
||||
please_check_the_box_to_continue: 'Please check the box to continue',
|
||||
please_check_the_box_to_continue: 'Please check the box to continue.',
|
||||
open_source_documents_software: 'open source documents software',
|
||||
verified_phone_number: 'Verify Phone Number',
|
||||
use_international_format: 'Use international format: +1xxx',
|
||||
@@ -89,7 +89,8 @@ const en = {
|
||||
pay_with_strip: 'Pay with Stripe',
|
||||
reupload: 'Reupload',
|
||||
upload: 'Upload',
|
||||
files: 'Files'
|
||||
files: 'Files',
|
||||
signature_is_too_small_please_redraw: 'Signature is too small. Please redraw.'
|
||||
}
|
||||
|
||||
const es = {
|
||||
@@ -160,7 +161,7 @@ const es = {
|
||||
documents_have_been_signed: '¡Los documentos han sido firmados!',
|
||||
create_a_free_account: 'Crear una Cuenta Gratuita',
|
||||
powered_by: 'Desarrollado por',
|
||||
please_check_the_box_to_continue: 'Por favor marque la casilla para continuar',
|
||||
please_check_the_box_to_continue: 'Por favor marque la casilla para continuar.',
|
||||
open_source_documents_software: 'software de documentos de código abierto',
|
||||
verified_phone_number: 'Verificar número de teléfono',
|
||||
use_international_format: 'Usar formato internacional: +1xxx',
|
||||
@@ -182,7 +183,8 @@ const es = {
|
||||
pay_with_strip: 'Pagar con Stripe',
|
||||
reupload: 'Volver a subir',
|
||||
upload: 'Subir',
|
||||
files: 'Archivos'
|
||||
files: 'Archivos',
|
||||
signature_is_too_small_please_redraw: 'La firma es demasiado pequeña. Por favor, dibújala de nuevo.'
|
||||
}
|
||||
|
||||
const it = {
|
||||
@@ -253,7 +255,7 @@ const it = {
|
||||
documents_have_been_signed: 'I documenti sono stati firmati!',
|
||||
create_a_free_account: 'Crea un Account Gratuito',
|
||||
powered_by: 'Desarrollado por',
|
||||
please_check_the_box_to_continue: 'Si prega di spuntare la casella per continuare',
|
||||
please_check_the_box_to_continue: 'Si prega di spuntare la casella per continuare.',
|
||||
open_source_documents_software: 'software di documenti open source',
|
||||
verified_phone_number: 'Verifica numero di telefono',
|
||||
use_international_format: 'Usa formato internazionale: +1xxx',
|
||||
@@ -275,7 +277,8 @@ const it = {
|
||||
pay_with_strip: 'Paga con Stripe',
|
||||
reupload: 'Ricarica',
|
||||
upload: 'Carica',
|
||||
files: 'File'
|
||||
files: 'File',
|
||||
signature_is_too_small_please_redraw: 'La firma è troppo piccola. Ridisegnala per favore.'
|
||||
}
|
||||
|
||||
const de = {
|
||||
@@ -346,7 +349,7 @@ const de = {
|
||||
documents_have_been_signed: 'Dokumente wurden unterschrieben!',
|
||||
create_a_free_account: 'Kostenloses Konto erstellen',
|
||||
powered_by: 'Bereitgestellt von',
|
||||
please_check_the_box_to_continue: 'Bitte setzen Sie das Häkchen, um fortzufahren',
|
||||
please_check_the_box_to_continue: 'Bitte setzen Sie das Häkchen, um fortzufahren.',
|
||||
open_source_documents_software: 'Open-Source-Dokumentensoftware',
|
||||
verified_phone_number: 'Telefonnummer überprüfen',
|
||||
use_international_format: 'Internationales Format verwenden: +1xxx',
|
||||
@@ -368,7 +371,8 @@ const de = {
|
||||
pay_with_strip: 'Mit Stripe bezahlen',
|
||||
reupload: 'Erneut hochladen',
|
||||
upload: 'Hochladen',
|
||||
files: 'Dateien'
|
||||
files: 'Dateien',
|
||||
signature_is_too_small_please_redraw: 'Die Unterschrift ist zu klein. Bitte erneut zeichnen.'
|
||||
}
|
||||
|
||||
const fr = {
|
||||
@@ -439,7 +443,7 @@ const fr = {
|
||||
documents_have_been_signed: 'Les documents ont été signés!',
|
||||
create_a_free_account: 'Créer un Compte Gratuit',
|
||||
powered_by: 'Propulsé par',
|
||||
please_check_the_box_to_continue: 'Veuillez cocher la case pour continuer',
|
||||
please_check_the_box_to_continue: 'Veuillez cocher la case pour continuer.',
|
||||
open_source_documents_software: 'logiciel de documents open source',
|
||||
verified_phone_number: 'Vérifier le numéro de téléphone',
|
||||
use_international_format: 'Utiliser le format international : +1xxx',
|
||||
@@ -461,7 +465,8 @@ const fr = {
|
||||
pay_with_strip: 'Paiement avec Stripe',
|
||||
reupload: 'Recharger',
|
||||
upload: 'Télécharger',
|
||||
files: 'Fichiers'
|
||||
files: 'Fichiers',
|
||||
signature_is_too_small_please_redraw: 'La signature est trop petite. Veuillez la redessiner.'
|
||||
}
|
||||
|
||||
const pl = {
|
||||
@@ -532,7 +537,7 @@ const pl = {
|
||||
documents_have_been_signed: 'Dokumenty zostały podpisane!',
|
||||
create_a_free_account: 'Utwórz darmowe konto',
|
||||
powered_by: 'Napędzany przez',
|
||||
please_check_the_box_to_continue: 'Proszę zaznaczyć pole, aby kontynuować',
|
||||
please_check_the_box_to_continue: 'Proszę zaznaczyć pole, aby kontynuować.',
|
||||
open_source_documents_software: 'oprogramowanie do dokumentów open source',
|
||||
verified_phone_number: 'Zweryfikuj numer telefonu',
|
||||
use_international_format: 'Użyj międzynarodowego formatu: +1xxx',
|
||||
@@ -554,7 +559,8 @@ const pl = {
|
||||
pay_with_strip: 'Płatność za pomocą Stripe',
|
||||
reupload: 'Ponowne przesłanie',
|
||||
upload: 'Przesyłanie',
|
||||
files: 'Pliki'
|
||||
files: 'Pliki',
|
||||
signature_is_too_small_please_redraw: 'Podpis jest zbyt mały. Proszę narysować go ponownie.'
|
||||
}
|
||||
|
||||
const uk = {
|
||||
@@ -625,7 +631,7 @@ const uk = {
|
||||
documents_have_been_signed: 'Документи були підписані!',
|
||||
create_a_free_account: 'Створити безкоштовний обліковий запис',
|
||||
powered_by: 'Працює на базі',
|
||||
please_check_the_box_to_continue: 'Будь ласка, позначте прапорець, щоб продовжити',
|
||||
please_check_the_box_to_continue: 'Будь ласка, позначте прапорець, щоб продовжити.',
|
||||
open_source_documents_software: 'відкритий програмний засіб для документів',
|
||||
verified_phone_number: 'Підтвердіть номер телефону',
|
||||
use_international_format: 'Використовуйте міжнародний формат: +1xxx',
|
||||
@@ -647,7 +653,8 @@ const uk = {
|
||||
pay_with_strip: 'Сплатити за допомогою Stripe',
|
||||
reupload: 'Перезавантажити',
|
||||
upload: 'Завантажити',
|
||||
files: 'Файли'
|
||||
files: 'Файли',
|
||||
signature_is_too_small_please_redraw: 'Підпис занадто малий. Будь ласка, перемалюйте його.'
|
||||
}
|
||||
|
||||
const cs = {
|
||||
@@ -718,7 +725,7 @@ const cs = {
|
||||
documents_have_been_signed: 'Dokumenty byly podepsány!',
|
||||
create_a_free_account: 'Vytvořit bezplatný účet',
|
||||
powered_by: 'Poháněno',
|
||||
please_check_the_box_to_continue: 'Prosím, zaškrtněte políčko pro pokračování',
|
||||
please_check_the_box_to_continue: 'Prosím, zaškrtněte políčko pro pokračování.',
|
||||
open_source_documents_software: 'open source software pro dokumenty',
|
||||
verified_phone_number: 'Ověřte telefonní číslo',
|
||||
use_international_format: 'Použijte mezinárodní formát: +1xxx',
|
||||
@@ -740,7 +747,8 @@ const cs = {
|
||||
pay_with_strip: 'Zaplacení přes Stripe',
|
||||
reupload: 'Znovu nahrát',
|
||||
upload: 'Nahrát',
|
||||
files: 'Soubory'
|
||||
files: 'Soubory',
|
||||
signature_is_too_small_please_redraw: 'Podpis je příliš malý. Prosím, překreslete ho.'
|
||||
}
|
||||
|
||||
const pt = {
|
||||
@@ -811,7 +819,7 @@ const pt = {
|
||||
documents_have_been_signed: 'Os documentos foram assinados!',
|
||||
create_a_free_account: 'Criar uma Conta Gratuita',
|
||||
powered_by: 'Desenvolvido por',
|
||||
please_check_the_box_to_continue: 'Por favor, marque a caixa para continuar',
|
||||
please_check_the_box_to_continue: 'Por favor, marque a caixa para continuar.',
|
||||
open_source_documents_software: 'software de documentos de código aberto',
|
||||
verified_phone_number: 'Verificar Número de Telefone',
|
||||
use_international_format: 'Use formato internacional: +1xxx',
|
||||
@@ -833,7 +841,8 @@ const pt = {
|
||||
pay_with_strip: 'Pagar com Stripe',
|
||||
reupload: 'Reenviar',
|
||||
upload: 'Carregar',
|
||||
files: 'Arquivos'
|
||||
files: 'Arquivos',
|
||||
signature_is_too_small_please_redraw: 'A assinatura é muito pequena. Por favor, redesenhe-a.'
|
||||
}
|
||||
|
||||
const he = {
|
||||
@@ -905,7 +914,7 @@ const he = {
|
||||
documents_have_been_signed: 'המסמכים נחתמו!',
|
||||
create_a_free_account: 'צור חשבון חינם',
|
||||
powered_by: 'מופעל על ידי',
|
||||
please_check_the_box_to_continue: 'אנא סמן את התיבה כדי להמשיך',
|
||||
please_check_the_box_to_continue: 'אנא סמן את התיבה כדי להמשיך.',
|
||||
open_source_documents_software: 'תוכנה פתוחה למסמכים',
|
||||
verified_phone_number: 'אימות מספר טלפון',
|
||||
use_international_format: 'השתמש בפורמט בינלאומי: +1xxx',
|
||||
@@ -927,7 +936,8 @@ const he = {
|
||||
pay_with_strip: 'שלם עם סטרייפ',
|
||||
reupload: 'העלה שוב',
|
||||
upload: 'העלאה',
|
||||
files: 'קבצים'
|
||||
files: 'קבצים',
|
||||
signature_is_too_small_please_redraw: 'החתימה קטנה מדי. אנא צייר מחדש.'
|
||||
}
|
||||
|
||||
const nl = {
|
||||
@@ -999,7 +1009,7 @@ const nl = {
|
||||
documents_have_been_signed: 'De documenten zijn ondertekend!',
|
||||
create_a_free_account: 'Maak een gratis account aan',
|
||||
powered_by: 'Aangedreven door',
|
||||
please_check_the_box_to_continue: 'Vink het vakje aan om door te gaan',
|
||||
please_check_the_box_to_continue: 'Vink het vakje aan om door te gaan.',
|
||||
open_source_documents_software: 'Open source documenten software',
|
||||
verified_phone_number: 'Geverifieerd telefoonnummer',
|
||||
use_international_format: 'Gebruik internationaal formaat: +1xxx',
|
||||
@@ -1021,7 +1031,8 @@ const nl = {
|
||||
pay_with_strip: 'Betalen met Stripe',
|
||||
reupload: 'Opnieuw uploaden',
|
||||
upload: 'Uploaden',
|
||||
files: 'Bestanden'
|
||||
files: 'Bestanden',
|
||||
signature_is_too_small_please_redraw: 'De handtekening is te klein. Teken deze opnieuw, alstublieft.'
|
||||
}
|
||||
|
||||
const ar = {
|
||||
@@ -1092,7 +1103,7 @@ const ar = {
|
||||
documents_have_been_signed: 'تم توقيع الوثائق!',
|
||||
create_a_free_account: 'إنشاء حساب مجاني',
|
||||
powered_by: 'مدعوم من',
|
||||
please_check_the_box_to_continue: 'الرجاء التحقق من الخانة للمتابعة',
|
||||
please_check_the_box_to_continue: 'الرجاء التحقق من الخانة للمتابعة.',
|
||||
open_source_documents_software: 'برنامج وثائق مفتوح المصدر',
|
||||
verified_phone_number: 'تحقق من رقم الهاتف',
|
||||
use_international_format: 'استخدم الشكل الدولي: +1xxx',
|
||||
@@ -1114,7 +1125,8 @@ const ar = {
|
||||
pay_with_strip: 'الدفع بواسطة Stripe',
|
||||
reupload: 'إعادة التحميل',
|
||||
upload: 'تحميل',
|
||||
files: 'الملفات'
|
||||
files: 'الملفات',
|
||||
signature_is_too_small_please_redraw: 'التوقيع صغير جدًا. يرجى إعادة الرسم.'
|
||||
}
|
||||
|
||||
const ko = {
|
||||
@@ -1184,7 +1196,7 @@ const ko = {
|
||||
documents_have_been_signed: '문서가 서명되었습니다!',
|
||||
create_a_free_account: '무료 계정 생성',
|
||||
powered_by: '구동',
|
||||
please_check_the_box_to_continue: '계속하려면 확인란을 선택하십시오',
|
||||
please_check_the_box_to_continue: '계속하려면 확인란을 선택하십시오.',
|
||||
open_source_documents_software: '오픈 소스 문서 소프트웨어',
|
||||
verified_phone_number: '전화번호 확인됨',
|
||||
use_international_format: '국제 포맷 사용: +1xxx',
|
||||
@@ -1206,7 +1218,8 @@ const ko = {
|
||||
pay_with_strip: '스트라이프로 결제',
|
||||
reupload: '다시 업로드',
|
||||
upload: '업로드',
|
||||
files: '파일'
|
||||
files: '파일',
|
||||
signature_is_too_small_please_redraw: '서명이 너무 작습니다. 다시 그려주세요.'
|
||||
}
|
||||
|
||||
const i18n = { en, es, it, de, fr, pl, uk, cs, pt, he, nl, ar, ko }
|
||||
|
||||
@@ -65,11 +65,15 @@
|
||||
v-show="!isCodeSent"
|
||||
class="flex w-full rounded-full outline-neutral-content outline-2 outline-offset-2 focus-within:outline"
|
||||
>
|
||||
<div class="relative inline-block">
|
||||
<div
|
||||
id="country_code"
|
||||
class="relative inline-block"
|
||||
>
|
||||
<div class="btn bg-base-200 border border-neutral-300 text-2xl whitespace-nowrap font-normal rounded-l-full">
|
||||
{{ selectedCountry.flag }} +{{ selectedCountry.dial }}
|
||||
</div>
|
||||
<select
|
||||
id="country_code_select"
|
||||
class="absolute top-0 bottom-0 right-0 left-0 opacity-0 w-full h-full cursor-pointer"
|
||||
:disabled="!!defaultValue"
|
||||
@change="onCountrySelect(countries.find((country) => country.flag === $event.target.value))"
|
||||
|
||||
@@ -259,7 +259,7 @@
|
||||
class="text-base-content/60 text-xs text-center w-full mt-1"
|
||||
>
|
||||
{{ t('by_clicking_you_agree_to_the').replace('{button}', buttonText.charAt(0).toUpperCase() + buttonText.slice(1)) }} <a
|
||||
href="https://www.docuseal.com/esign-disclosure"
|
||||
href="https://www.docuseal.co/esign-disclosure"
|
||||
target="_blank"
|
||||
>
|
||||
<span class="inline md:hidden">
|
||||
|
||||
@@ -114,7 +114,9 @@ export default {
|
||||
const area = this.field.areas?.[0]
|
||||
|
||||
if (area) {
|
||||
return parseInt(area.w / area.cell_w) + 1
|
||||
const num = area.w / area.cell_w
|
||||
|
||||
return (num % 1) > 0.2 ? parseInt(num) + 1 : parseInt(num)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
class="mx-auto pl-3 md:pl-4 h-full"
|
||||
>
|
||||
<div
|
||||
v-if="pendingFieldAttachmentUuids.length"
|
||||
v-if="pendingFieldAttachmentUuids.length && editable"
|
||||
class="top-1.5 sticky h-0 z-20 max-w-2xl mx-auto"
|
||||
>
|
||||
<div class="alert border-base-content/30 py-2 px-2.5">
|
||||
@@ -1342,7 +1342,9 @@ export default {
|
||||
}
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.$refs.previews.scrollTop = this.$refs.previews.scrollHeight
|
||||
if (this.$refs.previews) {
|
||||
this.$refs.previews.scrollTop = this.$refs.previews.scrollHeight
|
||||
}
|
||||
|
||||
this.scrollIntoDocument(data.schema[0])
|
||||
})
|
||||
|
||||
@@ -126,7 +126,6 @@ const en = {
|
||||
unlock_sms_verified_phone_number_field_with_paid_plan_use_text_field_for_phone_numbers_without_verification: 'Unlock SMS-verified phone number field with paid plan. Use text field for phone numbers without verification.',
|
||||
available_only_in_pro: 'Available only in Pro',
|
||||
failed_to_download_files: 'Failed to download files',
|
||||
signature_is_too_small_please_redraw: 'Signature is too small - please redraw.',
|
||||
please_add_fields_for_the_submitter_name_or_remove_the_submitter_name_if_not_needed: 'Please add fields for the {submitter_name}. Or, remove the {submitter_name} if not needed.',
|
||||
draw_field: 'Draw {field} Field',
|
||||
replace: 'Replace',
|
||||
@@ -277,7 +276,6 @@ const es = {
|
||||
unlock_sms_verified_phone_number_field_with_paid_plan_use_text_field_for_phone_numbers_without_verification: 'Desbloquea el campo de número de teléfono verificado por SMS con un plan pago. Usa el campo de texto para números de teléfono sin verificación.',
|
||||
available_only_in_pro: 'Disponible solo en Pro',
|
||||
failed_to_download_files: 'Error al descargar los archivos',
|
||||
signature_is_too_small_please_redraw: 'La firma es demasiado pequeña. Por favor, dibújala de nuevo.',
|
||||
please_add_fields_for_the_submitter_name_or_remove_the_submitter_name_if_not_needed: 'Por favor, añade campos para {submitter_name} o elimina {submitter_name} si no es necesario.',
|
||||
draw_field: 'Dibujar campo {field}',
|
||||
replace: 'Reemplazar',
|
||||
@@ -434,7 +432,6 @@ const it = {
|
||||
unlock_sms_verified_phone_number_field_with_paid_plan_use_text_field_for_phone_numbers_without_verification: 'Sblocca il campo numero di telefono verificato tramite SMS con un piano a pagamento. Usa il campo di testo per numeri di telefono senza verifica.',
|
||||
available_only_in_pro: 'Disponibile solo in Pro',
|
||||
failed_to_download_files: 'Impossibile scaricare i file',
|
||||
signature_is_too_small_please_redraw: 'La firma è troppo piccola. Ridisegnala per favore.',
|
||||
please_add_fields_for_the_submitter_name_or_remove_the_submitter_name_if_not_needed: 'Aggiungi campi per {submitter_name} o rimuovi {submitter_name} se non necessario.',
|
||||
draw_field: 'Disegna il campo {field}',
|
||||
replace: 'Sostituisci',
|
||||
@@ -585,7 +582,6 @@ const pt = {
|
||||
unlock_sms_verified_phone_number_field_with_paid_plan_use_text_field_for_phone_numbers_without_verification: 'Desbloqueie o campo de número de telefone verificado por SMS com um plano pago. Use o campo de texto para números de telefone sem verificação.',
|
||||
available_only_in_pro: 'Disponível apenas no Pro',
|
||||
failed_to_download_files: 'Falha ao baixar arquivos',
|
||||
signature_is_too_small_please_redraw: 'A assinatura é muito pequena. Por favor, redesenhe.',
|
||||
please_add_fields_for_the_submitter_name_or_remove_the_submitter_name_if_not_needed: 'Adicione campos para {submitter_name} ou remova {submitter_name} se não for necessário.',
|
||||
draw_field: 'Desenhar campo {field}',
|
||||
replace: 'Substituir',
|
||||
@@ -739,7 +735,6 @@ const fr = {
|
||||
unlock_sms_verified_phone_number_field_with_paid_plan_use_text_field_for_phone_numbers_without_verification: 'Débloquez le champ de numéro de téléphone vérifié par SMS avec un plan payant. Utilisez un champ texte pour les numéros de téléphone sans vérification.',
|
||||
available_only_in_pro: 'Disponible uniquement en Pro',
|
||||
failed_to_download_files: 'Échec du téléchargement des fichiers',
|
||||
signature_is_too_small_please_redraw: 'La signature est trop petite. Veuillez la redessiner.',
|
||||
please_add_fields_for_the_submitter_name_or_remove_the_submitter_name_if_not_needed: 'Veuillez ajouter des champs pour {submitter_name} ou retirer {submitter_name} si ce n\'est pas nécessaire.',
|
||||
draw_field: 'Dessiner le champ {field}',
|
||||
replace: 'Remplacer',
|
||||
@@ -893,7 +888,6 @@ const de = {
|
||||
unlock_sms_verified_phone_number_field_with_paid_plan_use_text_field_for_phone_numbers_without_verification: 'Schalte das SMS-verifizierte Telefonnummernfeld mit einem kostenpflichtigen Plan frei. Verwende das Textfeld für Telefonnummern ohne Verifizierung.',
|
||||
available_only_in_pro: 'Nur in Pro verfügbar',
|
||||
failed_to_download_files: 'Fehler beim Herunterladen der Dateien',
|
||||
signature_is_too_small_please_redraw: 'Die Unterschrift ist zu klein. Bitte erneut zeichnen.',
|
||||
please_add_fields_for_the_submitter_name_or_remove_the_submitter_name_if_not_needed: 'Bitte füge Felder für {submitter_name} hinzu oder entferne {submitter_name}, falls nicht erforderlich.',
|
||||
draw_field: 'Feld {field} zeichnen',
|
||||
replace: 'Ersetzen',
|
||||
|
||||
@@ -279,7 +279,11 @@ export default {
|
||||
|
||||
this.mappings.forEach((mapping) => {
|
||||
if (mapping.field_name && mapping.column_index != null) {
|
||||
submittersIndex[mapping.submitter_uuid] ||= { uuid: mapping.submitter_uuid, fields: [] }
|
||||
submittersIndex[mapping.submitter_uuid] ||= {
|
||||
uuid: mapping.submitter_uuid,
|
||||
role: this.submitters.find((s) => s.uuid === mapping.submitter_uuid).name,
|
||||
fields: []
|
||||
}
|
||||
|
||||
if (['name', 'email', 'phone', 'external_id'].includes(mapping.field_name.toLowerCase())) {
|
||||
submittersIndex[mapping.submitter_uuid][mapping.field_name.toLowerCase()] = row[mapping.column_index]
|
||||
@@ -296,7 +300,7 @@ export default {
|
||||
})
|
||||
|
||||
if (Object.keys(submittersIndex).length !== 0) {
|
||||
submissions.push({ submitters: Object.values(submittersIndex) })
|
||||
submissions.push({ submitters: this.submitters.map((s) => submittersIndex[s.uuid]).filter(Boolean) })
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ class ApplicationMailer < ActionMailer::Base
|
||||
after_action :set_message_uuid
|
||||
|
||||
def default_url_options
|
||||
Docuseal.default_url_options
|
||||
Docuseal.default_url_options.merge(host: ENV.fetch('EMAIL_HOST', Docuseal.default_url_options[:host]))
|
||||
end
|
||||
|
||||
def set_message_metadata
|
||||
|
||||
@@ -152,6 +152,9 @@ class SubmitterMailer < ApplicationMailer
|
||||
def add_completed_email_attachments!(submitter, with_audit_log: true, with_documents: true)
|
||||
documents = with_documents ? Submitters.select_attachments_for_download(submitter) : []
|
||||
|
||||
filename_format = AccountConfig.find_or_initialize_by(account_id: submitter.account_id,
|
||||
key: AccountConfig::DOCUMENT_FILENAME_FORMAT_KEY)&.value
|
||||
|
||||
total_size = 0
|
||||
audit_trail_data = nil
|
||||
|
||||
@@ -161,9 +164,14 @@ class SubmitterMailer < ApplicationMailer
|
||||
total_size = audit_trail_data.size
|
||||
end
|
||||
|
||||
total_size = add_attachments_with_size_limit(documents, total_size)
|
||||
total_size = add_attachments_with_size_limit(submitter, documents, total_size, filename_format)
|
||||
|
||||
attachments[submitter.submission.audit_trail.filename.to_s.tr('"', "'")] = audit_trail_data if audit_trail_data
|
||||
if audit_trail_data
|
||||
audit_trail_filename =
|
||||
Submitters.build_document_filename(submitter, submitter.submission.audit_trail.blob, filename_format)
|
||||
|
||||
attachments[audit_trail_filename.tr('"', "'")] = audit_trail_data
|
||||
end
|
||||
|
||||
if with_documents
|
||||
file_fields = submitter.submission.template_fields.select { |e| e['type'].in?(%w[file payment]) }
|
||||
@@ -172,7 +180,7 @@ class SubmitterMailer < ApplicationMailer
|
||||
storage_attachments =
|
||||
submitter.attachments.where(uuid: submitter.values.values_at(*file_fields.pluck('uuid')).flatten)
|
||||
|
||||
add_attachments_with_size_limit(storage_attachments, total_size)
|
||||
add_attachments_with_size_limit(submitter, storage_attachments, total_size)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -183,7 +191,7 @@ class SubmitterMailer < ApplicationMailer
|
||||
user.role == 'integration' ? user.friendly_name.sub(/\+\w+@/, '@') : user.friendly_name
|
||||
end
|
||||
|
||||
def add_attachments_with_size_limit(storage_attachments, current_size)
|
||||
def add_attachments_with_size_limit(submitter, storage_attachments, current_size, filename_format = nil)
|
||||
total_size = current_size
|
||||
|
||||
storage_attachments.each do |attachment|
|
||||
@@ -191,7 +199,8 @@ class SubmitterMailer < ApplicationMailer
|
||||
|
||||
break if total_size >= MAX_ATTACHMENTS_SIZE
|
||||
|
||||
attachments[attachment.filename.to_s.tr('"', "'")] = attachment.download
|
||||
filename = Submitters.build_document_filename(submitter, attachment.blob, filename_format)
|
||||
attachments[filename.to_s.tr('"', "'")] = attachment.download
|
||||
end
|
||||
|
||||
total_size
|
||||
|
||||
@@ -43,6 +43,7 @@ class AccountConfig < ApplicationRecord
|
||||
REQUIRE_SIGNING_REASON_KEY = 'require_signing_reason'
|
||||
REUSE_SIGNATURE_KEY = 'reuse_signature'
|
||||
COMBINE_PDF_RESULT_KEY = 'combine_pdf_result_key'
|
||||
DOCUMENT_FILENAME_FORMAT_KEY = 'document_filename_format'
|
||||
|
||||
DEFAULT_VALUES = {
|
||||
SUBMITTER_INVITATION_EMAIL_KEY => lambda {
|
||||
|
||||
@@ -62,8 +62,11 @@ class Submission < ApplicationRecord
|
||||
through: :template, source: :documents_attachments
|
||||
|
||||
scope :active, -> { where(archived_at: nil) }
|
||||
scope :pending, -> { joins(:submitters).where(submitters: { completed_at: nil }).distinct }
|
||||
scope :completed, -> { where.not(id: pending.select(:submission_id)) }
|
||||
scope :pending, -> { joins(:submitters).where(submitters: { completed_at: nil }).group(:id) }
|
||||
scope :completed, lambda {
|
||||
where.not(Submitter.where(Submitter.arel_table[:submission_id].eq(Submission.arel_table[:id])
|
||||
.and(Submitter.arel_table[:completed_at].eq(nil))).select(1).arel.exists)
|
||||
}
|
||||
|
||||
enum :source, {
|
||||
invite: 'invite',
|
||||
|
||||
@@ -164,6 +164,20 @@
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% account_config = AccountConfig.find_or_initialize_by(account: current_account, key: AccountConfig::DOCUMENT_FILENAME_FORMAT_KEY) %>
|
||||
<% if can?(:manage, account_config) %>
|
||||
<%= form_for account_config, url: account_configs_path, method: :post do |f| %>
|
||||
<%= f.hidden_field :key %>
|
||||
<div class="py-2.5">
|
||||
<span>
|
||||
<%= t('document_download_filename_format') %>
|
||||
</span>
|
||||
<div class="mt-3">
|
||||
<%= 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()' %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="<%= local_assigns[:class] %>" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path d="M11.5 21h-5.5a2 2 0 0 1 -2 -2v-12a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v6" />
|
||||
<path d="M16 3v4" />
|
||||
<path d="M8 3v4" />
|
||||
<path d="M4 11h16" />
|
||||
<path d="M15 19l2 2l4 -4" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 464 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="<%= local_assigns[:class] %>" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path d="M4 4h16v2.172a2 2 0 0 1 -.586 1.414l-4.414 4.414v7l-6 2v-8.5l-4.48 -4.928a2 2 0 0 1 -.52 -1.345v-2.227z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 400 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="<%= local_assigns[:class] %>" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" width="24" height="24" stroke-width="2">
|
||||
<path d="M18 6l-12 12"></path>
|
||||
<path d="M6 6l12 12"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 290 B |
@@ -2,5 +2,5 @@
|
||||
---
|
||||
</p>
|
||||
<p>
|
||||
<%= t('sent_using_product_name_free_document_signing_html', product_url: "#{Docuseal::PRODUCT_URL}/start", product_name: Docuseal.product_name) %>
|
||||
<%= t('sent_using_product_name_free_document_signing_html', product_url: "#{Docuseal::PRODUCT_EMAIL_URL}/start", product_name: Docuseal.product_name) %>
|
||||
</p>
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
<% if params[:status].present? %>
|
||||
<input name="status" value="<%= params[:status] %>" class="hidden">
|
||||
<% end %>
|
||||
<% Submissions::Filter::ALLOWED_PARAMS.each do |key| %>
|
||||
<% if params[key].present? %>
|
||||
<input name="<%= key %>" value="<%= params[key] %>" class="hidden">
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% if params[:q].present? %>
|
||||
<div class="relative">
|
||||
<a href="<%= url_for(params.to_unsafe_h.except(:q)) %>" title="<%= t('clear') %>" class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-auto text-neutral text-2xl font-extralight">
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% unless @template.archived_at? %>
|
||||
<% if !@template.archived_at? && !@template.account.archived_at? %>
|
||||
<%= form_for @submitter, url: start_form_path(@template.slug), data: { turbo_frame: :_top }, method: :put, html: { class: 'space-y-4' } do |f| %>
|
||||
<div dir="auto" class="form-control !mt-0">
|
||||
<%= f.label :email, t('email'), class: 'label' %>
|
||||
|
||||
@@ -2,7 +2,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, checked: can_send_emails && !local_assigns.key?(:resend_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] %>
|
||||
<span class="label"><%= local_assigns[:resend_email] ? t('re_send_email') : t('send_email') %></span>
|
||||
<% end %>
|
||||
<div>
|
||||
@@ -26,7 +26,7 @@
|
||||
<%= t('configure_smtp_settings_in_order_to_send_emails_') %>
|
||||
<br>
|
||||
<a class="link font-medium" data-turbo-frame="_top" href="<%= settings_email_index_path %>">
|
||||
<%= t('go_to_smtp_setting') %>
|
||||
<%= t('go_to_smtp_settings') %>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
</div>
|
||||
<% end %>
|
||||
<% elsif @submission.submitters.to_a.size == 1 && !@submission.expired? && !@submission.submitters.to_a.first.declined_at? && !@submission.archived_at? %>
|
||||
<%= render 'shared/clipboard_copy', text: submit_form_url(slug: @submission.submitters.to_a.first.slug), class: 'base-button', icon_class: 'w-6 h-6 text-white', copy_title: t('copy_share_link'), copied_title: t('copied_to_clipboard') %>
|
||||
<%= render 'shared/clipboard_copy', text: submit_form_url(slug: @submission.submitters.to_a.first.slug, host: form_link_host), class: 'base-button', icon_class: 'w-6 h-6 text-white', copy_title: t('copy_share_link'), copied_title: t('copied_to_clipboard') %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,22 +1,31 @@
|
||||
<% filter_params = params.permit(Submissions::Filter::ALLOWED_PARAMS).compact_blank %>
|
||||
<div>
|
||||
<%= link_to root_path do %>
|
||||
←
|
||||
<span><%= t('back_to_active') %></span>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="flex justify-between mb-4 items-center">
|
||||
<div>
|
||||
<h1 class="text-4xl font-bold md:block <%= 'hidden' if params[:q].present? %>"><%= t('submissions') %> <span class="badge badge-outline badge-lg align-middle"><%= t('archived') %></span></h1>
|
||||
<div class="flex flex-col md:flex-row md:items-center mb-4 gap-3">
|
||||
<div class="flex w-full justify-between">
|
||||
<div>
|
||||
<h1 class="text-4xl font-bold md:block <%= 'hidden' if params[:q].present? %>"><%= t('submissions') %> <span class="badge badge-outline badge-lg align-middle"><%= t('archived') %></span></h1>
|
||||
</div>
|
||||
<div>
|
||||
<% if params[:q].present? || @pagy.pages > 1 || filter_params.present? %>
|
||||
<%= render 'shared/search_input', placeholder: "#{t('search')}..." %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col items-end md:flex-row gap-2">
|
||||
<%= render 'submissions_filters/applied_filters', filter_params: %>
|
||||
<%= render 'submissions_filters/filter_button', filter_params: %>
|
||||
</div>
|
||||
<% if params[:q].present? || @pagy.pages > 1 %>
|
||||
<%= render 'shared/search_input', placeholder: "#{t('search')}..." %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% if @pagy.count > 0 %>
|
||||
<div class="space-y-4">
|
||||
<%= render partial: 'templates/submission', collection: @submissions, locals: { with_template: true, archived: true } %>
|
||||
</div>
|
||||
<% elsif params[:q].present? %>
|
||||
<% elsif params[:q].present? || filter_params.present? %>
|
||||
<div class="text-center">
|
||||
<div class="mt-16 text-3xl font-semibold">
|
||||
<%= t('submissions_not_found') %>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<% is_show_tabs = @pagy.count >= 5 || params[:status].present? %>
|
||||
<% filter_params = params.permit(Submissions::Filter::ALLOWED_PARAMS).compact_blank %>
|
||||
<% is_show_tabs = @pagy.count >= 5 || params[:status].present? || filter_params.present? %>
|
||||
<% if Docuseal.demo? %><%= render 'shared/demo_alert' %><% end %>
|
||||
<div class="flex justify-between items-center w-full mb-4">
|
||||
<div class="flex items-center flex-grow min-w-0">
|
||||
@@ -10,7 +11,7 @@
|
||||
</h1>
|
||||
</div>
|
||||
<div class="flex space-x-2">
|
||||
<% if params[:q].present? || @pagy.pages > 1 %>
|
||||
<% if params[:q].present? || @pagy.pages > 1 || filter_params.present? %>
|
||||
<%= render 'shared/search_input' %>
|
||||
<% end %>
|
||||
<% if can?(:create, ::Template) %>
|
||||
@@ -32,25 +33,31 @@
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% if is_show_tabs %>
|
||||
<div class="flex items-center md:items-end flex-col space-y-2 md:space-y-0 md:flex-row md:space-x-2 mb-4">
|
||||
<a href="<%= url_for(params.to_unsafe_h.except(:status)) %>" class="<%= params[:status].blank? ? 'border-neutral-700' : 'border-neutral-300' %> flex h-10 px-2 py-1 text-lg items-center justify-between border text-center text-neutral font-semibold rounded-xl w-full md:w-48 hover:border-neutral-600">
|
||||
<div class="flex items-center space-x-1">
|
||||
<%= svg_icon('list', class: 'w-5 h-5') %>
|
||||
<span class="font-normal"><%= t('all') %></span>
|
||||
</div>
|
||||
</a>
|
||||
<a href="<%= url_for(params.to_unsafe_h.merge(status: :pending)) %>" class="<%= params[:status] == 'pending' ? 'border-neutral-700' : 'border-neutral-300' %> flex h-10 px-2 py-1 text-lg items-center justify-between border text-center text-neutral font-semibold rounded-xl w-full md:w-48 hover:border-neutral-600">
|
||||
<div class="flex items-center space-x-1">
|
||||
<%= svg_icon('clock', class: 'w-5 h-5') %>
|
||||
<span class="font-normal"><%= t('pending') %></span>
|
||||
</div>
|
||||
</a>
|
||||
<a href="<%= url_for(params.to_unsafe_h.merge(status: :completed)) %>" class="<%= params[:status] == 'completed' ? 'border-neutral-700' : 'border-neutral-300' %> flex h-10 px-2 py-1 text-lg items-center justify-between border text-center text-neutral font-semibold rounded-xl w-full md:w-48 hover:border-neutral-600">
|
||||
<div class="flex items-center space-x-1">
|
||||
<%= svg_icon('circle_check', class: 'w-5 h-5') %>
|
||||
<span class="font-normal"><%= t('completed') %></span>
|
||||
</div>
|
||||
</a>
|
||||
<div class="flex items-center flex-col md:flex-row md:flex-wrap gap-2 mb-4">
|
||||
<div class="flex items-center md:items-end flex-col md:flex-row gap-2 w-full md:w-fit">
|
||||
<a href="<%= url_for(params.to_unsafe_h.except(:status)) %>" class="<%= params[:status].blank? ? 'border-neutral-700' : 'border-neutral-300' %> flex h-10 px-2 py-1 text-lg items-center justify-between border text-center text-neutral font-semibold rounded-xl w-full md:w-48 hover:border-neutral-600">
|
||||
<div class="flex items-center space-x-1">
|
||||
<%= svg_icon('list', class: 'w-5 h-5') %>
|
||||
<span class="font-normal"><%= t('all') %></span>
|
||||
</div>
|
||||
</a>
|
||||
<a href="<%= url_for(params.to_unsafe_h.merge(status: :pending)) %>" class="<%= params[:status] == 'pending' ? 'border-neutral-700' : 'border-neutral-300' %> flex h-10 px-2 py-1 text-lg items-center justify-between border text-center text-neutral font-semibold rounded-xl w-full md:w-48 hover:border-neutral-600">
|
||||
<div class="flex items-center space-x-1">
|
||||
<%= svg_icon('clock', class: 'w-5 h-5') %>
|
||||
<span class="font-normal"><%= t('pending') %></span>
|
||||
</div>
|
||||
</a>
|
||||
<a href="<%= url_for(params.to_unsafe_h.merge(status: :completed)) %>" class="<%= params[:status] == 'completed' ? 'border-neutral-700' : 'border-neutral-300' %> flex h-10 px-2 py-1 text-lg items-center justify-between border text-center text-neutral font-semibold rounded-xl w-full md:w-48 hover:border-neutral-600">
|
||||
<div class="flex items-center space-x-1">
|
||||
<%= svg_icon('circle_check', class: 'w-5 h-5') %>
|
||||
<span class="font-normal"><%= t('completed') %></span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex items-end flex-col md:flex-row gap-2 w-full md:w-fit">
|
||||
<%= render 'submissions_filters/applied_filters', filter_params: %>
|
||||
<%= render 'submissions_filters/filter_button', filter_params: %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if @pagy.count > 0 %>
|
||||
@@ -58,10 +65,10 @@
|
||||
<%= render partial: 'templates/submission', collection: @submissions, locals: { with_template: true } %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if params[:q].blank? && params[:status].blank? && @pagy.count < 5 %>
|
||||
<% if params[:q].blank? && params[:status].blank? && filter_params.blank? && @pagy.count < 5 %>
|
||||
<%= render 'templates/dropzone' %>
|
||||
<% end %>
|
||||
<% if @submissions.present? || params[:q].blank? %>
|
||||
<% if @submissions.present? || (params[:q].blank? && filter_params.blank?) %>
|
||||
<% if @pagy.pages > 1 %>
|
||||
<%= render 'shared/pagination', pagy: @pagy, items_name: 'submissions', left_additional_html: view_archived_html %>
|
||||
<% else %>
|
||||
@@ -69,7 +76,7 @@
|
||||
<%= view_archived_html %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% elsif params[:q].present? %>
|
||||
<% elsif params[:q].present? || filter_params.present? %>
|
||||
<div class="text-center">
|
||||
<div class="mt-16 text-3xl font-semibold">
|
||||
<%= t('submissions_not_found') %>
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
<% query_params = params.permit(:q, :status).merge(filter_params) %>
|
||||
<% if query_params[:completed_at_from].present? || query_params[:completed_at_to].present? %>
|
||||
<div class="tooltip tooltip-bottom flex h-10 px-2 py-1 text-lg items-center justify-between border text-center text-neutral font-semibold rounded-xl w-full md:w-34 border-neutral-700" data-tip="<%= t('completed_at') %>">
|
||||
<%= link_to submissions_filter_path('completed_at', query_params.merge(path: url_for, with_remove: true)), data: { turbo_frame: 'modal' }, class: 'flex items-center space-x-1 w-full pr-1 md:max-w-[140px]' do %>
|
||||
<%= svg_icon('calendar_check', class: 'w-5 h-5 shrink-0') %>
|
||||
<span class="flex flex-row md:flex-col font-normal text-left md:text-center md:text-xs">
|
||||
<% if query_params[:completed_at_from] == query_params[:completed_at_to] %>
|
||||
<span><%= l(Date.parse(query_params[:completed_at_from]), locale: current_account.locale) %></span>
|
||||
<% else %>
|
||||
<span><%= query_params[:completed_at_from].present? ? l(Date.parse(query_params[:completed_at_from]), locale: current_account.locale) : '∞' %></span>
|
||||
<span class="px-1 md:px-0 md:hidden">-</span>
|
||||
<span><%= query_params[:completed_at_to].present? ? l(Date.parse(query_params[:completed_at_to]), locale: current_account.locale) : t('today') %></span>
|
||||
<% end %>
|
||||
</span>
|
||||
<% end %>
|
||||
<%= link_to url_for(params.to_unsafe_h.except(:completed_at_from, :completed_at_to)), class: 'rounded-lg ml-1 hover:bg-base-content hover:text-white' do %>
|
||||
<%= svg_icon('x', class: 'w-5 h-5') %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if query_params[:created_at_from].present? || query_params[:created_at_to].present? %>
|
||||
<div class="tooltip tooltip-bottom flex h-10 px-2 py-1 text-lg items-center justify-between border text-center text-neutral font-semibold rounded-xl w-full md:w-34 border-neutral-700" data-tip="<%= t('created_at') %>">
|
||||
<%= link_to submissions_filter_path('created_at', query_params.merge(path: url_for, with_remove: true)), data: { turbo_frame: 'modal' }, class: 'flex items-center space-x-1 w-full pr-1 md:max-w-[140px]' do %>
|
||||
<%= svg_icon('calendar', class: 'w-5 h-5 shrink-0') %>
|
||||
<span class="flex flex-row md:flex-col font-normal text-left md:text-center md:text-xs">
|
||||
<% if query_params[:created_at_from] == query_params[:created_at_to] %>
|
||||
<span><%= l(Date.parse(query_params[:created_at_from]), locale: current_account.locale) %></span>
|
||||
<% else %>
|
||||
<span><%= query_params[:created_at_from].present? ? l(Date.parse(query_params[:created_at_from]), locale: current_account.locale) : '∞' %></span>
|
||||
<span class="px-1 md:px-0 md:hidden">-</span>
|
||||
<span><%= query_params[:created_at_to].present? ? l(Date.parse(query_params[:created_at_to]), locale: current_account.locale) : t('today') %></span>
|
||||
<% end %>
|
||||
</span>
|
||||
<% end %>
|
||||
<%= link_to url_for(params.to_unsafe_h.except(:created_at_to, :created_at_from)), class: 'rounded-lg ml-1 hover:bg-base-content hover:text-white' do %>
|
||||
<%= svg_icon('x', class: 'w-5 h-5') %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if params[:author].present? %>
|
||||
<div class="tooltip tooltip-bottom flex h-10 px-2 py-1 text-lg items-center justify-between border text-center text-neutral font-semibold rounded-xl w-full md:w-34 border-neutral-700" data-tip="<%= t('author') %>">
|
||||
<%= link_to submissions_filter_path('author', query_params.merge(path: url_for, with_remove: true)), data: { turbo_frame: 'modal' }, class: 'flex items-center space-x-1 w-full pr-1 md:max-w-[140px]' do %>
|
||||
<%= svg_icon('user', class: 'w-5 h-5 shrink-0') %>
|
||||
<span class="font-normal truncate"><%= current_account.users.accessible_by(current_ability).where(account: current_account).find_by(email: params[:author])&.full_name || 'NA' %></span>
|
||||
<% end %>
|
||||
<%= link_to url_for(params.to_unsafe_h.except(:author)), class: 'rounded-lg ml-1 hover:bg-base-content hover:text-white' do %>
|
||||
<%= svg_icon('x', class: 'w-5 h-5') %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
@@ -0,0 +1,39 @@
|
||||
<% current_time = Time.current.in_time_zone(current_account.timezone) %>
|
||||
<% week_start = TimeUtils.timezone_abbr(current_account.timezone, current_time).in?(TimeUtils::US_TIMEZONES) ? :sunday : :monday %>
|
||||
<div class="flex flex-wrap gap-2 mt-4 md:mt-2">
|
||||
<set-date-button data-from-value="<%= current_time.to_date %>" data-to-value="<%= current_time.to_date %>" data-from-id="date_from" data-to-id="date_to">
|
||||
<button class="btn btn-xs btn-primary font-medium normal-case" data-turbo="false" type="button">
|
||||
<%= t('today') %>
|
||||
</button>
|
||||
</set-date-button>
|
||||
<set-date-button data-from-value="<%= (current_time - 1.day).to_date %>" data-to-value="<%= (current_time - 1.day).to_date %>" data-from-id="date_from" data-to-id="date_to">
|
||||
<button class="btn btn-xs btn-primary font-medium normal-case" data-turbo="false" type="button">
|
||||
<%= t('yesterday') %>
|
||||
</button>
|
||||
</set-date-button>
|
||||
<set-date-button data-from-value="<%= current_time.beginning_of_week(week_start).to_date %>" data-to-value="<%= current_time.end_of_week(week_start).to_date %>" data-from-id="date_from" data-to-id="date_to">
|
||||
<button class="btn btn-xs btn-primary font-medium normal-case" data-turbo="false" type="button">
|
||||
<%= t('this_week') %>
|
||||
</button>
|
||||
</set-date-button>
|
||||
<set-date-button data-from-value="<%= (current_time - 1.week).beginning_of_week(week_start).to_date %>" data-to-value="<%= (current_time - 1.week).end_of_week(week_start).to_date %>" data-from-id="date_from" data-to-id="date_to">
|
||||
<button class="btn btn-xs btn-primary font-medium normal-case" data-turbo="false" type="button">
|
||||
<%= t('last_week') %>
|
||||
</button>
|
||||
</set-date-button>
|
||||
<set-date-button data-from-value="<%= current_time.beginning_of_month.to_date %>" data-to-value="<%= current_time.end_of_month.to_date %>" data-from-id="date_from" data-to-id="date_to">
|
||||
<button class="btn btn-xs btn-primary font-medium normal-case" data-turbo="false" type="button">
|
||||
<%= t('this_month') %>
|
||||
</button>
|
||||
</set-date-button>
|
||||
<set-date-button data-from-value="<%= (current_time - 1.month).beginning_of_month.to_date %>" data-to-value="<%= (current_time - 1.month).end_of_month.to_date %>" data-from-id="date_from" data-to-id="date_to">
|
||||
<button class="btn btn-xs btn-primary font-medium normal-case" data-turbo="false" type="button">
|
||||
<%= t('last_month') %>
|
||||
</button>
|
||||
</set-date-button>
|
||||
<set-date-button data-from-value="<%= current_time.beginning_of_year.to_date %>" data-to-value="<%= current_time.end_of_year.to_date %>" data-from-id="date_from" data-to-id="date_to">
|
||||
<button class="btn btn-xs btn-primary font-medium normal-case" data-turbo="false" type="button">
|
||||
<%= t('this_year') %>
|
||||
</button>
|
||||
</set-date-button>
|
||||
</div>
|
||||
@@ -0,0 +1,27 @@
|
||||
<% query_params = params.permit(:q, :status).merge(filter_params) %>
|
||||
<div class="dropdown dropdown-end">
|
||||
<label tabindex="0" class="cursor-pointer flex h-10 px-3 py-1 space-x-1 text-lg items-center justify-between border text-center text-neutral rounded-xl border-neutral-300 hover:border-neutral-700">
|
||||
<%= svg_icon('filter', class: 'w-5 h-5 flex-shrink-0 stroke-2') %>
|
||||
<span class="<%= filter_params.present? ? 'md:hidden' : '' %>">Filter</span>
|
||||
</label>
|
||||
<ul tabindex="0" class="z-10 dropdown-content p-2 mt-2 shadow menu text-base bg-base-100 rounded-box min-w-[180px] text-right">
|
||||
<li class="flex">
|
||||
<%= link_to submissions_filter_path('completed_at', query_params.merge(path: url_for)), data: { turbo_frame: 'modal' } do %>
|
||||
<%= svg_icon('calendar_check', class: 'w-5 h-5 flex-shrink-0 stroke-2') %>
|
||||
<span><%= t('completed_at') %></span>
|
||||
<% end %>
|
||||
</li>
|
||||
<li class="flex">
|
||||
<%= link_to submissions_filter_path('created_at', query_params.merge(path: url_for)), data: { turbo_frame: 'modal' } do %>
|
||||
<%= svg_icon('calendar', class: 'w-5 h-5 flex-shrink-0 stroke-2') %>
|
||||
<span><%= t('created_at') %></span>
|
||||
<% end %>
|
||||
</li>
|
||||
<li class="flex">
|
||||
<%= link_to submissions_filter_path('author', query_params.merge(path: url_for)), data: { turbo_frame: 'modal' } do %>
|
||||
<%= svg_icon('user', class: 'w-5 h-5 flex-shrink-0 stroke-2') %>
|
||||
<span><%= t('author') %></span>
|
||||
<% end %>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -0,0 +1,18 @@
|
||||
<%= render 'shared/turbo_modal', title: local_assigns[:title] do %>
|
||||
<%= form_for '', url: params[:path], method: :get, data: { turbo_frame: :_top }, html: { autocomplete: :off } do |f| %>
|
||||
<%= hidden_field_tag :status, params[:status] if params[:status].present? %>
|
||||
<%= hidden_field_tag :q, params[:q] if params[:q].present? %>
|
||||
<% local_assigns[:default_params].each do |key, value| %>
|
||||
<%= hidden_field_tag(key, value) if value.present? %>
|
||||
<% end %>
|
||||
<%= yield %>
|
||||
<div class="form-control mt-4">
|
||||
<%= f.button button_title(title: t('apply'), disabled_with: t('applying')), name: nil, class: 'base-button' %>
|
||||
</div>
|
||||
<% if params[:with_remove] %>
|
||||
<div class="text-center w-full mt-4">
|
||||
<%= link_to t('remove_filter'), "#{params[:path]}?#{params.to_unsafe_h.slice(:q, :status).merge(local_assigns[:default_params]).to_query}", class: 'link', data: { turbo_frame: :_top } %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
@@ -0,0 +1,7 @@
|
||||
<%= render 'filter_modal', title: t('author'), default_params: params.permit(*(Submissions::Filter::ALLOWED_PARAMS - ['author'])) do %>
|
||||
<div class="space-y-2">
|
||||
<div class="form-control mt-6">
|
||||
<%= select_tag :author, options_for_select(current_account.users.accessible_by(current_ability).where.not(role: :integration).where(account: current_account).map { |u| [u.full_name, u.email] }, params[:author].presence || current_user.email), required: true, class: 'base-select' %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
@@ -0,0 +1,15 @@
|
||||
<%= render 'filter_modal', title: t('completed_at'), default_params: params.permit(*(Submissions::Filter::ALLOWED_PARAMS - %w[completed_at_to completed_at_from])) do %>
|
||||
<div class="space-y-3">
|
||||
<div class="flex flex-col md:flex-row gap-2">
|
||||
<div class="form-control w-full">
|
||||
<%= label_tag 'completed_at_from', t('from'), for: 'date_from', class: 'label text-sm' %>
|
||||
<%= date_field_tag 'completed_at_from', params[:completed_at_from], id: 'date_from', class: 'base-input !h-10', autocomplete: 'off' %>
|
||||
</div>
|
||||
<div class="form-control w-full">
|
||||
<%= label_tag 'completed_at_to', t('to'), for: 'date_to', class: 'label text-sm' %>
|
||||
<%= date_field_tag 'completed_at_to', params[:completed_at_to], id: 'date_to', class: 'base-input !h-10', autocomplete: 'off' %>
|
||||
</div>
|
||||
</div>
|
||||
<%= render 'date_buttons' %>
|
||||
</div>
|
||||
<% end %>
|
||||
@@ -0,0 +1,15 @@
|
||||
<%= render 'filter_modal', title: t('created_at'), default_params: params.permit(*(Submissions::Filter::ALLOWED_PARAMS - %w[created_at_to created_at_from])) do %>
|
||||
<div class="space-y-3">
|
||||
<div class="flex flex-col md:flex-row gap-2">
|
||||
<div class="form-control w-full">
|
||||
<%= label_tag 'created_at_from', t('from'), for: 'date_from', class: 'label text-sm' %>
|
||||
<%= date_field_tag 'created_at_from', params[:created_at_from], id: 'date_from', class: 'base-input !h-10', autocomplete: 'off' %>
|
||||
</div>
|
||||
<div class="form-control w-full">
|
||||
<%= label_tag 'created_at_to', t('to'), for: 'date_to', class: 'label text-sm' %>
|
||||
<%= date_field_tag 'created_at_to', params[:created_at_to], id: 'date_to', class: 'base-input !h-10', autocomplete: 'off' %>
|
||||
</div>
|
||||
</div>
|
||||
<%= render 'date_buttons' %>
|
||||
</div>
|
||||
<% end %>
|
||||
@@ -7,7 +7,7 @@
|
||||
<% else %>
|
||||
<p><%= t('hi_there') %>,</p>
|
||||
<p><%= I18n.t(@submitter.with_signature_fields? ? :you_have_been_invited_to_sign_the_name : :you_have_been_invited_to_submit_the_name_form, name: @submitter.submission.template.name) %></p>
|
||||
<p><%= link_to I18n.t(@submitter.with_signature_fields? ? :review_and_sign : :review_and_submit), submit_form_url(slug: @submitter.slug, t: SubmissionEvents.build_tracking_param(@submitter, 'click_email')) %></p>
|
||||
<p><%= link_to I18n.t(@submitter.with_signature_fields? ? :review_and_sign : :review_and_submit), submit_form_url(slug: @submitter.slug, t: SubmissionEvents.build_tracking_param(@submitter, 'click_email'), host: ENV.fetch('EMAIL_HOST', Docuseal.default_url_options[:host])) %></p>
|
||||
<p><%= t('please_contact_us_by_replying_to_this_email_if_you_didn_t_request_this') %></p>
|
||||
<p>
|
||||
<%= t('thanks') %>,<br><%= @current_account.name %>
|
||||
|
||||
@@ -92,7 +92,7 @@
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="flex-1 md:flex-none">
|
||||
<%= render 'shared/clipboard_copy', text: submit_form_url(slug: submitter.slug), class: 'btn btn-sm btn-neutral text-white md:w-36 flex z-[1]', icon_class: 'w-6 h-6 text-white', copy_title: t('copy_link').length < 10 ? t('copy_link') : t('copy'), copy_title_md: t('copy'), copied_title_md: t('copied') %>
|
||||
<%= render 'shared/clipboard_copy', text: submit_form_url(slug: submitter.slug, host: form_link_host), class: 'btn btn-sm btn-neutral text-white md:w-36 flex z-[1]', icon_class: 'w-6 h-6 text-white', copy_title: t('copy_link').length < 10 ? t('copy_link') : t('copy'), copy_title_md: t('copy'), copied_title_md: t('copied') %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
@@ -176,7 +176,7 @@
|
||||
</span>
|
||||
</a>
|
||||
<% else %>
|
||||
<%= render 'shared/clipboard_copy', text: submit_form_url(slug: submitter.slug), class: 'absolute md:relative top-0 right-0 btn btn-xs text-xs btn-neutral text-white w-28 md:w-36 flex z-[1]', icon_class: 'w-4 h-4 text-white', copy_title: t('copy_link'), copy_title_md: t('copy_link').length < 10 ? t('copy_link') : t('copy'), copied_title_md: t('copied') %>
|
||||
<%= render 'shared/clipboard_copy', text: submit_form_url(slug: submitter.slug, host: form_link_host), class: 'absolute md:relative top-0 right-0 btn btn-xs text-xs btn-neutral text-white w-28 md:w-36 flex z-[1]', icon_class: 'w-4 h-4 text-white', copy_title: t('copy_link'), copy_title_md: t('copy_link').length < 10 ? t('copy_link') : t('copy'), copied_title_md: t('copied') %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div class="flex flex-col items-start md:flex-row space-y-2 md:space-y-0 md:space-x-2 md:justify-between md:items-start mb-6 md:mb-3">
|
||||
<div class="relative flex items-start justify-between w-full space-x-0">
|
||||
<div>
|
||||
<h1 class="text-3xl md:text-4xl font-semibold" style="overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;">
|
||||
<h1 class="text-3xl md:text-[2em] font-semibold" style="overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;">
|
||||
<% template.name.split(/(_)/).each do |item| %><%= item %><wbr><% end %>
|
||||
<% if template.archived_at? %>
|
||||
<span class="ml-1 badge badge-outline badge-lg align-middle"><%= t('archived') %></span>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<%= render 'title', template: @template %>
|
||||
<% is_show_tabs = @pagy.pages > 1 || params[:status].present? %>
|
||||
<% if !@pagy.count.zero? || params[:q].present? || params[:status].present? %>
|
||||
<% filter_params = params.permit(Submissions::Filter::ALLOWED_PARAMS).compact_blank %>
|
||||
<% is_show_tabs = @pagy.pages > 1 || params[:q].present? || params[:status].present? || filter_params.present? %>
|
||||
<% if !@pagy.count.zero? || params[:q].present? || params[:status].present? || filter_params.present? %>
|
||||
<div class="<%= is_show_tabs ? 'mb-4' : 'mb-6' %>">
|
||||
<div class="flex justify-between items-center md:items-end">
|
||||
<div>
|
||||
@@ -9,7 +10,7 @@
|
||||
</h2>
|
||||
</div>
|
||||
<div class="flex justify-end space-x-2">
|
||||
<% if params[:q].present? || params[:status].present? || @pagy.pages > 1 %>
|
||||
<% if params[:q].present? || params[:status].present? || filter_params.present? || @pagy.pages > 1 %>
|
||||
<%= render 'shared/search_input', title_selector: 'h2' %>
|
||||
<% end %>
|
||||
<%= link_to new_template_submissions_export_path(@template), class: 'hidden md:flex btn btn-ghost text-base', data: { turbo_frame: 'modal' } do %>
|
||||
@@ -27,34 +28,40 @@
|
||||
</div>
|
||||
<% end %>
|
||||
<% if is_show_tabs %>
|
||||
<div class="flex items-center md:items-end flex-col space-y-2 md:space-y-0 md:flex-row md:space-x-2 mb-4">
|
||||
<a href="<%= url_for(params.to_unsafe_h.except(:status)) %>" class="<%= params[:status].blank? ? 'border-neutral-700' : 'border-neutral-300' %> flex h-10 px-2 py-1 text-lg items-center justify-between border text-center text-neutral font-semibold rounded-xl w-full md:w-48 hover:border-neutral-600">
|
||||
<div class="flex items-center space-x-1">
|
||||
<%= svg_icon('list', class: 'w-5 h-5') %>
|
||||
<span class="font-normal"><%= t('all') %></span>
|
||||
</div>
|
||||
<div class="badge badge-neutral badge-outline font-medium">
|
||||
<%= params[:status].blank? ? @pagy.count : @base_submissions.count %>
|
||||
</div>
|
||||
</a>
|
||||
<a href="<%= url_for(params.to_unsafe_h.merge(status: :pending)) %>" class="<%= params[:status] == 'pending' ? 'border-neutral-700' : 'border-neutral-300' %> flex h-10 px-2 py-1 text-lg items-center justify-between border text-center text-neutral font-semibold rounded-xl w-full md:w-48 hover:border-neutral-600">
|
||||
<div class="flex items-center space-x-1">
|
||||
<%= svg_icon('clock', class: 'w-5 h-5') %>
|
||||
<span class="font-normal"><%= t('pending') %></span>
|
||||
</div>
|
||||
<div class="badge badge-neutral badge-outline font-medium">
|
||||
<%= params[:status] == 'pending' ? @pagy.count : @base_submissions.pending.count %>
|
||||
</div>
|
||||
</a>
|
||||
<a href="<%= url_for(params.to_unsafe_h.merge(status: :completed)) %>" class="<%= params[:status] == 'completed' ? 'border-neutral-700' : 'border-neutral-300' %> flex h-10 px-2 py-1 text-lg items-center justify-between border text-center text-neutral font-semibold rounded-xl w-full md:w-48 hover:border-neutral-600">
|
||||
<div class="flex items-center space-x-1">
|
||||
<%= svg_icon('circle_check', class: 'w-5 h-5') %>
|
||||
<span class="font-normal"><%= t('completed') %></span>
|
||||
</div>
|
||||
<div class="badge badge-neutral badge-outline font-medium">
|
||||
<%= params[:status] == 'completed' ? @pagy.count : @base_submissions.completed.count %>
|
||||
</div>
|
||||
</a>
|
||||
<div class="flex items-center flex-col md:flex-row md:flex-wrap gap-2 mb-4">
|
||||
<div class="flex items-center md:items-end flex-col md:flex-row gap-2 w-full md:w-fit">
|
||||
<a href="<%= url_for(params.to_unsafe_h.except(:status)) %>" class="<%= params[:status].blank? ? 'border-neutral-700' : 'border-neutral-300' %> flex h-10 px-2 py-1 text-lg items-center justify-between border text-center text-neutral font-semibold rounded-xl w-full md:w-48 hover:border-neutral-700">
|
||||
<div class="flex items-center space-x-1">
|
||||
<%= svg_icon('list', class: 'w-5 h-5') %>
|
||||
<span class="font-normal"><%= t('all') %></span>
|
||||
</div>
|
||||
<div class="badge badge-neutral badge-outline font-medium">
|
||||
<%= params[:status].blank? && filter_params.blank? ? @pagy.count : @base_submissions.unscope(:group, :order).count %>
|
||||
</div>
|
||||
</a>
|
||||
<a href="<%= url_for(params.to_unsafe_h.merge(status: :pending)) %>" class="<%= params[:status] == 'pending' ? 'border-neutral-700' : 'border-neutral-300' %> flex h-10 px-2 py-1 text-lg items-center justify-between border text-center text-neutral font-semibold rounded-xl w-full md:w-48 hover:border-neutral-700">
|
||||
<div class="flex items-center space-x-1">
|
||||
<%= svg_icon('clock', class: 'w-5 h-5') %>
|
||||
<span class="font-normal"><%= t('pending') %></span>
|
||||
</div>
|
||||
<div class="badge badge-neutral badge-outline font-medium">
|
||||
<%= params[:status] == 'pending' && filter_params.blank? ? @pagy.count : @base_submissions.pending.unscope(:group, :order).count %>
|
||||
</div>
|
||||
</a>
|
||||
<a href="<%= url_for(params.to_unsafe_h.merge(status: :completed)) %>" class="<%= params[:status] == 'completed' ? 'border-neutral-700' : 'border-neutral-300' %> flex h-10 px-2 py-1 text-lg items-center justify-between border text-center text-neutral font-semibold rounded-xl w-full md:w-48 hover:border-neutral-700">
|
||||
<div class="flex items-center space-x-1">
|
||||
<%= svg_icon('circle_check', class: 'w-5 h-5') %>
|
||||
<span class="font-normal"><%= t('completed') %></span>
|
||||
</div>
|
||||
<div class="badge badge-neutral badge-outline font-medium">
|
||||
<%= params[:status] == 'completed' && filter_params.blank? ? @pagy.count : @base_submissions.completed.unscope(:group, :order).count %>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex items-end flex-col md:flex-row gap-2 w-full md:w-fit">
|
||||
<%= render 'submissions_filters/applied_filters', filter_params: %>
|
||||
<%= render 'submissions_filters/filter_button', filter_params: %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if @submissions.present? %>
|
||||
@@ -65,31 +72,33 @@
|
||||
<div class="card bg-base-200">
|
||||
<div class="card-body text-center px-4 py-16">
|
||||
<div class="max-w-lg mx-auto">
|
||||
<p class="text-3xl font-bold text-base-content mb-4">
|
||||
<p class="text-3xl font-bold text-base-content">
|
||||
<%= t('there_are_no_submissions') %>
|
||||
</p>
|
||||
<% if @template.archived_at.blank? && params[:q].blank? %>
|
||||
<p><%= t('send_an_invitation_to_fill_and_complete_the_form') %></p>
|
||||
<div class="space-y-2 flex flex-col">
|
||||
<% if can?(:create, Submission) %>
|
||||
<%= link_to new_template_submission_path(@template, with_link: true), class: 'base-button mt-6', data: { turbo_frame: 'modal' } do %>
|
||||
<%= svg_icon('plus', class: 'w-6 h-6 stroke-2') %>
|
||||
<span class="mr-1"><%= t('send_to_recipients') %></span>
|
||||
<% if @template.archived_at.blank? && params[:q].blank? && filter_params.blank? %>
|
||||
<div class="mt-4">
|
||||
<p><%= t('send_an_invitation_to_fill_and_complete_the_form') %></p>
|
||||
<div class="space-y-2 flex flex-col">
|
||||
<% if can?(:create, Submission) %>
|
||||
<%= link_to new_template_submission_path(@template, with_link: true), class: 'base-button mt-6', data: { turbo_frame: 'modal' } do %>
|
||||
<%= svg_icon('plus', class: 'w-6 h-6 stroke-2') %>
|
||||
<span class="mr-1"><%= t('send_to_recipients') %></span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% if @template.submitters.size == 1 %>
|
||||
<%= link_to start_form_url(slug: @template.slug), class: 'white-button mt-6', target: '_blank', rel: 'noopener' do %>
|
||||
<%= svg_icon('writing', class: 'w-6 h-6') %>
|
||||
<span class="mr-1"><%= t('sign_it_yourself') %></span>
|
||||
<% if @template.submitters.size == 1 %>
|
||||
<%= link_to start_form_url(slug: @template.slug), class: 'white-button mt-6', target: '_blank', rel: 'noopener' do %>
|
||||
<%= svg_icon('writing', class: 'w-6 h-6') %>
|
||||
<span class="mr-1"><%= t('sign_it_yourself') %></span>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<%= link_to new_template_submission_path(@template, selfsign: true), class: 'white-button mt-6', data: { turbo_frame: 'modal' } do %>
|
||||
<%= svg_icon('writing', class: 'w-6 h-6') %>
|
||||
<span class="mr-1"><%= t('sign_it_yourself') %></span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<%= link_to new_template_submission_path(@template, selfsign: true), class: 'white-button mt-6', data: { turbo_frame: 'modal' } do %>
|
||||
<%= svg_icon('writing', class: 'w-6 h-6') %>
|
||||
<span class="mr-1"><%= t('sign_it_yourself') %></span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
<% filter_params = params.permit(Submissions::Filter::ALLOWED_PARAMS).compact_blank %>
|
||||
<% with_filters = @pagy.pages > 1 || params[:q].present? || filter_params.present? %>
|
||||
<%= render 'templates/title', template: @template %>
|
||||
<div>
|
||||
<%= link_to template_path(@template) do %>
|
||||
@@ -5,23 +7,33 @@
|
||||
<span><%= t('back_to_active') %></span>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="flex justify-between mb-6 md:items-end flex-col md:flex-row">
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold md:block"><%= t('submissions') %> <span class="badge badge-outline badge-lg align-middle"><%= t('archived') %></span></h1>
|
||||
</div>
|
||||
<div class="flex space-x-2 mt-3 md:mt-0 justify-end">
|
||||
<%= render 'shared/search_input' %>
|
||||
<%= link_to new_template_submissions_export_path(@template), class: 'order-3 md:order-1 btn btn-ghost text-base', data: { turbo_frame: 'modal' } do %>
|
||||
<%= svg_icon('download', class: 'w-6 h-6 stroke-2') %>
|
||||
<span><%= t('export') %></span>
|
||||
<% end %>
|
||||
<div class="flex flex-col md:flex-row md:items-center mb-6 gap-3">
|
||||
<div class="flex w-full justify-between md:items-end items-center">
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold md:block"><%= t('submissions') %> <span class="badge badge-outline badge-lg align-middle"><%= t('archived') %></span></h1>
|
||||
</div>
|
||||
<div class="flex space-x-2 justify-end">
|
||||
<% if with_filters %>
|
||||
<%= render 'shared/search_input' %>
|
||||
<% end %>
|
||||
<%= link_to new_template_submissions_export_path(@template), class: 'btn btn-ghost text-base', data: { turbo_frame: 'modal' } do %>
|
||||
<%= svg_icon('download', class: 'w-6 h-6 stroke-2') %>
|
||||
<span><%= t('export') %></span>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<% if with_filters %>
|
||||
<div class="flex flex-col items-end md:flex-row gap-2">
|
||||
<%= render 'submissions_filters/applied_filters', filter_params: %>
|
||||
<%= render 'submissions_filters/filter_button', filter_params: %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% if @pagy.count > 0 %>
|
||||
<div class="space-y-4">
|
||||
<%= render partial: 'templates/submission', collection: @submissions, locals: { template: @template, archived: true } %>
|
||||
</div>
|
||||
<% elsif params[:q].present? %>
|
||||
<% elsif params[:q].present? || filter_params.present? %>
|
||||
<div class="text-center">
|
||||
<div class="mt-16 text-3xl font-semibold">
|
||||
<%= t('submissions_not_found') %>
|
||||
|
||||
@@ -13,10 +13,11 @@ end
|
||||
ActiveSupport.on_load(:active_storage_blob) do
|
||||
attribute :uuid, :string, default: -> { SecureRandom.uuid }
|
||||
|
||||
def self.proxy_url(blob, expires_at: nil)
|
||||
def self.proxy_url(blob, expires_at: nil, filename: nil, host: nil)
|
||||
Rails.application.routes.url_helpers.blobs_proxy_url(
|
||||
signed_uuid: blob.signed_uuid(expires_at:), filename: blob.filename,
|
||||
**Docuseal.default_url_options
|
||||
signed_uuid: blob.signed_uuid(expires_at:), filename: filename || blob.filename,
|
||||
**Docuseal.default_url_options,
|
||||
**{ host: }.compact
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
+97
-7
@@ -22,6 +22,9 @@ en: &en
|
||||
thanks: Thanks
|
||||
unarchive: Unarchive
|
||||
first_party: 'First Party'
|
||||
remove_filter: Remove filter
|
||||
document_download_filename_format: Document download filename format
|
||||
document_name: Document Name
|
||||
docuseal_trusted_signature: DocuSeal Trusted Signature
|
||||
hello_name: Hello %{name}
|
||||
you_are_invited_to_product_name: You are invited to %{product_name}
|
||||
@@ -344,7 +347,7 @@ en: &en
|
||||
edit_message: Edit message
|
||||
smtp_not_configured: SMTP not Configured
|
||||
configure_smtp_settings_in_order_to_send_emails_: 'Configure SMTP settings in order to send emails:'
|
||||
go_to_smtp_setting: Go to SMTP setting
|
||||
go_to_smtp_settings: Go to SMTP settings
|
||||
save_as_default_template_message: Save as default template message
|
||||
re_send_sms: Re-send SMS
|
||||
send_sms: Send SMS
|
||||
@@ -619,7 +622,7 @@ en: &en
|
||||
verified: Verified
|
||||
unverified: Unverified
|
||||
document: Document
|
||||
completed_at: Completed At
|
||||
completed_at: Completed at
|
||||
edit_recipient: Edit Recipient
|
||||
update_recipient: Update Recipient
|
||||
use_international_format_1xxx_: 'Use internatioanl format: +1xxx...'
|
||||
@@ -631,6 +634,18 @@ en: &en
|
||||
key: Key
|
||||
value: Value
|
||||
webhook_secret: Webhook Secret
|
||||
author: Author
|
||||
to: To
|
||||
created_at: Created at
|
||||
apply: Apply
|
||||
applying: Applying
|
||||
today: Today
|
||||
yesterday: Yesterday
|
||||
this_week: This week
|
||||
last_week: Last week
|
||||
this_month: This month
|
||||
last_month: Last month
|
||||
this_year: This year
|
||||
submission_event_names:
|
||||
send_email_to_html: '<b>Email sent</b> to %{submitter_name}'
|
||||
send_reminder_email_to_html: '<b>Reminder email sent</b> to %{submitter_name}'
|
||||
@@ -667,6 +682,9 @@ en: &en
|
||||
read: Read your data
|
||||
|
||||
es: &es
|
||||
remove_filter: Eliminar filtro
|
||||
document_download_filename_format: Formato del nombre del archivo de descarga del documento
|
||||
document_name: Nombre del documento
|
||||
unarchive: Desarchivar
|
||||
awaiting_completion_by_the_other_party: "Esperando la finalización por la otra parte"
|
||||
enforce_recipients_order: 'Hacer cumplir el orden de los destinatarios'
|
||||
@@ -993,7 +1011,7 @@ es: &es
|
||||
edit_message: Editar mensaje
|
||||
smtp_not_configured: SMTP no configurado
|
||||
configure_smtp_settings_in_order_to_send_emails_: 'Configura los ajustes de SMTP para enviar correos electrónicos:'
|
||||
go_to_smtp_setting: Ir a la configuración de SMTP
|
||||
go_to_smtp_settings: Ir a la configuración de SMTP
|
||||
save_as_default_template_message: Guardar como mensaje de plantilla predeterminado
|
||||
re_send_sms: Reenviar SMS
|
||||
send_sms: Enviar SMS
|
||||
@@ -1280,6 +1298,18 @@ es: &es
|
||||
key: Clave
|
||||
value: Valor
|
||||
webhook_secret: Secreto del Webhook
|
||||
author: Autor
|
||||
to: A
|
||||
created_at: Creado el
|
||||
apply: Aplicar
|
||||
applying: Aplicando
|
||||
today: Hoy
|
||||
yesterday: Ayer
|
||||
this_week: Esta Semana
|
||||
last_week: La Semana Pasada
|
||||
this_month: Este Mes
|
||||
last_month: El Mes Pasado
|
||||
this_year: Este Año
|
||||
submission_event_names:
|
||||
send_email_to_html: '<b>Correo electrónico enviado</b> a %{submitter_name}'
|
||||
send_reminder_email_to_html: '<b>Correo de recordatorio enviado</b> a %{submitter_name}'
|
||||
@@ -1316,6 +1346,9 @@ es: &es
|
||||
read: Leer tus datos
|
||||
|
||||
it: &it
|
||||
remove_filter: Rimuovi filtro
|
||||
document_download_filename_format: Formato del nome file scaricato
|
||||
document_name: Nome del Documento
|
||||
unarchive: Ripristina
|
||||
awaiting_completion_by_the_other_party: "In attesa di completamento da parte dell'altra parte"
|
||||
enforce_recipients_order: Aplicar el orden de los destinatarios
|
||||
@@ -1642,7 +1675,7 @@ it: &it
|
||||
edit_message: Modifica messaggio
|
||||
smtp_not_configured: SMTP non configurato
|
||||
configure_smtp_settings_in_order_to_send_emails_: 'Configura le impostazioni SMTP per inviare email:'
|
||||
go_to_smtp_setting: Vai alle impostazioni SMTP
|
||||
go_to_smtp_settings: Vai alle impostazioni SMTP
|
||||
save_as_default_template_message: Salva come messaggio modello predefinito
|
||||
re_send_sms: Reinvio SMS
|
||||
send_sms: Invia SMS
|
||||
@@ -1929,6 +1962,18 @@ it: &it
|
||||
key: Chiave
|
||||
value: Valore
|
||||
webhook_secret: Segreto del Webhook
|
||||
author: Autore
|
||||
to: A
|
||||
created_at: Creato il
|
||||
apply: Applica
|
||||
applying: Applicazione
|
||||
today: Oggi
|
||||
yesterday: Ieri
|
||||
this_week: Questa Settimana
|
||||
last_week: Settimana Scorsa
|
||||
this_month: Questo Mese
|
||||
last_month: Mese Scorso
|
||||
this_year: "Quest'Anno"
|
||||
submission_event_names:
|
||||
send_email_to_html: '<b>E-mail inviato</b> a %{submitter_name}'
|
||||
send_reminder_email_to_html: '<b>E-mail di promemoria inviato</b> a %{submitter_name}'
|
||||
@@ -1965,6 +2010,9 @@ it: &it
|
||||
read: Leggi i tuoi dati
|
||||
|
||||
fr: &fr
|
||||
remove_filter: Supprimer le filtre
|
||||
document_download_filename_format: Format du nom de fichier du téléchargement de document
|
||||
document_name: Nom du document
|
||||
unarchive: Désarchiver
|
||||
awaiting_completion_by_the_other_party: "En attente de la complétion par l'autre partie"
|
||||
enforce_recipients_order: "Respecter l'ordre des destinataires"
|
||||
@@ -2292,7 +2340,7 @@ fr: &fr
|
||||
edit_message: Modifier le message
|
||||
smtp_not_configured: SMTP non configuré
|
||||
configure_smtp_settings_in_order_to_send_emails_: 'Configurez les paramètres SMTP pour envoyer des e-mails:'
|
||||
go_to_smtp_setting: Aller aux paramètres SMTP
|
||||
go_to_smtp_settings: Aller aux paramètres SMTP
|
||||
save_as_default_template_message: Enregistrer comme modèle de message par défaut
|
||||
re_send_sms: Renvoyer le SMS
|
||||
send_sms: Envoyer SMS
|
||||
@@ -2579,6 +2627,18 @@ fr: &fr
|
||||
key: Clé
|
||||
value: Valeur
|
||||
webhook_secret: Secret du Webhook
|
||||
author: Auteur
|
||||
to: À
|
||||
created_at: Créé le
|
||||
apply: Appliquer
|
||||
applying: Application en cours
|
||||
today: "Aujourd'hui"
|
||||
yesterday: Hier
|
||||
this_week: Cette Semaine
|
||||
last_week: La Semaine Dernière
|
||||
this_month: Ce Mois-ci
|
||||
last_month: Le Mois Dernier
|
||||
this_year: Cette Année
|
||||
submission_event_names:
|
||||
send_email_to_html: '<b>E-mail envoyé</b> à %{submitter_name}'
|
||||
send_reminder_email_to_html: '<b>E-mail de rappel envoyé</b> à %{submitter_name}'
|
||||
@@ -2615,6 +2675,9 @@ fr: &fr
|
||||
read: Lire vos données
|
||||
|
||||
pt: &pt
|
||||
remove_filter: Remover filtro
|
||||
document_download_filename_format: Formato do nome do arquivo de download do documento
|
||||
document_name: Nome do documento
|
||||
unarchive: Desarquivar
|
||||
awaiting_completion_by_the_other_party: "Aguardando a conclusão pela outra parte"
|
||||
enforce_recipients_order: 'Forçar a ordem dos recipientes'
|
||||
@@ -2941,7 +3004,7 @@ pt: &pt
|
||||
edit_message: Editar mensagem
|
||||
smtp_not_configured: SMTP não configurado
|
||||
configure_smtp_settings_in_order_to_send_emails_: 'Configure as configurações de SMTP para enviar e-mails:'
|
||||
go_to_smtp_setting: Ir para a configuração de SMTP
|
||||
go_to_smtp_settings: Ir para a configuração de SMTP
|
||||
save_as_default_template_message: Salvar como mensagem de modelo padrão
|
||||
re_send_sms: Reenviar SMS
|
||||
send_sms: Enviar SMS
|
||||
@@ -3228,6 +3291,18 @@ pt: &pt
|
||||
key: Chave
|
||||
value: Valor
|
||||
webhook_secret: Segredo do Webhook
|
||||
author: Autor
|
||||
to: Para
|
||||
created_at: Criado em
|
||||
apply: Aplicar
|
||||
applying: Aplicando
|
||||
today: Hoje
|
||||
yesterday: Ontem
|
||||
this_week: Esta Semana
|
||||
last_week: Semana Passada
|
||||
this_month: Este Mês
|
||||
last_month: Mês Passado
|
||||
this_year: Este Ano
|
||||
submission_event_names:
|
||||
send_email_to_html: '<b>E-mail enviado</b> para %{submitter_name}'
|
||||
send_reminder_email_to_html: '<b>E-mail de lembrete enviado</b> para %{submitter_name}'
|
||||
@@ -3264,6 +3339,9 @@ pt: &pt
|
||||
read: Ler seus dados
|
||||
|
||||
de: &de
|
||||
remove_filter: Filter entfernen
|
||||
document_download_filename_format: Format des Dateinamens beim Herunterladen von Dokumenten
|
||||
document_name: Dokumentname
|
||||
unarchive: Wiederherstellen
|
||||
awaiting_completion_by_the_other_party: "Warten auf die Fertigstellung durch die andere Partei"
|
||||
enforce_recipients_order: 'Empfängerreihenfolge durchsetzen'
|
||||
@@ -3590,7 +3668,7 @@ de: &de
|
||||
edit_message: Nachricht bearbeiten
|
||||
smtp_not_configured: SMTP nicht konfiguriert
|
||||
configure_smtp_settings_in_order_to_send_emails_: 'Konfiguriere die SMTP-Einstellungen, um E-Mails zu senden:'
|
||||
go_to_smtp_setting: Zu den SMTP-Einstellungen gehen
|
||||
go_to_smtp_settings: Zu den SMTP-Einstellungen gehen
|
||||
save_as_default_template_message: Als Standardvorlage speichern
|
||||
re_send_sms: SMS erneut senden
|
||||
send_sms: SMS senden
|
||||
@@ -3877,6 +3955,18 @@ de: &de
|
||||
key: Schlüssel
|
||||
value: Wert
|
||||
webhook_secret: Webhook-Geheimnis
|
||||
author: Autor
|
||||
to: An
|
||||
created_at: Erstellt am
|
||||
apply: Anwenden
|
||||
applying: Anwenden
|
||||
today: Heute
|
||||
yesterday: Gestern
|
||||
this_week: Diese Woche
|
||||
last_week: Letzte Woche
|
||||
this_month: Dieser Monat
|
||||
last_month: Letzter Monat
|
||||
this_year: Dieses Jahr
|
||||
submission_event_names:
|
||||
send_email_to_html: '<b>E-Mail gesendet</b> an %{submitter_name}'
|
||||
send_reminder_email_to_html: '<b>Erinnerungs-E-Mail gesendet</b> an %{submitter_name}'
|
||||
|
||||
@@ -90,6 +90,7 @@ Rails.application.routes.draw do
|
||||
resources :folders, only: %i[show edit update destroy], controller: 'template_folders'
|
||||
resources :template_sharings_testing, only: %i[create]
|
||||
resources :templates, only: %i[index], controller: 'templates_dashboard'
|
||||
resources :submissions_filters, only: %i[show], param: 'name'
|
||||
resources :templates, only: %i[new create edit update show destroy] do
|
||||
resource :debug, only: %i[show], controller: 'templates_debug' if Rails.env.development?
|
||||
resources :documents, only: %i[create], controller: 'template_documents'
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
module Docuseal
|
||||
URL_CACHE = ActiveSupport::Cache::MemoryStore.new
|
||||
PRODUCT_URL = 'https://www.docuseal.com'
|
||||
PRODUCT_EMAIL_URL = ENV.fetch('PRODUCT_EMAIL_URL', PRODUCT_URL)
|
||||
NEWSLETTER_URL = "#{PRODUCT_URL}/newsletters".freeze
|
||||
ENQUIRIES_URL = "#{PRODUCT_URL}/enquiries".freeze
|
||||
PRODUCT_NAME = 'DocuSeal'
|
||||
|
||||
@@ -19,6 +19,8 @@ module ReplaceEmailVariables
|
||||
DOCUMENTS_LINKS = /\{+documents\.links\}+/i
|
||||
DOCUMENTS_LINK = /\{+documents\.link\}+/i
|
||||
|
||||
EMAIL_HOST = ENV.fetch('EMAIL_HOST', nil)
|
||||
|
||||
module_function
|
||||
|
||||
# rubocop:disable Metrics
|
||||
@@ -64,10 +66,17 @@ module ReplaceEmailVariables
|
||||
|
||||
def build_submitter_link(submitter, tracking_event_type)
|
||||
if tracking_event_type == 'click_email'
|
||||
url_options =
|
||||
if EMAIL_HOST.present?
|
||||
{ host: EMAIL_HOST, protocol: ENV['FORCE_SSL'].present? ? 'https' : 'http' }
|
||||
else
|
||||
Docuseal.default_url_options
|
||||
end
|
||||
|
||||
Rails.application.routes.url_helpers.submit_form_url(
|
||||
slug: submitter.slug,
|
||||
t: SubmissionEvents.build_tracking_param(submitter, 'click_email'),
|
||||
**Docuseal.default_url_options
|
||||
**url_options
|
||||
)
|
||||
else
|
||||
Rails.application.routes.url_helpers.submit_form_url(
|
||||
|
||||
+7
-5
@@ -26,7 +26,7 @@ module Submissions
|
||||
arel = arel.or(Template.arel_table[:name].lower.matches("%#{keyword.downcase}%"))
|
||||
end
|
||||
|
||||
submissions.joins(:submitters).where(arel).distinct
|
||||
submissions.joins(:submitters).where(arel).group(:id)
|
||||
end
|
||||
|
||||
def update_template_fields!(submission)
|
||||
@@ -88,17 +88,19 @@ module Submissions
|
||||
)
|
||||
end
|
||||
|
||||
def send_signature_requests(submissions)
|
||||
submissions.each do |submission|
|
||||
def send_signature_requests(submissions, delay: nil)
|
||||
submissions.each_with_index do |submission, index|
|
||||
delay_seconds = (delay + index).seconds if delay
|
||||
|
||||
submitters = submission.submitters.reject(&:completed_at?)
|
||||
|
||||
if submission.submitters_order_preserved?
|
||||
first_submitter =
|
||||
submission.template_submitters.filter_map { |s| submitters.find { |e| e.uuid == s['uuid'] } }.first
|
||||
|
||||
Submitters.send_signature_requests([first_submitter]) if first_submitter
|
||||
Submitters.send_signature_requests([first_submitter], delay_seconds:) if first_submitter
|
||||
else
|
||||
Submitters.send_signature_requests(submitters)
|
||||
Submitters.send_signature_requests(submitters, delay_seconds:)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Submissions
|
||||
module Filter
|
||||
ALLOWED_PARAMS = %w[
|
||||
author
|
||||
completed_at_from
|
||||
completed_at_to
|
||||
created_at_from
|
||||
created_at_to
|
||||
].freeze
|
||||
|
||||
DATE_PARAMS = %w[
|
||||
completed_at_from
|
||||
completed_at_to
|
||||
created_at_from
|
||||
created_at_to
|
||||
].freeze
|
||||
|
||||
module_function
|
||||
|
||||
def call(submissions, current_user, params)
|
||||
filters = normalize_filter_params(params, current_user)
|
||||
|
||||
if filters[:author].present?
|
||||
user = current_user.account.users.find_by(email: filters[:author])
|
||||
submissions = submissions.where(created_by_user_id: user&.id || -1)
|
||||
end
|
||||
|
||||
submissions = submissions.where(created_at: filters[:created_at_from]..) if filters[:created_at_from].present?
|
||||
|
||||
if filters[:created_at_to].present?
|
||||
submissions = submissions.where(created_at: ..filters[:created_at_to].end_of_day)
|
||||
end
|
||||
|
||||
if filters[:completed_at_from].present? || filters[:completed_at_to].present?
|
||||
completed_arel = Submitter.arel_table[:completed_at].maximum
|
||||
submissions = submissions.completed.joins(:submitters).group(:id)
|
||||
|
||||
if filters[:completed_at_from].present?
|
||||
submissions = submissions.having(completed_arel.gteq(filters[:completed_at_from]))
|
||||
end
|
||||
|
||||
if filters[:completed_at_to].present?
|
||||
submissions = submissions.having(completed_arel.lteq(filters[:completed_at_to].end_of_day))
|
||||
end
|
||||
end
|
||||
|
||||
submissions
|
||||
end
|
||||
|
||||
def normalize_filter_params(params, current_user)
|
||||
tz = ActiveSupport::TimeZone[current_user.account.timezone] || Time.zone
|
||||
|
||||
ALLOWED_PARAMS.each_with_object({}) do |key, acc|
|
||||
next if params[key].blank?
|
||||
|
||||
value = DATE_PARAMS.include?(key) ? tz.parse(params[key]) : params[key]
|
||||
|
||||
acc[key.to_sym] = value
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -23,7 +23,7 @@ module Submissions
|
||||
RTL_REGEXP = TextUtils::RTL_REGEXP
|
||||
MAX_IMAGE_HEIGHT = 100
|
||||
|
||||
US_TIMEZONES = %w[EST CST MST PST HST AKDT].freeze
|
||||
US_TIMEZONES = TimeUtils::US_TIMEZONES
|
||||
|
||||
module_function
|
||||
|
||||
@@ -64,7 +64,10 @@ module Submissions
|
||||
|
||||
def build_audit_trail(submission)
|
||||
account = submission.account
|
||||
verify_url = Rails.application.routes.url_helpers.settings_esign_url(**Docuseal.default_url_options)
|
||||
verify_url = Rails.application.routes.url_helpers.settings_esign_url(
|
||||
**Docuseal.default_url_options, host: ENV.fetch('EMAIL_HOST', Docuseal.default_url_options[:host])
|
||||
)
|
||||
|
||||
page_size =
|
||||
if TimeUtils.timezone_abbr(account.timezone, Time.current.beginning_of_year).in?(US_TIMEZONES)
|
||||
:Letter
|
||||
@@ -411,7 +414,7 @@ module Submissions
|
||||
column.image(PdfIcons.logo_io, width: 40, height: 40, position: :float)
|
||||
|
||||
column.formatted_text([{ text: 'DocuSeal',
|
||||
link: Docuseal::PRODUCT_URL }],
|
||||
link: Docuseal::PRODUCT_EMAIL_URL }],
|
||||
font_size: 20,
|
||||
font: [FONT_NAME, { variant: :bold }],
|
||||
width: 100,
|
||||
|
||||
@@ -110,6 +110,10 @@ module Submissions
|
||||
{
|
||||
name: column_name(I18n.t('completed_at'), submitter_name, submitters_count),
|
||||
value: submitter.completed_at.to_s
|
||||
},
|
||||
{
|
||||
name: column_name(I18n.t('link'), submitter_name, submitters_count),
|
||||
value: submitter.completed_at? ? nil : r.submit_form_url(slug: submitter.slug, **Docuseal.default_url_options)
|
||||
}
|
||||
].reject { |e| e[:value].blank? }
|
||||
end
|
||||
@@ -150,5 +154,9 @@ module Submissions
|
||||
{ name: template_field_name, uuid: template_field['uuid'], value: }
|
||||
end
|
||||
end
|
||||
|
||||
def r
|
||||
Rails.application.routes.url_helpers
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+22
-3
@@ -94,12 +94,16 @@ module Submitters
|
||||
preferences
|
||||
end
|
||||
|
||||
def send_signature_requests(submitters)
|
||||
submitters.each do |submitter|
|
||||
def send_signature_requests(submitters, delay_seconds: nil)
|
||||
submitters.each_with_index do |submitter, index|
|
||||
next if submitter.email.blank?
|
||||
next if submitter.preferences['send_email'] == false
|
||||
|
||||
SendSubmitterInvitationEmailJob.perform_async('submitter_id' => submitter.id)
|
||||
if delay_seconds
|
||||
SendSubmitterInvitationEmailJob.perform_in((delay_seconds + index).seconds, 'submitter_id' => submitter.id)
|
||||
else
|
||||
SendSubmitterInvitationEmailJob.perform_async('submitter_id' => submitter.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -112,4 +116,19 @@ module Submitters
|
||||
acc && submitter.submission.submitters.find { |e| e.uuid == item['uuid'] }&.completed_at?
|
||||
end
|
||||
end
|
||||
|
||||
def build_document_filename(submitter, blob, filename_format)
|
||||
return blob.filename.to_s if filename_format.blank?
|
||||
|
||||
filename = ReplaceEmailVariables.call(filename_format, submitter:)
|
||||
|
||||
filename = filename.gsub('{document.name}', blob.filename.base)
|
||||
|
||||
filename = filename.gsub(
|
||||
'{submission.completed_at}',
|
||||
I18n.l(submitter.completed_at.beginning_of_year.in_time_zone(submitter.account.timezone), format: :short)
|
||||
)
|
||||
|
||||
"#{filename}.#{blob.filename.extension}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -93,7 +93,11 @@ module Submitters
|
||||
if params[:cast_boolean] == 'true'
|
||||
v == 'true'
|
||||
elsif params[:cast_number] == 'true'
|
||||
(v.to_f % 1).zero? ? v.to_i : v.to_f
|
||||
if v == ''
|
||||
nil
|
||||
else
|
||||
(v.to_f % 1).zero? ? v.to_i : v.to_f
|
||||
end
|
||||
elsif params[:normalize_phone] == 'true'
|
||||
v.to_s.gsub(/[^0-9+]/, '')
|
||||
else
|
||||
@@ -207,7 +211,7 @@ module Submitters
|
||||
end
|
||||
|
||||
def replace_default_variables(value, attrs, submission, with_time: false)
|
||||
return value if value.in?([true, false])
|
||||
return value if value.in?([true, false]) || value.is_a?(Numeric)
|
||||
return if value.blank?
|
||||
|
||||
value.to_s.gsub(VARIABLE_REGEXP) do |e|
|
||||
|
||||
@@ -22,6 +22,8 @@ module TimeUtils
|
||||
DEFAULT_DATE_FORMAT_US = 'MM/DD/YYYY'
|
||||
DEFAULT_DATE_FORMAT = 'DD/MM/YYYY'
|
||||
|
||||
US_TIMEZONES = %w[EST CST MST PST HST AKDT].freeze
|
||||
|
||||
module_function
|
||||
|
||||
def timezone_abbr(timezone, time = Time.current)
|
||||
|
||||
@@ -17,7 +17,8 @@ FactoryBot.define do
|
||||
submission.template_submitters.each do |template_submitter|
|
||||
create(:submitter, submission:,
|
||||
account_id: submission.account_id,
|
||||
uuid: template_submitter['uuid'])
|
||||
uuid: template_submitter['uuid'],
|
||||
created_at: submission.created_at)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5,7 +5,6 @@ FactoryBot.define do
|
||||
submission
|
||||
email { Faker::Internet.email }
|
||||
name { Faker::Name.name }
|
||||
phone { Faker::PhoneNumber.phone_number }
|
||||
|
||||
before(:create) do |submitter, _|
|
||||
submitter.account_id = submitter.submission.account_id
|
||||
|
||||
+273
-9
@@ -9,9 +9,13 @@ FactoryBot.define do
|
||||
|
||||
transient do
|
||||
submitter_count { 1 }
|
||||
only_field_types do
|
||||
%w[text date checkbox radio signature number multiple select initials image file stamp cells phone payment]
|
||||
end
|
||||
except_field_types { [] }
|
||||
end
|
||||
|
||||
after(:create) do |template, evaluator|
|
||||
after(:create) do |template, ev|
|
||||
blob = ActiveStorage::Blob.create_and_upload!(
|
||||
io: Rails.root.join('spec/fixtures/sample-document.pdf').open,
|
||||
filename: 'sample-document.pdf',
|
||||
@@ -28,7 +32,7 @@ FactoryBot.define do
|
||||
template.schema = [{ attachment_uuid: attachment.uuid, name: 'sample-document' }]
|
||||
number_words = %w[first second third fourth fifth sixth seventh eighth ninth tenth]
|
||||
|
||||
template.submitters = Array.new(evaluator.submitter_count) do |i|
|
||||
template.submitters = Array.new(ev.submitter_count) do |i|
|
||||
{
|
||||
'name' => "#{number_words[i]&.capitalize} Party",
|
||||
'uuid' => SecureRandom.uuid
|
||||
@@ -43,12 +47,13 @@ FactoryBot.define do
|
||||
'name' => 'First Name',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'preferences' => {},
|
||||
'areas' => [
|
||||
{
|
||||
'x' => 0.09027777777777778,
|
||||
'y' => 0.1197252208047105,
|
||||
'w' => 0.3069444444444444,
|
||||
'h' => 0.03336604514229637,
|
||||
'x' => 0.09273546006944444,
|
||||
'y' => 0.1099851117387033,
|
||||
'w' => 0.2701497395833333,
|
||||
'h' => 0.0372705365913556,
|
||||
'attachment_uuid' => attachment.uuid,
|
||||
'page' => 0
|
||||
}
|
||||
@@ -57,12 +62,271 @@ FactoryBot.define do
|
||||
{
|
||||
'uuid' => SecureRandom.uuid,
|
||||
'submitter_uuid' => submitter['uuid'],
|
||||
'name' => '',
|
||||
'name' => 'Birthday',
|
||||
'type' => 'date',
|
||||
'required' => true,
|
||||
'preferences' => { 'format' => 'DD/MM/YYYY' },
|
||||
'areas' => [
|
||||
{
|
||||
'x' => 0.09166666666666666,
|
||||
'y' => 0.1762778204144282,
|
||||
'w' => 0.2763888888888889,
|
||||
'h' => 0.0359029261474578,
|
||||
'attachment_uuid' => attachment.uuid,
|
||||
'page' => 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'uuid' => SecureRandom.uuid,
|
||||
'submitter_uuid' => submitter['uuid'],
|
||||
'name' => 'Do you agree?',
|
||||
'type' => 'checkbox',
|
||||
'required' => true,
|
||||
'preferences' => {},
|
||||
'areas' => [
|
||||
{
|
||||
'x' => 0.09051106770833334,
|
||||
'y' => 0.227587027259332,
|
||||
'w' => 0.2784450954861111,
|
||||
'h' => 0.04113074042239687,
|
||||
'attachment_uuid' => attachment.uuid,
|
||||
'page' => 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'uuid' => SecureRandom.uuid,
|
||||
'submitter_uuid' => submitter['uuid'],
|
||||
'name' => 'First child',
|
||||
'type' => 'radio',
|
||||
'required' => true,
|
||||
'preferences' => {},
|
||||
'options' => [
|
||||
{ 'value' => 'Girl', 'uuid' => SecureRandom.uuid },
|
||||
{ 'value' => 'Boy', 'uuid' => SecureRandom.uuid }
|
||||
],
|
||||
'areas' => [
|
||||
{
|
||||
'x' => 0.09027777777777778,
|
||||
'y' => 0.3020184190330008,
|
||||
'w' => 0.2,
|
||||
'h' => 0.02857142857142857,
|
||||
'attachment_uuid' => attachment.uuid,
|
||||
'page' => 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'uuid' => SecureRandom.uuid,
|
||||
'submitter_uuid' => submitter['uuid'],
|
||||
'name' => 'Signature',
|
||||
'type' => 'signature',
|
||||
'required' => true,
|
||||
'areas' => []
|
||||
'preferences' => {},
|
||||
'areas' => [
|
||||
{
|
||||
'x' => 0.08611111111111111,
|
||||
'y' => 0.3487183422870299,
|
||||
'w' => 0.2,
|
||||
'h' => 0.0707269155206287,
|
||||
'attachment_uuid' => attachment.uuid,
|
||||
'page' => 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'uuid' => SecureRandom.uuid,
|
||||
'submitter_uuid' => submitter['uuid'],
|
||||
'name' => 'House number',
|
||||
'type' => 'number',
|
||||
'required' => true,
|
||||
'preferences' => {},
|
||||
'areas' => [
|
||||
{
|
||||
'x' => 0.08333333333333333,
|
||||
'y' => 0.4582041442824252,
|
||||
'w' => 0.2,
|
||||
'h' => 0.02857142857142857,
|
||||
'attachment_uuid' => attachment.uuid,
|
||||
'page' => 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'uuid' => SecureRandom.uuid,
|
||||
'submitter_uuid' => submitter['uuid'],
|
||||
'name' => 'Colors',
|
||||
'type' => 'multiple',
|
||||
'required' => true,
|
||||
'preferences' => {},
|
||||
'options' => [
|
||||
{ 'value' => 'Red', 'uuid' => SecureRandom.uuid },
|
||||
{ 'value' => 'Green', 'uuid' => SecureRandom.uuid },
|
||||
{ 'value' => 'Blue', 'uuid' => SecureRandom.uuid }
|
||||
],
|
||||
'areas' => [
|
||||
{
|
||||
'x' => 0.45,
|
||||
'y' => 0.1133998465080583,
|
||||
'w' => 0.2,
|
||||
'h' => 0.02857142857142857,
|
||||
'attachment_uuid' => attachment.uuid,
|
||||
'page' => 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'uuid' => SecureRandom.uuid,
|
||||
'submitter_uuid' => submitter['uuid'],
|
||||
'name' => 'Gender',
|
||||
'type' => 'select',
|
||||
'required' => true,
|
||||
'preferences' => {},
|
||||
'options' => [
|
||||
{ 'value' => 'Male', 'uuid' => SecureRandom.uuid },
|
||||
{ 'value' => 'Female', 'uuid' => SecureRandom.uuid }
|
||||
],
|
||||
'areas' => [
|
||||
{
|
||||
'x' => 0.4513888888888889,
|
||||
'y' => 0.1752954719877206,
|
||||
'w' => 0.2,
|
||||
'h' => 0.02857142857142857,
|
||||
'attachment_uuid' => attachment.uuid,
|
||||
'page' => 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'uuid' => SecureRandom.uuid,
|
||||
'submitter_uuid' => submitter['uuid'],
|
||||
'name' => 'Initials',
|
||||
'type' => 'initials',
|
||||
'required' => true,
|
||||
'preferences' => {},
|
||||
'areas' => [
|
||||
{
|
||||
'x' => 0.4486111111111111,
|
||||
'y' => 0.2273599386032233,
|
||||
'w' => 0.1,
|
||||
'h' => 0.02857142857142857,
|
||||
'attachment_uuid' => attachment.uuid,
|
||||
'page' => 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'uuid' => SecureRandom.uuid,
|
||||
'submitter_uuid' => submitter['uuid'],
|
||||
'name' => 'Avatar',
|
||||
'type' => 'image',
|
||||
'required' => true,
|
||||
'preferences' => {},
|
||||
'areas' => [
|
||||
{
|
||||
'x' => 0.7180555555555556,
|
||||
'y' => 0.1129547198772064,
|
||||
'w' => 0.2,
|
||||
'h' => 0.1414538310412574,
|
||||
'attachment_uuid' => attachment.uuid,
|
||||
'page' => 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'uuid' => SecureRandom.uuid,
|
||||
'submitter_uuid' => submitter['uuid'],
|
||||
'name' => 'Attachment',
|
||||
'type' => 'file',
|
||||
'required' => true,
|
||||
'preferences' => {},
|
||||
'areas' => [
|
||||
{
|
||||
'x' => 0.7166666666666667,
|
||||
'y' => 0.3020107444359171,
|
||||
'w' => 0.2,
|
||||
'h' => 0.02857142857142857,
|
||||
'attachment_uuid' => attachment.uuid,
|
||||
'page' => 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'uuid' => SecureRandom.uuid,
|
||||
'submitter_uuid' => submitter['uuid'],
|
||||
'name' => 'Stamp',
|
||||
'type' => 'stamp',
|
||||
'required' => true,
|
||||
'readonly' => true,
|
||||
'preferences' => {},
|
||||
'areas' => [
|
||||
{
|
||||
'x' => 0.7166666666666667,
|
||||
'y' => 0.3771910974673829,
|
||||
'w' => 0.2,
|
||||
'h' => 0.0707269155206287,
|
||||
'attachment_uuid' => attachment.uuid,
|
||||
'page' => 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'uuid' => SecureRandom.uuid,
|
||||
'submitter_uuid' => submitter['uuid'],
|
||||
'name' => 'Cell code',
|
||||
'type' => 'cells',
|
||||
'required' => true,
|
||||
'preferences' => {},
|
||||
'areas' => [
|
||||
{
|
||||
'x' => 0.4472222222222222,
|
||||
'y' => 0.3530851880276286,
|
||||
'w' => 0.2,
|
||||
'h' => 0.02857142857142857,
|
||||
'cell_w' => 0.04,
|
||||
'attachment_uuid' => attachment.uuid,
|
||||
'page' => 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'uuid' => SecureRandom.uuid,
|
||||
'submitter_uuid' => submitter['uuid'],
|
||||
'name' => 'Payment',
|
||||
'type' => 'payment',
|
||||
'required' => true,
|
||||
'preferences' => { 'currency' => 'EUR', 'price' => 1000 },
|
||||
'areas' => [
|
||||
{
|
||||
'x' => 0.4486111111111111,
|
||||
'y' => 0.43168073676132,
|
||||
'w' => 0.2,
|
||||
'h' => 0.02857142857142857,
|
||||
'attachment_uuid' => attachment.uuid,
|
||||
'page' => 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'uuid' => SecureRandom.uuid,
|
||||
'submitter_uuid' => submitter['uuid'],
|
||||
'name' => 'Mobile Phone',
|
||||
'type' => 'phone',
|
||||
'required' => true,
|
||||
'preferences' => {},
|
||||
'areas' => [
|
||||
{
|
||||
'x' => 0.44443359375,
|
||||
'y' => 0.3010283960092095,
|
||||
'w' => 0.2,
|
||||
'h' => 0.02857142857142857,
|
||||
'attachment_uuid' => attachment.uuid,
|
||||
'page' => 0
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
].select { |f| ev.only_field_types.include?(f['type']) && ev.except_field_types.exclude?(f['type']) }
|
||||
|
||||
fields
|
||||
end
|
||||
|
||||
Vendored
BIN
Binary file not shown.
|
After Width: | Height: | Size: 129 KiB |
@@ -10,6 +10,7 @@ require 'capybara/cuprite'
|
||||
require 'capybara/rspec'
|
||||
require 'webmock/rspec'
|
||||
require 'sidekiq/testing'
|
||||
require 'signing_form_helper'
|
||||
|
||||
Sidekiq::Testing.fake!
|
||||
|
||||
@@ -50,6 +51,7 @@ RSpec.configure do |config|
|
||||
|
||||
config.include FactoryBot::Syntax::Methods
|
||||
config.include Devise::Test::IntegrationHelpers
|
||||
config.include SigningFormHelper
|
||||
|
||||
config.before(:each, type: :system) do
|
||||
if ENV['HEADLESS'] == 'false'
|
||||
@@ -67,3 +69,5 @@ RSpec.configure do |config|
|
||||
Sidekiq::Testing.inline! if example.metadata[:sidekiq] == :inline
|
||||
end
|
||||
end
|
||||
|
||||
ActiveSupport.run_load_hooks(:rails_specs, self)
|
||||
|
||||
@@ -176,33 +176,7 @@ describe 'Templates API', type: :request do
|
||||
id: template.id,
|
||||
slug: template.slug,
|
||||
name: template.name,
|
||||
fields: [
|
||||
{
|
||||
'uuid' => template.fields[0]['uuid'],
|
||||
'submitter_uuid' => template.submitters[0]['uuid'],
|
||||
'name' => 'First Name',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'areas' => [
|
||||
{
|
||||
'x' => 0.09027777777777778,
|
||||
'y' => 0.1197252208047105,
|
||||
'w' => 0.3069444444444444,
|
||||
'h' => 0.03336604514229637,
|
||||
'attachment_uuid' => template_attachment_uuid,
|
||||
'page' => 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'uuid' => template.fields[1]['uuid'],
|
||||
'submitter_uuid' => template.submitters[0]['uuid'],
|
||||
'name' => '',
|
||||
'type' => 'signature',
|
||||
'required' => true,
|
||||
'areas' => []
|
||||
}
|
||||
],
|
||||
fields: template.fields,
|
||||
submitters: [
|
||||
{
|
||||
name: 'First Party',
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module SigningFormHelper
|
||||
module_function
|
||||
|
||||
def draw_canvas
|
||||
page.find('canvas').click([], { x: 150, y: 100 })
|
||||
page.execute_script <<~JS
|
||||
const canvas = document.getElementsByTagName('canvas')[0];
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(150, 100);
|
||||
ctx.lineTo(450, 100);
|
||||
ctx.stroke();
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(150, 100);
|
||||
ctx.lineTo(150, 150);
|
||||
ctx.stroke();
|
||||
JS
|
||||
sleep 1
|
||||
end
|
||||
|
||||
def field_value(submitter, field_name)
|
||||
field = template_field(submitter.template, field_name)
|
||||
|
||||
submitter.values[field['uuid']]
|
||||
end
|
||||
|
||||
def template_field(template, field_name)
|
||||
template.fields.find { |f| f['name'] == field_name || f['title'] == field_name } || {}
|
||||
end
|
||||
end
|
||||
@@ -17,7 +17,5 @@ RSpec.describe 'Personalization' do
|
||||
expect(page).to have_content('Completed Notification Email')
|
||||
expect(page).to have_content('Documents Copy Email')
|
||||
expect(page).to have_content('Company Logo')
|
||||
expect(page).to have_content('Unlock with DocuSeal Pro')
|
||||
expect(page).to have_content('Display your company name and logo when signing documents')
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,598 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'Signing Form', type: :system do
|
||||
let(:account) { create(:account) }
|
||||
let(:author) { create(:user, account:) }
|
||||
|
||||
context 'when the template form link is opened' do
|
||||
let(:template) { create(:template, account:, author:, except_field_types: %w[phone payment stamp]) }
|
||||
|
||||
before do
|
||||
visit start_form_path(slug: template.slug)
|
||||
end
|
||||
|
||||
it 'shows the email step', type: :system do
|
||||
expect(page).to have_content('You have been invited to submit a form')
|
||||
expect(page).to have_content("Invited by #{account.name}")
|
||||
expect(page).to have_field('Email', type: 'email')
|
||||
expect(page).to have_button('Start')
|
||||
end
|
||||
|
||||
it 'completes the form' do
|
||||
# Submit's email step
|
||||
fill_in 'Email', with: 'john.dou@example.com'
|
||||
click_button 'Start'
|
||||
|
||||
# Text step
|
||||
fill_in 'First Name', with: 'John'
|
||||
click_button 'next'
|
||||
|
||||
# Date step
|
||||
fill_in 'Birthday', with: I18n.l(20.years.ago, format: '%Y-%m-%d')
|
||||
click_button 'next'
|
||||
|
||||
# Checkbox step
|
||||
check 'Do you agree?'
|
||||
click_button 'next'
|
||||
|
||||
# Radio step
|
||||
choose 'Boy'
|
||||
click_button 'next'
|
||||
|
||||
# Signature step
|
||||
draw_canvas
|
||||
click_button 'next'
|
||||
|
||||
# Number step
|
||||
fill_in 'House number', with: '123'
|
||||
click_button 'next'
|
||||
|
||||
# Multiple choice step
|
||||
%w[Red Blue].each { |color| check color }
|
||||
click_button 'next'
|
||||
|
||||
# Select step
|
||||
select 'Male', from: 'Gender'
|
||||
click_button 'next'
|
||||
|
||||
# Initials step
|
||||
draw_canvas
|
||||
click_button 'next'
|
||||
|
||||
# Image step
|
||||
find('#dropzone').click
|
||||
find('input[type="file"]', visible: false).attach_file(Rails.root.join('spec/fixtures/sample-image.png'))
|
||||
click_button 'next'
|
||||
|
||||
# File step
|
||||
find('#dropzone').click
|
||||
find('input[type="file"]', visible: false).attach_file(Rails.root.join('spec/fixtures/sample-document.pdf'))
|
||||
click_button 'next'
|
||||
|
||||
# Cell step
|
||||
fill_in 'Cell code', with: '123'
|
||||
click_on 'Complete'
|
||||
|
||||
expect(page).to have_button('Download')
|
||||
expect(page).to have_content('Document has been signed!')
|
||||
|
||||
submitter = template.submissions.last.submitters.last
|
||||
|
||||
expect(submitter.email).to eq('john.dou@example.com')
|
||||
expect(submitter.ip).to eq('127.0.0.1')
|
||||
expect(submitter.ua).to be_present
|
||||
expect(submitter.opened_at).to be_present
|
||||
expect(submitter.completed_at).to be_present
|
||||
expect(submitter.declined_at).to be_nil
|
||||
|
||||
expect(field_value(submitter, 'First Name')).to eq 'John'
|
||||
expect(field_value(submitter, 'Birthday')).to eq 20.years.ago.strftime('%Y-%m-%d')
|
||||
expect(field_value(submitter, 'Do you agree?')).to be_truthy
|
||||
expect(field_value(submitter, 'First child')).to eq 'Boy'
|
||||
expect(field_value(submitter, 'Signature')).to be_present
|
||||
expect(field_value(submitter, 'House number')).to eq 123
|
||||
expect(field_value(submitter, 'Colors')).to contain_exactly('Red', 'Blue')
|
||||
expect(field_value(submitter, 'Gender')).to eq 'Male'
|
||||
expect(field_value(submitter, 'Initials')).to be_present
|
||||
expect(field_value(submitter, 'Avatar')).to be_present
|
||||
expect(field_value(submitter, 'Attachment')).to be_present
|
||||
expect(field_value(submitter, 'Cell code')).to eq '123'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the submitter form link is opened' do
|
||||
let(:template) { create(:template, account:, author:, except_field_types: %w[phone payment stamp]) }
|
||||
let(:submission) { create(:submission, template:) }
|
||||
let(:submitter) do
|
||||
create(:submitter, submission:, uuid: template.submitters.first['uuid'], account:, email: 'robin@example.com')
|
||||
end
|
||||
|
||||
before do
|
||||
visit submit_form_path(slug: submitter.slug)
|
||||
end
|
||||
|
||||
it 'complete the form' do
|
||||
# Text step
|
||||
fill_in 'First Name', with: 'John'
|
||||
click_button 'next'
|
||||
|
||||
# Date step
|
||||
fill_in 'Birthday', with: I18n.l(20.years.ago, format: '%Y-%m-%d')
|
||||
click_button 'next'
|
||||
|
||||
# Checkbox step
|
||||
check 'Do you agree?'
|
||||
click_button 'next'
|
||||
|
||||
# Radio step
|
||||
choose 'Boy'
|
||||
click_button 'next'
|
||||
|
||||
# Signature step
|
||||
draw_canvas
|
||||
click_button 'next'
|
||||
|
||||
# Number step
|
||||
fill_in 'House number', with: '123'
|
||||
click_button 'next'
|
||||
|
||||
# Multiple choice step
|
||||
%w[Red Blue].each { |color| check color }
|
||||
click_button 'next'
|
||||
|
||||
# Select step
|
||||
select 'Male', from: 'Gender'
|
||||
click_button 'next'
|
||||
|
||||
# Initials step
|
||||
draw_canvas
|
||||
click_button 'next'
|
||||
|
||||
# Image step
|
||||
find('#dropzone').click
|
||||
find('input[type="file"]', visible: false).attach_file(Rails.root.join('spec/fixtures/sample-image.png'))
|
||||
click_button 'next'
|
||||
|
||||
# File step
|
||||
find('#dropzone').click
|
||||
find('input[type="file"]', visible: false).attach_file(Rails.root.join('spec/fixtures/sample-document.pdf'))
|
||||
click_button 'next'
|
||||
|
||||
# Cell step
|
||||
fill_in 'Cell code', with: '123'
|
||||
click_on 'Complete'
|
||||
|
||||
expect(page).to have_button('Download')
|
||||
expect(page).to have_content('Document has been signed!')
|
||||
|
||||
submitter.reload
|
||||
|
||||
expect(submitter.email).to eq 'robin@example.com'
|
||||
expect(submitter.ip).to eq('127.0.0.1')
|
||||
expect(submitter.ua).to be_present
|
||||
expect(submitter.opened_at).to be_present
|
||||
expect(submitter.completed_at).to be_present
|
||||
expect(submitter.declined_at).to be_nil
|
||||
|
||||
expect(field_value(submitter, 'First Name')).to eq 'John'
|
||||
expect(field_value(submitter, 'Birthday')).to eq 20.years.ago.strftime('%Y-%m-%d')
|
||||
expect(field_value(submitter, 'Do you agree?')).to be_truthy
|
||||
expect(field_value(submitter, 'First child')).to eq 'Boy'
|
||||
expect(field_value(submitter, 'Signature')).to be_present
|
||||
expect(field_value(submitter, 'House number')).to eq 123
|
||||
expect(field_value(submitter, 'Colors')).to contain_exactly('Red', 'Blue')
|
||||
expect(field_value(submitter, 'Gender')).to eq 'Male'
|
||||
expect(field_value(submitter, 'Initials')).to be_present
|
||||
expect(field_value(submitter, 'Avatar')).to be_present
|
||||
expect(field_value(submitter, 'Attachment')).to be_present
|
||||
expect(field_value(submitter, 'Cell code')).to eq '123'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the text step' do
|
||||
let(:template) { create(:template, account:, author:, only_field_types: %w[text]) }
|
||||
let(:submission) { create(:submission, template:) }
|
||||
let(:submitter) do
|
||||
create(:submitter, submission:, uuid: template.submitters.first['uuid'], account:)
|
||||
end
|
||||
|
||||
it 'completes the form if the field is filled' do
|
||||
visit submit_form_path(slug: submitter.slug)
|
||||
|
||||
input = find_field('First Name')
|
||||
|
||||
expect(input[:required]).to be_truthy
|
||||
expect(input[:placeholder]).to eq 'Type here...'
|
||||
|
||||
fill_in 'First Name', with: 'Mary'
|
||||
click_button 'Complete'
|
||||
|
||||
expect(page).to have_content('Form has been completed!')
|
||||
|
||||
submitter.reload
|
||||
|
||||
expect(submitter.completed_at).to be_present
|
||||
expect(field_value(submitter, 'First Name')).to eq 'Mary'
|
||||
end
|
||||
|
||||
it 'toggle multiple text button' do
|
||||
visit submit_form_path(slug: submitter.slug)
|
||||
|
||||
input = find_field('First Name')
|
||||
|
||||
expect(input.tag_name).to eq('input')
|
||||
|
||||
find(:css, 'div[data-tip="Toggle Multiline Text"]').click
|
||||
|
||||
input = find_field('First Name')
|
||||
|
||||
expect(input.tag_name).to eq('textarea')
|
||||
expect(page).not_to have_selector(:css, 'div[data-tip="Toggle Multiline Text"]')
|
||||
|
||||
fill_in 'First Name', with: 'Very long text'
|
||||
click_button 'Complete'
|
||||
|
||||
expect(page).to have_content('Form has been completed!')
|
||||
|
||||
submitter.reload
|
||||
|
||||
expect(field_value(submitter, 'First Name')).to eq 'Very long text'
|
||||
expect(submitter.completed_at).to be_present
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the date step' do
|
||||
let(:template) { create(:template, account:, author:, only_field_types: %w[date]) }
|
||||
let(:submission) { create(:submission, template:) }
|
||||
let(:submitter) do
|
||||
create(:submitter, submission:, uuid: template.submitters.first['uuid'], account:)
|
||||
end
|
||||
|
||||
it 'completes the form if the field is filled' do
|
||||
visit submit_form_path(slug: submitter.slug)
|
||||
|
||||
input = find_field('Birthday')
|
||||
|
||||
expect(input[:required]).to be_truthy
|
||||
|
||||
fill_in 'Birthday', with: I18n.l(25.years.ago, format: '%Y-%m-%d')
|
||||
click_button 'Complete'
|
||||
|
||||
expect(page).to have_content('Form has been completed!')
|
||||
|
||||
submitter.reload
|
||||
|
||||
expect(submitter.completed_at).to be_present
|
||||
expect(field_value(submitter, 'Birthday')).to eq 25.years.ago.strftime('%Y-%m-%d')
|
||||
end
|
||||
|
||||
it 'pre-fills the current date into the form field' do
|
||||
visit submit_form_path(slug: submitter.slug)
|
||||
|
||||
input = find_field('Birthday')
|
||||
|
||||
expect(input[:value]).to eq ''
|
||||
|
||||
click_button 'Set Today'
|
||||
|
||||
input = find_field('Birthday')
|
||||
|
||||
expect(input[:value]).to eq Time.zone.now.strftime('%Y-%m-%d')
|
||||
|
||||
click_button 'Complete'
|
||||
|
||||
expect(page).to have_content('Form has been completed!')
|
||||
|
||||
submitter.reload
|
||||
|
||||
expect(submitter.completed_at).to be_present
|
||||
expect(field_value(submitter, 'Birthday')).to eq Time.zone.now.strftime('%Y-%m-%d')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the checkbox step' do
|
||||
let(:template) { create(:template, account:, author:, only_field_types: %w[checkbox]) }
|
||||
let(:submission) { create(:submission, template:) }
|
||||
let(:submitter) do
|
||||
create(:submitter, submission:, uuid: template.submitters.first['uuid'], account:)
|
||||
end
|
||||
|
||||
it 'completes the form if the checkbox is checked' do
|
||||
visit submit_form_path(slug: submitter.slug)
|
||||
|
||||
check 'Do you agree?'
|
||||
click_button 'Complete'
|
||||
|
||||
expect(page).to have_content('Form has been completed!')
|
||||
|
||||
submitter.reload
|
||||
|
||||
expect(submitter.completed_at).to be_present
|
||||
expect(field_value(submitter, 'Do you agree?')).to be true
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the radio step' do
|
||||
let(:template) { create(:template, account:, author:, only_field_types: %w[radio]) }
|
||||
let(:submission) { create(:submission, template:) }
|
||||
let(:submitter) do
|
||||
create(:submitter, submission:, uuid: template.submitters.first['uuid'], account:)
|
||||
end
|
||||
|
||||
it 'completes the form if the checkbox is checked' do
|
||||
visit submit_form_path(slug: submitter.slug)
|
||||
|
||||
%w[Girl Boy].map { |v| find_field(v) }.each { |input| expect(input[:required]).to be_truthy }
|
||||
|
||||
choose 'Boy'
|
||||
click_button 'Complete'
|
||||
|
||||
expect(page).to have_content('Form has been completed!')
|
||||
|
||||
submitter.reload
|
||||
|
||||
expect(submitter.completed_at).to be_present
|
||||
expect(field_value(submitter, 'First child')).to eq 'Boy'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the signature step' do
|
||||
let(:template) { create(:template, account:, author:, only_field_types: %w[signature]) }
|
||||
let(:submission) { create(:submission, template:) }
|
||||
let(:submitter) do
|
||||
create(:submitter, submission:, uuid: template.submitters.first['uuid'], account:)
|
||||
end
|
||||
|
||||
it 'completes the form if the canvas is drawn' do
|
||||
visit submit_form_path(slug: submitter.slug)
|
||||
|
||||
find('#expand_form_button').click
|
||||
draw_canvas
|
||||
click_button 'Sign and Complete'
|
||||
|
||||
expect(page).to have_content('Document has been signed!')
|
||||
|
||||
submitter.reload
|
||||
|
||||
expect(submitter.completed_at).to be_present
|
||||
expect(field_value(submitter, 'Signature')).to be_present
|
||||
end
|
||||
|
||||
it 'completes the form if the canvas is typed' do
|
||||
visit submit_form_path(slug: submitter.slug)
|
||||
|
||||
find('#expand_form_button').click
|
||||
click_link 'Type'
|
||||
fill_in 'signature_text_input', with: 'John Doe'
|
||||
click_button 'Sign and Complete'
|
||||
|
||||
expect(page).to have_content('Document has been signed!')
|
||||
|
||||
submitter.reload
|
||||
|
||||
expect(submitter.completed_at).to be_present
|
||||
expect(field_value(submitter, 'Signature')).to be_present
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the number step' do
|
||||
let(:template) { create(:template, account:, author:, only_field_types: %w[number]) }
|
||||
let(:submission) { create(:submission, template:) }
|
||||
let(:submitter) do
|
||||
create(:submitter, submission:, uuid: template.submitters.first['uuid'], account:)
|
||||
end
|
||||
|
||||
it 'completes the form if the field is filled' do
|
||||
visit submit_form_path(slug: submitter.slug)
|
||||
|
||||
input = find_field('House number')
|
||||
|
||||
expect(input[:required]).to be_truthy
|
||||
expect(input[:placeholder]).to eq 'Type here...'
|
||||
|
||||
fill_in 'House number', with: '4'
|
||||
click_button 'Complete'
|
||||
|
||||
expect(page).to have_content('Form has been completed!')
|
||||
|
||||
submitter.reload
|
||||
|
||||
expect(submitter.completed_at).to be_present
|
||||
expect(field_value(submitter, 'House number')).to eq 4
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the multiple choice step' do
|
||||
let(:template) { create(:template, account:, author:, only_field_types: %w[multiple]) }
|
||||
let(:submission) { create(:submission, template:) }
|
||||
let(:submitter) do
|
||||
create(:submitter, submission:, uuid: template.submitters.first['uuid'], account:)
|
||||
end
|
||||
|
||||
it 'completes the form if the multiple choice is checked' do
|
||||
visit submit_form_path(slug: submitter.slug)
|
||||
|
||||
%w[Red Green].each { |color| check color }
|
||||
click_button 'Complete'
|
||||
|
||||
expect(page).to have_content('Form has been completed!')
|
||||
|
||||
submitter.reload
|
||||
|
||||
expect(submitter.completed_at).to be_present
|
||||
expect(field_value(submitter, 'Colors')).to contain_exactly('Red', 'Green')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the select step' do
|
||||
let(:template) { create(:template, account:, author:, only_field_types: %w[select]) }
|
||||
let(:submission) { create(:submission, template:) }
|
||||
let(:submitter) do
|
||||
create(:submitter, submission:, uuid: template.submitters.first['uuid'], account:)
|
||||
end
|
||||
|
||||
it 'completes the form if the multiple choice is checked' do
|
||||
visit submit_form_path(slug: submitter.slug)
|
||||
|
||||
select 'Female', from: 'Gender'
|
||||
|
||||
click_button 'Complete'
|
||||
|
||||
expect(page).to have_content('Form has been completed!')
|
||||
|
||||
submitter.reload
|
||||
|
||||
expect(submitter.completed_at).to be_present
|
||||
expect(field_value(submitter, 'Gender')).to eq 'Female'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the initials step' do
|
||||
let(:template) { create(:template, account:, author:, only_field_types: %w[initials]) }
|
||||
let(:submission) { create(:submission, template:) }
|
||||
let(:submitter) do
|
||||
create(:submitter, submission:, uuid: template.submitters.first['uuid'], account:)
|
||||
end
|
||||
|
||||
it 'completes the form if the canvas is typed' do
|
||||
visit submit_form_path(slug: submitter.slug)
|
||||
|
||||
find('#expand_form_button').click
|
||||
fill_in 'initials_text_input', with: 'John Doe'
|
||||
click_button 'Complete'
|
||||
|
||||
expect(page).to have_content('Document has been signed!')
|
||||
|
||||
submitter.reload
|
||||
|
||||
expect(submitter.completed_at).to be_present
|
||||
expect(field_value(submitter, 'Initials')).to be_present
|
||||
end
|
||||
|
||||
it 'completes the form if the canvas is drawn' do
|
||||
visit submit_form_path(slug: submitter.slug)
|
||||
|
||||
find('#expand_form_button').click
|
||||
click_link 'Draw'
|
||||
draw_canvas
|
||||
click_button 'Complete'
|
||||
|
||||
expect(page).to have_content('Document has been signed!')
|
||||
|
||||
submitter.reload
|
||||
|
||||
expect(submitter.completed_at).to be_present
|
||||
expect(field_value(submitter, 'Initials')).to be_present
|
||||
end
|
||||
|
||||
it 'completes the form if the initials is uploaded' do
|
||||
visit submit_form_path(slug: submitter.slug)
|
||||
|
||||
find('#expand_form_button').click
|
||||
find('span[data-tip="Click to upload"]').click
|
||||
find('input[type="file"]', visible: false).attach_file(Rails.root.join('spec/fixtures/sample-image.png'))
|
||||
click_button 'Complete'
|
||||
|
||||
expect(page).to have_content('Document has been signed!')
|
||||
|
||||
submitter.reload
|
||||
|
||||
expect(submitter.completed_at).to be_present
|
||||
expect(field_value(submitter, 'Initials')).to be_present
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the image step' do
|
||||
let(:template) { create(:template, account:, author:, only_field_types: %w[image]) }
|
||||
let(:submission) { create(:submission, template:) }
|
||||
let(:submitter) do
|
||||
create(:submitter, submission:, uuid: template.submitters.first['uuid'], account:)
|
||||
end
|
||||
|
||||
it 'completes the form if the image is uploaded' do
|
||||
visit submit_form_path(slug: submitter.slug)
|
||||
|
||||
find('#expand_form_button').click
|
||||
find('#dropzone').click
|
||||
find('input[type="file"]', visible: false).attach_file(Rails.root.join('spec/fixtures/sample-image.png'))
|
||||
click_button 'Complete'
|
||||
|
||||
expect(page).to have_content('Form has been completed!')
|
||||
|
||||
submitter.reload
|
||||
|
||||
expect(submitter.completed_at).to be_present
|
||||
expect(field_value(submitter, 'Avatar')).to be_present
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the file step' do
|
||||
let(:template) { create(:template, account:, author:, only_field_types: %w[file]) }
|
||||
let(:submission) { create(:submission, template:) }
|
||||
let(:submitter) do
|
||||
create(:submitter, submission:, uuid: template.submitters.first['uuid'], account:)
|
||||
end
|
||||
|
||||
it 'completes the form if the file is uploaded' do
|
||||
visit submit_form_path(slug: submitter.slug)
|
||||
|
||||
find('#expand_form_button').click
|
||||
find('#dropzone').click
|
||||
find('input[type="file"]', visible: false).attach_file(Rails.root.join('spec/fixtures/sample-document.pdf'))
|
||||
click_button 'Complete'
|
||||
|
||||
expect(page).to have_content('Form has been completed!')
|
||||
|
||||
submitter.reload
|
||||
|
||||
expect(submitter.completed_at).to be_present
|
||||
expect(field_value(submitter, 'Attachment')).to be_present
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the cells step' do
|
||||
let(:template) { create(:template, account:, author:, only_field_types: %w[cells]) }
|
||||
let(:submission) { create(:submission, template:) }
|
||||
let(:submitter) do
|
||||
create(:submitter, submission:, uuid: template.submitters.first['uuid'], account:)
|
||||
end
|
||||
|
||||
it 'completes the form if the field is filled' do
|
||||
visit submit_form_path(slug: submitter.slug)
|
||||
|
||||
input = find_field('Cell code')
|
||||
|
||||
expect(input[:required]).to be_truthy
|
||||
expect(input[:placeholder]).to eq 'Type here...'
|
||||
|
||||
fill_in 'Cell code', with: '456'
|
||||
click_button 'Complete'
|
||||
|
||||
expect(page).to have_content('Form has been completed!')
|
||||
|
||||
submitter.reload
|
||||
|
||||
expect(submitter.completed_at).to be_present
|
||||
expect(field_value(submitter, 'Cell code')).to eq '456'
|
||||
end
|
||||
end
|
||||
|
||||
it 'sends completed email' do
|
||||
template = create(:template, account:, author:, only_field_types: %w[text signature])
|
||||
submission = create(:submission, template:)
|
||||
submitter = create(:submitter, submission:, uuid: template.submitters.first['uuid'], account:)
|
||||
|
||||
visit submit_form_path(slug: submitter.slug)
|
||||
|
||||
fill_in 'First Name', with: 'Adam'
|
||||
click_on 'next'
|
||||
click_link 'Type'
|
||||
fill_in 'signature_text_input', with: 'Adam'
|
||||
|
||||
expect do
|
||||
click_on 'Sign and Complete'
|
||||
end.to change(ProcessSubmitterCompletionJob.jobs, :size).by(1)
|
||||
end
|
||||
end
|
||||
@@ -1,87 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'Submit Form' do
|
||||
let(:account) { create(:account) }
|
||||
let(:user) { create(:user, account:) }
|
||||
let(:template) { create(:template, account:, author: user) }
|
||||
|
||||
before do
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
context 'when initialized by shared link' do
|
||||
before do
|
||||
visit start_form_path(slug: template.slug)
|
||||
end
|
||||
|
||||
it 'shows start form page' do
|
||||
expect(page).to have_content('You have been invited to submit a form')
|
||||
expect(page).to have_content(template.name)
|
||||
expect(page).to have_content("Invited by #{template.account.name}")
|
||||
end
|
||||
|
||||
it 'complete the form' do
|
||||
fill_in 'Email', with: 'john.dou@example.com'
|
||||
click_button 'Start'
|
||||
|
||||
fill_in 'First Name', with: 'Adam'
|
||||
click_on 'next'
|
||||
click_on 'type_text_button'
|
||||
fill_in 'signature_text_input', with: 'Adam'
|
||||
|
||||
expect do
|
||||
click_on 'Sign and Complete'
|
||||
end.not_to(change(Submitter, :count))
|
||||
|
||||
submitter = Submitter.find_by(email: 'john.dou@example.com')
|
||||
|
||||
expect(page).to have_button('Download')
|
||||
expect(submitter.email).to eq('john.dou@example.com')
|
||||
expect(submitter.ip).to eq('127.0.0.1')
|
||||
expect(submitter.ua).to be_present
|
||||
expect(submitter.opened_at).to be_present
|
||||
expect(submitter.completed_at).to be_present
|
||||
expect(submitter.values.values).to include('Adam')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when initialized by shared email address' do
|
||||
let(:submission) { create(:submission, template:, created_by_user: user) }
|
||||
let(:submitters) { template.submitters.map { |s| create(:submitter, submission:, uuid: s['uuid']) } }
|
||||
let(:submitter) { submitters.first }
|
||||
|
||||
before do
|
||||
visit submit_form_path(slug: submitter.slug)
|
||||
end
|
||||
|
||||
it 'completes the form' do
|
||||
fill_in 'First Name', with: 'Sally'
|
||||
click_on 'next'
|
||||
click_on 'type_text_button'
|
||||
fill_in 'signature_text_input', with: 'Sally'
|
||||
click_on 'Sign and Complete'
|
||||
|
||||
submitter.reload
|
||||
|
||||
expect(page).to have_button('Download')
|
||||
expect(submitter.ip).to eq('127.0.0.1')
|
||||
expect(submitter.ua).to be_present
|
||||
expect(submitter.opened_at).to be_present
|
||||
expect(submitter.completed_at).to be_present
|
||||
expect(submitter.values.values).to include('Sally')
|
||||
end
|
||||
|
||||
it 'sends completed email' do
|
||||
fill_in 'First Name', with: 'Adam'
|
||||
click_on 'next'
|
||||
click_on 'type_text_button'
|
||||
fill_in 'signature_text_input', with: 'Adam'
|
||||
|
||||
expect do
|
||||
click_on 'Sign and Complete'
|
||||
end.to change(ProcessSubmitterCompletionJob.jobs, :size).by(1)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -5,7 +5,7 @@ require 'rails_helper'
|
||||
RSpec.describe 'Template' do
|
||||
let!(:account) { create(:account) }
|
||||
let!(:user) { create(:user, account:) }
|
||||
let!(:template) { create(:template, account:, author: user) }
|
||||
let!(:template) { create(:template, account:, author: user, except_field_types: %w[phone payment]) }
|
||||
|
||||
before do
|
||||
sign_in(user)
|
||||
@@ -140,4 +140,98 @@ RSpec.describe 'Template' do
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when filtering submissions' do
|
||||
let(:second_user) { create(:user, account:) }
|
||||
|
||||
it 'displays only submissions by the selected author' do
|
||||
first_user_submissions = create_list(:submission, 5, :with_submitters, template:, created_by_user: user)
|
||||
second_user_submissions = create_list(:submission, 6, :with_submitters, template:, created_by_user: second_user)
|
||||
|
||||
visit template_path(template)
|
||||
|
||||
(first_user_submissions + second_user_submissions).map(&:submitters).flatten.last(10).uniq.each do |submitter|
|
||||
expect(page).to have_content(submitter.name)
|
||||
end
|
||||
|
||||
page.find('.dropdown', text: 'Filter').click
|
||||
click_link 'Author'
|
||||
within '#modal' do
|
||||
select second_user.full_name, from: 'author'
|
||||
click_button 'Apply'
|
||||
end
|
||||
|
||||
second_user_submissions.map(&:submitters).flatten.uniq.each do |submitter|
|
||||
expect(page).to have_content(submitter.name)
|
||||
end
|
||||
|
||||
first_user_submissions.map(&:submitters).flatten.uniq.each do |submitter|
|
||||
expect(page).not_to have_content(submitter.name)
|
||||
end
|
||||
end
|
||||
|
||||
it 'displays submissions created within the selected date range' do
|
||||
last_week_submissions = create_list(:submission, 5, :with_submitters, template:, created_by_user: user,
|
||||
created_at: 9.days.ago)
|
||||
this_week_submissions = create_list(:submission, 6, :with_submitters, template:, created_by_user: user,
|
||||
created_at: 5.days.ago)
|
||||
|
||||
visit template_path(template)
|
||||
|
||||
(last_week_submissions + this_week_submissions).map(&:submitters).flatten.last(10).uniq.each do |submitter|
|
||||
expect(page).to have_content(submitter.name)
|
||||
end
|
||||
|
||||
page.find('.dropdown', text: 'Filter').click
|
||||
click_link 'Created at'
|
||||
within '#modal' do
|
||||
fill_in 'From', with: I18n.l(10.days.ago, format: '%Y-%m-%d')
|
||||
fill_in 'To', with: I18n.l(6.days.ago, format: '%Y-%m-%d')
|
||||
click_button 'Apply'
|
||||
end
|
||||
|
||||
last_week_submissions.map(&:submitters).flatten.uniq.each do |submitter|
|
||||
expect(page).to have_content(submitter.name)
|
||||
end
|
||||
|
||||
this_week_submissions.map(&:submitters).flatten.uniq.each do |submitter|
|
||||
expect(page).not_to have_content(submitter.name)
|
||||
end
|
||||
end
|
||||
|
||||
it 'displays submissions completed within the selected date range' do
|
||||
last_week_submissions = create_list(:submission, 5, :with_submitters, template:, created_by_user: user)
|
||||
this_week_submissions = create_list(:submission, 6, :with_submitters, template:, created_by_user: user)
|
||||
|
||||
last_week_submissions.map(&:submitters).flatten.each do |submitter|
|
||||
submitter.update!(completed_at: rand(6..10).days.ago)
|
||||
end
|
||||
|
||||
this_week_submissions.map(&:submitters).flatten.each do |submitter|
|
||||
submitter.update!(completed_at: rand(2..5).days.ago)
|
||||
end
|
||||
|
||||
visit template_path(template)
|
||||
|
||||
(last_week_submissions + this_week_submissions).map(&:submitters).flatten.last(10).uniq.each do |submitter|
|
||||
expect(page).to have_content(submitter.name)
|
||||
end
|
||||
|
||||
page.find('.dropdown', text: 'Filter').click
|
||||
click_link 'Completed at'
|
||||
within '#modal' do
|
||||
fill_in 'From', with: I18n.l(5.days.ago, format: '%Y-%m-%d')
|
||||
fill_in 'To', with: I18n.l(1.day.ago, format: '%Y-%m-%d')
|
||||
click_button 'Apply'
|
||||
end
|
||||
|
||||
this_week_submissions.map(&:submitters).flatten.uniq.each do |submitter|
|
||||
expect(page).to have_content(submitter.name)
|
||||
end
|
||||
|
||||
last_week_submissions.map(&:submitters).flatten.uniq.each do |submitter|
|
||||
expect(page).not_to have_content(submitter.name)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user