mirror of
https://github.com/docusealco/docuseal
synced 2026-11-04 14:42:32 -05:00
Compare 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 | ||
|
|
ce1323e849 | ||
|
|
fc3c1e65e3 | ||
|
|
f1ebd8f912 | ||
|
|
98f5bba832 | ||
|
|
e864e8a47f | ||
|
|
a573545ead | ||
|
|
15ff4386e5 | ||
|
|
7a4aa3bf87 | ||
|
|
c1ba6288c8 | ||
|
|
d0ba180729 | ||
|
|
2edc76414b | ||
|
|
452db5dc39 | ||
|
|
21c4c53feb | ||
|
|
6292789cac | ||
|
|
d445bbc02d | ||
|
|
8627f58cbc | ||
|
|
66ab0059bb | ||
|
|
3f06932277 | ||
|
|
59cf1b435e | ||
|
|
c9a5b0e66e | ||
|
|
bcd5e1da98 | ||
|
|
f74ad050b4 | ||
|
|
b6f43c9b23 | ||
|
|
298c64a119 | ||
|
|
8b80679b3a | ||
|
|
82a2bc561a | ||
|
|
e019542d3f | ||
|
|
9356247ef0 | ||
|
|
4db77c18e3 | ||
|
|
0d0b248988 |
No files matched your search
@@ -8,7 +8,8 @@
|
|||||||
},
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
"vue/no-deprecated-html-element-is": 0,
|
"vue/no-deprecated-html-element-is": 0,
|
||||||
"vue/no-mutating-props": 0
|
"vue/no-mutating-props": 0,
|
||||||
|
"vue/no-v-html": 0
|
||||||
},
|
},
|
||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
"ecmaVersion": 2022,
|
"ecmaVersion": 2022,
|
||||||
|
|||||||
+1
-1
@@ -44,7 +44,7 @@ Layout/LineLength:
|
|||||||
AllowedPatterns: ['\A\s*#']
|
AllowedPatterns: ['\A\s*#']
|
||||||
|
|
||||||
Metrics/AbcSize:
|
Metrics/AbcSize:
|
||||||
Max: 35
|
Max: 40
|
||||||
|
|
||||||
Metrics/ModuleLength:
|
Metrics/ModuleLength:
|
||||||
Max: 500
|
Max: 500
|
||||||
|
|||||||
+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 ./
|
COPY ./Gemfile ./Gemfile.lock ./
|
||||||
|
|
||||||
RUN bundle update --bundler && bundle install && rm -rf ~/.bundle
|
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 ./bin ./bin
|
||||||
COPY ./app ./app
|
COPY ./app ./app
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ source 'https://rubygems.org'
|
|||||||
|
|
||||||
ruby '3.2.2'
|
ruby '3.2.2'
|
||||||
|
|
||||||
|
gem 'arabic-letter-connector', require: 'arabic-letter-connector/logic'
|
||||||
gem 'aws-sdk-s3', require: false
|
gem 'aws-sdk-s3', require: false
|
||||||
gem 'azure-storage-blob', require: false
|
gem 'azure-storage-blob', require: false
|
||||||
gem 'bootsnap', require: false
|
gem 'bootsnap', require: false
|
||||||
@@ -25,6 +26,7 @@ gem 'pagy'
|
|||||||
gem 'pdf-reader'
|
gem 'pdf-reader'
|
||||||
gem 'pg', require: false
|
gem 'pg', require: false
|
||||||
gem 'premailer-rails'
|
gem 'premailer-rails'
|
||||||
|
gem 'pretender'
|
||||||
gem 'puma'
|
gem 'puma'
|
||||||
gem 'rack'
|
gem 'rack'
|
||||||
gem 'rails'
|
gem 'rails'
|
||||||
@@ -40,6 +42,7 @@ gem 'sidekiq', require: ENV.key?('REDIS_URL')
|
|||||||
gem 'sqlite3', require: false
|
gem 'sqlite3', require: false
|
||||||
gem 'strip_attributes'
|
gem 'strip_attributes'
|
||||||
gem 'turbo-rails'
|
gem 'turbo-rails'
|
||||||
|
gem 'twitter_cldr', require: false
|
||||||
gem 'tzinfo-data'
|
gem 'tzinfo-data'
|
||||||
|
|
||||||
group :development, :test do
|
group :development, :test do
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ GEM
|
|||||||
annotate (3.2.0)
|
annotate (3.2.0)
|
||||||
activerecord (>= 3.2, < 8.0)
|
activerecord (>= 3.2, < 8.0)
|
||||||
rake (>= 10.4, < 14.0)
|
rake (>= 10.4, < 14.0)
|
||||||
|
arabic-letter-connector (0.1.1)
|
||||||
ast (2.4.2)
|
ast (2.4.2)
|
||||||
aws-eventstream (1.3.0)
|
aws-eventstream (1.3.0)
|
||||||
aws-partitions (1.874.0)
|
aws-partitions (1.874.0)
|
||||||
@@ -124,6 +125,7 @@ GEM
|
|||||||
bullet (7.1.4)
|
bullet (7.1.4)
|
||||||
activesupport (>= 3.0.0)
|
activesupport (>= 3.0.0)
|
||||||
uniform_notifier (~> 1.11)
|
uniform_notifier (~> 1.11)
|
||||||
|
camertron-eprun (1.1.1)
|
||||||
cancancan (3.5.0)
|
cancancan (3.5.0)
|
||||||
capybara (3.39.2)
|
capybara (3.39.2)
|
||||||
addressable
|
addressable
|
||||||
@@ -135,6 +137,7 @@ GEM
|
|||||||
regexp_parser (>= 1.5, < 3.0)
|
regexp_parser (>= 1.5, < 3.0)
|
||||||
xpath (~> 3.2)
|
xpath (~> 3.2)
|
||||||
chunky_png (1.4.0)
|
chunky_png (1.4.0)
|
||||||
|
cldr-plurals-runtime-rb (1.1.0)
|
||||||
cmdparse (3.0.7)
|
cmdparse (3.0.7)
|
||||||
coderay (1.1.3)
|
coderay (1.1.3)
|
||||||
concurrent-ruby (1.2.2)
|
concurrent-ruby (1.2.2)
|
||||||
@@ -351,6 +354,8 @@ GEM
|
|||||||
actionmailer (>= 3)
|
actionmailer (>= 3)
|
||||||
net-smtp
|
net-smtp
|
||||||
premailer (~> 1.7, >= 1.7.9)
|
premailer (~> 1.7, >= 1.7.9)
|
||||||
|
pretender (0.5.0)
|
||||||
|
actionpack (>= 6.1)
|
||||||
pry (0.14.2)
|
pry (0.14.2)
|
||||||
coderay (~> 1.1)
|
coderay (~> 1.1)
|
||||||
method_source (~> 1.0)
|
method_source (~> 1.0)
|
||||||
@@ -526,6 +531,10 @@ GEM
|
|||||||
actionpack (>= 6.0.0)
|
actionpack (>= 6.0.0)
|
||||||
activejob (>= 6.0.0)
|
activejob (>= 6.0.0)
|
||||||
railties (>= 6.0.0)
|
railties (>= 6.0.0)
|
||||||
|
twitter_cldr (6.12.0)
|
||||||
|
camertron-eprun
|
||||||
|
cldr-plurals-runtime-rb (~> 1.1)
|
||||||
|
tzinfo
|
||||||
tzinfo (2.0.6)
|
tzinfo (2.0.6)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
tzinfo-data (1.2023.4)
|
tzinfo-data (1.2023.4)
|
||||||
@@ -558,6 +567,7 @@ PLATFORMS
|
|||||||
|
|
||||||
DEPENDENCIES
|
DEPENDENCIES
|
||||||
annotate
|
annotate
|
||||||
|
arabic-letter-connector
|
||||||
aws-sdk-s3
|
aws-sdk-s3
|
||||||
azure-storage-blob
|
azure-storage-blob
|
||||||
better_html
|
better_html
|
||||||
@@ -588,6 +598,7 @@ DEPENDENCIES
|
|||||||
pdf-reader
|
pdf-reader
|
||||||
pg
|
pg
|
||||||
premailer-rails
|
premailer-rails
|
||||||
|
pretender
|
||||||
pry-rails
|
pry-rails
|
||||||
puma
|
puma
|
||||||
rack
|
rack
|
||||||
@@ -610,6 +621,7 @@ DEPENDENCIES
|
|||||||
sqlite3
|
sqlite3
|
||||||
strip_attributes
|
strip_attributes
|
||||||
turbo-rails
|
turbo-rails
|
||||||
|
twitter_cldr
|
||||||
tzinfo-data
|
tzinfo-data
|
||||||
web-console
|
web-console
|
||||||
webmock
|
webmock
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ curl https://raw.githubusercontent.com/docusealco/docuseal/master/docker-compose
|
|||||||
|
|
||||||
Run the app under a custom domain over https using docker compose (make sure your DNS points to the server to automatically issue ssl certs with Caddy):
|
Run the app under a custom domain over https using docker compose (make sure your DNS points to the server to automatically issue ssl certs with Caddy):
|
||||||
```sh
|
```sh
|
||||||
HOST=your-domain-name.com docker-compose up
|
sudo HOST=your-domain-name.com docker compose up
|
||||||
```
|
```
|
||||||
|
|
||||||
## For Businesses
|
## For Businesses
|
||||||
@@ -81,7 +81,7 @@ HOST=your-domain-name.com docker-compose up
|
|||||||
|
|
||||||
At DocuSeal we have expertise and technologies to make documents creation, filling, signing and processing seamlessly integrated with your product. We specialize in working with various industries, including **Banking, Healthcare, Transport, Real Estate, eCommerce, KYC, CRM, and other software products** that require bulk document signing. By leveraging DocuSeal, we can assist in reducing the overall cost of developing and processing electronic documents while ensuring security and compliance with local electronic document laws.
|
At DocuSeal we have expertise and technologies to make documents creation, filling, signing and processing seamlessly integrated with your product. We specialize in working with various industries, including **Banking, Healthcare, Transport, Real Estate, eCommerce, KYC, CRM, and other software products** that require bulk document signing. By leveraging DocuSeal, we can assist in reducing the overall cost of developing and processing electronic documents while ensuring security and compliance with local electronic document laws.
|
||||||
|
|
||||||
[Book a Meeting](https://calendly.com/kriti-docuseal/30min)
|
[Book a Meeting](https://www.docuseal.co/contact)
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ class AccountConfigsController < ApplicationController
|
|||||||
ALLOWED_KEYS = [
|
ALLOWED_KEYS = [
|
||||||
AccountConfig::ALLOW_TYPED_SIGNATURE,
|
AccountConfig::ALLOW_TYPED_SIGNATURE,
|
||||||
AccountConfig::FORCE_MFA,
|
AccountConfig::FORCE_MFA,
|
||||||
|
AccountConfig::ALLOW_TO_RESUBMIT,
|
||||||
AccountConfig::ESIGNING_PREFERENCE_KEY
|
AccountConfig::ESIGNING_PREFERENCE_KEY
|
||||||
].freeze
|
].freeze
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,18 @@ class AccountsController < ApplicationController
|
|||||||
render :show, status: :unprocessable_entity
|
render :show, status: :unprocessable_entity
|
||||||
end
|
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
|
private
|
||||||
|
|
||||||
def load_account
|
def load_account
|
||||||
|
|||||||
@@ -8,11 +8,17 @@ module Api
|
|||||||
DEFAULT_LIMIT = 10
|
DEFAULT_LIMIT = 10
|
||||||
MAX_LIMIT = 100
|
MAX_LIMIT = 100
|
||||||
|
|
||||||
|
impersonates :user, with: ->(uuid) { User.find_by(uuid:) }
|
||||||
|
|
||||||
wrap_parameters false
|
wrap_parameters false
|
||||||
|
|
||||||
before_action :authenticate_user!
|
before_action :authenticate_user!
|
||||||
check_authorization
|
check_authorization
|
||||||
|
|
||||||
|
rescue_from Params::BaseValidator::InvalidParameterError do |e|
|
||||||
|
render json: { error: e.message }, status: :unprocessable_entity
|
||||||
|
end
|
||||||
|
|
||||||
if Rails.env.production?
|
if Rails.env.production?
|
||||||
rescue_from CanCan::AccessDenied do |e|
|
rescue_from CanCan::AccessDenied do |e|
|
||||||
Rollbar.error(e) if defined?(Rollbar)
|
Rollbar.error(e) if defined?(Rollbar)
|
||||||
@@ -44,7 +50,7 @@ module Api
|
|||||||
if request.headers['X-Auth-Token'].present?
|
if request.headers['X-Auth-Token'].present?
|
||||||
sha256 = Digest::SHA256.hexdigest(request.headers['X-Auth-Token'])
|
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
|
end
|
||||||
|
|
||||||
render json: { error: 'Not authenticated' }, status: :unauthorized unless current_user
|
render json: { error: 'Not authenticated' }, status: :unauthorized unless current_user
|
||||||
|
|||||||
@@ -63,8 +63,16 @@ module Api
|
|||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
|
Params::SubmissionCreateValidator.call(params, dry_run: !Docuseal.multitenant?)
|
||||||
|
|
||||||
return render json: { error: 'Template not found' }, status: :unprocessable_entity if @template.nil?
|
return render json: { error: 'Template not found' }, status: :unprocessable_entity if @template.nil?
|
||||||
|
|
||||||
|
if @template.fields.blank?
|
||||||
|
Rollbar.error("Template does not contain fields: #{@template.id}") if defined?(Rollbar)
|
||||||
|
|
||||||
|
return render json: { error: 'Template does not contain fields' }, status: :unprocessable_entity
|
||||||
|
end
|
||||||
|
|
||||||
params[:send_email] = true unless params.key?(:send_email)
|
params[:send_email] = true unless params.key?(:send_email)
|
||||||
params[:send_sms] = false unless params.key?(:send_sms)
|
params[:send_sms] = false unless params.key?(:send_sms)
|
||||||
|
|
||||||
@@ -88,7 +96,8 @@ module Api
|
|||||||
def create_submissions(template, params)
|
def create_submissions(template, params)
|
||||||
is_send_email = !params[:send_email].in?(['false', false])
|
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:,
|
Submissions.create_from_emails(template:,
|
||||||
user: current_user,
|
user: current_user,
|
||||||
source: :api,
|
source: :api,
|
||||||
@@ -123,8 +132,8 @@ module Api
|
|||||||
include: {
|
include: {
|
||||||
submitters: { only: %i[id slug uuid name email phone
|
submitters: { only: %i[id slug uuid name email phone
|
||||||
completed_at opened_at sent_at
|
completed_at opened_at sent_at
|
||||||
created_at updated_at],
|
created_at updated_at external_id metadata],
|
||||||
methods: %i[status] },
|
methods: %i[status application_key] },
|
||||||
template: { only: %i[id name created_at updated_at] },
|
template: { only: %i[id name created_at updated_at] },
|
||||||
created_by_user: { only: %i[id email first_name last_name] }
|
created_by_user: { only: %i[id email first_name last_name] }
|
||||||
}
|
}
|
||||||
@@ -132,19 +141,27 @@ module Api
|
|||||||
end
|
end
|
||||||
|
|
||||||
def submissions_params
|
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(
|
if params.key?(:submitters)
|
||||||
key => [
|
params.permit(*permitted_attrs)
|
||||||
[:send_email, :send_sms, {
|
else
|
||||||
message: %i[subject body],
|
key = params.key?(:submission) ? :submission : :submissions
|
||||||
submitters: [[:send_email, :send_sms, :uuid, :name, :email, :role,
|
|
||||||
:completed, :phone, :application_key,
|
params.permit(
|
||||||
{ values: {}, readonly_fields: [], message: %i[subject body],
|
key => [permitted_attrs]
|
||||||
fields: [%i[name default_value readonly validation_pattern invalid_message]] }]]
|
).fetch(key, [])
|
||||||
}]
|
end
|
||||||
]
|
|
||||||
).fetch(key, [])
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -7,7 +7,8 @@ module Api
|
|||||||
def index
|
def index
|
||||||
submitters = Submitters.search(@submitters, params[:q])
|
submitters = Submitters.search(@submitters, params[:q])
|
||||||
|
|
||||||
submitters = submitters.where(application_key: params[:application_key]) if params[:application_key].present?
|
submitters = submitters.where(external_id: params[:application_key]) if params[:application_key].present?
|
||||||
|
submitters = submitters.where(external_id: params[:external_id]) if params[:external_id].present?
|
||||||
submitters = submitters.where(submission_id: params[:submission_id]) if params[:submission_id].present?
|
submitters = submitters.where(submission_id: params[:submission_id]) if params[:submission_id].present?
|
||||||
|
|
||||||
submitters = paginate(
|
submitters = paginate(
|
||||||
@@ -44,7 +45,7 @@ module Api
|
|||||||
|
|
||||||
Submissions::CreateFromSubmitters.maybe_set_template_fields(@submitter.submission,
|
Submissions::CreateFromSubmitters.maybe_set_template_fields(@submitter.submission,
|
||||||
[normalized_params],
|
[normalized_params],
|
||||||
submitter_uuid: @submitter.uuid)
|
default_submitter_uuid: @submitter.uuid)
|
||||||
|
|
||||||
assign_submitter_attrs(@submitter, normalized_params)
|
assign_submitter_attrs(@submitter, normalized_params)
|
||||||
|
|
||||||
@@ -69,8 +70,9 @@ module Api
|
|||||||
submitter_params = params.key?(:submitter) ? params.require(:submitter) : params
|
submitter_params = params.key?(:submitter) ? params.require(:submitter) : params
|
||||||
|
|
||||||
submitter_params.permit(
|
submitter_params.permit(
|
||||||
:send_email, :send_sms, :uuid, :name, :email, :role, :completed, :phone, :application_key,
|
:send_email, :send_sms, :uuid, :name, :email, :role,
|
||||||
{ values: {}, readonly_fields: [], message: %i[subject body],
|
:completed, :phone, :application_key, :external_id,
|
||||||
|
{ metadata: {}, values: {}, readonly_fields: [], message: %i[subject body],
|
||||||
fields: [%i[name default_value readonly validation_pattern invalid_message]] }
|
fields: [%i[name default_value readonly validation_pattern invalid_message]] }
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
@@ -82,7 +84,9 @@ module Api
|
|||||||
submitter.phone = attrs[:phone].to_s.gsub(/[^0-9+]/, '') if attrs.key?(:phone)
|
submitter.phone = attrs[:phone].to_s.gsub(/[^0-9+]/, '') if attrs.key?(:phone)
|
||||||
submitter.values = submitter.values.merge(attrs[:values].to_unsafe_h) if attrs[:values].present?
|
submitter.values = submitter.values.merge(attrs[:values].to_unsafe_h) if attrs[:values].present?
|
||||||
submitter.completed_at = attrs[:completed] ? Time.current : submitter.completed_at
|
submitter.completed_at = attrs[:completed] ? Time.current : submitter.completed_at
|
||||||
submitter.application_key = attrs[:application_key] if attrs.key?(:application_key)
|
submitter.external_id = attrs[:application_key] if attrs.key?(:application_key)
|
||||||
|
submitter.external_id = attrs[:external_id] if attrs.key?(:external_id)
|
||||||
|
submitter.metadata = attrs[:metadata] if attrs.key?(:metadata)
|
||||||
|
|
||||||
assign_submission_fields(submitter.submission)
|
assign_submission_fields(submitter.submission)
|
||||||
assign_preferences(submitter, attrs)
|
assign_preferences(submitter, attrs)
|
||||||
|
|||||||
@@ -7,11 +7,13 @@ module Api
|
|||||||
def create
|
def create
|
||||||
authorize!(:manage, @template)
|
authorize!(:manage, @template)
|
||||||
|
|
||||||
cloned_template = Templates::Clone.call(@template,
|
cloned_template = Templates::Clone.call(
|
||||||
author: current_user,
|
@template,
|
||||||
name: params[:name],
|
author: current_user,
|
||||||
application_key: params[:application_key],
|
name: params[:name],
|
||||||
folder_name: params[:folder_name])
|
external_id: params[:external_id].presence || params[:application_key],
|
||||||
|
folder_name: params[:folder_name]
|
||||||
|
)
|
||||||
|
|
||||||
cloned_template.source = :api
|
cloned_template.source = :api
|
||||||
cloned_template.save!
|
cloned_template.save!
|
||||||
|
|||||||
@@ -5,11 +5,7 @@ module Api
|
|||||||
load_and_authorize_resource :template
|
load_and_authorize_resource :template
|
||||||
|
|
||||||
def index
|
def index
|
||||||
templates = Templates.search(@templates, params[:q])
|
templates = filter_templates(@templates, params)
|
||||||
|
|
||||||
templates = params[:archived] ? templates.archived : templates.active
|
|
||||||
templates = templates.where(application_key: params[:application_key]) if params[:application_key].present?
|
|
||||||
templates = templates.joins(:folder).where(folder: { name: params[:folder] }) if params[:folder].present?
|
|
||||||
|
|
||||||
templates = paginate(templates.preload(:author, documents_attachments: :blob))
|
templates = paginate(templates.preload(:author, documents_attachments: :blob))
|
||||||
|
|
||||||
@@ -38,17 +34,32 @@ module Api
|
|||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
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])
|
render json: @template.as_json(only: %i[id archived_at])
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def filter_templates(templates, params)
|
||||||
|
templates = Templates.search(templates, params[:q])
|
||||||
|
templates = params[:archived] ? templates.archived : templates.active
|
||||||
|
templates = templates.where(external_id: params[:application_key]) if params[:application_key].present?
|
||||||
|
templates = templates.where(external_id: params[:external_id]) if params[:external_id].present?
|
||||||
|
templates = templates.joins(:folder).where(folder: { name: params[:folder] }) if params[:folder].present?
|
||||||
|
|
||||||
|
templates
|
||||||
|
end
|
||||||
|
|
||||||
def serialize_params
|
def serialize_params
|
||||||
{
|
{
|
||||||
|
methods: %i[application_key],
|
||||||
include: { author: { only: %i[id email first_name last_name] },
|
include: { author: { only: %i[id email first_name last_name] },
|
||||||
documents: { only: %i[id uuid], methods: %i[url filename] } }
|
documents: { only: %i[id uuid], methods: %i[url preview_image_url filename] } }
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ module Api
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
rescue Templates::CreateAttachments::PdfEncrypted
|
||||||
|
render json: { error: 'PDF encrypted' }, status: :unprocessable_entity
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class ApplicationController < ActionController::Base
|
class ApplicationController < ActionController::Base
|
||||||
|
BROWSER_LOCALE_REGEXP = /\A\w{2}(?:-\w{2})?/
|
||||||
|
|
||||||
include ActiveStorage::SetCurrent
|
include ActiveStorage::SetCurrent
|
||||||
include Pagy::Backend
|
include Pagy::Backend
|
||||||
|
|
||||||
@@ -14,6 +16,8 @@ class ApplicationController < ActionController::Base
|
|||||||
:current_account,
|
:current_account,
|
||||||
:svg_icon
|
:svg_icon
|
||||||
|
|
||||||
|
impersonates :user, with: ->(uuid) { User.find_by(uuid:) }
|
||||||
|
|
||||||
rescue_from Pagy::OverflowError do
|
rescue_from Pagy::OverflowError do
|
||||||
redirect_to request.path
|
redirect_to request.path
|
||||||
end
|
end
|
||||||
@@ -22,7 +26,7 @@ class ApplicationController < ActionController::Base
|
|||||||
rescue_from CanCan::AccessDenied do |e|
|
rescue_from CanCan::AccessDenied do |e|
|
||||||
Rollbar.error(e) if defined?(Rollbar)
|
Rollbar.error(e) if defined?(Rollbar)
|
||||||
|
|
||||||
redirect_back(fallback_location: root_path, alert: e.message)
|
redirect_to root_path, alert: e.message
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -30,8 +34,32 @@ class ApplicationController < ActionController::Base
|
|||||||
Docuseal.default_url_options
|
Docuseal.default_url_options
|
||||||
end
|
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
|
private
|
||||||
|
|
||||||
|
def with_browser_locale(&)
|
||||||
|
locale = request.env['HTTP_ACCEPT_LANGUAGE'].to_s[BROWSER_LOCALE_REGEXP].to_s
|
||||||
|
|
||||||
|
locale =
|
||||||
|
if locale.starts_with?('en-') && locale != 'en-US'
|
||||||
|
'en-GB'
|
||||||
|
else
|
||||||
|
locale.split('-').first.presence || 'en-GB'
|
||||||
|
end
|
||||||
|
|
||||||
|
locale = 'en-GB' unless I18n.locale_available?(locale)
|
||||||
|
|
||||||
|
I18n.with_locale(locale, &)
|
||||||
|
end
|
||||||
|
|
||||||
def sign_in_for_demo
|
def sign_in_for_demo
|
||||||
sign_in(User.active.order('random()').take) unless signed_in?
|
sign_in(User.active.order('random()').take) unless signed_in?
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ class ConsoleRedirectController < ApplicationController
|
|||||||
skip_authorization_check
|
skip_authorization_check
|
||||||
|
|
||||||
def index
|
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,
|
scope: :console,
|
||||||
exp: 1.minute.from_now.to_i)
|
exp: 1.minute.from_now.to_i)
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,13 @@ class DashboardController < ApplicationController
|
|||||||
|
|
||||||
def filter_templates(templates)
|
def filter_templates(templates)
|
||||||
rel = templates.active.preload(:author).order(id: :desc)
|
rel = templates.active.preload(:author).order(id: :desc)
|
||||||
rel = rel.where(folder_id: current_account.default_template_folder.id) if params[:q].blank?
|
|
||||||
|
if params[:q].blank?
|
||||||
|
shared_template_ids =
|
||||||
|
TemplateSharing.where(account_id: [current_account.id, TemplateSharing::ALL_ID]).select(:template_id)
|
||||||
|
|
||||||
|
rel = rel.where(folder_id: current_account.default_template_folder.id).or(rel.where(id: shared_template_ids))
|
||||||
|
end
|
||||||
|
|
||||||
Templates.search(rel, params[:q])
|
Templates.search(rel, params[:q])
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -7,18 +7,28 @@ class PersonalizationSettingsController < ApplicationController
|
|||||||
|
|
||||||
def create
|
def create
|
||||||
account_config =
|
account_config =
|
||||||
current_account.account_configs.find_or_initialize_by(key: encrypted_config_params[:key])
|
current_account.account_configs.find_or_initialize_by(key: account_config_params[:key])
|
||||||
|
|
||||||
authorize!(:create, account_config)
|
authorize!(:create, account_config)
|
||||||
|
|
||||||
account_config.update!(encrypted_config_params)
|
account_config.update!(account_config_params)
|
||||||
|
|
||||||
redirect_back(fallback_location: settings_personalization_path, notice: 'Settings have been saved.')
|
redirect_back(fallback_location: settings_personalization_path, notice: 'Settings have been saved.')
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def encrypted_config_params
|
def account_config_params
|
||||||
params.require(:account_config).permit!
|
attrs = params.require(:account_config).permit!
|
||||||
|
|
||||||
|
attrs[:value]&.transform_values! do |value|
|
||||||
|
if value.in?(%w[true false])
|
||||||
|
value == 'true'
|
||||||
|
else
|
||||||
|
value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
attrs
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -6,10 +6,12 @@ class StartFormController < ApplicationController
|
|||||||
skip_before_action :authenticate_user!
|
skip_before_action :authenticate_user!
|
||||||
skip_authorization_check
|
skip_authorization_check
|
||||||
|
|
||||||
|
around_action :with_browser_locale, only: %i[show completed]
|
||||||
before_action :load_template
|
before_action :load_template
|
||||||
|
|
||||||
def show
|
def show
|
||||||
@submitter = @template.submissions.new.submitters.new(uuid: @template.submitters.first['uuid'])
|
@submitter = @template.submissions.new(account_id: @template.account_id)
|
||||||
|
.submitters.new(uuid: @template.submitters.first['uuid'])
|
||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
@@ -56,7 +58,8 @@ class StartFormController < ApplicationController
|
|||||||
ip: request.remote_ip,
|
ip: request.remote_ip,
|
||||||
ua: request.user_agent,
|
ua: request.user_agent,
|
||||||
values: resubmit_submitter&.preferences&.fetch('default_values', nil) || {},
|
values: resubmit_submitter&.preferences&.fetch('default_values', nil) || {},
|
||||||
preferences: resubmit_submitter&.preferences.presence || { 'send_email' => true }
|
preferences: resubmit_submitter&.preferences.presence || { 'send_email' => true },
|
||||||
|
metadata: resubmit_submitter&.metadata.presence || {}
|
||||||
)
|
)
|
||||||
|
|
||||||
if submitter.values.present?
|
if submitter.values.present?
|
||||||
@@ -66,6 +69,7 @@ class StartFormController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
submitter.submission ||= Submission.new(template:,
|
submitter.submission ||= Submission.new(template:,
|
||||||
|
account_id: template.account_id,
|
||||||
template_submitters: template.submitters,
|
template_submitters: template.submitters,
|
||||||
source: :link)
|
source: :link)
|
||||||
|
|
||||||
|
|||||||
@@ -75,6 +75,6 @@ class SubmissionsController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def load_template
|
def load_template
|
||||||
@template = current_account.templates.find(params[:template_id])
|
@template = Template.accessible_by(current_ability).find(params[:template_id])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
class SubmitFormController < ApplicationController
|
class SubmitFormController < ApplicationController
|
||||||
layout 'form'
|
layout 'form'
|
||||||
|
|
||||||
|
around_action :with_browser_locale, only: %i[show completed success]
|
||||||
skip_before_action :authenticate_user!
|
skip_before_action :authenticate_user!
|
||||||
skip_authorization_check
|
skip_authorization_check
|
||||||
|
|
||||||
@@ -32,7 +33,7 @@ class SubmitFormController < ApplicationController
|
|||||||
|
|
||||||
cookies[:submitter_sid] = @submitter.signed_id
|
cookies[:submitter_sid] = @submitter.signed_id
|
||||||
|
|
||||||
render @submitter.submission.template.archived_at? ? :archived : :show
|
render(@submitter.submission.template.archived_at? || @submitter.submission.archived_at? ? :archived : :show)
|
||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
@@ -58,4 +59,6 @@ class SubmitFormController < ApplicationController
|
|||||||
def completed
|
def completed
|
||||||
@submitter = Submitter.find_by!(slug: params[:submit_form_slug])
|
@submitter = Submitter.find_by!(slug: params[:submit_form_slug])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def success; end
|
||||||
end
|
end
|
||||||
@@ -11,9 +11,7 @@ class SubmittersSendEmailController < ApplicationController
|
|||||||
alert: 'Email has been sent already.')
|
alert: 'Email has been sent already.')
|
||||||
end
|
end
|
||||||
|
|
||||||
SubmitterMailer.invitation_email(@submitter).deliver_later!
|
SendSubmitterInvitationEmailJob.perform_later('submitter_id' => @submitter.id)
|
||||||
|
|
||||||
SubmissionEvent.create!(submitter: @submitter, event_type: 'send_email')
|
|
||||||
|
|
||||||
@submitter.sent_at ||= Time.current
|
@submitter.sent_at ||= Time.current
|
||||||
@submitter.save!
|
@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]
|
before_action :load_base_template, only: %i[new create]
|
||||||
|
|
||||||
def show
|
def show
|
||||||
submissions = @template.submissions
|
submissions = @template.submissions.accessible_by(current_ability)
|
||||||
submissions = submissions.active if @template.archived_at.blank?
|
submissions = submissions.active if @template.archived_at.blank?
|
||||||
submissions = Submissions.search(submissions, params[:q])
|
submissions = Submissions.search(submissions, params[:q])
|
||||||
|
|
||||||
@@ -47,11 +47,12 @@ class TemplatesController < ApplicationController
|
|||||||
name: params.dig(:template, :name),
|
name: params.dig(:template, :name),
|
||||||
folder_name: params[:folder_name])
|
folder_name: params[:folder_name])
|
||||||
else
|
else
|
||||||
@template.account = current_account
|
|
||||||
@template.author = current_user
|
@template.author = current_user
|
||||||
@template.folder = TemplateFolders.find_or_create_by_name(current_user, params[:folder_name])
|
@template.folder = TemplateFolders.find_or_create_by_name(current_user, params[:folder_name])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@template.account = current_account
|
||||||
|
|
||||||
if @template.save
|
if @template.save
|
||||||
Templates::CloneAttachments.call(template: @template, original_template: @base_template) if @base_template
|
Templates::CloneAttachments.call(template: @template, original_template: @base_template) if @base_template
|
||||||
|
|
||||||
@@ -87,6 +88,6 @@ class TemplatesController < ApplicationController
|
|||||||
def load_base_template
|
def load_base_template
|
||||||
return if params[:base_template_id].blank?
|
return if params[:base_template_id].blank?
|
||||||
|
|
||||||
@base_template = current_account.templates.find_by(id: params[:base_template_id])
|
@base_template = Template.accessible_by(current_ability).find_by(id: params[:base_template_id])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class TemplatesPreviewController < ApplicationController
|
||||||
|
load_and_authorize_resource :template
|
||||||
|
|
||||||
|
def show
|
||||||
|
ActiveRecord::Associations::Preloader.new(
|
||||||
|
records: [@template],
|
||||||
|
associations: [schema_documents: { preview_images_attachments: :blob }]
|
||||||
|
).call
|
||||||
|
|
||||||
|
@template_data =
|
||||||
|
@template.as_json.merge(
|
||||||
|
documents: @template.schema_documents.as_json(
|
||||||
|
methods: [:metadata],
|
||||||
|
include: { preview_images: { methods: %i[url metadata filename] } }
|
||||||
|
)
|
||||||
|
).to_json
|
||||||
|
|
||||||
|
render :show, layout: 'plain'
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -10,12 +10,7 @@ class TemplatesUploadsController < ApplicationController
|
|||||||
def create
|
def create
|
||||||
url_params = create_file_params_from_url if params[:url].present?
|
url_params = create_file_params_from_url if params[:url].present?
|
||||||
|
|
||||||
@template.account = current_account
|
save_template!(@template, url_params)
|
||||||
@template.author = current_user
|
|
||||||
@template.folder = TemplateFolders.find_or_create_by_name(current_user, params[:folder_name])
|
|
||||||
@template.name = File.basename((url_params || params)[:files].first.original_filename, '.*')
|
|
||||||
|
|
||||||
@template.save!
|
|
||||||
|
|
||||||
documents = Templates::CreateAttachments.call(@template, url_params || params)
|
documents = Templates::CreateAttachments.call(@template, url_params || params)
|
||||||
|
|
||||||
@@ -24,6 +19,8 @@ class TemplatesUploadsController < ApplicationController
|
|||||||
@template.update!(schema:)
|
@template.update!(schema:)
|
||||||
|
|
||||||
redirect_to edit_template_path(@template)
|
redirect_to edit_template_path(@template)
|
||||||
|
rescue Templates::CreateAttachments::PdfEncrypted
|
||||||
|
render turbo_stream: turbo_stream.append(params[:form_id], html: helpers.tag.prompt_password)
|
||||||
rescue StandardError => e
|
rescue StandardError => e
|
||||||
Rollbar.error(e) if defined?(Rollbar)
|
Rollbar.error(e) if defined?(Rollbar)
|
||||||
|
|
||||||
@@ -32,6 +29,17 @@ class TemplatesUploadsController < ApplicationController
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def save_template!(template, url_params)
|
||||||
|
template.account = current_account
|
||||||
|
template.author = current_user
|
||||||
|
template.folder = TemplateFolders.find_or_create_by_name(current_user, params[:folder_name])
|
||||||
|
template.name = File.basename((url_params || params)[:files].first.original_filename, '.*')
|
||||||
|
|
||||||
|
template.save!
|
||||||
|
|
||||||
|
template
|
||||||
|
end
|
||||||
|
|
||||||
def create_file_params_from_url
|
def create_file_params_from_url
|
||||||
tempfile = Tempfile.new
|
tempfile = Tempfile.new
|
||||||
tempfile.binmode
|
tempfile.binmode
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -19,6 +19,7 @@ import SubmittersAutocomplete from './elements/submitter_autocomplete'
|
|||||||
import FolderAutocomplete from './elements/folder_autocomplete'
|
import FolderAutocomplete from './elements/folder_autocomplete'
|
||||||
import SignatureForm from './elements/signature_form'
|
import SignatureForm from './elements/signature_form'
|
||||||
import SubmitForm from './elements/submit_form'
|
import SubmitForm from './elements/submit_form'
|
||||||
|
import PromptPassword from './elements/prompt_password'
|
||||||
|
|
||||||
import * as TurboInstantClick from './lib/turbo_instant_click'
|
import * as TurboInstantClick from './lib/turbo_instant_click'
|
||||||
|
|
||||||
@@ -51,6 +52,7 @@ window.customElements.define('submitters-autocomplete', SubmittersAutocomplete)
|
|||||||
window.customElements.define('folder-autocomplete', FolderAutocomplete)
|
window.customElements.define('folder-autocomplete', FolderAutocomplete)
|
||||||
window.customElements.define('signature-form', SignatureForm)
|
window.customElements.define('signature-form', SignatureForm)
|
||||||
window.customElements.define('submit-form', SubmitForm)
|
window.customElements.define('submit-form', SubmitForm)
|
||||||
|
window.customElements.define('prompt-password', PromptPassword)
|
||||||
|
|
||||||
document.addEventListener('turbo:before-fetch-request', encodeMethodIntoRequestBody)
|
document.addEventListener('turbo:before-fetch-request', encodeMethodIntoRequestBody)
|
||||||
document.addEventListener('turbo:submit-end', async (event) => {
|
document.addEventListener('turbo:submit-end', async (event) => {
|
||||||
@@ -86,7 +88,9 @@ window.customElements.define('template-builder', class extends HTMLElement {
|
|||||||
backgroundColor: '#faf7f5',
|
backgroundColor: '#faf7f5',
|
||||||
withPhone: this.dataset.withPhone === 'true',
|
withPhone: this.dataset.withPhone === 'true',
|
||||||
withLogo: this.dataset.withLogo !== 'false',
|
withLogo: this.dataset.withLogo !== 'false',
|
||||||
|
editable: this.dataset.editable !== 'false',
|
||||||
withPayment: this.dataset.withPayment === 'true',
|
withPayment: this.dataset.withPayment === 'true',
|
||||||
|
currencies: (this.dataset.currencies || '').split(',').filter(Boolean),
|
||||||
acceptFileTypes: this.dataset.acceptFileTypes,
|
acceptFileTypes: this.dataset.acceptFileTypes,
|
||||||
isDirectUpload: this.dataset.isDirectUpload === 'true'
|
isDirectUpload: this.dataset.isDirectUpload === 'true'
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
export default class extends HTMLElement {
|
||||||
|
connectedCallback () {
|
||||||
|
const input = document.createElement('input')
|
||||||
|
|
||||||
|
input.type = 'hidden'
|
||||||
|
input.name = 'password'
|
||||||
|
input.value = prompt('Enter PDF password')
|
||||||
|
|
||||||
|
this.form.append(input)
|
||||||
|
|
||||||
|
this.form.requestSubmit()
|
||||||
|
|
||||||
|
this.remove()
|
||||||
|
}
|
||||||
|
|
||||||
|
get form () {
|
||||||
|
return this.closest('form')
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,10 +15,11 @@ window.customElements.define('submission-form', class extends HTMLElement {
|
|||||||
goToLast: this.dataset.goToLast === 'true',
|
goToLast: this.dataset.goToLast === 'true',
|
||||||
isDemo: this.dataset.isDemo === 'true',
|
isDemo: this.dataset.isDemo === 'true',
|
||||||
attribution: this.dataset.attribution !== 'false',
|
attribution: this.dataset.attribution !== 'false',
|
||||||
withConfetti: true,
|
withConfetti: this.dataset.withConfetti !== 'false',
|
||||||
withTypedSignature: this.dataset.withTypedSignature !== 'false',
|
withTypedSignature: this.dataset.withTypedSignature !== 'false',
|
||||||
values: reactive(JSON.parse(this.dataset.values)),
|
values: reactive(JSON.parse(this.dataset.values)),
|
||||||
completedButton: JSON.parse(this.dataset.completedButton),
|
completedButton: JSON.parse(this.dataset.completedButton),
|
||||||
|
completedRedirectUrl: this.dataset.completedRedirectUrl,
|
||||||
attachments: reactive(JSON.parse(this.dataset.attachments)),
|
attachments: reactive(JSON.parse(this.dataset.attachments)),
|
||||||
fields: JSON.parse(this.dataset.fields)
|
fields: JSON.parse(this.dataset.fields)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
v-if="uniqueAreas.length > 1"
|
v-if="uniqueAreas.length > 1"
|
||||||
|
dir="auto"
|
||||||
class="mb-2"
|
class="mb-2"
|
||||||
>
|
>
|
||||||
<div class="flex space-x-1 text-sm ml-1 -mt-3">
|
<div class="flex space-x-1 text-sm ml-1 -mt-3">
|
||||||
@@ -40,7 +41,7 @@ export default {
|
|||||||
uniqueAreas () {
|
uniqueAreas () {
|
||||||
const areas = {}
|
const areas = {}
|
||||||
|
|
||||||
this.field.areas.forEach((area) => {
|
this.field.areas?.forEach((area) => {
|
||||||
areas[area.attachment_uuid + area.page] ||= area
|
areas[area.attachment_uuid + area.page] ||= area
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="flex absolute lg:text-base"
|
class="flex absolute lg:text-base -outline-offset-1"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
:style="computedStyle"
|
: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
|
<div
|
||||||
v-if="!isActive && !isValueSet && field.type !== 'checkbox' && submittable && !area.option_uuid"
|
v-if="!isActive && !isValueSet && field.type !== 'checkbox' && submittable && !area.option_uuid"
|
||||||
@@ -81,7 +81,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-else-if="field.type === 'checkbox'"
|
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
|
<input
|
||||||
v-if="submittable"
|
v-if="submittable"
|
||||||
@@ -100,14 +101,15 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-else-if="field.type === 'radio' && area.option_uuid"
|
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
|
<input
|
||||||
v-if="submittable"
|
v-if="submittable"
|
||||||
type="radio"
|
type="radio"
|
||||||
:value="false"
|
:value="false"
|
||||||
class="aspect-square base-radio"
|
class="aspect-square checked:checkbox checked:checkbox-xs"
|
||||||
:class="{ '!w-auto !h-full': area.w > area.h, '!w-full !h-auto': area.w <= area.h }"
|
: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)"
|
:checked="!!modelValue && modelValue === optionValue(option)"
|
||||||
@click="$emit('update:model-value', optionValue(option))"
|
@click="$emit('update:model-value', optionValue(option))"
|
||||||
>
|
>
|
||||||
@@ -119,7 +121,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-else-if="field.type === 'multiple' && area.option_uuid"
|
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
|
<input
|
||||||
v-if="submittable"
|
v-if="submittable"
|
||||||
@@ -233,20 +236,20 @@ export default {
|
|||||||
computed: {
|
computed: {
|
||||||
fieldNames () {
|
fieldNames () {
|
||||||
return {
|
return {
|
||||||
text: 'Text',
|
text: this.t('text'),
|
||||||
cells: 'Text',
|
signature: this.t('signature'),
|
||||||
signature: 'Signature',
|
initials: this.t('initials'),
|
||||||
date: 'Date',
|
date: this.t('date'),
|
||||||
image: 'Image',
|
image: this.t('image'),
|
||||||
stamp: 'Stamp',
|
file: this.t('file'),
|
||||||
initials: 'Initials',
|
select: this.t('select'),
|
||||||
file: 'File',
|
checkbox: this.t('checkbox'),
|
||||||
select: 'Select',
|
multiple: this.t('multiple'),
|
||||||
checkbox: 'Checkbox',
|
radio: this.t('radio'),
|
||||||
radio: 'Radio',
|
cells: this.t('cells'),
|
||||||
multiple: 'Multiple Select',
|
stamp: this.t('stamp'),
|
||||||
phone: 'Phone',
|
payment: this.t('payment'),
|
||||||
payment: 'Payment'
|
phone: this.t('phone')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
option () {
|
option () {
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
>
|
>
|
||||||
</template>
|
</template>
|
||||||
<FileDropzone
|
<FileDropzone
|
||||||
:message="`Upload ${field.name || 'Attachments'}${field.required ? '' : ' (optional)'}`"
|
:message="`${t('upload')} ${field.name || t('files')}${field.required ? '' : ` (${t('optional')})`}`"
|
||||||
:submitter-slug="submitterSlug"
|
:submitter-slug="submitterSlug"
|
||||||
:is-direct-upload="isDirectUpload"
|
:is-direct-upload="isDirectUpload"
|
||||||
:multiple="true"
|
:multiple="true"
|
||||||
@@ -62,6 +62,7 @@ export default {
|
|||||||
IconPaperclip,
|
IconPaperclip,
|
||||||
IconTrashX
|
IconTrashX
|
||||||
},
|
},
|
||||||
|
inject: ['t'],
|
||||||
props: {
|
props: {
|
||||||
field: {
|
field: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="mx-auto max-w-md flex flex-col">
|
<div
|
||||||
|
class="mx-auto max-w-md flex flex-col"
|
||||||
|
dir="auto"
|
||||||
|
>
|
||||||
<p class="font-medium text-2xl flex items-center space-x-1.5 mx-auto">
|
<p class="font-medium text-2xl flex items-center space-x-1.5 mx-auto">
|
||||||
<IconCircleCheck
|
<IconCircleCheck
|
||||||
class="inline text-green-600"
|
class="inline text-green-600"
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<label
|
<label
|
||||||
:for="field.uuid"
|
:for="field.uuid"
|
||||||
class="label text-2xl"
|
class="label text-2xl"
|
||||||
>{{ field.name || t('date') }}
|
>{{ field.name && showFieldNames ? field.name : t('date') }}
|
||||||
<template v-if="!field.required">({{ t('optional') }})</template>
|
<template v-if="!field.required">({{ t('optional') }})</template>
|
||||||
</label>
|
</label>
|
||||||
<button
|
<button
|
||||||
@@ -46,6 +46,11 @@ export default {
|
|||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
showFieldNames: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
modelValue: {
|
modelValue: {
|
||||||
type: String,
|
type: String,
|
||||||
required: false,
|
required: false,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
:steps="stepFields"
|
:steps="stepFields"
|
||||||
:values="values"
|
:values="values"
|
||||||
:attachments-index="attachmentsIndex"
|
:attachments-index="attachmentsIndex"
|
||||||
:with-label="!isAnonymousChecboxes"
|
:with-label="!isAnonymousChecboxes && showFieldNames"
|
||||||
:current-step="currentStepFields"
|
:current-step="currentStepFields"
|
||||||
@focus-step="[saveStep(), goToStep($event, false, true), currentField.type !== 'checkbox' ? isFormVisible = true : '']"
|
@focus-step="[saveStep(), goToStep($event, false, true), currentField.type !== 'checkbox' ? isFormVisible = true : '']"
|
||||||
/>
|
/>
|
||||||
@@ -64,6 +64,7 @@
|
|||||||
<TextStep
|
<TextStep
|
||||||
:key="currentField.uuid"
|
:key="currentField.uuid"
|
||||||
v-model="values[currentField.uuid]"
|
v-model="values[currentField.uuid]"
|
||||||
|
:show-field-names="showFieldNames"
|
||||||
:field="currentField"
|
:field="currentField"
|
||||||
@focus="scrollIntoField(currentField)"
|
@focus="scrollIntoField(currentField)"
|
||||||
/>
|
/>
|
||||||
@@ -72,12 +73,13 @@
|
|||||||
v-else-if="currentField.type === 'date'"
|
v-else-if="currentField.type === 'date'"
|
||||||
:key="currentField.uuid"
|
:key="currentField.uuid"
|
||||||
v-model="values[currentField.uuid]"
|
v-model="values[currentField.uuid]"
|
||||||
|
:show-field-names="showFieldNames"
|
||||||
:field="currentField"
|
:field="currentField"
|
||||||
@focus="scrollIntoField(currentField)"
|
@focus="scrollIntoField(currentField)"
|
||||||
/>
|
/>
|
||||||
<div v-else-if="currentField.type === 'select'">
|
<div v-else-if="currentField.type === 'select'">
|
||||||
<label
|
<label
|
||||||
v-if="currentField.name"
|
v-if="showFieldNames && currentField.name"
|
||||||
:for="currentField.uuid"
|
:for="currentField.uuid"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
class="label text-2xl mb-2"
|
class="label text-2xl mb-2"
|
||||||
@@ -116,15 +118,28 @@
|
|||||||
</div>
|
</div>
|
||||||
<div v-else-if="currentField.type === 'radio'">
|
<div v-else-if="currentField.type === 'radio'">
|
||||||
<label
|
<label
|
||||||
v-if="currentField.name"
|
v-if="showFieldNames && currentField.name"
|
||||||
:for="currentField.uuid"
|
:for="currentField.uuid"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
class="label text-2xl mb-2"
|
class="label text-2xl mb-2"
|
||||||
>{{ currentField.name }}
|
>{{ currentField.name }}
|
||||||
<template v-if="!currentField.required">({{ t('optional') }})</template>
|
<template v-if="!currentField.required">({{ t('optional') }})</template>
|
||||||
</label>
|
</label>
|
||||||
<div class="flex w-full">
|
<div class="flex w-full max-h-44 overflow-y-auto">
|
||||||
<div class="space-y-3.5 mx-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
|
<div
|
||||||
v-for="(option, index) in currentField.options"
|
v-for="(option, index) in currentField.options"
|
||||||
:key="option.uuid"
|
:key="option.uuid"
|
||||||
@@ -154,6 +169,8 @@
|
|||||||
v-else-if="currentField.type === 'multiple'"
|
v-else-if="currentField.type === 'multiple'"
|
||||||
:key="currentField.uuid"
|
:key="currentField.uuid"
|
||||||
v-model="values[currentField.uuid]"
|
v-model="values[currentField.uuid]"
|
||||||
|
:show-field-names="showFieldNames"
|
||||||
|
:is-last-step="stepFields.length === currentStep + 1"
|
||||||
:field="currentField"
|
:field="currentField"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
@@ -168,7 +185,7 @@
|
|||||||
<div
|
<div
|
||||||
class="space-y-3.5 mx-auto"
|
class="space-y-3.5 mx-auto"
|
||||||
>
|
>
|
||||||
<template v-if="isAnonymousChecboxes">
|
<template v-if="isAnonymousChecboxes || !showFieldNames">
|
||||||
<span class="text-xl">
|
<span class="text-xl">
|
||||||
{{ t('complete_hightlighted_checkboxes_and_click') }} <span class="font-semibold">{{ stepFields.length === currentStep + 1 ? t('submit') : t('next') }}</span>.
|
{{ t('complete_hightlighted_checkboxes_and_click') }} <span class="font-semibold">{{ stepFields.length === currentStep + 1 ? t('submit') : t('next') }}</span>.
|
||||||
</span>
|
</span>
|
||||||
@@ -204,7 +221,15 @@
|
|||||||
:checked="!!values[field.uuid]"
|
:checked="!!values[field.uuid]"
|
||||||
@click="[scrollIntoField(field), values[field.uuid] = !values[field.uuid]]"
|
@click="[scrollIntoField(field), values[field.uuid] = !values[field.uuid]]"
|
||||||
>
|
>
|
||||||
<span class="text-xl">
|
<span
|
||||||
|
v-if="field.title"
|
||||||
|
class="text-xl"
|
||||||
|
v-html="field.title"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
v-else
|
||||||
|
class="text-xl"
|
||||||
|
>
|
||||||
{{ field.name || field.type + ' ' + (index + 1) }}
|
{{ field.name || field.type + ' ' + (index + 1) }}
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
@@ -220,6 +245,7 @@
|
|||||||
:is-direct-upload="isDirectUpload"
|
:is-direct-upload="isDirectUpload"
|
||||||
:attachments-index="attachmentsIndex"
|
:attachments-index="attachmentsIndex"
|
||||||
:submitter-slug="submitterSlug"
|
:submitter-slug="submitterSlug"
|
||||||
|
:show-field-names="showFieldNames"
|
||||||
@attached="[attachments.push($event), scrollIntoField(currentField)]"
|
@attached="[attachments.push($event), scrollIntoField(currentField)]"
|
||||||
/>
|
/>
|
||||||
<SignatureStep
|
<SignatureStep
|
||||||
@@ -233,6 +259,7 @@
|
|||||||
:with-typed-signature="withTypedSignature"
|
:with-typed-signature="withTypedSignature"
|
||||||
:attachments-index="attachmentsIndex"
|
:attachments-index="attachmentsIndex"
|
||||||
:submitter-slug="submitterSlug"
|
:submitter-slug="submitterSlug"
|
||||||
|
:show-field-names="showFieldNames"
|
||||||
@attached="attachments.push($event)"
|
@attached="attachments.push($event)"
|
||||||
@start="scrollIntoField(currentField)"
|
@start="scrollIntoField(currentField)"
|
||||||
@minimize="isFormVisible = false"
|
@minimize="isFormVisible = false"
|
||||||
@@ -246,6 +273,7 @@
|
|||||||
:previous-value="previousInitialsValue"
|
:previous-value="previousInitialsValue"
|
||||||
:is-direct-upload="isDirectUpload"
|
:is-direct-upload="isDirectUpload"
|
||||||
:attachments-index="attachmentsIndex"
|
:attachments-index="attachmentsIndex"
|
||||||
|
:show-field-names="showFieldNames"
|
||||||
:submitter-slug="submitterSlug"
|
:submitter-slug="submitterSlug"
|
||||||
@attached="attachments.push($event)"
|
@attached="attachments.push($event)"
|
||||||
@start="scrollIntoField(currentField)"
|
@start="scrollIntoField(currentField)"
|
||||||
@@ -268,6 +296,7 @@
|
|||||||
:key="currentField.uuid"
|
:key="currentField.uuid"
|
||||||
v-model="values[currentField.uuid]"
|
v-model="values[currentField.uuid]"
|
||||||
:field="currentField"
|
:field="currentField"
|
||||||
|
:show-field-names="showFieldNames"
|
||||||
:default-value="submitter.phone"
|
:default-value="submitter.phone"
|
||||||
:submitter-slug="submitterSlug"
|
:submitter-slug="submitterSlug"
|
||||||
@focus="scrollIntoField(currentField)"
|
@focus="scrollIntoField(currentField)"
|
||||||
@@ -324,9 +353,9 @@
|
|||||||
v-else
|
v-else
|
||||||
:is-demo="isDemo"
|
:is-demo="isDemo"
|
||||||
:attribution="attribution"
|
:attribution="attribution"
|
||||||
:completed-button="completedButton"
|
:completed-button="completedRedirectUrl ? {} : completedButton"
|
||||||
:with-send-copy-button="withSendCopyButton"
|
:with-send-copy-button="withSendCopyButton && !completedRedirectUrl"
|
||||||
:with-download-button="withDownloadButton"
|
:with-download-button="withDownloadButton && !completedRedirectUrl"
|
||||||
:with-confetti="withConfetti"
|
:with-confetti="withConfetti"
|
||||||
:can-send-email="canSendEmail && !!submitter.email"
|
:can-send-email="canSendEmail && !!submitter.email"
|
||||||
:submitter-slug="submitterSlug"
|
:submitter-slug="submitterSlug"
|
||||||
@@ -368,7 +397,7 @@ import DateStep from './date_step'
|
|||||||
import FormCompleted from './completed'
|
import FormCompleted from './completed'
|
||||||
import { IconInnerShadowTop, IconArrowsDiagonal, IconArrowsDiagonalMinimize2 } from '@tabler/icons-vue'
|
import { IconInnerShadowTop, IconArrowsDiagonal, IconArrowsDiagonalMinimize2 } from '@tabler/icons-vue'
|
||||||
import AppearsOn from './appears_on'
|
import AppearsOn from './appears_on'
|
||||||
import { t } from './i18n'
|
import i18n from './i18n'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'SubmissionForm',
|
name: 'SubmissionForm',
|
||||||
@@ -393,6 +422,7 @@ export default {
|
|||||||
return {
|
return {
|
||||||
baseUrl: this.baseUrl,
|
baseUrl: this.baseUrl,
|
||||||
scrollIntoArea: this.scrollIntoArea,
|
scrollIntoArea: this.scrollIntoArea,
|
||||||
|
scrollIntoField: this.scrollIntoField,
|
||||||
t: this.t
|
t: this.t
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -423,6 +453,11 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
|
showFieldNames: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
withTypedSignature: {
|
withTypedSignature: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -473,6 +508,11 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: true
|
default: true
|
||||||
},
|
},
|
||||||
|
language: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
values: {
|
values: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -488,6 +528,11 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: true
|
default: true
|
||||||
},
|
},
|
||||||
|
completedRedirectUrl: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
completedButton: {
|
completedButton: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -509,6 +554,9 @@ export default {
|
|||||||
currentStepFields () {
|
currentStepFields () {
|
||||||
return this.stepFields[this.currentStep]
|
return this.stepFields[this.currentStep]
|
||||||
},
|
},
|
||||||
|
browserLanguage () {
|
||||||
|
return (navigator.language || navigator.userLanguage || 'en').split('-')[0]
|
||||||
|
},
|
||||||
queryParams () {
|
queryParams () {
|
||||||
return new URLSearchParams(window.location.search)
|
return new URLSearchParams(window.location.search)
|
||||||
},
|
},
|
||||||
@@ -626,7 +674,9 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
t,
|
t (key) {
|
||||||
|
return i18n[this.language?.toLowerCase()]?.[key] || i18n[this.browserLanguage]?.[key] || i18n.en[key] || key
|
||||||
|
},
|
||||||
maybeTrackEmailClick () {
|
maybeTrackEmailClick () {
|
||||||
const { queryParams } = this
|
const { queryParams } = this
|
||||||
|
|
||||||
@@ -737,7 +787,7 @@ export default {
|
|||||||
|
|
||||||
stepPromise().then(async () => {
|
stepPromise().then(async () => {
|
||||||
const emptyRequiredField = this.stepFields.find((fields, index) => {
|
const emptyRequiredField = this.stepFields.find((fields, index) => {
|
||||||
return index < this.currentStep && fields[0].required && (fields[0].type === 'phone' || !this.allowToSkip) && !this.submittedValues[fields[0].uuid]?.length
|
return index < this.currentStep && fields[0].required && (fields[0].type === 'phone' || !this.allowToSkip) && (this.submittedValues[fields[0].uuid] === true ? false : !this.submittedValues[fields[0].uuid]?.length)
|
||||||
})
|
})
|
||||||
|
|
||||||
const formData = new FormData(this.$refs.form)
|
const formData = new FormData(this.$refs.form)
|
||||||
@@ -776,6 +826,10 @@ export default {
|
|||||||
if (respData) {
|
if (respData) {
|
||||||
this.onComplete(JSON.parse(respData))
|
this.onComplete(JSON.parse(respData))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.completedRedirectUrl) {
|
||||||
|
window.location.href = this.completedRedirectUrl
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
|
|||||||
@@ -1,6 +1,21 @@
|
|||||||
const en = {
|
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',
|
submit_form: 'Submit Form',
|
||||||
type_here: 'Type here',
|
type_here_: 'Type here...',
|
||||||
optional: 'optional',
|
optional: 'optional',
|
||||||
option: 'Option',
|
option: 'Option',
|
||||||
appears_on: 'Appears on',
|
appears_on: 'Appears on',
|
||||||
@@ -13,8 +28,6 @@ const en = {
|
|||||||
or_drag_and_drop_files: 'or drag and drop files',
|
or_drag_and_drop_files: 'or drag and drop files',
|
||||||
send_copy_via_email: 'Send copy via email',
|
send_copy_via_email: 'Send copy via email',
|
||||||
download: 'Download',
|
download: 'Download',
|
||||||
signature: 'Signature',
|
|
||||||
initials: 'Initials',
|
|
||||||
clear: 'Clear',
|
clear: 'Clear',
|
||||||
redraw: 'Redraw',
|
redraw: 'Redraw',
|
||||||
draw_initials: 'Draw initials',
|
draw_initials: 'Draw initials',
|
||||||
@@ -40,15 +53,32 @@ const en = {
|
|||||||
draw: 'Draw',
|
draw: 'Draw',
|
||||||
type: 'Type',
|
type: 'Type',
|
||||||
type_text: 'Type text',
|
type_text: 'Type text',
|
||||||
date: 'Date',
|
|
||||||
email_has_been_sent: 'Email has been sent',
|
email_has_been_sent: 'Email has been sent',
|
||||||
processing: 'Processing',
|
processing: 'Processing',
|
||||||
pay_with_strip: 'Pay with Stripe'
|
pay_with_strip: 'Pay with Stripe',
|
||||||
|
reupload: 'Reupload',
|
||||||
|
upload: 'Upload',
|
||||||
|
files: 'Files'
|
||||||
}
|
}
|
||||||
|
|
||||||
const es = {
|
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',
|
submit_form: 'Enviar Formulario',
|
||||||
type_here: 'Escribe aquí',
|
type_here_: 'Escribe aquí...',
|
||||||
optional: 'opcional',
|
optional: 'opcional',
|
||||||
appears_on: 'Aparece en',
|
appears_on: 'Aparece en',
|
||||||
page: 'Página',
|
page: 'Página',
|
||||||
@@ -60,8 +90,6 @@ const es = {
|
|||||||
or_drag_and_drop_files: 'o arrastra y suelta archivos',
|
or_drag_and_drop_files: 'o arrastra y suelta archivos',
|
||||||
send_copy_via_email: 'Enviar copia por correo electrónico',
|
send_copy_via_email: 'Enviar copia por correo electrónico',
|
||||||
download: 'Descargar',
|
download: 'Descargar',
|
||||||
signature: 'Firma',
|
|
||||||
initials: 'Iniciales',
|
|
||||||
clear: 'Borrar',
|
clear: 'Borrar',
|
||||||
redraw: 'Redibujar',
|
redraw: 'Redibujar',
|
||||||
draw_initials: 'Dibujar iniciales',
|
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',
|
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',
|
please_fill_all_required_fields: 'Por favor, complete todos los campos obligatorios',
|
||||||
set_today: 'Establecer Hoy',
|
set_today: 'Establecer Hoy',
|
||||||
date: 'Fecha',
|
|
||||||
toggle_multiline_text: 'Alternar Texto Multilínea',
|
toggle_multiline_text: 'Alternar Texto Multilínea',
|
||||||
draw_signature: 'Dibujar firma',
|
draw_signature: 'Dibujar firma',
|
||||||
type_initial: 'Escribir iniciales',
|
type_initial: 'Escribir iniciales',
|
||||||
@@ -90,15 +117,33 @@ const es = {
|
|||||||
type_text: 'Escribir texto',
|
type_text: 'Escribir texto',
|
||||||
email_has_been_sent: 'El correo electrónico ha sido enviado',
|
email_has_been_sent: 'El correo electrónico ha sido enviado',
|
||||||
processing: 'Procesando',
|
processing: 'Procesando',
|
||||||
pay_with_strip: 'Pagar con Stripe'
|
pay_with_strip: 'Pagar con Stripe',
|
||||||
|
reupload: 'Volver a subir',
|
||||||
|
upload: 'Subir',
|
||||||
|
files: 'Archivos'
|
||||||
}
|
}
|
||||||
|
|
||||||
const it = {
|
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',
|
submit_form: 'Invia Modulo',
|
||||||
type_here: 'Digita qui',
|
type_here_: 'Digita qui...',
|
||||||
optional: 'opzionale',
|
optional: 'opzionale',
|
||||||
appears_on: 'Compare su',
|
appears_on: 'Compare su',
|
||||||
page: 'Pagina',
|
page: 'Pagina',
|
||||||
|
take_photo: 'Scattare una foto',
|
||||||
select_your_option: 'Seleziona la tua opzione',
|
select_your_option: 'Seleziona la tua opzione',
|
||||||
complete_hightlighted_checkboxes_and_click: 'Completa le caselle evidenziate e fai clic',
|
complete_hightlighted_checkboxes_and_click: 'Completa le caselle evidenziate e fai clic',
|
||||||
submit: 'invia',
|
submit: 'invia',
|
||||||
@@ -107,8 +152,6 @@ const it = {
|
|||||||
or_drag_and_drop_files: 'oppure trascina e rilascia i file',
|
or_drag_and_drop_files: 'oppure trascina e rilascia i file',
|
||||||
send_copy_via_email: 'Invia copia via email',
|
send_copy_via_email: 'Invia copia via email',
|
||||||
download: 'Scarica',
|
download: 'Scarica',
|
||||||
signature: 'Firma',
|
|
||||||
initials: 'Iniziali',
|
|
||||||
clear: 'Cancella',
|
clear: 'Cancella',
|
||||||
redraw: 'Ridisegna',
|
redraw: 'Ridisegna',
|
||||||
draw_initials: 'Disegna iniziali',
|
draw_initials: 'Disegna iniziali',
|
||||||
@@ -128,7 +171,6 @@ const it = {
|
|||||||
verification_code_has_been_resent: 'Il codice di verifica è stato rinviato tramite SMS',
|
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',
|
please_fill_all_required_fields: 'Si prega di compilare tutti i campi obbligatori',
|
||||||
set_today: 'Imposta Oggi',
|
set_today: 'Imposta Oggi',
|
||||||
date: 'Data',
|
|
||||||
draw_signature: 'Disegna firma',
|
draw_signature: 'Disegna firma',
|
||||||
type_initial: 'Inserisci iniziali',
|
type_initial: 'Inserisci iniziali',
|
||||||
draw: 'Disegna',
|
draw: 'Disegna',
|
||||||
@@ -137,15 +179,33 @@ const it = {
|
|||||||
toggle_multiline_text: 'Attiva Testo Multilinea',
|
toggle_multiline_text: 'Attiva Testo Multilinea',
|
||||||
email_has_been_sent: "L'email è stata inviata",
|
email_has_been_sent: "L'email è stata inviata",
|
||||||
processing: 'Elaborazione',
|
processing: 'Elaborazione',
|
||||||
pay_with_strip: 'Paga con Stripe'
|
pay_with_strip: 'Paga con Stripe',
|
||||||
|
reupload: 'Ricarica',
|
||||||
|
upload: 'Carica',
|
||||||
|
files: 'File'
|
||||||
}
|
}
|
||||||
|
|
||||||
const de = {
|
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',
|
submit_form: 'Formular absenden',
|
||||||
type_here: 'Hier eingeben',
|
type_here_: 'Hier eingeben...',
|
||||||
optional: 'optional',
|
optional: 'optional',
|
||||||
appears_on: 'Erscheint auf',
|
appears_on: 'Erscheint auf',
|
||||||
page: 'Seite',
|
page: 'Seite',
|
||||||
|
take_photo: 'Foto aufnehmen',
|
||||||
select_your_option: 'Wähle deine Option',
|
select_your_option: 'Wähle deine Option',
|
||||||
complete_hightlighted_checkboxes_and_click: 'Markierte Kontrollkästchen ausfüllen und klicken',
|
complete_hightlighted_checkboxes_and_click: 'Markierte Kontrollkästchen ausfüllen und klicken',
|
||||||
submit: 'absenden',
|
submit: 'absenden',
|
||||||
@@ -154,8 +214,6 @@ const de = {
|
|||||||
or_drag_and_drop_files: 'oder Dateien hierher ziehen und ablegen',
|
or_drag_and_drop_files: 'oder Dateien hierher ziehen und ablegen',
|
||||||
send_copy_via_email: 'Kopie per E-Mail senden',
|
send_copy_via_email: 'Kopie per E-Mail senden',
|
||||||
download: 'Herunterladen',
|
download: 'Herunterladen',
|
||||||
signature: 'Unterschrift',
|
|
||||||
initials: 'Initialen',
|
|
||||||
clear: 'Löschen',
|
clear: 'Löschen',
|
||||||
redraw: 'Neu zeichnen',
|
redraw: 'Neu zeichnen',
|
||||||
draw_initials: 'Initialen zeichnen',
|
draw_initials: 'Initialen zeichnen',
|
||||||
@@ -175,7 +233,6 @@ const de = {
|
|||||||
verification_code_has_been_resent: 'Die Verifizierungscode wurde erneut per SMS gesendet',
|
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',
|
please_fill_all_required_fields: 'Bitte füllen Sie alle erforderlichen Felder aus',
|
||||||
set_today: 'Heute einstellen',
|
set_today: 'Heute einstellen',
|
||||||
date: 'Datum',
|
|
||||||
draw_signature: 'Unterschrift zeichnen',
|
draw_signature: 'Unterschrift zeichnen',
|
||||||
type_initial: 'Initialen eingeben',
|
type_initial: 'Initialen eingeben',
|
||||||
draw: 'Zeichnen',
|
draw: 'Zeichnen',
|
||||||
@@ -184,15 +241,33 @@ const de = {
|
|||||||
toggle_multiline_text: 'Mehrzeiligen Text umschalten',
|
toggle_multiline_text: 'Mehrzeiligen Text umschalten',
|
||||||
email_has_been_sent: 'Die E-Mail wurde gesendet',
|
email_has_been_sent: 'Die E-Mail wurde gesendet',
|
||||||
processing: 'Verarbeitung',
|
processing: 'Verarbeitung',
|
||||||
pay_with_strip: 'Mit Stripe bezahlen'
|
pay_with_strip: 'Mit Stripe bezahlen',
|
||||||
|
reupload: 'Erneut hochladen',
|
||||||
|
upload: 'Hochladen',
|
||||||
|
files: 'Dateien'
|
||||||
}
|
}
|
||||||
|
|
||||||
const fr = {
|
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',
|
submit_form: 'Envoyer le Formulaire',
|
||||||
type_here: 'Tapez ici',
|
type_here_: 'Tapez ici...',
|
||||||
optional: 'facultatif',
|
optional: 'facultatif',
|
||||||
appears_on: 'Apparaît sur',
|
appears_on: 'Apparaît sur',
|
||||||
page: 'Page',
|
page: 'Page',
|
||||||
|
take_photo: 'Prendre une photo',
|
||||||
select_your_option: 'Sélectionnez votre option',
|
select_your_option: 'Sélectionnez votre option',
|
||||||
complete_hightlighted_checkboxes_and_click: 'Complétez les cases à cocher en surbrillance et cliquez',
|
complete_hightlighted_checkboxes_and_click: 'Complétez les cases à cocher en surbrillance et cliquez',
|
||||||
submit: 'envoyer',
|
submit: 'envoyer',
|
||||||
@@ -201,8 +276,6 @@ const fr = {
|
|||||||
or_drag_and_drop_files: 'ou faites glisser-déposer les fichiers',
|
or_drag_and_drop_files: 'ou faites glisser-déposer les fichiers',
|
||||||
send_copy_via_email: 'Envoyer une copie par e-mail',
|
send_copy_via_email: 'Envoyer une copie par e-mail',
|
||||||
download: 'Télécharger',
|
download: 'Télécharger',
|
||||||
signature: 'Signature',
|
|
||||||
initials: 'Initiales',
|
|
||||||
clear: 'Effacer',
|
clear: 'Effacer',
|
||||||
redraw: 'Redessiner',
|
redraw: 'Redessiner',
|
||||||
draw_initials: 'Dessiner les initiales',
|
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',
|
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',
|
please_fill_all_required_fields: 'Veuillez remplir tous les champs obligatoires',
|
||||||
set_today: "Définir Aujourd'hui",
|
set_today: "Définir Aujourd'hui",
|
||||||
date: 'Date',
|
|
||||||
draw_signature: 'Dessiner une signature',
|
draw_signature: 'Dessiner une signature',
|
||||||
type_initial: 'Saisir les initiales',
|
type_initial: 'Saisir les initiales',
|
||||||
draw: 'Dessiner',
|
draw: 'Dessiner',
|
||||||
@@ -231,12 +303,29 @@ const fr = {
|
|||||||
toggle_multiline_text: 'Basculer le Texte Multiligne',
|
toggle_multiline_text: 'Basculer le Texte Multiligne',
|
||||||
email_has_been_sent: "L'email a été envoyé",
|
email_has_been_sent: "L'email a été envoyé",
|
||||||
processing: 'Traitement',
|
processing: 'Traitement',
|
||||||
pay_with_strip: 'Paiement avec Stripe'
|
pay_with_strip: 'Paiement avec Stripe',
|
||||||
|
reupload: 'Recharger',
|
||||||
|
upload: 'Télécharger',
|
||||||
|
files: 'Fichiers'
|
||||||
}
|
}
|
||||||
|
|
||||||
const pl = {
|
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',
|
submit_form: 'Wyślij Formularz',
|
||||||
type_here: 'Wpisz tutaj',
|
type_here_: 'Wpisz tutaj...',
|
||||||
optional: 'opcjonalny',
|
optional: 'opcjonalny',
|
||||||
appears_on: 'Pojawia się na',
|
appears_on: 'Pojawia się na',
|
||||||
page: 'Strona',
|
page: 'Strona',
|
||||||
@@ -244,12 +333,11 @@ const pl = {
|
|||||||
complete_hightlighted_checkboxes_and_click: 'Wypełnij zaznaczone pola wyboru i kliknij',
|
complete_hightlighted_checkboxes_and_click: 'Wypełnij zaznaczone pola wyboru i kliknij',
|
||||||
submit: 'wyślij',
|
submit: 'wyślij',
|
||||||
next: 'dalej',
|
next: 'dalej',
|
||||||
|
take_photo: 'Zrób zdjęcie',
|
||||||
click_to_upload: 'Kliknij, aby przesłać',
|
click_to_upload: 'Kliknij, aby przesłać',
|
||||||
or_drag_and_drop_files: 'lub przeciągnij i upuść pliki',
|
or_drag_and_drop_files: 'lub przeciągnij i upuść pliki',
|
||||||
send_copy_via_email: 'Wyślij kopię drogą mailową',
|
send_copy_via_email: 'Wyślij kopię drogą mailową',
|
||||||
download: 'Pobierz',
|
download: 'Pobierz',
|
||||||
signature: 'Podpis',
|
|
||||||
initials: 'Inicjały',
|
|
||||||
clear: 'Wyczyść',
|
clear: 'Wyczyść',
|
||||||
redraw: 'Przerysuj',
|
redraw: 'Przerysuj',
|
||||||
draw_initials: 'Narysuj inicjały',
|
draw_initials: 'Narysuj inicjały',
|
||||||
@@ -269,7 +357,6 @@ const pl = {
|
|||||||
verification_code_has_been_resent: 'Kod weryfikacyjny został ponownie wysłany',
|
verification_code_has_been_resent: 'Kod weryfikacyjny został ponownie wysłany',
|
||||||
please_fill_all_required_fields: 'Proszę wypełnić wszystkie wymagane pola',
|
please_fill_all_required_fields: 'Proszę wypełnić wszystkie wymagane pola',
|
||||||
set_today: 'Ustaw Dziś',
|
set_today: 'Ustaw Dziś',
|
||||||
date: 'Data',
|
|
||||||
draw_signature: 'Rysuj podpis',
|
draw_signature: 'Rysuj podpis',
|
||||||
type_initial: 'Wprowadź inicjały',
|
type_initial: 'Wprowadź inicjały',
|
||||||
draw: 'Rysuj',
|
draw: 'Rysuj',
|
||||||
@@ -278,15 +365,33 @@ const pl = {
|
|||||||
toggle_multiline_text: 'Przełącz Tekst Wielolinijkowy',
|
toggle_multiline_text: 'Przełącz Tekst Wielolinijkowy',
|
||||||
email_has_been_sent: 'E-mail został wysłany',
|
email_has_been_sent: 'E-mail został wysłany',
|
||||||
processing: 'Przetwarzanie',
|
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 = {
|
const uk = {
|
||||||
|
text: 'Текст',
|
||||||
|
signature: 'Підпис',
|
||||||
|
initials: 'Ініціали',
|
||||||
|
date: 'Дата',
|
||||||
|
image: 'Зображення',
|
||||||
|
file: 'Файл',
|
||||||
|
select: 'Вибрати',
|
||||||
|
checkbox: 'Прапорець',
|
||||||
|
multiple: 'Множинний',
|
||||||
|
radio: 'Радіо',
|
||||||
|
cells: 'Клітинки',
|
||||||
|
stamp: 'Штамп',
|
||||||
|
payment: 'Платіж',
|
||||||
|
phone: 'Телефон',
|
||||||
submit_form: 'Надіслати Форму',
|
submit_form: 'Надіслати Форму',
|
||||||
type_here: 'Введіть тут',
|
type_here_: 'Введіть тут',
|
||||||
optional: 'необов’язково',
|
optional: 'необов’язково',
|
||||||
appears_on: "З'являється на",
|
appears_on: "З'являється на",
|
||||||
page: 'Сторінка',
|
page: 'Сторінка',
|
||||||
|
take_photo: 'Зробити фото',
|
||||||
select_your_option: 'Виберіть варіант',
|
select_your_option: 'Виберіть варіант',
|
||||||
complete_hightlighted_checkboxes_and_click: 'Заповніть позначені прапорці та натисніть',
|
complete_hightlighted_checkboxes_and_click: 'Заповніть позначені прапорці та натисніть',
|
||||||
submit: 'надіслати',
|
submit: 'надіслати',
|
||||||
@@ -295,8 +400,6 @@ const uk = {
|
|||||||
or_drag_and_drop_files: 'або перетягніть файли сюди',
|
or_drag_and_drop_files: 'або перетягніть файли сюди',
|
||||||
send_copy_via_email: 'Надіслати копію електронною поштою',
|
send_copy_via_email: 'Надіслати копію електронною поштою',
|
||||||
download: 'Завантажити',
|
download: 'Завантажити',
|
||||||
signature: 'Підпис',
|
|
||||||
initials: 'Ініціали',
|
|
||||||
clear: 'Очистити',
|
clear: 'Очистити',
|
||||||
redraw: 'Перемалювати',
|
redraw: 'Перемалювати',
|
||||||
draw_initials: 'Намалювати ініціали',
|
draw_initials: 'Намалювати ініціали',
|
||||||
@@ -316,7 +419,6 @@ const uk = {
|
|||||||
verification_code_has_been_resent: 'Код підтвердження був повторно надісланий',
|
verification_code_has_been_resent: 'Код підтвердження був повторно надісланий',
|
||||||
please_fill_all_required_fields: "Будь ласка, заповніть всі обов'язкові поля",
|
please_fill_all_required_fields: "Будь ласка, заповніть всі обов'язкові поля",
|
||||||
set_today: 'Задати Сьогодні',
|
set_today: 'Задати Сьогодні',
|
||||||
date: 'Дата',
|
|
||||||
draw_signature: 'Намалюйте підпис',
|
draw_signature: 'Намалюйте підпис',
|
||||||
type_initial: 'Введіть ініціали',
|
type_initial: 'Введіть ініціали',
|
||||||
draw: 'Підпис',
|
draw: 'Підпис',
|
||||||
@@ -325,16 +427,34 @@ const uk = {
|
|||||||
toggle_multiline_text: 'Перемкнути Багаторядковий Текст',
|
toggle_multiline_text: 'Перемкнути Багаторядковий Текст',
|
||||||
email_has_been_sent: 'Електронний лист був відправлений',
|
email_has_been_sent: 'Електронний лист був відправлений',
|
||||||
processing: 'Обробка',
|
processing: 'Обробка',
|
||||||
pay_with_strip: 'Сплатити за допомогою Stripe'
|
pay_with_strip: 'Сплатити за допомогою Stripe',
|
||||||
|
reupload: 'Перезавантажити',
|
||||||
|
upload: 'Завантажити',
|
||||||
|
files: 'Файли'
|
||||||
}
|
}
|
||||||
|
|
||||||
const cs = {
|
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ář',
|
submit_form: 'Odeslat formulář',
|
||||||
type_here: 'Zadejte zde',
|
type_here_: 'Zadejte zde',
|
||||||
optional: 'volitelné',
|
optional: 'volitelné',
|
||||||
appears_on: 'Zobrazuje se na',
|
appears_on: 'Zobrazuje se na',
|
||||||
page: 'Stránka',
|
page: 'Stránka',
|
||||||
select_your_option: 'Vyberte svou volbu',
|
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',
|
complete_hightlighted_checkboxes_and_click: 'Označte zvýrazněné zaškrtávací políčka a klikněte na',
|
||||||
submit: 'odeslat',
|
submit: 'odeslat',
|
||||||
next: 'další',
|
next: 'další',
|
||||||
@@ -342,8 +462,6 @@ const cs = {
|
|||||||
or_drag_and_drop_files: 'nebo přetáhněte soubory sem',
|
or_drag_and_drop_files: 'nebo přetáhněte soubory sem',
|
||||||
send_copy_via_email: 'Odeslat kopii e-mailem',
|
send_copy_via_email: 'Odeslat kopii e-mailem',
|
||||||
download: 'Stáhnout',
|
download: 'Stáhnout',
|
||||||
signature: 'Podpis',
|
|
||||||
initials: 'Iniciály',
|
|
||||||
clear: 'Smazat',
|
clear: 'Smazat',
|
||||||
redraw: 'Překreslit',
|
redraw: 'Překreslit',
|
||||||
draw_initials: 'Nakreslit iniciály',
|
draw_initials: 'Nakreslit iniciály',
|
||||||
@@ -363,7 +481,6 @@ const cs = {
|
|||||||
verification_code_has_been_resent: 'Ověřovací kód byl znovu odeslán',
|
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',
|
please_fill_all_required_fields: 'Prosím vyplňte všechny povinné položky',
|
||||||
set_today: 'Nastavit Dnes',
|
set_today: 'Nastavit Dnes',
|
||||||
date: 'Datum',
|
|
||||||
draw_signature: 'Nakreslit podpis',
|
draw_signature: 'Nakreslit podpis',
|
||||||
type_initial: 'Zadat iniciály',
|
type_initial: 'Zadat iniciály',
|
||||||
draw: 'Kreslit',
|
draw: 'Kreslit',
|
||||||
@@ -372,15 +489,33 @@ const cs = {
|
|||||||
toggle_multiline_text: 'Přepnout Víceřádkový Text',
|
toggle_multiline_text: 'Přepnout Víceřádkový Text',
|
||||||
email_has_been_sent: 'E-mail byl odeslán',
|
email_has_been_sent: 'E-mail byl odeslán',
|
||||||
processing: 'Zpracová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 = {
|
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',
|
submit_form: 'Enviar Formulário',
|
||||||
type_here: 'Digite aqui',
|
type_here_: 'Digite aqui',
|
||||||
optional: 'opcional',
|
optional: 'opcional',
|
||||||
appears_on: 'Aparece em',
|
appears_on: 'Aparece em',
|
||||||
page: 'Página',
|
page: 'Página',
|
||||||
|
take_photo: 'Tirar foto',
|
||||||
select_your_option: 'Selecione sua opção',
|
select_your_option: 'Selecione sua opção',
|
||||||
complete_hightlighted_checkboxes_and_click: 'Complete as caixas de seleção destacadas e clique',
|
complete_hightlighted_checkboxes_and_click: 'Complete as caixas de seleção destacadas e clique',
|
||||||
submit: 'enviar',
|
submit: 'enviar',
|
||||||
@@ -389,8 +524,6 @@ const pt = {
|
|||||||
or_drag_and_drop_files: 'ou arraste e solte arquivos',
|
or_drag_and_drop_files: 'ou arraste e solte arquivos',
|
||||||
send_copy_via_email: 'Enviar cópia por e-mail',
|
send_copy_via_email: 'Enviar cópia por e-mail',
|
||||||
download: 'Baixar',
|
download: 'Baixar',
|
||||||
signature: 'Assinatura',
|
|
||||||
initials: 'Iniciais',
|
|
||||||
clear: 'Limpar',
|
clear: 'Limpar',
|
||||||
redraw: 'Redesenhar',
|
redraw: 'Redesenhar',
|
||||||
draw_initials: 'Desenhar iniciais',
|
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',
|
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',
|
please_fill_all_required_fields: 'Por favor, preencha todos os campos obrigatórios',
|
||||||
set_today: 'Definir Hoje',
|
set_today: 'Definir Hoje',
|
||||||
date: 'Data',
|
|
||||||
draw_signature: 'Desenhar assinatura',
|
draw_signature: 'Desenhar assinatura',
|
||||||
type_initial: 'Inserir iniciais',
|
type_initial: 'Inserir iniciais',
|
||||||
draw: 'Desenhar',
|
draw: 'Desenhar',
|
||||||
@@ -419,14 +551,138 @@ const pt = {
|
|||||||
toggle_multiline_text: 'Alternar Texto Multilinha',
|
toggle_multiline_text: 'Alternar Texto Multilinha',
|
||||||
email_has_been_sent: 'Email enviado',
|
email_has_been_sent: 'Email enviado',
|
||||||
processing: 'Processamento',
|
processing: 'Processamento',
|
||||||
pay_with_strip: 'Pagar com Stripe'
|
pay_with_strip: 'Pagar com Stripe',
|
||||||
|
reupload: 'Reenviar',
|
||||||
|
upload: 'Carregar',
|
||||||
|
files: 'Arquivos'
|
||||||
}
|
}
|
||||||
|
|
||||||
const i18n = { en, es, it, de, fr, pl, uk, cs, pt }
|
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: 'שליחה',
|
||||||
|
next: 'הבא',
|
||||||
|
click_to_upload: 'לחץ להעלאה',
|
||||||
|
or_drag_and_drop_files: 'או גרור ושחרר קבצים',
|
||||||
|
send_copy_via_email: 'שלח עותק באמצעות אימייל',
|
||||||
|
download: 'הורדה',
|
||||||
|
clear: 'נקה',
|
||||||
|
redraw: 'צייר מחדש',
|
||||||
|
draw_initials: 'צייר ציוני ראשי תיבות',
|
||||||
|
type_signature_here: 'הקלד חתימה כאן',
|
||||||
|
type_initial_here: 'הקלד ציוני ראשי תיבות כאן',
|
||||||
|
form_has_been_completed: 'הטופס הושלם',
|
||||||
|
create_a_free_account: 'צור חשבון חינם',
|
||||||
|
signed_with: 'חתום באמצעות',
|
||||||
|
please_check_the_box_to_continue: 'אנא סמן את התיבה כדי להמשיך',
|
||||||
|
open_source_documents_software: 'תוכנה פתוחה למסמכים',
|
||||||
|
verified_phone_number: 'אימות מספר טלפון',
|
||||||
|
use_international_format: 'השתמש בפורמט בינלאומי: +1xxx',
|
||||||
|
six_digits_code: 'קוד משתמש שש ספרות',
|
||||||
|
change_phone_number: 'שינוי מספר טלפון',
|
||||||
|
sending: 'שולח',
|
||||||
|
resend_code: 'שלח מחדש קוד',
|
||||||
|
verification_code_has_been_resent: 'קוד האימות נשלח מחדש בSMS',
|
||||||
|
please_fill_all_required_fields: 'אנא מלא את כל השדות הדרושים',
|
||||||
|
set_today: 'קבע היום',
|
||||||
|
toggle_multiline_text: 'שנה בין טקסט במספר שורות לטקסט בשורה אחת',
|
||||||
|
draw_signature: 'צייר חתימה',
|
||||||
|
type_initial: 'הקלד ציוני ראשי תיבות',
|
||||||
|
draw: 'צייר',
|
||||||
|
type: 'הקלד',
|
||||||
|
type_text: 'הקלד טקסט',
|
||||||
|
email_has_been_sent: 'האימייל נשלח',
|
||||||
|
processing: 'מעבד',
|
||||||
|
pay_with_strip: 'שלם עם סטרייפ',
|
||||||
|
reupload: 'העלה שוב',
|
||||||
|
upload: 'העלאה',
|
||||||
|
files: 'קבצים'
|
||||||
|
}
|
||||||
|
|
||||||
const browserLanguage = (navigator.language || navigator.userLanguage || 'en').split('-')[0]
|
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 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 default i18n
|
||||||
export { t }
|
|
||||||
@@ -3,13 +3,13 @@
|
|||||||
<div class="flex justify-between items-center w-full mb-2">
|
<div class="flex justify-between items-center w-full mb-2">
|
||||||
<label
|
<label
|
||||||
class="label text-2xl"
|
class="label text-2xl"
|
||||||
>{{ field.name || 'Image' }}</label>
|
>{{ showFieldNames && field.name ? field.name : t('image') }}</label>
|
||||||
<button
|
<button
|
||||||
class="btn btn-outline btn-sm"
|
class="btn btn-outline btn-sm"
|
||||||
@click.prevent="remove"
|
@click.prevent="remove"
|
||||||
>
|
>
|
||||||
<IconReload :width="16" />
|
<IconReload :width="16" />
|
||||||
Reupload
|
{{ t('reupload') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<FileDropzone
|
<FileDropzone
|
||||||
v-if="!modelValue"
|
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"
|
:submitter-slug="submitterSlug"
|
||||||
:accept="'image/*'"
|
:accept="'image/*'"
|
||||||
:is-direct-upload="isDirectUpload"
|
:is-direct-upload="isDirectUpload"
|
||||||
@@ -46,6 +46,7 @@ export default {
|
|||||||
FileDropzone,
|
FileDropzone,
|
||||||
IconReload
|
IconReload
|
||||||
},
|
},
|
||||||
|
inject: ['t'],
|
||||||
props: {
|
props: {
|
||||||
field: {
|
field: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@@ -56,6 +57,11 @@ export default {
|
|||||||
required: true,
|
required: true,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
|
showFieldNames: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
submitterSlug: {
|
submitterSlug: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<div class="flex justify-between items-center w-full mb-2">
|
<div class="flex justify-between items-center w-full mb-2">
|
||||||
<label
|
<label
|
||||||
class="label text-2xl"
|
class="label text-2xl"
|
||||||
>{{ field.name || t('initials') }}</label>
|
>{{ showFieldNames && field.name ? field.name : t('initials') }}</label>
|
||||||
<div class="space-x-2 flex">
|
<div class="space-x-2 flex">
|
||||||
<span
|
<span
|
||||||
v-if="isDrawInitials"
|
v-if="isDrawInitials"
|
||||||
@@ -125,6 +125,11 @@ export default {
|
|||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
showFieldNames: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
isDirectUpload: {
|
isDirectUpload: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: true,
|
required: true,
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<label
|
<label
|
||||||
v-if="field.name"
|
v-if="showFieldNames && field.name"
|
||||||
:for="field.uuid"
|
:for="field.uuid"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
class="label text-2xl mb-2"
|
class="label text-2xl mb-2"
|
||||||
>{{ field.name }}</label>
|
>{{ field.name }}</label>
|
||||||
<div class="flex w-full">
|
<div class="flex w-full max-h-44 overflow-y-auto">
|
||||||
<input
|
<input
|
||||||
v-if="modelValue.length === 0"
|
v-if="modelValue.length === 0"
|
||||||
type="text"
|
type="text"
|
||||||
@@ -13,7 +13,18 @@
|
|||||||
:value="''"
|
:value="''"
|
||||||
class="hidden"
|
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
|
<div
|
||||||
v-for="(option, index) in field.options"
|
v-for="(option, index) in field.options"
|
||||||
:key="option.uuid"
|
:key="option.uuid"
|
||||||
@@ -44,12 +55,22 @@
|
|||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: 'MultiSelectStep',
|
name: 'MultiSelectStep',
|
||||||
inject: ['t'],
|
inject: ['t', 'scrollIntoField'],
|
||||||
props: {
|
props: {
|
||||||
field: {
|
field: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
isLastStep: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
showFieldNames: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
modelValue: {
|
modelValue: {
|
||||||
type: Array,
|
type: Array,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -62,6 +83,11 @@ export default {
|
|||||||
inputRefs: []
|
inputRefs: []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
showOptions () {
|
||||||
|
return this.showFieldNames && (this.field.options.some((e) => e.value) || this.field.options.length < 5)
|
||||||
|
}
|
||||||
|
},
|
||||||
beforeUpdate () {
|
beforeUpdate () {
|
||||||
this.inputRefs = []
|
this.inputRefs = []
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<label
|
<label
|
||||||
:for="isCodeSent ? 'one_time_code' : field.uuid"
|
:for="isCodeSent ? 'one_time_code' : field.uuid"
|
||||||
class="label text-2xl mb-2"
|
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>
|
<template v-if="!field.required">({{ t('optional') }})</template>
|
||||||
</label>
|
</label>
|
||||||
<div>
|
<div>
|
||||||
@@ -93,6 +93,11 @@ export default {
|
|||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
showFieldNames: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
modelValue: {
|
modelValue: {
|
||||||
type: String,
|
type: String,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -140,7 +145,11 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
async submit () {
|
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.sendVerificationCode()
|
||||||
|
|
||||||
this.$emit('update:model-value', this.$refs.phone.value)
|
this.$emit('update:model-value', this.$refs.phone.value)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<div class="flex justify-between items-center w-full mb-2">
|
<div class="flex justify-between items-center w-full mb-2">
|
||||||
<label
|
<label
|
||||||
class="label text-2xl"
|
class="label text-2xl"
|
||||||
>{{ field.name || t('signature') }}</label>
|
>{{ showFieldNames && field.name ? field.name : t('signature') }}</label>
|
||||||
<div class="space-x-2 flex">
|
<div class="space-x-2 flex">
|
||||||
<span
|
<span
|
||||||
v-if="isTextSignature"
|
v-if="isTextSignature"
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
class="tooltip"
|
class="tooltip"
|
||||||
data-tip="Take photo"
|
:data-tip="t('take_photo')"
|
||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
class="btn btn-outline btn-sm font-medium"
|
class="btn btn-outline btn-sm font-medium"
|
||||||
@@ -147,6 +147,11 @@ export default {
|
|||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
showFieldNames: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
isDirectUpload: {
|
isDirectUpload: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: true,
|
required: true,
|
||||||
@@ -221,7 +226,7 @@ export default {
|
|||||||
},
|
},
|
||||||
loadFont () {
|
loadFont () {
|
||||||
if (!isFontLoaded) {
|
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) => {
|
font.load().then((loadedFont) => {
|
||||||
document.fonts.add(loadedFont)
|
document.fonts.add(loadedFont)
|
||||||
@@ -260,6 +265,7 @@ export default {
|
|||||||
},
|
},
|
||||||
toggleTextInput () {
|
toggleTextInput () {
|
||||||
this.remove()
|
this.remove()
|
||||||
|
this.clear()
|
||||||
this.isTextSignature = !this.isTextSignature
|
this.isTextSignature = !this.isTextSignature
|
||||||
|
|
||||||
if (this.isTextSignature) {
|
if (this.isTextSignature) {
|
||||||
|
|||||||
@@ -1,16 +1,23 @@
|
|||||||
<template>
|
<template>
|
||||||
<label
|
<label
|
||||||
v-if="field.name"
|
v-if="showFieldNames && field.name"
|
||||||
:for="field.uuid"
|
:for="field.uuid"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
class="label text-2xl mb-2"
|
class="label text-2xl"
|
||||||
>{{ field.name }}
|
:class="{ 'mb-2': !field.description }"
|
||||||
|
><template v-if="field.title"><span v-html="field.title" /></template>
|
||||||
|
<template v-else>{{ field.name }}</template>
|
||||||
<template v-if="!field.required">({{ t('optional') }})</template>
|
<template v-if="!field.required">({{ t('optional') }})</template>
|
||||||
</label>
|
</label>
|
||||||
<div
|
<div
|
||||||
v-else
|
v-else
|
||||||
class="py-1"
|
class="py-1"
|
||||||
/>
|
/>
|
||||||
|
<div
|
||||||
|
v-if="field.description"
|
||||||
|
class="mb-3 px-1 text-lg"
|
||||||
|
v-html="field.description"
|
||||||
|
/>
|
||||||
<AppearsOn :field="field" />
|
<AppearsOn :field="field" />
|
||||||
<div class="items-center flex">
|
<div class="items-center flex">
|
||||||
<input
|
<input
|
||||||
@@ -19,12 +26,13 @@
|
|||||||
v-model="text"
|
v-model="text"
|
||||||
:maxlength="cellsMaxLegth"
|
:maxlength="cellsMaxLegth"
|
||||||
dir="auto"
|
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"
|
:required="field.required"
|
||||||
:pattern="field.validation?.pattern"
|
:pattern="field.validation?.pattern"
|
||||||
:oninvalid="field.validation?.message ? `this.setCustomValidity(${JSON.stringify(field.validation.message)})` : ''"
|
:oninvalid="field.validation?.message ? `this.setCustomValidity(${JSON.stringify(field.validation.message)})` : ''"
|
||||||
:oninput="field.validation?.message ? `this.setCustomValidity('')` : ''"
|
:oninput="field.validation?.message ? `this.setCustomValidity('')` : ''"
|
||||||
:placeholder="`${t('type_here')}...${field.required ? '' : ` (${t('optional')})`}`"
|
:placeholder="`${t('type_here_')}${field.required ? '' : ` (${t('optional')})`}`"
|
||||||
type="text"
|
type="text"
|
||||||
:name="`values[${field.uuid}]`"
|
:name="`values[${field.uuid}]`"
|
||||||
@focus="$emit('focus')"
|
@focus="$emit('focus')"
|
||||||
@@ -34,15 +42,16 @@
|
|||||||
:id="field.uuid"
|
:id="field.uuid"
|
||||||
ref="textarea"
|
ref="textarea"
|
||||||
v-model="text"
|
v-model="text"
|
||||||
|
dir="auto"
|
||||||
class="base-textarea !text-2xl w-full"
|
class="base-textarea !text-2xl w-full"
|
||||||
:placeholder="`${t('type_here')}...${field.required ? '' : ` (${t('optional')})`}`"
|
:placeholder="`${t('type_here_')}${field.required ? '' : ` (${t('optional')})`}`"
|
||||||
:required="field.required"
|
:required="field.required"
|
||||||
:name="`values[${field.uuid}]`"
|
:name="`values[${field.uuid}]`"
|
||||||
@input="resizeTextarea"
|
@input="resizeTextarea"
|
||||||
@focus="$emit('focus')"
|
@focus="$emit('focus')"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
v-if="!isTextArea && field.type !== 'cells'"
|
v-if="!isTextArea && field.type !== 'cells' && !field.validation?.pattern"
|
||||||
class="tooltip"
|
class="tooltip"
|
||||||
:data-tip="t('toggle_multiline_text')"
|
:data-tip="t('toggle_multiline_text')"
|
||||||
>
|
>
|
||||||
@@ -73,6 +82,11 @@ export default {
|
|||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
showFieldNames: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
modelValue: {
|
modelValue: {
|
||||||
type: String,
|
type: String,
|
||||||
required: false,
|
required: false,
|
||||||
|
|||||||
@@ -43,7 +43,8 @@
|
|||||||
class="border-r"
|
class="border-r"
|
||||||
:compact="true"
|
:compact="true"
|
||||||
:editable="editable && !defaultField"
|
: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"
|
:submitters="template.submitters"
|
||||||
@update:model-value="save"
|
@update:model-value="save"
|
||||||
@click="selectedAreaRef.value = area"
|
@click="selectedAreaRef.value = area"
|
||||||
@@ -166,6 +167,11 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: null
|
default: null
|
||||||
},
|
},
|
||||||
|
defaultSubmitters: {
|
||||||
|
type: Array,
|
||||||
|
required: false,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
editable: {
|
editable: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-if="$slots.buttons || withTitle"
|
v-if="$slots.buttons || withTitle"
|
||||||
|
id="title_container"
|
||||||
class="flex justify-between py-1.5 items-center pr-4 top-0 z-10"
|
class="flex justify-between py-1.5 items-center pr-4 top-0 z-10"
|
||||||
:class="{ sticky: withStickySubmitters || isBreakpointLg }"
|
:class="{ sticky: withStickySubmitters || isBreakpointLg }"
|
||||||
:style="{ backgroundColor }"
|
:style="{ backgroundColor }"
|
||||||
@@ -47,7 +48,7 @@
|
|||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
:href="`/templates/${template.id}/submissions/new`"
|
:href="`/templates/${template.id}/submissions/new?with_link=true`"
|
||||||
data-turbo-frame="modal"
|
data-turbo-frame="modal"
|
||||||
class="white-button md:!px-6"
|
class="white-button md:!px-6"
|
||||||
@click="maybeShowEmptyTemplateAlert"
|
@click="maybeShowEmptyTemplateAlert"
|
||||||
@@ -61,6 +62,7 @@
|
|||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
<button
|
<button
|
||||||
|
v-if="editable"
|
||||||
class="base-button"
|
class="base-button"
|
||||||
:class="{ disabled: isSaving }"
|
:class="{ disabled: isSaving }"
|
||||||
v-bind="isSaving ? { disabled: true } : {}"
|
v-bind="isSaving ? { disabled: true } : {}"
|
||||||
@@ -79,6 +81,15 @@
|
|||||||
{{ t('save') }}
|
{{ t('save') }}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
|
<a
|
||||||
|
v-else
|
||||||
|
:href="`/templates/${template.id}`"
|
||||||
|
class="base-button"
|
||||||
|
>
|
||||||
|
<span class="hidden md:inline">
|
||||||
|
{{ t('back') }}
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -112,7 +123,7 @@
|
|||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
class="sticky bottom-0 py-2"
|
class="sticky bottom-0 py-2"
|
||||||
:style="withStickySubmitters ? { backgroundColor } : {}"
|
:style="{ backgroundColor }"
|
||||||
>
|
>
|
||||||
<Upload
|
<Upload
|
||||||
v-if="sortedDocuments.length && editable && withUploadButton"
|
v-if="sortedDocuments.length && editable && withUploadButton"
|
||||||
@@ -148,6 +159,7 @@
|
|||||||
:is-drag="!!dragField"
|
:is-drag="!!dragField"
|
||||||
:default-fields="defaultFields"
|
:default-fields="defaultFields"
|
||||||
:allow-draw="!onlyDefinedFields"
|
:allow-draw="!onlyDefinedFields"
|
||||||
|
:default-submitters="defaultSubmitters"
|
||||||
:draw-field="drawField"
|
:draw-field="drawField"
|
||||||
:editable="editable"
|
:editable="editable"
|
||||||
:base-url="baseUrl"
|
:base-url="baseUrl"
|
||||||
@@ -179,29 +191,12 @@
|
|||||||
v-if="withUploadButton"
|
v-if="withUploadButton"
|
||||||
:template-id="template.id"
|
:template-id="template.id"
|
||||||
:is-direct-upload="isDirectUpload"
|
:is-direct-upload="isDirectUpload"
|
||||||
|
:accept-file-types="acceptFileTypes"
|
||||||
@success="updateFromUpload"
|
@success="updateFromUpload"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</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"
|
|
||||||
:selected-submitter="selectedSubmitter"
|
|
||||||
@select="startFieldDraw($event)"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="withFieldsList"
|
v-if="withFieldsList"
|
||||||
@@ -244,6 +239,7 @@
|
|||||||
:with-help="withHelp"
|
:with-help="withHelp"
|
||||||
:default-submitters="defaultSubmitters"
|
:default-submitters="defaultSubmitters"
|
||||||
:default-fields="defaultFields"
|
:default-fields="defaultFields"
|
||||||
|
:field-types="fieldTypes"
|
||||||
:with-sticky-submitters="withStickySubmitters"
|
:with-sticky-submitters="withStickySubmitters"
|
||||||
:only-defined-fields="onlyDefinedFields"
|
:only-defined-fields="onlyDefinedFields"
|
||||||
:editable="editable"
|
:editable="editable"
|
||||||
@@ -256,6 +252,27 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -298,7 +315,9 @@ export default {
|
|||||||
template: this.template,
|
template: this.template,
|
||||||
save: this.save,
|
save: this.save,
|
||||||
t: this.t,
|
t: this.t,
|
||||||
|
currencies: this.currencies,
|
||||||
baseFetch: this.baseFetch,
|
baseFetch: this.baseFetch,
|
||||||
|
fieldTypes: this.fieldTypes,
|
||||||
backgroundColor: this.backgroundColor,
|
backgroundColor: this.backgroundColor,
|
||||||
withPhone: this.withPhone,
|
withPhone: this.withPhone,
|
||||||
withPayment: this.withPayment,
|
withPayment: this.withPayment,
|
||||||
@@ -345,6 +364,16 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: () => []
|
default: () => []
|
||||||
},
|
},
|
||||||
|
currencies: {
|
||||||
|
type: Array,
|
||||||
|
required: false,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
fieldTypes: {
|
||||||
|
type: Array,
|
||||||
|
required: false,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
defaultSubmitters: {
|
defaultSubmitters: {
|
||||||
type: Array,
|
type: Array,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -372,6 +401,13 @@ export default {
|
|||||||
return () => {}
|
return () => {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onSave: {
|
||||||
|
type: Function,
|
||||||
|
required: false,
|
||||||
|
default () {
|
||||||
|
return () => {}
|
||||||
|
}
|
||||||
|
},
|
||||||
withStickySubmitters: {
|
withStickySubmitters: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -717,7 +753,11 @@ export default {
|
|||||||
const documentRef = this.documentRefs.find((e) => e.document.uuid === area.attachment_uuid)
|
const documentRef = this.documentRefs.find((e) => e.document.uuid === area.attachment_uuid)
|
||||||
const pageMask = documentRef.pageRefs[area.page].$refs.mask
|
const pageMask = documentRef.pageRefs[area.page].$refs.mask
|
||||||
|
|
||||||
const type = (pageMask.clientWidth * area.w) < 35 ? 'checkbox' : 'text'
|
let type = (pageMask.clientWidth * area.w) < 35 ? 'checkbox' : 'text'
|
||||||
|
|
||||||
|
if (this.fieldTypes.length !== 0 && !this.fieldTypes.includes(type)) {
|
||||||
|
type = this.fieldTypes[0]
|
||||||
|
}
|
||||||
|
|
||||||
if (type === 'checkbox') {
|
if (type === 'checkbox') {
|
||||||
const previousField = [...this.template.fields].reverse().find((f) => f.type === type)
|
const previousField = [...this.template.fields].reverse().find((f) => f.type === type)
|
||||||
@@ -864,7 +904,7 @@ export default {
|
|||||||
this.save()
|
this.save()
|
||||||
},
|
},
|
||||||
onDocumentRemove (item) {
|
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)
|
this.template.schema.splice(this.template.schema.indexOf(item), 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -967,6 +1007,10 @@ export default {
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
headers: { 'Content-Type': 'application/json' }
|
headers: { 'Content-Type': 'application/json' }
|
||||||
|
}).then(() => {
|
||||||
|
if (this.onSave) {
|
||||||
|
this.onSave(this.template)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
:allow-draw="allowDraw"
|
:allow-draw="allowDraw"
|
||||||
:is-drag="isDrag"
|
:is-drag="isDrag"
|
||||||
:default-fields="defaultFields"
|
:default-fields="defaultFields"
|
||||||
|
:default-submitters="defaultSubmitters"
|
||||||
:draw-field="drawField"
|
:draw-field="drawField"
|
||||||
:selected-submitter="selectedSubmitter"
|
:selected-submitter="selectedSubmitter"
|
||||||
:image="image"
|
:image="image"
|
||||||
@@ -42,6 +43,11 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: () => []
|
default: () => []
|
||||||
},
|
},
|
||||||
|
defaultSubmitters: {
|
||||||
|
type: Array,
|
||||||
|
required: false,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
allowDraw: {
|
allowDraw: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
|
|||||||
@@ -61,7 +61,7 @@
|
|||||||
<button
|
<button
|
||||||
v-if="submitters.length > 1 && editable"
|
v-if="submitters.length > 1 && editable"
|
||||||
class="px-2"
|
class="px-2"
|
||||||
@click.stop="remove(submitter)"
|
@click.prevent.stop="remove(submitter)"
|
||||||
>
|
>
|
||||||
<IconTrashX :width="18" />
|
<IconTrashX :width="18" />
|
||||||
</button>
|
</button>
|
||||||
@@ -125,7 +125,8 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</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">
|
<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"
|
width="18"
|
||||||
height="18"
|
height="18"
|
||||||
/>
|
/>
|
||||||
@@ -165,7 +166,7 @@
|
|||||||
title="Up"
|
title="Up"
|
||||||
class="relative w-2"
|
class="relative w-2"
|
||||||
style="font-size: 10px; margin-bottom: -4px"
|
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>
|
</button>
|
||||||
@@ -173,7 +174,7 @@
|
|||||||
title="Down"
|
title="Down"
|
||||||
class="relative w-2"
|
class="relative w-2"
|
||||||
style="font-size: 10px; margin-top: -4px"
|
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>
|
</button>
|
||||||
@@ -181,14 +182,14 @@
|
|||||||
<button
|
<button
|
||||||
v-if="!compact && submitters.length > 1 && editable"
|
v-if="!compact && submitters.length > 1 && editable"
|
||||||
class="hidden group-hover:block px-2"
|
class="hidden group-hover:block px-2"
|
||||||
@click.stop="remove(submitter)"
|
@click.prevent.stop="remove(submitter)"
|
||||||
>
|
>
|
||||||
<IconTrashX :width="18" />
|
<IconTrashX :width="18" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li v-if="submitters.length < 10 && editable">
|
<li v-if="submitters.length < 10 && editable && allowAddNew">
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
class="flex px-2"
|
class="flex px-2"
|
||||||
@@ -208,7 +209,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<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 Contenteditable from './contenteditable'
|
||||||
import { v4 } from 'uuid'
|
import { v4 } from 'uuid'
|
||||||
|
|
||||||
@@ -216,6 +217,7 @@ export default {
|
|||||||
name: 'FieldSubmitter',
|
name: 'FieldSubmitter',
|
||||||
components: {
|
components: {
|
||||||
IconUserPlus,
|
IconUserPlus,
|
||||||
|
IconChevronDown,
|
||||||
Contenteditable,
|
Contenteditable,
|
||||||
IconPlus,
|
IconPlus,
|
||||||
IconTrashX,
|
IconTrashX,
|
||||||
@@ -242,6 +244,11 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
|
allowAddNew: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
modelValue: {
|
modelValue: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true
|
||||||
@@ -296,7 +303,7 @@ export default {
|
|||||||
this.$emit('update:model-value', submitter.uuid)
|
this.$emit('update:model-value', submitter.uuid)
|
||||||
},
|
},
|
||||||
remove (submitter) {
|
remove (submitter) {
|
||||||
if (window.confirm('Are you sure?')) {
|
if (window.confirm(this.t('are_you_sure'))) {
|
||||||
this.$emit('remove', submitter)
|
this.$emit('remove', submitter)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
v-for="(icon, type) in fieldIcons"
|
v-for="(icon, type) in fieldIcons"
|
||||||
:key="type"
|
:key="type"
|
||||||
>
|
>
|
||||||
<li v-if="(withPhone || type != 'phone') && (withPayment || type != 'payment')">
|
<li v-if="(fieldTypes.length === 0 || fieldTypes.includes(type)) && (withPhone || type != 'phone') && (withPayment || type != 'payment')">
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
class="text-sm py-1 px-2"
|
class="text-sm py-1 px-2"
|
||||||
@@ -54,7 +54,7 @@ import { IconTextSize, IconWritingSign, IconCalendarEvent, IconPhoto, IconCheckb
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'FiledTypeDropdown',
|
name: 'FiledTypeDropdown',
|
||||||
inject: ['withPhone', 'withPayment', 't'],
|
inject: ['withPhone', 'withPayment', 't', 'fieldTypes'],
|
||||||
props: {
|
props: {
|
||||||
modelValue: {
|
modelValue: {
|
||||||
type: String,
|
type: String,
|
||||||
|
|||||||
@@ -72,7 +72,7 @@
|
|||||||
:key="type"
|
:key="type"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
v-if="(withPhone || type != 'phone') && (withPayment || type != 'payment')"
|
v-if="(fieldTypes.length === 0 || fieldTypes.includes(type)) && (withPhone || type != 'phone') && (withPayment || type != 'payment')"
|
||||||
draggable="true"
|
draggable="true"
|
||||||
class="group flex items-center justify-center border border-dashed border-base-300 hover:border-base-content/20 w-full rounded relative"
|
class="group flex items-center justify-center border border-dashed border-base-300 hover:border-base-content/20 w-full rounded relative"
|
||||||
:style="{ backgroundColor: backgroundColor }"
|
:style="{ backgroundColor: backgroundColor }"
|
||||||
@@ -91,7 +91,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
<div
|
<div
|
||||||
v-else-if="type == 'phone'"
|
v-else-if="type == 'phone' && (fieldTypes.length === 0 || fieldTypes.includes(type))"
|
||||||
class="tooltip tooltip-bottom flex"
|
class="tooltip tooltip-bottom flex"
|
||||||
:class="{'tooltip-bottom-start': !withPayment, 'tooltip-bottom': withPayment }"
|
:class="{'tooltip-bottom-start': !withPayment, 'tooltip-bottom': withPayment }"
|
||||||
data-tip="Unlock SMS-verified phone number field with paid plan. Use text field for phone numbers without verification."
|
data-tip="Unlock SMS-verified phone number field with paid plan. Use text field for phone numbers without verification."
|
||||||
@@ -190,6 +190,11 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: true
|
default: true
|
||||||
},
|
},
|
||||||
|
fieldTypes: {
|
||||||
|
type: Array,
|
||||||
|
required: false,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
submitters: {
|
submitters: {
|
||||||
type: Array,
|
type: Array,
|
||||||
required: true
|
required: true
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
const en = {
|
const en = {
|
||||||
|
are_you_sure: 'Are you sure?',
|
||||||
sign_yourself: 'Sign Yourself',
|
sign_yourself: 'Sign Yourself',
|
||||||
send: 'Send',
|
send: 'Send',
|
||||||
save: 'Save',
|
save: 'Save',
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<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">
|
<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">
|
<div class="flex items-center space-x-2">
|
||||||
<component
|
<component
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<span
|
<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
|
<label
|
||||||
class="btn btn-neutral text-white btn-circle btn-lg group"
|
class="btn btn-neutral text-white btn-circle btn-lg group"
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
v-for="(icon, type) in fieldIcons"
|
v-for="(icon, type) in fieldIcons"
|
||||||
:key="type"
|
:key="type"
|
||||||
>
|
>
|
||||||
<li v-if="(withPhone || type != 'phone') && (withPayment || type != 'payment')">
|
<li v-if="(fieldTypes.length === 0 || fieldTypes.includes(type)) && (withPhone || type != 'phone') && (withPayment || type != 'payment')">
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
class="text-sm py-1 px-2"
|
class="text-sm py-1 px-2"
|
||||||
@@ -94,6 +94,11 @@ export default {
|
|||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
fieldTypes: {
|
||||||
|
type: Array,
|
||||||
|
required: false,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
defaultFields: {
|
defaultFields: {
|
||||||
type: Array,
|
type: Array,
|
||||||
required: false,
|
required: false,
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
:field="item.field"
|
:field="item.field"
|
||||||
:editable="editable"
|
:editable="editable"
|
||||||
:default-field="defaultFields.find((f) => f.name === item.field.name)"
|
:default-field="defaultFields.find((f) => f.name === item.field.name)"
|
||||||
|
:default-submitters="defaultSubmitters"
|
||||||
@start-resize="resizeDirection = $event"
|
@start-resize="resizeDirection = $event"
|
||||||
@stop-resize="resizeDirection = null"
|
@stop-resize="resizeDirection = null"
|
||||||
@start-drag="isMove = true"
|
@start-drag="isMove = true"
|
||||||
@@ -33,7 +34,7 @@
|
|||||||
<FieldArea
|
<FieldArea
|
||||||
v-if="newArea"
|
v-if="newArea"
|
||||||
:is-draw="true"
|
:is-draw="true"
|
||||||
:field="{ submitter_uuid: selectedSubmitter.uuid, type: drawField?.type || 'text' }"
|
:field="{ submitter_uuid: selectedSubmitter.uuid, type: drawField?.type || defaultFieldType }"
|
||||||
:area="newArea"
|
:area="newArea"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -60,6 +61,7 @@ export default {
|
|||||||
components: {
|
components: {
|
||||||
FieldArea
|
FieldArea
|
||||||
},
|
},
|
||||||
|
inject: ['fieldTypes'],
|
||||||
props: {
|
props: {
|
||||||
image: {
|
image: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@@ -84,6 +86,11 @@ export default {
|
|||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
defaultSubmitters: {
|
||||||
|
type: Array,
|
||||||
|
required: false,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
drawField: {
|
drawField: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -115,6 +122,13 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
defaultFieldType () {
|
||||||
|
if (this.fieldTypes.length !== 0 && !this.fieldTypes.includes('text')) {
|
||||||
|
return this.fieldTypes[0]
|
||||||
|
} else {
|
||||||
|
return 'text'
|
||||||
|
}
|
||||||
|
},
|
||||||
isMobile () {
|
isMobile () {
|
||||||
return /android|iphone|ipad/i.test(navigator.userAgent)
|
return /android|iphone|ipad/i.test(navigator.userAgent)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
@change="save"
|
@change="save"
|
||||||
>
|
>
|
||||||
<option
|
<option
|
||||||
v-for="currency in currencies"
|
v-for="currency in currenciesList"
|
||||||
:key="currency"
|
:key="currency"
|
||||||
:value="currency"
|
:value="currency"
|
||||||
>
|
>
|
||||||
@@ -167,7 +167,7 @@ export default {
|
|||||||
IconInnerShadowTop,
|
IconInnerShadowTop,
|
||||||
IconBrandStripe
|
IconBrandStripe
|
||||||
},
|
},
|
||||||
inject: ['backgroundColor', 'save'],
|
inject: ['backgroundColor', 'save', 'currencies'],
|
||||||
props: {
|
props: {
|
||||||
field: {
|
field: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@@ -187,9 +187,12 @@ export default {
|
|||||||
redirectUri () {
|
redirectUri () {
|
||||||
return document.location.origin + '/auth/stripe_connect/callback'
|
return document.location.origin + '/auth/stripe_connect/callback'
|
||||||
},
|
},
|
||||||
currencies () {
|
defaultCurrencies () {
|
||||||
return ['USD', 'EUR', 'GBP']
|
return ['USD', 'EUR', 'GBP']
|
||||||
},
|
},
|
||||||
|
currenciesList () {
|
||||||
|
return this.currencies.length ? this.currencies : this.defaultCurrencies
|
||||||
|
},
|
||||||
authenticityToken () {
|
authenticityToken () {
|
||||||
return document.querySelector('meta[name="csrf-token"]')?.content
|
return document.querySelector('meta[name="csrf-token"]')?.content
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -123,9 +123,30 @@ export default {
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify({ blobs }),
|
body: JSON.stringify({ blobs }),
|
||||||
headers: { 'Content-Type': 'application/json' }
|
headers: { 'Content-Type': 'application/json' }
|
||||||
}).then(resp => resp.json()).then((data) => {
|
}).then((resp) => {
|
||||||
this.$emit('success', data)
|
if (resp.ok) {
|
||||||
this.$refs.input.value = ''
|
resp.json().then((data) => {
|
||||||
|
this.$emit('success', data)
|
||||||
|
this.$refs.input.value = ''
|
||||||
|
})
|
||||||
|
} else if (resp.status === 422) {
|
||||||
|
resp.json().then((data) => {
|
||||||
|
if (data.error === 'PDF encrypted') {
|
||||||
|
this.baseFetch(`/api/templates/${this.templateId}/documents`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({ blobs, password: prompt('Enter PDF password') }),
|
||||||
|
headers: { 'Content-Type': 'application/json' }
|
||||||
|
}).then(async (resp) => {
|
||||||
|
if (resp.ok) {
|
||||||
|
this.$emit('success', await resp.json())
|
||||||
|
this.$refs.input.value = ''
|
||||||
|
} else {
|
||||||
|
alert('Wrong password')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
this.isProcessing = false
|
this.isProcessing = false
|
||||||
})
|
})
|
||||||
@@ -133,9 +154,33 @@ export default {
|
|||||||
this.baseFetch(`/api/templates/${this.templateId}/documents`, {
|
this.baseFetch(`/api/templates/${this.templateId}/documents`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: new FormData(this.$refs.form)
|
body: new FormData(this.$refs.form)
|
||||||
}).then(resp => resp.json()).then((data) => {
|
}).then((resp) => {
|
||||||
this.$emit('success', data)
|
if (resp.ok) {
|
||||||
this.$refs.input.value = ''
|
resp.json().then((data) => {
|
||||||
|
this.$emit('success', data)
|
||||||
|
this.$refs.input.value = ''
|
||||||
|
})
|
||||||
|
} else if (resp.status === 422) {
|
||||||
|
resp.json().then((data) => {
|
||||||
|
if (data.error === 'PDF encrypted') {
|
||||||
|
const formData = new FormData(this.$refs.form)
|
||||||
|
|
||||||
|
formData.append('password', prompt('Enter PDF password'))
|
||||||
|
|
||||||
|
this.baseFetch(`/api/templates/${this.templateId}/documents`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
}).then(async (resp) => {
|
||||||
|
if (resp.ok) {
|
||||||
|
this.$emit('success', await resp.json())
|
||||||
|
this.$refs.input.value = ''
|
||||||
|
} else {
|
||||||
|
alert('Wrong password')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
this.isLoading = false
|
this.isLoading = false
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -22,16 +22,21 @@ class ProcessSubmitterCompletionJob < ApplicationJob
|
|||||||
end
|
end
|
||||||
|
|
||||||
def enqueue_completed_emails(submitter)
|
def enqueue_completed_emails(submitter)
|
||||||
user = submitter.submission.created_by_user || submitter.template.author
|
submission = submitter.submission
|
||||||
|
|
||||||
|
user = submission.created_by_user || submitter.template.author
|
||||||
|
|
||||||
if submitter.template.account.users.exists?(id: user.id) &&
|
if submitter.template.account.users.exists?(id: user.id) &&
|
||||||
submitter.submission.preferences['send_email'] != false
|
submission.preferences['send_email'] != false
|
||||||
SubmitterMailer.completed_email(submitter, user).deliver_later!
|
SubmitterMailer.completed_email(submitter, user).deliver_later!
|
||||||
|
|
||||||
bcc = submitter.submission.template.account.account_configs
|
bcc = submission.preferences['bcc_completed'].presence ||
|
||||||
.find_by(key: AccountConfig::BCC_EMAILS)&.value.presence
|
submission.template.account.account_configs
|
||||||
|
.find_by(key: AccountConfig::BCC_EMAILS)&.value.presence
|
||||||
|
|
||||||
SubmitterMailer.completed_email(submitter, user, to: bcc).deliver_later! if bcc
|
bcc.to_s.scan(User::EMAIL_REGEXP).each do |to|
|
||||||
|
SubmitterMailer.completed_email(submitter, user, to:).deliver_later!
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
to = build_to_addresses(submitter)
|
to = build_to_addresses(submitter)
|
||||||
|
|||||||
@@ -4,7 +4,11 @@ class SendSubmitterInvitationEmailJob < ApplicationJob
|
|||||||
def perform(params = {})
|
def perform(params = {})
|
||||||
submitter = Submitter.find(params['submitter_id'])
|
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')
|
SubmissionEvent.create!(submitter:, event_type: 'send_email')
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,9 @@ class SubmitterMailer < ApplicationMailer
|
|||||||
|
|
||||||
@email_config = AccountConfigs.find_for_account(@current_account, AccountConfig::SUBMITTER_COMPLETED_EMAIL_KEY)
|
@email_config = AccountConfigs.find_for_account(@current_account, AccountConfig::SUBMITTER_COMPLETED_EMAIL_KEY)
|
||||||
|
|
||||||
add_completed_email_attachments!(submitter)
|
add_completed_email_attachments!(
|
||||||
|
submitter, with_audit_log: @email_config.nil? || @email_config.value['attach_audit_log'] != false
|
||||||
|
)
|
||||||
|
|
||||||
subject =
|
subject =
|
||||||
if @email_config
|
if @email_config
|
||||||
@@ -65,10 +67,12 @@ class SubmitterMailer < ApplicationMailer
|
|||||||
|
|
||||||
Submissions::EnsureResultGenerated.call(@submitter)
|
Submissions::EnsureResultGenerated.call(@submitter)
|
||||||
|
|
||||||
@documents = add_completed_email_attachments!(submitter)
|
|
||||||
|
|
||||||
@email_config = AccountConfigs.find_for_account(@current_account, AccountConfig::SUBMITTER_DOCUMENTS_COPY_EMAIL_KEY)
|
@email_config = AccountConfigs.find_for_account(@current_account, AccountConfig::SUBMITTER_DOCUMENTS_COPY_EMAIL_KEY)
|
||||||
|
|
||||||
|
@documents = add_completed_email_attachments!(
|
||||||
|
submitter, with_audit_log: @email_config.nil? || @email_config.value['attach_audit_log'] != false
|
||||||
|
)
|
||||||
|
|
||||||
subject =
|
subject =
|
||||||
if @email_config
|
if @email_config
|
||||||
ReplaceEmailVariables.call(@email_config.value['subject'], submitter:)
|
ReplaceEmailVariables.call(@email_config.value['subject'], submitter:)
|
||||||
@@ -83,13 +87,13 @@ class SubmitterMailer < ApplicationMailer
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def add_completed_email_attachments!(submitter)
|
def add_completed_email_attachments!(submitter, with_audit_log: true)
|
||||||
documents = Submitters.select_attachments_for_download(submitter)
|
documents = Submitters.select_attachments_for_download(submitter)
|
||||||
|
|
||||||
total_size = 0
|
total_size = 0
|
||||||
audit_trail_data = nil
|
audit_trail_data = nil
|
||||||
|
|
||||||
if submitter.submission.audit_trail.present?
|
if with_audit_log && submitter.submission.audit_trail.present?
|
||||||
audit_trail_data = submitter.submission.audit_trail.download
|
audit_trail_data = submitter.submission.audit_trail.download
|
||||||
|
|
||||||
total_size = audit_trail_data.size
|
total_size = audit_trail_data.size
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ class AccessToken < ApplicationRecord
|
|||||||
|
|
||||||
belongs_to :user
|
belongs_to :user
|
||||||
|
|
||||||
before_validation :set_sha256, on: :create
|
before_validation :set_sha256
|
||||||
|
|
||||||
attribute :token, :string, default: -> { SecureRandom.base58(TOKEN_LENGTH) }
|
attribute :token, :string, default: -> { SecureRandom.base58(TOKEN_LENGTH) }
|
||||||
|
|
||||||
|
|||||||
@@ -22,12 +22,26 @@ class Account < ApplicationRecord
|
|||||||
class_name: 'TemplateFolder', dependent: :destroy, inverse_of: :account
|
class_name: 'TemplateFolder', dependent: :destroy, inverse_of: :account
|
||||||
has_many :submissions, through: :templates
|
has_many :submissions, through: :templates
|
||||||
has_many :submitters, through: :submissions
|
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,
|
has_many :active_users, -> { active }, dependent: :destroy,
|
||||||
inverse_of: :account, class_name: 'User'
|
inverse_of: :account, class_name: 'User'
|
||||||
|
|
||||||
attribute :timezone, :string, default: 'UTC'
|
attribute :timezone, :string, default: 'UTC'
|
||||||
attribute :locale, :string, default: 'en-US'
|
attribute :locale, :string, default: 'en-US'
|
||||||
|
|
||||||
|
def testing?
|
||||||
|
linked_account_account&.testing?
|
||||||
|
end
|
||||||
|
|
||||||
def default_template_folder
|
def default_template_folder
|
||||||
super || build_default_template_folder(name: TemplateFolder::DEFAULT_NAME,
|
super || build_default_template_folder(name: TemplateFolder::DEFAULT_NAME,
|
||||||
author_id: users.minimum(:id)).tap(&:save!)
|
author_id: users.minimum(:id)).tap(&:save!)
|
||||||
|
|||||||
@@ -27,8 +27,10 @@ class AccountConfig < ApplicationRecord
|
|||||||
BCC_EMAILS = 'bcc_emails'
|
BCC_EMAILS = 'bcc_emails'
|
||||||
FORCE_MFA = 'force_mfa'
|
FORCE_MFA = 'force_mfa'
|
||||||
ALLOW_TYPED_SIGNATURE = 'allow_typed_signature'
|
ALLOW_TYPED_SIGNATURE = 'allow_typed_signature'
|
||||||
|
ALLOW_TO_RESUBMIT = 'allow_to_resubmit'
|
||||||
SUBMITTER_REMAILERS = 'submitter_reminders'
|
SUBMITTER_REMAILERS = 'submitter_reminders'
|
||||||
FORM_COMPLETED_BUTTON_KEY = 'form_completed_button'
|
FORM_COMPLETED_BUTTON_KEY = 'form_completed_button'
|
||||||
|
FORM_WITH_CONFETTI_KEY = 'form_with_confetti'
|
||||||
ESIGNING_PREFERENCE_KEY = 'esigning_preference'
|
ESIGNING_PREFERENCE_KEY = 'esigning_preference'
|
||||||
|
|
||||||
DEFAULT_VALUES = {
|
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
|
# template_submitters :text
|
||||||
# created_at :datetime not null
|
# created_at :datetime not null
|
||||||
# updated_at :datetime not null
|
# updated_at :datetime not null
|
||||||
|
# account_id :bigint not null
|
||||||
# created_by_user_id :bigint
|
# created_by_user_id :bigint
|
||||||
# template_id :bigint not null
|
# template_id :bigint not null
|
||||||
#
|
#
|
||||||
# Indexes
|
# Indexes
|
||||||
#
|
#
|
||||||
|
# index_submissions_on_account_id (account_id)
|
||||||
# index_submissions_on_created_by_user_id (created_by_user_id)
|
# index_submissions_on_created_by_user_id (created_by_user_id)
|
||||||
# index_submissions_on_slug (slug) UNIQUE
|
# index_submissions_on_slug (slug) UNIQUE
|
||||||
# index_submissions_on_template_id (template_id)
|
# index_submissions_on_template_id (template_id)
|
||||||
@@ -31,7 +33,7 @@
|
|||||||
#
|
#
|
||||||
class Submission < ApplicationRecord
|
class Submission < ApplicationRecord
|
||||||
belongs_to :template
|
belongs_to :template
|
||||||
has_one :account, through: :template
|
belongs_to :account
|
||||||
belongs_to :created_by_user, class_name: 'User', optional: true
|
belongs_to :created_by_user, class_name: 'User', optional: true
|
||||||
|
|
||||||
has_many :submitters, dependent: :destroy
|
has_many :submitters, dependent: :destroy
|
||||||
|
|||||||
+25
-18
@@ -4,23 +4,24 @@
|
|||||||
#
|
#
|
||||||
# Table name: submitters
|
# Table name: submitters
|
||||||
#
|
#
|
||||||
# id :bigint not null, primary key
|
# id :bigint not null, primary key
|
||||||
# application_key :string
|
# completed_at :datetime
|
||||||
# completed_at :datetime
|
# email :string
|
||||||
# email :string
|
# ip :string
|
||||||
# ip :string
|
# metadata :text not null
|
||||||
# name :string
|
# name :string
|
||||||
# opened_at :datetime
|
# opened_at :datetime
|
||||||
# phone :string
|
# phone :string
|
||||||
# preferences :text not null
|
# preferences :text not null
|
||||||
# sent_at :datetime
|
# sent_at :datetime
|
||||||
# slug :string not null
|
# slug :string not null
|
||||||
# ua :string
|
# ua :string
|
||||||
# uuid :string not null
|
# uuid :string not null
|
||||||
# values :text not null
|
# values :text not null
|
||||||
# created_at :datetime not null
|
# created_at :datetime not null
|
||||||
# updated_at :datetime not null
|
# updated_at :datetime not null
|
||||||
# submission_id :bigint not null
|
# external_id :string
|
||||||
|
# submission_id :bigint not null
|
||||||
#
|
#
|
||||||
# Indexes
|
# Indexes
|
||||||
#
|
#
|
||||||
@@ -35,14 +36,16 @@
|
|||||||
class Submitter < ApplicationRecord
|
class Submitter < ApplicationRecord
|
||||||
belongs_to :submission
|
belongs_to :submission
|
||||||
has_one :template, through: :submission
|
has_one :template, through: :submission
|
||||||
has_one :account, through: :template
|
has_one :account, through: :submission
|
||||||
|
|
||||||
attribute :values, :string, default: -> { {} }
|
attribute :values, :string, default: -> { {} }
|
||||||
attribute :preferences, :string, default: -> { {} }
|
attribute :preferences, :string, default: -> { {} }
|
||||||
|
attribute :metadata, :string, default: -> { {} }
|
||||||
attribute :slug, :string, default: -> { SecureRandom.base58(14) }
|
attribute :slug, :string, default: -> { SecureRandom.base58(14) }
|
||||||
|
|
||||||
serialize :values, coder: JSON
|
serialize :values, coder: JSON
|
||||||
serialize :preferences, coder: JSON
|
serialize :preferences, coder: JSON
|
||||||
|
serialize :metadata, coder: JSON
|
||||||
|
|
||||||
has_many_attached :documents
|
has_many_attached :documents
|
||||||
has_many_attached :attachments
|
has_many_attached :attachments
|
||||||
@@ -62,6 +65,10 @@ class Submitter < ApplicationRecord
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def application_key
|
||||||
|
external_id
|
||||||
|
end
|
||||||
|
|
||||||
def friendly_name
|
def friendly_name
|
||||||
if name.present? && email.present? && email.exclude?(',')
|
if name.present? && email.present? && email.exclude?(',')
|
||||||
%("#{name.delete('"')}" <#{email}>)
|
%("#{name.delete('"')}" <#{email}>)
|
||||||
|
|||||||
+19
-14
@@ -4,20 +4,20 @@
|
|||||||
#
|
#
|
||||||
# Table name: templates
|
# Table name: templates
|
||||||
#
|
#
|
||||||
# id :bigint not null, primary key
|
# id :bigint not null, primary key
|
||||||
# application_key :string
|
# archived_at :datetime
|
||||||
# archived_at :datetime
|
# fields :text not null
|
||||||
# fields :text not null
|
# name :string not null
|
||||||
# name :string not null
|
# schema :text not null
|
||||||
# schema :text not null
|
# slug :string not null
|
||||||
# slug :string not null
|
# source :text not null
|
||||||
# source :text not null
|
# submitters :text not null
|
||||||
# submitters :text not null
|
# created_at :datetime not null
|
||||||
# created_at :datetime not null
|
# updated_at :datetime not null
|
||||||
# updated_at :datetime not null
|
# account_id :bigint not null
|
||||||
# account_id :bigint not null
|
# author_id :bigint not null
|
||||||
# author_id :bigint not null
|
# external_id :string
|
||||||
# folder_id :bigint not null
|
# folder_id :bigint not null
|
||||||
#
|
#
|
||||||
# Indexes
|
# Indexes
|
||||||
#
|
#
|
||||||
@@ -57,10 +57,15 @@ class Template < ApplicationRecord
|
|||||||
class_name: 'ActiveStorage::Attachment', dependent: :destroy, as: :record, inverse_of: :record
|
class_name: 'ActiveStorage::Attachment', dependent: :destroy, as: :record, inverse_of: :record
|
||||||
|
|
||||||
has_many :submissions, dependent: :destroy
|
has_many :submissions, dependent: :destroy
|
||||||
|
has_many :template_sharings, dependent: :destroy
|
||||||
|
|
||||||
scope :active, -> { where(archived_at: nil) }
|
scope :active, -> { where(archived_at: nil) }
|
||||||
scope :archived, -> { where.not(archived_at: nil) }
|
scope :archived, -> { where.not(archived_at: nil) }
|
||||||
|
|
||||||
|
def application_key
|
||||||
|
external_id
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def maybe_set_default_folder
|
def maybe_set_default_folder
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# == Schema Information
|
||||||
|
#
|
||||||
|
# Table name: template_sharings
|
||||||
|
#
|
||||||
|
# id :bigint not null, primary key
|
||||||
|
# ability :string not null
|
||||||
|
# created_at :datetime not null
|
||||||
|
# updated_at :datetime not null
|
||||||
|
# account_id :bigint not null
|
||||||
|
# template_id :bigint not null
|
||||||
|
#
|
||||||
|
# Indexes
|
||||||
|
#
|
||||||
|
# index_template_sharings_on_account_id_and_template_id (account_id,template_id) UNIQUE
|
||||||
|
# index_template_sharings_on_template_id (template_id)
|
||||||
|
#
|
||||||
|
# Foreign Keys
|
||||||
|
#
|
||||||
|
# fk_rails_... (template_id => templates.id)
|
||||||
|
#
|
||||||
|
class TemplateSharing < ApplicationRecord
|
||||||
|
ALL_ID = -1
|
||||||
|
|
||||||
|
belongs_to :template
|
||||||
|
belongs_to :account, optional: true
|
||||||
|
end
|
||||||
+2
-2
@@ -59,7 +59,7 @@ class User < ApplicationRecord
|
|||||||
has_many :encrypted_configs, dependent: :destroy, class_name: 'EncryptedUserConfig'
|
has_many :encrypted_configs, dependent: :destroy, class_name: 'EncryptedUserConfig'
|
||||||
has_many :email_messages, dependent: :destroy, foreign_key: :author_id, inverse_of: :author
|
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 :role, :string, default: ADMIN_ROLE
|
||||||
attribute :uuid, :string, default: -> { SecureRandom.uuid }
|
attribute :uuid, :string, default: -> { SecureRandom.uuid }
|
||||||
@@ -72,7 +72,7 @@ class User < ApplicationRecord
|
|||||||
end
|
end
|
||||||
|
|
||||||
def active_for_authentication?
|
def active_for_authentication?
|
||||||
!archived_at?
|
super && !archived_at?
|
||||||
end
|
end
|
||||||
|
|
||||||
def remember_me
|
def remember_me
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% encrypted_config = @encrypted_config || EncryptedConfig.find_or_initialize_by(account: current_account, key: EncryptedConfig::APP_URL_KEY) %>
|
<% 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| %>
|
<%= f.fields_for encrypted_config do |ff| %>
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<%= ff.label :value, 'App URL', class: 'label' %>
|
<%= ff.label :value, 'App URL', class: 'label' %>
|
||||||
@@ -63,6 +63,24 @@
|
|||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
<% account_config = AccountConfig.find_or_initialize_by(account: current_account, key: AccountConfig::ALLOW_TO_RESUBMIT) %>
|
||||||
|
<% if can?(:manage, account_config) %>
|
||||||
|
<%= form_for account_config, url: account_configs_path, method: :post do |f| %>
|
||||||
|
<%= f.hidden_field :key %>
|
||||||
|
<div class="flex items-center justify-between py-2.5">
|
||||||
|
<span>
|
||||||
|
Allow to resubmit completed forms
|
||||||
|
</span>
|
||||||
|
<%= f.check_box :value, class: 'toggle', checked: account_config.value != false, onchange: 'this.form.requestSubmit()' %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<% 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>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,14 +1,19 @@
|
|||||||
<div class="flex flex-wrap space-y-4 md:flex-nowrap md:space-y-0 md:space-x-10">
|
<div class="flex flex-wrap space-y-4 md:flex-nowrap md:space-y-0 md:space-x-10">
|
||||||
<%= render 'shared/settings_nav' %>
|
<%= render 'shared/settings_nav' %>
|
||||||
<div class="flex-grow">
|
<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 bg-base-200">
|
||||||
<div class="card-body p-6">
|
<div class="card-body p-6">
|
||||||
<label for="api_key" class="text-sm font-semibold">X-Auth-Token</label>
|
<label for="api_key" class="text-sm font-semibold">X-Auth-Token</label>
|
||||||
<div class="flex flex-row space-x-4">
|
<div class="flex flex-col md:flex-row gap-4">
|
||||||
<input id="api_key" type="text" value="<%= current_user.access_token.token %>" class="input font-mono input-bordered w-full" autocomplete="off" readonly>
|
<div class="flex w-full space-x-4">
|
||||||
<%= 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' %>
|
<input id="api_key" type="text" value="<%= current_user.access_token.token %>" class="input font-mono input-bordered w-full" autocomplete="off" readonly>
|
||||||
<%= 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?' } %>
|
<%= 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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -30,20 +35,16 @@
|
|||||||
--header 'X-Auth-Token: <%= current_user.access_token.token %>' \
|
--header 'X-Auth-Token: <%= current_user.access_token.token %>' \
|
||||||
--data-raw '{
|
--data-raw '{
|
||||||
"template_id": <%= current_account.templates.last&.id || 1 %>,
|
"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' %>",
|
||||||
"name": "John Doe",
|
"email": "<%= current_user.email.sub('@', '+test@') %>",
|
||||||
"role": "<%= current_account.templates.last ? current_account.templates.last.submitters.first['name'] : 'First Party' %>",
|
"values": {
|
||||||
"email": "<%= current_user.email.sub('@', '+test@') %>",
|
"Form Text Field Name": "Default Value"
|
||||||
"values": {
|
}
|
||||||
"Form Text Field Name": "Default Value"
|
},
|
||||||
}
|
{ "role": "Second Submitter", "email": "<%= current_user.email.sub('@', '+test2@') %>" }
|
||||||
},
|
|
||||||
{ "role": "Second Submitter", "email": "<%= current_user.email.sub('@', '+test2@') %>" }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
}'<% end.to_str %>
|
}'<% end.to_str %>
|
||||||
<span class="top-0 right-0 absolute">
|
<span class="top-0 right-0 absolute">
|
||||||
|
|||||||
@@ -1,100 +1,80 @@
|
|||||||
<% if Docuseal.demo? %><%= render 'shared/demo_alert' %><% end %>
|
<% 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">
|
||||||
<div class="flex justify-between mb-4 items-center">
|
<h1 class="text-4xl font-bold"><span class="hidden md:inline">Document</span> Templates</h1>
|
||||||
<h1 class="text-4xl font-bold"><span class="hidden md:inline">Document</span> Templates</h1>
|
<div class="flex space-x-2">
|
||||||
<div class="flex space-x-2">
|
<% if params[:q].present? || @pagy.pages > 1 || @template_folders.present? %>
|
||||||
<% if params[:q].present? || @pagy.pages > 1 || @template_folders.present? %>
|
<%= render 'shared/search_input' %>
|
||||||
<%= 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 %>
|
<% end %>
|
||||||
<% if can?(:create, ::Template) %>
|
<% end %>
|
||||||
<%= 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>
|
|
||||||
</div>
|
</div>
|
||||||
<% view_archived_html = capture do %>
|
</div>
|
||||||
<% if current_account.templates.where.not(archived_at: nil).exists? %>
|
<% view_archived_html = capture do %>
|
||||||
<div>
|
<% if current_account.templates.where.not(archived_at: nil).exists? %>
|
||||||
<a href="<%= templates_archived_index_path %>" class="link text-sm">View Archived</a>
|
<div>
|
||||||
</div>
|
<a href="<%= templates_archived_index_path %>" class="link text-sm">View Archived</a>
|
||||||
<% 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>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if @templates.present? %>
|
<% end %>
|
||||||
<div class="grid gap-4 md:grid-cols-3">
|
<% if @template_folders.present? %>
|
||||||
<%= render partial: 'templates/template', collection: @templates %>
|
<div class="grid gap-4 md:grid-cols-3 <%= 'mb-6' if @templates.present? %>">
|
||||||
</div>
|
<%= render partial: 'template_folders/folder', collection: @template_folders, as: :folder %>
|
||||||
<% end %>
|
</div>
|
||||||
<% 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)) %>
|
<% end %>
|
||||||
<%= form_for '', url: templates_upload_path, method: :post, class: 'mt-8 block', html: { enctype: 'multipart/form-data' } do %>
|
<% if @templates.present? %>
|
||||||
<button type="submit" class="hidden"></button>
|
<div class="grid gap-4 md:grid-cols-3">
|
||||||
<file-dropzone data-submit-on-upload="true" class="w-full">
|
<%= render partial: 'templates/template', collection: @templates %>
|
||||||
<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>
|
||||||
<div class="absolute top-0 right-0 left-0 bottom-0 flex items-center justify-center">
|
<% end %>
|
||||||
<div class="flex flex-col items-center">
|
<% 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)) %>
|
||||||
<span data-target="file-dropzone.icon" class="flex flex-col items-center">
|
<%= form_for '', url: templates_upload_path, id: form_id = SecureRandom.uuid, method: :post, class: 'mt-8 block', html: { enctype: 'multipart/form-data' } do %>
|
||||||
<span>
|
<input type="hidden" name="form_id" value="<%= form_id %>">
|
||||||
<%= svg_icon('cloud_upload', class: 'w-10 h-10') %>
|
<button type="submit" class="hidden"></button>
|
||||||
</span>
|
<file-dropzone data-submit-on-upload="true" class="w-full">
|
||||||
<div class="font-medium mb-1">
|
<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">
|
||||||
Upload New Document
|
<div class="absolute top-0 right-0 left-0 bottom-0 flex items-center justify-center">
|
||||||
</div>
|
<div class="flex flex-col items-center">
|
||||||
<div class="text-xs">
|
<span data-target="file-dropzone.icon" class="flex flex-col items-center">
|
||||||
<span class="font-medium">Click to upload</span> or drag and drop
|
<span>
|
||||||
</div>
|
<%= svg_icon('cloud_upload', class: 'w-10 h-10') %>
|
||||||
</span>
|
</span>
|
||||||
<span data-target="file-dropzone.loading" class="flex flex-col items-center hidden">
|
<div class="font-medium mb-1">
|
||||||
<%= svg_icon('loader', class: 'w-10 h-10 animate-spin') %>
|
Upload New Document
|
||||||
<div class="font-medium mb-1">
|
</div>
|
||||||
Uploading...
|
<div class="text-xs">
|
||||||
</div>
|
<span class="font-medium">Click to upload</span> or drag and drop
|
||||||
</span>
|
</div>
|
||||||
</div>
|
</span>
|
||||||
<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>
|
<span data-target="file-dropzone.loading" class="flex flex-col items-center hidden">
|
||||||
</div>
|
<%= svg_icon('loader', class: 'w-10 h-10 animate-spin') %>
|
||||||
</label>
|
<div class="font-medium mb-1">
|
||||||
</file-dropzone>
|
Uploading...
|
||||||
<% end %>
|
</div>
|
||||||
<% end %>
|
</span>
|
||||||
<% 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>
|
</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>
|
||||||
</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>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
@@ -41,13 +41,6 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<%= f.button button_title(title: 'Sign up', disabled_with: 'Signing up'), class: 'base-button' %>
|
<%= f.button button_title(title: 'Sign up', disabled_with: 'Signing up'), class: 'base-button' %>
|
||||||
<% if Docuseal.multitenant? %>
|
|
||||||
<button id="talk_to_sales_button" class="hidden" type="submit" formaction="<%= enquiries_path %>" formnovalidate="true" formmethod="post"></button>
|
|
||||||
<label class="flex items-center cursor-pointer">
|
|
||||||
<input type="checkbox" name="talk_to_sales" class="base-checkbox" onchange="window.talk_to_sales_button.click()">
|
|
||||||
<span class="label">I would like to talk to sales</span>
|
|
||||||
</label>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= render 'devise/shared/links' %>
|
<%= render 'devise/shared/links' %>
|
||||||
|
|||||||
@@ -1,6 +1,14 @@
|
|||||||
<div class="max-w-lg mx-auto px-2">
|
<div class="max-w-lg mx-auto px-2">
|
||||||
<%= render 'devise/shared/select_server' if Docuseal.multitenant? %>
|
<% if params[:redir].to_s.ends_with?('on_premise') %>
|
||||||
<h1 class="text-3xl font-bold text-center mt-8 mb-6">Create Free Account</h1>
|
<h1 class="text-3xl font-bold text-center mt-8 mb-6">DocuSeal Console</h1>
|
||||||
|
<div class="alert my-4 text-sm">
|
||||||
|
Sign up in DocuSeal Console to upgrade.
|
||||||
|
On-premises app is completely standalone, Console is used only to manage your license.
|
||||||
|
</div>
|
||||||
|
<% else %>
|
||||||
|
<%= render 'devise/shared/select_server' if Docuseal.multitenant? %>
|
||||||
|
<h1 class="text-3xl font-bold text-center mt-8 mb-6">Create Free Account</h1>
|
||||||
|
<% end %>
|
||||||
<%= form_for(User.new, html: { class: 'space-y-6' }, url: new_registration_path, data: { turbo: params[:redir].blank? }, method: :get) do |f| %>
|
<%= form_for(User.new, html: { class: 'space-y-6' }, url: new_registration_path, data: { turbo: params[:redir].blank? }, method: :get) do |f| %>
|
||||||
<% if params[:redir].present? %>
|
<% if params[:redir].present? %>
|
||||||
<%= hidden_field_tag :redir, params[:redir] %>
|
<%= hidden_field_tag :redir, params[:redir] %>
|
||||||
@@ -17,15 +25,19 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
<% if devise_mapping.omniauthable? %>
|
<% if devise_mapping.omniauthable? %>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<%= form_for '', url: omniauth_authorize_path(resource_name, :google_oauth2), data: { turbo: false }, method: :post do |f| %>
|
<% if User.omniauth_providers.include?(:google_oauth2) %>
|
||||||
<set-timezone data-input-id="state" data-params="true"></set-timezone>
|
<%= form_for '', url: omniauth_authorize_path(resource_name, :google_oauth2), data: { turbo: false }, method: :post do |f| %>
|
||||||
<%= hidden_field_tag :state, { redir: params[:redir].to_s }.compact_blank.to_query %>
|
<set-timezone data-input-id="state" data-params="true"></set-timezone>
|
||||||
<%= 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' %>
|
<%= 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 %>
|
<% end %>
|
||||||
<%= form_for '', url: omniauth_authorize_path(resource_name, :microsoft_office365), data: { turbo: false }, method: :post do |f| %>
|
<% if User.omniauth_providers.include?(:microsoft_office365) %>
|
||||||
<set-timezone data-input-id="state_microsoft" data-params="true"></set-timezone>
|
<%= form_for '', url: omniauth_authorize_path(resource_name, :microsoft_office365), data: { turbo: false }, method: :post do |f| %>
|
||||||
<%= hidden_field_tag :state, { redir: params[:redir].to_s }.compact_blank.to_query, id: 'state_microsoft' %>
|
<set-timezone data-input-id="state_microsoft" data-params="true"></set-timezone>
|
||||||
<%= f.button button_title(title: 'Sign up with Microsoft', icon: svg_icon('brand_microsoft', class: 'w-6 h-6')), class: 'white-button w-full' %>
|
<%= 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 %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -21,15 +21,19 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
<% if devise_mapping.omniauthable? %>
|
<% if devise_mapping.omniauthable? %>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<%= form_for '', url: omniauth_authorize_path(resource_name, :google_oauth2), data: { turbo: false }, method: :post do |f| %>
|
<% if User.omniauth_providers.include?(:google_oauth2) %>
|
||||||
<set-timezone data-input-id="state" data-params="true"></set-timezone>
|
<%= form_for '', url: omniauth_authorize_path(resource_name, :google_oauth2), data: { turbo: false }, method: :post do |f| %>
|
||||||
<%= hidden_field_tag :state, { redir: params[:redir].to_s }.compact_blank.to_query %>
|
<set-timezone data-input-id="state" data-params="true"></set-timezone>
|
||||||
<%= 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' %>
|
<%= 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 %>
|
<% end %>
|
||||||
<%= form_for '', url: omniauth_authorize_path(resource_name, :microsoft_office365), data: { turbo: false }, method: :post do |f| %>
|
<% if User.omniauth_providers.include?(:microsoft_office365) %>
|
||||||
<set-timezone data-input-id="state_microsoft" data-params="true"></set-timezone>
|
<%= form_for '', url: omniauth_authorize_path(resource_name, :microsoft_office365), data: { turbo: false }, method: :post do |f| %>
|
||||||
<%= hidden_field_tag :state, { redir: params[:redir].to_s }.compact_blank.to_query, id: 'state_microsoft' %>
|
<set-timezone data-input-id="state_microsoft" data-params="true"></set-timezone>
|
||||||
<%= f.button button_title(title: 'Sign in with Microsoft', icon: svg_icon('brand_microsoft', class: 'w-6 h-6')), class: 'white-button w-full' %>
|
<%= 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 %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<div class="form-control">
|
<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' %>
|
<%= f.text_field :otp_attempt, autofocus: true, placeholder: 'XXX-XXX', required: true, class: 'base-input' %>
|
||||||
</div>
|
</div>
|
||||||
</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 |
@@ -6,7 +6,7 @@
|
|||||||
<style>
|
<style>
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body dir="auto">
|
<body dir="<%= yield&.match?(TextUtils::RTL_REGEXP) ? 'rtl' : 'auto' %>">
|
||||||
<%= yield %>
|
<%= yield %>
|
||||||
<%= render partial: 'shared/mailer_attribution' %>
|
<%= render partial: 'shared/mailer_attribution' %>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
Use an authenticator mobile app like Google Authenticator or 1Password to scan the QR code below.
|
Use an authenticator mobile app like Google Authenticator or 1Password to scan the QR code below.
|
||||||
</p>
|
</p>
|
||||||
<div>
|
<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>
|
||||||
<div class="form-control my-6 space-y-2">
|
<div class="form-control my-6 space-y-2">
|
||||||
<%= f.text_field :otp_attempt, required: true, placeholder: 'XXX-XXX', class: 'base-input text-center' %>
|
<%= f.text_field :otp_attempt, required: true, placeholder: 'XXX-XXX', class: 'base-input text-center' %>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<%= f.hidden_field :key %>
|
<%= f.hidden_field :key %>
|
||||||
<div class="form-control">
|
<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')) %>
|
<% 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">
|
<div class="w-full">
|
||||||
<%= f.fields_for :value, record do |ff| %>
|
<%= f.fields_for :value, record do |ff| %>
|
||||||
<%= ff.label :first_duration, 'First reminder in', class: 'label' %>
|
<%= ff.label :first_duration, 'First reminder in', class: 'label' %>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<p>
|
<p>
|
||||||
Send automatic email reminders to your recipients.
|
Send automatic email reminders to your recipients.
|
||||||
<br>
|
<br>
|
||||||
<a class="link font-medium" target="_blank" href="<%= "#{Docuseal::CONSOLE_URL}/#{Docuseal.multitenant? ? 'plans' : 'on_premise'}" %>">Learn More</a>
|
<a class="link font-medium" target="_blank" href="<%= Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premise" }.to_query}" %>">Learn More</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
<div class="collapse-content">
|
<div class="collapse-content">
|
||||||
<%= form_for AccountConfigs.find_or_initialize_for_key(current_account, AccountConfig::SUBMITTER_DOCUMENTS_COPY_EMAIL_KEY), url: settings_personalization_path, method: :post, html: { autocomplete: 'off', class: 'space-y-4' } do |f| %>
|
<%= form_for AccountConfigs.find_or_initialize_for_key(current_account, AccountConfig::SUBMITTER_DOCUMENTS_COPY_EMAIL_KEY), url: settings_personalization_path, method: :post, html: { autocomplete: 'off', class: 'space-y-4' } do |f| %>
|
||||||
<%= f.hidden_field :key %>
|
<%= f.hidden_field :key %>
|
||||||
<%= f.fields_for :value, Struct.new(:subject, :body).new(*f.object.value.values_at('subject', 'body')) do |ff| %>
|
<%= f.fields_for :value, Struct.new(:subject, :body, :attach_audit_log).new(*f.object.value.values_at('subject', 'body', 'attach_audit_log')) do |ff| %>
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<%= ff.label :subject, class: 'label' %>
|
<%= ff.label :subject, class: 'label' %>
|
||||||
<%= ff.text_field :subject, required: true, class: 'base-input', dir: 'auto' %>
|
<%= ff.text_field :subject, required: true, class: 'base-input', dir: 'auto' %>
|
||||||
@@ -24,6 +24,12 @@
|
|||||||
<%= ff.text_area :body, required: true, class: 'base-input w-full py-2', dir: 'auto' %>
|
<%= ff.text_area :body, required: true, class: 'base-input w-full py-2', dir: 'auto' %>
|
||||||
</autoresize-textarea>
|
</autoresize-textarea>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex items-center justify-between py-2.5 mx-1">
|
||||||
|
<span>
|
||||||
|
Attach audit log PDF
|
||||||
|
</span>
|
||||||
|
<%= ff.check_box :attach_audit_log, { checked: ff.object.attach_audit_log != false, class: 'toggle' }, 'true', 'false' %>
|
||||||
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<div class="form-control pt-2">
|
<div class="form-control pt-2">
|
||||||
<%= f.button button_title(title: 'Save', disabled_with: 'Saving'), class: 'base-button' %>
|
<%= f.button button_title(title: 'Save', disabled_with: 'Saving'), class: 'base-button' %>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<p>
|
<p>
|
||||||
Display your company name and logo when signing documents.
|
Display your company name and logo when signing documents.
|
||||||
<br>
|
<br>
|
||||||
<a class="link font-medium" target="_blank" href="<%= "#{Docuseal::CONSOLE_URL}/#{Docuseal.multitenant? ? 'plans' : 'on_premise'}" %>">Learn More</a>
|
<a class="link font-medium" target="_blank" href="<%= Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premise" }.to_query}" %>">Learn More</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
<div class="collapse-content">
|
<div class="collapse-content">
|
||||||
<%= form_for AccountConfigs.find_or_initialize_for_key(current_account, AccountConfig::SUBMITTER_COMPLETED_EMAIL_KEY), url: settings_personalization_path, method: :post, html: { autocomplete: 'off', class: 'space-y-4' } do |f| %>
|
<%= form_for AccountConfigs.find_or_initialize_for_key(current_account, AccountConfig::SUBMITTER_COMPLETED_EMAIL_KEY), url: settings_personalization_path, method: :post, html: { autocomplete: 'off', class: 'space-y-4' } do |f| %>
|
||||||
<%= f.hidden_field :key %>
|
<%= f.hidden_field :key %>
|
||||||
<%= f.fields_for :value, Struct.new(:subject, :body).new(*f.object.value.values_at('subject', 'body')) do |ff| %>
|
<%= f.fields_for :value, Struct.new(:subject, :body, :attach_audit_log).new(*f.object.value.values_at('subject', 'body', 'attach_audit_log')) do |ff| %>
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<%= ff.label :subject, class: 'label' %>
|
<%= ff.label :subject, class: 'label' %>
|
||||||
@@ -29,6 +29,12 @@
|
|||||||
<%= ff.text_area :body, required: true, class: 'base-input w-full py-2', dir: 'auto' %>
|
<%= ff.text_area :body, required: true, class: 'base-input w-full py-2', dir: 'auto' %>
|
||||||
</autoresize-textarea>
|
</autoresize-textarea>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex items-center justify-between py-2.5 mx-1">
|
||||||
|
<span>
|
||||||
|
Attach audit log PDF
|
||||||
|
</span>
|
||||||
|
<%= ff.check_box :attach_audit_log, { checked: ff.object.attach_audit_log != false, class: 'toggle' }, 'true', 'false' %>
|
||||||
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<div class="form-control pt-2">
|
<div class="form-control pt-2">
|
||||||
<%= f.button button_title(title: 'Save', disabled_with: 'Saving'), class: 'base-button' %>
|
<%= f.button button_title(title: 'Save', disabled_with: 'Saving'), class: 'base-button' %>
|
||||||
|
|||||||
@@ -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
|
Upgrade
|
||||||
<% end %>
|
<% 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>
|
<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' %>
|
<%= link_to 'Account', settings_account_path, class: 'text-base hover:bg-base-300' %>
|
||||||
</li>
|
</li>
|
||||||
<% unless Docuseal.multitenant? %>
|
<% 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>
|
<li>
|
||||||
<%= link_to 'Email', settings_email_index_path, class: 'text-base hover:bg-base-300' %>
|
<%= link_to 'Email', settings_email_index_path, class: 'text-base hover:bg-base-300' %>
|
||||||
</li>
|
</li>
|
||||||
<% end %>
|
<% 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>
|
<li>
|
||||||
<%= link_to 'Storage', settings_storage_index_path, class: 'text-base hover:bg-base-300' %>
|
<%= link_to 'Storage', settings_storage_index_path, class: 'text-base hover:bg-base-300' %>
|
||||||
</li>
|
</li>
|
||||||
<% end %>
|
<% 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>
|
<li>
|
||||||
<%= link_to 'SMS', settings_sms_path, class: 'text-base hover:bg-base-300' %>
|
<%= link_to 'SMS', settings_sms_path, class: 'text-base hover:bg-base-300' %>
|
||||||
</li>
|
</li>
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
</li>
|
</li>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% unless Docuseal.multitenant? %>
|
<% 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>
|
<li>
|
||||||
<%= link_to 'SSO', settings_sso_index_path, class: 'text-base hover:bg-base-300' %>
|
<%= link_to 'SSO', settings_sso_index_path, class: 'text-base hover:bg-base-300' %>
|
||||||
</li>
|
</li>
|
||||||
@@ -69,7 +69,7 @@
|
|||||||
<%= render 'shared/settings_nav_extra' %>
|
<%= render 'shared/settings_nav_extra' %>
|
||||||
<% if !Docuseal.demo? && can?(:manage, EncryptedConfig) %>
|
<% if !Docuseal.demo? && can?(:manage, EncryptedConfig) %>
|
||||||
<li>
|
<li>
|
||||||
<%= link_to Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CONSOLE_URL}/on_premise", class: 'text-base hover:bg-base-300', data: { prefetch: false } do %>
|
<%= link_to Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premise" }.to_query}", class: 'text-base hover:bg-base-300', data: { prefetch: false } do %>
|
||||||
Plans
|
Plans
|
||||||
<span class="badge badge-warning">New</span>
|
<span class="badge badge-warning">New</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
@@ -88,10 +88,22 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
</li>
|
</li>
|
||||||
<% end %>
|
<% 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 %>
|
<% end %>
|
||||||
</ul>
|
</ul>
|
||||||
</menu-active>
|
</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="mx-4 border-t border-base-300 hidden md:block">
|
||||||
<div class="text-sm mt-3">
|
<div class="text-sm mt-3">
|
||||||
Need help? Ask a question:
|
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">
|
<p class="text-gray-700">
|
||||||
Unlock with DocuSeal Pro
|
Unlock with DocuSeal Pro
|
||||||
<br>
|
<br>
|
||||||
<a class="link font-medium" target="_blank" href="<%= "#{Docuseal::CONSOLE_URL}/#{Docuseal.multitenant? ? 'plans' : 'on_premise'}" %>">Learn More</a>
|
<a class="link font-medium" target="_blank" href="<%= Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premise" }.to_query}" %>">Learn More</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
<p class="text-gray-700">
|
<p class="text-gray-700">
|
||||||
Unlock with DocuSeal Pro
|
Unlock with DocuSeal Pro
|
||||||
<br>
|
<br>
|
||||||
<a class="link font-medium" target="_blank" href="<%= "#{Docuseal::CONSOLE_URL}/#{Docuseal.multitenant? ? 'plans' : 'on_premise'}" %>">Learn More</a>
|
<a class="link font-medium" target="_blank" href="<%= Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premise" }.to_query}" %>">Learn More</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -10,23 +10,23 @@
|
|||||||
<%= svg_icon('writing_sign', class: 'w-10 h-10') %>
|
<%= svg_icon('writing_sign', class: 'w-10 h-10') %>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p class="text-lg font-bold mb-1"><%= @submitter.submission.template.name %></p>
|
<p dir="auto" class="text-lg font-bold mb-1"><%= @submitter.submission.template.name %></p>
|
||||||
<p class="text-sm">Signed on <%= l(@submitter.completed_at.to_date, format: :long, locale: @submitter.submission.template.account.locale) %></p>
|
<p dir="auto" class="text-sm"><%= t('signed_on_time', time: l(@submitter.completed_at.to_date, format: :long)) %></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-center text-2xl font-semibold">
|
<div dir="auto" class="text-center text-2xl font-semibold">
|
||||||
Form has been submitted already
|
<%= t('form_has_been_submitted_already') %>
|
||||||
</div>
|
</div>
|
||||||
<% if Accounts.can_send_emails?(@submitter.submission.template.account) %>
|
<% if Accounts.can_send_emails?(@submitter.submission.template.account) %>
|
||||||
<div>
|
<div>
|
||||||
<%= button_to button_title(title: 'Send copy to Email', disabled_with: 'Sending', icon: svg_icon('mail_forward', class: 'w-6 h-6')), send_submission_email_index_path, params: { submitter_slug: @submitter.slug }, form: { onsubmit: 'event.submitter.disabled = true' }, class: 'base-button w-full' %>
|
<%= button_to button_title(title: t('send_copy_to_email'), disabled_with: t('sending'), icon: svg_icon('mail_forward', class: 'w-6 h-6')), send_submission_email_index_path, params: { submitter_slug: @submitter.slug }, form: { onsubmit: 'event.submitter.disabled = true' }, class: 'base-button w-full' %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if @template.submitters.to_a.size == 1 %>
|
<% if @template.submitters.to_a.size == 1 && %w[api embed].exclude?(@submitter.submission.source) && @submitter.account.account_configs.find_or_initialize_by(key: AccountConfig::ALLOW_TO_RESUBMIT).value != false %>
|
||||||
<div>
|
<div>
|
||||||
<%= button_to button_title(title: 'Resubmit', disabled_with: 'Resubmit', icon: svg_icon('reload', class: 'w-6 h-6')), start_form_path(@template.slug), params: { submitter: { email: params[:email] }, resubmit: @submitter.slug }, method: :put, form: { onsubmit: 'event.submitter.disabled = true' }, class: 'white-button w-full' %>
|
<%= button_to button_title(title: t('resubmit'), disabled_with: t('resubmit'), icon: svg_icon('reload', class: 'w-6 h-6')), start_form_path(@template.slug), params: { submitter: { email: params[:email] }, resubmit: @submitter.slug }, method: :put, form: { onsubmit: 'event.submitter.disabled = true' }, class: 'white-button w-full' %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<div class="text-center w-full space-y-6">
|
<div class="text-center w-full space-y-6">
|
||||||
<%= render 'banner' %>
|
<%= render 'banner' %>
|
||||||
<% unless @template.archived_at? %>
|
<% unless @template.archived_at? %>
|
||||||
<p class="text-xl font-semibold text-center">You have been invited to submit a form</p>
|
<p class="text-xl font-semibold text-center"><%= t('you_have_been_invited_to_submit_a_form') %></p>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center bg-base-200 rounded-xl p-4 mb-4">
|
<div class="flex items-center bg-base-200 rounded-xl p-4 mb-4">
|
||||||
@@ -15,9 +15,9 @@
|
|||||||
<div>
|
<div>
|
||||||
<p class="text-lg font-bold mb-1"><%= @template.name %></p>
|
<p class="text-lg font-bold mb-1"><%= @template.name %></p>
|
||||||
<% if @template.archived_at? %>
|
<% if @template.archived_at? %>
|
||||||
<p class="text-sm">Form has been deleted by <span class="font-semibold"><%= @template.account.name %></span>.</p>
|
<p dir="auto" class="text-sm"><%= t('form_has_been_deleted_by_html', name: @template.account.name) %></p>
|
||||||
<% else %>
|
<% else %>
|
||||||
<p class="text-sm">Invited by <span class="font-semibold"><%= @template.account.name %></span></p>
|
<p dir="auto" class="text-sm"><%= t('invited_by_html', name: @template.account.name) %></p>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -25,13 +25,13 @@
|
|||||||
</div>
|
</div>
|
||||||
<% unless @template.archived_at? %>
|
<% unless @template.archived_at? %>
|
||||||
<%= form_for @submitter, url: start_form_path(@template.slug), data: { turbo_frame: :_top }, method: :put, html: { class: 'space-y-4', onsubmit: 'event.submitter.disabled = true' } do |f| %>
|
<%= form_for @submitter, url: start_form_path(@template.slug), data: { turbo_frame: :_top }, method: :put, html: { class: 'space-y-4', onsubmit: 'event.submitter.disabled = true' } do |f| %>
|
||||||
<div class="form-control !mt-0">
|
<div dir="auto" class="form-control !mt-0">
|
||||||
<%= f.label :email, class: 'label' %>
|
<%= f.label :email, t('email'), class: 'label' %>
|
||||||
<%= f.email_field :email, value: current_user&.email || params[:email] || @submitter.email, required: true, class: 'base-input', placeholder: 'Provide your email to start' %>
|
<%= f.email_field :email, value: current_user&.email || params[:email] || @submitter.email, required: true, class: 'base-input', placeholder: t('provide_your_email_to_start') %>
|
||||||
<%= @error_message %>
|
<%= @error_message %>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-control">
|
<div dir="auto" class="form-control">
|
||||||
<%= f.button button_title(title: 'Start', disabled_with: 'Starting'), class: 'base-button' %>
|
<%= f.button button_title(title: t('start'), disabled_with: t('starting')), class: 'base-button' %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
<%= fff.label :endpoint, 'Endpoint (optional)', class: 'label' %>
|
<%= fff.label :endpoint, 'Endpoint (optional)', class: 'label' %>
|
||||||
<%= fff.text_field :endpoint, value: configs['endpoint'], type: :url, class: 'base-input' %>
|
<%= fff.text_field :endpoint, value: configs['endpoint'], type: :url, class: 'base-input' %>
|
||||||
<label class="label">
|
<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>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<div>
|
||||||
|
<div class="">
|
||||||
|
<input id="api_key" type="text" value="<%= start_form_url(slug: template.slug) %>" class="input font-mono input-bordered w-full bg-white" autocomplete="off" readonly>
|
||||||
|
<div class="mt-4">
|
||||||
|
<%= render 'shared/clipboard_copy', icon: 'copy', text: start_form_url(slug: template.slug), class: 'base-button w-full block', icon_class: 'w-6 h-6 text-white', copy_title: 'Copy', copied_title: 'Copied' %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="divider uppercase normal-case mt-5 mb-6 text-neutral-700">Or embed on your website</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>
|
||||||
|
</div>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<div class="mt-2 mb-1">
|
<div class="mt-2 mb-1">
|
||||||
<div class="tooltip w-full" data-tip="Unlock with Enterpise">
|
<div class="tooltip w-full" data-tip="Unlock with Enterpise">
|
||||||
<%= link_to submitter.sent_at? ? 'Re-send SMS' : 'Send SMS', "#{Docuseal::CONSOLE_URL}/#{Docuseal.multitenant? ? 'plans' : 'on_premise'}", class: 'btn btn-sm btn-primary text-gray-400 w-full' %>
|
<%= link_to submitter.sent_at? ? 'Re-send SMS' : 'Send SMS', Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premise" }.to_query}", class: 'btn btn-sm btn-primary text-gray-400 w-full' %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
<%= render 'shared/turbo_modal', title: params[:selfsign] ? 'Add Recipients' : 'Add New Recipients' do %>
|
<%= render 'shared/turbo_modal', title: params[:selfsign] ? 'Add Recipients' : 'Add New Recipients' do %>
|
||||||
<% options = [['via Email', 'email'], ['via Phone', 'phone'], ['Detailed', 'detailed']] %>
|
<% options = [['via Email', 'email'], ['via Phone', 'phone'], %w[Detailed detailed], (Docuseal.multitenant? && params[:with_link] && @template.submitters.to_a.size < 2 ? ['via Link', 'link'] : nil)].compact %>
|
||||||
<toggle-visible data-element-ids="<%= options.map(&:last).to_json %>" class="relative text-center mt-4 block">
|
<toggle-visible data-element-ids="<%= options.map(&:last).to_json %>" class="relative text-center mt-4 block">
|
||||||
<div class="join">
|
<div class="join">
|
||||||
<% options.each_with_index do |(label, value), index| %>
|
<% options.each_with_index do |(label, value), index| %>
|
||||||
<span>
|
<span>
|
||||||
<%= radio_button_tag 'option', value, value == 'email', class: 'peer hidden', data: { action: 'change:toggle-visible#trigger' } %>
|
<%= 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 %>
|
||||||
</label>
|
</label>
|
||||||
</span>
|
</span>
|
||||||
@@ -22,6 +22,11 @@
|
|||||||
<div id="detailed" class="hidden">
|
<div id="detailed" class="hidden">
|
||||||
<%= render 'detailed_form', template: @template %>
|
<%= render 'detailed_form', template: @template %>
|
||||||
</div>
|
</div>
|
||||||
|
<% if Docuseal.multitenant? && params[:with_link] && @template.submitters.to_a.size < 2 %>
|
||||||
|
<div id="link" class="hidden">
|
||||||
|
<%= render 'link_form', template: @template %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<%= content_for(:modal_extra) %>
|
<%= content_for(:modal_extra) %>
|
||||||
<% end %>
|
<% end %>
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
</a>
|
</a>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</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">
|
<div class="pr-3.5 pl-0.5">
|
||||||
<% fields_index = Templates.build_field_areas_index(@submission.template_fields || @submission.template.fields) %>
|
<% fields_index = Templates.build_field_areas_index(@submission.template_fields || @submission.template.fields) %>
|
||||||
<% values = @submission.submitters.reduce({}) { |acc, sub| acc.merge(sub.values) } %>
|
<% values = @submission.submitters.reduce({}) { |acc, sub| acc.merge(sub.values) } %>
|
||||||
@@ -71,7 +71,7 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</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] %>
|
<% 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_fields_index = (@submission.template_fields || @submission.template.fields).group_by { |f| f['submitter_uuid'] } %>
|
||||||
<% submitter_field_counters = Hash.new { 0 } %>
|
<% submitter_field_counters = Hash.new { 0 } %>
|
||||||
@@ -126,7 +126,7 @@
|
|||||||
<% if signed_in? && submitter && !submitter.completed_at? && can?(:create, submitter) %>
|
<% if signed_in? && submitter && !submitter.completed_at? && can?(:create, submitter) %>
|
||||||
<div class="mt-2 mb-1">
|
<div class="mt-2 mb-1">
|
||||||
<a class="btn btn-sm btn-primary w-full" target="_blank" href="<%= submit_form_path(slug: submitter.slug) %>">
|
<a class="btn btn-sm btn-primary w-full" target="_blank" href="<%= submit_form_path(slug: submitter.slug) %>">
|
||||||
Submit Form
|
Sign In-person
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
@@ -173,5 +173,16 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</div>
|
||||||
<%= render 'scripts/autosize_field' %>
|
<%= render 'scripts/autosize_field' %>
|
||||||
Loaded 100 of 162 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user