mirror of
https://github.com/docusealco/docuseal.git
synced 2026-08-07 15:25:16 +00:00
Compare commits
217 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dd18c51384 | |||
| c2014a1549 | |||
| 46edc7dc37 | |||
| d62d83ef8e | |||
| 79350c8fba | |||
| 6702dd52b6 | |||
| c8b74a2c43 | |||
| 1746920eb5 | |||
| bacbc4309c | |||
| 4429d742b3 | |||
| 38b5eccb65 | |||
| d8126c4a47 | |||
| b1c31672ee | |||
| 975f3a95fe | |||
| e3bed5ae67 | |||
| 4737b103c0 | |||
| c5178b96d3 | |||
| 39a2159196 | |||
| 5724159b7b | |||
| 05b7cc9fa6 | |||
| 18b13b5b1e | |||
| d25555b9b0 | |||
| eec266ee83 | |||
| 75683631f0 | |||
| 36c64d1de4 | |||
| 2552b24f21 | |||
| e6ca6dfd85 | |||
| fa2d86b20a | |||
| 8e212e4586 | |||
| a59c18f04b | |||
| 1ab0f1c90c | |||
| 9beaaa0851 | |||
| b86aac86d1 | |||
| c2eb94b571 | |||
| a377e45a63 | |||
| 58574abc6b | |||
| cbae888971 | |||
| 944e1f31db | |||
| 711b3b3190 | |||
| e7abadcefb | |||
| 18d60f5522 | |||
| 6d5c6e086d | |||
| 5a48ab0360 | |||
| 599785b6d1 | |||
| f2fd5fb82b | |||
| f2782bd84e | |||
| e52a721fc9 | |||
| b035123ff9 | |||
| ca8de23082 | |||
| f514654c40 | |||
| 99bc528a6c | |||
| f3c77d0177 | |||
| db2b5f35bd | |||
| 5728381725 | |||
| a7affb1a49 | |||
| 21bf5ed9f7 | |||
| 8ddd4ee916 | |||
| 0e2fd8ee4f | |||
| 27901063ed | |||
| ce31ebde1e | |||
| 0c2abf211e | |||
| 8ecaa4fd7a | |||
| 6759c26d86 | |||
| 76f1f140ac | |||
| ca7ab5aab7 | |||
| fd3530ac62 | |||
| 4ea863592b | |||
| 3f87d0336d | |||
| e251482481 | |||
| c79fca75f3 | |||
| 0b0f0b497e | |||
| e9034d8da7 | |||
| 8fbb8b8f6e | |||
| 4635fdd32b | |||
| 6dd4382446 | |||
| 50acff2e81 | |||
| cb6d24dfca | |||
| ab4a22d8ba | |||
| bc40a15de1 | |||
| 2542f26d96 | |||
| 84edb6dbda | |||
| 88776e7316 | |||
| f669045362 | |||
| 68d3efbdba | |||
| 22d4833496 | |||
| ff14caa9f7 | |||
| 05647135bc | |||
| ae76c2a4a8 | |||
| fb2b3d44fe | |||
| 3868557efd | |||
| 9bef28c8ba | |||
| f25fd3b03d | |||
| d1d7571ee4 | |||
| 6a298a068c | |||
| d4aef79b68 | |||
| 05d578d88f | |||
| 8773b30b10 | |||
| 1b636c5878 | |||
| 46e948ab99 | |||
| 889103aa5e | |||
| 558a14a5f2 | |||
| 72bbdb38bf | |||
| 3975a03cb4 | |||
| e398327fb7 | |||
| 9266694d65 | |||
| 350c4b1c2a | |||
| c3a933d87d | |||
| 970e53a5a3 | |||
| 520bac2ec7 | |||
| e1eddd31c4 | |||
| 3c616a824c | |||
| 90d86b58c2 | |||
| 4332b4ebb8 | |||
| e2f930f6f7 | |||
| c303159c63 | |||
| c1ce2bec00 | |||
| 3d2046f227 | |||
| babaf1aa48 | |||
| 3f03d343ca | |||
| 110cdb5123 | |||
| 440d1114be | |||
| 9032517d4e | |||
| 7c71fefc47 | |||
| e8bd8ff76f | |||
| fed420cae1 | |||
| 910490528d | |||
| ab810be250 | |||
| 178ee47093 | |||
| 6f90147a11 | |||
| 3ebaacbb7a | |||
| 64b39b53a2 | |||
| 2948910c25 | |||
| 1d20c79cf4 | |||
| 48f3b13f3e | |||
| 1fc1e9af16 | |||
| ce70016603 | |||
| e0e79e343d | |||
| 6568f81ee9 | |||
| 386f130d7a | |||
| 3ee41975fd | |||
| d9f314898b | |||
| 4fc7412cac | |||
| 9adbfc52c2 | |||
| 1ad8d9196a | |||
| e9172392b5 | |||
| d6d94de328 | |||
| 77261f457c | |||
| c258915031 | |||
| c1db8bdd3b | |||
| c700484ce8 | |||
| b62bd200ed | |||
| a164fe8dd8 | |||
| bb40e872b3 | |||
| bab80e9488 | |||
| d090d79ebf | |||
| 2299ba4e29 | |||
| d6474f0e6e | |||
| e0a297cf81 | |||
| 40f5fa4217 | |||
| 94e552d170 | |||
| cf1b0a4c16 | |||
| 9aa0a8ecbf | |||
| fd4bef1706 | |||
| d32b0e6d20 | |||
| 4a846be214 | |||
| 3dd3877ea9 | |||
| b4dba0d809 | |||
| dea80a82d8 | |||
| eefa994724 | |||
| 35cce24539 | |||
| e6c8203959 | |||
| 9479771fc8 | |||
| 711427521c | |||
| 5dae742361 | |||
| d8ccd4beee | |||
| c532a0115d | |||
| c9c3364764 | |||
| 1ff23d7c40 | |||
| 95e9b34033 | |||
| 6b42dcf91b | |||
| 28962c949b | |||
| 4ecbbf8410 | |||
| 990b35be18 | |||
| 8002764855 | |||
| b80982658c | |||
| 443ac23c29 | |||
| 37bb987e3e | |||
| c7b08e8d30 | |||
| dd502e8cef | |||
| a554182e57 | |||
| 0c039ee64f | |||
| c6b004f107 | |||
| 2fcb08d27d | |||
| 75fe5ce381 | |||
| fd7d0eaf53 | |||
| 7e537e4232 | |||
| a892975010 | |||
| 05ec92186b | |||
| 8b035f80b3 | |||
| c717293306 | |||
| b3f3cf2aa6 | |||
| 7406334f77 | |||
| 3e1350a221 | |||
| 961e0a1c89 | |||
| 2ce9bbecb9 | |||
| 704da3a2ec | |||
| 2f530ccfca | |||
| e6c20f5ff6 | |||
| d267978437 | |||
| e9d728fa4a | |||
| 3c60fb4ee6 | |||
| f1e494b669 | |||
| f7a8e4102b | |||
| 311c2b0e34 | |||
| 5dd59f4f7c | |||
| 571f5d8221 | |||
| 136b0eb5ed |
+5
-5
@@ -51,22 +51,22 @@ Metrics/AbcSize:
|
||||
- spec/**/*
|
||||
|
||||
Metrics/ModuleLength:
|
||||
Max: 500
|
||||
Max: 1000
|
||||
|
||||
Metrics/ClassLength:
|
||||
Max: 500
|
||||
Max: 1000
|
||||
|
||||
RSpec/NestedGroups:
|
||||
Max: 6
|
||||
|
||||
RSpec/MultipleExpectations:
|
||||
Max: 20
|
||||
Max: 25
|
||||
|
||||
RSpec/ExampleLength:
|
||||
Max: 40
|
||||
Max: 50
|
||||
|
||||
RSpec/MultipleMemoizedHelpers:
|
||||
Max: 6
|
||||
Max: 9
|
||||
|
||||
Metrics/BlockNesting:
|
||||
Max: 4
|
||||
|
||||
+2
-2
@@ -41,7 +41,7 @@ ENV OPENSSL_CONF=/app/openssl_legacy.cnf
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN apk add --no-cache sqlite-dev libpq-dev mariadb-dev vips-dev vips-poppler poppler-utils redis vips-heif gcompat ttf-freefont && mkdir /fonts && rm /usr/share/fonts/freefont/FreeSans.otf
|
||||
RUN echo '@edge https://dl-cdn.alpinelinux.org/alpine/edge/community' >> /etc/apk/repositories && apk add --no-cache sqlite-dev libpq-dev mariadb-dev vips-dev vips-poppler poppler-utils redis libheif@edge vips-heif gcompat ttf-freefont && mkdir /fonts && rm /usr/share/fonts/freefont/FreeSans.otf
|
||||
|
||||
RUN echo $'.include = /etc/ssl/openssl.cnf\n\
|
||||
\n\
|
||||
@@ -57,7 +57,7 @@ activate = 1' >> /app/openssl_legacy.cnf
|
||||
|
||||
COPY ./Gemfile ./Gemfile.lock ./
|
||||
|
||||
RUN apk add --no-cache build-base && bundle install && apk del build-base && 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
|
||||
RUN apk add --no-cache build-base && bundle install && apk del --no-cache build-base && rm -rf ~/.bundle /usr/local/bundle/cache && ruby -e "puts Dir['/usr/local/bundle/**/{spec,rdoc,resources/shared,resources/collation,resources/locales}']" | xargs rm -rf
|
||||
|
||||
COPY ./bin ./bin
|
||||
COPY ./app ./app
|
||||
|
||||
@@ -23,7 +23,6 @@ gem 'image_processing'
|
||||
gem 'jwt'
|
||||
gem 'lograge'
|
||||
gem 'mysql2', require: false
|
||||
gem 'net-smtp', '0.4.0'
|
||||
gem 'oj'
|
||||
gem 'pagy'
|
||||
gem 'pg', require: false
|
||||
@@ -40,7 +39,7 @@ gem 'ruby-vips'
|
||||
gem 'rubyXL'
|
||||
gem 'shakapacker'
|
||||
gem 'sidekiq'
|
||||
gem 'sqlite3', require: false, force_ruby_platform: true
|
||||
gem 'sqlite3', require: false
|
||||
gem 'strip_attributes'
|
||||
gem 'turbo-rails'
|
||||
gem 'twitter_cldr', require: false
|
||||
|
||||
+192
-189
@@ -1,105 +1,101 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
actioncable (7.1.3.4)
|
||||
actionpack (= 7.1.3.4)
|
||||
activesupport (= 7.1.3.4)
|
||||
actioncable (7.2.1.1)
|
||||
actionpack (= 7.2.1.1)
|
||||
activesupport (= 7.2.1.1)
|
||||
nio4r (~> 2.0)
|
||||
websocket-driver (>= 0.6.1)
|
||||
zeitwerk (~> 2.6)
|
||||
actionmailbox (7.1.3.4)
|
||||
actionpack (= 7.1.3.4)
|
||||
activejob (= 7.1.3.4)
|
||||
activerecord (= 7.1.3.4)
|
||||
activestorage (= 7.1.3.4)
|
||||
activesupport (= 7.1.3.4)
|
||||
mail (>= 2.7.1)
|
||||
net-imap
|
||||
net-pop
|
||||
net-smtp
|
||||
actionmailer (7.1.3.4)
|
||||
actionpack (= 7.1.3.4)
|
||||
actionview (= 7.1.3.4)
|
||||
activejob (= 7.1.3.4)
|
||||
activesupport (= 7.1.3.4)
|
||||
mail (~> 2.5, >= 2.5.4)
|
||||
net-imap
|
||||
net-pop
|
||||
net-smtp
|
||||
actionmailbox (7.2.1.1)
|
||||
actionpack (= 7.2.1.1)
|
||||
activejob (= 7.2.1.1)
|
||||
activerecord (= 7.2.1.1)
|
||||
activestorage (= 7.2.1.1)
|
||||
activesupport (= 7.2.1.1)
|
||||
mail (>= 2.8.0)
|
||||
actionmailer (7.2.1.1)
|
||||
actionpack (= 7.2.1.1)
|
||||
actionview (= 7.2.1.1)
|
||||
activejob (= 7.2.1.1)
|
||||
activesupport (= 7.2.1.1)
|
||||
mail (>= 2.8.0)
|
||||
rails-dom-testing (~> 2.2)
|
||||
actionpack (7.1.3.4)
|
||||
actionview (= 7.1.3.4)
|
||||
activesupport (= 7.1.3.4)
|
||||
actionpack (7.2.1.1)
|
||||
actionview (= 7.2.1.1)
|
||||
activesupport (= 7.2.1.1)
|
||||
nokogiri (>= 1.8.5)
|
||||
racc
|
||||
rack (>= 2.2.4)
|
||||
rack (>= 2.2.4, < 3.2)
|
||||
rack-session (>= 1.0.1)
|
||||
rack-test (>= 0.6.3)
|
||||
rails-dom-testing (~> 2.2)
|
||||
rails-html-sanitizer (~> 1.6)
|
||||
actiontext (7.1.3.4)
|
||||
actionpack (= 7.1.3.4)
|
||||
activerecord (= 7.1.3.4)
|
||||
activestorage (= 7.1.3.4)
|
||||
activesupport (= 7.1.3.4)
|
||||
useragent (~> 0.16)
|
||||
actiontext (7.2.1.1)
|
||||
actionpack (= 7.2.1.1)
|
||||
activerecord (= 7.2.1.1)
|
||||
activestorage (= 7.2.1.1)
|
||||
activesupport (= 7.2.1.1)
|
||||
globalid (>= 0.6.0)
|
||||
nokogiri (>= 1.8.5)
|
||||
actionview (7.1.3.4)
|
||||
activesupport (= 7.1.3.4)
|
||||
actionview (7.2.1.1)
|
||||
activesupport (= 7.2.1.1)
|
||||
builder (~> 3.1)
|
||||
erubi (~> 1.11)
|
||||
rails-dom-testing (~> 2.2)
|
||||
rails-html-sanitizer (~> 1.6)
|
||||
activejob (7.1.3.4)
|
||||
activesupport (= 7.1.3.4)
|
||||
activejob (7.2.1.1)
|
||||
activesupport (= 7.2.1.1)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (7.1.3.4)
|
||||
activesupport (= 7.1.3.4)
|
||||
activerecord (7.1.3.4)
|
||||
activemodel (= 7.1.3.4)
|
||||
activesupport (= 7.1.3.4)
|
||||
activemodel (7.2.1.1)
|
||||
activesupport (= 7.2.1.1)
|
||||
activerecord (7.2.1.1)
|
||||
activemodel (= 7.2.1.1)
|
||||
activesupport (= 7.2.1.1)
|
||||
timeout (>= 0.4.0)
|
||||
activestorage (7.1.3.4)
|
||||
actionpack (= 7.1.3.4)
|
||||
activejob (= 7.1.3.4)
|
||||
activerecord (= 7.1.3.4)
|
||||
activesupport (= 7.1.3.4)
|
||||
activestorage (7.2.1.1)
|
||||
actionpack (= 7.2.1.1)
|
||||
activejob (= 7.2.1.1)
|
||||
activerecord (= 7.2.1.1)
|
||||
activesupport (= 7.2.1.1)
|
||||
marcel (~> 1.0)
|
||||
activesupport (7.1.3.4)
|
||||
activesupport (7.2.1.1)
|
||||
base64
|
||||
bigdecimal
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
concurrent-ruby (~> 1.0, >= 1.3.1)
|
||||
connection_pool (>= 2.2.5)
|
||||
drb
|
||||
i18n (>= 1.6, < 2)
|
||||
logger (>= 1.4.2)
|
||||
minitest (>= 5.1)
|
||||
mutex_m
|
||||
tzinfo (~> 2.0)
|
||||
addressable (2.8.6)
|
||||
public_suffix (>= 2.0.2, < 6.0)
|
||||
securerandom (>= 0.3)
|
||||
tzinfo (~> 2.0, >= 2.0.5)
|
||||
addressable (2.8.7)
|
||||
public_suffix (>= 2.0.2, < 7.0)
|
||||
annotate (3.2.0)
|
||||
activerecord (>= 3.2, < 8.0)
|
||||
rake (>= 10.4, < 14.0)
|
||||
arabic-letter-connector (0.1.1)
|
||||
ast (2.4.2)
|
||||
aws-eventstream (1.3.0)
|
||||
aws-partitions (1.913.0)
|
||||
aws-sdk-core (3.191.6)
|
||||
aws-partitions (1.985.0)
|
||||
aws-sdk-core (3.209.1)
|
||||
aws-eventstream (~> 1, >= 1.3.0)
|
||||
aws-partitions (~> 1, >= 1.651.0)
|
||||
aws-sigv4 (~> 1.8)
|
||||
aws-sigv4 (~> 1.9)
|
||||
jmespath (~> 1, >= 1.6.1)
|
||||
aws-sdk-kms (1.79.0)
|
||||
aws-sdk-core (~> 3, >= 3.191.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-s3 (1.146.1)
|
||||
aws-sdk-core (~> 3, >= 3.191.0)
|
||||
aws-sdk-kms (1.94.0)
|
||||
aws-sdk-core (~> 3, >= 3.207.0)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sdk-s3 (1.167.0)
|
||||
aws-sdk-core (~> 3, >= 3.207.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.8)
|
||||
aws-sdk-secretsmanager (1.91.0)
|
||||
aws-sdk-core (~> 3, >= 3.191.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sigv4 (1.8.0)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sdk-secretsmanager (1.108.0)
|
||||
aws-sdk-core (~> 3, >= 3.207.0)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sigv4 (1.10.0)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
azure-storage-blob (2.0.3)
|
||||
azure-storage-common (~> 2.0)
|
||||
@@ -120,14 +116,14 @@ GEM
|
||||
smart_properties
|
||||
bigdecimal (3.1.8)
|
||||
bindex (0.8.1)
|
||||
bootsnap (1.18.3)
|
||||
bootsnap (1.18.4)
|
||||
msgpack (~> 1.2)
|
||||
builder (3.3.0)
|
||||
bullet (7.1.6)
|
||||
bullet (7.2.0)
|
||||
activesupport (>= 3.0.0)
|
||||
uniform_notifier (~> 1.11)
|
||||
camertron-eprun (1.1.1)
|
||||
cancancan (3.5.0)
|
||||
cancancan (3.6.1)
|
||||
capybara (3.40.0)
|
||||
addressable
|
||||
matrix
|
||||
@@ -137,7 +133,8 @@ GEM
|
||||
rack-test (>= 0.6.3)
|
||||
regexp_parser (>= 1.5, < 3.0)
|
||||
xpath (~> 3.2)
|
||||
childprocess (5.0.0)
|
||||
childprocess (5.1.0)
|
||||
logger (~> 1.5)
|
||||
chunky_png (1.4.0)
|
||||
cldr-plurals-runtime-rb (1.1.0)
|
||||
cmdparse (3.0.7)
|
||||
@@ -148,12 +145,12 @@ GEM
|
||||
bigdecimal
|
||||
rexml
|
||||
crass (1.0.6)
|
||||
css_parser (1.17.1)
|
||||
css_parser (1.19.0)
|
||||
addressable
|
||||
csv (3.3.0)
|
||||
cuprite (0.15)
|
||||
cuprite (0.15.1)
|
||||
capybara (~> 3.0)
|
||||
ferrum (~> 0.14.0)
|
||||
ferrum (~> 0.15.0)
|
||||
date (3.3.4)
|
||||
debug (1.9.2)
|
||||
irb (~> 1.10)
|
||||
@@ -165,7 +162,7 @@ GEM
|
||||
railties (>= 4.1.0)
|
||||
responders
|
||||
warden (~> 1.2.3)
|
||||
devise-two-factor (5.0.0)
|
||||
devise-two-factor (6.0.0)
|
||||
activesupport (~> 7.0)
|
||||
devise (~> 4.0)
|
||||
railties (~> 7.0)
|
||||
@@ -173,26 +170,26 @@ GEM
|
||||
diff-lcs (1.5.1)
|
||||
digest-crc (0.6.5)
|
||||
rake (>= 12.0.0, < 14.0.0)
|
||||
docile (1.4.0)
|
||||
dotenv (3.1.0)
|
||||
docile (1.4.1)
|
||||
dotenv (3.1.4)
|
||||
drb (2.2.1)
|
||||
email_typo (0.2.3)
|
||||
erb_lint (0.5.0)
|
||||
erb_lint (0.6.0)
|
||||
activesupport
|
||||
better_html (>= 2.0.1)
|
||||
parser (>= 2.7.1.4)
|
||||
rainbow
|
||||
rubocop
|
||||
rubocop (>= 1)
|
||||
smart_properties
|
||||
erubi (1.13.0)
|
||||
factory_bot (6.4.6)
|
||||
factory_bot (6.5.0)
|
||||
activesupport (>= 5.0.0)
|
||||
factory_bot_rails (6.4.3)
|
||||
factory_bot (~> 6.4)
|
||||
railties (>= 5.0.0)
|
||||
faker (3.3.1)
|
||||
faker (3.4.2)
|
||||
i18n (>= 1.8.11, < 2)
|
||||
faraday (1.10.3)
|
||||
faraday (1.10.4)
|
||||
faraday-em_http (~> 1.0)
|
||||
faraday-em_synchrony (~> 1.0)
|
||||
faraday-excon (~> 1.1)
|
||||
@@ -212,93 +209,96 @@ GEM
|
||||
faraday-httpclient (1.0.1)
|
||||
faraday-multipart (1.0.4)
|
||||
multipart-post (~> 2)
|
||||
faraday-net_http (1.0.1)
|
||||
faraday-net_http (1.0.2)
|
||||
faraday-net_http_persistent (1.2.0)
|
||||
faraday-patron (1.0.0)
|
||||
faraday-rack (1.0.0)
|
||||
faraday-retry (1.0.3)
|
||||
faraday_middleware (1.2.0)
|
||||
faraday_middleware (1.2.1)
|
||||
faraday (~> 1.0)
|
||||
ferrum (0.14)
|
||||
ferrum (0.15)
|
||||
addressable (~> 2.5)
|
||||
concurrent-ruby (~> 1.1)
|
||||
webrick (~> 1.7)
|
||||
websocket-driver (>= 0.6, < 0.8)
|
||||
ffi (1.16.3)
|
||||
websocket-driver (~> 0.7)
|
||||
ffi (1.17.0)
|
||||
ffi (1.17.0-aarch64-linux-gnu)
|
||||
ffi (1.17.0-x86_64-linux-gnu)
|
||||
geom2d (0.4.1)
|
||||
globalid (1.2.1)
|
||||
activesupport (>= 6.1)
|
||||
google-apis-core (0.14.1)
|
||||
google-apis-core (0.15.1)
|
||||
addressable (~> 2.5, >= 2.5.1)
|
||||
googleauth (~> 1.9)
|
||||
httpclient (>= 2.8.1, < 3.a)
|
||||
httpclient (>= 2.8.3, < 3.a)
|
||||
mini_mime (~> 1.0)
|
||||
mutex_m
|
||||
representable (~> 3.0)
|
||||
retriable (>= 2.0, < 4.a)
|
||||
rexml
|
||||
google-apis-iamcredentials_v1 (0.20.0)
|
||||
google-apis-core (>= 0.14.0, < 2.a)
|
||||
google-apis-storage_v1 (0.37.0)
|
||||
google-apis-core (>= 0.14.0, < 2.a)
|
||||
google-cloud-core (1.7.0)
|
||||
google-apis-iamcredentials_v1 (0.21.0)
|
||||
google-apis-core (>= 0.15.0, < 2.a)
|
||||
google-apis-storage_v1 (0.46.0)
|
||||
google-apis-core (>= 0.15.0, < 2.a)
|
||||
google-cloud-core (1.7.1)
|
||||
google-cloud-env (>= 1.0, < 3.a)
|
||||
google-cloud-errors (~> 1.0)
|
||||
google-cloud-env (2.1.1)
|
||||
google-cloud-env (2.2.1)
|
||||
faraday (>= 1.0, < 3.a)
|
||||
google-cloud-errors (1.4.0)
|
||||
google-cloud-storage (1.49.0)
|
||||
google-cloud-storage (1.52.0)
|
||||
addressable (~> 2.8)
|
||||
digest-crc (~> 0.4)
|
||||
google-apis-core (~> 0.13)
|
||||
google-apis-iamcredentials_v1 (~> 0.18)
|
||||
google-apis-storage_v1 (~> 0.33)
|
||||
google-apis-storage_v1 (~> 0.38)
|
||||
google-cloud-core (~> 1.6)
|
||||
googleauth (~> 1.9)
|
||||
mini_mime (~> 1.0)
|
||||
googleauth (1.11.0)
|
||||
googleauth (1.11.1)
|
||||
faraday (>= 1.0, < 3.a)
|
||||
google-cloud-env (~> 2.1)
|
||||
jwt (>= 1.4, < 3.0)
|
||||
multi_json (~> 1.11)
|
||||
os (>= 0.9, < 2.0)
|
||||
signet (>= 0.16, < 2.a)
|
||||
hashdiff (1.1.0)
|
||||
hexapdf (0.42.0)
|
||||
hashdiff (1.1.1)
|
||||
hexapdf (0.47.0)
|
||||
cmdparse (~> 3.0, >= 3.0.3)
|
||||
geom2d (~> 0.4, >= 0.4.1)
|
||||
openssl (>= 2.2.1)
|
||||
htmlentities (4.3.4)
|
||||
httpclient (2.8.3)
|
||||
i18n (1.14.5)
|
||||
i18n (1.14.6)
|
||||
concurrent-ruby (~> 1.0)
|
||||
image_processing (1.12.2)
|
||||
image_processing (1.13.0)
|
||||
mini_magick (>= 4.9.5, < 5)
|
||||
ruby-vips (>= 2.0.17, < 3)
|
||||
io-console (0.7.2)
|
||||
irb (1.13.1)
|
||||
irb (1.14.1)
|
||||
rdoc (>= 4.0.0)
|
||||
reline (>= 0.4.2)
|
||||
jmespath (1.6.2)
|
||||
json (2.7.2)
|
||||
jwt (2.8.1)
|
||||
jwt (2.9.3)
|
||||
base64
|
||||
language_server-protocol (3.17.0.3)
|
||||
launchy (3.0.0)
|
||||
launchy (3.0.1)
|
||||
addressable (~> 2.8)
|
||||
childprocess (~> 5.0)
|
||||
letter_opener (1.10.0)
|
||||
launchy (>= 2.2, < 4)
|
||||
letter_opener_web (2.0.0)
|
||||
actionmailer (>= 5.2)
|
||||
letter_opener (~> 1.7)
|
||||
railties (>= 5.2)
|
||||
letter_opener_web (3.0.0)
|
||||
actionmailer (>= 6.1)
|
||||
letter_opener (~> 1.9)
|
||||
railties (>= 6.1)
|
||||
rexml
|
||||
logger (1.6.1)
|
||||
lograge (0.14.0)
|
||||
actionpack (>= 4)
|
||||
activesupport (>= 4)
|
||||
railties (>= 4)
|
||||
request_store (~> 1.0)
|
||||
loofah (2.22.0)
|
||||
loofah (2.23.1)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.12.0)
|
||||
mail (2.8.1)
|
||||
@@ -309,49 +309,51 @@ GEM
|
||||
marcel (1.0.4)
|
||||
matrix (0.4.2)
|
||||
method_source (1.1.0)
|
||||
mini_magick (4.12.0)
|
||||
mini_magick (4.13.2)
|
||||
mini_mime (1.1.5)
|
||||
mini_portile2 (2.8.7)
|
||||
minitest (5.24.1)
|
||||
msgpack (1.7.2)
|
||||
mini_portile2 (2.8.8)
|
||||
minitest (5.25.1)
|
||||
msgpack (1.7.3)
|
||||
multi_json (1.15.0)
|
||||
multipart-post (2.4.0)
|
||||
multipart-post (2.4.1)
|
||||
mutex_m (0.2.0)
|
||||
mysql2 (0.5.6)
|
||||
net-http-persistent (4.0.2)
|
||||
net-http-persistent (4.0.4)
|
||||
connection_pool (~> 2.2)
|
||||
net-imap (0.4.14)
|
||||
net-imap (0.4.17)
|
||||
date
|
||||
net-protocol
|
||||
net-pop (0.1.2)
|
||||
net-protocol
|
||||
net-protocol (0.2.2)
|
||||
timeout
|
||||
net-smtp (0.4.0)
|
||||
net-smtp (0.5.0)
|
||||
net-protocol
|
||||
nio4r (2.7.1)
|
||||
nokogiri (1.16.7)
|
||||
nio4r (2.7.3)
|
||||
nokogiri (1.16.8)
|
||||
mini_portile2 (~> 2.8.2)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.16.7-aarch64-linux)
|
||||
nokogiri (1.16.8-aarch64-linux)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.16.7-x86_64-linux)
|
||||
nokogiri (1.16.8-x86_64-linux)
|
||||
racc (~> 1.4)
|
||||
oj (3.16.3)
|
||||
oj (3.16.6)
|
||||
bigdecimal (>= 3.0)
|
||||
ostruct (>= 0.2)
|
||||
openssl (3.2.0)
|
||||
orm_adapter (0.5.0)
|
||||
os (1.1.4)
|
||||
ostruct (0.6.0)
|
||||
package_json (0.1.0)
|
||||
pagy (8.1.2)
|
||||
parallel (1.25.1)
|
||||
parser (3.3.4.0)
|
||||
pagy (9.1.0)
|
||||
parallel (1.26.3)
|
||||
parser (3.3.5.0)
|
||||
ast (~> 2.4.1)
|
||||
racc
|
||||
pg (1.5.6)
|
||||
premailer (1.23.0)
|
||||
pg (1.5.8)
|
||||
premailer (1.27.0)
|
||||
addressable
|
||||
css_parser (>= 1.12.0)
|
||||
css_parser (>= 1.19.0)
|
||||
htmlentities (>= 4.0.0)
|
||||
premailer-rails (1.12.0)
|
||||
actionmailer (>= 3)
|
||||
@@ -362,15 +364,15 @@ GEM
|
||||
pry (0.14.2)
|
||||
coderay (~> 1.1)
|
||||
method_source (~> 1.0)
|
||||
pry-rails (0.3.9)
|
||||
pry (>= 0.10.4)
|
||||
pry-rails (0.3.11)
|
||||
pry (>= 0.13.0)
|
||||
psych (5.1.2)
|
||||
stringio
|
||||
public_suffix (5.0.5)
|
||||
puma (6.4.2)
|
||||
public_suffix (6.0.1)
|
||||
puma (6.4.3)
|
||||
nio4r (~> 2.0)
|
||||
racc (1.8.1)
|
||||
rack (3.1.7)
|
||||
rack (3.1.8)
|
||||
rack-proxy (0.7.7)
|
||||
rack
|
||||
rack-session (2.0.0)
|
||||
@@ -380,27 +382,27 @@ GEM
|
||||
rackup (2.1.0)
|
||||
rack (>= 3)
|
||||
webrick (~> 1.8)
|
||||
rails (7.1.3.4)
|
||||
actioncable (= 7.1.3.4)
|
||||
actionmailbox (= 7.1.3.4)
|
||||
actionmailer (= 7.1.3.4)
|
||||
actionpack (= 7.1.3.4)
|
||||
actiontext (= 7.1.3.4)
|
||||
actionview (= 7.1.3.4)
|
||||
activejob (= 7.1.3.4)
|
||||
activemodel (= 7.1.3.4)
|
||||
activerecord (= 7.1.3.4)
|
||||
activestorage (= 7.1.3.4)
|
||||
activesupport (= 7.1.3.4)
|
||||
rails (7.2.1.1)
|
||||
actioncable (= 7.2.1.1)
|
||||
actionmailbox (= 7.2.1.1)
|
||||
actionmailer (= 7.2.1.1)
|
||||
actionpack (= 7.2.1.1)
|
||||
actiontext (= 7.2.1.1)
|
||||
actionview (= 7.2.1.1)
|
||||
activejob (= 7.2.1.1)
|
||||
activemodel (= 7.2.1.1)
|
||||
activerecord (= 7.2.1.1)
|
||||
activestorage (= 7.2.1.1)
|
||||
activesupport (= 7.2.1.1)
|
||||
bundler (>= 1.15.0)
|
||||
railties (= 7.1.3.4)
|
||||
railties (= 7.2.1.1)
|
||||
rails-dom-testing (2.2.0)
|
||||
activesupport (>= 5.0.0)
|
||||
minitest
|
||||
nokogiri (>= 1.6)
|
||||
rails-html-sanitizer (1.6.0)
|
||||
rails-html-sanitizer (1.6.1)
|
||||
loofah (~> 2.21)
|
||||
nokogiri (~> 1.14)
|
||||
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
|
||||
rails-i18n (7.0.9)
|
||||
i18n (>= 0.7, < 2)
|
||||
railties (>= 6.0.0, < 8)
|
||||
@@ -408,100 +410,101 @@ GEM
|
||||
actionview (> 3.1)
|
||||
activesupport (> 3.1)
|
||||
railties (> 3.1)
|
||||
railties (7.1.3.4)
|
||||
actionpack (= 7.1.3.4)
|
||||
activesupport (= 7.1.3.4)
|
||||
irb
|
||||
railties (7.2.1.1)
|
||||
actionpack (= 7.2.1.1)
|
||||
activesupport (= 7.2.1.1)
|
||||
irb (~> 1.13)
|
||||
rackup (>= 1.0.0)
|
||||
rake (>= 12.2)
|
||||
thor (~> 1.0, >= 1.2.2)
|
||||
zeitwerk (~> 2.6)
|
||||
rainbow (3.1.1)
|
||||
rake (13.2.1)
|
||||
rdoc (6.6.3.1)
|
||||
rdoc (6.7.0)
|
||||
psych (>= 4.0.0)
|
||||
redis-client (0.22.2)
|
||||
connection_pool
|
||||
regexp_parser (2.9.2)
|
||||
reline (0.5.7)
|
||||
reline (0.5.10)
|
||||
io-console (~> 0.5)
|
||||
representable (3.2.0)
|
||||
declarative (< 0.1.0)
|
||||
trailblazer-option (>= 0.1.1, < 0.2.0)
|
||||
uber (< 0.2.0)
|
||||
request_store (1.6.0)
|
||||
request_store (1.7.0)
|
||||
rack (>= 1.4)
|
||||
responders (3.1.1)
|
||||
actionpack (>= 5.2)
|
||||
railties (>= 5.2)
|
||||
retriable (3.1.2)
|
||||
rexml (3.3.6)
|
||||
strscan
|
||||
rexml (3.3.9)
|
||||
rotp (6.3.0)
|
||||
rqrcode (2.2.0)
|
||||
chunky_png (~> 1.0)
|
||||
rqrcode_core (~> 1.0)
|
||||
rqrcode_core (1.2.0)
|
||||
rspec-core (3.13.0)
|
||||
rspec-core (3.13.1)
|
||||
rspec-support (~> 3.13.0)
|
||||
rspec-expectations (3.13.0)
|
||||
rspec-expectations (3.13.3)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.13.0)
|
||||
rspec-mocks (3.13.0)
|
||||
rspec-mocks (3.13.2)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.13.0)
|
||||
rspec-rails (6.1.2)
|
||||
actionpack (>= 6.1)
|
||||
activesupport (>= 6.1)
|
||||
railties (>= 6.1)
|
||||
rspec-rails (7.0.1)
|
||||
actionpack (>= 7.0)
|
||||
activesupport (>= 7.0)
|
||||
railties (>= 7.0)
|
||||
rspec-core (~> 3.13)
|
||||
rspec-expectations (~> 3.13)
|
||||
rspec-mocks (~> 3.13)
|
||||
rspec-support (~> 3.13)
|
||||
rspec-support (3.13.1)
|
||||
rubocop (1.65.0)
|
||||
rubocop (1.66.1)
|
||||
json (~> 2.3)
|
||||
language_server-protocol (>= 3.17.0)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 3.3.0.2)
|
||||
rainbow (>= 2.2.2, < 4.0)
|
||||
regexp_parser (>= 2.4, < 3.0)
|
||||
rexml (>= 3.2.5, < 4.0)
|
||||
rubocop-ast (>= 1.31.1, < 2.0)
|
||||
rubocop-ast (>= 1.32.2, < 2.0)
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (>= 2.4.0, < 3.0)
|
||||
rubocop-ast (1.31.3)
|
||||
rubocop-ast (1.32.3)
|
||||
parser (>= 3.3.1.0)
|
||||
rubocop-performance (1.21.0)
|
||||
rubocop-performance (1.22.1)
|
||||
rubocop (>= 1.48.1, < 2.0)
|
||||
rubocop-ast (>= 1.31.1, < 2.0)
|
||||
rubocop-rails (2.24.1)
|
||||
rubocop-rails (2.26.2)
|
||||
activesupport (>= 4.2.0)
|
||||
rack (>= 1.1)
|
||||
rubocop (>= 1.33.0, < 2.0)
|
||||
rubocop (>= 1.52.0, < 2.0)
|
||||
rubocop-ast (>= 1.31.1, < 2.0)
|
||||
rubocop-rspec (3.0.3)
|
||||
rubocop-rspec (3.1.0)
|
||||
rubocop (~> 1.61)
|
||||
ruby-progressbar (1.13.0)
|
||||
ruby-vips (2.2.1)
|
||||
ruby-vips (2.2.2)
|
||||
ffi (~> 1.12)
|
||||
logger
|
||||
ruby2_keywords (0.0.5)
|
||||
rubyXL (3.4.27)
|
||||
nokogiri (>= 1.10.8)
|
||||
rubyzip (>= 1.3.0)
|
||||
rubyzip (2.3.2)
|
||||
securerandom (0.3.1)
|
||||
semantic_range (3.0.0)
|
||||
shakapacker (8.0.0)
|
||||
shakapacker (8.0.2)
|
||||
activesupport (>= 5.2)
|
||||
package_json
|
||||
rack-proxy (>= 0.6.1)
|
||||
railties (>= 5.2)
|
||||
semantic_range (>= 2.3.0)
|
||||
sidekiq (7.2.4)
|
||||
sidekiq (7.3.2)
|
||||
concurrent-ruby (< 2)
|
||||
connection_pool (>= 2.3.0)
|
||||
logger
|
||||
rack (>= 2.2.4)
|
||||
redis-client (>= 0.19.0)
|
||||
redis-client (>= 0.22.2)
|
||||
signet (0.19.0)
|
||||
addressable (~> 2.8)
|
||||
faraday (>= 0.17.5, < 3.a)
|
||||
@@ -511,33 +514,34 @@ GEM
|
||||
docile (~> 1.1)
|
||||
simplecov-html (~> 0.11)
|
||||
simplecov_json_formatter (~> 0.1)
|
||||
simplecov-html (0.12.3)
|
||||
simplecov-html (0.13.1)
|
||||
simplecov_json_formatter (0.1.4)
|
||||
smart_properties (1.17.0)
|
||||
sqlite3 (1.7.3)
|
||||
sqlite3 (2.1.0)
|
||||
mini_portile2 (~> 2.8.0)
|
||||
stringio (3.1.0)
|
||||
sqlite3 (2.1.0-aarch64-linux-gnu)
|
||||
sqlite3 (2.1.0-x86_64-linux-gnu)
|
||||
stringio (3.1.1)
|
||||
strip_attributes (1.13.0)
|
||||
activemodel (>= 3.0, < 8.0)
|
||||
strscan (3.1.0)
|
||||
thor (1.3.1)
|
||||
thor (1.3.2)
|
||||
timeout (0.4.1)
|
||||
trailblazer-option (0.1.2)
|
||||
turbo-rails (2.0.5)
|
||||
turbo-rails (2.0.10)
|
||||
actionpack (>= 6.0.0)
|
||||
activejob (>= 6.0.0)
|
||||
railties (>= 6.0.0)
|
||||
twitter_cldr (6.12.0)
|
||||
twitter_cldr (6.12.1)
|
||||
camertron-eprun
|
||||
cldr-plurals-runtime-rb (~> 1.1)
|
||||
tzinfo
|
||||
tzinfo (2.0.6)
|
||||
concurrent-ruby (~> 1.0)
|
||||
tzinfo-data (1.2024.1)
|
||||
tzinfo-data (1.2024.2)
|
||||
tzinfo (>= 1.0.0)
|
||||
uber (0.1.0)
|
||||
unicode-display_width (2.5.0)
|
||||
unicode-display_width (2.6.0)
|
||||
uniform_notifier (1.16.0)
|
||||
useragent (0.16.10)
|
||||
warden (1.2.9)
|
||||
rack (>= 2.0.9)
|
||||
web-console (4.2.1)
|
||||
@@ -545,17 +549,17 @@ GEM
|
||||
activemodel (>= 6.0.0)
|
||||
bindex (>= 0.4.0)
|
||||
railties (>= 6.0.0)
|
||||
webmock (3.23.0)
|
||||
webmock (3.24.0)
|
||||
addressable (>= 2.8.0)
|
||||
crack (>= 0.3.2)
|
||||
hashdiff (>= 0.4.0, < 2.0.0)
|
||||
webrick (1.8.1)
|
||||
webrick (1.8.2)
|
||||
websocket-driver (0.7.6)
|
||||
websocket-extensions (>= 0.1.0)
|
||||
websocket-extensions (0.1.5)
|
||||
xpath (3.2.0)
|
||||
nokogiri (~> 1.8)
|
||||
zeitwerk (2.6.14)
|
||||
zeitwerk (2.7.0)
|
||||
|
||||
PLATFORMS
|
||||
aarch64-linux
|
||||
@@ -592,7 +596,6 @@ DEPENDENCIES
|
||||
letter_opener_web
|
||||
lograge
|
||||
mysql2
|
||||
net-smtp (= 0.4.0)
|
||||
oj
|
||||
pagy
|
||||
pg
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<h1 align="center" style="border-bottom: none">
|
||||
<div>
|
||||
<a href="https://www.docuseal.co">
|
||||
<a href="https://www.docuseal.com">
|
||||
<img alt="DocuSeal" src="https://github.com/docusealco/docuseal/assets/5418788/c12cd051-81cd-4402-bc3a-92f2cfdc1b06" width="80" />
|
||||
<br>
|
||||
</a>
|
||||
@@ -25,12 +25,12 @@
|
||||
DocuSeal is an open source platform that provides secure and efficient digital document signing and processing. Create PDF forms to have them filled and signed online on any device with an easy-to-use, mobile-optimized web tool.
|
||||
</p>
|
||||
<h2 align="center">
|
||||
<a href="https://demo.docuseal.co">✨ Live Demo</a>
|
||||
<a href="https://demo.docuseal.tech">✨ Live Demo</a>
|
||||
<span>|</span>
|
||||
<a href="https://docuseal.co/sign_up">☁️ Try in Cloud</a>
|
||||
<a href="https://docuseal.com/sign_up">☁️ Try in Cloud</a>
|
||||
</h2>
|
||||
|
||||
[](https://demo.docuseal.co)
|
||||
[](https://demo.docuseal.tech)
|
||||
|
||||
## Features
|
||||
- PDF form fields builder (WYSIWYG)
|
||||
@@ -42,7 +42,7 @@ DocuSeal is an open source platform that provides secure and efficient digital d
|
||||
- PDF signature verification
|
||||
- Users management
|
||||
- Mobile-optimized
|
||||
- Signing available in 13 languages
|
||||
- 6 UI languages with signing available in 13 languages
|
||||
- API and Webhooks for integrations
|
||||
- Easy to deploy in minutes
|
||||
|
||||
@@ -54,11 +54,11 @@ DocuSeal is an open source platform that provides secure and efficient digital d
|
||||
- Conditional fields and formulas
|
||||
- Bulk send with CSV, XLSX spreadsheet import
|
||||
- SSO / SAML
|
||||
- Template creation with HTML API ([Guide](https://www.docuseal.co/guides/create-pdf-document-fillable-form-with-html-api))
|
||||
- Template creation with PDF or DOCX and field tags API ([Guide](https://www.docuseal.co/guides/use-embedded-text-field-tags-in-the-pdf-to-create-a-fillable-form))
|
||||
- Embedded signing form ([React](https://github.com/docusealco/docuseal-react), [Vue](https://github.com/docusealco/docuseal-vue), [Angular](https://github.com/docusealco/docuseal-angular) or [JavaScript](https://www.docuseal.co/docs/embedded))
|
||||
- Embedded document form builder ([React](https://github.com/docusealco/docuseal-react), [Vue](https://github.com/docusealco/docuseal-vue), [Angular](https://github.com/docusealco/docuseal-angular) or [JavaScript](https://www.docuseal.co/docs/embedded))
|
||||
- [Learn more](https://www.docuseal.co/pricing)
|
||||
- Template creation with HTML API ([Guide](https://www.docuseal.com/guides/create-pdf-document-fillable-form-with-html-api))
|
||||
- Template creation with PDF or DOCX and field tags API ([Guide](https://www.docuseal.com/guides/use-embedded-text-field-tags-in-the-pdf-to-create-a-fillable-form))
|
||||
- Embedded signing form ([React](https://github.com/docusealco/docuseal-react), [Vue](https://github.com/docusealco/docuseal-vue), [Angular](https://github.com/docusealco/docuseal-angular) or [JavaScript](https://www.docuseal.com/docs/embedded))
|
||||
- Embedded document form builder ([React](https://github.com/docusealco/docuseal-react), [Vue](https://github.com/docusealco/docuseal-vue), [Angular](https://github.com/docusealco/docuseal-angular) or [JavaScript](https://www.docuseal.com/docs/embedded))
|
||||
- [Learn more](https://www.docuseal.com/pricing)
|
||||
|
||||
## Deploy
|
||||
|
||||
@@ -93,7 +93,7 @@ sudo HOST=your-domain-name.com docker compose up
|
||||
|
||||
At DocuSeal we have expertise and technologies to make documents creation, filling, signing and processing seamlessly integrated with your product. We specialize in working with various industries, including **Banking, Healthcare, Transport, Real Estate, eCommerce, KYC, CRM, and other software products** that require bulk document signing. By leveraging DocuSeal, we can assist in reducing the overall cost of developing and processing electronic documents while ensuring security and compliance with local electronic document laws.
|
||||
|
||||
[Book a Meeting](https://www.docuseal.co/contact)
|
||||
[Book a Meeting](https://www.docuseal.com/contact)
|
||||
|
||||
## License
|
||||
|
||||
@@ -102,6 +102,6 @@ Unless otherwise noted, all files © 2023 DocuSeal LLC.
|
||||
|
||||
## Tools
|
||||
|
||||
- [Signature Maker](https://www.docuseal.co/online-signature)
|
||||
- [Sign Document Online](https://www.docuseal.co/sign-documents-online)
|
||||
- [Fill PDF Online](https://www.docuseal.co/fill-pdf)
|
||||
- [Signature Maker](https://www.docuseal.com/online-signature)
|
||||
- [Sign Document Online](https://www.docuseal.com/sign-documents-online)
|
||||
- [Fill PDF Online](https://www.docuseal.com/fill-pdf)
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
# Reporting a Vulnerability
|
||||
|
||||
If you come across any security concern or vulnarability, please report the information via email to security@docuseal.co instead of opening a GitHub issue. We will promptly respond and will collaborate with you to validate the issue, and resolve it ASAP.
|
||||
If you come across any security concern or vulnarability, please report the information via email to security@docuseal.com instead of opening a GitHub issue. We will promptly respond and will collaborate with you to validate the issue, and resolve it ASAP.
|
||||
|
||||
**We have a bug bounty program to reward security researchers.**
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ class AccountConfigsController < ApplicationController
|
||||
AccountConfig::ALLOW_TYPED_SIGNATURE,
|
||||
AccountConfig::FORCE_MFA,
|
||||
AccountConfig::ALLOW_TO_RESUBMIT,
|
||||
AccountConfig::ALLOW_TO_DECLINE_KEY,
|
||||
AccountConfig::FORM_PREFILL_SIGNATURE_KEY,
|
||||
AccountConfig::ESIGNING_PREFERENCE_KEY,
|
||||
AccountConfig::FORM_WITH_CONFETTI_KEY,
|
||||
@@ -16,7 +17,8 @@ class AccountConfigsController < ApplicationController
|
||||
AccountConfig::FLATTEN_RESULT_PDF_KEY,
|
||||
AccountConfig::WITH_SIGNATURE_ID,
|
||||
AccountConfig::COMBINE_PDF_RESULT_KEY,
|
||||
AccountConfig::REQUIRE_SIGNING_REASON_KEY
|
||||
AccountConfig::REQUIRE_SIGNING_REASON_KEY,
|
||||
AccountConfig::DOCUMENT_FILENAME_FORMAT_KEY
|
||||
].freeze
|
||||
|
||||
InvalidKey = Class.new(StandardError)
|
||||
|
||||
@@ -4,11 +4,11 @@ class AccountsController < ApplicationController
|
||||
LOCALE_OPTIONS = {
|
||||
'en-US' => 'English (United States)',
|
||||
'en-GB' => 'English (United Kingdom)',
|
||||
'fr-FR' => 'French (France)',
|
||||
'es-ES' => 'Spanish (Spain)',
|
||||
'pt-PT' => 'Portuguese (Portugal)',
|
||||
'de-DE' => 'German (Germany)',
|
||||
'it-IT' => 'Italian (Italy)'
|
||||
'fr-FR' => 'Français',
|
||||
'es-ES' => 'Español',
|
||||
'pt-PT' => 'Português',
|
||||
'de-DE' => 'Deutsch',
|
||||
'it-IT' => 'Italiano'
|
||||
}.freeze
|
||||
|
||||
before_action :load_account
|
||||
@@ -19,21 +19,25 @@ class AccountsController < ApplicationController
|
||||
def update
|
||||
current_account.update!(account_params)
|
||||
|
||||
@encrypted_config = EncryptedConfig.find_or_initialize_by(account: current_account,
|
||||
key: EncryptedConfig::APP_URL_KEY)
|
||||
@encrypted_config.assign_attributes(app_url_params)
|
||||
unless Docuseal.multitenant?
|
||||
@encrypted_config = EncryptedConfig.find_or_initialize_by(account: current_account,
|
||||
key: EncryptedConfig::APP_URL_KEY)
|
||||
@encrypted_config.assign_attributes(app_url_params)
|
||||
|
||||
unless URI.parse(@encrypted_config.value.to_s).class.in?([URI::HTTP, URI::HTTPS])
|
||||
@encrypted_config.errors.add(:value, 'should be a valid URL')
|
||||
unless URI.parse(@encrypted_config.value.to_s).class.in?([URI::HTTP, URI::HTTPS])
|
||||
@encrypted_config.errors.add(:value, I18n.t('should_be_a_valid_url'))
|
||||
|
||||
return render :show, status: :unprocessable_entity
|
||||
return render :show, status: :unprocessable_entity
|
||||
end
|
||||
|
||||
@encrypted_config.save!
|
||||
|
||||
Docuseal.refresh_default_url_options!
|
||||
end
|
||||
|
||||
@encrypted_config.save!
|
||||
|
||||
Docuseal.refresh_default_url_options!
|
||||
|
||||
redirect_to settings_account_path, notice: 'Account information has been updated'
|
||||
with_locale do
|
||||
redirect_to settings_account_path, notice: I18n.t('account_information_has_been_updated')
|
||||
end
|
||||
rescue ActiveRecord::RecordInvalid
|
||||
render :show, status: :unprocessable_entity
|
||||
end
|
||||
@@ -43,11 +47,12 @@ class AccountsController < ApplicationController
|
||||
|
||||
true_user.update!(locked_at: Time.current)
|
||||
|
||||
# rubocop:disable Layout/LineLength
|
||||
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.')
|
||||
html: helpers.tag.p(I18n.t('your_account_removal_request_will_be_processed_within_2_weeks_please_contact_us_if_you_want_to_keep_your_account'))
|
||||
)
|
||||
# rubocop:enable Layout/LineLength
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -38,12 +38,12 @@ module Api
|
||||
private
|
||||
|
||||
def authorization_check!(blob)
|
||||
is_authorized =
|
||||
blob.attachments.all? do |a|
|
||||
a.name.in?(%w[logo preview_images]) ||
|
||||
(current_user && a.record.account.id == current_user.account_id) ||
|
||||
!a.record.account.account_configs.find_or_initialize_by(key: AccountConfig::DOWNLOAD_LINKS_AUTH_KEY).value
|
||||
end
|
||||
attachment = blob.attachments.take
|
||||
|
||||
is_authorized = attachment.name.in?(%w[logo preview_images]) ||
|
||||
(current_user && attachment.record.account.id == current_user.account_id) ||
|
||||
!attachment.record.account.account_configs
|
||||
.find_or_initialize_by(key: AccountConfig::DOWNLOAD_LINKS_AUTH_KEY).value
|
||||
|
||||
return if is_authorized
|
||||
|
||||
|
||||
@@ -25,9 +25,9 @@ module Api
|
||||
render json: { error: 'Too many requests' }, status: :too_many_requests
|
||||
end
|
||||
|
||||
if Rails.env.production?
|
||||
unless Rails.env.development?
|
||||
rescue_from CanCan::AccessDenied do |e|
|
||||
render json: { error: e.message }, status: :forbidden
|
||||
render json: { error: access_denied_error_message(e) }, status: :forbidden
|
||||
end
|
||||
|
||||
rescue_from JSON::ParserError do |e|
|
||||
@@ -39,6 +39,33 @@ module Api
|
||||
|
||||
private
|
||||
|
||||
def access_denied_error_message(error)
|
||||
return 'Not authorized' if request.headers['X-Auth-Token'].blank?
|
||||
return 'Not authorized' unless error.subject.is_a?(ActiveRecord::Base)
|
||||
return 'Not authorized' unless error.subject.respond_to?(:account_id)
|
||||
|
||||
linked_account_record_exists =
|
||||
if current_user.account.testing?
|
||||
current_user.account.linked_account_accounts.where(account_type: 'testing')
|
||||
.exists?(account_id: error.subject.account_id)
|
||||
else
|
||||
current_user.account.testing_accounts.exists?(id: error.subject.account_id)
|
||||
end
|
||||
|
||||
return 'Not authorized' unless linked_account_record_exists
|
||||
|
||||
object_name = error.subject.model_name.human
|
||||
id = error.subject.id
|
||||
|
||||
if current_user.account.testing?
|
||||
"#{object_name} #{id} not found using testing API key; Use production API key to " \
|
||||
"access production #{object_name.downcase.pluralize}."
|
||||
else
|
||||
"#{object_name} #{id} not found using production API key; Use testing API key to " \
|
||||
"access testing #{object_name.downcase.pluralize}."
|
||||
end
|
||||
end
|
||||
|
||||
def paginate(relation, field: :id)
|
||||
result = relation.order(field => :desc)
|
||||
.limit([params.fetch(:limit, DEFAULT_LIMIT).to_i, MAX_LIMIT].min)
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Api
|
||||
class SubmissionDocumentsController < ApiBaseController
|
||||
load_and_authorize_resource :submission
|
||||
|
||||
def index
|
||||
documents =
|
||||
if @submission.submitters.all?(&:completed_at?)
|
||||
last_submitter = @submission.submitters.max_by(&:completed_at)
|
||||
|
||||
if last_submitter.documents_attachments.blank?
|
||||
last_submitter.documents_attachments = Submissions::EnsureResultGenerated.call(submitter)
|
||||
end
|
||||
|
||||
last_submitter.documents_attachments
|
||||
else
|
||||
values_hash = Submissions::GeneratePreviewAttachments.build_values_hash(@submission)
|
||||
|
||||
if @submission.preview_documents.present? &&
|
||||
@submission.preview_documents.all? { |s| s.metadata['values_hash'] == values_hash }
|
||||
@submission.preview_documents
|
||||
else
|
||||
ApplicationRecord.no_touching do
|
||||
@submission.preview_documents.each(&:destroy)
|
||||
end
|
||||
|
||||
Submissions::GeneratePreviewAttachments.call(@submission, values_hash:)
|
||||
end
|
||||
end
|
||||
|
||||
ActiveRecord::Associations::Preloader.new(
|
||||
records: documents,
|
||||
associations: [:blob]
|
||||
).call
|
||||
|
||||
render json: {
|
||||
id: @submission.id,
|
||||
documents: documents.map do |attachment|
|
||||
{ name: attachment.filename.base, url: ActiveStorage::Blob.proxy_url(attachment.blob) }
|
||||
end
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -67,20 +67,26 @@ module Api
|
||||
|
||||
submissions = create_submissions(@template, params)
|
||||
|
||||
submissions.each do |submission|
|
||||
SendSubmissionCreatedWebhookRequestJob.perform_async({ 'submission_id' => submission.id })
|
||||
WebhookUrls.for_account_id(@template.account_id, 'submission.created').each do |webhook_url|
|
||||
submissions.each do |submission|
|
||||
SendSubmissionCreatedWebhookRequestJob.perform_async('submission_id' => submission.id,
|
||||
'webhook_url_id' => webhook_url.id)
|
||||
end
|
||||
end
|
||||
|
||||
Submissions.send_signature_requests(submissions)
|
||||
|
||||
submissions.each do |submission|
|
||||
if submission.submitters.all?(&:completed_at?) && submission.submitters.last
|
||||
ProcessSubmitterCompletionJob.perform_async({ 'submitter_id' => submission.submitters.last.id })
|
||||
submission.submitters.each do |submitter|
|
||||
next unless submitter.completed_at?
|
||||
|
||||
ProcessSubmitterCompletionJob.perform_async('submitter_id' => submitter.id, 'send_invitation_email' => false)
|
||||
end
|
||||
end
|
||||
|
||||
render json: build_create_json(submissions)
|
||||
rescue Submitters::NormalizeValues::BaseError, DownloadUtils::UnableToDownload => e
|
||||
rescue Submitters::NormalizeValues::BaseError, Submissions::CreateFromSubmitters::BaseError,
|
||||
DownloadUtils::UnableToDownload => e
|
||||
Rollbar.warning(e) if defined?(Rollbar)
|
||||
|
||||
render json: { error: e.message }, status: :unprocessable_entity
|
||||
@@ -92,7 +98,10 @@ module Api
|
||||
else
|
||||
@submission.update!(archived_at: Time.current)
|
||||
|
||||
SendSubmissionArchivedWebhookRequestJob.perform_async('submission_id' => @submission.id)
|
||||
WebhookUrls.for_account_id(@submission.account_id, 'submission.archived').each do |webhook_url|
|
||||
SendSubmissionArchivedWebhookRequestJob.perform_async('submission_id' => @submission.id,
|
||||
'webhook_url_id' => webhook_url.id)
|
||||
end
|
||||
end
|
||||
|
||||
render json: @submission.as_json(only: %i[id archived_at])
|
||||
|
||||
@@ -13,7 +13,10 @@ module Api
|
||||
|
||||
SubmissionEvents.create_with_tracking_data(submitter, 'view_form', request)
|
||||
|
||||
SendFormViewedWebhookRequestJob.perform_async({ 'submitter_id' => submitter.id })
|
||||
WebhookUrls.for_account_id(submitter.account_id, 'form.viewed').each do |webhook_url|
|
||||
SendFormViewedWebhookRequestJob.perform_async('submitter_id' => submitter.id,
|
||||
'webhook_url_id' => webhook_url.id)
|
||||
end
|
||||
|
||||
render json: {}
|
||||
end
|
||||
|
||||
@@ -68,7 +68,7 @@ module Api
|
||||
end
|
||||
|
||||
if @submitter.completed_at?
|
||||
ProcessSubmitterCompletionJob.perform_async({ 'submitter_id' => @submitter.id })
|
||||
ProcessSubmitterCompletionJob.perform_async('submitter_id' => @submitter.id)
|
||||
elsif normalized_params[:send_email] || normalized_params[:send_sms]
|
||||
Submitters.send_signature_requests([@submitter])
|
||||
end
|
||||
@@ -120,6 +120,7 @@ module Api
|
||||
end&.dig('uuid')
|
||||
|
||||
submitter.email = Submissions.normalize_email(attrs[:email]) if attrs.key?(:email)
|
||||
submitter.name = attrs[:name] if attrs.key?(:name)
|
||||
|
||||
if attrs.key?(:phone)
|
||||
submitter.phone = attrs[:phone].to_s.gsub(/[^0-9+]/, '')
|
||||
|
||||
@@ -25,7 +25,10 @@ module Api
|
||||
|
||||
schema_documents = Templates::CloneAttachments.call(template: cloned_template, original_template: @template)
|
||||
|
||||
SendTemplateCreatedWebhookRequestJob.perform_async('template_id' => cloned_template.id)
|
||||
WebhookUrls.for_account_id(cloned_template.account_id, 'template.created').each do |webhook_url|
|
||||
SendTemplateCreatedWebhookRequestJob.perform_async('template_id' => cloned_template.id,
|
||||
'webhook_url_id' => webhook_url.id)
|
||||
end
|
||||
|
||||
render json: Templates::SerializeForApi.call(cloned_template, schema_documents)
|
||||
end
|
||||
|
||||
@@ -65,7 +65,10 @@ module Api
|
||||
|
||||
@template.update!(template_params)
|
||||
|
||||
SendTemplateUpdatedWebhookRequestJob.perform_async('template_id' => @template.id)
|
||||
WebhookUrls.for_account_id(@template.account_id, 'template.updated').each do |webhook_url|
|
||||
SendTemplateUpdatedWebhookRequestJob.perform_async('template_id' => @template.id,
|
||||
'webhook_url_id' => webhook_url.id)
|
||||
end
|
||||
|
||||
render json: @template.as_json(only: %i[id updated_at])
|
||||
end
|
||||
@@ -84,7 +87,7 @@ module Api
|
||||
|
||||
def filter_templates(templates, params)
|
||||
templates = Templates.search(templates, params[:q])
|
||||
templates = params[:archived] ? templates.archived : templates.active
|
||||
templates = params[:archived].in?(['true', true]) ? 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?
|
||||
@@ -97,12 +100,12 @@ module Api
|
||||
:name,
|
||||
:external_id,
|
||||
{
|
||||
submitters: [%i[name uuid is_requester invite_by_uuid invite_by_uuid linked_to_uuid email]],
|
||||
submitters: [%i[name uuid is_requester invite_by_uuid linked_to_uuid email]],
|
||||
fields: [[:uuid, :submitter_uuid, :name, :type,
|
||||
:required, :readonly, :default_value,
|
||||
:title, :description,
|
||||
{ preferences: {},
|
||||
conditions: [%i[field_uuid value action]],
|
||||
conditions: [%i[field_uuid value action operation]],
|
||||
options: [%i[value uuid]],
|
||||
validation: %i[message pattern],
|
||||
areas: [%i[x y w h cell_w attachment_uuid option_uuid page]] }]]
|
||||
|
||||
@@ -20,10 +20,7 @@ module Api
|
||||
pdf = HexaPDF::Document.new(io: StringIO.new(file))
|
||||
|
||||
trusted_certs = Accounts.load_trusted_certs(current_account)
|
||||
|
||||
is_checksum_found = ActiveStorage::Attachment.joins(:blob)
|
||||
.where(name: 'documents', record_type: 'Submitter')
|
||||
.exists?(blob: { checksum: Digest::MD5.base64digest(file) })
|
||||
is_checksum_found = CompletedDocument.exists?(sha256: Base64.urlsafe_encode64(Digest::SHA256.digest(file)))
|
||||
|
||||
render json: {
|
||||
checksum_status: is_checksum_found ? 'verified' : 'not_found',
|
||||
|
||||
@@ -12,6 +12,6 @@ class ApiSettingsController < ApplicationController
|
||||
|
||||
current_user.access_token.save!
|
||||
|
||||
redirect_back(fallback_location: settings_api_index_path, notice: 'API token as been updated.')
|
||||
redirect_back(fallback_location: settings_api_index_path, notice: I18n.t('api_token_has_been_updated'))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -8,12 +8,14 @@ class ApplicationController < ActionController::Base
|
||||
|
||||
check_authorization unless: :devise_controller?
|
||||
|
||||
around_action :with_locale
|
||||
before_action :sign_in_for_demo, if: -> { Docuseal.demo? }
|
||||
before_action :maybe_redirect_to_setup, unless: :signed_in?
|
||||
before_action :authenticate_user!, unless: :devise_controller?
|
||||
|
||||
helper_method :button_title,
|
||||
:current_account,
|
||||
:form_link_host,
|
||||
:svg_icon
|
||||
|
||||
impersonates :user, with: ->(uuid) { User.find_by(uuid:) }
|
||||
@@ -37,6 +39,10 @@ class ApplicationController < ActionController::Base
|
||||
end
|
||||
|
||||
def default_url_options
|
||||
if request.domain == 'docuseal.com'
|
||||
return { host: 'docuseal.com', protocol: ENV['FORCE_SSL'].present? ? 'https' : 'http' }
|
||||
end
|
||||
|
||||
Docuseal.default_url_options
|
||||
end
|
||||
|
||||
@@ -51,7 +57,18 @@ class ApplicationController < ActionController::Base
|
||||
|
||||
private
|
||||
|
||||
def with_locale(&)
|
||||
return yield unless current_account
|
||||
|
||||
locale = params[:lang].presence if Rails.env.development?
|
||||
locale ||= current_account.locale
|
||||
|
||||
I18n.with_locale(locale, &)
|
||||
end
|
||||
|
||||
def with_browser_locale(&)
|
||||
return yield if I18n.locale != :'en-US' && I18n.locale != :en
|
||||
|
||||
locale = params[:lang].presence
|
||||
locale ||= request.env['HTTP_ACCEPT_LANGUAGE'].to_s[BROWSER_LOCALE_REGEXP].to_s
|
||||
|
||||
@@ -79,7 +96,8 @@ class ApplicationController < ActionController::Base
|
||||
redirect_to setup_index_path unless User.exists?
|
||||
end
|
||||
|
||||
def button_title(title: 'Submit', disabled_with: 'Submitting', title_class: '', icon: nil, icon_disabled: nil)
|
||||
def button_title(title: I18n.t('submit'), disabled_with: I18n.t('submitting'), title_class: '', icon: nil,
|
||||
icon_disabled: nil)
|
||||
render_to_string(partial: 'shared/button_title',
|
||||
locals: { title:, disabled_with:, title_class:, icon:, icon_disabled: })
|
||||
end
|
||||
@@ -87,4 +105,14 @@ class ApplicationController < ActionController::Base
|
||||
def svg_icon(icon_name, class: '')
|
||||
render_to_string(partial: "icons/#{icon_name}", locals: { class: })
|
||||
end
|
||||
|
||||
def form_link_host
|
||||
Docuseal.default_url_options[:host]
|
||||
end
|
||||
|
||||
def maybe_redirect_com
|
||||
return if request.domain != 'docuseal.co'
|
||||
|
||||
redirect_to request.url.gsub('.co/', '.com/'), allow_other_host: true, status: :moved_permanently
|
||||
end
|
||||
end
|
||||
|
||||
@@ -33,7 +33,7 @@ class DashboardController < ApplicationController
|
||||
account_id: current_user.account_id,
|
||||
key: AccountConfig::FORCE_MFA)
|
||||
|
||||
redirect_to mfa_setup_path, notice: 'Setup 2FA to continue'
|
||||
redirect_to mfa_setup_path, notice: I18n.t('setup_2fa_to_continue')
|
||||
end
|
||||
|
||||
def maybe_render_landing
|
||||
|
||||
@@ -9,9 +9,9 @@ class EmailSmtpSettingsController < ApplicationController
|
||||
|
||||
def create
|
||||
if @encrypted_config.update(email_configs)
|
||||
SettingsMailer.smtp_successful_setup(@encrypted_config.value['from_email']).deliver_now!
|
||||
SettingsMailer.smtp_successful_setup(@encrypted_config.value['from_email'] || current_user.email).deliver_now!
|
||||
|
||||
redirect_to settings_email_index_path, notice: 'Changes have been saved'
|
||||
redirect_to settings_email_index_path, notice: I18n.t('changes_have_been_saved')
|
||||
else
|
||||
render :index, status: :unprocessable_entity
|
||||
end
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
class ErrorsController < ActionController::Base
|
||||
ENTERPRISE_FEATURE_MESSAGE =
|
||||
'This feature is available in Pro Edition: https://www.docuseal.co/pricing'
|
||||
'This feature is available in Pro Edition: https://www.docuseal.com/pricing'
|
||||
|
||||
ENTERPRISE_PATHS = [
|
||||
'/templates/html',
|
||||
|
||||
@@ -11,6 +11,8 @@ class EsignSettingsController < ApplicationController
|
||||
end
|
||||
end
|
||||
|
||||
prepend_before_action :maybe_redirect_com, only: %i[show]
|
||||
|
||||
before_action :load_encrypted_config
|
||||
authorize_resource :encrypted_config, parent: false, only: %i[new create]
|
||||
authorize_resource :encrypted_config, only: %i[update destroy show]
|
||||
@@ -21,9 +23,9 @@ class EsignSettingsController < ApplicationController
|
||||
default_pkcs = GenerateCertificate.load_pkcs(cert_data) if cert_data['cert'].present?
|
||||
|
||||
custom_pkcs_list = (cert_data['custom'] || []).map do |e|
|
||||
{ 'pkcs' => OpenSSL::PKCS12.new(Base64.urlsafe_decode64(e['data']), e['password'].to_s),
|
||||
'name' => e['name'],
|
||||
'status' => e['status'] }
|
||||
pkcs = e['data'].present? ? OpenSSL::PKCS12.new(Base64.urlsafe_decode64(e['data']), e['password'].to_s) : nil
|
||||
|
||||
{ 'pkcs' => pkcs, 'name' => e['name'], 'status' => e['status'] }
|
||||
end
|
||||
|
||||
@pkcs_list = [
|
||||
@@ -48,7 +50,7 @@ class EsignSettingsController < ApplicationController
|
||||
if (@encrypted_config.value && @encrypted_config.value['custom']&.any? { |e| e['name'] == @cert_record.name }) ||
|
||||
@cert_record.name == DEFAULT_CERT_NAME
|
||||
|
||||
@cert_record.errors.add(:name, 'already exists')
|
||||
@cert_record.errors.add(:name, I18n.t('already_exists'))
|
||||
|
||||
return render turbo_stream: turbo_stream.replace(:modal, template: 'esign_settings/new'),
|
||||
status: :unprocessable_entity
|
||||
@@ -56,7 +58,7 @@ class EsignSettingsController < ApplicationController
|
||||
|
||||
save_new_cert!(@encrypted_config, @cert_record)
|
||||
|
||||
redirect_to settings_esign_path, notice: 'Certificate has been successfully added!'
|
||||
redirect_to settings_esign_path, notice: I18n.t('certificate_has_been_successfully_added')
|
||||
rescue OpenSSL::PKCS12::PKCS12Error => e
|
||||
Rollbar.error(e) if defined?(Rollbar)
|
||||
|
||||
@@ -66,14 +68,20 @@ class EsignSettingsController < ApplicationController
|
||||
end
|
||||
|
||||
def update
|
||||
@encrypted_config.value['custom'].each { |e| e['status'] = 'validate' }
|
||||
@encrypted_config.value['custom'].to_a.each { |e| e['status'] = 'validate' }
|
||||
|
||||
custom_cert_data = @encrypted_config.value['custom'].find { |e| e['name'] == params[:name] }
|
||||
custom_cert_data['status'] = 'default' if custom_cert_data
|
||||
custom_cert_data = @encrypted_config.value['custom'].to_a.find { |e| e['name'] == params[:name] }
|
||||
|
||||
if custom_cert_data
|
||||
custom_cert_data['status'] = 'default'
|
||||
elsif params[:name] == Docuseal::AATL_CERT_NAME
|
||||
@encrypted_config.value['custom'] ||= []
|
||||
@encrypted_config.value['custom'] << { 'name' => params[:name], 'status' => 'default' }
|
||||
end
|
||||
|
||||
@encrypted_config.save!
|
||||
|
||||
redirect_to settings_esign_path, notice: 'Default certificate has been selected'
|
||||
redirect_to settings_esign_path, notice: I18n.t('default_certificate_has_been_selected')
|
||||
end
|
||||
|
||||
def destroy
|
||||
@@ -81,7 +89,7 @@ class EsignSettingsController < ApplicationController
|
||||
|
||||
@encrypted_config.save!
|
||||
|
||||
redirect_to settings_esign_path, notice: 'Certificate has been removed'
|
||||
redirect_to settings_esign_path, notice: I18n.t('certificate_has_been_removed')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -18,11 +18,11 @@ class MfaSetupController < ApplicationController
|
||||
current_user.otp_required_for_login = true
|
||||
current_user.save!
|
||||
|
||||
redirect_to settings_profile_index_path, notice: '2FA has been configured'
|
||||
redirect_to settings_profile_index_path, notice: I18n.t('2fa_has_been_configured')
|
||||
else
|
||||
@provision_url = current_user.otp_provisioning_uri(current_user.email, issuer: Docuseal.product_name)
|
||||
|
||||
@error_message = 'Code is invalid'
|
||||
@error_message = I18n.t('code_is_invalid')
|
||||
|
||||
render turbo_stream: turbo_stream.replace(:mfa_form, partial: 'mfa_setup/form'), status: :unprocessable_entity
|
||||
end
|
||||
@@ -32,9 +32,9 @@ class MfaSetupController < ApplicationController
|
||||
if current_user.validate_and_consume_otp!(params[:otp_attempt])
|
||||
current_user.update!(otp_required_for_login: false, otp_secret: nil)
|
||||
|
||||
redirect_to settings_profile_index_path, notice: '2FA has been removed'
|
||||
redirect_to settings_profile_index_path, notice: I18n.t('2fa_has_been_removed')
|
||||
else
|
||||
@error_message = 'Code is invalid'
|
||||
@error_message = I18n.t('code_is_invalid')
|
||||
|
||||
render turbo_stream: turbo_stream.replace(:modal, template: 'mfa_setup/edit'), status: :unprocessable_entity
|
||||
end
|
||||
@@ -43,7 +43,7 @@ class MfaSetupController < ApplicationController
|
||||
private
|
||||
|
||||
def set_provision_url
|
||||
return redirect_to root_path, alert: '2FA has been set up already' if current_user.otp_required_for_login
|
||||
return redirect_to root_path, alert: I18n.t('2fa_has_been_set_up_already') if current_user.otp_required_for_login
|
||||
|
||||
current_user.otp_secret ||= User.generate_otp_secret
|
||||
|
||||
|
||||
@@ -13,9 +13,9 @@ class NotificationsSettingsController < ApplicationController
|
||||
|
||||
def create
|
||||
if @account_config.value.present? ? @account_config.save : @account_config.delete
|
||||
redirect_back fallback_location: settings_notifications_path, notice: 'Changes have been saved'
|
||||
redirect_back fallback_location: settings_notifications_path, notice: I18n.t('changes_have_been_saved')
|
||||
else
|
||||
redirect_back fallback_location: settings_notifications_path, alert: 'Unable to save'
|
||||
redirect_back fallback_location: settings_notifications_path, alert: I18n.t('unable_to_save')
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -6,7 +6,8 @@ class PersonalizationSettingsController < ApplicationController
|
||||
AccountConfig::SUBMITTER_INVITATION_EMAIL_KEY,
|
||||
AccountConfig::SUBMITTER_DOCUMENTS_COPY_EMAIL_KEY,
|
||||
AccountConfig::SUBMITTER_COMPLETED_EMAIL_KEY,
|
||||
AccountConfig::FORM_COMPLETED_MESSAGE_KEY
|
||||
AccountConfig::FORM_COMPLETED_MESSAGE_KEY,
|
||||
*(Docuseal.multitenant? ? [] : [AccountConfig::POLICY_LINKS_KEY])
|
||||
].freeze
|
||||
|
||||
InvalidKey = Class.new(StandardError)
|
||||
@@ -30,7 +31,7 @@ class PersonalizationSettingsController < ApplicationController
|
||||
@account_config.save!
|
||||
end
|
||||
|
||||
redirect_back(fallback_location: settings_personalization_path, notice: 'Settings have been saved.')
|
||||
redirect_back(fallback_location: settings_personalization_path, notice: I18n.t('settings_have_been_saved'))
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -12,7 +12,8 @@ class PreviewDocumentPageController < ActionController::API
|
||||
|
||||
return head :not_found unless attachment
|
||||
|
||||
preview_image = attachment.preview_images.joins(:blob).find_by(blob: { filename: "#{params[:id]}#{FORMAT}" })
|
||||
preview_image = attachment.preview_images.joins(:blob)
|
||||
.find_by(blob: { filename: ["#{params[:id]}.png", "#{params[:id]}.jpg"] })
|
||||
|
||||
return redirect_to preview_image.url, allow_other_host: true if preview_image
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ class ProfileController < ApplicationController
|
||||
|
||||
def update_contact
|
||||
if current_user.update(contact_params)
|
||||
redirect_to settings_profile_index_path, notice: 'Contact information has been updated'
|
||||
redirect_to settings_profile_index_path, notice: I18n.t('contact_information_has_been_update')
|
||||
else
|
||||
render :index, status: :unprocessable_entity
|
||||
end
|
||||
@@ -18,7 +18,7 @@ class ProfileController < ApplicationController
|
||||
def update_password
|
||||
if current_user.update(password_params)
|
||||
bypass_sign_in(current_user)
|
||||
redirect_to settings_profile_index_path, notice: 'Password has been changed'
|
||||
redirect_to settings_profile_index_path, notice: I18n.t('password_has_been_changed')
|
||||
else
|
||||
render :index, status: :unprocessable_entity
|
||||
end
|
||||
|
||||
@@ -7,6 +7,8 @@ class SendSubmissionEmailController < ApplicationController
|
||||
skip_before_action :verify_authenticity_token
|
||||
skip_authorization_check
|
||||
|
||||
SEND_DURATION = 30.minutes
|
||||
|
||||
def success; end
|
||||
|
||||
def create
|
||||
@@ -23,7 +25,10 @@ class SendSubmissionEmailController < ApplicationController
|
||||
|
||||
RateLimit.call("send-email-#{@submitter.id}", limit: 2, ttl: 5.minutes)
|
||||
|
||||
SubmitterMailer.documents_copy_email(@submitter, sig: true).deliver_later!
|
||||
unless EmailEvent.exists?(tag: :submitter_documents_copy, email: @submitter.email, emailable: @submitter,
|
||||
event_type: :send, created_at: SEND_DURATION.ago..Time.current)
|
||||
SubmitterMailer.documents_copy_email(@submitter, sig: true).deliver_later!
|
||||
end
|
||||
|
||||
respond_to do |f|
|
||||
f.html { redirect_to success_send_submission_email_index_path }
|
||||
|
||||
@@ -12,7 +12,7 @@ class SessionsController < Devise::SessionsController
|
||||
Rollbar.warning('Sign in new user') if defined?(Rollbar)
|
||||
|
||||
return redirect_to new_registration_path(sign_up: true, user: sign_in_params.slice(:email)),
|
||||
notice: 'Create a new account'
|
||||
notice: I18n.t('create_a_new_account')
|
||||
end
|
||||
|
||||
if User.exists?(email:, otp_required_for_login: true) && sign_in_params[:otp_attempt].blank?
|
||||
|
||||
@@ -21,7 +21,7 @@ class SetupController < ApplicationController
|
||||
@encrypted_config = EncryptedConfig.new(encrypted_config_params)
|
||||
|
||||
unless URI.parse(encrypted_config_params[:value].to_s).class.in?([URI::HTTP, URI::HTTPS])
|
||||
@encrypted_config.errors.add(:value, 'should be a valid URL')
|
||||
@encrypted_config.errors.add(:value, I18n.t('should_be_a_valid_url'))
|
||||
|
||||
return render :index, status: :unprocessable_entity
|
||||
end
|
||||
@@ -56,7 +56,7 @@ class SetupController < ApplicationController
|
||||
def account_params
|
||||
return {} unless params[:account]
|
||||
|
||||
params.require(:account).permit(:name, :timezone)
|
||||
params.require(:account).permit(:name, :timezone, :locale)
|
||||
end
|
||||
|
||||
def encrypted_config_params
|
||||
@@ -66,10 +66,10 @@ class SetupController < ApplicationController
|
||||
end
|
||||
|
||||
def redirect_to_root_if_signed
|
||||
redirect_to root_path, notice: 'You are already signed in'
|
||||
redirect_to root_path, notice: I18n.t('you_are_already_signed_in')
|
||||
end
|
||||
|
||||
def ensure_first_user_not_created!
|
||||
redirect_to new_user_session_path, notice: 'Please sign in.' if User.exists?
|
||||
redirect_to new_user_session_path, notice: I18n.t('please_sign_in') if User.exists?
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,6 +7,7 @@ class StartFormController < ApplicationController
|
||||
skip_authorization_check
|
||||
|
||||
around_action :with_browser_locale, only: %i[show completed]
|
||||
before_action :maybe_redirect_com, only: %i[show completed]
|
||||
before_action :load_template
|
||||
|
||||
def show
|
||||
@@ -24,7 +25,7 @@ class StartFormController < ApplicationController
|
||||
redirect_to start_form_completed_path(@template.slug, email: submitter_params[:email])
|
||||
else
|
||||
if filter_undefined_submitters(@template).size > 1 && @submitter.new_record?
|
||||
@error_message = 'Not found'
|
||||
@error_message = I18n.t('not_found')
|
||||
|
||||
return render :show
|
||||
end
|
||||
@@ -37,7 +38,10 @@ class StartFormController < ApplicationController
|
||||
|
||||
if @submitter.save
|
||||
if is_new_record
|
||||
SendSubmissionCreatedWebhookRequestJob.perform_async({ 'submission_id' => @submitter.submission.id })
|
||||
WebhookUrls.for_account_id(@submitter.account_id, 'submission.created').each do |webhook_url|
|
||||
SendSubmissionCreatedWebhookRequestJob.perform_async('submission_id' => @submitter.submission_id,
|
||||
'webhook_url_id' => webhook_url.id)
|
||||
end
|
||||
end
|
||||
|
||||
redirect_to submit_form_path(@submitter.slug)
|
||||
|
||||
@@ -11,7 +11,7 @@ class StorageSettingsController < ApplicationController
|
||||
if @encrypted_config.update(storage_configs)
|
||||
LoadActiveStorageConfigs.reload
|
||||
|
||||
redirect_to settings_storage_index_path, notice: 'Changes have been saved'
|
||||
redirect_to settings_storage_index_path, notice: I18n.t('changes_have_been_saved')
|
||||
else
|
||||
render :index, status: :unprocessable_entity
|
||||
end
|
||||
|
||||
@@ -8,8 +8,18 @@ class SubmissionsArchivedController < ApplicationController
|
||||
@submissions = @submissions.where.not(archived_at: nil)
|
||||
.or(@submissions.where.not(templates: { archived_at: nil }))
|
||||
.preload(:created_by_user, template: :author)
|
||||
@submissions = Submissions.search(@submissions, params[:q], search_template: true)
|
||||
|
||||
@pagy, @submissions = pagy(@submissions.preload(:submitters).order(id: :desc))
|
||||
@submissions = @submissions.preload(:template_accesses) unless current_user.role.in?(%w[admin superadmin])
|
||||
|
||||
@submissions = Submissions.search(@submissions, params[:q], search_template: true)
|
||||
@submissions = Submissions::Filter.call(@submissions, current_user, params)
|
||||
|
||||
@submissions = if params[:completed_at_from].present? || params[:completed_at_to].present?
|
||||
@submissions.order(Submitter.arel_table[:completed_at].maximum.desc)
|
||||
else
|
||||
@submissions.order(id: :desc)
|
||||
end
|
||||
|
||||
@pagy, @submissions = pagy(@submissions.preload(submitters: :start_form_submission_events))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,9 +6,18 @@ class SubmissionsController < ApplicationController
|
||||
|
||||
load_and_authorize_resource :submission, only: %i[show destroy]
|
||||
|
||||
prepend_before_action :maybe_redirect_com, only: %i[show]
|
||||
|
||||
def show
|
||||
@submission = Submissions.preload_with_pages(@submission)
|
||||
|
||||
unless @submission.submitters.all?(&:completed_at?)
|
||||
ActiveRecord::Associations::Preloader.new(
|
||||
records: [@submission],
|
||||
associations: [submitters: :start_form_submission_events]
|
||||
).call
|
||||
end
|
||||
|
||||
render :show, layout: 'plain'
|
||||
end
|
||||
|
||||
@@ -43,13 +52,11 @@ class SubmissionsController < ApplicationController
|
||||
params: params.merge('send_completed_email' => true))
|
||||
end
|
||||
|
||||
submissions.each do |submission|
|
||||
SendSubmissionCreatedWebhookRequestJob.perform_async({ 'submission_id' => submission.id })
|
||||
end
|
||||
enqueue_submission_created_webhooks(@template, submissions)
|
||||
|
||||
Submissions.send_signature_requests(submissions)
|
||||
|
||||
redirect_to template_path(@template), notice: 'New recipients have been added'
|
||||
redirect_to template_path(@template), notice: I18n.t('new_recipients_have_been_added')
|
||||
end
|
||||
|
||||
def destroy
|
||||
@@ -57,13 +64,16 @@ class SubmissionsController < ApplicationController
|
||||
if params[:permanently].present?
|
||||
@submission.destroy!
|
||||
|
||||
'Submission has been removed'
|
||||
I18n.t('submission_has_been_removed')
|
||||
else
|
||||
@submission.update!(archived_at: Time.current)
|
||||
|
||||
SendSubmissionArchivedWebhookRequestJob.perform_async('submission_id' => @submission.id)
|
||||
WebhookUrls.for_account_id(@submission.account_id, 'submission.archived').each do |webhook_url|
|
||||
SendSubmissionArchivedWebhookRequestJob.perform_async('submission_id' => @submission.id,
|
||||
'webhook_url_id' => webhook_url.id)
|
||||
end
|
||||
|
||||
'Submission has been archived'
|
||||
I18n.t('submission_has_been_archived')
|
||||
end
|
||||
|
||||
redirect_back(fallback_location: template_path(@submission.template), notice:)
|
||||
@@ -78,6 +88,15 @@ class SubmissionsController < ApplicationController
|
||||
template.save!
|
||||
end
|
||||
|
||||
def enqueue_submission_created_webhooks(template, submissions)
|
||||
WebhookUrls.for_account_id(template.account_id, 'submission.created').each do |webhook_url|
|
||||
submissions.each do |submission|
|
||||
SendSubmissionCreatedWebhookRequestJob.perform_async('submission_id' => submission.id,
|
||||
'webhook_url_id' => webhook_url.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def submissions_params
|
||||
params.permit(submission: { submitters: [%i[uuid email phone name]] })
|
||||
end
|
||||
|
||||
@@ -10,11 +10,20 @@ class SubmissionsDashboardController < ApplicationController
|
||||
.where(templates: { archived_at: nil })
|
||||
.preload(:created_by_user, template: :author)
|
||||
|
||||
@submissions = @submissions.preload(:template_accesses) unless current_user.role.in?(%w[admin superadmin])
|
||||
|
||||
@submissions = Submissions.search(@submissions, params[:q], search_template: true)
|
||||
@submissions = Submissions::Filter.call(@submissions, current_user, params)
|
||||
|
||||
@submissions = @submissions.pending if params[:status] == 'pending'
|
||||
@submissions = @submissions.completed if params[:status] == 'completed'
|
||||
|
||||
@pagy, @submissions = pagy(@submissions.preload(:submitters).order(id: :desc))
|
||||
@submissions = if params[:completed_at_from].present? || params[:completed_at_to].present?
|
||||
@submissions.order(Submitter.arel_table[:completed_at].maximum.desc)
|
||||
else
|
||||
@submissions.order(id: :desc)
|
||||
end
|
||||
|
||||
@pagy, @submissions = pagy(@submissions.preload(submitters: :start_form_submission_events))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -53,8 +53,15 @@ class SubmissionsDownloadController < ApplicationController
|
||||
end
|
||||
|
||||
def build_urls(submitter)
|
||||
filename_format = AccountConfig.find_or_initialize_by(account_id: submitter.account_id,
|
||||
key: AccountConfig::DOCUMENT_FILENAME_FORMAT_KEY)&.value
|
||||
|
||||
Submitters.select_attachments_for_download(submitter).map do |attachment|
|
||||
ActiveStorage::Blob.proxy_url(attachment.blob, expires_at: FILES_TTL.from_now.to_i)
|
||||
ActiveStorage::Blob.proxy_url(
|
||||
attachment.blob,
|
||||
expires_at: FILES_TTL.from_now.to_i,
|
||||
filename: Submitters.build_document_filename(submitter, attachment.blob, filename_format)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -65,6 +72,13 @@ class SubmissionsDownloadController < ApplicationController
|
||||
attachment = submitter.submission.combined_document_attachment
|
||||
attachment ||= Submissions::GenerateCombinedAttachment.call(submitter)
|
||||
|
||||
ActiveStorage::Blob.proxy_url(attachment.blob, expires_at: FILES_TTL.from_now.to_i)
|
||||
filename_format = AccountConfig.find_or_initialize_by(account_id: submitter.account_id,
|
||||
key: AccountConfig::DOCUMENT_FILENAME_FORMAT_KEY)&.value
|
||||
|
||||
ActiveStorage::Blob.proxy_url(
|
||||
attachment.blob,
|
||||
expires_at: FILES_TTL.from_now.to_i,
|
||||
filename: Submitters.build_document_filename(submitter, attachment.blob, filename_format)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class SubmissionsFiltersController < ApplicationController
|
||||
ALLOWED_NAMES = %w[
|
||||
author
|
||||
completed_at
|
||||
created_at
|
||||
].freeze
|
||||
|
||||
skip_authorization_check
|
||||
|
||||
def show
|
||||
return head :not_found unless ALLOWED_NAMES.include?(params[:name])
|
||||
|
||||
render params[:name]
|
||||
end
|
||||
end
|
||||
@@ -4,6 +4,8 @@ class SubmissionsPreviewController < ApplicationController
|
||||
skip_before_action :authenticate_user!
|
||||
skip_authorization_check
|
||||
|
||||
prepend_before_action :maybe_redirect_com, only: %i[show completed]
|
||||
|
||||
TTL = 40.minutes
|
||||
|
||||
def show
|
||||
@@ -18,8 +20,8 @@ class SubmissionsPreviewController < ApplicationController
|
||||
|
||||
@submission ||= Submission.find_by!(slug: params[:slug])
|
||||
|
||||
if !@submission.submitters.all?(&:completed_at?) && current_user.blank?
|
||||
raise ActionController::RoutingError, 'Not Found'
|
||||
if @submission.account.archived_at? || (!@submission.submitters.all?(&:completed_at?) && current_user.blank?)
|
||||
raise ActionController::RoutingError, I18n.t('not_found')
|
||||
end
|
||||
|
||||
if !submission_valid_ttl?(@submission) && !signature_valid
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class SubmissionsUnarchiveController < ApplicationController
|
||||
load_and_authorize_resource :submission
|
||||
|
||||
def create
|
||||
@submission.update!(archived_at: nil)
|
||||
|
||||
redirect_to submission_path(@submission), notice: I18n.t('submission_has_been_unarchived')
|
||||
end
|
||||
end
|
||||
@@ -12,17 +12,22 @@ class SubmitFormController < ApplicationController
|
||||
def show
|
||||
@submitter = Submitter.find_by!(slug: params[:slug])
|
||||
|
||||
submission = @submitter.submission
|
||||
|
||||
return redirect_to submit_form_completed_path(@submitter.slug) if @submitter.completed_at?
|
||||
return render :archived if @submitter.submission.template.archived_at? || @submitter.submission.archived_at?
|
||||
return render :expired if @submitter.submission.expired?
|
||||
return render :archived if submission.template.archived_at? ||
|
||||
submission.archived_at? ||
|
||||
@submitter.account.archived_at?
|
||||
return render :expired if submission.expired?
|
||||
return render :declined if @submitter.declined_at?
|
||||
return render :awaiting if submission.template.preferences['submitters_order'] == 'preserved' &&
|
||||
!Submitters.current_submitter_order?(@submitter)
|
||||
|
||||
Submitters.preload_with_pages(@submitter)
|
||||
|
||||
Submitters::MaybeUpdateDefaultValues.call(@submitter, current_user)
|
||||
|
||||
@attachments_index = ActiveStorage::Attachment.where(record: @submitter.submission.submitters, name: :attachments)
|
||||
.preload(:blob).index_by(&:uuid)
|
||||
@attachments_index = build_attachments_index(submission)
|
||||
|
||||
@form_configs = Submitters::FormConfigs.call(@submitter, CONFIG_KEYS)
|
||||
|
||||
@@ -46,18 +51,21 @@ class SubmitFormController < ApplicationController
|
||||
submitter = Submitter.find_by!(slug: params[:slug])
|
||||
|
||||
if submitter.completed_at?
|
||||
return render json: { error: 'Form has been completed already.' }, status: :unprocessable_entity
|
||||
return render json: { error: I18n.t('form_has_been_completed_already') }, status: :unprocessable_entity
|
||||
end
|
||||
|
||||
if submitter.template.archived_at? || submitter.submission.archived_at?
|
||||
return render json: { error: 'Form has been archived.' }, status: :unprocessable_entity
|
||||
return render json: { error: I18n.t('form_has_been_archived') }, status: :unprocessable_entity
|
||||
end
|
||||
|
||||
if submitter.submission.expired?
|
||||
return render json: { error: 'Form has been expired.' }, status: :unprocessable_entity
|
||||
return render json: { error: I18n.t('form_has_been_expired') }, status: :unprocessable_entity
|
||||
end
|
||||
|
||||
return render json: { error: 'Form has been declined.' }, status: :unprocessable_entity if submitter.declined_at?
|
||||
if submitter.declined_at?
|
||||
return render json: { error: I18n.t('form_has_been_declined') },
|
||||
status: :unprocessable_entity
|
||||
end
|
||||
|
||||
Submitters::SubmitValues.call(submitter, params, request)
|
||||
|
||||
@@ -71,4 +79,11 @@ class SubmitFormController < ApplicationController
|
||||
end
|
||||
|
||||
def success; end
|
||||
|
||||
private
|
||||
|
||||
def build_attachments_index(submission)
|
||||
ActiveStorage::Attachment.where(record: submission.submitters, name: :attachments)
|
||||
.preload(:blob).index_by(&:uuid)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -21,8 +21,14 @@ class SubmitFormDeclineController < ApplicationController
|
||||
|
||||
user = submitter.submission.created_by_user || submitter.template.author
|
||||
|
||||
SubmitterMailer.declined_email(submitter, user).deliver_later!
|
||||
SendFormDeclinedWebhookRequestJob.perform_async('submitter_id' => submitter.id)
|
||||
if user.user_configs.find_by(key: UserConfig::RECEIVE_DECLINED_EMAIL)&.value != false
|
||||
SubmitterMailer.declined_email(submitter, user).deliver_later!
|
||||
end
|
||||
|
||||
WebhookUrls.for_account_id(submitter.account_id, 'form.declined').each do |webhook_url|
|
||||
SendFormDeclinedWebhookRequestJob.perform_async('submitter_id' => submitter.id,
|
||||
'webhook_url_id' => webhook_url.id)
|
||||
end
|
||||
|
||||
redirect_to submit_form_path(submitter.slug)
|
||||
end
|
||||
|
||||
@@ -9,7 +9,8 @@ class SubmittersAutocompleteController < ApplicationController
|
||||
def index
|
||||
submitters = search_submitters(@submitters)
|
||||
|
||||
values = submitters.limit(LIMIT).group(SELECT_COLUMNS.join(', ')).pluck(SELECT_COLUMNS.join(', '))
|
||||
arel_columns = SELECT_COLUMNS.map { |col| Submitter.arel_table[col] }
|
||||
values = submitters.limit(LIMIT).group(arel_columns).pluck(arel_columns)
|
||||
|
||||
attrs = values.map { |row| SELECT_COLUMNS.zip(row).to_h }
|
||||
attrs = attrs.uniq { |e| e[params[:field]] } if params[:field].present?
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class SubmittersController < ApplicationController
|
||||
load_and_authorize_resource :submitter, only: %i[edit update]
|
||||
|
||||
def edit
|
||||
@submitter_email_message =
|
||||
if @submitter.preferences['email_message_uuid'].present?
|
||||
@submitter.account.email_messages.find_by(uuid: @submitter.preferences['email_message_uuid'])
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
submission = @submitter.submission
|
||||
|
||||
if @submitter.submission_events.exists?(event_type: 'start_form') || submission.archived_at? || submission.expired?
|
||||
return redirect_back fallback_location: submission_path(submission), alert: I18n.t('submitter_cannot_be_updated')
|
||||
end
|
||||
|
||||
if submitter_params.values.all?(&:blank?)
|
||||
return redirect_back fallback_location: submission_path(submission),
|
||||
alert: I18n.t('at_least_one_field_must_be_filled')
|
||||
end
|
||||
|
||||
if params[:is_custom_message] != '1'
|
||||
params.delete(:subject)
|
||||
params.delete(:body)
|
||||
end
|
||||
|
||||
submitter_preferences = Submitters.normalize_preferences(@submitter.account, current_user, params)
|
||||
|
||||
if submitter_preferences.key?('email_message_uuid')
|
||||
@submitter.preferences['email_message_uuid'] = submitter_preferences['email_message_uuid']
|
||||
end
|
||||
|
||||
assign_submitter_attrs(@submitter, submitter_params)
|
||||
|
||||
if @submitter.save
|
||||
maybe_resend_email_sms(@submitter, params)
|
||||
|
||||
redirect_back fallback_location: submission_path(submission), notice: I18n.t('changes_have_been_saved')
|
||||
else
|
||||
redirect_back fallback_location: submission_path(submission), alert: I18n.t('unable_to_save')
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def maybe_resend_email_sms(submitter, params)
|
||||
if params[:send_email] == '1' && submitter.email.present?
|
||||
is_sent_recently = Docuseal.multitenant? &&
|
||||
EmailEvent.exists?(email: submitter.email,
|
||||
tag: 'submitter_invitation',
|
||||
emailable: submitter,
|
||||
event_type: 'send',
|
||||
created_at: 4.hours.ago..Time.current)
|
||||
|
||||
SendSubmitterInvitationEmailJob.perform_async('submitter_id' => submitter.id) unless is_sent_recently
|
||||
end
|
||||
|
||||
return if submitter.phone.blank?
|
||||
return unless params[:send_sms] == '1'
|
||||
|
||||
SendSubmitterInvitationSmsJob.perform_async('submitter_id' => submitter.id)
|
||||
end
|
||||
|
||||
def assign_submitter_attrs(submitter, attrs)
|
||||
submitter.phone = attrs[:phone].to_s.gsub(/[^0-9+]/, '') if attrs.key?(:phone)
|
||||
|
||||
submitter.email = Submissions.normalize_email(attrs[:email]) if attrs.key?(:email)
|
||||
|
||||
submitter.name = attrs[:name] if attrs.key?(:name)
|
||||
|
||||
submitter
|
||||
end
|
||||
|
||||
def submitter_params
|
||||
params.require(:submitter).permit(:email, :name, :phone).transform_values(&:strip)
|
||||
end
|
||||
end
|
||||
@@ -6,11 +6,11 @@ class SubmittersSendEmailController < ApplicationController
|
||||
def create
|
||||
if Docuseal.multitenant? && SubmissionEvent.exists?(submitter: @submitter,
|
||||
event_type: 'send_email',
|
||||
created_at: 24.hours.ago..Time.current)
|
||||
created_at: 10.hours.ago..Time.current)
|
||||
Rollbar.warning("Already sent: #{@submitter.id}") if defined?(Rollbar)
|
||||
|
||||
return redirect_back(fallback_location: submission_path(@submitter.submission),
|
||||
alert: 'Email has been sent already.')
|
||||
alert: I18n.t('email_has_been_sent_already'))
|
||||
end
|
||||
|
||||
SendSubmitterInvitationEmailJob.perform_async('submitter_id' => @submitter.id)
|
||||
@@ -18,6 +18,6 @@ class SubmittersSendEmailController < ApplicationController
|
||||
@submitter.sent_at ||= Time.current
|
||||
@submitter.save!
|
||||
|
||||
redirect_back(fallback_location: submission_path(@submitter.submission), notice: 'Email has been sent')
|
||||
redirect_back(fallback_location: submission_path(@submitter.submission), notice: I18n.t('email_has_been_sent'))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5,7 +5,7 @@ class TemplateDocumentsController < ApplicationController
|
||||
|
||||
def create
|
||||
if params[:blobs].blank? && params[:files].blank?
|
||||
return render json: { error: 'File is missing' }, status: :unprocessable_entity
|
||||
return render json: { error: I18n.t('file_is_missing') }, status: :unprocessable_entity
|
||||
end
|
||||
|
||||
old_fields_hash = @template.fields.hash
|
||||
@@ -28,6 +28,6 @@ class TemplateDocumentsController < ApplicationController
|
||||
)
|
||||
}
|
||||
rescue Templates::CreateAttachments::PdfEncrypted
|
||||
render json: { error: 'PDF encrypted' }, status: :unprocessable_entity
|
||||
render json: { error: 'PDF encrypted', status: 'pdf_encrypted' }, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,10 +4,10 @@ class TemplateFoldersController < ApplicationController
|
||||
load_and_authorize_resource :template_folder
|
||||
|
||||
def show
|
||||
@templates = @template_folder.templates.active.preload(:author).order(id: :desc)
|
||||
@templates = @template_folder.templates.active.preload(:author, :template_accesses).order(id: :desc)
|
||||
@templates = Templates.search(@templates, params[:q])
|
||||
|
||||
@pagy, @templates = pagy(@templates, items: 12)
|
||||
@pagy, @templates = pagy(@templates, limit: 12)
|
||||
end
|
||||
|
||||
def edit; end
|
||||
@@ -15,9 +15,9 @@ class TemplateFoldersController < ApplicationController
|
||||
def update
|
||||
if @template_folder != current_account.default_template_folder &&
|
||||
@template_folder.update(template_folder_params)
|
||||
redirect_to folder_path(@template_folder), notice: 'Folder name has been updated'
|
||||
redirect_to folder_path(@template_folder), notice: I18n.t('folder_name_has_been_updated')
|
||||
else
|
||||
redirect_to folder_path(@template_folder), alert: 'Unable to rename folder'
|
||||
redirect_to folder_path(@template_folder), alert: I18n.t('unable_to_rename_folder')
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -4,9 +4,9 @@ class TemplatesArchivedController < ApplicationController
|
||||
load_and_authorize_resource :template, parent: false
|
||||
|
||||
def index
|
||||
@templates = @templates.where.not(archived_at: nil).preload(:author, :folder).order(id: :desc)
|
||||
@templates = @templates.where.not(archived_at: nil).preload(:author, :folder, :template_accesses).order(id: :desc)
|
||||
@templates = Templates.search(@templates, params[:q])
|
||||
|
||||
@pagy, @templates = pagy(@templates, items: 12)
|
||||
@pagy, @templates = pagy(@templates, limit: 12)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,8 +7,15 @@ class TemplatesArchivedSubmissionsController < ApplicationController
|
||||
def index
|
||||
@submissions = @submissions.where.not(archived_at: nil)
|
||||
@submissions = Submissions.search(@submissions, params[:q], search_values: true)
|
||||
@submissions = Submissions::Filter.call(@submissions, current_user, params)
|
||||
|
||||
@pagy, @submissions = pagy(@submissions.preload(:submitters).order(id: :desc))
|
||||
@submissions = if params[:completed_at_from].present? || params[:completed_at_to].present?
|
||||
@submissions.order(Submitter.arel_table[:completed_at].maximum.desc)
|
||||
else
|
||||
@submissions.order(id: :desc)
|
||||
end
|
||||
|
||||
@pagy, @submissions = pagy(@submissions.preload(submitters: :start_form_submission_events))
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
redirect_to root_path
|
||||
end
|
||||
|
||||
@@ -9,19 +9,28 @@ class TemplatesController < ApplicationController
|
||||
submissions = @template.submissions.accessible_by(current_ability)
|
||||
submissions = submissions.active if @template.archived_at.blank?
|
||||
submissions = Submissions.search(submissions, params[:q], search_values: true)
|
||||
submissions = Submissions::Filter.call(submissions, current_user, params)
|
||||
|
||||
@base_submissions = submissions
|
||||
|
||||
submissions = submissions.pending if params[:status] == 'pending'
|
||||
submissions = submissions.completed if params[:status] == 'completed'
|
||||
|
||||
@pagy, @submissions = pagy(submissions.preload(:submitters).order(id: :desc))
|
||||
submissions = if params[:completed_at_from].present? || params[:completed_at_to].present?
|
||||
submissions.order(Submitter.arel_table[:completed_at].maximum.desc)
|
||||
else
|
||||
submissions.order(id: :desc)
|
||||
end
|
||||
|
||||
submissions = submissions.preload(:template_accesses) unless current_user.role.in?(%w[admin superadmin])
|
||||
|
||||
@pagy, @submissions = pagy(submissions.preload(submitters: :start_form_submission_events))
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
redirect_to root_path
|
||||
end
|
||||
|
||||
def new
|
||||
@template.name = "#{@base_template.name} (Clone)" if @base_template
|
||||
@template.name = "#{@base_template.name} (#{I18n.t('clone')})" if @base_template
|
||||
end
|
||||
|
||||
def edit
|
||||
@@ -66,7 +75,7 @@ class TemplatesController < ApplicationController
|
||||
if @template.save
|
||||
Templates::CloneAttachments.call(template: @template, original_template: @base_template) if @base_template
|
||||
|
||||
SendTemplateUpdatedWebhookRequestJob.perform_async('template_id' => @template.id)
|
||||
enqueue_template_created_webhooks(@template)
|
||||
|
||||
maybe_redirect_to_template(@template)
|
||||
else
|
||||
@@ -77,7 +86,7 @@ class TemplatesController < ApplicationController
|
||||
def update
|
||||
@template.update!(template_params)
|
||||
|
||||
SendTemplateUpdatedWebhookRequestJob.perform_async('template_id' => @template.id)
|
||||
enqueue_template_updated_webhooks(@template)
|
||||
|
||||
head :ok
|
||||
end
|
||||
@@ -87,11 +96,11 @@ class TemplatesController < ApplicationController
|
||||
if params[:permanently].present?
|
||||
@template.destroy!
|
||||
|
||||
'Template has been removed.'
|
||||
I18n.t('template_has_been_removed')
|
||||
else
|
||||
@template.update!(archived_at: Time.current)
|
||||
|
||||
'Template has been archived.'
|
||||
I18n.t('template_has_been_archived')
|
||||
end
|
||||
|
||||
redirect_back(fallback_location: root_path, notice:)
|
||||
@@ -102,13 +111,13 @@ class TemplatesController < ApplicationController
|
||||
def template_params
|
||||
params.require(:template).permit(
|
||||
:name,
|
||||
{ schema: [%i[attachment_uuid name]],
|
||||
{ schema: [[:attachment_uuid, :name, { conditions: [%i[field_uuid value action operation]] }]],
|
||||
submitters: [%i[name uuid is_requester linked_to_uuid invite_by_uuid email]],
|
||||
fields: [[:uuid, :submitter_uuid, :name, :type,
|
||||
:required, :readonly, :default_value,
|
||||
:title, :description,
|
||||
{ preferences: {},
|
||||
conditions: [%i[field_uuid value action]],
|
||||
conditions: [%i[field_uuid value action operation]],
|
||||
options: [%i[value uuid]],
|
||||
validation: %i[message pattern],
|
||||
areas: [%i[x y w h cell_w attachment_uuid option_uuid page]] }]] }
|
||||
@@ -116,14 +125,29 @@ class TemplatesController < ApplicationController
|
||||
end
|
||||
|
||||
def authorized_clone_account_id?(account_id)
|
||||
true_user.account_id.to_s == account_id.to_s || true_user.account.linked_accounts.exists?(id: account_id)
|
||||
true_user.account_id.to_s == account_id.to_s ||
|
||||
true_user.account.linked_accounts.accessible_by(current_ability).exists?(id: account_id)
|
||||
end
|
||||
|
||||
def maybe_redirect_to_template(template)
|
||||
if template.account == current_account
|
||||
redirect_to(edit_template_path(@template))
|
||||
else
|
||||
redirect_back(fallback_location: root_path, notice: 'Template has been cloned')
|
||||
redirect_back(fallback_location: root_path, notice: I18n.t('template_has_been_cloned'))
|
||||
end
|
||||
end
|
||||
|
||||
def enqueue_template_created_webhooks(template)
|
||||
WebhookUrls.for_account_id(template.account_id, 'template.created').each do |webhook_url|
|
||||
SendTemplateCreatedWebhookRequestJob.perform_async('template_id' => template.id,
|
||||
'webhook_url_id' => webhook_url.id)
|
||||
end
|
||||
end
|
||||
|
||||
def enqueue_template_updated_webhooks(template)
|
||||
WebhookUrls.for_account_id(template.account_id, 'template.updated').each do |webhook_url|
|
||||
SendTemplateUpdatedWebhookRequestJob.perform_async('template_id' => template.id,
|
||||
'webhook_url_id' => webhook_url.id)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -23,14 +23,14 @@ class TemplatesDashboardController < ApplicationController
|
||||
@template_folders = @template_folders.reject { |e| e.name == TemplateFolder::DEFAULT_NAME }
|
||||
@templates = filter_templates(@templates)
|
||||
|
||||
items =
|
||||
limit =
|
||||
if @template_folders.size < 4
|
||||
TEMPLATES_PER_PAGE
|
||||
else
|
||||
(@template_folders.size < 7 ? 9 : 6)
|
||||
end
|
||||
|
||||
@pagy, @templates = pagy(@templates, items:)
|
||||
@pagy, @templates = pagy(@templates, limit:)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -45,7 +45,7 @@ class TemplatesDashboardController < ApplicationController
|
||||
end
|
||||
|
||||
def filter_templates(templates)
|
||||
rel = templates.active.preload(:author).order(id: :desc)
|
||||
rel = templates.active.preload(:author, :template_accesses).order(id: :desc)
|
||||
|
||||
if params[:q].blank?
|
||||
if Docuseal.multitenant? && !current_account.testing?
|
||||
|
||||
@@ -9,9 +9,9 @@ class TemplatesFoldersController < ApplicationController
|
||||
@template.folder = TemplateFolders.find_or_create_by_name(current_user, params[:name])
|
||||
|
||||
if @template.save
|
||||
redirect_back(fallback_location: template_path(@template), notice: 'Document template has been moved')
|
||||
redirect_back(fallback_location: template_path(@template), notice: I18n.t('document_template_has_been_moved'))
|
||||
else
|
||||
redirect_back(fallback_location: template_path(@template), notice: 'Unable to move template into folder')
|
||||
redirect_back(fallback_location: template_path(@template), notice: I18n.t('unable_to_move_template_into_folder'))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -22,8 +22,10 @@ class TemplatesPreferencesController < ApplicationController
|
||||
preferences: %i[bcc_completed request_email_subject request_email_body
|
||||
documents_copy_email_subject documents_copy_email_body
|
||||
documents_copy_email_enabled documents_copy_email_attach_audit
|
||||
documents_copy_email_attach_documents
|
||||
completed_notification_email_attach_documents
|
||||
completed_redirect_url
|
||||
submitters_order
|
||||
completed_notification_email_subject completed_notification_email_body
|
||||
completed_notification_email_enabled completed_notification_email_attach_audit] +
|
||||
[completed_message: %i[title body]]
|
||||
|
||||
@@ -6,6 +6,6 @@ class TemplatesRestoreController < ApplicationController
|
||||
def create
|
||||
@template.update!(archived_at: nil)
|
||||
|
||||
redirect_to template_path(@template), notice: 'Template has been unarchived'
|
||||
redirect_to template_path(@template), notice: I18n.t('template_has_been_unarchived')
|
||||
end
|
||||
end
|
||||
|
||||
@@ -23,7 +23,7 @@ class TemplatesUploadsController < ApplicationController
|
||||
|
||||
@template.update!(schema:)
|
||||
|
||||
SendTemplateCreatedWebhookRequestJob.perform_async('template_id' => @template.id)
|
||||
enqueue_template_created_webhooks(@template)
|
||||
|
||||
redirect_to edit_template_path(@template)
|
||||
rescue Templates::CreateAttachments::PdfEncrypted
|
||||
@@ -33,7 +33,7 @@ class TemplatesUploadsController < ApplicationController
|
||||
|
||||
raise if Rails.env.local?
|
||||
|
||||
redirect_to root_path, alert: 'Unable to upload file'
|
||||
redirect_to root_path, alert: I18n.t('unable_to_update_file')
|
||||
end
|
||||
|
||||
private
|
||||
@@ -55,14 +55,22 @@ class TemplatesUploadsController < ApplicationController
|
||||
tempfile.write(DownloadUtils.call(params[:url]).body)
|
||||
tempfile.rewind
|
||||
|
||||
filename = URI.decode_www_form_component(params[:filename]) if params[:filename].present?
|
||||
filename ||= File.basename(URI.decode_www_form_component(params[:url]))
|
||||
|
||||
file = ActionDispatch::Http::UploadedFile.new(
|
||||
tempfile:,
|
||||
filename: File.basename(
|
||||
URI.decode_www_form_component(params[:filename].presence || params[:url]), '.*'
|
||||
),
|
||||
filename:,
|
||||
type: Marcel::MimeType.for(tempfile)
|
||||
)
|
||||
|
||||
{ files: [file] }
|
||||
end
|
||||
|
||||
def enqueue_template_created_webhooks(template)
|
||||
WebhookUrls.for_account_id(template.account_id, 'template.created').each do |webhook_url|
|
||||
SendTemplateCreatedWebhookRequestJob.perform_async('template_id' => template.id,
|
||||
'webhook_url_id' => webhook_url.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,9 +4,8 @@ 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)
|
||||
@webhook_url = current_account.webhook_urls.first_or_initialize
|
||||
|
||||
authorize!(:manage, @webhook_config)
|
||||
authorize!(:manage, @webhook_url)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -14,12 +14,12 @@ class TimestampServerController < ApplicationController
|
||||
test_timeserver_url(@encrypted_config.value) if @encrypted_config.value.present?
|
||||
|
||||
if @encrypted_config.value.present? ? @encrypted_config.save : @encrypted_config.delete
|
||||
redirect_back fallback_location: settings_notifications_path, notice: 'Changes have been saved'
|
||||
redirect_back fallback_location: settings_notifications_path, notice: I18n.t('changes_have_been_saved')
|
||||
else
|
||||
redirect_back fallback_location: settings_notifications_path, alert: 'Unable to save'
|
||||
redirect_back fallback_location: settings_notifications_path, alert: I18n.t('unable_to_save')
|
||||
end
|
||||
rescue SocketError, TimestampError, OpenSSL::Timestamp::TimestampError
|
||||
redirect_back fallback_location: settings_notifications_path, alert: 'Invalid Timeserver'
|
||||
redirect_back fallback_location: settings_notifications_path, alert: t('invalid_timeserver')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -9,7 +9,7 @@ class UserInitialsController < ApplicationController
|
||||
def update
|
||||
file = params[:file]
|
||||
|
||||
return redirect_to settings_profile_index_path, notice: 'Unable to save initials' if file.blank?
|
||||
return redirect_to settings_profile_index_path, notice: I18n.t('unable_to_save_initials') if file.blank?
|
||||
|
||||
blob = ActiveStorage::Blob.create_and_upload!(io: file.open,
|
||||
filename: file.original_filename,
|
||||
@@ -22,16 +22,16 @@ class UserInitialsController < ApplicationController
|
||||
)
|
||||
|
||||
if @user_config.update(value: attachment.uuid)
|
||||
redirect_to settings_profile_index_path, notice: 'Initials has been saved'
|
||||
redirect_to settings_profile_index_path, notice: I18n.t('initials_has_been_saved')
|
||||
else
|
||||
redirect_to settings_profile_index_path, notice: 'Unable to save initials'
|
||||
redirect_to settings_profile_index_path, notice: I18n.t('unable_to_save_initials')
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
@user_config.destroy
|
||||
|
||||
redirect_to settings_profile_index_path, notice: 'Initials has been removed'
|
||||
redirect_to settings_profile_index_path, notice: I18n.t('initials_has_been_removed')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -9,7 +9,7 @@ class UserSignaturesController < ApplicationController
|
||||
def update
|
||||
file = params[:file]
|
||||
|
||||
return redirect_to settings_profile_index_path, notice: 'Unable to save signature' if file.blank?
|
||||
return redirect_to settings_profile_index_path, notice: I18n.t('unable_to_save_signature') if file.blank?
|
||||
|
||||
blob = ActiveStorage::Blob.create_and_upload!(io: file.open,
|
||||
filename: file.original_filename,
|
||||
@@ -22,16 +22,16 @@ class UserSignaturesController < ApplicationController
|
||||
)
|
||||
|
||||
if @user_config.update(value: attachment.uuid)
|
||||
redirect_to settings_profile_index_path, notice: 'Signature has been saved'
|
||||
redirect_to settings_profile_index_path, notice: I18n.t('signature_has_been_saved')
|
||||
else
|
||||
redirect_to settings_profile_index_path, notice: 'Unable to save signature'
|
||||
redirect_to settings_profile_index_path, notice: I18n.t('unable_to_save_signature')
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
@user_config.destroy
|
||||
|
||||
redirect_to settings_profile_index_path, notice: 'Signature has been removed'
|
||||
redirect_to settings_profile_index_path, notice: I18n.t('signature_has_been_removed')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -9,9 +9,11 @@ class UsersController < ApplicationController
|
||||
def index
|
||||
@users =
|
||||
if params[:status] == 'archived'
|
||||
@users.archived
|
||||
@users.archived.where.not(role: 'integration')
|
||||
elsif params[:status] == 'integration'
|
||||
@users.active.where(role: 'integration')
|
||||
else
|
||||
@users.active
|
||||
@users.active.where.not(role: 'integration')
|
||||
end
|
||||
|
||||
@pagy, @users = pagy(@users.where(account: current_account).order(id: :desc))
|
||||
@@ -32,17 +34,19 @@ class UsersController < ApplicationController
|
||||
@user = existing_user
|
||||
end
|
||||
|
||||
@user.role = User::ADMIN_ROLE unless role_valid?(@user.role)
|
||||
|
||||
if @user.save
|
||||
UserMailer.invitation_email(@user).deliver_later!
|
||||
|
||||
redirect_back fallback_location: settings_users_path, notice: 'User has been invited'
|
||||
redirect_back fallback_location: settings_users_path, notice: I18n.t('user_has_been_invited')
|
||||
else
|
||||
render turbo_stream: turbo_stream.replace(:modal, template: 'users/new'), status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
return redirect_to settings_users_path, notice: 'Unable to update user.' if Docuseal.demo?
|
||||
return redirect_to settings_users_path, notice: I18n.t('unable_to_update_user') if Docuseal.demo?
|
||||
|
||||
attrs = user_params.compact_blank.merge(user_params.slice(:archived_at))
|
||||
attrs.delete(:role) if !role_valid?(attrs[:role]) || current_user == @user
|
||||
@@ -56,7 +60,7 @@ class UsersController < ApplicationController
|
||||
end
|
||||
|
||||
if @user.update(attrs)
|
||||
redirect_back fallback_location: settings_users_path, notice: 'User has been updated'
|
||||
redirect_back fallback_location: settings_users_path, notice: I18n.t('user_has_been_updated')
|
||||
else
|
||||
render turbo_stream: turbo_stream.replace(:modal, template: 'users/edit'), status: :unprocessable_entity
|
||||
end
|
||||
@@ -64,12 +68,12 @@ class UsersController < ApplicationController
|
||||
|
||||
def destroy
|
||||
if Docuseal.demo? || @user.id == current_user.id
|
||||
return redirect_to settings_users_path, notice: 'Unable to remove user'
|
||||
return redirect_to settings_users_path, notice: I18n.t('unable_to_remove_user')
|
||||
end
|
||||
|
||||
@user.update!(archived_at: Time.current)
|
||||
|
||||
redirect_back fallback_location: settings_users_path, notice: 'User has been removed'
|
||||
redirect_back fallback_location: settings_users_path, notice: I18n.t('user_has_been_removed')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -14,6 +14,6 @@ class VerifyPdfSignatureController < ApplicationController
|
||||
render turbo_stream: turbo_stream.replace('result', partial: 'result',
|
||||
locals: { pdfs:, files: params[:files], trusted_certs: })
|
||||
rescue HexaPDF::MalformedPDFError
|
||||
render turbo_stream: turbo_stream.replace('result', html: helpers.tag.div('Invalid PDF', id: 'result'))
|
||||
render turbo_stream: turbo_stream.replace('result', html: helpers.tag.div(I18n.t('invalid_pdf'), id: 'result'))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,37 +1,22 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class WebhookPreferencesController < ApplicationController
|
||||
EVENTS = %w[
|
||||
form.viewed
|
||||
form.started
|
||||
form.completed
|
||||
form.declined
|
||||
template.created
|
||||
template.updated
|
||||
submission.created
|
||||
submission.archived
|
||||
].freeze
|
||||
load_and_authorize_resource :webhook_url, parent: false
|
||||
|
||||
before_action :load_account_config
|
||||
authorize_resource :account_config, parent: false
|
||||
def update
|
||||
webhook_preferences_params[:events].each do |event, val|
|
||||
@webhook_url.events.delete(event) if val == '0'
|
||||
@webhook_url.events.push(event) if val == '1' && @webhook_url.events.exclude?(event)
|
||||
end
|
||||
|
||||
def create
|
||||
@account_config.value[account_config_params[:event]] = account_config_params[:value] == '1'
|
||||
|
||||
@account_config.save!
|
||||
@webhook_url.save!
|
||||
|
||||
head :ok
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_account_config
|
||||
@account_config =
|
||||
current_account.account_configs.find_or_initialize_by(key: AccountConfig::WEBHOOK_PREFERENCES_KEY)
|
||||
@account_config.value ||= {}
|
||||
end
|
||||
|
||||
def account_config_params
|
||||
params.permit(:event, :value)
|
||||
def webhook_preferences_params
|
||||
params.require(:webhook_url).permit(events: {})
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,29 +1,22 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class WebhookSecretController < ApplicationController
|
||||
before_action :load_encrypted_config
|
||||
authorize_resource :encrypted_config, parent: false
|
||||
load_and_authorize_resource :webhook_url, parent: false
|
||||
|
||||
def index; end
|
||||
def show; end
|
||||
|
||||
def create
|
||||
@encrypted_config.assign_attributes(value: {
|
||||
encrypted_config_params[:key] => encrypted_config_params[:value]
|
||||
def update
|
||||
@webhook_url.update!(secret: {
|
||||
webhook_secret_params[:key] => webhook_secret_params[:value]
|
||||
}.compact_blank)
|
||||
|
||||
@encrypted_config.value.present? ? @encrypted_config.save! : @encrypted_config.delete
|
||||
|
||||
redirect_back(fallback_location: settings_webhooks_path, notice: 'Webhook Secret has been saved.')
|
||||
redirect_back(fallback_location: settings_webhook_path(@webhook_url),
|
||||
notice: I18n.t('webhook_secret_has_been_saved'))
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_encrypted_config
|
||||
@encrypted_config =
|
||||
current_account.encrypted_configs.find_or_initialize_by(key: EncryptedConfig::WEBHOOK_SECRET_KEY)
|
||||
end
|
||||
|
||||
def encrypted_config_params
|
||||
params.require(:encrypted_config).permit(value: %i[key value]).fetch(:value, {})
|
||||
def webhook_secret_params
|
||||
params.require(:webhook_url).permit(secret: %i[key value]).fetch(:secret, {})
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,36 +1,60 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class WebhookSettingsController < ApplicationController
|
||||
before_action :load_encrypted_config
|
||||
authorize_resource :encrypted_config, parent: false
|
||||
load_and_authorize_resource :webhook_url, parent: false, only: %i[index show new create update destroy]
|
||||
load_and_authorize_resource :webhook_url, only: %i[resend], id_param: :webhook_id
|
||||
|
||||
def index
|
||||
@webhook_urls = @webhook_urls.order(id: :desc)
|
||||
@webhook_url = @webhook_urls.first_or_initialize
|
||||
|
||||
render @webhook_urls.size > 1 ? 'index' : 'show'
|
||||
end
|
||||
|
||||
def show; end
|
||||
|
||||
def new; end
|
||||
|
||||
def create
|
||||
@encrypted_config.assign_attributes(encrypted_config_params)
|
||||
@webhook_url.save!
|
||||
|
||||
@encrypted_config.value.present? ? @encrypted_config.save! : @encrypted_config.delete
|
||||
|
||||
redirect_back(fallback_location: settings_webhooks_path, notice: 'Webhook URL has been saved.')
|
||||
redirect_to settings_webhooks_path, notice: I18n.t('webhook_url_has_been_saved')
|
||||
end
|
||||
|
||||
def update
|
||||
@webhook_url.update!(update_params)
|
||||
|
||||
redirect_back(fallback_location: settings_webhook_path(@webhook_url),
|
||||
notice: I18n.t('webhook_url_has_been_updated'))
|
||||
end
|
||||
|
||||
def destroy
|
||||
@webhook_url.destroy!
|
||||
|
||||
redirect_to settings_webhooks_path, notice: I18n.t('webhook_url_has_been_deleted')
|
||||
end
|
||||
|
||||
def resend
|
||||
submitter = current_account.submitters.where.not(completed_at: nil).order(:id).last
|
||||
|
||||
SendFormCompletedWebhookRequestJob.perform_async({ 'submitter_id' => submitter.id,
|
||||
'encrypted_config_id' => @encrypted_config.id })
|
||||
if submitter.blank? || @webhook_url.blank?
|
||||
return redirect_back(fallback_location: settings_webhooks_path,
|
||||
alert: I18n.t('unable_to_resend_webhook_request'))
|
||||
end
|
||||
|
||||
redirect_back(fallback_location: settings_webhooks_path, notice: 'Webhook request has been sent.')
|
||||
SendFormCompletedWebhookRequestJob.perform_async('submitter_id' => submitter.id,
|
||||
'webhook_url_id' => @webhook_url.id)
|
||||
|
||||
redirect_back(fallback_location: settings_webhooks_path, notice: I18n.t('webhook_request_has_been_sent'))
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_encrypted_config
|
||||
@encrypted_config =
|
||||
current_account.encrypted_configs.find_or_initialize_by(key: EncryptedConfig::WEBHOOK_URL_KEY)
|
||||
def create_params
|
||||
params.require(:webhook_url).permit(:url, events: []).reverse_merge(events: [])
|
||||
end
|
||||
|
||||
def encrypted_config_params
|
||||
params.require(:encrypted_config).permit(:value)
|
||||
def update_params
|
||||
params.require(:webhook_url).permit(:url)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -29,6 +29,8 @@ import SearchInput from './elements/search_input'
|
||||
import ToggleAttribute from './elements/toggle_attribute'
|
||||
import LinkedInput from './elements/linked_input'
|
||||
import CheckboxGroup from './elements/checkbox_group'
|
||||
import MaskedInput from './elements/masked_input'
|
||||
import SetDateButton from './elements/set_date_button'
|
||||
|
||||
import * as TurboInstantClick from './lib/turbo_instant_click'
|
||||
|
||||
@@ -95,6 +97,8 @@ safeRegisterElement('search-input', SearchInput)
|
||||
safeRegisterElement('toggle-attribute', ToggleAttribute)
|
||||
safeRegisterElement('linked-input', LinkedInput)
|
||||
safeRegisterElement('checkbox-group', CheckboxGroup)
|
||||
safeRegisterElement('masked-input', MaskedInput)
|
||||
safeRegisterElement('set-date-button', SetDateButton)
|
||||
|
||||
safeRegisterElement('template-builder', class extends HTMLElement {
|
||||
connectedCallback () {
|
||||
@@ -109,6 +113,7 @@ safeRegisterElement('template-builder', class extends HTMLElement {
|
||||
backgroundColor: '#faf7f5',
|
||||
locale: this.dataset.locale,
|
||||
withPhone: this.dataset.withPhone === 'true',
|
||||
withVerification: ['true', 'false'].includes(this.dataset.withVerification) ? this.dataset.withVerification === 'true' : null,
|
||||
withLogo: this.dataset.withLogo !== 'false',
|
||||
editable: this.dataset.editable !== 'false',
|
||||
authenticityToken: document.querySelector('meta[name="csrf-token"]')?.content,
|
||||
@@ -150,7 +155,8 @@ safeRegisterElement('import-list', class extends HTMLElement {
|
||||
this.app = createApp(ImportList, {
|
||||
template: JSON.parse(this.dataset.template),
|
||||
multitenant: this.dataset.multitenant === 'true',
|
||||
authenticityToken: document.querySelector('meta[name="csrf-token"]')?.content
|
||||
authenticityToken: document.querySelector('meta[name="csrf-token"]')?.content,
|
||||
i18n: JSON.parse(this.dataset.i18n || '{}')
|
||||
})
|
||||
|
||||
this.app.mount(this.appElem)
|
||||
|
||||
@@ -51,10 +51,6 @@ button[disabled] .enabled {
|
||||
@apply input input-bordered bg-white;
|
||||
}
|
||||
|
||||
.base-input-slim {
|
||||
@apply input input-bordered bg-white h-10;
|
||||
}
|
||||
|
||||
.base-textarea {
|
||||
@apply textarea textarea-bordered bg-white rounded-3xl;
|
||||
}
|
||||
@@ -101,6 +97,23 @@ button[disabled] .enabled {
|
||||
bottom: auto;
|
||||
}
|
||||
|
||||
.tooltip-bottom-start:before {
|
||||
transform: translateX(-31%);
|
||||
top: var(--tooltip-offset);
|
||||
left: 100%;
|
||||
right: auto;
|
||||
bottom: auto;
|
||||
}
|
||||
|
||||
.tooltip-bottom-start:after {
|
||||
transform: translateX(-25%);
|
||||
border-color: transparent transparent var(--tooltip-color) transparent;
|
||||
top: var(--tooltip-tail-offset);
|
||||
left: 50%;
|
||||
right: auto;
|
||||
bottom: auto;
|
||||
}
|
||||
|
||||
.autocomplete {
|
||||
background: white;
|
||||
z-index: 1000;
|
||||
@@ -134,7 +147,3 @@ button[disabled] .enabled {
|
||||
outline-offset: 3px;
|
||||
outline-color: hsl(var(--bc) / 0.2);
|
||||
}
|
||||
|
||||
select:required:invalid {
|
||||
color: gray !important;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,10 @@ window.customElements.define('draw-signature', class extends HTMLElement {
|
||||
|
||||
this.pad = new SignaturePad(this.canvas)
|
||||
|
||||
if (this.dataset.color) {
|
||||
this.pad.penColor = this.dataset.color
|
||||
}
|
||||
|
||||
this.pad.addEventListener('endStroke', () => {
|
||||
this.updateSubmitButtonVisibility()
|
||||
})
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
export default class extends HTMLElement {
|
||||
connectedCallback () {
|
||||
const maskedToken = this.input.value
|
||||
|
||||
this.input.addEventListener('focus', () => {
|
||||
this.input.value = this.dataset.token
|
||||
this.input.select()
|
||||
})
|
||||
|
||||
this.input.addEventListener('focusout', () => {
|
||||
this.input.value = maskedToken
|
||||
})
|
||||
}
|
||||
|
||||
get input () {
|
||||
return this.querySelector('input')
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
export default class extends HTMLElement {
|
||||
connectedCallback () {
|
||||
this.button.addEventListener('click', () => {
|
||||
this.fromInput.value = this.dataset.fromValue || ''
|
||||
this.toInput.value = this.dataset.toValue || ''
|
||||
})
|
||||
}
|
||||
|
||||
get button () {
|
||||
return this.querySelector('button')
|
||||
}
|
||||
|
||||
get fromInput () {
|
||||
return document.getElementById(this.dataset.fromId)
|
||||
}
|
||||
|
||||
get toInput () {
|
||||
return document.getElementById(this.dataset.toId)
|
||||
}
|
||||
}
|
||||
@@ -15,18 +15,21 @@ safeRegisterElement('submission-form', class extends HTMLElement {
|
||||
this.app = createApp(Form, {
|
||||
submitter: JSON.parse(this.dataset.submitter),
|
||||
inviteSubmitters: JSON.parse(this.dataset.inviteSubmitters),
|
||||
schema: JSON.parse(this.dataset.schema),
|
||||
canSendEmail: this.dataset.canSendEmail === 'true',
|
||||
previousSignatureValue: this.dataset.previousSignatureValue,
|
||||
goToLast: this.dataset.goToLast === 'true',
|
||||
isDemo: this.dataset.isDemo === 'true',
|
||||
attribution: this.dataset.attribution !== 'false',
|
||||
scrollPadding: this.dataset.scrollPadding || '-80px',
|
||||
language: this.dataset.language,
|
||||
dryRun: this.dataset.dryRun === 'true',
|
||||
expand: ['true', 'false'].includes(this.dataset.expand) ? this.dataset.expand === 'true' : null,
|
||||
withSignatureId: this.dataset.withSignatureId === 'true',
|
||||
requireSigningReason: this.dataset.requireSigningReason === 'true',
|
||||
withConfetti: this.dataset.withConfetti !== 'false',
|
||||
withDisclosure: this.dataset.withDisclosure === 'true',
|
||||
reuseSignature: this.dataset.reuseSignature !== 'false',
|
||||
withTypedSignature: this.dataset.withTypedSignature !== 'false',
|
||||
authenticityToken: document.querySelector('meta[name="csrf-token"]')?.content,
|
||||
rememberSignature: this.dataset.rememberSignature === 'true',
|
||||
|
||||
@@ -31,10 +31,6 @@ button[disabled] .enabled {
|
||||
display: none;
|
||||
}
|
||||
|
||||
select:required:invalid {
|
||||
@apply text-gray-300;
|
||||
}
|
||||
|
||||
.input-bordered {
|
||||
@apply border-base-content/20;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
class="field-area flex absolute lg:text-base -outline-offset-1"
|
||||
dir="auto"
|
||||
:style="computedStyle"
|
||||
:class="{ 'text-[1.6vw] 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, 'field-area-active outline-red-500 outline-dashed outline-2 z-10': isActive && submittable, 'bg-opacity-40': (isActive || isValueSet) && submittable }"
|
||||
:class="{ 'font-serif': field.preferences?.font === 'Times', 'text-[1.6vw] 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, 'field-area-active outline-red-500 outline-dashed outline-2 z-10': isActive && submittable, 'bg-opacity-40': (isActive || isValueSet) && submittable }"
|
||||
>
|
||||
<div
|
||||
v-if="(!withFieldPlaceholder || !field.name || field.type === 'cells') && !isActive && !isValueSet && field.type !== 'checkbox' && submittable && !area.option_uuid"
|
||||
@@ -29,17 +29,16 @@
|
||||
{{ optionValue(option) }}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ field.name || fieldNames[field.type] }}
|
||||
{{ field.title || field.name || fieldNames[field.type] }}
|
||||
<template v-if="field.type === 'checkbox' && !field.name">
|
||||
{{ fieldIndex + 1 }}
|
||||
</template>
|
||||
<template v-else-if="!field.required && field.type !== 'checkbox'">
|
||||
(optional)
|
||||
({{ t('optional') }})
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
<div
|
||||
v-if="isActive"
|
||||
ref="scrollToElem"
|
||||
class="absolute"
|
||||
:style="{ top: scrollPadding }"
|
||||
@@ -210,7 +209,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { IconTextSize, IconWritingSign, IconCalendarEvent, IconPhoto, IconCheckbox, IconPaperclip, IconSelect, IconCircleDot, IconChecks, IconCheck, IconColumns3, IconPhoneCheck, IconLetterCaseUpper, IconCreditCard, IconRubberStamp, IconSquareNumber1 } from '@tabler/icons-vue'
|
||||
import { IconTextSize, IconWritingSign, IconCalendarEvent, IconPhoto, IconCheckbox, IconPaperclip, IconSelect, IconCircleDot, IconChecks, IconCheck, IconColumns3, IconPhoneCheck, IconLetterCaseUpper, IconCreditCard, IconRubberStamp, IconSquareNumber1, IconId } from '@tabler/icons-vue'
|
||||
|
||||
export default {
|
||||
name: 'FieldArea',
|
||||
@@ -312,7 +311,8 @@ export default {
|
||||
cells: this.t('cells'),
|
||||
stamp: this.t('stamp'),
|
||||
payment: this.t('payment'),
|
||||
phone: this.t('phone')
|
||||
phone: this.t('phone'),
|
||||
verification: this.t('verify_id')
|
||||
}
|
||||
},
|
||||
alignClasses () {
|
||||
@@ -341,7 +341,8 @@ export default {
|
||||
cells: IconColumns3,
|
||||
multiple: IconChecks,
|
||||
phone: IconPhoneCheck,
|
||||
payment: IconCreditCard
|
||||
payment: IconCreditCard,
|
||||
verification: IconId
|
||||
}
|
||||
},
|
||||
image () {
|
||||
@@ -405,7 +406,12 @@ export default {
|
||||
}
|
||||
|
||||
if (this.field.preferences?.font_size) {
|
||||
style.fontSize = this.field.preferences.font_size + 'pt'
|
||||
style.fontSize = `clamp(4pt, 1.6vw, ${parseInt(this.field.preferences.font_size) * 1.23}pt)`
|
||||
style.lineHeight = `clamp(6pt, 2.0vw, ${parseInt(this.field.preferences.font_size) * 1.23 + 3}pt)`
|
||||
}
|
||||
|
||||
if (this.field.preferences?.color) {
|
||||
style.color = this.field.preferences.color
|
||||
}
|
||||
|
||||
return style
|
||||
|
||||
@@ -132,10 +132,18 @@ export default {
|
||||
this.scrollInContainer(areaRef.$el)
|
||||
} else {
|
||||
const targetRect = areaRef.$refs.scrollToElem.getBoundingClientRect()
|
||||
const root = this.$root.$el?.parentNode?.classList?.contains('ds') ? this.$root.$el : document.body
|
||||
const rootRect = root.getBoundingClientRect()
|
||||
const scrollEl = this.scrollEl || window
|
||||
|
||||
let rootRect = {}
|
||||
|
||||
if (this.scrollEl === document.documentElement) {
|
||||
rootRect = this.scrollEl.getBoundingClientRect()
|
||||
} else {
|
||||
const root = this.$root.$el?.parentNode?.classList?.contains('ds') ? this.$root.$el : document.body
|
||||
|
||||
rootRect = root.getBoundingClientRect()
|
||||
}
|
||||
|
||||
scrollEl.scrollTo({ top: targetRect.top - rootRect.top, behavior: 'smooth' })
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<div
|
||||
id="form_completed"
|
||||
class="mx-auto max-w-md flex flex-col"
|
||||
dir="auto"
|
||||
>
|
||||
@@ -10,7 +11,7 @@
|
||||
:height="30"
|
||||
/>
|
||||
<span>
|
||||
{{ completedMessage.title || t('form_has_been_completed') }}
|
||||
{{ completedMessage.title || (hasSignatureFields ? (hasMultipleDocuments ? t('documents_have_been_signed') : t('document_has_been_signed')) : t('form_has_been_completed')) }}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
@@ -75,7 +76,7 @@
|
||||
</a>
|
||||
<a
|
||||
v-if="isDemo"
|
||||
href="https://docuseal.co/sign_up"
|
||||
href="https://docuseal.com/sign_up"
|
||||
class="white-button flex items-center space-x-1 w-full"
|
||||
>
|
||||
<IconLogin />
|
||||
@@ -88,9 +89,9 @@
|
||||
v-if="attribution"
|
||||
class="text-center mt-4"
|
||||
>
|
||||
{{ t('signed_with') }}
|
||||
{{ t('powered_by') }}
|
||||
<a
|
||||
href="https://www.docuseal.co/start"
|
||||
href="https://www.docuseal.com/start"
|
||||
target="_blank"
|
||||
class="underline"
|
||||
>DocuSeal</a> - {{ t('open_source_documents_software') }}
|
||||
@@ -129,6 +130,16 @@ export default {
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
hasSignatureFields: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
hasMultipleDocuments: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
withDownloadButton: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
@@ -212,7 +223,7 @@ export default {
|
||||
this.downloadUrls(urls)
|
||||
}
|
||||
} else {
|
||||
alert('Failed to download files')
|
||||
alert(this.t('failed_to_download_files'))
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
<AppearsOn :field="field" />
|
||||
<div class="text-center">
|
||||
<input
|
||||
:id="field.uuid"
|
||||
ref="input"
|
||||
v-model="value"
|
||||
class="base-input !text-2xl text-center w-full"
|
||||
@@ -45,6 +46,7 @@
|
||||
:name="`values[${field.uuid}]`"
|
||||
@keydown.enter="onEnter"
|
||||
@focus="$emit('focus')"
|
||||
@paste="onPaste"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
@@ -98,6 +100,25 @@ export default {
|
||||
this.$emit('submit')
|
||||
}
|
||||
},
|
||||
onPaste (e) {
|
||||
e.preventDefault()
|
||||
|
||||
let pasteData = e.clipboardData.getData('text').trim()
|
||||
|
||||
if (pasteData.match(/^\d{2}\.\d{2}\.\d{4}$/)) {
|
||||
pasteData = pasteData.split('.').reverse().join('-')
|
||||
}
|
||||
|
||||
const parsedDate = new Date(pasteData)
|
||||
|
||||
if (!isNaN(parsedDate)) {
|
||||
const inputEl = this.$refs.input
|
||||
|
||||
inputEl.valueAsDate = new Date(parsedDate.getTime() - parsedDate.getTimezoneOffset() * 60000)
|
||||
|
||||
inputEl.dispatchEvent(new Event('input', { bubbles: true }))
|
||||
}
|
||||
},
|
||||
setCurrentDate () {
|
||||
const inputEl = this.$refs.input
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<div
|
||||
id="dropzone"
|
||||
class="flex h-32 w-full"
|
||||
@dragover.prevent
|
||||
@drop.prevent="onDropFiles"
|
||||
|
||||
@@ -11,12 +11,13 @@
|
||||
:with-label="!isAnonymousChecboxes && showFieldNames"
|
||||
:current-step="currentStepFields"
|
||||
:scroll-padding="scrollPadding"
|
||||
@focus-step="[saveStep(), currentField.type !== 'checkbox' ? isFormVisible = true : '', goToStep($event, false, true)]"
|
||||
@focus-step="[saveStep(), goToStep($event, false, true), currentField.type !== 'checkbox' ? isFormVisible = true : '']"
|
||||
/>
|
||||
<FieldAreas
|
||||
:steps="readonlyConditionalFields.map((e) => [e])"
|
||||
:values="readonlyConditionalFields.reduce((acc, f) => { acc[f.uuid] = f.default_value; return acc }, {})"
|
||||
:values="readonlyConditionalFields.reduce((acc, f) => { acc[f.uuid] = (values[f.uuid] || f.default_value); return acc }, {})"
|
||||
:submitter="submitter"
|
||||
:attachments-index="attachmentsIndex"
|
||||
:submittable="false"
|
||||
/>
|
||||
<FormulaFieldAreas
|
||||
@@ -24,6 +25,44 @@
|
||||
:fields="formulaFields"
|
||||
:values="values"
|
||||
/>
|
||||
<Teleport
|
||||
v-if="completeButtonToRef"
|
||||
:to="completeButtonToRef"
|
||||
>
|
||||
<span
|
||||
v-if="(emptyValueRequiredStep && ((stepFields.length - 1) !== currentStep || currentStepFields !== emptyValueRequiredStep)) || isCompleted"
|
||||
class="tooltip-left"
|
||||
:class="{ tooltip: !isCompleted }"
|
||||
:data-tip="t('fill_all_required_fields_to_complete')"
|
||||
>
|
||||
<button
|
||||
class="btn btn-sm btn-neutral text-white px-4 w-full flex justify-center btn-disabled pointer-events-auto"
|
||||
@click="[isFormVisible = true, !isCompleted && goToStep(stepFields.indexOf(emptyValueRequiredStep), true, false)]"
|
||||
>
|
||||
{{ t('complete') }}
|
||||
</button>
|
||||
</span>
|
||||
<button
|
||||
v-else
|
||||
id="complete_form_button"
|
||||
class="btn btn-sm btn-neutral text-white px-4 w-full flex justify-center"
|
||||
form="steps_form"
|
||||
type="submit"
|
||||
name="completed"
|
||||
value="true"
|
||||
:disabled="isSubmittingComplete"
|
||||
>
|
||||
<span class="flex items-center">
|
||||
<IconInnerShadowTop
|
||||
v-if="isSubmittingComplete"
|
||||
class="mr-1 animate-spin w-5 h-5"
|
||||
/>
|
||||
<span>
|
||||
{{ t('complete') }}
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</Teleport>
|
||||
<button
|
||||
v-if="!isFormVisible"
|
||||
id="expand_form_button"
|
||||
@@ -38,8 +77,11 @@
|
||||
<template v-else-if="alwaysMinimize">
|
||||
{{ t('next') }}
|
||||
</template>
|
||||
<template v-else-if="isShowContinue">
|
||||
{{ t('continue') }}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ t('submit_form') }}
|
||||
{{ t('start_now') }}
|
||||
</template>
|
||||
<IconArrowsDiagonal
|
||||
class="absolute right-0 mr-4"
|
||||
@@ -59,7 +101,7 @@
|
||||
id="minimize_form_button"
|
||||
class="absolute right-0 mr-2 mt-2 top-0 hidden md:block"
|
||||
:title="t('minimize')"
|
||||
@click.prevent="isFormVisible = false"
|
||||
@click.prevent="minimizeForm"
|
||||
>
|
||||
<IconArrowsDiagonalMinimize2
|
||||
:width="20"
|
||||
@@ -71,6 +113,7 @@
|
||||
>
|
||||
<form
|
||||
v-if="!isCompleted && !isInvite"
|
||||
id="steps_form"
|
||||
ref="form"
|
||||
:action="submitPath"
|
||||
method="post"
|
||||
@@ -112,7 +155,7 @@
|
||||
v-model="values[currentField.uuid]"
|
||||
:show-field-names="showFieldNames"
|
||||
:field="currentField"
|
||||
@submit="submitStep"
|
||||
@submit="!isSubmitting && submitStep()"
|
||||
@focus="scrollIntoField(currentField)"
|
||||
/>
|
||||
<div v-else-if="currentField.type === 'select'">
|
||||
@@ -149,6 +192,7 @@
|
||||
dir="auto"
|
||||
:required="currentField.required"
|
||||
class="select base-input !text-2xl w-full text-center font-normal"
|
||||
:class="{ 'text-gray-300': !values[currentField.uuid] }"
|
||||
:name="`values[${currentField.uuid}]`"
|
||||
@change="values[currentField.uuid] = $event.target.value"
|
||||
@focus="scrollIntoField(currentField)"
|
||||
@@ -156,6 +200,7 @@
|
||||
<option
|
||||
value=""
|
||||
:selected="!values[currentField.uuid]"
|
||||
class="text-gray-300"
|
||||
>
|
||||
{{ t('select_your_option') }}
|
||||
</option>
|
||||
@@ -164,6 +209,7 @@
|
||||
:key="option.uuid"
|
||||
:selected="values[currentField.uuid] == option.value"
|
||||
:value="option.value"
|
||||
class="text-base-content"
|
||||
>
|
||||
{{ option.value }}
|
||||
</option>
|
||||
@@ -201,7 +247,7 @@
|
||||
<span
|
||||
@click="scrollIntoField(currentField)"
|
||||
>
|
||||
{{ 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">{{ submitButtonText }}</span>.
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
@@ -265,7 +311,7 @@
|
||||
>
|
||||
<template v-if="isAnonymousChecboxes || !showFieldNames">
|
||||
<span class="text-xl">
|
||||
{{ t('complete_hightlighted_checkboxes_and_click') }} <span class="font-semibold">{{ stepFields.length === currentStep + 1 ? t('submit') : t('next') }}</span>.
|
||||
{{ t('complete_hightlighted_checkboxes_and_click') }} <span class="font-semibold">{{ submitButtonText }}</span>.
|
||||
</span>
|
||||
<input
|
||||
v-for="field in currentStepFields"
|
||||
@@ -340,7 +386,7 @@
|
||||
:remember-signature="rememberSignature"
|
||||
:attachments-index="attachmentsIndex"
|
||||
:require-signing-reason="requireSigningReason"
|
||||
:button-text="buttonText"
|
||||
:button-text="submitButtonText"
|
||||
:dry-run="dryRun"
|
||||
:with-disclosure="withDisclosure"
|
||||
:with-qr-button="withQrButton"
|
||||
@@ -349,7 +395,7 @@
|
||||
@update:reason="values[currentField.preferences?.reason_field_uuid] = $event"
|
||||
@attached="attachments.push($event)"
|
||||
@start="scrollIntoField(currentField)"
|
||||
@minimize="isFormVisible = false"
|
||||
@minimize="minimizeForm"
|
||||
/>
|
||||
<InitialsStep
|
||||
v-else-if="currentField.type === 'initials'"
|
||||
@@ -365,7 +411,7 @@
|
||||
@attached="attachments.push($event)"
|
||||
@start="scrollIntoField(currentField)"
|
||||
@focus="scrollIntoField(currentField)"
|
||||
@minimize="isFormVisible = false"
|
||||
@minimize="minimizeForm"
|
||||
/>
|
||||
<AttachmentStep
|
||||
v-else-if="currentField.type === 'file'"
|
||||
@@ -389,7 +435,7 @@
|
||||
:default-value="submitter.phone"
|
||||
:submitter-slug="submitterSlug"
|
||||
@focus="scrollIntoField(currentField)"
|
||||
@submit="submitStep"
|
||||
@submit="!isSubmitting && submitStep()"
|
||||
/>
|
||||
<PaymentStep
|
||||
v-else-if="currentField.type === 'payment'"
|
||||
@@ -401,11 +447,24 @@
|
||||
:values="values"
|
||||
@attached="attachments.push($event)"
|
||||
@focus="scrollIntoField(currentField)"
|
||||
@submit="submitStep"
|
||||
@submit="!isSubmitting && submitStep()"
|
||||
/>
|
||||
<VerificationStep
|
||||
v-else-if="currentField.type === 'verification'"
|
||||
ref="currentStep"
|
||||
:key="currentField.uuid"
|
||||
:locale="language?.toLowerCase() || browserLanguage"
|
||||
:submitter="submitter"
|
||||
:empty-value-required-step="emptyValueRequiredStep"
|
||||
:field="currentField"
|
||||
:submitter-slug="submitterSlug"
|
||||
:values="values"
|
||||
@focus="scrollIntoField(currentField)"
|
||||
@submit="!isSubmitting && submitStep()"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="currentField.type !== 'payment' || submittedValues[currentField.uuid]"
|
||||
v-if="(currentField.type !== 'payment' && currentField.type !== 'verification') || submittedValues[currentField.uuid]"
|
||||
:class="currentField.type === 'signature' ? 'mt-2' : 'mt-6 md:mt-8'"
|
||||
>
|
||||
<button
|
||||
@@ -421,7 +480,7 @@
|
||||
class="mr-1 animate-spin"
|
||||
/>
|
||||
<span>
|
||||
{{ buttonText }}
|
||||
{{ submitButtonText }}
|
||||
</span><span
|
||||
v-if="isSubmitting"
|
||||
class="w-6 flex justify-start mr-1"
|
||||
@@ -449,6 +508,8 @@
|
||||
v-else
|
||||
:is-demo="isDemo"
|
||||
:attribution="attribution"
|
||||
:has-signature-fields="stepFields.some((fields) => fields.some((f) => ['signature', 'initials'].includes(f.type)))"
|
||||
:has-multiple-documents="hasMultipleDocuments"
|
||||
:completed-button="completedRedirectUrl ? {} : completedButton"
|
||||
:completed-message="completedRedirectUrl ? {} : completedMessage"
|
||||
:with-send-copy-button="withSendCopyButton && !completedRedirectUrl"
|
||||
@@ -490,6 +551,7 @@ import AttachmentStep from './attachment_step'
|
||||
import MultiSelectStep from './multi_select_step'
|
||||
import PhoneStep from './phone_step'
|
||||
import PaymentStep from './payment_step'
|
||||
import VerificationStep from './verification_step'
|
||||
import TextStep from './text_step'
|
||||
import NumberStep from './number_step'
|
||||
import DateStep from './date_step'
|
||||
@@ -532,6 +594,7 @@ export default {
|
||||
IconWritingSign,
|
||||
AttachmentStep,
|
||||
InitialsStep,
|
||||
VerificationStep,
|
||||
InviteForm,
|
||||
MultiSelectStep,
|
||||
IconInnerShadowTop,
|
||||
@@ -584,6 +647,16 @@ export default {
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
completeButtonToRef: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
schema: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => []
|
||||
},
|
||||
attachments: {
|
||||
type: Array,
|
||||
required: false,
|
||||
@@ -759,10 +832,12 @@ export default {
|
||||
isFormVisible: this.expand !== false,
|
||||
showFillAllRequiredFields: false,
|
||||
currentStep: 0,
|
||||
isShowContinue: false,
|
||||
enableScrollIntoField: true,
|
||||
phoneVerifiedValues: {},
|
||||
orientation: screen?.orientation?.type,
|
||||
isSubmitting: false,
|
||||
isSubmittingComplete: false,
|
||||
submittedValues: {},
|
||||
recalculateButtonDisabledKey: ''
|
||||
}
|
||||
@@ -771,9 +846,37 @@ export default {
|
||||
isMobile () {
|
||||
return /android|iphone|ipad/i.test(navigator.userAgent)
|
||||
},
|
||||
buttonText () {
|
||||
if (this.alwaysMinimize || this.stepFields.length === this.currentStep + 1) {
|
||||
attachmentConditionsIndex () {
|
||||
return this.schema.reduce((acc, item) => {
|
||||
if (item.conditions?.length) {
|
||||
if (item.conditions.every((c) => this.fieldsUuidIndex[c.field_uuid])) {
|
||||
acc[item.attachment_uuid] = this.checkFieldConditions(item)
|
||||
} else {
|
||||
acc[item.attachment_uuid] = true
|
||||
}
|
||||
} else {
|
||||
acc[item.attachment_uuid] = true
|
||||
}
|
||||
|
||||
return acc
|
||||
}, {})
|
||||
},
|
||||
emptyValueRequiredStep () {
|
||||
return this.stepFields.find((fields, index) => {
|
||||
return fields.some((f) => {
|
||||
return f.required && isEmpty(this.values[f.uuid])
|
||||
})
|
||||
})
|
||||
},
|
||||
submitButtonText () {
|
||||
if (this.alwaysMinimize) {
|
||||
return this.t('submit')
|
||||
} else if (this.stepFields.length === this.currentStep + 1) {
|
||||
if (this.currentField.type === 'signature') {
|
||||
return this.t('sign_and_complete')
|
||||
} else {
|
||||
return this.t('complete')
|
||||
}
|
||||
} else {
|
||||
return this.t('next')
|
||||
}
|
||||
@@ -781,6 +884,18 @@ export default {
|
||||
alwaysMinimize () {
|
||||
return this.minimize || (this.orientation?.includes('landscape') && this.isMobile && parseInt(window.innerHeight) < 550)
|
||||
},
|
||||
hasMultipleDocuments () {
|
||||
return Object.keys(
|
||||
this.stepFields.reduce((acc, fields) => {
|
||||
fields.forEach((f) => {
|
||||
f.areas?.forEach((a) => {
|
||||
acc[a.attachment_uuid] = 1
|
||||
})
|
||||
})
|
||||
return acc
|
||||
}, {})
|
||||
).filter(Boolean).length > 1
|
||||
},
|
||||
currentStepFields () {
|
||||
return this.stepFields[this.currentStep] || []
|
||||
},
|
||||
@@ -826,13 +941,29 @@ export default {
|
||||
return this.currentStepFields[0]
|
||||
},
|
||||
readonlyConditionalFields () {
|
||||
return this.fields.filter((f) => f.readonly && f.conditions?.length && this.checkFieldConditions(f))
|
||||
return this.fields.filter((f) => f.readonly && f.conditions?.length && this.checkFieldConditions(f) && this.checkFieldDocumentsConditions(f))
|
||||
},
|
||||
stepFields () {
|
||||
return this.fields.filter((f) => !f.readonly).reduce((acc, f) => {
|
||||
const verificationFields = []
|
||||
|
||||
const sortedFields = this.fields.reduce((acc, f) => {
|
||||
if (f.type === 'verification') {
|
||||
verificationFields.push(f)
|
||||
} else if (!f.readonly) {
|
||||
acc.push(f)
|
||||
}
|
||||
|
||||
return acc
|
||||
}, [])
|
||||
|
||||
if (verificationFields.length) {
|
||||
sortedFields.push(verificationFields.pop())
|
||||
}
|
||||
|
||||
return sortedFields.reduce((acc, f) => {
|
||||
const prevStep = acc[acc.length - 1]
|
||||
|
||||
if (this.checkFieldConditions(f)) {
|
||||
if (this.checkFieldConditions(f) && this.checkFieldDocumentsConditions(f)) {
|
||||
if (f.type === 'checkbox' && Array.isArray(prevStep) && prevStep[0].type === 'checkbox' && !f.description) {
|
||||
prevStep.push(f)
|
||||
} else {
|
||||
@@ -865,6 +996,23 @@ export default {
|
||||
if (isEmpty(value) && this.currentStep > 0) {
|
||||
this.currentStep -= 1
|
||||
}
|
||||
},
|
||||
attachmentConditionsIndex: {
|
||||
deep: true,
|
||||
immediate: true,
|
||||
handler (value) {
|
||||
this.$nextTick(() => {
|
||||
const root = this.$root.$el.parentNode.getRootNode()
|
||||
|
||||
for (const key in value) {
|
||||
const doc = root.querySelector(`[id="document-${key}"`)
|
||||
|
||||
if (doc) {
|
||||
doc.classList.toggle('hidden', !value[key])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
beforeUnmount () {
|
||||
@@ -873,7 +1021,7 @@ export default {
|
||||
mounted () {
|
||||
this.submittedValues = JSON.parse(JSON.stringify(this.values))
|
||||
|
||||
screen?.orientation.addEventListener('change', this.onOrientationChange)
|
||||
screen?.orientation?.addEventListener('change', this.onOrientationChange)
|
||||
|
||||
this.fields.forEach((field) => {
|
||||
if (field.default_value && !field.readonly) {
|
||||
@@ -905,11 +1053,11 @@ export default {
|
||||
}
|
||||
|
||||
if (document.body?.clientWidth >= 768 && this.expand !== true && ['signature', 'initials', 'file', 'image'].includes(this.currentField?.type)) {
|
||||
this.isFormVisible = false
|
||||
this.minimizeForm()
|
||||
}
|
||||
|
||||
if (this.alwaysMinimize) {
|
||||
this.isFormVisible = false
|
||||
this.minimizeForm()
|
||||
}
|
||||
|
||||
if (/iPhone|iPad|iPod/i.test(navigator.userAgent)) {
|
||||
@@ -946,33 +1094,57 @@ export default {
|
||||
onOrientationChange (event) {
|
||||
this.orientation = event.target.type
|
||||
},
|
||||
checkFieldDocumentsConditions (field) {
|
||||
if (field.areas?.length) {
|
||||
return field.areas.some((area) => {
|
||||
return this.attachmentConditionsIndex[area.attachment_uuid]
|
||||
})
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
},
|
||||
checkFieldConditions (field) {
|
||||
if (field.conditions?.length) {
|
||||
return field.conditions.reduce((acc, c) => {
|
||||
const field = this.fieldsUuidIndex[c.field_uuid]
|
||||
|
||||
if (['not_empty', 'checked', 'equal', 'contains'].includes(c.action) && field && !this.checkFieldConditions(field)) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (['empty', 'unchecked'].includes(c.action)) {
|
||||
return acc && isEmpty(this.values[c.field_uuid])
|
||||
} else if (['not_empty', 'checked'].includes(c.action)) {
|
||||
return acc && !isEmpty(this.values[c.field_uuid])
|
||||
} else if (['equal', 'contains'].includes(c.action) && field) {
|
||||
const option = field.options.find((o) => o.uuid === c.value)
|
||||
const values = [this.values[c.field_uuid]].flat()
|
||||
|
||||
return acc && values.includes(this.optionValue(option, field.options.indexOf(option)))
|
||||
} else if (['not_equal', 'does_not_contain'].includes(c.action) && field) {
|
||||
const option = field.options.find((o) => o.uuid === c.value)
|
||||
const values = [this.values[c.field_uuid]].flat()
|
||||
|
||||
return acc && !values.includes(this.optionValue(option, field.options.indexOf(option)))
|
||||
const result = field.conditions.reduce((acc, cond) => {
|
||||
if (cond.operation === 'or') {
|
||||
acc.push(acc.pop() || this.checkFieldCondition(cond))
|
||||
} else {
|
||||
return acc
|
||||
acc.push(this.checkFieldCondition(cond))
|
||||
}
|
||||
}, true)
|
||||
|
||||
return acc
|
||||
}, [])
|
||||
|
||||
return !result.includes(false)
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
},
|
||||
checkFieldCondition (condition) {
|
||||
const field = this.fieldsUuidIndex[condition.field_uuid]
|
||||
|
||||
if (['not_empty', 'checked', 'equal', 'contains'].includes(condition.action) && field && !this.checkFieldConditions(field)) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (['empty', 'unchecked'].includes(condition.action)) {
|
||||
return isEmpty(this.values[condition.field_uuid])
|
||||
} else if (['not_empty', 'checked'].includes(condition.action)) {
|
||||
return !isEmpty(this.values[condition.field_uuid])
|
||||
} else if (['equal', 'contains'].includes(condition.action) && field) {
|
||||
if (field.options) {
|
||||
const option = field.options.find((o) => o.uuid === condition.value)
|
||||
const values = [this.values[condition.field_uuid]].flat()
|
||||
|
||||
return values.includes(this.optionValue(option, field.options.indexOf(option)))
|
||||
} else {
|
||||
return [this.values[condition.field_uuid]].flat().includes(condition.value)
|
||||
}
|
||||
} else if (['not_equal', 'does_not_contain'].includes(condition.action) && field) {
|
||||
const option = field.options.find((o) => o.uuid === condition.value)
|
||||
const values = [this.values[condition.field_uuid]].flat()
|
||||
|
||||
return !values.includes(this.optionValue(option, field.options.indexOf(option)))
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
@@ -1080,6 +1252,10 @@ export default {
|
||||
const currentFieldUuids = this.currentStepFields.map((f) => f.uuid)
|
||||
const currentFieldType = this.currentField.type
|
||||
|
||||
if (!formData && !this.$refs.form.checkValidity()) {
|
||||
return
|
||||
}
|
||||
|
||||
if (this.dryRun) {
|
||||
currentFieldUuids.forEach((fieldUuid) => {
|
||||
this.submittedValues[fieldUuid] = this.values[fieldUuid]
|
||||
@@ -1115,16 +1291,24 @@ export default {
|
||||
scrollIntoArea (area) {
|
||||
return this.$refs.areas.scrollIntoArea(area)
|
||||
},
|
||||
async submitStep () {
|
||||
async submitStep (e) {
|
||||
this.isSubmitting = true
|
||||
|
||||
const stepPromise = ['signature', 'phone', 'initials', 'payment'].includes(this.currentField.type)
|
||||
const forceComplete = e?.submitter?.getAttribute('name') === 'completed'
|
||||
|
||||
if (forceComplete) {
|
||||
this.isSubmittingComplete = true
|
||||
}
|
||||
|
||||
const submitStep = this.currentStep
|
||||
|
||||
const stepPromise = ['signature', 'phone', 'initials', 'payment', 'verification'].includes(this.currentField.type)
|
||||
? this.$refs.currentStep.submit
|
||||
: () => Promise.resolve({})
|
||||
|
||||
stepPromise().then(async () => {
|
||||
const emptyRequiredField = this.stepFields.find((fields, index) => {
|
||||
if (index >= this.currentStep) {
|
||||
if (forceComplete ? index === submitStep : index >= submitStep) {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -1134,7 +1318,7 @@ export default {
|
||||
})
|
||||
|
||||
const formData = new FormData(this.$refs.form)
|
||||
const isLastStep = this.currentStep === this.stepFields.length - 1
|
||||
const isLastStep = (submitStep === this.stepFields.length - 1) || forceComplete
|
||||
|
||||
if (isLastStep && !emptyRequiredField && !this.inviteSubmitters.length) {
|
||||
formData.append('completed', 'true')
|
||||
@@ -1152,22 +1336,22 @@ export default {
|
||||
if (response.status === 422 || response.status === 500) {
|
||||
const data = await response.json()
|
||||
|
||||
const i18nError = data.error ? this.t(data.error.replace(/\s+/g, '_').toLowerCase()) : ''
|
||||
if (data.error) {
|
||||
const i18nKey = data.error.replace(/\s+/g, '_').toLowerCase()
|
||||
|
||||
alert(i18nError !== data.error ? i18nError : (data.error || this.t('value_is_invalid')))
|
||||
alert(this.t(i18nKey) !== i18nKey ? this.t(i18nKey) : data.error)
|
||||
} else {
|
||||
alert(this.t('value_is_invalid'))
|
||||
}
|
||||
|
||||
return Promise.reject(new Error(data.error))
|
||||
}
|
||||
|
||||
if (isLastStep) {
|
||||
this.isSecondWalkthrough = true
|
||||
}
|
||||
|
||||
const nextStep = (isLastStep && emptyRequiredField) || this.stepFields[this.currentStep + 1]
|
||||
const nextStep = (isLastStep && emptyRequiredField) || (forceComplete ? null : this.stepFields[submitStep + 1])
|
||||
|
||||
if (nextStep) {
|
||||
if (this.alwaysMinimize) {
|
||||
this.isFormVisible = false
|
||||
this.minimizeForm()
|
||||
}
|
||||
|
||||
this.goToStep(this.stepFields.indexOf(nextStep), this.autoscrollFields)
|
||||
@@ -1184,21 +1368,27 @@ export default {
|
||||
console.error(error)
|
||||
}).finally(() => {
|
||||
this.isSubmitting = false
|
||||
this.isSubmittingComplete = false
|
||||
})
|
||||
}).catch(error => {
|
||||
if (error?.message === 'Image too small') {
|
||||
alert('Signature is too small - please redraw.')
|
||||
alert(this.t('signature_is_too_small_please_redraw'))
|
||||
} else {
|
||||
console.log(error)
|
||||
}
|
||||
}).finally(() => {
|
||||
this.isSubmitting = false
|
||||
this.isSubmittingComplete = false
|
||||
})
|
||||
},
|
||||
minimizeForm () {
|
||||
this.isFormVisible = false
|
||||
this.isShowContinue = true
|
||||
},
|
||||
async performComplete (resp) {
|
||||
this.isCompleted = true
|
||||
|
||||
if (resp) {
|
||||
if (resp?.text) {
|
||||
const respData = await resp.text()
|
||||
|
||||
if (respData) {
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
const en = {
|
||||
complete_all_required_fields_to_proceed_with_identity_verification: 'Complete all required fields to proceed with identity verification.',
|
||||
verify_id: 'Verify ID',
|
||||
identity_verification: 'Identity verification',
|
||||
complete: 'Complete',
|
||||
fill_all_required_fields_to_complete: 'Fill all required fields to complete',
|
||||
sign_and_complete: 'Sign and Complete',
|
||||
text: 'Text',
|
||||
by_clicking_you_agree_to_the: 'By clicking "{button}", you agree to the',
|
||||
electronic_signature_disclosure: 'Electronic Signature Disclosure',
|
||||
@@ -38,7 +44,8 @@ const en = {
|
||||
minimize: 'Minimize',
|
||||
payment: 'Payment',
|
||||
phone: 'Phone',
|
||||
submit_form: 'Submit Form',
|
||||
start_now: 'Start Now',
|
||||
continue: 'Continue',
|
||||
sign_now: 'Sign Now',
|
||||
type_here_: 'Type here...',
|
||||
optional: 'optional',
|
||||
@@ -59,9 +66,11 @@ const en = {
|
||||
type_signature_here: 'Type signature here',
|
||||
type_initial_here: 'Type initials here',
|
||||
form_has_been_completed: 'Form has been completed!',
|
||||
document_has_been_signed: 'Document has been signed!',
|
||||
documents_have_been_signed: 'Documents have been signed!',
|
||||
create_a_free_account: 'Create a Free Account',
|
||||
signed_with: 'Signed with',
|
||||
please_check_the_box_to_continue: 'Please check the box to continue',
|
||||
powered_by: 'Powered by',
|
||||
please_check_the_box_to_continue: 'Please check the box to continue.',
|
||||
open_source_documents_software: 'open source documents software',
|
||||
verified_phone_number: 'Verify Phone Number',
|
||||
use_international_format: 'Use international format: +1xxx',
|
||||
@@ -83,10 +92,17 @@ const en = {
|
||||
pay_with_strip: 'Pay with Stripe',
|
||||
reupload: 'Reupload',
|
||||
upload: 'Upload',
|
||||
files: 'Files'
|
||||
files: 'Files',
|
||||
signature_is_too_small_please_redraw: 'Signature is too small. Please redraw.'
|
||||
}
|
||||
|
||||
const es = {
|
||||
complete_all_required_fields_to_proceed_with_identity_verification: 'Complete todos los campos requeridos para continuar con la verificación de identidad.',
|
||||
verify_id: 'Verificar ID',
|
||||
identity_verification: 'Verificación de identidad',
|
||||
complete: 'Completar',
|
||||
fill_all_required_fields_to_complete: 'Complete todos los campos requeridos para finalizar',
|
||||
sign_and_complete: 'Firmar y Completar',
|
||||
invite: 'Invitar',
|
||||
email: 'Correo electrónico',
|
||||
approved: 'Aprobado',
|
||||
@@ -126,7 +142,8 @@ const es = {
|
||||
payment: 'Pago',
|
||||
phone: 'Teléfono',
|
||||
take_photo: 'Tomar foto',
|
||||
submit_form: 'Enviar Formulario',
|
||||
start_now: 'Comenzar ahora',
|
||||
continue: 'Continuar',
|
||||
sign_now: 'Firmar ahora',
|
||||
type_here_: 'Escribe aquí...',
|
||||
optional: 'opcional',
|
||||
@@ -146,9 +163,11 @@ const es = {
|
||||
type_signature_here: 'Escribe la firma aquí',
|
||||
type_initial_here: 'Escribe las iniciales aquí',
|
||||
form_has_been_completed: '¡El formulario ha sido completado!',
|
||||
document_has_been_signed: '¡El documento ha sido firmado!',
|
||||
documents_have_been_signed: '¡Los documentos han sido firmados!',
|
||||
create_a_free_account: 'Crear una Cuenta Gratuita',
|
||||
signed_with: 'Firmado con',
|
||||
please_check_the_box_to_continue: 'Por favor marque la casilla para continuar',
|
||||
powered_by: 'Desarrollado por',
|
||||
please_check_the_box_to_continue: 'Por favor marque la casilla para continuar.',
|
||||
open_source_documents_software: 'software de documentos de código abierto',
|
||||
verified_phone_number: 'Verificar número de teléfono',
|
||||
use_international_format: 'Usar formato internacional: +1xxx',
|
||||
@@ -170,10 +189,17 @@ const es = {
|
||||
pay_with_strip: 'Pagar con Stripe',
|
||||
reupload: 'Volver a subir',
|
||||
upload: 'Subir',
|
||||
files: 'Archivos'
|
||||
files: 'Archivos',
|
||||
signature_is_too_small_please_redraw: 'La firma es demasiado pequeña. Por favor, dibújala de nuevo.'
|
||||
}
|
||||
|
||||
const it = {
|
||||
complete_all_required_fields_to_proceed_with_identity_verification: "Compila tutti i campi obbligatori per procedere con la verifica dell'identità.",
|
||||
verify_id: 'Verifica ID',
|
||||
identity_verification: "Verifica dell'identità",
|
||||
complete: 'Completa',
|
||||
fill_all_required_fields_to_complete: 'Compila tutti i campi obbligatori per completare',
|
||||
sign_and_complete: 'Firma e Completa',
|
||||
invite: 'Invita',
|
||||
email: 'Email',
|
||||
approved: 'Approvato',
|
||||
@@ -204,7 +230,7 @@ const it = {
|
||||
pay: 'Pagamento',
|
||||
file: 'File',
|
||||
select: 'Seleziona',
|
||||
checkbox: 'Casella di controllo',
|
||||
checkbox: 'Checkbox',
|
||||
multiple: 'Multiplo',
|
||||
radio: 'Radio',
|
||||
cells: 'Celle',
|
||||
@@ -212,7 +238,8 @@ const it = {
|
||||
payment: 'Pagamento',
|
||||
number_phone_is_invalid: '{number} telefono non è valido',
|
||||
phone: 'Telefono',
|
||||
submit_form: 'Invia Modulo',
|
||||
start_now: 'Inizia ora',
|
||||
continue: 'Continua',
|
||||
sign_now: 'Firma ora',
|
||||
type_here_: 'Digita qui...',
|
||||
optional: 'opzionale',
|
||||
@@ -233,9 +260,11 @@ const it = {
|
||||
type_signature_here: 'Scrivi la firma qui',
|
||||
type_initial_here: 'Scrivi le iniziali qui',
|
||||
form_has_been_completed: 'Il modulo è stato completato!',
|
||||
document_has_been_signed: 'Il documento è stato firmato!',
|
||||
documents_have_been_signed: 'I documenti sono stati firmati!',
|
||||
create_a_free_account: 'Crea un Account Gratuito',
|
||||
signed_with: 'Firmato con',
|
||||
please_check_the_box_to_continue: 'Si prega di spuntare la casella per continuare',
|
||||
powered_by: 'Desarrollado por',
|
||||
please_check_the_box_to_continue: 'Si prega di spuntare la casella per continuare.',
|
||||
open_source_documents_software: 'software di documenti open source',
|
||||
verified_phone_number: 'Verifica numero di telefono',
|
||||
use_international_format: 'Usa formato internazionale: +1xxx',
|
||||
@@ -257,10 +286,17 @@ const it = {
|
||||
pay_with_strip: 'Paga con Stripe',
|
||||
reupload: 'Ricarica',
|
||||
upload: 'Carica',
|
||||
files: 'File'
|
||||
files: 'File',
|
||||
signature_is_too_small_please_redraw: 'La firma è troppo piccola. Ridisegnala per favore.'
|
||||
}
|
||||
|
||||
const de = {
|
||||
complete_all_required_fields_to_proceed_with_identity_verification: 'Vervollständigen Sie alle erforderlichen Felder, um mit der Identitätsverifizierung fortzufahren.',
|
||||
verify_id: 'ID überprüfen',
|
||||
identity_verification: 'Identitätsüberprüfung',
|
||||
complete: 'Abschließen',
|
||||
fill_all_required_fields_to_complete: 'Alle erforderlichen Felder ausfüllen, um abzuschließen',
|
||||
sign_and_complete: 'Signieren und Abschließen',
|
||||
invite: 'Einladen',
|
||||
email: 'E-Mail',
|
||||
approved: 'Genehmigt',
|
||||
@@ -299,7 +335,8 @@ const de = {
|
||||
payment: 'Zahlung',
|
||||
number_phone_is_invalid: '{number} Telefonnummer ist ungültig',
|
||||
phone: 'Telefon',
|
||||
submit_form: 'Formular absenden',
|
||||
start_now: 'Jetzt starten',
|
||||
continue: 'Fortsetzen',
|
||||
sign_now: 'Jetzt unterschreiben',
|
||||
type_here_: 'Hier eingeben...',
|
||||
optional: 'optional',
|
||||
@@ -320,9 +357,11 @@ const de = {
|
||||
type_signature_here: 'Unterschrift hier eingeben',
|
||||
type_initial_here: 'Initialen hier eingeben',
|
||||
form_has_been_completed: 'Formular wurde ausgefüllt!',
|
||||
document_has_been_signed: 'Dokument wurde unterschrieben!',
|
||||
documents_have_been_signed: 'Dokumente wurden unterschrieben!',
|
||||
create_a_free_account: 'Kostenloses Konto erstellen',
|
||||
signed_with: 'Unterschrieben mit',
|
||||
please_check_the_box_to_continue: 'Bitte setzen Sie das Häkchen, um fortzufahren',
|
||||
powered_by: 'Bereitgestellt von',
|
||||
please_check_the_box_to_continue: 'Bitte setzen Sie das Häkchen, um fortzufahren.',
|
||||
open_source_documents_software: 'Open-Source-Dokumentensoftware',
|
||||
verified_phone_number: 'Telefonnummer überprüfen',
|
||||
use_international_format: 'Internationales Format verwenden: +1xxx',
|
||||
@@ -344,10 +383,17 @@ const de = {
|
||||
pay_with_strip: 'Mit Stripe bezahlen',
|
||||
reupload: 'Erneut hochladen',
|
||||
upload: 'Hochladen',
|
||||
files: 'Dateien'
|
||||
files: 'Dateien',
|
||||
signature_is_too_small_please_redraw: 'Die Unterschrift ist zu klein. Bitte erneut zeichnen.'
|
||||
}
|
||||
|
||||
const fr = {
|
||||
complete_all_required_fields_to_proceed_with_identity_verification: "Veuillez remplir tous les champs obligatoires pour continuer la vérification de l'identité.",
|
||||
verif_id: "Vérification de l'ID",
|
||||
verif_identite: "Vérification de l'identité",
|
||||
complete: 'Terminer',
|
||||
fill_all_required_fields_to_complete: 'Veuillez remplir tous les champs obligatoires pour compléter',
|
||||
sign_and_complete: 'Signer et Terminer',
|
||||
invite: 'Inviter',
|
||||
email: 'Courriel',
|
||||
approved: 'Approuvé',
|
||||
@@ -386,7 +432,8 @@ const fr = {
|
||||
payment: 'Paiement',
|
||||
number_phone_is_invalid: '{number} téléphone est invalide',
|
||||
phone: 'Téléphone',
|
||||
submit_form: 'Envoyer le Formulaire',
|
||||
start_now: 'Commencer maintenant',
|
||||
continue: 'Continuer',
|
||||
sign_now: 'Signer maintenant',
|
||||
type_here_: 'Tapez ici...',
|
||||
optional: 'facultatif',
|
||||
@@ -407,9 +454,11 @@ const fr = {
|
||||
type_signature_here: 'Tapez la signature ici',
|
||||
type_initial_here: 'Tapez les initiales ici',
|
||||
form_has_been_completed: 'Le formulaire a été complété !',
|
||||
document_has_been_signed: 'Le document a été signé!',
|
||||
documents_have_been_signed: 'Les documents ont été signés!',
|
||||
create_a_free_account: 'Créer un Compte Gratuit',
|
||||
signed_with: 'Signé avec',
|
||||
please_check_the_box_to_continue: 'Veuillez cocher la case pour continuer',
|
||||
powered_by: 'Propulsé par',
|
||||
please_check_the_box_to_continue: 'Veuillez cocher la case pour continuer.',
|
||||
open_source_documents_software: 'logiciel de documents open source',
|
||||
verified_phone_number: 'Vérifier le numéro de téléphone',
|
||||
use_international_format: 'Utiliser le format international : +1xxx',
|
||||
@@ -431,10 +480,17 @@ const fr = {
|
||||
pay_with_strip: 'Paiement avec Stripe',
|
||||
reupload: 'Recharger',
|
||||
upload: 'Télécharger',
|
||||
files: 'Fichiers'
|
||||
files: 'Fichiers',
|
||||
signature_is_too_small_please_redraw: 'La signature est trop petite. Veuillez la redessiner.'
|
||||
}
|
||||
|
||||
const pl = {
|
||||
complete_all_required_fields_to_proceed_with_identity_verification: 'Uzupełnij wszystkie wymagane pola, aby kontynuować weryfikację tożsamości.',
|
||||
verify_id: 'Zweryfikuj ID',
|
||||
identity_verification: 'Weryfikacja tożsamości',
|
||||
complete: 'Zakończ',
|
||||
fill_all_required_fields_to_complete: 'Uzupełnij wszystkie wymagane pola, aby zakończyć',
|
||||
sign_and_complete: 'Podpisz i zakończ',
|
||||
invite: 'Zaproś',
|
||||
email: 'E-mail',
|
||||
approved: 'Zaakceptowany',
|
||||
@@ -473,7 +529,8 @@ const pl = {
|
||||
payment: 'Płatność',
|
||||
number_phone_is_invalid: '{number} telefon jest nieprawidłowy',
|
||||
phone: 'Telefon',
|
||||
submit_form: 'Wyślij Formularz',
|
||||
start_now: 'Zacznij teraz',
|
||||
continue: 'Kontynuuj',
|
||||
sign_now: 'Podpisz teraz',
|
||||
type_here_: 'Wpisz tutaj...',
|
||||
optional: 'opcjonalny',
|
||||
@@ -494,9 +551,11 @@ const pl = {
|
||||
type_signature_here: 'Wpisz podpis tutaj',
|
||||
type_initial_here: 'Wpisz inicjały tutaj',
|
||||
form_has_been_completed: 'Formularz został wypełniony!',
|
||||
document_has_been_signed: 'Dokument został podpisany!',
|
||||
documents_have_been_signed: 'Dokumenty zostały podpisane!',
|
||||
create_a_free_account: 'Utwórz darmowe konto',
|
||||
signed_with: 'Podpisane za pomocą',
|
||||
please_check_the_box_to_continue: 'Proszę zaznaczyć pole, aby kontynuować',
|
||||
powered_by: 'Napędzany przez',
|
||||
please_check_the_box_to_continue: 'Proszę zaznaczyć pole, aby kontynuować.',
|
||||
open_source_documents_software: 'oprogramowanie do dokumentów open source',
|
||||
verified_phone_number: 'Zweryfikuj numer telefonu',
|
||||
use_international_format: 'Użyj międzynarodowego formatu: +1xxx',
|
||||
@@ -518,10 +577,17 @@ const pl = {
|
||||
pay_with_strip: 'Płatność za pomocą Stripe',
|
||||
reupload: 'Ponowne przesłanie',
|
||||
upload: 'Przesyłanie',
|
||||
files: 'Pliki'
|
||||
files: 'Pliki',
|
||||
signature_is_too_small_please_redraw: 'Podpis jest zbyt mały. Proszę narysować go ponownie.'
|
||||
}
|
||||
|
||||
const uk = {
|
||||
complete_all_required_fields_to_proceed_with_identity_verification: "Заповніть всі обов'язкові поля, щоб продовжити перевірку особи.",
|
||||
verify_id: 'Підтвердження ідентичності',
|
||||
identity_verification: 'Ідентифікація особи',
|
||||
complete: 'Завершити',
|
||||
fill_all_required_fields_to_complete: "Заповніть всі обов'язкові поля для завершення",
|
||||
sign_and_complete: 'Підписати і завершити',
|
||||
invite: 'Запросити',
|
||||
email: 'Електронна пошта',
|
||||
approved: 'Затверджено',
|
||||
@@ -560,7 +626,8 @@ const uk = {
|
||||
payment: 'Платіж',
|
||||
number_phone_is_invalid: '{number} телефон недійсний',
|
||||
phone: 'Телефон',
|
||||
submit_form: 'Надіслати Форму',
|
||||
start_now: 'Почати зараз',
|
||||
continue: 'Продовжити',
|
||||
sign_now: 'Підписати зараз',
|
||||
type_here_: 'Введіть тут',
|
||||
optional: 'необов’язково',
|
||||
@@ -581,9 +648,11 @@ const uk = {
|
||||
type_signature_here: 'Введіть підпис тут',
|
||||
type_initial_here: 'Введіть ініціали тут',
|
||||
form_has_been_completed: 'Форму заповнено!',
|
||||
document_has_been_signed: 'Документ був підписаний!',
|
||||
documents_have_been_signed: 'Документи були підписані!',
|
||||
create_a_free_account: 'Створити безкоштовний обліковий запис',
|
||||
signed_with: 'Підписано за допомогою',
|
||||
please_check_the_box_to_continue: 'Будь ласка, позначте прапорець, щоб продовжити',
|
||||
powered_by: 'Працює на базі',
|
||||
please_check_the_box_to_continue: 'Будь ласка, позначте прапорець, щоб продовжити.',
|
||||
open_source_documents_software: 'відкритий програмний засіб для документів',
|
||||
verified_phone_number: 'Підтвердіть номер телефону',
|
||||
use_international_format: 'Використовуйте міжнародний формат: +1xxx',
|
||||
@@ -605,10 +674,17 @@ const uk = {
|
||||
pay_with_strip: 'Сплатити за допомогою Stripe',
|
||||
reupload: 'Перезавантажити',
|
||||
upload: 'Завантажити',
|
||||
files: 'Файли'
|
||||
files: 'Файли',
|
||||
signature_is_too_small_please_redraw: 'Підпис занадто малий. Будь ласка, перемалюйте його.'
|
||||
}
|
||||
|
||||
const cs = {
|
||||
complete_all_required_fields_to_proceed_with_identity_verification: 'Vyplňte všechna povinná pole, abyste mohli pokračovat v ověření identity.',
|
||||
verify_id: 'Ověřit ID',
|
||||
identity_verification: 'Ověření identity',
|
||||
complete: 'Dokončit',
|
||||
fill_all_required_fields_to_complete: 'Please complete all mandatory fields',
|
||||
sign_and_complete: 'Podepsat a dokončit',
|
||||
invite: 'Pozvat',
|
||||
email: 'E-mail',
|
||||
approved: 'Schváleno',
|
||||
@@ -647,7 +723,8 @@ const cs = {
|
||||
payment: 'Platba',
|
||||
phone: 'Telefon',
|
||||
number_phone_is_invalid: '{number} telefon je neplatný',
|
||||
submit_form: 'Odeslat formulář',
|
||||
start_now: 'Začít nyní',
|
||||
continue: 'Pokračovat',
|
||||
sign_now: 'Podepsat nyní',
|
||||
type_here_: 'Zadejte zde',
|
||||
optional: 'volitelné',
|
||||
@@ -668,9 +745,11 @@ const cs = {
|
||||
type_signature_here: 'Sem zadejte podpis',
|
||||
type_initial_here: 'Sem zadejte iniciály',
|
||||
form_has_been_completed: 'Formulář byl dokončen!',
|
||||
document_has_been_signed: 'Dokument byl podepsán!',
|
||||
documents_have_been_signed: 'Dokumenty byly podepsány!',
|
||||
create_a_free_account: 'Vytvořit bezplatný účet',
|
||||
signed_with: 'Podepsáno pomocí',
|
||||
please_check_the_box_to_continue: 'Prosím, zaškrtněte políčko pro pokračování',
|
||||
powered_by: 'Poháněno',
|
||||
please_check_the_box_to_continue: 'Prosím, zaškrtněte políčko pro pokračování.',
|
||||
open_source_documents_software: 'open source software pro dokumenty',
|
||||
verified_phone_number: 'Ověřte telefonní číslo',
|
||||
use_international_format: 'Použijte mezinárodní formát: +1xxx',
|
||||
@@ -692,10 +771,17 @@ const cs = {
|
||||
pay_with_strip: 'Zaplacení přes Stripe',
|
||||
reupload: 'Znovu nahrát',
|
||||
upload: 'Nahrát',
|
||||
files: 'Soubory'
|
||||
files: 'Soubory',
|
||||
signature_is_too_small_please_redraw: 'Podpis je příliš malý. Prosím, překreslete ho.'
|
||||
}
|
||||
|
||||
const pt = {
|
||||
complete_all_required_fields_to_proceed_with_identity_verification: 'Preencha todos os campos obrigatórios para prosseguir com a verificação de identidade.',
|
||||
verify_id: 'Verificar ID',
|
||||
identity_verification: 'Verificação de identidade',
|
||||
complete: 'Completar',
|
||||
fill_all_required_fields_to_complete: 'Preencher todos os campos obrigatórios para concluir',
|
||||
sign_and_complete: 'Assinar e Completar',
|
||||
invite: 'Convidar',
|
||||
email: 'E-mail',
|
||||
approved: 'Aprovado',
|
||||
@@ -734,7 +820,8 @@ const pt = {
|
||||
payment: 'Pagamento',
|
||||
number_phone_is_invalid: '{number} telefone é inválido',
|
||||
phone: 'Telefone',
|
||||
submit_form: 'Enviar Formulário',
|
||||
start_now: 'Começar agora',
|
||||
continue: 'Continuar',
|
||||
sign_now: 'Assinar agora',
|
||||
type_here_: 'Digite aqui',
|
||||
optional: 'opcional',
|
||||
@@ -755,9 +842,11 @@ const pt = {
|
||||
type_signature_here: 'Digite a assinatura aqui',
|
||||
type_initial_here: 'Digite as iniciais aqui',
|
||||
form_has_been_completed: 'O formulário foi concluído!',
|
||||
document_has_been_signed: 'O documento foi assinado!',
|
||||
documents_have_been_signed: 'Os documentos foram assinados!',
|
||||
create_a_free_account: 'Criar uma Conta Gratuita',
|
||||
signed_with: 'Assinado com',
|
||||
please_check_the_box_to_continue: 'Por favor, marque a caixa para continuar',
|
||||
powered_by: 'Desenvolvido por',
|
||||
please_check_the_box_to_continue: 'Por favor, marque a caixa para continuar.',
|
||||
open_source_documents_software: 'software de documentos de código aberto',
|
||||
verified_phone_number: 'Verificar Número de Telefone',
|
||||
use_international_format: 'Use formato internacional: +1xxx',
|
||||
@@ -779,10 +868,17 @@ const pt = {
|
||||
pay_with_strip: 'Pagar com Stripe',
|
||||
reupload: 'Reenviar',
|
||||
upload: 'Carregar',
|
||||
files: 'Arquivos'
|
||||
files: 'Arquivos',
|
||||
signature_is_too_small_please_redraw: 'A assinatura é muito pequena. Por favor, redesenhe-a.'
|
||||
}
|
||||
|
||||
const he = {
|
||||
complete_all_required_fields_to_proceed_with_identity_verification: 'מלא את כל השדות הנדרשים כדי להמשיך עם אימות זהות.',
|
||||
verify_id: 'אמת מזהה',
|
||||
identity_verification: 'אימות זהות',
|
||||
complete: 'השלם',
|
||||
fill_all_required_fields_to_complete: 'נא למלא את כל השדות הנדרשים להשלמה',
|
||||
sign_and_complete: 'חתום והשלם',
|
||||
invite: 'הזמן',
|
||||
email: 'דוא"ל',
|
||||
approved: 'מאושר',
|
||||
@@ -821,7 +917,8 @@ const he = {
|
||||
payment: 'תשלום',
|
||||
phone: 'טלפון',
|
||||
number_phone_is_invalid: '{number} טלפון אינו חוקי',
|
||||
submit_form: 'שלח טופס',
|
||||
start_now: 'התחל עכשיו',
|
||||
continue: 'המשך',
|
||||
sign_now: 'חתום כעת',
|
||||
type_here_: 'הקלד כאן',
|
||||
optional: 'אופציונלי',
|
||||
@@ -843,9 +940,11 @@ const he = {
|
||||
type_signature_here: 'הקלד חתימה כאן',
|
||||
type_initial_here: 'הקלד ציוני ראשי תיבות כאן',
|
||||
form_has_been_completed: 'הטופס הושלם',
|
||||
document_has_been_signed: 'המסמך נחתם!',
|
||||
documents_have_been_signed: 'המסמכים נחתמו!',
|
||||
create_a_free_account: 'צור חשבון חינם',
|
||||
signed_with: 'חתום באמצעות',
|
||||
please_check_the_box_to_continue: 'אנא סמן את התיבה כדי להמשיך',
|
||||
powered_by: 'מופעל על ידי',
|
||||
please_check_the_box_to_continue: 'אנא סמן את התיבה כדי להמשיך.',
|
||||
open_source_documents_software: 'תוכנה פתוחה למסמכים',
|
||||
verified_phone_number: 'אימות מספר טלפון',
|
||||
use_international_format: 'השתמש בפורמט בינלאומי: +1xxx',
|
||||
@@ -867,10 +966,17 @@ const he = {
|
||||
pay_with_strip: 'שלם עם סטרייפ',
|
||||
reupload: 'העלה שוב',
|
||||
upload: 'העלאה',
|
||||
files: 'קבצים'
|
||||
files: 'קבצים',
|
||||
signature_is_too_small_please_redraw: 'החתימה קטנה מדי. אנא צייר מחדש.'
|
||||
}
|
||||
|
||||
const nl = {
|
||||
complete_all_required_fields_to_proceed_with_identity_verification: 'Vul alle verplichte velden in om door te gaan met de identiteitsverificatie.',
|
||||
verify_id: 'Verifiëren ID',
|
||||
identity_verification: 'Identiteitsverificatie',
|
||||
complete: 'Voltooien',
|
||||
fill_all_required_fields_to_complete: 'Vul alle verplichte velden in om te voltooien',
|
||||
sign_and_complete: 'Ondertekenen en voltooien',
|
||||
invite: 'Uitnodigen',
|
||||
email: 'E-mail',
|
||||
approved: 'Goedgekeurd',
|
||||
@@ -910,7 +1016,8 @@ const nl = {
|
||||
payment: 'Betaling',
|
||||
phone: 'Telefoon',
|
||||
number_phone_is_invalid: '{number} telefoon is ongeldig',
|
||||
submit_form: 'Formulier verzenden',
|
||||
start_now: 'Nu beginnen',
|
||||
continue: 'Doorgaan',
|
||||
sign_now: 'Nu ondertekenen',
|
||||
type_here_: 'Typ hier...',
|
||||
optional: 'Optioneel',
|
||||
@@ -931,9 +1038,11 @@ const nl = {
|
||||
type_signature_here: 'Typ hier uw handtekening',
|
||||
type_initial_here: 'Typ hier uw initialen',
|
||||
form_has_been_completed: 'Formulier is voltooid!',
|
||||
document_has_been_signed: 'Document is ondertekend!',
|
||||
documents_have_been_signed: 'De documenten zijn ondertekend!',
|
||||
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',
|
||||
powered_by: 'Aangedreven door',
|
||||
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',
|
||||
@@ -955,10 +1064,17 @@ const nl = {
|
||||
pay_with_strip: 'Betalen met Stripe',
|
||||
reupload: 'Opnieuw uploaden',
|
||||
upload: 'Uploaden',
|
||||
files: 'Bestanden'
|
||||
files: 'Bestanden',
|
||||
signature_is_too_small_please_redraw: 'De handtekening is te klein. Teken deze opnieuw, alstublieft.'
|
||||
}
|
||||
|
||||
const ar = {
|
||||
complete_all_required_fields_to_proceed_with_identity_verification: 'أكمل جميع الحقول المطلوبة للمتابعة في التحقق من الهوية.',
|
||||
verify_id: 'تحقق من الهوية',
|
||||
identity_verification: 'التحقق من الهوية',
|
||||
complete: 'اكتمال',
|
||||
fill_all_required_fields_to_complete: 'يرجى ملء جميع الحقول المطلوبة لإكمال',
|
||||
sign_and_complete: 'التوقيع والاكتمال',
|
||||
invite: 'دعوة',
|
||||
email: 'البريد الإلكتروني',
|
||||
approved: 'موافق عليه',
|
||||
@@ -997,7 +1113,8 @@ const ar = {
|
||||
payment: 'الدفع',
|
||||
phone: 'هاتف',
|
||||
number_phone_is_invalid: '{number} هاتف غير صالح',
|
||||
submit_form: 'إرسال النموذج',
|
||||
start_now: 'ابدأ الآن',
|
||||
continue: 'متابعة',
|
||||
sign_now: 'وقع الآن',
|
||||
type_here_: 'اكتب هنا',
|
||||
optional: 'اختياري',
|
||||
@@ -1018,9 +1135,11 @@ const ar = {
|
||||
type_signature_here: 'اكتب التوقيع هنا',
|
||||
type_initial_here: 'اكتب الاختصارات هنا',
|
||||
form_has_been_completed: 'تم إكمال النموذج!',
|
||||
document_has_been_signed: 'تم توقيع الوثيقة!',
|
||||
documents_have_been_signed: 'تم توقيع الوثائق!',
|
||||
create_a_free_account: 'إنشاء حساب مجاني',
|
||||
signed_with: 'تم التوقيع بواسطة',
|
||||
please_check_the_box_to_continue: 'الرجاء التحقق من الخانة للمتابعة',
|
||||
powered_by: 'مدعوم من',
|
||||
please_check_the_box_to_continue: 'الرجاء التحقق من الخانة للمتابعة.',
|
||||
open_source_documents_software: 'برنامج وثائق مفتوح المصدر',
|
||||
verified_phone_number: 'تحقق من رقم الهاتف',
|
||||
use_international_format: 'استخدم الشكل الدولي: +1xxx',
|
||||
@@ -1042,10 +1161,17 @@ const ar = {
|
||||
pay_with_strip: 'الدفع بواسطة Stripe',
|
||||
reupload: 'إعادة التحميل',
|
||||
upload: 'تحميل',
|
||||
files: 'الملفات'
|
||||
files: 'الملفات',
|
||||
signature_is_too_small_please_redraw: 'التوقيع صغير جدًا. يرجى إعادة الرسم.'
|
||||
}
|
||||
|
||||
const ko = {
|
||||
complete_all_required_fields_to_proceed_with_identity_verification: '신원 확인을 진행하려면 모든 필수 필드를 작성하십시오.',
|
||||
verify_id: '아이디 확인',
|
||||
identity_verification: '신원 확인',
|
||||
complete: '완료',
|
||||
fill_all_required_fields_to_complete: '모든 필수 필드를 작성하여 완료하세요',
|
||||
sign_and_complete: '서명하고 완료하기',
|
||||
invite: '초대하기',
|
||||
email: '이메일',
|
||||
approved: '승인됨',
|
||||
@@ -1083,7 +1209,8 @@ const ko = {
|
||||
payment: '결제',
|
||||
phone: '전화',
|
||||
number_phone_is_invalid: '{number} 전화번호가 유효하지 않습니다',
|
||||
submit_form: '양식 제출',
|
||||
start_now: '지금 시작하기',
|
||||
continue: '계속하다',
|
||||
sign_now: '지금 서명',
|
||||
type_here_: '여기에 입력...',
|
||||
optional: '선택 사항',
|
||||
@@ -1104,9 +1231,11 @@ const ko = {
|
||||
type_signature_here: '여기에 서명 입력',
|
||||
type_initial_here: '여기에 이니셜 입력',
|
||||
form_has_been_completed: '양식이 완료되었습니다!',
|
||||
document_has_been_signed: '문서가 서명되었습니다!',
|
||||
documents_have_been_signed: '문서가 서명되었습니다!',
|
||||
create_a_free_account: '무료 계정 생성',
|
||||
signed_with: '다음으로 서명:',
|
||||
please_check_the_box_to_continue: '계속하려면 확인란을 선택하십시오',
|
||||
powered_by: '구동',
|
||||
please_check_the_box_to_continue: '계속하려면 확인란을 선택하십시오.',
|
||||
open_source_documents_software: '오픈 소스 문서 소프트웨어',
|
||||
verified_phone_number: '전화번호 확인됨',
|
||||
use_international_format: '국제 포맷 사용: +1xxx',
|
||||
@@ -1128,7 +1257,8 @@ const ko = {
|
||||
pay_with_strip: '스트라이프로 결제',
|
||||
reupload: '다시 업로드',
|
||||
upload: '업로드',
|
||||
files: '파일'
|
||||
files: '파일',
|
||||
signature_is_too_small_please_redraw: '서명이 너무 작습니다. 다시 그려주세요.'
|
||||
}
|
||||
|
||||
const i18n = { en, es, it, de, fr, pl, uk, cs, pt, he, nl, ar, ko }
|
||||
|
||||
@@ -226,6 +226,10 @@ export default {
|
||||
if (this.$refs.canvas) {
|
||||
this.pad = new SignaturePad(this.$refs.canvas)
|
||||
|
||||
if (this.field.preferences?.color) {
|
||||
this.pad.penColor = this.field.preferences.color
|
||||
}
|
||||
|
||||
this.pad.addEventListener('beginStroke', () => {
|
||||
this.isInitialsStarted = true
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<template>
|
||||
<label
|
||||
v-if="!modelValue && !sessionId"
|
||||
:for="field.uuid"
|
||||
class="label text-2xl mb-2"
|
||||
>
|
||||
<MarkdownContent
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -61,30 +61,59 @@
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
<div
|
||||
v-show="!isCodeSent"
|
||||
:id="field.uuid"
|
||||
ref="phone"
|
||||
:value="modelValue || defaultValue"
|
||||
:readonly="!!defaultValue"
|
||||
class="base-input !text-2xl w-full"
|
||||
autocomplete="tel"
|
||||
pattern="^\+[0-9\s\-]+$"
|
||||
type="tel"
|
||||
inputmode="tel"
|
||||
:required="field.required"
|
||||
placeholder="+1 234 567-8900"
|
||||
:name="`values[${field.uuid}]`"
|
||||
@invalid="$event.target.value ? $event.target.setCustomValidity(`${t('use_international_format')}...`) : ''"
|
||||
@input="[$event.target.setCustomValidity(''), $emit('update:model-value', $event.target.value)]"
|
||||
@focus="$emit('focus')"
|
||||
class="flex w-full rounded-full outline-neutral-content outline-2 outline-offset-2 focus-within:outline"
|
||||
>
|
||||
<div
|
||||
id="country_code"
|
||||
class="relative inline-block"
|
||||
>
|
||||
<div class="btn bg-base-200 border border-neutral-300 text-2xl whitespace-nowrap font-normal rounded-l-full">
|
||||
{{ selectedCountry.flag }} +{{ selectedCountry.dial }}
|
||||
</div>
|
||||
<select
|
||||
id="country_code_select"
|
||||
class="absolute top-0 bottom-0 right-0 left-0 opacity-0 w-full h-full cursor-pointer"
|
||||
:disabled="!!defaultValue"
|
||||
@change="onCountrySelect(countries.find((country) => country.flag === $event.target.value))"
|
||||
>
|
||||
<option
|
||||
v-for="(country, index) in countries"
|
||||
:key="index"
|
||||
:value="country.flag"
|
||||
>
|
||||
{{ country.flag }} {{ country.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<input
|
||||
:name="`values[${field.uuid}]`"
|
||||
:value="fullInternationalPhoneValue"
|
||||
hidden
|
||||
>
|
||||
<input
|
||||
:id="field.uuid"
|
||||
ref="phone"
|
||||
:value="phoneValue"
|
||||
:readonly="!!defaultValue"
|
||||
class="base-input !text-2xl !rounded-l-none !border-l-0 !outline-none w-full"
|
||||
autocomplete="tel"
|
||||
type="tel"
|
||||
inputmode="tel"
|
||||
:required="field.required"
|
||||
placeholder="234 567-8900"
|
||||
@input="onPhoneInput"
|
||||
@focus="$emit('focus')"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MarkdownContent from './markdown_content'
|
||||
import phoneData from './phone_data'
|
||||
|
||||
function throttle (func, delay) {
|
||||
let lastCallTime = 0
|
||||
@@ -144,13 +173,73 @@ export default {
|
||||
data () {
|
||||
return {
|
||||
isCodeSent: false,
|
||||
isResendLoading: false
|
||||
isResendLoading: false,
|
||||
phoneValue: this.modelValue || this.defaultValue || '',
|
||||
selectedCountry: {}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
countries () {
|
||||
return phoneData.map(([iso, name, dial, flag, tz]) => {
|
||||
return { iso, name, dial, flag, tz }
|
||||
})
|
||||
},
|
||||
countriesDialIndex () {
|
||||
return this.countries.reduce((acc, item) => {
|
||||
acc[item.dial] ||= item
|
||||
|
||||
return acc
|
||||
}, {})
|
||||
},
|
||||
dialCodesRegexp () {
|
||||
const dialCodes = this.countries.map((country) => country.dial).sort((a, b) => b.length - a.length)
|
||||
|
||||
return new RegExp(`^\\+(${dialCodes.join('|')})`)
|
||||
},
|
||||
detectedPhoneValueDialCode () {
|
||||
return (this.phoneValue || '').replace(/[^\d+]/g, '').match(this.dialCodesRegexp)?.[1]
|
||||
},
|
||||
fullInternationalPhoneValue () {
|
||||
if (this.detectedPhoneValueDialCode) {
|
||||
return this.phoneValue
|
||||
} else if (this.phoneValue) {
|
||||
return ['+', this.selectedCountry.dial, this.phoneValue].filter(Boolean).join('')
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
const browserTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone
|
||||
|
||||
if (this.detectedPhoneValueDialCode) {
|
||||
this.selectedCountry = this.countriesDialIndex[this.detectedPhoneValueDialCode]
|
||||
} else if (browserTimeZone) {
|
||||
const tz = browserTimeZone.split('/')[1]
|
||||
|
||||
this.selectedCountry = this.countries.find((country) => country.tz.includes(tz)) || this.countries[0]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
emitSubmit: throttle(function (e) {
|
||||
this.$emit('submit')
|
||||
}, 1000),
|
||||
onCountrySelect (country) {
|
||||
if (this.selectedCountry.flag !== country.flag) {
|
||||
this.phoneValue = this.phoneValue.replace(`+${this.selectedCountry.dial}`, `+${country.dial}`)
|
||||
}
|
||||
|
||||
this.selectedCountry = country
|
||||
|
||||
this.$refs.phone.focus()
|
||||
},
|
||||
onPhoneInput (e) {
|
||||
this.phoneValue = e.target.value
|
||||
|
||||
if (this.detectedPhoneValueDialCode) {
|
||||
this.selectedCountry = this.countriesDialIndex[this.detectedPhoneValueDialCode]
|
||||
}
|
||||
},
|
||||
onInputCode (e) {
|
||||
if (e.target.value.length === 6) {
|
||||
this.emitSubmit()
|
||||
@@ -171,14 +260,14 @@ export default {
|
||||
body: JSON.stringify({
|
||||
submitter_slug: this.submitterSlug,
|
||||
locale: this.locale,
|
||||
phone: this.$refs.phone.value
|
||||
phone: this.fullInternationalPhoneValue
|
||||
}),
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
}).then(async (resp) => {
|
||||
if (resp.status === 422) {
|
||||
const data = await resp.json()
|
||||
|
||||
alert(this.t('number_phone_is_invalid').replace('{number}', this.$refs.phone.value))
|
||||
alert(this.t('number_phone_is_invalid').replace('{number}', this.fullInternationalPhoneValue))
|
||||
|
||||
return Promise.reject(new Error(data.error))
|
||||
}
|
||||
@@ -189,13 +278,13 @@ export default {
|
||||
return Promise.resolve({})
|
||||
}
|
||||
|
||||
if (!this.$refs.phone.value.toString().startsWith('+')) {
|
||||
if (!this.fullInternationalPhoneValue.toString().startsWith('+')) {
|
||||
alert(this.t('use_international_format'))
|
||||
|
||||
return Promise.reject(new Error('phone invalid'))
|
||||
} else if (!this.isCodeSent) {
|
||||
return this.sendVerificationCode().then(() => {
|
||||
this.$emit('update:model-value', this.$refs.phone.value)
|
||||
this.$emit('update:model-value', this.fullInternationalPhoneValue)
|
||||
|
||||
this.isCodeSent = true
|
||||
|
||||
|
||||
@@ -145,6 +145,10 @@
|
||||
{{ t('clear') }}
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
v-if="isTextSignature"
|
||||
class="absolute top-0 right-0 left-0 bottom-0"
|
||||
/>
|
||||
<canvas
|
||||
v-show="!modelValue && !computedPreviousValue"
|
||||
ref="canvas"
|
||||
@@ -197,6 +201,7 @@
|
||||
<select
|
||||
v-if="requireSigningReason && !isOtherReason"
|
||||
class="select base-input !text-2xl w-full mt-6 text-center"
|
||||
:class="{ 'text-gray-300': !reason }"
|
||||
required
|
||||
:name="`values[${field.preferences.reason_field_uuid}]`"
|
||||
@change="$event.target.value === 'other' ? [reason = '', isOtherReason = true] : $emit('update:reason', $event.target.value)"
|
||||
@@ -205,6 +210,7 @@
|
||||
value=""
|
||||
disabled
|
||||
:selected="!reason"
|
||||
class="text-gray-300"
|
||||
>
|
||||
{{ t('select_a_reason') }}
|
||||
</option>
|
||||
@@ -213,10 +219,14 @@
|
||||
:key="option"
|
||||
:value="option"
|
||||
:selected="reason === option"
|
||||
class="text-base-content"
|
||||
>
|
||||
{{ label }}
|
||||
</option>
|
||||
<option value="other">
|
||||
<option
|
||||
value="other"
|
||||
class="text-base-content"
|
||||
>
|
||||
{{ t('other') }}
|
||||
</option>
|
||||
</select>
|
||||
@@ -249,7 +259,7 @@
|
||||
class="text-base-content/60 text-xs text-center w-full mt-1"
|
||||
>
|
||||
{{ t('by_clicking_you_agree_to_the').replace('{button}', buttonText.charAt(0).toUpperCase() + buttonText.slice(1)) }} <a
|
||||
href="https://www.docuseal.co/esign-disclosure"
|
||||
href="https://www.docuseal.com/esign-disclosure"
|
||||
target="_blank"
|
||||
>
|
||||
<span class="inline md:hidden">
|
||||
@@ -413,6 +423,10 @@ export default {
|
||||
if (this.$refs.canvas) {
|
||||
this.pad = new SignaturePad(this.$refs.canvas)
|
||||
|
||||
if (this.field.preferences?.color) {
|
||||
this.pad.penColor = this.field.preferences.color
|
||||
}
|
||||
|
||||
this.pad.addEventListener('endStroke', () => {
|
||||
this.isSignatureStarted = true
|
||||
|
||||
|
||||
@@ -114,7 +114,9 @@ export default {
|
||||
const area = this.field.areas?.[0]
|
||||
|
||||
if (area) {
|
||||
return parseInt(area.w / area.cell_w) + 1
|
||||
const num = area.w / area.cell_w
|
||||
|
||||
return (num % 1) > 0.2 ? parseInt(num) + 1 : parseInt(num)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
<template>
|
||||
<label
|
||||
class="label text-2xl mb-2"
|
||||
>
|
||||
<MarkdownContent
|
||||
v-if="field.title"
|
||||
:string="field.title"
|
||||
/>
|
||||
<template v-else>{{ field.name || t('identity_verification') }}</template>
|
||||
</label>
|
||||
<div
|
||||
v-if="field.description"
|
||||
dir="auto"
|
||||
class="mb-4 px-1"
|
||||
>
|
||||
<MarkdownContent :string="field.description" />
|
||||
</div>
|
||||
<div
|
||||
v-if="emptyValueRequiredStep && emptyValueRequiredStep[0] !== field"
|
||||
class="px-1"
|
||||
>
|
||||
{{ t('complete_all_required_fields_to_proceed_with_identity_verification') }}
|
||||
</div>
|
||||
<div v-else>
|
||||
<div
|
||||
v-if="isLoading"
|
||||
class="w-full flex space-x-2 justify-center mb-2"
|
||||
>
|
||||
<IconInnerShadowTop
|
||||
width="40"
|
||||
class="animate-spin h-10"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
ref="widgetContainer"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MarkdownContent from './markdown_content'
|
||||
import { IconInnerShadowTop } from '@tabler/icons-vue'
|
||||
import phoneData from './phone_data'
|
||||
|
||||
export default {
|
||||
name: 'VerificationStep',
|
||||
components: {
|
||||
MarkdownContent,
|
||||
IconInnerShadowTop
|
||||
},
|
||||
inject: ['baseUrl', 't'],
|
||||
props: {
|
||||
modelValue: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: ''
|
||||
},
|
||||
submitter: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
field: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
emptyValueRequiredStep: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
values: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
locale: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'en'
|
||||
},
|
||||
submitterSlug: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
emits: ['focus', 'submit', 'update:model-value', 'attached'],
|
||||
data () {
|
||||
return {
|
||||
isCreatingCheckout: false,
|
||||
isMathLoaded: false,
|
||||
isLoading: false,
|
||||
eidEasyData: {}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
countryCode () {
|
||||
const browserTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone
|
||||
const browserTz = browserTimeZone.split('/')[1]
|
||||
const country = phoneData.find(([a, b, c, e, tz]) => tz.includes(browserTz))
|
||||
|
||||
return country[0]
|
||||
},
|
||||
browserCountry () {
|
||||
return (navigator.language || navigator.userLanguage || 'en').split('-')[1]
|
||||
},
|
||||
widgetSettings () {
|
||||
return {
|
||||
clientId: this.eidEasyData.client_id,
|
||||
docId: this.eidEasyData.doc_id,
|
||||
language: this.locale,
|
||||
countryCode: this.countryCode,
|
||||
enabledMethods: {
|
||||
signature: this.eidEasyData.available_methods
|
||||
},
|
||||
selectedMethod: null,
|
||||
enabledCountries: 'all',
|
||||
onSuccess: (data) => {
|
||||
this.$emit('submit')
|
||||
},
|
||||
onFail: (error) => {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
async mounted () {
|
||||
this.isLoading = true
|
||||
|
||||
Promise.all([
|
||||
import('@eid-easy/eideasy-widget'),
|
||||
this.start()
|
||||
]).finally(() => {
|
||||
this.isLoading = false
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
start () {
|
||||
return fetch(this.baseUrl + `/api/identity_verification/${this.field.uuid}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({
|
||||
submitter_slug: this.submitterSlug
|
||||
}),
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
}).then(async (resp) => {
|
||||
this.eidEasyData = await resp.json()
|
||||
|
||||
const eidEasyWidget = document.createElement('eideasy-widget')
|
||||
|
||||
for (const key in this.widgetSettings) {
|
||||
eidEasyWidget[key] = this.widgetSettings[key]
|
||||
}
|
||||
|
||||
this.$refs.widgetContainer.innerHTML = ''
|
||||
this.$refs.widgetContainer.appendChild(eidEasyWidget)
|
||||
})
|
||||
},
|
||||
async submit () {
|
||||
return fetch(this.baseUrl + '/api/identity_verification', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
submitter_slug: this.submitterSlug
|
||||
}),
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
}).then(async (resp) => {
|
||||
return resp
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -175,6 +175,7 @@
|
||||
<div
|
||||
ref="textContainer"
|
||||
class="flex items-center px-0.5"
|
||||
:style="{ color: field.preferences?.color }"
|
||||
:class="{ 'w-full h-full': ['cells', 'checkbox'].includes(field.type) }"
|
||||
>
|
||||
<IconCheck
|
||||
@@ -236,7 +237,7 @@
|
||||
/>
|
||||
<span
|
||||
v-if="field?.type && editable"
|
||||
class="h-4 w-4 md:h-2.5 md:w-2.5 -right-1 rounded-full -bottom-1 border-gray-400 bg-white shadow-md border absolute cursor-nwse-resize"
|
||||
class="h-4 w-4 lg:h-2.5 lg:w-2.5 -right-1 rounded-full -bottom-1 border-gray-400 bg-white shadow-md border absolute cursor-nwse-resize"
|
||||
@mousedown.stop="startResize"
|
||||
@touchstart="startTouchResize"
|
||||
/>
|
||||
@@ -256,7 +257,7 @@
|
||||
:to="modalContainerEl"
|
||||
>
|
||||
<ConditionsModal
|
||||
:field="field"
|
||||
:item="field"
|
||||
:build-default-name="buildDefaultName"
|
||||
@close="isShowConditionsModal = false"
|
||||
/>
|
||||
@@ -360,6 +361,7 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
fieldNames: FieldType.computed.fieldNames,
|
||||
fieldLabels: FieldType.computed.fieldLabels,
|
||||
fieldIcons: FieldType.computed.fieldIcons,
|
||||
isDefaultValuePresent () {
|
||||
if (this.field?.type === 'radio' && this.field?.areas?.length > 1) {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<template>
|
||||
<div
|
||||
style="max-width: 1600px"
|
||||
class="mx-auto pl-3 md:pl-4 h-full"
|
||||
class="mx-auto pl-3 h-full"
|
||||
:class="isMobile ? 'pl-4' : 'md:pl-4'"
|
||||
>
|
||||
<div
|
||||
v-if="pendingFieldAttachmentUuids.length"
|
||||
v-if="pendingFieldAttachmentUuids.length && editable"
|
||||
class="top-1.5 sticky h-0 z-20 max-w-2xl mx-auto"
|
||||
>
|
||||
<div class="alert border-base-content/30 py-2 px-2.5">
|
||||
@@ -131,7 +132,7 @@
|
||||
class="flex items-center justify-center space-x-2"
|
||||
>
|
||||
<IconEye class="w-6 h-6 flex-shrink-0" />
|
||||
<span class="whitespace-nowrap">Save and Preview</span>
|
||||
<span class="whitespace-nowrap">{{ t('save_and_preview') }}</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
@@ -142,7 +143,7 @@
|
||||
@click="closeDropdown"
|
||||
>
|
||||
<IconAdjustments class="w-6 h-6 flex-shrink-0" />
|
||||
<span class="whitespace-nowrap">Preferences</span>
|
||||
<span class="whitespace-nowrap">{{ t('preferences') }}</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -163,7 +164,7 @@
|
||||
<div
|
||||
id="main_container"
|
||||
class="flex"
|
||||
:class="$slots.buttons || withTitle ? 'md:max-h-[calc(100%_-_60px)]' : 'md:max-h-[100%]'"
|
||||
:class="$slots.buttons || withTitle ? (isMobile ? 'max-h-[calc(100%_-_60px)]' : 'md:max-h-[calc(100%_-_60px)]') : (isMobile ? 'max-h-[100%]' : 'md:max-h-[100%]')"
|
||||
>
|
||||
<div
|
||||
v-if="withDocumentsList"
|
||||
@@ -219,7 +220,8 @@
|
||||
</div>
|
||||
<div
|
||||
id="pages_container"
|
||||
class="w-full overflow-y-hidden md:overflow-y-auto overflow-x-hidden mt-0.5 pt-0.5"
|
||||
class="w-full overflow-y-hidden overflow-x-hidden mt-0.5 pt-0.5"
|
||||
:class="isMobile ? 'overflow-y-auto' : 'md:overflow-y-auto'"
|
||||
>
|
||||
<div
|
||||
ref="documents"
|
||||
@@ -320,7 +322,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="withFieldsList"
|
||||
v-if="withFieldsList && !isMobile"
|
||||
id="fields_list_container"
|
||||
class="relative w-80 flex-none mt-1 pr-4 pl-0.5 hidden md:block"
|
||||
:class="drawField ? 'overflow-hidden' : 'overflow-y-auto overflow-x-hidden'"
|
||||
@@ -332,7 +334,7 @@
|
||||
>
|
||||
<div class="bg-base-200 rounded-lg p-5 text-center space-y-4">
|
||||
<p>
|
||||
{{ t('draw_field_on_the_document').replace('{field}', drawField?.name || '') }}
|
||||
{{ t('draw_field_on_the_document') }}
|
||||
</p>
|
||||
<div>
|
||||
<button
|
||||
@@ -362,6 +364,7 @@
|
||||
:default-submitters="defaultSubmitters"
|
||||
:draw-field-type="drawFieldType"
|
||||
:default-fields="[...defaultRequiredFields, ...defaultFields]"
|
||||
:template="template"
|
||||
:default-required-fields="defaultRequiredFields"
|
||||
:field-types="fieldTypes"
|
||||
:with-sticky-submitters="withStickySubmitters"
|
||||
@@ -378,14 +381,14 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sticky bottom-0">
|
||||
<div class="sticky bottom-0 z-10">
|
||||
<MobileDrawField
|
||||
v-if="drawField && isBreakpointLg"
|
||||
v-if="drawField && (isBreakpointLg || isMobile)"
|
||||
:draw-field="drawField"
|
||||
:fields="template.fields"
|
||||
:submitters="template.submitters"
|
||||
:selected-submitter="selectedSubmitter"
|
||||
class="md:hidden"
|
||||
:class="{ 'md:hidden': !isMobile }"
|
||||
:editable="editable"
|
||||
@cancel="[drawField = null, drawOption = null]"
|
||||
@change-submitter="[selectedSubmitter = $event, drawField.submitter_uuid = $event.uuid]"
|
||||
@@ -396,6 +399,7 @@
|
||||
:default-fields="[...defaultRequiredFields, ...defaultFields]"
|
||||
:default-required-fields="defaultRequiredFields"
|
||||
:field-types="fieldTypes"
|
||||
:class="{ 'md:hidden': !isMobile }"
|
||||
:selected-submitter="selectedSubmitter"
|
||||
@select="startFieldDraw($event)"
|
||||
/>
|
||||
@@ -415,10 +419,11 @@ import Contenteditable from './contenteditable'
|
||||
import DocumentPreview from './preview'
|
||||
import DocumentControls from './controls'
|
||||
import MobileFields from './mobile_fields'
|
||||
import FieldSubmitter from './field_submitter'
|
||||
import { IconPlus, IconUsersPlus, IconDeviceFloppy, IconChevronDown, IconEye, IconWritingSign, IconInnerShadowTop, IconInfoCircle, IconAdjustments } from '@tabler/icons-vue'
|
||||
import { v4 } from 'uuid'
|
||||
import { ref, computed } from 'vue'
|
||||
import { en as i18nEn } from './i18n'
|
||||
import * as i18n from './i18n'
|
||||
|
||||
export default {
|
||||
name: 'TemplateBuilder',
|
||||
@@ -454,6 +459,7 @@ export default {
|
||||
fieldTypes: this.fieldTypes,
|
||||
backgroundColor: this.backgroundColor,
|
||||
withPhone: this.withPhone,
|
||||
withVerification: this.withVerification,
|
||||
withPayment: this.withPayment,
|
||||
isPaymentConnected: this.isPaymentConnected,
|
||||
withFormula: this.withFormula,
|
||||
@@ -558,6 +564,11 @@ export default {
|
||||
required: false,
|
||||
default: () => []
|
||||
},
|
||||
defineSubmitters: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => []
|
||||
},
|
||||
acceptFileTypes: {
|
||||
type: String,
|
||||
required: false,
|
||||
@@ -629,6 +640,11 @@ export default {
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
withVerification: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
withPayment: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
@@ -677,8 +693,15 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
submitterDefaultNames: FieldSubmitter.computed.names,
|
||||
selectedAreaRef: () => ref(),
|
||||
fieldsDragFieldRef: () => ref(),
|
||||
language () {
|
||||
return this.locale.split('-')[0].toLowerCase()
|
||||
},
|
||||
isMobile () {
|
||||
return /android|iphone|ipad/i.test(navigator.userAgent)
|
||||
},
|
||||
defaultDateFormat () {
|
||||
const isUsBrowser = Intl.DateTimeFormat().resolvedOptions().locale.endsWith('-US')
|
||||
const isUsTimezone = new Intl.DateTimeFormat('en-US', { timeZoneName: 'short' }).format(new Date()).match(/\s(?:CST|CDT|PST|PDT|EST|EDT)$/)
|
||||
@@ -690,6 +713,7 @@ export default {
|
||||
'de-DE': 'DD.MM.YYYY',
|
||||
'fr-FR': 'DD/MM/YYYY',
|
||||
'it-IT': 'DD/MM/YYYY',
|
||||
'en-GB': 'DD/MM/YYYY',
|
||||
'es-ES': 'DD/MM/YYYY'
|
||||
}
|
||||
},
|
||||
@@ -745,6 +769,18 @@ export default {
|
||||
}
|
||||
})
|
||||
|
||||
this.defineSubmitters.forEach((name, index) => {
|
||||
const submitter = (this.template.submitters[index] ||= {})
|
||||
|
||||
submitter.name = name || this.submitterDefaultNames[index]
|
||||
|
||||
if (existingSubmittersUuids.filter(Boolean).length) {
|
||||
submitter.uuid = existingSubmittersUuids[index] || submitter.uuid || v4()
|
||||
} else {
|
||||
submitter.uuid ||= v4()
|
||||
}
|
||||
})
|
||||
|
||||
this.selectedSubmitter = this.template.submitters[0]
|
||||
},
|
||||
mounted () {
|
||||
@@ -786,7 +822,7 @@ export default {
|
||||
document.activeElement.blur()
|
||||
},
|
||||
t (key) {
|
||||
return this.i18n[key] || i18nEn[key] || key
|
||||
return this.i18n[key] || i18n[this.language]?.[key] || i18n.en[key] || key
|
||||
},
|
||||
removePendingFields () {
|
||||
this.template.fields = this.template.fields.filter((f) => {
|
||||
@@ -994,6 +1030,22 @@ export default {
|
||||
|
||||
if (!field.areas.length) {
|
||||
this.template.fields.splice(this.template.fields.indexOf(field), 1)
|
||||
|
||||
this.template.fields.forEach((f) => {
|
||||
(f.conditions || []).forEach((c) => {
|
||||
if (c.field_uuid === field.uuid) {
|
||||
f.conditions.splice(f.conditions.indexOf(c), 1)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
this.template.schema.forEach((item) => {
|
||||
(item.conditions || []).forEach((c) => {
|
||||
if (c.field_uuid === field.uuid) {
|
||||
item.conditions.splice(item.conditions.indexOf(c), 1)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
this.save()
|
||||
@@ -1052,7 +1104,7 @@ export default {
|
||||
} else if (type === 'image') {
|
||||
area.w = pageMask.clientWidth / 5 / pageMask.clientWidth
|
||||
area.h = (pageMask.clientWidth / 5 / pageMask.clientWidth) * (pageMask.clientWidth / pageMask.clientHeight)
|
||||
} else if (type === 'signature' || type === 'stamp') {
|
||||
} else if (type === 'signature' || type === 'stamp' || type === 'verification') {
|
||||
area.w = pageMask.clientWidth / 5 / pageMask.clientWidth
|
||||
area.h = (pageMask.clientWidth / 5 / pageMask.clientWidth) * (pageMask.clientWidth / pageMask.clientHeight) / 2
|
||||
} else if (type === 'initials') {
|
||||
@@ -1216,7 +1268,7 @@ export default {
|
||||
w: area.maskW / 5 / area.maskW,
|
||||
h: (area.maskW / 5 / area.maskW) * (area.maskW / area.maskH)
|
||||
}
|
||||
} else if (field.type === 'signature' || field.type === 'stamp') {
|
||||
} else if (field.type === 'signature' || field.type === 'stamp' || field.type === 'verification') {
|
||||
baseArea = {
|
||||
w: area.maskW / 5 / area.maskW,
|
||||
h: (area.maskW / 5 / area.maskW) * (area.maskW / area.maskH) / 2
|
||||
@@ -1319,7 +1371,9 @@ export default {
|
||||
}
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.$refs.previews.scrollTop = this.$refs.previews.scrollHeight
|
||||
if (this.$refs.previews) {
|
||||
this.$refs.previews.scrollTop = this.$refs.previews.scrollHeight
|
||||
}
|
||||
|
||||
this.scrollIntoDocument(data.schema[0])
|
||||
})
|
||||
@@ -1352,7 +1406,7 @@ export default {
|
||||
this.save()
|
||||
},
|
||||
onDocumentRemove (item) {
|
||||
if (window.confirm(this.t('are_you_sure'))) {
|
||||
if (window.confirm(this.t('are_you_sure_'))) {
|
||||
this.template.schema.splice(this.template.schema.indexOf(item), 1)
|
||||
}
|
||||
|
||||
@@ -1451,7 +1505,7 @@ export default {
|
||||
if (!this.template.fields.length) {
|
||||
e.preventDefault()
|
||||
|
||||
alert('Please draw fields to prepare the document.')
|
||||
alert(this.t('please_draw_fields_to_prepare_the_document'))
|
||||
} else {
|
||||
const submitterWithoutFields =
|
||||
this.template.submitters.find((submitter) => !this.template.fields.some((f) => f.submitter_uuid === submitter.uuid))
|
||||
@@ -1459,7 +1513,7 @@ export default {
|
||||
if (submitterWithoutFields) {
|
||||
e.preventDefault()
|
||||
|
||||
alert(`Please add fields for the ${submitterWithoutFields.name}. Or, remove the ${submitterWithoutFields.name} if not needed.`)
|
||||
alert(this.t('please_add_fields_for_the_submitter_name_or_remove_the_submitter_name_if_not_needed').replaceAll('{submitter_name}', submitterWithoutFields.name))
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1475,13 +1529,13 @@ export default {
|
||||
}
|
||||
|
||||
if (!this.template.fields.length) {
|
||||
alert('Please draw fields to prepare the document.')
|
||||
alert(this.t('please_draw_fields_to_prepare_the_document'))
|
||||
} else {
|
||||
const submitterWithoutFields =
|
||||
this.template.submitters.find((submitter) => !this.template.fields.some((f) => f.submitter_uuid === submitter.uuid))
|
||||
|
||||
if (submitterWithoutFields) {
|
||||
alert(`Please add fields for the ${submitterWithoutFields.name}. Or, remove the ${submitterWithoutFields.name} if not needed.`)
|
||||
alert(this.t('please_add_fields_for_the_submitter_name_or_remove_the_submitter_name_if_not_needed').replaceAll('{submitter_name}', submitterWithoutFields.name))
|
||||
} else {
|
||||
this.isSaving = true
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<div class="modal-box pt-4 pb-6 px-6 mt-20 max-h-none w-full max-w-xl">
|
||||
<div class="flex justify-between items-center border-b pb-2 mb-2 font-medium">
|
||||
<span>
|
||||
{{ t('condition') }} - {{ field.name || buildDefaultName(field, template.fields) }}
|
||||
{{ t('condition') }} - {{ item.name || buildDefaultName(item, template.fields) }}
|
||||
</span>
|
||||
<a
|
||||
href="#"
|
||||
@@ -23,21 +23,32 @@
|
||||
class="bg-base-300 rounded-xl py-2 px-3 text-center"
|
||||
>
|
||||
<a
|
||||
href="https://www.docuseal.co/pricing"
|
||||
href="https://www.docuseal.com/pricing"
|
||||
target="_blank"
|
||||
class="link"
|
||||
>Available in Pro</a>
|
||||
>{{ t('available_in_pro') }}</a>
|
||||
</div>
|
||||
<form @submit.prevent="validateSaveAndClose">
|
||||
<div class="my-4 space-y-5">
|
||||
<div class="my-4">
|
||||
<div
|
||||
v-for="(condition, cindex) in conditions"
|
||||
:key="cindex"
|
||||
class="space-y-4 relative"
|
||||
>
|
||||
<div
|
||||
v-if="cindex > 0"
|
||||
class="divider -mb-2 mx-1"
|
||||
>
|
||||
<button
|
||||
class="btn btn-xs btn-primary w-24"
|
||||
@click.prevent="condition.operation === 'or' ? delete condition.operation : condition.operation = 'or'"
|
||||
>
|
||||
{{ condition.operation === 'or' ? t('or') : t('and') }}
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
v-if="conditions.length > 1"
|
||||
class="flex justify-between border-b mx-1 -mb-1 pb-1"
|
||||
class="flex justify-between mx-1"
|
||||
>
|
||||
<span class="text-sm">
|
||||
{{ t('condition') }} {{ cindex + 1 }}
|
||||
@@ -50,6 +61,7 @@
|
||||
</div>
|
||||
<select
|
||||
class="select select-bordered select-sm w-full bg-white h-11 pl-4 text-base font-normal"
|
||||
:class="{ 'text-gray-300': !condition.field_uuid }"
|
||||
required
|
||||
@change="[
|
||||
condition.field_uuid = $event.target.value,
|
||||
@@ -68,6 +80,7 @@
|
||||
v-for="f in fields"
|
||||
:key="f.uuid"
|
||||
:value="f.uuid"
|
||||
class="text-base-content"
|
||||
:selected="condition.field_uuid === f.uuid"
|
||||
>
|
||||
{{ f.name || buildDefaultName(f, template.fields) }}
|
||||
@@ -88,8 +101,9 @@
|
||||
</option>
|
||||
</select>
|
||||
<select
|
||||
v-if="conditionField(condition)?.options?.length"
|
||||
v-if="['radio', 'select', 'multiple'].includes(conditionField(condition)?.type) && conditionField(condition)?.options"
|
||||
class="select select-bordered select-sm w-full bg-white h-11 pl-4 text-base font-normal"
|
||||
:class="{ 'text-gray-300': !condition.value }"
|
||||
required
|
||||
@change="condition.value = $event.target.value"
|
||||
>
|
||||
@@ -105,6 +119,7 @@
|
||||
:key="option.uuid"
|
||||
:value="option.uuid"
|
||||
:selected="condition.value === option.uuid"
|
||||
class="text-base-content"
|
||||
>
|
||||
{{ option.value || `${t('option')} ${index + 1}` }}
|
||||
</option>
|
||||
@@ -123,12 +138,12 @@
|
||||
</button>
|
||||
</form>
|
||||
<div
|
||||
v-if="field.conditions?.[0]?.field_uuid"
|
||||
v-if="item.conditions?.[0]?.field_uuid"
|
||||
class="text-center w-full mt-4"
|
||||
>
|
||||
<button
|
||||
class="link"
|
||||
@click="[conditions = [], delete field.conditions, validateSaveAndClose()]"
|
||||
@click="[conditions = [], delete item.conditions, validateSaveAndClose()]"
|
||||
>
|
||||
{{ t('remove_condition') }}
|
||||
</button>
|
||||
@@ -143,7 +158,7 @@ export default {
|
||||
name: 'ConditionModal',
|
||||
inject: ['t', 'save', 'template', 'withConditions'],
|
||||
props: {
|
||||
field: {
|
||||
item: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
@@ -155,22 +170,26 @@ export default {
|
||||
emits: ['close'],
|
||||
data () {
|
||||
return {
|
||||
conditions: this.field.conditions?.[0] ? JSON.parse(JSON.stringify(this.field.conditions)) : [{}]
|
||||
conditions: this.item.conditions?.[0] ? JSON.parse(JSON.stringify(this.item.conditions)) : [{}]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
fields () {
|
||||
return this.template.fields.reduce((acc, f) => {
|
||||
if (f !== this.field && f.submitter_uuid === this.field.submitter_uuid) {
|
||||
acc.push(f)
|
||||
}
|
||||
if (this.item.submitter_uuid) {
|
||||
return this.template.fields.reduce((acc, f) => {
|
||||
if (f !== this.item && f.submitter_uuid === this.item.submitter_uuid) {
|
||||
acc.push(f)
|
||||
}
|
||||
|
||||
return acc
|
||||
}, [])
|
||||
return acc
|
||||
}, [])
|
||||
} else {
|
||||
return this.template.fields
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.field.conditions ||= []
|
||||
this.item.conditions ||= []
|
||||
},
|
||||
methods: {
|
||||
conditionField (condition) {
|
||||
@@ -200,13 +219,13 @@ export default {
|
||||
},
|
||||
validateSaveAndClose () {
|
||||
if (!this.withConditions) {
|
||||
return alert('Available only in Pro')
|
||||
return alert(this.t('available_only_in_pro'))
|
||||
}
|
||||
|
||||
if (this.conditions.find((f) => f.field_uuid)) {
|
||||
this.field.conditions = this.conditions
|
||||
this.item.conditions = this.conditions
|
||||
} else {
|
||||
delete this.field.conditions
|
||||
delete this.item.conditions
|
||||
}
|
||||
|
||||
this.save()
|
||||
|
||||
@@ -105,7 +105,7 @@ export default {
|
||||
|
||||
this.upload()
|
||||
} else {
|
||||
alert('Only PDF and images are supported.')
|
||||
alert(this.t('only_pdf_and_images_are_supported'))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,7 +236,7 @@
|
||||
:to="modalContainerEl"
|
||||
>
|
||||
<ConditionsModal
|
||||
:field="field"
|
||||
:item="field"
|
||||
:build-default-name="buildDefaultName"
|
||||
@close="isShowConditionsModal = false"
|
||||
/>
|
||||
@@ -312,6 +312,7 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
fieldNames: FieldType.computed.fieldNames,
|
||||
fieldLabels: FieldType.computed.fieldLabels,
|
||||
dropdownBgColor () {
|
||||
return ['', null, 'transparent'].includes(this.backgroundColor) ? 'white' : this.backgroundColor
|
||||
},
|
||||
@@ -364,11 +365,10 @@ export default {
|
||||
} else {
|
||||
const typeIndex = fields.filter((f) => f.type === field.type).indexOf(field)
|
||||
|
||||
if (this.field.type === 'heading') {
|
||||
if (field.type === 'heading') {
|
||||
return `${this.fieldNames[field.type]} ${typeIndex + 1}`
|
||||
} else {
|
||||
const suffix = { multiple: this.t('select'), radio: this.t('group') }[field.type] || this.t('field')
|
||||
return `${this.fieldNames[field.type]} ${suffix} ${typeIndex + 1}`
|
||||
return `${this.fieldLabels[field.type]} ${typeIndex + 1}`
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -79,7 +79,7 @@
|
||||
:stroke-width="1.6"
|
||||
/>
|
||||
<span class="py-1">
|
||||
{{ t('add') }} {{ names[submitters.length] }}
|
||||
{{ t('add') }} {{ names[lastPartyIndex] }}
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
@@ -202,7 +202,7 @@
|
||||
:stroke-width="1.6"
|
||||
/>
|
||||
<span class="py-1">
|
||||
{{ t('add') }} {{ names[submitters.length] }}
|
||||
{{ t('add') }} {{ names[lastPartyIndex] }}
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
@@ -321,6 +321,15 @@ export default {
|
||||
this.t('twentieth_party')
|
||||
]
|
||||
},
|
||||
lastPartyIndex () {
|
||||
const index = Math.max(...this.submitters.map((s) => this.names.indexOf(s.name)))
|
||||
|
||||
if (index !== -1) {
|
||||
return index + 1
|
||||
} else {
|
||||
return this.submitters.length
|
||||
}
|
||||
},
|
||||
selectedSubmitter () {
|
||||
return this.submitters.find((e) => e.uuid === this.modelValue)
|
||||
}
|
||||
@@ -330,7 +339,7 @@ export default {
|
||||
this.$emit('update:model-value', submitter.uuid)
|
||||
},
|
||||
remove (submitter) {
|
||||
if (window.confirm(this.t('are_you_sure'))) {
|
||||
if (window.confirm(this.t('are_you_sure_'))) {
|
||||
this.$emit('remove', submitter)
|
||||
}
|
||||
},
|
||||
@@ -353,7 +362,7 @@ export default {
|
||||
},
|
||||
addSubmitter () {
|
||||
const newSubmitter = {
|
||||
name: this.names[this.submitters.length],
|
||||
name: this.names[this.lastPartyIndex],
|
||||
uuid: v4()
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
v-for="(icon, type) in fieldIconsSorted"
|
||||
:key="type"
|
||||
>
|
||||
<li v-if="(fieldTypes.length === 0 || fieldTypes.includes(type)) && (withPhone || type != 'phone') && (withPayment || type != 'payment')">
|
||||
<li v-if="(fieldTypes.length === 0 || fieldTypes.includes(type)) && (withPhone || type != 'phone') && (withPayment || type != 'payment') && (withVerification || type != 'verification')">
|
||||
<a
|
||||
href="#"
|
||||
class="text-sm py-1 px-2"
|
||||
@@ -51,11 +51,11 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { IconTextSize, IconWritingSign, IconCalendarEvent, IconPhoto, IconCheckbox, IconPaperclip, IconSelect, IconCircleDot, IconChecks, IconColumns3, IconPhoneCheck, IconLetterCaseUpper, IconCreditCard, IconRubberStamp, IconSquareNumber1, IconHeading } from '@tabler/icons-vue'
|
||||
import { IconTextSize, IconWritingSign, IconCalendarEvent, IconPhoto, IconCheckbox, IconPaperclip, IconSelect, IconCircleDot, IconChecks, IconColumns3, IconPhoneCheck, IconLetterCaseUpper, IconCreditCard, IconRubberStamp, IconSquareNumber1, IconHeading, IconId } from '@tabler/icons-vue'
|
||||
|
||||
export default {
|
||||
name: 'FiledTypeDropdown',
|
||||
inject: ['withPhone', 'withPayment', 't', 'fieldTypes'],
|
||||
inject: ['withPhone', 'withPayment', 'withVerification', 't', 'fieldTypes'],
|
||||
props: {
|
||||
modelValue: {
|
||||
type: String,
|
||||
@@ -111,7 +111,28 @@ export default {
|
||||
cells: this.t('cells'),
|
||||
stamp: this.t('stamp'),
|
||||
payment: this.t('payment'),
|
||||
phone: this.t('phone')
|
||||
phone: this.t('phone'),
|
||||
verification: this.t('verify_id')
|
||||
}
|
||||
},
|
||||
fieldLabels () {
|
||||
return {
|
||||
text: this.t('text_field'),
|
||||
signature: this.t('signature_field'),
|
||||
initials: this.t('initials_field'),
|
||||
date: this.t('date_field'),
|
||||
number: this.t('number_field'),
|
||||
image: this.t('image_field'),
|
||||
file: this.t('file_field'),
|
||||
select: this.t('select_field'),
|
||||
checkbox: this.t('checkbox_field'),
|
||||
multiple: this.t('multiple_field'),
|
||||
radio: this.t('radio_field'),
|
||||
cells: this.t('cells_field'),
|
||||
stamp: this.t('stamp_field'),
|
||||
payment: this.t('payment_field'),
|
||||
phone: this.t('phone_field'),
|
||||
verification: this.t('verify_id')
|
||||
}
|
||||
},
|
||||
fieldIcons () {
|
||||
@@ -131,7 +152,8 @@ export default {
|
||||
cells: IconColumns3,
|
||||
stamp: IconRubberStamp,
|
||||
payment: IconCreditCard,
|
||||
phone: IconPhoneCheck
|
||||
phone: IconPhoneCheck,
|
||||
verification: IconId
|
||||
}
|
||||
},
|
||||
fieldIconsSorted () {
|
||||
|
||||
@@ -111,14 +111,14 @@
|
||||
:key="type"
|
||||
>
|
||||
<button
|
||||
v-if="(fieldTypes.length === 0 || fieldTypes.includes(type)) && (withPhone || type != 'phone') && (withPayment || type != 'payment')"
|
||||
v-if="(fieldTypes.length === 0 || fieldTypes.includes(type)) && (withPhone || type != 'phone') && (withPayment || type != 'payment') && (withVerification || type != 'verification')"
|
||||
draggable="true"
|
||||
class="field-type-button group flex items-center justify-center border border-dashed w-full rounded relative"
|
||||
:style="{ backgroundColor }"
|
||||
:class="drawFieldType === type ? 'border-base-content/40' : 'border-base-300 hover:border-base-content/20'"
|
||||
@dragstart="onDragstart({ type: type })"
|
||||
@dragend="$emit('drag-end')"
|
||||
@click="['file', 'payment'].includes(type) ? $emit('add-field', type) : $emit('set-draw-type', type)"
|
||||
@click="['file', 'payment', 'verification'].includes(type) ? $emit('add-field', type) : $emit('set-draw-type', type)"
|
||||
>
|
||||
<div
|
||||
class="flex items-console transition-all cursor-grab h-full absolute left-0"
|
||||
@@ -137,10 +137,36 @@
|
||||
v-else-if="type == 'phone' && (fieldTypes.length === 0 || fieldTypes.includes(type))"
|
||||
class="tooltip tooltip-bottom flex"
|
||||
:class="{'tooltip-bottom-end': 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="t('unlock_sms_verified_phone_number_field_with_paid_plan_use_text_field_for_phone_numbers_without_verification')"
|
||||
>
|
||||
<a
|
||||
href="https://www.docuseal.co/pricing"
|
||||
href="https://www.docuseal.com/pricing"
|
||||
target="_blank"
|
||||
class="opacity-50 flex items-center justify-center border border-dashed border-base-300 w-full rounded relative"
|
||||
:style="{ backgroundColor }"
|
||||
>
|
||||
<div class="w-0 absolute left-0">
|
||||
<IconLock
|
||||
width="18"
|
||||
height="18"
|
||||
stroke-width="1.5"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center flex-col px-2 py-2">
|
||||
<component :is="icon" />
|
||||
<span class="text-xs mt-1">
|
||||
{{ fieldNames[type] }}
|
||||
</span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="withVerification === false && type == 'verification' && (fieldTypes.length === 0 || fieldTypes.includes(type))"
|
||||
class="tooltip tooltip-bottom flex tooltip-bottom-start"
|
||||
:data-tip="t('obtain_qualified_electronic_signature_with_the_trusted_provider_click_to_learn_more')"
|
||||
>
|
||||
<a
|
||||
href="https://www.docuseal.com/contact"
|
||||
target="_blank"
|
||||
class="opacity-50 flex items-center justify-center border border-dashed border-base-300 w-full rounded relative"
|
||||
:style="{ backgroundColor }"
|
||||
@@ -168,13 +194,13 @@
|
||||
>
|
||||
<ul class="list-disc list-outside ml-3">
|
||||
<li>
|
||||
Draw a text field on the page with a mouse
|
||||
{{ t('draw_a_text_field_on_the_page_with_a_mouse') }}
|
||||
</li>
|
||||
<li>
|
||||
Drag & drop any other field type on the page
|
||||
{{ t('drag_and_drop_any_other_field_type_on_the_page') }}
|
||||
</li>
|
||||
<li>
|
||||
Click on the field type above to start drawing it
|
||||
{{ t('click_on_the_field_type_above_to_start_drawing_it') }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -197,12 +223,16 @@ export default {
|
||||
IconDrag,
|
||||
IconLock
|
||||
},
|
||||
inject: ['save', 'backgroundColor', 'withPhone', 'withPayment', 't', 'fieldsDragFieldRef'],
|
||||
inject: ['save', 'backgroundColor', 'withPhone', 'withVerification', 'withPayment', 't', 'fieldsDragFieldRef'],
|
||||
props: {
|
||||
fields: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
template: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
withHelp: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
@@ -354,6 +384,14 @@ export default {
|
||||
})
|
||||
})
|
||||
|
||||
this.template.schema.forEach((item) => {
|
||||
(item.conditions || []).forEach((c) => {
|
||||
if (c.field_uuid === field.uuid) {
|
||||
item.conditions.splice(item.conditions.indexOf(c), 1)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
this.save()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,10 +23,10 @@
|
||||
class="bg-base-300 rounded-xl py-2 px-3 text-center"
|
||||
>
|
||||
<a
|
||||
href="https://www.docuseal.co/pricing"
|
||||
href="https://www.docuseal.com/pricing"
|
||||
target="_blank"
|
||||
class="link"
|
||||
>Available in Pro</a>
|
||||
>{{ t('available_in_pro') }}</a>
|
||||
</div>
|
||||
<div class="flex-inline mb-2 gap-2 space-y-1">
|
||||
<button
|
||||
@@ -195,13 +195,13 @@ export default {
|
||||
},
|
||||
validateSaveAndClose () {
|
||||
if (!this.withFormula) {
|
||||
return alert('Available only in Pro')
|
||||
return alert(this.t('available_only_in_pro'))
|
||||
}
|
||||
|
||||
const normalizedFormula = this.normalizeFormula(this.formula)
|
||||
|
||||
if (normalizedFormula.includes('FIELD NOT FOUND')) {
|
||||
alert('Some fields are missing in the formula.')
|
||||
alert(this.t('some_fields_are_missing_in_the_formula'))
|
||||
} else {
|
||||
this.field.preferences.formula = normalizedFormula
|
||||
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
const en = {
|
||||
verify_id: 'Verify ID',
|
||||
obtain_qualified_electronic_signature_with_the_trusted_provider_click_to_learn_more: 'Obtain qualified electronic signature (QeS) with the trusted provider. Click to learn more.',
|
||||
editable: 'Editable',
|
||||
recurrent: 'Recurrent',
|
||||
one_off: 'One-off',
|
||||
search_field: 'Search field',
|
||||
field_not_found: 'Field not found',
|
||||
clear: 'Clear',
|
||||
@@ -29,7 +33,7 @@ const en = {
|
||||
select_value_: 'Select value...',
|
||||
remove_condition: 'Remove condition',
|
||||
add_condition: 'Add condition',
|
||||
are_you_sure: 'Are you sure?',
|
||||
are_you_sure_: 'Are you sure?',
|
||||
sign_yourself: 'Sign Yourself',
|
||||
set_signing_date: 'Set signing date',
|
||||
signing_date: 'Signing Date',
|
||||
@@ -46,7 +50,7 @@ const en = {
|
||||
drawn: 'Drawn',
|
||||
formula: 'Formula',
|
||||
typed: 'Typed',
|
||||
draw_field_on_the_document: 'Draw {field} field on the document',
|
||||
draw_field_on_the_document: 'Draw a field on the document',
|
||||
click_to_upload: 'Click to upload',
|
||||
or_drag_and_drop_files: 'or drag and drop files',
|
||||
uploading: 'Uploading',
|
||||
@@ -101,9 +105,30 @@ const en = {
|
||||
stamp: 'Stamp',
|
||||
payment: 'Payment',
|
||||
phone: 'Phone',
|
||||
field: 'Field',
|
||||
group: 'Group',
|
||||
text_field: 'Text Field',
|
||||
signature_field: 'Signature Field',
|
||||
initials_field: 'Initials Field',
|
||||
date_field: 'Date Field',
|
||||
number_field: 'Number Field',
|
||||
image_field: 'Image Field',
|
||||
file_field: 'File Field',
|
||||
select_field: 'Select Field',
|
||||
checkbox_field: 'Checkbox Field',
|
||||
multiple_field: 'Multiple Select Field',
|
||||
radio_field: 'Radio Group Field',
|
||||
cells_field: 'Cells Field',
|
||||
stamp_field: 'Stamp Field',
|
||||
payment_field: 'Payment Field',
|
||||
phone_field: 'Phone Field',
|
||||
draw_a_text_field_on_the_page_with_a_mouse: 'Draw a text field on the page with a mouse',
|
||||
drag_and_drop_any_other_field_type_on_the_page: 'Drag & drop any other field type on the page',
|
||||
click_on_the_field_type_above_to_start_drawing_it: 'Click on the field type above to start drawing it',
|
||||
please_draw_fields_to_prepare_the_document: 'Please draw fields to prepare the document.',
|
||||
only_pdf_and_images_are_supported: 'Only PDF and images are supported.',
|
||||
unlock_sms_verified_phone_number_field_with_paid_plan_use_text_field_for_phone_numbers_without_verification: 'Unlock SMS-verified phone number field with paid plan. Use text field for phone numbers without verification.',
|
||||
available_only_in_pro: 'Available only in Pro',
|
||||
failed_to_download_files: 'Failed to download files',
|
||||
please_add_fields_for_the_submitter_name_or_remove_the_submitter_name_if_not_needed: 'Please add fields for the {submitter_name}. Or, remove the {submitter_name} if not needed.',
|
||||
draw_field: 'Draw {field} Field',
|
||||
replace: 'Replace',
|
||||
uploading_: 'Uploading...',
|
||||
@@ -117,7 +142,802 @@ const en = {
|
||||
custom: 'Custom',
|
||||
numbers_only: 'Numbers only',
|
||||
letters_only: 'Letters only',
|
||||
regexp_validation: 'Regexp validation'
|
||||
regexp_validation: 'Regexp validation',
|
||||
enter_pdf_password: 'Enter PDF password',
|
||||
wrong_password: 'Wrong password',
|
||||
currency: 'Currency',
|
||||
save_and_preview: 'Save and Preview',
|
||||
preferences: 'Preferences',
|
||||
available_in_pro: 'Available in Pro',
|
||||
some_fields_are_missing_in_the_formula: 'Some fields are missing in the formula.',
|
||||
learn_more: 'Learn more',
|
||||
and: 'and',
|
||||
or: 'or'
|
||||
}
|
||||
|
||||
export { en }
|
||||
const es = {
|
||||
verify_id: 'Verificar ID',
|
||||
obtain_qualified_electronic_signature_with_the_trusted_provider_click_to_learn_more: 'Obtenga una firma electrónica cualificada (QeS) con el proveedor de confianza. Haga clic para obtener más información.',
|
||||
recurrent: 'Recurrente',
|
||||
one_off: 'Único',
|
||||
editable: 'Editable',
|
||||
search_field: 'Campo de búsqueda',
|
||||
field_not_found: 'Campo no encontrado',
|
||||
clear: 'Borrar',
|
||||
type_value: 'Escriba valor',
|
||||
align: 'Alinear',
|
||||
add_all_required_fields_to_continue: 'Agregar todos los campos requeridos para continuar',
|
||||
uploaded_pdf_contains_form_fields_keep_or_remove_them: 'El PDF cargado tiene campos. ¿Mantenerlos o eliminarlos?',
|
||||
keep: 'Mantener',
|
||||
left: 'Izquierda',
|
||||
heading: 'Encabezado',
|
||||
validation: 'Validación',
|
||||
add_blank_page: 'Agregar página en blanco',
|
||||
right: 'Derecha',
|
||||
center: 'Centro',
|
||||
with_logo: 'Con logotipo',
|
||||
description: 'Descripción',
|
||||
signing_date: 'Fecha de Firma',
|
||||
display_title: 'Título de visualización',
|
||||
unchecked: 'No marcado',
|
||||
price: 'Precio',
|
||||
equal: 'Igual',
|
||||
not_equal: 'No es igual',
|
||||
contains: 'Contiene',
|
||||
does_not_contain: 'No contiene',
|
||||
not_empty: 'No vacío',
|
||||
empty: 'Vacío',
|
||||
select_field_: 'Seleccionar campo...',
|
||||
select_value_: 'Seleccionar valor...',
|
||||
remove_condition: 'Eliminar condición',
|
||||
add_condition: 'Agregar condición',
|
||||
condition: 'Condición',
|
||||
formula: 'Fórmula',
|
||||
edit: 'Editar',
|
||||
settings: 'Configuración',
|
||||
up: 'Arriba',
|
||||
down: 'Abajo',
|
||||
set_signing_date: 'Establecer fecha de firma',
|
||||
are_you_sure_: '¿Estás seguro?',
|
||||
sign_yourself: 'Firma tú mismo',
|
||||
checked: 'Seleccionado',
|
||||
send: 'Enviar',
|
||||
remove: 'Eliminar',
|
||||
save: 'Guardar',
|
||||
cancel: 'Cancelar',
|
||||
or_add_field_without_drawing: 'O añadir campo sin dibujar',
|
||||
draw_field_on_the_document: 'Dibujar un campo en el documento',
|
||||
click_to_upload: 'Haz clic para cargar',
|
||||
or_drag_and_drop_files: 'o arrastra y suelta archivos',
|
||||
uploading: 'Subiendo',
|
||||
processing_: 'Procesando...',
|
||||
add_pdf_documents_or_images: 'Agregar documentos PDF o imágenes',
|
||||
add_documents_or_images: 'Agregar documentos o imágenes',
|
||||
required: 'Requerido',
|
||||
default_value: 'Valor predeterminado',
|
||||
format: 'Formato',
|
||||
read_only: 'Solo lectura',
|
||||
page: 'Página',
|
||||
draw_new_area: 'Dibujar nueva área',
|
||||
copy_to_all_pages: 'Copiar a todas las páginas',
|
||||
add_option: 'Agregar opción',
|
||||
option: 'Opción',
|
||||
first_party: 'Primera Parte',
|
||||
second_party: 'Segunda Parte',
|
||||
third_party: 'Tercera Parte',
|
||||
fourth_party: 'Cuarta Parte',
|
||||
fifth_party: 'Quinta Parte',
|
||||
sixth_party: 'Sexta Parte',
|
||||
seventh_party: 'Séptima Parte',
|
||||
eighth_party: 'Octava Parte',
|
||||
ninth_party: 'Novena Parte',
|
||||
tenth_party: 'Décima Parte',
|
||||
eleventh_party: 'Undécimo Partido',
|
||||
twelfth_party: 'Duodécimo Partido',
|
||||
thirteenth_party: 'Decimotercer Partido',
|
||||
fourteenth_party: 'Catorceavo Partido',
|
||||
fifteenth_party: 'Quinceavo Partido',
|
||||
sixteenth_party: 'Dieciséisavo Partido',
|
||||
seventeenth_party: 'Diecisieteavo Partido',
|
||||
eighteenth_party: 'Dieciochoavo Partido',
|
||||
nineteenth_party: 'Decimonovena Fiesta',
|
||||
twentieth_party: 'Vigésima Fiesta',
|
||||
draw: 'Dibujar',
|
||||
add: 'Agregar',
|
||||
text: 'Texto',
|
||||
signature: 'Firma',
|
||||
initials: 'Iniciales',
|
||||
date: 'Fecha',
|
||||
number: 'Número',
|
||||
image: 'Imagen',
|
||||
file: 'Archivo',
|
||||
select: 'Seleccionar',
|
||||
checkbox: 'Casilla',
|
||||
multiple: 'Múltiple',
|
||||
radio: 'Radio',
|
||||
cells: 'Celdas',
|
||||
stamp: 'Sello',
|
||||
payment: 'Pago',
|
||||
phone: 'Teléfono',
|
||||
text_field: 'Campo de Texto',
|
||||
signature_field: 'Campo de Firma',
|
||||
initials_field: 'Campo de Iniciales',
|
||||
date_field: 'Campo de Fecha',
|
||||
number_field: 'Campo de Número',
|
||||
image_field: 'Campo de Imagen',
|
||||
file_field: 'Campo de Archivo',
|
||||
select_field: 'Campo de Selección',
|
||||
checkbox_field: 'Campo de Casilla',
|
||||
multiple_field: 'Campo Múltiple',
|
||||
radio_field: 'Campo de Grupo Radio',
|
||||
cells_field: 'Campo de Celdas',
|
||||
stamp_field: 'Campo de Sello',
|
||||
payment_field: 'Campo de Pago',
|
||||
phone_field: 'Campo de Teléfono',
|
||||
draw_a_text_field_on_the_page_with_a_mouse: 'Dibujar un campo de texto en la página con el mouse',
|
||||
drag_and_drop_any_other_field_type_on_the_page: 'Arrastra y suelta cualquier otro tipo de campo en la página',
|
||||
click_on_the_field_type_above_to_start_drawing_it: 'Haz clic en el tipo de campo de arriba para comenzar a dibujarlo',
|
||||
please_draw_fields_to_prepare_the_document: 'Por favor, dibuja los campos para preparar el documento.',
|
||||
only_pdf_and_images_are_supported: 'Solo se admiten PDF e imágenes.',
|
||||
unlock_sms_verified_phone_number_field_with_paid_plan_use_text_field_for_phone_numbers_without_verification: 'Desbloquea el campo de número de teléfono verificado por SMS con un plan pago. Usa el campo de texto para números de teléfono sin verificación.',
|
||||
available_only_in_pro: 'Disponible solo en Pro',
|
||||
failed_to_download_files: 'Error al descargar los archivos',
|
||||
please_add_fields_for_the_submitter_name_or_remove_the_submitter_name_if_not_needed: 'Por favor, añade campos para {submitter_name} o elimina {submitter_name} si no es necesario.',
|
||||
draw_field: 'Dibujar campo {field}',
|
||||
replace: 'Reemplazar',
|
||||
uploading_: 'Subiendo...',
|
||||
add_document: 'Subir',
|
||||
any: 'Cualquier',
|
||||
drawn: 'Dibujado',
|
||||
typed: 'Escrito',
|
||||
none: 'Ninguno',
|
||||
ssn: 'SSN',
|
||||
ein: 'EIN',
|
||||
email: 'Correo electrónico',
|
||||
url: 'URL',
|
||||
zip: 'ZIP',
|
||||
custom: 'Personalizado',
|
||||
numbers_only: 'Solo números',
|
||||
letters_only: 'Solo letras',
|
||||
regexp_validation: 'Validación de expresión regular',
|
||||
enter_pdf_password: 'Ingrese la contraseña del PDF',
|
||||
wrong_password: 'Contraseña incorrecta',
|
||||
currency: 'Moneda',
|
||||
save_and_preview: 'Guardar y previsualizar',
|
||||
preferences: 'Preferencias',
|
||||
available_in_pro: 'Disponible en Pro',
|
||||
some_fields_are_missing_in_the_formula: 'Faltan algunos campos en la fórmula.',
|
||||
learn_more: 'Aprende más',
|
||||
and: 'y',
|
||||
or: 'o'
|
||||
}
|
||||
|
||||
const it = {
|
||||
verify_id: 'Verifica ID',
|
||||
obtain_qualified_electronic_signature_with_the_trusted_provider_click_to_learn_more: 'Ottieni una firma elettronica qualificata (QeS) con il fornitore di fiducia. Clicca per saperne di più.',
|
||||
ricorrente: 'Ricorrente',
|
||||
una_volta: 'Una volta',
|
||||
editable: 'Modificabile',
|
||||
search_field: 'Campo di ricerca',
|
||||
field_not_found: 'Campo non trovato',
|
||||
clear: 'Cancella',
|
||||
align: 'Allinea',
|
||||
add_all_required_fields_to_continue: 'Aggiungi tutti i campi obbligatori per continuare',
|
||||
uploaded_pdf_contains_form_fields_keep_or_remove_them: 'Il PDF caricato contiene campi del modulo. Mantenerli o rimuoverli?',
|
||||
keep: 'Mantieni',
|
||||
left: 'Sinistra',
|
||||
heading: 'Intestazione',
|
||||
validation: 'Validazione',
|
||||
add_blank_page: 'Aggiungi pagina vuota',
|
||||
right: 'Destra',
|
||||
center: 'Centro',
|
||||
description: 'Descrizione',
|
||||
display_title: 'Mostra titolo',
|
||||
with_logo: 'Con logo',
|
||||
unchecked: 'Non selezionato',
|
||||
price: 'Prezzo',
|
||||
type_value: 'Inserisci valore',
|
||||
equal: 'Uguale',
|
||||
not_equal: 'Non uguale',
|
||||
contains: 'Contiene',
|
||||
does_not_contain: 'Non contiene',
|
||||
not_empty: 'Non vuoto',
|
||||
empty: 'Vuoto',
|
||||
select_field_: 'Seleziona campo...',
|
||||
select_value_: 'Seleziona valore...',
|
||||
remove_condition: 'Rimuovi condizione',
|
||||
add_condition: 'Aggiungi condizione',
|
||||
are_you_sure_: 'Sei sicuro?',
|
||||
sign_yourself: 'Firma te stesso',
|
||||
set_signing_date: 'Imposta data di firma',
|
||||
signing_date: 'Data di firma',
|
||||
send: 'Invia',
|
||||
remove: 'Rimuovi',
|
||||
edit: 'Modifica',
|
||||
settings: 'Impostazioni',
|
||||
up: 'Su',
|
||||
down: 'Giù',
|
||||
checked: 'Selezionato',
|
||||
save: 'Salva',
|
||||
cancel: 'Annulla',
|
||||
any: 'Qualsiasi',
|
||||
drawn: 'Disegnato',
|
||||
formula: 'Formula',
|
||||
typed: 'Digitato',
|
||||
draw_field_on_the_document: 'Disegnare un campo sul documento',
|
||||
click_to_upload: 'Clicca per caricare',
|
||||
or_drag_and_drop_files: 'o trascina e rilascia i file',
|
||||
uploading: 'Caricamento in corso',
|
||||
processing_: 'Elaborazione...',
|
||||
add_pdf_documents_or_images: 'Aggiungi documenti PDF o immagini',
|
||||
add_documents_or_images: 'Aggiungi documenti o immagini',
|
||||
required: 'Obbligatorio',
|
||||
default_value: 'Valore predefinito',
|
||||
format: 'Formato',
|
||||
read_only: 'Sola lettura',
|
||||
page: 'Pagina',
|
||||
draw_new_area: 'Disegna nuova area',
|
||||
copy_to_all_pages: 'Copia in tutte le pagine',
|
||||
add_option: 'Aggiungi opzione',
|
||||
option: 'Opzione',
|
||||
condition: 'Condizione',
|
||||
first_party: 'Prima parte',
|
||||
second_party: 'Seconda parte',
|
||||
third_party: 'Terza parte',
|
||||
fourth_party: 'Quarta parte',
|
||||
fifth_party: 'Quinta parte',
|
||||
sixth_party: 'Sesta parte',
|
||||
seventh_party: 'Settima parte',
|
||||
eighth_party: 'Ottava parte',
|
||||
ninth_party: 'Nona parte',
|
||||
tenth_party: 'Decima parte',
|
||||
eleventh_party: 'Undicesima parte',
|
||||
twelfth_party: 'Dodicesima parte',
|
||||
thirteenth_party: 'Tredicesima parte',
|
||||
fourteenth_party: 'Quattordicesima parte',
|
||||
fifteenth_party: 'Quindicesima parte',
|
||||
sixteenth_party: 'Sedicesima parte',
|
||||
seventeenth_party: 'Diciassettesima parte',
|
||||
eighteenth_party: 'Diciottesima parte',
|
||||
nineteenth_party: 'Diciannovesima parte',
|
||||
twentieth_party: 'Ventesima parte',
|
||||
draw: 'Disegna',
|
||||
add: 'Aggiungi',
|
||||
or_add_field_without_drawing: 'Oppure aggiungi campo senza disegno',
|
||||
text: 'Testo',
|
||||
number: 'Numero',
|
||||
signature: 'Firma',
|
||||
initials: 'Iniziali',
|
||||
date: 'Data',
|
||||
image: 'Immagine',
|
||||
file: 'File',
|
||||
select: 'Seleziona',
|
||||
checkbox: 'Checkbox',
|
||||
multiple: 'Multiplo',
|
||||
radio: 'Radio',
|
||||
cells: 'Celle',
|
||||
stamp: 'Timbro',
|
||||
payment: 'Pagamento',
|
||||
phone: 'Telefono',
|
||||
text_field: 'Campo di Testo',
|
||||
signature_field: 'Campo di Firma',
|
||||
initials_field: 'Campo di Iniziali',
|
||||
date_field: 'Campo Data',
|
||||
number_field: 'Campo Numero',
|
||||
image_field: 'Campo Immagine',
|
||||
file_field: 'Campo File',
|
||||
select_field: 'Campo di Selezione',
|
||||
checkbox_field: 'Campo di Checkbox',
|
||||
multiple_field: 'Campo Selezione Multipla',
|
||||
radio_field: 'Campo di Gruppo Radio',
|
||||
cells_field: 'Campo Celle',
|
||||
stamp_field: 'Campo Timbro',
|
||||
payment_field: 'Campo Pagamento',
|
||||
phone_field: 'Campo Telefono',
|
||||
draw_a_text_field_on_the_page_with_a_mouse: 'Disegna un campo di testo sulla pagina con il mouse',
|
||||
drag_and_drop_any_other_field_type_on_the_page: 'Trascina e rilascia qualsiasi altro tipo di campo sulla pagina',
|
||||
click_on_the_field_type_above_to_start_drawing_it: 'Clicca sul tipo di campo sopra per iniziare a disegnarlo',
|
||||
please_draw_fields_to_prepare_the_document: 'Per favore, disegna i campi per preparare il documento.',
|
||||
only_pdf_and_images_are_supported: 'Sono supportati solo PDF e immagini.',
|
||||
unlock_sms_verified_phone_number_field_with_paid_plan_use_text_field_for_phone_numbers_without_verification: 'Sblocca il campo numero di telefono verificato tramite SMS con un piano a pagamento. Usa il campo di testo per numeri di telefono senza verifica.',
|
||||
available_only_in_pro: 'Disponibile solo in Pro',
|
||||
failed_to_download_files: 'Impossibile scaricare i file',
|
||||
please_add_fields_for_the_submitter_name_or_remove_the_submitter_name_if_not_needed: 'Aggiungi campi per {submitter_name} o rimuovi {submitter_name} se non necessario.',
|
||||
draw_field: 'Disegna il campo {field}',
|
||||
replace: 'Sostituisci',
|
||||
uploading_: 'Caricamento in corso...',
|
||||
add_document: 'Aggiungi',
|
||||
none: 'Nessuno',
|
||||
ssn: 'SSN',
|
||||
ein: 'EIN',
|
||||
email: 'Email',
|
||||
url: 'URL',
|
||||
zip: 'CAP',
|
||||
custom: 'Personalizzato',
|
||||
numbers_only: 'Solo numeri',
|
||||
letters_only: 'Solo lettere',
|
||||
regexp_validation: 'Validazione regexp',
|
||||
enter_pdf_password: 'Inserisci password PDF',
|
||||
wrong_password: 'Password errata',
|
||||
currency: 'Valuta',
|
||||
save_and_preview: 'Salva e Anteprima',
|
||||
preferences: 'Preferenze',
|
||||
available_in_pro: 'Disponibile in Pro',
|
||||
some_fields_are_missing_in_the_formula: 'Alcuni campi mancano nella formula.',
|
||||
learn_more: 'Scopri di più',
|
||||
and: 'e',
|
||||
or: 'o'
|
||||
}
|
||||
|
||||
const pt = {
|
||||
verify_id: 'Verificar ID',
|
||||
obtain_qualified_electronic_signature_with_the_trusted_provider_click_to_learn_more: 'Obtenha a assinatura eletrônica qualificada (QeS) com o provedor confiável. Clique para saber mais.',
|
||||
recurrent: 'Recurrente',
|
||||
one_off: 'Único',
|
||||
editable: 'Editável',
|
||||
search_field: 'Campo de busca',
|
||||
field_not_found: 'Campo não encontrado',
|
||||
clear: 'Limpar',
|
||||
type_value: 'Digite valor',
|
||||
add_all_required_fields_to_continue: 'Adicione todos os campos obrigatórios para continuar',
|
||||
uploaded_pdf_contains_form_fields_keep_or_remove_them: 'O PDF carregado contém campos. Manter ou removê-los?',
|
||||
keep: 'Manter',
|
||||
align: 'Alinhar',
|
||||
left: 'Esquerda',
|
||||
heading: 'Cabeçalho',
|
||||
validation: 'Validação',
|
||||
add_blank_page: 'Adicionar página em branco',
|
||||
right: 'Direita',
|
||||
center: 'Centro',
|
||||
with_logo: 'Com logotipo',
|
||||
description: 'Descrição',
|
||||
display_title: 'Título de exibição',
|
||||
signing_date: 'Data da Assinatura',
|
||||
unchecked: 'Não marcado',
|
||||
price: 'Preço',
|
||||
equal: 'Igual',
|
||||
not_equal: 'Não é igual',
|
||||
contains: 'Contém',
|
||||
does_not_contain: 'Não contém',
|
||||
not_empty: 'Não vazio',
|
||||
empty: 'Vazio',
|
||||
add_condition: 'Adicionar condição',
|
||||
select_field_: 'Selecionar campo...',
|
||||
select_value_: 'Selecionar valor...',
|
||||
remove_condition: 'Remover condição',
|
||||
condition: 'Condição',
|
||||
formula: 'Fórmula',
|
||||
edit: 'Editar',
|
||||
settings: 'Configurações',
|
||||
up: 'Para cima',
|
||||
down: 'Para baixo',
|
||||
set_signing_date: 'Definir data de assinatura',
|
||||
are_you_sure_: 'Tem certeza?',
|
||||
sign_yourself: 'Assine você mesmo',
|
||||
checked: 'Marcado',
|
||||
send: 'Enviar',
|
||||
remove: 'Remover',
|
||||
save: 'Salvar',
|
||||
cancel: 'Cancelar',
|
||||
or_add_field_without_drawing: 'Ou adicione campo sem desenhar',
|
||||
draw_field_on_the_document: 'Desenhar um campo no documento',
|
||||
click_to_upload: 'Clique para enviar',
|
||||
or_drag_and_drop_files: 'ou arraste e solte arquivos',
|
||||
uploading: 'Carregando',
|
||||
processing_: 'Processando...',
|
||||
add_pdf_documents_or_images: 'Adicionar documentos PDF ou imagens',
|
||||
add_documents_or_images: 'Adicionar documentos ou imagens',
|
||||
required: 'Obrigatório',
|
||||
default_value: 'Valor padrão',
|
||||
format: 'Formato',
|
||||
read_only: 'Somente leitura',
|
||||
page: 'Página',
|
||||
draw_new_area: 'Desenhar nova área',
|
||||
copy_to_all_pages: 'Copiar para todas as páginas',
|
||||
add_option: 'Adicionar opção',
|
||||
option: 'Opção',
|
||||
first_party: 'Primeira Parte',
|
||||
second_party: 'Segunda Parte',
|
||||
third_party: 'Terceira Parte',
|
||||
fourth_party: 'Quarta Parte',
|
||||
fifth_party: 'Quinta Parte',
|
||||
sixth_party: 'Sexta Parte',
|
||||
seventh_party: 'Sétima Parte',
|
||||
eighth_party: 'Oitava Parte',
|
||||
ninth_party: 'Nona Parte',
|
||||
tenth_party: 'Décima Parte',
|
||||
eleventh_party: 'Décima Primeira Parte',
|
||||
twelfth_party: 'Décima Segunda Parte',
|
||||
thirteenth_party: 'Décima Terceira Parte',
|
||||
fourteenth_party: 'Décima Quarta Parte',
|
||||
fifteenth_party: 'Décima Quinta Parte',
|
||||
sixteenth_party: 'Décima Sexta Parte',
|
||||
seventeenth_party: 'Décima Sétima Parte',
|
||||
eighteenth_party: 'Décima Oitava Parte',
|
||||
nineteenth_party: 'Décima Nona Parte',
|
||||
twentieth_party: 'Vigésima Parte',
|
||||
draw: 'Desenhar',
|
||||
add: 'Adicionar',
|
||||
text: 'Texto',
|
||||
signature: 'Assinatura',
|
||||
initials: 'Rúbrica',
|
||||
date: 'Data',
|
||||
number: 'Número',
|
||||
image: 'Imagem',
|
||||
file: 'Arquivo',
|
||||
select: 'Selecionar',
|
||||
checkbox: 'Caixa',
|
||||
multiple: 'Múltiplo',
|
||||
radio: 'Rádio',
|
||||
cells: 'Células',
|
||||
stamp: 'Carimbo',
|
||||
payment: 'Pagamento',
|
||||
phone: 'Telefone',
|
||||
text_field: 'Campo de Texto',
|
||||
signature_field: 'Campo de Assinatura',
|
||||
initials_field: 'Campo de Rúbrica',
|
||||
date_field: 'Campo de Data',
|
||||
number_field: 'Campo de Número',
|
||||
image_field: 'Campo de Imagem',
|
||||
file_field: 'Campo de Arquivo',
|
||||
select_field: 'Campo de Seleção',
|
||||
checkbox_field: 'Campo de Caixa',
|
||||
multiple_field: 'Campo de Opção Múltipla',
|
||||
radio_field: 'Campo de Grupo Rádio',
|
||||
cells_field: 'Campo de Células',
|
||||
stamp_field: 'Campo de Carimbo',
|
||||
payment_field: 'Campo de Pagamento',
|
||||
phone_field: 'Campo de Telefone',
|
||||
draw_a_text_field_on_the_page_with_a_mouse: 'Desenhar um campo de texto na página com o mouse',
|
||||
drag_and_drop_any_other_field_type_on_the_page: 'Arraste e solte qualquer outro tipo de campo na página',
|
||||
click_on_the_field_type_above_to_start_drawing_it: 'Clique no tipo de campo acima para começar a desenhá-lo',
|
||||
please_draw_fields_to_prepare_the_document: 'Por favor, desenhe os campos para preparar o documento.',
|
||||
only_pdf_and_images_are_supported: 'Apenas PDFs e imagens são suportados.',
|
||||
unlock_sms_verified_phone_number_field_with_paid_plan_use_text_field_for_phone_numbers_without_verification: 'Desbloqueie o campo de número de telefone verificado por SMS com um plano pago. Use o campo de texto para números de telefone sem verificação.',
|
||||
available_only_in_pro: 'Disponível apenas no Pro',
|
||||
failed_to_download_files: 'Falha ao baixar arquivos',
|
||||
please_add_fields_for_the_submitter_name_or_remove_the_submitter_name_if_not_needed: 'Adicione campos para {submitter_name} ou remova {submitter_name} se não for necessário.',
|
||||
draw_field: 'Desenhar campo {field}',
|
||||
replace: 'Substituir',
|
||||
uploading_: 'Carregando...',
|
||||
add_document: 'Enviar',
|
||||
any: 'Qualquer',
|
||||
drawn: 'Desenhado',
|
||||
typed: 'Digitado',
|
||||
none: 'Nenhum',
|
||||
ssn: 'SSN',
|
||||
ein: 'EIN',
|
||||
email: 'Email',
|
||||
url: 'URL',
|
||||
zip: 'ZIP',
|
||||
custom: 'Personalizado',
|
||||
numbers_only: 'Somente números',
|
||||
letters_only: 'Somente letras',
|
||||
regexp_validation: 'Validação de expressão regular',
|
||||
enter_pdf_password: 'Digite a senha do PDF',
|
||||
wrong_password: 'Senha incorreta',
|
||||
currency: 'Moeda',
|
||||
save_and_preview: 'Salvar e Pré-visualizar',
|
||||
preferences: 'Preferências',
|
||||
available_in_pro: 'Disponível no Pro',
|
||||
some_fields_are_missing_in_the_formula: 'Faltam alguns campos na fórmula.',
|
||||
learn_more: 'Saiba mais',
|
||||
and: 'e',
|
||||
or: 'ou'
|
||||
}
|
||||
|
||||
const fr = {
|
||||
verify_id: "Vérifier l'ID",
|
||||
obtain_qualified_electronic_signature_with_the_trusted_provider_click_to_learn_more: 'Obtenez une signature électronique qualifiée (QeS) avec le fournisseur de confiance. Cliquez pour en savoir plus.',
|
||||
recurrent: 'Récurrent',
|
||||
one_off: 'Ponctuel',
|
||||
editable: 'Éditable',
|
||||
search_field: 'Champ de recherche',
|
||||
field_not_found: 'Champ non trouvé',
|
||||
clear: 'Effacer',
|
||||
type_value: 'Tapez la valeur',
|
||||
add_all_required_fields_to_continue: 'Ajoutez tous les champs obligatoires pour continuer',
|
||||
uploaded_pdf_contains_form_fields_keep_or_remove_them: 'Le PDF téléchargé contient des champs. Les conserver ou les supprimer?',
|
||||
keep: 'Conserver',
|
||||
align: 'Aligner',
|
||||
left: 'Gauche',
|
||||
heading: 'En-tête',
|
||||
validation: 'Validation',
|
||||
add_blank_page: 'Ajouter une page blanche',
|
||||
right: 'Droite',
|
||||
add_condition: 'Ajouter une condition',
|
||||
center: 'Centre',
|
||||
with_logo: 'Avec logo',
|
||||
description: 'Description',
|
||||
display_title: "Titre d'affichage",
|
||||
unchecked: 'Non coché',
|
||||
price: 'Prix',
|
||||
equal: 'Égal',
|
||||
not_equal: 'Non égal',
|
||||
contains: 'Contient',
|
||||
signing_date: 'Date de signature',
|
||||
does_not_contain: 'Ne contient pas',
|
||||
not_empty: 'Non vide',
|
||||
empty: 'Vide',
|
||||
select_field_: 'Sélectionner un champ...',
|
||||
select_value_: 'Sélectionner une valeur...',
|
||||
remove_condition: 'Supprimer la condition',
|
||||
condition: 'Condition',
|
||||
formula: 'Formule',
|
||||
edit: 'Éditer',
|
||||
settings: 'Paramètres',
|
||||
up: 'Haut',
|
||||
down: 'Bas',
|
||||
set_signing_date: 'Définir la date de signature',
|
||||
are_you_sure_: 'Êtes-vous sûr?',
|
||||
sign_yourself: 'Signez-vous',
|
||||
checked: 'Coché',
|
||||
send: 'Envoyer',
|
||||
remove: 'Supprimer',
|
||||
save: 'Enregistrer',
|
||||
cancel: 'Annuler',
|
||||
draw_field_on_the_document: 'Dessiner un champ sur le document',
|
||||
click_to_upload: 'Cliquez pour télécharger',
|
||||
or_drag_and_drop_files: 'ou faites glisser-déposer des fichiers',
|
||||
uploading: 'Téléchargement en cours',
|
||||
processing_: 'Traitement en cours...',
|
||||
add_pdf_documents_or_images: 'Ajoutez des documents PDF ou des images',
|
||||
add_documents_or_images: 'Ajoutez des documents ou des images',
|
||||
required: 'Requis',
|
||||
default_value: 'Valeur par défaut',
|
||||
format: 'Format',
|
||||
read_only: 'Lecture seule',
|
||||
page: 'Page',
|
||||
draw_new_area: 'Dessiner une nouvelle zone',
|
||||
copy_to_all_pages: 'Copier sur toutes les pages',
|
||||
add_option: 'Ajouter une option',
|
||||
option: 'Option',
|
||||
first_party: 'Première partie',
|
||||
second_party: 'Deuxième partie',
|
||||
third_party: 'Troisième partie',
|
||||
fourth_party: 'Quatrième partie',
|
||||
fifth_party: 'Cinquième partie',
|
||||
sixth_party: 'Sixième partie',
|
||||
seventh_party: 'Septième partie',
|
||||
eighth_party: 'Huitième partie',
|
||||
ninth_party: 'Neuvième partie',
|
||||
tenth_party: 'Dixième partie',
|
||||
eleventh_party: 'Onzième Parti',
|
||||
twelfth_party: 'Douzième Parti',
|
||||
thirteenth_party: 'Treizième Parti',
|
||||
fourteenth_party: 'Quatorzième Parti',
|
||||
fifteenth_party: 'Quinzième Parti',
|
||||
sixteenth_party: 'Seizième Parti',
|
||||
seventeenth_party: 'Dix-septième Parti',
|
||||
eighteenth_party: 'Dix-huitième Parti',
|
||||
nineteenth_party: 'Dix-Neuvième Fête',
|
||||
twentieth_party: 'Vingtième Fête',
|
||||
draw: 'Dessiner',
|
||||
add: 'Ajouter',
|
||||
or_add_field_without_drawing: 'Ou ajoutez un champ sans dessiner',
|
||||
text: 'Texte',
|
||||
signature: 'Signature',
|
||||
initials: 'Initiales',
|
||||
date: 'Date',
|
||||
number: 'Numéro',
|
||||
image: 'Image',
|
||||
file: 'Fichier',
|
||||
select: 'Choisir',
|
||||
checkbox: 'Coche',
|
||||
multiple: 'Multiple',
|
||||
radio: 'Radio',
|
||||
cells: 'Cellules',
|
||||
stamp: 'Tampon',
|
||||
payment: 'Paiement',
|
||||
phone: 'Téléphone',
|
||||
text_field: 'Champ de Texte',
|
||||
signature_field: 'Champ de Signature',
|
||||
initials_field: "Champ d'Initiales",
|
||||
date_field: 'Champ de Date',
|
||||
number_field: 'Champ de Numéro',
|
||||
image_field: "Champ d'Image",
|
||||
file_field: 'Champ de Fichier',
|
||||
select_field: 'Champ de Choix',
|
||||
checkbox_field: 'Champ de Coche',
|
||||
multiple_field: 'Champ de Choix Multiple',
|
||||
radio_field: 'Champ de Groupe Radio',
|
||||
cells_field: 'Champ de Cellules',
|
||||
stamp_field: 'Champ de Tampon',
|
||||
payment_field: 'Champ de Paiement',
|
||||
phone_field: 'Champ de Téléphone',
|
||||
draw_a_text_field_on_the_page_with_a_mouse: 'Dessinez un champ texte sur la page avec une souris',
|
||||
drag_and_drop_any_other_field_type_on_the_page: 'Glissez et déposez tout autre type de champ sur la page',
|
||||
click_on_the_field_type_above_to_start_drawing_it: 'Cliquez sur le type de champ ci-dessus pour commencer à le dessiner',
|
||||
please_draw_fields_to_prepare_the_document: 'Veuillez dessiner les champs pour préparer le document.',
|
||||
only_pdf_and_images_are_supported: 'Seuls les PDF et les images sont pris en charge.',
|
||||
unlock_sms_verified_phone_number_field_with_paid_plan_use_text_field_for_phone_numbers_without_verification: 'Débloquez le champ de numéro de téléphone vérifié par SMS avec un plan payant. Utilisez un champ texte pour les numéros de téléphone sans vérification.',
|
||||
available_only_in_pro: 'Disponible uniquement en Pro',
|
||||
failed_to_download_files: 'Échec du téléchargement des fichiers',
|
||||
please_add_fields_for_the_submitter_name_or_remove_the_submitter_name_if_not_needed: 'Veuillez ajouter des champs pour {submitter_name} ou retirer {submitter_name} si ce n\'est pas nécessaire.',
|
||||
draw_field: 'Dessiner le champ {field}',
|
||||
replace: 'Remplacer',
|
||||
uploading_: 'Téléchargement en cours...',
|
||||
add_document: 'Télécharger',
|
||||
any: 'Tout',
|
||||
drawn: 'Dessiné',
|
||||
typed: 'Tapé',
|
||||
none: 'Aucun',
|
||||
ssn: 'SSN',
|
||||
ein: 'EIN',
|
||||
email: 'E-mail',
|
||||
url: 'URL',
|
||||
zip: 'ZIP',
|
||||
custom: 'Personnalisé',
|
||||
numbers_only: 'Chiffres uniquement',
|
||||
letters_only: 'Lettres uniquement',
|
||||
regexp_validation: 'Validation par expression régulière',
|
||||
enter_pdf_password: 'Entrez le mot de passe PDF',
|
||||
wrong_password: 'Mot de passe incorrect',
|
||||
currency: 'Devise',
|
||||
save_and_preview: 'Enregistrer et prévisualiser',
|
||||
preferences: 'Préférences',
|
||||
available_in_pro: 'Disponible en version Pro',
|
||||
some_fields_are_missing_in_the_formula: 'Certains champs manquent dans la formule.',
|
||||
learn_more: 'En savoir plus',
|
||||
and: 'et',
|
||||
or: 'ou'
|
||||
}
|
||||
|
||||
const de = {
|
||||
verify_id: 'ID überprüfen',
|
||||
obtain_qualified_electronic_signature_with_the_trusted_provider_click_to_learn_more: 'Erhalten Sie eine qualifizierte elektronische Signatur (QeS) beim vertrauenswürdigen Anbieter. Klicken Sie hier, um mehr zu erfahren.',
|
||||
wiederkehrend: 'Wiederkehrend',
|
||||
einmalig: 'Einmalig',
|
||||
editable: 'Bearbeitbar',
|
||||
search_field: 'Suchfeld',
|
||||
field_not_found: 'Feld nicht gefunden',
|
||||
clear: 'Löschen',
|
||||
type_value: 'Wert eingeben',
|
||||
add_all_required_fields_to_continue: 'Fügen Sie alle erforderlichen Felder hinzu, um fortzufahren',
|
||||
uploaded_pdf_contains_form_fields_keep_or_remove_them: 'Das hochgeladene PDF enthält Formularfelder. Behalten oder entfernen?',
|
||||
keep: 'Behalten',
|
||||
align: 'Ausrichten',
|
||||
left: 'Links',
|
||||
heading: 'Überschrift',
|
||||
validation: 'Validierung',
|
||||
add_blank_page: 'Leere Seite hinzufügen',
|
||||
right: 'Rechts',
|
||||
add_condition: 'Bedingung hinzufügen',
|
||||
center: 'Zentriert',
|
||||
with_logo: 'Mit Logo',
|
||||
description: 'Beschreibung',
|
||||
display_title: 'Anzeigetitel',
|
||||
unchecked: 'Nicht überprüft',
|
||||
price: 'Preis',
|
||||
equal: 'Gleich',
|
||||
not_equal: 'Ungleich',
|
||||
contains: 'Enthält',
|
||||
does_not_contain: 'Enthält nicht',
|
||||
not_empty: 'Nicht leer',
|
||||
empty: 'Leer',
|
||||
signing_date: 'Signaturdatum',
|
||||
select_field_: 'Feld auswählen...',
|
||||
select_value_: 'Wert auswählen...',
|
||||
remove_condition: 'Bedingung entfernen',
|
||||
condition: 'Bedingung',
|
||||
formula: 'Formel',
|
||||
edit: 'Bearbeiten',
|
||||
settings: 'Einstellungen',
|
||||
up: 'Nach oben',
|
||||
down: 'Nach unten',
|
||||
set_signing_date: 'Unterschriftdatum festlegen',
|
||||
are_you_sure_: 'Bist du sicher?',
|
||||
sign_yourself: 'Unterschreiben Sie selbst',
|
||||
checked: 'Ausgewählt',
|
||||
send: 'Senden',
|
||||
remove: 'Entfernen',
|
||||
save: 'Speichern',
|
||||
cancel: 'Abbrechen',
|
||||
draw_field_on_the_document: 'Ein Feld auf dem Dokument zeichnen',
|
||||
click_to_upload: 'Klicken Sie zum Hochladen',
|
||||
or_drag_and_drop_files: 'oder Dateien hierher ziehen',
|
||||
uploading: 'Hochladen',
|
||||
processing_: 'Verarbeitung...',
|
||||
add_pdf_documents_or_images: 'PDF-Dokumente oder Bilder hinzufügen',
|
||||
add_documents_or_images: 'Dokumente oder Bilder hinzufügen',
|
||||
required: 'Erforderlich',
|
||||
default_value: 'Standardwert',
|
||||
format: 'Format',
|
||||
read_only: 'Nur lesen',
|
||||
page: 'Seite',
|
||||
draw_new_area: 'Neuen Bereich zeichnen',
|
||||
copy_to_all_pages: 'Auf alle Seiten kopieren',
|
||||
add_option: 'Option hinzufügen',
|
||||
option: 'Option',
|
||||
first_party: 'Erste Partei',
|
||||
second_party: 'Zweite Partei',
|
||||
third_party: 'Dritte Partei',
|
||||
fourth_party: 'Vierte Partei',
|
||||
fifth_party: 'Fünfte Partei',
|
||||
sixth_party: 'Sechste Partei',
|
||||
seventh_party: 'Siebte Partei',
|
||||
eighth_party: 'Achte Partei',
|
||||
ninth_party: 'Neunte Partei',
|
||||
tenth_party: 'Zehnte Partei',
|
||||
eleventh_party: 'Elfte Partei',
|
||||
twelfth_party: 'Zwölfte Partei',
|
||||
thirteenth_party: 'Dreizehnte Partei',
|
||||
fourteenth_party: 'Vierzehnte Partei',
|
||||
fifteenth_party: 'Fünfzehnte Partei',
|
||||
sixteenth_party: 'Sechzehnte Partei',
|
||||
seventeenth_party: 'Siebzehnte Partei',
|
||||
eighteenth_party: 'Achtzehnte Partei',
|
||||
nineteenth_party: 'Neunzehnte Party',
|
||||
twentieth_party: 'Zwanzigste Party',
|
||||
draw: 'Zeichnen',
|
||||
add: 'Hinzufügen',
|
||||
or_add_field_without_drawing: 'Oder Feld ohne Zeichnung hinzufügen',
|
||||
text: 'Text',
|
||||
signature: 'Unterschrift',
|
||||
initials: 'Initialen',
|
||||
date: 'Datum',
|
||||
number: 'Nummer',
|
||||
image: 'Bild',
|
||||
file: 'Datei',
|
||||
select: 'Auswählen',
|
||||
checkbox: 'Checkbox',
|
||||
multiple: 'Mehrere',
|
||||
radio: 'Radio',
|
||||
cells: 'Zellen',
|
||||
stamp: 'Stempel',
|
||||
payment: 'Zahlung',
|
||||
phone: 'Telefon',
|
||||
text_field: 'Textfeld',
|
||||
signature_field: 'Unterschriftsfeld',
|
||||
initials_field: 'Initialenfeld',
|
||||
date_field: 'Datumsfeld',
|
||||
number_field: 'Zahlenfeld',
|
||||
image_field: 'Bildfeld',
|
||||
file_field: 'Dateifeld',
|
||||
select_field: 'Auswahlfeld',
|
||||
checkbox_field: 'Checkbox-Feld',
|
||||
multiple_field: 'Mehrfach-Auswahlfeld',
|
||||
radio_field: 'Radio-Gruppenfeld',
|
||||
cells_field: 'Zellenfeld',
|
||||
stamp_field: 'Stempelfeld',
|
||||
payment_field: 'Zahlungsfeld',
|
||||
phone_field: 'Telefonfeld',
|
||||
draw_a_text_field_on_the_page_with_a_mouse: 'Textfeld auf der Seite mit der Maus zeichnen',
|
||||
drag_and_drop_any_other_field_type_on_the_page: 'Ziehe und lasse einen anderen Feldtyp auf die Seite fallen',
|
||||
click_on_the_field_type_above_to_start_drawing_it: 'Klicke auf den Feldtyp oben, um mit dem Zeichnen zu beginnen',
|
||||
please_draw_fields_to_prepare_the_document: 'Bitte zeichne Felder, um das Dokument vorzubereiten.',
|
||||
only_pdf_and_images_are_supported: 'Nur PDFs und Bilder werden unterstützt.',
|
||||
unlock_sms_verified_phone_number_field_with_paid_plan_use_text_field_for_phone_numbers_without_verification: 'Schalte das SMS-verifizierte Telefonnummernfeld mit einem kostenpflichtigen Plan frei. Verwende das Textfeld für Telefonnummern ohne Verifizierung.',
|
||||
available_only_in_pro: 'Nur in Pro verfügbar',
|
||||
failed_to_download_files: 'Fehler beim Herunterladen der Dateien',
|
||||
please_add_fields_for_the_submitter_name_or_remove_the_submitter_name_if_not_needed: 'Bitte füge Felder für {submitter_name} hinzu oder entferne {submitter_name}, falls nicht erforderlich.',
|
||||
draw_field: 'Feld {field} zeichnen',
|
||||
replace: 'Ersetzen',
|
||||
uploading_: 'Hochladen...',
|
||||
add_document: 'Hochladen',
|
||||
any: 'Jede',
|
||||
drawn: 'Gezeichnet',
|
||||
typed: 'Getippt',
|
||||
none: 'Keine',
|
||||
ssn: 'SSN',
|
||||
ein: 'EIN',
|
||||
email: 'E-Mail',
|
||||
url: 'URL',
|
||||
zip: 'ZIP',
|
||||
custom: 'Benutzerdefiniert',
|
||||
numbers_only: 'Nur Zahlen',
|
||||
letters_only: 'Nur Buchstaben',
|
||||
regexp_validation: 'Regulärer Ausdruck Überprüfung',
|
||||
enter_pdf_password: 'Geben Sie das PDF-Passwort ein',
|
||||
wrong_password: 'Falsches Passwort',
|
||||
currency: 'Währung',
|
||||
save_and_preview: 'Speichern und Vorschau',
|
||||
preferences: 'Einstellungen',
|
||||
available_in_pro: 'In Pro verfügbar',
|
||||
some_fields_are_missing_in_the_formula: 'Einige Felder fehlen in der Formel.',
|
||||
learn_more: 'Erfahren Sie mehr',
|
||||
and: 'und',
|
||||
or: 'oder'
|
||||
}
|
||||
|
||||
export { en, es, it, pt, fr, de }
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div v-if="selectedSheetIndex === null && spreadsheet">
|
||||
<form @submit.prevent="[selectedSheetIndex = $refs.selectWorksheet.value, buildDefaultMappings()]">
|
||||
<label class="label">
|
||||
Select Worksheet
|
||||
{{ t('select_worksheet') }}
|
||||
</label>
|
||||
<select
|
||||
ref="selectWorksheet"
|
||||
@@ -18,7 +18,7 @@
|
||||
</option>
|
||||
</select>
|
||||
<button class="base-button mt-4 w-full">
|
||||
Open
|
||||
{{ t('open') }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
@@ -36,10 +36,10 @@
|
||||
</div>
|
||||
<div class="flex">
|
||||
<div class="relative w-full py-2 px-2 text-sm">
|
||||
Recipient field
|
||||
{{ t('recipient_field') }}
|
||||
</div>
|
||||
<div class="relative w-full py-2 pl-4 text-sm">
|
||||
Spreadsheet column
|
||||
{{ t('spreadsheet_column') }}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
@@ -50,6 +50,7 @@
|
||||
<div class="flex">
|
||||
<select
|
||||
class="base-select !select-sm !h-10"
|
||||
:class="{ '!text-gray-300': !mapping.field_name }"
|
||||
required
|
||||
@change="mapping.field_name = $event.target.value"
|
||||
>
|
||||
@@ -58,13 +59,14 @@
|
||||
value=""
|
||||
:selected="!mapping.field_name"
|
||||
>
|
||||
Select Field
|
||||
{{ t('select_field') }}
|
||||
</option>
|
||||
<option
|
||||
v-for="(field, index) in selectFieldsForSubmitter(submitter)"
|
||||
:key="index"
|
||||
:value="field.name"
|
||||
:selected="mapping.field_name === field.name"
|
||||
class="text-base-content"
|
||||
>
|
||||
{{ field.name }}
|
||||
</option>
|
||||
@@ -75,6 +77,7 @@
|
||||
<div class="w-full relative">
|
||||
<select
|
||||
class="base-select !select-sm !h-10"
|
||||
:class="{ '!text-gray-300': !mapping.column_index && mapping.column_index != 0 }"
|
||||
required
|
||||
@change="mapping.column_index = parseInt($event.target.value)"
|
||||
>
|
||||
@@ -83,7 +86,7 @@
|
||||
value=""
|
||||
:selected="mapping.column_index == null"
|
||||
>
|
||||
Select Column
|
||||
{{ t('select_column') }}
|
||||
</option>
|
||||
<template
|
||||
v-for="(column, index) in columns"
|
||||
@@ -93,6 +96,7 @@
|
||||
v-if="column"
|
||||
:value="index"
|
||||
:selected="index === mapping.column_index"
|
||||
class="text-base-content"
|
||||
>
|
||||
{{ column }}
|
||||
</option>
|
||||
@@ -118,8 +122,8 @@
|
||||
</div>
|
||||
<div class="flex items-center pl-1">
|
||||
<span
|
||||
class="tooltip tooltip-top"
|
||||
data-tip="Remove"
|
||||
class="tooltip tooltip-left"
|
||||
:data-tip="t('remove')"
|
||||
>
|
||||
<button
|
||||
:disabled="mappings.filter((m) => m.submitter_uuid === submitter.uuid).length < 2"
|
||||
@@ -138,7 +142,7 @@
|
||||
@click.prevent="addMapping(submitter)"
|
||||
>
|
||||
<IconPlus class="w-4 h-4" />
|
||||
New Field Mapping
|
||||
{{ t('new_field_mapping') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -152,7 +156,7 @@
|
||||
<div
|
||||
class="px-3 border-y py-2 border-base-300 text-center w-full text-sm font-semibold"
|
||||
>
|
||||
Total entries: {{ submissionsData.length }}
|
||||
{{ t('total_entries') }}: {{ submissionsData.length }}
|
||||
<template v-if="multitenant && submissionsData.length >= 1000">
|
||||
/ 1000
|
||||
</template>
|
||||
@@ -187,10 +191,10 @@
|
||||
<div
|
||||
class="font-medium text-lg mb-1"
|
||||
>
|
||||
Upload CSV or XLSX Spreadsheet
|
||||
{{ t('upload_csv_or_xlsx_spreadsheet') }}
|
||||
</div>
|
||||
<div class="text-sm">
|
||||
<span class="font-medium">Click to Upload</span> or drag and drop files.
|
||||
<span class="font-medium">{{ t('click_to_upload') }}</span> {{ t('or_drag_and_drop_files') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -210,11 +214,11 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="text-center mt-2">
|
||||
Or <a
|
||||
{{ t('or') }} <a
|
||||
:download="`${template.name}.csv`"
|
||||
:href="`data:text/csv;base64,${csvBase64}`"
|
||||
class="link font-medium"
|
||||
>download</a> a spreadsheet to fill and import
|
||||
>{{ t('download') }}</a> {{ t('a_sample_spreadsheet_to_fill_and_import') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -248,6 +252,11 @@ export default {
|
||||
type: String,
|
||||
required: false,
|
||||
default: ''
|
||||
},
|
||||
i18n: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
data () {
|
||||
@@ -270,13 +279,17 @@ export default {
|
||||
|
||||
this.mappings.forEach((mapping) => {
|
||||
if (mapping.field_name && mapping.column_index != null) {
|
||||
submittersIndex[mapping.submitter_uuid] ||= { uuid: mapping.submitter_uuid, fields: [] }
|
||||
submittersIndex[mapping.submitter_uuid] ||= {
|
||||
uuid: mapping.submitter_uuid,
|
||||
role: this.submitters.find((s) => s.uuid === mapping.submitter_uuid).name,
|
||||
fields: []
|
||||
}
|
||||
|
||||
if (['name', 'email', 'phone', 'external_id'].includes(mapping.field_name.toLowerCase())) {
|
||||
submittersIndex[mapping.submitter_uuid][mapping.field_name.toLowerCase()] = row[mapping.column_index]
|
||||
}
|
||||
|
||||
const fieldType = this.fieldTypesIndex[mapping.submitter_uuid][mapping.field_name]
|
||||
const fieldType = this.fieldTypesIndex[mapping.submitter_uuid]?.[mapping.field_name]
|
||||
|
||||
if (fieldType && fieldType !== 'phone') {
|
||||
submittersIndex[mapping.submitter_uuid].fields.push({
|
||||
@@ -287,7 +300,7 @@ export default {
|
||||
})
|
||||
|
||||
if (Object.keys(submittersIndex).length !== 0) {
|
||||
submissions.push({ submitters: Object.values(submittersIndex) })
|
||||
submissions.push({ submitters: this.submitters.map((s) => submittersIndex[s.uuid]).filter(Boolean) })
|
||||
}
|
||||
})
|
||||
|
||||
@@ -354,6 +367,9 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
t (key) {
|
||||
return this.i18n[key] || key
|
||||
},
|
||||
onDropFiles (e) {
|
||||
this.uploadFile(e.dataTransfer.files[0])
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<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">
|
||||
<div class="flex items-center space-x-2">
|
||||
<component
|
||||
:is="fieldIcons[drawField.type]"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<span
|
||||
class="dropdown dropdown-top dropdown-end absolute bottom-4 right-4 z-10 md:hidden"
|
||||
class="dropdown dropdown-top dropdown-end absolute bottom-4 right-4 z-10"
|
||||
>
|
||||
<label
|
||||
class="btn btn-neutral text-white btn-circle btn-lg group"
|
||||
@@ -12,9 +12,10 @@
|
||||
height="28"
|
||||
/>
|
||||
<IconX
|
||||
class="hidden group-focus:inline"
|
||||
width="28"
|
||||
height="28"
|
||||
class="hidden group-focus:inline p-3"
|
||||
width="64"
|
||||
height="50"
|
||||
@click="closeDropdown"
|
||||
/>
|
||||
</label>
|
||||
<ul
|
||||
@@ -55,7 +56,7 @@
|
||||
v-for="(icon, type) in fieldIconsSorted"
|
||||
:key="type"
|
||||
>
|
||||
<li v-if="(fieldTypes.length === 0 || fieldTypes.includes(type)) && (withPhone || type != 'phone') && (withPayment || type != 'payment')">
|
||||
<li v-if="(fieldTypes.length === 0 || fieldTypes.includes(type)) && (withPhone || type != 'phone') && (withPayment || type != 'payment') && (withVerification || type != 'verification')">
|
||||
<a
|
||||
href="#"
|
||||
class="text-sm py-1 px-2"
|
||||
@@ -85,7 +86,7 @@ export default {
|
||||
IconPlus,
|
||||
IconX
|
||||
},
|
||||
inject: ['withPhone', 'withPayment', 'backgroundColor', 't'],
|
||||
inject: ['withPhone', 'withPayment', 'withVerification', 'backgroundColor', 't'],
|
||||
props: {
|
||||
modelValue: {
|
||||
type: String,
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
@click="closeDropdown"
|
||||
>
|
||||
<div
|
||||
v-if="!('price_id' in field.preferences)"
|
||||
class="py-1.5 px-1 relative"
|
||||
@click.stop
|
||||
>
|
||||
@@ -43,7 +44,7 @@
|
||||
class="absolute -top-1 left-2.5 px-1 h-4"
|
||||
style="font-size: 8px"
|
||||
>
|
||||
Currency
|
||||
{{ t('currency') }}
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
@@ -58,6 +59,13 @@
|
||||
class="input input-bordered input-xs w-full max-w-xs h-7 !outline-0"
|
||||
@blur="save"
|
||||
>
|
||||
<input
|
||||
v-else-if="'price_id' in field.preferences"
|
||||
v-model="field.preferences.price_id"
|
||||
placeholder="Price ID: price_XXXXX"
|
||||
class="input input-bordered input-xs w-full max-w-xs h-7 !outline-0"
|
||||
@blur="save"
|
||||
>
|
||||
<input
|
||||
v-else
|
||||
v-model="field.preferences.price"
|
||||
@@ -72,8 +80,25 @@
|
||||
class="absolute -top-1 left-2.5 px-1 h-4"
|
||||
style="font-size: 8px"
|
||||
>
|
||||
Price
|
||||
{{ t('price') }}
|
||||
</label>
|
||||
<div class="flex items-center justify-center">
|
||||
<a
|
||||
href="#"
|
||||
class="hover:underline"
|
||||
style="font-size: 11px"
|
||||
:class="{'underline': !('price_id' in field.preferences)}"
|
||||
@click="delete field.preferences.price_id"
|
||||
>{{ t('one_off') }}</a>
|
||||
<span class="h-2.5 border-l border-base-content mx-1" />
|
||||
<a
|
||||
href="#"
|
||||
class="hover:underline"
|
||||
style="font-size: 11px"
|
||||
:class="{'underline': ('price_id' in field.preferences)}"
|
||||
@click="field.preferences.price_id ??= ''"
|
||||
>{{ t('recurrent') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="!isConnected || isOauthSuccess"
|
||||
@@ -153,12 +178,15 @@
|
||||
<a
|
||||
v-if="!isConnected"
|
||||
class="block link text-center mt-1"
|
||||
href="https://www.docuseal.co/blog/accept-payments-and-request-signatures-with-ease"
|
||||
href="https://www.docuseal.com/blog/accept-payments-and-request-signatures-with-ease"
|
||||
target="_blank"
|
||||
data-turbo="false"
|
||||
>Learn more</a>
|
||||
>{{ t('learn_more') }}</a>
|
||||
</div>
|
||||
<li class="mb-1">
|
||||
<li
|
||||
v-if="!('price_id' in field.preferences)"
|
||||
class="mb-1"
|
||||
>
|
||||
<label
|
||||
class="label-text cursor-pointer text-center w-full flex items-center"
|
||||
@click="$emit('click-formula')"
|
||||
|
||||
@@ -9,14 +9,30 @@
|
||||
loading="lazy"
|
||||
>
|
||||
<div
|
||||
class="group flex justify-end cursor-pointer top-0 bottom-0 left-0 right-0 absolute p-1"
|
||||
class="group flex justify-end cursor-pointer top-0 bottom-0 left-0 right-0 absolute p-1 hover:bg-black/10 transition-colors"
|
||||
@click="$emit('scroll-to', item)"
|
||||
>
|
||||
<div
|
||||
v-if="editable"
|
||||
class="flex justify-between w-full"
|
||||
>
|
||||
<div style="width: 26px" />
|
||||
<div
|
||||
style="width: 26px"
|
||||
class="flex flex-col justify-between group-hover:opacity-100"
|
||||
:class="{'opacity-0': !item.conditions?.length }"
|
||||
>
|
||||
<div>
|
||||
<button
|
||||
class="btn border-base-200 bg-white text-base-content btn-xs rounded hover:text-base-100 hover:bg-base-content hover:border-base-content w-full transition-colors p-0"
|
||||
@click.stop="isShowConditionsModal = true"
|
||||
>
|
||||
<IconRouteAltLeft
|
||||
:width="14"
|
||||
:stroke-width="1.6"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="">
|
||||
<ReplaceButton
|
||||
v-if="withReplaceButton"
|
||||
@@ -73,19 +89,36 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Teleport
|
||||
v-if="isShowConditionsModal"
|
||||
:to="modalContainerEl"
|
||||
>
|
||||
<ConditionsModal
|
||||
:item="item"
|
||||
:build-default-name="buildDefaultName"
|
||||
@close="isShowConditionsModal = false"
|
||||
/>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Contenteditable from './contenteditable'
|
||||
import Upload from './upload'
|
||||
import { IconRouteAltLeft } from '@tabler/icons-vue'
|
||||
import ConditionsModal from './conditions_modal'
|
||||
import ReplaceButton from './replace'
|
||||
import Field from './field'
|
||||
import FieldType from './field_type'
|
||||
|
||||
export default {
|
||||
name: 'DocumentPreview',
|
||||
components: {
|
||||
Contenteditable,
|
||||
IconRouteAltLeft,
|
||||
ConditionsModal,
|
||||
ReplaceButton
|
||||
},
|
||||
inject: ['t'],
|
||||
props: {
|
||||
item: {
|
||||
type: Object,
|
||||
@@ -121,13 +154,24 @@ export default {
|
||||
}
|
||||
},
|
||||
emits: ['scroll-to', 'change', 'remove', 'up', 'down', 'replace'],
|
||||
data () {
|
||||
return {
|
||||
isShowConditionsModal: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
fieldNames: FieldType.computed.fieldNames,
|
||||
fieldLabels: FieldType.computed.fieldLabels,
|
||||
previewImage () {
|
||||
return [...this.document.preview_images].sort((a, b) => parseInt(a.filename) - parseInt(b.filename))[0]
|
||||
},
|
||||
modalContainerEl () {
|
||||
return this.$el.getRootNode().querySelector('#docuseal_modal_container')
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
upload: Upload.methods.upload,
|
||||
buildDefaultName: Field.methods.buildDefaultName,
|
||||
onUpdateName (value) {
|
||||
this.item.name = value
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user