mirror of
https://github.com/docusealco/docuseal
synced 2026-11-04 22:42:32 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
95f734e4b8 | ||
|
|
ff74574e84 | ||
|
|
843f6c405f | ||
|
|
f8b24ebfce | ||
|
|
d1d26b5424 | ||
|
|
8aa6f45da8 | ||
|
|
48d5bee95b | ||
|
|
98c79d2dec | ||
|
|
0f51238f62 | ||
|
|
b7323b929f | ||
|
|
05eb1537f7 | ||
|
|
bc9f27cfed | ||
|
|
8f457be719 | ||
|
|
6d590e13c9 | ||
|
|
c987ec172a | ||
|
|
49f1637c47 | ||
|
|
13379bd699 | ||
|
|
5ee762c08c | ||
|
|
edb6d561f1 | ||
|
|
1b90f77e82 | ||
|
|
c37785ecbc | ||
|
|
c67188edf8 | ||
|
|
8630c68631 | ||
|
|
ddcb5c5d40 | ||
|
|
07f0fbb50f | ||
|
|
3781b0aa72 | ||
|
|
42a5cea521 | ||
|
|
fb6000ca2c | ||
|
|
59f98db754 | ||
|
|
6add842a27 | ||
|
|
43ca4a6365 | ||
|
|
68196d6af0 | ||
|
|
a582d39bb9 | ||
|
|
7a3e89c4d8 | ||
|
|
87f036ad88 | ||
|
|
5738b8dd58 | ||
|
|
8d73371e0f |
No files matched your search
+2
-2
@@ -30,7 +30,7 @@ Metrics/ParameterLists:
|
|||||||
Max: 10
|
Max: 10
|
||||||
|
|
||||||
Metrics/MethodLength:
|
Metrics/MethodLength:
|
||||||
Max: 25
|
Max: 30
|
||||||
Exclude:
|
Exclude:
|
||||||
- 'db/migrate/**'
|
- 'db/migrate/**'
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@ Layout/LineLength:
|
|||||||
AllowedPatterns: ['\A\s*#']
|
AllowedPatterns: ['\A\s*#']
|
||||||
|
|
||||||
Metrics/AbcSize:
|
Metrics/AbcSize:
|
||||||
Max: 35
|
Max: 40
|
||||||
|
|
||||||
Metrics/ModuleLength:
|
Metrics/ModuleLength:
|
||||||
Max: 500
|
Max: 500
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ sudo HOST=your-domain-name.com docker compose up
|
|||||||
|
|
||||||
At DocuSeal we have expertise and technologies to make documents creation, filling, signing and processing seamlessly integrated with your product. We specialize in working with various industries, including **Banking, Healthcare, Transport, Real Estate, eCommerce, KYC, CRM, and other software products** that require bulk document signing. By leveraging DocuSeal, we can assist in reducing the overall cost of developing and processing electronic documents while ensuring security and compliance with local electronic document laws.
|
At DocuSeal we have expertise and technologies to make documents creation, filling, signing and processing seamlessly integrated with your product. We specialize in working with various industries, including **Banking, Healthcare, Transport, Real Estate, eCommerce, KYC, CRM, and other software products** that require bulk document signing. By leveraging DocuSeal, we can assist in reducing the overall cost of developing and processing electronic documents while ensuring security and compliance with local electronic document laws.
|
||||||
|
|
||||||
[Book a Meeting](https://calendly.com/kriti-docuseal/30min)
|
[Book a Meeting](https://www.docuseal.co/contact)
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ class AccountConfigsController < ApplicationController
|
|||||||
ALLOWED_KEYS = [
|
ALLOWED_KEYS = [
|
||||||
AccountConfig::ALLOW_TYPED_SIGNATURE,
|
AccountConfig::ALLOW_TYPED_SIGNATURE,
|
||||||
AccountConfig::FORCE_MFA,
|
AccountConfig::FORCE_MFA,
|
||||||
|
AccountConfig::ALLOW_TO_RESUBMIT,
|
||||||
AccountConfig::ESIGNING_PREFERENCE_KEY
|
AccountConfig::ESIGNING_PREFERENCE_KEY
|
||||||
].freeze
|
].freeze
|
||||||
|
|
||||||
|
|||||||
@@ -33,12 +33,18 @@ module Api
|
|||||||
def show
|
def show
|
||||||
submitters = @submission.submitters.preload(documents_attachments: :blob, attachments_attachments: :blob)
|
submitters = @submission.submitters.preload(documents_attachments: :blob, attachments_attachments: :blob)
|
||||||
|
|
||||||
serialized_submitters = submitters.map do |submitter|
|
submitters.each do |submitter|
|
||||||
Submissions::EnsureResultGenerated.call(submitter) if submitter.completed_at?
|
if submitter.completed_at? && submitter.documents_attachments.blank?
|
||||||
|
submitter.documents_attachments = Submissions::EnsureResultGenerated.call(submitter)
|
||||||
Submitters::SerializeForApi.call(submitter)
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if @submission.audit_trail_attachment.blank? && submitters.all?(&:completed_at?)
|
||||||
|
@submission.audit_trail_attachment = Submissions::GenerateAuditTrail.call(@submission)
|
||||||
|
end
|
||||||
|
|
||||||
|
serialized_submitters = submitters.map { |submitter| Submitters::SerializeForApi.call(submitter) }
|
||||||
|
|
||||||
json = @submission.as_json(
|
json = @submission.as_json(
|
||||||
serialize_params.deep_merge(
|
serialize_params.deep_merge(
|
||||||
include: { submission_events: { only: %i[id submitter_id event_type event_timestamp] } }
|
include: { submission_events: { only: %i[id submitter_id event_type event_timestamp] } }
|
||||||
@@ -80,6 +86,12 @@ module Api
|
|||||||
|
|
||||||
Submissions.send_signature_requests(submissions)
|
Submissions.send_signature_requests(submissions)
|
||||||
|
|
||||||
|
submissions.each do |submission|
|
||||||
|
if submission.submitters.all?(&:completed_at?) && submission.submitters.last
|
||||||
|
ProcessSubmitterCompletionJob.perform_later(submission.submitters.last)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
render json: submissions.flat_map(&:submitters)
|
render json: submissions.flat_map(&:submitters)
|
||||||
rescue Submitters::NormalizeValues::UnknownFieldName, Submitters::NormalizeValues::UnknownSubmitterName => e
|
rescue Submitters::NormalizeValues::UnknownFieldName, Submitters::NormalizeValues::UnknownSubmitterName => e
|
||||||
render json: { error: e.message }, status: :unprocessable_entity
|
render json: { error: e.message }, status: :unprocessable_entity
|
||||||
@@ -118,8 +130,13 @@ module Api
|
|||||||
params:
|
params:
|
||||||
)
|
)
|
||||||
|
|
||||||
Submissions::NormalizeParamUtils.save_default_value_attachments!(attachments,
|
submitters = submissions.flat_map(&:submitters)
|
||||||
submissions.flat_map(&:submitters))
|
|
||||||
|
Submissions::NormalizeParamUtils.save_default_value_attachments!(attachments, submitters)
|
||||||
|
|
||||||
|
submitters.each do |submitter|
|
||||||
|
SubmissionEvents.create_with_tracking_data(submitter, 'api_complete_form', request) if submitter.completed_at?
|
||||||
|
end
|
||||||
|
|
||||||
submissions
|
submissions
|
||||||
end
|
end
|
||||||
@@ -132,8 +149,8 @@ module Api
|
|||||||
include: {
|
include: {
|
||||||
submitters: { only: %i[id slug uuid name email phone
|
submitters: { only: %i[id slug uuid name email phone
|
||||||
completed_at opened_at sent_at
|
completed_at opened_at sent_at
|
||||||
created_at updated_at],
|
created_at updated_at external_id metadata],
|
||||||
methods: %i[status] },
|
methods: %i[status application_key] },
|
||||||
template: { only: %i[id name created_at updated_at] },
|
template: { only: %i[id name created_at updated_at] },
|
||||||
created_by_user: { only: %i[id email first_name last_name] }
|
created_by_user: { only: %i[id email first_name last_name] }
|
||||||
}
|
}
|
||||||
@@ -146,8 +163,8 @@ module Api
|
|||||||
{
|
{
|
||||||
message: %i[subject body],
|
message: %i[subject body],
|
||||||
submitters: [[:send_email, :send_sms, :completed_redirect_url, :uuid, :name, :email, :role,
|
submitters: [[:send_email, :send_sms, :completed_redirect_url, :uuid, :name, :email, :role,
|
||||||
:completed, :phone, :application_key,
|
:completed, :phone, :application_key, :external_id,
|
||||||
{ values: {}, readonly_fields: [], message: %i[subject body],
|
{ metadata: {}, values: {}, readonly_fields: [], message: %i[subject body],
|
||||||
fields: [%i[name default_value title description
|
fields: [%i[name default_value title description
|
||||||
readonly validation_pattern invalid_message]] }]]
|
readonly validation_pattern invalid_message]] }]]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ module Api
|
|||||||
def index
|
def index
|
||||||
submitters = Submitters.search(@submitters, params[:q])
|
submitters = Submitters.search(@submitters, params[:q])
|
||||||
|
|
||||||
submitters = submitters.where(application_key: params[:application_key]) if params[:application_key].present?
|
submitters = submitters.where(external_id: params[:application_key]) if params[:application_key].present?
|
||||||
|
submitters = submitters.where(external_id: params[:external_id]) if params[:external_id].present?
|
||||||
submitters = submitters.where(submission_id: params[:submission_id]) if params[:submission_id].present?
|
submitters = submitters.where(submission_id: params[:submission_id]) if params[:submission_id].present?
|
||||||
|
|
||||||
submitters = paginate(
|
submitters = paginate(
|
||||||
@@ -54,6 +55,8 @@ module Api
|
|||||||
@submitter.save!
|
@submitter.save!
|
||||||
|
|
||||||
@submitter.submission.save!
|
@submitter.submission.save!
|
||||||
|
|
||||||
|
SubmissionEvents.create_with_tracking_data(@submitter, 'api_complete_form', request) if @submitter.completed_at?
|
||||||
end
|
end
|
||||||
|
|
||||||
if @submitter.completed_at?
|
if @submitter.completed_at?
|
||||||
@@ -69,8 +72,9 @@ module Api
|
|||||||
submitter_params = params.key?(:submitter) ? params.require(:submitter) : params
|
submitter_params = params.key?(:submitter) ? params.require(:submitter) : params
|
||||||
|
|
||||||
submitter_params.permit(
|
submitter_params.permit(
|
||||||
:send_email, :send_sms, :uuid, :name, :email, :role, :completed, :phone, :application_key,
|
:send_email, :send_sms, :uuid, :name, :email, :role,
|
||||||
{ values: {}, readonly_fields: [], message: %i[subject body],
|
:completed, :phone, :application_key, :external_id,
|
||||||
|
{ metadata: {}, values: {}, readonly_fields: [], message: %i[subject body],
|
||||||
fields: [%i[name default_value readonly validation_pattern invalid_message]] }
|
fields: [%i[name default_value readonly validation_pattern invalid_message]] }
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
@@ -82,7 +86,9 @@ module Api
|
|||||||
submitter.phone = attrs[:phone].to_s.gsub(/[^0-9+]/, '') if attrs.key?(:phone)
|
submitter.phone = attrs[:phone].to_s.gsub(/[^0-9+]/, '') if attrs.key?(:phone)
|
||||||
submitter.values = submitter.values.merge(attrs[:values].to_unsafe_h) if attrs[:values].present?
|
submitter.values = submitter.values.merge(attrs[:values].to_unsafe_h) if attrs[:values].present?
|
||||||
submitter.completed_at = attrs[:completed] ? Time.current : submitter.completed_at
|
submitter.completed_at = attrs[:completed] ? Time.current : submitter.completed_at
|
||||||
submitter.application_key = attrs[:application_key] if attrs.key?(:application_key)
|
submitter.external_id = attrs[:application_key] if attrs.key?(:application_key)
|
||||||
|
submitter.external_id = attrs[:external_id] if attrs.key?(:external_id)
|
||||||
|
submitter.metadata = attrs[:metadata] if attrs.key?(:metadata)
|
||||||
|
|
||||||
assign_submission_fields(submitter.submission)
|
assign_submission_fields(submitter.submission)
|
||||||
assign_preferences(submitter, attrs)
|
assign_preferences(submitter, attrs)
|
||||||
|
|||||||
@@ -7,11 +7,13 @@ module Api
|
|||||||
def create
|
def create
|
||||||
authorize!(:manage, @template)
|
authorize!(:manage, @template)
|
||||||
|
|
||||||
cloned_template = Templates::Clone.call(@template,
|
cloned_template = Templates::Clone.call(
|
||||||
author: current_user,
|
@template,
|
||||||
name: params[:name],
|
author: current_user,
|
||||||
application_key: params[:application_key],
|
name: params[:name],
|
||||||
folder_name: params[:folder_name])
|
external_id: params[:external_id].presence || params[:application_key],
|
||||||
|
folder_name: params[:folder_name]
|
||||||
|
)
|
||||||
|
|
||||||
cloned_template.source = :api
|
cloned_template.source = :api
|
||||||
cloned_template.save!
|
cloned_template.save!
|
||||||
|
|||||||
@@ -5,11 +5,7 @@ module Api
|
|||||||
load_and_authorize_resource :template
|
load_and_authorize_resource :template
|
||||||
|
|
||||||
def index
|
def index
|
||||||
templates = Templates.search(@templates, params[:q])
|
templates = filter_templates(@templates, params)
|
||||||
|
|
||||||
templates = params[:archived] ? templates.archived : templates.active
|
|
||||||
templates = templates.where(application_key: params[:application_key]) if params[:application_key].present?
|
|
||||||
templates = templates.joins(:folder).where(folder: { name: params[:folder] }) if params[:folder].present?
|
|
||||||
|
|
||||||
templates = paginate(templates.preload(:author, documents_attachments: :blob))
|
templates = paginate(templates.preload(:author, documents_attachments: :blob))
|
||||||
|
|
||||||
@@ -28,10 +24,16 @@ module Api
|
|||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
if (folder_name = params.dig(:template, :folder_name))
|
if (folder_name = params[:folder_name] || params.dig(:template, :folder_name))
|
||||||
@template.folder = TemplateFolders.find_or_create_by_name(current_user, folder_name)
|
@template.folder = TemplateFolders.find_or_create_by_name(current_user, folder_name)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
archived = params.key?(:archived) ? params[:archived] : params.dig(:template, :archived)
|
||||||
|
|
||||||
|
if archived.in?([true, false])
|
||||||
|
@template.archived_at = archived == true ? Time.current : nil
|
||||||
|
end
|
||||||
|
|
||||||
@template.update!(template_params)
|
@template.update!(template_params)
|
||||||
|
|
||||||
render json: @template.as_json(only: %i[id updated_at])
|
render json: @template.as_json(only: %i[id updated_at])
|
||||||
@@ -49,23 +51,40 @@ module Api
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def filter_templates(templates, params)
|
||||||
|
templates = Templates.search(templates, params[:q])
|
||||||
|
templates = params[:archived] ? templates.archived : templates.active
|
||||||
|
templates = templates.where(external_id: params[:application_key]) if params[:application_key].present?
|
||||||
|
templates = templates.where(external_id: params[:external_id]) if params[:external_id].present?
|
||||||
|
templates = templates.joins(:folder).where(folder: { name: params[:folder] }) if params[:folder].present?
|
||||||
|
|
||||||
|
templates
|
||||||
|
end
|
||||||
|
|
||||||
def serialize_params
|
def serialize_params
|
||||||
{
|
{
|
||||||
|
methods: %i[application_key],
|
||||||
include: { author: { only: %i[id email first_name last_name] },
|
include: { author: { only: %i[id email first_name last_name] },
|
||||||
documents: { only: %i[id uuid], methods: %i[url filename] } }
|
documents: { only: %i[id uuid], methods: %i[url preview_image_url filename] } }
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def template_params
|
def template_params
|
||||||
params.require(:template).permit(
|
permit_params = [
|
||||||
:name,
|
:name,
|
||||||
schema: [%i[attachment_uuid name]],
|
{ schema: [%i[attachment_uuid name]],
|
||||||
submitters: [%i[name uuid]],
|
submitters: [%i[name uuid]],
|
||||||
fields: [[:uuid, :submitter_uuid, :name, :type, :required, :readonly, :default_value,
|
fields: [[:uuid, :submitter_uuid, :name, :type, :required, :readonly, :default_value,
|
||||||
{ preferences: {},
|
{ preferences: {},
|
||||||
options: [%i[value uuid]],
|
options: [%i[value uuid]],
|
||||||
areas: [%i[x y w h cell_w attachment_uuid option_uuid page]] }]]
|
areas: [%i[x y w h cell_w attachment_uuid option_uuid page]] }]] }
|
||||||
)
|
]
|
||||||
|
|
||||||
|
if params.key?(:template)
|
||||||
|
params.require(:template).permit(*permit_params)
|
||||||
|
else
|
||||||
|
params.permit(*permit_params)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -52,7 +52,13 @@ class DashboardController < ApplicationController
|
|||||||
|
|
||||||
def filter_templates(templates)
|
def filter_templates(templates)
|
||||||
rel = templates.active.preload(:author).order(id: :desc)
|
rel = templates.active.preload(:author).order(id: :desc)
|
||||||
rel = rel.where(folder_id: current_account.default_template_folder.id) if params[:q].blank?
|
|
||||||
|
if params[:q].blank?
|
||||||
|
shared_template_ids =
|
||||||
|
TemplateSharing.where(account_id: [current_account.id, TemplateSharing::ALL_ID]).select(:template_id)
|
||||||
|
|
||||||
|
rel = rel.where(folder_id: current_account.default_template_folder.id).or(rel.where(id: shared_template_ids))
|
||||||
|
end
|
||||||
|
|
||||||
Templates.search(rel, params[:q])
|
Templates.search(rel, params[:q])
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ class StartFormController < ApplicationController
|
|||||||
before_action :load_template
|
before_action :load_template
|
||||||
|
|
||||||
def show
|
def show
|
||||||
@submitter = @template.submissions.new.submitters.new(uuid: @template.submitters.first['uuid'])
|
@submitter = @template.submissions.new(account_id: @template.account_id)
|
||||||
|
.submitters.new(uuid: @template.submitters.first['uuid'])
|
||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
@@ -57,7 +58,8 @@ class StartFormController < ApplicationController
|
|||||||
ip: request.remote_ip,
|
ip: request.remote_ip,
|
||||||
ua: request.user_agent,
|
ua: request.user_agent,
|
||||||
values: resubmit_submitter&.preferences&.fetch('default_values', nil) || {},
|
values: resubmit_submitter&.preferences&.fetch('default_values', nil) || {},
|
||||||
preferences: resubmit_submitter&.preferences.presence || { 'send_email' => true }
|
preferences: resubmit_submitter&.preferences.presence || { 'send_email' => true },
|
||||||
|
metadata: resubmit_submitter&.metadata.presence || {}
|
||||||
)
|
)
|
||||||
|
|
||||||
if submitter.values.present?
|
if submitter.values.present?
|
||||||
@@ -67,6 +69,7 @@ class StartFormController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
submitter.submission ||= Submission.new(template:,
|
submitter.submission ||= Submission.new(template:,
|
||||||
|
account_id: template.account_id,
|
||||||
template_submitters: template.submitters,
|
template_submitters: template.submitters,
|
||||||
source: :link)
|
source: :link)
|
||||||
|
|
||||||
|
|||||||
@@ -75,6 +75,6 @@ class SubmissionsController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def load_template
|
def load_template
|
||||||
@template = current_account.templates.find(params[:template_id])
|
@template = Template.accessible_by(current_ability).find(params[:template_id])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class TemplateSharingsTestingController < ApplicationController
|
||||||
|
load_and_authorize_resource :template, parent: true
|
||||||
|
|
||||||
|
before_action do
|
||||||
|
authorize!(:manage, TemplateSharing.new(template: @template))
|
||||||
|
end
|
||||||
|
|
||||||
|
def create
|
||||||
|
testing_account = Accounts.find_or_create_testing_user(true_user.account).account
|
||||||
|
|
||||||
|
if params[:value] == '1'
|
||||||
|
TemplateSharing.create!(ability: :manage, account: testing_account, template: @template)
|
||||||
|
else
|
||||||
|
TemplateSharing.find_by(template: @template, account: testing_account)&.destroy!
|
||||||
|
end
|
||||||
|
|
||||||
|
head :ok
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class TemplatesCodeModalController < ApplicationController
|
||||||
|
load_and_authorize_resource :template
|
||||||
|
|
||||||
|
def show; end
|
||||||
|
end
|
||||||
@@ -6,7 +6,7 @@ class TemplatesController < ApplicationController
|
|||||||
before_action :load_base_template, only: %i[new create]
|
before_action :load_base_template, only: %i[new create]
|
||||||
|
|
||||||
def show
|
def show
|
||||||
submissions = @template.submissions
|
submissions = @template.submissions.accessible_by(current_ability)
|
||||||
submissions = submissions.active if @template.archived_at.blank?
|
submissions = submissions.active if @template.archived_at.blank?
|
||||||
submissions = Submissions.search(submissions, params[:q])
|
submissions = Submissions.search(submissions, params[:q])
|
||||||
|
|
||||||
@@ -47,11 +47,12 @@ class TemplatesController < ApplicationController
|
|||||||
name: params.dig(:template, :name),
|
name: params.dig(:template, :name),
|
||||||
folder_name: params[:folder_name])
|
folder_name: params[:folder_name])
|
||||||
else
|
else
|
||||||
@template.account = current_account
|
|
||||||
@template.author = current_user
|
@template.author = current_user
|
||||||
@template.folder = TemplateFolders.find_or_create_by_name(current_user, params[:folder_name])
|
@template.folder = TemplateFolders.find_or_create_by_name(current_user, params[:folder_name])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@template.account = current_account
|
||||||
|
|
||||||
if @template.save
|
if @template.save
|
||||||
Templates::CloneAttachments.call(template: @template, original_template: @base_template) if @base_template
|
Templates::CloneAttachments.call(template: @template, original_template: @base_template) if @base_template
|
||||||
|
|
||||||
@@ -87,6 +88,6 @@ class TemplatesController < ApplicationController
|
|||||||
def load_base_template
|
def load_base_template
|
||||||
return if params[:base_template_id].blank?
|
return if params[:base_template_id].blank?
|
||||||
|
|
||||||
@base_template = current_account.templates.find_by(id: params[:base_template_id])
|
@base_template = Template.accessible_by(current_ability).find_by(id: params[:base_template_id])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class TemplatesPreviewController < ApplicationController
|
||||||
|
load_and_authorize_resource :template
|
||||||
|
|
||||||
|
def show
|
||||||
|
ActiveRecord::Associations::Preloader.new(
|
||||||
|
records: [@template],
|
||||||
|
associations: [schema_documents: { preview_images_attachments: :blob }]
|
||||||
|
).call
|
||||||
|
|
||||||
|
@template_data =
|
||||||
|
@template.as_json.merge(
|
||||||
|
documents: @template.schema_documents.as_json(
|
||||||
|
methods: [:metadata],
|
||||||
|
include: { preview_images: { methods: %i[url metadata filename] } }
|
||||||
|
)
|
||||||
|
).to_json
|
||||||
|
|
||||||
|
render :show, layout: 'plain'
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -7,7 +7,7 @@ class TestingAccountsController < ApplicationController
|
|||||||
authorize!(:manage, current_account)
|
authorize!(:manage, current_account)
|
||||||
authorize!(:manage, current_user)
|
authorize!(:manage, current_user)
|
||||||
|
|
||||||
impersonate_user(Accounts.find_or_create_testing_user(current_account))
|
impersonate_user(Accounts.find_or_create_testing_user(true_user.account))
|
||||||
|
|
||||||
redirect_back(fallback_location: root_path)
|
redirect_back(fallback_location: root_path)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ window.customElements.define('template-builder', class extends HTMLElement {
|
|||||||
backgroundColor: '#faf7f5',
|
backgroundColor: '#faf7f5',
|
||||||
withPhone: this.dataset.withPhone === 'true',
|
withPhone: this.dataset.withPhone === 'true',
|
||||||
withLogo: this.dataset.withLogo !== 'false',
|
withLogo: this.dataset.withLogo !== 'false',
|
||||||
|
editable: this.dataset.editable !== 'false',
|
||||||
withPayment: this.dataset.withPayment === 'true',
|
withPayment: this.dataset.withPayment === 'true',
|
||||||
currencies: (this.dataset.currencies || '').split(',').filter(Boolean),
|
currencies: (this.dataset.currencies || '').split(',').filter(Boolean),
|
||||||
acceptFileTypes: this.dataset.acceptFileTypes,
|
acceptFileTypes: this.dataset.acceptFileTypes,
|
||||||
|
|||||||
@@ -307,7 +307,7 @@ export default {
|
|||||||
formattedDate () {
|
formattedDate () {
|
||||||
if (this.field.type === 'date' && this.modelValue) {
|
if (this.field.type === 'date' && this.modelValue) {
|
||||||
return this.formatDate(
|
return this.formatDate(
|
||||||
new Date(this.modelValue),
|
this.modelValue === '{{date}}' ? new Date() : new Date(this.modelValue),
|
||||||
this.field.preferences?.format || (this.locale.endsWith('-US') ? 'MM/DD/YYYY' : 'DD/MM/YYYY')
|
this.field.preferences?.format || (this.locale.endsWith('-US') ? 'MM/DD/YYYY' : 'DD/MM/YYYY')
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -621,7 +621,7 @@ export default {
|
|||||||
|
|
||||||
this.fields.forEach((field) => {
|
this.fields.forEach((field) => {
|
||||||
if (field.default_value && !field.readonly) {
|
if (field.default_value && !field.readonly) {
|
||||||
this.values[field.uuid] = field.default_value
|
this.values[field.uuid] ||= field.default_value
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -644,7 +644,7 @@ const nl = {
|
|||||||
page: 'Pagina',
|
page: 'Pagina',
|
||||||
select_your_option: 'Selecteer uw optie',
|
select_your_option: 'Selecteer uw optie',
|
||||||
complete_hightlighted_checkboxes_and_click: 'Voltooi de gemarkeerde selectievakjes en klik',
|
complete_hightlighted_checkboxes_and_click: 'Voltooi de gemarkeerde selectievakjes en klik',
|
||||||
submit: 'Verzenden',
|
submit: 'Voltooien',
|
||||||
next: 'Volgende',
|
next: 'Volgende',
|
||||||
click_to_upload: 'Klik om te uploaden',
|
click_to_upload: 'Klik om te uploaden',
|
||||||
or_drag_and_drop_files: 'of sleep en plaats bestanden',
|
or_drag_and_drop_files: 'of sleep en plaats bestanden',
|
||||||
@@ -664,11 +664,11 @@ const nl = {
|
|||||||
use_international_format: 'Gebruik internationaal formaat: +1xxx',
|
use_international_format: 'Gebruik internationaal formaat: +1xxx',
|
||||||
six_digits_code: '6-cijferige code',
|
six_digits_code: '6-cijferige code',
|
||||||
change_phone_number: 'Wijzig telefoonnummer',
|
change_phone_number: 'Wijzig telefoonnummer',
|
||||||
sending: 'Verzenden...',
|
sending: 'Voltooien...',
|
||||||
resend_code: 'Code opnieuw verzenden',
|
resend_code: 'Code opnieuw verzenden',
|
||||||
verification_code_has_been_resent: 'Verificatiecode is opnieuw verzonden via SMS',
|
verification_code_has_been_resent: 'Verificatiecode is opnieuw verzonden via SMS',
|
||||||
please_fill_all_required_fields: 'Vul alle verplichte velden in',
|
please_fill_all_required_fields: 'Vul alle verplichte velden in',
|
||||||
set_today: 'Stel vandaag in',
|
set_today: 'Vandaag',
|
||||||
toggle_multiline_text: 'Schakel meerregelige tekst in/uit',
|
toggle_multiline_text: 'Schakel meerregelige tekst in/uit',
|
||||||
draw_signature: 'Handtekening tekenen',
|
draw_signature: 'Handtekening tekenen',
|
||||||
type_initial: 'Typ initialen',
|
type_initial: 'Typ initialen',
|
||||||
|
|||||||
@@ -84,7 +84,7 @@
|
|||||||
<canvas
|
<canvas
|
||||||
v-show="!modelValue && !computedPreviousValue"
|
v-show="!modelValue && !computedPreviousValue"
|
||||||
ref="canvas"
|
ref="canvas"
|
||||||
class="bg-white border border-base-300 rounded-2xl"
|
class="bg-white border border-base-300 rounded-2xl w-full"
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
v-if="!isDrawInitials && !modelValue && !computedPreviousValue"
|
v-if="!isDrawInitials && !modelValue && !computedPreviousValue"
|
||||||
@@ -106,6 +106,8 @@ import { IconReload, IconTextSize, IconSignature, IconArrowsDiagonalMinimize2 }
|
|||||||
import SignaturePad from 'signature_pad'
|
import SignaturePad from 'signature_pad'
|
||||||
import AppearsOn from './appears_on'
|
import AppearsOn from './appears_on'
|
||||||
|
|
||||||
|
const scale = 3
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'InitialsStep',
|
name: 'InitialsStep',
|
||||||
components: {
|
components: {
|
||||||
@@ -171,8 +173,10 @@ export default {
|
|||||||
async mounted () {
|
async mounted () {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
if (this.$refs.canvas) {
|
if (this.$refs.canvas) {
|
||||||
this.$refs.canvas.width = this.$refs.canvas.parentNode.clientWidth
|
this.$refs.canvas.width = this.$refs.canvas.parentNode.clientWidth * scale
|
||||||
this.$refs.canvas.height = this.$refs.canvas.parentNode.clientWidth / 5
|
this.$refs.canvas.height = (this.$refs.canvas.parentNode.clientWidth / 3) * scale
|
||||||
|
|
||||||
|
this.$refs.canvas.getContext('2d').scale(scale, scale)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$refs.textInput?.focus()
|
this.$refs.textInput?.focus()
|
||||||
@@ -190,8 +194,26 @@ export default {
|
|||||||
|
|
||||||
this.$emit('start')
|
this.$emit('start')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
this.intersectionObserver = new IntersectionObserver((entries, observer) => {
|
||||||
|
entries.forEach(entry => {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
this.$refs.canvas.width = this.$refs.canvas.parentNode.clientWidth * scale
|
||||||
|
this.$refs.canvas.height = (this.$refs.canvas.parentNode.clientWidth / 3) * scale
|
||||||
|
|
||||||
|
this.$refs.canvas.getContext('2d').scale(scale, scale)
|
||||||
|
|
||||||
|
this.intersectionObserver?.disconnect()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
this.intersectionObserver.observe(this.$refs.canvas)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
beforeUnmount () {
|
||||||
|
this.intersectionObserver?.disconnect()
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
remove () {
|
remove () {
|
||||||
this.$emit('update:model-value', '')
|
this.$emit('update:model-value', '')
|
||||||
@@ -222,8 +244,8 @@ export default {
|
|||||||
context.font = fontStyle + ' ' + fontWeight + ' ' + fontSize + ' ' + fontFamily
|
context.font = fontStyle + ' ' + fontWeight + ' ' + fontSize + ' ' + fontFamily
|
||||||
context.textAlign = 'center'
|
context.textAlign = 'center'
|
||||||
|
|
||||||
context.clearRect(0, 0, canvas.width, canvas.height)
|
context.clearRect(0, 0, canvas.width / scale, canvas.height / scale)
|
||||||
context.fillText(e.target.value, canvas.width / 2, canvas.height / 2 + 11)
|
context.fillText(e.target.value, canvas.width / 2 / scale, canvas.height / 2 / scale + 11)
|
||||||
},
|
},
|
||||||
toggleTextInput () {
|
toggleTextInput () {
|
||||||
this.remove()
|
this.remove()
|
||||||
|
|||||||
@@ -104,7 +104,7 @@
|
|||||||
v-show="!modelValue && !computedPreviousValue"
|
v-show="!modelValue && !computedPreviousValue"
|
||||||
ref="canvas"
|
ref="canvas"
|
||||||
style="padding: 1px; 0"
|
style="padding: 1px; 0"
|
||||||
class="bg-white border border-base-300 rounded-2xl"
|
class="bg-white border border-base-300 rounded-2xl w-full"
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
v-if="isTextSignature"
|
v-if="isTextSignature"
|
||||||
@@ -127,6 +127,8 @@ import AppearsOn from './appears_on'
|
|||||||
|
|
||||||
let isFontLoaded = false
|
let isFontLoaded = false
|
||||||
|
|
||||||
|
const scale = 3
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'SignatureStep',
|
name: 'SignatureStep',
|
||||||
components: {
|
components: {
|
||||||
@@ -198,8 +200,10 @@ export default {
|
|||||||
async mounted () {
|
async mounted () {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
if (this.$refs.canvas) {
|
if (this.$refs.canvas) {
|
||||||
this.$refs.canvas.width = this.$refs.canvas.parentNode.clientWidth
|
this.$refs.canvas.width = this.$refs.canvas.parentNode.clientWidth * scale
|
||||||
this.$refs.canvas.height = this.$refs.canvas.parentNode.clientWidth / 3
|
this.$refs.canvas.height = this.$refs.canvas.parentNode.clientWidth * scale / 3
|
||||||
|
|
||||||
|
this.$refs.canvas.getContext('2d').scale(scale, scale)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -215,8 +219,26 @@ export default {
|
|||||||
|
|
||||||
this.$emit('start')
|
this.$emit('start')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
this.intersectionObserver = new IntersectionObserver((entries, observer) => {
|
||||||
|
entries.forEach(entry => {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
this.$refs.canvas.width = this.$refs.canvas.parentNode.clientWidth * scale
|
||||||
|
this.$refs.canvas.height = this.$refs.canvas.parentNode.clientWidth * scale / 3
|
||||||
|
|
||||||
|
this.$refs.canvas.getContext('2d').scale(scale, scale)
|
||||||
|
|
||||||
|
this.intersectionObserver?.disconnect()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
this.intersectionObserver.observe(this.$refs.canvas)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
beforeUnmount () {
|
||||||
|
this.intersectionObserver?.disconnect()
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
remove () {
|
remove () {
|
||||||
this.$emit('update:model-value', '')
|
this.$emit('update:model-value', '')
|
||||||
@@ -260,8 +282,8 @@ export default {
|
|||||||
context.font = fontStyle + ' ' + fontWeight + ' ' + fontSize + ' ' + fontFamily
|
context.font = fontStyle + ' ' + fontWeight + ' ' + fontSize + ' ' + fontFamily
|
||||||
context.textAlign = 'center'
|
context.textAlign = 'center'
|
||||||
|
|
||||||
context.clearRect(0, 0, canvas.width, canvas.height)
|
context.clearRect(0, 0, canvas.width / scale, canvas.height / scale)
|
||||||
context.fillText(e.target.value, canvas.width / 2, canvas.height / 2 + 11)
|
context.fillText(e.target.value, canvas.width / 2 / scale, canvas.height / 2 / scale + 11)
|
||||||
},
|
},
|
||||||
toggleTextInput () {
|
toggleTextInput () {
|
||||||
this.remove()
|
this.remove()
|
||||||
@@ -297,14 +319,16 @@ export default {
|
|||||||
const context = canvas.getContext('2d')
|
const context = canvas.getContext('2d')
|
||||||
|
|
||||||
const aspectRatio = img.width / img.height
|
const aspectRatio = img.width / img.height
|
||||||
|
const canvasWidth = canvas.width / scale
|
||||||
|
const canvasHeight = canvas.height / scale
|
||||||
|
|
||||||
let targetWidth = canvas.width
|
let targetWidth = canvasWidth
|
||||||
let targetHeight = canvas.height
|
let targetHeight = canvasHeight
|
||||||
|
|
||||||
if (canvas.width / canvas.height > aspectRatio) {
|
if (canvasWidth / canvasHeight > aspectRatio) {
|
||||||
targetWidth = canvas.height * aspectRatio
|
targetWidth = canvasHeight * aspectRatio
|
||||||
} else {
|
} else {
|
||||||
targetHeight = canvas.width / aspectRatio
|
targetHeight = canvasWidth / aspectRatio
|
||||||
}
|
}
|
||||||
|
|
||||||
if (targetHeight > targetWidth) {
|
if (targetHeight > targetWidth) {
|
||||||
@@ -313,10 +337,10 @@ export default {
|
|||||||
targetHeight = targetHeight * scale
|
targetHeight = targetHeight * scale
|
||||||
}
|
}
|
||||||
|
|
||||||
const x = (canvas.width - targetWidth) / 2
|
const x = (canvasWidth - targetWidth) / 2
|
||||||
const y = (canvas.height - targetHeight) / 2
|
const y = (canvasHeight - targetHeight) / 2
|
||||||
|
|
||||||
context.clearRect(0, 0, canvas.width, canvas.height)
|
context.clearRect(0, 0, canvasWidth, canvasHeight)
|
||||||
context.drawImage(img, x, y, targetWidth, targetHeight)
|
context.drawImage(img, x, y, targetWidth, targetHeight)
|
||||||
|
|
||||||
this.$emit('start')
|
this.$emit('start')
|
||||||
@@ -324,6 +348,8 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
reader.readAsDataURL(file)
|
reader.readAsDataURL(file)
|
||||||
|
|
||||||
|
event.target.value = null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async submit () {
|
async submit () {
|
||||||
|
|||||||
@@ -123,12 +123,14 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
this.isTextArea = this.modelValue?.includes('\n')
|
if (this.modelValue) {
|
||||||
|
this.isTextArea = this.modelValue.toString().includes('\n')
|
||||||
|
|
||||||
if (this.isTextArea) {
|
if (this.isTextArea) {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.resizeTextarea()
|
this.resizeTextarea()
|
||||||
})
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|||||||
@@ -98,21 +98,32 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="flex items-center h-full w-full"
|
class="flex items-center h-full w-full"
|
||||||
|
dir="auto"
|
||||||
:class="[bgColors[submitterIndex], field?.default_value ? '' : 'justify-center']"
|
:class="[bgColors[submitterIndex], field?.default_value ? '' : 'justify-center']"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
v-if="field"
|
v-if="field"
|
||||||
class="flex justify-center items-center space-x-1 h-full"
|
class="flex justify-center items-center space-x-1 h-full"
|
||||||
|
:class="{'w-full h-full': field.type == 'checkbox'}"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-if="field?.default_value"
|
v-if="field?.default_value"
|
||||||
:class="{ 'text-[1.5vw] lg:text-base': !textOverflowChars, 'text-[1.0vw] lg:text-xs': textOverflowChars }"
|
:class="{ 'w-full h-full': field.type == 'checkbox', 'text-[1.5vw] lg:text-base': !textOverflowChars, 'text-[1.0vw] lg:text-xs': textOverflowChars }"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
ref="textContainer"
|
ref="textContainer"
|
||||||
class="flex items-center px-0.5"
|
class="flex items-center px-0.5"
|
||||||
|
:class="{'w-full h-full': field.type == 'checkbox'}"
|
||||||
>
|
>
|
||||||
<span class="whitespace-pre-wrap">{{ field.default_value }}</span>
|
<IconCheck
|
||||||
|
v-if="field.type == 'checkbox'"
|
||||||
|
class="aspect-square mx-auto"
|
||||||
|
:class="{ '!w-auto !h-full': area.w > area.h, '!w-full !h-auto': area.w <= area.h }"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
v-else
|
||||||
|
class="whitespace-pre-wrap"
|
||||||
|
>{{ field.default_value }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<component
|
<component
|
||||||
@@ -141,13 +152,14 @@
|
|||||||
import FieldSubmitter from './field_submitter'
|
import FieldSubmitter from './field_submitter'
|
||||||
import FieldType from './field_type'
|
import FieldType from './field_type'
|
||||||
import Field from './field'
|
import Field from './field'
|
||||||
import { IconX } from '@tabler/icons-vue'
|
import { IconX, IconCheck } from '@tabler/icons-vue'
|
||||||
import { v4 } from 'uuid'
|
import { v4 } from 'uuid'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'FieldArea',
|
name: 'FieldArea',
|
||||||
components: {
|
components: {
|
||||||
FieldType,
|
FieldType,
|
||||||
|
IconCheck,
|
||||||
FieldSubmitter,
|
FieldSubmitter,
|
||||||
IconX
|
IconX
|
||||||
},
|
},
|
||||||
@@ -225,6 +237,16 @@ export default {
|
|||||||
},
|
},
|
||||||
borderColors () {
|
borderColors () {
|
||||||
return [
|
return [
|
||||||
|
'border-red-500/80',
|
||||||
|
'border-sky-500/80',
|
||||||
|
'border-emerald-500/80',
|
||||||
|
'border-yellow-300/80',
|
||||||
|
'border-purple-600/80',
|
||||||
|
'border-pink-500/80',
|
||||||
|
'border-cyan-500/80',
|
||||||
|
'border-orange-500/80',
|
||||||
|
'border-lime-500/80',
|
||||||
|
'border-indigo-500/80',
|
||||||
'border-red-500/80',
|
'border-red-500/80',
|
||||||
'border-sky-500/80',
|
'border-sky-500/80',
|
||||||
'border-emerald-500/80',
|
'border-emerald-500/80',
|
||||||
@@ -239,6 +261,16 @@ export default {
|
|||||||
},
|
},
|
||||||
bgColors () {
|
bgColors () {
|
||||||
return [
|
return [
|
||||||
|
'bg-red-100/80',
|
||||||
|
'bg-sky-100/80',
|
||||||
|
'bg-emerald-100/80',
|
||||||
|
'bg-yellow-100/80',
|
||||||
|
'bg-purple-100/80',
|
||||||
|
'bg-pink-100/80',
|
||||||
|
'bg-cyan-100/80',
|
||||||
|
'bg-orange-100/80',
|
||||||
|
'bg-lime-100/80',
|
||||||
|
'bg-indigo-100/80',
|
||||||
'bg-red-100/80',
|
'bg-red-100/80',
|
||||||
'bg-sky-100/80',
|
'bg-sky-100/80',
|
||||||
'bg-emerald-100/80',
|
'bg-emerald-100/80',
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-if="$slots.buttons || withTitle"
|
v-if="$slots.buttons || withTitle"
|
||||||
|
id="title_container"
|
||||||
class="flex justify-between py-1.5 items-center pr-4 top-0 z-10"
|
class="flex justify-between py-1.5 items-center pr-4 top-0 z-10"
|
||||||
:class="{ sticky: withStickySubmitters || isBreakpointLg }"
|
:class="{ sticky: withStickySubmitters || isBreakpointLg }"
|
||||||
:style="{ backgroundColor }"
|
:style="{ backgroundColor }"
|
||||||
@@ -61,6 +62,7 @@
|
|||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
<button
|
<button
|
||||||
|
v-if="editable"
|
||||||
class="base-button"
|
class="base-button"
|
||||||
:class="{ disabled: isSaving }"
|
:class="{ disabled: isSaving }"
|
||||||
v-bind="isSaving ? { disabled: true } : {}"
|
v-bind="isSaving ? { disabled: true } : {}"
|
||||||
@@ -79,6 +81,15 @@
|
|||||||
{{ t('save') }}
|
{{ t('save') }}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
|
<a
|
||||||
|
v-else
|
||||||
|
:href="`/templates/${template.id}`"
|
||||||
|
class="base-button"
|
||||||
|
>
|
||||||
|
<span class="hidden md:inline">
|
||||||
|
{{ t('back') }}
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -156,6 +156,34 @@
|
|||||||
<span class="label-text">{{ t('required') }}</span>
|
<span class="label-text">{{ t('required') }}</span>
|
||||||
</label>
|
</label>
|
||||||
</li>
|
</li>
|
||||||
|
<li
|
||||||
|
v-if="field.type == 'checkbox'"
|
||||||
|
@click.stop
|
||||||
|
>
|
||||||
|
<label class="cursor-pointer py-1.5">
|
||||||
|
<input
|
||||||
|
v-model="field.default_value"
|
||||||
|
type="checkbox"
|
||||||
|
class="toggle toggle-xs"
|
||||||
|
@update:model-value="[field.default_value = $event, field.readonly = $event, save()]"
|
||||||
|
>
|
||||||
|
<span class="label-text">{{ t('checked') }}</span>
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
v-if="field.type == 'date'"
|
||||||
|
@click.stop
|
||||||
|
>
|
||||||
|
<label class="cursor-pointer py-1.5">
|
||||||
|
<input
|
||||||
|
v-model="field.readonly"
|
||||||
|
type="checkbox"
|
||||||
|
class="toggle toggle-xs"
|
||||||
|
@update:model-value="[field.default_value = $event ? '{{date}}' : null, field.readonly = $event, save()]"
|
||||||
|
>
|
||||||
|
<span class="label-text">{{ t('set_signing_date') }}</span>
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
<li
|
<li
|
||||||
v-if="field.type === 'text' && !defaultField"
|
v-if="field.type === 'text' && !defaultField"
|
||||||
@click.stop
|
@click.stop
|
||||||
|
|||||||
@@ -67,7 +67,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li v-if="submitters.length < 10 && editable">
|
<li v-if="submitters.length < names.length && editable">
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
class="flex px-2"
|
class="flex px-2"
|
||||||
@@ -145,7 +145,7 @@
|
|||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
class="flex px-2 group justify-between items-center"
|
class="flex px-2 group justify-between items-center"
|
||||||
:class="{ 'active': submitter === selectedSubmitter }"
|
:class="{ 'active': submitter === selectedSubmitter, 'py-0.5': submitters.length > 8 }"
|
||||||
@click.prevent="selectSubmitter(submitter)"
|
@click.prevent="selectSubmitter(submitter)"
|
||||||
>
|
>
|
||||||
<span class="py-1 flex items-center">
|
<span class="py-1 flex items-center">
|
||||||
@@ -189,7 +189,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li v-if="submitters.length < 10 && editable && allowAddNew">
|
<li v-if="submitters.length < names.length && editable && allowAddNew">
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
class="flex px-2"
|
class="flex px-2"
|
||||||
@@ -268,6 +268,16 @@ export default {
|
|||||||
computed: {
|
computed: {
|
||||||
colors () {
|
colors () {
|
||||||
return [
|
return [
|
||||||
|
'bg-red-500',
|
||||||
|
'bg-sky-500',
|
||||||
|
'bg-emerald-500',
|
||||||
|
'bg-yellow-300',
|
||||||
|
'bg-purple-600',
|
||||||
|
'bg-pink-500',
|
||||||
|
'bg-cyan-500',
|
||||||
|
'bg-orange-500',
|
||||||
|
'bg-lime-500',
|
||||||
|
'bg-indigo-500',
|
||||||
'bg-red-500',
|
'bg-red-500',
|
||||||
'bg-sky-500',
|
'bg-sky-500',
|
||||||
'bg-emerald-500',
|
'bg-emerald-500',
|
||||||
@@ -291,7 +301,17 @@ export default {
|
|||||||
this.t('seventh_party'),
|
this.t('seventh_party'),
|
||||||
this.t('eighth_party'),
|
this.t('eighth_party'),
|
||||||
this.t('ninth_party'),
|
this.t('ninth_party'),
|
||||||
this.t('tenth_party')
|
this.t('tenth_party'),
|
||||||
|
this.t('eleventh_party'),
|
||||||
|
this.t('twelfth_party'),
|
||||||
|
this.t('thirteenth_party'),
|
||||||
|
this.t('fourteenth_party'),
|
||||||
|
this.t('fifteenth_party'),
|
||||||
|
this.t('sixteenth_party'),
|
||||||
|
this.t('seventeenth_party'),
|
||||||
|
this.t('eighteenth_party'),
|
||||||
|
this.t('nineteenth_party'),
|
||||||
|
this.t('twentieth_party')
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
selectedSubmitter () {
|
selectedSubmitter () {
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
const en = {
|
const en = {
|
||||||
are_you_sure: 'Are you sure?',
|
are_you_sure: 'Are you sure?',
|
||||||
sign_yourself: 'Sign Yourself',
|
sign_yourself: 'Sign Yourself',
|
||||||
|
set_signing_date: 'Set signing date',
|
||||||
send: 'Send',
|
send: 'Send',
|
||||||
|
checked: 'Checked',
|
||||||
save: 'Save',
|
save: 'Save',
|
||||||
cancel: 'Cancel',
|
cancel: 'Cancel',
|
||||||
draw_field_on_the_document: 'Draw {field} field on the document',
|
draw_field_on_the_document: 'Draw {field} field on the document',
|
||||||
@@ -30,6 +32,16 @@ const en = {
|
|||||||
eighth_party: 'Eighth Party',
|
eighth_party: 'Eighth Party',
|
||||||
ninth_party: 'Ninth Party',
|
ninth_party: 'Ninth Party',
|
||||||
tenth_party: 'Tenth Party',
|
tenth_party: 'Tenth Party',
|
||||||
|
eleventh_party: 'Eleventh Party',
|
||||||
|
twelfth_party: 'Twelfth Party',
|
||||||
|
thirteenth_party: 'Thirteenth Party',
|
||||||
|
fourteenth_party: 'Fourteenth Party',
|
||||||
|
fifteenth_party: 'Fifteenth Party',
|
||||||
|
sixteenth_party: 'Sixteenth Party',
|
||||||
|
seventeenth_party: 'Seventeenth Party',
|
||||||
|
eighteenth_party: 'Eighteenth Party',
|
||||||
|
nineteenth_party: 'Nineteenth Party',
|
||||||
|
twentieth_party: 'Twentieth Party',
|
||||||
add: 'Add',
|
add: 'Add',
|
||||||
or_add_field_without_drawing: 'Or add field without drawing',
|
or_add_field_without_drawing: 'Or add field without drawing',
|
||||||
text: 'Text',
|
text: 'Text',
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
class SendFormCompletedWebhookRequestJob < ApplicationJob
|
class SendFormCompletedWebhookRequestJob < ApplicationJob
|
||||||
USER_AGENT = 'DocuSeal.co Webhook'
|
USER_AGENT = 'DocuSeal.co Webhook'
|
||||||
|
|
||||||
|
NotSuccessStatus = Class.new(StandardError)
|
||||||
|
|
||||||
def perform(submitter)
|
def perform(submitter)
|
||||||
config = Accounts.load_webhook_configs(submitter.submission.account)
|
config = Accounts.load_webhook_configs(submitter.submission.account)
|
||||||
|
|
||||||
@@ -12,13 +14,17 @@ class SendFormCompletedWebhookRequestJob < ApplicationJob
|
|||||||
|
|
||||||
ActiveStorage::Current.url_options = Docuseal.default_url_options
|
ActiveStorage::Current.url_options = Docuseal.default_url_options
|
||||||
|
|
||||||
Faraday.post(config.value,
|
resp = Faraday.post(config.value,
|
||||||
{
|
{
|
||||||
event_type: 'form.completed',
|
event_type: 'form.completed',
|
||||||
timestamp: Time.current,
|
timestamp: Time.current,
|
||||||
data: Submitters::SerializeForWebhook.call(submitter)
|
data: Submitters::SerializeForWebhook.call(submitter)
|
||||||
}.to_json,
|
}.to_json,
|
||||||
'Content-Type' => 'application/json',
|
'Content-Type' => 'application/json',
|
||||||
'User-Agent' => USER_AGENT)
|
'User-Agent' => USER_AGENT)
|
||||||
|
|
||||||
|
if resp.status.to_i >= 400 && (!Docuseal.multitenant? || submitter.account.account_configs.exists?(key: :plan))
|
||||||
|
raise NotSuccessStatus, resp.status.to_s
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -3,6 +3,8 @@
|
|||||||
class SendFormStartedWebhookRequestJob < ApplicationJob
|
class SendFormStartedWebhookRequestJob < ApplicationJob
|
||||||
USER_AGENT = 'DocuSeal.co Webhook'
|
USER_AGENT = 'DocuSeal.co Webhook'
|
||||||
|
|
||||||
|
NotSuccessStatus = Class.new(StandardError)
|
||||||
|
|
||||||
def perform(submitter)
|
def perform(submitter)
|
||||||
config = Accounts.load_webhook_configs(submitter.submission.account)
|
config = Accounts.load_webhook_configs(submitter.submission.account)
|
||||||
|
|
||||||
@@ -10,13 +12,17 @@ class SendFormStartedWebhookRequestJob < ApplicationJob
|
|||||||
|
|
||||||
ActiveStorage::Current.url_options = Docuseal.default_url_options
|
ActiveStorage::Current.url_options = Docuseal.default_url_options
|
||||||
|
|
||||||
Faraday.post(config.value,
|
resp = Faraday.post(config.value,
|
||||||
{
|
{
|
||||||
event_type: 'form.started',
|
event_type: 'form.started',
|
||||||
timestamp: Time.current,
|
timestamp: Time.current,
|
||||||
data: Submitters::SerializeForWebhook.call(submitter)
|
data: Submitters::SerializeForWebhook.call(submitter)
|
||||||
}.to_json,
|
}.to_json,
|
||||||
'Content-Type' => 'application/json',
|
'Content-Type' => 'application/json',
|
||||||
'User-Agent' => USER_AGENT)
|
'User-Agent' => USER_AGENT)
|
||||||
|
|
||||||
|
if resp.status.to_i >= 400 && (!Docuseal.multitenant? || submitter.account.account_configs.exists?(key: :plan))
|
||||||
|
raise NotSuccessStatus, resp.status.to_s
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -3,6 +3,8 @@
|
|||||||
class SendFormViewedWebhookRequestJob < ApplicationJob
|
class SendFormViewedWebhookRequestJob < ApplicationJob
|
||||||
USER_AGENT = 'DocuSeal.co Webhook'
|
USER_AGENT = 'DocuSeal.co Webhook'
|
||||||
|
|
||||||
|
NotSuccessStatus = Class.new(StandardError)
|
||||||
|
|
||||||
def perform(submitter)
|
def perform(submitter)
|
||||||
config = Accounts.load_webhook_configs(submitter.submission.account)
|
config = Accounts.load_webhook_configs(submitter.submission.account)
|
||||||
|
|
||||||
@@ -10,13 +12,17 @@ class SendFormViewedWebhookRequestJob < ApplicationJob
|
|||||||
|
|
||||||
ActiveStorage::Current.url_options = Docuseal.default_url_options
|
ActiveStorage::Current.url_options = Docuseal.default_url_options
|
||||||
|
|
||||||
Faraday.post(config.value,
|
resp = Faraday.post(config.value,
|
||||||
{
|
{
|
||||||
event_type: 'form.viewed',
|
event_type: 'form.viewed',
|
||||||
timestamp: Time.current,
|
timestamp: Time.current,
|
||||||
data: Submitters::SerializeForWebhook.call(submitter)
|
data: Submitters::SerializeForWebhook.call(submitter)
|
||||||
}.to_json,
|
}.to_json,
|
||||||
'Content-Type' => 'application/json',
|
'Content-Type' => 'application/json',
|
||||||
'User-Agent' => USER_AGENT)
|
'User-Agent' => USER_AGENT)
|
||||||
|
|
||||||
|
if resp.status.to_i >= 400 && (!Docuseal.multitenant? || submitter.account.account_configs.exists?(key: :plan))
|
||||||
|
raise NotSuccessStatus, resp.status.to_s
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -27,6 +27,7 @@ class AccountConfig < ApplicationRecord
|
|||||||
BCC_EMAILS = 'bcc_emails'
|
BCC_EMAILS = 'bcc_emails'
|
||||||
FORCE_MFA = 'force_mfa'
|
FORCE_MFA = 'force_mfa'
|
||||||
ALLOW_TYPED_SIGNATURE = 'allow_typed_signature'
|
ALLOW_TYPED_SIGNATURE = 'allow_typed_signature'
|
||||||
|
ALLOW_TO_RESUBMIT = 'allow_to_resubmit'
|
||||||
SUBMITTER_REMAILERS = 'submitter_reminders'
|
SUBMITTER_REMAILERS = 'submitter_reminders'
|
||||||
FORM_COMPLETED_BUTTON_KEY = 'form_completed_button'
|
FORM_COMPLETED_BUTTON_KEY = 'form_completed_button'
|
||||||
FORM_WITH_CONFETTI_KEY = 'form_with_confetti'
|
FORM_WITH_CONFETTI_KEY = 'form_with_confetti'
|
||||||
|
|||||||
@@ -15,11 +15,13 @@
|
|||||||
# template_submitters :text
|
# template_submitters :text
|
||||||
# created_at :datetime not null
|
# created_at :datetime not null
|
||||||
# updated_at :datetime not null
|
# updated_at :datetime not null
|
||||||
|
# account_id :bigint not null
|
||||||
# created_by_user_id :bigint
|
# created_by_user_id :bigint
|
||||||
# template_id :bigint not null
|
# template_id :bigint not null
|
||||||
#
|
#
|
||||||
# Indexes
|
# Indexes
|
||||||
#
|
#
|
||||||
|
# index_submissions_on_account_id (account_id)
|
||||||
# index_submissions_on_created_by_user_id (created_by_user_id)
|
# index_submissions_on_created_by_user_id (created_by_user_id)
|
||||||
# index_submissions_on_slug (slug) UNIQUE
|
# index_submissions_on_slug (slug) UNIQUE
|
||||||
# index_submissions_on_template_id (template_id)
|
# index_submissions_on_template_id (template_id)
|
||||||
@@ -31,7 +33,7 @@
|
|||||||
#
|
#
|
||||||
class Submission < ApplicationRecord
|
class Submission < ApplicationRecord
|
||||||
belongs_to :template
|
belongs_to :template
|
||||||
has_one :account, through: :template
|
belongs_to :account
|
||||||
belongs_to :created_by_user, class_name: 'User', optional: true
|
belongs_to :created_by_user, class_name: 'User', optional: true
|
||||||
|
|
||||||
has_many :submitters, dependent: :destroy
|
has_many :submitters, dependent: :destroy
|
||||||
|
|||||||
@@ -46,7 +46,8 @@ class SubmissionEvent < ApplicationRecord
|
|||||||
phone_verified: 'phone_verified',
|
phone_verified: 'phone_verified',
|
||||||
start_form: 'start_form',
|
start_form: 'start_form',
|
||||||
view_form: 'view_form',
|
view_form: 'view_form',
|
||||||
complete_form: 'complete_form'
|
complete_form: 'complete_form',
|
||||||
|
api_complete_form: 'api_complete_form'
|
||||||
}, scope: false
|
}, scope: false
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|||||||
+25
-18
@@ -4,23 +4,24 @@
|
|||||||
#
|
#
|
||||||
# Table name: submitters
|
# Table name: submitters
|
||||||
#
|
#
|
||||||
# id :bigint not null, primary key
|
# id :bigint not null, primary key
|
||||||
# application_key :string
|
# completed_at :datetime
|
||||||
# completed_at :datetime
|
# email :string
|
||||||
# email :string
|
# ip :string
|
||||||
# ip :string
|
# metadata :text not null
|
||||||
# name :string
|
# name :string
|
||||||
# opened_at :datetime
|
# opened_at :datetime
|
||||||
# phone :string
|
# phone :string
|
||||||
# preferences :text not null
|
# preferences :text not null
|
||||||
# sent_at :datetime
|
# sent_at :datetime
|
||||||
# slug :string not null
|
# slug :string not null
|
||||||
# ua :string
|
# ua :string
|
||||||
# uuid :string not null
|
# uuid :string not null
|
||||||
# values :text not null
|
# values :text not null
|
||||||
# created_at :datetime not null
|
# created_at :datetime not null
|
||||||
# updated_at :datetime not null
|
# updated_at :datetime not null
|
||||||
# submission_id :bigint not null
|
# external_id :string
|
||||||
|
# submission_id :bigint not null
|
||||||
#
|
#
|
||||||
# Indexes
|
# Indexes
|
||||||
#
|
#
|
||||||
@@ -35,14 +36,16 @@
|
|||||||
class Submitter < ApplicationRecord
|
class Submitter < ApplicationRecord
|
||||||
belongs_to :submission
|
belongs_to :submission
|
||||||
has_one :template, through: :submission
|
has_one :template, through: :submission
|
||||||
has_one :account, through: :template
|
has_one :account, through: :submission
|
||||||
|
|
||||||
attribute :values, :string, default: -> { {} }
|
attribute :values, :string, default: -> { {} }
|
||||||
attribute :preferences, :string, default: -> { {} }
|
attribute :preferences, :string, default: -> { {} }
|
||||||
|
attribute :metadata, :string, default: -> { {} }
|
||||||
attribute :slug, :string, default: -> { SecureRandom.base58(14) }
|
attribute :slug, :string, default: -> { SecureRandom.base58(14) }
|
||||||
|
|
||||||
serialize :values, coder: JSON
|
serialize :values, coder: JSON
|
||||||
serialize :preferences, coder: JSON
|
serialize :preferences, coder: JSON
|
||||||
|
serialize :metadata, coder: JSON
|
||||||
|
|
||||||
has_many_attached :documents
|
has_many_attached :documents
|
||||||
has_many_attached :attachments
|
has_many_attached :attachments
|
||||||
@@ -62,6 +65,10 @@ class Submitter < ApplicationRecord
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def application_key
|
||||||
|
external_id
|
||||||
|
end
|
||||||
|
|
||||||
def friendly_name
|
def friendly_name
|
||||||
if name.present? && email.present? && email.exclude?(',')
|
if name.present? && email.present? && email.exclude?(',')
|
||||||
%("#{name.delete('"')}" <#{email}>)
|
%("#{name.delete('"')}" <#{email}>)
|
||||||
|
|||||||
+19
-14
@@ -4,20 +4,20 @@
|
|||||||
#
|
#
|
||||||
# Table name: templates
|
# Table name: templates
|
||||||
#
|
#
|
||||||
# id :bigint not null, primary key
|
# id :bigint not null, primary key
|
||||||
# application_key :string
|
# archived_at :datetime
|
||||||
# archived_at :datetime
|
# fields :text not null
|
||||||
# fields :text not null
|
# name :string not null
|
||||||
# name :string not null
|
# schema :text not null
|
||||||
# schema :text not null
|
# slug :string not null
|
||||||
# slug :string not null
|
# source :text not null
|
||||||
# source :text not null
|
# submitters :text not null
|
||||||
# submitters :text not null
|
# created_at :datetime not null
|
||||||
# created_at :datetime not null
|
# updated_at :datetime not null
|
||||||
# updated_at :datetime not null
|
# account_id :bigint not null
|
||||||
# account_id :bigint not null
|
# author_id :bigint not null
|
||||||
# author_id :bigint not null
|
# external_id :string
|
||||||
# folder_id :bigint not null
|
# folder_id :bigint not null
|
||||||
#
|
#
|
||||||
# Indexes
|
# Indexes
|
||||||
#
|
#
|
||||||
@@ -57,10 +57,15 @@ class Template < ApplicationRecord
|
|||||||
class_name: 'ActiveStorage::Attachment', dependent: :destroy, as: :record, inverse_of: :record
|
class_name: 'ActiveStorage::Attachment', dependent: :destroy, as: :record, inverse_of: :record
|
||||||
|
|
||||||
has_many :submissions, dependent: :destroy
|
has_many :submissions, dependent: :destroy
|
||||||
|
has_many :template_sharings, dependent: :destroy
|
||||||
|
|
||||||
scope :active, -> { where(archived_at: nil) }
|
scope :active, -> { where(archived_at: nil) }
|
||||||
scope :archived, -> { where.not(archived_at: nil) }
|
scope :archived, -> { where.not(archived_at: nil) }
|
||||||
|
|
||||||
|
def application_key
|
||||||
|
external_id
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def maybe_set_default_folder
|
def maybe_set_default_folder
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# == Schema Information
|
||||||
|
#
|
||||||
|
# Table name: template_sharings
|
||||||
|
#
|
||||||
|
# id :bigint not null, primary key
|
||||||
|
# ability :string not null
|
||||||
|
# created_at :datetime not null
|
||||||
|
# updated_at :datetime not null
|
||||||
|
# account_id :bigint not null
|
||||||
|
# template_id :bigint not null
|
||||||
|
#
|
||||||
|
# Indexes
|
||||||
|
#
|
||||||
|
# index_template_sharings_on_account_id_and_template_id (account_id,template_id) UNIQUE
|
||||||
|
# index_template_sharings_on_template_id (template_id)
|
||||||
|
#
|
||||||
|
# Foreign Keys
|
||||||
|
#
|
||||||
|
# fk_rails_... (template_id => templates.id)
|
||||||
|
#
|
||||||
|
class TemplateSharing < ApplicationRecord
|
||||||
|
ALL_ID = -1
|
||||||
|
|
||||||
|
belongs_to :template
|
||||||
|
belongs_to :account, optional: true
|
||||||
|
end
|
||||||
@@ -63,6 +63,18 @@
|
|||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
<% account_config = AccountConfig.find_or_initialize_by(account: current_account, key: AccountConfig::ALLOW_TO_RESUBMIT) %>
|
||||||
|
<% if can?(:manage, account_config) %>
|
||||||
|
<%= form_for account_config, url: account_configs_path, method: :post do |f| %>
|
||||||
|
<%= f.hidden_field :key %>
|
||||||
|
<div class="flex items-center justify-between py-2.5">
|
||||||
|
<span>
|
||||||
|
Allow to resubmit completed forms
|
||||||
|
</span>
|
||||||
|
<%= f.check_box :value, class: 'toggle', checked: account_config.value != false, onchange: 'this.form.requestSubmit()' %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if can?(:manage, current_account) && Docuseal.multitenant? && true_user == current_user %>
|
<% if can?(:manage, current_account) && Docuseal.multitenant? && true_user == current_user %>
|
||||||
|
|||||||
@@ -1,6 +1,14 @@
|
|||||||
<div class="max-w-lg mx-auto px-2">
|
<div class="max-w-lg mx-auto px-2">
|
||||||
<%= render 'devise/shared/select_server' if Docuseal.multitenant? %>
|
<% if params[:redir].to_s.ends_with?('on_premise') %>
|
||||||
<h1 class="text-3xl font-bold text-center mt-8 mb-6">Create Free Account</h1>
|
<h1 class="text-3xl font-bold text-center mt-8 mb-6">DocuSeal Console</h1>
|
||||||
|
<div class="alert my-4 text-sm">
|
||||||
|
Sign up in DocuSeal Console to upgrade.
|
||||||
|
On-premises app is completely standalone, Console is used only to manage your license.
|
||||||
|
</div>
|
||||||
|
<% else %>
|
||||||
|
<%= render 'devise/shared/select_server' if Docuseal.multitenant? %>
|
||||||
|
<h1 class="text-3xl font-bold text-center mt-8 mb-6">Create Free Account</h1>
|
||||||
|
<% end %>
|
||||||
<%= form_for(User.new, html: { class: 'space-y-6' }, url: new_registration_path, data: { turbo: params[:redir].blank? }, method: :get) do |f| %>
|
<%= form_for(User.new, html: { class: 'space-y-6' }, url: new_registration_path, data: { turbo: params[:redir].blank? }, method: :get) do |f| %>
|
||||||
<% if params[:redir].present? %>
|
<% if params[:redir].present? %>
|
||||||
<%= hidden_field_tag :redir, params[:redir] %>
|
<%= hidden_field_tag :redir, params[:redir] %>
|
||||||
|
|||||||
@@ -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 d="M7 8l-4 4l4 4" />
|
||||||
|
<path d="M17 8l4 4l-4 4" />
|
||||||
|
<path d="M14 4l-4 16" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 369 B |
@@ -5,7 +5,7 @@
|
|||||||
<p>
|
<p>
|
||||||
Send automatic email reminders to your recipients.
|
Send automatic email reminders to your recipients.
|
||||||
<br>
|
<br>
|
||||||
<a class="link font-medium" target="_blank" href="<%= "#{Docuseal::CONSOLE_URL}/#{Docuseal.multitenant? ? 'plans' : 'on_premise'}" %>">Learn More</a>
|
<a class="link font-medium" target="_blank" href="<%= Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premise" }.to_query}" %>">Learn More</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
<p>
|
<p>
|
||||||
Display your company name and logo when signing documents.
|
Display your company name and logo when signing documents.
|
||||||
<br>
|
<br>
|
||||||
<a class="link font-medium" target="_blank" href="<%= "#{Docuseal::CONSOLE_URL}/#{Docuseal.multitenant? ? 'plans' : 'on_premise'}" %>">Learn More</a>
|
<a class="link font-medium" target="_blank" href="<%= Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premise" }.to_query}" %>">Learn More</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<%= link_to Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CONSOLE_URL}/on_premise", class: 'hidden md:inline-flex btn btn-warning btn-sm', data: { prefetch: false } do %>
|
<%= link_to Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premise" }.to_query}", class: 'hidden md:inline-flex btn btn-warning btn-sm', data: { prefetch: false } do %>
|
||||||
Upgrade
|
Upgrade
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if signed_in? && current_user != true_user %>
|
<% if signed_in? && current_user != true_user %>
|
||||||
|
|||||||
@@ -69,7 +69,7 @@
|
|||||||
<%= render 'shared/settings_nav_extra' %>
|
<%= render 'shared/settings_nav_extra' %>
|
||||||
<% if !Docuseal.demo? && can?(:manage, EncryptedConfig) %>
|
<% if !Docuseal.demo? && can?(:manage, EncryptedConfig) %>
|
||||||
<li>
|
<li>
|
||||||
<%= link_to Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CONSOLE_URL}/on_premise", class: 'text-base hover:bg-base-300', data: { prefetch: false } do %>
|
<%= link_to Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premise" }.to_query}", class: 'text-base hover:bg-base-300', data: { prefetch: false } do %>
|
||||||
Plans
|
Plans
|
||||||
<span class="badge badge-warning">New</span>
|
<span class="badge badge-warning">New</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<p class="text-gray-700">
|
<p class="text-gray-700">
|
||||||
Unlock with DocuSeal Pro
|
Unlock with DocuSeal Pro
|
||||||
<br>
|
<br>
|
||||||
<a class="link font-medium" target="_blank" href="<%= "#{Docuseal::CONSOLE_URL}/#{Docuseal.multitenant? ? 'plans' : 'on_premise'}" %>">Learn More</a>
|
<a class="link font-medium" target="_blank" href="<%= Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premise" }.to_query}" %>">Learn More</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
<p class="text-gray-700">
|
<p class="text-gray-700">
|
||||||
Unlock with DocuSeal Pro
|
Unlock with DocuSeal Pro
|
||||||
<br>
|
<br>
|
||||||
<a class="link font-medium" target="_blank" href="<%= "#{Docuseal::CONSOLE_URL}/#{Docuseal.multitenant? ? 'plans' : 'on_premise'}" %>">Learn More</a>
|
<a class="link font-medium" target="_blank" href="<%= Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premise" }.to_query}" %>">Learn More</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -24,7 +24,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: { submitter_slug: @submitter.slug }, form: { onsubmit: 'event.submitter.disabled = true' }, class: 'base-button w-full' %>
|
<%= 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: { submitter_slug: @submitter.slug }, form: { onsubmit: 'event.submitter.disabled = true' }, class: 'base-button w-full' %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if @template.submitters.to_a.size == 1 %>
|
<% if @template.submitters.to_a.size == 1 && %w[api embed].exclude?(@submitter.submission.source) && @submitter.account.account_configs.find_or_initialize_by(key: AccountConfig::ALLOW_TO_RESUBMIT).value != false %>
|
||||||
<div>
|
<div>
|
||||||
<%= 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: { email: params[:email] }, resubmit: @submitter.slug }, method: :put, form: { onsubmit: 'event.submitter.disabled = true' }, class: 'white-button w-full' %>
|
<%= 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: { email: params[:email] }, resubmit: @submitter.slug }, method: :put, form: { onsubmit: 'event.submitter.disabled = true' }, class: 'white-button w-full' %>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,181 +6,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="divider uppercase normal-case mt-5 mb-6 text-neutral-700">Or embed on your website</div>
|
<div class="divider uppercase normal-case mt-5 mb-6 text-neutral-700">Or embed on your website</div>
|
||||||
<toggle-visible data-element-ids="["js_1","react_1","vue_1"]" class="block relative" data-catalyst="">
|
<%= render 'templates/embedding', template: %>
|
||||||
<ul class="items-center w-full text-sm font-medium text-gray-900 my-4 space-y-2 sm:space-y-0 sm:flex sm:space-x-2">
|
|
||||||
<li class="w-full h-10 text-sm font-medium flex items-center relative group py-3.5">
|
|
||||||
<input type="radio" name="option_1" id="js_radio_1" value="js_1" data-action="change:toggle-visible#trigger" class="relative peer z-10 hidden" checked="checked">
|
|
||||||
<label for="js_radio_1" class="absolute border-neutral-focus space-x-2 border rounded-xl left-0 right-0 top-0 bottom-0 flex items-center justify-center group-hover:bg-neutral group-hover:text-white peer-checked:btn-neutral">
|
|
||||||
<span><svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" x="0px" y="0px" width="100" height="100" viewBox="0 0 48 48">
|
|
||||||
<path fill="#ffd600" d="M6,42V6h36v36H6z"></path><path fill="#000001" d="M29.538 32.947c.692 1.124 1.444 2.201 3.037 2.201 1.338 0 2.04-.665 2.04-1.585 0-1.101-.726-1.492-2.198-2.133l-.807-.344c-2.329-.988-3.878-2.226-3.878-4.841 0-2.41 1.845-4.244 4.728-4.244 2.053 0 3.528.711 4.592 2.573l-2.514 1.607c-.553-.988-1.151-1.377-2.078-1.377-.946 0-1.545.597-1.545 1.377 0 .964.6 1.354 1.985 1.951l.807.344C36.452 29.645 38 30.839 38 33.523 38 36.415 35.716 38 32.65 38c-2.999 0-4.702-1.505-5.65-3.368L29.538 32.947zM17.952 33.029c.506.906 1.275 1.603 2.381 1.603 1.058 0 1.667-.418 1.667-2.043V22h3.333v11.101c0 3.367-1.953 4.899-4.805 4.899-2.577 0-4.437-1.746-5.195-3.368L17.952 33.029z"></path>
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
<span>JavaScript</span>
|
|
||||||
</label>
|
|
||||||
</li>
|
|
||||||
<li class="w-full h-10 text-sm font-medium flex items-center relative group py-3.5">
|
|
||||||
<input type="radio" name="option_1" id="react_radio_1" value="react_1" data-action="change:toggle-visible#trigger" class="relative peer z-10 hidden">
|
|
||||||
<label for="react_radio_1" class="absolute border-neutral-focus space-x-2 border rounded-xl left-0 right-0 top-0 bottom-0 flex items-center justify-center group-hover:bg-neutral group-hover:text-white peer-checked:btn-neutral">
|
|
||||||
<span><svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="-11.5 -10.23174 23 20.46348">
|
|
||||||
<circle cx="0" cy="0" r="2.05" fill="#61dafb"></circle>
|
|
||||||
<g stroke="#61dafb" stroke-width="1" fill="none">
|
|
||||||
<ellipse rx="11" ry="4.2"></ellipse>
|
|
||||||
<ellipse rx="11" ry="4.2" transform="rotate(60)"></ellipse>
|
|
||||||
<ellipse rx="11" ry="4.2" transform="rotate(120)"></ellipse>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
<span>React</span>
|
|
||||||
</label>
|
|
||||||
</li>
|
|
||||||
<li class="w-full h-10 text-sm font-medium flex items-center relative group py-3.5">
|
|
||||||
<input type="radio" name="option_1" id="vue_radio_1" value="vue_1" data-action="change:toggle-visible#trigger" class="relative peer z-10 hidden">
|
|
||||||
<label for="vue_radio_1" class="absolute border-neutral-focus space-x-2 border rounded-xl left-0 right-0 top-0 bottom-0 flex items-center justify-center group-hover:bg-neutral group-hover:text-white peer-checked:btn-neutral">
|
|
||||||
<span><svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" version="1.1" viewBox="0 0 261.76 226.69"><g transform="matrix(1.3333 0 0 -1.3333 -76.311 313.34)"><g transform="translate(178.06 235.01)"><path d="m0 0-22.669-39.264-22.669 39.264h-75.491l98.16-170.02 98.16 170.02z" fill="#41b883"></path></g><g transform="translate(178.06 235.01)"><path d="m0 0-22.669-39.264-22.669 39.264h-36.227l58.896-102.01 58.896 102.01z" fill="#34495e"></path></g></g></svg>
|
|
||||||
</span>
|
|
||||||
<span>Vue</span>
|
|
||||||
</label>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</toggle-visible>
|
|
||||||
<div id="js_1" class="block my-4">
|
|
||||||
<div class="mockup-code overflow-hidden pb-0 mt-4">
|
|
||||||
<span class="top-0 right-0 absolute flex">
|
|
||||||
<%= link_to 'Learn More', console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/embedding/form"), target: '_blank', data: { turbo: false }, class: 'btn btn-ghost text-gray-100 flex', rel: 'noopener' %>
|
|
||||||
<clipboard-copy data-text="<script src="<%= Docuseal::CDN_URL %>/js/form.js"></script>
|
|
||||||
|
|
||||||
<docuseal-form data-src="<%= start_form_url(slug: template.slug) %>"></docuseal-form>
|
|
||||||
">
|
|
||||||
<label class="btn btn-ghost text-gray-100">
|
|
||||||
<input type="radio" class="peer hidden">
|
|
||||||
<span class="peer-checked:hidden flex items-center space-x-2">
|
|
||||||
<%= svg_icon('copy', class: 'w-6 h-6 text-white') %>
|
|
||||||
<span class="hidden md:inline">
|
|
||||||
Copy
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
<span class="hidden peer-checked:flex items-center space-x-2">
|
|
||||||
<%= svg_icon('clipboard_copy', class: 'w-6 h-6 text-white') %>
|
|
||||||
<span class="hidden md:inline">
|
|
||||||
Copied
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
</clipboard-copy>
|
|
||||||
|
|
||||||
</span>
|
|
||||||
<pre class="before:!m-0 pl-6 pb-4 overflow-auto"><code class="overflow-hidden w-full"><span style="color: #f4bf75"><script </span><span style="color: #6a9fb5">src=</span><span style="color: #90a959">"<%= Docuseal::CDN_URL %>/js/form.js"</span><span style="color: #f4bf75">></script></span>
|
|
||||||
|
|
||||||
<span style="color: #f4bf75"><docuseal-form</span> <span style="color: #6a9fb5">data-src=</span><span style="color: #90a959">"<%= start_form_url(slug: template.slug) %>"</span><span style="color: #f4bf75">></span><span style="color: #f4bf75"></docuseal-form></span>
|
|
||||||
</code></pre>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div id="react_1" class="block my-4 hidden">
|
|
||||||
<div class="mockup-code overflow-hidden pb-0 mt-4">
|
|
||||||
<span class="top-0 right-0 absolute flex">
|
|
||||||
<%= link_to 'Learn More', console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/embedding/form"), target: '_blank', data: { turbo: false }, class: 'btn btn-ghost text-gray-100 flex', rel: 'noopener' %>
|
|
||||||
<clipboard-copy data-text="import React from "react"
|
|
||||||
import { DocusealForm } from '@docuseal/react'
|
|
||||||
|
|
||||||
export function App() {
|
|
||||||
return (
|
|
||||||
<DocusealForm
|
|
||||||
src="<%= start_form_url(slug: template.slug) %>"
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
">
|
|
||||||
<label class="btn btn-ghost text-white">
|
|
||||||
<input type="radio" class="peer hidden">
|
|
||||||
<span class="peer-checked:hidden flex items-center space-x-2">
|
|
||||||
<%= svg_icon('copy', class: 'w-6 h-6 text-white') %>
|
|
||||||
<span class="hidden md:inline">
|
|
||||||
Copy
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
<span class="hidden peer-checked:flex items-center space-x-2">
|
|
||||||
<%= svg_icon('clipboard_copy', class: 'w-6 h-6 text-white') %>
|
|
||||||
<span class="hidden md:inline">
|
|
||||||
Copied
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
</clipboard-copy>
|
|
||||||
|
|
||||||
</span>
|
|
||||||
<pre class="before:!m-0 pl-6 pb-4 overflow-auto"><code class="overflow-hidden w-full"><span style="color: #aa759f">import</span> <span style="color: #d0d0d0;background-color: #151515">React</span> <span style="color: #aa759f">from</span> <span style="color: #90a959">"</span><span style="color: #90a959">react</span><span style="color: #90a959">"</span>
|
|
||||||
<span style="color: #aa759f">import</span> <span style="color: #d0d0d0">{</span> <span style="color: #d0d0d0;background-color: #151515">DocusealForm</span> <span style="color: #d0d0d0">}</span> <span style="color: #aa759f">from</span> <span style="color: #90a959">'</span><span style="color: #90a959">@docuseal/react</span><span style="color: #90a959">'</span>
|
|
||||||
|
|
||||||
<span style="color: #aa759f">export</span> <span style="color: #d28445">function</span> <span style="color: #d0d0d0;background-color: #151515">App</span><span style="color: #d0d0d0">()</span> <span style="color: #d0d0d0">{</span>
|
|
||||||
<span style="color: #aa759f">return </span><span style="color: #d0d0d0">(</span>
|
|
||||||
<span style="color: #d0d0d0"><</span><span style="color: #f4bf75">DocusealForm</span>
|
|
||||||
<span style="color: #6a9fb5">src</span><span style="color: #d0d0d0">=</span><span style="color: #90a959">"<%= start_form_url(slug: template.slug) %>"</span>
|
|
||||||
<span style="color: #d0d0d0">/></span>
|
|
||||||
<span style="color: #d0d0d0">);</span>
|
|
||||||
<span style="color: #d0d0d0">}</span>
|
|
||||||
</code></pre>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div id="vue_1" class="block my-4 hidden">
|
|
||||||
<div class="mockup-code overflow-hidden pb-0 mt-4">
|
|
||||||
<span class="top-0 right-0 absolute flex">
|
|
||||||
<%= link_to 'Learn More', console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/embedding/form"), target: '_blank', data: { turbo: false }, class: 'btn btn-ghost text-gray-100 flex', rel: 'noopener' %>
|
|
||||||
<clipboard-copy data-text="<template>
|
|
||||||
<DocusealForm
|
|
||||||
:src="'<%= start_form_url(slug: template.slug) %>'"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { DocusealForm } from '@docuseal/vue'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'App',
|
|
||||||
components: {
|
|
||||||
DocusealForm
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
">
|
|
||||||
<label class="btn btn-ghost text-white">
|
|
||||||
<input type="radio" class="peer hidden">
|
|
||||||
<span class="peer-checked:hidden flex items-center space-x-2">
|
|
||||||
<%= svg_icon('copy', class: 'w-6 h-6 text-white') %>
|
|
||||||
<span class="hidden md:inline">
|
|
||||||
Copy
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
<span class="hidden peer-checked:flex items-center space-x-2">
|
|
||||||
<%= svg_icon('clipboard_copy', class: 'w-6 h-6 text-white') %>
|
|
||||||
<span class="hidden md:inline">
|
|
||||||
Copied
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
</clipboard-copy>
|
|
||||||
|
|
||||||
</span>
|
|
||||||
<pre class="before:!m-0 pl-6 pb-4 overflow-auto"><code class="overflow-hidden w-full"><span style="color: #f4bf75"><template></span>
|
|
||||||
<span style="color: #f4bf75"><DocusealForm</span>
|
|
||||||
<span style="color: #6a9fb5">:src=</span><span style="color: #90a959">"'<%= start_form_url(slug: template.slug) %>'"</span>
|
|
||||||
<span style="color: #f4bf75">/></span>
|
|
||||||
<span style="color: #f4bf75"></template></span>
|
|
||||||
|
|
||||||
<span style="color: #f4bf75"><</span><span style="color: #f4bf75">script</span><span style="color: #f4bf75">></span>
|
|
||||||
<span style="color: #aa759f">import</span> <span style="color: #d0d0d0">{</span> <span style="color: #d0d0d0;background-color: #151515">DocusealForm</span> <span style="color: #d0d0d0">}</span> <span style="color: #aa759f">from</span> <span style="color: #90a959">'</span><span style="color: #90a959">@docuseal/vue</span><span style="color: #90a959">'</span>
|
|
||||||
|
|
||||||
<span style="color: #aa759f">export</span> <span style="color: #aa759f">default</span> <span style="color: #d0d0d0">{</span>
|
|
||||||
<span style="color: #6a9fb5">name</span><span style="color: #d0d0d0">:</span> <span style="color: #90a959">'</span><span style="color: #90a959">App</span><span style="color: #90a959">'</span><span style="color: #d0d0d0">,</span>
|
|
||||||
<span style="color: #6a9fb5">components</span><span style="color: #d0d0d0">:</span> <span style="color: #d0d0d0">{</span>
|
|
||||||
<span style="color: #d0d0d0;background-color: #151515">DocusealForm</span>
|
|
||||||
<span style="color: #d0d0d0">}</span>
|
|
||||||
<span style="color: #d0d0d0">}</span>
|
|
||||||
<span style="color: #f4bf75"></</span><span style="color: #f4bf75">script</span><span style="color: #f4bf75">></span>
|
|
||||||
</code></pre>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
<a href="#" class="link text-center block" data-action="click:turbo-modal#close">Close</a>
|
<a href="#" class="link text-center block" data-action="click:turbo-modal#close">Close</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<div class="mt-2 mb-1">
|
<div class="mt-2 mb-1">
|
||||||
<div class="tooltip w-full" data-tip="Unlock with Enterpise">
|
<div class="tooltip w-full" data-tip="Unlock with Enterpise">
|
||||||
<%= link_to submitter.sent_at? ? 'Re-send SMS' : 'Send SMS', "#{Docuseal::CONSOLE_URL}/#{Docuseal.multitenant? ? 'plans' : 'on_premise'}", class: 'btn btn-sm btn-primary text-gray-400 w-full' %>
|
<%= link_to submitter.sent_at? ? 'Re-send SMS' : 'Send SMS', Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premise" }.to_query}", class: 'btn btn-sm btn-primary text-gray-400 w-full' %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
</span>
|
</span>
|
||||||
</download-button>
|
</download-button>
|
||||||
</div>
|
</div>
|
||||||
<% if @submitter.submission.template.submitters.size == 1 && %w[api embed].exclude?(@submitter.submission.source) %>
|
<% if @submitter.submission.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 %>
|
||||||
<div class="divider uppercase"><%= t('or') %></div>
|
<div class="divider uppercase"><%= t('or') %></div>
|
||||||
<div>
|
<div>
|
||||||
<%= button_to button_title(title: t('resubmit'), disabled_with: t('resubmit'), icon: svg_icon('reload', class: 'w-6 h-6')), start_form_path(@submitter.submission.template.slug), params: { submitter: { email: @submitter.email, phone: @submitter.phone, name: @submitter.name }, resubmit: @submitter.slug }, method: :put, form: { onsubmit: 'event.submitter.disabled = true' }, class: 'white-button w-full' %>
|
<%= button_to button_title(title: t('resubmit'), disabled_with: t('resubmit'), icon: svg_icon('reload', class: 'w-6 h-6')), start_form_path(@submitter.submission.template.slug), params: { submitter: { email: @submitter.email, phone: @submitter.phone, name: @submitter.name }, resubmit: @submitter.slug }, method: :put, form: { onsubmit: 'event.submitter.disabled = true' }, class: 'white-button w-full' %>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<% if @email_config || @body.present? %>
|
<% if @email_config || @body.present? %>
|
||||||
<% body = (@body.presence || @email_config.value['body']).to_s %>
|
<% body = (@body.presence || @email_config.value['body']).to_s %>
|
||||||
<%= auto_link(simple_format(h(ReplaceEmailVariables.call(body, submitter: @submitter)))) %>
|
<%= auto_link(simple_format(h(ReplaceEmailVariables.call(body, submitter: @submitter)))) %>
|
||||||
<% if !body.include?(ReplaceEmailVariables::SUBMITTER_LINK) && !body.include?(ReplaceEmailVariables::SUBMITTER_ID) && !body.include?(ReplaceEmailVariables::SUBMISSION_LINK) && !body.include?(ReplaceEmailVariables::TEMPLATE_ID) %>
|
<% if !body.include?(ReplaceEmailVariables::SUBMITTER_LINK) && !body.include?(ReplaceEmailVariables::SUBMITTER_ID) && !body.include?(ReplaceEmailVariables::SUBMISSION_LINK) && !body.include?(ReplaceEmailVariables::TEMPLATE_ID) && !@submitter.submission.source.in?(%w[api embed]) %>
|
||||||
<p><%= link_to nil, submit_form_url(slug: @submitter.slug, t: SubmissionEvents.build_tracking_param(@submitter, 'click_email')) %></p>
|
<p><%= link_to nil, submit_form_url(slug: @submitter.slug, t: SubmissionEvents.build_tracking_param(@submitter, 'click_email')) %></p>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% else %>
|
<% else %>
|
||||||
|
|||||||
@@ -0,0 +1,175 @@
|
|||||||
|
<toggle-visible data-element-ids="["js_1","react_1","vue_1"]" class="block relative" data-catalyst="">
|
||||||
|
<ul class="items-center w-full text-sm font-medium text-gray-900 my-4 space-y-2 sm:space-y-0 sm:flex sm:space-x-2">
|
||||||
|
<li class="w-full h-10 text-sm font-medium flex items-center relative group py-3.5">
|
||||||
|
<input type="radio" name="option_1" id="js_radio_1" value="js_1" data-action="change:toggle-visible#trigger" class="relative peer z-10 hidden" checked="checked">
|
||||||
|
<label for="js_radio_1" class="absolute border-neutral-focus space-x-2 border rounded-xl left-0 right-0 top-0 bottom-0 flex items-center justify-center group-hover:bg-neutral group-hover:text-white peer-checked:btn-neutral">
|
||||||
|
<span><svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" x="0px" y="0px" width="100" height="100" viewBox="0 0 48 48">
|
||||||
|
<path fill="#ffd600" d="M6,42V6h36v36H6z"></path><path fill="#000001" d="M29.538 32.947c.692 1.124 1.444 2.201 3.037 2.201 1.338 0 2.04-.665 2.04-1.585 0-1.101-.726-1.492-2.198-2.133l-.807-.344c-2.329-.988-3.878-2.226-3.878-4.841 0-2.41 1.845-4.244 4.728-4.244 2.053 0 3.528.711 4.592 2.573l-2.514 1.607c-.553-.988-1.151-1.377-2.078-1.377-.946 0-1.545.597-1.545 1.377 0 .964.6 1.354 1.985 1.951l.807.344C36.452 29.645 38 30.839 38 33.523 38 36.415 35.716 38 32.65 38c-2.999 0-4.702-1.505-5.65-3.368L29.538 32.947zM17.952 33.029c.506.906 1.275 1.603 2.381 1.603 1.058 0 1.667-.418 1.667-2.043V22h3.333v11.101c0 3.367-1.953 4.899-4.805 4.899-2.577 0-4.437-1.746-5.195-3.368L17.952 33.029z"></path>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<span>JavaScript</span>
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
<li class="w-full h-10 text-sm font-medium flex items-center relative group py-3.5">
|
||||||
|
<input type="radio" name="option_1" id="react_radio_1" value="react_1" data-action="change:toggle-visible#trigger" class="relative peer z-10 hidden">
|
||||||
|
<label for="react_radio_1" class="absolute border-neutral-focus space-x-2 border rounded-xl left-0 right-0 top-0 bottom-0 flex items-center justify-center group-hover:bg-neutral group-hover:text-white peer-checked:btn-neutral">
|
||||||
|
<span><svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="-11.5 -10.23174 23 20.46348">
|
||||||
|
<circle cx="0" cy="0" r="2.05" fill="#61dafb"></circle>
|
||||||
|
<g stroke="#61dafb" stroke-width="1" fill="none">
|
||||||
|
<ellipse rx="11" ry="4.2"></ellipse>
|
||||||
|
<ellipse rx="11" ry="4.2" transform="rotate(60)"></ellipse>
|
||||||
|
<ellipse rx="11" ry="4.2" transform="rotate(120)"></ellipse>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<span>React</span>
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
<li class="w-full h-10 text-sm font-medium flex items-center relative group py-3.5">
|
||||||
|
<input type="radio" name="option_1" id="vue_radio_1" value="vue_1" data-action="change:toggle-visible#trigger" class="relative peer z-10 hidden">
|
||||||
|
<label for="vue_radio_1" class="absolute border-neutral-focus space-x-2 border rounded-xl left-0 right-0 top-0 bottom-0 flex items-center justify-center group-hover:bg-neutral group-hover:text-white peer-checked:btn-neutral">
|
||||||
|
<span><svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" version="1.1" viewBox="0 0 261.76 226.69"><g transform="matrix(1.3333 0 0 -1.3333 -76.311 313.34)"><g transform="translate(178.06 235.01)"><path d="m0 0-22.669-39.264-22.669 39.264h-75.491l98.16-170.02 98.16 170.02z" fill="#41b883"></path></g><g transform="translate(178.06 235.01)"><path d="m0 0-22.669-39.264-22.669 39.264h-36.227l58.896-102.01 58.896 102.01z" fill="#34495e"></path></g></g></svg>
|
||||||
|
</span>
|
||||||
|
<span>Vue</span>
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</toggle-visible>
|
||||||
|
<div id="js_1" class="block my-4">
|
||||||
|
<div class="mockup-code overflow-hidden pb-0 mt-4">
|
||||||
|
<span class="top-0 right-0 absolute flex">
|
||||||
|
<%= link_to 'Learn More', console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/embedding/form"), target: '_blank', data: { turbo: false }, class: 'btn btn-ghost text-gray-100 flex', rel: 'noopener' %>
|
||||||
|
<clipboard-copy data-text="<script src="<%= Docuseal::CDN_URL %>/js/form.js"></script>
|
||||||
|
|
||||||
|
<docuseal-form data-src="<%= start_form_url(slug: template.slug) %>"></docuseal-form>
|
||||||
|
">
|
||||||
|
<label class="btn btn-ghost text-gray-100">
|
||||||
|
<input type="radio" class="peer hidden">
|
||||||
|
<span class="peer-checked:hidden flex items-center space-x-2">
|
||||||
|
<%= svg_icon('copy', class: 'w-6 h-6 text-white') %>
|
||||||
|
<span class="hidden md:inline">
|
||||||
|
Copy
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span class="hidden peer-checked:flex items-center space-x-2">
|
||||||
|
<%= svg_icon('clipboard_copy', class: 'w-6 h-6 text-white') %>
|
||||||
|
<span class="hidden md:inline">
|
||||||
|
Copied
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</clipboard-copy>
|
||||||
|
|
||||||
|
</span>
|
||||||
|
<pre class="before:!m-0 pl-6 pb-4 overflow-auto"><code class="overflow-hidden w-full"><span style="color: #f4bf75"><script </span><span style="color: #6a9fb5">src=</span><span style="color: #90a959">"<%= Docuseal::CDN_URL %>/js/form.js"</span><span style="color: #f4bf75">></script></span>
|
||||||
|
|
||||||
|
<span style="color: #f4bf75"><docuseal-form</span> <span style="color: #6a9fb5">data-src=</span><span style="color: #90a959">"<%= start_form_url(slug: template.slug) %>"</span><span style="color: #f4bf75">></span><span style="color: #f4bf75"></docuseal-form></span>
|
||||||
|
</code></pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div id="react_1" class="block my-4 hidden">
|
||||||
|
<div class="mockup-code overflow-hidden pb-0 mt-4">
|
||||||
|
<span class="top-0 right-0 absolute flex">
|
||||||
|
<%= link_to 'Learn More', console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/embedding/form"), target: '_blank', data: { turbo: false }, class: 'btn btn-ghost text-gray-100 flex', rel: 'noopener' %>
|
||||||
|
<clipboard-copy data-text="import React from "react"
|
||||||
|
import { DocusealForm } from '@docuseal/react'
|
||||||
|
|
||||||
|
export function App() {
|
||||||
|
return (
|
||||||
|
<DocusealForm
|
||||||
|
src="<%= start_form_url(slug: template.slug) %>"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
">
|
||||||
|
<label class="btn btn-ghost text-white">
|
||||||
|
<input type="radio" class="peer hidden">
|
||||||
|
<span class="peer-checked:hidden flex items-center space-x-2">
|
||||||
|
<%= svg_icon('copy', class: 'w-6 h-6 text-white') %>
|
||||||
|
<span class="hidden md:inline">
|
||||||
|
Copy
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span class="hidden peer-checked:flex items-center space-x-2">
|
||||||
|
<%= svg_icon('clipboard_copy', class: 'w-6 h-6 text-white') %>
|
||||||
|
<span class="hidden md:inline">
|
||||||
|
Copied
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</clipboard-copy>
|
||||||
|
|
||||||
|
</span>
|
||||||
|
<pre class="before:!m-0 pl-6 pb-4 overflow-auto"><code class="overflow-hidden w-full"><span style="color: #aa759f">import</span> <span style="color: #d0d0d0;background-color: #151515">React</span> <span style="color: #aa759f">from</span> <span style="color: #90a959">"</span><span style="color: #90a959">react</span><span style="color: #90a959">"</span>
|
||||||
|
<span style="color: #aa759f">import</span> <span style="color: #d0d0d0">{</span> <span style="color: #d0d0d0;background-color: #151515">DocusealForm</span> <span style="color: #d0d0d0">}</span> <span style="color: #aa759f">from</span> <span style="color: #90a959">'</span><span style="color: #90a959">@docuseal/react</span><span style="color: #90a959">'</span>
|
||||||
|
|
||||||
|
<span style="color: #aa759f">export</span> <span style="color: #d28445">function</span> <span style="color: #d0d0d0;background-color: #151515">App</span><span style="color: #d0d0d0">()</span> <span style="color: #d0d0d0">{</span>
|
||||||
|
<span style="color: #aa759f">return </span><span style="color: #d0d0d0">(</span>
|
||||||
|
<span style="color: #d0d0d0"><</span><span style="color: #f4bf75">DocusealForm</span>
|
||||||
|
<span style="color: #6a9fb5">src</span><span style="color: #d0d0d0">=</span><span style="color: #90a959">"<%= start_form_url(slug: template.slug) %>"</span>
|
||||||
|
<span style="color: #d0d0d0">/></span>
|
||||||
|
<span style="color: #d0d0d0">);</span>
|
||||||
|
<span style="color: #d0d0d0">}</span>
|
||||||
|
</code></pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div id="vue_1" class="block my-4 hidden">
|
||||||
|
<div class="mockup-code overflow-hidden pb-0 mt-4">
|
||||||
|
<span class="top-0 right-0 absolute flex">
|
||||||
|
<%= link_to 'Learn More', console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/embedding/form"), target: '_blank', data: { turbo: false }, class: 'btn btn-ghost text-gray-100 flex', rel: 'noopener' %>
|
||||||
|
<clipboard-copy data-text="<template>
|
||||||
|
<DocusealForm
|
||||||
|
:src="'<%= start_form_url(slug: template.slug) %>'"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { DocusealForm } from '@docuseal/vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'App',
|
||||||
|
components: {
|
||||||
|
DocusealForm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
">
|
||||||
|
<label class="btn btn-ghost text-white">
|
||||||
|
<input type="radio" class="peer hidden">
|
||||||
|
<span class="peer-checked:hidden flex items-center space-x-2">
|
||||||
|
<%= svg_icon('copy', class: 'w-6 h-6 text-white') %>
|
||||||
|
<span class="hidden md:inline">
|
||||||
|
Copy
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span class="hidden peer-checked:flex items-center space-x-2">
|
||||||
|
<%= svg_icon('clipboard_copy', class: 'w-6 h-6 text-white') %>
|
||||||
|
<span class="hidden md:inline">
|
||||||
|
Copied
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</clipboard-copy>
|
||||||
|
|
||||||
|
</span>
|
||||||
|
<pre class="before:!m-0 pl-6 pb-4 overflow-auto"><code class="overflow-hidden w-full"><span style="color: #f4bf75"><template></span>
|
||||||
|
<span style="color: #f4bf75"><DocusealForm</span>
|
||||||
|
<span style="color: #6a9fb5">:src=</span><span style="color: #90a959">"'<%= start_form_url(slug: template.slug) %>'"</span>
|
||||||
|
<span style="color: #f4bf75">/></span>
|
||||||
|
<span style="color: #f4bf75"></template></span>
|
||||||
|
|
||||||
|
<span style="color: #f4bf75"><</span><span style="color: #f4bf75">script</span><span style="color: #f4bf75">></span>
|
||||||
|
<span style="color: #aa759f">import</span> <span style="color: #d0d0d0">{</span> <span style="color: #d0d0d0;background-color: #151515">DocusealForm</span> <span style="color: #d0d0d0">}</span> <span style="color: #aa759f">from</span> <span style="color: #90a959">'</span><span style="color: #90a959">@docuseal/vue</span><span style="color: #90a959">'</span>
|
||||||
|
|
||||||
|
<span style="color: #aa759f">export</span> <span style="color: #aa759f">default</span> <span style="color: #d0d0d0">{</span>
|
||||||
|
<span style="color: #6a9fb5">name</span><span style="color: #d0d0d0">:</span> <span style="color: #90a959">'</span><span style="color: #90a959">App</span><span style="color: #90a959">'</span><span style="color: #d0d0d0">,</span>
|
||||||
|
<span style="color: #6a9fb5">components</span><span style="color: #d0d0d0">:</span> <span style="color: #d0d0d0">{</span>
|
||||||
|
<span style="color: #d0d0d0;background-color: #151515">DocusealForm</span>
|
||||||
|
<span style="color: #d0d0d0">}</span>
|
||||||
|
<span style="color: #d0d0d0">}</span>
|
||||||
|
<span style="color: #f4bf75"></</span><span style="color: #f4bf75">script</span><span style="color: #f4bf75">></span>
|
||||||
|
</code></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
<% status_badges = { 'awaiting' => 'badge-info', 'sent' => 'badge-info', 'completed' => 'badge-success', 'opened' => 'badge-warning' } %>
|
<% status_badges = { 'awaiting' => 'badge-info', 'sent' => 'badge-info', 'completed' => 'badge-success', 'opened' => 'badge-warning' } %>
|
||||||
<a href="<%= submission_path(submission) %>" class="bg-base-200 w-full flex flex-col md:flex-row space-y-4 md:space-y-0 md:justify-between rounded-2xl px-6 py-5 md:items-center">
|
<a href="<%= submission_path(submission) %>" class="bg-base-200 w-full flex flex-col md:flex-row space-y-4 md:space-y-0 md:justify-between rounded-2xl px-5 md:px-6 py-5 md:items-center">
|
||||||
<% submitters = (submission.template_submitters || submission.template.submitters).filter_map { |item| submission.submitters.find { |e| e.uuid == item['uuid'] } } %>
|
<% 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 = submitters.all?(&:completed_at?) && submitters.size.positive? %>
|
||||||
<% if submitters.size == 1 %>
|
<% if submitters.size == 1 %>
|
||||||
<div>
|
<div>
|
||||||
<% submitter = submitters.first %>
|
<% submitter = submitters.first %>
|
||||||
<div class="flex items-center space-x-4">
|
<div class="flex items-center space-x-4">
|
||||||
<span class="flex items-center space-x-3">
|
<span class="flex flex-col md:flex-row md:items-center gap-3">
|
||||||
<div class="tooltip flex" data-tip="<%= l(submitter.status_event_at.in_time_zone(current_account.timezone), format: :short, locale: current_account.locale) %>">
|
<div class="tooltip flex" data-tip="<%= l(submitter.status_event_at.in_time_zone(current_account.timezone), format: :short, locale: current_account.locale) %>">
|
||||||
<span class="badge <%= status_badges[submitter.status] %> md:w-32 badge-lg bg-opacity-50 uppercase text-sm font-semibold">
|
<span class="badge <%= status_badges[submitter.status] %> md:w-32 badge-lg bg-opacity-50 uppercase text-sm font-semibold">
|
||||||
<%= submitter.status %>
|
<%= submitter.status %>
|
||||||
@@ -20,14 +20,14 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex space-x-2 items-center">
|
<div class="flex space-x-2 items-center">
|
||||||
<% if submitter.completed_at? %>
|
<% if submitter.completed_at? %>
|
||||||
<form onsubmit="event.preventDefault()">
|
<form onsubmit="event.preventDefault()" class="flex-1 md:flex-none">
|
||||||
<button onclick="event.stopPropagation()">
|
<button onclick="event.stopPropagation()" class="w-full md:w-fit">
|
||||||
<download-button data-src="<%= submitter_download_index_path(submitter.slug) %>" class="btn btn-sm btn-neutral text-white md:w-36">
|
<download-button data-src="<%= submitter_download_index_path(submitter.slug) %>" class="btn btn-sm btn-neutral text-white w-full md:w-36">
|
||||||
<span class="flex items-center justify-center space-x-2" data-target="download-button.defaultButton">
|
<span class="flex items-center justify-center space-x-1 md:space-x-2" data-target="download-button.defaultButton">
|
||||||
<%= svg_icon('download', class: 'w-5 h-5 stroke-2') %>
|
<%= svg_icon('download', class: 'w-5 h-5 stroke-2') %>
|
||||||
<span class="inline">Download</span>
|
<span class="inline">Download</span>
|
||||||
</span>
|
</span>
|
||||||
<span class="flex items-center justify-center space-x-2 hidden" data-target="download-button.loadingButton">
|
<span class="flex items-center justify-center space-x-1 md:space-x-2 hidden" data-target="download-button.loadingButton">
|
||||||
<%= svg_icon('loader', class: 'w-5 h-5 animate-spin') %>
|
<%= svg_icon('loader', class: 'w-5 h-5 animate-spin') %>
|
||||||
<span class="inline">Downloa...</span>
|
<span class="inline">Downloa...</span>
|
||||||
</span>
|
</span>
|
||||||
@@ -36,26 +36,30 @@
|
|||||||
</form>
|
</form>
|
||||||
<% else %>
|
<% else %>
|
||||||
<% if current_user.email == submitter.email %>
|
<% if current_user.email == submitter.email %>
|
||||||
<form data-turbo="false" method="get" action="<%= submit_form_url(slug: submitter.slug) %>">
|
<form data-turbo="false" method="get" action="<%= submit_form_url(slug: submitter.slug) %>" class="flex-1 md:flex-none">
|
||||||
<button onclick="event.stopPropagation()" class="btn btn-sm btn-neutral btn-outline bg-white md:w-36 flex">
|
<button onclick="event.stopPropagation()" class="btn btn-sm btn-neutral btn-outline bg-white w-full md:w-36 flex">
|
||||||
<span class="flex items-center justify-center space-x-2">
|
<span class="flex items-center justify-center space-x-1 md:space-x-2">
|
||||||
<%= svg_icon('writing_sign', class: 'w-4 h-4 stroke-2') %>
|
<%= svg_icon('writing_sign', class: 'w-4 h-4 stroke-2') %>
|
||||||
<span class="hidden md:inline">Sign now</span>
|
<span class="inline shrink-0">Sign now</span>
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
<% else %>
|
<% else %>
|
||||||
<%= render 'shared/clipboard_copy', text: submit_form_url(slug: submitter.slug), class: 'btn btn-sm btn-neutral text-white md:w-36 flex', icon_class: 'w-6 h-6 text-white', copy_title: 'Copy Link', copy_title_md: 'Copy', copied_title_md: 'Copied' %>
|
<div class="flex-1 md:flex-none">
|
||||||
|
<%= render 'shared/clipboard_copy', text: submit_form_url(slug: submitter.slug), class: 'btn btn-sm btn-neutral text-white md:w-36 flex', icon_class: 'w-6 h-6 text-white', copy_title: 'Copy Link', copy_title_md: 'Copy', copied_title_md: 'Copied' %>
|
||||||
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<span class="btn btn-outline btn-sm w-20 md:w-24">View</span>
|
<div class="flex-1 md:flex-none">
|
||||||
|
<span class="btn btn-outline btn-sm w-full md:w-24">View</span>
|
||||||
|
</div>
|
||||||
<% if !submission.archived_at? && can?(:destroy, submission) %>
|
<% if !submission.archived_at? && can?(:destroy, submission) %>
|
||||||
<%= button_to button_title(title: nil, disabled_with: 'Remov', icon: svg_icon('trash', class: 'w-6 h-6')), submission_path(submission), class: 'btn btn-outline btn-sm', form: { class: 'flex' }, title: 'Delete', method: :delete, data: { turbo_confirm: 'Are you sure?' }, onclick: 'event.stopPropagation()' %>
|
<%= button_to button_title(title: nil, disabled_with: 'Remov', icon: svg_icon('trash', class: 'w-6 h-6')), submission_path(submission), class: 'btn btn-outline btn-sm w-full md:w-fit', form: { class: 'flex' }, title: 'Delete', method: :delete, data: { turbo_confirm: 'Are you sure?' }, onclick: 'event.stopPropagation()' %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="space-y-1 w-full md:mr-2">
|
<div class="space-y-1 w-full md:mr-2">
|
||||||
<div class="flex items-center space-x-3">
|
<div class="flex flex-col md:flex-row md:items-center gap-3">
|
||||||
<% if is_submission_completed %>
|
<% if is_submission_completed %>
|
||||||
<% latest_submitter = submitters.select(&:completed_at?).max_by(&:completed_at) %>
|
<% latest_submitter = submitters.select(&:completed_at?).max_by(&:completed_at) %>
|
||||||
<div class="tooltip flex" data-tip="<%= l(latest_submitter.status_event_at.in_time_zone(current_account.timezone), format: :short, locale: current_account.locale) %>">
|
<div class="tooltip flex" data-tip="<%= l(latest_submitter.status_event_at.in_time_zone(current_account.timezone), format: :short, locale: current_account.locale) %>">
|
||||||
@@ -64,10 +68,10 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<div class="w-full">
|
<div class="w-full <%= is_submission_completed ? 'space-y-1' : 'space-y-4' %> md:space-y-0">
|
||||||
<% submitters.each_with_index do |submitter, index| %>
|
<% submitters.each_with_index do |submitter, index| %>
|
||||||
<div class="flex justify-between items-center space-x-3">
|
<div class="relative flex justify-between items-start md:items-center space-x-3">
|
||||||
<span class="flex items-center space-x-3">
|
<span class="flex flex-col md:flex-row md:items-center gap-2">
|
||||||
<% unless is_submission_completed %>
|
<% unless is_submission_completed %>
|
||||||
<div class="tooltip flex" data-tip="<%= l(submitter.status_event_at.in_time_zone(current_account.timezone), format: :short, locale: current_account.locale) %>">
|
<div class="tooltip flex" data-tip="<%= l(submitter.status_event_at.in_time_zone(current_account.timezone), format: :short, locale: current_account.locale) %>">
|
||||||
<span class="badge md:w-24 <%= status_badges[submitter.status] %> bg-opacity-50 uppercase text-xs font-semibold">
|
<span class="badge md:w-24 <%= status_badges[submitter.status] %> bg-opacity-50 uppercase text-xs font-semibold">
|
||||||
@@ -82,31 +86,31 @@
|
|||||||
<% if submitter.completed_at? && !is_submission_completed %>
|
<% if submitter.completed_at? && !is_submission_completed %>
|
||||||
<form onsubmit="event.preventDefault()">
|
<form onsubmit="event.preventDefault()">
|
||||||
<button onclick="event.stopPropagation()">
|
<button onclick="event.stopPropagation()">
|
||||||
<download-button data-src="<%= submitter_download_index_path(submitter.slug) %>" class="btn btn-xs btn-neutral text-white md:w-36">
|
<download-button data-src="<%= submitter_download_index_path(submitter.slug) %>" class="absolute md:relative top-0 right-0 btn btn-xs btn-neutral text-white md:w-36">
|
||||||
<span class="flex items-center justify-center space-x-2" data-target="download-button.defaultButton">
|
<span class="flex items-center justify-center space-x-1 md:space-x-2" data-target="download-button.defaultButton">
|
||||||
<%= svg_icon('download', class: 'w-4 h-4 stroke-2') %>
|
<%= svg_icon('download', class: 'w-4 h-4 stroke-2') %>
|
||||||
<span class="hidden md:inline">Download</span>
|
<span class="inline">Download</span>
|
||||||
</span>
|
</span>
|
||||||
<span class="flex items-center justify-center space-x-2 hidden" data-target="download-button.loadingButton">
|
<span class="flex items-center justify-center space-x-1 md:space-x-2 hidden" data-target="download-button.loadingButton">
|
||||||
<%= svg_icon('loader', class: 'w-4 h-4 animate-spin') %>
|
<%= svg_icon('loader', class: 'w-4 h-4 animate-spin') %>
|
||||||
<span class="hidden md:inline">Downloa...</span>
|
<span class="inline">Downloa...</span>
|
||||||
</span>
|
</span>
|
||||||
</download-button>
|
</download-button>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
<% elsif !is_submission_completed %>
|
<% elsif !is_submission_completed %>
|
||||||
<div class="flex items-center space-x-3">
|
<div class="relative flex items-center space-x-3">
|
||||||
<% if current_user.email == submitter.email %>
|
<% if current_user.email == submitter.email %>
|
||||||
<form data-turbo="false" method="get" action="<%= submit_form_url(slug: submitter.slug) %>">
|
<form data-turbo="false" method="get" action="<%= submit_form_url(slug: submitter.slug) %>">
|
||||||
<button onclick="event.stopPropagation()" class="btn btn-xs btn-outline btn-neutral bg-white md:w-36">
|
<button onclick="event.stopPropagation()" class="absolute md:relative top-0 right-0 btn btn-xs btn-outline btn-neutral bg-white w-28 md:w-36">
|
||||||
<span class="flex items-center justify-center space-x-2">
|
<span class="flex items-center justify-center space-x-1 md:space-x-2">
|
||||||
<%= svg_icon('writing_sign', class: 'w-4 h-4 stroke-2') %>
|
<%= svg_icon('writing_sign', class: 'w-4 h-4 stroke-2') %>
|
||||||
<span class="hidden md:inline">Sign now</span>
|
<span class="inline shrink-0">Sign now</span>
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
<% else %>
|
<% else %>
|
||||||
<%= render 'shared/clipboard_copy', text: submit_form_url(slug: submitter.slug), class: 'btn btn-xs text-xs btn-neutral text-white md:w-36 flex', icon_class: 'w-4 h-4 text-white', copy_title: 'Copy Link' %>
|
<%= render 'shared/clipboard_copy', text: submit_form_url(slug: submitter.slug), class: 'absolute md:relative top-0 right-0 btn btn-xs text-xs btn-neutral text-white w-28 md:w-36 flex', icon_class: 'w-4 h-4 text-white', copy_title: 'Copy Link', copy_title_md: 'Copy Link', copied_title_md: 'Copied' %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
@@ -115,27 +119,31 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex space-x-2 items-center">
|
<div class="flex space-x-1 md:space-x-2 items-center">
|
||||||
<% if is_submission_completed %>
|
<% if is_submission_completed %>
|
||||||
<% latest_submitter = submitters.select(&:completed_at?).max_by(&:completed_at) %>
|
<% latest_submitter = submitters.select(&:completed_at?).max_by(&:completed_at) %>
|
||||||
<form onsubmit="event.preventDefault()">
|
<div class="flex-1 md:flex-none">
|
||||||
<button onclick="event.stopPropagation()">
|
<form onsubmit="event.preventDefault()" class="w-full md:w-fit">
|
||||||
<download-button data-src="<%= submitter_download_index_path(latest_submitter.slug) %>" class="btn btn-sm btn-neutral text-white md:w-36">
|
<button onclick="event.stopPropagation()">
|
||||||
<span class="flex items-center justify-center space-x-2" data-target="download-button.defaultButton">
|
<download-button data-src="<%= submitter_download_index_path(latest_submitter.slug) %>" class="btn btn-sm btn-neutral text-white md:w-36">
|
||||||
<%= svg_icon('download', class: 'w-5 h-5 stroke-2') %>
|
<span class="flex items-center justify-center space-x-1 md:space-x-2" data-target="download-button.defaultButton">
|
||||||
<span class="inline">Download</span>
|
<%= svg_icon('download', class: 'w-5 h-5 stroke-2') %>
|
||||||
</span>
|
<span class="inline">Download</span>
|
||||||
<span class="flex items-center justify-center space-x-2 hidden" data-target="download-button.loadingButton">
|
</span>
|
||||||
<%= svg_icon('loader', class: 'w-5 h-5 animate-spin') %>
|
<span class="flex items-center justify-center space-x-1 md:space-x-2 hidden" data-target="download-button.loadingButton">
|
||||||
<span class="inline">Downloa...</span>
|
<%= svg_icon('loader', class: 'w-5 h-5 animate-spin') %>
|
||||||
</span>
|
<span class="inline">Downloa...</span>
|
||||||
</download-button>
|
</span>
|
||||||
</button>
|
</download-button>
|
||||||
</form>
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<span class="btn btn-outline btn-sm w-20 md:w-24">View</span>
|
<div class="flex-1 md:flex-none">
|
||||||
|
<span class="btn btn-outline btn-sm w-full md:w-24">View</span>
|
||||||
|
</div>
|
||||||
<% unless submission.archived_at? %>
|
<% unless submission.archived_at? %>
|
||||||
<%= button_to button_title(title: nil, disabled_with: 'Remov', icon: svg_icon('trash', class: 'w-6 h-6')), submission_path(submission), class: 'btn btn-outline btn-sm', form: { class: 'flex' }, title: 'Delete', method: :delete, data: { turbo_confirm: 'Are you sure?' }, onclick: 'event.stopPropagation()' %>
|
<%= button_to button_title(title: nil, disabled_with: 'Remov', icon: svg_icon('trash', class: 'w-6 h-6')), submission_path(submission), class: 'btn btn-outline btn-sm w-full md:w-fit', form: { class: 'flex' }, title: 'Delete', method: :delete, data: { turbo_confirm: 'Are you sure?' }, onclick: 'event.stopPropagation()' %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -7,6 +7,9 @@
|
|||||||
<p class="flex items-center space-x-1 text-xs text-base-content/60">
|
<p class="flex items-center space-x-1 text-xs text-base-content/60">
|
||||||
<%= svg_icon('user', class: 'w-4 h-4') %>
|
<%= svg_icon('user', class: 'w-4 h-4') %>
|
||||||
<span><%= template.author.full_name.presence || template.author.email.to_s.sub(/\+\w+@/, '@') %></span>
|
<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">shared</span>
|
||||||
|
<% end %>
|
||||||
</p>
|
</p>
|
||||||
<p class="flex text-xs text-base-content/60">
|
<p class="flex text-xs text-base-content/60">
|
||||||
<span class="flex items-center space-x-1 w-1/2">
|
<span class="flex items-center space-x-1 w-1/2">
|
||||||
@@ -24,7 +27,7 @@
|
|||||||
</a>
|
</a>
|
||||||
<div class="absolute top-0 bottom-0 w-0 flex items-center hidden md:group-hover:flex" style="right: 37px">
|
<div class="absolute top-0 bottom-0 w-0 flex items-center hidden md:group-hover:flex" style="right: 37px">
|
||||||
<div class="space-y-1">
|
<div class="space-y-1">
|
||||||
<% if can?(:update, template) && !template.archived_at? %>
|
<% if can?(:update, template) && !template.archived_at? && template.account_id == current_account.id %>
|
||||||
<span class="tooltip tooltip-left" data-tip="Move">
|
<span class="tooltip tooltip-left" data-tip="Move">
|
||||||
<a href="<%= edit_template_folder_path(template.id) %>" data-turbo-frame="modal" class="btn btn-xs hover:btn-outline bg-base-200 btn-circle">
|
<a href="<%= edit_template_folder_path(template.id) %>" data-turbo-frame="modal" class="btn btn-xs hover:btn-outline bg-base-200 btn-circle">
|
||||||
<%= svg_icon('folder_share', class: 'w-4 h-4') %>
|
<%= svg_icon('folder_share', class: 'w-4 h-4') %>
|
||||||
|
|||||||
@@ -1,50 +1,86 @@
|
|||||||
<div class="flex flex-col items-start md:flex-row space-y-2 md:space-y-0 md:justify-between md:items-start mb-3">
|
<div class="flex flex-col items-start md:flex-row space-y-2 md:space-y-0 md:space-x-2 md:justify-between md:items-start mb-6 md:mb-3">
|
||||||
<div>
|
<div class="relative flex items-start justify-between w-full space-x-0">
|
||||||
<h1 class="text-4xl font-semibold mr-4" style="overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;">
|
<div>
|
||||||
<%= template.name %>
|
<h1 class="text-4xl font-semibold" style="overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;">
|
||||||
<% if template.archived_at? %>
|
<%= template.name %>
|
||||||
<span class="badge badge-outline badge-lg align-middle">Archived</span>
|
<% if template.archived_at? %>
|
||||||
<% end %>
|
<span class="badge badge-outline badge-lg align-middle">Archived</span>
|
||||||
</h1>
|
<% end %>
|
||||||
<div class="flex items-center">
|
</h1>
|
||||||
<a href="<%= folder_path(@template.folder) %>" class="flex items-center space-x-1 mt-1 peer">
|
<% if @template.account_id == current_account.id %>
|
||||||
<%= svg_icon('folder', class: 'w-5 h-5 flex-shrink-0') %>
|
<div class="flex items-center justify-between">
|
||||||
<span class="text-sm">
|
<div class="flex items-center">
|
||||||
<%= @template.folder.name %>
|
<a href="<%= folder_path(@template.folder) %>" class="flex items-center space-x-1 mt-1 peer">
|
||||||
</span>
|
<%= svg_icon('folder', class: 'w-5 h-5 flex-shrink-0') %>
|
||||||
</a>
|
<span class="text-sm">
|
||||||
<% if can?(:update, template) %>
|
<%= @template.folder.name %>
|
||||||
<span class="pl-1 tooltip tooltip-right md:opacity-0 hover:opacity-100 peer-hover:opacity-100" data-tip="Move">
|
</span>
|
||||||
<a href="<%= edit_template_folder_path(template.id) %>" data-turbo-frame="modal">
|
</a>
|
||||||
<%= svg_icon('pencil_share', class: 'w-5 h-5') %>
|
<% if can?(:update, template) %>
|
||||||
</a>
|
<span class="pl-1 tooltip tooltip-right md:opacity-0 hover:opacity-100 peer-hover:opacity-100" data-tip="Move">
|
||||||
</span>
|
<a href="<%= edit_template_folder_path(template.id) %>" data-turbo-frame="modal">
|
||||||
|
<%= svg_icon('pencil_share', class: 'w-5 h-5') %>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<% 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">shared</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div class="flex md:justify-between space-x-2 flex-none pt-1">
|
|
||||||
<% if !template.archived_at? %>
|
<% if !template.archived_at? %>
|
||||||
<%= render 'shared/clipboard_copy', text: start_form_url(slug: @template.slug), class: 'btn btn-sm btn-neutral text-white', icon_class: 'w-6 h-6 text-white', copy_title: 'Copy Link', copied_title: 'Copied', copy_title_md: 'Link', copied_title_md: 'Copied' %>
|
<div class="flex items-center space-x-2">
|
||||||
|
<% if can?(:update, template) && (Docuseal.multitenant? || current_account.testing? || current_account.id == 1) %>
|
||||||
|
<div class="tooltip" data-tip="API and Embedding">
|
||||||
|
<%= link_to template_code_modal_path(template), class: 'btn btn-ghost btn-sm flex-1 hidden md:flex', data: { turbo_frame: :modal } do %>
|
||||||
|
<span class="flex items-center justify-center space-x-2">
|
||||||
|
<%= svg_icon('code', class: 'w-6 h-6') %>
|
||||||
|
</span>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
<%= render 'shared/clipboard_copy', text: start_form_url(slug: @template.slug), class: 'absolute md:relative bottom-0 right-0 btn btn-xs md:btn-sm whitespace-nowrap btn-neutral text-white mt-1 px-2', icon_class: 'w-4 h-4 md:w-6 md:h-6 text-white', copy_title: 'Link', copied_title: 'Copied', copy_title_md: 'Link', copied_title_md: 'Copied' %>
|
||||||
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<div class="flex space-x-2 w-full md:w-fit md:justify-between md:flex-none md:pt-1">
|
||||||
<% if !template.archived_at? && can?(:destroy, template) %>
|
<% if !template.archived_at? && can?(:destroy, template) %>
|
||||||
<%= button_to button_title(title: 'Archive', disabled_with: 'Archiving', title_class: 'hidden md:inline', icon: svg_icon('archive', class: 'w-6 h-6')), template_path(template), class: 'btn btn-outline btn-sm', method: :delete, data: { turbo_confirm: 'Are you sure?' } %>
|
<%= button_to button_title(title: 'Archive', disabled_with: 'Archiving', title_class: 'inline', icon: svg_icon('archive', class: 'w-6 h-6')), template_path(template), class: 'btn btn-outline btn-sm w-full', form_class: 'flex-1', method: :delete, data: { turbo_confirm: 'Are you sure?' } %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if can?(:create, template) %>
|
<% if can?(:create, template) %>
|
||||||
<%= link_to new_template_path(base_template_id: template.id), class: 'btn btn-outline btn-sm', data: { turbo_frame: :modal } do %>
|
<%= link_to new_template_path(base_template_id: template.id), class: 'btn btn-outline btn-sm flex-1', data: { turbo_frame: :modal } do %>
|
||||||
<%= svg_icon('copy', class: 'w-6 h-6') %>
|
<span class="flex items-center justify-center space-x-2">
|
||||||
<span class="hidden md:inline">Clone</span>
|
<%= svg_icon('copy', class: 'w-6 h-6') %>
|
||||||
|
<span class="inline">Clone</span>
|
||||||
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if !template.archived_at? && can?(:update, template) %>
|
<% if !template.archived_at? %>
|
||||||
<%= link_to edit_template_path(template), class: 'btn btn-outline btn-sm' do %>
|
<% if can?(:update, template) %>
|
||||||
<span class="flex items-center justify-center space-x-2">
|
<%= link_to edit_template_path(template), class: 'btn btn-outline btn-sm flex-1' do %>
|
||||||
<%= svg_icon('pencil', class: 'w-6 h-6') %>
|
<span class="flex items-center justify-center space-x-2">
|
||||||
<span>Edit</span>
|
<%= svg_icon('pencil', class: 'w-6 h-6') %>
|
||||||
</span>
|
<span>Edit</span>
|
||||||
|
</span>
|
||||||
|
<% end %>
|
||||||
|
<% elsif can?(:read, template) %>
|
||||||
|
<%= link_to template_preview_path(template), class: 'btn btn-outline btn-sm flex-1' do %>
|
||||||
|
<span class="flex items-center justify-center space-x-2">
|
||||||
|
<%= svg_icon('file_text', class: 'w-6 h-6') %>
|
||||||
|
<span>Preview</span>
|
||||||
|
</span>
|
||||||
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if template.archived_at? && can?(:create, template) %>
|
<% if template.archived_at? && can?(:create, template) %>
|
||||||
<%= button_to button_title(title: 'Restore', disabled_with: 'Restoring', icon: svg_icon('rotate', class: 'w-6 h-6')), template_restore_index_path(template), class: 'btn btn-outline btn-sm' %>
|
<%= button_to button_title(title: 'Restore', disabled_with: 'Restoring', icon: svg_icon('rotate', class: 'w-6 h-6')), template_restore_index_path(template), class: 'btn btn-outline btn-sm flex-1' %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1,22 +1,24 @@
|
|||||||
<%= render 'title', template: @template %>
|
<%= render 'title', template: @template %>
|
||||||
<% is_show_tabs = @pagy.pages > 1 || params[:status].present? %>
|
<% is_show_tabs = @pagy.pages > 1 || params[:status].present? %>
|
||||||
<% if !@pagy.count.zero? || params[:q].present? %>
|
<% if !@pagy.count.zero? || params[:q].present? %>
|
||||||
<div class="flex justify-between md:items-end flex-col md:flex-row <%= is_show_tabs ? 'mb-4' : 'mb-6' %>">
|
<div class="<%= is_show_tabs ? 'mb-4' : 'mb-6' %>">
|
||||||
<p class="text-3xl font-bold">Submissions</p>
|
<div class="flex justify-between items-center md:items-end">
|
||||||
<div class="flex justify-end space-x-2 mt-3 md:mt-0">
|
<p class="text-3xl font-bold">Submissions</p>
|
||||||
<% if params[:q].present? || params[:status].present? || @pagy.pages > 1 %>
|
<div class="flex justify-end space-x-2">
|
||||||
<%= render 'shared/search_input' %>
|
<% if params[:q].present? || params[:status].present? || @pagy.pages > 1 %>
|
||||||
<% end %>
|
<%= render 'shared/search_input' %>
|
||||||
<%= link_to new_template_submissions_export_path(@template), class: 'btn btn-ghost text-base', data: { turbo_frame: 'modal' } do %>
|
|
||||||
<%= svg_icon('download', class: 'w-6 h-6 stroke-2') %>
|
|
||||||
<span>Export</span>
|
|
||||||
<% end %>
|
|
||||||
<% if !@template.archived_at? && can?(:create, Submission) %>
|
|
||||||
<%= link_to new_template_submission_path(@template), class: 'white-button !border', data: { turbo_frame: 'modal' } do %>
|
|
||||||
<%= svg_icon('plus', class: 'w-6 h-6 stroke-2') %>
|
|
||||||
<span>Add <span class="hidden md:inline">Recipients</span></span>
|
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<%= link_to new_template_submissions_export_path(@template), class: 'hidden md:flex btn btn-ghost text-base', data: { turbo_frame: 'modal' } do %>
|
||||||
|
<%= svg_icon('download', class: 'w-6 h-6 stroke-2') %>
|
||||||
|
<span>Export</span>
|
||||||
|
<% end %>
|
||||||
|
<% if !@template.archived_at? && can?(:create, Submission) %>
|
||||||
|
<%= link_to new_template_submission_path(@template), class: 'white-button !border', data: { turbo_frame: 'modal' } do %>
|
||||||
|
<%= svg_icon('plus', class: 'w-6 h-6 stroke-2') %>
|
||||||
|
<span>Add <span class="hidden md:inline">Recipients</span></span>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<div class="alert">
|
||||||
|
<%= svg_icon('info_circle', class: 'w-6 h-6') %>
|
||||||
|
<div>
|
||||||
|
<p class="font-bold">API and Embedding</p>
|
||||||
|
<p class="text-gray-700">
|
||||||
|
Unlock with DocuSeal Pro
|
||||||
|
<br>
|
||||||
|
<a class="link font-medium" target="_blank" href="<%= Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premise" }.to_query}" %>">Learn More</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
Whitespace-only changes.
@@ -0,0 +1,36 @@
|
|||||||
|
<%= render 'shared/turbo_modal', title: 'API and Embedding', close_after_submit: false do %>
|
||||||
|
<div>
|
||||||
|
<label class="text-sm font-semibold" for="template_id">
|
||||||
|
Template ID
|
||||||
|
</label>
|
||||||
|
<div class="flex gap-2 mb-4 mt-2">
|
||||||
|
<input id="template_id" type="text" value="<%= @template.id %>" class="base-input w-full" autocomplete="off" readonly>
|
||||||
|
<%= render 'shared/clipboard_copy', icon: 'copy', text: @template.id, class: 'base-button', icon_class: 'w-6 h-6 text-white', copy_title: 'Copy', copied_title: 'Copied' %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="text-sm font-semibold" for="embedding_url">
|
||||||
|
Embedding URL
|
||||||
|
</label>
|
||||||
|
<div class="flex gap-2 mb-4 mt-2">
|
||||||
|
<input id="embedding_url" type="text" value="<%= start_form_url(slug: @template.slug) %>" class="base-input w-full" autocomplete="off" readonly>
|
||||||
|
<%= render 'shared/clipboard_copy', icon: 'copy', text: start_form_url(slug: @template.slug), class: 'base-button', icon_class: 'w-6 h-6 text-white', copy_title: 'Copy', copied_title: 'Copied' %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<%= render 'templates/embedding', template: @template %>
|
||||||
|
<% if can?(:manage, TemplateSharing.new(template: @template)) %>
|
||||||
|
<%= form_for '', url: template_sharings_testing_index_path, method: :post, html: { class: 'mt-1' } do |f| %>
|
||||||
|
<%= f.hidden_field :template_id, value: @template.id %>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<span>
|
||||||
|
Share template with Test Environment
|
||||||
|
</span>
|
||||||
|
<%= f.check_box :value, class: 'toggle', checked: @template.template_sharings.exists?(account_id: current_account.testing_accounts), onchange: 'this.form.requestSubmit()' %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
<div class="mb-4">
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
<%= render 'templates_code_modal/preferences' %>
|
||||||
|
<%= render 'templates_code_modal/placeholder' %>
|
||||||
|
<% end %>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<template-builder class="grid" data-editable="false" data-is-direct-upload="<%= Docuseal.active_storage_public? %>" data-template="<%= @template_data %>"></template-builder>
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
<option value="editor" disabled>Editor</option>
|
<option value="editor" disabled>Editor</option>
|
||||||
<option value="viewer" disabled>Viewer</option>
|
<option value="viewer" disabled>Viewer</option>
|
||||||
<% end %>
|
<% end %>
|
||||||
<a class="text-sm mt-3 px-4 py-2 bg-base-300 rounded-full block" target="_blank" href="<%= "#{Docuseal::CONSOLE_URL}/#{Docuseal.multitenant? ? 'plans' : 'on_premise'}" %>">
|
<a class="text-sm mt-3 px-4 py-2 bg-base-300 rounded-full block" target="_blank" href="<%= Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premise" }.to_query}" %>">
|
||||||
<%= svg_icon('info_circle', class: 'w-4 h-4 inline align-text-bottom') %>
|
<%= svg_icon('info_circle', class: 'w-4 h-4 inline align-text-bottom') %>
|
||||||
Unlock more user roles with DocuSeal Pro.
|
Unlock more user roles with DocuSeal Pro.
|
||||||
<span class="link font-medium">Learn More</a>
|
<span class="link font-medium">Learn More</a>
|
||||||
|
|||||||
@@ -121,12 +121,26 @@ Rails.application.configure do
|
|||||||
config.active_record.dump_schema_after_migration = false
|
config.active_record.dump_schema_after_migration = false
|
||||||
|
|
||||||
config.lograge.enabled = true
|
config.lograge.enabled = true
|
||||||
config.lograge.formatter = Lograge::Formatters::Json.new
|
|
||||||
config.lograge.base_controller_class = ['ActionController::API', 'ActionController::Base']
|
config.lograge.base_controller_class = ['ActionController::API', 'ActionController::Base']
|
||||||
|
|
||||||
config.lograge.custom_payload do |controller|
|
if ENV['MULTITENANT'] == 'true'
|
||||||
{
|
config.lograge.formatter = ->(data) { data.except(:path, :location).to_json }
|
||||||
fwd: controller.request.ip
|
|
||||||
}
|
config.lograge.custom_payload do |controller|
|
||||||
|
{
|
||||||
|
fwd: controller.request.ip.to_s[/\A\d+\.(.*)/, 1],
|
||||||
|
params: controller.request.params&.slice(:id),
|
||||||
|
host: controller.request.host,
|
||||||
|
uid: controller.instance_variable_get(:@current_user).try(:id)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
else
|
||||||
|
config.lograge.formatter = Lograge::Formatters::Json.new
|
||||||
|
|
||||||
|
config.lograge.custom_payload do |controller|
|
||||||
|
{
|
||||||
|
fwd: controller.request.ip
|
||||||
|
}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -4,6 +4,10 @@ ActiveSupport.on_load(:active_storage_attachment) do
|
|||||||
attribute :uuid, :string, default: -> { SecureRandom.uuid }
|
attribute :uuid, :string, default: -> { SecureRandom.uuid }
|
||||||
|
|
||||||
has_many_attached :preview_images
|
has_many_attached :preview_images
|
||||||
|
|
||||||
|
def preview_image_url
|
||||||
|
preview_images.joins(:blob).find_by(blob: { filename: '0.jpg' })&.url
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
ActiveStorage::LogSubscriber.detach_from(:active_storage) if Rails.env.production?
|
ActiveStorage::LogSubscriber.detach_from(:active_storage) if Rails.env.production?
|
||||||
|
|||||||
@@ -216,7 +216,7 @@ nl:
|
|||||||
signed_on_time: 'Ondertekend op %{time}'
|
signed_on_time: 'Ondertekend op %{time}'
|
||||||
form_has_been_submitted_already: Formulier is al ingediend
|
form_has_been_submitted_already: Formulier is al ingediend
|
||||||
send_copy_to_email: Stuur kopie naar E-mail
|
send_copy_to_email: Stuur kopie naar E-mail
|
||||||
sending: Verzenden
|
sending: Voltooien
|
||||||
resubmit: Opnieuw indienen
|
resubmit: Opnieuw indienen
|
||||||
form_has_been_deleted_by_html: 'Formulier is verwijderd door <span class="font-semibold">%{name}</span>.'
|
form_has_been_deleted_by_html: 'Formulier is verwijderd door <span class="font-semibold">%{name}</span>.'
|
||||||
or: of
|
or: of
|
||||||
|
|||||||
@@ -70,11 +70,14 @@ Rails.application.routes.draw do
|
|||||||
end
|
end
|
||||||
resources :templates_archived, only: %i[index], path: 'archived'
|
resources :templates_archived, only: %i[index], path: 'archived'
|
||||||
resources :folders, only: %i[show edit update destroy], controller: 'template_folders'
|
resources :folders, only: %i[show edit update destroy], controller: 'template_folders'
|
||||||
|
resources :template_sharings_testing, only: %i[create]
|
||||||
resources :templates, only: %i[new create edit show destroy] do
|
resources :templates, only: %i[new create edit show destroy] do
|
||||||
resources :restore, only: %i[create], controller: 'templates_restore'
|
resources :restore, only: %i[create], controller: 'templates_restore'
|
||||||
resources :archived, only: %i[index], controller: 'templates_archived_submissions'
|
resources :archived, only: %i[index], controller: 'templates_archived_submissions'
|
||||||
resources :submissions, only: %i[new create]
|
resources :submissions, only: %i[new create]
|
||||||
resource :folder, only: %i[edit update], controller: 'templates_folders'
|
resource :folder, only: %i[edit update], controller: 'templates_folders'
|
||||||
|
resource :preview, only: %i[show], controller: 'templates_preview'
|
||||||
|
resource :code_modal, only: %i[show], controller: 'templates_code_modal'
|
||||||
resources :submissions_export, only: %i[index new]
|
resources :submissions_export, only: %i[index new]
|
||||||
end
|
end
|
||||||
resources :preview_document_page, only: %i[show], path: '/preview/:attachment_uuid'
|
resources :preview_document_page, only: %i[show], path: '/preview/:attachment_uuid'
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class RenameApplicationKeyToExternalId < ActiveRecord::Migration[7.1]
|
||||||
|
def change
|
||||||
|
rename_column :templates, :application_key, :external_id
|
||||||
|
rename_column :submitters, :application_key, :external_id
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AddSubmitterMetadata < ActiveRecord::Migration[7.1]
|
||||||
|
class MigrationSubmitter < ApplicationRecord
|
||||||
|
self.table_name = 'submitters'
|
||||||
|
end
|
||||||
|
|
||||||
|
def change
|
||||||
|
add_column :submitters, :metadata, :text
|
||||||
|
|
||||||
|
MigrationSubmitter.where(metadata: nil).update_all(metadata: '{}')
|
||||||
|
|
||||||
|
change_column_null :submitters, :metadata, false
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AddAccountIdToSubmissions < ActiveRecord::Migration[7.1]
|
||||||
|
class MigrationSubmission < ApplicationRecord
|
||||||
|
self.table_name = 'submissions'
|
||||||
|
end
|
||||||
|
|
||||||
|
class MigrationTemplate < ApplicationRecord
|
||||||
|
self.table_name = 'templates'
|
||||||
|
end
|
||||||
|
|
||||||
|
class MigrationAccount < ApplicationRecord
|
||||||
|
self.table_name = 'accounts'
|
||||||
|
end
|
||||||
|
|
||||||
|
def change
|
||||||
|
add_reference :submissions, :account, index: true, null: true
|
||||||
|
|
||||||
|
MigrationAccount.all.each do |account|
|
||||||
|
MigrationSubmission.where(template_id: MigrationTemplate.where(account_id: account.id).select(:id))
|
||||||
|
.update_all(account_id: account.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
change_column_null :submissions, :account_id, false
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class CreateTemplateSharings < ActiveRecord::Migration[7.1]
|
||||||
|
def change
|
||||||
|
create_table :template_sharings do |t|
|
||||||
|
t.references :template, null: false, foreign_key: true, index: true
|
||||||
|
t.references :account, null: false, foreign_key: false, index: false
|
||||||
|
t.string :ability, null: false
|
||||||
|
|
||||||
|
t.index %i[account_id template_id], unique: true
|
||||||
|
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
+17
-3
@@ -10,7 +10,7 @@
|
|||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema[7.1].define(version: 2024_01_20_192055) do
|
ActiveRecord::Schema[7.1].define(version: 2024_02_03_113455) do
|
||||||
# These are extensions that must be enabled in order to support this database
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "plpgsql"
|
enable_extension "plpgsql"
|
||||||
|
|
||||||
@@ -151,6 +151,8 @@ ActiveRecord::Schema[7.1].define(version: 2024_01_20_192055) do
|
|||||||
t.string "submitters_order", null: false
|
t.string "submitters_order", null: false
|
||||||
t.string "slug", null: false
|
t.string "slug", null: false
|
||||||
t.text "preferences", null: false
|
t.text "preferences", null: false
|
||||||
|
t.bigint "account_id", null: false
|
||||||
|
t.index ["account_id"], name: "index_submissions_on_account_id"
|
||||||
t.index ["created_by_user_id"], name: "index_submissions_on_created_by_user_id"
|
t.index ["created_by_user_id"], name: "index_submissions_on_created_by_user_id"
|
||||||
t.index ["slug"], name: "index_submissions_on_slug", unique: true
|
t.index ["slug"], name: "index_submissions_on_slug", unique: true
|
||||||
t.index ["template_id"], name: "index_submissions_on_template_id"
|
t.index ["template_id"], name: "index_submissions_on_template_id"
|
||||||
@@ -171,8 +173,9 @@ ActiveRecord::Schema[7.1].define(version: 2024_01_20_192055) do
|
|||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
t.string "name"
|
t.string "name"
|
||||||
t.string "phone"
|
t.string "phone"
|
||||||
t.string "application_key"
|
t.string "external_id"
|
||||||
t.text "preferences", null: false
|
t.text "preferences", null: false
|
||||||
|
t.text "metadata", null: false
|
||||||
t.index ["email"], name: "index_submitters_on_email"
|
t.index ["email"], name: "index_submitters_on_email"
|
||||||
t.index ["slug"], name: "index_submitters_on_slug", unique: true
|
t.index ["slug"], name: "index_submitters_on_slug", unique: true
|
||||||
t.index ["submission_id"], name: "index_submitters_on_submission_id"
|
t.index ["submission_id"], name: "index_submitters_on_submission_id"
|
||||||
@@ -189,6 +192,16 @@ ActiveRecord::Schema[7.1].define(version: 2024_01_20_192055) do
|
|||||||
t.index ["author_id"], name: "index_template_folders_on_author_id"
|
t.index ["author_id"], name: "index_template_folders_on_author_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
create_table "template_sharings", force: :cascade do |t|
|
||||||
|
t.bigint "template_id", null: false
|
||||||
|
t.bigint "account_id", null: false
|
||||||
|
t.string "ability", null: false
|
||||||
|
t.datetime "created_at", null: false
|
||||||
|
t.datetime "updated_at", null: false
|
||||||
|
t.index ["account_id", "template_id"], name: "index_template_sharings_on_account_id_and_template_id", unique: true
|
||||||
|
t.index ["template_id"], name: "index_template_sharings_on_template_id"
|
||||||
|
end
|
||||||
|
|
||||||
create_table "templates", force: :cascade do |t|
|
create_table "templates", force: :cascade do |t|
|
||||||
t.string "slug", null: false
|
t.string "slug", null: false
|
||||||
t.string "name", null: false
|
t.string "name", null: false
|
||||||
@@ -202,7 +215,7 @@ ActiveRecord::Schema[7.1].define(version: 2024_01_20_192055) do
|
|||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
t.text "source", null: false
|
t.text "source", null: false
|
||||||
t.bigint "folder_id", null: false
|
t.bigint "folder_id", null: false
|
||||||
t.string "application_key"
|
t.string "external_id"
|
||||||
t.index ["account_id"], name: "index_templates_on_account_id"
|
t.index ["account_id"], name: "index_templates_on_account_id"
|
||||||
t.index ["author_id"], name: "index_templates_on_author_id"
|
t.index ["author_id"], name: "index_templates_on_author_id"
|
||||||
t.index ["folder_id"], name: "index_templates_on_folder_id"
|
t.index ["folder_id"], name: "index_templates_on_folder_id"
|
||||||
@@ -269,6 +282,7 @@ ActiveRecord::Schema[7.1].define(version: 2024_01_20_192055) do
|
|||||||
add_foreign_key "submitters", "submissions"
|
add_foreign_key "submitters", "submissions"
|
||||||
add_foreign_key "template_folders", "accounts"
|
add_foreign_key "template_folders", "accounts"
|
||||||
add_foreign_key "template_folders", "users", column: "author_id"
|
add_foreign_key "template_folders", "users", column: "author_id"
|
||||||
|
add_foreign_key "template_sharings", "templates"
|
||||||
add_foreign_key "templates", "accounts"
|
add_foreign_key "templates", "accounts"
|
||||||
add_foreign_key "templates", "template_folders", column: "folder_id"
|
add_foreign_key "templates", "template_folders", column: "folder_id"
|
||||||
add_foreign_key "templates", "users", column: "author_id"
|
add_foreign_key "templates", "users", column: "author_id"
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Abilities
|
||||||
|
module TemplateConditions
|
||||||
|
module_function
|
||||||
|
|
||||||
|
def collection(user, ability: nil)
|
||||||
|
shared_ids =
|
||||||
|
Template.joins(:template_sharings)
|
||||||
|
.where(template_sharings: { ability:,
|
||||||
|
account_id: [user.account_id, TemplateSharing::ALL_ID] }.compact)
|
||||||
|
.select(:id)
|
||||||
|
|
||||||
|
Template.where(account_id: user.account_id).or(Template.where(id: shared_ids))
|
||||||
|
end
|
||||||
|
|
||||||
|
def entity(template, user:, ability: nil)
|
||||||
|
return true if template.account_id == user.account_id
|
||||||
|
|
||||||
|
account_ids = [user.account_id, TemplateSharing::ALL_ID]
|
||||||
|
|
||||||
|
template.template_sharings.any? do |e|
|
||||||
|
e.account_id.in?(account_ids) && (ability.nil? || e.ability == ability)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
+9
-2
@@ -5,9 +5,16 @@ class Ability
|
|||||||
|
|
||||||
def initialize(user)
|
def initialize(user)
|
||||||
can :manage, Template, account_id: user.account_id
|
can :manage, Template, account_id: user.account_id
|
||||||
|
|
||||||
|
can %i[read update create], Template,
|
||||||
|
Abilities::TemplateConditions.collection(user) do |template|
|
||||||
|
Abilities::TemplateConditions.entity(template, user:, ability: 'manage')
|
||||||
|
end
|
||||||
|
|
||||||
can :manage, TemplateFolder, account_id: user.account_id
|
can :manage, TemplateFolder, account_id: user.account_id
|
||||||
can :manage, Submission, template: { account_id: user.account_id }
|
can :manage, TemplateSharing, template: { account_id: user.account_id }
|
||||||
can :manage, Submitter, template: { account_id: user.account_id }
|
can :manage, Submission, account_id: user.account_id
|
||||||
|
can :manage, Submitter, submission: { account_id: user.account_id }
|
||||||
can :manage, User, account_id: user.account_id
|
can :manage, User, account_id: user.account_id
|
||||||
can :manage, EncryptedConfig, account_id: user.account_id
|
can :manage, EncryptedConfig, account_id: user.account_id
|
||||||
can :manage, EncryptedUserConfig, user_id: user.id
|
can :manage, EncryptedUserConfig, user_id: user.id
|
||||||
|
|||||||
@@ -19,6 +19,11 @@ module Docuseal
|
|||||||
else
|
else
|
||||||
'https://console.docuseal.co'
|
'https://console.docuseal.co'
|
||||||
end
|
end
|
||||||
|
CLOUD_URL = if Rails.env.development?
|
||||||
|
'http://localhost:3000'
|
||||||
|
else
|
||||||
|
'https://docuseal.co'
|
||||||
|
end
|
||||||
CDN_URL = if Rails.env.development?
|
CDN_URL = if Rails.env.development?
|
||||||
'http://localhost:3000'
|
'http://localhost:3000'
|
||||||
elsif ENV['MULTITENANT'] == 'true'
|
elsif ENV['MULTITENANT'] == 'true'
|
||||||
|
|||||||
@@ -5,10 +5,13 @@ module Params
|
|||||||
def call
|
def call
|
||||||
if params[:submission].blank? && (params[:emails].present? || params[:email].present?)
|
if params[:submission].blank? && (params[:emails].present? || params[:email].present?)
|
||||||
validate_creation_from_emails(params)
|
validate_creation_from_emails(params)
|
||||||
elsif params[:submitters].present?
|
elsif params.key?(:submitters)
|
||||||
validate_creation_from_submitters(params)
|
validate_creation_from_submitters(params)
|
||||||
else
|
elsif params.key?(:submission) || params.key?(:submissions)
|
||||||
validate_creation_from_submission(params)
|
validate_creation_from_submission(params)
|
||||||
|
else
|
||||||
|
required(params, :submitters)
|
||||||
|
type(params, :submitters, Array)
|
||||||
end
|
end
|
||||||
|
|
||||||
true
|
true
|
||||||
@@ -40,6 +43,7 @@ module Params
|
|||||||
type(params, :completed_redirect_url, String)
|
type(params, :completed_redirect_url, String)
|
||||||
type(params, :bcc_completed, String)
|
type(params, :bcc_completed, String)
|
||||||
type(params, :message, Hash)
|
type(params, :message, Hash)
|
||||||
|
type(params, :submitters, Array)
|
||||||
|
|
||||||
in_path(params, :message) do |message_params|
|
in_path(params, :message) do |message_params|
|
||||||
type(message_params, :subject, String)
|
type(message_params, :subject, String)
|
||||||
@@ -50,6 +54,12 @@ module Params
|
|||||||
|
|
||||||
value_in(params, :order, %w[preserved random], allow_nil: true)
|
value_in(params, :order, %w[preserved random], allow_nil: true)
|
||||||
|
|
||||||
|
if params[:submitters].present?
|
||||||
|
in_path(params, :submitters) do |submitters_params|
|
||||||
|
type(submitters_params, 0, Hash)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
in_path_each(params, :submitters) do |submitter_params|
|
in_path_each(params, :submitters) do |submitter_params|
|
||||||
validate_submitter(submitter_params)
|
validate_submitter(submitter_params)
|
||||||
end
|
end
|
||||||
@@ -65,6 +75,7 @@ module Params
|
|||||||
format(submitter_params, :phone, /\A\+\d+\z/,
|
format(submitter_params, :phone, /\A\+\d+\z/,
|
||||||
message: 'phone should start with +<country code> and contain only digits')
|
message: 'phone should start with +<country code> and contain only digits')
|
||||||
type(submitter_params, :values, Hash)
|
type(submitter_params, :values, Hash)
|
||||||
|
type(submitter_params, :metadata, Hash)
|
||||||
boolean(submitter_params, :send_email)
|
boolean(submitter_params, :send_email)
|
||||||
boolean(submitter_params, :send_sms)
|
boolean(submitter_params, :send_sms)
|
||||||
type(submitter_params, :completed_redirect_url, String)
|
type(submitter_params, :completed_redirect_url, String)
|
||||||
|
|||||||
@@ -25,6 +25,11 @@ class PdfProcessor < HexaPDF::Content::Processor
|
|||||||
@pos += 1
|
@pos += 1
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@pos = 0
|
||||||
|
@num += 1
|
||||||
|
rescue HexaPDF::Error => e
|
||||||
|
Rails.logger.error(e.message)
|
||||||
|
|
||||||
@pos = 0
|
@pos = 0
|
||||||
@num += 1
|
@num += 1
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ module SubmissionEvents
|
|||||||
phone_verified: 'Phone verified',
|
phone_verified: 'Phone verified',
|
||||||
start_form: 'Submission started',
|
start_form: 'Submission started',
|
||||||
view_form: 'Form viewed',
|
view_form: 'Form viewed',
|
||||||
complete_form: 'Submission completed'
|
complete_form: 'Submission completed',
|
||||||
|
api_complete_form: 'Submission completed via API'
|
||||||
}.freeze
|
}.freeze
|
||||||
|
|
||||||
module_function
|
module_function
|
||||||
|
|||||||
+2
-1
@@ -32,6 +32,7 @@ module Submissions
|
|||||||
|
|
||||||
parse_emails(emails).uniq.map do |email|
|
parse_emails(emails).uniq.map do |email|
|
||||||
submission = template.submissions.new(created_by_user: user,
|
submission = template.submissions.new(created_by_user: user,
|
||||||
|
account_id: user.account_id,
|
||||||
source:,
|
source:,
|
||||||
template_submitters: template.submitters)
|
template_submitters: template.submitters)
|
||||||
|
|
||||||
@@ -65,7 +66,7 @@ module Submissions
|
|||||||
first_submitter =
|
first_submitter =
|
||||||
submission.template_submitters.filter_map { |s| submitters.find { |e| e.uuid == s['uuid'] } }.first
|
submission.template_submitters.filter_map { |s| submitters.find { |e| e.uuid == s['uuid'] } }.first
|
||||||
|
|
||||||
Submitters.send_signature_requests([first_submitter])
|
Submitters.send_signature_requests([first_submitter]) if first_submitter
|
||||||
else
|
else
|
||||||
Submitters.send_signature_requests(submitters)
|
Submitters.send_signature_requests(submitters)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ module Submissions
|
|||||||
set_submission_preferences['send_email'] = true if params['send_completed_email']
|
set_submission_preferences['send_email'] = true if params['send_completed_email']
|
||||||
|
|
||||||
submission = template.submissions.new(created_by_user: user, source:,
|
submission = template.submissions.new(created_by_user: user, source:,
|
||||||
|
account_id: user.account_id,
|
||||||
preferences: set_submission_preferences,
|
preferences: set_submission_preferences,
|
||||||
template_submitters: [], submitters_order:)
|
template_submitters: [], submitters_order:)
|
||||||
|
|
||||||
@@ -65,6 +66,7 @@ module Submissions
|
|||||||
template_fields.each do |f|
|
template_fields.each do |f|
|
||||||
next if f['submitter_uuid'] != submitter_uuid ||
|
next if f['submitter_uuid'] != submitter_uuid ||
|
||||||
(!f['name'].in?(readonly_fields) &&
|
(!f['name'].in?(readonly_fields) &&
|
||||||
|
!f['name'].to_s.downcase.in?(readonly_fields) &&
|
||||||
!f['name'].to_s.parameterize.underscore.in?(readonly_fields))
|
!f['name'].to_s.parameterize.underscore.in?(readonly_fields))
|
||||||
|
|
||||||
f['readonly'] = true
|
f['readonly'] = true
|
||||||
@@ -93,7 +95,7 @@ module Submissions
|
|||||||
next if f['submitter_uuid'] != submitter_uuid
|
next if f['submitter_uuid'] != submitter_uuid
|
||||||
|
|
||||||
field_configs = fields.find do |e|
|
field_configs = fields.find do |e|
|
||||||
e['name'] == f['name'] || e['name'] == f['name'].to_s.parameterize.underscore
|
e['name'].to_s.casecmp(f['name'].to_s).zero? || e['name'] == f['name'].to_s.parameterize.underscore
|
||||||
end
|
end
|
||||||
|
|
||||||
next if field_configs.blank?
|
next if field_configs.blank?
|
||||||
@@ -107,8 +109,10 @@ module Submissions
|
|||||||
field['description'] = attrs['description'] if attrs['description'].present?
|
field['description'] = attrs['description'] if attrs['description'].present?
|
||||||
field['readonly'] = attrs['readonly'] if attrs.key?('readonly')
|
field['readonly'] = attrs['readonly'] if attrs.key?('readonly')
|
||||||
field['required'] = attrs['required'] if attrs.key?('required')
|
field['required'] = attrs['required'] if attrs.key?('required')
|
||||||
field['default_value'] = attrs['default_value'] if attrs['default_value'].present? &&
|
|
||||||
!field['type'].in?(%w[signature image initials file])
|
if attrs['default_value'].present? && !field['type'].in?(%w[signature image initials file])
|
||||||
|
field['default_value'] = Submitters::NormalizeValues.normalize_value(field, attrs['default_value'])
|
||||||
|
end
|
||||||
|
|
||||||
return field if attrs['validation_pattern'].blank?
|
return field if attrs['validation_pattern'].blank?
|
||||||
|
|
||||||
@@ -134,10 +138,11 @@ module Submissions
|
|||||||
email:,
|
email:,
|
||||||
phone: attrs[:phone].to_s.gsub(/[^0-9+]/, ''),
|
phone: attrs[:phone].to_s.gsub(/[^0-9+]/, ''),
|
||||||
name: attrs[:name],
|
name: attrs[:name],
|
||||||
application_key: attrs[:application_key],
|
external_id: attrs[:external_id].presence || attrs[:application_key],
|
||||||
completed_at: attrs[:completed] ? Time.current : nil,
|
completed_at: attrs[:completed] ? Time.current : nil,
|
||||||
sent_at: mark_as_sent && email.present? && is_order_sent ? Time.current : nil,
|
sent_at: mark_as_sent && email.present? && is_order_sent ? Time.current : nil,
|
||||||
values: attrs[:values] || {},
|
values: attrs[:values] || {},
|
||||||
|
metadata: attrs[:metadata] || {},
|
||||||
preferences: preferences.merge(submitter_preferences)
|
preferences: preferences.merge(submitter_preferences)
|
||||||
.merge({ default_values: attrs[:values] }.compact_blank)
|
.merge({ default_values: attrs[:values] }.compact_blank)
|
||||||
.except('bcc_completed'),
|
.except('bcc_completed'),
|
||||||
|
|||||||
@@ -23,9 +23,11 @@ module Submissions
|
|||||||
else
|
else
|
||||||
submitter.document_generation_events.create!(event_name: events.present? ? :retry : :start)
|
submitter.document_generation_events.create!(event_name: events.present? ? :retry : :start)
|
||||||
|
|
||||||
GenerateResultAttachments.call(submitter)
|
documents = GenerateResultAttachments.call(submitter)
|
||||||
|
|
||||||
submitter.document_generation_events.create!(event_name: :complete)
|
submitter.document_generation_events.create!(event_name: :complete)
|
||||||
|
|
||||||
|
documents
|
||||||
end
|
end
|
||||||
rescue ActiveRecord::RecordNotUnique
|
rescue ActiveRecord::RecordNotUnique
|
||||||
sleep WAIT_FOR_RETRY
|
sleep WAIT_FOR_RETRY
|
||||||
@@ -49,7 +51,7 @@ module Submissions
|
|||||||
DocumentGenerationEvent.where(submitter:).order(:created_at).last
|
DocumentGenerationEvent.where(submitter:).order(:created_at).last
|
||||||
end
|
end
|
||||||
|
|
||||||
break last_event if last_event.event_name.in?(%w[complete fail])
|
break submitter.documents if last_event.event_name.in?(%w[complete fail])
|
||||||
|
|
||||||
raise WaitForCompleteTimeout if total_wait_time > CHECK_COMPLETE_TIMEOUT
|
raise WaitForCompleteTimeout if total_wait_time > CHECK_COMPLETE_TIMEOUT
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ module Submissions
|
|||||||
composer.column(columns: 1) do |column|
|
composer.column(columns: 1) do |column|
|
||||||
column.text("Envelope ID: #{submission.id}", font_size: 12, padding: [20, 0, 10, 0], position: :float)
|
column.text("Envelope ID: #{submission.id}", font_size: 12, padding: [20, 0, 10, 0], position: :float)
|
||||||
|
|
||||||
unless submission.source_embed?
|
unless submission.source.in?(%w[embed api])
|
||||||
column.formatted_text([{ link: verify_url, text: 'Verify', style: :link }],
|
column.formatted_text([{ link: verify_url, text: 'Verify', style: :link }],
|
||||||
font_size: 9, padding: [22, 0, 10, 0], position: :float, align: :right)
|
font_size: 9, padding: [22, 0, 10, 0], position: :float, align: :right)
|
||||||
end
|
end
|
||||||
@@ -236,6 +236,7 @@ module Submissions
|
|||||||
end
|
end
|
||||||
|
|
||||||
value = value.join(', ') if value.is_a?(Array)
|
value = value.join(', ') if value.is_a?(Array)
|
||||||
|
value = Submissions::GenerateResultAttachments::REPLACE_EMOJI[value] || value
|
||||||
|
|
||||||
composer.formatted_text_box([{ text: TextUtils.maybe_rtl_reverse(value.to_s.presence || 'n/a') }],
|
composer.formatted_text_box([{ text: TextUtils.maybe_rtl_reverse(value.to_s.presence || 'n/a') }],
|
||||||
align: value.to_s.match?(RTL_REGEXP) ? :right : :left,
|
align: value.to_s.match?(RTL_REGEXP) ? :right : :left,
|
||||||
@@ -278,6 +279,7 @@ module Submissions
|
|||||||
sign_params = {
|
sign_params = {
|
||||||
reason: SIGN_REASON,
|
reason: SIGN_REASON,
|
||||||
certificate: pkcs.certificate,
|
certificate: pkcs.certificate,
|
||||||
|
doc_mdp_permissions: :no_changes,
|
||||||
key: pkcs.key,
|
key: pkcs.key,
|
||||||
certificate_chain: pkcs.ca_certs || []
|
certificate_chain: pkcs.ca_certs || []
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,10 @@ module Submissions
|
|||||||
A4_SIZE = [595, 842].freeze
|
A4_SIZE = [595, 842].freeze
|
||||||
SUPPORTED_IMAGE_TYPES = ['image/png', 'image/jpeg'].freeze
|
SUPPORTED_IMAGE_TYPES = ['image/png', 'image/jpeg'].freeze
|
||||||
|
|
||||||
|
REPLACE_EMOJI = {
|
||||||
|
'✔️' => 'V'
|
||||||
|
}.freeze
|
||||||
|
|
||||||
module_function
|
module_function
|
||||||
|
|
||||||
# rubocop:disable Metrics
|
# rubocop:disable Metrics
|
||||||
@@ -173,6 +177,7 @@ module Submissions
|
|||||||
end
|
end
|
||||||
|
|
||||||
value = TextUtils.maybe_rtl_reverse(Array.wrap(value).join(', '))
|
value = TextUtils.maybe_rtl_reverse(Array.wrap(value).join(', '))
|
||||||
|
value = REPLACE_EMOJI[value] || value
|
||||||
|
|
||||||
text = HexaPDF::Layout::TextFragment.create(value, font: pdf.fonts.add(FONT_NAME),
|
text = HexaPDF::Layout::TextFragment.create(value, font: pdf.fonts.add(FONT_NAME),
|
||||||
font_size:)
|
font_size:)
|
||||||
@@ -294,6 +299,12 @@ module Submissions
|
|||||||
HexaPDF::Document.new(io: StringIO.new(attachment.download))
|
HexaPDF::Document.new(io: StringIO.new(attachment.download))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
begin
|
||||||
|
pdf.acro_form&.flatten
|
||||||
|
rescue StandardError => e
|
||||||
|
Rollbar.error(e) if defined?(Rollbar)
|
||||||
|
end
|
||||||
|
|
||||||
[attachment.uuid, pdf]
|
[attachment.uuid, pdf]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ module Submitters
|
|||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
# rubocop:disable Metrics
|
|
||||||
def generate_stamp_image(submitter)
|
def generate_stamp_image(submitter)
|
||||||
logo = Vips::Image.new_from_buffer(load_logo(submitter).read, '')
|
logo = Vips::Image.new_from_buffer(load_logo(submitter).read, '')
|
||||||
|
|
||||||
@@ -82,7 +81,6 @@ module Submitters
|
|||||||
|
|
||||||
Vips::Image.text(text, width: WIDTH, height: HEIGHT)
|
Vips::Image.text(text, width: WIDTH, height: HEIGHT)
|
||||||
end
|
end
|
||||||
# rubocop:enable Metrics
|
|
||||||
|
|
||||||
def load_logo(_submitter)
|
def load_logo(_submitter)
|
||||||
PdfIcons.logo_io
|
PdfIcons.logo_io
|
||||||
|
|||||||
@@ -28,7 +28,9 @@ module Submitters
|
|||||||
|
|
||||||
next if key.blank?
|
next if key.blank?
|
||||||
|
|
||||||
if fields_uuid_index[key]['type'].in?(%w[initials signature image file]) && value.present?
|
field = fields_uuid_index[key]
|
||||||
|
|
||||||
|
if field['type'].in?(%w[initials signature image file]) && value.present?
|
||||||
new_value, new_attachments = normalize_attachment_value(value, template.account, for_submitter)
|
new_value, new_attachments = normalize_attachment_value(value, template.account, for_submitter)
|
||||||
|
|
||||||
attachments.push(*new_attachments)
|
attachments.push(*new_attachments)
|
||||||
@@ -36,12 +38,30 @@ module Submitters
|
|||||||
value = new_value
|
value = new_value
|
||||||
end
|
end
|
||||||
|
|
||||||
[key, value]
|
[key, normalize_value(field, value)]
|
||||||
end.to_h
|
end.to_h
|
||||||
|
|
||||||
[normalized_values, attachments]
|
[normalized_values, attachments]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def normalize_value(field, value)
|
||||||
|
if field['type'] == 'text' && value.present?
|
||||||
|
value.to_s
|
||||||
|
elsif field['type'] == 'date' && value.present?
|
||||||
|
if value.is_a?(Integer)
|
||||||
|
Time.zone.at(value.to_s.first(10).to_i).to_date
|
||||||
|
else
|
||||||
|
Date.parse(value).to_s
|
||||||
|
end
|
||||||
|
else
|
||||||
|
value
|
||||||
|
end
|
||||||
|
rescue Date::Error => e
|
||||||
|
Rollbar.error(e) if defined?(Rollbar)
|
||||||
|
|
||||||
|
value
|
||||||
|
end
|
||||||
|
|
||||||
def fetch_fields(template, submitter_name: nil, for_submitter: nil)
|
def fetch_fields(template, submitter_name: nil, for_submitter: nil)
|
||||||
if submitter_name
|
if submitter_name
|
||||||
submitter =
|
submitter =
|
||||||
|
|||||||
@@ -18,9 +18,9 @@ module Submitters
|
|||||||
|
|
||||||
serialize_params = {
|
serialize_params = {
|
||||||
include: {},
|
include: {},
|
||||||
only: %i[id slug uuid name email phone completed_at application_key
|
only: %i[id slug uuid name email phone completed_at external_id
|
||||||
opened_at sent_at created_at updated_at],
|
metadata opened_at sent_at created_at updated_at],
|
||||||
methods: %i[status]
|
methods: %i[status application_key]
|
||||||
}
|
}
|
||||||
|
|
||||||
serialize_params[:include][:template] = { only: %i[id name created_at updated_at] } if with_template
|
serialize_params[:include][:template] = { only: %i[id name created_at updated_at] } if with_template
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ module Submitters
|
|||||||
submitter_name = (submitter.submission.template_submitters ||
|
submitter_name = (submitter.submission.template_submitters ||
|
||||||
submitter.submission.template.submitters).find { |e| e['uuid'] == submitter.uuid }['name']
|
submitter.submission.template.submitters).find { |e| e['uuid'] == submitter.uuid }['name']
|
||||||
|
|
||||||
submitter.as_json(include: [template: { only: %i[id name created_at updated_at] }])
|
submitter.as_json(methods: %i[application_key],
|
||||||
|
include: [template: { only: %i[id name external_id created_at updated_at] }])
|
||||||
.except('uuid', 'values', 'slug')
|
.except('uuid', 'values', 'slug')
|
||||||
.merge('values' => values,
|
.merge('values' => values,
|
||||||
'documents' => documents,
|
'documents' => documents,
|
||||||
|
|||||||
@@ -103,6 +103,7 @@ module Submitters
|
|||||||
end
|
end
|
||||||
|
|
||||||
def replace_default_variables(value, attrs, template, with_time: false)
|
def replace_default_variables(value, attrs, template, with_time: false)
|
||||||
|
return value if value.in?([true, false])
|
||||||
return if value.blank?
|
return if value.blank?
|
||||||
|
|
||||||
value.to_s.gsub(VARIABLE_REGEXP) do |e|
|
value.to_s.gsub(VARIABLE_REGEXP) do |e|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ module Templates
|
|||||||
pdf.pages.flat_map.with_index do |page, index|
|
pdf.pages.flat_map.with_index do |page, index|
|
||||||
annotations = page.objects.deref!(page.attributes[:Annots]) || []
|
annotations = page.objects.deref!(page.attributes[:Annots]) || []
|
||||||
annotations.filter_map do |annot|
|
annotations.filter_map do |annot|
|
||||||
|
next if annot.nil?
|
||||||
next if annot[:A].blank? || annot[:A][:URI].blank?
|
next if annot[:A].blank? || annot[:A][:URI].blank?
|
||||||
next unless annot[:Subtype] == :Link
|
next unless annot[:Subtype] == :Link
|
||||||
next if !annot[:A][:URI].starts_with?('https://') && !annot[:A][:URI].starts_with?('http://')
|
next if !annot[:A][:URI].starts_with?('https://') && !annot[:A][:URI].starts_with?('http://')
|
||||||
|
|||||||
@@ -4,12 +4,12 @@ module Templates
|
|||||||
module Clone
|
module Clone
|
||||||
module_function
|
module_function
|
||||||
|
|
||||||
def call(original_template, author:, application_key: nil, name: nil, folder_name: nil)
|
def call(original_template, author:, external_id: nil, name: nil, folder_name: nil)
|
||||||
original_template_account = original_template.account
|
original_template_account = original_template.account
|
||||||
|
|
||||||
template = original_template_account.templates.new
|
template = original_template_account.templates.new
|
||||||
|
|
||||||
template.application_key = application_key
|
template.external_id = external_id
|
||||||
template.author = author
|
template.author = author
|
||||||
template.name = name || "#{original_template.name} (Clone)"
|
template.name = name || "#{original_template.name} (Clone)"
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ FactoryBot.define do
|
|||||||
created_by_user factory: %i[user]
|
created_by_user factory: %i[user]
|
||||||
|
|
||||||
before(:create) do |submission, _|
|
before(:create) do |submission, _|
|
||||||
|
submission.account_id = submission.template.account_id
|
||||||
submission.template_fields = submission.template.fields
|
submission.template_fields = submission.template.fields
|
||||||
submission.template_schema = submission.template.schema
|
submission.template_schema = submission.template.schema
|
||||||
submission.template_submitters = submission.template.submitters
|
submission.template_submitters = submission.template.submitters
|
||||||
|
|||||||
Reference in new issue
Block a user