mirror of
https://github.com/docusealco/docuseal.git
synced 2026-08-07 07:14:43 +00:00
Compare commits
55 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d86b16de6b | |||
| d41710f507 | |||
| 2b53166763 | |||
| 4e56a16a58 | |||
| ac7cc77017 | |||
| 9194879c43 | |||
| 1902cdaa55 | |||
| 6314987e69 | |||
| 65bcbd7737 | |||
| 9b25b8538e | |||
| bbf8bb2a94 | |||
| 2085227cd9 | |||
| 93e0730210 | |||
| 25c5c11ab3 | |||
| 6ed5b5d35d | |||
| 673cc1e0df | |||
| 065dcf6f9f | |||
| a66b16ce1c | |||
| e4c0b30284 | |||
| 3cb37748e9 | |||
| e54d76f0e2 | |||
| f19e037011 | |||
| ee3f47d918 | |||
| a49a645022 | |||
| cf30441ac6 | |||
| c9bcf866ab | |||
| 506609f3cd | |||
| 721172c980 | |||
| b183db5b80 | |||
| c7f277f404 | |||
| 8ac03a9f8a | |||
| dc1c386d7e | |||
| 1eaf4e5bab | |||
| 3c0f1c2e73 | |||
| b2790776ef | |||
| 2840359396 | |||
| c18ddb99a8 | |||
| 432435aa23 | |||
| 90c1a6f4ad | |||
| 0e8f478c2e | |||
| 5f23d5a5ab | |||
| 76821a4c2d | |||
| e4a8cca90a | |||
| 6024ed4cd1 | |||
| 3811f8bbb8 | |||
| 306218e5df | |||
| cabfe45608 | |||
| 4ade6a5911 | |||
| 10d45c3e20 | |||
| 6fb247ec06 | |||
| 1546258529 | |||
| 7ec83b8af6 | |||
| 21d0a87c17 | |||
| 528f59c1bf | |||
| d1535c4cd8 |
+9
-9
@@ -144,7 +144,7 @@ GEM
|
||||
cldr-plurals-runtime-rb (1.1.0)
|
||||
cmdparse (3.0.7)
|
||||
coderay (1.1.3)
|
||||
concurrent-ruby (1.3.6)
|
||||
concurrent-ruby (1.3.7)
|
||||
connection_pool (3.0.2)
|
||||
crack (1.0.1)
|
||||
bigdecimal
|
||||
@@ -195,13 +195,13 @@ GEM
|
||||
railties (>= 6.1.0)
|
||||
faker (3.6.1)
|
||||
i18n (>= 1.8.11, < 2)
|
||||
faraday (2.14.2)
|
||||
faraday (2.14.3)
|
||||
faraday-net_http (>= 2.0, < 3.5)
|
||||
json
|
||||
logger
|
||||
faraday-follow_redirects (0.5.0)
|
||||
faraday (>= 1, < 3)
|
||||
faraday-net_http (3.4.2)
|
||||
faraday-net_http (3.4.4)
|
||||
net-http (~> 0.5)
|
||||
ferrum (0.17.2)
|
||||
addressable (~> 2.5)
|
||||
@@ -271,7 +271,7 @@ GEM
|
||||
rdoc (>= 4.0.0)
|
||||
reline (>= 0.4.2)
|
||||
jmespath (1.6.2)
|
||||
json (2.19.8)
|
||||
json (2.19.9)
|
||||
jwt (3.2.0)
|
||||
base64
|
||||
language_server-protocol (3.17.0.5)
|
||||
@@ -323,15 +323,15 @@ GEM
|
||||
net-smtp (0.5.1)
|
||||
net-protocol
|
||||
nio4r (2.7.5)
|
||||
nokogiri (1.19.3-aarch64-linux-gnu)
|
||||
nokogiri (1.19.4-aarch64-linux-gnu)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.19.3-aarch64-linux-musl)
|
||||
nokogiri (1.19.4-aarch64-linux-musl)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.19.3-arm64-darwin)
|
||||
nokogiri (1.19.4-arm64-darwin)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.19.3-x86_64-linux-gnu)
|
||||
nokogiri (1.19.4-x86_64-linux-gnu)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.19.3-x86_64-linux-musl)
|
||||
nokogiri (1.19.4-x86_64-linux-musl)
|
||||
racc (~> 1.4)
|
||||
numo-narray-alt (0.10.3)
|
||||
onnxruntime (0.10.1-aarch64-linux)
|
||||
|
||||
@@ -9,7 +9,7 @@ module Api
|
||||
(@submission.schema_documents || @submission.template.schema_documents).size > 1
|
||||
|
||||
documents =
|
||||
if @submission.submitters.all?(&:completed_at?)
|
||||
if @submission.completed_at?
|
||||
build_completed_documents(@submission, merge: is_merge)
|
||||
else
|
||||
build_preview_documents(@submission, merge: is_merge)
|
||||
|
||||
@@ -5,7 +5,7 @@ module Api
|
||||
load_and_authorize_resource :submission, parent: false
|
||||
|
||||
def index
|
||||
submissions = build_completed_query(@submissions)
|
||||
submissions = @submissions.active.where.not(completed_at: nil)
|
||||
|
||||
params[:after] = Time.zone.at(params[:after].to_i) if params[:after].present?
|
||||
params[:before] = Time.zone.at(params[:before].to_i) if params[:before].present?
|
||||
@@ -36,20 +36,5 @@ module Api
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def build_completed_query(submissions)
|
||||
submissions = submissions.where(
|
||||
Submitter.where(completed_at: nil).where(
|
||||
Submitter.arel_table[:submission_id].eq(Submission.arel_table[:id])
|
||||
).select(1).arel.exists.not
|
||||
)
|
||||
|
||||
submissions.joins(:submitters)
|
||||
.group(:id)
|
||||
.select(Submission.arel_table[Arel.star],
|
||||
Submitter.arel_table[:completed_at].maximum.as('completed_at'))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
|
||||
module Api
|
||||
class SubmissionsController < ApiBaseController
|
||||
SUBMISSION_COLUMNS = %i[id name slug source submitters_order expire_at completed_at created_at updated_at
|
||||
archived_at variables template_id template_submitters created_by_user_id].freeze
|
||||
TEMPLATE_COLUMNS = %i[id name external_id created_at updated_at folder_id submitters].freeze
|
||||
|
||||
load_and_authorize_resource :template, only: :create
|
||||
load_and_authorize_resource :submission, only: %i[show index destroy]
|
||||
|
||||
@@ -9,14 +13,28 @@ module Api
|
||||
authorize!(:create, Submission)
|
||||
end
|
||||
|
||||
before_action :maybe_return_template_error, only: :create
|
||||
|
||||
def index
|
||||
submissions = Submissions.search(current_user, @submissions, params[:q])
|
||||
submissions = filter_submissions(submissions, params)
|
||||
|
||||
submissions = paginate(submissions.preload(:created_by_user, :submitters,
|
||||
template: { folder: :parent_folder },
|
||||
combined_document_attachment: :blob,
|
||||
audit_trail_attachment: :blob))
|
||||
with_fields = params[:include].to_s.include?('fields') || params[:include].to_s.include?('combined_document_url')
|
||||
|
||||
submissions = paginate(
|
||||
submissions.select(with_fields ? nil : SUBMISSION_COLUMNS)
|
||||
.preload(:created_by_user, :submitters, combined_document_attachment: :blob,
|
||||
audit_trail_attachment: :blob)
|
||||
)
|
||||
|
||||
ActiveRecord::Associations::Preloader.new(
|
||||
records: submissions,
|
||||
associations: :template,
|
||||
scope: with_fields ? nil : Template.select(TEMPLATE_COLUMNS)
|
||||
).call
|
||||
|
||||
ActiveRecord::Associations::Preloader.new(records: submissions.filter_map(&:template),
|
||||
associations: { folder: :parent_folder }).call
|
||||
|
||||
expires_at = Accounts.link_expires_at(current_account)
|
||||
|
||||
@@ -42,7 +60,7 @@ module Api
|
||||
end
|
||||
end
|
||||
|
||||
if @submission.audit_trail_attachment.blank? && submitters.all?(&:completed_at?)
|
||||
if @submission.audit_trail_attachment.blank? && @submission.completed_at?
|
||||
@submission.audit_trail_attachment = Submissions::EnsureAuditGenerated.call(@submission)
|
||||
end
|
||||
|
||||
@@ -52,20 +70,6 @@ module Api
|
||||
def create
|
||||
Params::SubmissionCreateValidator.call(params)
|
||||
|
||||
return render json: { error: 'Template not found' }, status: :unprocessable_content if @template.nil?
|
||||
|
||||
if @template.archived_at?
|
||||
Rollbar.warning("Archived template submission: #{@template.id}") if defined?(Rollbar)
|
||||
|
||||
return render json: { error: 'Template has been archived' }, status: :unprocessable_content
|
||||
end
|
||||
|
||||
if @template.fields.blank?
|
||||
Rollbar.warning("Template does not contain fields: #{@template.id}") if defined?(Rollbar)
|
||||
|
||||
return render json: { error: 'Template does not contain fields' }, status: :unprocessable_content
|
||||
end
|
||||
|
||||
params[:send_email] = true unless params.key?(:send_email)
|
||||
params[:send_sms] = false unless params.key?(:send_sms)
|
||||
|
||||
@@ -76,10 +80,16 @@ module Api
|
||||
Submissions.send_signature_requests(submissions)
|
||||
|
||||
submissions.each do |submission|
|
||||
if submission.submitters.all?(&:completed_at?) && Submissions.maybe_update_completed_at(submission)
|
||||
last_submitter = submission.submitters.max_by(&:completed_at)
|
||||
end
|
||||
|
||||
submission.submitters.each do |submitter|
|
||||
next unless submitter.completed_at?
|
||||
|
||||
ProcessSubmitterCompletionJob.perform_async('submitter_id' => submitter.id, 'send_invitation_email' => false)
|
||||
ProcessSubmitterCompletionJob.perform_async('submitter_id' => submitter.id,
|
||||
'is_last' => submitter == last_submitter,
|
||||
'send_invitation_email' => false)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -107,6 +117,22 @@ module Api
|
||||
|
||||
private
|
||||
|
||||
def maybe_return_template_error
|
||||
return render json: { error: 'Template not found' }, status: :unprocessable_content if @template.nil?
|
||||
|
||||
if @template.archived_at?
|
||||
Rollbar.warning("Archived template submission: #{@template.id}") if defined?(Rollbar)
|
||||
|
||||
return render json: { error: 'Template has been archived' }, status: :unprocessable_content
|
||||
end
|
||||
|
||||
return if @template.fields.present?
|
||||
|
||||
Rollbar.warning("Template does not contain fields: #{@template.id}") if defined?(Rollbar)
|
||||
|
||||
render json: { error: 'Template does not contain fields' }, status: :unprocessable_content
|
||||
end
|
||||
|
||||
def filter_submissions(submissions, params)
|
||||
submissions = submissions.where(template_id: params[:template_id]) if params[:template_id].present?
|
||||
submissions = submissions.where(slug: params[:slug]) if params[:slug].present?
|
||||
|
||||
@@ -4,6 +4,8 @@ module Api
|
||||
class SubmittersController < ApiBaseController
|
||||
load_and_authorize_resource :submitter
|
||||
|
||||
before_action :maybe_return_submitter_error, only: :update
|
||||
|
||||
def index
|
||||
submitters = Submitters.search(current_user, @submitters, params[:q])
|
||||
|
||||
@@ -36,14 +38,6 @@ module Api
|
||||
|
||||
# rubocop:disable Metrics/MethodLength
|
||||
def update
|
||||
if @submitter.completed_at?
|
||||
return render json: { error: 'Submitter has already completed the submission.' }, status: :unprocessable_content
|
||||
end
|
||||
|
||||
if @submitter.declined_at?
|
||||
return render json: { error: 'Submitter has already declined the submission.' }, status: :unprocessable_content
|
||||
end
|
||||
|
||||
submission = @submitter.submission
|
||||
role = submission.template_submitters.find { |e| e['uuid'] == @submitter.uuid }['name']
|
||||
|
||||
@@ -73,7 +67,9 @@ module Api
|
||||
end
|
||||
|
||||
if @submitter.completed_at?
|
||||
ProcessSubmitterCompletionJob.perform_async('submitter_id' => @submitter.id)
|
||||
is_last = Submissions.maybe_update_completed_at(@submitter.submission)
|
||||
|
||||
ProcessSubmitterCompletionJob.perform_async('submitter_id' => @submitter.id, 'is_last' => is_last)
|
||||
elsif normalized_params[:send_email] || normalized_params[:send_sms]
|
||||
Submitters.send_signature_requests([@submitter])
|
||||
end
|
||||
@@ -104,7 +100,17 @@ module Api
|
||||
|
||||
private
|
||||
|
||||
def maybe_filder_by_completed_at(submitters, params)
|
||||
def maybe_return_submitter_error
|
||||
if @submitter.completed_at?
|
||||
return render json: { error: 'Submitter has already completed the submission.' }, status: :unprocessable_content
|
||||
end
|
||||
|
||||
return unless @submitter.declined_at?
|
||||
|
||||
render json: { error: 'Submitter has already declined the submission.' }, status: :unprocessable_content
|
||||
end
|
||||
|
||||
def maybe_filter_by_completed_at(submitters, params)
|
||||
if params[:completed_after].present?
|
||||
submitters = submitters.where(completed_at: Time.zone.parse(params[:completed_after])..)
|
||||
end
|
||||
@@ -177,7 +183,7 @@ module Api
|
||||
submitters = submitters.joins(:submission).where(submissions: { template_id: params[:template_id] })
|
||||
end
|
||||
|
||||
maybe_filder_by_completed_at(submitters, params)
|
||||
maybe_filter_by_completed_at(submitters, params)
|
||||
end
|
||||
|
||||
def assign_external_id(submitter, attrs)
|
||||
|
||||
@@ -48,7 +48,7 @@ module Api
|
||||
|
||||
archived = params.key?(:archived) ? params[:archived] : params.dig(:template, :archived)
|
||||
|
||||
if archived.in?([true, false])
|
||||
if archived.in?([true, false]) && current_ability.can?(:destroy, @template)
|
||||
@template.archived_at = archived == true ? Time.current : nil
|
||||
end
|
||||
|
||||
@@ -57,7 +57,10 @@ module Api
|
||||
SearchEntries.enqueue_reindex(@template)
|
||||
|
||||
WebhookUrls.enqueue_events(@template, 'template.updated')
|
||||
WebhookUrls.enqueue_events(@template, 'template.archived') if archived == true
|
||||
|
||||
if @template.saved_change_to_archived_at? && @template.archived_at?
|
||||
WebhookUrls.enqueue_events(@template, 'template.archived')
|
||||
end
|
||||
|
||||
render json: @template.as_json(only: %i[id updated_at])
|
||||
end
|
||||
|
||||
@@ -25,7 +25,10 @@ class PreviewDocumentPageController < ActionController::API
|
||||
preview_image = attachment.preview_images.joins(:blob)
|
||||
.find_by(blob: { filename: ["#{params[:id]}.png", "#{params[:id]}.jpg"] })
|
||||
|
||||
return redirect_to preview_image.url, allow_other_host: true if preview_image
|
||||
if preview_image
|
||||
return redirect_to preview_image.url(time: ActiveStorage::Attachment.service_url_time),
|
||||
allow_other_host: true
|
||||
end
|
||||
|
||||
file_path =
|
||||
if attachment.service.name == :disk
|
||||
@@ -37,7 +40,7 @@ class PreviewDocumentPageController < ActionController::API
|
||||
preview_image =
|
||||
Templates::ProcessDocument.generate_pdf_preview_from_file(attachment, file_path, params[:id].to_i)
|
||||
|
||||
redirect_to preview_image.url, allow_other_host: true
|
||||
redirect_to preview_image.url(time: ActiveStorage::Attachment.service_url_time), allow_other_host: true
|
||||
end
|
||||
|
||||
def find_or_create_document_tempfile_path(attachment)
|
||||
|
||||
@@ -108,12 +108,16 @@ class StartFormController < ApplicationController
|
||||
end
|
||||
|
||||
def can_resubmit?(submitter)
|
||||
submitter.account.account_configs.find_or_initialize_by(key: AccountConfig::ALLOW_TO_RESUBMIT).value != false
|
||||
submitter.completed_at? && submitter.completed_at > 14.days.ago &&
|
||||
%w[api embed mcp].exclude?(submitter.submission.source) &&
|
||||
submitter.account.account_configs.find_or_initialize_by(key: AccountConfig::ALLOW_TO_RESUBMIT).value != false
|
||||
end
|
||||
|
||||
def authorize_start!
|
||||
return redirect_to submit_form_path(@resubmit_submitter.slug) if @resubmit_submitter && @template.archived_at?
|
||||
return redirect_to start_form_path(@template.slug) if @template.archived_at?
|
||||
is_archived = @template.archived_at? || @template.account.archived_at?
|
||||
|
||||
return redirect_to submit_form_path(@resubmit_submitter.slug) if @resubmit_submitter && is_archived
|
||||
return redirect_to start_form_path(@template.slug) if is_archived
|
||||
|
||||
return if @resubmit_submitter
|
||||
return if @template.shared_link? || (current_user && current_ability.can?(:read, @template))
|
||||
|
||||
@@ -7,17 +7,27 @@ class SubmissionsArchivedController < ApplicationController
|
||||
@submissions = @submissions.left_joins(:template)
|
||||
@submissions = @submissions.where.not(archived_at: nil)
|
||||
.or(@submissions.where.not(templates: { archived_at: nil }))
|
||||
.preload(:template_accesses, :created_by_user, template: :author)
|
||||
.preload(:template_accesses, :created_by_user)
|
||||
|
||||
@submissions = Submissions.search(current_user, @submissions, params[:q], search_template: true)
|
||||
@submissions = Submissions::Filter.call(@submissions, current_user, params)
|
||||
|
||||
@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
|
||||
@submissions =
|
||||
if params[:status] == 'completed' || params[:completed_at_from].present? || params[:completed_at_to].present?
|
||||
@submissions.order(completed_at: :desc)
|
||||
else
|
||||
@submissions.order(id: :desc)
|
||||
end
|
||||
|
||||
@pagy, @submissions = pagy_auto(@submissions.preload(submitters: :start_form_submission_events))
|
||||
@pagy, @submissions = pagy_auto(@submissions.select_for_list.preload(submitters: :start_form_submission_events))
|
||||
|
||||
template_scope = @submissions.all?(&:template_submitters) ? Template.select_for_list : nil
|
||||
|
||||
ActiveRecord::Associations::Preloader.new(records: @submissions,
|
||||
associations: :template,
|
||||
scope: template_scope).call
|
||||
|
||||
ActiveRecord::Associations::Preloader.new(records: @submissions.filter_map(&:template),
|
||||
associations: :author).call
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class SubmissionsController < ApplicationController
|
||||
before_action :load_template, only: %i[new create]
|
||||
authorize_resource :template, only: %i[new create]
|
||||
|
||||
load_and_authorize_resource :template, only: %i[new create]
|
||||
load_and_authorize_resource :submission, only: %i[show destroy]
|
||||
|
||||
prepend_before_action :maybe_redirect_com, only: %i[show]
|
||||
@@ -23,7 +21,7 @@ class SubmissionsController < ApplicationController
|
||||
def show
|
||||
@submission = Submissions.preload_with_pages(@submission)
|
||||
|
||||
unless @submission.submitters.all?(&:completed_at?)
|
||||
unless @submission.completed_at?
|
||||
ActiveRecord::Associations::Preloader.new(
|
||||
records: [@submission],
|
||||
associations: [{ submitters: :start_form_submission_events }]
|
||||
@@ -113,8 +111,4 @@ class SubmissionsController < ApplicationController
|
||||
def submissions_params
|
||||
params.permit(submission: { submitters: [:uuid, :email, :phone, :name, { values: {} }] })
|
||||
end
|
||||
|
||||
def load_template
|
||||
@template = Template.accessible_by(current_ability).find(params[:template_id])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -8,17 +8,27 @@ class SubmissionsDashboardController < ApplicationController
|
||||
|
||||
@submissions = @submissions.where(archived_at: nil)
|
||||
.where(templates: { archived_at: nil })
|
||||
.preload(:template_accesses, :created_by_user, template: :author)
|
||||
.preload(:template_accesses, :created_by_user)
|
||||
|
||||
@submissions = Submissions.search(current_user, @submissions, params[:q], search_template: true)
|
||||
@submissions = Submissions::Filter.call(@submissions, current_user, params)
|
||||
|
||||
@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
|
||||
@submissions =
|
||||
if params[:status] == 'completed' || params[:completed_at_from].present? || params[:completed_at_to].present?
|
||||
@submissions.order(completed_at: :desc)
|
||||
else
|
||||
@submissions.order(id: :desc)
|
||||
end
|
||||
|
||||
@pagy, @submissions = pagy_auto(@submissions.preload(submitters: :start_form_submission_events))
|
||||
@pagy, @submissions = pagy_auto(@submissions.select_for_list.preload(submitters: :start_form_submission_events))
|
||||
|
||||
template_scope = @submissions.all?(&:template_submitters) ? Template.select_for_list : nil
|
||||
|
||||
ActiveRecord::Associations::Preloader.new(records: @submissions,
|
||||
associations: :template,
|
||||
scope: template_scope).call
|
||||
|
||||
ActiveRecord::Associations::Preloader.new(records: @submissions.filter_map(&:template),
|
||||
associations: :author).call
|
||||
end
|
||||
end
|
||||
|
||||
@@ -25,7 +25,7 @@ class SubmissionsPreviewController < ApplicationController
|
||||
|
||||
raise ActionController::RoutingError, I18n.t('not_found') if @submission.account.archived_at?
|
||||
|
||||
if !@submission.submitters.all?(&:completed_at?) && !signature_valid &&
|
||||
if !@submission.completed_at? && !signature_valid &&
|
||||
(!current_user || !current_ability.can?(:read, @submission))
|
||||
raise ActionController::RoutingError, I18n.t('not_found')
|
||||
end
|
||||
|
||||
@@ -4,7 +4,7 @@ class SubmissionsUnarchiveController < ApplicationController
|
||||
load_and_authorize_resource :submission
|
||||
|
||||
def create
|
||||
authorize!(:update, @submission)
|
||||
authorize!(:destroy, @submission)
|
||||
|
||||
@submission.update!(archived_at: nil)
|
||||
|
||||
|
||||
@@ -7,7 +7,8 @@ class SubmitFormDeclineController < ApplicationController
|
||||
before_action :load_submitter
|
||||
|
||||
def create
|
||||
return redirect_to submit_form_path(@submitter.slug) if @submitter.declined_at? ||
|
||||
return redirect_to submit_form_path(@submitter.slug) if declining_disabled? ||
|
||||
@submitter.declined_at? ||
|
||||
@submitter.completed_at? ||
|
||||
@submitter.submission.archived_at? ||
|
||||
@submitter.submission.expired? ||
|
||||
@@ -35,6 +36,10 @@ class SubmitFormDeclineController < ApplicationController
|
||||
|
||||
private
|
||||
|
||||
def declining_disabled?
|
||||
@submitter.account.account_configs.find_by(key: AccountConfig::ALLOW_TO_DECLINE_KEY)&.value == false
|
||||
end
|
||||
|
||||
def load_submitter
|
||||
@submitter = Submitter.find_by!(slug: params[:submit_form_slug])
|
||||
end
|
||||
|
||||
@@ -13,7 +13,7 @@ class SubmittersController < ApplicationController
|
||||
def update
|
||||
submission = @submitter.submission
|
||||
|
||||
if @submitter.submission_events.exists?(event_type: 'start_form') || submission.archived_at? || submission.expired?
|
||||
unless submitter_editable?(submission)
|
||||
return redirect_back fallback_location: submission_path(submission), alert: I18n.t('submitter_cannot_be_updated')
|
||||
end
|
||||
|
||||
@@ -48,6 +48,12 @@ class SubmittersController < ApplicationController
|
||||
|
||||
private
|
||||
|
||||
def submitter_editable?(submission)
|
||||
!@submitter.submission_events.exists?(event_type: 'start_form') &&
|
||||
!@submitter.completed_at? && !@submitter.declined_at? &&
|
||||
!submission.archived_at? && !submission.expired? && !submission.template&.archived_at?
|
||||
end
|
||||
|
||||
def maybe_resend_email_sms(submitter, params)
|
||||
if params[:send_email] == '1' && submitter.email.present?
|
||||
is_sent_recently = Docuseal.multitenant? &&
|
||||
|
||||
@@ -5,6 +5,8 @@ class SubmittersResubmitController < ApplicationController
|
||||
|
||||
def update
|
||||
return redirect_to submit_form_path(slug: @submitter.slug) if @submitter.email != current_user.email
|
||||
return redirect_to submit_form_path(slug: @submitter.slug) if @submitter.completed_at.blank? ||
|
||||
@submitter.completed_at < 1.month.ago
|
||||
|
||||
submission = @submitter.account.submissions.new(created_by_user: current_user,
|
||||
submitters_order: :preserved,
|
||||
|
||||
@@ -10,7 +10,7 @@ class TemplateDocumentsModifyController < ApplicationController
|
||||
params.require(:documents).map do |item|
|
||||
item.permit(:attachment_uuid,
|
||||
pages: [:attachment_uuid, :page, :rotate,
|
||||
{ redact: [%i[x y w h]], replaced_page: %i[attachment_uuid page] }]).to_h
|
||||
{ redact: [%i[x y w h color]], replaced_page: %i[attachment_uuid page] }]).to_h
|
||||
end
|
||||
|
||||
Templates::ModifyDocuments.call(@template, documents_layout)
|
||||
|
||||
@@ -30,9 +30,11 @@ class TemplateFoldersController < ApplicationController
|
||||
(@template_folders.size < 7 ? 9 : 6)
|
||||
end
|
||||
|
||||
@pagy, @templates = pagy_auto(@templates, limit:)
|
||||
@pagy, @templates = pagy_auto(@templates.select_for_list, limit:)
|
||||
|
||||
load_related_submissions if params[:q].present? && @templates.blank?
|
||||
if params[:q].present? && @templates.blank?
|
||||
@related_submissions_pagy, @related_submissions = load_related_submissions(@template_folder)
|
||||
end
|
||||
else
|
||||
@pagy, @template_folders = pagy(@template_folders, limit: FOLDERS_PER_PAGE)
|
||||
|
||||
@@ -55,11 +57,10 @@ class TemplateFoldersController < ApplicationController
|
||||
|
||||
def selected_order
|
||||
@selected_order ||=
|
||||
if cookies.permanent[:dashboard_templates_order].blank? ||
|
||||
(cookies.permanent[:dashboard_templates_order] == 'used_at' && can?(:manage, :countless))
|
||||
if can?(:manage, :countless)
|
||||
'created_at'
|
||||
else
|
||||
cookies.permanent[:dashboard_templates_order]
|
||||
cookies.permanent[:dashboard_templates_order].presence || 'created_at'
|
||||
end
|
||||
end
|
||||
|
||||
@@ -67,20 +68,20 @@ class TemplateFoldersController < ApplicationController
|
||||
params.require(:template_folder).permit(:name)
|
||||
end
|
||||
|
||||
def load_related_submissions
|
||||
@related_submissions =
|
||||
def load_related_submissions(template_folder)
|
||||
related_submissions =
|
||||
Submission.accessible_by(current_ability)
|
||||
.where(archived_at: nil)
|
||||
.where(template_id: current_account.templates.active
|
||||
.where(folder: [@template_folder, *@template_folder.subfolders])
|
||||
.where(folder: [template_folder, *template_folder.subfolders])
|
||||
.select(:id))
|
||||
.preload(:template_accesses, :created_by_user,
|
||||
template: :author,
|
||||
submitters: :start_form_submission_events)
|
||||
|
||||
@related_submissions = Submissions.search(current_user, @related_submissions, params[:q])
|
||||
.order(id: :desc)
|
||||
related_submissions = Submissions.search(current_user, related_submissions, params[:q])
|
||||
.order(id: :desc)
|
||||
|
||||
@related_submissions_pagy, @related_submissions = pagy_auto(@related_submissions, limit: 5)
|
||||
pagy_auto(related_submissions.select_for_list, limit: 5)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -10,11 +10,17 @@ class TemplatesArchivedController < ApplicationController
|
||||
|
||||
@templates = Templates.search(current_user, @templates, params[:q])
|
||||
|
||||
@pagy, @templates = pagy_auto(@templates, limit: 12)
|
||||
@pagy, @templates = pagy_auto(@templates.select_for_list, limit: 12)
|
||||
|
||||
return unless params[:q].present? && @templates.blank?
|
||||
|
||||
@related_submissions =
|
||||
@related_submissions_pagy, @related_submissions = load_related_submissions
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_related_submissions
|
||||
related_submissions =
|
||||
Submission.accessible_by(current_ability)
|
||||
.joins(:template)
|
||||
.where.not(templates: { archived_at: nil })
|
||||
@@ -22,9 +28,9 @@ class TemplatesArchivedController < ApplicationController
|
||||
template: :author,
|
||||
submitters: :start_form_submission_events)
|
||||
|
||||
@related_submissions = Submissions.search(current_user, @related_submissions, params[:q])
|
||||
.order(id: :desc)
|
||||
related_submissions = Submissions.search(current_user, related_submissions, params[:q])
|
||||
.order(id: :desc)
|
||||
|
||||
@related_submissions_pagy, @related_submissions = pagy_auto(@related_submissions, limit: 5)
|
||||
pagy_auto(related_submissions.select_for_list, limit: 5)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -10,12 +10,12 @@ class TemplatesArchivedSubmissionsController < ApplicationController
|
||||
@submissions = Submissions::Filter.call(@submissions, current_user, params)
|
||||
|
||||
@submissions = if params[:completed_at_from].present? || params[:completed_at_to].present?
|
||||
@submissions.order(Submitter.arel_table[:completed_at].maximum.desc)
|
||||
@submissions.order(completed_at: :desc)
|
||||
else
|
||||
@submissions.order(id: :desc)
|
||||
end
|
||||
|
||||
@pagy, @submissions = pagy_auto(@submissions.preload(submitters: :start_form_submission_events))
|
||||
@pagy, @submissions = pagy_auto(@submissions.select_for_list.preload(submitters: :start_form_submission_events))
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
redirect_to root_path
|
||||
end
|
||||
|
||||
@@ -14,12 +14,13 @@ class TemplatesController < ApplicationController
|
||||
submissions = Submissions::Filter.filter_by_status(submissions, params)
|
||||
|
||||
submissions = if params[:completed_at_from].present? || params[:completed_at_to].present?
|
||||
submissions.order(Submitter.arel_table[:completed_at].maximum.desc)
|
||||
submissions.order(completed_at: :desc)
|
||||
else
|
||||
submissions.order(id: :desc)
|
||||
end
|
||||
|
||||
@pagy, @submissions = pagy_auto(submissions.preload(:template_accesses, submitters: :start_form_submission_events))
|
||||
@pagy, @submissions =
|
||||
pagy_auto(submissions.select_for_list.preload(:template_accesses, submitters: :start_form_submission_events))
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
redirect_to root_path
|
||||
end
|
||||
|
||||
@@ -11,86 +11,126 @@ class TemplatesDashboardController < ApplicationController
|
||||
helper_method :selected_order
|
||||
|
||||
def index
|
||||
@default_folder = current_account.default_template_folder
|
||||
|
||||
@template_folders =
|
||||
TemplateFolders.filter_active_folders(@template_folders.where(parent_folder_id: nil), @templates)
|
||||
|
||||
@template_folders = @template_folders.where.not(id: @default_folder.id) if params[:q].blank?
|
||||
|
||||
@template_folders = TemplateFolders.search(@template_folders, params[:q])
|
||||
@template_folders = TemplateFolders.sort(@template_folders, current_user, selected_order)
|
||||
|
||||
@pagy, @template_folders = pagy(
|
||||
@template_folders,
|
||||
limit: FOLDERS_PER_PAGE,
|
||||
page: @template_folders.count > SHOW_TEMPLATES_FOLDERS_THRESHOLD ? params[:page] : 1
|
||||
)
|
||||
@shared_templates = Templates.shared(current_user).active
|
||||
|
||||
@pagy, @template_folders, @show_default_folder, @show_shared_folder, @show_shared_inline =
|
||||
load_folders(@template_folders, @templates, @shared_templates)
|
||||
|
||||
if @pagy.count > SHOW_TEMPLATES_FOLDERS_THRESHOLD
|
||||
@templates = @templates.none
|
||||
else
|
||||
@template_folders = @template_folders.reject { |e| e.name == TemplateFolder::DEFAULT_NAME }
|
||||
@templates = filter_templates(@templates).preload(:author, :template_accesses)
|
||||
@templates = Templates::Order.call(@templates, current_user, selected_order)
|
||||
if @show_shared_inline
|
||||
@templates = @shared_templates.preload(:template_sharings)
|
||||
else
|
||||
@templates = @templates.active
|
||||
@templates = @templates.where(folder_id: @default_folder.id) if params[:q].blank?
|
||||
end
|
||||
|
||||
limit =
|
||||
if @template_folders.size < 4
|
||||
TEMPLATES_PER_PAGE
|
||||
else
|
||||
(@template_folders.size < 7 ? 9 : 6)
|
||||
end
|
||||
@pagy, @templates = load_templates(@templates.select_for_list, @pagy.count,
|
||||
show_shared_inline: @show_shared_inline)
|
||||
|
||||
@pagy, @templates = pagy_auto(@templates, limit:)
|
||||
|
||||
load_related_submissions if params[:q].present? && @templates.blank?
|
||||
if params[:q].present? && @templates.blank?
|
||||
@related_submissions_pagy, @related_submissions = load_related_submissions
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def filter_templates(templates)
|
||||
rel = templates.active
|
||||
def load_templates(templates, folders_count, show_shared_inline: false)
|
||||
templates = templates.preload(:author, :template_accesses)
|
||||
|
||||
if params[:q].blank?
|
||||
if Docuseal.multitenant? ? current_account.testing? : current_account.linked_account_account
|
||||
shared_account_ids = [current_user.account_id]
|
||||
shared_account_ids << TemplateSharing::ALL_ID if !Docuseal.multitenant? && !current_account.testing?
|
||||
|
||||
shared_template_ids = TemplateSharing.where(account_id: shared_account_ids).select(:template_id)
|
||||
|
||||
rel = Template.where(
|
||||
Template.arel_table[:id].in(
|
||||
rel.where(folder_id: current_account.default_template_folder.id).select(:id).arel
|
||||
.union(:all, shared_template_ids.arel)
|
||||
)
|
||||
)
|
||||
templates =
|
||||
if show_shared_inline
|
||||
Templates.search_shared(current_user, templates, params[:q])
|
||||
else
|
||||
rel = rel.where(folder_id: current_account.default_template_folder.id)
|
||||
Templates.search(current_user, templates, params[:q])
|
||||
end
|
||||
end
|
||||
|
||||
Templates.search(current_user, rel, params[:q])
|
||||
templates = Templates::Order.call(templates, current_user, selected_order)
|
||||
|
||||
limit =
|
||||
if folders_count < 4
|
||||
TEMPLATES_PER_PAGE
|
||||
else
|
||||
(folders_count < 7 ? 9 : 6)
|
||||
end
|
||||
|
||||
pagy_auto(templates, limit:)
|
||||
end
|
||||
|
||||
def load_folders(template_folders, templates, shared_templates)
|
||||
if params[:q].present?
|
||||
pagy(template_folders, limit: FOLDERS_PER_PAGE,
|
||||
page: template_folders.count > SHOW_TEMPLATES_FOLDERS_THRESHOLD ? params[:page] : 1)
|
||||
else
|
||||
load_folders_with_pinned(template_folders, templates, shared_templates)
|
||||
end
|
||||
end
|
||||
|
||||
def load_folders_with_pinned(template_folders, templates, shared_templates)
|
||||
folders_count = template_folders.count
|
||||
|
||||
shared_exists = shared_templates.exists?
|
||||
default_has_templates = templates.active.exists?(folder_id: current_account.default_template_folder.id)
|
||||
|
||||
show_inline_folders =
|
||||
folders_count + (shared_exists && default_has_templates ? 1 : 0) <= SHOW_TEMPLATES_FOLDERS_THRESHOLD
|
||||
|
||||
show_shared_inline = shared_exists && !default_has_templates && show_inline_folders
|
||||
|
||||
show_shared_in_grid = shared_exists && !show_shared_inline
|
||||
show_default_in_grid = !show_inline_folders && default_has_templates
|
||||
|
||||
pinned_count = (show_default_in_grid ? 1 : 0) + (show_shared_in_grid ? 1 : 0)
|
||||
|
||||
pagy = Pagy::Offset.new(count: folders_count + pinned_count,
|
||||
page: show_inline_folders ? 1 : [params[:page].to_s.to_i, 1].max,
|
||||
limit: FOLDERS_PER_PAGE,
|
||||
raise_range_error: true)
|
||||
|
||||
show_default_folder = show_default_in_grid && pagy.page == 1
|
||||
show_shared_folder = show_shared_in_grid && pagy.page == 1
|
||||
|
||||
folder_offset = pagy.page == 1 ? 0 : pagy.offset - pinned_count
|
||||
folder_limit = pagy.page == 1 ? FOLDERS_PER_PAGE - pinned_count : FOLDERS_PER_PAGE
|
||||
|
||||
template_folders = template_folders.offset(folder_offset).limit(folder_limit)
|
||||
|
||||
[pagy, template_folders, show_default_folder, show_shared_folder, show_shared_inline]
|
||||
end
|
||||
|
||||
def selected_order
|
||||
@selected_order ||=
|
||||
if cookies.permanent[:dashboard_templates_order].blank? ||
|
||||
(cookies.permanent[:dashboard_templates_order] == 'used_at' && can?(:manage, :countless))
|
||||
if can?(:manage, :countless)
|
||||
'created_at'
|
||||
else
|
||||
cookies.permanent[:dashboard_templates_order]
|
||||
cookies.permanent[:dashboard_templates_order].presence || 'created_at'
|
||||
end
|
||||
end
|
||||
|
||||
def load_related_submissions
|
||||
@related_submissions = Submission.accessible_by(current_ability)
|
||||
.left_joins(:template)
|
||||
.where(archived_at: nil)
|
||||
.where(templates: { archived_at: nil })
|
||||
.preload(:template_accesses, :created_by_user,
|
||||
template: :author,
|
||||
submitters: :start_form_submission_events)
|
||||
related_submissions = Submission.accessible_by(current_ability)
|
||||
.left_joins(:template)
|
||||
.where(archived_at: nil)
|
||||
.where(templates: { archived_at: nil })
|
||||
.preload(:template_accesses, :created_by_user,
|
||||
template: :author,
|
||||
submitters: :start_form_submission_events)
|
||||
|
||||
@related_submissions = Submissions.search(current_user, @related_submissions, params[:q])
|
||||
.order(id: :desc)
|
||||
related_submissions = Submissions.search(current_user, related_submissions, params[:q])
|
||||
.order(id: :desc)
|
||||
|
||||
@related_submissions_pagy, @related_submissions = pagy_auto(@related_submissions, limit: 5)
|
||||
pagy_auto(related_submissions.select_for_list, limit: 5)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,7 +4,7 @@ class TemplatesRestoreController < ApplicationController
|
||||
load_and_authorize_resource :template
|
||||
|
||||
def create
|
||||
authorize!(:update, @template)
|
||||
authorize!(:destroy, @template)
|
||||
|
||||
@template.update!(archived_at: nil)
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class TemplatesSharedController < ApplicationController
|
||||
def index
|
||||
authorize!(:read, Template)
|
||||
|
||||
@is_archived = params[:archived] == 'true'
|
||||
|
||||
@templates = Templates.shared(current_user)
|
||||
|
||||
@has_archived = !@is_archived && @templates.archived.exists?
|
||||
|
||||
@templates = @is_archived ? @templates.archived : @templates.active
|
||||
|
||||
@templates = @templates.preload(:author, :template_accesses, :template_sharings)
|
||||
.order(id: :desc)
|
||||
|
||||
@templates = Templates.search_shared(current_user, @templates, params[:q])
|
||||
|
||||
@pagy, @templates = pagy_auto(@templates.select_for_list, limit: 12)
|
||||
|
||||
return unless params[:q].present? && @templates.blank?
|
||||
|
||||
@related_submissions_pagy, @related_submissions = load_related_submissions(is_archived: @is_archived)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_related_submissions(is_archived:)
|
||||
shared_templates = Templates.shared(current_user)
|
||||
shared_templates = is_archived ? shared_templates.archived : shared_templates.active
|
||||
|
||||
related_submissions =
|
||||
Submission.accessible_by(current_ability)
|
||||
.where(template_id: shared_templates.select(:id))
|
||||
.preload(:template_accesses, :created_by_user,
|
||||
template: :author,
|
||||
submitters: :start_form_submission_events)
|
||||
|
||||
related_submissions = related_submissions.where(archived_at: nil) unless is_archived
|
||||
|
||||
related_submissions = Submissions.search(current_user, related_submissions, params[:q])
|
||||
.order(id: :desc)
|
||||
|
||||
pagy_auto(related_submissions.select_for_list, limit: 5)
|
||||
end
|
||||
end
|
||||
@@ -289,6 +289,7 @@
|
||||
:id="currentField.uuid"
|
||||
dir="auto"
|
||||
:required="currentField.required"
|
||||
:aria-label="showFieldNames && (currentField.name || currentField.title) ? undefined : (currentField.name || currentField.title || t('select_your_option'))"
|
||||
:aria-describedby="currentField.description ? currentField.uuid + '-desc' : undefined"
|
||||
class="select base-input !text-2xl w-full text-center font-normal"
|
||||
:class="{ 'text-gray-300': !values[currentField.uuid] }"
|
||||
@@ -317,7 +318,7 @@
|
||||
<div v-else-if="currentField.type === 'radio'">
|
||||
<label
|
||||
v-if="showFieldNames && (currentField.name || currentField.title)"
|
||||
:for="currentField.uuid"
|
||||
:id="currentField.uuid + '-radio-label'"
|
||||
dir="auto"
|
||||
class="label text-xl sm:text-2xl py-0 mb-2 sm:mb-3.5 field-name-label"
|
||||
:class="{ 'mb-2': !currentField.description }"
|
||||
@@ -356,6 +357,8 @@
|
||||
</div>
|
||||
<div
|
||||
class="space-y-3.5 mx-auto"
|
||||
role="radiogroup"
|
||||
:aria-labelledby="(currentField.name || currentField.title) ? currentField.uuid + '-radio-label' : null"
|
||||
:class="{ hidden: !showFieldNames || (currentField.options.every((e) => !e.value) && currentField.options.length > 4) }"
|
||||
>
|
||||
<div
|
||||
|
||||
@@ -4,6 +4,7 @@ const en = {
|
||||
close: 'Close',
|
||||
uploaded_files: 'Uploaded files',
|
||||
signature_drawing_area: 'Signature drawing area. Use mouse or touch to draw your signature.',
|
||||
initials_drawing_area: 'Initials drawing area. Use mouse or touch to draw your initials.',
|
||||
kba: 'KBA',
|
||||
please_upload_an_image_file: 'Please upload an image file',
|
||||
must_be_characters_length: 'Must be {number} characters long',
|
||||
@@ -88,6 +89,7 @@ const en = {
|
||||
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',
|
||||
country_code: 'Country code',
|
||||
use_international_format: 'Use international format: +1xxx',
|
||||
six_digits_code: '6-digit code',
|
||||
change_phone_number: 'Change phone number',
|
||||
@@ -120,6 +122,7 @@ const es = {
|
||||
close: 'Cerrar',
|
||||
uploaded_files: 'Archivos subidos',
|
||||
signature_drawing_area: 'Área de dibujo de firma. Use el ratón o toque para dibujar su firma.',
|
||||
initials_drawing_area: 'Área de dibujo de iniciales. Use el ratón o toque para dibujar sus iniciales.',
|
||||
kba: 'KBA',
|
||||
please_upload_an_image_file: 'Por favor, sube un archivo de imagen',
|
||||
must_be_characters_length: 'Debe tener {number} caracteres de longitud',
|
||||
@@ -204,6 +207,7 @@ const es = {
|
||||
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',
|
||||
country_code: 'Código de país',
|
||||
use_international_format: 'Usar formato internacional: +1xxx',
|
||||
six_digits_code: 'Código de 6 dígitos',
|
||||
change_phone_number: 'Cambiar número de teléfono',
|
||||
@@ -236,6 +240,7 @@ const it = {
|
||||
close: 'Chiudi',
|
||||
uploaded_files: 'File caricati',
|
||||
signature_drawing_area: 'Area di disegno della firma. Usa il mouse o il tocco per disegnare la tua firma.',
|
||||
initials_drawing_area: 'Area di disegno delle iniziali. Usa il mouse o il tocco per disegnare le tue iniziali.',
|
||||
kba: 'KBA',
|
||||
please_upload_an_image_file: 'Per favore carica un file immagine',
|
||||
must_be_characters_length: 'Deve essere lungo {number} caratteri',
|
||||
@@ -320,6 +325,7 @@ const it = {
|
||||
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',
|
||||
country_code: 'Prefisso internazionale',
|
||||
use_international_format: 'Usa formato internazionale: +1xxx',
|
||||
six_digits_code: 'Codice a 6 cifre',
|
||||
change_phone_number: 'Cambia numero di telefono',
|
||||
@@ -352,6 +358,7 @@ const de = {
|
||||
close: 'Schließen',
|
||||
uploaded_files: 'Hochgeladene Dateien',
|
||||
signature_drawing_area: 'Unterschriftszeichenbereich. Verwenden Sie die Maus oder Berührung, um Ihre Unterschrift zu zeichnen.',
|
||||
initials_drawing_area: 'Zeichenbereich für Initialen. Verwenden Sie die Maus oder Berührung, um Ihre Initialen zu zeichnen.',
|
||||
kba: 'KBA',
|
||||
please_upload_an_image_file: 'Bitte laden Sie eine Bilddatei hoch',
|
||||
must_be_characters_length: 'Muss {number} Zeichen lang sein',
|
||||
@@ -436,6 +443,7 @@ const de = {
|
||||
please_check_the_box_to_continue: 'Bitte aktivieren Sie das Kontrollkästchen, um fortzufahren.',
|
||||
open_source_documents_software: 'Open-Source-Dokumentensoftware',
|
||||
verified_phone_number: 'Telefonnummer verifizieren',
|
||||
country_code: 'Ländervorwahl',
|
||||
use_international_format: 'Internationales Format verwenden: +1xxx',
|
||||
six_digits_code: '6-stelliger Code',
|
||||
change_phone_number: 'Telefonnummer ändern',
|
||||
@@ -468,6 +476,7 @@ const fr = {
|
||||
close: 'Fermer',
|
||||
uploaded_files: 'Fichiers téléchargés',
|
||||
signature_drawing_area: 'Zone de dessin de signature. Utilisez la souris ou le toucher pour dessiner votre signature.',
|
||||
initials_drawing_area: 'Zone de dessin des initiales. Utilisez la souris ou le toucher pour dessiner vos initiales.',
|
||||
kba: 'KBA',
|
||||
please_upload_an_image_file: 'Veuillez téléverser un fichier image',
|
||||
must_be_characters_length: 'Doit comporter {number} caractères',
|
||||
@@ -552,6 +561,7 @@ const fr = {
|
||||
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',
|
||||
country_code: 'Indicatif du pays',
|
||||
use_international_format: 'Utilisez le format international : +1xxx',
|
||||
six_digits_code: 'Code à 6 chiffres',
|
||||
change_phone_number: 'Changer de numéro de téléphone',
|
||||
@@ -584,6 +594,7 @@ const pl = {
|
||||
close: 'Zamknij',
|
||||
uploaded_files: 'Przesłane pliki',
|
||||
signature_drawing_area: 'Obszar rysowania podpisu. Użyj myszy lub dotyku, aby narysować swój podpis.',
|
||||
initials_drawing_area: 'Obszar rysowania inicjałów. Użyj myszy lub dotyku, aby narysować swoje inicjały.',
|
||||
kba: 'KBA',
|
||||
please_upload_an_image_file: 'Proszę przesłać plik obrazu',
|
||||
must_be_characters_length: 'Musi mieć długość {number} znaków',
|
||||
@@ -668,6 +679,7 @@ const pl = {
|
||||
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',
|
||||
country_code: 'Kod kraju',
|
||||
use_international_format: 'Użyj międzynarodowego formatu: +1xxx',
|
||||
six_digits_code: '6-cyfrowy kod',
|
||||
change_phone_number: 'Zmień numer telefonu',
|
||||
@@ -700,6 +712,7 @@ const uk = {
|
||||
close: 'Закрити',
|
||||
uploaded_files: 'Завантажені файли',
|
||||
signature_drawing_area: 'Область малювання підпису. Використовуйте мишу або дотик, щоб намалювати свій підпис.',
|
||||
initials_drawing_area: 'Область малювання ініціалів. Використовуйте мишу або дотик, щоб намалювати свої ініціали.',
|
||||
kba: 'KBA',
|
||||
please_upload_an_image_file: 'Будь ласка, завантажте файл зображення',
|
||||
must_be_characters_length: 'Має містити {number} символів',
|
||||
@@ -784,6 +797,7 @@ const uk = {
|
||||
please_check_the_box_to_continue: 'Будь ласка, позначте прапорець, щоб продовжити.',
|
||||
open_source_documents_software: 'відкрите програмне забезпечення для документів',
|
||||
verified_phone_number: 'Підтвердіть номер телефону',
|
||||
country_code: 'Код країни',
|
||||
use_international_format: 'Використовуйте міжнародний формат: +1xxx',
|
||||
six_digits_code: '6-значний код',
|
||||
change_phone_number: 'Змінити номер телефону',
|
||||
@@ -816,6 +830,7 @@ const cs = {
|
||||
close: 'Zavřít',
|
||||
uploaded_files: 'Nahrané soubory',
|
||||
signature_drawing_area: 'Oblast pro kreslení podpisu. Použijte myš nebo dotyk k nakreslení podpisu.',
|
||||
initials_drawing_area: 'Oblast pro kreslení iniciál. Použijte myš nebo dotyk k nakreslení iniciál.',
|
||||
kba: 'KBA',
|
||||
please_upload_an_image_file: 'Nahrajte prosím obrázkový soubor',
|
||||
must_be_characters_length: 'Musí mít délku {number} znaků',
|
||||
@@ -900,6 +915,7 @@ const cs = {
|
||||
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',
|
||||
country_code: 'Předvolba země',
|
||||
use_international_format: 'Použijte mezinárodní formát: +1xxx',
|
||||
six_digits_code: '6-místný kód',
|
||||
change_phone_number: 'Změnit telefonní číslo',
|
||||
@@ -932,6 +948,7 @@ const pt = {
|
||||
close: 'Fechar',
|
||||
uploaded_files: 'Arquivos enviados',
|
||||
signature_drawing_area: 'Área de desenho da assinatura. Use o mouse ou toque para desenhar sua assinatura.',
|
||||
initials_drawing_area: 'Área de desenho das iniciais. Use o mouse ou toque para desenhar suas iniciais.',
|
||||
kba: 'KBA',
|
||||
please_upload_an_image_file: 'Por favor, envie um arquivo de imagem',
|
||||
must_be_characters_length: 'Deve ter {number} caracteres',
|
||||
@@ -1016,6 +1033,7 @@ const pt = {
|
||||
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',
|
||||
country_code: 'Código do país',
|
||||
use_international_format: 'Use formato internacional: +1xxx',
|
||||
six_digits_code: 'Código de 6 dígitos',
|
||||
change_phone_number: 'Alterar número de telefone',
|
||||
@@ -1048,6 +1066,7 @@ const he = {
|
||||
close: 'סגור',
|
||||
uploaded_files: 'קבצים שהועלו',
|
||||
signature_drawing_area: 'אזור ציור חתימה. השתמש בעכבר או במגע כדי לצייר את החתימה שלך.',
|
||||
initials_drawing_area: 'אזור ציור ראשי תיבות. השתמש בעכבר או במגע כדי לצייר את ראשי התיבות שלך.',
|
||||
kba: 'KBA',
|
||||
please_upload_an_image_file: 'אנא העלה קובץ תמונה',
|
||||
must_be_characters_length: 'חייב להיות באורך של {number} תווים',
|
||||
@@ -1132,6 +1151,7 @@ const he = {
|
||||
please_check_the_box_to_continue: 'אנא סמן את התיבה כדי להמשיך.',
|
||||
open_source_documents_software: 'תוכנה פתוחה למסמכים',
|
||||
verified_phone_number: 'אימות מספר טלפון',
|
||||
country_code: 'קידומת מדינה',
|
||||
use_international_format: 'השתמש בפורמט בינלאומי: +1xxx',
|
||||
six_digits_code: 'קוד שש ספרות',
|
||||
change_phone_number: 'שינוי מספר טלפון',
|
||||
@@ -1164,6 +1184,7 @@ const nl = {
|
||||
close: 'Sluiten',
|
||||
uploaded_files: 'Geüploade bestanden',
|
||||
signature_drawing_area: 'Handtekening tekengebied. Gebruik de muis of aanraking om uw handtekening te tekenen.',
|
||||
initials_drawing_area: 'Tekengebied voor initialen. Gebruik de muis of aanraking om uw initialen te tekenen.',
|
||||
kba: 'KBA',
|
||||
please_upload_an_image_file: 'Upload alstublieft een afbeeldingsbestand',
|
||||
must_be_characters_length: 'Moet {number} tekens lang zijn',
|
||||
@@ -1248,6 +1269,7 @@ const nl = {
|
||||
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: 'Telefoonnummer verifiëren',
|
||||
country_code: 'Landcode',
|
||||
use_international_format: 'Gebruik internationaal formaat: +1xxx',
|
||||
six_digits_code: '6-cijferige code',
|
||||
change_phone_number: 'Wijzig telefoonnummer',
|
||||
@@ -1280,6 +1302,7 @@ const ar = {
|
||||
close: 'إغلاق',
|
||||
uploaded_files: 'الملفات المرفوعة',
|
||||
signature_drawing_area: 'منطقة رسم التوقيع. استخدم الماوس أو اللمس لرسم توقيعك.',
|
||||
initials_drawing_area: 'منطقة رسم الحروف الأولى. استخدم الماوس أو اللمس لرسم الحروف الأولى.',
|
||||
kba: 'KBA',
|
||||
please_upload_an_image_file: 'يرجى تحميل ملف صورة',
|
||||
must_be_characters_length: 'يجب أن يكون الطول {number} حرفًا',
|
||||
@@ -1364,6 +1387,7 @@ const ar = {
|
||||
please_check_the_box_to_continue: 'الرجاء التحقق من الخانة للمتابعة.',
|
||||
open_source_documents_software: 'برنامج وثائق مفتوح المصدر',
|
||||
verified_phone_number: 'تحقق من رقم الهاتف',
|
||||
country_code: 'رمز الدولة',
|
||||
use_international_format: 'استخدم الشكل الدولي: +1xxx',
|
||||
six_digits_code: 'رمز مكون من 6 أرقام',
|
||||
change_phone_number: 'تغيير رقم الهاتف',
|
||||
@@ -1396,6 +1420,7 @@ const ko = {
|
||||
close: '닫기',
|
||||
uploaded_files: '업로드된 파일',
|
||||
signature_drawing_area: '서명 그리기 영역. 마우스 또는 터치를 사용하여 서명을 그리세요.',
|
||||
initials_drawing_area: '이니셜 그리기 영역. 마우스 또는 터치를 사용하여 이니셜을 그리세요.',
|
||||
kba: 'KBA',
|
||||
please_upload_an_image_file: '이미지 파일을 업로드해 주세요',
|
||||
must_be_characters_length: '{number}자여야 합니다',
|
||||
@@ -1480,6 +1505,7 @@ const ko = {
|
||||
please_check_the_box_to_continue: '계속하려면 확인란을 선택하십시오.',
|
||||
open_source_documents_software: '오픈 소스 문서 소프트웨어',
|
||||
verified_phone_number: '전화번호 확인됨',
|
||||
country_code: '국가 번호',
|
||||
use_international_format: '국제 포맷 사용: +1xxx',
|
||||
six_digits_code: '6자리 코드',
|
||||
change_phone_number: '전화번호 변경',
|
||||
@@ -1512,6 +1538,7 @@ const ja = {
|
||||
close: '閉じる',
|
||||
uploaded_files: 'アップロードされたファイル',
|
||||
signature_drawing_area: '署名描画エリア。マウスまたはタッチを使用して署名を描いてください。',
|
||||
initials_drawing_area: 'イニシャル描画エリア。マウスまたはタッチを使用してイニシャルを描いてください。',
|
||||
kba: 'KBA',
|
||||
please_upload_an_image_file: '画像ファイルをアップロードしてください',
|
||||
must_be_characters_length: '{number}文字でなければなりません',
|
||||
@@ -1596,6 +1623,7 @@ const ja = {
|
||||
please_check_the_box_to_continue: '続行するにはボックスにチェックを入れてください。',
|
||||
open_source_documents_software: 'オープンソースのドキュメントソフトウェア',
|
||||
verified_phone_number: '電話番号を認証',
|
||||
country_code: '国番号',
|
||||
use_international_format: '国際形式を使用してください:+1xxx',
|
||||
six_digits_code: '6桁のコード',
|
||||
change_phone_number: '電話番号を変更',
|
||||
|
||||
@@ -141,6 +141,7 @@
|
||||
<img
|
||||
v-if="modelValue || computedPreviousValue"
|
||||
:src="attachmentsIndex[modelValue || computedPreviousValue].url"
|
||||
:alt="field.name || t('initials')"
|
||||
class="mx-auto bg-white border border-base-300 rounded max-h-44"
|
||||
>
|
||||
<div class="relative">
|
||||
@@ -156,6 +157,8 @@
|
||||
<canvas
|
||||
v-show="!modelValue && !computedPreviousValue"
|
||||
ref="canvas"
|
||||
role="img"
|
||||
:aria-label="t('initials_drawing_area')"
|
||||
class="bg-white border border-base-300 rounded-2xl w-full draw-canvas"
|
||||
/>
|
||||
</div>
|
||||
@@ -165,6 +168,7 @@
|
||||
ref="textInput"
|
||||
class="base-input !text-2xl w-full mt-6 text-center"
|
||||
:required="field.required && !isInitialsStarted"
|
||||
:aria-label="field.name || t('initials')"
|
||||
:placeholder="`${t('type_initial_here')}...`"
|
||||
type="text"
|
||||
@focus="$emit('focus')"
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
:placeholder="t('email')"
|
||||
type="email"
|
||||
:required="submitters.includes(submitter)"
|
||||
autofocus="true"
|
||||
:autofocus="index === 0"
|
||||
name="submission[submitters][][email]"
|
||||
>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<label
|
||||
v-if="showFieldNames && (field.name || field.title)"
|
||||
:for="field.uuid"
|
||||
:id="field.uuid + '-group-label'"
|
||||
dir="auto"
|
||||
class="label text-xl sm:text-2xl py-0 mb-2 sm:mb-3.5 field-name-label"
|
||||
:class="{ 'mb-2': !field.description }"
|
||||
@@ -44,6 +44,8 @@
|
||||
<div
|
||||
class="space-y-3.5 mx-auto"
|
||||
:class="{ hidden: !showOptions }"
|
||||
role="group"
|
||||
:aria-labelledby="showFieldNames && (field.name || field.title) ? field.uuid + '-group-label' : undefined"
|
||||
>
|
||||
<div
|
||||
v-for="(option, index) in field.options"
|
||||
|
||||
@@ -81,6 +81,7 @@
|
||||
<select
|
||||
id="country_code_select"
|
||||
class="absolute top-0 bottom-0 right-0 left-0 opacity-0 w-full h-full cursor-pointer"
|
||||
:aria-label="t('country_code')"
|
||||
:disabled="!!defaultValue"
|
||||
@change="onCountrySelect(countries.find((country) => country.flag === $event.target.value))"
|
||||
>
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
>
|
||||
<button
|
||||
id="type_text_button"
|
||||
ref="typeTextButton"
|
||||
type="button"
|
||||
:aria-label="t('draw_signature')"
|
||||
class="btn btn-outline btn-sm font-medium type-text-button"
|
||||
@@ -49,6 +50,7 @@
|
||||
>
|
||||
<button
|
||||
id="type_text_button"
|
||||
ref="typeTextButton"
|
||||
type="button"
|
||||
:aria-label="t('type_text')"
|
||||
class="btn btn-outline btn-sm font-medium inline-flex flex-nowrap type-text-button"
|
||||
@@ -71,7 +73,7 @@
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
:aria-label="t('take_photo')"
|
||||
:aria-label="t('upload')"
|
||||
class="btn btn-outline btn-sm font-medium inline-flex flex-nowrap upload-image-button"
|
||||
@click="$refs.takePhotoInput.click()"
|
||||
>
|
||||
@@ -199,7 +201,7 @@
|
||||
<canvas
|
||||
v-show="!modelValue && !computedPreviousValue"
|
||||
ref="canvas"
|
||||
role="application"
|
||||
role="img"
|
||||
:aria-label="t('signature_drawing_area')"
|
||||
style="padding: 1px; 0"
|
||||
class="bg-white border border-base-300 rounded-2xl w-full draw-canvas"
|
||||
@@ -228,6 +230,8 @@
|
||||
>
|
||||
<canvas
|
||||
ref="qrCanvas"
|
||||
role="img"
|
||||
:aria-label="t('scan_the_qr_code_with_the_camera_app_to_open_the_form_on_mobile_and_draw_your_signature')"
|
||||
class="h-full"
|
||||
width="132"
|
||||
height="132"
|
||||
@@ -242,6 +246,7 @@
|
||||
ref="textInput"
|
||||
class="base-input !text-2xl w-full mt-6"
|
||||
:required="field.required && !isSignatureStarted"
|
||||
:aria-label="field.name || t('signature')"
|
||||
:placeholder="`${t('type_signature_here')}...`"
|
||||
type="text"
|
||||
@input="updateWrittenSignature"
|
||||
@@ -251,6 +256,7 @@
|
||||
class="select base-input !text-2xl w-full mt-6 text-center"
|
||||
:class="{ 'text-gray-300': !reason }"
|
||||
required
|
||||
:aria-label="t('select_a_reason')"
|
||||
:name="`values[${field.preferences.reason_field_uuid}]`"
|
||||
@change="$event.target.value === 'other' ? [reason = '', isOtherReason = true] : $emit('update:reason', $event.target.value)"
|
||||
>
|
||||
@@ -296,6 +302,7 @@
|
||||
class="base-input !text-2xl w-full mt-6"
|
||||
required
|
||||
:name="`values[${field.preferences.reason_field_uuid}]`"
|
||||
:aria-label="t('select_a_reason')"
|
||||
:placeholder="t('type_here_')"
|
||||
:value="reason"
|
||||
type="text"
|
||||
@@ -716,10 +723,12 @@ export default {
|
||||
this.clear()
|
||||
this.isTextSignature = !this.isTextSignature
|
||||
|
||||
if (this.isTextSignature) {
|
||||
this.$nextTick(() => {
|
||||
this.$nextTick(() => {
|
||||
if (this.isTextSignature) {
|
||||
if (this.$refs.textInput) {
|
||||
if (!this.submitter.name) {
|
||||
if (this.submitter.name) {
|
||||
this.$refs.typeTextButton?.focus()
|
||||
} else {
|
||||
this.$refs.textInput.focus()
|
||||
}
|
||||
|
||||
@@ -727,8 +736,10 @@ export default {
|
||||
|
||||
this.$emit('start')
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
this.$refs.typeTextButton?.focus()
|
||||
}
|
||||
})
|
||||
},
|
||||
async initTypedSignature () {
|
||||
if (this.signatureText) {
|
||||
|
||||
@@ -726,6 +726,10 @@ export default {
|
||||
return
|
||||
}
|
||||
|
||||
if (this.inputMode && (this.isValueInput || this.isCheckboxInput || this.isSelectInput)) {
|
||||
return
|
||||
}
|
||||
|
||||
document.activeElement?.blur()
|
||||
|
||||
e.preventDefault()
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
class="absolute w-5 h-5 -ml-2.5 -mt-2.5 rounded-full bg-white border-2 border-neutral-600 cursor-move shadow"
|
||||
:style="{ left: `${corner.x * 100}%`, top: `${corner.y * 100}%` }"
|
||||
@mousedown.prevent="onCornerMousedown(cornerIndex)"
|
||||
@touchstart.prevent="onCornerTouchstart(cornerIndex)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -218,6 +219,9 @@ export default {
|
||||
beforeUnmount () {
|
||||
window.removeEventListener('mousemove', this.onMousemove)
|
||||
window.removeEventListener('mouseup', this.onMouseup)
|
||||
window.removeEventListener('touchmove', this.onTouchmove)
|
||||
window.removeEventListener('touchend', this.onTouchend)
|
||||
window.removeEventListener('touchcancel', this.onTouchend)
|
||||
},
|
||||
methods: {
|
||||
transformPoint (point, rotate, flipH, flipV) {
|
||||
@@ -276,27 +280,48 @@ export default {
|
||||
y: Math.min(Math.max((event.clientY - rect.top) / rect.height, 0), 1)
|
||||
}
|
||||
},
|
||||
onCornerMousedown (index) {
|
||||
startCornerDrag (index) {
|
||||
this.draggingIndex = index
|
||||
this.cornersTouched = true
|
||||
},
|
||||
dragCorner (point) {
|
||||
if (this.draggingIndex === null) {
|
||||
return
|
||||
}
|
||||
|
||||
this.corners[this.draggingIndex] = this.inverseTransformPoint(this.pagePoint(point), this.rotate, this.flipH, this.flipV)
|
||||
},
|
||||
onCornerMousedown (index) {
|
||||
this.startCornerDrag(index)
|
||||
|
||||
window.addEventListener('mousemove', this.onMousemove)
|
||||
window.addEventListener('mouseup', this.onMouseup, { once: true })
|
||||
},
|
||||
onMousemove (event) {
|
||||
if (this.draggingIndex === null) {
|
||||
return
|
||||
}
|
||||
|
||||
const point = this.inverseTransformPoint(this.pagePoint(event), this.rotate, this.flipH, this.flipV)
|
||||
|
||||
this.corners[this.draggingIndex] = point
|
||||
this.dragCorner(event)
|
||||
},
|
||||
onMouseup () {
|
||||
window.removeEventListener('mousemove', this.onMousemove)
|
||||
|
||||
this.draggingIndex = null
|
||||
},
|
||||
onCornerTouchstart (index) {
|
||||
this.startCornerDrag(index)
|
||||
|
||||
window.addEventListener('touchmove', this.onTouchmove, { passive: false })
|
||||
window.addEventListener('touchend', this.onTouchend)
|
||||
window.addEventListener('touchcancel', this.onTouchend)
|
||||
},
|
||||
onTouchmove (event) {
|
||||
this.dragCorner(event.touches[0])
|
||||
},
|
||||
onTouchend () {
|
||||
window.removeEventListener('touchmove', this.onTouchmove)
|
||||
window.removeEventListener('touchend', this.onTouchend)
|
||||
window.removeEventListener('touchcancel', this.onTouchend)
|
||||
|
||||
this.draggingIndex = null
|
||||
},
|
||||
submit (scan) {
|
||||
this.isProcessing = scan ? 'scan' : 'crop'
|
||||
|
||||
|
||||
@@ -36,7 +36,8 @@
|
||||
<div
|
||||
v-for="(rect, rectIndex) in page.redact"
|
||||
:key="`redact-${rectIndex}`"
|
||||
class="absolute bg-black"
|
||||
class="absolute"
|
||||
:class="rect.color === 'white' ? 'bg-white' : 'bg-black'"
|
||||
:style="{
|
||||
left: `${rect.x * 100}%`,
|
||||
top: `${rect.y * 100}%`,
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
class="relative mx-auto select-none cursor-crosshair"
|
||||
:style="pageStyle"
|
||||
@mousedown.prevent="onMousedown"
|
||||
@touchstart.prevent="onTouchstart"
|
||||
>
|
||||
<img
|
||||
:src="imageUrl"
|
||||
@@ -22,7 +23,8 @@
|
||||
<div
|
||||
v-for="(rect, rectIndex) in redactRects"
|
||||
:key="`rect-${rectIndex}`"
|
||||
class="absolute bg-black pointer-events-none"
|
||||
class="absolute pointer-events-none"
|
||||
:class="color === 'white' ? 'bg-white' : 'bg-black'"
|
||||
:style="{
|
||||
left: `${rect.x * 100}%`,
|
||||
top: `${rect.y * 100}%`,
|
||||
@@ -47,6 +49,26 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-56 flex-none border-l px-4 py-4 space-y-2">
|
||||
<div class="flex items-center justify-between mb-1">
|
||||
<span class="text-sm pl-1">{{ t('color') }}</span>
|
||||
<div
|
||||
class="join rounded"
|
||||
style="height: 28px"
|
||||
>
|
||||
<button
|
||||
v-for="option in colors"
|
||||
:key="option"
|
||||
class="btn btn-sm join-item !h-7 !min-h-0 bg-white input-bordered hover:border-base-content/20 hover:bg-base-100/50 px-2"
|
||||
:class="{ '!bg-base-200': color === option }"
|
||||
@click.prevent="color = option"
|
||||
>
|
||||
<span
|
||||
class="block w-10 h-4 border border-base-content/30"
|
||||
:style="{ backgroundColor: option }"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="btn btn-sm w-full justify-start normal-case font-normal rounded disabled:bg-base-300"
|
||||
:disabled="!hasRedactions && !wasReset"
|
||||
@@ -122,11 +144,15 @@ export default {
|
||||
selectedNodes: {},
|
||||
freeRects: [],
|
||||
rects: [],
|
||||
color: 'black',
|
||||
wasReset: false,
|
||||
marquee: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
colors () {
|
||||
return ['black', 'white']
|
||||
},
|
||||
rotate () {
|
||||
return this.page.rotate || 0
|
||||
},
|
||||
@@ -186,6 +212,10 @@ export default {
|
||||
}
|
||||
},
|
||||
created () {
|
||||
if ((this.page.redact || []).some((rect) => rect.color === 'white')) {
|
||||
this.color = 'white'
|
||||
}
|
||||
|
||||
if (this.imagePage) {
|
||||
this.rects = (this.page.redact || []).map((rect) => ({ ...rect }))
|
||||
|
||||
@@ -222,6 +252,9 @@ export default {
|
||||
beforeUnmount () {
|
||||
window.removeEventListener('mousemove', this.onMousemove)
|
||||
window.removeEventListener('mouseup', this.onMouseup)
|
||||
window.removeEventListener('touchmove', this.onTouchmove)
|
||||
window.removeEventListener('touchend', this.onTouchend)
|
||||
window.removeEventListener('touchcancel', this.onTouchend)
|
||||
},
|
||||
methods: {
|
||||
inverseRotatePoint (point, rotate) {
|
||||
@@ -238,7 +271,17 @@ export default {
|
||||
return { x, y }
|
||||
},
|
||||
apply () {
|
||||
this.$emit('apply', this.redactRects)
|
||||
const rects = this.redactRects.map((rect) => {
|
||||
const next = { x: rect.x, y: rect.y, w: rect.w, h: rect.h }
|
||||
|
||||
if (this.color === 'white') {
|
||||
next.color = 'white'
|
||||
}
|
||||
|
||||
return next
|
||||
})
|
||||
|
||||
this.$emit('apply', rects)
|
||||
},
|
||||
reset () {
|
||||
this.wasReset = true
|
||||
@@ -318,27 +361,61 @@ export default {
|
||||
y: Math.min(Math.max((event.clientY - rect.top) / rect.height, 0), 1)
|
||||
}
|
||||
},
|
||||
startMarquee (point) {
|
||||
const start = this.pagePoint(point)
|
||||
|
||||
this.marquee = { x1: start.x, y1: start.y, x2: start.x, y2: start.y }
|
||||
},
|
||||
updateMarquee (point) {
|
||||
if (!this.marquee) {
|
||||
return
|
||||
}
|
||||
|
||||
const next = this.pagePoint(point)
|
||||
|
||||
this.marquee.x2 = next.x
|
||||
this.marquee.y2 = next.y
|
||||
},
|
||||
onMousedown (event) {
|
||||
if (event.button !== 0 || (!this.imagePage && !this.textNodes)) {
|
||||
return
|
||||
}
|
||||
|
||||
const point = this.pagePoint(event)
|
||||
|
||||
this.marquee = { x1: point.x, y1: point.y, x2: point.x, y2: point.y }
|
||||
this.startMarquee(event)
|
||||
|
||||
window.addEventListener('mousemove', this.onMousemove)
|
||||
window.addEventListener('mouseup', this.onMouseup, { once: true })
|
||||
},
|
||||
onMousemove (event) {
|
||||
const point = this.pagePoint(event)
|
||||
|
||||
this.marquee.x2 = point.x
|
||||
this.marquee.y2 = point.y
|
||||
this.updateMarquee(event)
|
||||
},
|
||||
onMouseup () {
|
||||
window.removeEventListener('mousemove', this.onMousemove)
|
||||
|
||||
this.finishMarquee()
|
||||
},
|
||||
onTouchstart (event) {
|
||||
if (!this.imagePage && !this.textNodes) {
|
||||
return
|
||||
}
|
||||
|
||||
this.startMarquee(event.touches[0])
|
||||
|
||||
window.addEventListener('touchmove', this.onTouchmove, { passive: false })
|
||||
window.addEventListener('touchend', this.onTouchend)
|
||||
window.addEventListener('touchcancel', this.onTouchend)
|
||||
},
|
||||
onTouchmove (event) {
|
||||
this.updateMarquee(event.touches[0])
|
||||
},
|
||||
onTouchend () {
|
||||
window.removeEventListener('touchmove', this.onTouchmove)
|
||||
window.removeEventListener('touchend', this.onTouchend)
|
||||
window.removeEventListener('touchcancel', this.onTouchend)
|
||||
|
||||
this.finishMarquee()
|
||||
},
|
||||
finishMarquee () {
|
||||
if (!this.marquee) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -600,7 +600,8 @@ export default {
|
||||
}
|
||||
},
|
||||
showFont () {
|
||||
return ['text', 'number', 'date', 'select', 'heading', 'cells'].includes(this.field.type)
|
||||
return ['text', 'number', 'date', 'select', 'heading', 'cells'].includes(this.field.type) ||
|
||||
(['radio', 'multiple'].includes(this.field.type) && this.field.areas?.every((a) => !a.option_uuid))
|
||||
},
|
||||
showDescription () {
|
||||
return !['stamp', 'heading', 'strikethrough'].includes(this.field.type)
|
||||
|
||||
@@ -452,7 +452,7 @@
|
||||
class="pb-0.5 mt-0.5"
|
||||
>
|
||||
<li
|
||||
v-if="['text', 'number', 'date', 'select', 'heading', 'cells'].includes(field.type)"
|
||||
v-if="['text', 'number', 'date', 'select', 'heading', 'cells'].includes(field.type) || (['radio', 'multiple'].includes(field.type) && field.areas?.every((a) => !a.option_uuid))"
|
||||
class="field-settings-font"
|
||||
>
|
||||
<label
|
||||
|
||||
@@ -111,6 +111,7 @@ const en = {
|
||||
flip_horizontal: 'Flip horizontal',
|
||||
flip_vertical: 'Flip vertical',
|
||||
there_is_no_text_to_redact_on_this_page: 'This page contains only images. Redact tool can be used only with text pages',
|
||||
color: 'Color',
|
||||
reset: 'Reset',
|
||||
upload_to_document: 'Upload to "{document}"',
|
||||
replace_existing_document: 'Replace existing document',
|
||||
@@ -351,6 +352,7 @@ const es = {
|
||||
flip_horizontal: 'Voltear horizontal',
|
||||
flip_vertical: 'Voltear vertical',
|
||||
there_is_no_text_to_redact_on_this_page: 'Esta página contiene solo imágenes. La herramienta de censura solo puede usarse con páginas de texto',
|
||||
color: 'Color',
|
||||
reset: 'Restablecer',
|
||||
upload_to_document: 'Subir a "{document}"',
|
||||
replace_existing_document: 'Reemplazar documento existente',
|
||||
@@ -597,6 +599,7 @@ const it = {
|
||||
flip_horizontal: 'Rifletti orizzontale',
|
||||
flip_vertical: 'Rifletti verticale',
|
||||
there_is_no_text_to_redact_on_this_page: 'Questa pagina contiene solo immagini. Lo strumento di oscuramento può essere usato solo con pagine di testo',
|
||||
color: 'Colore',
|
||||
reset: 'Reimposta',
|
||||
upload_to_document: 'Carica in "{document}"',
|
||||
replace_existing_document: 'Sostituisci documento esistente',
|
||||
@@ -837,6 +840,7 @@ const pt = {
|
||||
flip_horizontal: 'Inverter horizontal',
|
||||
flip_vertical: 'Inverter vertical',
|
||||
there_is_no_text_to_redact_on_this_page: 'Esta página contém apenas imagens. A ferramenta de censura só pode ser usada com páginas de texto',
|
||||
color: 'Cor',
|
||||
reset: 'Redefinir',
|
||||
upload_to_document: 'Enviar para "{document}"',
|
||||
replace_existing_document: 'Substituir documento existente',
|
||||
@@ -1083,6 +1087,7 @@ const fr = {
|
||||
flip_horizontal: 'Miroir horizontal',
|
||||
flip_vertical: 'Miroir vertical',
|
||||
there_is_no_text_to_redact_on_this_page: 'Cette page ne contient que des images. L\'outil de caviardage ne peut être utilisé qu\'avec des pages de texte',
|
||||
color: 'Couleur',
|
||||
reset: 'Réinitialiser',
|
||||
upload_to_document: 'Téléverser dans "{document}"',
|
||||
replace_existing_document: 'Remplacer le document existant',
|
||||
@@ -1326,6 +1331,7 @@ const de = {
|
||||
flip_horizontal: 'Horizontal spiegeln',
|
||||
flip_vertical: 'Vertikal spiegeln',
|
||||
there_is_no_text_to_redact_on_this_page: 'Diese Seite enthält nur Bilder. Das Schwärzungswerkzeug kann nur mit Textseiten verwendet werden',
|
||||
color: 'Farbe',
|
||||
reset: 'Zurücksetzen',
|
||||
upload_to_document: 'In "{document}" hochladen',
|
||||
replace_existing_document: 'Vorhandenes Dokument ersetzen',
|
||||
@@ -1569,6 +1575,7 @@ const nl = {
|
||||
flip_horizontal: 'Horizontaal spiegelen',
|
||||
flip_vertical: 'Verticaal spiegelen',
|
||||
there_is_no_text_to_redact_on_this_page: 'Deze pagina bevat alleen afbeeldingen. De redactietool kan alleen worden gebruikt met tekstpagina\'s',
|
||||
color: 'Kleur',
|
||||
reset: 'Opnieuw instellen',
|
||||
upload_to_document: 'Uploaden naar "{document}"',
|
||||
replace_existing_document: 'Bestaand document vervangen',
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
<template>
|
||||
<div class="inline-flex items-stretch replace-document-control">
|
||||
<div
|
||||
class="inline-flex items-stretch replace-document-control"
|
||||
:class="{ 'opacity-100': isLoading }"
|
||||
>
|
||||
<label
|
||||
:for="inputId"
|
||||
class="btn btn-neutral btn-xs transition-none replace-document-button"
|
||||
|
||||
@@ -11,7 +11,7 @@ class ProcessSubmissionExpiredJob
|
||||
return if submission.archived_at?
|
||||
return if submission.template&.archived_at?
|
||||
return if submission.submitters.where.not(declined_at: nil).exists?
|
||||
return unless submission.submitters.exists?(completed_at: nil)
|
||||
return if submission.completed_at?
|
||||
|
||||
WebhookUrls.enqueue_events(submission, 'submission.expired')
|
||||
end
|
||||
|
||||
@@ -5,30 +5,38 @@ class ProcessSubmitterCompletionJob
|
||||
|
||||
def perform(params = {})
|
||||
submitter = Submitter.find(params['submitter_id'])
|
||||
submission = submitter.submission
|
||||
|
||||
create_completed_submitter!(submitter)
|
||||
|
||||
is_all_completed = !submitter.submission.submitters.exists?(completed_at: nil)
|
||||
is_last =
|
||||
if params.key?('is_last')
|
||||
params['is_last']
|
||||
else
|
||||
!submission.submitters.exists?(completed_at: nil) &&
|
||||
submitter.completed_at == submission.submitters.maximum(:completed_at)
|
||||
end
|
||||
|
||||
Submissions::EnsureResultGenerated.call(submitter)
|
||||
|
||||
if is_all_completed && submitter.completed_at == submitter.submission.submitters.maximum(:completed_at)
|
||||
if submitter.submission.account.account_configs.exists?(key: AccountConfig::COMBINE_PDF_RESULT_KEY, value: true)
|
||||
if is_last
|
||||
if submission.account.account_configs.exists?(key: AccountConfig::COMBINE_PDF_RESULT_KEY, value: true)
|
||||
Submissions::EnsureCombinedGenerated.call(submitter)
|
||||
end
|
||||
|
||||
Submissions::EnsureAuditGenerated.call(submitter.submission)
|
||||
Submissions::EnsureAuditGenerated.call(submission)
|
||||
|
||||
enqueue_completed_emails(submitter)
|
||||
end
|
||||
|
||||
create_completed_documents!(submitter)
|
||||
|
||||
if !is_all_completed && submitter.submission.submitters_order_preserved? && params['send_invitation_email'] != false
|
||||
if !submission.completed_at && submission.submitters_order_preserved? && params['send_invitation_email'] != false &&
|
||||
Submission.exists?(id: submission.id, completed_at: nil)
|
||||
enqueue_next_submitter_request_notification(submitter)
|
||||
end
|
||||
|
||||
enqueue_completed_webhooks(submitter, is_all_completed:)
|
||||
enqueue_completed_webhooks(submitter, is_last:)
|
||||
end
|
||||
|
||||
def create_completed_submitter!(submitter)
|
||||
@@ -77,7 +85,7 @@ class ProcessSubmitterCompletionJob
|
||||
end
|
||||
end
|
||||
|
||||
def enqueue_completed_webhooks(submitter, is_all_completed: false)
|
||||
def enqueue_completed_webhooks(submitter, is_last: false)
|
||||
event_uuids = {}
|
||||
|
||||
WebhookUrls.for_account_id(submitter.account_id, %w[form.completed submission.completed]).each do |webhook|
|
||||
@@ -89,7 +97,7 @@ class ProcessSubmitterCompletionJob
|
||||
'webhook_url_id' => webhook.id)
|
||||
end
|
||||
|
||||
next unless webhook.events.include?('submission.completed') && is_all_completed
|
||||
next unless webhook.events.include?('submission.completed') && is_last
|
||||
|
||||
event_uuids['submission.completed'] ||= SecureRandom.uuid
|
||||
|
||||
|
||||
@@ -7,7 +7,9 @@ class SendSubmitterInvitationEmailJob
|
||||
submitter = Submitter.find(params['submitter_id'])
|
||||
|
||||
return if submitter.completed_at?
|
||||
return if submitter.declined_at?
|
||||
return if submitter.submission.archived_at?
|
||||
return if submitter.submission.expired?
|
||||
return if submitter.template&.archived_at?
|
||||
return if submitter.submission.source == 'invite' && !Accounts.can_send_emails?(submitter.account, on_events: true)
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ class SubmitterMailer < ApplicationMailer
|
||||
|
||||
template_submitters_index = @email_message.blank? ? build_submitter_preferences_index(@submitter) : {}
|
||||
|
||||
@body = @email_message&.body.presence ||
|
||||
@body = @email_message&.normalized_body.presence ||
|
||||
template_submitters_index.dig(@submitter.uuid, 'request_email_body').presence ||
|
||||
@submitter.template&.preferences&.dig('request_email_body').presence
|
||||
|
||||
@@ -97,7 +97,7 @@ class SubmitterMailer < ApplicationMailer
|
||||
to: user.role == 'integration' ? user.friendly_name.sub(/\+\w+@/, '@') : user.friendly_name,
|
||||
reply_to: @submitter.friendly_name,
|
||||
subject: I18n.t(:name_declined_by_submitter,
|
||||
name: (@submission.name || @submission.template.name).truncate(20),
|
||||
name: (@submission.name || @submission.template&.name).to_s.truncate(20),
|
||||
submitter: @submitter.name || @submitter.email || @submitter.phone))
|
||||
end
|
||||
end
|
||||
@@ -144,6 +144,7 @@ class SubmitterMailer < ApplicationMailer
|
||||
end
|
||||
|
||||
def otp_verification_email(submitter, locale: nil)
|
||||
@current_account = submitter.account
|
||||
@submitter = submitter
|
||||
@otp_code = EmailVerificationCodes.generate([submitter.email.downcase.strip, submitter.slug].join(':'))
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ class Account < ApplicationRecord
|
||||
has_many :encrypted_configs, dependent: :destroy
|
||||
has_many :account_configs, dependent: :destroy
|
||||
has_many :email_messages, dependent: :destroy
|
||||
has_many :email_message_assets, dependent: :destroy
|
||||
has_many :templates, dependent: :destroy
|
||||
has_many :template_folders, dependent: :destroy
|
||||
has_one :default_template_folder, -> { where(name: TemplateFolder::DEFAULT_NAME) },
|
||||
|
||||
@@ -33,6 +33,15 @@ class EmailMessage < ApplicationRecord
|
||||
|
||||
before_validation :set_sha1, on: :create
|
||||
|
||||
def normalized_body
|
||||
@normalized_body ||=
|
||||
if body&.include?(EmailMessages::ASSET_PREFIX)
|
||||
EmailMessages.rebuild_body_with_assets(account_id, body)
|
||||
else
|
||||
body
|
||||
end
|
||||
end
|
||||
|
||||
def set_sha1
|
||||
self.sha1 = Digest::SHA1.hexdigest({ subject:, body: }.to_json)
|
||||
end
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: email_message_assets
|
||||
#
|
||||
# id :bigint not null, primary key
|
||||
# data :text not null
|
||||
# sha1 :string not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# account_id :bigint not null
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
# index_email_message_assets_on_account_id_and_sha1 (account_id,sha1) UNIQUE
|
||||
#
|
||||
# Foreign Keys
|
||||
#
|
||||
# fk_rails_... (account_id => accounts.id)
|
||||
#
|
||||
class EmailMessageAsset < ApplicationRecord
|
||||
belongs_to :account
|
||||
|
||||
before_validation :set_sha1, on: :create
|
||||
|
||||
def set_sha1
|
||||
self.sha1 = Digest::SHA1.hexdigest(data.to_s)
|
||||
end
|
||||
end
|
||||
+12
-13
@@ -6,6 +6,7 @@
|
||||
#
|
||||
# id :bigint not null, primary key
|
||||
# archived_at :datetime
|
||||
# completed_at :datetime
|
||||
# expire_at :datetime
|
||||
# name :text
|
||||
# preferences :text not null
|
||||
@@ -25,7 +26,9 @@
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
# index_submissions_on_account_id_and_completed_at (account_id,completed_at) WHERE ((completed_at IS NOT NULL) AND (archived_at IS NULL))
|
||||
# index_submissions_on_account_id_and_id (account_id,id)
|
||||
# index_submissions_on_account_id_and_id_pending (account_id,id) WHERE ((completed_at IS NULL) AND (archived_at IS NULL))
|
||||
# index_submissions_on_account_id_and_template_id_and_id (account_id,template_id,id) WHERE (archived_at IS NULL)
|
||||
# index_submissions_on_account_id_and_template_id_and_id_archived (account_id,template_id,id) WHERE (archived_at IS NOT NULL)
|
||||
# index_submissions_on_created_by_user_id (created_by_user_id)
|
||||
@@ -89,22 +92,18 @@ class Submission < ApplicationRecord
|
||||
scope :active, -> { where(archived_at: nil) }
|
||||
scope :archived, -> { where.not(archived_at: nil) }
|
||||
scope :pending, lambda {
|
||||
where(expire_at: nil).or(where(expire_at: Time.current..))
|
||||
.where(Submitter.where(Submitter.arel_table[:submission_id].eq(Submission.arel_table[:id])
|
||||
.and(Submitter.arel_table[:completed_at].eq(nil))).select(1).arel.exists)
|
||||
}
|
||||
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)
|
||||
where(expire_at: nil).or(where(expire_at: Time.current..)).where(completed_at: nil)
|
||||
}
|
||||
scope :completed, -> { where.not(completed_at: nil) }
|
||||
scope :declined, lambda {
|
||||
where(Submitter.where(Submitter.arel_table[:submission_id].eq(Submission.arel_table[:id])
|
||||
.and(Submitter.arel_table[:declined_at].not_eq(nil))).select(1).arel.exists)
|
||||
where(Submitter.where(Submitter.arel_table[:submission_id].eq(Submission.arel_table[:id]))
|
||||
.where.not(declined_at: nil).limit(1).arel.exists)
|
||||
}
|
||||
scope :expired, lambda {
|
||||
where(expire_at: ..Time.current)
|
||||
.where(Submitter.where(Submitter.arel_table[:submission_id].eq(Submission.arel_table[:id])
|
||||
.and(Submitter.arel_table[:completed_at].eq(nil))).select(1).arel.exists)
|
||||
scope :expired, -> { where(expire_at: ..Time.current).where(completed_at: nil) }
|
||||
|
||||
scope :select_for_list, lambda {
|
||||
select(:id, :name, :created_by_user_id, :account_id, :completed_at,
|
||||
:created_at, :archived_at, :expire_at, :template_id, :template_submitters)
|
||||
}
|
||||
|
||||
enum :source, {
|
||||
|
||||
@@ -28,8 +28,8 @@
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
# index_submitters_on_account_id_and_completed_at (account_id,completed_at) WHERE (completed_at IS NOT NULL)
|
||||
# index_submitters_on_account_id_and_id (account_id,id)
|
||||
# index_submitters_on_completed_at_and_account_id (completed_at,account_id)
|
||||
# index_submitters_on_email (email)
|
||||
# index_submitters_on_external_id (external_id)
|
||||
# index_submitters_on_slug (slug) UNIQUE
|
||||
|
||||
@@ -81,6 +81,8 @@ class Template < ApplicationRecord
|
||||
scope :active, -> { where(archived_at: nil) }
|
||||
scope :archived, -> { where.not(archived_at: nil) }
|
||||
|
||||
scope :select_for_list, -> { select(:id, :name, :author_id, :account_id, :created_at, :archived_at, :folder_id) }
|
||||
|
||||
def application_key
|
||||
external_id
|
||||
end
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="<%= local_assigns[:class] %>" width="44" height="44" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M10 19h-5a2 2 0 0 1 -2 -2v-11a2 2 0 0 1 2 -2h4l3 3h7a2 2 0 0 1 2 2v2.5"></path>
|
||||
<path d="M17.8 20.817l-2.172 1.138a.392 .392 0 0 1 -.568 -.41l.415 -2.411l-1.757 -1.707a.389 .389 0 0 1 .217 -.665l2.428 -.352l1.086 -2.193a.392 .392 0 0 1 .702 0l1.086 2.193l2.428 .352a.39 .39 0 0 1 .217 .665l-1.757 1.707l.414 2.41a.39 .39 0 0 1 -.567 .411l-2.172 -1.138"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 662 B |
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="<%= local_assigns[:class] %>" width="44" height="44" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M12 19h-7a2 2 0 0 1 -2 -2v-11a2 2 0 0 1 2 -2h4l3 3h7a2 2 0 0 1 2 2v3.5"></path>
|
||||
<path d="M19 22v-6"></path>
|
||||
<path d="M22 19l-3 -3l-3 3"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 447 B |
@@ -36,7 +36,7 @@
|
||||
<% if signature %>
|
||||
<div class="flex justify-center mb-4 relative">
|
||||
<%= button_to button_title(title: t('remove'), disabled_with: t('removing')), user_signature_path, method: :delete, class: 'right-0 top-0 absolute link' %>
|
||||
<img src="<%= signature.url %>" style="height: 130px; width: 100%; object-fit: contain;">
|
||||
<img src="<%= signature.url(time: ActiveStorage::Attachment.service_url_time) %>" style="height: 130px; width: 100%; object-fit: contain;">
|
||||
</div>
|
||||
<% end %>
|
||||
<a href="<%= edit_user_signature_path %>" data-turbo-frame="modal" class="base-button w-full">
|
||||
@@ -49,7 +49,7 @@
|
||||
<% if initials %>
|
||||
<div class="flex justify-center mb-4 relative">
|
||||
<%= button_to button_title(title: t('remove'), disabled_with: t('removing')), user_initials_path, method: :delete, class: 'right-0 top-0 absolute link' %>
|
||||
<img src="<%= initials.url %>" style="height: 130px; width: 100%; object-fit: contain;">
|
||||
<img src="<%= initials.url(time: ActiveStorage::Attachment.service_url_time) %>" style="height: 130px; width: 100%; object-fit: contain;">
|
||||
</div>
|
||||
<% end %>
|
||||
<a href="<%= edit_user_initials_path %>" data-turbo-frame="modal" class="base-button w-full">
|
||||
@@ -70,7 +70,7 @@
|
||||
<div class="form-control">
|
||||
<%= f.label :current_password, t('current_password'), class: 'label' %>
|
||||
<%= f.password_field :current_password, autocomplete: 'current-password', class: 'base-input' %>
|
||||
<% if Accounts.can_send_emails?(current_account) %>
|
||||
<% if Docuseal.multitenant? || Accounts.can_send_emails?(current_account) %>
|
||||
<span class="label-text-alt mt-1">
|
||||
<%= t('dont_remember_your_current_password_click_here_to_reset_it_html') %>
|
||||
</span>
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
<p>
|
||||
<% if @current_account&.testing? %>
|
||||
<%= t('sent_using_product_name_in_testing_mode_html', product_url: "#{Docuseal::PRODUCT_EMAIL_URL}/start", product_name: Docuseal.product_name) %>
|
||||
<% else %>
|
||||
<% elsif Docuseal.multitenant? %>
|
||||
<%= t('sent_using_product_name_free_document_signing_html', product_url: "#{Docuseal::PRODUCT_EMAIL_URL}/start", product_name: Docuseal.product_name) %>
|
||||
<% else %>
|
||||
<%= t('sent_using_product_name_open_source_software_html', product_url: "#{Docuseal::PRODUCT_EMAIL_URL}/open", product_name: Docuseal.product_name) %>
|
||||
<% end %>
|
||||
</p>
|
||||
|
||||
@@ -71,10 +71,11 @@
|
||||
<% end %>
|
||||
<% end %>
|
||||
<li>
|
||||
<%= button_to destroy_user_session_path, method: :delete, data: { turbo: false }, class: 'flex items-center' do %>
|
||||
<%= svg_icon('logout', class: 'w-5 h-5 flex-shrink-0 stroke-2 mr-2 inline') %>
|
||||
<span class="mr-1 whitespace-nowrap"><%= t('sign_out') %></span>
|
||||
<% end %>
|
||||
<button form="destroy_user_session_form">
|
||||
<%= svg_icon('logout', class: 'w-5 h-5 flex-shrink-0 stroke-2 inline') %>
|
||||
<span class="whitespace-nowrap"><%= t('sign_out') %></span>
|
||||
</button>
|
||||
<%= button_to '', destroy_user_session_path, method: :delete, data: { turbo: false }, form: { id: 'destroy_user_session_form' }, form_class: 'hidden' %>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
<input name="<%= key %>" value="<%= params[key] %>" class="hidden">
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% if params[:archived].present? %>
|
||||
<input name="archived" value="<%= params[:archived] %>" class="hidden">
|
||||
<% end %>
|
||||
<% if params[:q].present? %>
|
||||
<div class="relative">
|
||||
<a href="<%= url_for(params: request.query_parameters.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">
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<%= svg_icon('writing_sign', class: 'w-10 h-10') %>
|
||||
</div>
|
||||
<div>
|
||||
<p dir="auto" class="text-lg font-bold mb-1"><%= @submitter.submission.template.name %></p>
|
||||
<p dir="auto" class="text-lg font-bold mb-1"><%= @submitter.submission.template&.name %></p>
|
||||
<p dir="auto" class="text-sm">
|
||||
<%= t(@submitter.with_signature_fields? ? 'signed_on_time' : 'completed_on_time', time: l(@submitter.completed_at.to_date, format: :long)) %>
|
||||
</p>
|
||||
@@ -26,7 +26,7 @@
|
||||
<%= button_to button_title(title: t('send_copy_to_email'), disabled_with: t('sending'), icon: svg_icon('mail_forward', class: 'w-6 h-6')), send_submission_email_index_path, params: { template_slug: @template.slug, email: params[:email] }, class: 'base-button w-full' %>
|
||||
</toggle-submit>
|
||||
<% end %>
|
||||
<% if Templates.filter_undefined_submitters(@template.submitters).size == 1 && %w[api embed].exclude?(@submitter.submission.source) && @submitter.account.account_configs.find_or_initialize_by(key: AccountConfig::ALLOW_TO_RESUBMIT).value != false && @template.shared_link? %>
|
||||
<% if Templates.filter_undefined_submitters(@template.submitters).size == 1 && %w[api embed mcp].exclude?(@submitter.submission.source) && @submitter.account.account_configs.find_or_initialize_by(key: AccountConfig::ALLOW_TO_RESUBMIT).value != false && @template.shared_link? %>
|
||||
<toggle-submit class="block">
|
||||
<%= button_to button_title(title: t('resubmit'), disabled_with: t('resubmit'), icon: svg_icon('reload', class: 'w-6 h-6')), start_form_path(@template.slug), params: { submitter: params.permit(:name, :email, :phone).compact_blank, resubmit: true }, method: :put, class: 'white-button w-full' %>
|
||||
</toggle-submit>
|
||||
|
||||
@@ -91,8 +91,8 @@
|
||||
</dynamic-list>
|
||||
<%= local_assigns[:variables_form] %>
|
||||
<div>
|
||||
<%= render('submitters_order', f:, template:) if Accounts.can_send_emails?(current_account) %>
|
||||
<%= render 'send_email', f:, template: %>
|
||||
<%= render('submitters_order', f:, template:) if can_send_emails %>
|
||||
<%= render 'send_email', f:, template:, can_send_emails: %>
|
||||
<% if has_phone_field %>
|
||||
<%= render 'send_sms', f: %>
|
||||
<% end %>
|
||||
|
||||
@@ -69,8 +69,8 @@
|
||||
<% end %>
|
||||
<%= local_assigns[:variables_form] %>
|
||||
<div>
|
||||
<%= render('submitters_order', f:, template:) if Accounts.can_send_emails?(current_account) %>
|
||||
<%= render 'send_email', f:, template: %>
|
||||
<%= render('submitters_order', f:, template:) if can_send_emails %>
|
||||
<%= render 'send_email', f:, template:, can_send_emails: %>
|
||||
<%= render 'extra_fields', f: %>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
</dynamic-list>
|
||||
<%= local_assigns[:variables_form] %>
|
||||
<div>
|
||||
<%= render('submitters_order', f:, template:) if Accounts.can_send_emails?(current_account) %>
|
||||
<%= render 'submitters_order', f:, template: %>
|
||||
<%= render 'send_sms', f:, checked: true %>
|
||||
<%= render 'extra_phone_fields', f: %>
|
||||
</div>
|
||||
|
||||
@@ -2,15 +2,14 @@
|
||||
<% template_submitters = local_assigns[:submitter]&.submission&.template_submitters || template.submitters %>
|
||||
<% message_field_id = "message_field_#{SecureRandom.hex(3)}" %>
|
||||
<div class="form-control">
|
||||
<% can_send_emails = Accounts.can_send_emails?(current_account) %>
|
||||
<div class="flex justify-between items-center">
|
||||
<%= f.label :send_email, for: uuid = SecureRandom.uuid, class: 'flex items-center cursor-pointer' do %>
|
||||
<%= f.check_box :send_email, id: uuid, class: 'base-checkbox', disabled: !can_send_emails || local_assigns[:disable_email], checked: can_send_emails && !local_assigns.key?(:resend_email) && !local_assigns[:disable_email] && template&.preferences&.dig('request_email_enabled') != false %>
|
||||
<span class="label"><%= local_assigns[:resend_email] ? t('re_send_email') : t('send_email') %></span>
|
||||
<% end %>
|
||||
<div>
|
||||
<%= render 'submissions/email_stats' %>
|
||||
<% if can_send_emails %>
|
||||
<%= render 'submissions/email_stats' %>
|
||||
<%= content_for(:edit_button) || capture do %>
|
||||
<toggle-visible data-element-ids="<%= [message_field_id].to_json %>" class="flex">
|
||||
<label>
|
||||
@@ -57,7 +56,7 @@
|
||||
<div class="form-control">
|
||||
<%= f.label :message, t('body'), class: 'label' %>
|
||||
<% body_variables = AccountConfig::EMAIL_VARIABLES[AccountConfig::SUBMITTER_INVITATION_EMAIL_KEY] %>
|
||||
<%= render 'personalization_settings/markdown_editor', name: f.field_name(:body), value: local_assigns[:submitter_email_message]&.body.presence || submitter_preferences_index.dig(local_assigns[:submitter]&.uuid, 'request_email_body').presence || template&.preferences&.dig('request_email_body').presence || config.value['body'], variables: body_variables %>
|
||||
<%= render 'personalization_settings/markdown_editor', name: f.field_name(:body), value: local_assigns[:submitter_email_message]&.normalized_body.presence || submitter_preferences_index.dig(local_assigns[:submitter]&.uuid, 'request_email_body').presence || template&.preferences&.dig('request_email_body').presence || config.value['body'], variables: body_variables %>
|
||||
<% unless local_assigns.fetch(:disable_save_as_default_template_option, false) %>
|
||||
<label for="<%= uuid = SecureRandom.uuid %>" class="flex items-center cursor-pointer">
|
||||
<%= check_box_tag :save_message, id: uuid, class: 'base-checkbox', checked: false %>
|
||||
@@ -92,7 +91,7 @@
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<%= ff.label :message, t('body'), class: 'label' %>
|
||||
<%= render 'personalization_settings/markdown_editor', name: ff.field_name(:body), value: local_assigns[:submitter_email_message]&.body.presence || submitter_preferences_index.dig(submitter['uuid'], 'request_email_body').presence || template&.preferences&.dig('request_email_body').presence || config.value['body'], variables: body_variables %>
|
||||
<%= render 'personalization_settings/markdown_editor', name: ff.field_name(:body), value: local_assigns[:submitter_email_message]&.normalized_body.presence || submitter_preferences_index.dig(submitter['uuid'], 'request_email_body').presence || template&.preferences&.dig('request_email_body').presence || config.value['body'], variables: body_variables %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
<% if template.preferences['submitters_order'] == 'preserved' || template.submitters.any? { |s| s['order'] } || (template.submitters.size > 1 && template.fields.any? { |f| f['type'] == 'verification' }) %>
|
||||
<%= f.hidden_field :preserve_order, value: '1' %>
|
||||
<% elsif template.submitters.size > 1 %>
|
||||
<% account_submissions = template.submissions.where(account_id: current_account.id) %>
|
||||
<% last_submission = account_submissions.active.last || account_submissions.archived.last %>
|
||||
<div class="form-control">
|
||||
<%= f.label :preserve_order, for: uuid = SecureRandom.uuid, class: 'flex items-center cursor-pointer' do %>
|
||||
<%= f.check_box :preserve_order, id: uuid, class: 'base-checkbox', checked: template.submissions.last&.submitters_order.in?(['preserved', nil]) %>
|
||||
<%= f.check_box :preserve_order, id: uuid, class: 'base-checkbox', checked: last_submission&.submitters_order.in?(['preserved', nil]) %>
|
||||
<span class="label"><%= t('preserve_order') %></span>
|
||||
<span class="tooltip" data-tip="<%= t('when_checked_notifications_will_be_sent_to_the_second_party_once_the_form_is_completed_by_the_previous_party_uncheck_this_option_to_send_notifications_to_all_parties_simultaneously_right_away') %>">
|
||||
<%= svg_icon('info_circle', class: 'w-4 h-4') %>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<% is_narrow = area['h'].to_f.positive? && ((area['w'].to_f * local_assigns[:page_width]) / (area['h'].to_f * local_assigns[:page_height])) > 4.5 %>
|
||||
<div class="flex justify-between w-full h-full gap-1 <%= is_narrow && (local_assigns[:with_signature_id] || field.dig('preferences', 'reason_field_uuid').present?) ? 'flex-row' : 'flex-col' %>">
|
||||
<div class="flex overflow-hidden <%= is_narrow && (local_assigns[:with_signature_id] || field.dig('preferences', 'reason_field_uuid').present?) ? 'w-1/2' : 'flex-grow' %>" style="min-height: 50%">
|
||||
<img class="object-contain mx-auto" src="<%= attachments_index[value].url %>" alt="<%= field['name'].presence || field['title'].presence || field['type'] %>">
|
||||
<img class="object-contain mx-auto" src="<%= attachments_index[value].url(time: ActiveStorage::Attachment.service_url_time) %>" alt="<%= field['name'].presence || field['title'].presence || field['type'] %>">
|
||||
</div>
|
||||
<% if (local_assigns[:with_signature_id] || field.dig('preferences', 'reason_field_uuid').present?) && attachment = attachments_index[value] %>
|
||||
<div class="text-[1vw] lg:text-[0.55rem] lg:leading-[0.65rem] <%= is_narrow ? 'w-1/2' : 'w-full' %>">
|
||||
@@ -35,7 +35,7 @@
|
||||
<% end %>
|
||||
</div>
|
||||
<% elsif field['type'].in?(['image', 'initials', 'stamp', 'kba']) && attachments_index[value].image? %>
|
||||
<img class="object-contain mx-auto" src="<%= attachments_index[value].url %>" loading="lazy" alt="<%= field['name'].presence || field['title'].presence || field['type'] %>">
|
||||
<img class="object-contain mx-auto" src="<%= attachments_index[value].url(time: ActiveStorage::Attachment.service_url_time) %>" loading="lazy" alt="<%= field['name'].presence || field['title'].presence || field['type'] %>">
|
||||
<% elsif field['type'].in?(['file', 'payment', 'image']) %>
|
||||
<autosize-field></autosize-field>
|
||||
<div class="px-0.5 flex flex-col justify-center">
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<% prefillable_fields = @template.fields.select { |f| f['prefillable'] } %>
|
||||
<% default_tab = cookies.permanent[:add_recipients_tab].presence || 'email' %>
|
||||
<% recipient_form_fields = Accounts.load_recipient_form_fields(current_account) if prefillable_fields.blank? %>
|
||||
<% can_send_emails = Accounts.can_send_emails?(current_account) %>
|
||||
<% only_detailed = require_phone_2fa || require_email_2fa || prefillable_fields.present? || recipient_form_fields.present? %>
|
||||
<% with_list = @template.variables_schema.blank? %>
|
||||
<% variables_form = render 'variables_form', schema: @template.variables_schema if @template.variables_schema.present? && @template.variables_schema.any? { |_, v| !v['disabled'] } %>
|
||||
@@ -25,18 +26,18 @@
|
||||
<div class="px-5 mb-5 mt-4">
|
||||
<% unless only_detailed %>
|
||||
<div id="email" class="<%= 'hidden' if default_tab != 'email' %>">
|
||||
<%= render 'email_form', template: @template, variables_form: %>
|
||||
<%= render 'email_form', template: @template, variables_form:, can_send_emails: %>
|
||||
</div>
|
||||
<div id="phone" class="<%= 'hidden' if default_tab != 'phone' %>">
|
||||
<%= render 'phone_form', template: @template, variables_form: %>
|
||||
</div>
|
||||
<% end %>
|
||||
<div id="detailed" class="<%= 'hidden' if !only_detailed && default_tab != 'detailed' %>">
|
||||
<%= render 'detailed_form', template: @template, require_phone_2fa:, require_email_2fa:, prefillable_fields:, recipient_form_fields:, variables_form: %>
|
||||
<%= render 'detailed_form', template: @template, require_phone_2fa:, require_email_2fa:, prefillable_fields:, recipient_form_fields:, variables_form:, can_send_emails: %>
|
||||
</div>
|
||||
<% if with_list %>
|
||||
<div id="list" class="hidden">
|
||||
<%= render 'list_form', template: @template %>
|
||||
<%= render 'list_form', template: @template, can_send_emails: %>
|
||||
</div>
|
||||
<% end %>
|
||||
<%= render 'submissions/error' %>
|
||||
|
||||
@@ -12,11 +12,10 @@
|
||||
<div class="flex justify-between py-1.5 items-center pr-4 sticky top-0 md:relative z-10 bg-base-100">
|
||||
<a href="<%= signed_in? && @submission.account_id == current_account&.id && @submission.template ? template_path(@submission.template) : '/' %>" class="flex items-center space-x-3 py-1">
|
||||
<span><%= render 'submissions/logo' %></span>
|
||||
<h1 class="text-xl md:text-3xl font-semibold focus:text-clip" style="overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;"><% (@submission.name || @submission.template.name).split(/(_)/).each do |item| %><%= item %><wbr><% end %></h1>
|
||||
<h1 class="text-xl md:text-3xl font-semibold focus:text-clip" style="overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;"><% (@submission.name || @submission.template&.name).to_s.split(/(_)/).each do |item| %><%= item %><wbr><% end %></h1>
|
||||
</a>
|
||||
<div class="space-x-3 flex items-center">
|
||||
<% is_all_completed = @submission.submitters.to_a.all?(&:completed_at?) %>
|
||||
<% if signed_in? && can?(:create, @submission) && @submission.archived_at? && !is_all_completed %>
|
||||
<% if signed_in? && can?(:destroy, @submission) && @submission.archived_at? && !@submission.completed_at? %>
|
||||
<%= button_to button_title(title: t('unarchive'), disabled_with: t('unarchive')[0..-2], icon: svg_icon('rotate', class: 'w-6 h-6')), submission_unarchive_index_path(@submission), class: 'btn btn-primary btn-ghost text-base hidden md:flex' %>
|
||||
<% end %>
|
||||
<% if @submission.audit_trail.present? %>
|
||||
@@ -30,16 +29,19 @@
|
||||
<span class="hidden md:block"><%= t('event_log') %></span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% if signed_in? && !is_all_completed && can?(:manage, :resend_all) && @submission.submitters.to_a.size > 3 && !@submission.archived_at? && !@submission.template&.archived_at? && !@submission.expired? && can?(:update, @submission) %>
|
||||
<% if signed_in? && !@submission.completed_at? && can?(:manage, :resend_all) && @submission.submitters.to_a.size > 3 && !@submission.archived_at? && !@submission.template&.archived_at? && !@submission.expired? && can?(:update, @submission) %>
|
||||
<% pending_submitters_count = @submission.submitters.to_a.count { |s| !s.completed_at? && s.email.present? && !s.declined_at? } %>
|
||||
<% if pending_submitters_count.positive? %>
|
||||
<%= button_to button_title(title: t('re_send_emails'), title_class: 'hidden md:inline', disabled_with: t('sending'), icon: svg_icon('mail_forward', class: 'w-6 h-6')), submission_resend_email_index_path(@submission), class: 'white-button', data: { turbo_confirm: t('are_you_sure_you_want_to_re_send_email_to_n_recipients', count: pending_submitters_count) } %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% if @submission.submitters.to_a.any?(&:completed_at?) %>
|
||||
<% if is_all_completed || !is_combined_enabled %>
|
||||
<% show_combined_download = @submission.completed_at? && !is_combined_enabled %>
|
||||
<% show_unarchive = signed_in? && can?(:destroy, @submission) && @submission.archived_at? && @submission.completed_at? %>
|
||||
<% show_download_dropdown = show_combined_download || show_unarchive %>
|
||||
<% if @submission.completed_at? || !is_combined_enabled %>
|
||||
<div class="join relative">
|
||||
<download-button role="button" tabindex="0" aria-label="<%= t('download') %>" data-src="<%= local_assigns[:is_preview] ? (@sig_submitter ? submit_form_documents_path(@sig_submitter.slug, { sig: params[:sig], combined: is_combined_enabled }.compact_blank) : submissions_preview_download_index_path(@submission.slug, combined: is_combined_enabled.presence)) : submission_download_index_path(@submission, combined: is_combined_enabled.presence) %>" class="base-button <%= '!rounded-r-none !pr-2' if is_all_completed && !is_combined_enabled %>">
|
||||
<download-button role="button" tabindex="0" aria-label="<%= t('download') %>" data-src="<%= local_assigns[:is_preview] ? (@sig_submitter ? submit_form_documents_path(@sig_submitter.slug, { sig: params[:sig], combined: is_combined_enabled }.compact_blank) : submissions_preview_download_index_path(@submission.slug, combined: is_combined_enabled.presence)) : submission_download_index_path(@submission, combined: is_combined_enabled.presence) %>" class="base-button <%= '!rounded-r-none !pr-2' if show_download_dropdown %>">
|
||||
<span class="flex items-center justify-center space-x-2" data-target="download-button.defaultButton">
|
||||
<%= svg_icon('download', class: 'w-6 h-6') %>
|
||||
<span class="hidden md:inline"><%= t('download') %></span>
|
||||
@@ -49,26 +51,36 @@
|
||||
<span class="hidden md:inline"><%= t('downloading') %></span>
|
||||
</span>
|
||||
</download-button>
|
||||
<% if is_all_completed && !is_combined_enabled %>
|
||||
<div class="dropdown dropdown-end">
|
||||
<% if show_download_dropdown %>
|
||||
<div class="dropdown dropdown-end has-[button:disabled]:dropdown-open">
|
||||
<label tabindex="0" aria-label="<%= t('download') %>" class="base-button !rounded-l-none !pl-1 !pr-2 !border-l-neutral-500">
|
||||
<span class="text-sm align-text-top">
|
||||
<%= svg_icon('chevron_down', class: 'w-6 h-6 flex-shrink-0 stroke-2') %>
|
||||
</span>
|
||||
</label>
|
||||
<ul class="z-10 dropdown-content p-2 mt-2 shadow menu text-base bg-base-100 rounded-box text-right">
|
||||
<li>
|
||||
<download-button role="button" tabindex="0" data-src="<%= local_assigns[:is_preview] ? (@sig_submitter ? submit_form_documents_path(@sig_submitter.slug, { sig: params[:sig], combined: true }.compact) : submissions_preview_download_index_path(@submission.slug, combined: true)) : submission_download_index_path(@submission, combined: true) %>" class="flex items-center">
|
||||
<span class="flex items-center justify-center space-x-2" data-target="download-button.defaultButton">
|
||||
<%= svg_icon('download', class: 'w-6 h-6 flex-shrink-0') %>
|
||||
<span class="whitespace-nowrap"><%= t('download_combined_pdf') %></span>
|
||||
</span>
|
||||
<span class="flex items-center justify-center space-x-2 hidden" data-target="download-button.loadingButton">
|
||||
<%= svg_icon('loader', class: 'w-6 h-6 animate-spin') %>
|
||||
<span><%= t('downloading') %></span>
|
||||
</span>
|
||||
</download-button>
|
||||
</li>
|
||||
<% if show_combined_download %>
|
||||
<li>
|
||||
<download-button role="button" tabindex="0" data-src="<%= local_assigns[:is_preview] ? (@sig_submitter ? submit_form_documents_path(@sig_submitter.slug, { sig: params[:sig], combined: true }.compact) : submissions_preview_download_index_path(@submission.slug, combined: true)) : submission_download_index_path(@submission, combined: true) %>" class="flex items-center">
|
||||
<span class="flex items-center justify-center space-x-2" data-target="download-button.defaultButton">
|
||||
<%= svg_icon('download', class: 'w-6 h-6 flex-shrink-0') %>
|
||||
<span class="whitespace-nowrap"><%= t('download_combined_pdf') %></span>
|
||||
</span>
|
||||
<span class="flex items-center justify-center space-x-2 hidden" data-target="download-button.loadingButton">
|
||||
<%= svg_icon('loader', class: 'w-6 h-6 animate-spin') %>
|
||||
<span><%= t('downloading') %></span>
|
||||
</span>
|
||||
</download-button>
|
||||
</li>
|
||||
<% end %>
|
||||
<% if show_unarchive %>
|
||||
<li>
|
||||
<button form="submission_unarchive_form">
|
||||
<%= button_title(title: t('unarchive'), disabled_with: t('unarchive'), icon: svg_icon('rotate', class: 'w-6 h-6 flex-shrink-0')) %>
|
||||
</button>
|
||||
<%= button_to '', submission_unarchive_index_path(@submission), form: { id: 'submission_unarchive_form' }, form_class: 'hidden' %>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</div>
|
||||
<% end %>
|
||||
@@ -83,11 +95,12 @@
|
||||
<div class="overflow-y-auto overflow-x-hidden hidden lg:block w-52 flex-none pr-3 mt-0.5 pt-0.5" tabindex="0">
|
||||
<% values = @submission.submitters.reduce({}) { |acc, sub| acc.merge(sub.values) } %>
|
||||
<% schema = Submissions.filtered_conditions_schema(@submission, values:) %>
|
||||
<% service_url_time = ActiveStorage::Attachment.service_url_time %>
|
||||
<% schema.each do |item| %>
|
||||
<% document = @submission.schema_documents.find { |a| item['attachment_uuid'] == a.uuid } %>
|
||||
<% if document.preview_images.first %>
|
||||
<scroll-to data-selector-id="page-<%= document.uuid %>-0" class="block cursor-pointer">
|
||||
<img src="<%= (document.preview_images.find { |e| e.filename.base.to_i.zero? } || document.preview_images.first).url %>" width="<%= document.preview_images.first.metadata['width'] %>" height="<%= document.preview_images.first.metadata['height'] %>" class="rounded border" loading="lazy" alt="<%= item['name'].presence || document.filename.base %>">
|
||||
<img src="<%= (document.preview_images.find { |e| e.filename.base.to_i.zero? } || document.preview_images.first).url(time: service_url_time) %>" width="<%= document.preview_images.first.metadata['width'] %>" height="<%= document.preview_images.first.metadata['height'] %>" class="rounded border" loading="lazy" alt="<%= item['name'].presence || document.filename.base %>">
|
||||
<div class="pb-2 pt-1.5 text-center" dir="auto">
|
||||
<%= item['name'].presence || document.filename.base %>
|
||||
</div>
|
||||
@@ -111,7 +124,7 @@
|
||||
<% (document.metadata.dig('pdf', 'number_of_pages') || (document.preview_images.loaded? ? preview_images_index.size : document.preview_images.size)).times do |index| %>
|
||||
<% page = preview_images_index[index] || page_blob_struct.new(metadata: lazyload_metadata, url: preview_document_page_path(document.signed_key, "#{index}#{Templates::ProcessDocument::PREVIEW_FORMAT}")) %>
|
||||
<page-container id="<%= "page-#{document.uuid}-#{index}" %>" class="block before:border before:absolute before:top-0 before:bottom-0 before:left-0 before:right-0 before:rounded relative mb-4" style="container-type: size; aspect-ratio: <%= width = page.metadata['width'] %> / <%= height = page.metadata['height'] %>">
|
||||
<img loading="lazy" src="<%= page.url %>" width="<%= width %>" class="rounded" height="<%= height %>" alt="<%= "#{item['name']} - #{t('page')} #{index + 1}" %>">
|
||||
<img loading="lazy" src="<%= page.is_a?(ActiveStorage::Attachment) ? page.url(time: service_url_time) : page.url %>" width="<%= width %>" class="rounded" height="<%= height %>" alt="<%= "#{item['name']} - #{t('page')} #{index + 1}" %>">
|
||||
<div class="top-0 bottom-0 left-0 right-0 absolute">
|
||||
<% document_annots_index[index]&.each do |annot| %>
|
||||
<%= render 'submissions/annotation', annot: %>
|
||||
@@ -231,7 +244,7 @@
|
||||
</span>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if signed_in? && submitter && submitter.email && !submitter.completed_at && !@submission.archived_at? && !@submission.template&.archived_at? && can?(:update, @submission) && Accounts.can_send_emails?(current_account) && !@submission.expired? && !submitter.declined_at? %>
|
||||
<% if signed_in? && submitter && submitter.email && !submitter.completed_at && !@submission.archived_at? && !@submission.template&.archived_at? && can?(:update, @submission) && (Docuseal.multitenant? || Accounts.can_send_emails?(current_account)) && !@submission.expired? && !submitter.declined_at? %>
|
||||
<div class="mt-2 mb-1">
|
||||
<%= button_to button_title(title: submitter.sent_at? ? t('re_send_email') : t('send_email'), disabled_with: t('sending')), submitter_send_email_index_path(submitter), class: 'btn btn-sm btn-primary w-full' %>
|
||||
</div>
|
||||
@@ -267,11 +280,11 @@
|
||||
<% if field['type'].in?(%w[signature initials]) %>
|
||||
<div class="w-full bg-base-300 py-1">
|
||||
<% img_height = attachments_index[value].metadata['height'] %>
|
||||
<img class="object-contain mx-auto" style="<%= 'max-' if img_height %>height: <%= field['type'] == 'signature' ? 100 : 50 %>px" height="<%= img_height %>" width="<%= attachments_index[value].metadata['width'] %>" src="<%= attachments_index[value].url %>" loading="lazy" alt="<%= field['name'] || field['title'] || field['type'] %>">
|
||||
<img class="object-contain mx-auto" style="<%= 'max-' if img_height %>height: <%= field['type'] == 'signature' ? 100 : 50 %>px" height="<%= img_height %>" width="<%= attachments_index[value].metadata['width'] %>" src="<%= attachments_index[value].url(time: service_url_time) %>" loading="lazy" alt="<%= field['name'] || field['title'] || field['type'] %>">
|
||||
</div>
|
||||
<% elsif field['type'].in?(['image', 'stamp', 'kba']) && attachments_index[value].image? %>
|
||||
<% img_height = attachments_index[value].metadata['height'] %>
|
||||
<img class="object-contain mx-auto" style="<%= 'max-' if img_height %>height: 200px" height="<%= img_height %>" width="<%= attachments_index[value].metadata['width'] %>" src="<%= attachments_index[value].url %>" loading="lazy" alt="<%= field['name'] || field['title'] || field['type'] %>">
|
||||
<img class="object-contain mx-auto" style="<%= 'max-' if img_height %>height: 200px" height="<%= img_height %>" width="<%= attachments_index[value].metadata['width'] %>" src="<%= attachments_index[value].url(time: service_url_time) %>" loading="lazy" alt="<%= field['name'] || field['title'] || field['type'] %>">
|
||||
<% elsif field['type'].in?(['file', 'payment', 'image']) %>
|
||||
<div class="flex flex-col justify-center">
|
||||
<% Array.wrap(value).each do |val| %>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<%= svg_icon('writing_sign', class: 'w-10 h-10') %>
|
||||
</div>
|
||||
<div>
|
||||
<p dir="auto" class="text-lg font-bold mb-1"><%= @submission.name || @submission.template.name %></p>
|
||||
<p dir="auto" class="text-lg font-bold mb-1"><%= @submission.name || @submission.template&.name %></p>
|
||||
<% last_submitter = @submission.submitters.completed.order(:completed_at).last %>
|
||||
<% if last_submitter %>
|
||||
<p dir="auto" class="text-sm">
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
<% data_fields = Submissions.filtered_conditions_fields(submitter).to_json %>
|
||||
<% invite_submitters = (submitter.submission.template_submitters || submitter.submission.template.submitters).select { |s| s['invite_by_uuid'] == submitter.uuid && submitter.submission.submitters.none? { |e| e.uuid == s['uuid'] } }.to_json %>
|
||||
<% optional_invite_submitters = (submitter.submission.template_submitters || submitter.submission.template.submitters).select { |s| s['optional_invite_by_uuid'] == submitter.uuid && submitter.submission.submitters.none? { |e| e.uuid == s['uuid'] } }.to_json %>
|
||||
<submission-form data-is-demo="<%= Docuseal.demo? %>" data-schema="<%= schema.to_json %>" data-reuse-signature="<%= configs[:reuse_signature] %>" data-require-signing-reason="<%= configs[:require_signing_reason] %>" data-with-signature-id="<%= configs[:with_signature_id] %>" data-with-field-labels="<%= configs[:with_field_labels] %>" data-with-confetti="<%= configs[:with_confetti] %>" data-completed-redirect-url="<%= submitter.preferences['completed_redirect_url'].presence || submitter.submission.template&.preferences&.dig('completed_redirect_url') %>" data-completed-message="<%= (configs[:completed_message]&.compact_blank.presence || submitter.submission.template&.preferences&.dig('completed_message') || {}).to_json %>" data-completed-button="<%= configs[:completed_button].to_json %>" data-go-to-last="<%= submitter.preferences.key?('go_to_last') ? submitter.preferences['go_to_last'] : submitter.opened_at? %>" data-submitter="<%= submitter.to_json(only: %i[uuid slug name phone email]) %>" data-can-send-email="<%= Accounts.can_send_emails?(submitter.submission.account) %>" data-optional-invite-submitters="<%= optional_invite_submitters %>" data-invite-submitters="<%= invite_submitters %>" data-attachments="<%= data_attachments %>" data-fields="<%= data_fields %>" data-values="<%= submitter.values.to_json %>" data-with-typed-signature="<%= configs[:with_typed_signature] %>" data-signature-text="<%= params[:signature] %>" data-previous-signature-value="<%= local_assigns[:signature_attachment]&.uuid %>" data-remember-signature="<%= configs[:prefill_signature] %>" data-dry-run="<%= local_assigns[:dry_run] %>" data-expand="<%= local_assigns[:expand] %>" data-scroll-padding="<%= local_assigns[:scroll_padding] %>" data-language="<%= I18n.locale.to_s.split('-').first %>"></submission-form>
|
||||
<submission-form data-is-demo="<%= Docuseal.demo? %>" data-schema="<%= schema.to_json %>" data-reuse-signature="<%= configs[:reuse_signature] %>" data-require-signing-reason="<%= configs[:require_signing_reason] %>" data-with-signature-id="<%= configs[:with_signature_id] %>" data-with-field-labels="<%= configs[:with_field_labels] %>" data-with-confetti="<%= configs[:with_confetti] %>" data-completed-redirect-url="<%= submitter.preferences['completed_redirect_url'].presence || submitter.submission.template&.preferences&.dig('completed_redirect_url') %>" data-completed-message="<%= (configs[:completed_message]&.compact_blank.presence || submitter.submission.template&.preferences&.dig('completed_message') || {}).to_json %>" data-completed-button="<%= configs[:completed_button].to_json %>" data-go-to-last="<%= submitter.preferences.key?('go_to_last') ? submitter.preferences['go_to_last'] : submitter.opened_at? %>" data-submitter="<%= submitter.to_json(only: %i[uuid slug name phone email]) %>" data-can-send-email="<%= Docuseal.multitenant? || Accounts.can_send_emails?(submitter.submission.account) %>" data-optional-invite-submitters="<%= optional_invite_submitters %>" data-invite-submitters="<%= invite_submitters %>" data-attachments="<%= data_attachments %>" data-fields="<%= data_fields %>" data-values="<%= submitter.values.to_json %>" data-with-typed-signature="<%= configs[:with_typed_signature] %>" data-signature-text="<%= params[:signature] %>" data-previous-signature-value="<%= local_assigns[:signature_attachment]&.uuid %>" data-remember-signature="<%= configs[:prefill_signature] %>" data-dry-run="<%= local_assigns[:dry_run] %>" data-expand="<%= local_assigns[:expand] %>" data-scroll-padding="<%= local_assigns[:scroll_padding] %>" data-language="<%= I18n.locale.to_s.split('-').first %>"></submission-form>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<%= svg_icon('writing_sign', class: 'w-10 h-10') %>
|
||||
</div>
|
||||
<div dir="auto">
|
||||
<p class="text-lg font-bold mb-1"><%= @submitter.submission.name || @submitter.submission.template.name %></p>
|
||||
<p class="text-lg font-bold mb-1"><%= @submitter.submission.name || @submitter.submission.template&.name %></p>
|
||||
<p class="text-sm"><%= t('form_has_been_deleted_by_html', name: @submitter.account.name) %></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<%= svg_icon('writing_sign', class: 'w-10 h-10') %>
|
||||
</div>
|
||||
<div dir="auto">
|
||||
<p class="text-lg font-bold mb-1"><%= @submitter.submission.name || @submitter.submission.template.name %></p>
|
||||
<p class="text-lg font-bold mb-1"><%= @submitter.submission.name || @submitter.submission.template&.name %></p>
|
||||
<p class="text-sm"><%= t('awaiting_completion_by_the_other_party') %></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<%= svg_icon('writing_sign', class: 'w-10 h-10') %>
|
||||
</div>
|
||||
<div>
|
||||
<p dir="auto" class="text-lg font-bold mb-1"><%= @submitter.submission.name || @submitter.submission.template.name %></p>
|
||||
<p dir="auto" class="text-lg font-bold mb-1"><%= @submitter.submission.name || @submitter.submission.template&.name %></p>
|
||||
<p dir="auto" class="text-sm">
|
||||
<%= t(@submitter.with_signature_fields? ? 'signed_on_time' : 'completed_on_time', time: l(@submitter.completed_at.to_date, format: :long)) %>
|
||||
</p>
|
||||
@@ -43,7 +43,7 @@
|
||||
<% end %>
|
||||
</div>
|
||||
<% undefined_submitters = Templates.filter_undefined_submitters(@submitter.submission.template_submitters) %>
|
||||
<% if undefined_submitters.size == 1 && undefined_submitters.first['uuid'] == @submitter.uuid && %w[api embed].exclude?(@submitter.submission.source) && @submitter.account.account_configs.find_or_initialize_by(key: AccountConfig::ALLOW_TO_RESUBMIT).value != false && @submitter.template && !@submitter.template.archived_at? %>
|
||||
<% if undefined_submitters.size == 1 && undefined_submitters.first['uuid'] == @submitter.uuid && @submitter.completed_at? && @submitter.completed_at > 14.days.ago && %w[api embed mcp].exclude?(@submitter.submission.source) && @submitter.account.account_configs.find_or_initialize_by(key: AccountConfig::ALLOW_TO_RESUBMIT).value != false && @submitter.template && !@submitter.template.archived_at? %>
|
||||
<div class="divider uppercase"><%= t('or') %></div>
|
||||
<toggle-submit class="block">
|
||||
<%= button_to button_title(title: t('resubmit'), disabled_with: t('resubmit'), icon: svg_icon('reload', class: 'w-6 h-6')), resubmit_form_path, params: { resubmit: @submitter.slug }, method: :put, class: 'white-button w-full' %>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<%= svg_icon('writing_sign', class: 'w-10 h-10') %>
|
||||
</div>
|
||||
<div dir="auto">
|
||||
<p class="text-lg font-bold mb-1"><%= @submitter.submission.name || @submitter.submission.template.name %></p>
|
||||
<p class="text-lg font-bold mb-1"><%= @submitter.submission.name || @submitter.submission.template&.name %></p>
|
||||
<p class="text-sm"><%= t('form_has_been_declined_on_html', time: l(@submitter.declined_at, format: :long)) %></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<%= svg_icon('user_share', class: 'w-10 h-10') %>
|
||||
</div>
|
||||
<div dir="auto">
|
||||
<p class="text-lg font-bold mb-1"><%= @submitter.submission.name || @submitter.submission.template.name %></p>
|
||||
<p class="text-lg font-bold mb-1"><%= @submitter.submission.name || @submitter.submission.template&.name %></p>
|
||||
<p class="text-sm"><%= t('document_has_been_delegated_on_html', time: l(@submitter.submission_events.order(event_timestamp: :desc).find_by!(event_type: :delegate_form).event_timestamp, format: :long)) %></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<% content_for(:html_title, "#{@submitter.submission.name || @submitter.submission.template.name} | DocuSeal") %>
|
||||
<% content_for(:html_title, "#{@submitter.submission.name || @submitter.submission.template&.name} | DocuSeal") %>
|
||||
<% I18n.with_locale(@submitter.account.locale) do %>
|
||||
<% content_for(:html_description, t('account_name_has_invited_you_to_fill_and_sign_documents_online_effortlessly_with_a_secure_fast_and_user_friendly_digital_document_signing_solution', account_name: @submitter.account.name)) %>
|
||||
<% end %>
|
||||
@@ -14,7 +14,7 @@
|
||||
<%= svg_icon('writing_sign', class: 'w-10 h-10') %>
|
||||
</div>
|
||||
<div>
|
||||
<p dir="auto" class="text-lg font-bold mb-1"><%= @submitter.submission.name || @submitter.submission.template.name %></p>
|
||||
<p dir="auto" class="text-lg font-bold mb-1"><%= @submitter.submission.name || @submitter.submission.template&.name %></p>
|
||||
<% last_submitter = @submitter.submission.submitters.completed.order(:completed_at).last %>
|
||||
<% if last_submitter %>
|
||||
<p dir="auto" class="text-sm">
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<%= svg_icon('writing_sign', class: 'w-10 h-10') %>
|
||||
</div>
|
||||
<div dir="auto">
|
||||
<p class="text-lg font-bold mb-1"><%= @submitter.submission.name || @submitter.submission.template.name %></p>
|
||||
<p class="text-lg font-bold mb-1"><%= @submitter.submission.name || @submitter.submission.template&.name %></p>
|
||||
<p class="text-sm"><%= t('form_expired_at_html', time: l(@submitter.submission.expire_at, format: :long)) %></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<% content_for(:html_title, "#{@submitter.submission.name || @submitter.submission.template.name} | DocuSeal") %>
|
||||
<% content_for(:html_title, "#{@submitter.submission.name || @submitter.submission.template&.name} | DocuSeal") %>
|
||||
<% I18n.with_locale(@submitter.account.locale) do %>
|
||||
<% content_for(:html_description, t('account_name_has_invited_you_to_fill_and_sign_documents_online_effortlessly_with_a_secure_fast_and_user_friendly_digital_document_signing_solution', account_name: @submitter.account.name)) %>
|
||||
<% end %>
|
||||
@@ -6,6 +6,7 @@
|
||||
<% values = @submitter.submission.submitters.reduce({}) { |acc, sub| acc.merge(sub.values) } %>
|
||||
<% submitters_index = @submitter.submission.submitters.index_by(&:uuid) %>
|
||||
<% page_blob_struct = Struct.new(:url, :metadata) %>
|
||||
<% service_url_time = ActiveStorage::Attachment.service_url_time %>
|
||||
<% schema = Submissions.filtered_conditions_schema(@submitter.submission, values:, include_submitter_uuid: @submitter.uuid) %>
|
||||
<% font_scale = 1000.0 / PdfUtils::US_LETTER_W %>
|
||||
<% decline_modal_id = nil %>
|
||||
@@ -19,7 +20,7 @@
|
||||
<%= render('submit_form/banner') %>
|
||||
<header id="signing_form_header" class="sticky min-[1230px]:static top-0 z-50 bg-base-100 py-2 px-2 flex items-center md:-mx-[8px]" style="margin-bottom: -16px">
|
||||
<h1 class="text-xl md:text-2xl font-medium focus:text-clip" style="width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
|
||||
<%= @submitter.submission.name || @submitter.submission.template.name %>
|
||||
<%= @submitter.submission.name || @submitter.submission.template&.name %>
|
||||
</h1>
|
||||
<div class="flex items-center gap-2 group" style="margin-left: 20px; flex-shrink: 0">
|
||||
<% if @form_configs[:with_decline] %>
|
||||
@@ -138,7 +139,7 @@
|
||||
<% (document.metadata.dig('pdf', 'number_of_pages') || (document.preview_images.loaded? ? preview_images_index.size : document.preview_images.size)).times do |index| %>
|
||||
<% page = preview_images_index[index] || page_blob_struct.new(metadata: lazyload_metadata, url: preview_document_page_path(document.signed_key, "#{index}#{Templates::ProcessDocument::PREVIEW_FORMAT}")) %>
|
||||
<page-container class="block relative my-4 shadow-md" style="container-type: size; aspect-ratio: <%= width = page.metadata['width'] %> / <%= height = page.metadata['height'] %>">
|
||||
<img loading="lazy" src="<%= page.url %>" width="<%= width %>" height="<%= height %>" alt="<%= "#{item['name']} - #{t('page')} #{index + 1}" %>">
|
||||
<img loading="lazy" src="<%= page.is_a?(ActiveStorage::Attachment) ? page.url(time: service_url_time) : page.url %>" width="<%= width %>" height="<%= height %>" alt="<%= "#{item['name']} - #{t('page')} #{index + 1}" %>">
|
||||
<div id="page-<%= [document.uuid, index].join('-') %>" class="top-0 bottom-0 left-0 right-0 absolute">
|
||||
<% if annots = document_annots_index[index] %>
|
||||
<%= render 'submit_form/annotations', annots: %>
|
||||
|
||||
@@ -2,6 +2,6 @@
|
||||
<%= render 'custom_content', content: @body, submitter: @submitter %>
|
||||
<% else %>
|
||||
<p><%= t('hi_there') %>,</p>
|
||||
<p><%= I18n.t(:name_has_been_completed_by_submitters, name: @submitter.submission.name || @submitter.submission.template.name, submitters: @submitter.submission.submitters.order(:completed_at).map { |e| e.name || e.email || e.phone }.uniq.join(', ')) %></p>
|
||||
<p><%= I18n.t(:name_has_been_completed_by_submitters, name: @submitter.submission.name || @submitter.submission.template&.name, submitters: @submitter.submission.submitters.order(:completed_at).map { |e| e.name || e.email || e.phone }.uniq.join(', ')) %></p>
|
||||
<p><%= link_to submission_url(@submitter.submission), submission_url(@submitter.submission) %></p>
|
||||
<% end %>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<p><%= t('hi_there') %>,</p>
|
||||
<p><%= t('name_declined_by_submitter_with_the_following_reason', name: @submitter.submission.name || @submitter.submission.template.name, submitter: @submitter.name || @submitter.email || @submitter.phone) %></p>
|
||||
<p><%= t('name_declined_by_submitter_with_the_following_reason', name: @submitter.submission.name || @submitter.submission.template&.name, submitter: @submitter.name || @submitter.email || @submitter.phone) %></p>
|
||||
<%= simple_format(h(@submitter.submission_events.find_by(event_type: :decline_form).data['reason']), {}, sanitize: false) %>
|
||||
<p><%= link_to submission_url(@submitter.submission), submission_url(@submitter.submission) %></p>
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
<%= render 'custom_content', content: @body, submitter: @submitter, sig: @sig %>
|
||||
<% else %>
|
||||
<p><%= t('hi_there') %>,</p>
|
||||
<p><%= t('please_check_the_copy_of_your_name_in_the_email_attachments', name: @submitter.submission.name || @submitter.submission.template.name) %>
|
||||
<p><%= t('please_check_the_copy_of_your_name_in_the_email_attachments', name: @submitter.submission.name || @submitter.submission.template&.name) %>
|
||||
<p><%= t('alternatively_you_can_review_and_download_your_copy_using_the_link_below') %></p>
|
||||
<p>
|
||||
<%= link_to @submitter.submission.name || @submitter.submission.template.name, submissions_preview_url(@submitter.submission.slug, { sig: @sig, host: @custom_domain || ENV.fetch('EMAIL_HOST', Docuseal.default_url_options[:host]) }.compact) %>
|
||||
<%= link_to @submitter.submission.name || @submitter.submission.template&.name, submissions_preview_url(@submitter.submission.slug, { sig: @sig, host: @custom_domain || ENV.fetch('EMAIL_HOST', Docuseal.default_url_options[:host]) }.compact) %>
|
||||
</p>
|
||||
<p>
|
||||
<%= t('thanks') %>,<br><%= @current_account.name %>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<% end %>
|
||||
<% 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.name || @submitter.submission.template.name) %></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.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'), host: @custom_domain || ENV.fetch('EMAIL_HOST', Docuseal.default_url_options[:host])) %></p>
|
||||
<p><%= t('please_contact_us_by_replying_to_this_email_if_you_have_any_questions') %></p>
|
||||
<p>
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<p><%= t('your_verification_code_to_access_the_name', name: @submitter.submission.name || @submitter.submission.template.name) %></p>
|
||||
<p><%= t('your_verification_code_to_access_the_name', name: @submitter.submission.name || @submitter.submission.template&.name) %></p>
|
||||
<p><b><%= @otp_code %></b></p>
|
||||
<p><%= t('please_reply_to_this_email_if_you_didnt_request_this') %></p>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
</submitter-item>
|
||||
</div>
|
||||
<div>
|
||||
<%= render 'submissions/send_email', f:, template: @submitter.template, submitter: @submitter, resend_email: @submitter.sent_at?, submitter_email_message: @submitter_email_message, disable_save_as_default_template_option: true, message_per_submitter: false %>
|
||||
<%= render 'submissions/send_email', f:, template: @submitter.template, submitter: @submitter, resend_email: @submitter.sent_at?, submitter_email_message: @submitter_email_message, disable_save_as_default_template_option: true, message_per_submitter: false, can_send_emails: Accounts.can_send_emails?(current_account) %>
|
||||
<%= render 'submissions/send_sms', f:, resend_sms: @submitter.sent_at? %>
|
||||
</div>
|
||||
<div class="form-control mt-4">
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<% is_long = folder.name.size > 32 %>
|
||||
<% icon = folder.default? ? 'folder_star' : 'folder' %>
|
||||
<a href="<%= folder_path(folder) %>" class="flex h-full flex-col justify-between rounded-2xl py-5 px-6 w-full bg-base-200 before:border-2 before:border-base-300 before:border-dashed before:absolute before:left-0 before:right-0 before:top-0 before:bottom-0 before:hidden before:rounded-2xl relative" data-targets="dashboard-dropzone.folderCards" data-full-name="<%= folder.full_name %>">
|
||||
<% if !is_long %>
|
||||
<%= svg_icon('folder', class: 'w-6 h-6') %>
|
||||
<%= svg_icon(icon, class: 'w-6 h-6') %>
|
||||
<% end %>
|
||||
<div class="text-lg font-semibold mt-1" style="overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: <%= is_long ? 2 : 1 %>;">
|
||||
<% if is_long %>
|
||||
<%= svg_icon('folder', class: 'w-6 h-6 inline') %>
|
||||
<%= svg_icon(icon, class: 'w-6 h-6 inline') %>
|
||||
<% end %>
|
||||
<%= folder.name %>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<a href="<%= templates_shared_index_path %>" class="flex h-full flex-col justify-between rounded-2xl py-5 px-6 w-full bg-base-200 before:border-2 before:border-base-300 before:border-dashed before:absolute before:left-0 before:right-0 before:top-0 before:bottom-0 before:hidden before:rounded-2xl relative">
|
||||
<%= svg_icon('folder_up', class: 'w-6 h-6') %>
|
||||
<div class="text-lg font-semibold mt-1 capitalize" style="overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 1;">
|
||||
<%= t('shared') %>
|
||||
</div>
|
||||
</a>
|
||||
@@ -30,7 +30,7 @@
|
||||
<% end %>
|
||||
</h1>
|
||||
<div class="flex space-x-2">
|
||||
<% if params[:q].present? || @pagy.pages > 1 || @template_folders.present? %>
|
||||
<% if params[:q].present? || @pagy.count.nil? || @pagy.count > 0 || @template_folders.present? %>
|
||||
<%= render 'shared/search_input' %>
|
||||
<% end %>
|
||||
<% if can?(:create, ::Template) %>
|
||||
@@ -54,8 +54,8 @@
|
||||
<%= render partial: 'templates/template', collection: @templates %>
|
||||
</div>
|
||||
<% templates_order_select_html = capture do %>
|
||||
<% if params[:q].blank? && @pagy.pages > 1 %>
|
||||
<%= render 'shared/templates_order_select', with_recently_used: @pagy.count.present? && @pagy.count < 10_000 && !can?(:manage, :countless), selected_order: %>
|
||||
<% if params[:q].blank? && @pagy.pages > 1 && !can?(:manage, :countless) %>
|
||||
<%= render 'shared/templates_order_select', with_recently_used: @pagy.count.present? && @pagy.count < 10_000, selected_order: %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<%= render 'shared/pagination', pagy: @pagy, items_name: @templates.present? ? 'templates' : 'template_folders', right_additional_html: templates_order_select_html %>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<div class="font-medium items-start w-full group-hover:link text-sm flex space-x-1">
|
||||
<%= svg_icon('file_text', class: 'w-4 h-4 mt-0.5 flex-shrink-0') %>
|
||||
<span style="overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;">
|
||||
<% (submission.name || template.name).split(/(_)/).each do |item| %><%= item %><wbr><% end %>
|
||||
<% (submission.name || template&.name).to_s.split(/(_)/).each do |item| %><%= item %><wbr><% end %>
|
||||
<%= svg_icon('arrow_right', class: 'w-4 h-4 sm:inline group-hover:visible invisible hidden') %>
|
||||
</span>
|
||||
</div>
|
||||
@@ -28,7 +28,7 @@
|
||||
<% end %>
|
||||
<div class="w-full flex flex-col md:flex-row space-y-4 md:space-y-0 md:justify-between px-5 md:px-6 pb-5 md:items-center pt-5 relative cursor-pointer">
|
||||
<% submitters = (submission.template_submitters || submission.template.submitters).filter_map { |item| submission.submitters.find { |e| e.uuid == item['uuid'] } } %>
|
||||
<% is_submission_completed = submitters.all?(&:completed_at?) && submitters.size.positive? %>
|
||||
<% is_submission_completed = submission.completed_at? %>
|
||||
<% if submitters.size == 1 %>
|
||||
<div>
|
||||
<% submitter = submitters.first %>
|
||||
@@ -51,7 +51,7 @@
|
||||
<a href="<%= submission_path(submission) %>" class="text-lg break-all peer">
|
||||
<%= submitter.name || submitter.email || submitter.phone %>
|
||||
</a>
|
||||
<% if can?(:update, submission) && !submitter.start_form_submission_events.any? && !submission.archived_at? && !submission.expired? && !submitter.declined_at? %>
|
||||
<% if !submitter.completed_at? && can?(:update, submission) && !submitter.start_form_submission_events.any? && !submission.archived_at? && !submission.expired? && !submitter.declined_at? %>
|
||||
<span class="pl-0.5 tooltip tooltip-top md:opacity-0 md:hover:opacity-100 md:peer-hover:opacity-100" data-tip="<%= t('edit') %>">
|
||||
<%= link_to edit_submitter_path(submitter), class: 'shrink-0', data: { turbo_frame: 'modal' } do %>
|
||||
<%= svg_icon('pencil', class: 'w-5 h-5') %>
|
||||
@@ -144,7 +144,7 @@
|
||||
<a href="<%= submission_path(submission) %>" class="text-lg break-all peer">
|
||||
<%= submitter.name || submitter.email || submitter.phone %>
|
||||
</a>
|
||||
<% if can?(:update, submission) && !submitter.start_form_submission_events.any? && !submission.archived_at? && !submission.expired? && !submitter.declined_at? %>
|
||||
<% if !submitter.completed_at? && can?(:update, submission) && !submitter.start_form_submission_events.any? && !submission.archived_at? && !submission.expired? && !submitter.declined_at? %>
|
||||
<span class="pl-0.5 tooltip tooltip-top md:opacity-0 md:hover:opacity-100 md:peer-hover:opacity-100" data-tip="<%= t('edit') %>">
|
||||
<%= link_to edit_submitter_path(submitter), class: 'shrink-0', data: { turbo_frame: 'modal' } do %>
|
||||
<%= svg_icon('pencil', class: 'w-5 h-5') %>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<%= svg_icon('user', class: 'w-4 h-4') %>
|
||||
<span><%= template.author.full_name.presence || template.author.email.to_s.sub(/\+\w+@/, '@') %></span>
|
||||
<% if template.account_id != current_account.id %>
|
||||
<span class="badge badge-neutral badge-outline badge-sm text-[10px] uppercase"><%= t('shared') %></span>
|
||||
<span class="badge badge-neutral badge-outline badge-sm text-[10px] uppercase !border-base-content/60"><%= t('shared') %></span>
|
||||
<% end %>
|
||||
</p>
|
||||
<p class="flex text-xs text-base-content/60">
|
||||
@@ -19,7 +19,7 @@
|
||||
<%= svg_icon('calendar', class: 'w-4 h-4') %>
|
||||
<span><%= l(template.created_at.in_time_zone(current_account.timezone), format: :short, locale: current_account.locale) %></span>
|
||||
</span>
|
||||
<% if template.archived_at? %>
|
||||
<% if local_assigns[:with_folder] %>
|
||||
<span class="flex items-center space-x-1 w-1/2">
|
||||
<%= svg_icon('folder', class: 'w-4 h-4 flex-shrink-0') %>
|
||||
<span class="truncate"><%= template.folder.full_name %></span>
|
||||
@@ -37,14 +37,14 @@
|
||||
</a>
|
||||
</span>
|
||||
<% end %>
|
||||
<% if template.archived_at? && can?(:update, template) %>
|
||||
<% if template.archived_at? && can?(:destroy, template) %>
|
||||
<span class="tooltip tooltip-left" data-tip="<%= t('restore') %>">
|
||||
<%= button_to template_restore_index_path(template), class: 'btn btn-xs hover:btn-outline bg-base-200 btn-circle' do %>
|
||||
<%= svg_icon('rotate', class: 'w-4 h-4 enabled') %>
|
||||
<%= svg_icon('loader', class: 'w-4 h-4 animate-spin disabled') %>
|
||||
<% end %>
|
||||
</span>
|
||||
<% elsif can?(:update, template) %>
|
||||
<% elsif !template.archived_at? && can?(:update, template) %>
|
||||
<span class="tooltip tooltip-left" data-tip="<%= t('edit') %>">
|
||||
<a href="<%= edit_template_path(template) %>" class="btn btn-xs hover:btn-outline bg-base-200 btn-circle">
|
||||
<%= svg_icon('pencil', class: 'w-4 h-4') %>
|
||||
|
||||
@@ -31,9 +31,12 @@
|
||||
<% else %>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<div class="flex items-center space-x-1 mt-1 peer">
|
||||
<span class="badge badge-neutral badge-outline badge-md text-xs text-white uppercase"><%= t('shared') %></span>
|
||||
</div>
|
||||
<a href="<%= templates_shared_index_path %>" class="flex items-center space-x-1 mt-1">
|
||||
<%= svg_icon('folder', class: 'w-5 h-5 flex-shrink-0') %>
|
||||
<span class="text-sm capitalize">
|
||||
<%= t('shared') %>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
@@ -88,7 +91,7 @@
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% if template.archived_at? %>
|
||||
<% if can?(:create, template) %>
|
||||
<% if can?(:destroy, template) %>
|
||||
<%= button_to button_title(title: t('restore'), disabled_with: t('restoring')[..-4], icon: svg_icon('rotate', class: 'w-6 h-6')), template_restore_index_path(template), class: 'btn btn-outline btn-sm flex-1' %>
|
||||
<% end %>
|
||||
<%= link_to template_preview_path(template), class: 'btn btn-outline btn-sm flex-1' do %>
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
</div>
|
||||
<% unless can?(:manage, :countless) %>
|
||||
<div class="badge badge-neutral badge-outline font-medium">
|
||||
<%= params[:status].blank? && filter_params.blank? ? @pagy.count : @base_submissions.unscope(:group, :order).select(:id).distinct.count %>
|
||||
<%= params[:status].blank? && filter_params.blank? ? @pagy.count : @base_submissions.count %>
|
||||
</div>
|
||||
<% end %>
|
||||
</a>
|
||||
@@ -48,7 +48,7 @@
|
||||
</div>
|
||||
<% unless can?(:manage, :countless) %>
|
||||
<div class="badge badge-neutral badge-outline font-medium">
|
||||
<%= params[:status] == 'pending' && filter_params.blank? ? @pagy.count : @base_submissions.pending.unscope(:group, :order).select(:id).distinct.count %>
|
||||
<%= params[:status] == 'pending' && filter_params.blank? ? @pagy.count : @base_submissions.pending.count %>
|
||||
</div>
|
||||
<% end %>
|
||||
</a>
|
||||
@@ -59,7 +59,7 @@
|
||||
</div>
|
||||
<% unless can?(:manage, :countless) %>
|
||||
<div class="badge badge-neutral badge-outline font-medium">
|
||||
<%= params[:status] == 'completed' && filter_params.blank? ? @pagy.count : @base_submissions.completed.unscope(:group, :order).select(:id).distinct.count %>
|
||||
<%= params[:status] == 'completed' && filter_params.blank? ? @pagy.count : @base_submissions.completed.count %>
|
||||
</div>
|
||||
<% end %>
|
||||
</a>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
</div>
|
||||
<% if @pagy.count.nil? || @pagy.count > 0 %>
|
||||
<div class="grid gap-4 md:grid-cols-3">
|
||||
<%= render partial: 'templates/template', collection: @templates %>
|
||||
<%= render partial: 'templates/template', collection: @templates, locals: { with_folder: true } %>
|
||||
</div>
|
||||
<% elsif params[:q].present? %>
|
||||
<div class="text-center">
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
</h1>
|
||||
</div>
|
||||
<div class="flex space-x-2">
|
||||
<% if params[:q].present? || @pagy.pages > 1 || @template_folders.present? %>
|
||||
<% if params[:q].present? || @pagy.count.nil? || @pagy.count > 1 || @template_folders.present? %>
|
||||
<%= render 'shared/search_input' %>
|
||||
<% end %>
|
||||
<% if can?(:create, ::Template) %>
|
||||
@@ -45,12 +45,18 @@
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% templates_order_select_html = capture do %>
|
||||
<% if params[:q].blank? && @pagy.pages > 1 %>
|
||||
<%= render 'shared/templates_order_select', with_recently_used: @pagy.count.present? && @pagy.count < 10_000 && !can?(:manage, :countless), selected_order: %>
|
||||
<% if params[:q].blank? && @pagy.pages > 1 && !can?(:manage, :countless) %>
|
||||
<%= render 'shared/templates_order_select', with_recently_used: @pagy.count.present? && @pagy.count < 10_000, selected_order: %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% if @template_folders.present? %>
|
||||
<% if @template_folders.present? || @show_default_folder || @show_shared_folder %>
|
||||
<div class="grid gap-4 md:grid-cols-3 <%= 'mb-6' if @templates.present? %>">
|
||||
<% if @show_default_folder %>
|
||||
<%= render 'template_folders/folder', folder: @default_folder %>
|
||||
<% end %>
|
||||
<% if @show_shared_folder %>
|
||||
<%= render 'template_folders/shared_folder' %>
|
||||
<% end %>
|
||||
<%= render partial: 'template_folders/folder', collection: @template_folders, as: :folder %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
<div>
|
||||
<%= link_to(@is_archived ? templates_shared_index_path : root_path, class: 'flex items-center') do %>
|
||||
<%= svg_icon('chevron_left', class: 'w-5 h-5') %>
|
||||
<span style="margin-left: 3px"><%= @is_archived ? t('back_to_active') : t('home') %></span>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="relative flex justify-between items-center w-full mb-4">
|
||||
<h1 class="text-2xl truncate md:text-3xl font-bold flex items-center flex-grow min-w-0 space-x-2 md:flex <%= 'hidden' if params[:q].present? %>">
|
||||
<%= svg_icon('folder', class: 'w-9 h-9 flex-shrink-0') %>
|
||||
<span class="truncate capitalize"><%= t('shared') %></span>
|
||||
<% if @is_archived %>
|
||||
<span class="badge badge-outline badge-lg align-middle"><%= t('archived') %></span>
|
||||
<% end %>
|
||||
</h1>
|
||||
<div class="flex space-x-2">
|
||||
<% if params[:q].present? || @pagy.count.nil? || @pagy.count > 1 %>
|
||||
<%= render 'shared/search_input' %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<% view_archived_html = capture do %>
|
||||
<% if @has_archived && params[:q].blank? %>
|
||||
<div>
|
||||
<a href="<%= templates_shared_index_path(archived: true) %>" class="link text-sm"><%= t('view_archived') %></a>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% if @pagy.count.nil? || @pagy.count > 0 %>
|
||||
<div class="grid gap-4 md:grid-cols-3">
|
||||
<%= render partial: 'templates/template', collection: @templates %>
|
||||
</div>
|
||||
<% elsif params[:q].present? %>
|
||||
<div class="text-center">
|
||||
<div class="mt-16 text-3xl font-semibold">
|
||||
<%= t('templates_not_found') %>
|
||||
</div>
|
||||
</div>
|
||||
<% if @related_submissions.present? %>
|
||||
<h1 class="text-2xl md:text-3xl sm:text-4xl font-bold mt-8 md:mt-4">
|
||||
<%= t('submissions') %>
|
||||
</h1>
|
||||
<div class="space-y-4 mt-4">
|
||||
<%= render partial: 'templates/submission', collection: @related_submissions, locals: { with_template: true } %>
|
||||
</div>
|
||||
<%= render 'shared/pagination', pagy: @related_submissions_pagy, items_name: 'submissions', next_page_path: @is_archived ? submissions_archived_index_path(q: params[:q]) : submissions_path(q: params[:q]) %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% if @pagy.pages > 1 %>
|
||||
<%= render 'shared/pagination', pagy: @pagy, items_name: 'templates', left_additional_html: view_archived_html %>
|
||||
<% else %>
|
||||
<div class="mt-2">
|
||||
<%= view_archived_html %>
|
||||
</div>
|
||||
<% end %>
|
||||
@@ -1,30 +1,50 @@
|
||||
<confirm-upload>
|
||||
<% if Addressable::URI.parse(params[:url]).host == 'docuseal-website.s3.amazonaws.com' %>
|
||||
<div class="h-screen">
|
||||
<div class="text-center p-8 h-full flex items-center justify-center">
|
||||
<div class="space-y-4">
|
||||
<div data-target="confirm-upload.logo" class="mx-auto">
|
||||
<%= render 'shared/logo', width: 50, height: 50, class: 'mx-auto' %>
|
||||
</div>
|
||||
<div data-target="confirm-upload.processing" class="hidden">
|
||||
<span><%= t('processing') %>...</span>
|
||||
</div>
|
||||
<div data-target="confirm-upload.prompt">
|
||||
<p class="text-lg">
|
||||
<%= t('open_file_from') %>
|
||||
<a href="<%= params[:url] %>" target="_blank" class="link" rel="noopener noreferrer nofollow">
|
||||
<%= params[:filename].presence || params[:url] %>
|
||||
</a>
|
||||
</p>
|
||||
<div class="flex items-center justify-center gap-2 mt-4">
|
||||
<%= link_to t('cancel'), root_path, class: 'white-button w-44' %>
|
||||
<%= form_for '', url: templates_upload_path, method: :post do |f| %>
|
||||
<input type="hidden" name="url" value="<%= params[:url] %>">
|
||||
<input type="hidden" name="filename" value="<%= params[:filename] %>">
|
||||
<%= f.button button_title(title: t('open')), class: 'base-button w-44' %>
|
||||
<% end %>
|
||||
<div>
|
||||
<%= render 'shared/logo', width: 50, height: 50, class: 'mx-auto animate-bounce' %>
|
||||
<span>
|
||||
<%= t('processing') %>...
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<submit-form>
|
||||
<%= form_for '', url: templates_upload_path, method: :post, class: 'hidden' do %>
|
||||
<button type="submit"></button>
|
||||
<input name="url" value="<%= params[:url] %>">
|
||||
<input name="filename" value="<%= params[:filename] %>">
|
||||
<% end %>
|
||||
</submit-form>
|
||||
<% else %>
|
||||
<confirm-upload>
|
||||
<div class="h-screen">
|
||||
<div class="text-center p-8 h-full flex items-center justify-center">
|
||||
<div class="space-y-4">
|
||||
<div data-target="confirm-upload.logo" class="mx-auto">
|
||||
<%= render 'shared/logo', width: 50, height: 50, class: 'mx-auto' %>
|
||||
</div>
|
||||
<div data-target="confirm-upload.processing" class="hidden">
|
||||
<span><%= t('processing') %>...</span>
|
||||
</div>
|
||||
<div data-target="confirm-upload.prompt">
|
||||
<p class="text-lg">
|
||||
<%= t('open_file_from') %>
|
||||
<a href="<%= params[:url] %>" target="_blank" class="link" rel="noopener noreferrer nofollow">
|
||||
<%= params[:filename].presence || params[:url] %>
|
||||
</a>
|
||||
</p>
|
||||
<div class="flex items-center justify-center gap-2 mt-4">
|
||||
<%= link_to t('cancel'), root_path, class: 'white-button w-44' %>
|
||||
<%= form_for '', url: templates_upload_path, method: :post do |f| %>
|
||||
<input type="hidden" name="url" value="<%= params[:url] %>">
|
||||
<input type="hidden" name="filename" value="<%= params[:filename] %>">
|
||||
<%= f.button button_title(title: t('open')), class: 'base-button w-44' %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</confirm-upload>
|
||||
</confirm-upload>
|
||||
<% end %>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<span class="label-text-alt"><%= t('email_address_is_awaiting_confirmation_follow_the_link_in_the_email_to_confirm', email: f.object.unconfirmed_email) %></span>
|
||||
</label>
|
||||
<% end %>
|
||||
<% if user.persisted? && Accounts.can_send_emails?(current_account) %>
|
||||
<% if user.persisted? && (Docuseal.multitenant? || Accounts.can_send_emails?(current_account)) %>
|
||||
<span class="label-text-alt mt-2 mx-1">
|
||||
<%= t('click_here_to_send_a_reset_password_email_html') %>
|
||||
</span>
|
||||
|
||||
@@ -27,6 +27,8 @@ module DocuSeal
|
||||
|
||||
config.active_storage.analyzers = []
|
||||
|
||||
config.active_storage.previewers = []
|
||||
|
||||
config.active_storage.variant_processor = :disabled
|
||||
|
||||
config.active_storage.content_types_to_serve_as_binary += %w[
|
||||
|
||||
@@ -5,6 +5,14 @@ ActiveSupport.on_load(:active_storage_attachment) do
|
||||
|
||||
has_many_attached :preview_images
|
||||
|
||||
def self.service_url_time
|
||||
return unless Docuseal.multitenant?
|
||||
|
||||
now = Time.current
|
||||
|
||||
now.min < 30 ? now.beginning_of_hour : now.beginning_of_hour + 30.minutes
|
||||
end
|
||||
|
||||
def signed_uuid
|
||||
@signed_uuid ||= ApplicationRecord.signed_id_verifier.generate(uuid, expires_in: 6.hours, purpose: :attachment)
|
||||
end
|
||||
|
||||
@@ -86,6 +86,7 @@ en: &en
|
||||
you_have_been_invited_to_account_name_product_name_please_sign_up_using_the_link_below_: 'You have been invited to %{account_name} %{product_name}. Please sign up using the link below:'
|
||||
sent_using_product_name_in_testing_mode_html: 'Sent using <a href="%{product_url}">%{product_name}</a> in testing mode'
|
||||
sent_using_product_name_free_document_signing_html: 'Sent using <a href="%{product_url}">%{product_name}</a> free document signing.'
|
||||
sent_using_product_name_open_source_software_html: 'Sent using <a href="%{product_url}">%{product_name}</a> open-source software.'
|
||||
sent_with_docuseal_pro_html: 'Sent with <a href="%{product_url}">DocuSeal Pro</a>'
|
||||
show_send_with_docuseal_pro_attribution_in_emails_html: Show "Sent with <span class="link">DocuSeal Pro</span>" attribution in emails
|
||||
sign_documents_with_trusted_certificate_provided_by_docu_seal_your_documents_and_data_are_never_shared_with_docu_seal_p_d_f_checksum_is_provided_to_generate_a_trusted_signature: Sign documents with trusted certificate provided by DocuSeal. Your documents and data are never shared with DocuSeal. PDF checksum is provided to generate a trusted signature.
|
||||
@@ -1142,6 +1143,7 @@ es: &es
|
||||
you_have_been_invited_to_account_name_product_name_please_sign_up_using_the_link_below_: 'Has sido invitado a %{account_name} %{product_name}. Por favor, regístrate usando el enlace a continuación:'
|
||||
sent_using_product_name_in_testing_mode_html: 'Enviado usando <a href="%{product_url}">%{product_name}</a> en Modo de Prueba'
|
||||
sent_using_product_name_free_document_signing_html: 'Enviado usando la firma de documentos gratuita de <a href="%{product_url}">%{product_name}</a>.'
|
||||
sent_using_product_name_open_source_software_html: 'Enviado usando el software de código abierto de <a href="%{product_url}">%{product_name}</a>.'
|
||||
sent_with_docuseal_pro_html: 'Enviado con <a href="%{product_url}">DocuSeal Pro</a>'
|
||||
show_send_with_docuseal_pro_attribution_in_emails_html: Mostrar el mensaje "Enviado con <span class="link">DocuSeal Pro</span>" en los correos electrónicos
|
||||
sign_documents_with_trusted_certificate_provided_by_docu_seal_your_documents_and_data_are_never_shared_with_docu_seal_p_d_f_checksum_is_provided_to_generate_a_trusted_signature: Firme documentos con un certificado de confianza proporcionado por DocuSeal. Sus documentos y datos nunca se comparten con DocuSeal. Se proporciona un checksum de PDF para generar una firma de confianza.
|
||||
@@ -2195,6 +2197,7 @@ it: &it
|
||||
you_have_been_invited_to_account_name_product_name_please_sign_up_using_the_link_below_: 'Sei stato invitato a %{account_name} %{product_name}. Registrati utilizzando il link qui sotto:'
|
||||
sent_using_product_name_in_testing_mode_html: 'Inviato utilizzando <a href="%{product_url}">%{product_name}</a> in Modalità di Test'
|
||||
sent_using_product_name_free_document_signing_html: 'Inviato utilizzando la firma di documenti gratuita di <a href="%{product_url}">%{product_name}</a>.'
|
||||
sent_using_product_name_open_source_software_html: 'Inviato utilizzando il software open source di <a href="%{product_url}">%{product_name}</a>.'
|
||||
sent_with_docuseal_pro_html: 'Inviato con <a href="%{product_url}">DocuSeal Pro</a>'
|
||||
show_send_with_docuseal_pro_attribution_in_emails_html: Mostra la dicitura "Inviato con <span class="link">DocuSeal Pro</span>" nelle email
|
||||
sign_documents_with_trusted_certificate_provided_by_docu_seal_your_documents_and_data_are_never_shared_with_docu_seal_p_d_f_checksum_is_provided_to_generate_a_trusted_signature: "Firma documenti con un certificato di fiducia fornito da DocuSeal. I tuoi documenti e i tuoi dati non vengono mai condivisi con DocuSeal. Il checksum PDF è fornito per generare una firma di fiducia."
|
||||
@@ -3248,6 +3251,7 @@ fr: &fr
|
||||
you_have_been_invited_to_account_name_product_name_please_sign_up_using_the_link_below_: 'Vous avez été invité à %{account_name} %{product_name}. Veuillez vous inscrire en utilisant le lien ci-dessous :'
|
||||
sent_using_product_name_in_testing_mode_html: Envoyé avec <a href="%{product_url}">%{product_name}</a> en mode test
|
||||
sent_using_product_name_free_document_signing_html: Envoyé avec <a href="%{product_url}">%{product_name}</a> signature de documents gratuite.
|
||||
sent_using_product_name_open_source_software_html: Envoyé avec le logiciel open source <a href="%{product_url}">%{product_name}</a>.
|
||||
sent_with_docuseal_pro_html: Envoyé avec <a href="%{product_url}">DocuSeal Pro</a>
|
||||
show_send_with_docuseal_pro_attribution_in_emails_html: Afficher l’attribution "Envoyé avec <span class="link">DocuSeal Pro</span>" dans les e‑mails
|
||||
sign_documents_with_trusted_certificate_provided_by_docu_seal_your_documents_and_data_are_never_shared_with_docu_seal_p_d_f_checksum_is_provided_to_generate_a_trusted_signature: Signez des documents avec un certificat de confiance fourni par DocuSeal. Vos documents et données ne sont jamais partagés avec DocuSeal. Une empreinte (checksum) PDF est fournie pour générer une signature de confiance.
|
||||
@@ -4298,6 +4302,7 @@ pt: &pt
|
||||
you_have_been_invited_to_account_name_product_name_please_sign_up_using_the_link_below_: 'Você foi convidado para %{account_name} %{product_name}. Inscreva-se usando o link abaixo:'
|
||||
sent_using_product_name_in_testing_mode_html: 'Enviado usando <a href="%{product_url}">%{product_name}</a> no Modo de Teste'
|
||||
sent_using_product_name_free_document_signing_html: 'Enviado usando a assinatura gratuita de documentos de <a href="%{product_url}">%{product_name}</a>.'
|
||||
sent_using_product_name_open_source_software_html: 'Enviado usando o software de código aberto <a href="%{product_url}">%{product_name}</a>.'
|
||||
sent_with_docuseal_pro_html: 'Enviado com <a href="%{product_url}">DocuSeal Pro</a>'
|
||||
show_send_with_docuseal_pro_attribution_in_emails_html: Mostrar "Enviado com <span class="link">DocuSeal Pro</span>" nos e-mails
|
||||
sign_documents_with_trusted_certificate_provided_by_docu_seal_your_documents_and_data_are_never_shared_with_docu_seal_p_d_f_checksum_is_provided_to_generate_a_trusted_signature: Assine documentos com certificado confiável fornecido pela DocuSeal. Seus documentos e dados nunca são compartilhados com a DocuSeal. O checksum do PDF é fornecido para gerar uma assinatura confiável.
|
||||
@@ -5351,6 +5356,7 @@ de: &de
|
||||
you_have_been_invited_to_account_name_product_name_please_sign_up_using_the_link_below_: 'Sie wurden zu %{account_name} %{product_name} eingeladen. Bitte registrieren Sie sich über den folgenden Link:'
|
||||
sent_using_product_name_in_testing_mode_html: 'Gesendet über <a href="%{product_url}">%{product_name}</a> im Testmodus'
|
||||
sent_using_product_name_free_document_signing_html: 'Gesendet mit der kostenlosen Dokumentensignierung von <a href="%{product_url}">%{product_name}</a>.'
|
||||
sent_using_product_name_open_source_software_html: 'Gesendet mit der Open-Source-Software von <a href="%{product_url}">%{product_name}</a>.'
|
||||
sent_with_docuseal_pro_html: Gesendet mit <a href="%{product_url}">DocuSeal Pro</a>
|
||||
show_send_with_docuseal_pro_attribution_in_emails_html: '"Gesendet mit <span class="link">DocuSeal Pro</span>" in E-Mails anzeigen'
|
||||
sign_documents_with_trusted_certificate_provided_by_docu_seal_your_documents_and_data_are_never_shared_with_docu_seal_p_d_f_checksum_is_provided_to_generate_a_trusted_signature: Unterzeichnen Sie Dokumente mit einem vertrauenswürdigen Zertifikat von DocuSeal. Ihre Dokumente und Daten werden niemals mit DocuSeal geteilt. Eine PDF-Prüfsumme wird bereitgestellt, um eine vertrauenswürdige Signatur zu generieren.
|
||||
@@ -6808,6 +6814,7 @@ nl: &nl
|
||||
you_have_been_invited_to_account_name_product_name_please_sign_up_using_the_link_below_: 'U bent uitgenodigd voor %{account_name} %{product_name}. Meld u aan via de onderstaande link:'
|
||||
sent_using_product_name_in_testing_mode_html: Verzonden met <a href="%{product_url}">%{product_name}</a> in testmodus
|
||||
sent_using_product_name_free_document_signing_html: Verzonden met <a href="%{product_url}">%{product_name}</a> gratis documentondertekening.
|
||||
sent_using_product_name_open_source_software_html: Verzonden met de open-source software <a href="%{product_url}">%{product_name}</a>.
|
||||
sent_with_docuseal_pro_html: Verzonden met <a href="%{product_url}">DocuSeal Pro</a>
|
||||
show_send_with_docuseal_pro_attribution_in_emails_html: Toon de vermelding 'Verzonden met <span class="link">DocuSeal Pro</span>' in e-mails
|
||||
? sign_documents_with_trusted_certificate_provided_by_docu_seal_your_documents_and_data_are_never_shared_with_docu_seal_p_d_f_checksum_is_provided_to_generate_a_trusted_signature
|
||||
|
||||
@@ -91,6 +91,7 @@ Rails.application.routes.draw do
|
||||
resource :templates_upload, only: %i[show], path: 'new'
|
||||
end
|
||||
resources :templates_archived, only: %i[index], path: 'templates/archived'
|
||||
resources :templates_shared, only: %i[index], path: 'templates/shared'
|
||||
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'
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class RemoveSubmittersCompletedAtIndex < ActiveRecord::Migration[8.1]
|
||||
def up
|
||||
remove_index :submitters, %i[completed_at account_id], if_exists: true
|
||||
end
|
||||
|
||||
def down
|
||||
add_index :submitters, %i[completed_at account_id], if_not_exists: true
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,11 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class AddSubmittersCompletedAtPartialIndex < ActiveRecord::Migration[8.1]
|
||||
def up
|
||||
add_index :submitters, %i[account_id completed_at], where: 'completed_at IS NOT NULL', if_not_exists: true
|
||||
end
|
||||
|
||||
def down
|
||||
remove_index :submitters, %i[account_id completed_at], if_exists: true
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,14 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class CreateEmailMessageAssets < ActiveRecord::Migration[8.1]
|
||||
def change
|
||||
create_table :email_message_assets do |t|
|
||||
t.references :account, null: false, foreign_key: true, index: false
|
||||
t.text :data, null: false
|
||||
t.string :sha1, null: false
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
add_index :email_message_assets, %i[account_id sha1], unique: true
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class AddSubmissionCompletedAt < ActiveRecord::Migration[8.1]
|
||||
def change
|
||||
add_column :submissions, :completed_at, :datetime
|
||||
end
|
||||
end
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user