mirror of
https://github.com/docusealco/docuseal.git
synced 2026-08-07 15:25:16 +00:00
Compare commits
66 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1b90f77e82 | |||
| c37785ecbc | |||
| c67188edf8 | |||
| 8630c68631 | |||
| ddcb5c5d40 | |||
| 07f0fbb50f | |||
| 3781b0aa72 | |||
| 42a5cea521 | |||
| fb6000ca2c | |||
| 59f98db754 | |||
| 6add842a27 | |||
| 43ca4a6365 | |||
| 68196d6af0 | |||
| a582d39bb9 | |||
| 7a3e89c4d8 | |||
| 87f036ad88 | |||
| 5738b8dd58 | |||
| 8d73371e0f | |||
| 3d870b2305 | |||
| 27d8cd57bd | |||
| 353b2e7a94 | |||
| d77bd87acb | |||
| bae7b924ae | |||
| 89867fdde3 | |||
| c9a1f067ac | |||
| eb26835d57 | |||
| 7301661468 | |||
| e4fe984a70 | |||
| 12ccf622c1 | |||
| 6c35ff6d1c | |||
| 1d7c84302e | |||
| f3e8889710 | |||
| 4b2f9ff306 | |||
| b4ba642f8e | |||
| 5c69b965c0 | |||
| cba196385c | |||
| 00abad56cb | |||
| d1070c4955 | |||
| e84be523c6 | |||
| 5d598e8268 | |||
| 0e9d922204 | |||
| 80c1b04e83 | |||
| b2fca3edf3 | |||
| 2a292c8e80 | |||
| e6052ad0d8 | |||
| 8646e17d5a | |||
| e6785b0afd | |||
| 009033a411 | |||
| fdc3d7742a | |||
| f9ccca1376 | |||
| 0a00fe6ab8 | |||
| 6253e80a70 | |||
| 3dcb7c813f | |||
| 39cdc41a67 | |||
| d3000487f4 | |||
| 80254da1c1 | |||
| 73fc2d92f7 | |||
| f69aa33f3d | |||
| e16202c9f7 | |||
| 7453d0d274 | |||
| c102828c93 | |||
| afeb6dd318 | |||
| 6156623f0e | |||
| ccc06fcc3d | |||
| bbe3942056 | |||
| 49cea59b94 |
+1
-1
@@ -44,7 +44,7 @@ Layout/LineLength:
|
||||
AllowedPatterns: ['\A\s*#']
|
||||
|
||||
Metrics/AbcSize:
|
||||
Max: 35
|
||||
Max: 40
|
||||
|
||||
Metrics/ModuleLength:
|
||||
Max: 500
|
||||
|
||||
+1
-1
@@ -43,7 +43,7 @@ RUN apk add --no-cache build-base sqlite-dev libpq-dev mariadb-dev vips-dev vips
|
||||
|
||||
COPY ./Gemfile ./Gemfile.lock ./
|
||||
|
||||
RUN bundle update --bundler && bundle install && rm -rf ~/.bundle /usr/local/bundle/cache && find /usr/local/bundle -type d \( -name "rdoc" -o -name "spec" \) -exec rm -rf {} +
|
||||
RUN bundle update --bundler && bundle install && rm -rf ~/.bundle /usr/local/bundle/cache && ruby -e "puts Dir['/usr/local/bundle/**/{spec,rdoc,resources/shared,resources/collation,resources/locales}']" | xargs rm -rf
|
||||
|
||||
COPY ./bin ./bin
|
||||
COPY ./app ./app
|
||||
|
||||
@@ -26,6 +26,7 @@ gem 'pagy'
|
||||
gem 'pdf-reader'
|
||||
gem 'pg', require: false
|
||||
gem 'premailer-rails'
|
||||
gem 'pretender'
|
||||
gem 'puma'
|
||||
gem 'rack'
|
||||
gem 'rails'
|
||||
|
||||
@@ -354,6 +354,8 @@ GEM
|
||||
actionmailer (>= 3)
|
||||
net-smtp
|
||||
premailer (~> 1.7, >= 1.7.9)
|
||||
pretender (0.5.0)
|
||||
actionpack (>= 6.1)
|
||||
pry (0.14.2)
|
||||
coderay (~> 1.1)
|
||||
method_source (~> 1.0)
|
||||
@@ -596,6 +598,7 @@ DEPENDENCIES
|
||||
pdf-reader
|
||||
pg
|
||||
premailer-rails
|
||||
pretender
|
||||
pry-rails
|
||||
puma
|
||||
rack
|
||||
|
||||
@@ -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.
|
||||
|
||||
[Book a Meeting](https://calendly.com/kriti-docuseal/30min)
|
||||
[Book a Meeting](https://www.docuseal.co/contact)
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ class AccountConfigsController < ApplicationController
|
||||
ALLOWED_KEYS = [
|
||||
AccountConfig::ALLOW_TYPED_SIGNATURE,
|
||||
AccountConfig::FORCE_MFA,
|
||||
AccountConfig::ALLOW_TO_RESUBMIT,
|
||||
AccountConfig::ESIGNING_PREFERENCE_KEY
|
||||
].freeze
|
||||
|
||||
|
||||
@@ -37,6 +37,18 @@ class AccountsController < ApplicationController
|
||||
render :show, status: :unprocessable_entity
|
||||
end
|
||||
|
||||
def destroy
|
||||
authorize!(:manage, current_account)
|
||||
|
||||
true_user.update!(locked_at: Time.current)
|
||||
|
||||
render turbo_stream: turbo_stream.replace(
|
||||
:account_delete_button,
|
||||
html: helpers.tag.p('Your account removal request will be processed within 2 weeks. ' \
|
||||
'Please contact us if you want to keep your account.')
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_account
|
||||
|
||||
@@ -8,11 +8,17 @@ module Api
|
||||
DEFAULT_LIMIT = 10
|
||||
MAX_LIMIT = 100
|
||||
|
||||
impersonates :user, with: ->(uuid) { User.find_by(uuid:) }
|
||||
|
||||
wrap_parameters false
|
||||
|
||||
before_action :authenticate_user!
|
||||
check_authorization
|
||||
|
||||
rescue_from Params::BaseValidator::InvalidParameterError do |e|
|
||||
render json: { error: e.message }, status: :unprocessable_entity
|
||||
end
|
||||
|
||||
if Rails.env.production?
|
||||
rescue_from CanCan::AccessDenied do |e|
|
||||
Rollbar.error(e) if defined?(Rollbar)
|
||||
@@ -44,7 +50,7 @@ module Api
|
||||
if request.headers['X-Auth-Token'].present?
|
||||
sha256 = Digest::SHA256.hexdigest(request.headers['X-Auth-Token'])
|
||||
|
||||
User.joins(:access_token).find_by(access_token: { sha256: })
|
||||
User.joins(:access_token).active.find_by(access_token: { sha256: })
|
||||
end
|
||||
|
||||
render json: { error: 'Not authenticated' }, status: :unauthorized unless current_user
|
||||
|
||||
@@ -63,6 +63,8 @@ module Api
|
||||
end
|
||||
|
||||
def create
|
||||
Params::SubmissionCreateValidator.call(params, dry_run: !Docuseal.multitenant?)
|
||||
|
||||
return render json: { error: 'Template not found' }, status: :unprocessable_entity if @template.nil?
|
||||
|
||||
if @template.fields.blank?
|
||||
@@ -94,7 +96,8 @@ module Api
|
||||
def create_submissions(template, params)
|
||||
is_send_email = !params[:send_email].in?(['false', false])
|
||||
|
||||
if (emails = (params[:emails] || params[:email]).presence) && params[:submission].blank?
|
||||
if (emails = (params[:emails] || params[:email]).presence) &&
|
||||
(params[:submission].blank? && params[:submitters].blank?)
|
||||
Submissions.create_from_emails(template:,
|
||||
user: current_user,
|
||||
source: :api,
|
||||
@@ -129,8 +132,8 @@ module Api
|
||||
include: {
|
||||
submitters: { only: %i[id slug uuid name email phone
|
||||
completed_at opened_at sent_at
|
||||
created_at updated_at],
|
||||
methods: %i[status] },
|
||||
created_at updated_at external_id metadata],
|
||||
methods: %i[status application_key] },
|
||||
template: { only: %i[id name created_at updated_at] },
|
||||
created_by_user: { only: %i[id email first_name last_name] }
|
||||
}
|
||||
@@ -138,20 +141,27 @@ module Api
|
||||
end
|
||||
|
||||
def submissions_params
|
||||
key = params.key?(:submission) ? :submission : :submissions
|
||||
permitted_attrs = [
|
||||
:send_email, :send_sms, :bcc_completed, :completed_redirect_url,
|
||||
{
|
||||
message: %i[subject body],
|
||||
submitters: [[:send_email, :send_sms, :completed_redirect_url, :uuid, :name, :email, :role,
|
||||
:completed, :phone, :application_key, :external_id,
|
||||
{ metadata: {}, values: {}, readonly_fields: [], message: %i[subject body],
|
||||
fields: [%i[name default_value title description
|
||||
readonly validation_pattern invalid_message]] }]]
|
||||
}
|
||||
]
|
||||
|
||||
params.permit(
|
||||
key => [
|
||||
[:send_email, :send_sms, :bcc_completed, :completed_redirect_url, {
|
||||
message: %i[subject body],
|
||||
submitters: [[:send_email, :send_sms, :completed_redirect_url, :uuid, :name, :email, :role,
|
||||
:completed, :phone, :application_key,
|
||||
{ values: {}, readonly_fields: [], message: %i[subject body],
|
||||
fields: [%i[name default_value title description
|
||||
readonly validation_pattern invalid_message]] }]]
|
||||
}]
|
||||
]
|
||||
).fetch(key, [])
|
||||
if params.key?(:submitters)
|
||||
params.permit(*permitted_attrs)
|
||||
else
|
||||
key = params.key?(:submission) ? :submission : :submissions
|
||||
|
||||
params.permit(
|
||||
key => [permitted_attrs]
|
||||
).fetch(key, [])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,7 +7,8 @@ module Api
|
||||
def index
|
||||
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 = paginate(
|
||||
@@ -44,7 +45,7 @@ module Api
|
||||
|
||||
Submissions::CreateFromSubmitters.maybe_set_template_fields(@submitter.submission,
|
||||
[normalized_params],
|
||||
submitter_uuid: @submitter.uuid)
|
||||
default_submitter_uuid: @submitter.uuid)
|
||||
|
||||
assign_submitter_attrs(@submitter, normalized_params)
|
||||
|
||||
@@ -69,8 +70,9 @@ module Api
|
||||
submitter_params = params.key?(:submitter) ? params.require(:submitter) : params
|
||||
|
||||
submitter_params.permit(
|
||||
:send_email, :send_sms, :uuid, :name, :email, :role, :completed, :phone, :application_key,
|
||||
{ values: {}, readonly_fields: [], message: %i[subject body],
|
||||
:send_email, :send_sms, :uuid, :name, :email, :role,
|
||||
:completed, :phone, :application_key, :external_id,
|
||||
{ metadata: {}, values: {}, readonly_fields: [], message: %i[subject body],
|
||||
fields: [%i[name default_value readonly validation_pattern invalid_message]] }
|
||||
)
|
||||
end
|
||||
@@ -82,7 +84,9 @@ module Api
|
||||
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.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_preferences(submitter, attrs)
|
||||
|
||||
@@ -7,11 +7,13 @@ module Api
|
||||
def create
|
||||
authorize!(:manage, @template)
|
||||
|
||||
cloned_template = Templates::Clone.call(@template,
|
||||
author: current_user,
|
||||
name: params[:name],
|
||||
application_key: params[:application_key],
|
||||
folder_name: params[:folder_name])
|
||||
cloned_template = Templates::Clone.call(
|
||||
@template,
|
||||
author: current_user,
|
||||
name: params[:name],
|
||||
external_id: params[:external_id].presence || params[:application_key],
|
||||
folder_name: params[:folder_name]
|
||||
)
|
||||
|
||||
cloned_template.source = :api
|
||||
cloned_template.save!
|
||||
|
||||
@@ -5,11 +5,7 @@ module Api
|
||||
load_and_authorize_resource :template
|
||||
|
||||
def index
|
||||
templates = Templates.search(@templates, params[:q])
|
||||
|
||||
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 = filter_templates(@templates, params)
|
||||
|
||||
templates = paginate(templates.preload(:author, documents_attachments: :blob))
|
||||
|
||||
@@ -38,17 +34,32 @@ module Api
|
||||
end
|
||||
|
||||
def destroy
|
||||
@template.update!(archived_at: Time.current)
|
||||
if params[:permanently] == 'true' && !Docuseal.multitenant?
|
||||
@template.destroy!
|
||||
else
|
||||
@template.update!(archived_at: Time.current)
|
||||
end
|
||||
|
||||
render json: @template.as_json(only: %i[id archived_at])
|
||||
end
|
||||
|
||||
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
|
||||
{
|
||||
methods: %i[application_key],
|
||||
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
|
||||
|
||||
|
||||
@@ -16,6 +16,8 @@ class ApplicationController < ActionController::Base
|
||||
:current_account,
|
||||
:svg_icon
|
||||
|
||||
impersonates :user, with: ->(uuid) { User.find_by(uuid:) }
|
||||
|
||||
rescue_from Pagy::OverflowError do
|
||||
redirect_to request.path
|
||||
end
|
||||
@@ -24,7 +26,7 @@ class ApplicationController < ActionController::Base
|
||||
rescue_from CanCan::AccessDenied do |e|
|
||||
Rollbar.error(e) if defined?(Rollbar)
|
||||
|
||||
redirect_back(fallback_location: root_path, alert: e.message)
|
||||
redirect_to root_path, alert: e.message
|
||||
end
|
||||
end
|
||||
|
||||
@@ -32,6 +34,15 @@ class ApplicationController < ActionController::Base
|
||||
Docuseal.default_url_options
|
||||
end
|
||||
|
||||
def impersonate_user(user)
|
||||
raise ArgumentError unless user
|
||||
raise Pretender::Error unless true_user
|
||||
|
||||
@impersonated_user = user
|
||||
|
||||
request.session[:impersonated_user_id] = user.uuid
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def with_browser_locale(&)
|
||||
|
||||
@@ -5,9 +5,9 @@ class ConsoleRedirectController < ApplicationController
|
||||
skip_authorization_check
|
||||
|
||||
def index
|
||||
return redirect_to(new_user_session_path({ redir: params[:redir] }.compact)) if current_user.blank?
|
||||
return redirect_to(new_user_session_path({ redir: params[:redir] }.compact)) if true_user.blank?
|
||||
|
||||
auth = JsonWebToken.encode(uuid: current_user.uuid,
|
||||
auth = JsonWebToken.encode(uuid: true_user.uuid,
|
||||
scope: :console,
|
||||
exp: 1.minute.from_now.to_i)
|
||||
|
||||
|
||||
@@ -52,7 +52,13 @@ class DashboardController < ApplicationController
|
||||
|
||||
def filter_templates(templates)
|
||||
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])
|
||||
end
|
||||
|
||||
@@ -10,7 +10,8 @@ class StartFormController < ApplicationController
|
||||
before_action :load_template
|
||||
|
||||
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
|
||||
|
||||
def update
|
||||
@@ -57,7 +58,8 @@ class StartFormController < ApplicationController
|
||||
ip: request.remote_ip,
|
||||
ua: request.user_agent,
|
||||
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?
|
||||
@@ -67,6 +69,7 @@ class StartFormController < ApplicationController
|
||||
end
|
||||
|
||||
submitter.submission ||= Submission.new(template:,
|
||||
account_id: template.account_id,
|
||||
template_submitters: template.submitters,
|
||||
source: :link)
|
||||
|
||||
|
||||
@@ -75,6 +75,6 @@ class SubmissionsController < ApplicationController
|
||||
end
|
||||
|
||||
def load_template
|
||||
@template = current_account.templates.find(params[:template_id])
|
||||
@template = Template.accessible_by(current_ability).find(params[:template_id])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -11,9 +11,7 @@ class SubmittersSendEmailController < ApplicationController
|
||||
alert: 'Email has been sent already.')
|
||||
end
|
||||
|
||||
SubmitterMailer.invitation_email(@submitter).deliver_later!
|
||||
|
||||
SubmissionEvent.create!(submitter: @submitter, event_type: 'send_email')
|
||||
SendSubmitterInvitationEmailJob.perform_later('submitter_id' => @submitter.id)
|
||||
|
||||
@submitter.sent_at ||= Time.current
|
||||
@submitter.save!
|
||||
|
||||
@@ -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]
|
||||
|
||||
def show
|
||||
submissions = @template.submissions
|
||||
submissions = @template.submissions.accessible_by(current_ability)
|
||||
submissions = submissions.active if @template.archived_at.blank?
|
||||
submissions = Submissions.search(submissions, params[:q])
|
||||
|
||||
@@ -47,11 +47,12 @@ class TemplatesController < ApplicationController
|
||||
name: params.dig(:template, :name),
|
||||
folder_name: params[:folder_name])
|
||||
else
|
||||
@template.account = current_account
|
||||
@template.author = current_user
|
||||
@template.folder = TemplateFolders.find_or_create_by_name(current_user, params[:folder_name])
|
||||
end
|
||||
|
||||
@template.account = current_account
|
||||
|
||||
if @template.save
|
||||
Templates::CloneAttachments.call(template: @template, original_template: @base_template) if @base_template
|
||||
|
||||
@@ -87,6 +88,6 @@ class TemplatesController < ApplicationController
|
||||
def load_base_template
|
||||
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
|
||||
|
||||
@@ -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
|
||||
@@ -0,0 +1,20 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class TestingAccountsController < ApplicationController
|
||||
skip_authorization_check only: :destroy
|
||||
|
||||
def show
|
||||
authorize!(:manage, current_account)
|
||||
authorize!(:manage, current_user)
|
||||
|
||||
impersonate_user(Accounts.find_or_create_testing_user(true_user.account))
|
||||
|
||||
redirect_back(fallback_location: root_path)
|
||||
end
|
||||
|
||||
def destroy
|
||||
stop_impersonating_user
|
||||
|
||||
redirect_back(fallback_location: root_path)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,12 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class TestingApiSettingsController < ApplicationController
|
||||
def index
|
||||
authorize!(:manage, current_user.access_token)
|
||||
|
||||
@webhook_config =
|
||||
current_account.encrypted_configs.find_or_initialize_by(key: EncryptedConfig::WEBHOOK_URL_KEY)
|
||||
|
||||
authorize!(:manage, @webhook_config)
|
||||
end
|
||||
end
|
||||
@@ -88,7 +88,9 @@ window.customElements.define('template-builder', class extends HTMLElement {
|
||||
backgroundColor: '#faf7f5',
|
||||
withPhone: this.dataset.withPhone === 'true',
|
||||
withLogo: this.dataset.withLogo !== 'false',
|
||||
editable: this.dataset.editable !== 'false',
|
||||
withPayment: this.dataset.withPayment === 'true',
|
||||
currencies: (this.dataset.currencies || '').split(',').filter(Boolean),
|
||||
acceptFileTypes: this.dataset.acceptFileTypes,
|
||||
isDirectUpload: this.dataset.isDirectUpload === 'true'
|
||||
})
|
||||
|
||||
@@ -15,7 +15,7 @@ window.customElements.define('submission-form', class extends HTMLElement {
|
||||
goToLast: this.dataset.goToLast === 'true',
|
||||
isDemo: this.dataset.isDemo === 'true',
|
||||
attribution: this.dataset.attribution !== 'false',
|
||||
withConfetti: true,
|
||||
withConfetti: this.dataset.withConfetti !== 'false',
|
||||
withTypedSignature: this.dataset.withTypedSignature !== 'false',
|
||||
values: reactive(JSON.parse(this.dataset.values)),
|
||||
completedButton: JSON.parse(this.dataset.completedButton),
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<div
|
||||
class="flex absolute lg:text-base"
|
||||
class="flex absolute lg:text-base -outline-offset-1"
|
||||
dir="auto"
|
||||
:style="computedStyle"
|
||||
:class="{ 'text-[1.5vw] lg:text-base': !textOverflowChars, 'text-[1.0vw] lg:text-xs': textOverflowChars, 'cursor-default': !submittable, 'bg-red-100 border cursor-pointer ': submittable, 'border-red-100': !isActive && submittable, 'bg-opacity-80': !isActive && !isValueSet && submittable, 'border-red-500 border-dashed border-2 z-10': isActive && submittable, 'bg-opacity-40': (isActive || isValueSet) && submittable }"
|
||||
:class="{ 'text-[1.5vw] lg:text-base': !textOverflowChars, 'text-[1.0vw] lg:text-xs': textOverflowChars, 'cursor-default': !submittable, 'border border-red-100 bg-red-100 cursor-pointer': submittable, 'border border-red-100': !isActive && submittable, 'bg-opacity-80': !isActive && !isValueSet && submittable, 'outline-red-500 outline-dashed outline-2 z-10': isActive && submittable, 'bg-opacity-40': (isActive || isValueSet) && submittable }"
|
||||
>
|
||||
<div
|
||||
v-if="!isActive && !isValueSet && field.type !== 'checkbox' && submittable && !area.option_uuid"
|
||||
@@ -81,7 +81,8 @@
|
||||
</div>
|
||||
<div
|
||||
v-else-if="field.type === 'checkbox'"
|
||||
class="w-full p-[0.2vw] flex items-center justify-center"
|
||||
class="w-full p-[1px] flex items-center justify-center"
|
||||
@click="$event.target.querySelector('input')?.click()"
|
||||
>
|
||||
<input
|
||||
v-if="submittable"
|
||||
@@ -100,14 +101,15 @@
|
||||
</div>
|
||||
<div
|
||||
v-else-if="field.type === 'radio' && area.option_uuid"
|
||||
class="w-full p-[0.2vw] flex items-center justify-center"
|
||||
class="w-full p-[1px] flex items-center justify-center"
|
||||
@click="$event.target.querySelector('input')?.click()"
|
||||
>
|
||||
<input
|
||||
v-if="submittable"
|
||||
type="radio"
|
||||
:value="false"
|
||||
class="aspect-square base-radio"
|
||||
:class="{ '!w-auto !h-full': area.w > area.h, '!w-full !h-auto': area.w <= area.h }"
|
||||
class="aspect-square checked:checkbox checked:checkbox-xs"
|
||||
:class="{ 'base-radio': !modelValue || modelValue !== optionValue(option), '!w-auto !h-full': area.w > area.h, '!w-full !h-auto': area.w <= area.h }"
|
||||
:checked="!!modelValue && modelValue === optionValue(option)"
|
||||
@click="$emit('update:model-value', optionValue(option))"
|
||||
>
|
||||
@@ -119,7 +121,8 @@
|
||||
</div>
|
||||
<div
|
||||
v-else-if="field.type === 'multiple' && area.option_uuid"
|
||||
class="w-full p-[0.2vw] flex items-center justify-center"
|
||||
class="w-full p-[1px] flex items-center justify-center"
|
||||
@click="$event.target.querySelector('input')?.click()"
|
||||
>
|
||||
<input
|
||||
v-if="submittable"
|
||||
@@ -233,20 +236,20 @@ export default {
|
||||
computed: {
|
||||
fieldNames () {
|
||||
return {
|
||||
text: 'Text',
|
||||
cells: 'Text',
|
||||
signature: 'Signature',
|
||||
date: 'Date',
|
||||
image: 'Image',
|
||||
stamp: 'Stamp',
|
||||
initials: 'Initials',
|
||||
file: 'File',
|
||||
select: 'Select',
|
||||
checkbox: 'Checkbox',
|
||||
radio: 'Radio',
|
||||
multiple: 'Multiple Select',
|
||||
phone: 'Phone',
|
||||
payment: 'Payment'
|
||||
text: this.t('text'),
|
||||
signature: this.t('signature'),
|
||||
initials: this.t('initials'),
|
||||
date: this.t('date'),
|
||||
image: this.t('image'),
|
||||
file: this.t('file'),
|
||||
select: this.t('select'),
|
||||
checkbox: this.t('checkbox'),
|
||||
multiple: this.t('multiple'),
|
||||
radio: this.t('radio'),
|
||||
cells: this.t('cells'),
|
||||
stamp: this.t('stamp'),
|
||||
payment: this.t('payment'),
|
||||
phone: this.t('phone')
|
||||
}
|
||||
},
|
||||
option () {
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
>
|
||||
</template>
|
||||
<FileDropzone
|
||||
:message="`Upload ${field.name || 'Attachments'}${field.required ? '' : ' (optional)'}`"
|
||||
:message="`${t('upload')} ${field.name || t('files')}${field.required ? '' : ` (${t('optional')})`}`"
|
||||
:submitter-slug="submitterSlug"
|
||||
:is-direct-upload="isDirectUpload"
|
||||
:multiple="true"
|
||||
@@ -62,6 +62,7 @@ export default {
|
||||
IconPaperclip,
|
||||
IconTrashX
|
||||
},
|
||||
inject: ['t'],
|
||||
props: {
|
||||
field: {
|
||||
type: Object,
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<label
|
||||
:for="field.uuid"
|
||||
class="label text-2xl"
|
||||
>{{ field.name || t('date') }}
|
||||
>{{ field.name && showFieldNames ? field.name : t('date') }}
|
||||
<template v-if="!field.required">({{ t('optional') }})</template>
|
||||
</label>
|
||||
<button
|
||||
@@ -46,6 +46,11 @@ export default {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
showFieldNames: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
modelValue: {
|
||||
type: String,
|
||||
required: false,
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
:steps="stepFields"
|
||||
:values="values"
|
||||
:attachments-index="attachmentsIndex"
|
||||
:with-label="!isAnonymousChecboxes"
|
||||
:with-label="!isAnonymousChecboxes && showFieldNames"
|
||||
:current-step="currentStepFields"
|
||||
@focus-step="[saveStep(), goToStep($event, false, true), currentField.type !== 'checkbox' ? isFormVisible = true : '']"
|
||||
/>
|
||||
@@ -64,6 +64,7 @@
|
||||
<TextStep
|
||||
:key="currentField.uuid"
|
||||
v-model="values[currentField.uuid]"
|
||||
:show-field-names="showFieldNames"
|
||||
:field="currentField"
|
||||
@focus="scrollIntoField(currentField)"
|
||||
/>
|
||||
@@ -72,12 +73,13 @@
|
||||
v-else-if="currentField.type === 'date'"
|
||||
:key="currentField.uuid"
|
||||
v-model="values[currentField.uuid]"
|
||||
:show-field-names="showFieldNames"
|
||||
:field="currentField"
|
||||
@focus="scrollIntoField(currentField)"
|
||||
/>
|
||||
<div v-else-if="currentField.type === 'select'">
|
||||
<label
|
||||
v-if="currentField.name"
|
||||
v-if="showFieldNames && currentField.name"
|
||||
:for="currentField.uuid"
|
||||
dir="auto"
|
||||
class="label text-2xl mb-2"
|
||||
@@ -116,15 +118,28 @@
|
||||
</div>
|
||||
<div v-else-if="currentField.type === 'radio'">
|
||||
<label
|
||||
v-if="currentField.name"
|
||||
v-if="showFieldNames && currentField.name"
|
||||
:for="currentField.uuid"
|
||||
dir="auto"
|
||||
class="label text-2xl mb-2"
|
||||
>{{ currentField.name }}
|
||||
<template v-if="!currentField.required">({{ t('optional') }})</template>
|
||||
</label>
|
||||
<div class="flex w-full">
|
||||
<div class="space-y-3.5 mx-auto">
|
||||
<div class="flex w-full max-h-44 overflow-y-auto">
|
||||
<div
|
||||
v-if="!showFieldNames || (currentField.options.every((e) => !e.value) && currentField.options.length > 4)"
|
||||
class="text-xl px-1"
|
||||
>
|
||||
<span
|
||||
@click="scrollIntoField(currentField)"
|
||||
>
|
||||
{{ t('complete_hightlighted_checkboxes_and_click') }} <span class="font-semibold">{{ stepFields.length === currentStep + 1 ? t('submit') : t('next') }}</span>.
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="space-y-3.5 mx-auto"
|
||||
:class="{ hidden: !showFieldNames || (currentField.options.every((e) => !e.value) && currentField.options.length > 4) }"
|
||||
>
|
||||
<div
|
||||
v-for="(option, index) in currentField.options"
|
||||
:key="option.uuid"
|
||||
@@ -154,6 +169,8 @@
|
||||
v-else-if="currentField.type === 'multiple'"
|
||||
:key="currentField.uuid"
|
||||
v-model="values[currentField.uuid]"
|
||||
:show-field-names="showFieldNames"
|
||||
:is-last-step="stepFields.length === currentStep + 1"
|
||||
:field="currentField"
|
||||
/>
|
||||
<div
|
||||
@@ -168,7 +185,7 @@
|
||||
<div
|
||||
class="space-y-3.5 mx-auto"
|
||||
>
|
||||
<template v-if="isAnonymousChecboxes">
|
||||
<template v-if="isAnonymousChecboxes || !showFieldNames">
|
||||
<span class="text-xl">
|
||||
{{ t('complete_hightlighted_checkboxes_and_click') }} <span class="font-semibold">{{ stepFields.length === currentStep + 1 ? t('submit') : t('next') }}</span>.
|
||||
</span>
|
||||
@@ -228,6 +245,7 @@
|
||||
:is-direct-upload="isDirectUpload"
|
||||
:attachments-index="attachmentsIndex"
|
||||
:submitter-slug="submitterSlug"
|
||||
:show-field-names="showFieldNames"
|
||||
@attached="[attachments.push($event), scrollIntoField(currentField)]"
|
||||
/>
|
||||
<SignatureStep
|
||||
@@ -241,6 +259,7 @@
|
||||
:with-typed-signature="withTypedSignature"
|
||||
:attachments-index="attachmentsIndex"
|
||||
:submitter-slug="submitterSlug"
|
||||
:show-field-names="showFieldNames"
|
||||
@attached="attachments.push($event)"
|
||||
@start="scrollIntoField(currentField)"
|
||||
@minimize="isFormVisible = false"
|
||||
@@ -254,6 +273,7 @@
|
||||
:previous-value="previousInitialsValue"
|
||||
:is-direct-upload="isDirectUpload"
|
||||
:attachments-index="attachmentsIndex"
|
||||
:show-field-names="showFieldNames"
|
||||
:submitter-slug="submitterSlug"
|
||||
@attached="attachments.push($event)"
|
||||
@start="scrollIntoField(currentField)"
|
||||
@@ -276,6 +296,7 @@
|
||||
:key="currentField.uuid"
|
||||
v-model="values[currentField.uuid]"
|
||||
:field="currentField"
|
||||
:show-field-names="showFieldNames"
|
||||
:default-value="submitter.phone"
|
||||
:submitter-slug="submitterSlug"
|
||||
@focus="scrollIntoField(currentField)"
|
||||
@@ -376,7 +397,7 @@ import DateStep from './date_step'
|
||||
import FormCompleted from './completed'
|
||||
import { IconInnerShadowTop, IconArrowsDiagonal, IconArrowsDiagonalMinimize2 } from '@tabler/icons-vue'
|
||||
import AppearsOn from './appears_on'
|
||||
import { t } from './i18n'
|
||||
import i18n from './i18n'
|
||||
|
||||
export default {
|
||||
name: 'SubmissionForm',
|
||||
@@ -401,6 +422,7 @@ export default {
|
||||
return {
|
||||
baseUrl: this.baseUrl,
|
||||
scrollIntoArea: this.scrollIntoArea,
|
||||
scrollIntoField: this.scrollIntoField,
|
||||
t: this.t
|
||||
}
|
||||
},
|
||||
@@ -431,6 +453,11 @@ export default {
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
showFieldNames: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
withTypedSignature: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
@@ -481,6 +508,11 @@ export default {
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
language: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: ''
|
||||
},
|
||||
values: {
|
||||
type: Object,
|
||||
required: false,
|
||||
@@ -522,6 +554,9 @@ export default {
|
||||
currentStepFields () {
|
||||
return this.stepFields[this.currentStep]
|
||||
},
|
||||
browserLanguage () {
|
||||
return (navigator.language || navigator.userLanguage || 'en').split('-')[0]
|
||||
},
|
||||
queryParams () {
|
||||
return new URLSearchParams(window.location.search)
|
||||
},
|
||||
@@ -639,7 +674,9 @@ export default {
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
t,
|
||||
t (key) {
|
||||
return i18n[this.language?.toLowerCase()]?.[key] || i18n[this.browserLanguage]?.[key] || i18n.en[key] || key
|
||||
},
|
||||
maybeTrackEmailClick () {
|
||||
const { queryParams } = this
|
||||
|
||||
|
||||
@@ -1,4 +1,19 @@
|
||||
const en = {
|
||||
text: 'Text',
|
||||
signature: 'Signature',
|
||||
initials: 'Initials',
|
||||
date: 'Date',
|
||||
image: 'Image',
|
||||
take_photo: 'Take photo',
|
||||
file: 'File',
|
||||
select: 'Select',
|
||||
checkbox: 'Checkbox',
|
||||
multiple: 'Multiple',
|
||||
radio: 'Radio',
|
||||
cells: 'Cells',
|
||||
stamp: 'Stamp',
|
||||
payment: 'Payment',
|
||||
phone: 'Phone',
|
||||
submit_form: 'Submit Form',
|
||||
type_here_: 'Type here...',
|
||||
optional: 'optional',
|
||||
@@ -13,8 +28,6 @@ const en = {
|
||||
or_drag_and_drop_files: 'or drag and drop files',
|
||||
send_copy_via_email: 'Send copy via email',
|
||||
download: 'Download',
|
||||
signature: 'Signature',
|
||||
initials: 'Initials',
|
||||
clear: 'Clear',
|
||||
redraw: 'Redraw',
|
||||
draw_initials: 'Draw initials',
|
||||
@@ -40,13 +53,30 @@ const en = {
|
||||
draw: 'Draw',
|
||||
type: 'Type',
|
||||
type_text: 'Type text',
|
||||
date: 'Date',
|
||||
email_has_been_sent: 'Email has been sent',
|
||||
processing: 'Processing',
|
||||
pay_with_strip: 'Pay with Stripe'
|
||||
pay_with_strip: 'Pay with Stripe',
|
||||
reupload: 'Reupload',
|
||||
upload: 'Upload',
|
||||
files: 'Files'
|
||||
}
|
||||
|
||||
const es = {
|
||||
text: 'Texto',
|
||||
signature: 'Firma',
|
||||
initials: 'Iniciales',
|
||||
date: 'Fecha',
|
||||
image: 'Imagen',
|
||||
file: 'Archivo',
|
||||
select: 'Seleccionar',
|
||||
checkbox: 'Casilla',
|
||||
multiple: 'Múltiple',
|
||||
radio: 'Radio',
|
||||
cells: 'Celdas',
|
||||
stamp: 'Sello',
|
||||
payment: 'Pago',
|
||||
phone: 'Teléfono',
|
||||
take_photo: 'Tomar foto',
|
||||
submit_form: 'Enviar Formulario',
|
||||
type_here_: 'Escribe aquí...',
|
||||
optional: 'opcional',
|
||||
@@ -60,8 +90,6 @@ const es = {
|
||||
or_drag_and_drop_files: 'o arrastra y suelta archivos',
|
||||
send_copy_via_email: 'Enviar copia por correo electrónico',
|
||||
download: 'Descargar',
|
||||
signature: 'Firma',
|
||||
initials: 'Iniciales',
|
||||
clear: 'Borrar',
|
||||
redraw: 'Redibujar',
|
||||
draw_initials: 'Dibujar iniciales',
|
||||
@@ -81,7 +109,6 @@ const es = {
|
||||
verification_code_has_been_resent: 'El código de verificación ha sido reenviado por SMS',
|
||||
please_fill_all_required_fields: 'Por favor, complete todos los campos obligatorios',
|
||||
set_today: 'Establecer Hoy',
|
||||
date: 'Fecha',
|
||||
toggle_multiline_text: 'Alternar Texto Multilínea',
|
||||
draw_signature: 'Dibujar firma',
|
||||
type_initial: 'Escribir iniciales',
|
||||
@@ -90,15 +117,33 @@ const es = {
|
||||
type_text: 'Escribir texto',
|
||||
email_has_been_sent: 'El correo electrónico ha sido enviado',
|
||||
processing: 'Procesando',
|
||||
pay_with_strip: 'Pagar con Stripe'
|
||||
pay_with_strip: 'Pagar con Stripe',
|
||||
reupload: 'Volver a subir',
|
||||
upload: 'Subir',
|
||||
files: 'Archivos'
|
||||
}
|
||||
|
||||
const it = {
|
||||
text: 'Testo',
|
||||
signature: 'Firma',
|
||||
initials: 'Iniziali',
|
||||
date: 'Data',
|
||||
image: 'Immagine',
|
||||
file: 'File',
|
||||
select: 'Seleziona',
|
||||
checkbox: 'Casella di controllo',
|
||||
multiple: 'Multiplo',
|
||||
radio: 'Radio',
|
||||
cells: 'Celle',
|
||||
stamp: 'Timbro',
|
||||
payment: 'Pagamento',
|
||||
phone: 'Telefono',
|
||||
submit_form: 'Invia Modulo',
|
||||
type_here_: 'Digita qui...',
|
||||
optional: 'opzionale',
|
||||
appears_on: 'Compare su',
|
||||
page: 'Pagina',
|
||||
take_photo: 'Scattare una foto',
|
||||
select_your_option: 'Seleziona la tua opzione',
|
||||
complete_hightlighted_checkboxes_and_click: 'Completa le caselle evidenziate e fai clic',
|
||||
submit: 'invia',
|
||||
@@ -107,8 +152,6 @@ const it = {
|
||||
or_drag_and_drop_files: 'oppure trascina e rilascia i file',
|
||||
send_copy_via_email: 'Invia copia via email',
|
||||
download: 'Scarica',
|
||||
signature: 'Firma',
|
||||
initials: 'Iniziali',
|
||||
clear: 'Cancella',
|
||||
redraw: 'Ridisegna',
|
||||
draw_initials: 'Disegna iniziali',
|
||||
@@ -128,7 +171,6 @@ const it = {
|
||||
verification_code_has_been_resent: 'Il codice di verifica è stato rinviato tramite SMS',
|
||||
please_fill_all_required_fields: 'Si prega di compilare tutti i campi obbligatori',
|
||||
set_today: 'Imposta Oggi',
|
||||
date: 'Data',
|
||||
draw_signature: 'Disegna firma',
|
||||
type_initial: 'Inserisci iniziali',
|
||||
draw: 'Disegna',
|
||||
@@ -137,15 +179,33 @@ const it = {
|
||||
toggle_multiline_text: 'Attiva Testo Multilinea',
|
||||
email_has_been_sent: "L'email è stata inviata",
|
||||
processing: 'Elaborazione',
|
||||
pay_with_strip: 'Paga con Stripe'
|
||||
pay_with_strip: 'Paga con Stripe',
|
||||
reupload: 'Ricarica',
|
||||
upload: 'Carica',
|
||||
files: 'File'
|
||||
}
|
||||
|
||||
const de = {
|
||||
text: 'Text',
|
||||
signature: 'Unterschrift',
|
||||
initials: 'Initialen',
|
||||
date: 'Datum',
|
||||
image: 'Bild',
|
||||
file: 'Datei',
|
||||
select: 'Auswählen',
|
||||
checkbox: 'Checkbox',
|
||||
multiple: 'Mehrere',
|
||||
radio: 'Radio',
|
||||
cells: 'Zellen',
|
||||
stamp: 'Stempel',
|
||||
payment: 'Zahlung',
|
||||
phone: 'Telefon',
|
||||
submit_form: 'Formular absenden',
|
||||
type_here_: 'Hier eingeben...',
|
||||
optional: 'optional',
|
||||
appears_on: 'Erscheint auf',
|
||||
page: 'Seite',
|
||||
take_photo: 'Foto aufnehmen',
|
||||
select_your_option: 'Wähle deine Option',
|
||||
complete_hightlighted_checkboxes_and_click: 'Markierte Kontrollkästchen ausfüllen und klicken',
|
||||
submit: 'absenden',
|
||||
@@ -154,8 +214,6 @@ const de = {
|
||||
or_drag_and_drop_files: 'oder Dateien hierher ziehen und ablegen',
|
||||
send_copy_via_email: 'Kopie per E-Mail senden',
|
||||
download: 'Herunterladen',
|
||||
signature: 'Unterschrift',
|
||||
initials: 'Initialen',
|
||||
clear: 'Löschen',
|
||||
redraw: 'Neu zeichnen',
|
||||
draw_initials: 'Initialen zeichnen',
|
||||
@@ -175,7 +233,6 @@ const de = {
|
||||
verification_code_has_been_resent: 'Die Verifizierungscode wurde erneut per SMS gesendet',
|
||||
please_fill_all_required_fields: 'Bitte füllen Sie alle erforderlichen Felder aus',
|
||||
set_today: 'Heute einstellen',
|
||||
date: 'Datum',
|
||||
draw_signature: 'Unterschrift zeichnen',
|
||||
type_initial: 'Initialen eingeben',
|
||||
draw: 'Zeichnen',
|
||||
@@ -184,15 +241,33 @@ const de = {
|
||||
toggle_multiline_text: 'Mehrzeiligen Text umschalten',
|
||||
email_has_been_sent: 'Die E-Mail wurde gesendet',
|
||||
processing: 'Verarbeitung',
|
||||
pay_with_strip: 'Mit Stripe bezahlen'
|
||||
pay_with_strip: 'Mit Stripe bezahlen',
|
||||
reupload: 'Erneut hochladen',
|
||||
upload: 'Hochladen',
|
||||
files: 'Dateien'
|
||||
}
|
||||
|
||||
const fr = {
|
||||
text: 'Texte',
|
||||
signature: 'Signature',
|
||||
initials: 'Initiales',
|
||||
date: 'Date',
|
||||
image: 'Image',
|
||||
file: 'Fichier',
|
||||
select: 'Choisir',
|
||||
checkbox: 'Coche',
|
||||
multiple: 'Multiple',
|
||||
radio: 'Radio',
|
||||
cells: 'Cellules',
|
||||
stamp: 'Tampon',
|
||||
payment: 'Paiement',
|
||||
phone: 'Téléphone',
|
||||
submit_form: 'Envoyer le Formulaire',
|
||||
type_here_: 'Tapez ici...',
|
||||
optional: 'facultatif',
|
||||
appears_on: 'Apparaît sur',
|
||||
page: 'Page',
|
||||
take_photo: 'Prendre une photo',
|
||||
select_your_option: 'Sélectionnez votre option',
|
||||
complete_hightlighted_checkboxes_and_click: 'Complétez les cases à cocher en surbrillance et cliquez',
|
||||
submit: 'envoyer',
|
||||
@@ -201,8 +276,6 @@ const fr = {
|
||||
or_drag_and_drop_files: 'ou faites glisser-déposer les fichiers',
|
||||
send_copy_via_email: 'Envoyer une copie par e-mail',
|
||||
download: 'Télécharger',
|
||||
signature: 'Signature',
|
||||
initials: 'Initiales',
|
||||
clear: 'Effacer',
|
||||
redraw: 'Redessiner',
|
||||
draw_initials: 'Dessiner les initiales',
|
||||
@@ -222,7 +295,6 @@ const fr = {
|
||||
verification_code_has_been_resent: 'Le code de vérification a été renvoyé par SMS',
|
||||
please_fill_all_required_fields: 'Veuillez remplir tous les champs obligatoires',
|
||||
set_today: "Définir Aujourd'hui",
|
||||
date: 'Date',
|
||||
draw_signature: 'Dessiner une signature',
|
||||
type_initial: 'Saisir les initiales',
|
||||
draw: 'Dessiner',
|
||||
@@ -231,10 +303,27 @@ const fr = {
|
||||
toggle_multiline_text: 'Basculer le Texte Multiligne',
|
||||
email_has_been_sent: "L'email a été envoyé",
|
||||
processing: 'Traitement',
|
||||
pay_with_strip: 'Paiement avec Stripe'
|
||||
pay_with_strip: 'Paiement avec Stripe',
|
||||
reupload: 'Recharger',
|
||||
upload: 'Télécharger',
|
||||
files: 'Fichiers'
|
||||
}
|
||||
|
||||
const pl = {
|
||||
text: 'Tekst',
|
||||
signature: 'Podpis',
|
||||
initials: 'Inicjały',
|
||||
date: 'Data',
|
||||
image: 'Obraz',
|
||||
file: 'Plik',
|
||||
select: 'Wybierz',
|
||||
checkbox: 'Pole wyboru',
|
||||
multiple: 'Wielokrotne',
|
||||
radio: 'Radio',
|
||||
cells: 'Komórki',
|
||||
stamp: 'Pieczątka',
|
||||
payment: 'Płatność',
|
||||
phone: 'Telefon',
|
||||
submit_form: 'Wyślij Formularz',
|
||||
type_here_: 'Wpisz tutaj...',
|
||||
optional: 'opcjonalny',
|
||||
@@ -244,12 +333,11 @@ const pl = {
|
||||
complete_hightlighted_checkboxes_and_click: 'Wypełnij zaznaczone pola wyboru i kliknij',
|
||||
submit: 'wyślij',
|
||||
next: 'dalej',
|
||||
take_photo: 'Zrób zdjęcie',
|
||||
click_to_upload: 'Kliknij, aby przesłać',
|
||||
or_drag_and_drop_files: 'lub przeciągnij i upuść pliki',
|
||||
send_copy_via_email: 'Wyślij kopię drogą mailową',
|
||||
download: 'Pobierz',
|
||||
signature: 'Podpis',
|
||||
initials: 'Inicjały',
|
||||
clear: 'Wyczyść',
|
||||
redraw: 'Przerysuj',
|
||||
draw_initials: 'Narysuj inicjały',
|
||||
@@ -269,7 +357,6 @@ const pl = {
|
||||
verification_code_has_been_resent: 'Kod weryfikacyjny został ponownie wysłany',
|
||||
please_fill_all_required_fields: 'Proszę wypełnić wszystkie wymagane pola',
|
||||
set_today: 'Ustaw Dziś',
|
||||
date: 'Data',
|
||||
draw_signature: 'Rysuj podpis',
|
||||
type_initial: 'Wprowadź inicjały',
|
||||
draw: 'Rysuj',
|
||||
@@ -278,15 +365,33 @@ const pl = {
|
||||
toggle_multiline_text: 'Przełącz Tekst Wielolinijkowy',
|
||||
email_has_been_sent: 'E-mail został wysłany',
|
||||
processing: 'Przetwarzanie',
|
||||
pay_with_strip: 'Płatność za pomocą Stripe'
|
||||
pay_with_strip: 'Płatność za pomocą Stripe',
|
||||
reupload: 'Ponowne przesłanie',
|
||||
upload: 'Przesyłanie',
|
||||
files: 'Pliki'
|
||||
}
|
||||
|
||||
const uk = {
|
||||
text: 'Текст',
|
||||
signature: 'Підпис',
|
||||
initials: 'Ініціали',
|
||||
date: 'Дата',
|
||||
image: 'Зображення',
|
||||
file: 'Файл',
|
||||
select: 'Вибрати',
|
||||
checkbox: 'Прапорець',
|
||||
multiple: 'Множинний',
|
||||
radio: 'Радіо',
|
||||
cells: 'Клітинки',
|
||||
stamp: 'Штамп',
|
||||
payment: 'Платіж',
|
||||
phone: 'Телефон',
|
||||
submit_form: 'Надіслати Форму',
|
||||
type_here_: 'Введіть тут',
|
||||
optional: 'необов’язково',
|
||||
appears_on: "З'являється на",
|
||||
page: 'Сторінка',
|
||||
take_photo: 'Зробити фото',
|
||||
select_your_option: 'Виберіть варіант',
|
||||
complete_hightlighted_checkboxes_and_click: 'Заповніть позначені прапорці та натисніть',
|
||||
submit: 'надіслати',
|
||||
@@ -295,8 +400,6 @@ const uk = {
|
||||
or_drag_and_drop_files: 'або перетягніть файли сюди',
|
||||
send_copy_via_email: 'Надіслати копію електронною поштою',
|
||||
download: 'Завантажити',
|
||||
signature: 'Підпис',
|
||||
initials: 'Ініціали',
|
||||
clear: 'Очистити',
|
||||
redraw: 'Перемалювати',
|
||||
draw_initials: 'Намалювати ініціали',
|
||||
@@ -316,7 +419,6 @@ const uk = {
|
||||
verification_code_has_been_resent: 'Код підтвердження був повторно надісланий',
|
||||
please_fill_all_required_fields: "Будь ласка, заповніть всі обов'язкові поля",
|
||||
set_today: 'Задати Сьогодні',
|
||||
date: 'Дата',
|
||||
draw_signature: 'Намалюйте підпис',
|
||||
type_initial: 'Введіть ініціали',
|
||||
draw: 'Підпис',
|
||||
@@ -325,16 +427,34 @@ const uk = {
|
||||
toggle_multiline_text: 'Перемкнути Багаторядковий Текст',
|
||||
email_has_been_sent: 'Електронний лист був відправлений',
|
||||
processing: 'Обробка',
|
||||
pay_with_strip: 'Сплатити за допомогою Stripe'
|
||||
pay_with_strip: 'Сплатити за допомогою Stripe',
|
||||
reupload: 'Перезавантажити',
|
||||
upload: 'Завантажити',
|
||||
files: 'Файли'
|
||||
}
|
||||
|
||||
const cs = {
|
||||
text: 'Text',
|
||||
signature: 'Podpis',
|
||||
initials: 'Iniciály',
|
||||
date: 'Datum',
|
||||
image: 'Obrázek',
|
||||
file: 'Soubor',
|
||||
select: 'Vybrat',
|
||||
checkbox: 'Zaškrtávací políčko',
|
||||
multiple: 'Více možností',
|
||||
radio: 'Rádio',
|
||||
cells: 'Buňky',
|
||||
stamp: 'Razítko',
|
||||
payment: 'Platba',
|
||||
phone: 'Telefon',
|
||||
submit_form: 'Odeslat formulář',
|
||||
type_here_: 'Zadejte zde',
|
||||
optional: 'volitelné',
|
||||
appears_on: 'Zobrazuje se na',
|
||||
page: 'Stránka',
|
||||
select_your_option: 'Vyberte svou volbu',
|
||||
take_photo: 'Fotit',
|
||||
complete_hightlighted_checkboxes_and_click: 'Označte zvýrazněné zaškrtávací políčka a klikněte na',
|
||||
submit: 'odeslat',
|
||||
next: 'další',
|
||||
@@ -342,8 +462,6 @@ const cs = {
|
||||
or_drag_and_drop_files: 'nebo přetáhněte soubory sem',
|
||||
send_copy_via_email: 'Odeslat kopii e-mailem',
|
||||
download: 'Stáhnout',
|
||||
signature: 'Podpis',
|
||||
initials: 'Iniciály',
|
||||
clear: 'Smazat',
|
||||
redraw: 'Překreslit',
|
||||
draw_initials: 'Nakreslit iniciály',
|
||||
@@ -363,7 +481,6 @@ const cs = {
|
||||
verification_code_has_been_resent: 'Ověřovací kód byl znovu odeslán',
|
||||
please_fill_all_required_fields: 'Prosím vyplňte všechny povinné položky',
|
||||
set_today: 'Nastavit Dnes',
|
||||
date: 'Datum',
|
||||
draw_signature: 'Nakreslit podpis',
|
||||
type_initial: 'Zadat iniciály',
|
||||
draw: 'Kreslit',
|
||||
@@ -372,15 +489,33 @@ const cs = {
|
||||
toggle_multiline_text: 'Přepnout Víceřádkový Text',
|
||||
email_has_been_sent: 'E-mail byl odeslán',
|
||||
processing: 'Zpracování',
|
||||
pay_with_strip: 'Zaplacení přes Stripe'
|
||||
pay_with_strip: 'Zaplacení přes Stripe',
|
||||
reupload: 'Znovu nahrát',
|
||||
upload: 'Nahrát',
|
||||
files: 'Soubory'
|
||||
}
|
||||
|
||||
const pt = {
|
||||
text: 'Texto',
|
||||
signature: 'Assinatura',
|
||||
initials: 'Iniciais',
|
||||
date: 'Data',
|
||||
image: 'Imagem',
|
||||
file: 'Arquivo',
|
||||
select: 'Selecionar',
|
||||
checkbox: 'Caixa',
|
||||
multiple: 'Múltiplo',
|
||||
radio: 'Rádio',
|
||||
cells: 'Células',
|
||||
stamp: 'Carimbo',
|
||||
payment: 'Pagamento',
|
||||
phone: 'Telefone',
|
||||
submit_form: 'Enviar Formulário',
|
||||
type_here_: 'Digite aqui',
|
||||
optional: 'opcional',
|
||||
appears_on: 'Aparece em',
|
||||
page: 'Página',
|
||||
take_photo: 'Tirar foto',
|
||||
select_your_option: 'Selecione sua opção',
|
||||
complete_hightlighted_checkboxes_and_click: 'Complete as caixas de seleção destacadas e clique',
|
||||
submit: 'enviar',
|
||||
@@ -389,8 +524,6 @@ const pt = {
|
||||
or_drag_and_drop_files: 'ou arraste e solte arquivos',
|
||||
send_copy_via_email: 'Enviar cópia por e-mail',
|
||||
download: 'Baixar',
|
||||
signature: 'Assinatura',
|
||||
initials: 'Iniciais',
|
||||
clear: 'Limpar',
|
||||
redraw: 'Redesenhar',
|
||||
draw_initials: 'Desenhar iniciais',
|
||||
@@ -410,7 +543,6 @@ const pt = {
|
||||
verification_code_has_been_resent: 'O código de verificação foi reenviado via SMS',
|
||||
please_fill_all_required_fields: 'Por favor, preencha todos os campos obrigatórios',
|
||||
set_today: 'Definir Hoje',
|
||||
date: 'Data',
|
||||
draw_signature: 'Desenhar assinatura',
|
||||
type_initial: 'Inserir iniciais',
|
||||
draw: 'Desenhar',
|
||||
@@ -419,16 +551,34 @@ const pt = {
|
||||
toggle_multiline_text: 'Alternar Texto Multilinha',
|
||||
email_has_been_sent: 'Email enviado',
|
||||
processing: 'Processamento',
|
||||
pay_with_strip: 'Pagar com Stripe'
|
||||
pay_with_strip: 'Pagar com Stripe',
|
||||
reupload: 'Reenviar',
|
||||
upload: 'Carregar',
|
||||
files: 'Arquivos'
|
||||
}
|
||||
|
||||
const he = {
|
||||
text: 'טקסט',
|
||||
signature: 'חתימה',
|
||||
initials: 'ראשי תיקיות',
|
||||
date: 'תאריך',
|
||||
image: 'תמונה',
|
||||
file: 'קובץ',
|
||||
select: 'בחר',
|
||||
checkbox: 'תיק בחירה',
|
||||
multiple: 'רב ערכים',
|
||||
radio: 'רדיו',
|
||||
cells: 'תאים',
|
||||
stamp: 'חותמת',
|
||||
payment: 'תשלום',
|
||||
phone: 'טלפון',
|
||||
submit_form: 'שלח טופס',
|
||||
type_here_: 'הקלד כאן',
|
||||
optional: 'אופציונלי',
|
||||
option: 'אפשרות',
|
||||
appears_on: 'מופיע ב',
|
||||
page: 'עמוד',
|
||||
take_photo: 'צלם תמונה',
|
||||
select_your_option: 'בחר את האפשרות שלך',
|
||||
complete_hightlighted_checkboxes_and_click: 'השלם את תיבות הסימון המודגשות ולחץ',
|
||||
submit: 'שליחה',
|
||||
@@ -437,8 +587,6 @@ const he = {
|
||||
or_drag_and_drop_files: 'או גרור ושחרר קבצים',
|
||||
send_copy_via_email: 'שלח עותק באמצעות אימייל',
|
||||
download: 'הורדה',
|
||||
signature: 'חתימה',
|
||||
initials: 'ציוני ראשי תיבות',
|
||||
clear: 'נקה',
|
||||
redraw: 'צייר מחדש',
|
||||
draw_initials: 'צייר ציוני ראשי תיבות',
|
||||
@@ -464,17 +612,77 @@ const he = {
|
||||
draw: 'צייר',
|
||||
type: 'הקלד',
|
||||
type_text: 'הקלד טקסט',
|
||||
date: 'תאריך',
|
||||
email_has_been_sent: 'האימייל נשלח',
|
||||
processing: 'מעבד',
|
||||
pay_with_strip: 'שלם עם סטרייפ'
|
||||
pay_with_strip: 'שלם עם סטרייפ',
|
||||
reupload: 'העלה שוב',
|
||||
upload: 'העלאה',
|
||||
files: 'קבצים'
|
||||
}
|
||||
|
||||
const i18n = { en, es, it, de, fr, pl, uk, cs, pt, he }
|
||||
const nl = {
|
||||
text: 'Tekst',
|
||||
signature: 'Handtekening',
|
||||
initials: 'Initialen',
|
||||
date: 'Datum',
|
||||
image: 'Afbeelding',
|
||||
take_photo: 'Maak een foto',
|
||||
file: 'Bestand',
|
||||
select: 'Selecteer',
|
||||
checkbox: 'Aankruisvakje',
|
||||
multiple: 'Meerdere',
|
||||
radio: 'Radio',
|
||||
cells: 'Cellen',
|
||||
stamp: 'Stempel',
|
||||
payment: 'Betaling',
|
||||
phone: 'Telefoon',
|
||||
submit_form: 'Formulier verzenden',
|
||||
type_here_: 'Typ hier...',
|
||||
optional: 'Optioneel',
|
||||
option: 'Optie',
|
||||
appears_on: 'Verschijnt op',
|
||||
page: 'Pagina',
|
||||
select_your_option: 'Selecteer uw optie',
|
||||
complete_hightlighted_checkboxes_and_click: 'Voltooi de gemarkeerde selectievakjes en klik',
|
||||
submit: 'Voltooien',
|
||||
next: 'Volgende',
|
||||
click_to_upload: 'Klik om te uploaden',
|
||||
or_drag_and_drop_files: 'of sleep en plaats bestanden',
|
||||
send_copy_via_email: 'Stuur kopie per e-mail',
|
||||
download: 'Downloaden',
|
||||
clear: 'Wissen',
|
||||
redraw: 'Opnieuw tekenen',
|
||||
draw_initials: 'Initialen tekenen',
|
||||
type_signature_here: 'Typ hier uw handtekening',
|
||||
type_initial_here: 'Typ hier uw initialen',
|
||||
form_has_been_completed: 'Formulier is voltooid!',
|
||||
create_a_free_account: 'Maak een gratis account aan',
|
||||
signed_with: 'Ondertekend met',
|
||||
please_check_the_box_to_continue: 'Vink het vakje aan om door te gaan',
|
||||
open_source_documents_software: 'Open source documenten software',
|
||||
verified_phone_number: 'Geverifieerd telefoonnummer',
|
||||
use_international_format: 'Gebruik internationaal formaat: +1xxx',
|
||||
six_digits_code: '6-cijferige code',
|
||||
change_phone_number: 'Wijzig telefoonnummer',
|
||||
sending: 'Voltooien...',
|
||||
resend_code: 'Code opnieuw verzenden',
|
||||
verification_code_has_been_resent: 'Verificatiecode is opnieuw verzonden via SMS',
|
||||
please_fill_all_required_fields: 'Vul alle verplichte velden in',
|
||||
set_today: 'Vandaag',
|
||||
toggle_multiline_text: 'Schakel meerregelige tekst in/uit',
|
||||
draw_signature: 'Handtekening tekenen',
|
||||
type_initial: 'Typ initialen',
|
||||
draw: 'Tekenen',
|
||||
type: 'Typen',
|
||||
type_text: 'Typ tekst',
|
||||
email_has_been_sent: 'E-mail is verzonden',
|
||||
processing: 'Verwerken',
|
||||
pay_with_strip: 'Betalen met Stripe',
|
||||
reupload: 'Opnieuw uploaden',
|
||||
upload: 'Uploaden',
|
||||
files: 'Bestanden'
|
||||
}
|
||||
|
||||
const browserLanguage = (navigator.language || navigator.userLanguage || 'en').split('-')[0]
|
||||
|
||||
const t = (key) => i18n[browserLanguage]?.[key] || i18n.en[key] || key
|
||||
const i18n = { en, es, it, de, fr, pl, uk, cs, pt, he, nl }
|
||||
|
||||
export default i18n
|
||||
export { t }
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
<div class="flex justify-between items-center w-full mb-2">
|
||||
<label
|
||||
class="label text-2xl"
|
||||
>{{ field.name || 'Image' }}</label>
|
||||
>{{ showFieldNames && field.name ? field.name : t('image') }}</label>
|
||||
<button
|
||||
class="btn btn-outline btn-sm"
|
||||
@click.prevent="remove"
|
||||
>
|
||||
<IconReload :width="16" />
|
||||
Reupload
|
||||
{{ t('reupload') }}
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
@@ -27,7 +27,7 @@
|
||||
<div>
|
||||
<FileDropzone
|
||||
v-if="!modelValue"
|
||||
:message="`Upload ${field.name || 'Image'}${field.required ? '' : ' (optional)'}`"
|
||||
:message="`${t('upload')} ${field.name || t('image')}${field.required ? '' : ` (${t('optional')})`}`"
|
||||
:submitter-slug="submitterSlug"
|
||||
:accept="'image/*'"
|
||||
:is-direct-upload="isDirectUpload"
|
||||
@@ -46,6 +46,7 @@ export default {
|
||||
FileDropzone,
|
||||
IconReload
|
||||
},
|
||||
inject: ['t'],
|
||||
props: {
|
||||
field: {
|
||||
type: Object,
|
||||
@@ -56,6 +57,11 @@ export default {
|
||||
required: true,
|
||||
default: false
|
||||
},
|
||||
showFieldNames: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
submitterSlug: {
|
||||
type: String,
|
||||
required: true
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div class="flex justify-between items-center w-full mb-2">
|
||||
<label
|
||||
class="label text-2xl"
|
||||
>{{ field.name || t('initials') }}</label>
|
||||
>{{ showFieldNames && field.name ? field.name : t('initials') }}</label>
|
||||
<div class="space-x-2 flex">
|
||||
<span
|
||||
v-if="isDrawInitials"
|
||||
@@ -125,6 +125,11 @@ export default {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
showFieldNames: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
isDirectUpload: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<label
|
||||
v-if="field.name"
|
||||
v-if="showFieldNames && field.name"
|
||||
:for="field.uuid"
|
||||
dir="auto"
|
||||
class="label text-2xl mb-2"
|
||||
>{{ field.name }}</label>
|
||||
<div class="flex w-full">
|
||||
<div class="flex w-full max-h-44 overflow-y-auto">
|
||||
<input
|
||||
v-if="modelValue.length === 0"
|
||||
type="text"
|
||||
@@ -13,7 +13,18 @@
|
||||
:value="''"
|
||||
class="hidden"
|
||||
>
|
||||
<div class="space-y-3.5 mx-auto">
|
||||
<div
|
||||
v-if="!showOptions"
|
||||
class="text-xl px-1"
|
||||
>
|
||||
<span @click="scrollIntoField(field)">
|
||||
{{ t('complete_hightlighted_checkboxes_and_click') }} <span class="font-semibold">{{ isLastStep ? t('submit') : t('next') }}</span>.
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="space-y-3.5 mx-auto"
|
||||
:class="{ hidden: !showOptions }"
|
||||
>
|
||||
<div
|
||||
v-for="(option, index) in field.options"
|
||||
:key="option.uuid"
|
||||
@@ -44,12 +55,22 @@
|
||||
<script>
|
||||
export default {
|
||||
name: 'MultiSelectStep',
|
||||
inject: ['t'],
|
||||
inject: ['t', 'scrollIntoField'],
|
||||
props: {
|
||||
field: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
isLastStep: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
default: false
|
||||
},
|
||||
showFieldNames: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
modelValue: {
|
||||
type: Array,
|
||||
required: false,
|
||||
@@ -62,6 +83,11 @@ export default {
|
||||
inputRefs: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
showOptions () {
|
||||
return this.showFieldNames && (this.field.options.some((e) => e.value) || this.field.options.length < 5)
|
||||
}
|
||||
},
|
||||
beforeUpdate () {
|
||||
this.inputRefs = []
|
||||
},
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<label
|
||||
:for="isCodeSent ? 'one_time_code' : field.uuid"
|
||||
class="label text-2xl mb-2"
|
||||
>{{ field.name || t('verified_phone_number') }}
|
||||
>{{ showFieldNames && field.name ? field.name : t('verified_phone_number') }}
|
||||
<template v-if="!field.required">({{ t('optional') }})</template>
|
||||
</label>
|
||||
<div>
|
||||
@@ -93,6 +93,11 @@ export default {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
showFieldNames: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
modelValue: {
|
||||
type: String,
|
||||
required: false,
|
||||
@@ -140,7 +145,11 @@ export default {
|
||||
})
|
||||
},
|
||||
async submit () {
|
||||
if (!this.isCodeSent) {
|
||||
if (!this.$refs.phone.value.toString().startsWith('+')) {
|
||||
alert(this.t('use_international_format'))
|
||||
|
||||
return Promise.reject(new Error('phone invalid'))
|
||||
} else if (!this.isCodeSent) {
|
||||
this.sendVerificationCode()
|
||||
|
||||
this.$emit('update:model-value', this.$refs.phone.value)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div class="flex justify-between items-center w-full mb-2">
|
||||
<label
|
||||
class="label text-2xl"
|
||||
>{{ field.name || t('signature') }}</label>
|
||||
>{{ showFieldNames && field.name ? field.name : t('signature') }}</label>
|
||||
<div class="space-x-2 flex">
|
||||
<span
|
||||
v-if="isTextSignature"
|
||||
@@ -41,7 +41,7 @@
|
||||
</span>
|
||||
<span
|
||||
class="tooltip"
|
||||
data-tip="Take photo"
|
||||
:data-tip="t('take_photo')"
|
||||
>
|
||||
<label
|
||||
class="btn btn-outline btn-sm font-medium"
|
||||
@@ -147,6 +147,11 @@ export default {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
showFieldNames: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
isDirectUpload: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
@@ -221,7 +226,7 @@ export default {
|
||||
},
|
||||
loadFont () {
|
||||
if (!isFontLoaded) {
|
||||
const font = new FontFace('Dancing Script', `url(${this.baseUrl}/fonts/DancingScript.otf) format("opentype")`)
|
||||
const font = new FontFace('Dancing Script', `url(${this.baseUrl}/fonts/DancingScript-Regular.otf) format("opentype")`)
|
||||
|
||||
font.load().then((loadedFont) => {
|
||||
document.fonts.add(loadedFont)
|
||||
@@ -260,6 +265,7 @@ export default {
|
||||
},
|
||||
toggleTextInput () {
|
||||
this.remove()
|
||||
this.clear()
|
||||
this.isTextSignature = !this.isTextSignature
|
||||
|
||||
if (this.isTextSignature) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<label
|
||||
v-if="field.name"
|
||||
v-if="showFieldNames && field.name"
|
||||
:for="field.uuid"
|
||||
dir="auto"
|
||||
class="label text-2xl"
|
||||
@@ -26,7 +26,8 @@
|
||||
v-model="text"
|
||||
:maxlength="cellsMaxLegth"
|
||||
dir="auto"
|
||||
class="base-input !text-2xl w-full !pr-11 -mr-10"
|
||||
class="base-input !text-2xl w-full"
|
||||
:class="{ '!pr-11 -mr-10': !field.validation?.pattern }"
|
||||
:required="field.required"
|
||||
:pattern="field.validation?.pattern"
|
||||
:oninvalid="field.validation?.message ? `this.setCustomValidity(${JSON.stringify(field.validation.message)})` : ''"
|
||||
@@ -50,7 +51,7 @@
|
||||
@focus="$emit('focus')"
|
||||
/>
|
||||
<div
|
||||
v-if="!isTextArea && field.type !== 'cells'"
|
||||
v-if="!isTextArea && field.type !== 'cells' && !field.validation?.pattern"
|
||||
class="tooltip"
|
||||
:data-tip="t('toggle_multiline_text')"
|
||||
>
|
||||
@@ -81,6 +82,11 @@ export default {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
showFieldNames: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
modelValue: {
|
||||
type: String,
|
||||
required: false,
|
||||
|
||||
@@ -43,7 +43,8 @@
|
||||
class="border-r"
|
||||
:compact="true"
|
||||
:editable="editable && !defaultField"
|
||||
:menu-classes="'dropdown-content bg-white menu menu-xs p-2 shadow rounded-box w-52 rounded-t-none -left-[1px]'"
|
||||
:allow-add-new="!defaultSubmitters.length"
|
||||
:menu-classes="'dropdown-content bg-white menu menu-xs p-2 shadow rounded-box w-52 rounded-t-none -left-[1px] mt-[1px]'"
|
||||
:submitters="template.submitters"
|
||||
@update:model-value="save"
|
||||
@click="selectedAreaRef.value = area"
|
||||
@@ -166,6 +167,11 @@ export default {
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
defaultSubmitters: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => []
|
||||
},
|
||||
editable: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
>
|
||||
<div
|
||||
v-if="$slots.buttons || withTitle"
|
||||
id="title_container"
|
||||
class="flex justify-between py-1.5 items-center pr-4 top-0 z-10"
|
||||
:class="{ sticky: withStickySubmitters || isBreakpointLg }"
|
||||
:style="{ backgroundColor }"
|
||||
@@ -61,6 +62,7 @@
|
||||
</span>
|
||||
</a>
|
||||
<button
|
||||
v-if="editable"
|
||||
class="base-button"
|
||||
:class="{ disabled: isSaving }"
|
||||
v-bind="isSaving ? { disabled: true } : {}"
|
||||
@@ -79,6 +81,15 @@
|
||||
{{ t('save') }}
|
||||
</span>
|
||||
</button>
|
||||
<a
|
||||
v-else
|
||||
:href="`/templates/${template.id}`"
|
||||
class="base-button"
|
||||
>
|
||||
<span class="hidden md:inline">
|
||||
{{ t('back') }}
|
||||
</span>
|
||||
</a>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
@@ -112,7 +123,7 @@
|
||||
/>
|
||||
<div
|
||||
class="sticky bottom-0 py-2"
|
||||
:style="withStickySubmitters ? { backgroundColor } : {}"
|
||||
:style="{ backgroundColor }"
|
||||
>
|
||||
<Upload
|
||||
v-if="sortedDocuments.length && editable && withUploadButton"
|
||||
@@ -148,6 +159,7 @@
|
||||
:is-drag="!!dragField"
|
||||
:default-fields="defaultFields"
|
||||
:allow-draw="!onlyDefinedFields"
|
||||
:default-submitters="defaultSubmitters"
|
||||
:draw-field="drawField"
|
||||
:editable="editable"
|
||||
:base-url="baseUrl"
|
||||
@@ -185,25 +197,6 @@
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<MobileDrawField
|
||||
v-if="drawField && isBreakpointLg"
|
||||
:draw-field="drawField"
|
||||
:fields="template.fields"
|
||||
:submitters="template.submitters"
|
||||
:selected-submitter="selectedSubmitter"
|
||||
class="md:hidden"
|
||||
:editable="editable"
|
||||
@cancel="[drawField = null, drawOption = null]"
|
||||
@change-submitter="[selectedSubmitter = $event, drawField.submitter_uuid = $event.uuid]"
|
||||
/>
|
||||
<MobileFields
|
||||
v-if="sortedDocuments.length && !drawField && editable"
|
||||
:fields="template.fields"
|
||||
:default-fields="defaultFields"
|
||||
:field-types="fieldTypes"
|
||||
:selected-submitter="selectedSubmitter"
|
||||
@select="startFieldDraw($event)"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="withFieldsList"
|
||||
@@ -259,6 +252,27 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sticky bottom-0">
|
||||
<MobileDrawField
|
||||
v-if="drawField && isBreakpointLg"
|
||||
:draw-field="drawField"
|
||||
:fields="template.fields"
|
||||
:submitters="template.submitters"
|
||||
:selected-submitter="selectedSubmitter"
|
||||
class="md:hidden"
|
||||
:editable="editable"
|
||||
@cancel="[drawField = null, drawOption = null]"
|
||||
@change-submitter="[selectedSubmitter = $event, drawField.submitter_uuid = $event.uuid]"
|
||||
/>
|
||||
<MobileFields
|
||||
v-if="sortedDocuments.length && !drawField && editable"
|
||||
:fields="template.fields"
|
||||
:default-fields="defaultFields"
|
||||
:field-types="fieldTypes"
|
||||
:selected-submitter="selectedSubmitter"
|
||||
@select="startFieldDraw($event)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -301,6 +315,7 @@ export default {
|
||||
template: this.template,
|
||||
save: this.save,
|
||||
t: this.t,
|
||||
currencies: this.currencies,
|
||||
baseFetch: this.baseFetch,
|
||||
fieldTypes: this.fieldTypes,
|
||||
backgroundColor: this.backgroundColor,
|
||||
@@ -349,6 +364,11 @@ export default {
|
||||
required: false,
|
||||
default: () => []
|
||||
},
|
||||
currencies: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => []
|
||||
},
|
||||
fieldTypes: {
|
||||
type: Array,
|
||||
required: false,
|
||||
@@ -884,7 +904,7 @@ export default {
|
||||
this.save()
|
||||
},
|
||||
onDocumentRemove (item) {
|
||||
if (window.confirm('Are you sure?')) {
|
||||
if (window.confirm(this.t('are_you_sure'))) {
|
||||
this.template.schema.splice(this.template.schema.indexOf(item), 1)
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
:allow-draw="allowDraw"
|
||||
:is-drag="isDrag"
|
||||
:default-fields="defaultFields"
|
||||
:default-submitters="defaultSubmitters"
|
||||
:draw-field="drawField"
|
||||
:selected-submitter="selectedSubmitter"
|
||||
:image="image"
|
||||
@@ -42,6 +43,11 @@ export default {
|
||||
required: false,
|
||||
default: () => []
|
||||
},
|
||||
defaultSubmitters: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => []
|
||||
},
|
||||
allowDraw: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
<button
|
||||
v-if="submitters.length > 1 && editable"
|
||||
class="px-2"
|
||||
@click.stop="remove(submitter)"
|
||||
@click.prevent.stop="remove(submitter)"
|
||||
>
|
||||
<IconTrashX :width="18" />
|
||||
</button>
|
||||
@@ -125,7 +125,8 @@
|
||||
/>
|
||||
</div>
|
||||
<span class="flex items-center transition-all duration-75 group-hover:border border-base-content/20 border-dashed w-6 h-6 flex justify-center items-center rounded">
|
||||
<IconPlus
|
||||
<component
|
||||
:is="editable ? 'IconPlus' : 'IconChevronDown'"
|
||||
width="18"
|
||||
height="18"
|
||||
/>
|
||||
@@ -165,7 +166,7 @@
|
||||
title="Up"
|
||||
class="relative w-2"
|
||||
style="font-size: 10px; margin-bottom: -4px"
|
||||
@click.stop="[move(submitter, -1), $refs.label.focus()] "
|
||||
@click.prevent.stop="[move(submitter, -1), $refs.label.focus()] "
|
||||
>
|
||||
▲
|
||||
</button>
|
||||
@@ -173,7 +174,7 @@
|
||||
title="Down"
|
||||
class="relative w-2"
|
||||
style="font-size: 10px; margin-top: -4px"
|
||||
@click.stop="[move(submitter, 1), $refs.label.focus()] "
|
||||
@click.prevent.stop="[move(submitter, 1), $refs.label.focus()] "
|
||||
>
|
||||
▼
|
||||
</button>
|
||||
@@ -181,14 +182,14 @@
|
||||
<button
|
||||
v-if="!compact && submitters.length > 1 && editable"
|
||||
class="hidden group-hover:block px-2"
|
||||
@click.stop="remove(submitter)"
|
||||
@click.prevent.stop="remove(submitter)"
|
||||
>
|
||||
<IconTrashX :width="18" />
|
||||
</button>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
<li v-if="submitters.length < 10 && editable">
|
||||
<li v-if="submitters.length < 10 && editable && allowAddNew">
|
||||
<a
|
||||
href="#"
|
||||
class="flex px-2"
|
||||
@@ -208,7 +209,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { IconUserPlus, IconTrashX, IconPlus, IconChevronUp } from '@tabler/icons-vue'
|
||||
import { IconUserPlus, IconTrashX, IconPlus, IconChevronUp, IconChevronDown } from '@tabler/icons-vue'
|
||||
import Contenteditable from './contenteditable'
|
||||
import { v4 } from 'uuid'
|
||||
|
||||
@@ -216,6 +217,7 @@ export default {
|
||||
name: 'FieldSubmitter',
|
||||
components: {
|
||||
IconUserPlus,
|
||||
IconChevronDown,
|
||||
Contenteditable,
|
||||
IconPlus,
|
||||
IconTrashX,
|
||||
@@ -242,6 +244,11 @@ export default {
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
allowAddNew: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
modelValue: {
|
||||
type: String,
|
||||
required: true
|
||||
@@ -296,7 +303,7 @@ export default {
|
||||
this.$emit('update:model-value', submitter.uuid)
|
||||
},
|
||||
remove (submitter) {
|
||||
if (window.confirm('Are you sure?')) {
|
||||
if (window.confirm(this.t('are_you_sure'))) {
|
||||
this.$emit('remove', submitter)
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
const en = {
|
||||
are_you_sure: 'Are you sure?',
|
||||
sign_yourself: 'Sign Yourself',
|
||||
send: 'Send',
|
||||
save: 'Save',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="fixed text-center w-full bottom-0 pr-6 mb-4">
|
||||
<div class="absolute text-center w-full bottom-0 pr-3 mb-4">
|
||||
<span class="w-full bg-base-200 px-4 py-2 rounded-md inline-flex space-x-2 mx-auto items-center justify-between mb-2 z-20 md:hidden">
|
||||
<div class="flex items-center space-x-2">
|
||||
<component
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<span
|
||||
class="dropdown dropdown-top dropdown-end fixed bottom-4 right-4 z-10 md:hidden"
|
||||
class="dropdown dropdown-top dropdown-end absolute bottom-4 right-4 z-10 md:hidden"
|
||||
>
|
||||
<label
|
||||
class="btn btn-neutral text-white btn-circle btn-lg group"
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
:field="item.field"
|
||||
:editable="editable"
|
||||
:default-field="defaultFields.find((f) => f.name === item.field.name)"
|
||||
:default-submitters="defaultSubmitters"
|
||||
@start-resize="resizeDirection = $event"
|
||||
@stop-resize="resizeDirection = null"
|
||||
@start-drag="isMove = true"
|
||||
@@ -85,6 +86,11 @@ export default {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
defaultSubmitters: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => []
|
||||
},
|
||||
drawField: {
|
||||
type: Object,
|
||||
required: false,
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
@change="save"
|
||||
>
|
||||
<option
|
||||
v-for="currency in currencies"
|
||||
v-for="currency in currenciesList"
|
||||
:key="currency"
|
||||
:value="currency"
|
||||
>
|
||||
@@ -167,7 +167,7 @@ export default {
|
||||
IconInnerShadowTop,
|
||||
IconBrandStripe
|
||||
},
|
||||
inject: ['backgroundColor', 'save'],
|
||||
inject: ['backgroundColor', 'save', 'currencies'],
|
||||
props: {
|
||||
field: {
|
||||
type: Object,
|
||||
@@ -187,9 +187,12 @@ export default {
|
||||
redirectUri () {
|
||||
return document.location.origin + '/auth/stripe_connect/callback'
|
||||
},
|
||||
currencies () {
|
||||
defaultCurrencies () {
|
||||
return ['USD', 'EUR', 'GBP']
|
||||
},
|
||||
currenciesList () {
|
||||
return this.currencies.length ? this.currencies : this.defaultCurrencies
|
||||
},
|
||||
authenticityToken () {
|
||||
return document.querySelector('meta[name="csrf-token"]')?.content
|
||||
},
|
||||
|
||||
@@ -4,7 +4,11 @@ class SendSubmitterInvitationEmailJob < ApplicationJob
|
||||
def perform(params = {})
|
||||
submitter = Submitter.find(params['submitter_id'])
|
||||
|
||||
SubmitterMailer.invitation_email(submitter).deliver_now!
|
||||
mail = SubmitterMailer.invitation_email(submitter)
|
||||
|
||||
Submitters::ValidateSending.call(submitter, mail)
|
||||
|
||||
mail.deliver_now!
|
||||
|
||||
SubmissionEvent.create!(submitter:, event_type: 'send_email')
|
||||
|
||||
|
||||
@@ -22,12 +22,26 @@ class Account < ApplicationRecord
|
||||
class_name: 'TemplateFolder', dependent: :destroy, inverse_of: :account
|
||||
has_many :submissions, through: :templates
|
||||
has_many :submitters, through: :submissions
|
||||
has_many :account_linked_accounts, dependent: :destroy
|
||||
has_many :account_testing_accounts, -> { testing }, dependent: :destroy,
|
||||
class_name: 'AccountLinkedAccount',
|
||||
inverse_of: :account
|
||||
has_one :linked_account_account, dependent: :destroy,
|
||||
foreign_key: :linked_account_id,
|
||||
class_name: 'AccountLinkedAccount',
|
||||
inverse_of: :linked_account
|
||||
has_many :linked_accounts, through: :account_linked_accounts
|
||||
has_many :testing_accounts, through: :account_testing_accounts, source: :linked_account
|
||||
has_many :active_users, -> { active }, dependent: :destroy,
|
||||
inverse_of: :account, class_name: 'User'
|
||||
|
||||
attribute :timezone, :string, default: 'UTC'
|
||||
attribute :locale, :string, default: 'en-US'
|
||||
|
||||
def testing?
|
||||
linked_account_account&.testing?
|
||||
end
|
||||
|
||||
def default_template_folder
|
||||
super || build_default_template_folder(name: TemplateFolder::DEFAULT_NAME,
|
||||
author_id: users.minimum(:id)).tap(&:save!)
|
||||
|
||||
@@ -27,8 +27,10 @@ class AccountConfig < ApplicationRecord
|
||||
BCC_EMAILS = 'bcc_emails'
|
||||
FORCE_MFA = 'force_mfa'
|
||||
ALLOW_TYPED_SIGNATURE = 'allow_typed_signature'
|
||||
ALLOW_TO_RESUBMIT = 'allow_to_resubmit'
|
||||
SUBMITTER_REMAILERS = 'submitter_reminders'
|
||||
FORM_COMPLETED_BUTTON_KEY = 'form_completed_button'
|
||||
FORM_WITH_CONFETTI_KEY = 'form_with_confetti'
|
||||
ESIGNING_PREFERENCE_KEY = 'esigning_preference'
|
||||
|
||||
DEFAULT_VALUES = {
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: account_linked_accounts
|
||||
#
|
||||
# id :bigint not null, primary key
|
||||
# account_type :text not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# account_id :bigint not null
|
||||
# linked_account_id :bigint not null
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
# idx_on_account_id_linked_account_id_48ab9f79d2 (account_id,linked_account_id) UNIQUE
|
||||
# index_account_linked_accounts_on_account_id (account_id)
|
||||
# index_account_linked_accounts_on_linked_account_id (linked_account_id)
|
||||
#
|
||||
# Foreign Keys
|
||||
#
|
||||
# fk_rails_... (account_id => accounts.id)
|
||||
# fk_rails_... (linked_account_id => accounts.id)
|
||||
#
|
||||
class AccountLinkedAccount < ApplicationRecord
|
||||
belongs_to :account
|
||||
belongs_to :linked_account, class_name: 'Account'
|
||||
|
||||
attribute :account_type, :string, default: 'testing'
|
||||
|
||||
scope :testing, -> { where(account_type: :testing) }
|
||||
|
||||
def testing?
|
||||
account_type == 'testing'
|
||||
end
|
||||
end
|
||||
@@ -15,11 +15,13 @@
|
||||
# template_submitters :text
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# account_id :bigint not null
|
||||
# created_by_user_id :bigint
|
||||
# template_id :bigint not null
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
# index_submissions_on_account_id (account_id)
|
||||
# index_submissions_on_created_by_user_id (created_by_user_id)
|
||||
# index_submissions_on_slug (slug) UNIQUE
|
||||
# index_submissions_on_template_id (template_id)
|
||||
@@ -31,7 +33,7 @@
|
||||
#
|
||||
class Submission < ApplicationRecord
|
||||
belongs_to :template
|
||||
has_one :account, through: :template
|
||||
belongs_to :account
|
||||
belongs_to :created_by_user, class_name: 'User', optional: true
|
||||
|
||||
has_many :submitters, dependent: :destroy
|
||||
|
||||
+25
-18
@@ -4,23 +4,24 @@
|
||||
#
|
||||
# Table name: submitters
|
||||
#
|
||||
# id :bigint not null, primary key
|
||||
# application_key :string
|
||||
# completed_at :datetime
|
||||
# email :string
|
||||
# ip :string
|
||||
# name :string
|
||||
# opened_at :datetime
|
||||
# phone :string
|
||||
# preferences :text not null
|
||||
# sent_at :datetime
|
||||
# slug :string not null
|
||||
# ua :string
|
||||
# uuid :string not null
|
||||
# values :text not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# submission_id :bigint not null
|
||||
# id :bigint not null, primary key
|
||||
# completed_at :datetime
|
||||
# email :string
|
||||
# ip :string
|
||||
# metadata :text not null
|
||||
# name :string
|
||||
# opened_at :datetime
|
||||
# phone :string
|
||||
# preferences :text not null
|
||||
# sent_at :datetime
|
||||
# slug :string not null
|
||||
# ua :string
|
||||
# uuid :string not null
|
||||
# values :text not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# external_id :string
|
||||
# submission_id :bigint not null
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
@@ -35,14 +36,16 @@
|
||||
class Submitter < ApplicationRecord
|
||||
belongs_to :submission
|
||||
has_one :template, through: :submission
|
||||
has_one :account, through: :template
|
||||
has_one :account, through: :submission
|
||||
|
||||
attribute :values, :string, default: -> { {} }
|
||||
attribute :preferences, :string, default: -> { {} }
|
||||
attribute :metadata, :string, default: -> { {} }
|
||||
attribute :slug, :string, default: -> { SecureRandom.base58(14) }
|
||||
|
||||
serialize :values, coder: JSON
|
||||
serialize :preferences, coder: JSON
|
||||
serialize :metadata, coder: JSON
|
||||
|
||||
has_many_attached :documents
|
||||
has_many_attached :attachments
|
||||
@@ -62,6 +65,10 @@ class Submitter < ApplicationRecord
|
||||
end
|
||||
end
|
||||
|
||||
def application_key
|
||||
external_id
|
||||
end
|
||||
|
||||
def friendly_name
|
||||
if name.present? && email.present? && email.exclude?(',')
|
||||
%("#{name.delete('"')}" <#{email}>)
|
||||
|
||||
+19
-14
@@ -4,20 +4,20 @@
|
||||
#
|
||||
# Table name: templates
|
||||
#
|
||||
# id :bigint not null, primary key
|
||||
# application_key :string
|
||||
# archived_at :datetime
|
||||
# fields :text not null
|
||||
# name :string not null
|
||||
# schema :text not null
|
||||
# slug :string not null
|
||||
# source :text not null
|
||||
# submitters :text not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# account_id :bigint not null
|
||||
# author_id :bigint not null
|
||||
# folder_id :bigint not null
|
||||
# id :bigint not null, primary key
|
||||
# archived_at :datetime
|
||||
# fields :text not null
|
||||
# name :string not null
|
||||
# schema :text not null
|
||||
# slug :string not null
|
||||
# source :text not null
|
||||
# submitters :text not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# account_id :bigint not null
|
||||
# author_id :bigint not null
|
||||
# external_id :string
|
||||
# folder_id :bigint not null
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
@@ -57,10 +57,15 @@ class Template < ApplicationRecord
|
||||
class_name: 'ActiveStorage::Attachment', dependent: :destroy, as: :record, inverse_of: :record
|
||||
|
||||
has_many :submissions, dependent: :destroy
|
||||
has_many :template_sharings, dependent: :destroy
|
||||
|
||||
scope :active, -> { where(archived_at: nil) }
|
||||
scope :archived, -> { where.not(archived_at: nil) }
|
||||
|
||||
def application_key
|
||||
external_id
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
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
|
||||
+2
-2
@@ -59,7 +59,7 @@ class User < ApplicationRecord
|
||||
has_many :encrypted_configs, dependent: :destroy, class_name: 'EncryptedUserConfig'
|
||||
has_many :email_messages, dependent: :destroy, foreign_key: :author_id, inverse_of: :author
|
||||
|
||||
devise :two_factor_authenticatable, :recoverable, :rememberable, :validatable, :trackable
|
||||
devise :two_factor_authenticatable, :recoverable, :rememberable, :validatable, :trackable, :lockable
|
||||
|
||||
attribute :role, :string, default: ADMIN_ROLE
|
||||
attribute :uuid, :string, default: -> { SecureRandom.uuid }
|
||||
@@ -72,7 +72,7 @@ class User < ApplicationRecord
|
||||
end
|
||||
|
||||
def active_for_authentication?
|
||||
!archived_at?
|
||||
super && !archived_at?
|
||||
end
|
||||
|
||||
def remember_me
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
</div>
|
||||
<% end %>
|
||||
<% encrypted_config = @encrypted_config || EncryptedConfig.find_or_initialize_by(account: current_account, key: EncryptedConfig::APP_URL_KEY) %>
|
||||
<% if !Docuseal.multitenant? && can?(:manage, encrypted_config) %>
|
||||
<% if !Docuseal.multitenant? && can?(:manage, encrypted_config) && !current_account.testing? %>
|
||||
<%= f.fields_for encrypted_config do |ff| %>
|
||||
<div class="form-control">
|
||||
<%= ff.label :value, 'App URL', class: 'label' %>
|
||||
@@ -63,6 +63,24 @@
|
||||
</div>
|
||||
<% 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>
|
||||
<% end %>
|
||||
<% if can?(:manage, current_account) && Docuseal.multitenant? && true_user == current_user %>
|
||||
<div class="px-1 mt-8">
|
||||
<h2 class="text-2xl font-bold mb-2">Danger Zone</h2>
|
||||
<%= button_to button_title(title: 'Delete my account'), settings_account_path, class: 'btn btn-outline btn-error block', data: { turbo_confirm: 'Schedule account for deletion?' }, method: :delete, id: :account_delete_button %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
<div class="flex flex-wrap space-y-4 md:flex-nowrap md:space-y-0 md:space-x-10">
|
||||
<%= render 'shared/settings_nav' %>
|
||||
<div class="flex-grow">
|
||||
<h1 class="text-4xl font-bold mb-4">API</h1>
|
||||
<div class="flex justify-between">
|
||||
<h1 class="text-4xl font-bold mb-4">API</h1>
|
||||
<%= render 'shared/test_mode_toggle' %>
|
||||
</div>
|
||||
<div class="card bg-base-200">
|
||||
<div class="card-body p-6">
|
||||
<label for="api_key" class="text-sm font-semibold">X-Auth-Token</label>
|
||||
<div class="flex flex-row space-x-4">
|
||||
<input id="api_key" type="text" value="<%= current_user.access_token.token %>" class="input font-mono input-bordered w-full" autocomplete="off" readonly>
|
||||
<%= render 'shared/clipboard_copy', icon: 'copy', text: current_user.access_token.token, class: 'base-button', icon_class: 'w-6 h-6 text-white', copy_title: 'Copy', copied_title: 'Copied' %>
|
||||
<%= button_to button_title(title: 'Rotate', disabled_with: 'Rotate', icon: svg_icon('reload', class: 'w-6 h-6')), settings_api_index_path, class: 'white-button', data: { turbo_confirm: 'Remove existing API token and generated a new one. Are you sure?' } %>
|
||||
<div class="flex flex-col md:flex-row gap-4">
|
||||
<div class="flex w-full space-x-4">
|
||||
<input id="api_key" type="text" value="<%= current_user.access_token.token %>" class="input font-mono input-bordered w-full" autocomplete="off" readonly>
|
||||
<%= render 'shared/clipboard_copy', icon: 'copy', text: current_user.access_token.token, class: 'base-button', icon_class: 'w-6 h-6 text-white', copy_title: 'Copy', copied_title: 'Copied' %>
|
||||
</div>
|
||||
<%= button_to button_title(title: 'Rotate', disabled_with: 'Rotate', icon: svg_icon('reload', class: 'w-6 h-6')), settings_api_index_path, class: 'white-button w-full', data: { turbo_confirm: 'Remove existing API token and generated a new one. Are you sure?' } %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -30,20 +35,16 @@
|
||||
--header 'X-Auth-Token: <%= current_user.access_token.token %>' \
|
||||
--data-raw '{
|
||||
"template_id": <%= current_account.templates.last&.id || 1 %>,
|
||||
"submission": [
|
||||
"submitters": [
|
||||
{
|
||||
"submitters": [
|
||||
{
|
||||
"name": "John Doe",
|
||||
"role": "<%= current_account.templates.last ? current_account.templates.last.submitters.first['name'] : 'First Party' %>",
|
||||
"email": "<%= current_user.email.sub('@', '+test@') %>",
|
||||
"values": {
|
||||
"Form Text Field Name": "Default Value"
|
||||
}
|
||||
},
|
||||
{ "role": "Second Submitter", "email": "<%= current_user.email.sub('@', '+test2@') %>" }
|
||||
]
|
||||
}
|
||||
"name": "John Doe",
|
||||
"role": "<%= current_account.templates.last ? current_account.templates.last.submitters.first['name'] : 'First Party' %>",
|
||||
"email": "<%= current_user.email.sub('@', '+test@') %>",
|
||||
"values": {
|
||||
"Form Text Field Name": "Default Value"
|
||||
}
|
||||
},
|
||||
{ "role": "Second Submitter", "email": "<%= current_user.email.sub('@', '+test2@') %>" }
|
||||
]
|
||||
}'<% end.to_str %>
|
||||
<span class="top-0 right-0 absolute">
|
||||
|
||||
@@ -1,101 +1,80 @@
|
||||
<% if Docuseal.demo? %><%= render 'shared/demo_alert' %><% end %>
|
||||
<% if @pagy.count > 0 || params[:q].present? || @template_folders.present? %>
|
||||
<div class="flex justify-between mb-4 items-center">
|
||||
<h1 class="text-4xl font-bold"><span class="hidden md:inline">Document</span> Templates</h1>
|
||||
<div class="flex space-x-2">
|
||||
<% if params[:q].present? || @pagy.pages > 1 || @template_folders.present? %>
|
||||
<%= render 'shared/search_input' %>
|
||||
<div class="flex justify-between mb-4 items-center">
|
||||
<h1 class="text-4xl font-bold"><span class="hidden md:inline">Document</span> Templates</h1>
|
||||
<div class="flex space-x-2">
|
||||
<% if params[:q].present? || @pagy.pages > 1 || @template_folders.present? %>
|
||||
<%= render 'shared/search_input' %>
|
||||
<% end %>
|
||||
<% if can?(:create, ::Template) %>
|
||||
<%= render 'templates/upload_button' %>
|
||||
<%= link_to new_template_path, class: 'white-button !border gap-2', data: { turbo_frame: :modal } do %>
|
||||
<%= svg_icon('plus', class: 'w-6 h-6 stroke-2') %>
|
||||
<span class="hidden md:block">Create</span>
|
||||
<% end %>
|
||||
<% if can?(:create, ::Template) %>
|
||||
<%= render 'templates/upload_button' %>
|
||||
<%= link_to new_template_path, class: 'white-button !border gap-2', data: { turbo_frame: :modal } do %>
|
||||
<%= svg_icon('plus', class: 'w-6 h-6 stroke-2') %>
|
||||
<span class="hidden md:block">Create</span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% view_archived_html = capture do %>
|
||||
<% if current_account.templates.where.not(archived_at: nil).exists? %>
|
||||
<div>
|
||||
<a href="<%= templates_archived_index_path %>" class="link text-sm">View Archived</a>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% if @template_folders.present? %>
|
||||
<div class="grid gap-4 md:grid-cols-3 <%= 'mb-6' if @templates.present? %>">
|
||||
<%= render partial: 'template_folders/folder', collection: @template_folders, as: :folder %>
|
||||
</div>
|
||||
<% view_archived_html = capture do %>
|
||||
<% if current_account.templates.where.not(archived_at: nil).exists? %>
|
||||
<div>
|
||||
<a href="<%= templates_archived_index_path %>" class="link text-sm">View Archived</a>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if @templates.present? %>
|
||||
<div class="grid gap-4 md:grid-cols-3">
|
||||
<%= render partial: 'templates/template', collection: @templates %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if params[:q].blank? && @pagy.pages == 1 && ((@template_folders.size < 10 && @templates.size.zero?) || (@template_folders.size < 7 && @templates.size < 4) || (@template_folders.size < 4 && @templates.size < 7)) %>
|
||||
<%= form_for '', url: templates_upload_path, id: form_id = SecureRandom.uuid, method: :post, class: 'mt-8 block', html: { enctype: 'multipart/form-data' } do %>
|
||||
<input type="hidden" name="form_id" value="<%= form_id %>">
|
||||
<button type="submit" class="hidden"></button>
|
||||
<file-dropzone data-submit-on-upload="true" class="w-full">
|
||||
<label for="file_dropzone_input" class="w-full block h-52 relative hover:bg-base-200/30 rounded-xl border border-2 border-base-300 border-dashed">
|
||||
<div class="absolute top-0 right-0 left-0 bottom-0 flex items-center justify-center">
|
||||
<div class="flex flex-col items-center">
|
||||
<span data-target="file-dropzone.icon" class="flex flex-col items-center">
|
||||
<span>
|
||||
<%= svg_icon('cloud_upload', class: 'w-10 h-10') %>
|
||||
</span>
|
||||
<div class="font-medium mb-1">
|
||||
Upload New Document
|
||||
</div>
|
||||
<div class="text-xs">
|
||||
<span class="font-medium">Click to upload</span> or drag and drop
|
||||
</div>
|
||||
<% end %>
|
||||
<% if @template_folders.present? %>
|
||||
<div class="grid gap-4 md:grid-cols-3 <%= 'mb-6' if @templates.present? %>">
|
||||
<%= render partial: 'template_folders/folder', collection: @template_folders, as: :folder %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if @templates.present? %>
|
||||
<div class="grid gap-4 md:grid-cols-3">
|
||||
<%= render partial: 'templates/template', collection: @templates %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if params[:q].blank? && @pagy.pages == 1 && ((@template_folders.size < 10 && @templates.size.zero?) || (@template_folders.size < 7 && @templates.size < 4) || (@template_folders.size < 4 && @templates.size < 7)) %>
|
||||
<%= form_for '', url: templates_upload_path, id: form_id = SecureRandom.uuid, method: :post, class: 'mt-8 block', html: { enctype: 'multipart/form-data' } do %>
|
||||
<input type="hidden" name="form_id" value="<%= form_id %>">
|
||||
<button type="submit" class="hidden"></button>
|
||||
<file-dropzone data-submit-on-upload="true" class="w-full">
|
||||
<label for="file_dropzone_input" class="w-full block h-52 relative hover:bg-base-200/30 rounded-xl border border-2 border-base-300 border-dashed">
|
||||
<div class="absolute top-0 right-0 left-0 bottom-0 flex items-center justify-center">
|
||||
<div class="flex flex-col items-center">
|
||||
<span data-target="file-dropzone.icon" class="flex flex-col items-center">
|
||||
<span>
|
||||
<%= svg_icon('cloud_upload', class: 'w-10 h-10') %>
|
||||
</span>
|
||||
<span data-target="file-dropzone.loading" class="flex flex-col items-center hidden">
|
||||
<%= svg_icon('loader', class: 'w-10 h-10 animate-spin') %>
|
||||
<div class="font-medium mb-1">
|
||||
Uploading...
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
<input id="file_dropzone_input" name="files[]" class="hidden" data-action="change:file-dropzone#onSelectFiles" data-target="file-dropzone.input" type="file" accept="image/*, application/pdf<%= ', .docx, .doc, .xlsx, .xls' if Docuseal.multitenant? %>" multiple>
|
||||
</div>
|
||||
</label>
|
||||
</file-dropzone>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% if @templates.present? || params[:q].blank? %>
|
||||
<% if @pagy.pages > 1 %>
|
||||
<%= render 'shared/pagination', pagy: @pagy, items_name: 'templates', left_additional_html: view_archived_html %>
|
||||
<% else %>
|
||||
<div class="mt-2">
|
||||
<%= view_archived_html %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% elsif params[:q].present? %>
|
||||
<div class="text-center">
|
||||
<div class="mt-16 text-3xl font-semibold">
|
||||
Templates not Found
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<div class="card bg-base-200 h-96 mb-2">
|
||||
<div class="card-body text-center">
|
||||
<div class="flex items-center h-full">
|
||||
<div class="mx-auto">
|
||||
<div class="max-w-xl mx-auto">
|
||||
<h1 class="text-5xl font-bold text-base-content">👋 Welcome to <%= Docuseal.product_name %></h1>
|
||||
</div>
|
||||
<div class="max-w-lg mx-auto">
|
||||
<p class="py-6 text-gray-600">Streamline document workflows, from creating customizable templates to filling and signing document forms</p>
|
||||
<%= link_to new_template_path, class: 'base-button', data: { turbo_frame: :modal } do %>
|
||||
<%= svg_icon('plus', class: 'w-6 h-6 stroke-2') %>
|
||||
<span class="mr-1">Create Template</span>
|
||||
<% end %>
|
||||
<div class="font-medium mb-1">
|
||||
Upload New Document
|
||||
</div>
|
||||
<div class="text-xs">
|
||||
<span class="font-medium">Click to upload</span> or drag and drop
|
||||
</div>
|
||||
</span>
|
||||
<span data-target="file-dropzone.loading" class="flex flex-col items-center hidden">
|
||||
<%= svg_icon('loader', class: 'w-10 h-10 animate-spin') %>
|
||||
<div class="font-medium mb-1">
|
||||
Uploading...
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
<input id="file_dropzone_input" name="files[]" class="hidden" data-action="change:file-dropzone#onSelectFiles" data-target="file-dropzone.input" type="file" accept="image/*, application/pdf<%= ', .docx, .doc, .xlsx, .xls' if Docuseal.multitenant? %>" multiple>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
</file-dropzone>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% if @templates.present? || params[:q].blank? %>
|
||||
<% if @pagy.pages > 1 %>
|
||||
<%= render 'shared/pagination', pagy: @pagy, items_name: 'templates', left_additional_html: view_archived_html %>
|
||||
<% else %>
|
||||
<div class="mt-2">
|
||||
<%= view_archived_html %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% elsif params[:q].present? %>
|
||||
<div class="text-center">
|
||||
<div class="mt-16 text-3xl font-semibold">
|
||||
Templates not Found
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
<div class="max-w-lg mx-auto px-2">
|
||||
<%= render 'devise/shared/select_server' if Docuseal.multitenant? %>
|
||||
<h1 class="text-3xl font-bold text-center mt-8 mb-6">Create Free Account</h1>
|
||||
<% if params[:redir].to_s.ends_with?('on_premise') %>
|
||||
<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| %>
|
||||
<% if params[:redir].present? %>
|
||||
<%= hidden_field_tag :redir, params[:redir] %>
|
||||
@@ -17,15 +25,19 @@
|
||||
<% end %>
|
||||
<% if devise_mapping.omniauthable? %>
|
||||
<div class="space-y-4">
|
||||
<%= form_for '', url: omniauth_authorize_path(resource_name, :google_oauth2), data: { turbo: false }, method: :post do |f| %>
|
||||
<set-timezone data-input-id="state" data-params="true"></set-timezone>
|
||||
<%= hidden_field_tag :state, { redir: params[:redir].to_s }.compact_blank.to_query %>
|
||||
<%= f.button button_title(title: 'Sign up with Google', icon: svg_icon('brand_google', class: 'w-6 h-6')), class: 'white-button w-full mt-4' %>
|
||||
<% if User.omniauth_providers.include?(:google_oauth2) %>
|
||||
<%= form_for '', url: omniauth_authorize_path(resource_name, :google_oauth2), data: { turbo: false }, method: :post do |f| %>
|
||||
<set-timezone data-input-id="state" data-params="true"></set-timezone>
|
||||
<%= hidden_field_tag :state, { redir: params[:redir].to_s }.compact_blank.to_query %>
|
||||
<%= f.button button_title(title: 'Sign up with Google', icon: svg_icon('brand_google', class: 'w-6 h-6')), class: 'white-button w-full mt-4' %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<%= form_for '', url: omniauth_authorize_path(resource_name, :microsoft_office365), data: { turbo: false }, method: :post do |f| %>
|
||||
<set-timezone data-input-id="state_microsoft" data-params="true"></set-timezone>
|
||||
<%= hidden_field_tag :state, { redir: params[:redir].to_s }.compact_blank.to_query, id: 'state_microsoft' %>
|
||||
<%= f.button button_title(title: 'Sign up with Microsoft', icon: svg_icon('brand_microsoft', class: 'w-6 h-6')), class: 'white-button w-full' %>
|
||||
<% if User.omniauth_providers.include?(:microsoft_office365) %>
|
||||
<%= form_for '', url: omniauth_authorize_path(resource_name, :microsoft_office365), data: { turbo: false }, method: :post do |f| %>
|
||||
<set-timezone data-input-id="state_microsoft" data-params="true"></set-timezone>
|
||||
<%= hidden_field_tag :state, { redir: params[:redir].to_s }.compact_blank.to_query, id: 'state_microsoft' %>
|
||||
<%= f.button button_title(title: 'Sign up with Microsoft', icon: svg_icon('brand_microsoft', class: 'w-6 h-6')), class: 'white-button w-full' %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
@@ -21,15 +21,19 @@
|
||||
<% end %>
|
||||
<% if devise_mapping.omniauthable? %>
|
||||
<div class="space-y-4">
|
||||
<%= form_for '', url: omniauth_authorize_path(resource_name, :google_oauth2), data: { turbo: false }, method: :post do |f| %>
|
||||
<set-timezone data-input-id="state" data-params="true"></set-timezone>
|
||||
<%= hidden_field_tag :state, { redir: params[:redir].to_s }.compact_blank.to_query %>
|
||||
<%= f.button button_title(title: 'Sign in with Google', icon: svg_icon('brand_google', class: 'w-6 h-6')), class: 'white-button w-full mt-4' %>
|
||||
<% if User.omniauth_providers.include?(:google_oauth2) %>
|
||||
<%= form_for '', url: omniauth_authorize_path(resource_name, :google_oauth2), data: { turbo: false }, method: :post do |f| %>
|
||||
<set-timezone data-input-id="state" data-params="true"></set-timezone>
|
||||
<%= hidden_field_tag :state, { redir: params[:redir].to_s }.compact_blank.to_query %>
|
||||
<%= f.button button_title(title: 'Sign in with Google', icon: svg_icon('brand_google', class: 'w-6 h-6')), class: 'white-button w-full mt-4' %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<%= form_for '', url: omniauth_authorize_path(resource_name, :microsoft_office365), data: { turbo: false }, method: :post do |f| %>
|
||||
<set-timezone data-input-id="state_microsoft" data-params="true"></set-timezone>
|
||||
<%= hidden_field_tag :state, { redir: params[:redir].to_s }.compact_blank.to_query, id: 'state_microsoft' %>
|
||||
<%= f.button button_title(title: 'Sign in with Microsoft', icon: svg_icon('brand_microsoft', class: 'w-6 h-6')), class: 'white-button w-full' %>
|
||||
<% if User.omniauth_providers.include?(:microsoft_office365) %>
|
||||
<%= form_for '', url: omniauth_authorize_path(resource_name, :microsoft_office365), data: { turbo: false }, method: :post do |f| %>
|
||||
<set-timezone data-input-id="state_microsoft" data-params="true"></set-timezone>
|
||||
<%= hidden_field_tag :state, { redir: params[:redir].to_s }.compact_blank.to_query, id: 'state_microsoft' %>
|
||||
<%= f.button button_title(title: 'Sign in with Microsoft', icon: svg_icon('brand_microsoft', class: 'w-6 h-6')), class: 'white-button w-full' %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<% end %>
|
||||
<div class="space-y-2">
|
||||
<div class="form-control">
|
||||
<%= f.label :otp_attempt, 'Two-Factor Code', class: 'label' %>
|
||||
<%= f.label :otp_attempt, 'Two-Factor Code from Authenticator App', class: 'label' %>
|
||||
<%= f.text_field :otp_attempt, autofocus: true, placeholder: 'XXX-XXX', required: true, class: 'base-input' %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
<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="M4 13h5" />
|
||||
<path d="M12 16v-8h3a2 2 0 0 1 2 2v1a2 2 0 0 1 -2 2h-3" />
|
||||
<path d="M20 8v8" />
|
||||
<path d="M9 16v-5.5a2.5 2.5 0 0 0 -5 0v5.5" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 439 B |
@@ -0,0 +1,4 @@
|
||||
<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="M15 6l-6 6l6 6" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 313 B |
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="<%= local_assigns[:class] %>" width="44" height="44" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path 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 |
@@ -0,0 +1,7 @@
|
||||
<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="M8.5 13.5l-1.5 -1.5l1.5 -1.5" />
|
||||
<path d="M15.5 10.5l1.5 1.5l-1.5 1.5" />
|
||||
<path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" />
|
||||
<path d="M13 9.5l-2 5.5" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 458 B |
@@ -3,7 +3,7 @@
|
||||
Use an authenticator mobile app like Google Authenticator or 1Password to scan the QR code below.
|
||||
</p>
|
||||
<div>
|
||||
<%== RQRCode::QRCode.new(@provision_url).as_svg(viewbox: true, svg_attributes: { class: 'w-80 h-80 my-4 mx-auto' }) %>
|
||||
<%== RQRCode::QRCode.new(@provision_url).as_svg(viewbox: true, svg_attributes: { class: 'md:w-80 md:h-80 my-4 mx-auto' }) %>
|
||||
</div>
|
||||
<div class="form-control my-6 space-y-2">
|
||||
<%= f.text_field :otp_attempt, required: true, placeholder: 'XXX-XXX', class: 'base-input text-center' %>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<%= f.hidden_field :key %>
|
||||
<div class="form-control">
|
||||
<% record = Struct.new(:first_duration, :second_duration, :third_duration).new(*(f.object.value || {}).values_at('first_duration', 'second_duration', 'third_duration')) %>
|
||||
<div class="flex space-x-4">
|
||||
<div class="flex flex-col md:flex-row gap-4">
|
||||
<div class="w-full">
|
||||
<%= f.fields_for :value, record do |ff| %>
|
||||
<%= ff.label :first_duration, 'First reminder in', class: 'label' %>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<p>
|
||||
Send automatic email reminders to your recipients.
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<p>
|
||||
Display your company name and logo when signing documents.
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
<%= 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
|
||||
<% end %>
|
||||
<% if signed_in? && current_user != true_user %>
|
||||
<span class="hidden md:inline-flex h-3 border-r border-base-content"></span>
|
||||
<%= render 'shared/test_alert' %>
|
||||
<% end %>
|
||||
<span class="hidden md:inline-flex h-3 border-r border-base-content"></span>
|
||||
|
||||
@@ -11,17 +11,17 @@
|
||||
<%= link_to 'Account', settings_account_path, class: 'text-base hover:bg-base-300' %>
|
||||
</li>
|
||||
<% unless Docuseal.multitenant? %>
|
||||
<% if can?(:read, EncryptedConfig.new(key: EncryptedConfig::EMAIL_SMTP_KEY, account: current_account)) && ENV['SMTP_ADDRESS'].blank? %>
|
||||
<% if can?(:read, EncryptedConfig.new(key: EncryptedConfig::EMAIL_SMTP_KEY, account: current_account)) && ENV['SMTP_ADDRESS'].blank? && true_user == current_user %>
|
||||
<li>
|
||||
<%= link_to 'Email', settings_email_index_path, class: 'text-base hover:bg-base-300' %>
|
||||
</li>
|
||||
<% end %>
|
||||
<% if can?(:read, EncryptedConfig.new(key: EncryptedConfig::FILES_STORAGE_KEY, account: current_account)) %>
|
||||
<% if can?(:read, EncryptedConfig.new(key: EncryptedConfig::FILES_STORAGE_KEY, account: current_account)) && true_user == current_user %>
|
||||
<li>
|
||||
<%= link_to 'Storage', settings_storage_index_path, class: 'text-base hover:bg-base-300' %>
|
||||
</li>
|
||||
<% end %>
|
||||
<% if can?(:read, EncryptedConfig.new(key: 'submitter_invitation_sms', account: current_account)) %>
|
||||
<% if can?(:read, EncryptedConfig.new(key: 'submitter_invitation_sms', account: current_account)) && true_user == current_user %>
|
||||
<li>
|
||||
<%= link_to 'SMS', settings_sms_path, class: 'text-base hover:bg-base-300' %>
|
||||
</li>
|
||||
@@ -43,7 +43,7 @@
|
||||
</li>
|
||||
<% end %>
|
||||
<% unless Docuseal.multitenant? %>
|
||||
<% if can?(:read, EncryptedConfig.new(key: 'saml_configs', account: current_account)) %>
|
||||
<% if can?(:read, EncryptedConfig.new(key: 'saml_configs', account: current_account)) && true_user == current_user %>
|
||||
<li>
|
||||
<%= link_to 'SSO', settings_sso_index_path, class: 'text-base hover:bg-base-300' %>
|
||||
</li>
|
||||
@@ -69,7 +69,7 @@
|
||||
<%= render 'shared/settings_nav_extra' %>
|
||||
<% if !Docuseal.demo? && can?(:manage, EncryptedConfig) %>
|
||||
<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
|
||||
<span class="badge badge-warning">New</span>
|
||||
<% end %>
|
||||
@@ -88,10 +88,22 @@
|
||||
<% end %>
|
||||
</li>
|
||||
<% end %>
|
||||
<% if (can?(:manage, EncryptedConfig) && current_user == true_user) || (current_user != true_user && current_account.testing?) %>
|
||||
<li>
|
||||
<%= form_for '', url: testing_account_path, method: current_account.testing? ? :delete : :get, html: { class: 'flex w-full' } do |f| %>
|
||||
<label class="flex items-center text-base hover:bg-base-300 w-full justify-between" for="testing_toggle">
|
||||
<span class="mr-2 w-full">
|
||||
Test Environment
|
||||
</span>
|
||||
<%= f.check_box :testing_toggle, class: 'toggle toggle-sm', checked: current_account.testing?, onchange: 'this.form.requestSubmit()' %>
|
||||
</label>
|
||||
<% end %>
|
||||
</li>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</ul>
|
||||
</menu-active>
|
||||
<% if !can?(:manage, :tenants) %>
|
||||
<% if Docuseal.multitenant? || cannot?(:manage, :tenants) %>
|
||||
<div class="mx-4 border-t border-base-300 hidden md:block">
|
||||
<div class="text-sm mt-3">
|
||||
Need help? Ask a question:
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
<% if signed_in? && current_user != true_user && current_account.testing? %>
|
||||
<div class="alert py-1 text-sm font-medium gap-x-2 flex whitespace-nowrap">
|
||||
<a href="<%= testing_api_settings_path %>" data-turbo-frame="modal" class="link font-semibold flex">
|
||||
<%= svg_icon('code_circle', class: 'w-5 h-5 mr-1') %>Testing Environment
|
||||
</a>
|
||||
<span>
|
||||
|
|
||||
</span>
|
||||
<%= button_to testing_account_path, method: :delete, class: 'inline flex' do %>
|
||||
<% title = capture do %>
|
||||
<span class="link">Exit</span>
|
||||
<span>×</span>
|
||||
<% end %>
|
||||
<%= button_title(title:, disabled_with: 'Leave') %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
@@ -0,0 +1,10 @@
|
||||
<% if can?(:manage, EncryptedConfig) || (current_user != true_user && current_account.testing?) %>
|
||||
<%= form_for '', url: testing_account_path, method: current_account.testing? ? :delete : :get, html: { class: 'flex' } do |f| %>
|
||||
<label class="flex items-center justify-between py-2.5" for="testing_toggle">
|
||||
<span class="mr-2 text-lg">
|
||||
Test Environment
|
||||
</span>
|
||||
<%= f.check_box :testing_toggle, class: 'toggle', checked: current_account.testing?, onchange: 'this.form.requestSubmit()' %>
|
||||
</label>
|
||||
<% end %>
|
||||
<% end %>
|
||||
@@ -5,7 +5,7 @@
|
||||
<p class="text-gray-700">
|
||||
Unlock with DocuSeal Pro
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<p class="text-gray-700">
|
||||
Unlock with DocuSeal Pro
|
||||
<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>
|
||||
</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' %>
|
||||
</div>
|
||||
<% 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>
|
||||
<%= 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>
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<%= fff.label :endpoint, 'Endpoint (optional)', class: 'label' %>
|
||||
<%= fff.text_field :endpoint, value: configs['endpoint'], type: :url, class: 'base-input' %>
|
||||
<label class="label">
|
||||
<span class="label-text-alt">For AWS S3 compantiable APIs like Minio.</span>
|
||||
<span class="label-text-alt">For AWS S3 compatible APIs like Minio.</span>
|
||||
</label>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
@@ -6,181 +6,7 @@
|
||||
</div>
|
||||
</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="">
|
||||
<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>
|
||||
<%= render 'templates/embedding', template: %>
|
||||
<div class="mt-4">
|
||||
<a href="#" class="link text-center block" data-action="click:turbo-modal#close">Close</a>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<div class="mt-2 mb-1">
|
||||
<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>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<% options.each_with_index do |(label, value), index| %>
|
||||
<span>
|
||||
<%= radio_button_tag 'option', value, value == 'email', class: 'peer hidden', data: { action: 'change:toggle-visible#trigger' } %>
|
||||
<label for="option_<%= value %>" class="<%= '!rounded-s-full' if index.zero? %> btn btn-focus btn-sm join-item w-28 peer-checked:btn-active normal-case">
|
||||
<label for="option_<%= value %>" class="<%= '!rounded-s-full' if index.zero? %> btn btn-focus btn-sm join-item md:w-28 peer-checked:btn-active normal-case">
|
||||
<%= label %>
|
||||
</label>
|
||||
</span>
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
</a>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="w-full overflow-y-auto overflow-x-hidden mt-0.5 pt-0.5">
|
||||
<div id="document_view" class="w-full overflow-y-auto overflow-x-hidden mt-0.5 pt-0.5">
|
||||
<div class="pr-3.5 pl-0.5">
|
||||
<% fields_index = Templates.build_field_areas_index(@submission.template_fields || @submission.template.fields) %>
|
||||
<% values = @submission.submitters.reduce({}) { |acc, sub| acc.merge(sub.values) } %>
|
||||
@@ -71,7 +71,7 @@
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hidden md:block relative w-80 flex-none pt-0.5 pr-4 pl-0.5 overflow-auto space">
|
||||
<div id="parties_view" class="hidden md:block relative w-full md:w-80 flex-none pt-0.5 pr-4 pl-0.5 overflow-auto space">
|
||||
<% colors = %w[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] %>
|
||||
<% submitter_fields_index = (@submission.template_fields || @submission.template.fields).group_by { |f| f['submitter_uuid'] } %>
|
||||
<% submitter_field_counters = Hash.new { 0 } %>
|
||||
@@ -173,5 +173,16 @@
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<label class="md:hidden btn btn-sm btn-neutral text-white text-base z-10 fixed bottom-2 right-2 h-16 shadow-lg">
|
||||
<input type="checkbox" class="peer hidden" onclick="[document_view.classList.toggle('hidden'), parties_view.classList.toggle('hidden')]">
|
||||
<span class="peer-checked:hidden flex items-center space-x-2">
|
||||
<%= svg_icon('users', class: 'w-8 h-8') %>
|
||||
<span>Signers</span>
|
||||
</span>
|
||||
<span class="hidden peer-checked:flex items-center">
|
||||
<%= svg_icon('chevron_left', class: 'w-8 h-8') %>
|
||||
<span>Back</span>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<%= render 'scripts/autosize_field' %>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<% data_attachments = attachments_index.values.select { |e| e.record_id == submitter.id }.to_json(only: %i[uuid], methods: %i[url filename content_type]) %>
|
||||
<% data_fields = (submitter.submission.template_fields || submitter.submission.template.fields).select { |f| f['submitter_uuid'] == submitter.uuid }.to_json %>
|
||||
<% configs = Submitters::FormConfigs.call(submitter) %>
|
||||
<submission-form data-is-demo="<%= Docuseal.demo? %>" data-completed-redirect-url="<%= submitter.preferences['completed_redirect_url'] %>" data-completed-button="<%= configs[:completed_button].to_json %>" data-go-to-last="<%= submitter.opened_at? %>" data-is-direct-upload="<%= Docuseal.active_storage_public? %>" data-submitter="<%= submitter.to_json(only: %i[uuid slug name phone email]) %>" data-can-send-email="<%= Accounts.can_send_emails?(Struct.new(:id).new(@submitter.submission.template.account_id)) %>" data-attachments="<%= data_attachments %>" data-fields="<%= data_fields %>" data-values="<%= submitter.values.to_json %>" data-with-typed-signature="<%= configs[:with_typed_signature] %>"></submission-form>
|
||||
<submission-form data-is-demo="<%= Docuseal.demo? %>" data-with-confetti="<%= configs[:with_confetti] %>" data-completed-redirect-url="<%= submitter.preferences['completed_redirect_url'] %>" data-completed-button="<%= configs[:completed_button].to_json %>" data-go-to-last="<%= submitter.opened_at? %>" data-is-direct-upload="<%= Docuseal.active_storage_public? %>" data-submitter="<%= submitter.to_json(only: %i[uuid slug name phone email]) %>" data-can-send-email="<%= Accounts.can_send_emails?(Struct.new(:id).new(@submitter.submission.template.account_id)) %>" data-attachments="<%= data_attachments %>" data-fields="<%= data_fields %>" data-values="<%= submitter.values.to_json %>" data-with-typed-signature="<%= configs[:with_typed_signature] %>"></submission-form>
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
</span>
|
||||
</download-button>
|
||||
</div>
|
||||
<% if @submitter.submission.template.submitters.size == 1 %>
|
||||
<% 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>
|
||||
<%= 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? %>
|
||||
<% body = (@body.presence || @email_config.value['body']).to_s %>
|
||||
<%= 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>
|
||||
<% end %>
|
||||
<% 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' } %>
|
||||
<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'] } } %>
|
||||
<% is_submission_completed = submitters.all?(&:completed_at?) && submitters.size.positive? %>
|
||||
<% if submitters.size == 1 %>
|
||||
<div>
|
||||
<% submitter = submitters.first %>
|
||||
<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) %>">
|
||||
<span class="badge <%= status_badges[submitter.status] %> md:w-32 badge-lg bg-opacity-50 uppercase text-sm font-semibold">
|
||||
<%= submitter.status %>
|
||||
@@ -20,14 +20,14 @@
|
||||
</div>
|
||||
<div class="flex space-x-2 items-center">
|
||||
<% if submitter.completed_at? %>
|
||||
<form onsubmit="event.preventDefault()">
|
||||
<button onclick="event.stopPropagation()">
|
||||
<download-button data-src="<%= submitter_download_index_path(submitter.slug) %>" class="btn btn-sm btn-neutral text-white md:w-36">
|
||||
<span class="flex items-center justify-center space-x-2" data-target="download-button.defaultButton">
|
||||
<form onsubmit="event.preventDefault()" class="flex-1 md:flex-none">
|
||||
<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 w-full md:w-36">
|
||||
<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') %>
|
||||
<span class="inline">Download</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') %>
|
||||
<span class="inline">Downloa...</span>
|
||||
</span>
|
||||
@@ -36,26 +36,30 @@
|
||||
</form>
|
||||
<% else %>
|
||||
<% if current_user.email == submitter.email %>
|
||||
<form data-turbo="false" method="get" action="<%= submit_form_url(slug: submitter.slug) %>">
|
||||
<button onclick="event.stopPropagation()" class="btn btn-sm btn-neutral btn-outline bg-white md:w-36 flex">
|
||||
<span class="flex items-center justify-center space-x-2">
|
||||
<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 w-full md:w-36 flex">
|
||||
<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') %>
|
||||
<span class="hidden md:inline">Sign now</span>
|
||||
<span class="inline shrink-0">Sign now</span>
|
||||
</span>
|
||||
</button>
|
||||
</form>
|
||||
<% 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 %>
|
||||
<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) %>
|
||||
<%= 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 %>
|
||||
</div>
|
||||
<% else %>
|
||||
<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 %>
|
||||
<% 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) %>">
|
||||
@@ -64,10 +68,10 @@
|
||||
</span>
|
||||
</div>
|
||||
<% 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| %>
|
||||
<div class="flex justify-between items-center space-x-3">
|
||||
<span class="flex items-center space-x-3">
|
||||
<div class="relative flex justify-between items-start md:items-center space-x-3">
|
||||
<span class="flex flex-col md:flex-row md:items-center gap-2">
|
||||
<% 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) %>">
|
||||
<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 %>
|
||||
<form onsubmit="event.preventDefault()">
|
||||
<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">
|
||||
<span class="flex items-center justify-center space-x-2" data-target="download-button.defaultButton">
|
||||
<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-1 md:space-x-2" data-target="download-button.defaultButton">
|
||||
<%= svg_icon('download', class: 'w-4 h-4 stroke-2') %>
|
||||
<span class="hidden md:inline">Download</span>
|
||||
<span class="inline">Download</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') %>
|
||||
<span class="hidden md:inline">Downloa...</span>
|
||||
<span class="inline">Downloa...</span>
|
||||
</span>
|
||||
</download-button>
|
||||
</button>
|
||||
</form>
|
||||
<% 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 %>
|
||||
<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">
|
||||
<span class="flex items-center justify-center space-x-2">
|
||||
<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-1 md:space-x-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>
|
||||
</button>
|
||||
</form>
|
||||
<% 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 %>
|
||||
</div>
|
||||
<% end %>
|
||||
@@ -115,27 +119,31 @@
|
||||
</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 %>
|
||||
<% latest_submitter = submitters.select(&:completed_at?).max_by(&:completed_at) %>
|
||||
<form onsubmit="event.preventDefault()">
|
||||
<button onclick="event.stopPropagation()">
|
||||
<download-button data-src="<%= submitter_download_index_path(latest_submitter.slug) %>" class="btn btn-sm btn-neutral text-white md:w-36">
|
||||
<span class="flex items-center justify-center space-x-2" data-target="download-button.defaultButton">
|
||||
<%= svg_icon('download', class: 'w-5 h-5 stroke-2') %>
|
||||
<span class="inline">Download</span>
|
||||
</span>
|
||||
<span class="flex items-center justify-center space-x-2 hidden" data-target="download-button.loadingButton">
|
||||
<%= svg_icon('loader', class: 'w-5 h-5 animate-spin') %>
|
||||
<span class="inline">Downloa...</span>
|
||||
</span>
|
||||
</download-button>
|
||||
</button>
|
||||
</form>
|
||||
<div class="flex-1 md:flex-none">
|
||||
<form onsubmit="event.preventDefault()" class="w-full md:w-fit">
|
||||
<button onclick="event.stopPropagation()">
|
||||
<download-button data-src="<%= submitter_download_index_path(latest_submitter.slug) %>" class="btn btn-sm btn-neutral text-white md:w-36">
|
||||
<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') %>
|
||||
<span class="inline">Download</span>
|
||||
</span>
|
||||
<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') %>
|
||||
<span class="inline">Downloa...</span>
|
||||
</span>
|
||||
</download-button>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<% 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? %>
|
||||
<%= 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 %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
<p class="flex items-center space-x-1 text-xs text-base-content/60">
|
||||
<%= svg_icon('user', class: 'w-4 h-4') %>
|
||||
<span><%= template.author.full_name.presence || template.author.email.to_s.sub(/\+\w+@/, '@') %></span>
|
||||
<% if template.account_id != current_account.id %>
|
||||
<span class="badge badge-neutral badge-outline badge-sm text-[10px] uppercase">shared</span>
|
||||
<% end %>
|
||||
</p>
|
||||
<p class="flex text-xs text-base-content/60">
|
||||
<span class="flex items-center space-x-1 w-1/2">
|
||||
@@ -24,7 +27,7 @@
|
||||
</a>
|
||||
<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">
|
||||
<% 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">
|
||||
<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') %>
|
||||
|
||||
@@ -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>
|
||||
<h1 class="text-4xl font-semibold mr-4" style="overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;">
|
||||
<%= template.name %>
|
||||
<% if template.archived_at? %>
|
||||
<span class="badge badge-outline badge-lg align-middle">Archived</span>
|
||||
<% end %>
|
||||
</h1>
|
||||
<div class="flex items-center">
|
||||
<a href="<%= folder_path(@template.folder) %>" class="flex items-center space-x-1 mt-1 peer">
|
||||
<%= svg_icon('folder', class: 'w-5 h-5 flex-shrink-0') %>
|
||||
<span class="text-sm">
|
||||
<%= @template.folder.name %>
|
||||
</span>
|
||||
</a>
|
||||
<% if can?(:update, template) %>
|
||||
<span class="pl-1 tooltip tooltip-right md:opacity-0 hover:opacity-100 peer-hover:opacity-100" data-tip="Move">
|
||||
<a href="<%= edit_template_folder_path(template.id) %>" data-turbo-frame="modal">
|
||||
<%= svg_icon('pencil_share', class: 'w-5 h-5') %>
|
||||
</a>
|
||||
</span>
|
||||
<div class="flex flex-col items-start md:flex-row space-y-2 md:space-y-0 md:space-x-2 md:justify-between md:items-start mb-6 md:mb-3">
|
||||
<div class="relative flex items-start justify-between w-full space-x-0">
|
||||
<div>
|
||||
<h1 class="text-4xl font-semibold" style="overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;">
|
||||
<%= template.name %>
|
||||
<% if template.archived_at? %>
|
||||
<span class="badge badge-outline badge-lg align-middle">Archived</span>
|
||||
<% end %>
|
||||
</h1>
|
||||
<% if @template.account_id == current_account.id %>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<a href="<%= folder_path(@template.folder) %>" class="flex items-center space-x-1 mt-1 peer">
|
||||
<%= svg_icon('folder', class: 'w-5 h-5 flex-shrink-0') %>
|
||||
<span class="text-sm">
|
||||
<%= @template.folder.name %>
|
||||
</span>
|
||||
</a>
|
||||
<% if can?(:update, template) %>
|
||||
<span class="pl-1 tooltip tooltip-right md:opacity-0 hover:opacity-100 peer-hover:opacity-100" data-tip="Move">
|
||||
<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 %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex md:justify-between space-x-2 flex-none pt-1">
|
||||
<% 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 %>
|
||||
</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) %>
|
||||
<%= 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 %>
|
||||
<% 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 %>
|
||||
<%= svg_icon('copy', class: 'w-6 h-6') %>
|
||||
<span class="hidden md:inline">Clone</span>
|
||||
<%= link_to new_template_path(base_template_id: template.id), class: 'btn btn-outline btn-sm flex-1', data: { turbo_frame: :modal } do %>
|
||||
<span class="flex items-center justify-center space-x-2">
|
||||
<%= svg_icon('copy', class: 'w-6 h-6') %>
|
||||
<span class="inline">Clone</span>
|
||||
</span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% if !template.archived_at? && can?(:update, template) %>
|
||||
<%= link_to edit_template_path(template), class: 'btn btn-outline btn-sm' do %>
|
||||
<span class="flex items-center justify-center space-x-2">
|
||||
<%= svg_icon('pencil', class: 'w-6 h-6') %>
|
||||
<span>Edit</span>
|
||||
</span>
|
||||
<% if !template.archived_at? %>
|
||||
<% if can?(:update, template) %>
|
||||
<%= link_to edit_template_path(template), class: 'btn btn-outline btn-sm flex-1' do %>
|
||||
<span class="flex items-center justify-center space-x-2">
|
||||
<%= svg_icon('pencil', class: 'w-6 h-6') %>
|
||||
<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 %>
|
||||
<% 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 %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
<%= svg_icon('folder', class: 'w-6 h-6') %>
|
||||
</a>
|
||||
<folder-autocomplete class="flex justify-between w-full">
|
||||
<input id="folder_name" placeholder="Folder Name" type="text" class="w-64 outline-none border-transparent focus:border-transparent focus:ring-0 bg-base-100 px-1 peer" name="folder_name" value="<%= params[:folder_name].presence || @base_template&.folder&.name || TemplateFolder::DEFAULT_NAME %>" onblur="window.folder_name.value = window.folder_name.value || 'Default'" autocomplete="off">
|
||||
<a href="#" onclick="[event.preventDefault(), window.folder_name.value = '', window.folder_name.focus()]" class="link peer-focus:hidden mr-1.5">
|
||||
<input id="folder_name" placeholder="Folder Name" type="text" class="w-full outline-none border-transparent focus:border-transparent focus:ring-0 bg-base-100 px-1 peer" name="folder_name" value="<%= params[:folder_name].presence || @base_template&.folder&.name || TemplateFolder::DEFAULT_NAME %>" onblur="window.folder_name.value = window.folder_name.value || 'Default'" autocomplete="off">
|
||||
<a href="#" onclick="[event.preventDefault(), window.folder_name.value = '', window.folder_name.focus()]" class="shrink-0 link peer-focus:hidden mr-1.5">
|
||||
Change Folder
|
||||
</a>
|
||||
</folder-autocomplete>
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
<%= render 'title', template: @template %>
|
||||
<% is_show_tabs = @pagy.pages > 1 || params[:status].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' %>">
|
||||
<p class="text-3xl font-bold">Submissions</p>
|
||||
<div class="flex justify-end space-x-2 mt-3 md:mt-0">
|
||||
<% if params[:q].present? || params[:status].present? || @pagy.pages > 1 %>
|
||||
<%= render 'shared/search_input' %>
|
||||
<% end %>
|
||||
<%= link_to new_template_submissions_export_path(@template), class: 'btn btn-ghost text-base', data: { turbo_frame: 'modal' } do %>
|
||||
<%= svg_icon('download', class: 'w-6 h-6 stroke-2') %>
|
||||
<span>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>
|
||||
<div class="<%= is_show_tabs ? 'mb-4' : 'mb-6' %>">
|
||||
<div class="flex justify-between items-center md:items-end">
|
||||
<p class="text-3xl font-bold">Submissions</p>
|
||||
<div class="flex justify-end space-x-2">
|
||||
<% if params[:q].present? || params[:status].present? || @pagy.pages > 1 %>
|
||||
<%= render 'shared/search_input' %>
|
||||
<% 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>
|
||||
<% 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>
|
||||
@@ -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>
|
||||
@@ -0,0 +1,18 @@
|
||||
<%= render 'shared/turbo_modal', title: 'Testing Environment' do %>
|
||||
<div>
|
||||
<label class="text-sm font-semibold" for="api_key">
|
||||
x-Auth-Token
|
||||
</label>
|
||||
<div class="flex gap-2 mb-4 mt-2">
|
||||
<input id="api_key" type="text" value="<%= current_user.access_token.token %>" class="base-input w-full" autocomplete="off" readonly>
|
||||
<%= render 'shared/clipboard_copy', icon: 'copy', text: current_user.access_token.token, class: 'base-button', icon_class: 'w-6 h-6 text-white', copy_title: 'Copy', copied_title: 'Copied' %>
|
||||
</div>
|
||||
</div>
|
||||
<%= form_for @webhook_config, url: settings_webhooks_path, method: :post, html: { autocomplete: 'off' }, data: { turbo_frame: :_top } do |f| %>
|
||||
<%= f.label :value, 'Webhook URL', class: 'text-sm font-semibold' %>
|
||||
<div class="space-y-2 md:flex-nowrap mt-2">
|
||||
<%= f.url_field :value, class: 'base-input w-full', placeholder: 'https://example.com/hook' %>
|
||||
<%= f.button button_title(title: 'Save', disabled_with: 'Saving'), class: 'base-button w-full' %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
@@ -5,7 +5,7 @@
|
||||
<% options.each_with_index do |(label, value), index| %>
|
||||
<span>
|
||||
<%= radio_button_tag 'option', value, value == 'draw', class: 'peer hidden', data: { action: 'change:toggle-visible#trigger' } %>
|
||||
<label for="option_<%= value %>" class="<%= '!rounded-s-full' if index.zero? %> btn btn-focus btn-sm join-item w-28 peer-checked:btn-active normal-case">
|
||||
<label for="option_<%= value %>" class="<%= '!rounded-s-full' if index.zero? %> btn btn-focus btn-sm join-item md:w-28 peer-checked:btn-active normal-case">
|
||||
<%= label %>
|
||||
</label>
|
||||
</span>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<option value="editor" disabled>Editor</option>
|
||||
<option value="viewer" disabled>Viewer</option>
|
||||
<% 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') %>
|
||||
Unlock more user roles with DocuSeal Pro.
|
||||
<span class="link font-medium">Learn More</a>
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
<div class="flex flex-wrap space-y-4 md:flex-nowrap md:space-y-0 md:space-x-10">
|
||||
<%= render 'shared/settings_nav' %>
|
||||
<div class="flex-grow">
|
||||
<h1 class="text-4xl font-bold mb-4">Webhooks</h1>
|
||||
<div class="flex justify-between">
|
||||
<h1 class="text-4xl font-bold mb-4">Webhooks</h1>
|
||||
<%= render 'shared/test_mode_toggle' %>
|
||||
</div>
|
||||
<div class="card bg-base-200">
|
||||
<div class="card-body p-6">
|
||||
<%= form_for @encrypted_config, url: settings_webhooks_path, method: :post, html: { autocomplete: 'off' } do |f| %>
|
||||
|
||||
@@ -22,7 +22,7 @@ module DocuSeal
|
||||
|
||||
config.active_storage.routes_prefix = ''
|
||||
|
||||
config.i18n.available_locales = %i[en en-US en-GB es-ES fr-FR pt-PT de-DE es it de fr pl uk cs pt he]
|
||||
config.i18n.available_locales = %i[en en-US en-GB es-ES fr-FR pt-PT de-DE es it de fr pl uk cs pt he nl]
|
||||
config.i18n.fallbacks = [:en]
|
||||
|
||||
config.exceptions_app = ->(env) { ErrorsController.action(:show).call(env) }
|
||||
|
||||
@@ -4,6 +4,10 @@ ActiveSupport.on_load(:active_storage_attachment) do
|
||||
attribute :uuid, :string, default: -> { SecureRandom.uuid }
|
||||
|
||||
has_many_attached :preview_images
|
||||
|
||||
def preview_image_url
|
||||
preview_images.joins(:blob).find_by(blob: { filename: '0.jpg' })&.url
|
||||
end
|
||||
end
|
||||
|
||||
ActiveStorage::LogSubscriber.detach_from(:active_storage) if Rails.env.production?
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user