Compare commits

..
58 Commits
Author SHA1 Message Date
Pete Matsyburka d9f314898b with style false 2024-10-14 12:28:26 +03:00
Pete Matsyburka 4fc7412cac fix ios 18 heif images 2024-10-14 12:28:26 +03:00
Pete Matsyburka 9adbfc52c2 fix signing date format 2024-10-14 12:28:26 +03:00
Pete Matsyburka 1ad8d9196a optimize phone step 2024-10-14 12:28:26 +03:00
Oleksandr Turchyn e9172392b5 add the phone code selector 2024-10-14 12:28:26 +03:00
Pete Matsyburka d6d94de328 fix submit form step 2024-10-14 12:28:26 +03:00
Alex Turchyn 77261f457c fix selector colors 2024-10-14 12:28:26 +03:00
Pete Matsyburka c258915031 fix z index 2024-10-14 12:28:26 +03:00
Pete Matsyburka c1db8bdd3b rename job 2024-10-14 12:28:26 +03:00
Pete Matsyburka c700484ce8 add reuse signature config 2024-10-14 12:28:26 +03:00
Pete Matsyburka b62bd200ed fix layout 2024-10-14 12:28:26 +03:00
Pete Matsyburka a164fe8dd8 fix sign 2024-10-14 12:28:26 +03:00
Pete Matsyburka bb40e872b3 fix custom message 2024-10-14 12:28:26 +03:00
Pete Matsyburka bab80e9488 fix image preview format 2024-10-14 12:28:26 +03:00
Pete Matsyburka d090d79ebf process images async 2024-10-14 12:28:26 +03:00
Pete Matsyburka 2299ba4e29 fix clear button 2024-10-14 12:28:26 +03:00
Pete Matsyburka d6474f0e6e adjust submitter edit 2024-10-14 12:28:26 +03:00
Oleksandr Turchyn e0a297cf81 add ability to edit submitter details 2024-10-14 12:28:26 +03:00
Pete Matsyburka 40f5fa4217 add recurrent payment 2024-10-14 12:28:26 +03:00
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
123 changed files with 7632 additions and 1895 deletions

No files matched your search

+2 -2
View File
@@ -41,7 +41,7 @@ ENV OPENSSL_CONF=/app/openssl_legacy.cnf
WORKDIR /app
RUN apk add --no-cache sqlite-dev libpq-dev mariadb-dev vips-dev vips-poppler poppler-utils redis vips-heif gcompat ttf-freefont && mkdir /fonts && rm /usr/share/fonts/freefont/FreeSans.otf
RUN echo '@edge https://dl-cdn.alpinelinux.org/alpine/edge/community' >> /etc/apk/repositories && apk add --no-cache sqlite-dev libpq-dev mariadb-dev vips-dev vips-poppler poppler-utils redis libheif@edge vips-heif gcompat ttf-freefont && mkdir /fonts && rm /usr/share/fonts/freefont/FreeSans.otf
RUN echo $'.include = /etc/ssl/openssl.cnf\n\
\n\
@@ -57,7 +57,7 @@ activate = 1' >> /app/openssl_legacy.cnf
COPY ./Gemfile ./Gemfile.lock ./
RUN apk add --no-cache build-base && bundle install && apk del build-base && rm -rf ~/.bundle /usr/local/bundle/cache && ruby -e "puts Dir['/usr/local/bundle/**/{spec,rdoc,resources/shared,resources/collation,resources/locales}']" | xargs rm -rf
RUN apk add --no-cache build-base && bundle install && apk del --no-cache build-base && rm -rf ~/.bundle /usr/local/bundle/cache && ruby -e "puts Dir['/usr/local/bundle/**/{spec,rdoc,resources/shared,resources/collation,resources/locales}']" | xargs rm -rf
COPY ./bin ./bin
COPY ./app ./app
+1 -2
View File
@@ -23,7 +23,6 @@ gem 'image_processing'
gem 'jwt'
gem 'lograge'
gem 'mysql2', require: false
gem 'net-smtp', '0.4.0'
gem 'oj'
gem 'pagy'
gem 'pg', require: false
@@ -40,7 +39,7 @@ gem 'ruby-vips'
gem 'rubyXL'
gem 'shakapacker'
gem 'sidekiq'
gem 'sqlite3', require: false, force_ruby_platform: true
gem 'sqlite3', require: false
gem 'strip_attributes'
gem 'turbo-rails'
gem 'twitter_cldr', require: false
+174 -171
View File
@@ -1,105 +1,101 @@
GEM
remote: https://rubygems.org/
specs:
actioncable (7.1.3.4)
actionpack (= 7.1.3.4)
activesupport (= 7.1.3.4)
actioncable (7.2.1)
actionpack (= 7.2.1)
activesupport (= 7.2.1)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
zeitwerk (~> 2.6)
actionmailbox (7.1.3.4)
actionpack (= 7.1.3.4)
activejob (= 7.1.3.4)
activerecord (= 7.1.3.4)
activestorage (= 7.1.3.4)
activesupport (= 7.1.3.4)
mail (>= 2.7.1)
net-imap
net-pop
net-smtp
actionmailer (7.1.3.4)
actionpack (= 7.1.3.4)
actionview (= 7.1.3.4)
activejob (= 7.1.3.4)
activesupport (= 7.1.3.4)
mail (~> 2.5, >= 2.5.4)
net-imap
net-pop
net-smtp
actionmailbox (7.2.1)
actionpack (= 7.2.1)
activejob (= 7.2.1)
activerecord (= 7.2.1)
activestorage (= 7.2.1)
activesupport (= 7.2.1)
mail (>= 2.8.0)
actionmailer (7.2.1)
actionpack (= 7.2.1)
actionview (= 7.2.1)
activejob (= 7.2.1)
activesupport (= 7.2.1)
mail (>= 2.8.0)
rails-dom-testing (~> 2.2)
actionpack (7.1.3.4)
actionview (= 7.1.3.4)
activesupport (= 7.1.3.4)
actionpack (7.2.1)
actionview (= 7.2.1)
activesupport (= 7.2.1)
nokogiri (>= 1.8.5)
racc
rack (>= 2.2.4)
rack (>= 2.2.4, < 3.2)
rack-session (>= 1.0.1)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6)
actiontext (7.1.3.4)
actionpack (= 7.1.3.4)
activerecord (= 7.1.3.4)
activestorage (= 7.1.3.4)
activesupport (= 7.1.3.4)
useragent (~> 0.16)
actiontext (7.2.1)
actionpack (= 7.2.1)
activerecord (= 7.2.1)
activestorage (= 7.2.1)
activesupport (= 7.2.1)
globalid (>= 0.6.0)
nokogiri (>= 1.8.5)
actionview (7.1.3.4)
activesupport (= 7.1.3.4)
actionview (7.2.1)
activesupport (= 7.2.1)
builder (~> 3.1)
erubi (~> 1.11)
rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6)
activejob (7.1.3.4)
activesupport (= 7.1.3.4)
activejob (7.2.1)
activesupport (= 7.2.1)
globalid (>= 0.3.6)
activemodel (7.1.3.4)
activesupport (= 7.1.3.4)
activerecord (7.1.3.4)
activemodel (= 7.1.3.4)
activesupport (= 7.1.3.4)
activemodel (7.2.1)
activesupport (= 7.2.1)
activerecord (7.2.1)
activemodel (= 7.2.1)
activesupport (= 7.2.1)
timeout (>= 0.4.0)
activestorage (7.1.3.4)
actionpack (= 7.1.3.4)
activejob (= 7.1.3.4)
activerecord (= 7.1.3.4)
activesupport (= 7.1.3.4)
activestorage (7.2.1)
actionpack (= 7.2.1)
activejob (= 7.2.1)
activerecord (= 7.2.1)
activesupport (= 7.2.1)
marcel (~> 1.0)
activesupport (7.1.3.4)
activesupport (7.2.1)
base64
bigdecimal
concurrent-ruby (~> 1.0, >= 1.0.2)
concurrent-ruby (~> 1.0, >= 1.3.1)
connection_pool (>= 2.2.5)
drb
i18n (>= 1.6, < 2)
logger (>= 1.4.2)
minitest (>= 5.1)
mutex_m
tzinfo (~> 2.0)
addressable (2.8.6)
public_suffix (>= 2.0.2, < 6.0)
securerandom (>= 0.3)
tzinfo (~> 2.0, >= 2.0.5)
addressable (2.8.7)
public_suffix (>= 2.0.2, < 7.0)
annotate (3.2.0)
activerecord (>= 3.2, < 8.0)
rake (>= 10.4, < 14.0)
arabic-letter-connector (0.1.1)
ast (2.4.2)
aws-eventstream (1.3.0)
aws-partitions (1.913.0)
aws-sdk-core (3.191.6)
aws-partitions (1.985.0)
aws-sdk-core (3.209.1)
aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.651.0)
aws-sigv4 (~> 1.8)
aws-sigv4 (~> 1.9)
jmespath (~> 1, >= 1.6.1)
aws-sdk-kms (1.79.0)
aws-sdk-core (~> 3, >= 3.191.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.146.1)
aws-sdk-core (~> 3, >= 3.191.0)
aws-sdk-kms (1.94.0)
aws-sdk-core (~> 3, >= 3.207.0)
aws-sigv4 (~> 1.5)
aws-sdk-s3 (1.167.0)
aws-sdk-core (~> 3, >= 3.207.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.8)
aws-sdk-secretsmanager (1.91.0)
aws-sdk-core (~> 3, >= 3.191.0)
aws-sigv4 (~> 1.1)
aws-sigv4 (1.8.0)
aws-sigv4 (~> 1.5)
aws-sdk-secretsmanager (1.108.0)
aws-sdk-core (~> 3, >= 3.207.0)
aws-sigv4 (~> 1.5)
aws-sigv4 (1.10.0)
aws-eventstream (~> 1, >= 1.0.2)
azure-storage-blob (2.0.3)
azure-storage-common (~> 2.0)
@@ -120,14 +116,14 @@ GEM
smart_properties
bigdecimal (3.1.8)
bindex (0.8.1)
bootsnap (1.18.3)
bootsnap (1.18.4)
msgpack (~> 1.2)
builder (3.3.0)
bullet (7.1.6)
bullet (7.2.0)
activesupport (>= 3.0.0)
uniform_notifier (~> 1.11)
camertron-eprun (1.1.1)
cancancan (3.5.0)
cancancan (3.6.1)
capybara (3.40.0)
addressable
matrix
@@ -137,7 +133,8 @@ GEM
rack-test (>= 0.6.3)
regexp_parser (>= 1.5, < 3.0)
xpath (~> 3.2)
childprocess (5.0.0)
childprocess (5.1.0)
logger (~> 1.5)
chunky_png (1.4.0)
cldr-plurals-runtime-rb (1.1.0)
cmdparse (3.0.7)
@@ -148,12 +145,12 @@ GEM
bigdecimal
rexml
crass (1.0.6)
css_parser (1.17.1)
css_parser (1.19.0)
addressable
csv (3.3.0)
cuprite (0.15)
cuprite (0.15.1)
capybara (~> 3.0)
ferrum (~> 0.14.0)
ferrum (~> 0.15.0)
date (3.3.4)
debug (1.9.2)
irb (~> 1.10)
@@ -173,26 +170,26 @@ GEM
diff-lcs (1.5.1)
digest-crc (0.6.5)
rake (>= 12.0.0, < 14.0.0)
docile (1.4.0)
dotenv (3.1.0)
docile (1.4.1)
dotenv (3.1.4)
drb (2.2.1)
email_typo (0.2.3)
erb_lint (0.5.0)
erb_lint (0.6.0)
activesupport
better_html (>= 2.0.1)
parser (>= 2.7.1.4)
rainbow
rubocop
rubocop (>= 1)
smart_properties
erubi (1.13.0)
factory_bot (6.4.6)
factory_bot (6.5.0)
activesupport (>= 5.0.0)
factory_bot_rails (6.4.3)
factory_bot (~> 6.4)
railties (>= 5.0.0)
faker (3.3.1)
faker (3.4.2)
i18n (>= 1.8.11, < 2)
faraday (1.10.3)
faraday (1.10.4)
faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0)
faraday-excon (~> 1.1)
@@ -212,58 +209,60 @@ GEM
faraday-httpclient (1.0.1)
faraday-multipart (1.0.4)
multipart-post (~> 2)
faraday-net_http (1.0.1)
faraday-net_http (1.0.2)
faraday-net_http_persistent (1.2.0)
faraday-patron (1.0.0)
faraday-rack (1.0.0)
faraday-retry (1.0.3)
faraday_middleware (1.2.0)
faraday_middleware (1.2.1)
faraday (~> 1.0)
ferrum (0.14)
ferrum (0.15)
addressable (~> 2.5)
concurrent-ruby (~> 1.1)
webrick (~> 1.7)
websocket-driver (>= 0.6, < 0.8)
ffi (1.16.3)
websocket-driver (~> 0.7)
ffi (1.17.0)
ffi (1.17.0-aarch64-linux-gnu)
ffi (1.17.0-x86_64-linux-gnu)
geom2d (0.4.1)
globalid (1.2.1)
activesupport (>= 6.1)
google-apis-core (0.14.1)
google-apis-core (0.15.1)
addressable (~> 2.5, >= 2.5.1)
googleauth (~> 1.9)
httpclient (>= 2.8.1, < 3.a)
httpclient (>= 2.8.3, < 3.a)
mini_mime (~> 1.0)
mutex_m
representable (~> 3.0)
retriable (>= 2.0, < 4.a)
rexml
google-apis-iamcredentials_v1 (0.20.0)
google-apis-core (>= 0.14.0, < 2.a)
google-apis-storage_v1 (0.37.0)
google-apis-core (>= 0.14.0, < 2.a)
google-cloud-core (1.7.0)
google-apis-iamcredentials_v1 (0.21.0)
google-apis-core (>= 0.15.0, < 2.a)
google-apis-storage_v1 (0.46.0)
google-apis-core (>= 0.15.0, < 2.a)
google-cloud-core (1.7.1)
google-cloud-env (>= 1.0, < 3.a)
google-cloud-errors (~> 1.0)
google-cloud-env (2.1.1)
google-cloud-env (2.2.1)
faraday (>= 1.0, < 3.a)
google-cloud-errors (1.4.0)
google-cloud-storage (1.49.0)
google-cloud-storage (1.52.0)
addressable (~> 2.8)
digest-crc (~> 0.4)
google-apis-core (~> 0.13)
google-apis-iamcredentials_v1 (~> 0.18)
google-apis-storage_v1 (~> 0.33)
google-apis-storage_v1 (~> 0.38)
google-cloud-core (~> 1.6)
googleauth (~> 1.9)
mini_mime (~> 1.0)
googleauth (1.11.0)
googleauth (1.11.1)
faraday (>= 1.0, < 3.a)
google-cloud-env (~> 2.1)
jwt (>= 1.4, < 3.0)
multi_json (~> 1.11)
os (>= 0.9, < 2.0)
signet (>= 0.16, < 2.a)
hashdiff (1.1.0)
hexapdf (0.42.0)
hashdiff (1.1.1)
hexapdf (0.47.0)
cmdparse (~> 3.0, >= 3.0.3)
geom2d (~> 0.4, >= 0.4.1)
openssl (>= 2.2.1)
@@ -271,28 +270,29 @@ GEM
httpclient (2.8.3)
i18n (1.14.6)
concurrent-ruby (~> 1.0)
image_processing (1.12.2)
image_processing (1.13.0)
mini_magick (>= 4.9.5, < 5)
ruby-vips (>= 2.0.17, < 3)
io-console (0.7.2)
irb (1.14.0)
irb (1.14.1)
rdoc (>= 4.0.0)
reline (>= 0.4.2)
jmespath (1.6.2)
json (2.7.2)
jwt (2.8.1)
jwt (2.9.3)
base64
language_server-protocol (3.17.0.3)
launchy (3.0.0)
launchy (3.0.1)
addressable (~> 2.8)
childprocess (~> 5.0)
letter_opener (1.10.0)
launchy (>= 2.2, < 4)
letter_opener_web (2.0.0)
actionmailer (>= 5.2)
letter_opener (~> 1.7)
railties (>= 5.2)
letter_opener_web (3.0.0)
actionmailer (>= 6.1)
letter_opener (~> 1.9)
railties (>= 6.1)
rexml
logger (1.6.1)
lograge (0.14.0)
actionpack (>= 4)
activesupport (>= 4)
@@ -309,25 +309,25 @@ GEM
marcel (1.0.4)
matrix (0.4.2)
method_source (1.1.0)
mini_magick (4.12.0)
mini_magick (4.13.2)
mini_mime (1.1.5)
mini_portile2 (2.8.7)
minitest (5.25.1)
msgpack (1.7.2)
msgpack (1.7.3)
multi_json (1.15.0)
multipart-post (2.4.0)
multipart-post (2.4.1)
mutex_m (0.2.0)
mysql2 (0.5.6)
net-http-persistent (4.0.2)
net-http-persistent (4.0.4)
connection_pool (~> 2.2)
net-imap (0.4.14)
net-imap (0.4.16)
date
net-protocol
net-pop (0.1.2)
net-protocol
net-protocol (0.2.2)
timeout
net-smtp (0.4.0)
net-smtp (0.5.0)
net-protocol
nio4r (2.7.3)
nokogiri (1.16.7)
@@ -337,21 +337,23 @@ GEM
racc (~> 1.4)
nokogiri (1.16.7-x86_64-linux)
racc (~> 1.4)
oj (3.16.3)
oj (3.16.6)
bigdecimal (>= 3.0)
ostruct (>= 0.2)
openssl (3.2.0)
orm_adapter (0.5.0)
os (1.1.4)
ostruct (0.6.0)
package_json (0.1.0)
pagy (8.1.2)
parallel (1.25.1)
parser (3.3.4.0)
pagy (9.1.0)
parallel (1.26.3)
parser (3.3.5.0)
ast (~> 2.4.1)
racc
pg (1.5.6)
premailer (1.23.0)
pg (1.5.8)
premailer (1.27.0)
addressable
css_parser (>= 1.12.0)
css_parser (>= 1.19.0)
htmlentities (>= 4.0.0)
premailer-rails (1.12.0)
actionmailer (>= 3)
@@ -362,11 +364,11 @@ GEM
pry (0.14.2)
coderay (~> 1.1)
method_source (~> 1.0)
pry-rails (0.3.9)
pry (>= 0.10.4)
pry-rails (0.3.11)
pry (>= 0.13.0)
psych (5.1.2)
stringio
public_suffix (5.0.5)
public_suffix (6.0.1)
puma (6.4.3)
nio4r (~> 2.0)
racc (1.8.1)
@@ -380,20 +382,20 @@ GEM
rackup (2.1.0)
rack (>= 3)
webrick (~> 1.8)
rails (7.1.3.4)
actioncable (= 7.1.3.4)
actionmailbox (= 7.1.3.4)
actionmailer (= 7.1.3.4)
actionpack (= 7.1.3.4)
actiontext (= 7.1.3.4)
actionview (= 7.1.3.4)
activejob (= 7.1.3.4)
activemodel (= 7.1.3.4)
activerecord (= 7.1.3.4)
activestorage (= 7.1.3.4)
activesupport (= 7.1.3.4)
rails (7.2.1)
actioncable (= 7.2.1)
actionmailbox (= 7.2.1)
actionmailer (= 7.2.1)
actionpack (= 7.2.1)
actiontext (= 7.2.1)
actionview (= 7.2.1)
activejob (= 7.2.1)
activemodel (= 7.2.1)
activerecord (= 7.2.1)
activestorage (= 7.2.1)
activesupport (= 7.2.1)
bundler (>= 1.15.0)
railties (= 7.1.3.4)
railties (= 7.2.1)
rails-dom-testing (2.2.0)
activesupport (>= 5.0.0)
minitest
@@ -408,10 +410,10 @@ GEM
actionview (> 3.1)
activesupport (> 3.1)
railties (> 3.1)
railties (7.1.3.4)
actionpack (= 7.1.3.4)
activesupport (= 7.1.3.4)
irb
railties (7.2.1)
actionpack (= 7.2.1)
activesupport (= 7.2.1)
irb (~> 1.13)
rackup (>= 1.0.0)
rake (>= 12.2)
thor (~> 1.0, >= 1.2.2)
@@ -429,79 +431,80 @@ GEM
declarative (< 0.1.0)
trailblazer-option (>= 0.1.1, < 0.2.0)
uber (< 0.2.0)
request_store (1.6.0)
request_store (1.7.0)
rack (>= 1.4)
responders (3.1.1)
actionpack (>= 5.2)
railties (>= 5.2)
retriable (3.1.2)
rexml (3.3.6)
strscan
rexml (3.3.8)
rotp (6.3.0)
rqrcode (2.2.0)
chunky_png (~> 1.0)
rqrcode_core (~> 1.0)
rqrcode_core (1.2.0)
rspec-core (3.13.0)
rspec-core (3.13.1)
rspec-support (~> 3.13.0)
rspec-expectations (3.13.0)
rspec-expectations (3.13.3)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0)
rspec-mocks (3.13.0)
rspec-mocks (3.13.2)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0)
rspec-rails (6.1.2)
actionpack (>= 6.1)
activesupport (>= 6.1)
railties (>= 6.1)
rspec-rails (7.0.1)
actionpack (>= 7.0)
activesupport (>= 7.0)
railties (>= 7.0)
rspec-core (~> 3.13)
rspec-expectations (~> 3.13)
rspec-mocks (~> 3.13)
rspec-support (~> 3.13)
rspec-support (3.13.1)
rubocop (1.65.0)
rubocop (1.66.1)
json (~> 2.3)
language_server-protocol (>= 3.17.0)
parallel (~> 1.10)
parser (>= 3.3.0.2)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 2.4, < 3.0)
rexml (>= 3.2.5, < 4.0)
rubocop-ast (>= 1.31.1, < 2.0)
rubocop-ast (>= 1.32.2, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 3.0)
rubocop-ast (1.31.3)
rubocop-ast (1.32.3)
parser (>= 3.3.1.0)
rubocop-performance (1.21.0)
rubocop-performance (1.22.1)
rubocop (>= 1.48.1, < 2.0)
rubocop-ast (>= 1.31.1, < 2.0)
rubocop-rails (2.24.1)
rubocop-rails (2.26.2)
activesupport (>= 4.2.0)
rack (>= 1.1)
rubocop (>= 1.33.0, < 2.0)
rubocop (>= 1.52.0, < 2.0)
rubocop-ast (>= 1.31.1, < 2.0)
rubocop-rspec (3.0.3)
rubocop-rspec (3.1.0)
rubocop (~> 1.61)
ruby-progressbar (1.13.0)
ruby-vips (2.2.1)
ruby-vips (2.2.2)
ffi (~> 1.12)
logger
ruby2_keywords (0.0.5)
rubyXL (3.4.27)
nokogiri (>= 1.10.8)
rubyzip (>= 1.3.0)
rubyzip (2.3.2)
securerandom (0.3.1)
semantic_range (3.0.0)
shakapacker (8.0.0)
shakapacker (8.0.2)
activesupport (>= 5.2)
package_json
rack-proxy (>= 0.6.1)
railties (>= 5.2)
semantic_range (>= 2.3.0)
sidekiq (7.2.4)
sidekiq (7.3.2)
concurrent-ruby (< 2)
connection_pool (>= 2.3.0)
logger
rack (>= 2.2.4)
redis-client (>= 0.19.0)
redis-client (>= 0.22.2)
signet (0.19.0)
addressable (~> 2.8)
faraday (>= 0.17.5, < 3.a)
@@ -511,33 +514,34 @@ GEM
docile (~> 1.1)
simplecov-html (~> 0.11)
simplecov_json_formatter (~> 0.1)
simplecov-html (0.12.3)
simplecov-html (0.13.1)
simplecov_json_formatter (0.1.4)
smart_properties (1.17.0)
sqlite3 (1.7.3)
sqlite3 (2.1.0)
mini_portile2 (~> 2.8.0)
sqlite3 (2.1.0-aarch64-linux-gnu)
sqlite3 (2.1.0-x86_64-linux-gnu)
stringio (3.1.1)
strip_attributes (1.13.0)
activemodel (>= 3.0, < 8.0)
strscan (3.1.0)
thor (1.3.2)
timeout (0.4.1)
trailblazer-option (0.1.2)
turbo-rails (2.0.5)
turbo-rails (2.0.10)
actionpack (>= 6.0.0)
activejob (>= 6.0.0)
railties (>= 6.0.0)
twitter_cldr (6.12.0)
twitter_cldr (6.12.1)
camertron-eprun
cldr-plurals-runtime-rb (~> 1.1)
tzinfo
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
tzinfo-data (1.2024.1)
tzinfo-data (1.2024.2)
tzinfo (>= 1.0.0)
uber (0.1.0)
unicode-display_width (2.5.0)
unicode-display_width (2.6.0)
uniform_notifier (1.16.0)
useragent (0.16.10)
warden (1.2.9)
rack (>= 2.0.9)
web-console (4.2.1)
@@ -545,11 +549,11 @@ GEM
activemodel (>= 6.0.0)
bindex (>= 0.4.0)
railties (>= 6.0.0)
webmock (3.23.0)
webmock (3.24.0)
addressable (>= 2.8.0)
crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0)
webrick (1.8.1)
webrick (1.8.2)
websocket-driver (0.7.6)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5)
@@ -592,7 +596,6 @@ DEPENDENCIES
letter_opener_web
lograge
mysql2
net-smtp (= 0.4.0)
oj
pagy
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
- Users management
- Mobile-optimized
- Signing available in 13 languages
- 6 UI languages with signing available in 13 languages
- API and Webhooks for integrations
- Easy to deploy in minutes
+20 -16
View File
@@ -4,11 +4,11 @@ class AccountsController < ApplicationController
LOCALE_OPTIONS = {
'en-US' => 'English (United States)',
'en-GB' => 'English (United Kingdom)',
'fr-FR' => 'French (France)',
'es-ES' => 'Spanish (Spain)',
'pt-PT' => 'Portuguese (Portugal)',
'de-DE' => 'German (Germany)',
'it-IT' => 'Italian (Italy)'
'fr-FR' => 'Français',
'es-ES' => 'Español',
'pt-PT' => 'Português',
'de-DE' => 'Deutsch',
'it-IT' => 'Italiano'
}.freeze
before_action :load_account
@@ -19,21 +19,25 @@ class AccountsController < ApplicationController
def update
current_account.update!(account_params)
@encrypted_config = EncryptedConfig.find_or_initialize_by(account: current_account,
key: EncryptedConfig::APP_URL_KEY)
@encrypted_config.assign_attributes(app_url_params)
unless Docuseal.multitenant?
@encrypted_config = EncryptedConfig.find_or_initialize_by(account: current_account,
key: EncryptedConfig::APP_URL_KEY)
@encrypted_config.assign_attributes(app_url_params)
unless URI.parse(@encrypted_config.value.to_s).class.in?([URI::HTTP, URI::HTTPS])
@encrypted_config.errors.add(:value, I18n.t('should_be_a_valid_url'))
unless URI.parse(@encrypted_config.value.to_s).class.in?([URI::HTTP, URI::HTTPS])
@encrypted_config.errors.add(:value, I18n.t('should_be_a_valid_url'))
return render :show, status: :unprocessable_entity
return render :show, status: :unprocessable_entity
end
@encrypted_config.save!
Docuseal.refresh_default_url_options!
end
@encrypted_config.save!
Docuseal.refresh_default_url_options!
redirect_to settings_account_path, notice: I18n.t('account_information_has_been_updated')
with_locale do
redirect_to settings_account_path, notice: I18n.t('account_information_has_been_updated')
end
rescue ActiveRecord::RecordInvalid
render :show, status: :unprocessable_entity
end
@@ -38,12 +38,12 @@ module Api
private
def authorization_check!(blob)
is_authorized =
blob.attachments.all? do |a|
a.name.in?(%w[logo preview_images]) ||
(current_user && a.record.account.id == current_user.account_id) ||
!a.record.account.account_configs.find_or_initialize_by(key: AccountConfig::DOWNLOAD_LINKS_AUTH_KEY).value
end
attachment = blob.attachments.take
is_authorized = attachment.name.in?(%w[logo preview_images]) ||
(current_user && attachment.record.account.id == current_user.account_id) ||
!attachment.record.account.account_configs
.find_or_initialize_by(key: AccountConfig::DOWNLOAD_LINKS_AUTH_KEY).value
return if is_authorized
+12
View File
@@ -8,6 +8,7 @@ class ApplicationController < ActionController::Base
check_authorization unless: :devise_controller?
around_action :with_locale
before_action :sign_in_for_demo, if: -> { Docuseal.demo? }
before_action :maybe_redirect_to_setup, unless: :signed_in?
before_action :authenticate_user!, unless: :devise_controller?
@@ -51,7 +52,18 @@ class ApplicationController < ActionController::Base
private
def with_locale(&)
return yield unless current_account
locale = params[:lang].presence if Rails.env.development?
locale ||= current_account.locale
I18n.with_locale(locale, &)
end
def with_browser_locale(&)
return yield if I18n.locale != :'en-US' && I18n.locale != :en
locale = params[:lang].presence
locale ||= request.env['HTTP_ACCEPT_LANGUAGE'].to_s[BROWSER_LOCALE_REGEXP].to_s
+14 -8
View File
@@ -21,9 +21,9 @@ class EsignSettingsController < ApplicationController
default_pkcs = GenerateCertificate.load_pkcs(cert_data) if cert_data['cert'].present?
custom_pkcs_list = (cert_data['custom'] || []).map do |e|
{ 'pkcs' => OpenSSL::PKCS12.new(Base64.urlsafe_decode64(e['data']), e['password'].to_s),
'name' => e['name'],
'status' => e['status'] }
pkcs = e['data'].present? ? OpenSSL::PKCS12.new(Base64.urlsafe_decode64(e['data']), e['password'].to_s) : nil
{ 'pkcs' => pkcs, 'name' => e['name'], 'status' => e['status'] }
end
@pkcs_list = [
@@ -56,7 +56,7 @@ class EsignSettingsController < ApplicationController
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
Rollbar.error(e) if defined?(Rollbar)
@@ -66,10 +66,16 @@ class EsignSettingsController < ApplicationController
end
def update
@encrypted_config.value['custom'].each { |e| e['status'] = 'validate' }
@encrypted_config.value['custom'].to_a.each { |e| e['status'] = 'validate' }
custom_cert_data = @encrypted_config.value['custom'].find { |e| e['name'] == params[:name] }
custom_cert_data['status'] = 'default' if custom_cert_data
custom_cert_data = @encrypted_config.value['custom'].to_a.find { |e| e['name'] == params[:name] }
if custom_cert_data
custom_cert_data['status'] = 'default'
elsif params[:name] == Docuseal::AATL_CERT_NAME
@encrypted_config.value['custom'] ||= []
@encrypted_config.value['custom'] << { 'name' => params[:name], 'status' => 'default' }
end
@encrypted_config.save!
@@ -81,7 +87,7 @@ class EsignSettingsController < ApplicationController
@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
private
@@ -12,7 +12,8 @@ class PreviewDocumentPageController < ActionController::API
return head :not_found unless attachment
preview_image = attachment.preview_images.joins(:blob).find_by(blob: { filename: "#{params[:id]}#{FORMAT}" })
preview_image = attachment.preview_images.joins(:blob)
.find_by(blob: { filename: ["#{params[:id]}.png", "#{params[:id]}.jpg"] })
return redirect_to preview_image.url, allow_other_host: true if preview_image
+1 -1
View File
@@ -56,7 +56,7 @@ class SetupController < ApplicationController
def account_params
return {} unless params[:account]
params.require(:account).permit(:name, :timezone)
params.require(:account).permit(:name, :timezone, :locale)
end
def encrypted_config_params
@@ -9,6 +9,13 @@ class SubmissionsController < ApplicationController
def show
@submission = Submissions.preload_with_pages(@submission)
unless @submission.submitters.all?(&:completed_at?)
ActiveRecord::Associations::Preloader.new(
records: [@submission],
associations: [submitters: :start_form_submission_events]
).call
end
render :show, layout: 'plain'
end
@@ -15,6 +15,6 @@ class SubmissionsDashboardController < ApplicationController
@submissions = @submissions.pending if params[:status] == 'pending'
@submissions = @submissions.completed if params[:status] == 'completed'
@pagy, @submissions = pagy(@submissions.preload(:submitters).order(id: :desc))
@pagy, @submissions = pagy(@submissions.preload(submitters: :start_form_submission_events).order(id: :desc))
end
end
+7 -3
View File
@@ -12,16 +12,20 @@ class SubmitFormController < ApplicationController
def show
@submitter = Submitter.find_by!(slug: params[:slug])
submission = @submitter.submission
return redirect_to submit_form_completed_path(@submitter.slug) if @submitter.completed_at?
return render :archived if @submitter.submission.template.archived_at? || @submitter.submission.archived_at?
return render :expired if @submitter.submission.expired?
return render :archived if submission.template.archived_at? || submission.archived_at?
return render :expired if submission.expired?
return render :declined if @submitter.declined_at?
return render :awaiting if submission.template.preferences['submitters_order'] == 'preserved' &&
!Submitters.current_submitter_order?(@submitter)
Submitters.preload_with_pages(@submitter)
Submitters::MaybeUpdateDefaultValues.call(@submitter, current_user)
@attachments_index = ActiveStorage::Attachment.where(record: @submitter.submission.submitters, name: :attachments)
@attachments_index = ActiveStorage::Attachment.where(record: submission.submitters, name: :attachments)
.preload(:blob).index_by(&:uuid)
@form_configs = Submitters::FormConfigs.call(@submitter, CONFIG_KEYS)
+80
View File
@@ -0,0 +1,80 @@
# frozen_string_literal: true
class SubmittersController < ApplicationController
load_and_authorize_resource :submitter, only: %i[edit update]
def edit
@submitter_email_message =
if @submitter.preferences['email_message_uuid'].present?
@submitter.account.email_messages.find_by(uuid: @submitter.preferences['email_message_uuid'])
end
end
def update
submission = @submitter.submission
if @submitter.submission_events.exists?(event_type: 'start_form') || submission.archived_at? || submission.expired?
return redirect_back fallback_location: submission_path(submission), alert: I18n.t('submitter_cannot_be_updated')
end
if submitter_params.values.all?(&:blank?)
return redirect_back fallback_location: submission_path(submission),
alert: I18n.t('at_least_one_field_must_be_filled')
end
if params[:is_custom_message] != '1'
params.delete(:subject)
params.delete(:body)
end
submitter_preferences = Submitters.normalize_preferences(@submitter.account, current_user, params)
if submitter_preferences.key?('email_message_uuid')
@submitter.preferences['email_message_uuid'] = submitter_preferences['email_message_uuid']
end
assign_submitter_attrs(@submitter, submitter_params)
if @submitter.save
maybe_resend_email_sms(@submitter, params)
redirect_back fallback_location: submission_path(submission), notice: I18n.t('changes_have_been_saved')
else
redirect_back fallback_location: submission_path(submission), alert: I18n.t('unable_to_save')
end
end
private
def maybe_resend_email_sms(submitter, params)
if params[:send_email] == '1' && submitter.email.present?
is_sent_recently = Docuseal.multitenant? &&
EmailEvent.exists?(email: submitter.email,
tag: 'submitter_invitation',
emailable: submitter,
event_type: 'send',
created_at: 4.hours.ago..Time.current)
SendSubmitterInvitationEmailJob.perform_async('submitter_id' => submitter.id) unless is_sent_recently
end
return if submitter.phone.blank?
return unless params[:send_sms] == '1'
SendSubmitterInvitationSmsJob.perform_async('submitter_id' => submitter.id)
end
def assign_submitter_attrs(submitter, attrs)
submitter.phone = attrs[:phone].to_s.gsub(/[^0-9+]/, '') if attrs.key?(:phone)
submitter.email = Submissions.normalize_email(attrs[:email]) if attrs.key?(:email)
submitter.name = attrs[:name] if attrs.key?(:name)
submitter
end
def submitter_params
params.require(:submitter).permit(:email, :name, :phone).transform_values(&:strip)
end
end
@@ -7,7 +7,7 @@ class TemplateFoldersController < ApplicationController
@templates = @template_folder.templates.active.preload(:author).order(id: :desc)
@templates = Templates.search(@templates, params[:q])
@pagy, @templates = pagy(@templates, items: 12)
@pagy, @templates = pagy(@templates, limit: 12)
end
def edit; end
@@ -7,6 +7,6 @@ class TemplatesArchivedController < ApplicationController
@templates = @templates.where.not(archived_at: nil).preload(:author, :folder).order(id: :desc)
@templates = Templates.search(@templates, params[:q])
@pagy, @templates = pagy(@templates, items: 12)
@pagy, @templates = pagy(@templates, limit: 12)
end
end
+3 -2
View File
@@ -15,7 +15,7 @@ class TemplatesController < ApplicationController
submissions = submissions.pending if params[:status] == 'pending'
submissions = submissions.completed if params[:status] == 'completed'
@pagy, @submissions = pagy(submissions.preload(:submitters).order(id: :desc))
@pagy, @submissions = pagy(submissions.preload(submitters: :start_form_submission_events).order(id: :desc))
rescue ActiveRecord::RecordNotFound
redirect_to root_path
end
@@ -116,7 +116,8 @@ class TemplatesController < ApplicationController
end
def authorized_clone_account_id?(account_id)
true_user.account_id.to_s == account_id.to_s || true_user.account.linked_accounts.exists?(id: account_id)
true_user.account_id.to_s == account_id.to_s ||
true_user.account.linked_accounts.accessible_by(current_ability).exists?(id: account_id)
end
def maybe_redirect_to_template(template)
@@ -23,14 +23,14 @@ class TemplatesDashboardController < ApplicationController
@template_folders = @template_folders.reject { |e| e.name == TemplateFolder::DEFAULT_NAME }
@templates = filter_templates(@templates)
items =
limit =
if @template_folders.size < 4
TEMPLATES_PER_PAGE
else
(@template_folders.size < 7 ? 9 : 6)
end
@pagy, @templates = pagy(@templates, items:)
@pagy, @templates = pagy(@templates, limit:)
end
end
@@ -24,6 +24,7 @@ class TemplatesPreferencesController < ApplicationController
documents_copy_email_enabled documents_copy_email_attach_audit
completed_notification_email_attach_documents
completed_redirect_url
submitters_order
completed_notification_email_subject completed_notification_email_body
completed_notification_email_enabled completed_notification_email_attach_audit] +
[completed_message: %i[title body]]
@@ -9,7 +9,7 @@ class UserSignaturesController < ApplicationController
def update
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,
filename: file.original_filename,
@@ -24,7 +24,7 @@ class UserSignaturesController < ApplicationController
if @user_config.update(value: attachment.uuid)
redirect_to settings_profile_index_path, notice: I18n.t('signature_has_been_saved')
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
+2 -1
View File
@@ -150,7 +150,8 @@ safeRegisterElement('import-list', class extends HTMLElement {
this.app = createApp(ImportList, {
template: JSON.parse(this.dataset.template),
multitenant: this.dataset.multitenant === 'true',
authenticityToken: document.querySelector('meta[name="csrf-token"]')?.content
authenticityToken: document.querySelector('meta[name="csrf-token"]')?.content,
i18n: JSON.parse(this.dataset.i18n || '{}')
})
this.app.mount(this.appElem)
-8
View File
@@ -51,10 +51,6 @@ button[disabled] .enabled {
@apply input input-bordered bg-white;
}
.base-input-slim {
@apply input input-bordered bg-white h-10;
}
.base-textarea {
@apply textarea textarea-bordered bg-white rounded-3xl;
}
@@ -134,7 +130,3 @@ button[disabled] .enabled {
outline-offset: 3px;
outline-color: hsl(var(--bc) / 0.2);
}
select:required:invalid {
color: gray !important;
}
+2
View File
@@ -21,12 +21,14 @@ safeRegisterElement('submission-form', class extends HTMLElement {
isDemo: this.dataset.isDemo === 'true',
attribution: this.dataset.attribution !== 'false',
scrollPadding: this.dataset.scrollPadding || '-80px',
language: this.dataset.language,
dryRun: this.dataset.dryRun === 'true',
expand: ['true', 'false'].includes(this.dataset.expand) ? this.dataset.expand === 'true' : null,
withSignatureId: this.dataset.withSignatureId === 'true',
requireSigningReason: this.dataset.requireSigningReason === 'true',
withConfetti: this.dataset.withConfetti !== 'false',
withDisclosure: this.dataset.withDisclosure === 'true',
reuseSignature: this.dataset.reuseSignature !== 'false',
withTypedSignature: this.dataset.withTypedSignature !== 'false',
authenticityToken: document.querySelector('meta[name="csrf-token"]')?.content,
rememberSignature: this.dataset.rememberSignature === 'true',
-4
View File
@@ -31,10 +31,6 @@ button[disabled] .enabled {
display: none;
}
select:required:invalid {
@apply text-gray-300;
}
.input-bordered {
@apply border-base-content/20;
}
+2 -1
View File
@@ -1,5 +1,6 @@
<template>
<div
id="form_completed"
class="mx-auto max-w-md flex flex-col"
dir="auto"
>
@@ -222,7 +223,7 @@ export default {
this.downloadUrls(urls)
}
} else {
alert('Failed to download files')
alert(this.t('failed_to_download_files'))
}
})
},
+13 -12
View File
@@ -115,7 +115,7 @@
v-model="values[currentField.uuid]"
:show-field-names="showFieldNames"
:field="currentField"
@submit="submitStep"
@submit="!isSubmitting && submitStep()"
@focus="scrollIntoField(currentField)"
/>
<div v-else-if="currentField.type === 'select'">
@@ -152,6 +152,7 @@
dir="auto"
:required="currentField.required"
class="select base-input !text-2xl w-full text-center font-normal"
:class="{ 'text-gray-300': !values[currentField.uuid] }"
:name="`values[${currentField.uuid}]`"
@change="values[currentField.uuid] = $event.target.value"
@focus="scrollIntoField(currentField)"
@@ -159,6 +160,7 @@
<option
value=""
:selected="!values[currentField.uuid]"
class="text-gray-300"
>
{{ t('select_your_option') }}
</option>
@@ -167,6 +169,7 @@
:key="option.uuid"
:selected="values[currentField.uuid] == option.value"
:value="option.value"
class="text-base-content"
>
{{ option.value }}
</option>
@@ -392,7 +395,7 @@
:default-value="submitter.phone"
:submitter-slug="submitterSlug"
@focus="scrollIntoField(currentField)"
@submit="submitStep"
@submit="!isSubmitting && submitStep()"
/>
<PaymentStep
v-else-if="currentField.type === 'payment'"
@@ -404,7 +407,7 @@
:values="values"
@attached="attachments.push($event)"
@focus="scrollIntoField(currentField)"
@submit="submitStep"
@submit="!isSubmitting && submitStep()"
/>
</div>
<div
@@ -897,7 +900,7 @@ export default {
mounted () {
this.submittedValues = JSON.parse(JSON.stringify(this.values))
screen?.orientation.addEventListener('change', this.onOrientationChange)
screen?.orientation?.addEventListener('change', this.onOrientationChange)
this.fields.forEach((field) => {
if (field.default_value && !field.readonly) {
@@ -1142,13 +1145,15 @@ export default {
async submitStep () {
this.isSubmitting = true
const submitStep = this.currentStep
const stepPromise = ['signature', 'phone', 'initials', 'payment'].includes(this.currentField.type)
? this.$refs.currentStep.submit
: () => Promise.resolve({})
stepPromise().then(async () => {
const emptyRequiredField = this.stepFields.find((fields, index) => {
if (index >= this.currentStep) {
if (index >= submitStep) {
return false
}
@@ -1158,7 +1163,7 @@ export default {
})
const formData = new FormData(this.$refs.form)
const isLastStep = this.currentStep === this.stepFields.length - 1
const isLastStep = submitStep === this.stepFields.length - 1
if (isLastStep && !emptyRequiredField && !this.inviteSubmitters.length) {
formData.append('completed', 'true')
@@ -1183,11 +1188,7 @@ export default {
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[submitStep + 1]
if (nextStep) {
if (this.alwaysMinimize) {
@@ -1211,7 +1212,7 @@ export default {
})
}).catch(error => {
if (error?.message === 'Image too small') {
alert('Signature is too small - please redraw.')
alert(this.t('signature_is_too_small_please_redraw'))
} else {
console.log(error)
}
+1 -1
View File
@@ -216,7 +216,7 @@ const it = {
pay: 'Pagamento',
file: 'File',
select: 'Seleziona',
checkbox: 'Casella di controllo',
checkbox: 'Checkbox',
multiple: 'Multiplo',
radio: 'Radio',
cells: 'Celle',
File diff suppressed because it is too large. Load diff
+106 -21
View File
@@ -61,30 +61,55 @@
</a>
</div>
</div>
<input
<div
v-show="!isCodeSent"
:id="field.uuid"
ref="phone"
:value="modelValue || defaultValue"
:readonly="!!defaultValue"
class="base-input !text-2xl w-full"
autocomplete="tel"
pattern="^\+[0-9\s\-]+$"
type="tel"
inputmode="tel"
:required="field.required"
placeholder="+1 234 567-8900"
:name="`values[${field.uuid}]`"
@invalid="$event.target.value ? $event.target.setCustomValidity(`${t('use_international_format')}...`) : ''"
@input="[$event.target.setCustomValidity(''), $emit('update:model-value', $event.target.value)]"
@focus="$emit('focus')"
class="flex w-full rounded-full outline-neutral-content outline-2 outline-offset-2 focus-within:outline"
>
<div class="relative inline-block">
<div class="btn bg-base-200 border border-neutral-300 text-2xl whitespace-nowrap font-normal rounded-l-full">
{{ selectedCountry.flag }} +{{ selectedCountry.dial }}
</div>
<select
class="absolute top-0 bottom-0 right-0 left-0 opacity-0 w-full h-full cursor-pointer"
:disabled="!!defaultValue"
@change="onCountrySelect(countries.find((country) => country.flag === $event.target.value))"
>
<option
v-for="(country, index) in countries"
:key="index"
:value="country.flag"
>
{{ country.flag }} {{ country.name }}
</option>
</select>
</div>
<input
:name="`values[${field.uuid}]`"
:value="fullInternationalPhoneValue"
hidden
>
<input
:id="field.uuid"
ref="phone"
:value="phoneValue"
:readonly="!!defaultValue"
class="base-input !text-2xl !rounded-l-none !border-l-0 !outline-none w-full"
autocomplete="tel"
type="tel"
inputmode="tel"
:required="field.required"
placeholder="234 567-8900"
@input="onPhoneInput"
@focus="$emit('focus')"
>
</div>
</div>
</div>
</template>
<script>
import MarkdownContent from './markdown_content'
import phoneData from './phone_data'
function throttle (func, delay) {
let lastCallTime = 0
@@ -144,13 +169,73 @@ export default {
data () {
return {
isCodeSent: false,
isResendLoading: false
isResendLoading: false,
phoneValue: this.modelValue || this.defaultValue || '',
selectedCountry: {}
}
},
computed: {
countries () {
return phoneData.map(([name, dial, flag, tz]) => {
return { name, dial, flag, tz }
})
},
countriesDialIndex () {
return this.countries.reduce((acc, item) => {
acc[item.dial] ||= item
return acc
}, {})
},
dialCodesRegexp () {
const dialCodes = this.countries.map((country) => country.dial).sort((a, b) => b.length - a.length)
return new RegExp(`^\\+(${dialCodes.join('|')})`)
},
detectedPhoneValueDialCode () {
return (this.phoneValue || '').replace(/[^\d+]/g, '').match(this.dialCodesRegexp)?.[1]
},
fullInternationalPhoneValue () {
if (this.detectedPhoneValueDialCode) {
return this.phoneValue
} else if (this.phoneValue) {
return ['+', this.selectedCountry.dial, this.phoneValue].filter(Boolean).join('')
} else {
return ''
}
}
},
mounted () {
const browserTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone
if (this.detectedPhoneValueDialCode) {
this.selectedCountry = this.countriesDialIndex[this.detectedPhoneValueDialCode]
} else if (browserTimeZone) {
const tz = browserTimeZone.split('/')[1]
this.selectedCountry = this.countries.find((country) => country.tz.includes(tz)) || this.countries[0]
}
},
methods: {
emitSubmit: throttle(function (e) {
this.$emit('submit')
}, 1000),
onCountrySelect (country) {
if (this.selectedCountry.flag !== country.flag) {
this.phoneValue = this.phoneValue.replace(`+${this.selectedCountry.dial}`, `+${country.dial}`)
}
this.selectedCountry = country
this.$refs.phone.focus()
},
onPhoneInput (e) {
this.phoneValue = e.target.value
if (this.detectedPhoneValueDialCode) {
this.selectedCountry = this.countriesDialIndex[this.detectedPhoneValueDialCode]
}
},
onInputCode (e) {
if (e.target.value.length === 6) {
this.emitSubmit()
@@ -171,14 +256,14 @@ export default {
body: JSON.stringify({
submitter_slug: this.submitterSlug,
locale: this.locale,
phone: this.$refs.phone.value
phone: this.fullInternationalPhoneValue
}),
headers: { 'Content-Type': 'application/json' }
}).then(async (resp) => {
if (resp.status === 422) {
const data = await resp.json()
alert(this.t('number_phone_is_invalid').replace('{number}', this.$refs.phone.value))
alert(this.t('number_phone_is_invalid').replace('{number}', this.fullInternationalPhoneValue))
return Promise.reject(new Error(data.error))
}
@@ -189,13 +274,13 @@ export default {
return Promise.resolve({})
}
if (!this.$refs.phone.value.toString().startsWith('+')) {
if (!this.fullInternationalPhoneValue.toString().startsWith('+')) {
alert(this.t('use_international_format'))
return Promise.reject(new Error('phone invalid'))
} else if (!this.isCodeSent) {
return this.sendVerificationCode().then(() => {
this.$emit('update:model-value', this.$refs.phone.value)
this.$emit('update:model-value', this.fullInternationalPhoneValue)
this.isCodeSent = true
@@ -197,6 +197,7 @@
<select
v-if="requireSigningReason && !isOtherReason"
class="select base-input !text-2xl w-full mt-6 text-center"
:class="{ 'text-gray-300': !reason }"
required
:name="`values[${field.preferences.reason_field_uuid}]`"
@change="$event.target.value === 'other' ? [reason = '', isOtherReason = true] : $emit('update:reason', $event.target.value)"
@@ -205,6 +206,7 @@
value=""
disabled
:selected="!reason"
class="text-gray-300"
>
{{ t('select_a_reason') }}
</option>
@@ -213,10 +215,14 @@
:key="option"
:value="option"
:selected="reason === option"
class="text-base-content"
>
{{ label }}
</option>
<option value="other">
<option
value="other"
class="text-base-content"
>
{{ t('other') }}
</option>
</select>
+1
View File
@@ -360,6 +360,7 @@ export default {
},
computed: {
fieldNames: FieldType.computed.fieldNames,
fieldLabels: FieldType.computed.fieldLabels,
fieldIcons: FieldType.computed.fieldIcons,
isDefaultValuePresent () {
if (this.field?.type === 'radio' && this.field?.areas?.length > 1) {
+11 -8
View File
@@ -332,7 +332,7 @@
>
<div class="bg-base-200 rounded-lg p-5 text-center space-y-4">
<p>
{{ t('draw_field_on_the_document').replace('{field}', drawField?.name || '') }}
{{ t('draw_field_on_the_document') }}
</p>
<div>
<button
@@ -418,7 +418,7 @@ import MobileFields from './mobile_fields'
import { IconPlus, IconUsersPlus, IconDeviceFloppy, IconChevronDown, IconEye, IconWritingSign, IconInnerShadowTop, IconInfoCircle, IconAdjustments } from '@tabler/icons-vue'
import { v4 } from 'uuid'
import { ref, computed } from 'vue'
import { en as i18nEn } from './i18n'
import * as i18n from './i18n'
export default {
name: 'TemplateBuilder',
@@ -679,6 +679,9 @@ export default {
computed: {
selectedAreaRef: () => ref(),
fieldsDragFieldRef: () => ref(),
language () {
return this.locale.split('-')[0].toLowerCase()
},
defaultDateFormat () {
const isUsBrowser = Intl.DateTimeFormat().resolvedOptions().locale.endsWith('-US')
const isUsTimezone = new Intl.DateTimeFormat('en-US', { timeZoneName: 'short' }).format(new Date()).match(/\s(?:CST|CDT|PST|PDT|EST|EDT)$/)
@@ -786,7 +789,7 @@ export default {
document.activeElement.blur()
},
t (key) {
return this.i18n[key] || i18nEn[key] || key
return this.i18n[key] || i18n[this.language]?.[key] || i18n.en[key] || key
},
removePendingFields () {
this.template.fields = this.template.fields.filter((f) => {
@@ -1352,7 +1355,7 @@ export default {
this.save()
},
onDocumentRemove (item) {
if (window.confirm(this.t('are_you_sure'))) {
if (window.confirm(this.t('are_you_sure_'))) {
this.template.schema.splice(this.template.schema.indexOf(item), 1)
}
@@ -1451,7 +1454,7 @@ export default {
if (!this.template.fields.length) {
e.preventDefault()
alert('Please draw fields to prepare the document.')
alert(this.t('please_draw_fields_to_prepare_the_document'))
} else {
const submitterWithoutFields =
this.template.submitters.find((submitter) => !this.template.fields.some((f) => f.submitter_uuid === submitter.uuid))
@@ -1459,7 +1462,7 @@ export default {
if (submitterWithoutFields) {
e.preventDefault()
alert(`Please add fields for the ${submitterWithoutFields.name}. Or, remove the ${submitterWithoutFields.name} if not needed.`)
alert(this.t('please_add_fields_for_the_submitter_name_or_remove_the_submitter_name_if_not_needed').replaceAll('{submitter_name}', submitterWithoutFields.name))
}
}
},
@@ -1475,13 +1478,13 @@ export default {
}
if (!this.template.fields.length) {
alert('Please draw fields to prepare the document.')
alert(this.t('please_draw_fields_to_prepare_the_document'))
} else {
const submitterWithoutFields =
this.template.submitters.find((submitter) => !this.template.fields.some((f) => f.submitter_uuid === submitter.uuid))
if (submitterWithoutFields) {
alert(`Please add fields for the ${submitterWithoutFields.name}. Or, remove the ${submitterWithoutFields.name} if not needed.`)
alert(this.t('please_add_fields_for_the_submitter_name_or_remove_the_submitter_name_if_not_needed').replaceAll('{submitter_name}', submitterWithoutFields.name))
} else {
this.isSaving = true
@@ -50,6 +50,7 @@
</div>
<select
class="select select-bordered select-sm w-full bg-white h-11 pl-4 text-base font-normal"
:class="{ 'text-gray-300': !condition.field_uuid }"
required
@change="[
condition.field_uuid = $event.target.value,
@@ -68,6 +69,7 @@
v-for="f in fields"
:key="f.uuid"
:value="f.uuid"
class="text-base-content"
:selected="condition.field_uuid === f.uuid"
>
{{ f.name || buildDefaultName(f, template.fields) }}
@@ -90,6 +92,7 @@
<select
v-if="conditionField(condition)?.options?.length"
class="select select-bordered select-sm w-full bg-white h-11 pl-4 text-base font-normal"
:class="{ 'text-gray-300': !condition.value }"
required
@change="condition.value = $event.target.value"
>
@@ -105,6 +108,7 @@
:key="option.uuid"
:value="option.uuid"
:selected="condition.value === option.uuid"
class="text-base-content"
>
{{ option.value || `${t('option')} ${index + 1}` }}
</option>
@@ -200,7 +204,7 @@ export default {
},
validateSaveAndClose () {
if (!this.withConditions) {
return alert('Available only in Pro')
return alert(this.t('available_only_in_pro'))
}
if (this.conditions.find((f) => f.field_uuid)) {
+1 -1
View File
@@ -105,7 +105,7 @@ export default {
this.upload()
} else {
alert('Only PDF and images are supported.')
alert(this.t('only_pdf_and_images_are_supported'))
}
}
}
+2 -2
View File
@@ -312,6 +312,7 @@ export default {
},
computed: {
fieldNames: FieldType.computed.fieldNames,
fieldLabels: FieldType.computed.fieldLabels,
dropdownBgColor () {
return ['', null, 'transparent'].includes(this.backgroundColor) ? 'white' : this.backgroundColor
},
@@ -367,8 +368,7 @@ export default {
if (this.field.type === 'heading') {
return `${this.fieldNames[field.type]} ${typeIndex + 1}`
} else {
const suffix = { multiple: this.t('select'), radio: this.t('group') }[field.type] || this.t('field')
return `${this.fieldNames[field.type]} ${suffix} ${typeIndex + 1}`
return `${this.fieldLabels[field.type]} ${typeIndex + 1}`
}
}
},
@@ -330,7 +330,7 @@ export default {
this.$emit('update:model-value', submitter.uuid)
},
remove (submitter) {
if (window.confirm(this.t('are_you_sure'))) {
if (window.confirm(this.t('are_you_sure_'))) {
this.$emit('remove', submitter)
}
},
@@ -114,6 +114,25 @@ export default {
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 () {
return {
heading: IconHeading,
+4 -4
View File
@@ -137,7 +137,7 @@
v-else-if="type == 'phone' && (fieldTypes.length === 0 || fieldTypes.includes(type))"
class="tooltip tooltip-bottom flex"
:class="{'tooltip-bottom-end': withPayment, 'tooltip-bottom': !withPayment }"
data-tip="Unlock SMS-verified phone number field with paid plan. Use text field for phone numbers without verification."
:data-tip="t('unlock_sms_verified_phone_number_field_with_paid_plan_use_text_field_for_phone_numbers_without_verification')"
>
<a
href="https://www.docuseal.co/pricing"
@@ -168,13 +168,13 @@
>
<ul class="list-disc list-outside ml-3">
<li>
Draw a text field on the page with a mouse
{{ t('draw_a_text_field_on_the_page_with_a_mouse') }}
</li>
<li>
Drag &amp; drop any other field type on the page
{{ t('drag_and_drop_any_other_field_type_on_the_page') }}
</li>
<li>
Click on the field type above to start drawing it
{{ t('click_on_the_field_type_above_to_start_drawing_it') }}
</li>
</ul>
</div>
@@ -195,7 +195,7 @@ export default {
},
validateSaveAndClose () {
if (!this.withFormula) {
return alert('Available only in Pro')
return alert(this.t('available_only_in_pro'))
}
const normalizedFormula = this.normalizeFormula(this.formula)
+799 -5
View File
@@ -1,5 +1,7 @@
const en = {
editable: 'Editable',
recurrent: 'Recurrent',
one_off: 'One-off',
search_field: 'Search field',
field_not_found: 'Field not found',
clear: 'Clear',
@@ -29,7 +31,7 @@ const en = {
select_value_: 'Select value...',
remove_condition: 'Remove condition',
add_condition: 'Add condition',
are_you_sure: 'Are you sure?',
are_you_sure_: 'Are you sure?',
sign_yourself: 'Sign Yourself',
set_signing_date: 'Set signing date',
signing_date: 'Signing Date',
@@ -46,7 +48,7 @@ const en = {
drawn: 'Drawn',
formula: 'Formula',
typed: 'Typed',
draw_field_on_the_document: 'Draw {field} field on the document',
draw_field_on_the_document: 'Draw a field on the document',
click_to_upload: 'Click to upload',
or_drag_and_drop_files: 'or drag and drop files',
uploading: 'Uploading',
@@ -101,9 +103,31 @@ const en = {
stamp: 'Stamp',
payment: 'Payment',
phone: 'Phone',
field: 'Field',
group: 'Group',
text_field: 'Text Field',
signature_field: 'Signature Field',
initials_field: 'Initials Field',
date_field: 'Date Field',
number_field: 'Number Field',
image_field: 'Image Field',
file_field: 'File Field',
select_field: 'Select Field',
checkbox_field: 'Checkbox Field',
multiple_field: 'Multiple Select Field',
radio_field: 'Radio Group Field',
cells_field: 'Cells Field',
stamp_field: 'Stamp Field',
payment_field: 'Payment Field',
phone_field: 'Phone Field',
draw_a_text_field_on_the_page_with_a_mouse: 'Draw a text field on the page with a mouse',
drag_and_drop_any_other_field_type_on_the_page: 'Drag & drop any other field type on the page',
click_on_the_field_type_above_to_start_drawing_it: 'Click on the field type above to start drawing it',
please_draw_fields_to_prepare_the_document: 'Please draw fields to prepare the document.',
only_pdf_and_images_are_supported: 'Only PDF and images are supported.',
unlock_sms_verified_phone_number_field_with_paid_plan_use_text_field_for_phone_numbers_without_verification: 'Unlock SMS-verified phone number field with paid plan. Use text field for phone numbers without verification.',
available_only_in_pro: 'Available only in Pro',
failed_to_download_files: 'Failed to download files',
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',
replace: 'Replace',
uploading_: 'Uploading...',
@@ -128,4 +152,774 @@ const en = {
learn_more: 'Learn more'
}
export { en }
const es = {
recurrent: 'Recurrente',
one_off: 'Único',
editable: 'Editable',
search_field: 'Campo de búsqueda',
field_not_found: 'Campo no encontrado',
clear: 'Borrar',
type_value: 'Escriba valor',
align: 'Alinear',
add_all_required_fields_to_continue: 'Agregar todos los campos requeridos para continuar',
uploaded_pdf_contains_form_fields_keep_or_remove_them: 'El PDF cargado tiene campos. ¿Mantenerlos o eliminarlos?',
keep: 'Mantener',
left: 'Izquierda',
heading: 'Encabezado',
validation: 'Validación',
add_blank_page: 'Agregar página en blanco',
right: 'Derecha',
center: 'Centro',
with_logo: 'Con logotipo',
description: 'Descripción',
signing_date: 'Fecha de Firma',
display_title: 'Título de visualización',
unchecked: 'No marcado',
price: 'Precio',
equal: 'Igual',
not_equal: 'No es igual',
contains: 'Contiene',
does_not_contain: 'No contiene',
not_empty: 'No vacío',
empty: 'Vacío',
select_field_: 'Seleccionar campo...',
select_value_: 'Seleccionar valor...',
remove_condition: 'Eliminar condición',
add_condition: 'Agregar condición',
condition: 'Condición',
formula: 'Fórmula',
edit: 'Editar',
settings: 'Configuración',
up: 'Arriba',
down: 'Abajo',
set_signing_date: 'Establecer fecha de firma',
are_you_sure_: '¿Estás seguro?',
sign_yourself: 'Firma tú mismo',
checked: 'Seleccionado',
send: 'Enviar',
remove: 'Eliminar',
save: 'Guardar',
cancel: 'Cancelar',
or_add_field_without_drawing: 'O añadir campo sin dibujar',
draw_field_on_the_document: 'Dibujar un campo en el documento',
click_to_upload: 'Haz clic para cargar',
or_drag_and_drop_files: 'o arrastra y suelta archivos',
uploading: 'Subiendo',
processing_: 'Procesando...',
add_pdf_documents_or_images: 'Agregar documentos PDF o imágenes',
add_documents_or_images: 'Agregar documentos o imágenes',
required: 'Requerido',
default_value: 'Valor predeterminado',
format: 'Formato',
read_only: 'Solo lectura',
page: 'Página',
draw_new_area: 'Dibujar nueva área',
copy_to_all_pages: 'Copiar a todas las páginas',
add_option: 'Agregar opción',
option: 'Opción',
first_party: 'Primera Parte',
second_party: 'Segunda Parte',
third_party: 'Tercera Parte',
fourth_party: 'Cuarta Parte',
fifth_party: 'Quinta Parte',
sixth_party: 'Sexta Parte',
seventh_party: 'Séptima Parte',
eighth_party: 'Octava Parte',
ninth_party: 'Novena Parte',
tenth_party: 'Décima Parte',
eleventh_party: 'Undécimo Partido',
twelfth_party: 'Duodécimo Partido',
thirteenth_party: 'Decimotercer Partido',
fourteenth_party: 'Catorceavo Partido',
fifteenth_party: 'Quinceavo Partido',
sixteenth_party: 'Dieciséisavo Partido',
seventeenth_party: 'Diecisieteavo Partido',
eighteenth_party: 'Dieciochoavo Partido',
nineteenth_party: 'Decimonovena Fiesta',
twentieth_party: 'Vigésima Fiesta',
draw: 'Dibujar',
add: 'Agregar',
text: 'Texto',
signature: 'Firma',
initials: 'Iniciales',
date: 'Fecha',
number: 'Número',
image: 'Imagen',
file: 'Archivo',
select: 'Seleccionar',
checkbox: 'Casilla',
multiple: 'Múltiple',
radio: 'Radio',
cells: 'Celdas',
stamp: 'Sello',
payment: 'Pago',
phone: 'Teléfono',
text_field: 'Campo de Texto',
signature_field: 'Campo de Firma',
initials_field: 'Campo de Iniciales',
date_field: 'Campo de Fecha',
number_field: 'Campo de Número',
image_field: 'Campo de Imagen',
file_field: 'Campo de Archivo',
select_field: 'Campo de Selección',
checkbox_field: 'Campo de Casilla',
multiple_field: 'Campo Múltiple',
radio_field: 'Campo de Grupo Radio',
cells_field: 'Campo de Celdas',
stamp_field: 'Campo de Sello',
payment_field: 'Campo de Pago',
phone_field: 'Campo de Teléfono',
draw_a_text_field_on_the_page_with_a_mouse: 'Dibujar un campo de texto en la página con el mouse',
drag_and_drop_any_other_field_type_on_the_page: 'Arrastra y suelta cualquier otro tipo de campo en la página',
click_on_the_field_type_above_to_start_drawing_it: 'Haz clic en el tipo de campo de arriba para comenzar a dibujarlo',
please_draw_fields_to_prepare_the_document: 'Por favor, dibuja los campos para preparar el documento.',
only_pdf_and_images_are_supported: 'Solo se admiten PDF e imágenes.',
unlock_sms_verified_phone_number_field_with_paid_plan_use_text_field_for_phone_numbers_without_verification: 'Desbloquea el campo de número de teléfono verificado por SMS con un plan pago. Usa el campo de texto para números de teléfono sin verificación.',
available_only_in_pro: 'Disponible solo en Pro',
failed_to_download_files: 'Error al descargar los archivos',
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 = {
ricorrente: 'Ricorrente',
una_volta: 'Una volta',
editable: 'Modificabile',
search_field: 'Campo di ricerca',
field_not_found: 'Campo non trovato',
clear: 'Cancella',
align: 'Allinea',
add_all_required_fields_to_continue: 'Aggiungi tutti i campi obbligatori per continuare',
uploaded_pdf_contains_form_fields_keep_or_remove_them: 'Il PDF caricato contiene campi del modulo. Mantenerli o rimuoverli?',
keep: 'Mantieni',
left: 'Sinistra',
heading: 'Intestazione',
validation: 'Validazione',
add_blank_page: 'Aggiungi pagina vuota',
right: 'Destra',
center: 'Centro',
description: 'Descrizione',
display_title: 'Mostra titolo',
with_logo: 'Con logo',
unchecked: 'Non selezionato',
price: 'Prezzo',
type_value: 'Inserisci valore',
equal: 'Uguale',
not_equal: 'Non uguale',
contains: 'Contiene',
does_not_contain: 'Non contiene',
not_empty: 'Non vuoto',
empty: 'Vuoto',
select_field_: 'Seleziona campo...',
select_value_: 'Seleziona valore...',
remove_condition: 'Rimuovi condizione',
add_condition: 'Aggiungi condizione',
are_you_sure_: 'Sei sicuro?',
sign_yourself: 'Firma te stesso',
set_signing_date: 'Imposta data di firma',
signing_date: 'Data di firma',
send: 'Invia',
remove: 'Rimuovi',
edit: 'Modifica',
settings: 'Impostazioni',
up: 'Su',
down: 'Giù',
checked: 'Selezionato',
save: 'Salva',
cancel: 'Annulla',
any: 'Qualsiasi',
drawn: 'Disegnato',
formula: 'Formula',
typed: 'Digitato',
draw_field_on_the_document: 'Disegnare un campo sul documento',
click_to_upload: 'Clicca per caricare',
or_drag_and_drop_files: 'o trascina e rilascia i file',
uploading: 'Caricamento in corso',
processing_: 'Elaborazione...',
add_pdf_documents_or_images: 'Aggiungi documenti PDF o immagini',
add_documents_or_images: 'Aggiungi documenti o immagini',
required: 'Obbligatorio',
default_value: 'Valore predefinito',
format: 'Formato',
read_only: 'Sola lettura',
page: 'Pagina',
draw_new_area: 'Disegna nuova area',
copy_to_all_pages: 'Copia in tutte le pagine',
add_option: 'Aggiungi opzione',
option: 'Opzione',
condition: 'Condizione',
first_party: 'Prima parte',
second_party: 'Seconda parte',
third_party: 'Terza parte',
fourth_party: 'Quarta parte',
fifth_party: 'Quinta parte',
sixth_party: 'Sesta parte',
seventh_party: 'Settima parte',
eighth_party: 'Ottava parte',
ninth_party: 'Nona parte',
tenth_party: 'Decima parte',
eleventh_party: 'Undicesima parte',
twelfth_party: 'Dodicesima parte',
thirteenth_party: 'Tredicesima parte',
fourteenth_party: 'Quattordicesima parte',
fifteenth_party: 'Quindicesima parte',
sixteenth_party: 'Sedicesima parte',
seventeenth_party: 'Diciassettesima parte',
eighteenth_party: 'Diciottesima parte',
nineteenth_party: 'Diciannovesima parte',
twentieth_party: 'Ventesima parte',
draw: 'Disegna',
add: 'Aggiungi',
or_add_field_without_drawing: 'Oppure aggiungi campo senza disegno',
text: 'Testo',
number: 'Numero',
signature: 'Firma',
initials: 'Iniziali',
date: 'Data',
image: 'Immagine',
file: 'File',
select: 'Seleziona',
checkbox: 'Checkbox',
multiple: 'Multiplo',
radio: 'Radio',
cells: 'Celle',
stamp: 'Timbro',
payment: 'Pagamento',
phone: 'Telefono',
text_field: 'Campo di Testo',
signature_field: 'Campo di Firma',
initials_field: 'Campo di Iniziali',
date_field: 'Campo Data',
number_field: 'Campo Numero',
image_field: 'Campo Immagine',
file_field: 'Campo File',
select_field: 'Campo di Selezione',
checkbox_field: 'Campo di Checkbox',
multiple_field: 'Campo Selezione Multipla',
radio_field: 'Campo di Gruppo Radio',
cells_field: 'Campo Celle',
stamp_field: 'Campo Timbro',
payment_field: 'Campo Pagamento',
phone_field: 'Campo Telefono',
draw_a_text_field_on_the_page_with_a_mouse: 'Disegna un campo di testo sulla pagina con il mouse',
drag_and_drop_any_other_field_type_on_the_page: 'Trascina e rilascia qualsiasi altro tipo di campo sulla pagina',
click_on_the_field_type_above_to_start_drawing_it: 'Clicca sul tipo di campo sopra per iniziare a disegnarlo',
please_draw_fields_to_prepare_the_document: 'Per favore, disegna i campi per preparare il documento.',
only_pdf_and_images_are_supported: 'Sono supportati solo PDF e immagini.',
unlock_sms_verified_phone_number_field_with_paid_plan_use_text_field_for_phone_numbers_without_verification: 'Sblocca il campo numero di telefono verificato tramite SMS con un piano a pagamento. Usa il campo di testo per numeri di telefono senza verifica.',
available_only_in_pro: 'Disponibile solo in Pro',
failed_to_download_files: 'Impossibile scaricare i file',
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 = {
recurrent: 'Recurrente',
one_off: 'Único',
editable: 'Editável',
search_field: 'Campo de busca',
field_not_found: 'Campo não encontrado',
clear: 'Limpar',
type_value: 'Digite valor',
add_all_required_fields_to_continue: 'Adicione todos os campos obrigatórios para continuar',
uploaded_pdf_contains_form_fields_keep_or_remove_them: 'O PDF carregado contém campos. Manter ou removê-los?',
keep: 'Manter',
align: 'Alinhar',
left: 'Esquerda',
heading: 'Cabeçalho',
validation: 'Validação',
add_blank_page: 'Adicionar página em branco',
right: 'Direita',
center: 'Centro',
with_logo: 'Com logotipo',
description: 'Descrição',
display_title: 'Título de exibição',
signing_date: 'Data da Assinatura',
unchecked: 'Não marcado',
price: 'Preço',
equal: 'Igual',
not_equal: 'Não é igual',
contains: 'Contém',
does_not_contain: 'Não contém',
not_empty: 'Não vazio',
empty: 'Vazio',
add_condition: 'Adicionar condição',
select_field_: 'Selecionar campo...',
select_value_: 'Selecionar valor...',
remove_condition: 'Remover condição',
condition: 'Condição',
formula: 'Fórmula',
edit: 'Editar',
settings: 'Configurações',
up: 'Para cima',
down: 'Para baixo',
set_signing_date: 'Definir data de assinatura',
are_you_sure_: 'Tem certeza?',
sign_yourself: 'Assine você mesmo',
checked: 'Marcado',
send: 'Enviar',
remove: 'Remover',
save: 'Salvar',
cancel: 'Cancelar',
or_add_field_without_drawing: 'Ou adicione campo sem desenhar',
draw_field_on_the_document: 'Desenhar um campo no documento',
click_to_upload: 'Clique para enviar',
or_drag_and_drop_files: 'ou arraste e solte arquivos',
uploading: 'Carregando',
processing_: 'Processando...',
add_pdf_documents_or_images: 'Adicionar documentos PDF ou imagens',
add_documents_or_images: 'Adicionar documentos ou imagens',
required: 'Obrigatório',
default_value: 'Valor padrão',
format: 'Formato',
read_only: 'Somente leitura',
page: 'Página',
draw_new_area: 'Desenhar nova área',
copy_to_all_pages: 'Copiar para todas as páginas',
add_option: 'Adicionar opção',
option: 'Opção',
first_party: 'Primeira Parte',
second_party: 'Segunda Parte',
third_party: 'Terceira Parte',
fourth_party: 'Quarta Parte',
fifth_party: 'Quinta Parte',
sixth_party: 'Sexta Parte',
seventh_party: 'Sétima Parte',
eighth_party: 'Oitava Parte',
ninth_party: 'Nona Parte',
tenth_party: 'Décima Parte',
eleventh_party: 'Décima Primeira Parte',
twelfth_party: 'Décima Segunda Parte',
thirteenth_party: 'Décima Terceira Parte',
fourteenth_party: 'Décima Quarta Parte',
fifteenth_party: 'Décima Quinta Parte',
sixteenth_party: 'Décima Sexta Parte',
seventeenth_party: 'Décima Sétima Parte',
eighteenth_party: 'Décima Oitava Parte',
nineteenth_party: 'Décima Nona Parte',
twentieth_party: 'Vigésima Parte',
draw: 'Desenhar',
add: 'Adicionar',
text: 'Texto',
signature: 'Assinatura',
initials: 'Rúbrica',
date: 'Data',
number: 'Número',
image: 'Imagem',
file: 'Arquivo',
select: 'Selecionar',
checkbox: 'Caixa',
multiple: 'Múltiplo',
radio: 'Rádio',
cells: 'Células',
stamp: 'Carimbo',
payment: 'Pagamento',
phone: 'Telefone',
text_field: 'Campo de Texto',
signature_field: 'Campo de Assinatura',
initials_field: 'Campo de Rúbrica',
date_field: 'Campo de Data',
number_field: 'Campo de Número',
image_field: 'Campo de Imagem',
file_field: 'Campo de Arquivo',
select_field: 'Campo de Seleção',
checkbox_field: 'Campo de Caixa',
multiple_field: 'Campo de Opção Múltipla',
radio_field: 'Campo de Grupo Rádio',
cells_field: 'Campo de Células',
stamp_field: 'Campo de Carimbo',
payment_field: 'Campo de Pagamento',
phone_field: 'Campo de Telefone',
draw_a_text_field_on_the_page_with_a_mouse: 'Desenhar um campo de texto na página com o mouse',
drag_and_drop_any_other_field_type_on_the_page: 'Arraste e solte qualquer outro tipo de campo na página',
click_on_the_field_type_above_to_start_drawing_it: 'Clique no tipo de campo acima para começar a desenhá-lo',
please_draw_fields_to_prepare_the_document: 'Por favor, desenhe os campos para preparar o documento.',
only_pdf_and_images_are_supported: 'Apenas PDFs e imagens são suportados.',
unlock_sms_verified_phone_number_field_with_paid_plan_use_text_field_for_phone_numbers_without_verification: 'Desbloqueie o campo de número de telefone verificado por SMS com um plano pago. Use o campo de texto para números de telefone sem verificação.',
available_only_in_pro: 'Disponível apenas no Pro',
failed_to_download_files: 'Falha ao baixar arquivos',
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 = {
recurrent: 'Récurrent',
one_off: 'Ponctuel',
editable: 'Éditable',
search_field: 'Champ de recherche',
field_not_found: 'Champ non trouvé',
clear: 'Effacer',
type_value: 'Tapez la valeur',
add_all_required_fields_to_continue: 'Ajoutez tous les champs obligatoires pour continuer',
uploaded_pdf_contains_form_fields_keep_or_remove_them: 'Le PDF téléchargé contient des champs. Les conserver ou les supprimer?',
keep: 'Conserver',
align: 'Aligner',
left: 'Gauche',
heading: 'En-tête',
validation: 'Validation',
add_blank_page: 'Ajouter une page blanche',
right: 'Droite',
add_condition: 'Ajouter une condition',
center: 'Centre',
with_logo: 'Avec logo',
description: 'Description',
display_title: "Titre d'affichage",
unchecked: 'Non coché',
price: 'Prix',
equal: 'Égal',
not_equal: 'Non égal',
contains: 'Contient',
signing_date: 'Date de signature',
does_not_contain: 'Ne contient pas',
not_empty: 'Non vide',
empty: 'Vide',
select_field_: 'Sélectionner un champ...',
select_value_: 'Sélectionner une valeur...',
remove_condition: 'Supprimer la condition',
condition: 'Condition',
formula: 'Formule',
edit: 'Éditer',
settings: 'Paramètres',
up: 'Haut',
down: 'Bas',
set_signing_date: 'Définir la date de signature',
are_you_sure_: 'Êtes-vous sûr?',
sign_yourself: 'Signez-vous',
checked: 'Coché',
send: 'Envoyer',
remove: 'Supprimer',
save: 'Enregistrer',
cancel: 'Annuler',
draw_field_on_the_document: 'Dessiner un champ sur le document',
click_to_upload: 'Cliquez pour télécharger',
or_drag_and_drop_files: 'ou faites glisser-déposer des fichiers',
uploading: 'Téléchargement en cours',
processing_: 'Traitement en cours...',
add_pdf_documents_or_images: 'Ajoutez des documents PDF ou des images',
add_documents_or_images: 'Ajoutez des documents ou des images',
required: 'Requis',
default_value: 'Valeur par défaut',
format: 'Format',
read_only: 'Lecture seule',
page: 'Page',
draw_new_area: 'Dessiner une nouvelle zone',
copy_to_all_pages: 'Copier sur toutes les pages',
add_option: 'Ajouter une option',
option: 'Option',
first_party: 'Première partie',
second_party: 'Deuxième partie',
third_party: 'Troisième partie',
fourth_party: 'Quatrième partie',
fifth_party: 'Cinquième partie',
sixth_party: 'Sixième partie',
seventh_party: 'Septième partie',
eighth_party: 'Huitième partie',
ninth_party: 'Neuvième partie',
tenth_party: 'Dixième partie',
eleventh_party: 'Onzième Parti',
twelfth_party: 'Douzième Parti',
thirteenth_party: 'Treizième Parti',
fourteenth_party: 'Quatorzième Parti',
fifteenth_party: 'Quinzième Parti',
sixteenth_party: 'Seizième Parti',
seventeenth_party: 'Dix-septième Parti',
eighteenth_party: 'Dix-huitième Parti',
nineteenth_party: 'Dix-Neuvième Fête',
twentieth_party: 'Vingtième Fête',
draw: 'Dessiner',
add: 'Ajouter',
or_add_field_without_drawing: 'Ou ajoutez un champ sans dessiner',
text: 'Texte',
signature: 'Signature',
initials: 'Initiales',
date: 'Date',
number: 'Numéro',
image: 'Image',
file: 'Fichier',
select: 'Choisir',
checkbox: 'Coche',
multiple: 'Multiple',
radio: 'Radio',
cells: 'Cellules',
stamp: 'Tampon',
payment: 'Paiement',
phone: 'Téléphone',
text_field: 'Champ de Texte',
signature_field: 'Champ de Signature',
initials_field: "Champ d'Initiales",
date_field: 'Champ de Date',
number_field: 'Champ de Numéro',
image_field: "Champ d'Image",
file_field: 'Champ de Fichier',
select_field: 'Champ de Choix',
checkbox_field: 'Champ de Coche',
multiple_field: 'Champ de Choix Multiple',
radio_field: 'Champ de Groupe Radio',
cells_field: 'Champ de Cellules',
stamp_field: 'Champ de Tampon',
payment_field: 'Champ de Paiement',
phone_field: 'Champ de Téléphone',
draw_a_text_field_on_the_page_with_a_mouse: 'Dessinez un champ texte sur la page avec une souris',
drag_and_drop_any_other_field_type_on_the_page: 'Glissez et déposez tout autre type de champ sur la page',
click_on_the_field_type_above_to_start_drawing_it: 'Cliquez sur le type de champ ci-dessus pour commencer à le dessiner',
please_draw_fields_to_prepare_the_document: 'Veuillez dessiner les champs pour préparer le document.',
only_pdf_and_images_are_supported: 'Seuls les PDF et les images sont pris en charge.',
unlock_sms_verified_phone_number_field_with_paid_plan_use_text_field_for_phone_numbers_without_verification: 'Débloquez le champ de numéro de téléphone vérifié par SMS avec un plan payant. Utilisez un champ texte pour les numéros de téléphone sans vérification.',
available_only_in_pro: 'Disponible uniquement en Pro',
failed_to_download_files: 'Échec du téléchargement des fichiers',
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 = {
wiederkehrend: 'Wiederkehrend',
einmalig: 'Einmalig',
editable: 'Bearbeitbar',
search_field: 'Suchfeld',
field_not_found: 'Feld nicht gefunden',
clear: 'Löschen',
type_value: 'Wert eingeben',
add_all_required_fields_to_continue: 'Fügen Sie alle erforderlichen Felder hinzu, um fortzufahren',
uploaded_pdf_contains_form_fields_keep_or_remove_them: 'Das hochgeladene PDF enthält Formularfelder. Behalten oder entfernen?',
keep: 'Behalten',
align: 'Ausrichten',
left: 'Links',
heading: 'Überschrift',
validation: 'Validierung',
add_blank_page: 'Leere Seite hinzufügen',
right: 'Rechts',
add_condition: 'Bedingung hinzufügen',
center: 'Zentriert',
with_logo: 'Mit Logo',
description: 'Beschreibung',
display_title: 'Anzeigetitel',
unchecked: 'Nicht überprüft',
price: 'Preis',
equal: 'Gleich',
not_equal: 'Ungleich',
contains: 'Enthält',
does_not_contain: 'Enthält nicht',
not_empty: 'Nicht leer',
empty: 'Leer',
signing_date: 'Signaturdatum',
select_field_: 'Feld auswählen...',
select_value_: 'Wert auswählen...',
remove_condition: 'Bedingung entfernen',
condition: 'Bedingung',
formula: 'Formel',
edit: 'Bearbeiten',
settings: 'Einstellungen',
up: 'Nach oben',
down: 'Nach unten',
set_signing_date: 'Unterschriftdatum festlegen',
are_you_sure_: 'Bist du sicher?',
sign_yourself: 'Unterschreiben Sie selbst',
checked: 'Ausgewählt',
send: 'Senden',
remove: 'Entfernen',
save: 'Speichern',
cancel: 'Abbrechen',
draw_field_on_the_document: 'Ein Feld auf dem Dokument zeichnen',
click_to_upload: 'Klicken Sie zum Hochladen',
or_drag_and_drop_files: 'oder Dateien hierher ziehen',
uploading: 'Hochladen',
processing_: 'Verarbeitung...',
add_pdf_documents_or_images: 'PDF-Dokumente oder Bilder hinzufügen',
add_documents_or_images: 'Dokumente oder Bilder hinzufügen',
required: 'Erforderlich',
default_value: 'Standardwert',
format: 'Format',
read_only: 'Nur lesen',
page: 'Seite',
draw_new_area: 'Neuen Bereich zeichnen',
copy_to_all_pages: 'Auf alle Seiten kopieren',
add_option: 'Option hinzufügen',
option: 'Option',
first_party: 'Erste Partei',
second_party: 'Zweite Partei',
third_party: 'Dritte Partei',
fourth_party: 'Vierte Partei',
fifth_party: 'Fünfte Partei',
sixth_party: 'Sechste Partei',
seventh_party: 'Siebte Partei',
eighth_party: 'Achte Partei',
ninth_party: 'Neunte Partei',
tenth_party: 'Zehnte Partei',
eleventh_party: 'Elfte Partei',
twelfth_party: 'Zwölfte Partei',
thirteenth_party: 'Dreizehnte Partei',
fourteenth_party: 'Vierzehnte Partei',
fifteenth_party: 'Fünfzehnte Partei',
sixteenth_party: 'Sechzehnte Partei',
seventeenth_party: 'Siebzehnte Partei',
eighteenth_party: 'Achtzehnte Partei',
nineteenth_party: 'Neunzehnte Party',
twentieth_party: 'Zwanzigste Party',
draw: 'Zeichnen',
add: 'Hinzufügen',
or_add_field_without_drawing: 'Oder Feld ohne Zeichnung hinzufügen',
text: 'Text',
signature: 'Unterschrift',
initials: 'Initialen',
date: 'Datum',
number: 'Nummer',
image: 'Bild',
file: 'Datei',
select: 'Auswählen',
checkbox: 'Checkbox',
multiple: 'Mehrere',
radio: 'Radio',
cells: 'Zellen',
stamp: 'Stempel',
payment: 'Zahlung',
phone: 'Telefon',
text_field: 'Textfeld',
signature_field: 'Unterschriftsfeld',
initials_field: 'Initialenfeld',
date_field: 'Datumsfeld',
number_field: 'Zahlenfeld',
image_field: 'Bildfeld',
file_field: 'Dateifeld',
select_field: 'Auswahlfeld',
checkbox_field: 'Checkbox-Feld',
multiple_field: 'Mehrfach-Auswahlfeld',
radio_field: 'Radio-Gruppenfeld',
cells_field: 'Zellenfeld',
stamp_field: 'Stempelfeld',
payment_field: 'Zahlungsfeld',
phone_field: 'Telefonfeld',
draw_a_text_field_on_the_page_with_a_mouse: 'Textfeld auf der Seite mit der Maus zeichnen',
drag_and_drop_any_other_field_type_on_the_page: 'Ziehe und lasse einen anderen Feldtyp auf die Seite fallen',
click_on_the_field_type_above_to_start_drawing_it: 'Klicke auf den Feldtyp oben, um mit dem Zeichnen zu beginnen',
please_draw_fields_to_prepare_the_document: 'Bitte zeichne Felder, um das Dokument vorzubereiten.',
only_pdf_and_images_are_supported: 'Nur PDFs und Bilder werden unterstützt.',
unlock_sms_verified_phone_number_field_with_paid_plan_use_text_field_for_phone_numbers_without_verification: 'Schalte das SMS-verifizierte Telefonnummernfeld mit einem kostenpflichtigen Plan frei. Verwende das Textfeld für Telefonnummern ohne Verifizierung.',
available_only_in_pro: 'Nur in Pro verfügbar',
failed_to_download_files: 'Fehler beim Herunterladen der Dateien',
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 }
+26 -14
View File
@@ -3,7 +3,7 @@
<div v-if="selectedSheetIndex === null && spreadsheet">
<form @submit.prevent="[selectedSheetIndex = $refs.selectWorksheet.value, buildDefaultMappings()]">
<label class="label">
Select Worksheet
{{ t('select_worksheet') }}
</label>
<select
ref="selectWorksheet"
@@ -18,7 +18,7 @@
</option>
</select>
<button class="base-button mt-4 w-full">
Open
{{ t('open') }}
</button>
</form>
</div>
@@ -36,10 +36,10 @@
</div>
<div class="flex">
<div class="relative w-full py-2 px-2 text-sm">
Recipient field
{{ t('recipient_field') }}
</div>
<div class="relative w-full py-2 pl-4 text-sm">
Spreadsheet column
{{ t('spreadsheet_column') }}
</div>
</div>
<div
@@ -50,6 +50,7 @@
<div class="flex">
<select
class="base-select !select-sm !h-10"
:class="{ '!text-gray-300': !mapping.field_name }"
required
@change="mapping.field_name = $event.target.value"
>
@@ -58,13 +59,14 @@
value=""
:selected="!mapping.field_name"
>
Select Field
{{ t('select_field') }}
</option>
<option
v-for="(field, index) in selectFieldsForSubmitter(submitter)"
:key="index"
:value="field.name"
:selected="mapping.field_name === field.name"
class="text-base-content"
>
{{ field.name }}
</option>
@@ -75,6 +77,7 @@
<div class="w-full relative">
<select
class="base-select !select-sm !h-10"
:class="{ '!text-gray-300': !mapping.column_index }"
required
@change="mapping.column_index = parseInt($event.target.value)"
>
@@ -83,7 +86,7 @@
value=""
:selected="mapping.column_index == null"
>
Select Column
{{ t('select_column') }}
</option>
<template
v-for="(column, index) in columns"
@@ -93,6 +96,7 @@
v-if="column"
:value="index"
:selected="index === mapping.column_index"
class="text-base-content"
>
{{ column }}
</option>
@@ -118,8 +122,8 @@
</div>
<div class="flex items-center pl-1">
<span
class="tooltip tooltip-top"
data-tip="<%= t('remove') %>"
class="tooltip tooltip-left"
:data-tip="t('remove')"
>
<button
:disabled="mappings.filter((m) => m.submitter_uuid === submitter.uuid).length < 2"
@@ -138,7 +142,7 @@
@click.prevent="addMapping(submitter)"
>
<IconPlus class="w-4 h-4" />
New Field Mapping
{{ t('new_field_mapping') }}
</button>
</div>
</div>
@@ -152,7 +156,7 @@
<div
class="px-3 border-y py-2 border-base-300 text-center w-full text-sm font-semibold"
>
Total entries: {{ submissionsData.length }}
{{ t('total_entries') }}: {{ submissionsData.length }}
<template v-if="multitenant && submissionsData.length >= 1000">
/ 1000
</template>
@@ -187,10 +191,10 @@
<div
class="font-medium text-lg mb-1"
>
Upload CSV or XLSX Spreadsheet
{{ t('upload_csv_or_xlsx_spreadsheet') }}
</div>
<div class="text-sm">
<span class="font-medium">Click to Upload</span> or drag and drop files.
<span class="font-medium">{{ t('click_to_upload') }}</span> {{ t('or_drag_and_drop_files') }}
</div>
</div>
</div>
@@ -210,11 +214,11 @@
</label>
</div>
<div class="text-center mt-2">
Or <a
{{ t('or') }} <a
:download="`${template.name}.csv`"
:href="`data:text/csv;base64,${csvBase64}`"
class="link font-medium"
>download</a> a spreadsheet to fill and import
>{{ t('download') }}</a> {{ t('a_sample_spreadsheet_to_fill_and_import') }}
</div>
</div>
</div>
@@ -248,6 +252,11 @@ export default {
type: String,
required: false,
default: ''
},
i18n: {
type: Object,
required: false,
default: () => ({})
}
},
data () {
@@ -354,6 +363,9 @@ export default {
}
},
methods: {
t (key) {
return this.i18n[key] || key
},
onDropFiles (e) {
this.uploadFile(e.dataTransfer.files[0])
},
@@ -21,6 +21,7 @@
@click="closeDropdown"
>
<div
v-if="!('price_id' in field.preferences)"
class="py-1.5 px-1 relative"
@click.stop
>
@@ -58,6 +59,13 @@
class="input input-bordered input-xs w-full max-w-xs h-7 !outline-0"
@blur="save"
>
<input
v-else-if="'price_id' in field.preferences"
v-model="field.preferences.price_id"
placeholder="Price ID: price_XXXXX"
class="input input-bordered input-xs w-full max-w-xs h-7 !outline-0"
@blur="save"
>
<input
v-else
v-model="field.preferences.price"
@@ -72,8 +80,25 @@
class="absolute -top-1 left-2.5 px-1 h-4"
style="font-size: 8px"
>
Price
{{ t('price') }}
</label>
<div class="flex items-center justify-center">
<a
href="#"
class="hover:underline"
style="font-size: 11px"
:class="{'underline': !('price_id' in field.preferences)}"
@click="delete field.preferences.price_id"
>{{ t('one_off') }}</a>
<span class="h-2.5 border-l border-base-content mx-1" />
<a
href="#"
class="hover:underline"
style="font-size: 11px"
:class="{'underline': ('price_id' in field.preferences)}"
@click="field.preferences.price_id ??= ''"
>{{ t('recurrent') }}</a>
</div>
</div>
<div
v-if="!isConnected || isOauthSuccess"
@@ -158,7 +183,10 @@
data-turbo="false"
>{{ t('learn_more') }}</a>
</div>
<li class="mb-1">
<li
v-if="!('price_id' in field.preferences)"
class="mb-1"
>
<label
class="label-text cursor-pointer text-center w-full flex items-center"
@click="$emit('click-formula')"
+17
View File
@@ -0,0 +1,17 @@
# frozen_string_literal: true
class GeneratePreviewImagesJob
include Sidekiq::Job
sidekiq_options queue: :images
def perform(params = {})
attachment = ActiveStorage::Attachment.find(params['attachment_id'])
max_page = [attachment.metadata['pdf']['number_of_pages'].to_i - 1,
Templates::ProcessDocument::MAX_NUMBER_OF_PAGES_PROCESSED].min
Templates::ProcessDocument.generate_document_preview_images(attachment, attachment.download, (1..max_page),
concurrency: 1)
end
end
+41 -31
View File
@@ -23,21 +23,23 @@ class SubmitterMailer < ApplicationMailer
reply_to = build_submitter_reply_to(@submitter)
subject =
if @email_config || @subject
ReplaceEmailVariables.call(@subject || @email_config.value['subject'], submitter:)
elsif @submitter.with_signature_fields?
I18n.t(:you_are_invited_to_sign_a_document)
else
I18n.t(:you_are_invited_to_submit_a_form)
end
I18n.with_locale(@current_account.locale) do
subject =
if @email_config || @subject
ReplaceEmailVariables.call(@subject || @email_config.value['subject'], submitter:)
elsif @submitter.with_signature_fields?
I18n.t(:you_are_invited_to_sign_a_document)
else
I18n.t(:you_are_invited_to_submit_a_form)
end
mail(
to: @submitter.friendly_name,
from: from_address_for_submitter(submitter),
subject:,
reply_to:
)
mail(
to: @submitter.friendly_name,
from: from_address_for_submitter(submitter),
subject:,
reply_to:
)
end
end
def completed_email(submitter, user, to: nil)
@@ -66,13 +68,15 @@ class SubmitterMailer < ApplicationMailer
assign_message_metadata('submitter_completed', @submitter)
subject =
ReplaceEmailVariables.call(@subject.presence || I18n.t(:template_name_has_been_completed_by_submitters),
submitter:)
I18n.with_locale(@current_account.locale) do
subject =
ReplaceEmailVariables.call(@subject.presence || I18n.t(:template_name_has_been_completed_by_submitters),
submitter:)
mail(from: from_address_for_submitter(submitter),
to: to || (user.role == 'integration' ? user.friendly_name.sub(/\+\w+@/, '@') : user.friendly_name),
subject:)
mail(from: from_address_for_submitter(submitter),
to: to || normalize_user_email(user),
subject:)
end
end
def declined_email(submitter, user)
@@ -83,7 +87,7 @@ class SubmitterMailer < ApplicationMailer
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),
to: user.role == 'integration' ? user.friendly_name.sub(/\+\w+@/, '@') : user.friendly_name,
reply_to: @submitter.friendly_name,
@@ -116,17 +120,19 @@ class SubmitterMailer < ApplicationMailer
assign_message_metadata('submitter_documents_copy', @submitter)
reply_to = build_submitter_reply_to(submitter)
subject =
if @subject.present?
ReplaceEmailVariables.call(@subject, submitter:)
else
I18n.t(:your_document_copy)
end
I18n.with_locale(@current_account.locale) do
subject =
if @subject.present?
ReplaceEmailVariables.call(@subject, submitter:)
else
I18n.t(:your_document_copy)
end
mail(from: from_address_for_submitter(submitter),
to: to || @submitter.friendly_name,
reply_to:,
subject:)
mail(from: from_address_for_submitter(submitter),
to: to || @submitter.friendly_name,
reply_to:,
subject:)
end
end
private
@@ -171,6 +177,10 @@ class SubmitterMailer < ApplicationMailer
documents
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)
total_size = current_size
+4 -2
View File
@@ -8,7 +8,9 @@ class UserMailer < ApplicationMailer
assign_message_metadata('user_invitation', @user)
mail(to: @user.friendly_name,
subject: "You are invited to #{Docuseal.product_name}")
I18n.with_locale(@current_account.locale) do
mail(to: @user.friendly_name,
subject: I18n.t('you_are_invited_to_product_name', product_name: Docuseal.product_name))
end
end
end
+2
View File
@@ -39,7 +39,9 @@ class AccountConfig < ApplicationRecord
FORCE_SSO_AUTH_KEY = 'force_sso_auth'
FLATTEN_RESULT_PDF_KEY = 'flatten_result_pdf'
WITH_SIGNATURE_ID = 'with_signature_id'
WITH_AUDIT_VALUES_KEY = 'with_audit_values'
REQUIRE_SIGNING_REASON_KEY = 'require_signing_reason'
REUSE_SIGNATURE_KEY = 'reuse_signature'
COMBINE_PDF_RESULT_KEY = 'combine_pdf_result_key'
DEFAULT_VALUES = {
+4 -2
View File
@@ -56,6 +56,8 @@ class Submitter < ApplicationRecord
has_many :document_generation_events, dependent: :destroy
has_many :submission_events, dependent: :destroy
has_many :start_form_submission_events, -> { where(event_type: :start_form) },
class_name: 'SubmissionEvent', dependent: :destroy, inverse_of: :submitter
scope :completed, -> { where.not(completed_at: nil) }
@@ -86,11 +88,11 @@ class Submitter < ApplicationRecord
end
def first_name
name.split(/\s+/, 2).first
name&.split(/\s+/, 2)&.first
end
def last_name
name.split(/\s+/, 2).last
name&.split(/\s+/, 2)&.last
end
def status_event_at
+1 -1
View File
@@ -46,7 +46,7 @@ class Template < ApplicationRecord
attribute :preferences, :string, default: -> { {} }
attribute :fields, :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 :source, :string, default: 'native'
+2 -2
View File
@@ -18,8 +18,8 @@
<% end %>
</div>
<div class="form-control">
<%= ff.label :locale, t('time_format'), class: 'label' %>
<%= ff.select :locale, options_for_select(controller.class::LOCALE_OPTIONS.invert, current_account.locale), {}, class: 'base-select' %>
<%= ff.label :locale, t('language'), class: 'label' %>
<%= ff.select :locale, options_for_select(AccountsController::LOCALE_OPTIONS.invert, current_account.locale), {}, class: 'base-select' %>
</div>
</div>
<% 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">
<%= render 'shared/settings_nav' %>
<div class="flex-grow">
<div class="flex justify-between">
<h1 class="text-4xl font-bold mb-4">API</h1>
<div class="flex flex-col gap-2 md:flex-row md:justify-between md:items-end mb-4">
<h1 class="text-4xl font-bold">API</h1>
<%= render 'shared/test_mode_toggle' %>
</div>
<div class="card bg-base-200">
+1 -1
View File
@@ -38,7 +38,7 @@
<div class="form-control">
<%= ff.label :security_label, 'SMTP Security', class: 'label' %>
<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.radio_button :security, val, checked: (value['security'].blank? && val == 'none') || value['security'] == val, id: "#{val}_radio", class: 'base-radio mr-2' %>
<%= 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 class="form-control">
<%= 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">
<span class="label-text-alt">
<%= t('use_a_valid_der_p12_or_pfx_file') %>
+7 -3
View File
@@ -19,8 +19,8 @@
<% end %>
<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">
<div class="absolute top-0 right-0 left-0 bottom-0 flex items-center justify-center">
<div class="flex flex-col items-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 text-center">
<span data-target="file-dropzone.icon">
<%= svg_icon('cloud_upload', class: 'w-10 h-10') %>
</span>
@@ -40,7 +40,7 @@
</file-dropzone>
<% end %>
</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">
<%= t('signing_certificates') %>
</h2>
@@ -71,6 +71,7 @@
</thead>
<tbody>
<% @pkcs_list.each do |item| %>
<% next if item['pkcs'].blank? %>
<tr scope="row" class="group">
<td>
<%= item['name'] %>
@@ -98,6 +99,9 @@
</td>
</tr>
<% end %>
<% unless Docuseal.multitenant? %>
<%= render 'default_signature_row' %>
<% end %>
</tbody>
</table>
</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>
<%= t('completed_documents_notification_bcc_address') %>
</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') %>
</span>
</span>
@@ -3,22 +3,22 @@
<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')) %>
<% 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">
<%= 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' %>
<% end %>
</div>
<div class="w-full">
<%= 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' %>
<% end %>
</div>
<div class="w-full">
<%= 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' %>
<% end %>
</div>
@@ -2,7 +2,7 @@
<input type="checkbox">
<div class="collapse-title text-xl font-medium capitalize">
<div>
<%= t('copleted_notification_email') %>
<%= t('completed_notification_email') %>
</div>
</div>
<div class="collapse-content">
+6
View File
@@ -56,6 +56,12 @@
<%= ff.text_field :value, required: true, class: 'base-input' %>
</div>
<% 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 class="form-control">
<%= 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">
<%= svg_icon(local_assigns[:icon] || 'link', class: local_assigns[:icon_class] || 'w-6 h-6 text-white') %>
<span class="hidden md:inline">
<%= local_assigns[:copy_title] || 'Copy' %>
<%= local_assigns[:copy_title] || t('copy') %>
</span>
<% if local_assigns[:copy_title_md] %>
<span class="inline md:hidden">
@@ -15,7 +15,7 @@
<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') %>
<span class="hidden md:inline">
<%= local_assigns[:copied_title] || 'Copied' %>
<%= local_assigns[:copied_title] || t('copied') %>
</span>
<% if local_assigns[:copied_title_md] %>
<span class="inline md:hidden">
+1 -1
View File
@@ -2,5 +2,5 @@
---
</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>
+1 -1
View File
@@ -15,5 +15,5 @@
<%= yield %>
</div>
</div>
<label class="modal-backdrop" for="<%= uuid %>"><%= t('close') %></label>
<label class="modal-backdrop" for="<%= uuid %>"></label>
</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">
<span class="text-sm align-text-top"><%= current_user.initials %></span>
</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>
<%= link_to settings_profile_index_path, class: 'flex items-center' do %>
<%= svg_icon('adjustments', class: 'w-5 h-5 flex-shrink-0 stroke-2') %>
@@ -36,7 +36,7 @@
<% if !Docuseal.demo? && can?(:manage, EncryptedConfig) %>
<li>
<%= 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') %>
<% end %>
</li>
@@ -44,15 +44,15 @@
<% if can?(:read, EncryptedConfig.new(key: EncryptedConfig::ESIGN_CERTS_KEY, account: current_account)) %>
<li>
<%= link_to settings_esign_path, class: 'flex items-center' do %>
<%= svg_icon('zoom_check', class: 'w-5 h-5 stroke-2') %>
<span class="mr-1"><%= t('verify_pdf') %></span>
<%= svg_icon('zoom_check', class: 'w-5 h-5 flex-shrink-0 stroke-2') %>
<span class="mr-1 whitespace-nowrap"><%= t('verify_pdf') %></span>
<% end %>
</li>
<% end %>
<li>
<%= 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') %>
<span class="mr-1"><%= t('sign_out') %></span>
<%= svg_icon('logout', class: 'w-5 h-5 flex-shrink-0 stroke-2 mr-2 inline') %>
<span class="mr-1 whitespace-nowrap"><%= t('sign_out') %></span>
<% end %>
</li>
</ul>
+1 -1
View File
@@ -9,5 +9,5 @@
<% else %>
<%= t('powered_by') %>
<% 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>
+3 -3
View File
@@ -4,15 +4,15 @@
<% end %>
<% if params[:q].present? %>
<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;
</a>
</div>
<% end %>
<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>
<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">
<%= svg_icon('search', class: 'w-6 h-6 stroke-2') %>
</span>
+1 -1
View File
@@ -1,7 +1,7 @@
<div class="block w-full md:w-52 flex-none">
<menu-active>
<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">
<span class="!bg-transparent"><%= t('settings') %></span>
</li>
+1 -1
View File
@@ -1,5 +1,5 @@
<% 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">
<%= svg_icon('code_circle', class: 'w-5 h-5 mr-1') %>
<span><%= t('testing_environment') %></span>
+1 -1
View File
@@ -1,6 +1,6 @@
<% if can?(:manage, EncryptedConfig) || (current_user != true_user && current_account.testing?) %>
<%= form_for '', url: testing_account_path, method: current_account.testing? ? :delete : :get, html: { class: 'flex' } do |f| %>
<label class="flex items-center justify-between py-2.5" for="testing_toggle">
<label class="flex items-center justify-between" for="testing_toggle">
<span class="mr-2 text-lg">
<%= t('test_environment') %>
</span>
@@ -20,18 +20,18 @@
<input type="hidden" name="submission[1][submitters][][uuid]" value="<%= item['uuid'] %>">
<submitters-autocomplete data-field="name">
<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>
</submitters-autocomplete>
<div class="grid <%= 'md:grid-cols-2 gap-1' if submitters.size == 1 %>">
<submitters-autocomplete data-field="email">
<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>
</submitters-autocomplete>
<submitters-autocomplete data-field="phone">
<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('<%= t('use_international_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>
</submitters-autocomplete>
</div>
+1 -1
View File
@@ -29,7 +29,7 @@
<input type="hidden" name="submission[1][submitters][][uuid]" value="<%= item['uuid'] %>">
<submitters-autocomplete data-field="email">
<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>
</submitters-autocomplete>
</submitter-item>
+3 -3
View File
@@ -21,13 +21,13 @@
<input type="hidden" name="submission[1][submitters][][uuid]" value="<%= item['uuid'] %>">
<submitters-autocomplete data-field="phone">
<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('#{t('use_international_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>
</submitters-autocomplete>
<% if submitters.size > 1 %>
<submitters-autocomplete data-field="name">
<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>
</submitters-autocomplete>
<% end %>
@@ -36,7 +36,7 @@
<div class="form-control flex">
<submitters-autocomplete data-field="name">
<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>
</submitters-autocomplete>
</div>
+12 -10
View File
@@ -2,12 +2,12 @@
<% can_send_emails = Accounts.can_send_emails?(current_account) %>
<div class="flex justify-between items-center">
<%= f.label :send_email, for: uuid = SecureRandom.uuid, class: 'flex items-center cursor-pointer' do %>
<%= f.check_box :send_email, id: uuid, class: 'base-checkbox', disabled: !can_send_emails, checked: can_send_emails %>
<span class="label"><%= t('send_emails') %></span>
<%= f.check_box :send_email, id: uuid, class: 'base-checkbox', disabled: !can_send_emails, checked: can_send_emails && !local_assigns.key?(:resend_email) %>
<span class="label"><%= local_assigns[:resend_email] ? t('re_send_email') : t('send_email') %></span>
<% end %>
<div>
<% if can_send_emails %>
<%= render 'email_stats' %>
<%= render 'submissions/email_stats' %>
<%= content_for(:edit_button) || capture do %>
<label>
<%= f.check_box :is_custom_message, onchange: "[this.form.querySelector('#message_field').classList.toggle('hidden', !event.currentTarget.checked)]", checked: false, class: 'hidden peer' %>
@@ -39,7 +39,7 @@
<div class="form-control space-y-2">
<div class="form-control">
<%= f.label :subject, t('subject'), class: 'label' %>
<%= f.text_field :subject, value: @template.preferences['request_email_subject'].presence || config.value['subject'], required: true, class: '!text-sm base-input w-full', dir: 'auto' %>
<%= f.text_field :subject, value: local_assigns[:submitter_email_message]&.subject.presence || template.preferences['request_email_subject'].presence || config.value['subject'], required: true, class: '!text-sm base-input w-full', dir: 'auto' %>
</div>
<div class="form-control">
<div class="flex items-center">
@@ -49,14 +49,16 @@
</span>
</div>
<autoresize-textarea>
<%= f.text_area :body, value: @template.preferences['request_email_body'].presence || config.value['body'], required: true, class: 'base-textarea w-full', rows: 10, dir: 'auto' %>
<%= f.text_area :body, value: local_assigns[:submitter_email_message]&.body.presence || template.preferences['request_email_body'].presence || config.value['body'], required: true, class: 'base-textarea w-full', rows: 10, dir: 'auto' %>
</autoresize-textarea>
<label for="<%= uuid = SecureRandom.uuid %>" class="flex items-center cursor-pointer">
<%= check_box_tag :save_message, id: uuid, class: 'base-checkbox', checked: false %>
<span class="label"><%= t('save_as_default_template_message') %></span>
</label>
<% unless local_assigns.fetch(:disable_save_as_default_template_option, false) %>
<label for="<%= uuid = SecureRandom.uuid %>" class="flex items-center cursor-pointer">
<%= check_box_tag :save_message, id: uuid, class: 'base-checkbox', checked: false %>
<span class="label"><%= t('save_as_default_template_message') %></span>
</label>
<% end %>
</div>
<%= render 'message_fields' %>
<%= render 'submissions/message_fields' %>
</div>
</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">
<%= 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]) %>
+5 -5
View File
@@ -1,14 +1,14 @@
<%= 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">
<div class="join">
<div class="flex justify-center">
<% 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' } %>
<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>
</span>
</div>
<% end %>
</div>
</toggle-visible>
+20 -11
View File
@@ -53,7 +53,7 @@
<% end %>
</div>
<% 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 %>
</div>
</div>
@@ -107,12 +107,21 @@
<% (@submission.template_submitters || @submission.template.submitters).each_with_index do |item, index| %>
<% submitter = @submission.submitters.find { |e| e.uuid == item['uuid'] } %>
<div class="sticky -top-1 bg-base-100 pt-1 -mt-1">
<div class="border border-base-300 rounded-md px-2 py-1 mb-1">
<div class="flex items-center space-x-1">
<span class="mx-1 w-3 h-3 rounded-full <%= colors[index] %>"></span>
<span class="text-lg" dir="auto">
<%= (@submission.template_submitters || @submission.template.submitters).find { |e| e['uuid'] == submitter&.uuid }&.dig('name') || "#{(index + 1).ordinalize} Submitter" %>
</span>
<div class="group border border-base-300 rounded-md px-2 py-1 mb-1">
<div class="flex items-center justify-between">
<div class="flex items-center space-x-1">
<span class="mx-1 w-3 h-3 shrink-0 rounded-full <%= colors[index] %>"></span>
<span class="text-lg" dir="auto">
<%= (@submission.template_submitters || @submission.template.submitters).find { |e| e['uuid'] == submitter&.uuid }&.dig('name') || "#{(index + 1).ordinalize} Submitter" %>
</span>
</div>
<% if signed_in? && can?(:update, submitter) && !submitter.completed_at? && !submitter.declined_at? && !@submission.archived_at? && !@submission.expired? && !submitter.start_form_submission_events.any? %>
<span class="tooltip tooltip-left" data-tip="<%= t('edit') %>">
<%= link_to edit_submitter_path(submitter), class: 'shrink-0 inline md:hidden md:group-hover:inline', data: { turbo_frame: 'modal' } do %>
<%= svg_icon('pencil', class: 'w-5 h-5') %>
<% end %>
</span>
<% end %>
</div>
<% if submitter&.name.present? %>
<div class="flex items-center space-x-1 mt-1">
@@ -144,7 +153,7 @@
<% 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)) %>
<% 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 %>
<%= t('not_invited_yet') %>
<% end %>
@@ -153,14 +162,14 @@
<% if submitter&.declined_at? %>
<div class="flex items-center space-x-1 mt-1">
<span>
<%= t('Reason') %>:
<%= t('reason') %>:
<%= simple_format(h(submitter.submission_events.find_by(event_type: :decline_form).data['reason'])) %>
</span>
</div>
<% 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? %>
<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>
<% end %>
<% if signed_in? && submitter && submitter.phone && !submitter.completed_at && !@submission.archived_at? && can?(:update, submitter) && !@submission.expired? && !submitter.declined_at? %>
@@ -182,7 +191,7 @@
<% next if value.blank? %>
<div class="pt-2.5 border-b border-base-300">
<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 dir="auto">
<% if field['type'].in?(%w[signature initials]) %>
@@ -1,11 +1,11 @@
<% is_show_tabs = @pagy.count >= 5 || params[:status].present? %>
<% if Docuseal.demo? %><%= render 'shared/demo_alert' %><% end %>
<div class="flex justify-between mb-4 items-center">
<div class="flex items-center">
<div class="flex justify-between items-center w-full mb-4">
<div class="flex items-center flex-grow min-w-0">
<div class="mr-2">
<%= render 'dashboard/toggle_view', selected: 'submissions' %>
</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') %>
</h1>
</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">
<%= 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">
@@ -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_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 %>
<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-reuse-signature="<%= configs[:reuse_signature] %>" 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 %>
+5 -4
View File
@@ -21,12 +21,13 @@
<% end %>
</div>
<download-button data-src="<%= submit_form_download_index_path(@submitter.slug) %>" class="btn btn-neutral text-white btn-sm !px-4">
<span class="flex items-center justify-center space-x-2" data-target="download-button.defaultButton">
<span><%= t('download') %></span>
<span class="flex items-center justify-center" data-target="download-button.defaultButton">
<%= svg_icon('download', class: 'w-6 h-6 inline md:hidden') %>
<span class="hidden md:inline"><%= t('download') %></span>
</span>
<span class="flex items-center justify-center space-x-2 hidden" data-target="download-button.loadingButton">
<%= svg_icon('loader', class: 'w-6 h-6 animate-spin') %>
<span><%= t('downloading') %></span>
<span class="hidden md:inline"><%= t('downloading') %></span>
</span>
</download-button>
</div>
@@ -59,7 +60,7 @@
</div>
<% end %>
<% end %>
<%= render 'shared/attribution', link_path: '/start', account: @submitter.account %>
<%= render 'shared/attribution', link_path: '/start', account: @submitter.account, with_style: false %>
</div>
</div>
</div>
@@ -23,7 +23,7 @@
</div>
<div class="w-full bg-white rounded-2xl border relative" style="height: 300px">
<canvas class="w-full"></canvas>
<button aria-label="<%= t('clear') %>" class="btn btn-ghost btn-sm font-medium top-0 right-0 absolute mt-1 mr-1">
<button aria-label="Clear" class="btn btn-ghost btn-sm font-medium top-0 right-0 absolute mt-1 mr-1">
<%= svg_icon('reload', class: 'w-5 h-5') %>
<span class="inline"><%= t('clear') %></span>
</button>
+29
View File
@@ -0,0 +1,29 @@
<%= render 'shared/turbo_modal_large', title: t('edit_recipient') do %>
<div class="px-5 mb-5 mt-4">
<%= form_for '', url: submitter_path(@submitter), method: :patch, html: { autocomplete: 'off' }, data: { turbo_frame: :_top } do |f| %>
<div class="mb-2">
<submitter-item class="form-control">
<submitters-autocomplete data-field="name">
<%= text_field_tag 'submitter[name]', @submitter.name, autocomplete: 'off', class: 'base-input !h-10 w-full', placeholder: "#{t('name')} (#{t('optional')})", dir: 'auto' %>
</submitters-autocomplete>
<div class="grid md:grid-cols-2 gap-1.5 mt-1.5">
<submitters-autocomplete data-field="email">
<%= email_field_tag 'submitter[email]', @submitter.email, autocomplete: 'off', class: 'base-input !h-10 mt-1.5 w-full', placeholder: "#{t('email')} (#{t('optional')})" %>
</submitters-autocomplete>
<submitters-autocomplete data-field="phone">
<%= telephone_field_tag 'submitter[phone]', @submitter.phone, autocomplete: 'off', pattern: '^\+[0-9\s\-]+$', class: 'base-input !h-10 mt-1.5 w-full', placeholder: "#{t('phone')} (#{t('optional')})", oninvalid: "this.value ? this.setCustomValidity('#{t('use_international_format_1xxx_')}') : ''", oninput: "this.setCustomValidity('')" %>
</submitters-autocomplete>
</div>
</submitter-item>
</div>
<div>
<%= render 'submissions/send_email', f:, template: @submitter.template, submitter: @submitter, resend_email: @submitter.sent_at?, submitter_email_message: @submitter_email_message, disable_save_as_default_template_option: true %>
<%= render 'submissions/send_sms', f:, resend_sms: @submitter.sent_at? %>
</div>
<div class="form-control mt-4">
<%= f.button button_title(title: t('update_recipient'), disabled_with: t('updating')), class: 'base-button' %>
</div>
<% end %>
</div>
<%= content_for(:modal_extra) %>
<% end %>
+3 -3
View File
@@ -4,10 +4,10 @@
<span><%= t('home') %></span>
<% end %>
</div>
<div class="flex justify-between mb-4 items-center">
<h1 class="text-4xl font-bold flex items-center space-x-2 md:flex <%= 'hidden' if params[:q].present? %>">
<div class="flex justify-between items-center w-full mb-4">
<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') %>
<span class="peer">
<span class="peer truncate">
<%= @template_folder.name %>
</span>
<% 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>
<file-dropzone data-submit-on-upload="true" class="w-full">
<label for="file_dropzone_input" class="w-full block h-52 relative hover:bg-base-200/30 rounded-xl border border-2 border-base-300 border-dashed">
<div class="absolute top-0 right-0 left-0 bottom-0 flex items-center justify-center">
<div class="flex flex-col items-center">
<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 text-center">
<span data-target="file-dropzone.icon" class="flex flex-col items-center">
<span>
<%= svg_icon('cloud_upload', class: 'w-10 h-10') %>
+92 -73
View File
@@ -26,7 +26,7 @@
</div>
</a>
<% end %>
<a href="<%= submission_path(submission) %>" class="w-full flex flex-col md:flex-row space-y-4 md:space-y-0 md:justify-between px-5 md:px-6 pb-5 md:items-center pt-5">
<div class="w-full flex flex-col md:flex-row space-y-4 md:space-y-0 md:justify-between px-5 md:px-6 pb-5 md:items-center pt-5 relative cursor-pointer">
<% submitters = (submission.template_submitters || submission.template.submitters).filter_map { |item| submission.submitters.find { |e| e.uuid == item['uuid'] } } %>
<% is_submission_completed = submitters.all?(&:completed_at?) && submitters.size.positive? %>
<% if submitters.size == 1 %>
@@ -41,24 +41,33 @@
</span>
</div>
<% else %>
<div class="tooltip flex" data-tip="<%= l(submitter.status_event_at.in_time_zone(current_account.timezone), format: :short, locale: current_account.locale) %>">
<span class="badge <%= status_badges[submitter.status] %> md:w-32 badge-lg bg-opacity-50 uppercase text-sm font-semibold">
<%= submitter.status %>
<a href="<%= submission_path(submission) %>" class="flex z-[1]">
<span class="badge <%= status_badges[submitter.status] %> md:w-32 badge-lg bg-opacity-50 uppercase text-sm font-semibold tooltip" data-tip="<%= l(submitter.status_event_at.in_time_zone(current_account.timezone), format: :short, locale: current_account.locale) %>">
<%= t(submitter.status) %>
</span>
</div>
</a>
<% end %>
<span class="text-lg break-all flex items-center">
<%= submitter.name || submitter.email || submitter.phone %>
<span class="flex items-center z-[1]">
<a href="<%= submission_path(submission) %>" class="text-lg break-all peer">
<%= submitter.name || submitter.email || submitter.phone %>
</a>
<% if can?(:update, submitter) && !submitter.start_form_submission_events.any? && !submission.archived_at? && !submission.expired? %>
<span class="pl-0.5 tooltip tooltip-top md:opacity-0 md:hover:opacity-100 md:peer-hover:opacity-100" data-tip="<%= t('edit') %>">
<%= link_to edit_submitter_path(submitter), class: 'shrink-0', data: { turbo_frame: 'modal' } do %>
<%= svg_icon('pencil', class: 'w-5 h-5') %>
<% end %>
</span>
<% end %>
</span>
</span>
</div>
</div>
<div class="flex space-x-2 items-center">
<div class="flex space-x-2 items-center z-[1]">
<% if submitter.completed_at? %>
<form onsubmit="event.preventDefault()" class="flex-1 md:flex-none">
<button onclick="event.stopPropagation()" class="w-full md:w-fit">
<div class="flex-1 md:flex-none">
<div class="w-full md:w-fit">
<download-button data-src="<%= submitter_download_index_path(submitter.slug) %>" class="btn btn-sm btn-neutral text-white w-full md:w-36">
<span class="flex items-center justify-center space-x-1 md:space-x-2" data-target="download-button.defaultButton">
<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') %>
<span class="inline"><%= t('download') %></span>
</span>
@@ -67,35 +76,39 @@
<span class="inline"><%= t('download')[..-2] %>...</span>
</span>
</download-button>
</button>
</form>
</div>
</div>
<% elsif !submission.archived_at? && !template.archived_at? && !submission.expired? && !submitter.declined_at? %>
<% if current_user.email == submitter.email %>
<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">
<div class="flex-1 md:flex-none md:w-36 flex">
<a href="<%= submit_form_url(slug: submitter.slug) %>" data-turbo="false" target="_blank" class="btn btn-sm btn-neutral btn-outline bg-white w-full md:w-36 flex z-[1]">
<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>
</button>
</form>
</a>
</div>
<% else %>
<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 z-[1]', 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>
<% end %>
<% end %>
<div class="flex-1 md:flex-none">
<span class="btn btn-outline btn-sm w-full md:w-24"><%= t('view') %></span>
<div class="flex-1 md:flex-none z-[1]">
<a href="<%= submission_path(submission) %>" class="btn btn-outline btn-sm w-full md:w-24">
<%= t('view') %>
</a>
</div>
<% if !submission.archived_at? && !template.archived_at? && can?(:destroy, submission) %>
<span data-tip="<%= t('archive') %>" class="sm:tooltip tooltip-top">
<%= button_to button_title(title: nil, disabled_with: t(:archive).first(4), icon: svg_icon('archive', class: 'w-6 h-6')), submission_path(submission), class: 'btn btn-outline btn-sm w-full md:w-fit', form: { class: 'flex' }, title: t('archive'), method: :delete, onclick: 'event.stopPropagation()' %>
<%= button_to button_title(title: nil, disabled_with: t(:archive).first(4), icon: svg_icon('archive', class: 'w-6 h-6')), submission_path(submission), class: 'btn btn-outline btn-sm w-full md:w-fit', form: { class: 'flex' }, title: t('archive'), method: :delete %>
</span>
<% end %>
<% if local_assigns[:archived] && can?(:destroy, submission) %>
<span data-tip="<%= t('remove') %>" class="sm:tooltip tooltip-top">
<%= button_to button_title(title: nil, disabled_with: t(:remove).first(3), icon: svg_icon('trash', class: 'w-6 h-6')), submission_path(submission, permanently: true), class: 'btn btn-outline btn-sm w-full md:w-fit', form: { class: 'flex' }, title: t('remove'), method: :delete, data: { turbo_confirm: t('submission_deletion_is_irreversible_and_will_permanently_remove_all_associated_signed_documents_with_it_are_you_sure_') }, onclick: 'event.stopPropagation()' %>
<%= button_to button_title(title: nil, disabled_with: t(:remove).first(3), icon: svg_icon('trash', class: 'w-6 h-6')), submission_path(submission, permanently: true), class: 'btn btn-outline btn-sm w-full md:w-fit', form: { class: 'flex' }, title: t('remove'), method: :delete, data: { turbo_confirm: t('submission_deletion_is_irreversible_and_will_permanently_remove_all_associated_signed_documents_with_it_are_you_sure_') } %>
</span>
<% end %>
</div>
@@ -104,11 +117,11 @@
<div class="flex flex-col md:flex-row md:items-center gap-3">
<% if is_submission_completed %>
<% latest_submitter = submitters.select(&:completed_at?).max_by(&:completed_at) %>
<div class="tooltip flex" data-tip="<%= l(latest_submitter.status_event_at.in_time_zone(current_account.timezone), format: :short, locale: current_account.locale) %>">
<a href="<%= submission_path(submission) %>" class="z-[1] 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">
<%= latest_submitter.status %>
<%= t(latest_submitter.status) %>
</span>
</div>
</a>
<% elsif submission.expired? %>
<div class="tooltip flex" data-tip="<%= l(submission.expire_at.in_time_zone(current_account.timezone), format: :short, locale: current_account.locale) %>">
<span class="badge badge-error md:w-32 bg-opacity-50 badge-lg uppercase text-sm font-semibold">
@@ -121,44 +134,49 @@
<div class="relative flex justify-between items-start md:items-center space-x-3">
<span class="flex flex-col md:flex-row md:items-center gap-2">
<% 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) %>">
<a href="<%= submission_path(submission) %>" class="z-[1] 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">
<%= submitter.status %>
<%= t(submitter.status) %>
</span>
</div>
</a>
<% end %>
<span class="text-lg break-all">
<%= submitter.name || submitter.email || submitter.phone %>
<span class="flex items-center z-[1]">
<a href="<%= submission_path(submission) %>" class="text-lg break-all peer">
<%= submitter.name || submitter.email || submitter.phone %>
</a>
<% if can?(:update, submitter) && !submitter.start_form_submission_events.any? && !submission.archived_at? && !submission.expired? %>
<span class="pl-0.5 tooltip tooltip-top md:opacity-0 md:hover:opacity-100 md:peer-hover:opacity-100" data-tip="<%= t('edit') %>">
<%= link_to edit_submitter_path(submitter), class: 'shrink-0', data: { turbo_frame: 'modal' } do %>
<%= svg_icon('pencil', class: 'w-5 h-5') %>
<% end %>
</span>
<% end %>
</span>
</span>
<% if submitter.completed_at? && !is_submission_completed %>
<form onsubmit="event.preventDefault()">
<button onclick="event.stopPropagation()">
<download-button data-src="<%= submitter_download_index_path(submitter.slug) %>" class="absolute md:relative top-0 right-0 btn btn-xs btn-neutral text-white md:w-36">
<span class="flex items-center justify-center space-x-1 md:space-x-2" data-target="download-button.defaultButton">
<%= svg_icon('download', class: 'w-4 h-4 stroke-2') %>
<span class="inline"><%= t('download') %></span>
</span>
<span class="flex items-center justify-center space-x-1 md:space-x-2 hidden" data-target="download-button.loadingButton">
<%= svg_icon('loader', class: 'w-4 h-4 animate-spin') %>
<span class="inline"><%= t('download')[..-2] %>...</span>
</span>
</download-button>
</button>
</form>
<download-button data-src="<%= submitter_download_index_path(submitter.slug) %>" class="absolute md:relative top-0 right-0 btn btn-xs btn-neutral text-white md:w-36 z-[1]">
<span class="flex items-center justify-center space-x-1 md:space-x-2" data-target="download-button.defaultButton">
<%= svg_icon('download', class: 'w-4 h-4 stroke-2') %>
<span class="inline"><%= t('download') %></span>
</span>
<span class="flex items-center justify-center space-x-1 md:space-x-2 hidden" data-target="download-button.loadingButton">
<%= svg_icon('loader', class: 'w-4 h-4 animate-spin') %>
<span class="inline"><%= t('download')[..-2] %>...</span>
</span>
</download-button>
<% elsif !template.archived_at? && !submission.archived_at? && !is_submission_completed && !submission.expired? && !submitter.declined_at? %>
<div class="relative flex items-center space-x-3">
<% if current_user.email == submitter.email %>
<form data-turbo="false" method="get" action="<%= submit_form_url(slug: submitter.slug) %>">
<button onclick="event.stopPropagation()" class="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">
<a href="<%= submit_form_url(slug: submitter.slug) %>" data-turbo="false" target="_blank" class="absolute md:relative top-0 right-0 btn btn-xs btn-outline btn-neutral bg-white w-28 md:w-36 z-[1]">
<span class="flex items-center justify-center space-x-1 md:space-x-2">
<% if t('sign_now').length < 12 %>
<%= svg_icon('writing_sign', class: 'w-4 h-4 stroke-2') %>
<span class="inline shrink-0"><%= t('sign_now') %></span>
</span>
</button>
</form>
<% end %>
<span class="inline shrink-0"><%= t('sign_now') %></span>
</span>
</a>
<% 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 z-[1]', 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 %>
</div>
<% end %>
@@ -167,40 +185,41 @@
</div>
</div>
</div>
<div class="flex space-x-1 md:space-x-2 items-center">
<div class="flex space-x-1 md:space-x-2 items-center z-[1]">
<% if is_submission_completed %>
<% latest_submitter = submitters.select(&:completed_at?).max_by(&:completed_at) %>
<div class="flex-1 md:flex-none">
<form onsubmit="event.preventDefault()" class="w-full md:w-fit">
<button onclick="event.stopPropagation()">
<download-button data-src="<%= submitter_download_index_path(latest_submitter.slug) %>" class="btn btn-sm btn-neutral text-white md:w-36">
<span class="flex items-center justify-center space-x-1 md:space-x-2" data-target="download-button.defaultButton">
<%= svg_icon('download', class: 'w-5 h-5 stroke-2') %>
<span class="inline"><%= t('download') %></span>
</span>
<span class="flex items-center justify-center space-x-1 md:space-x-2 hidden" data-target="download-button.loadingButton">
<%= svg_icon('loader', class: 'w-5 h-5 animate-spin') %>
<span class="inline"><%= t('download')[..-2] %>...</span>
</span>
</download-button>
</button>
</form>
<div class="w-full md:w-fit">
<download-button data-src="<%= submitter_download_index_path(latest_submitter.slug) %>" class="btn btn-sm btn-neutral text-white w-full md:w-36 z-[1]">
<span class="flex items-center justify-center space-x-1 md:space-x-2" data-target="download-button.defaultButton">
<%= svg_icon('download', class: 'w-5 h-5 stroke-2') %>
<span class="inline"><%= t('download') %></span>
</span>
<span class="flex items-center justify-center space-x-1 md:space-x-2 hidden" data-target="download-button.loadingButton">
<%= svg_icon('loader', class: 'w-5 h-5 animate-spin') %>
<span class="inline"><%= t('download')[..-2] %>...</span>
</span>
</download-button>
</div>
</div>
<% end %>
<div class="flex-1 md:flex-none">
<span class="btn btn-outline btn-sm w-full md:w-24"><%= t('view') %></span>
<div class="flex-1 md:flex-none z-[1]">
<a href="<%= submission_path(submission) %>" class="btn btn-outline btn-sm w-full md:w-24">
<%= t('view') %>
</a>
</div>
<% if !submission.archived_at? && !template.archived_at? %>
<span data-tip="<%= t('archive') %>" class="sm:tooltip tooltip-top">
<%= button_to button_title(title: nil, disabled_with: t(:archive).first(4), icon: svg_icon('archive', class: 'w-6 h-6')), submission_path(submission), class: 'btn btn-outline btn-sm w-full md:w-fit', form: { class: 'flex' }, title: t('archive'), method: :delete, onclick: 'event.stopPropagation()' %>
<%= button_to button_title(title: nil, disabled_with: t(:archive).first(4), icon: svg_icon('archive', class: 'w-6 h-6')), submission_path(submission), class: 'btn btn-outline btn-sm w-full md:w-fit', form: { class: 'flex' }, title: t('archive'), method: :delete %>
</span>
<% end %>
<% if local_assigns[:archived] && can?(:destroy, submission) %>
<span data-tip="<%= t('remove') %>" class="sm:tooltip tooltip-top">
<%= button_to button_title(title: nil, disabled_with: t(:remove).first(3), icon: svg_icon('trash', class: 'w-6 h-6')), submission_path(submission, permanently: true), class: 'btn btn-outline btn-sm w-full md:w-fit', form: { class: 'flex' }, title: t('remove'), method: :delete, data: { turbo_confirm: t('submission_deletion_is_irreversible_and_will_permanently_remove_all_associated_signed_documents_with_it_are_you_sure_') }, onclick: 'event.stopPropagation()' %>
<%= button_to button_title(title: nil, disabled_with: t(:remove).first(3), icon: svg_icon('trash', class: 'w-6 h-6')), submission_path(submission, permanently: true), class: 'btn btn-outline btn-sm w-full md:w-fit', form: { class: 'flex' }, title: t('remove'), method: :delete, data: { turbo_confirm: t('submission_deletion_is_irreversible_and_will_permanently_remove_all_associated_signed_documents_with_it_are_you_sure_') } %>
</span>
<% end %>
</div>
<% end %>
</a>
<a href="<%= submission_path(submission) %>" class="top-0 bottom-0 left-0 right-0 absolute"></a>
</div>
</div>
+2 -2
View File
@@ -46,11 +46,11 @@
<% end %>
</div>
<% end %>
<%= render 'shared/clipboard_copy', text: start_form_url(slug: @template.slug), class: 'absolute md:relative bottom-0 right-0 btn btn-xs md:btn-sm whitespace-nowrap btn-neutral text-white mt-1 px-2', icon_class: 'w-4 h-4 md:w-6 md:h-6 text-white', copy_title: 'Link', copied_title: 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>
<% end %>
</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) %>
<%= 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 %>
+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 %>
<%= hidden_field_tag :base_template_id, @base_template.id %>
<% 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">
<%= 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>
<% end %>
<div class="form-control mt-6">
@@ -23,7 +23,7 @@
</folder-autocomplete>
</div>
<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>
<% end %>
<% end %>
+1 -1
View File
@@ -63,7 +63,7 @@
</div>
<% else %>
<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">
<p class="text-3xl font-bold text-base-content mb-4">
<%= t('there_are_no_submissions') %>
+5 -3
View File
@@ -1,13 +1,15 @@
<% has_archived = current_account.templates.where.not(archived_at: nil).exists? %>
<% if Docuseal.demo? %><%= render 'shared/demo_alert' %><% end %>
<div class="flex justify-between mb-4 items-center">
<div class="flex items-center">
<div class="flex justify-between items-center w-full mb-4">
<div class="flex items-center flex-grow min-w-0">
<% if has_archived || @pagy.count > 0 %>
<div class="mr-2">
<%= render 'dashboard/toggle_view', selected: 'templates' %>
</div>
<% 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 class="flex space-x-2">
<% 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">
<input type="checkbox" name="accordion">
<div class="collapse-title text-xl font-medium">
<%= t('copleted_notification_email') %>
<%= t('completed_notification_email') %>
</div>
<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| %>
@@ -256,10 +256,20 @@
</div>
<% end %>
</div>
<div class="form-control mt-6 pb-2">
<%= f.button button_title(title: t('save'), disabled_with: t('updating')), class: 'base-button' %>
<% end %>
<%= 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>
<% 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>
<% end %>
<% if show_api %>
@@ -285,7 +295,7 @@
<%= render 'templates_code_modal/placeholder' %>
<%= render 'templates/embedding', template: @template %>
<% if can?(:manage, TemplateSharing.new(template: @template)) %>
<%= form_for '', url: template_sharings_testing_index_path, method: :post, html: { class: 'mt-1' } do |f| %>
<%= 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 %>
<div class="flex items-center justify-between">
<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| %>
<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">
<div class="absolute top-0 right-0 left-0 bottom-0 flex items-center justify-center">
<div class="flex flex-col items-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 text-center">
<span data-target="file-dropzone.icon">
<%= svg_icon('cloud_upload', class: 'w-10 h-10') %>
</span>
@@ -1,7 +1,7 @@
<p>Hello <%= @user.first_name %>,</p>
<p>You have been invited to <%= @user.account.name %> <%= Docuseal.product_name %>. Please sign up using the link below:</p>
<p><%= link_to '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><%= @user.first_name.present? ? t('hello_name', name: @user.first_name) : t('hi_there') %>,</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 t('sign_up'), invitation_url(reset_password_token: @token) %></p>
<p><%= t('please_contact_us_by_replying_to_this_email_if_you_didn_t_request_this') %></p>
<p>
Thanks,<br><%= @current_account.name %>
<%= t('thanks') %>,<br><%= @current_account.name %>
</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| %>
<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">
<div class="absolute top-0 right-0 left-0 bottom-0 flex items-center justify-center">
<div class="flex flex-col items-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 text-center">
<span data-target="file-dropzone.icon">
<%= svg_icon('cloud_upload', class: 'w-10 h-10') %>
</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">
<%= render 'shared/settings_nav' %>
<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">
<%= t('status_users', status: params[:status].to_s.titleize) %>
</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)) %>
<%= render 'users/extra_buttons' %>
<% if content_for(:add_user_button) %>
<%= content_for(:add_user_button) %>
<% 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') %>
<span><%= t('new_user') %></span>
<% 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">
<%= render 'shared/settings_nav' %>
<div class="flex-grow">
<div class="flex justify-between">
<h1 class="text-4xl font-bold mb-4">Webhooks</h1>
<div class="flex flex-col gap-2 md:flex-row md:justify-between md:items-end mb-4">
<h1 class="text-4xl font-bold">Webhooks</h1>
<%= render 'shared/test_mode_toggle' %>
</div>
<div class="card bg-base-200">
@@ -19,7 +19,7 @@
<% end %>
<% preference = current_account.account_configs.find_by(key: AccountConfig::WEBHOOK_PREFERENCES_KEY)&.value || {} %>
<% 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| %>
<%= form_for '', url: webhook_preferences_path, method: :post do |f| %>
<%= f.hidden_field :event, value: event %>
@@ -43,7 +43,7 @@
<div class="space-y-4 mt-4">
<div class="collapse collapse-open bg-base-200 px-1">
<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>
<%= t('submission_example_payload') %>
</span>
+1 -1
View File
@@ -17,7 +17,7 @@ Bundler.require(*Rails.groups)
module DocuSeal
class Application < Rails::Application
config.load_defaults 7.1
config.load_defaults 7.2
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),
user_name: ENV.fetch('SMTP_USERNAME', 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'
}.compact
end
+13
View File
@@ -1,6 +1,19 @@
# frozen_string_literal: true
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
class SecurityHandler
def encrypt_string(str, obj)
+1 -1
View File
@@ -1,6 +1,6 @@
# frozen_string_literal: true
Pagy::DEFAULT[:items] = 10
Pagy::DEFAULT[:limit] = 10
Pagy::DEFAULT.freeze
ActiveSupport.on_load(:action_view) do
Loaded 100 of 123 files, more files were not shown because too many files have changed in this diff. Show more