Compare commits

..
39 Commits
Author SHA1 Message Date
Oleksandr Turchyn 94e552d170 improve translations 2024-10-07 15:30:39 +03:00
Pete Matsyburka cf1b0a4c16 add noverify smtp option 2024-10-07 15:30:39 +03:00
Pete Matsyburka 9aa0a8ecbf skip autocorrect tld 2024-10-07 15:30:39 +03:00
Pete Matsyburka fd4bef1706 fix i18n 2024-10-07 15:30:39 +03:00
Pete Matsyburka d32b0e6d20 add completed id 2024-10-07 15:30:39 +03:00
Pete Matsyburka 4a846be214 fix js 2024-10-07 15:30:39 +03:00
Pete Matsyburka 3dd3877ea9 update deps 2024-10-07 15:30:39 +03:00
Pete Matsyburka b4dba0d809 fix smtp without password 2024-10-07 15:30:39 +03:00
Pete Matsyburka dea80a82d8 fix pagination 2024-10-07 15:30:39 +03:00
Pete Matsyburka eefa994724 rails 7.2 2024-10-07 15:30:39 +03:00
Pete Matsyburka 35cce24539 adjust audit values 2024-10-07 15:30:39 +03:00
Pete Matsyburka e6c8203959 fix variables 2024-10-07 15:30:39 +03:00
Oleksandr Turchyn 9479771fc8 fix i18n 2024-10-07 15:30:39 +03:00
Pete Matsyburka 711427521c fix file url performance 2024-10-07 15:30:39 +03:00
Alex Turchyn 5dae742361 add I18n invitation email 2024-10-07 15:30:39 +03:00
Pete Matsyburka d8ccd4beee i18n signature 2024-10-07 15:30:39 +03:00
Pete Matsyburka c532a0115d remove audit log file links 2024-10-07 15:30:39 +03:00
Alex Turchyn c9c3364764 add matcher 2024-10-07 15:30:39 +03:00
Alex Turchyn 1ff23d7c40 add I18n Audit log 2024-10-07 15:30:39 +03:00
Pete Matsyburka 95e9b34033 fix sign field name 2024-10-07 15:30:39 +03:00
Pete Matsyburka 6b42dcf91b adjust combined pdf sign reason 2024-10-07 15:30:39 +03:00
Pete Matsyburka 28962c949b configure audit log values 2024-10-07 15:30:39 +03:00
Pete Matsyburka 4ecbbf8410 update rubocop 2024-09-30 12:13:07 +03:00
Pete Matsyburka 990b35be18 i18n emails 2024-09-30 12:13:07 +03:00
Pete Matsyburka 8002764855 update readme 2024-09-30 12:13:07 +03:00
Pete Matsyburka b80982658c fix i18n 2024-09-30 12:13:07 +03:00
Pete Matsyburka 443ac23c29 fix account form 2024-09-30 12:13:07 +03:00
Pete Matsyburka 37bb987e3e fix i18n 2024-09-30 12:13:07 +03:00
Alex Turchyn c7b08e8d30 add languages 2024-09-30 12:13:07 +03:00
Pete Matsyburka dd502e8cef remove unused code 2024-09-30 12:13:07 +03:00
Pete Matsyburka a554182e57 fix html modal 2024-09-30 12:13:07 +03:00
Pete Matsyburka 0c039ee64f fix tempalte clone roles 2024-09-30 12:13:07 +03:00
Pete Matsyburka c6b004f107 fix submitter name 2024-09-30 12:13:07 +03:00
Pete Matsyburka 2fcb08d27d await for completion when order enforced 2024-09-30 12:13:07 +03:00
Pete Matsyburka 75fe5ce381 fix custom certs 2024-09-30 12:13:07 +03:00
Pete Matsyburka fd7d0eaf53 enforce order toggle 2024-09-30 12:13:07 +03:00
Pete Matsyburka 7e537e4232 adjust signing certs 2024-09-30 12:13:07 +03:00
Pete Matsyburka a892975010 adjust download documents 2024-09-30 12:13:07 +03:00
Alex Turchyn 05ec92186b fix i18n 2024-09-30 12:13:07 +03:00
103 changed files with 5709 additions and 1755 deletions

No files matched your search

+1 -2
View File
@@ -23,7 +23,6 @@ gem 'image_processing'
gem 'jwt' gem 'jwt'
gem 'lograge' gem 'lograge'
gem 'mysql2', require: false gem 'mysql2', require: false
gem 'net-smtp', '0.4.0'
gem 'oj' gem 'oj'
gem 'pagy' gem 'pagy'
gem 'pg', require: false gem 'pg', require: false
@@ -40,7 +39,7 @@ gem 'ruby-vips'
gem 'rubyXL' gem 'rubyXL'
gem 'shakapacker' gem 'shakapacker'
gem 'sidekiq' gem 'sidekiq'
gem 'sqlite3', require: false, force_ruby_platform: true gem 'sqlite3', require: false
gem 'strip_attributes' gem 'strip_attributes'
gem 'turbo-rails' gem 'turbo-rails'
gem 'twitter_cldr', require: false gem 'twitter_cldr', require: false
+174 -171
View File
@@ -1,105 +1,101 @@
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
actioncable (7.1.3.4) actioncable (7.2.1)
actionpack (= 7.1.3.4) actionpack (= 7.2.1)
activesupport (= 7.1.3.4) activesupport (= 7.2.1)
nio4r (~> 2.0) nio4r (~> 2.0)
websocket-driver (>= 0.6.1) websocket-driver (>= 0.6.1)
zeitwerk (~> 2.6) zeitwerk (~> 2.6)
actionmailbox (7.1.3.4) actionmailbox (7.2.1)
actionpack (= 7.1.3.4) actionpack (= 7.2.1)
activejob (= 7.1.3.4) activejob (= 7.2.1)
activerecord (= 7.1.3.4) activerecord (= 7.2.1)
activestorage (= 7.1.3.4) activestorage (= 7.2.1)
activesupport (= 7.1.3.4) activesupport (= 7.2.1)
mail (>= 2.7.1) mail (>= 2.8.0)
net-imap actionmailer (7.2.1)
net-pop actionpack (= 7.2.1)
net-smtp actionview (= 7.2.1)
actionmailer (7.1.3.4) activejob (= 7.2.1)
actionpack (= 7.1.3.4) activesupport (= 7.2.1)
actionview (= 7.1.3.4) mail (>= 2.8.0)
activejob (= 7.1.3.4)
activesupport (= 7.1.3.4)
mail (~> 2.5, >= 2.5.4)
net-imap
net-pop
net-smtp
rails-dom-testing (~> 2.2) rails-dom-testing (~> 2.2)
actionpack (7.1.3.4) actionpack (7.2.1)
actionview (= 7.1.3.4) actionview (= 7.2.1)
activesupport (= 7.1.3.4) activesupport (= 7.2.1)
nokogiri (>= 1.8.5) nokogiri (>= 1.8.5)
racc racc
rack (>= 2.2.4) rack (>= 2.2.4, < 3.2)
rack-session (>= 1.0.1) rack-session (>= 1.0.1)
rack-test (>= 0.6.3) rack-test (>= 0.6.3)
rails-dom-testing (~> 2.2) rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6) rails-html-sanitizer (~> 1.6)
actiontext (7.1.3.4) useragent (~> 0.16)
actionpack (= 7.1.3.4) actiontext (7.2.1)
activerecord (= 7.1.3.4) actionpack (= 7.2.1)
activestorage (= 7.1.3.4) activerecord (= 7.2.1)
activesupport (= 7.1.3.4) activestorage (= 7.2.1)
activesupport (= 7.2.1)
globalid (>= 0.6.0) globalid (>= 0.6.0)
nokogiri (>= 1.8.5) nokogiri (>= 1.8.5)
actionview (7.1.3.4) actionview (7.2.1)
activesupport (= 7.1.3.4) activesupport (= 7.2.1)
builder (~> 3.1) builder (~> 3.1)
erubi (~> 1.11) erubi (~> 1.11)
rails-dom-testing (~> 2.2) rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6) rails-html-sanitizer (~> 1.6)
activejob (7.1.3.4) activejob (7.2.1)
activesupport (= 7.1.3.4) activesupport (= 7.2.1)
globalid (>= 0.3.6) globalid (>= 0.3.6)
activemodel (7.1.3.4) activemodel (7.2.1)
activesupport (= 7.1.3.4) activesupport (= 7.2.1)
activerecord (7.1.3.4) activerecord (7.2.1)
activemodel (= 7.1.3.4) activemodel (= 7.2.1)
activesupport (= 7.1.3.4) activesupport (= 7.2.1)
timeout (>= 0.4.0) timeout (>= 0.4.0)
activestorage (7.1.3.4) activestorage (7.2.1)
actionpack (= 7.1.3.4) actionpack (= 7.2.1)
activejob (= 7.1.3.4) activejob (= 7.2.1)
activerecord (= 7.1.3.4) activerecord (= 7.2.1)
activesupport (= 7.1.3.4) activesupport (= 7.2.1)
marcel (~> 1.0) marcel (~> 1.0)
activesupport (7.1.3.4) activesupport (7.2.1)
base64 base64
bigdecimal bigdecimal
concurrent-ruby (~> 1.0, >= 1.0.2) concurrent-ruby (~> 1.0, >= 1.3.1)
connection_pool (>= 2.2.5) connection_pool (>= 2.2.5)
drb drb
i18n (>= 1.6, < 2) i18n (>= 1.6, < 2)
logger (>= 1.4.2)
minitest (>= 5.1) minitest (>= 5.1)
mutex_m securerandom (>= 0.3)
tzinfo (~> 2.0) tzinfo (~> 2.0, >= 2.0.5)
addressable (2.8.6) addressable (2.8.7)
public_suffix (>= 2.0.2, < 6.0) public_suffix (>= 2.0.2, < 7.0)
annotate (3.2.0) annotate (3.2.0)
activerecord (>= 3.2, < 8.0) activerecord (>= 3.2, < 8.0)
rake (>= 10.4, < 14.0) rake (>= 10.4, < 14.0)
arabic-letter-connector (0.1.1) arabic-letter-connector (0.1.1)
ast (2.4.2) ast (2.4.2)
aws-eventstream (1.3.0) aws-eventstream (1.3.0)
aws-partitions (1.913.0) aws-partitions (1.985.0)
aws-sdk-core (3.191.6) aws-sdk-core (3.209.1)
aws-eventstream (~> 1, >= 1.3.0) aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.651.0) aws-partitions (~> 1, >= 1.651.0)
aws-sigv4 (~> 1.8) aws-sigv4 (~> 1.9)
jmespath (~> 1, >= 1.6.1) jmespath (~> 1, >= 1.6.1)
aws-sdk-kms (1.79.0) aws-sdk-kms (1.94.0)
aws-sdk-core (~> 3, >= 3.191.0) aws-sdk-core (~> 3, >= 3.207.0)
aws-sigv4 (~> 1.1) aws-sigv4 (~> 1.5)
aws-sdk-s3 (1.146.1) aws-sdk-s3 (1.167.0)
aws-sdk-core (~> 3, >= 3.191.0) aws-sdk-core (~> 3, >= 3.207.0)
aws-sdk-kms (~> 1) aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.8) aws-sigv4 (~> 1.5)
aws-sdk-secretsmanager (1.91.0) aws-sdk-secretsmanager (1.108.0)
aws-sdk-core (~> 3, >= 3.191.0) aws-sdk-core (~> 3, >= 3.207.0)
aws-sigv4 (~> 1.1) aws-sigv4 (~> 1.5)
aws-sigv4 (1.8.0) aws-sigv4 (1.10.0)
aws-eventstream (~> 1, >= 1.0.2) aws-eventstream (~> 1, >= 1.0.2)
azure-storage-blob (2.0.3) azure-storage-blob (2.0.3)
azure-storage-common (~> 2.0) azure-storage-common (~> 2.0)
@@ -120,14 +116,14 @@ GEM
smart_properties smart_properties
bigdecimal (3.1.8) bigdecimal (3.1.8)
bindex (0.8.1) bindex (0.8.1)
bootsnap (1.18.3) bootsnap (1.18.4)
msgpack (~> 1.2) msgpack (~> 1.2)
builder (3.3.0) builder (3.3.0)
bullet (7.1.6) bullet (7.2.0)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
uniform_notifier (~> 1.11) uniform_notifier (~> 1.11)
camertron-eprun (1.1.1) camertron-eprun (1.1.1)
cancancan (3.5.0) cancancan (3.6.1)
capybara (3.40.0) capybara (3.40.0)
addressable addressable
matrix matrix
@@ -137,7 +133,8 @@ GEM
rack-test (>= 0.6.3) rack-test (>= 0.6.3)
regexp_parser (>= 1.5, < 3.0) regexp_parser (>= 1.5, < 3.0)
xpath (~> 3.2) xpath (~> 3.2)
childprocess (5.0.0) childprocess (5.1.0)
logger (~> 1.5)
chunky_png (1.4.0) chunky_png (1.4.0)
cldr-plurals-runtime-rb (1.1.0) cldr-plurals-runtime-rb (1.1.0)
cmdparse (3.0.7) cmdparse (3.0.7)
@@ -148,12 +145,12 @@ GEM
bigdecimal bigdecimal
rexml rexml
crass (1.0.6) crass (1.0.6)
css_parser (1.17.1) css_parser (1.19.0)
addressable addressable
csv (3.3.0) csv (3.3.0)
cuprite (0.15) cuprite (0.15.1)
capybara (~> 3.0) capybara (~> 3.0)
ferrum (~> 0.14.0) ferrum (~> 0.15.0)
date (3.3.4) date (3.3.4)
debug (1.9.2) debug (1.9.2)
irb (~> 1.10) irb (~> 1.10)
@@ -173,26 +170,26 @@ GEM
diff-lcs (1.5.1) diff-lcs (1.5.1)
digest-crc (0.6.5) digest-crc (0.6.5)
rake (>= 12.0.0, < 14.0.0) rake (>= 12.0.0, < 14.0.0)
docile (1.4.0) docile (1.4.1)
dotenv (3.1.0) dotenv (3.1.4)
drb (2.2.1) drb (2.2.1)
email_typo (0.2.3) email_typo (0.2.3)
erb_lint (0.5.0) erb_lint (0.6.0)
activesupport activesupport
better_html (>= 2.0.1) better_html (>= 2.0.1)
parser (>= 2.7.1.4) parser (>= 2.7.1.4)
rainbow rainbow
rubocop rubocop (>= 1)
smart_properties smart_properties
erubi (1.13.0) erubi (1.13.0)
factory_bot (6.4.6) factory_bot (6.5.0)
activesupport (>= 5.0.0) activesupport (>= 5.0.0)
factory_bot_rails (6.4.3) factory_bot_rails (6.4.3)
factory_bot (~> 6.4) factory_bot (~> 6.4)
railties (>= 5.0.0) railties (>= 5.0.0)
faker (3.3.1) faker (3.4.2)
i18n (>= 1.8.11, < 2) i18n (>= 1.8.11, < 2)
faraday (1.10.3) faraday (1.10.4)
faraday-em_http (~> 1.0) faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0) faraday-em_synchrony (~> 1.0)
faraday-excon (~> 1.1) faraday-excon (~> 1.1)
@@ -212,58 +209,60 @@ GEM
faraday-httpclient (1.0.1) faraday-httpclient (1.0.1)
faraday-multipart (1.0.4) faraday-multipart (1.0.4)
multipart-post (~> 2) multipart-post (~> 2)
faraday-net_http (1.0.1) faraday-net_http (1.0.2)
faraday-net_http_persistent (1.2.0) faraday-net_http_persistent (1.2.0)
faraday-patron (1.0.0) faraday-patron (1.0.0)
faraday-rack (1.0.0) faraday-rack (1.0.0)
faraday-retry (1.0.3) faraday-retry (1.0.3)
faraday_middleware (1.2.0) faraday_middleware (1.2.1)
faraday (~> 1.0) faraday (~> 1.0)
ferrum (0.14) ferrum (0.15)
addressable (~> 2.5) addressable (~> 2.5)
concurrent-ruby (~> 1.1) concurrent-ruby (~> 1.1)
webrick (~> 1.7) webrick (~> 1.7)
websocket-driver (>= 0.6, < 0.8) websocket-driver (~> 0.7)
ffi (1.16.3) ffi (1.17.0)
ffi (1.17.0-aarch64-linux-gnu)
ffi (1.17.0-x86_64-linux-gnu)
geom2d (0.4.1) geom2d (0.4.1)
globalid (1.2.1) globalid (1.2.1)
activesupport (>= 6.1) activesupport (>= 6.1)
google-apis-core (0.14.1) google-apis-core (0.15.1)
addressable (~> 2.5, >= 2.5.1) addressable (~> 2.5, >= 2.5.1)
googleauth (~> 1.9) googleauth (~> 1.9)
httpclient (>= 2.8.1, < 3.a) httpclient (>= 2.8.3, < 3.a)
mini_mime (~> 1.0) mini_mime (~> 1.0)
mutex_m
representable (~> 3.0) representable (~> 3.0)
retriable (>= 2.0, < 4.a) retriable (>= 2.0, < 4.a)
rexml google-apis-iamcredentials_v1 (0.21.0)
google-apis-iamcredentials_v1 (0.20.0) google-apis-core (>= 0.15.0, < 2.a)
google-apis-core (>= 0.14.0, < 2.a) google-apis-storage_v1 (0.46.0)
google-apis-storage_v1 (0.37.0) google-apis-core (>= 0.15.0, < 2.a)
google-apis-core (>= 0.14.0, < 2.a) google-cloud-core (1.7.1)
google-cloud-core (1.7.0)
google-cloud-env (>= 1.0, < 3.a) google-cloud-env (>= 1.0, < 3.a)
google-cloud-errors (~> 1.0) google-cloud-errors (~> 1.0)
google-cloud-env (2.1.1) google-cloud-env (2.2.1)
faraday (>= 1.0, < 3.a) faraday (>= 1.0, < 3.a)
google-cloud-errors (1.4.0) google-cloud-errors (1.4.0)
google-cloud-storage (1.49.0) google-cloud-storage (1.52.0)
addressable (~> 2.8) addressable (~> 2.8)
digest-crc (~> 0.4) digest-crc (~> 0.4)
google-apis-core (~> 0.13) google-apis-core (~> 0.13)
google-apis-iamcredentials_v1 (~> 0.18) google-apis-iamcredentials_v1 (~> 0.18)
google-apis-storage_v1 (~> 0.33) google-apis-storage_v1 (~> 0.38)
google-cloud-core (~> 1.6) google-cloud-core (~> 1.6)
googleauth (~> 1.9) googleauth (~> 1.9)
mini_mime (~> 1.0) mini_mime (~> 1.0)
googleauth (1.11.0) googleauth (1.11.1)
faraday (>= 1.0, < 3.a) faraday (>= 1.0, < 3.a)
google-cloud-env (~> 2.1) google-cloud-env (~> 2.1)
jwt (>= 1.4, < 3.0) jwt (>= 1.4, < 3.0)
multi_json (~> 1.11) multi_json (~> 1.11)
os (>= 0.9, < 2.0) os (>= 0.9, < 2.0)
signet (>= 0.16, < 2.a) signet (>= 0.16, < 2.a)
hashdiff (1.1.0) hashdiff (1.1.1)
hexapdf (0.42.0) hexapdf (0.47.0)
cmdparse (~> 3.0, >= 3.0.3) cmdparse (~> 3.0, >= 3.0.3)
geom2d (~> 0.4, >= 0.4.1) geom2d (~> 0.4, >= 0.4.1)
openssl (>= 2.2.1) openssl (>= 2.2.1)
@@ -271,28 +270,29 @@ GEM
httpclient (2.8.3) httpclient (2.8.3)
i18n (1.14.6) i18n (1.14.6)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
image_processing (1.12.2) image_processing (1.13.0)
mini_magick (>= 4.9.5, < 5) mini_magick (>= 4.9.5, < 5)
ruby-vips (>= 2.0.17, < 3) ruby-vips (>= 2.0.17, < 3)
io-console (0.7.2) io-console (0.7.2)
irb (1.14.0) irb (1.14.1)
rdoc (>= 4.0.0) rdoc (>= 4.0.0)
reline (>= 0.4.2) reline (>= 0.4.2)
jmespath (1.6.2) jmespath (1.6.2)
json (2.7.2) json (2.7.2)
jwt (2.8.1) jwt (2.9.3)
base64 base64
language_server-protocol (3.17.0.3) language_server-protocol (3.17.0.3)
launchy (3.0.0) launchy (3.0.1)
addressable (~> 2.8) addressable (~> 2.8)
childprocess (~> 5.0) childprocess (~> 5.0)
letter_opener (1.10.0) letter_opener (1.10.0)
launchy (>= 2.2, < 4) launchy (>= 2.2, < 4)
letter_opener_web (2.0.0) letter_opener_web (3.0.0)
actionmailer (>= 5.2) actionmailer (>= 6.1)
letter_opener (~> 1.7) letter_opener (~> 1.9)
railties (>= 5.2) railties (>= 6.1)
rexml rexml
logger (1.6.1)
lograge (0.14.0) lograge (0.14.0)
actionpack (>= 4) actionpack (>= 4)
activesupport (>= 4) activesupport (>= 4)
@@ -309,25 +309,25 @@ GEM
marcel (1.0.4) marcel (1.0.4)
matrix (0.4.2) matrix (0.4.2)
method_source (1.1.0) method_source (1.1.0)
mini_magick (4.12.0) mini_magick (4.13.2)
mini_mime (1.1.5) mini_mime (1.1.5)
mini_portile2 (2.8.7) mini_portile2 (2.8.7)
minitest (5.25.1) minitest (5.25.1)
msgpack (1.7.2) msgpack (1.7.3)
multi_json (1.15.0) multi_json (1.15.0)
multipart-post (2.4.0) multipart-post (2.4.1)
mutex_m (0.2.0) mutex_m (0.2.0)
mysql2 (0.5.6) mysql2 (0.5.6)
net-http-persistent (4.0.2) net-http-persistent (4.0.4)
connection_pool (~> 2.2) connection_pool (~> 2.2)
net-imap (0.4.14) net-imap (0.4.16)
date date
net-protocol net-protocol
net-pop (0.1.2) net-pop (0.1.2)
net-protocol net-protocol
net-protocol (0.2.2) net-protocol (0.2.2)
timeout timeout
net-smtp (0.4.0) net-smtp (0.5.0)
net-protocol net-protocol
nio4r (2.7.3) nio4r (2.7.3)
nokogiri (1.16.7) nokogiri (1.16.7)
@@ -337,21 +337,23 @@ GEM
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.16.7-x86_64-linux) nokogiri (1.16.7-x86_64-linux)
racc (~> 1.4) racc (~> 1.4)
oj (3.16.3) oj (3.16.6)
bigdecimal (>= 3.0) bigdecimal (>= 3.0)
ostruct (>= 0.2)
openssl (3.2.0) openssl (3.2.0)
orm_adapter (0.5.0) orm_adapter (0.5.0)
os (1.1.4) os (1.1.4)
ostruct (0.6.0)
package_json (0.1.0) package_json (0.1.0)
pagy (8.1.2) pagy (9.1.0)
parallel (1.25.1) parallel (1.26.3)
parser (3.3.4.0) parser (3.3.5.0)
ast (~> 2.4.1) ast (~> 2.4.1)
racc racc
pg (1.5.6) pg (1.5.8)
premailer (1.23.0) premailer (1.27.0)
addressable addressable
css_parser (>= 1.12.0) css_parser (>= 1.19.0)
htmlentities (>= 4.0.0) htmlentities (>= 4.0.0)
premailer-rails (1.12.0) premailer-rails (1.12.0)
actionmailer (>= 3) actionmailer (>= 3)
@@ -362,11 +364,11 @@ GEM
pry (0.14.2) pry (0.14.2)
coderay (~> 1.1) coderay (~> 1.1)
method_source (~> 1.0) method_source (~> 1.0)
pry-rails (0.3.9) pry-rails (0.3.11)
pry (>= 0.10.4) pry (>= 0.13.0)
psych (5.1.2) psych (5.1.2)
stringio stringio
public_suffix (5.0.5) public_suffix (6.0.1)
puma (6.4.3) puma (6.4.3)
nio4r (~> 2.0) nio4r (~> 2.0)
racc (1.8.1) racc (1.8.1)
@@ -380,20 +382,20 @@ GEM
rackup (2.1.0) rackup (2.1.0)
rack (>= 3) rack (>= 3)
webrick (~> 1.8) webrick (~> 1.8)
rails (7.1.3.4) rails (7.2.1)
actioncable (= 7.1.3.4) actioncable (= 7.2.1)
actionmailbox (= 7.1.3.4) actionmailbox (= 7.2.1)
actionmailer (= 7.1.3.4) actionmailer (= 7.2.1)
actionpack (= 7.1.3.4) actionpack (= 7.2.1)
actiontext (= 7.1.3.4) actiontext (= 7.2.1)
actionview (= 7.1.3.4) actionview (= 7.2.1)
activejob (= 7.1.3.4) activejob (= 7.2.1)
activemodel (= 7.1.3.4) activemodel (= 7.2.1)
activerecord (= 7.1.3.4) activerecord (= 7.2.1)
activestorage (= 7.1.3.4) activestorage (= 7.2.1)
activesupport (= 7.1.3.4) activesupport (= 7.2.1)
bundler (>= 1.15.0) bundler (>= 1.15.0)
railties (= 7.1.3.4) railties (= 7.2.1)
rails-dom-testing (2.2.0) rails-dom-testing (2.2.0)
activesupport (>= 5.0.0) activesupport (>= 5.0.0)
minitest minitest
@@ -408,10 +410,10 @@ GEM
actionview (> 3.1) actionview (> 3.1)
activesupport (> 3.1) activesupport (> 3.1)
railties (> 3.1) railties (> 3.1)
railties (7.1.3.4) railties (7.2.1)
actionpack (= 7.1.3.4) actionpack (= 7.2.1)
activesupport (= 7.1.3.4) activesupport (= 7.2.1)
irb irb (~> 1.13)
rackup (>= 1.0.0) rackup (>= 1.0.0)
rake (>= 12.2) rake (>= 12.2)
thor (~> 1.0, >= 1.2.2) thor (~> 1.0, >= 1.2.2)
@@ -429,79 +431,80 @@ GEM
declarative (< 0.1.0) declarative (< 0.1.0)
trailblazer-option (>= 0.1.1, < 0.2.0) trailblazer-option (>= 0.1.1, < 0.2.0)
uber (< 0.2.0) uber (< 0.2.0)
request_store (1.6.0) request_store (1.7.0)
rack (>= 1.4) rack (>= 1.4)
responders (3.1.1) responders (3.1.1)
actionpack (>= 5.2) actionpack (>= 5.2)
railties (>= 5.2) railties (>= 5.2)
retriable (3.1.2) retriable (3.1.2)
rexml (3.3.6) rexml (3.3.8)
strscan
rotp (6.3.0) rotp (6.3.0)
rqrcode (2.2.0) rqrcode (2.2.0)
chunky_png (~> 1.0) chunky_png (~> 1.0)
rqrcode_core (~> 1.0) rqrcode_core (~> 1.0)
rqrcode_core (1.2.0) rqrcode_core (1.2.0)
rspec-core (3.13.0) rspec-core (3.13.1)
rspec-support (~> 3.13.0) rspec-support (~> 3.13.0)
rspec-expectations (3.13.0) rspec-expectations (3.13.3)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0) rspec-support (~> 3.13.0)
rspec-mocks (3.13.0) rspec-mocks (3.13.2)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0) rspec-support (~> 3.13.0)
rspec-rails (6.1.2) rspec-rails (7.0.1)
actionpack (>= 6.1) actionpack (>= 7.0)
activesupport (>= 6.1) activesupport (>= 7.0)
railties (>= 6.1) railties (>= 7.0)
rspec-core (~> 3.13) rspec-core (~> 3.13)
rspec-expectations (~> 3.13) rspec-expectations (~> 3.13)
rspec-mocks (~> 3.13) rspec-mocks (~> 3.13)
rspec-support (~> 3.13) rspec-support (~> 3.13)
rspec-support (3.13.1) rspec-support (3.13.1)
rubocop (1.65.0) rubocop (1.66.1)
json (~> 2.3) json (~> 2.3)
language_server-protocol (>= 3.17.0) language_server-protocol (>= 3.17.0)
parallel (~> 1.10) parallel (~> 1.10)
parser (>= 3.3.0.2) parser (>= 3.3.0.2)
rainbow (>= 2.2.2, < 4.0) rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 2.4, < 3.0) regexp_parser (>= 2.4, < 3.0)
rexml (>= 3.2.5, < 4.0) rubocop-ast (>= 1.32.2, < 2.0)
rubocop-ast (>= 1.31.1, < 2.0)
ruby-progressbar (~> 1.7) ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 3.0) unicode-display_width (>= 2.4.0, < 3.0)
rubocop-ast (1.31.3) rubocop-ast (1.32.3)
parser (>= 3.3.1.0) parser (>= 3.3.1.0)
rubocop-performance (1.21.0) rubocop-performance (1.22.1)
rubocop (>= 1.48.1, < 2.0) rubocop (>= 1.48.1, < 2.0)
rubocop-ast (>= 1.31.1, < 2.0) rubocop-ast (>= 1.31.1, < 2.0)
rubocop-rails (2.24.1) rubocop-rails (2.26.2)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
rack (>= 1.1) rack (>= 1.1)
rubocop (>= 1.33.0, < 2.0) rubocop (>= 1.52.0, < 2.0)
rubocop-ast (>= 1.31.1, < 2.0) rubocop-ast (>= 1.31.1, < 2.0)
rubocop-rspec (3.0.3) rubocop-rspec (3.1.0)
rubocop (~> 1.61) rubocop (~> 1.61)
ruby-progressbar (1.13.0) ruby-progressbar (1.13.0)
ruby-vips (2.2.1) ruby-vips (2.2.2)
ffi (~> 1.12) ffi (~> 1.12)
logger
ruby2_keywords (0.0.5) ruby2_keywords (0.0.5)
rubyXL (3.4.27) rubyXL (3.4.27)
nokogiri (>= 1.10.8) nokogiri (>= 1.10.8)
rubyzip (>= 1.3.0) rubyzip (>= 1.3.0)
rubyzip (2.3.2) rubyzip (2.3.2)
securerandom (0.3.1)
semantic_range (3.0.0) semantic_range (3.0.0)
shakapacker (8.0.0) shakapacker (8.0.2)
activesupport (>= 5.2) activesupport (>= 5.2)
package_json package_json
rack-proxy (>= 0.6.1) rack-proxy (>= 0.6.1)
railties (>= 5.2) railties (>= 5.2)
semantic_range (>= 2.3.0) semantic_range (>= 2.3.0)
sidekiq (7.2.4) sidekiq (7.3.2)
concurrent-ruby (< 2) concurrent-ruby (< 2)
connection_pool (>= 2.3.0) connection_pool (>= 2.3.0)
logger
rack (>= 2.2.4) rack (>= 2.2.4)
redis-client (>= 0.19.0) redis-client (>= 0.22.2)
signet (0.19.0) signet (0.19.0)
addressable (~> 2.8) addressable (~> 2.8)
faraday (>= 0.17.5, < 3.a) faraday (>= 0.17.5, < 3.a)
@@ -511,33 +514,34 @@ GEM
docile (~> 1.1) docile (~> 1.1)
simplecov-html (~> 0.11) simplecov-html (~> 0.11)
simplecov_json_formatter (~> 0.1) simplecov_json_formatter (~> 0.1)
simplecov-html (0.12.3) simplecov-html (0.13.1)
simplecov_json_formatter (0.1.4) simplecov_json_formatter (0.1.4)
smart_properties (1.17.0) smart_properties (1.17.0)
sqlite3 (1.7.3) sqlite3 (2.1.0)
mini_portile2 (~> 2.8.0) mini_portile2 (~> 2.8.0)
sqlite3 (2.1.0-aarch64-linux-gnu)
sqlite3 (2.1.0-x86_64-linux-gnu)
stringio (3.1.1) stringio (3.1.1)
strip_attributes (1.13.0) strip_attributes (1.13.0)
activemodel (>= 3.0, < 8.0) activemodel (>= 3.0, < 8.0)
strscan (3.1.0)
thor (1.3.2) thor (1.3.2)
timeout (0.4.1) timeout (0.4.1)
trailblazer-option (0.1.2) trailblazer-option (0.1.2)
turbo-rails (2.0.5) turbo-rails (2.0.10)
actionpack (>= 6.0.0) actionpack (>= 6.0.0)
activejob (>= 6.0.0)
railties (>= 6.0.0) railties (>= 6.0.0)
twitter_cldr (6.12.0) twitter_cldr (6.12.1)
camertron-eprun camertron-eprun
cldr-plurals-runtime-rb (~> 1.1) cldr-plurals-runtime-rb (~> 1.1)
tzinfo tzinfo
tzinfo (2.0.6) tzinfo (2.0.6)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
tzinfo-data (1.2024.1) tzinfo-data (1.2024.2)
tzinfo (>= 1.0.0) tzinfo (>= 1.0.0)
uber (0.1.0) uber (0.1.0)
unicode-display_width (2.5.0) unicode-display_width (2.6.0)
uniform_notifier (1.16.0) uniform_notifier (1.16.0)
useragent (0.16.10)
warden (1.2.9) warden (1.2.9)
rack (>= 2.0.9) rack (>= 2.0.9)
web-console (4.2.1) web-console (4.2.1)
@@ -545,11 +549,11 @@ GEM
activemodel (>= 6.0.0) activemodel (>= 6.0.0)
bindex (>= 0.4.0) bindex (>= 0.4.0)
railties (>= 6.0.0) railties (>= 6.0.0)
webmock (3.23.0) webmock (3.24.0)
addressable (>= 2.8.0) addressable (>= 2.8.0)
crack (>= 0.3.2) crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0) hashdiff (>= 0.4.0, < 2.0.0)
webrick (1.8.1) webrick (1.8.2)
websocket-driver (0.7.6) websocket-driver (0.7.6)
websocket-extensions (>= 0.1.0) websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5) websocket-extensions (0.1.5)
@@ -592,7 +596,6 @@ DEPENDENCIES
letter_opener_web letter_opener_web
lograge lograge
mysql2 mysql2
net-smtp (= 0.4.0)
oj oj
pagy pagy
pg pg
+1 -1
View File
@@ -42,7 +42,7 @@ DocuSeal is an open source platform that provides secure and efficient digital d
- PDF signature verification - PDF signature verification
- Users management - Users management
- Mobile-optimized - Mobile-optimized
- Signing available in 13 languages - 6 UI languages with signing available in 13 languages
- API and Webhooks for integrations - API and Webhooks for integrations
- Easy to deploy in minutes - Easy to deploy in minutes
+20 -16
View File
@@ -4,11 +4,11 @@ class AccountsController < ApplicationController
LOCALE_OPTIONS = { LOCALE_OPTIONS = {
'en-US' => 'English (United States)', 'en-US' => 'English (United States)',
'en-GB' => 'English (United Kingdom)', 'en-GB' => 'English (United Kingdom)',
'fr-FR' => 'French (France)', 'fr-FR' => 'Français',
'es-ES' => 'Spanish (Spain)', 'es-ES' => 'Español',
'pt-PT' => 'Portuguese (Portugal)', 'pt-PT' => 'Português',
'de-DE' => 'German (Germany)', 'de-DE' => 'Deutsch',
'it-IT' => 'Italian (Italy)' 'it-IT' => 'Italiano'
}.freeze }.freeze
before_action :load_account before_action :load_account
@@ -19,21 +19,25 @@ class AccountsController < ApplicationController
def update def update
current_account.update!(account_params) current_account.update!(account_params)
@encrypted_config = EncryptedConfig.find_or_initialize_by(account: current_account, unless Docuseal.multitenant?
key: EncryptedConfig::APP_URL_KEY) @encrypted_config = EncryptedConfig.find_or_initialize_by(account: current_account,
@encrypted_config.assign_attributes(app_url_params) 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]) 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')) @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 end
@encrypted_config.save! with_locale do
redirect_to settings_account_path, notice: I18n.t('account_information_has_been_updated')
Docuseal.refresh_default_url_options! end
redirect_to settings_account_path, notice: I18n.t('account_information_has_been_updated')
rescue ActiveRecord::RecordInvalid rescue ActiveRecord::RecordInvalid
render :show, status: :unprocessable_entity render :show, status: :unprocessable_entity
end end
@@ -38,12 +38,12 @@ module Api
private private
def authorization_check!(blob) def authorization_check!(blob)
is_authorized = attachment = blob.attachments.take
blob.attachments.all? do |a|
a.name.in?(%w[logo preview_images]) || is_authorized = attachment.name.in?(%w[logo preview_images]) ||
(current_user && a.record.account.id == current_user.account_id) || (current_user && attachment.record.account.id == current_user.account_id) ||
!a.record.account.account_configs.find_or_initialize_by(key: AccountConfig::DOWNLOAD_LINKS_AUTH_KEY).value !attachment.record.account.account_configs
end .find_or_initialize_by(key: AccountConfig::DOWNLOAD_LINKS_AUTH_KEY).value
return if is_authorized return if is_authorized
+12
View File
@@ -8,6 +8,7 @@ class ApplicationController < ActionController::Base
check_authorization unless: :devise_controller? check_authorization unless: :devise_controller?
around_action :with_locale
before_action :sign_in_for_demo, if: -> { Docuseal.demo? } before_action :sign_in_for_demo, if: -> { Docuseal.demo? }
before_action :maybe_redirect_to_setup, unless: :signed_in? before_action :maybe_redirect_to_setup, unless: :signed_in?
before_action :authenticate_user!, unless: :devise_controller? before_action :authenticate_user!, unless: :devise_controller?
@@ -51,7 +52,18 @@ class ApplicationController < ActionController::Base
private 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(&) def with_browser_locale(&)
return yield if I18n.locale != :'en-US' && I18n.locale != :en
locale = params[:lang].presence locale = params[:lang].presence
locale ||= request.env['HTTP_ACCEPT_LANGUAGE'].to_s[BROWSER_LOCALE_REGEXP].to_s locale ||= request.env['HTTP_ACCEPT_LANGUAGE'].to_s[BROWSER_LOCALE_REGEXP].to_s
+14 -8
View File
@@ -21,9 +21,9 @@ class EsignSettingsController < ApplicationController
default_pkcs = GenerateCertificate.load_pkcs(cert_data) if cert_data['cert'].present? default_pkcs = GenerateCertificate.load_pkcs(cert_data) if cert_data['cert'].present?
custom_pkcs_list = (cert_data['custom'] || []).map do |e| custom_pkcs_list = (cert_data['custom'] || []).map do |e|
{ 'pkcs' => OpenSSL::PKCS12.new(Base64.urlsafe_decode64(e['data']), e['password'].to_s), pkcs = e['data'].present? ? OpenSSL::PKCS12.new(Base64.urlsafe_decode64(e['data']), e['password'].to_s) : nil
'name' => e['name'],
'status' => e['status'] } { 'pkcs' => pkcs, 'name' => e['name'], 'status' => e['status'] }
end end
@pkcs_list = [ @pkcs_list = [
@@ -56,7 +56,7 @@ class EsignSettingsController < ApplicationController
save_new_cert!(@encrypted_config, @cert_record) save_new_cert!(@encrypted_config, @cert_record)
redirect_to settings_esign_path, notice: I18n.t('certificate_has_been_successfully_added_') redirect_to settings_esign_path, notice: I18n.t('certificate_has_been_successfully_added')
rescue OpenSSL::PKCS12::PKCS12Error => e rescue OpenSSL::PKCS12::PKCS12Error => e
Rollbar.error(e) if defined?(Rollbar) Rollbar.error(e) if defined?(Rollbar)
@@ -66,10 +66,16 @@ class EsignSettingsController < ApplicationController
end end
def update 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 = @encrypted_config.value['custom'].to_a.find { |e| e['name'] == params[:name] }
custom_cert_data['status'] = 'default' if custom_cert_data
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! @encrypted_config.save!
@@ -81,7 +87,7 @@ class EsignSettingsController < ApplicationController
@encrypted_config.save! @encrypted_config.save!
redirect_to settings_esign_path, notice: I18m.t('certificate_has_been_removed') redirect_to settings_esign_path, notice: I18n.t('certificate_has_been_removed')
end end
private private
+1 -1
View File
@@ -56,7 +56,7 @@ class SetupController < ApplicationController
def account_params def account_params
return {} unless params[:account] return {} unless params[:account]
params.require(:account).permit(:name, :timezone) params.require(:account).permit(:name, :timezone, :locale)
end end
def encrypted_config_params def encrypted_config_params
+7 -3
View File
@@ -12,16 +12,20 @@ class SubmitFormController < ApplicationController
def show def show
@submitter = Submitter.find_by!(slug: params[:slug]) @submitter = Submitter.find_by!(slug: params[:slug])
submission = @submitter.submission
return redirect_to submit_form_completed_path(@submitter.slug) if @submitter.completed_at? 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 :archived if submission.template.archived_at? || submission.archived_at?
return render :expired if @submitter.submission.expired? return render :expired if submission.expired?
return render :declined if @submitter.declined_at? 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.preload_with_pages(@submitter)
Submitters::MaybeUpdateDefaultValues.call(@submitter, current_user) Submitters::MaybeUpdateDefaultValues.call(@submitter, current_user)
@attachments_index = ActiveStorage::Attachment.where(record: @submitter.submission.submitters, name: :attachments) @attachments_index = ActiveStorage::Attachment.where(record: submission.submitters, name: :attachments)
.preload(:blob).index_by(&:uuid) .preload(:blob).index_by(&:uuid)
@form_configs = Submitters::FormConfigs.call(@submitter, CONFIG_KEYS) @form_configs = Submitters::FormConfigs.call(@submitter, CONFIG_KEYS)
@@ -7,7 +7,7 @@ class TemplateFoldersController < ApplicationController
@templates = @template_folder.templates.active.preload(:author).order(id: :desc) @templates = @template_folder.templates.active.preload(:author).order(id: :desc)
@templates = Templates.search(@templates, params[:q]) @templates = Templates.search(@templates, params[:q])
@pagy, @templates = pagy(@templates, items: 12) @pagy, @templates = pagy(@templates, limit: 12)
end end
def edit; end def edit; end
@@ -7,6 +7,6 @@ class TemplatesArchivedController < ApplicationController
@templates = @templates.where.not(archived_at: nil).preload(:author, :folder).order(id: :desc) @templates = @templates.where.not(archived_at: nil).preload(:author, :folder).order(id: :desc)
@templates = Templates.search(@templates, params[:q]) @templates = Templates.search(@templates, params[:q])
@pagy, @templates = pagy(@templates, items: 12) @pagy, @templates = pagy(@templates, limit: 12)
end end
end end
+2 -1
View File
@@ -116,7 +116,8 @@ class TemplatesController < ApplicationController
end end
def authorized_clone_account_id?(account_id) 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 end
def maybe_redirect_to_template(template) def maybe_redirect_to_template(template)
@@ -23,14 +23,14 @@ class TemplatesDashboardController < ApplicationController
@template_folders = @template_folders.reject { |e| e.name == TemplateFolder::DEFAULT_NAME } @template_folders = @template_folders.reject { |e| e.name == TemplateFolder::DEFAULT_NAME }
@templates = filter_templates(@templates) @templates = filter_templates(@templates)
items = limit =
if @template_folders.size < 4 if @template_folders.size < 4
TEMPLATES_PER_PAGE TEMPLATES_PER_PAGE
else else
(@template_folders.size < 7 ? 9 : 6) (@template_folders.size < 7 ? 9 : 6)
end end
@pagy, @templates = pagy(@templates, items:) @pagy, @templates = pagy(@templates, limit:)
end end
end end
@@ -24,6 +24,7 @@ class TemplatesPreferencesController < ApplicationController
documents_copy_email_enabled documents_copy_email_attach_audit documents_copy_email_enabled documents_copy_email_attach_audit
completed_notification_email_attach_documents completed_notification_email_attach_documents
completed_redirect_url completed_redirect_url
submitters_order
completed_notification_email_subject completed_notification_email_body completed_notification_email_subject completed_notification_email_body
completed_notification_email_enabled completed_notification_email_attach_audit] + completed_notification_email_enabled completed_notification_email_attach_audit] +
[completed_message: %i[title body]] [completed_message: %i[title body]]
@@ -9,7 +9,7 @@ class UserSignaturesController < ApplicationController
def update def update
file = params[:file] file = params[:file]
return redirect_to settings_profile_index_path, notice: I18n.t('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, blob = ActiveStorage::Blob.create_and_upload!(io: file.open,
filename: file.original_filename, filename: file.original_filename,
@@ -24,7 +24,7 @@ class UserSignaturesController < ApplicationController
if @user_config.update(value: attachment.uuid) if @user_config.update(value: attachment.uuid)
redirect_to settings_profile_index_path, notice: I18n.t('signature_has_been_saved') redirect_to settings_profile_index_path, notice: I18n.t('signature_has_been_saved')
else else
redirect_to settings_profile_index_path, notice: I18n.t('Unable to save signature') redirect_to settings_profile_index_path, notice: I18n.t('unable_to_save_signature')
end end
end end
+2 -1
View File
@@ -150,7 +150,8 @@ safeRegisterElement('import-list', class extends HTMLElement {
this.app = createApp(ImportList, { this.app = createApp(ImportList, {
template: JSON.parse(this.dataset.template), template: JSON.parse(this.dataset.template),
multitenant: this.dataset.multitenant === 'true', 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) this.app.mount(this.appElem)
-4
View File
@@ -51,10 +51,6 @@ button[disabled] .enabled {
@apply input input-bordered bg-white; @apply input input-bordered bg-white;
} }
.base-input-slim {
@apply input input-bordered bg-white h-10;
}
.base-textarea { .base-textarea {
@apply textarea textarea-bordered bg-white rounded-3xl; @apply textarea textarea-bordered bg-white rounded-3xl;
} }
+1
View File
@@ -21,6 +21,7 @@ safeRegisterElement('submission-form', class extends HTMLElement {
isDemo: this.dataset.isDemo === 'true', isDemo: this.dataset.isDemo === 'true',
attribution: this.dataset.attribution !== 'false', attribution: this.dataset.attribution !== 'false',
scrollPadding: this.dataset.scrollPadding || '-80px', scrollPadding: this.dataset.scrollPadding || '-80px',
language: this.dataset.language,
dryRun: this.dataset.dryRun === 'true', dryRun: this.dataset.dryRun === 'true',
expand: ['true', 'false'].includes(this.dataset.expand) ? this.dataset.expand === 'true' : null, expand: ['true', 'false'].includes(this.dataset.expand) ? this.dataset.expand === 'true' : null,
withSignatureId: this.dataset.withSignatureId === 'true', withSignatureId: this.dataset.withSignatureId === 'true',
+2 -1
View File
@@ -1,5 +1,6 @@
<template> <template>
<div <div
id="form_completed"
class="mx-auto max-w-md flex flex-col" class="mx-auto max-w-md flex flex-col"
dir="auto" dir="auto"
> >
@@ -222,7 +223,7 @@ export default {
this.downloadUrls(urls) this.downloadUrls(urls)
} }
} else { } else {
alert('Failed to download files') alert(this.t('failed_to_download_files'))
} }
}) })
}, },
+2 -6
View File
@@ -897,7 +897,7 @@ export default {
mounted () { mounted () {
this.submittedValues = JSON.parse(JSON.stringify(this.values)) this.submittedValues = JSON.parse(JSON.stringify(this.values))
screen?.orientation.addEventListener('change', this.onOrientationChange) screen?.orientation?.addEventListener('change', this.onOrientationChange)
this.fields.forEach((field) => { this.fields.forEach((field) => {
if (field.default_value && !field.readonly) { if (field.default_value && !field.readonly) {
@@ -1183,10 +1183,6 @@ export default {
return Promise.reject(new Error(data.error)) 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) || this.stepFields[this.currentStep + 1]
if (nextStep) { if (nextStep) {
@@ -1211,7 +1207,7 @@ export default {
}) })
}).catch(error => { }).catch(error => {
if (error?.message === 'Image too small') { if (error?.message === 'Image too small') {
alert('Signature is too small - please redraw.') alert(this.t('signature_is_too_small_please_redraw'))
} else { } else {
console.log(error) console.log(error)
} }
+1 -1
View File
@@ -216,7 +216,7 @@ const it = {
pay: 'Pagamento', pay: 'Pagamento',
file: 'File', file: 'File',
select: 'Seleziona', select: 'Seleziona',
checkbox: 'Casella di controllo', checkbox: 'Checkbox',
multiple: 'Multiplo', multiple: 'Multiplo',
radio: 'Radio', radio: 'Radio',
cells: 'Celle', cells: 'Celle',
+1
View File
@@ -360,6 +360,7 @@ export default {
}, },
computed: { computed: {
fieldNames: FieldType.computed.fieldNames, fieldNames: FieldType.computed.fieldNames,
fieldLabels: FieldType.computed.fieldLabels,
fieldIcons: FieldType.computed.fieldIcons, fieldIcons: FieldType.computed.fieldIcons,
isDefaultValuePresent () { isDefaultValuePresent () {
if (this.field?.type === 'radio' && this.field?.areas?.length > 1) { if (this.field?.type === 'radio' && this.field?.areas?.length > 1) {
+11 -8
View File
@@ -332,7 +332,7 @@
> >
<div class="bg-base-200 rounded-lg p-5 text-center space-y-4"> <div class="bg-base-200 rounded-lg p-5 text-center space-y-4">
<p> <p>
{{ t('draw_field_on_the_document').replace('{field}', drawField?.name || '') }} {{ t('draw_field_on_the_document') }}
</p> </p>
<div> <div>
<button <button
@@ -418,7 +418,7 @@ import MobileFields from './mobile_fields'
import { IconPlus, IconUsersPlus, IconDeviceFloppy, IconChevronDown, IconEye, IconWritingSign, IconInnerShadowTop, IconInfoCircle, IconAdjustments } from '@tabler/icons-vue' import { IconPlus, IconUsersPlus, IconDeviceFloppy, IconChevronDown, IconEye, IconWritingSign, IconInnerShadowTop, IconInfoCircle, IconAdjustments } from '@tabler/icons-vue'
import { v4 } from 'uuid' import { v4 } from 'uuid'
import { ref, computed } from 'vue' import { ref, computed } from 'vue'
import { en as i18nEn } from './i18n' import * as i18n from './i18n'
export default { export default {
name: 'TemplateBuilder', name: 'TemplateBuilder',
@@ -679,6 +679,9 @@ export default {
computed: { computed: {
selectedAreaRef: () => ref(), selectedAreaRef: () => ref(),
fieldsDragFieldRef: () => ref(), fieldsDragFieldRef: () => ref(),
language () {
return this.locale.split('-')[0].toLowerCase()
},
defaultDateFormat () { defaultDateFormat () {
const isUsBrowser = Intl.DateTimeFormat().resolvedOptions().locale.endsWith('-US') 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)$/) const isUsTimezone = new Intl.DateTimeFormat('en-US', { timeZoneName: 'short' }).format(new Date()).match(/\s(?:CST|CDT|PST|PDT|EST|EDT)$/)
@@ -786,7 +789,7 @@ export default {
document.activeElement.blur() document.activeElement.blur()
}, },
t (key) { t (key) {
return this.i18n[key] || i18nEn[key] || key return this.i18n[key] || i18n[this.language]?.[key] || i18n.en[key] || key
}, },
removePendingFields () { removePendingFields () {
this.template.fields = this.template.fields.filter((f) => { this.template.fields = this.template.fields.filter((f) => {
@@ -1352,7 +1355,7 @@ export default {
this.save() this.save()
}, },
onDocumentRemove (item) { 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) this.template.schema.splice(this.template.schema.indexOf(item), 1)
} }
@@ -1451,7 +1454,7 @@ export default {
if (!this.template.fields.length) { if (!this.template.fields.length) {
e.preventDefault() e.preventDefault()
alert('Please draw fields to prepare the document.') alert(this.t('please_draw_fields_to_prepare_the_document'))
} else { } else {
const submitterWithoutFields = const submitterWithoutFields =
this.template.submitters.find((submitter) => !this.template.fields.some((f) => f.submitter_uuid === submitter.uuid)) this.template.submitters.find((submitter) => !this.template.fields.some((f) => f.submitter_uuid === submitter.uuid))
@@ -1459,7 +1462,7 @@ export default {
if (submitterWithoutFields) { if (submitterWithoutFields) {
e.preventDefault() 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 +1478,13 @@ export default {
} }
if (!this.template.fields.length) { if (!this.template.fields.length) {
alert('Please draw fields to prepare the document.') alert(this.t('please_draw_fields_to_prepare_the_document'))
} else { } else {
const submitterWithoutFields = const submitterWithoutFields =
this.template.submitters.find((submitter) => !this.template.fields.some((f) => f.submitter_uuid === submitter.uuid)) this.template.submitters.find((submitter) => !this.template.fields.some((f) => f.submitter_uuid === submitter.uuid))
if (submitterWithoutFields) { 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 { } else {
this.isSaving = true this.isSaving = true
@@ -200,7 +200,7 @@ export default {
}, },
validateSaveAndClose () { validateSaveAndClose () {
if (!this.withConditions) { 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)) { if (this.conditions.find((f) => f.field_uuid)) {
+1 -1
View File
@@ -105,7 +105,7 @@ export default {
this.upload() this.upload()
} else { } else {
alert('Only PDF and images are supported.') alert(this.t('only_pdf_and_images_are_supported'))
} }
} }
} }
+2 -2
View File
@@ -312,6 +312,7 @@ export default {
}, },
computed: { computed: {
fieldNames: FieldType.computed.fieldNames, fieldNames: FieldType.computed.fieldNames,
fieldLabels: FieldType.computed.fieldLabels,
dropdownBgColor () { dropdownBgColor () {
return ['', null, 'transparent'].includes(this.backgroundColor) ? 'white' : this.backgroundColor return ['', null, 'transparent'].includes(this.backgroundColor) ? 'white' : this.backgroundColor
}, },
@@ -367,8 +368,7 @@ export default {
if (this.field.type === 'heading') { if (this.field.type === 'heading') {
return `${this.fieldNames[field.type]} ${typeIndex + 1}` return `${this.fieldNames[field.type]} ${typeIndex + 1}`
} else { } else {
const suffix = { multiple: this.t('select'), radio: this.t('group') }[field.type] || this.t('field') return `${this.fieldLabels[field.type]} ${typeIndex + 1}`
return `${this.fieldNames[field.type]} ${suffix} ${typeIndex + 1}`
} }
} }
}, },
@@ -330,7 +330,7 @@ export default {
this.$emit('update:model-value', submitter.uuid) this.$emit('update:model-value', submitter.uuid)
}, },
remove (submitter) { remove (submitter) {
if (window.confirm(this.t('are_you_sure'))) { if (window.confirm(this.t('are_you_sure_'))) {
this.$emit('remove', submitter) this.$emit('remove', submitter)
} }
}, },
@@ -114,6 +114,25 @@ export default {
phone: this.t('phone') phone: this.t('phone')
} }
}, },
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')
}
},
fieldIcons () { fieldIcons () {
return { return {
heading: IconHeading, heading: IconHeading,
+4 -4
View File
@@ -137,7 +137,7 @@
v-else-if="type == 'phone' && (fieldTypes.length === 0 || fieldTypes.includes(type))" v-else-if="type == 'phone' && (fieldTypes.length === 0 || fieldTypes.includes(type))"
class="tooltip tooltip-bottom flex" class="tooltip tooltip-bottom flex"
:class="{'tooltip-bottom-end': withPayment, 'tooltip-bottom': !withPayment }" :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 <a
href="https://www.docuseal.co/pricing" href="https://www.docuseal.co/pricing"
@@ -168,13 +168,13 @@
> >
<ul class="list-disc list-outside ml-3"> <ul class="list-disc list-outside ml-3">
<li> <li>
Draw a text field on the page with a mouse {{ t('draw_a_text_field_on_the_page_with_a_mouse') }}
</li> </li>
<li> <li>
Drag &amp; drop any other field type on the page {{ t('drag_and_drop_any_other_field_type_on_the_page') }}
</li> </li>
<li> <li>
Click on the field type above to start drawing it {{ t('click_on_the_field_type_above_to_start_drawing_it') }}
</li> </li>
</ul> </ul>
</div> </div>
@@ -195,7 +195,7 @@ export default {
}, },
validateSaveAndClose () { validateSaveAndClose () {
if (!this.withFormula) { if (!this.withFormula) {
return alert('Available only in Pro') return alert(this.t('available_only_in_pro'))
} }
const normalizedFormula = this.normalizeFormula(this.formula) const normalizedFormula = this.normalizeFormula(this.formula)
+787 -5
View File
@@ -29,7 +29,7 @@ const en = {
select_value_: 'Select value...', select_value_: 'Select value...',
remove_condition: 'Remove condition', remove_condition: 'Remove condition',
add_condition: 'Add condition', add_condition: 'Add condition',
are_you_sure: 'Are you sure?', are_you_sure_: 'Are you sure?',
sign_yourself: 'Sign Yourself', sign_yourself: 'Sign Yourself',
set_signing_date: 'Set signing date', set_signing_date: 'Set signing date',
signing_date: 'Signing Date', signing_date: 'Signing Date',
@@ -46,7 +46,7 @@ const en = {
drawn: 'Drawn', drawn: 'Drawn',
formula: 'Formula', formula: 'Formula',
typed: 'Typed', 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', click_to_upload: 'Click to upload',
or_drag_and_drop_files: 'or drag and drop files', or_drag_and_drop_files: 'or drag and drop files',
uploading: 'Uploading', uploading: 'Uploading',
@@ -101,9 +101,31 @@ const en = {
stamp: 'Stamp', stamp: 'Stamp',
payment: 'Payment', payment: 'Payment',
phone: 'Phone', phone: 'Phone',
field: 'Field', text_field: 'Text Field',
group: 'Group', 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', 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',
signature_is_too_small_please_redraw: 'Signature is too small - please redraw.',
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', draw_field: 'Draw {field} Field',
replace: 'Replace', replace: 'Replace',
uploading_: 'Uploading...', uploading_: 'Uploading...',
@@ -128,4 +150,764 @@ const en = {
learn_more: 'Learn more' learn_more: 'Learn more'
} }
export { en } const es = {
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',
signature_is_too_small_please_redraw: 'La firma es demasiado pequeña. Por favor, dibújala de nuevo.',
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'
}
const it = {
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',
signature_is_too_small_please_redraw: 'La firma è troppo piccola. Ridisegnala per favore.',
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ù'
}
const pt = {
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',
signature_is_too_small_please_redraw: 'A assinatura é muito pequena. Por favor, redesenhe.',
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'
}
const fr = {
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',
signature_is_too_small_please_redraw: 'La signature est trop petite. Veuillez la redessiner.',
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'
}
const de = {
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',
signature_is_too_small_please_redraw: 'Die Unterschrift ist zu klein. Bitte erneut zeichnen.',
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'
}
export { en, es, it, pt, fr, de }
+22 -14
View File
@@ -3,7 +3,7 @@
<div v-if="selectedSheetIndex === null && spreadsheet"> <div v-if="selectedSheetIndex === null && spreadsheet">
<form @submit.prevent="[selectedSheetIndex = $refs.selectWorksheet.value, buildDefaultMappings()]"> <form @submit.prevent="[selectedSheetIndex = $refs.selectWorksheet.value, buildDefaultMappings()]">
<label class="label"> <label class="label">
Select Worksheet {{ t('select_worksheet') }}
</label> </label>
<select <select
ref="selectWorksheet" ref="selectWorksheet"
@@ -18,7 +18,7 @@
</option> </option>
</select> </select>
<button class="base-button mt-4 w-full"> <button class="base-button mt-4 w-full">
Open {{ t('open') }}
</button> </button>
</form> </form>
</div> </div>
@@ -36,10 +36,10 @@
</div> </div>
<div class="flex"> <div class="flex">
<div class="relative w-full py-2 px-2 text-sm"> <div class="relative w-full py-2 px-2 text-sm">
Recipient field {{ t('recipient_field') }}
</div> </div>
<div class="relative w-full py-2 pl-4 text-sm"> <div class="relative w-full py-2 pl-4 text-sm">
Spreadsheet column {{ t('spreadsheet_column') }}
</div> </div>
</div> </div>
<div <div
@@ -58,7 +58,7 @@
value="" value=""
:selected="!mapping.field_name" :selected="!mapping.field_name"
> >
Select Field {{ t('select_field') }}
</option> </option>
<option <option
v-for="(field, index) in selectFieldsForSubmitter(submitter)" v-for="(field, index) in selectFieldsForSubmitter(submitter)"
@@ -83,7 +83,7 @@
value="" value=""
:selected="mapping.column_index == null" :selected="mapping.column_index == null"
> >
Select Column {{ t('select_column') }}
</option> </option>
<template <template
v-for="(column, index) in columns" v-for="(column, index) in columns"
@@ -118,8 +118,8 @@
</div> </div>
<div class="flex items-center pl-1"> <div class="flex items-center pl-1">
<span <span
class="tooltip tooltip-top" class="tooltip tooltip-left"
data-tip="<%= t('remove') %>" :data-tip="t('remove')"
> >
<button <button
:disabled="mappings.filter((m) => m.submitter_uuid === submitter.uuid).length < 2" :disabled="mappings.filter((m) => m.submitter_uuid === submitter.uuid).length < 2"
@@ -138,7 +138,7 @@
@click.prevent="addMapping(submitter)" @click.prevent="addMapping(submitter)"
> >
<IconPlus class="w-4 h-4" /> <IconPlus class="w-4 h-4" />
New Field Mapping {{ t('new_field_mapping') }}
</button> </button>
</div> </div>
</div> </div>
@@ -152,7 +152,7 @@
<div <div
class="px-3 border-y py-2 border-base-300 text-center w-full text-sm font-semibold" 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"> <template v-if="multitenant && submissionsData.length >= 1000">
/ 1000 / 1000
</template> </template>
@@ -187,10 +187,10 @@
<div <div
class="font-medium text-lg mb-1" class="font-medium text-lg mb-1"
> >
Upload CSV or XLSX Spreadsheet {{ t('upload_csv_or_xlsx_spreadsheet') }}
</div> </div>
<div class="text-sm"> <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> </div>
</div> </div>
@@ -210,11 +210,11 @@
</label> </label>
</div> </div>
<div class="text-center mt-2"> <div class="text-center mt-2">
Or <a {{ t('or') }} <a
:download="`${template.name}.csv`" :download="`${template.name}.csv`"
:href="`data:text/csv;base64,${csvBase64}`" :href="`data:text/csv;base64,${csvBase64}`"
class="link font-medium" 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> </div>
</div> </div>
@@ -248,6 +248,11 @@ export default {
type: String, type: String,
required: false, required: false,
default: '' default: ''
},
i18n: {
type: Object,
required: false,
default: () => ({})
} }
}, },
data () { data () {
@@ -354,6 +359,9 @@ export default {
} }
}, },
methods: { methods: {
t (key) {
return this.i18n[key] || key
},
onDropFiles (e) { onDropFiles (e) {
this.uploadFile(e.dataTransfer.files[0]) this.uploadFile(e.dataTransfer.files[0])
}, },
@@ -72,7 +72,7 @@
class="absolute -top-1 left-2.5 px-1 h-4" class="absolute -top-1 left-2.5 px-1 h-4"
style="font-size: 8px" style="font-size: 8px"
> >
Price {{ t('price') }}
</label> </label>
</div> </div>
<div <div
+41 -31
View File
@@ -23,21 +23,23 @@ class SubmitterMailer < ApplicationMailer
reply_to = build_submitter_reply_to(@submitter) reply_to = build_submitter_reply_to(@submitter)
subject = I18n.with_locale(@current_account.locale) do
if @email_config || @subject subject =
ReplaceEmailVariables.call(@subject || @email_config.value['subject'], submitter:) if @email_config || @subject
elsif @submitter.with_signature_fields? ReplaceEmailVariables.call(@subject || @email_config.value['subject'], submitter:)
I18n.t(:you_are_invited_to_sign_a_document) elsif @submitter.with_signature_fields?
else I18n.t(:you_are_invited_to_sign_a_document)
I18n.t(:you_are_invited_to_submit_a_form) else
end I18n.t(:you_are_invited_to_submit_a_form)
end
mail( mail(
to: @submitter.friendly_name, to: @submitter.friendly_name,
from: from_address_for_submitter(submitter), from: from_address_for_submitter(submitter),
subject:, subject:,
reply_to: reply_to:
) )
end
end end
def completed_email(submitter, user, to: nil) def completed_email(submitter, user, to: nil)
@@ -66,13 +68,15 @@ class SubmitterMailer < ApplicationMailer
assign_message_metadata('submitter_completed', @submitter) assign_message_metadata('submitter_completed', @submitter)
subject = I18n.with_locale(@current_account.locale) do
ReplaceEmailVariables.call(@subject.presence || I18n.t(:template_name_has_been_completed_by_submitters), subject =
submitter:) ReplaceEmailVariables.call(@subject.presence || I18n.t(:template_name_has_been_completed_by_submitters),
submitter:)
mail(from: from_address_for_submitter(submitter), mail(from: from_address_for_submitter(submitter),
to: to || (user.role == 'integration' ? user.friendly_name.sub(/\+\w+@/, '@') : user.friendly_name), to: to || normalize_user_email(user),
subject:) subject:)
end
end end
def declined_email(submitter, user) def declined_email(submitter, user)
@@ -83,7 +87,7 @@ class SubmitterMailer < ApplicationMailer
assign_message_metadata('submitter_declined', @submitter) assign_message_metadata('submitter_declined', @submitter)
I18n.with_locale(submitter.account.locale) do I18n.with_locale(@current_account.locale) do
mail(from: from_address_for_submitter(submitter), mail(from: from_address_for_submitter(submitter),
to: user.role == 'integration' ? user.friendly_name.sub(/\+\w+@/, '@') : user.friendly_name, to: user.role == 'integration' ? user.friendly_name.sub(/\+\w+@/, '@') : user.friendly_name,
reply_to: @submitter.friendly_name, reply_to: @submitter.friendly_name,
@@ -116,17 +120,19 @@ class SubmitterMailer < ApplicationMailer
assign_message_metadata('submitter_documents_copy', @submitter) assign_message_metadata('submitter_documents_copy', @submitter)
reply_to = build_submitter_reply_to(submitter) reply_to = build_submitter_reply_to(submitter)
subject = I18n.with_locale(@current_account.locale) do
if @subject.present? subject =
ReplaceEmailVariables.call(@subject, submitter:) if @subject.present?
else ReplaceEmailVariables.call(@subject, submitter:)
I18n.t(:your_document_copy) else
end I18n.t(:your_document_copy)
end
mail(from: from_address_for_submitter(submitter), mail(from: from_address_for_submitter(submitter),
to: to || @submitter.friendly_name, to: to || @submitter.friendly_name,
reply_to:, reply_to:,
subject:) subject:)
end
end end
private private
@@ -171,6 +177,10 @@ class SubmitterMailer < ApplicationMailer
documents documents
end end
def normalize_user_email(user)
user.role == 'integration' ? user.friendly_name.sub(/\+\w+@/, '@') : user.friendly_name
end
def add_attachments_with_size_limit(storage_attachments, current_size) def add_attachments_with_size_limit(storage_attachments, current_size)
total_size = current_size total_size = current_size
+4 -2
View File
@@ -8,7 +8,9 @@ class UserMailer < ApplicationMailer
assign_message_metadata('user_invitation', @user) assign_message_metadata('user_invitation', @user)
mail(to: @user.friendly_name, I18n.with_locale(@current_account.locale) do
subject: "You are invited to #{Docuseal.product_name}") mail(to: @user.friendly_name,
subject: I18n.t('you_are_invited_to_product_name', product_name: Docuseal.product_name))
end
end end
end end
+1
View File
@@ -39,6 +39,7 @@ class AccountConfig < ApplicationRecord
FORCE_SSO_AUTH_KEY = 'force_sso_auth' FORCE_SSO_AUTH_KEY = 'force_sso_auth'
FLATTEN_RESULT_PDF_KEY = 'flatten_result_pdf' FLATTEN_RESULT_PDF_KEY = 'flatten_result_pdf'
WITH_SIGNATURE_ID = 'with_signature_id' WITH_SIGNATURE_ID = 'with_signature_id'
WITH_AUDIT_VALUES_KEY = 'with_audit_values'
REQUIRE_SIGNING_REASON_KEY = 'require_signing_reason' REQUIRE_SIGNING_REASON_KEY = 'require_signing_reason'
COMBINE_PDF_RESULT_KEY = 'combine_pdf_result_key' COMBINE_PDF_RESULT_KEY = 'combine_pdf_result_key'
+2 -2
View File
@@ -86,11 +86,11 @@ class Submitter < ApplicationRecord
end end
def first_name def first_name
name.split(/\s+/, 2).first name&.split(/\s+/, 2)&.first
end end
def last_name def last_name
name.split(/\s+/, 2).last name&.split(/\s+/, 2)&.last
end end
def status_event_at def status_event_at
+1 -1
View File
@@ -46,7 +46,7 @@ class Template < ApplicationRecord
attribute :preferences, :string, default: -> { {} } attribute :preferences, :string, default: -> { {} }
attribute :fields, :string, default: -> { [] } attribute :fields, :string, default: -> { [] }
attribute :schema, :string, default: -> { [] } attribute :schema, :string, default: -> { [] }
attribute :submitters, :string, default: -> { [{ name: DEFAULT_SUBMITTER_NAME, uuid: SecureRandom.uuid }] } attribute :submitters, :string, default: -> { [{ name: I18n.t(:first_party), uuid: SecureRandom.uuid }] }
attribute :slug, :string, default: -> { SecureRandom.base58(14) } attribute :slug, :string, default: -> { SecureRandom.base58(14) }
attribute :source, :string, default: 'native' attribute :source, :string, default: 'native'
+2 -2
View File
@@ -18,8 +18,8 @@
<% end %> <% end %>
</div> </div>
<div class="form-control"> <div class="form-control">
<%= ff.label :locale, t('time_format'), class: 'label' %> <%= ff.label :locale, t('language'), class: 'label' %>
<%= ff.select :locale, options_for_select(controller.class::LOCALE_OPTIONS.invert, current_account.locale), {}, class: 'base-select' %> <%= ff.select :locale, options_for_select(AccountsController::LOCALE_OPTIONS.invert, current_account.locale), {}, class: 'base-select' %>
</div> </div>
</div> </div>
<% end %> <% end %>
+2 -2
View File
@@ -1,8 +1,8 @@
<div class="flex flex-wrap space-y-4 md:flex-nowrap md:space-y-0 md:space-x-10"> <div class="flex flex-wrap space-y-4 md:flex-nowrap md:space-y-0 md:space-x-10">
<%= render 'shared/settings_nav' %> <%= render 'shared/settings_nav' %>
<div class="flex-grow"> <div class="flex-grow">
<div class="flex justify-between"> <div class="flex flex-col gap-2 md:flex-row md:justify-between md:items-end mb-4">
<h1 class="text-4xl font-bold mb-4">API</h1> <h1 class="text-4xl font-bold">API</h1>
<%= render 'shared/test_mode_toggle' %> <%= render 'shared/test_mode_toggle' %>
</div> </div>
<div class="card bg-base-200"> <div class="card bg-base-200">
+1 -1
View File
@@ -38,7 +38,7 @@
<div class="form-control"> <div class="form-control">
<%= ff.label :security_label, 'SMTP Security', class: 'label' %> <%= ff.label :security_label, 'SMTP Security', class: 'label' %>
<div class="flex items-center space-x-6"> <div class="flex items-center space-x-6">
<% [%w[Auto none], %w[SSL ssl], %w[TLS tls]].each do |(label, val)| %> <% [%w[Auto none], %w[SSL ssl], %w[TLS tls], %w[Noverify noverify]].each do |(label, val)| %>
<%= ff.label :security, value: val, for: "#{val}_radio", class: 'label' do %> <%= ff.label :security, value: val, for: "#{val}_radio", class: 'label' do %>
<%= ff.radio_button :security, val, checked: (value['security'].blank? && val == 'none') || value['security'] == val, id: "#{val}_radio", class: 'base-radio mr-2' %> <%= ff.radio_button :security, val, checked: (value['security'].blank? && val == 'none') || value['security'] == val, id: "#{val}_radio", class: 'base-radio mr-2' %>
<%= label %> <%= label %>
@@ -0,0 +1,25 @@
<tr scope="row" class="group">
<td class="flex items-center space-x-1">
<%= svg_icon('discount_check_filled', class: 'w-6 h-6 text-green-500') %>
<span class="flex items-center">
<%= t('docuseal_trusted_signature') %>
<div class="tooltip ml-1" data-tip="<%= t('sign_documents_with_trusted_certificate_provided_by_docu_seal_your_documents_and_data_are_never_shared_with_docu_seal_p_d_f_checksum_is_provided_to_generate_a_trusted_signature') %>">
<%= svg_icon('circle_question', class: 'w-4 h-4 stroke-1') %>
</div>
</span>
</td>
<td>
<a href="<%= "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premise" }.to_query}" %>" class="btn btn-neutral btn-sm text-white">
<%= t('unlock_with_docuseal_pro') %>
</a>
</td>
<td>
<div class="tooltip" data-tip="<%= t('unlock_with_docuseal_pro') %>">
<%= button_to settings_esign_path, method: :put, params: { name: Docuseal::AATL_CERT_NAME }, class: 'btn btn-outline btn-neutral btn-xs whitespace-nowrap', title: t('make_default'), disabled: true do %>
<%= t('make_default') %>
<% end %>
</div>
</td>
<td>
</td>
</tr>
+1 -1
View File
@@ -7,7 +7,7 @@
</div> </div>
<div class="form-control"> <div class="form-control">
<%= f.label :file, t('file'), class: 'label' %> <%= f.label :file, t('file'), class: 'label' %>
<%= f.file_field :file, required: true %> <%= f.file_field :file, class: 'file-input file-input-bordered w-full', required: true %>
<label class="label"> <label class="label">
<span class="label-text-alt"> <span class="label-text-alt">
<%= t('use_a_valid_der_p12_or_pfx_file') %> <%= t('use_a_valid_der_p12_or_pfx_file') %>
+7 -3
View File
@@ -19,8 +19,8 @@
<% end %> <% end %>
<file-dropzone data-name="verify_attachments" data-submit-on-upload="true" class="w-full"> <file-dropzone data-name="verify_attachments" data-submit-on-upload="true" class="w-full">
<label for="file" class="w-full block h-32 relative bg-base-200 hover:bg-base-200/70 rounded-md border border-base-content border-dashed"> <label for="file" class="w-full block h-32 relative bg-base-200 hover:bg-base-200/70 rounded-md border border-base-content border-dashed">
<div class="absolute top-0 right-0 left-0 bottom-0 flex items-center justify-center"> <div class="absolute top-0 right-0 left-0 bottom-0 flex items-center justify-center p-2">
<div class="flex flex-col items-center"> <div class="flex flex-col items-center text-center">
<span data-target="file-dropzone.icon"> <span data-target="file-dropzone.icon">
<%= svg_icon('cloud_upload', class: 'w-10 h-10') %> <%= svg_icon('cloud_upload', class: 'w-10 h-10') %>
</span> </span>
@@ -40,7 +40,7 @@
</file-dropzone> </file-dropzone>
<% end %> <% end %>
</div> </div>
<div class="flex justify-between items-end mb-4 mt-8"> <div class="flex flex-col md:flex-row gap-2 md:justify-between md:items-end mb-4 mt-8">
<h2 class="text-3xl font-bold"> <h2 class="text-3xl font-bold">
<%= t('signing_certificates') %> <%= t('signing_certificates') %>
</h2> </h2>
@@ -71,6 +71,7 @@
</thead> </thead>
<tbody> <tbody>
<% @pkcs_list.each do |item| %> <% @pkcs_list.each do |item| %>
<% next if item['pkcs'].blank? %>
<tr scope="row" class="group"> <tr scope="row" class="group">
<td> <td>
<%= item['name'] %> <%= item['name'] %>
@@ -98,6 +99,9 @@
</td> </td>
</tr> </tr>
<% end %> <% end %>
<% unless Docuseal.multitenant? %>
<%= render 'default_signature_row' %>
<% end %>
</tbody> </tbody>
</table> </table>
</div> </div>
@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" class="<%= local_assigns[:class] %>" width="44" height="44" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M12.01 2.011a3.2 3.2 0 0 1 2.113 .797l.154 .145l.698 .698a1.2 1.2 0 0 0 .71 .341l.135 .008h1a3.2 3.2 0 0 1 3.195 3.018l.005 .182v1c0 .27 .092 .533 .258 .743l.09 .1l.697 .698a3.2 3.2 0 0 1 .147 4.382l-.145 .154l-.698 .698a1.2 1.2 0 0 0 -.341 .71l-.008 .135v1a3.2 3.2 0 0 1 -3.018 3.195l-.182 .005h-1a1.2 1.2 0 0 0 -.743 .258l-.1 .09l-.698 .697a3.2 3.2 0 0 1 -4.382 .147l-.154 -.145l-.698 -.698a1.2 1.2 0 0 0 -.71 -.341l-.135 -.008h-1a3.2 3.2 0 0 1 -3.195 -3.018l-.005 -.182v-1a1.2 1.2 0 0 0 -.258 -.743l-.09 -.1l-.697 -.698a3.2 3.2 0 0 1 -.147 -4.382l.145 -.154l.698 -.698a1.2 1.2 0 0 0 .341 -.71l.008 -.135v-1l.005 -.182a3.2 3.2 0 0 1 3.013 -3.013l.182 -.005h1a1.2 1.2 0 0 0 .743 -.258l.1 -.09l.698 -.697a3.2 3.2 0 0 1 2.269 -.944zm3.697 7.282a1 1 0 0 0 -1.414 0l-3.293 3.292l-1.293 -1.292l-.094 -.083a1 1 0 0 0 -1.32 1.497l2 2l.094 .083a1 1 0 0 0 1.32 -.083l4 -4l.083 -.094a1 1 0 0 0 -.083 -1.32z" stroke-width="0" fill="currentColor" />
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -6,7 +6,7 @@
<span> <span>
<%= t('completed_documents_notification_bcc_address') %> <%= t('completed_documents_notification_bcc_address') %>
</span> </span>
<span class="tooltip" data-tip="Send email copy with completed documents to a specified BCC address."> <span class="tooltip" data-tip="<%= t('send_email_copy_with_completed_documents_to_a_specified_bcc_address') %>">
<%= svg_icon('info_circle', class: 'w-4 h-4') %> <%= svg_icon('info_circle', class: 'w-4 h-4') %>
</span> </span>
</span> </span>
@@ -3,22 +3,22 @@
<div class="form-control"> <div class="form-control">
<% record = Struct.new(:first_duration, :second_duration, :third_duration).new(*(f.object.value || {}).values_at('first_duration', 'second_duration', 'third_duration')) %> <% record = Struct.new(:first_duration, :second_duration, :third_duration).new(*(f.object.value || {}).values_at('first_duration', 'second_duration', 'third_duration')) %>
<% durations = (Docuseal.multitenant? ? AccountConfigs::REMINDER_DURATIONS.except('one_hour', 'two_hours') : AccountConfigs::REMINDER_DURATIONS).keys.map { |v| [t(v.underscore), v] } %> <% durations = (Docuseal.multitenant? ? AccountConfigs::REMINDER_DURATIONS.except('one_hour', 'two_hours') : AccountConfigs::REMINDER_DURATIONS).keys.map { |v| [t(v.underscore), v] } %>
<div class="flex flex-col md:flex-row gap-4"> <div class="flex flex-col md:flex-row gap-2">
<div class="w-full"> <div class="w-full">
<%= f.fields_for :value, record do |ff| %> <%= f.fields_for :value, record do |ff| %>
<%= ff.label :first_duration, t('first_reminder_in'), class: 'label' %> <%= ff.label :first_duration, t('first_reminder_in'), class: 'label truncate' %>
<%= ff.select :first_duration, durations, { include_blank: 'None' }, class: 'base-select' %> <%= ff.select :first_duration, durations, { include_blank: 'None' }, class: 'base-select' %>
<% end %> <% end %>
</div> </div>
<div class="w-full"> <div class="w-full">
<%= f.fields_for :value, record do |ff| %> <%= f.fields_for :value, record do |ff| %>
<%= ff.label :second_duration, t('second_reminder_in'), class: 'label' %> <%= ff.label :second_duration, t('second_reminder_in'), class: 'label truncate' %>
<%= ff.select :second_duration, durations, { include_blank: 'None' }, class: 'base-select' %> <%= ff.select :second_duration, durations, { include_blank: 'None' }, class: 'base-select' %>
<% end %> <% end %>
</div> </div>
<div class="w-full"> <div class="w-full">
<%= f.fields_for :value, record do |ff| %> <%= f.fields_for :value, record do |ff| %>
<%= ff.label :third_duration, t('third_reminder_in'), class: 'label' %> <%= ff.label :third_duration, t('third_reminder_in'), class: 'label truncate' %>
<%= ff.select :third_duration, durations, { include_blank: 'None' }, class: 'base-select' %> <%= ff.select :third_duration, durations, { include_blank: 'None' }, class: 'base-select' %>
<% end %> <% end %>
</div> </div>
@@ -2,7 +2,7 @@
<input type="checkbox"> <input type="checkbox">
<div class="collapse-title text-xl font-medium capitalize"> <div class="collapse-title text-xl font-medium capitalize">
<div> <div>
<%= t('copleted_notification_email') %> <%= t('completed_notification_email') %>
</div> </div>
</div> </div>
<div class="collapse-content"> <div class="collapse-content">
+6
View File
@@ -56,6 +56,12 @@
<%= ff.text_field :value, required: true, class: 'base-input' %> <%= ff.text_field :value, required: true, class: 'base-input' %>
</div> </div>
<% end %> <% end %>
<%= fields_for @account do |ff| %>
<div class="form-control">
<%= ff.label :locale, t('language'), class: 'label' %>
<%= ff.select :locale, options_for_select(AccountsController::LOCALE_OPTIONS.invert, @account.locale), {}, class: 'base-select' %>
</div>
<% end %>
</div> </div>
<div class="form-control"> <div class="form-control">
<%= f.button button_title, class: 'base-button' %> <%= f.button button_title, class: 'base-button' %>
+2 -2
View File
@@ -4,7 +4,7 @@
<span class="peer-checked:hidden flex items-center space-x-2"> <span class="peer-checked:hidden flex items-center space-x-2">
<%= svg_icon(local_assigns[:icon] || 'link', class: local_assigns[:icon_class] || 'w-6 h-6 text-white') %> <%= svg_icon(local_assigns[:icon] || 'link', class: local_assigns[:icon_class] || 'w-6 h-6 text-white') %>
<span class="hidden md:inline"> <span class="hidden md:inline">
<%= local_assigns[:copy_title] || 'Copy' %> <%= local_assigns[:copy_title] || t('copy') %>
</span> </span>
<% if local_assigns[:copy_title_md] %> <% if local_assigns[:copy_title_md] %>
<span class="inline md:hidden"> <span class="inline md:hidden">
@@ -15,7 +15,7 @@
<span class="hidden peer-checked:flex items-center space-x-2"> <span class="hidden peer-checked:flex items-center space-x-2">
<%= svg_icon(local_assigns[:copied_icon] || 'clipboard_copy', class: local_assigns[:icon_class] || 'w-6 h-6 text-white') %> <%= svg_icon(local_assigns[:copied_icon] || 'clipboard_copy', class: local_assigns[:icon_class] || 'w-6 h-6 text-white') %>
<span class="hidden md:inline"> <span class="hidden md:inline">
<%= local_assigns[:copied_title] || 'Copied' %> <%= local_assigns[:copied_title] || t('copied') %>
</span> </span>
<% if local_assigns[:copied_title_md] %> <% if local_assigns[:copied_title_md] %>
<span class="inline md:hidden"> <span class="inline md:hidden">
+1 -1
View File
@@ -2,5 +2,5 @@
--- ---
</p> </p>
<p> <p>
Sent using <a href="<%= Docuseal::PRODUCT_URL %>/start"><%= Docuseal.product_name %></a> free document signing. <%= t('sent_using_product_name_free_document_signing_html', product_url: "#{Docuseal::PRODUCT_URL}/start", product_name: Docuseal.product_name) %>
</p> </p>
+1 -1
View File
@@ -15,5 +15,5 @@
<%= yield %> <%= yield %>
</div> </div>
</div> </div>
<label class="modal-backdrop" for="<%= uuid %>"><%= t('close') %></label> <label class="modal-backdrop" for="<%= uuid %>"></label>
</div> </div>
+6 -6
View File
@@ -26,7 +26,7 @@
<label tabindex="0" class="cursor-pointer bg-base-content text-purple-300 rounded-full p-2 w-9 justify-center flex"> <label tabindex="0" class="cursor-pointer bg-base-content text-purple-300 rounded-full p-2 w-9 justify-center flex">
<span class="text-sm align-text-top"><%= current_user.initials %></span> <span class="text-sm align-text-top"><%= current_user.initials %></span>
</label> </label>
<ul tabindex="0" class="z-10 dropdown-content p-2 mt-2 shadow menu text-base bg-base-100 rounded-box w-40 text-right"> <ul tabindex="0" class="z-10 dropdown-content p-2 mt-2 shadow menu text-base bg-base-100 rounded-box min-w-[160px] text-right">
<li> <li>
<%= link_to settings_profile_index_path, class: 'flex items-center' do %> <%= link_to settings_profile_index_path, class: 'flex items-center' do %>
<%= svg_icon('adjustments', class: 'w-5 h-5 flex-shrink-0 stroke-2') %> <%= svg_icon('adjustments', class: 'w-5 h-5 flex-shrink-0 stroke-2') %>
@@ -36,7 +36,7 @@
<% if !Docuseal.demo? && can?(:manage, EncryptedConfig) %> <% if !Docuseal.demo? && can?(:manage, EncryptedConfig) %>
<li> <li>
<%= link_to Docuseal.multitenant? ? console_redirect_index_path : Docuseal::CONSOLE_URL, data: { prefetch: false }, class: 'flex items-center' do %> <%= link_to Docuseal.multitenant? ? console_redirect_index_path : Docuseal::CONSOLE_URL, data: { prefetch: false }, class: 'flex items-center' do %>
<%= svg_icon('terminal', class: 'w-5 h-5 stroke-2') %> <%= svg_icon('terminal', class: 'w-5 h-5 flex-shrink-0 stroke-2') %>
<%= t('console') %> <%= t('console') %>
<% end %> <% end %>
</li> </li>
@@ -44,15 +44,15 @@
<% if can?(:read, EncryptedConfig.new(key: EncryptedConfig::ESIGN_CERTS_KEY, account: current_account)) %> <% if can?(:read, EncryptedConfig.new(key: EncryptedConfig::ESIGN_CERTS_KEY, account: current_account)) %>
<li> <li>
<%= link_to settings_esign_path, class: 'flex items-center' do %> <%= link_to settings_esign_path, class: 'flex items-center' do %>
<%= svg_icon('zoom_check', class: 'w-5 h-5 stroke-2') %> <%= svg_icon('zoom_check', class: 'w-5 h-5 flex-shrink-0 stroke-2') %>
<span class="mr-1"><%= t('verify_pdf') %></span> <span class="mr-1 whitespace-nowrap"><%= t('verify_pdf') %></span>
<% end %> <% end %>
</li> </li>
<% end %> <% end %>
<li> <li>
<%= button_to destroy_user_session_path, method: :delete, data: { turbo: false }, class: 'flex items-center' do %> <%= button_to destroy_user_session_path, method: :delete, data: { turbo: false }, class: 'flex items-center' do %>
<%= svg_icon('logout', class: 'w-5 h-5 stroke-2 mr-2 inline') %> <%= svg_icon('logout', class: 'w-5 h-5 flex-shrink-0 stroke-2 mr-2 inline') %>
<span class="mr-1"><%= t('sign_out') %></span> <span class="mr-1 whitespace-nowrap"><%= t('sign_out') %></span>
<% end %> <% end %>
</li> </li>
</ul> </ul>
+1 -1
View File
@@ -9,5 +9,5 @@
<% else %> <% else %>
<%= t('powered_by') %> <%= t('powered_by') %>
<% end %> <% end %>
<a href="<%= Docuseal::PRODUCT_URL %><%= local_assigns[:link_path] %>" class="underline"><%= Docuseal.product_name %></a> - open source documents software <a href="<%= Docuseal::PRODUCT_URL %><%= local_assigns[:link_path] %>" class="underline"><%= Docuseal.product_name %></a> - <%= t('open_source_documents_software') %>
</div> </div>
+3 -3
View File
@@ -4,15 +4,15 @@
<% end %> <% end %>
<% if params[:q].present? %> <% if params[:q].present? %>
<div class="relative"> <div class="relative">
<a href="<%= url_for(params.to_unsafe_h.except(:q)) %>" title="Clear" class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-auto text-neutral text-2xl font-extralight"> <a href="<%= url_for(params.to_unsafe_h.except(:q)) %>" title="<%= t('clear') %>" class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-auto text-neutral text-2xl font-extralight">
&times; &times;
</a> </a>
</div> </div>
<% end %> <% end %>
<search-input data-title="<%= local_assigns[:title_selector] || 'h1' %>"> <search-input data-title="<%= local_assigns[:title_selector] || 'h1' %>">
<input id="search" name="q" value="<%= params[:q] %>" class="input input-ghost text-lg pr-10 -mr-12 w-0 md:w-64 <%= 'pl-8 input-outlined w-64' if params[:q].present? %>" placeholder="<%= local_assigns[:placeholder] %>"> <input id="search" name="q" value="<%= params[:q] %>" class="input text-lg pr-10 -mr-12 w-0 md:w-60 <%= 'pl-8 input-outlined w-60' if params[:q].present? %>" placeholder="<%= local_assigns[:placeholder] %>">
</search-input> </search-input>
<button type="submit" title="Search" class="btn btn-ghost btn-circle" onclick="window.search.value || document.activeElement === window.search ? null : [event.preventDefault(), window.search.focus()]"> <button type="submit" title="<%= t('search') %>" class="btn btn-ghost btn-circle" onclick="window.search.value || document.activeElement === window.search ? null : [event.preventDefault(), window.search.focus()]">
<span class="enabled"> <span class="enabled">
<%= svg_icon('search', class: 'w-6 h-6 stroke-2') %> <%= svg_icon('search', class: 'w-6 h-6 stroke-2') %>
</span> </span>
+1 -1
View File
@@ -1,7 +1,7 @@
<div class="block w-full md:w-52 flex-none"> <div class="block w-full md:w-52 flex-none">
<menu-active> <menu-active>
<ul class="menu px-0"> <ul class="menu px-0">
<li class="menu-title py-0 !bg-transparent mb-3 -mt-5"><a href="<%= '/' %>" class="!bg-transparent !text-neutral font-medium">&larr; Back</a></li> <li class="menu-title py-0 !bg-transparent mb-3 -mt-5"><a href="<%= '/' %>" class="!bg-transparent !text-neutral font-medium">&larr; <%= t('back') %></a></li>
<li class="menu-title py-0 !bg-transparent"> <li class="menu-title py-0 !bg-transparent">
<span class="!bg-transparent"><%= t('settings') %></span> <span class="!bg-transparent"><%= t('settings') %></span>
</li> </li>
+1 -1
View File
@@ -1,5 +1,5 @@
<% if signed_in? && current_user != true_user && current_account.testing? %> <% if signed_in? && current_user != true_user && current_account.testing? %>
<div class="alert py-1 text-sm font-medium gap-x-2 flex whitespace-nowrap"> <div class="hidden md:flex alert py-1 text-sm font-medium gap-x-2 whitespace-nowrap">
<a href="<%= testing_api_settings_path %>" data-turbo-frame="modal" class="link font-semibold flex"> <a href="<%= testing_api_settings_path %>" data-turbo-frame="modal" class="link font-semibold flex">
<%= svg_icon('code_circle', class: 'w-5 h-5 mr-1') %> <%= svg_icon('code_circle', class: 'w-5 h-5 mr-1') %>
<span><%= t('testing_environment') %></span> <span><%= t('testing_environment') %></span>
+1 -1
View File
@@ -1,6 +1,6 @@
<% if can?(:manage, EncryptedConfig) || (current_user != true_user && current_account.testing?) %> <% if can?(:manage, EncryptedConfig) || (current_user != true_user && current_account.testing?) %>
<%= form_for '', url: testing_account_path, method: current_account.testing? ? :delete : :get, html: { class: 'flex' } do |f| %> <%= form_for '', url: testing_account_path, method: current_account.testing? ? :delete : :get, html: { class: 'flex' } do |f| %>
<label class="flex items-center justify-between py-2.5" for="testing_toggle"> <label class="flex items-center justify-between" for="testing_toggle">
<span class="mr-2 text-lg"> <span class="mr-2 text-lg">
<%= t('test_environment') %> <%= t('test_environment') %>
</span> </span>
@@ -20,18 +20,18 @@
<input type="hidden" name="submission[1][submitters][][uuid]" value="<%= item['uuid'] %>"> <input type="hidden" name="submission[1][submitters][][uuid]" value="<%= item['uuid'] %>">
<submitters-autocomplete data-field="name"> <submitters-autocomplete data-field="name">
<linked-input data-target-id="<%= "detailed_name_#{item['linked_to_uuid']}" if item['linked_to_uuid'].present? %>"> <linked-input data-target-id="<%= "detailed_name_#{item['linked_to_uuid']}" if item['linked_to_uuid'].present? %>">
<%= tag.input type: 'text', name: 'submission[1][submitters][][name]', autocomplete: 'off', class: 'base-input-slim w-full', placeholder: 'Name', required: index.zero?, value: (params[:selfsign] && index.zero?) || item['is_requester'] ? current_user.full_name : '', dir: 'auto', id: "detailed_name_#{item['uuid']}" %> <%= tag.input type: 'text', name: 'submission[1][submitters][][name]', autocomplete: 'off', class: 'base-input !h-10 w-full', placeholder: t('name'), required: index.zero?, value: (params[:selfsign] && index.zero?) || item['is_requester'] ? current_user.full_name : '', dir: 'auto', id: "detailed_name_#{item['uuid']}" %>
</linked-input> </linked-input>
</submitters-autocomplete> </submitters-autocomplete>
<div class="grid <%= 'md:grid-cols-2 gap-1' if submitters.size == 1 %>"> <div class="grid <%= 'md:grid-cols-2 gap-1' if submitters.size == 1 %>">
<submitters-autocomplete data-field="email"> <submitters-autocomplete data-field="email">
<linked-input data-target-id="<%= "detailed_email_#{item['linked_to_uuid']}" if item['linked_to_uuid'].present? %>"> <linked-input data-target-id="<%= "detailed_email_#{item['linked_to_uuid']}" if item['linked_to_uuid'].present? %>">
<input type="email" multiple name="submission[1][submitters][][email]" autocomplete="off" class="base-input-slim mt-1.5 w-full" placeholder="Email (optional)" value="<%= item['email'].presence || ((params[:selfsign] && index.zero?) || item['is_requester'] ? current_user.email : '') %>" id="detailed_email_<%= item['uuid'] %>"> <input type="email" multiple name="submission[1][submitters][][email]" autocomplete="off" class="base-input !h-10 mt-1.5 w-full" placeholder="<%= "#{t('email')} (#{t('optional')})" %>" value="<%= item['email'].presence || ((params[:selfsign] && index.zero?) || item['is_requester'] ? current_user.email : '') %>" id="detailed_email_<%= item['uuid'] %>">
</linked-input> </linked-input>
</submitters-autocomplete> </submitters-autocomplete>
<submitters-autocomplete data-field="phone"> <submitters-autocomplete data-field="phone">
<linked-input data-target-id="<%= "detailed_phone_#{item['linked_to_uuid']}" if item['linked_to_uuid'].present? %>"> <linked-input data-target-id="<%= "detailed_phone_#{item['linked_to_uuid']}" if item['linked_to_uuid'].present? %>">
<input type="tel" pattern="^\+[0-9\s\-]+$" oninvalid="this.value ? this.setCustomValidity('Use internatioanl format: +1xxx...') : ''" oninput="this.setCustomValidity('')" name="submission[1][submitters][][phone]" autocomplete="off" class="base-input-slim mt-1.5 w-full" placeholder="Phone (optional)" id="detailed_phone_<%= item['uuid'] %>"> <input type="tel" pattern="^\+[0-9\s\-]+$" oninvalid="this.value ? this.setCustomValidity('Use internatioanl format: +1xxx...') : ''" oninput="this.setCustomValidity('')" name="submission[1][submitters][][phone]" autocomplete="off" class="base-input !h-10 mt-1.5 w-full" placeholder="<%= "#{t('phone')} (#{t('optional')})" %>" id="detailed_phone_<%= item['uuid'] %>">
</linked-input> </linked-input>
</submitters-autocomplete> </submitters-autocomplete>
</div> </div>
+1 -1
View File
@@ -29,7 +29,7 @@
<input type="hidden" name="submission[1][submitters][][uuid]" value="<%= item['uuid'] %>"> <input type="hidden" name="submission[1][submitters][][uuid]" value="<%= item['uuid'] %>">
<submitters-autocomplete data-field="email"> <submitters-autocomplete data-field="email">
<linked-input data-target-id="<%= "email_#{item['linked_to_uuid']}" if item['linked_to_uuid'].present? %>"> <linked-input data-target-id="<%= "email_#{item['linked_to_uuid']}" if item['linked_to_uuid'].present? %>">
<%= tag.input type: 'email', multiple: true, name: 'submission[1][submitters][][email]', autocomplete: 'off', class: 'base-input-slim w-full', placeholder: t('email'), required: index.zero?, value: item['email'].presence || ((params[:selfsign] && index.zero?) || item['is_requester'] ? current_user.email : ''), id: "email_#{item['uuid']}" %> <%= tag.input type: 'email', multiple: true, name: 'submission[1][submitters][][email]', autocomplete: 'off', class: 'base-input !h-10 w-full', placeholder: t('email'), required: index.zero?, value: item['email'].presence || ((params[:selfsign] && index.zero?) || item['is_requester'] ? current_user.email : ''), id: "email_#{item['uuid']}" %>
</linked-input> </linked-input>
</submitters-autocomplete> </submitters-autocomplete>
</submitter-item> </submitter-item>
+3 -3
View File
@@ -21,13 +21,13 @@
<input type="hidden" name="submission[1][submitters][][uuid]" value="<%= item['uuid'] %>"> <input type="hidden" name="submission[1][submitters][][uuid]" value="<%= item['uuid'] %>">
<submitters-autocomplete data-field="phone"> <submitters-autocomplete data-field="phone">
<linked-input data-target-id="<%= "phone_phone_#{item['linked_to_uuid']}" if item['linked_to_uuid'].present? %>"> <linked-input data-target-id="<%= "phone_phone_#{item['linked_to_uuid']}" if item['linked_to_uuid'].present? %>">
<%= tag.input type: 'tel', pattern: '^\+[0-9\s\-]+$', oninvalid: "this.value ? this.setCustomValidity('Use internatioanl format: +1xxx...') : ''", oninput: "this.setCustomValidity('')", name: 'submission[1][submitters][][phone]', autocomplete: 'off', class: 'base-input-slim w-full', placeholder: t('phone'), required: index.zero?, id: "phone_phone_#{item['uuid']}" %> <%= tag.input type: 'tel', pattern: '^\+[0-9\s\-]+$', oninvalid: "this.value ? this.setCustomValidity('Use internatioanl format: +1xxx...') : ''", oninput: "this.setCustomValidity('')", name: 'submission[1][submitters][][phone]', autocomplete: 'off', class: 'base-input !h-10 w-full', placeholder: t('phone'), required: index.zero?, id: "phone_phone_#{item['uuid']}" %>
</linked-input> </linked-input>
</submitters-autocomplete> </submitters-autocomplete>
<% if submitters.size > 1 %> <% if submitters.size > 1 %>
<submitters-autocomplete data-field="name"> <submitters-autocomplete data-field="name">
<linked-input data-target-id="<%= "phone_name_#{item['linked_to_uuid']}" if item['linked_to_uuid'].present? %>"> <linked-input data-target-id="<%= "phone_name_#{item['linked_to_uuid']}" if item['linked_to_uuid'].present? %>">
<input type="text" name="submission[1][submitters][][name]" autocomplete="off" class="base-input-slim mt-1.5 w-full" placeholder="<%= "#{t('name')} (#{t('optional')})" %>" value="<%= (params[:selfsign] && index.zero?) || item['is_requester'] ? current_user.full_name : '' %>" dir="auto" id="phone_name_<%= item['uuid'] %>"> <input type="text" name="submission[1][submitters][][name]" autocomplete="off" class="base-input !h-10 mt-1.5 w-full" placeholder="<%= "#{t('name')} (#{t('optional')})" %>" value="<%= (params[:selfsign] && index.zero?) || item['is_requester'] ? current_user.full_name : '' %>" dir="auto" id="phone_name_<%= item['uuid'] %>">
</linked-input> </linked-input>
</submitters-autocomplete> </submitters-autocomplete>
<% end %> <% end %>
@@ -36,7 +36,7 @@
<div class="form-control flex"> <div class="form-control flex">
<submitters-autocomplete data-field="name"> <submitters-autocomplete data-field="name">
<linked-input data-target-id="<%= "phone_name_#{item['linked_to_uuid']}" if item['linked_to_uuid'].present? %>"> <linked-input data-target-id="<%= "phone_name_#{item['linked_to_uuid']}" if item['linked_to_uuid'].present? %>">
<input type="text" name="submission[1][submitters][][name]" autocomplete="off" class="base-input-slim w-full" placeholder="<%= "#{t('name')} (#{t('optional')})" %>" value="<%= (params[:selfsign] && index.zero?) || item['is_requester'] ? current_user.full_name : '' %>" dir="auto" id="phone_name_<%= item['uuid'] %>"> <input type="text" name="submission[1][submitters][][name]" autocomplete="off" class="base-input !h-10 w-full" placeholder="<%= "#{t('name')} (#{t('optional')})" %>" value="<%= (params[:selfsign] && index.zero?) || item['is_requester'] ? current_user.full_name : '' %>" dir="auto" id="phone_name_<%= item['uuid'] %>">
</linked-input> </linked-input>
</submitters-autocomplete> </submitters-autocomplete>
</div> </div>
@@ -1,4 +1,6 @@
<% if template.submitters.size > 1 %> <% if template.preferences['submitters_order'] == 'preserved' %>
<%= f.hidden_field :preserve_order, value: '1' %>
<% elsif template.submitters.size > 1 %>
<div class="form-control"> <div class="form-control">
<%= f.label :preserve_order, for: uuid = SecureRandom.uuid, class: 'flex items-center cursor-pointer' do %> <%= f.label :preserve_order, for: uuid = SecureRandom.uuid, class: 'flex items-center cursor-pointer' do %>
<%= f.check_box :preserve_order, id: uuid, class: 'base-checkbox', checked: template.submissions.last&.submitters_order.in?(['preserved', nil]) %> <%= f.check_box :preserve_order, id: uuid, class: 'base-checkbox', checked: template.submissions.last&.submitters_order.in?(['preserved', nil]) %>
+5 -5
View File
@@ -1,14 +1,14 @@
<%= render 'shared/turbo_modal_large', title: params[:selfsign] ? t('add_recipients') : t('add_new_recipients') do %> <%= render 'shared/turbo_modal_large', title: params[:selfsign] ? t('add_recipients') : t('add_new_recipients') do %>
<% options = [['via Email', 'email'], ['via Phone', 'phone'], %w[Detailed detailed], ['Upload List', 'list']].compact %> <% options = [[t('via_email'), 'email'], [t('via_phone'), 'phone'], [t('detailed'), 'detailed'], [t('upload_list'), 'list']].compact %>
<toggle-visible data-element-ids="<%= options.map(&:last).to_json %>" class="relative text-center px-2 mt-4 block"> <toggle-visible data-element-ids="<%= options.map(&:last).to_json %>" class="relative text-center px-2 mt-4 block">
<div class="join"> <div class="flex justify-center">
<% options.each_with_index do |(label, value), index| %> <% options.each_with_index do |(label, value), index| %>
<span> <div>
<%= radio_button_tag 'option', value, value == 'email', class: 'peer hidden', data: { action: 'change:toggle-visible#trigger' } %> <%= radio_button_tag 'option', value, value == 'email', class: 'peer hidden', data: { action: 'change:toggle-visible#trigger' } %>
<label for="option_<%= value %>" class="<%= '!rounded-s-full' if index.zero? %> btn btn-focus btn-sm join-item md:w-28 peer-checked:btn-active normal-case"> <label for="option_<%= value %>" class="block bg-base-200 md:min-w-[112px] text-sm font-semibold whitespace-nowrap py-1.5 px-4 peer-checked:bg-base-300 <%= 'hidden sm:inline-block' if value == 'list' %> <%= 'rounded-l-3xl' if index.zero? %> <%= 'rounded-r-3xl sm:rounded-r-none' if value == 'detailed' %> <%= 'rounded-r-3xl' if index == options.size - 1 %>">
<%= label %> <%= label %>
</label> </label>
</span> </div>
<% end %> <% end %>
</div> </div>
</toggle-visible> </toggle-visible>
+5 -5
View File
@@ -53,7 +53,7 @@
<% end %> <% end %>
</div> </div>
<% elsif @submission.submitters.to_a.size == 1 && !@submission.expired? && !@submission.submitters.to_a.first.declined_at? %> <% elsif @submission.submitters.to_a.size == 1 && !@submission.expired? && !@submission.submitters.to_a.first.declined_at? %>
<%= render 'shared/clipboard_copy', text: submit_form_url(slug: @submission.submitters.to_a.first.slug), class: 'base-button', icon_class: 'w-6 h-6 text-white', copy_title: 'Copy Share Link', copied_title: 'Copied to Clipboard' %> <%= render 'shared/clipboard_copy', text: submit_form_url(slug: @submission.submitters.to_a.first.slug), class: 'base-button', icon_class: 'w-6 h-6 text-white', copy_title: t('copy_share_link'), copied_title: t('copied_to_clipboard') %>
<% end %> <% end %>
</div> </div>
</div> </div>
@@ -144,7 +144,7 @@
<% if submitter&.declined_at? %> <% if submitter&.declined_at? %>
<%= t('declined_on_time', time: l(submitter.declined_at.in_time_zone(@submission.account.timezone), format: :short, locale: @submission.account.locale)) %> <%= t('declined_on_time', time: l(submitter.declined_at.in_time_zone(@submission.account.timezone), format: :short, locale: @submission.account.locale)) %>
<% elsif submitter %> <% elsif submitter %>
<%= submitter.completed_at? ? l(submitter.completed_at.in_time_zone(@submission.account.timezone), format: :long, locale: @submission.account.locale) : t('not_copleted_yet') %> <%= submitter.completed_at? ? l(submitter.completed_at.in_time_zone(@submission.account.timezone), format: :long, locale: @submission.account.locale) : t('not_completed_yet') %>
<% else %> <% else %>
<%= t('not_invited_yet') %> <%= t('not_invited_yet') %>
<% end %> <% end %>
@@ -153,14 +153,14 @@
<% if submitter&.declined_at? %> <% if submitter&.declined_at? %>
<div class="flex items-center space-x-1 mt-1"> <div class="flex items-center space-x-1 mt-1">
<span> <span>
<%= t('Reason') %>: <%= t('reason') %>:
<%= simple_format(h(submitter.submission_events.find_by(event_type: :decline_form).data['reason'])) %> <%= simple_format(h(submitter.submission_events.find_by(event_type: :decline_form).data['reason'])) %>
</span> </span>
</div> </div>
<% end %> <% end %>
<% if signed_in? && submitter && submitter.email && !submitter.completed_at && !@submission.archived_at? && can?(:update, submitter) && Accounts.can_send_emails?(current_account) && !@submission.expired? && !submitter.declined_at? %> <% if signed_in? && submitter && submitter.email && !submitter.completed_at && !@submission.archived_at? && can?(:update, submitter) && Accounts.can_send_emails?(current_account) && !@submission.expired? && !submitter.declined_at? %>
<div class="mt-2 mb-1"> <div class="mt-2 mb-1">
<%= button_to button_title(title: submitter.sent_at? ? 'Re-send Email' : 'Send Email', disabled_with: t('sending')), submitter_send_email_index_path(submitter_slug: submitter.slug), class: 'btn btn-sm btn-primary w-full' %> <%= button_to button_title(title: submitter.sent_at? ? t('re_send_email') : t('send_email'), disabled_with: t('sending')), submitter_send_email_index_path(submitter_slug: submitter.slug), class: 'btn btn-sm btn-primary w-full' %>
</div> </div>
<% end %> <% end %>
<% if signed_in? && submitter && submitter.phone && !submitter.completed_at && !@submission.archived_at? && can?(:update, submitter) && !@submission.expired? && !submitter.declined_at? %> <% if signed_in? && submitter && submitter.phone && !submitter.completed_at && !@submission.archived_at? && can?(:update, submitter) && !@submission.expired? && !submitter.declined_at? %>
@@ -182,7 +182,7 @@
<% next if value.blank? %> <% next if value.blank? %>
<div class="pt-2.5 border-b border-base-300"> <div class="pt-2.5 border-b border-base-300">
<div class="text-xs font-medium uppercase mb-0.5" dir="auto"> <div class="text-xs font-medium uppercase mb-0.5" dir="auto">
<%= field['name'].presence || "#{field['type'].titleize} Field #{submitter_field_counters[field['type']]}" %> <%= field['name'].presence || "#{t("#{field['type']}_field")} #{submitter_field_counters[field['type']]}" %>
</div> </div>
<div dir="auto"> <div dir="auto">
<% if field['type'].in?(%w[signature initials]) %> <% if field['type'].in?(%w[signature initials]) %>
@@ -1,11 +1,11 @@
<% is_show_tabs = @pagy.count >= 5 || params[:status].present? %> <% is_show_tabs = @pagy.count >= 5 || params[:status].present? %>
<% if Docuseal.demo? %><%= render 'shared/demo_alert' %><% end %> <% if Docuseal.demo? %><%= render 'shared/demo_alert' %><% end %>
<div class="flex justify-between mb-4 items-center"> <div class="flex justify-between items-center w-full mb-4">
<div class="flex items-center"> <div class="flex items-center flex-grow min-w-0">
<div class="mr-2"> <div class="mr-2">
<%= render 'dashboard/toggle_view', selected: 'submissions' %> <%= render 'dashboard/toggle_view', selected: 'submissions' %>
</div> </div>
<h1 class="text-2xl md:text-3xl sm:text-4xl font-bold md:block <%= 'hidden' if params[:q].present? %>"> <h1 class="text-2xl truncate md:text-3xl sm:text-4xl font-bold md:block <%= 'hidden' if params[:q].present? %>">
<%= t('submissions') %> <%= t('submissions') %>
</h1> </h1>
</div> </div>
+1 -1
View File
@@ -1,4 +1,4 @@
<%= render 'shared/turbo_modal', title: 'Export', close_after_submit: false do %> <%= render 'shared/turbo_modal', title: t('export'), close_after_submit: false do %>
<div class="space-y-2"> <div class="space-y-2">
<%= button_to template_submissions_export_index_path(@template), params: { format: :xlsx }, method: :get, data: { turbo_frame: :_top } do %> <%= button_to template_submissions_export_index_path(@template), params: { format: :xlsx }, method: :get, data: { turbo_frame: :_top } do %>
<div class="flex items-center p-4 text-left rounded-2xl border border-neutral-300 hover:cursor-pointer hover:bg-neutral hover:text-gray-300"> <div class="flex items-center p-4 text-left rounded-2xl border border-neutral-300 hover:cursor-pointer hover:bg-neutral hover:text-gray-300">
@@ -1,4 +1,4 @@
<% data_attachments = attachments_index.values.select { |e| e.record_id == submitter.id }.to_json(only: %i[uuid created_at], methods: %i[url filename content_type]) %> <% data_attachments = attachments_index.values.select { |e| e.record_id == submitter.id }.to_json(only: %i[uuid created_at], methods: %i[url filename content_type]) %>
<% data_fields = (submitter.submission.template_fields || submitter.submission.template.fields).select { |f| f['submitter_uuid'] == submitter.uuid }.to_json %> <% data_fields = (submitter.submission.template_fields || submitter.submission.template.fields).select { |f| f['submitter_uuid'] == submitter.uuid }.to_json %>
<% invite_submitters = (submitter.submission.template_submitters || submitter.submission.template.submitters).select { |s| s['invite_by_uuid'] == submitter.uuid && submitter.submission.submitters.none? { |e| e.uuid == s['uuid'] } }.to_json %> <% invite_submitters = (submitter.submission.template_submitters || submitter.submission.template.submitters).select { |s| s['invite_by_uuid'] == submitter.uuid && submitter.submission.submitters.none? { |e| e.uuid == s['uuid'] } }.to_json %>
<submission-form data-is-demo="<%= Docuseal.demo? %>" data-require-signing-reason="<%= configs[:require_signing_reason] %>" data-with-signature-id="<%= configs[:with_signature_id] %>" data-with-confetti="<%= configs[:with_confetti] %>" data-completed-redirect-url="<%= submitter.preferences['completed_redirect_url'].presence || submitter.submission.template.preferences['completed_redirect_url'] %>" data-completed-message="<%= (configs[:completed_message]&.compact_blank.presence || submitter.submission.template.preferences['completed_message'] || {}).to_json %>" data-completed-button="<%= configs[:completed_button].to_json %>" data-go-to-last="<%= submitter.preferences.key?('go_to_last') ? submitter.preferences['go_to_last'] : submitter.opened_at? %>" data-submitter="<%= submitter.to_json(only: %i[uuid slug name phone email]) %>" data-can-send-email="<%= Accounts.can_send_emails?(submitter.submission.account) %>" data-invite-submitters="<%= invite_submitters %>" data-attachments="<%= data_attachments %>" data-fields="<%= data_fields %>" data-values="<%= submitter.values.to_json %>" data-with-typed-signature="<%= configs[:with_typed_signature] %>" data-previous-signature-value="<%= local_assigns[:signature_attachment]&.uuid %>" data-remember-signature="<%= configs[:prefill_signature] %>" data-dry-run="<%= local_assigns[:dry_run] %>" data-expand="<%= local_assigns[:expand] %>" data-scroll-padding="<%= local_assigns[:scroll_padding] %>"></submission-form> <submission-form data-is-demo="<%= Docuseal.demo? %>" data-require-signing-reason="<%= configs[:require_signing_reason] %>" data-with-signature-id="<%= configs[:with_signature_id] %>" data-with-confetti="<%= configs[:with_confetti] %>" data-completed-redirect-url="<%= submitter.preferences['completed_redirect_url'].presence || submitter.submission.template.preferences['completed_redirect_url'] %>" data-completed-message="<%= (configs[:completed_message]&.compact_blank.presence || submitter.submission.template.preferences['completed_message'] || {}).to_json %>" data-completed-button="<%= configs[:completed_button].to_json %>" data-go-to-last="<%= submitter.preferences.key?('go_to_last') ? submitter.preferences['go_to_last'] : submitter.opened_at? %>" data-submitter="<%= submitter.to_json(only: %i[uuid slug name phone email]) %>" data-can-send-email="<%= Accounts.can_send_emails?(submitter.submission.account) %>" data-invite-submitters="<%= invite_submitters %>" data-attachments="<%= data_attachments %>" data-fields="<%= data_fields %>" data-values="<%= submitter.values.to_json %>" data-with-typed-signature="<%= configs[:with_typed_signature] %>" data-previous-signature-value="<%= local_assigns[:signature_attachment]&.uuid %>" data-remember-signature="<%= configs[:prefill_signature] %>" data-dry-run="<%= local_assigns[:dry_run] %>" data-expand="<%= local_assigns[:expand] %>" data-scroll-padding="<%= local_assigns[:scroll_padding] %>" data-language="<%= I18n.locale.to_s.split('-').first %>"></submission-form>
+21
View File
@@ -0,0 +1,21 @@
<div class="max-w-md mx-auto px-2 mt-12 mb-4">
<div class="space-y-6 mx-auto">
<div class="space-y-6">
<div class="flex items-center justify-center">
<%= render 'start_form/banner' %>
</div>
<div class="flex items-center bg-base-200 rounded-xl p-4 mb-4">
<div class="flex items-center">
<div class="mr-3">
<%= svg_icon('writing_sign', class: 'w-10 h-10') %>
</div>
<div dir="auto">
<p class="text-lg font-bold mb-1"><%= @submitter.submission.template.name %></p>
<p class="text-sm"><%= t('awaiting_completion_by_the_other_party') %></p>
</div>
</div>
</div>
</div>
</div>
</div>
<%= render 'shared/attribution', link_path: '/start', account: @submitter.account %>
+3 -3
View File
@@ -4,10 +4,10 @@
<span><%= t('home') %></span> <span><%= t('home') %></span>
<% end %> <% end %>
</div> </div>
<div class="flex justify-between mb-4 items-center"> <div class="flex justify-between items-center w-full mb-4">
<h1 class="text-4xl font-bold flex items-center space-x-2 md:flex <%= 'hidden' if params[:q].present? %>"> <h1 class="text-4xl font-bold flex flex-grow min-w-0 space-x-2 md:flex <%= 'hidden' if params[:q].present? %>">
<%= svg_icon('folder', class: 'w-9 h-9 flex-shrink-0') %> <%= svg_icon('folder', class: 'w-9 h-9 flex-shrink-0') %>
<span class="peer"> <span class="peer truncate">
<%= @template_folder.name %> <%= @template_folder.name %>
</span> </span>
<% if can?(:update, @template_folder) && @template_folder.name != TemplateFolder::DEFAULT_NAME %> <% if can?(:update, @template_folder) && @template_folder.name != TemplateFolder::DEFAULT_NAME %>
+2 -2
View File
@@ -3,8 +3,8 @@
<button type="submit" class="hidden"></button> <button type="submit" class="hidden"></button>
<file-dropzone data-submit-on-upload="true" class="w-full"> <file-dropzone data-submit-on-upload="true" class="w-full">
<label for="file_dropzone_input" class="w-full block h-52 relative hover:bg-base-200/30 rounded-xl border border-2 border-base-300 border-dashed"> <label for="file_dropzone_input" class="w-full block h-52 relative hover:bg-base-200/30 rounded-xl border border-2 border-base-300 border-dashed">
<div class="absolute top-0 right-0 left-0 bottom-0 flex items-center justify-center"> <div class="absolute top-0 right-0 left-0 bottom-0 flex items-center justify-center p-2">
<div class="flex flex-col items-center"> <div class="flex flex-col items-center text-center">
<span data-target="file-dropzone.icon" class="flex flex-col items-center"> <span data-target="file-dropzone.icon" class="flex flex-col items-center">
<span> <span>
<%= svg_icon('cloud_upload', class: 'w-10 h-10') %> <%= svg_icon('cloud_upload', class: 'w-10 h-10') %>
+14 -10
View File
@@ -43,7 +43,7 @@
<% else %> <% else %>
<div class="tooltip flex" data-tip="<%= l(submitter.status_event_at.in_time_zone(current_account.timezone), format: :short, locale: current_account.locale) %>"> <div class="tooltip flex" data-tip="<%= l(submitter.status_event_at.in_time_zone(current_account.timezone), format: :short, locale: current_account.locale) %>">
<span class="badge <%= status_badges[submitter.status] %> md:w-32 badge-lg bg-opacity-50 uppercase text-sm font-semibold"> <span class="badge <%= status_badges[submitter.status] %> md:w-32 badge-lg bg-opacity-50 uppercase text-sm font-semibold">
<%= submitter.status %> <%= t(submitter.status) %>
</span> </span>
</div> </div>
<% end %> <% end %>
@@ -58,7 +58,7 @@
<form onsubmit="event.preventDefault()" class="flex-1 md:flex-none"> <form onsubmit="event.preventDefault()" class="flex-1 md:flex-none">
<button onclick="event.stopPropagation()" class="w-full md:w-fit"> <button onclick="event.stopPropagation()" class="w-full md:w-fit">
<download-button data-src="<%= submitter_download_index_path(submitter.slug) %>" class="btn btn-sm btn-neutral text-white w-full md:w-36"> <download-button data-src="<%= submitter_download_index_path(submitter.slug) %>" class="btn btn-sm btn-neutral text-white w-full md:w-36">
<span class="flex items-center justify-center space-x-1 md:space-x-2" data-target="download-button.defaultButton"> <span class="flex items-center justify-center space-x-1 <%= 'md:space-x-2' if t('download').length < 11 %>" data-target="download-button.defaultButton">
<%= svg_icon('download', class: 'w-5 h-5 stroke-2') %> <%= svg_icon('download', class: 'w-5 h-5 stroke-2') %>
<span class="inline"><%= t('download') %></span> <span class="inline"><%= t('download') %></span>
</span> </span>
@@ -74,14 +74,16 @@
<form data-turbo="false" method="get" action="<%= submit_form_url(slug: submitter.slug) %>" class="flex-1 md:flex-none"> <form data-turbo="false" method="get" action="<%= submit_form_url(slug: submitter.slug) %>" class="flex-1 md:flex-none">
<button onclick="event.stopPropagation()" class="btn btn-sm btn-neutral btn-outline bg-white w-full md:w-36 flex"> <button onclick="event.stopPropagation()" class="btn btn-sm btn-neutral btn-outline bg-white w-full md:w-36 flex">
<span class="flex items-center justify-center space-x-1 md:space-x-2"> <span class="flex items-center justify-center space-x-1 md:space-x-2">
<%= svg_icon('writing_sign', class: 'w-4 h-4 stroke-2') %> <% if t('sign_now').length < 12 %>
<%= svg_icon('writing_sign', class: 'w-4 h-4 stroke-2') %>
<% end %>
<span class="inline shrink-0"><%= t('sign_now') %></span> <span class="inline shrink-0"><%= t('sign_now') %></span>
</span> </span>
</button> </button>
</form> </form>
<% else %> <% else %>
<div class="flex-1 md:flex-none"> <div class="flex-1 md:flex-none">
<%= render 'shared/clipboard_copy', text: submit_form_url(slug: submitter.slug), class: 'btn btn-sm btn-neutral text-white md:w-36 flex', icon_class: 'w-6 h-6 text-white', copy_title: 'Copy Link', copy_title_md: t('copy'), copied_title_md: t('copied') %> <%= render 'shared/clipboard_copy', text: submit_form_url(slug: submitter.slug), class: 'btn btn-sm btn-neutral text-white md:w-36 flex', icon_class: 'w-6 h-6 text-white', copy_title: t('copy_link').length < 10 ? t('copy_link') : t('copy'), copy_title_md: t('copy'), copied_title_md: t('copied') %>
</div> </div>
<% end %> <% end %>
<% end %> <% end %>
@@ -106,7 +108,7 @@
<% latest_submitter = submitters.select(&:completed_at?).max_by(&:completed_at) %> <% latest_submitter = submitters.select(&:completed_at?).max_by(&:completed_at) %>
<div class="tooltip flex" data-tip="<%= l(latest_submitter.status_event_at.in_time_zone(current_account.timezone), format: :short, locale: current_account.locale) %>"> <div class="tooltip flex" data-tip="<%= l(latest_submitter.status_event_at.in_time_zone(current_account.timezone), format: :short, locale: current_account.locale) %>">
<span class="badge <%= status_badges[latest_submitter.status] %> md:w-32 bg-opacity-50 badge-lg uppercase text-sm font-semibold"> <span class="badge <%= status_badges[latest_submitter.status] %> md:w-32 bg-opacity-50 badge-lg uppercase text-sm font-semibold">
<%= latest_submitter.status %> <%= t(latest_submitter.status) %>
</span> </span>
</div> </div>
<% elsif submission.expired? %> <% elsif submission.expired? %>
@@ -123,7 +125,7 @@
<% if !is_submission_completed && !submission.expired? %> <% if !is_submission_completed && !submission.expired? %>
<div class="tooltip flex" data-tip="<%= l(submitter.status_event_at.in_time_zone(current_account.timezone), format: :short, locale: current_account.locale) %>"> <div class="tooltip flex" data-tip="<%= l(submitter.status_event_at.in_time_zone(current_account.timezone), format: :short, locale: current_account.locale) %>">
<span class="badge md:w-24 <%= status_badges[submitter.status] %> bg-opacity-50 uppercase text-xs font-semibold"> <span class="badge md:w-24 <%= status_badges[submitter.status] %> bg-opacity-50 uppercase text-xs font-semibold">
<%= submitter.status %> <%= t(submitter.status) %>
</span> </span>
</div> </div>
<% end %> <% end %>
@@ -152,13 +154,15 @@
<form data-turbo="false" method="get" action="<%= submit_form_url(slug: submitter.slug) %>"> <form data-turbo="false" method="get" action="<%= submit_form_url(slug: submitter.slug) %>">
<button onclick="event.stopPropagation()" class="absolute md:relative top-0 right-0 btn btn-xs btn-outline btn-neutral bg-white w-28 md:w-36"> <button onclick="event.stopPropagation()" class="absolute md:relative top-0 right-0 btn btn-xs btn-outline btn-neutral bg-white w-28 md:w-36">
<span class="flex items-center justify-center space-x-1 md:space-x-2"> <span class="flex items-center justify-center space-x-1 md:space-x-2">
<%= svg_icon('writing_sign', class: 'w-4 h-4 stroke-2') %> <% if t('sign_now').length < 12 %>
<%= svg_icon('writing_sign', class: 'w-4 h-4 stroke-2') %>
<% end %>
<span class="inline shrink-0"><%= t('sign_now') %></span> <span class="inline shrink-0"><%= t('sign_now') %></span>
</span> </span>
</button> </button>
</form> </form>
<% else %> <% else %>
<%= render 'shared/clipboard_copy', text: submit_form_url(slug: submitter.slug), class: 'absolute md:relative top-0 right-0 btn btn-xs text-xs btn-neutral text-white w-28 md:w-36 flex', icon_class: 'w-4 h-4 text-white', copy_title: 'Copy Link', copy_title_md: 'Copy Link', copied_title_md: t('copied') %> <%= render 'shared/clipboard_copy', text: submit_form_url(slug: submitter.slug), class: 'absolute md:relative top-0 right-0 btn btn-xs text-xs btn-neutral text-white w-28 md:w-36 flex', icon_class: 'w-4 h-4 text-white', copy_title: t('copy_link'), copy_title_md: t('copy_link').length < 10 ? t('copy_link') : t('copy'), copied_title_md: t('copied') %>
<% end %> <% end %>
</div> </div>
<% end %> <% end %>
@@ -172,8 +176,8 @@
<% latest_submitter = submitters.select(&:completed_at?).max_by(&:completed_at) %> <% latest_submitter = submitters.select(&:completed_at?).max_by(&:completed_at) %>
<div class="flex-1 md:flex-none"> <div class="flex-1 md:flex-none">
<form onsubmit="event.preventDefault()" class="w-full md:w-fit"> <form onsubmit="event.preventDefault()" class="w-full md:w-fit">
<button onclick="event.stopPropagation()"> <button onclick="event.stopPropagation()" class="w-full">
<download-button data-src="<%= submitter_download_index_path(latest_submitter.slug) %>" class="btn btn-sm btn-neutral text-white md:w-36"> <download-button data-src="<%= submitter_download_index_path(latest_submitter.slug) %>" class="btn btn-sm btn-neutral text-white w-full md:w-36">
<span class="flex items-center justify-center space-x-1 md:space-x-2" data-target="download-button.defaultButton"> <span class="flex items-center justify-center space-x-1 md:space-x-2" data-target="download-button.defaultButton">
<%= svg_icon('download', class: 'w-5 h-5 stroke-2') %> <%= svg_icon('download', class: 'w-5 h-5 stroke-2') %>
<span class="inline"><%= t('download') %></span> <span class="inline"><%= t('download') %></span>
+2 -2
View File
@@ -46,11 +46,11 @@
<% end %> <% end %>
</div> </div>
<% end %> <% end %>
<%= render 'shared/clipboard_copy', text: start_form_url(slug: @template.slug), class: 'absolute md:relative bottom-0 right-0 btn btn-xs md:btn-sm whitespace-nowrap btn-neutral text-white mt-1 px-2', icon_class: 'w-4 h-4 md:w-6 md:h-6 text-white', copy_title: 'Link', copied_title: t('copied'), copy_title_md: 'Link', copied_title_md: t('copied') %> <%= render 'shared/clipboard_copy', text: start_form_url(slug: @template.slug), class: 'absolute md:relative bottom-0 right-0 btn btn-xs md:btn-sm whitespace-nowrap btn-neutral text-white mt-1 px-2', icon_class: 'w-4 h-4 md:w-6 md:h-6 text-white', copy_title: t('link'), copied_title: t('copied'), copy_title_md: t('link'), copied_title_md: t('copied') %>
</div> </div>
<% end %> <% end %>
</div> </div>
<div class="flex space-x-2 w-full md:w-fit md:justify-between md:flex-none md:pt-1"> <div class="flex flex-wrap gap-2 w-full md:w-fit md:justify-between md:flex-none md:pt-1">
<% if !template.archived_at? && can?(:destroy, template) %> <% if !template.archived_at? && can?(:destroy, template) %>
<%= button_to button_title(title: t('archive'), disabled_with: t('archiving'), title_class: 'inline', icon: svg_icon('archive', class: 'w-6 h-6')), template_path(template), class: 'btn btn-outline btn-sm w-full', form_class: 'flex-1', method: :delete, data: { turbo_confirm: t('are_you_sure_') } %> <%= button_to button_title(title: t('archive'), disabled_with: t('archiving'), title_class: 'inline', icon: svg_icon('archive', class: 'w-6 h-6')), template_path(template), class: 'btn btn-outline btn-sm w-full', form_class: 'flex-1', method: :delete, data: { turbo_confirm: t('are_you_sure_') } %>
<% end %> <% end %>
+1 -1
View File
@@ -1 +1 @@
<template-builder class="grid" data-template="<%= @template_data %>" data-with-sign-yourself-button="<%= !@template.archived_at? %>" data-with-send-button="<%= !@template.archived_at? && can?(:create, @template.submissions.new(account: current_account)) %>" data-locale="<%= current_account.locale %>"></template-builder> <template-builder class="grid" data-template="<%= @template_data %>" data-with-sign-yourself-button="<%= !@template.archived_at? %>" data-with-send-button="<%= !@template.archived_at? && can?(:create, @template.submissions.new(account: current_account)) %>" data-locale="<%= I18n.locale %>"></template-builder>
+3 -3
View File
@@ -3,9 +3,9 @@
<% if @base_template %> <% if @base_template %>
<%= hidden_field_tag :base_template_id, @base_template.id %> <%= hidden_field_tag :base_template_id, @base_template.id %>
<% end %> <% end %>
<% if @base_template && (can?(:manage, :tenants) || true_user != current_user) && true_user.account.linked_accounts.active.exists? %> <% if @base_template && (can?(:manage, :tenants) || true_user != current_user) && true_user.account.linked_accounts.active.accessible_by(current_ability).exists? %>
<div class="form-control -mb-2 mt-2"> <div class="form-control -mb-2 mt-2">
<%= select_tag :account_id, options_for_select([true_user.account, *true_user.account.linked_accounts.active].uniq.map { |e| [e.name, e.id] }, current_account.id), required: true, class: 'base-select' %> <%= select_tag :account_id, options_for_select([true_user.account, *true_user.account.linked_accounts.active.accessible_by(current_ability)].uniq.map { |e| [e.name, e.id] }, current_account.id), required: true, class: 'base-select' %>
</div> </div>
<% end %> <% end %>
<div class="form-control mt-6"> <div class="form-control mt-6">
@@ -23,7 +23,7 @@
</folder-autocomplete> </folder-autocomplete>
</div> </div>
<div class="form-control"> <div class="form-control">
<%= f.button button_title(title: @base_template ? 'Submit' : 'Create', disabled_with: 'Creating'), class: 'base-button' %> <%= f.button button_title(title: @base_template ? t('submit') : t('create'), disabled_with: t('creating')), class: 'base-button' %>
</div> </div>
<% end %> <% end %>
<% end %> <% end %>
+1 -1
View File
@@ -63,7 +63,7 @@
</div> </div>
<% else %> <% else %>
<div class="card bg-base-200"> <div class="card bg-base-200">
<div class="card-body text-center py-16"> <div class="card-body text-center px-4 py-16">
<div class="max-w-lg mx-auto"> <div class="max-w-lg mx-auto">
<p class="text-3xl font-bold text-base-content mb-4"> <p class="text-3xl font-bold text-base-content mb-4">
<%= t('there_are_no_submissions') %> <%= t('there_are_no_submissions') %>
+5 -3
View File
@@ -1,13 +1,15 @@
<% has_archived = current_account.templates.where.not(archived_at: nil).exists? %> <% has_archived = current_account.templates.where.not(archived_at: nil).exists? %>
<% if Docuseal.demo? %><%= render 'shared/demo_alert' %><% end %> <% if Docuseal.demo? %><%= render 'shared/demo_alert' %><% end %>
<div class="flex justify-between mb-4 items-center"> <div class="flex justify-between items-center w-full mb-4">
<div class="flex items-center"> <div class="flex items-center flex-grow min-w-0">
<% if has_archived || @pagy.count > 0 %> <% if has_archived || @pagy.count > 0 %>
<div class="mr-2"> <div class="mr-2">
<%= render 'dashboard/toggle_view', selected: 'templates' %> <%= render 'dashboard/toggle_view', selected: 'templates' %>
</div> </div>
<% end %> <% end %>
<h1 class="text-2xl md:text-3xl sm:text-4xl font-bold md:block <%= 'hidden' if params[:q].present? %>"><%= t('document_templates_html') %></h1> <h1 class="text-2xl truncate md:text-3xl sm:text-4xl font-bold md:block <%= 'hidden' if params[:q].present? %>">
<%= t('document_templates_html') %>
</h1>
</div> </div>
<div class="flex space-x-2"> <div class="flex space-x-2">
<% if params[:q].present? || @pagy.pages > 1 || @template_folders.present? %> <% if params[:q].present? || @pagy.pages > 1 || @template_folders.present? %>
+14 -4
View File
@@ -158,7 +158,7 @@
<div class="collapse collapse-arrow join-item border border-base-300"> <div class="collapse collapse-arrow join-item border border-base-300">
<input type="checkbox" name="accordion"> <input type="checkbox" name="accordion">
<div class="collapse-title text-xl font-medium"> <div class="collapse-title text-xl font-medium">
<%= t('copleted_notification_email') %> <%= t('completed_notification_email') %>
</div> </div>
<div class="collapse-content"> <div class="collapse-content">
<%= form_for @template, url: template_preferences_path(@template), method: :post, html: { autocomplete: 'off', class: 'mt-1' }, data: { close_on_submit: false } do |f| %> <%= form_for @template, url: template_preferences_path(@template), method: :post, html: { autocomplete: 'off', class: 'mt-1' }, data: { close_on_submit: false } do |f| %>
@@ -256,10 +256,20 @@
</div> </div>
<% end %> <% end %>
</div> </div>
<div class="form-control mt-6 pb-2"> <% end %>
<%= f.button button_title(title: t('save'), disabled_with: t('updating')), class: 'base-button' %> <%= form_for @template, url: template_preferences_path(@template), method: :post, html: { autocomplete: 'off', class: 'mt-2' }, data: { close_on_submit: false } do |f| %>
<div class="flex items-center pt-4 mt-4 justify-between border-t w-full">
<span>
<%= t('enforce_recipients_order') %>
</span>
<%= f.fields_for :preferences, Struct.new(:submitters_order).new(@template.preferences['submitters_order']) do |ff| %>
<%= ff.check_box :submitters_order, { class: 'toggle', onchange: 'this.form.requestSubmit()' }, 'preserved', '' %>
<% end %>
</div> </div>
<% end %> <% end %>
<div class="form-control mt-5 pb-2">
<%= button_tag button_title(title: t('save'), disabled_with: t('updating')), class: 'base-button', form: :submitters_form %>
</div>
</div> </div>
<% end %> <% end %>
<% if show_api %> <% if show_api %>
@@ -285,7 +295,7 @@
<%= render 'templates_code_modal/placeholder' %> <%= render 'templates_code_modal/placeholder' %>
<%= render 'templates/embedding', template: @template %> <%= render 'templates/embedding', template: @template %>
<% if can?(:manage, TemplateSharing.new(template: @template)) %> <% if can?(:manage, TemplateSharing.new(template: @template)) %>
<%= form_for '', url: template_sharings_testing_index_path, method: :post, html: { class: 'mt-1' } do |f| %> <%= form_for '', url: template_sharings_testing_index_path, method: :post, html: { class: 'mt-1' }, data: { close_on_submit: false } do |f| %>
<%= f.hidden_field :template_id, value: @template.id %> <%= f.hidden_field :template_id, value: @template.id %>
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<span> <span>
+2 -2
View File
@@ -28,8 +28,8 @@
<%= form_for @user_config, url: user_initials_path, method: :put, data: { turbo_frame: :_top }, html: { autocomplete: :off, enctype: 'multipart/form-data' } do |f| %> <%= form_for @user_config, url: user_initials_path, method: :put, data: { turbo_frame: :_top }, html: { autocomplete: :off, enctype: 'multipart/form-data' } do |f| %>
<file-dropzone data-submit-on-upload="true" class="w-full"> <file-dropzone data-submit-on-upload="true" class="w-full">
<label for="file" class="w-full block h-32 relative bg-base-200 hover:bg-base-200/70 rounded-md border border-base-content border-dashed"> <label for="file" class="w-full block h-32 relative bg-base-200 hover:bg-base-200/70 rounded-md border border-base-content border-dashed">
<div class="absolute top-0 right-0 left-0 bottom-0 flex items-center justify-center"> <div class="absolute top-0 right-0 left-0 bottom-0 flex items-center justify-center p-2">
<div class="flex flex-col items-center"> <div class="flex flex-col items-center text-center">
<span data-target="file-dropzone.icon"> <span data-target="file-dropzone.icon">
<%= svg_icon('cloud_upload', class: 'w-10 h-10') %> <%= svg_icon('cloud_upload', class: 'w-10 h-10') %>
</span> </span>
@@ -1,7 +1,7 @@
<p>Hello <%= @user.first_name %>,</p> <p><%= @user.first_name.present? ? t('hello_name', name: @user.first_name) : t('hi_there') %>,</p>
<p>You have been invited to <%= @user.account.name %> <%= Docuseal.product_name %>. Please sign up using the link below:</p> <p><%= t('you_have_been_invited_to_account_name_product_name_please_sign_up_using_the_link_below_', account_name: @user.account.name, product_name: Docuseal.product_name) %></p>
<p><%= link_to 'Sign up', invitation_url(reset_password_token: @token) %></p> <p><%= link_to t('sign_up'), invitation_url(reset_password_token: @token) %></p>
<p>Please contact us by replying to this email if you didn't request this.</p> <p><%= t('please_contact_us_by_replying_to_this_email_if_you_didn_t_request_this') %></p>
<p> <p>
Thanks,<br><%= @current_account.name %> <%= t('thanks') %>,<br><%= @current_account.name %>
</p> </p>
+2 -2
View File
@@ -28,8 +28,8 @@
<%= form_for @user_config, url: user_signature_path, method: :put, data: { turbo_frame: :_top }, html: { autocomplete: :off, enctype: 'multipart/form-data' } do |f| %> <%= form_for @user_config, url: user_signature_path, method: :put, data: { turbo_frame: :_top }, html: { autocomplete: :off, enctype: 'multipart/form-data' } do |f| %>
<file-dropzone data-submit-on-upload="true" class="w-full"> <file-dropzone data-submit-on-upload="true" class="w-full">
<label for="file" class="w-full block h-32 relative bg-base-200 hover:bg-base-200/70 rounded-md border border-base-content border-dashed"> <label for="file" class="w-full block h-32 relative bg-base-200 hover:bg-base-200/70 rounded-md border border-base-content border-dashed">
<div class="absolute top-0 right-0 left-0 bottom-0 flex items-center justify-center"> <div class="absolute top-0 right-0 left-0 bottom-0 flex items-center justify-center p-2">
<div class="flex flex-col items-center"> <div class="flex flex-col items-center text-center">
<span data-target="file-dropzone.icon"> <span data-target="file-dropzone.icon">
<%= svg_icon('cloud_upload', class: 'w-10 h-10') %> <%= svg_icon('cloud_upload', class: 'w-10 h-10') %>
</span> </span>
+3 -3
View File
@@ -1,17 +1,17 @@
<div class="flex-wrap space-y-4 md:flex md:flex-nowrap md:space-y-0 md:space-x-10"> <div class="flex-wrap space-y-4 md:flex md:flex-nowrap md:space-y-0 md:space-x-10">
<%= render 'shared/settings_nav' %> <%= render 'shared/settings_nav' %>
<div class="md:flex-grow"> <div class="md:flex-grow">
<div class="flex justify-between mb-4"> <div class="flex flex-col md:flex-row gap-2 md:justify-between md:items-end mb-4">
<h1 class="text-4xl font-bold"> <h1 class="text-4xl font-bold">
<%= t('status_users', status: params[:status].to_s.titleize) %> <%= t('status_users', status: params[:status].to_s.titleize) %>
</h1> </h1>
<div class="flex items-center space-x-4"> <div class="flex flex-col md:flex-row gap-y-2 gap-x-4 md:items-center">
<% if can?(:create, User.new(account: current_account)) %> <% if can?(:create, User.new(account: current_account)) %>
<%= render 'users/extra_buttons' %> <%= render 'users/extra_buttons' %>
<% if content_for(:add_user_button) %> <% if content_for(:add_user_button) %>
<%= content_for(:add_user_button) %> <%= content_for(:add_user_button) %>
<% else %> <% else %>
<%= link_to new_user_path, class: 'btn btn-primary btn-md gap-2', data: { turbo_frame: 'modal' } do %> <%= link_to new_user_path, class: 'btn btn-primary btn-md gap-2 w-full md:w-fit', data: { turbo_frame: 'modal' } do %>
<%= svg_icon('plus', class: 'w-6 h-6') %> <%= svg_icon('plus', class: 'w-6 h-6') %>
<span><%= t('new_user') %></span> <span><%= t('new_user') %></span>
<% end %> <% end %>
+4 -4
View File
@@ -1,8 +1,8 @@
<div class="flex flex-wrap space-y-4 md:flex-nowrap md:space-y-0 md:space-x-10"> <div class="flex flex-wrap space-y-4 md:flex-nowrap md:space-y-0 md:space-x-10">
<%= render 'shared/settings_nav' %> <%= render 'shared/settings_nav' %>
<div class="flex-grow"> <div class="flex-grow">
<div class="flex justify-between"> <div class="flex flex-col gap-2 md:flex-row md:justify-between md:items-end mb-4">
<h1 class="text-4xl font-bold mb-4">Webhooks</h1> <h1 class="text-4xl font-bold">Webhooks</h1>
<%= render 'shared/test_mode_toggle' %> <%= render 'shared/test_mode_toggle' %>
</div> </div>
<div class="card bg-base-200"> <div class="card bg-base-200">
@@ -19,7 +19,7 @@
<% end %> <% end %>
<% preference = current_account.account_configs.find_by(key: AccountConfig::WEBHOOK_PREFERENCES_KEY)&.value || {} %> <% preference = current_account.account_configs.find_by(key: AccountConfig::WEBHOOK_PREFERENCES_KEY)&.value || {} %>
<% WebhookPreferencesController::EVENTS.group_by { |e| e.include?('form') }.each do |_, events| %> <% WebhookPreferencesController::EVENTS.group_by { |e| e.include?('form') }.each do |_, events| %>
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 mt-2 gap-y-2"> <div class="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-4 mt-2 gap-y-2">
<% events.each do |event| %> <% events.each do |event| %>
<%= form_for '', url: webhook_preferences_path, method: :post do |f| %> <%= form_for '', url: webhook_preferences_path, method: :post do |f| %>
<%= f.hidden_field :event, value: event %> <%= f.hidden_field :event, value: event %>
@@ -43,7 +43,7 @@
<div class="space-y-4 mt-4"> <div class="space-y-4 mt-4">
<div class="collapse collapse-open bg-base-200 px-1"> <div class="collapse collapse-open bg-base-200 px-1">
<div class="p-4 text-xl font-medium"> <div class="p-4 text-xl font-medium">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between gap-2">
<span> <span>
<%= t('submission_example_payload') %> <%= t('submission_example_payload') %>
</span> </span>
+1 -1
View File
@@ -17,7 +17,7 @@ Bundler.require(*Rails.groups)
module DocuSeal module DocuSeal
class Application < Rails::Application class Application < Rails::Application
config.load_defaults 7.1 config.load_defaults 7.2
config.autoload_lib(ignore: %w[assets tasks puma]) config.autoload_lib(ignore: %w[assets tasks puma])
+1 -1
View File
@@ -87,7 +87,7 @@ Rails.application.configure do
domain: ENV.fetch('SMTP_DOMAIN', nil), domain: ENV.fetch('SMTP_DOMAIN', nil),
user_name: ENV.fetch('SMTP_USERNAME', nil), user_name: ENV.fetch('SMTP_USERNAME', nil),
password: ENV.fetch('SMTP_PASSWORD', nil), password: ENV.fetch('SMTP_PASSWORD', nil),
authentication: ENV.fetch('SMTP_AUTHENTICATION', 'plain'), authentication: ENV.fetch('SMTP_PASSWORD', nil).present? ? ENV.fetch('SMTP_AUTHENTICATION', 'plain') : nil,
enable_starttls_auto: ENV['SMTP_ENABLE_STARTTLS_AUTO'] != 'false' enable_starttls_auto: ENV['SMTP_ENABLE_STARTTLS_AUTO'] != 'false'
}.compact }.compact
end end
+13
View File
@@ -1,6 +1,19 @@
# frozen_string_literal: true # frozen_string_literal: true
module HexaPDF module HexaPDF
module DigitalSignature
class Signatures
private
def generate_field_name
index = (@document.acro_form.each_field
.map { |field| field.full_field_name.to_s.scan(/\ASignature(\d+)/).first&.first.to_i }
.max || 0) + 1
"Signature#{index}"
end
end
end
module Encryption module Encryption
class SecurityHandler class SecurityHandler
def encrypt_string(str, obj) def encrypt_string(str, obj)
+1 -1
View File
@@ -1,6 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
Pagy::DEFAULT[:items] = 10 Pagy::DEFAULT[:limit] = 10
Pagy::DEFAULT.freeze Pagy::DEFAULT.freeze
ActiveSupport.on_load(:action_view) do ActiveSupport.on_load(:action_view) do
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+8 -2
View File
@@ -118,7 +118,11 @@ module Accounts
end end
if (default_cert = cert_data['custom']&.find { |e| e['status'] == 'default' }) if (default_cert = cert_data['custom']&.find { |e| e['status'] == 'default' })
OpenSSL::PKCS12.new(Base64.urlsafe_decode64(default_cert['data']), default_cert['password'].to_s) if default_cert['name'] == Docuseal::AATL_CERT_NAME
Docuseal.default_pkcs
else
OpenSSL::PKCS12.new(Base64.urlsafe_decode64(default_cert['data']), default_cert['password'].to_s)
end
else else
GenerateCertificate.load_pkcs(cert_data) GenerateCertificate.load_pkcs(cert_data)
end end
@@ -153,7 +157,9 @@ module Accounts
default_pkcs = GenerateCertificate.load_pkcs(cert_data) default_pkcs = GenerateCertificate.load_pkcs(cert_data)
custom_certs = cert_data.fetch('custom', []).map do |e| custom_certs = cert_data.fetch('custom', []).filter_map do |e|
next if e['data'].blank?
OpenSSL::PKCS12.new(Base64.urlsafe_decode64(e['data']), e['password'].to_s) OpenSSL::PKCS12.new(Base64.urlsafe_decode64(e['data']), e['password'].to_s)
end end
+3 -3
View File
@@ -43,9 +43,9 @@ module ActionMailerConfigsInterceptor
address: value['host'], address: value['host'],
port: value['port'], port: value['port'],
domain: value['domain'], domain: value['domain'],
openssl_verify_mode: OpenSSL::SSL::VERIFY_NONE, openssl_verify_mode: value['security'] == 'noverify' ? OpenSSL::SSL::VERIFY_NONE : nil,
authentication: value.fetch('authentication', 'plain'), authentication: value['password'].present? ? value.fetch('authentication', 'plain') : nil,
enable_starttls_auto: true, enable_starttls_auto: value['security'] != 'tls',
open_timeout: OPEN_TIMEOUT, open_timeout: OPEN_TIMEOUT,
read_timeout: READ_TIMEOUT, read_timeout: READ_TIMEOUT,
ssl: value['security'] == 'ssl', ssl: value['security'] == 'ssl',
+1
View File
@@ -14,6 +14,7 @@ module Docuseal
CHATGPT_URL = 'https://chatgpt.com/g/g-9hg8AAw0r-docuseal' CHATGPT_URL = 'https://chatgpt.com/g/g-9hg8AAw0r-docuseal'
SUPPORT_EMAIL = 'support@docuseal.co' SUPPORT_EMAIL = 'support@docuseal.co'
HOST = ENV.fetch('HOST', 'localhost') HOST = ENV.fetch('HOST', 'localhost')
AATL_CERT_NAME = 'docuseal_aatl'
CONSOLE_URL = if Rails.env.development? CONSOLE_URL = if Rails.env.development?
'http://console.localhost.io:3001' 'http://console.localhost.io:3001'
elsif ENV['MULTITENANT'] == 'true' elsif ENV['MULTITENANT'] == 'true'
+1 -1
View File
@@ -23,7 +23,7 @@ module ReplaceEmailVariables
# rubocop:disable Metrics # rubocop:disable Metrics
def call(text, submitter:, tracking_event_type: 'click_email', html_escape: false, sig: nil) def call(text, submitter:, tracking_event_type: 'click_email', html_escape: false, sig: nil)
text = replace(text, TEMPLATE_NAME, html_escape:) { submitter.template.name } text = replace(text, TEMPLATE_NAME, html_escape:) { (submitter.template || submitter.submission.template).name }
text = replace(text, TEMPLATE_ID, html_escape:) { submitter.template.id } text = replace(text, TEMPLATE_ID, html_escape:) { submitter.template.id }
text = replace(text, SUBMITTER_ID, html_escape:) { submitter.id } text = replace(text, SUBMITTER_ID, html_escape:) { submitter.id }
text = replace(text, SUBMITTER_SLUG, html_escape:) { submitter.slug } text = replace(text, SUBMITTER_SLUG, html_escape:) { submitter.slug }
-16
View File
@@ -3,22 +3,6 @@
module SubmissionEvents module SubmissionEvents
TRACKING_PARAM_LENGTH = 6 TRACKING_PARAM_LENGTH = 6
EVENT_NAMES = {
send_email: 'Email sent',
send_reminder_email: 'Reminder email sent',
send_sms: 'SMS sent',
send_2fa_sms: 'Verification SMS sent',
open_email: 'Email opened',
click_email: 'Email link clicked',
click_sms: 'SMS link clicked',
phone_verified: 'Phone verified',
start_form: 'Submission started',
view_form: 'Form viewed',
invite_party: 'Invited',
complete_form: 'Submission completed',
api_complete_form: 'Submission completed via API'
}.freeze
module_function module_function
def build_tracking_param(submitter, event_type = 'click_email') def build_tracking_param(submitter, event_type = 'click_email')
+3
View File
@@ -111,6 +111,9 @@ module Submissions
return email.downcase if email.to_s.include?('.mm') return email.downcase if email.to_s.include?('.mm')
return email.downcase if email.to_s.include?('.cm') return email.downcase if email.to_s.include?('.cm')
return email.downcase if email.to_s.include?('.et') return email.downcase if email.to_s.include?('.et')
return email.downcase if email.to_s.include?('.mo')
return email.downcase if email.to_s.include?('.nz')
return email.downcase if email.to_s.include?('.za')
return email.downcase unless email.to_s.include?('.') return email.downcase unless email.to_s.include?('.')
fixed_email = EmailTypo.call(email.delete_prefix('<')) fixed_email = EmailTypo.call(email.delete_prefix('<'))
+76 -63
View File
@@ -12,9 +12,6 @@ module Submissions
'Helvetica' 'Helvetica'
end end
VERIFIED_TEXT = 'Verified'
UNVERIFIED_TEXT = 'Unverified'
CURRENCY_SYMBOLS = { CURRENCY_SYMBOLS = {
'USD' => '$', 'USD' => '$',
'EUR' => '€', 'EUR' => '€',
@@ -32,33 +29,37 @@ module Submissions
# rubocop:disable Metrics # rubocop:disable Metrics
def call(submission) def call(submission)
document = build_audit_trail(submission)
account = submission.account account = submission.account
pkcs = Accounts.load_signing_pkcs(account) I18n.with_locale(account.locale) do
tsa_url = Accounts.load_timeserver_url(account) document = build_audit_trail(submission)
io = StringIO.new pkcs = Accounts.load_signing_pkcs(account)
tsa_url = Accounts.load_timeserver_url(account)
document.trailer.info[:Creator] = "#{Docuseal.product_name} (#{Docuseal::PRODUCT_URL})" io = StringIO.new
sign_params = { document.trailer.info[:Creator] = "#{Docuseal.product_name} (#{Docuseal::PRODUCT_URL})"
reason: sign_reason,
**Submissions::GenerateResultAttachments.build_signing_params(pkcs, tsa_url)
}
document.sign(io, **sign_params) last_submitter = submission.submitters.select(&:completed_at).max_by(&:completed_at)
Submissions::GenerateResultAttachments.maybe_enable_ltv(io, sign_params) sign_params = {
reason: sign_reason,
**Submissions::GenerateResultAttachments.build_signing_params(last_submitter, pkcs, tsa_url)
}
ActiveStorage::Attachment.create!( document.sign(io, **sign_params)
blob: ActiveStorage::Blob.create_and_upload!(
io: io.tap(&:rewind), filename: "Audit Log - #{submission.template.name}.pdf" Submissions::GenerateResultAttachments.maybe_enable_ltv(io, sign_params)
),
name: 'audit_trail', ActiveStorage::Attachment.create!(
record: submission blob: ActiveStorage::Blob.create_and_upload!(
) io: io.tap(&:rewind), filename: "#{I18n.t('audit_log')} - #{submission.template.name}.pdf"
),
name: 'audit_trail',
record: submission
)
end
end end
def build_audit_trail(submission) def build_audit_trail(submission)
@@ -97,6 +98,12 @@ module Submissions
height: 1 height: 1
) )
configs = submission.account.account_configs.where(key: [AccountConfig::WITH_AUDIT_VALUES_KEY,
AccountConfig::WITH_SIGNATURE_ID])
with_signature_id = configs.find { |c| c.key == AccountConfig::WITH_SIGNATURE_ID }&.value == true
with_audit_values = configs.find { |c| c.key == AccountConfig::WITH_AUDIT_VALUES_KEY }&.value != false
composer.page_style(:default, page_size:) do |canvas, style| composer.page_style(:default, page_size:) do |canvas, style|
box = canvas.context.box(:media) box = canvas.context.box(:media)
canvas.save_graphics_state do canvas.save_graphics_state do
@@ -108,9 +115,6 @@ module Submissions
maybe_add_background(canvas, submission, page_size) maybe_add_background(canvas, submission, page_size)
end end
with_signature_id = submission.account.account_configs
.exists?(key: AccountConfig::WITH_SIGNATURE_ID, value: true)
if with_signature_id || submission.account.testing? if with_signature_id || submission.account.testing?
canvas.save_graphics_state do canvas.save_graphics_state do
document_id = Digest::MD5.hexdigest(submission.slug).upcase document_id = Digest::MD5.hexdigest(submission.slug).upcase
@@ -125,7 +129,7 @@ module Submissions
TESTING_FOOTER TESTING_FOOTER
end end
else else
"Document ID: #{document_id}" "#{I18n.t('document_id')}: #{document_id}"
end end
text = HexaPDF::Layout::TextFragment.create( text = HexaPDF::Layout::TextFragment.create(
@@ -153,24 +157,26 @@ module Submissions
composer.column(columns: 1) do |column| composer.column(columns: 1) do |column|
add_logo(column, submission) add_logo(column, submission)
column.text(account.testing? ? 'Testing Log - Not for Production Use' : 'Audit Log', column.text(account.testing? ? I18n.t('testing_log_not_for_production_use') : I18n.t('audit_log'),
font_size: 16, font_size: 16,
padding: [10, 0, 0, 0], padding: [10, 0, 0, 0],
position: :float, text_align: :right) position: :float, text_align: :right)
end end
composer.column(columns: 1) do |column| composer.column(columns: 1) do |column|
column.text("Envelope ID: #{submission.id}", font_size: 12, padding: [15, 0, 8, 0], position: :float) column.text("#{I18n.t('envelope_id')}: #{submission.id}", font_size: 12,
padding: [15, 0, 8, 0],
position: :float)
unless submission.source.in?(%w[embed api]) unless submission.source.in?(%w[embed api])
column.formatted_text([{ link: verify_url, text: 'Verify', style: :link }], column.formatted_text([{ link: verify_url, text: I18n.t('verify'), style: :link }],
font_size: 9, padding: [15, 0, 10, 0], position: :float, text_align: :right) font_size: 9, padding: [15, 0, 10, 0], position: :float, text_align: :right)
end end
end end
composer.draw_box(divider) composer.draw_box(divider)
last_submitter = submission.submitters.where.not(completed_at: nil).order(:completed_at).last last_submitter = submission.submitters.select(&:completed_at).max_by(&:completed_at)
documents_data = Submitters.select_attachments_for_download(last_submitter).map do |document| documents_data = Submitters.select_attachments_for_download(last_submitter).map do |document|
original_documents = submission.template.documents.select do |e| original_documents = submission.template.documents.select do |e|
@@ -183,22 +189,19 @@ module Submissions
end end
end end
link =
ActiveStorage::Blob.proxy_url(document.blob)
[ [
composer.document.layout.formatted_text_box( composer.document.layout.formatted_text_box(
[{ text: document.filename.to_s, link: }] [{ text: document.filename.to_s }]
), ),
composer.document.layout.formatted_text_box( composer.document.layout.formatted_text_box(
[ [
{ text: "Original SHA256:\n", font: [FONT_NAME, { variant: :bold }] }, { text: "#{I18n.t('original_sha256')}:\n", font: [FONT_NAME, { variant: :bold }] },
original_documents.map { |d| d.metadata['sha256'] || d.checksum }.join("\n"), original_documents.map { |d| d.metadata['sha256'] || d.checksum }.join("\n"),
"\n", "\n",
{ text: "Result SHA256:\n", font: [FONT_NAME, { variant: :bold }] }, { text: "#{I18n.t('result_sha256')}:\n", font: [FONT_NAME, { variant: :bold }] },
document.metadata['sha256'] || document.checksum, document.metadata['sha256'] || document.checksum,
"\n", "\n",
{ text: 'Generated at: ', font: [FONT_NAME, { variant: :bold }] }, { text: "#{I18n.t('generated_at')}: ", font: [FONT_NAME, { variant: :bold }] },
"#{I18n.l(document.created_at.in_time_zone(account.timezone), format: :long, locale: account.locale)} " \ "#{I18n.l(document.created_at.in_time_zone(account.timezone), format: :long, locale: account.locale)} " \
"#{TimeUtils.timezone_abbr(account.timezone, document.created_at)}" "#{TimeUtils.timezone_abbr(account.timezone, document.created_at)}"
], line_spacing: 1.3 ], line_spacing: 1.3
@@ -245,13 +248,13 @@ module Submissions
composer.document.layout.formatted_text_box( composer.document.layout.formatted_text_box(
[ [
submitter.email && click_email_event && { submitter.email && click_email_event && {
text: "Email verification: #{VERIFIED_TEXT}\n" text: "#{I18n.t('email_verification')}: #{I18n.t('verified')}\n"
}, },
submitter.phone && is_phone_verified && { submitter.phone && is_phone_verified && {
text: "Phone verification: #{VERIFIED_TEXT}\n" text: "#{I18n.t('phone_verification')}: #{I18n.t('verified')}\n"
}, },
completed_event.data['ip'] && { text: "IP: #{completed_event.data['ip']}\n" }, completed_event.data['ip'] && { text: "IP: #{completed_event.data['ip']}\n" },
completed_event.data['sid'] && { text: "Session ID: #{completed_event.data['sid']}\n" }, completed_event.data['sid'] && { text: "#{I18n.t('session_id')}: #{completed_event.data['sid']}\n" },
completed_event.data['ua'] && { text: "User agent: #{completed_event.data['ua']}\n" }, completed_event.data['ua'] && { text: "User agent: #{completed_event.data['ua']}\n" },
"\n" "\n"
].compact_blank, line_spacing: 1.3, padding: [10, 20, 20, 0] ].compact_blank, line_spacing: 1.3, padding: [10, 20, 20, 0]
@@ -264,6 +267,7 @@ module Submissions
submission.template_fields.filter_map do |field| submission.template_fields.filter_map do |field|
next if field['submitter_uuid'] != submitter.uuid next if field['submitter_uuid'] != submitter.uuid
next if field['type'] == 'heading' next if field['type'] == 'heading'
next if !with_audit_values && !field['type'].in?(%w[signature initials])
submitter_field_counters[field['type']] += 1 submitter_field_counters[field['type']] += 1
@@ -278,7 +282,7 @@ module Submissions
[ [
{ {
text: TextUtils.maybe_rtl_reverse(field_name).upcase.presence || text: TextUtils.maybe_rtl_reverse(field_name).upcase.presence ||
"#{field['type']} Field #{submitter_field_counters[field['type']]}\n".upcase, "#{I18n.t("#{field['type']}_field")} #{submitter_field_counters[field['type']]}\n".upcase,
font_size: 6 font_size: 6
} }
].compact_blank, ].compact_blank,
@@ -310,7 +314,8 @@ module Submissions
price = ApplicationController.helpers.number_to_currency(field['preferences']['price'], unit:) price = ApplicationController.helpers.number_to_currency(field['preferences']['price'], unit:)
composer.formatted_text_box([{ text: "Paid #{price}\n" }], padding: [0, 0, 10, 0]) composer.formatted_text_box([{ text: "#{I18n.t('paid_price', price:)}\n" }],
padding: [0, 0, 10, 0])
end end
composer.formatted_text_box( composer.formatted_text_box(
@@ -330,7 +335,10 @@ module Submissions
value = TimeUtils.format_date_string(value, field.dig('preferences', 'format'), account.locale) value = TimeUtils.format_date_string(value, field.dig('preferences', 'format'), account.locale)
end end
value = NumberUtils.format_number(value, field.dig('preferences', 'format')) if field['type'] == 'number' if field['type'] == 'number'
value = NumberUtils.format_number(value,
field.dig('preferences', 'format'))
end
value = value.join(', ') if value.is_a?(Array) value = value.join(', ') if value.is_a?(Array)
@@ -340,37 +348,42 @@ module Submissions
end end
] ]
end end
composer.draw_box(divider)
end end
composer.text('Event Log', font_size: 12, padding: [10, 0, 20, 0]) composer.draw_box(divider)
composer.text(I18n.t('event_log'), font_size: 12, padding: [10, 0, 20, 0])
events_data = submission.submission_events.sort_by(&:event_timestamp).map do |event| events_data = submission.submission_events.sort_by(&:event_timestamp).map do |event|
submitter = submission.submitters.find { |e| e.id == event.submitter_id } submitter = submission.submitters.find { |e| e.id == event.submitter_id }
submitter_name =
if event.event_type.include?('sms') || event.event_type.include?('phone')
event.data['phone'] || submitter.phone
else
submitter.name || submitter.email || submitter.phone
end
text = SubmissionEvents::EVENT_NAMES[event.event_type.to_sym] text =
if event.event_type == 'invite_party' &&
(invited_submitter = submission.submitters.find { |e| e.uuid == event.data['uuid'] }) &&
(name = submission.template_submitters.find { |e| e['uuid'] == event.data['uuid'] }&.dig('name'))
invited_submitter_name = [invited_submitter.name || invited_submitter.email || invited_submitter.phone,
name].join(' ')
I18n.t('submission_event_names.invite_party_by_html', invited_submitter_name:,
submitter_name:)
elsif event.event_type.include?('send_')
I18n.t("submission_event_names.#{event.event_type}_to_html", submitter_name:)
else
I18n.t("submission_event_names.#{event.event_type}_by_html", submitter_name:)
end
if event.event_type == 'invite_party' && bold_text, normal_text = text.match(%r{<b>(.*?)</b>(.*)}).captures
(invited_submitter = submission.submitters.find { |e| e.uuid == event.data['uuid'] }) &&
(name = submission.template_submitters.find { |e| e['uuid'] == event.data['uuid'] }&.dig('name'))
text += ['', invited_submitter.name || invited_submitter.email || invited_submitter.phone, name].join(' ')
end
[ [
"#{I18n.l(event.event_timestamp.in_time_zone(account.timezone), format: :long, locale: account.locale)} " \ "#{I18n.l(event.event_timestamp.in_time_zone(account.timezone), format: :long, locale: account.locale)} " \
"#{TimeUtils.timezone_abbr(account.timezone, event.event_timestamp)}", "#{TimeUtils.timezone_abbr(account.timezone, event.event_timestamp)}",
composer.document.layout.formatted_text_box( composer.document.layout.formatted_text_box([{ text: bold_text, font: [FONT_NAME, { variant: :bold }] },
[ normal_text])
{ text:, font: [FONT_NAME, { variant: :bold }] },
event.event_type.include?('send_') ? ' to ' : ' by ',
if event.event_type.include?('sms') || event.event_type.include?('phone')
event.data['phone'] || submitter.phone
else
submitter.name || submitter.email || submitter.phone
end
]
)
] ]
end end
@@ -18,8 +18,8 @@ module Submissions
pdf.trailer.info[:Creator] = "#{Docuseal.product_name} (#{Docuseal::PRODUCT_URL})" pdf.trailer.info[:Creator] = "#{Docuseal.product_name} (#{Docuseal::PRODUCT_URL})"
sign_params = { sign_params = {
reason: sign_reason, reason: Submissions::GenerateResultAttachments.single_sign_reason(submitter),
**Submissions::GenerateResultAttachments.build_signing_params(pkcs, tsa_url) **Submissions::GenerateResultAttachments.build_signing_params(submitter, pkcs, tsa_url)
} }
pdf.sign(io, **sign_params) pdf.sign(io, **sign_params)
@@ -38,7 +38,9 @@ module Submissions
def build_combined_pdf(submitter) def build_combined_pdf(submitter)
pdfs_index = Submissions::GenerateResultAttachments.generate_pdfs(submitter) pdfs_index = Submissions::GenerateResultAttachments.generate_pdfs(submitter)
audit_trail = Submissions::GenerateAuditTrail.build_audit_trail(submitter.submission) audit_trail = I18n.with_locale(submitter.account.locale) do
Submissions::GenerateAuditTrail.build_audit_trail(submitter.submission)
end
audit_trail.dispatch_message(:complete_objects) audit_trail.dispatch_message(:complete_objects)
@@ -56,9 +58,5 @@ module Submissions
result result
end end
def sign_reason
'Signed with DocuSeal.co'
end
end end
end end
+9 -8
View File
@@ -21,7 +21,7 @@ module Submissions
def rows_to_xlsx(rows) def rows_to_xlsx(rows)
workbook = RubyXL::Workbook.new workbook = RubyXL::Workbook.new
worksheet = workbook[0] worksheet = workbook[0]
worksheet.sheet_name = I18n.l(Time.current.to_date) worksheet.sheet_name = Time.current.to_date.to_s
headers = build_headers(rows) headers = build_headers(rows)
headers.each_with_index do |column_name, column_index| headers.each_with_index do |column_name, column_index|
@@ -79,7 +79,7 @@ module Submissions
submission_data += submitter.documents.map.with_index(1) do |attachment, index| submission_data += submitter.documents.map.with_index(1) do |attachment, index|
{ {
name: "Document #{index}", name: "#{I18n.t('document')} #{index}",
value: ActiveStorage::Blob.proxy_url(attachment.blob) value: ActiveStorage::Blob.proxy_url(attachment.blob)
} }
end end
@@ -92,20 +92,20 @@ module Submissions
def build_submission_data(submitter, submitter_name, submitters_count) def build_submission_data(submitter, submitter_name, submitters_count)
[ [
{ {
name: column_name('Name', submitter_name, submitters_count), name: column_name(I18n.t('name'), submitter_name, submitters_count),
value: submitter.name value: submitter.name
}, },
{ {
name: column_name('Email', submitter_name, submitters_count), name: column_name(I18n.t('email'), submitter_name, submitters_count),
value: submitter.email value: submitter.email
}, },
{ {
name: column_name('Phone', submitter_name, submitters_count), name: column_name(I18n.t('phone'), submitter_name, submitters_count),
value: submitter.phone value: submitter.phone
}, },
{ {
name: column_name('Completed At', submitter_name, submitters_count), name: column_name(I18n.t('completed_at'), submitter_name, submitters_count),
value: submitter.completed_at value: submitter.completed_at.to_s
} }
].reject { |e| e[:value].blank? } ].reject { |e| e[:value].blank? }
end end
@@ -127,7 +127,8 @@ module Submissions
template_field_type = template_field['type'] template_field_type = template_field['type']
template_field_counters[template_field_type] += 1 template_field_counters[template_field_type] += 1
template_field_name = template_field['name'].presence template_field_name = template_field['name'].presence
template_field_name ||= "#{template_field_type.titleize} Field #{template_field_counters[template_field_type]}" template_field_name ||=
"#{I18n.t("#{template_field_type}_field")} #{template_field_counters[template_field_type]}"
value = value =
if template_field_type.in?(%w[image signature]) if template_field_type.in?(%w[image signature])
+11 -9
View File
@@ -127,7 +127,7 @@ module Submissions
TESTING_FOOTER TESTING_FOOTER
end end
else else
"Document ID: #{document_id}" "#{I18n.t('document_id', locale: submitter.account.locale)}: #{document_id}"
end end
text = HexaPDF::Layout::TextFragment.create( text = HexaPDF::Layout::TextFragment.create(
@@ -224,11 +224,12 @@ module Submissions
reason_value = submitter.values[field.dig('preferences', 'reason_field_uuid')].presence reason_value = submitter.values[field.dig('preferences', 'reason_field_uuid')].presence
reason_string = reason_string =
"#{I18n.t('reason')}: #{reason_value || I18n.t('digitally_signed_by')} " \ I18n.with_locale(submitter.account.locale) do
"#{submitter.name}#{submitter.email.present? ? " <#{submitter.email}>" : ''}\n" \ "#{I18n.t('reason')}: #{reason_value || I18n.t('digitally_signed_by')} " \
"#{I18n.l(attachment.created_at.in_time_zone(submitter.account.timezone), "#{submitter.name}#{submitter.email.present? ? " <#{submitter.email}>" : ''}\n" \
format: :long, locale: submitter.account.locale)} " \ "#{I18n.l(attachment.created_at.in_time_zone(submitter.account.timezone), format: :long)} " \
"#{TimeUtils.timezone_abbr(submitter.account.timezone, attachment.created_at)}" "#{TimeUtils.timezone_abbr(submitter.account.timezone, attachment.created_at)}"
end
reason_text = HexaPDF::Layout::TextFragment.create(reason_string, reason_text = HexaPDF::Layout::TextFragment.create(reason_string,
font:, font:,
@@ -340,7 +341,8 @@ module Submissions
if field['type'].in?(%w[multiple radio]) if field['type'].in?(%w[multiple radio])
option = field['options']&.find { |o| o['uuid'] == area['option_uuid'] } option = field['options']&.find { |o| o['uuid'] == area['option_uuid'] }
option_name = option['value'].presence || "Option #{field['options'].index(option) + 1}" option_name = option['value'].presence
option_name ||= "#{I18n.t('option', locale: account.locale)} #{field['options'].index(option) + 1}"
value = Array.wrap(value).include?(option_name) value = Array.wrap(value).include?(option_name)
end end
@@ -454,7 +456,7 @@ module Submissions
if sign_reason if sign_reason
sign_params = { sign_params = {
reason: sign_reason, reason: sign_reason,
**build_signing_params(pkcs, tsa_url) **build_signing_params(submitter, pkcs, tsa_url)
} }
begin begin
@@ -491,7 +493,7 @@ module Submissions
io io
end end
def build_signing_params(pkcs, tsa_url) def build_signing_params(_submitter, pkcs, tsa_url)
params = { params = {
certificate: pkcs.certificate, certificate: pkcs.certificate,
key: pkcs.key, key: pkcs.key,
+11 -1
View File
@@ -27,7 +27,7 @@ module Submitters
return [submitter.submission.combined_document_attachment] return [submitter.submission.combined_document_attachment]
end end
original_documents = submitter.submission.template.documents.preload(:blob) original_documents = submitter.submission.template_schema_documents.preload(:blob)
is_more_than_two_images = original_documents.count(&:image?) > 1 is_more_than_two_images = original_documents.count(&:image?) > 1
submitter.documents.preload(:blob).reject do |attachment| submitter.documents.preload(:blob).reject do |attachment|
@@ -102,4 +102,14 @@ module Submitters
SendSubmitterInvitationEmailJob.perform_async('submitter_id' => submitter.id) SendSubmitterInvitationEmailJob.perform_async('submitter_id' => submitter.id)
end end
end end
def current_submitter_order?(submitter)
submitter_items = submitter.submission.template_submitters || submitter.submission.template.submitters
before_items = submitter_items[0...(submitter_items.find_index { |e| e['uuid'] == submitter.uuid })]
before_items.reduce(true) do |acc, item|
acc && submitter.submission.submitters.find { |e| e.uuid == item['uuid'] }&.completed_at?
end
end
end end
+3 -1
View File
@@ -2,6 +2,8 @@
class UserMailerPreview < ActionMailer::Preview class UserMailerPreview < ActionMailer::Preview
def invitation_email def invitation_email
UserMailer.invitation_email(User.last) user = User.first
user.account.locale = I18n.locale
UserMailer.invitation_email(user)
end end
end end
Loaded 100 of 103 files, more files were not shown because too many files have changed in this diff. Show more