mirror of
https://github.com/docusealco/docuseal
synced 2026-11-04 14:42:32 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
05d578d88f | ||
|
|
8773b30b10 | ||
|
|
1b636c5878 | ||
|
|
46e948ab99 | ||
|
|
889103aa5e | ||
|
|
558a14a5f2 | ||
|
|
72bbdb38bf | ||
|
|
3975a03cb4 | ||
|
|
e398327fb7 | ||
|
|
9266694d65 | ||
|
|
350c4b1c2a | ||
|
|
c3a933d87d | ||
|
|
970e53a5a3 | ||
|
|
520bac2ec7 | ||
|
|
e1eddd31c4 | ||
|
|
3c616a824c | ||
|
|
90d86b58c2 | ||
|
|
4332b4ebb8 | ||
|
|
e2f930f6f7 | ||
|
|
c303159c63 | ||
|
|
c1ce2bec00 | ||
|
|
3d2046f227 | ||
|
|
babaf1aa48 | ||
|
|
3f03d343ca | ||
|
|
110cdb5123 | ||
|
|
440d1114be | ||
|
|
9032517d4e | ||
|
|
7c71fefc47 | ||
|
|
e8bd8ff76f | ||
|
|
fed420cae1 | ||
|
|
910490528d | ||
|
|
ab810be250 | ||
|
|
178ee47093 | ||
|
|
6f90147a11 | ||
|
|
3ebaacbb7a | ||
|
|
64b39b53a2 | ||
|
|
2948910c25 | ||
|
|
1d20c79cf4 | ||
|
|
48f3b13f3e | ||
|
|
1fc1e9af16 | ||
|
|
ce70016603 | ||
|
|
e0e79e343d | ||
|
|
6568f81ee9 | ||
|
|
386f130d7a | ||
|
|
3ee41975fd | ||
|
|
d9f314898b | ||
|
|
4fc7412cac | ||
|
|
9adbfc52c2 | ||
|
|
1ad8d9196a | ||
|
|
e9172392b5 | ||
|
|
d6d94de328 | ||
|
|
77261f457c | ||
|
|
c258915031 | ||
|
|
c1db8bdd3b | ||
|
|
c700484ce8 | ||
|
|
b62bd200ed | ||
|
|
a164fe8dd8 | ||
|
|
bb40e872b3 | ||
|
|
bab80e9488 | ||
|
|
d090d79ebf | ||
|
|
2299ba4e29 | ||
|
|
d6474f0e6e | ||
|
|
e0a297cf81 | ||
|
|
40f5fa4217 | ||
|
|
94e552d170 | ||
|
|
cf1b0a4c16 | ||
|
|
9aa0a8ecbf | ||
|
|
fd4bef1706 | ||
|
|
d32b0e6d20 | ||
|
|
4a846be214 | ||
|
|
3dd3877ea9 | ||
|
|
b4dba0d809 | ||
|
|
dea80a82d8 | ||
|
|
eefa994724 | ||
|
|
35cce24539 | ||
|
|
e6c8203959 | ||
|
|
9479771fc8 | ||
|
|
711427521c | ||
|
|
5dae742361 | ||
|
|
d8ccd4beee | ||
|
|
c532a0115d | ||
|
|
c9c3364764 | ||
|
|
1ff23d7c40 | ||
|
|
95e9b34033 | ||
|
|
6b42dcf91b | ||
|
|
28962c949b |
No files matched your search
+1
-1
@@ -66,7 +66,7 @@ RSpec/ExampleLength:
|
||||
Max: 40
|
||||
|
||||
RSpec/MultipleMemoizedHelpers:
|
||||
Max: 6
|
||||
Max: 9
|
||||
|
||||
Metrics/BlockNesting:
|
||||
Max: 4
|
||||
|
||||
+2
-2
@@ -41,7 +41,7 @@ ENV OPENSSL_CONF=/app/openssl_legacy.cnf
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN apk add --no-cache sqlite-dev libpq-dev mariadb-dev vips-dev vips-poppler poppler-utils redis vips-heif gcompat ttf-freefont && mkdir /fonts && rm /usr/share/fonts/freefont/FreeSans.otf
|
||||
RUN echo '@edge https://dl-cdn.alpinelinux.org/alpine/edge/community' >> /etc/apk/repositories && apk add --no-cache sqlite-dev libpq-dev mariadb-dev vips-dev vips-poppler poppler-utils redis libheif@edge vips-heif gcompat ttf-freefont && mkdir /fonts && rm /usr/share/fonts/freefont/FreeSans.otf
|
||||
|
||||
RUN echo $'.include = /etc/ssl/openssl.cnf\n\
|
||||
\n\
|
||||
@@ -57,7 +57,7 @@ activate = 1' >> /app/openssl_legacy.cnf
|
||||
|
||||
COPY ./Gemfile ./Gemfile.lock ./
|
||||
|
||||
RUN apk add --no-cache build-base && bundle install && apk del build-base && rm -rf ~/.bundle /usr/local/bundle/cache && ruby -e "puts Dir['/usr/local/bundle/**/{spec,rdoc,resources/shared,resources/collation,resources/locales}']" | xargs rm -rf
|
||||
RUN apk add --no-cache build-base && bundle install && apk del --no-cache build-base && rm -rf ~/.bundle /usr/local/bundle/cache && ruby -e "puts Dir['/usr/local/bundle/**/{spec,rdoc,resources/shared,resources/collation,resources/locales}']" | xargs rm -rf
|
||||
|
||||
COPY ./bin ./bin
|
||||
COPY ./app ./app
|
||||
|
||||
@@ -23,7 +23,6 @@ gem 'image_processing'
|
||||
gem 'jwt'
|
||||
gem 'lograge'
|
||||
gem 'mysql2', require: false
|
||||
gem 'net-smtp', '0.4.0'
|
||||
gem 'oj'
|
||||
gem 'pagy'
|
||||
gem 'pg', require: false
|
||||
@@ -40,7 +39,7 @@ gem 'ruby-vips'
|
||||
gem 'rubyXL'
|
||||
gem 'shakapacker'
|
||||
gem 'sidekiq'
|
||||
gem 'sqlite3', require: false, force_ruby_platform: true
|
||||
gem 'sqlite3', require: false
|
||||
gem 'strip_attributes'
|
||||
gem 'turbo-rails'
|
||||
gem 'twitter_cldr', require: false
|
||||
|
||||
+169
-163
@@ -1,105 +1,101 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
actioncable (7.1.3.4)
|
||||
actionpack (= 7.1.3.4)
|
||||
activesupport (= 7.1.3.4)
|
||||
actioncable (7.2.1.1)
|
||||
actionpack (= 7.2.1.1)
|
||||
activesupport (= 7.2.1.1)
|
||||
nio4r (~> 2.0)
|
||||
websocket-driver (>= 0.6.1)
|
||||
zeitwerk (~> 2.6)
|
||||
actionmailbox (7.1.3.4)
|
||||
actionpack (= 7.1.3.4)
|
||||
activejob (= 7.1.3.4)
|
||||
activerecord (= 7.1.3.4)
|
||||
activestorage (= 7.1.3.4)
|
||||
activesupport (= 7.1.3.4)
|
||||
mail (>= 2.7.1)
|
||||
net-imap
|
||||
net-pop
|
||||
net-smtp
|
||||
actionmailer (7.1.3.4)
|
||||
actionpack (= 7.1.3.4)
|
||||
actionview (= 7.1.3.4)
|
||||
activejob (= 7.1.3.4)
|
||||
activesupport (= 7.1.3.4)
|
||||
mail (~> 2.5, >= 2.5.4)
|
||||
net-imap
|
||||
net-pop
|
||||
net-smtp
|
||||
actionmailbox (7.2.1.1)
|
||||
actionpack (= 7.2.1.1)
|
||||
activejob (= 7.2.1.1)
|
||||
activerecord (= 7.2.1.1)
|
||||
activestorage (= 7.2.1.1)
|
||||
activesupport (= 7.2.1.1)
|
||||
mail (>= 2.8.0)
|
||||
actionmailer (7.2.1.1)
|
||||
actionpack (= 7.2.1.1)
|
||||
actionview (= 7.2.1.1)
|
||||
activejob (= 7.2.1.1)
|
||||
activesupport (= 7.2.1.1)
|
||||
mail (>= 2.8.0)
|
||||
rails-dom-testing (~> 2.2)
|
||||
actionpack (7.1.3.4)
|
||||
actionview (= 7.1.3.4)
|
||||
activesupport (= 7.1.3.4)
|
||||
actionpack (7.2.1.1)
|
||||
actionview (= 7.2.1.1)
|
||||
activesupport (= 7.2.1.1)
|
||||
nokogiri (>= 1.8.5)
|
||||
racc
|
||||
rack (>= 2.2.4)
|
||||
rack (>= 2.2.4, < 3.2)
|
||||
rack-session (>= 1.0.1)
|
||||
rack-test (>= 0.6.3)
|
||||
rails-dom-testing (~> 2.2)
|
||||
rails-html-sanitizer (~> 1.6)
|
||||
actiontext (7.1.3.4)
|
||||
actionpack (= 7.1.3.4)
|
||||
activerecord (= 7.1.3.4)
|
||||
activestorage (= 7.1.3.4)
|
||||
activesupport (= 7.1.3.4)
|
||||
useragent (~> 0.16)
|
||||
actiontext (7.2.1.1)
|
||||
actionpack (= 7.2.1.1)
|
||||
activerecord (= 7.2.1.1)
|
||||
activestorage (= 7.2.1.1)
|
||||
activesupport (= 7.2.1.1)
|
||||
globalid (>= 0.6.0)
|
||||
nokogiri (>= 1.8.5)
|
||||
actionview (7.1.3.4)
|
||||
activesupport (= 7.1.3.4)
|
||||
actionview (7.2.1.1)
|
||||
activesupport (= 7.2.1.1)
|
||||
builder (~> 3.1)
|
||||
erubi (~> 1.11)
|
||||
rails-dom-testing (~> 2.2)
|
||||
rails-html-sanitizer (~> 1.6)
|
||||
activejob (7.1.3.4)
|
||||
activesupport (= 7.1.3.4)
|
||||
activejob (7.2.1.1)
|
||||
activesupport (= 7.2.1.1)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (7.1.3.4)
|
||||
activesupport (= 7.1.3.4)
|
||||
activerecord (7.1.3.4)
|
||||
activemodel (= 7.1.3.4)
|
||||
activesupport (= 7.1.3.4)
|
||||
activemodel (7.2.1.1)
|
||||
activesupport (= 7.2.1.1)
|
||||
activerecord (7.2.1.1)
|
||||
activemodel (= 7.2.1.1)
|
||||
activesupport (= 7.2.1.1)
|
||||
timeout (>= 0.4.0)
|
||||
activestorage (7.1.3.4)
|
||||
actionpack (= 7.1.3.4)
|
||||
activejob (= 7.1.3.4)
|
||||
activerecord (= 7.1.3.4)
|
||||
activesupport (= 7.1.3.4)
|
||||
activestorage (7.2.1.1)
|
||||
actionpack (= 7.2.1.1)
|
||||
activejob (= 7.2.1.1)
|
||||
activerecord (= 7.2.1.1)
|
||||
activesupport (= 7.2.1.1)
|
||||
marcel (~> 1.0)
|
||||
activesupport (7.1.3.4)
|
||||
activesupport (7.2.1.1)
|
||||
base64
|
||||
bigdecimal
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
concurrent-ruby (~> 1.0, >= 1.3.1)
|
||||
connection_pool (>= 2.2.5)
|
||||
drb
|
||||
i18n (>= 1.6, < 2)
|
||||
logger (>= 1.4.2)
|
||||
minitest (>= 5.1)
|
||||
mutex_m
|
||||
tzinfo (~> 2.0)
|
||||
addressable (2.8.6)
|
||||
public_suffix (>= 2.0.2, < 6.0)
|
||||
securerandom (>= 0.3)
|
||||
tzinfo (~> 2.0, >= 2.0.5)
|
||||
addressable (2.8.7)
|
||||
public_suffix (>= 2.0.2, < 7.0)
|
||||
annotate (3.2.0)
|
||||
activerecord (>= 3.2, < 8.0)
|
||||
rake (>= 10.4, < 14.0)
|
||||
arabic-letter-connector (0.1.1)
|
||||
ast (2.4.2)
|
||||
aws-eventstream (1.3.0)
|
||||
aws-partitions (1.913.0)
|
||||
aws-sdk-core (3.191.6)
|
||||
aws-partitions (1.985.0)
|
||||
aws-sdk-core (3.209.1)
|
||||
aws-eventstream (~> 1, >= 1.3.0)
|
||||
aws-partitions (~> 1, >= 1.651.0)
|
||||
aws-sigv4 (~> 1.8)
|
||||
aws-sigv4 (~> 1.9)
|
||||
jmespath (~> 1, >= 1.6.1)
|
||||
aws-sdk-kms (1.79.0)
|
||||
aws-sdk-core (~> 3, >= 3.191.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-s3 (1.146.1)
|
||||
aws-sdk-core (~> 3, >= 3.191.0)
|
||||
aws-sdk-kms (1.94.0)
|
||||
aws-sdk-core (~> 3, >= 3.207.0)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sdk-s3 (1.167.0)
|
||||
aws-sdk-core (~> 3, >= 3.207.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.8)
|
||||
aws-sdk-secretsmanager (1.91.0)
|
||||
aws-sdk-core (~> 3, >= 3.191.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sigv4 (1.8.0)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sdk-secretsmanager (1.108.0)
|
||||
aws-sdk-core (~> 3, >= 3.207.0)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sigv4 (1.10.0)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
azure-storage-blob (2.0.3)
|
||||
azure-storage-common (~> 2.0)
|
||||
@@ -120,14 +116,14 @@ GEM
|
||||
smart_properties
|
||||
bigdecimal (3.1.8)
|
||||
bindex (0.8.1)
|
||||
bootsnap (1.18.3)
|
||||
bootsnap (1.18.4)
|
||||
msgpack (~> 1.2)
|
||||
builder (3.3.0)
|
||||
bullet (7.1.6)
|
||||
bullet (7.2.0)
|
||||
activesupport (>= 3.0.0)
|
||||
uniform_notifier (~> 1.11)
|
||||
camertron-eprun (1.1.1)
|
||||
cancancan (3.5.0)
|
||||
cancancan (3.6.1)
|
||||
capybara (3.40.0)
|
||||
addressable
|
||||
matrix
|
||||
@@ -137,7 +133,8 @@ GEM
|
||||
rack-test (>= 0.6.3)
|
||||
regexp_parser (>= 1.5, < 3.0)
|
||||
xpath (~> 3.2)
|
||||
childprocess (5.0.0)
|
||||
childprocess (5.1.0)
|
||||
logger (~> 1.5)
|
||||
chunky_png (1.4.0)
|
||||
cldr-plurals-runtime-rb (1.1.0)
|
||||
cmdparse (3.0.7)
|
||||
@@ -148,12 +145,12 @@ GEM
|
||||
bigdecimal
|
||||
rexml
|
||||
crass (1.0.6)
|
||||
css_parser (1.17.1)
|
||||
css_parser (1.19.0)
|
||||
addressable
|
||||
csv (3.3.0)
|
||||
cuprite (0.15)
|
||||
cuprite (0.15.1)
|
||||
capybara (~> 3.0)
|
||||
ferrum (~> 0.14.0)
|
||||
ferrum (~> 0.15.0)
|
||||
date (3.3.4)
|
||||
debug (1.9.2)
|
||||
irb (~> 1.10)
|
||||
@@ -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.17)
|
||||
date
|
||||
net-protocol
|
||||
net-pop (0.1.2)
|
||||
net-protocol
|
||||
net-protocol (0.2.2)
|
||||
timeout
|
||||
net-smtp (0.4.0)
|
||||
net-smtp (0.5.0)
|
||||
net-protocol
|
||||
nio4r (2.7.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)
|
||||
pagy (9.1.0)
|
||||
parallel (1.26.3)
|
||||
parser (3.3.5.0)
|
||||
ast (~> 2.4.1)
|
||||
racc
|
||||
pg (1.5.6)
|
||||
premailer (1.23.0)
|
||||
pg (1.5.8)
|
||||
premailer (1.27.0)
|
||||
addressable
|
||||
css_parser (>= 1.12.0)
|
||||
css_parser (>= 1.19.0)
|
||||
htmlentities (>= 4.0.0)
|
||||
premailer-rails (1.12.0)
|
||||
actionmailer (>= 3)
|
||||
@@ -362,15 +364,15 @@ GEM
|
||||
pry (0.14.2)
|
||||
coderay (~> 1.1)
|
||||
method_source (~> 1.0)
|
||||
pry-rails (0.3.9)
|
||||
pry (>= 0.10.4)
|
||||
pry-rails (0.3.11)
|
||||
pry (>= 0.13.0)
|
||||
psych (5.1.2)
|
||||
stringio
|
||||
public_suffix (5.0.5)
|
||||
public_suffix (6.0.1)
|
||||
puma (6.4.3)
|
||||
nio4r (~> 2.0)
|
||||
racc (1.8.1)
|
||||
rack (3.1.7)
|
||||
rack (3.1.8)
|
||||
rack-proxy (0.7.7)
|
||||
rack
|
||||
rack-session (2.0.0)
|
||||
@@ -380,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.1)
|
||||
actioncable (= 7.2.1.1)
|
||||
actionmailbox (= 7.2.1.1)
|
||||
actionmailer (= 7.2.1.1)
|
||||
actionpack (= 7.2.1.1)
|
||||
actiontext (= 7.2.1.1)
|
||||
actionview (= 7.2.1.1)
|
||||
activejob (= 7.2.1.1)
|
||||
activemodel (= 7.2.1.1)
|
||||
activerecord (= 7.2.1.1)
|
||||
activestorage (= 7.2.1.1)
|
||||
activesupport (= 7.2.1.1)
|
||||
bundler (>= 1.15.0)
|
||||
railties (= 7.1.3.4)
|
||||
railties (= 7.2.1.1)
|
||||
rails-dom-testing (2.2.0)
|
||||
activesupport (>= 5.0.0)
|
||||
minitest
|
||||
@@ -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.1)
|
||||
actionpack (= 7.2.1.1)
|
||||
activesupport (= 7.2.1.1)
|
||||
irb (~> 1.13)
|
||||
rackup (>= 1.0.0)
|
||||
rake (>= 12.2)
|
||||
thor (~> 1.0, >= 1.2.2)
|
||||
@@ -429,7 +431,7 @@ 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)
|
||||
@@ -441,18 +443,18 @@ GEM
|
||||
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)
|
||||
@@ -470,36 +472,39 @@ GEM
|
||||
unicode-display_width (>= 2.4.0, < 3.0)
|
||||
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)
|
||||
@@ -509,32 +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)
|
||||
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.6.0)
|
||||
uniform_notifier (1.16.0)
|
||||
useragent (0.16.10)
|
||||
warden (1.2.9)
|
||||
rack (>= 2.0.9)
|
||||
web-console (4.2.1)
|
||||
@@ -542,17 +549,17 @@ GEM
|
||||
activemodel (>= 6.0.0)
|
||||
bindex (>= 0.4.0)
|
||||
railties (>= 6.0.0)
|
||||
webmock (3.23.0)
|
||||
webmock (3.24.0)
|
||||
addressable (>= 2.8.0)
|
||||
crack (>= 0.3.2)
|
||||
hashdiff (>= 0.4.0, < 2.0.0)
|
||||
webrick (1.8.1)
|
||||
webrick (1.8.2)
|
||||
websocket-driver (0.7.6)
|
||||
websocket-extensions (>= 0.1.0)
|
||||
websocket-extensions (0.1.5)
|
||||
xpath (3.2.0)
|
||||
nokogiri (~> 1.8)
|
||||
zeitwerk (2.6.18)
|
||||
zeitwerk (2.7.0)
|
||||
|
||||
PLATFORMS
|
||||
aarch64-linux
|
||||
@@ -589,7 +596,6 @@ DEPENDENCIES
|
||||
letter_opener_web
|
||||
lograge
|
||||
mysql2
|
||||
net-smtp (= 0.4.0)
|
||||
oj
|
||||
pagy
|
||||
pg
|
||||
|
||||
@@ -25,12 +25,12 @@
|
||||
DocuSeal is an open source platform that provides secure and efficient digital document signing and processing. Create PDF forms to have them filled and signed online on any device with an easy-to-use, mobile-optimized web tool.
|
||||
</p>
|
||||
<h2 align="center">
|
||||
<a href="https://demo.docuseal.co">✨ Live Demo</a>
|
||||
<a href="https://demo.docuseal.tech">✨ Live Demo</a>
|
||||
<span>|</span>
|
||||
<a href="https://docuseal.co/sign_up">☁️ Try in Cloud</a>
|
||||
<a href="https://docuseal.com/sign_up">☁️ Try in Cloud</a>
|
||||
</h2>
|
||||
|
||||
[](https://demo.docuseal.co)
|
||||
[](https://demo.docuseal.tech)
|
||||
|
||||
## Features
|
||||
- PDF form fields builder (WYSIWYG)
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
# Reporting a Vulnerability
|
||||
|
||||
If you come across any security concern or vulnarability, please report the information via email to security@docuseal.co instead of opening a GitHub issue. We will promptly respond and will collaborate with you to validate the issue, and resolve it ASAP.
|
||||
If you come across any security concern or vulnarability, please report the information via email to security@docuseal.com instead of opening a GitHub issue. We will promptly respond and will collaborate with you to validate the issue, and resolve it ASAP.
|
||||
|
||||
**We have a bug bounty program to reward security researchers.**
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ class AccountConfigsController < ApplicationController
|
||||
AccountConfig::ALLOW_TYPED_SIGNATURE,
|
||||
AccountConfig::FORCE_MFA,
|
||||
AccountConfig::ALLOW_TO_RESUBMIT,
|
||||
AccountConfig::ALLOW_TO_DECLINE_KEY,
|
||||
AccountConfig::FORM_PREFILL_SIGNATURE_KEY,
|
||||
AccountConfig::ESIGNING_PREFERENCE_KEY,
|
||||
AccountConfig::FORM_WITH_CONFETTI_KEY,
|
||||
|
||||
@@ -38,12 +38,12 @@ module Api
|
||||
private
|
||||
|
||||
def authorization_check!(blob)
|
||||
is_authorized =
|
||||
blob.attachments.all? do |a|
|
||||
a.name.in?(%w[logo preview_images]) ||
|
||||
(current_user && a.record.account.id == current_user.account_id) ||
|
||||
!a.record.account.account_configs.find_or_initialize_by(key: AccountConfig::DOWNLOAD_LINKS_AUTH_KEY).value
|
||||
end
|
||||
attachment = blob.attachments.take
|
||||
|
||||
is_authorized = attachment.name.in?(%w[logo preview_images]) ||
|
||||
(current_user && attachment.record.account.id == current_user.account_id) ||
|
||||
!attachment.record.account.account_configs
|
||||
.find_or_initialize_by(key: AccountConfig::DOWNLOAD_LINKS_AUTH_KEY).value
|
||||
|
||||
return if is_authorized
|
||||
|
||||
|
||||
@@ -25,9 +25,9 @@ module Api
|
||||
render json: { error: 'Too many requests' }, status: :too_many_requests
|
||||
end
|
||||
|
||||
if Rails.env.production?
|
||||
unless Rails.env.development?
|
||||
rescue_from CanCan::AccessDenied do |e|
|
||||
render json: { error: e.message }, status: :forbidden
|
||||
render json: { error: access_denied_error_message(e) }, status: :forbidden
|
||||
end
|
||||
|
||||
rescue_from JSON::ParserError do |e|
|
||||
@@ -39,6 +39,33 @@ module Api
|
||||
|
||||
private
|
||||
|
||||
def access_denied_error_message(error)
|
||||
return 'Not authorized' if request.headers['X-Auth-Token'].blank?
|
||||
return 'Not authorized' unless error.subject.is_a?(ActiveRecord::Base)
|
||||
return 'Not authorized' unless error.subject.respond_to?(:account_id)
|
||||
|
||||
linked_account_record_exists =
|
||||
if current_user.account.testing?
|
||||
current_user.account.linked_account_accounts.where(account_type: 'testing')
|
||||
.exists?(account_id: error.subject.account_id)
|
||||
else
|
||||
current_user.account.testing_accounts.exists?(id: error.subject.account_id)
|
||||
end
|
||||
|
||||
return 'Not authorized' unless linked_account_record_exists
|
||||
|
||||
object_name = error.subject.model_name.human
|
||||
id = error.subject.id
|
||||
|
||||
if current_user.account.testing?
|
||||
"#{object_name} #{id} not found using testing API key; Use production API key to " \
|
||||
"access production #{object_name.downcase.pluralize}."
|
||||
else
|
||||
"#{object_name} #{id} not found using production API key; Use testing API key to " \
|
||||
"access testing #{object_name.downcase.pluralize}."
|
||||
end
|
||||
end
|
||||
|
||||
def paginate(relation, field: :id)
|
||||
result = relation.order(field => :desc)
|
||||
.limit([params.fetch(:limit, DEFAULT_LIMIT).to_i, MAX_LIMIT].min)
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Api
|
||||
class SubmissionDocumentsController < ApiBaseController
|
||||
load_and_authorize_resource :submission
|
||||
|
||||
def index
|
||||
documents =
|
||||
if @submission.submitters.all?(&:completed_at?)
|
||||
last_submitter = @submission.submitters.max_by(&:completed_at)
|
||||
|
||||
if last_submitter.documents_attachments.blank?
|
||||
last_submitter.documents_attachments = Submissions::EnsureResultGenerated.call(submitter)
|
||||
end
|
||||
|
||||
last_submitter.documents_attachments
|
||||
else
|
||||
values_hash = Submissions::GeneratePreviewAttachments.build_values_hash(@submission)
|
||||
|
||||
if @submission.preview_documents.present? &&
|
||||
@submission.preview_documents.all? { |s| s.metadata['values_hash'] == values_hash }
|
||||
@submission.preview_documents
|
||||
else
|
||||
ApplicationRecord.no_touching do
|
||||
@submission.preview_documents.each(&:destroy)
|
||||
end
|
||||
|
||||
Submissions::GeneratePreviewAttachments.call(@submission, values_hash:)
|
||||
end
|
||||
end
|
||||
|
||||
ActiveRecord::Associations::Preloader.new(
|
||||
records: documents,
|
||||
associations: [:blob]
|
||||
).call
|
||||
|
||||
render json: {
|
||||
id: @submission.id,
|
||||
documents: documents.map do |attachment|
|
||||
{ name: attachment.filename.base, url: ActiveStorage::Blob.proxy_url(attachment.blob) }
|
||||
end
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -74,13 +74,14 @@ module Api
|
||||
Submissions.send_signature_requests(submissions)
|
||||
|
||||
submissions.each do |submission|
|
||||
if submission.submitters.all?(&:completed_at?) && submission.submitters.last
|
||||
ProcessSubmitterCompletionJob.perform_async({ 'submitter_id' => submission.submitters.last.id })
|
||||
submission.submitters.each do |submitter|
|
||||
ProcessSubmitterCompletionJob.perform_async({ 'submitter_id' => submitter.id }) if submitter.completed_at?
|
||||
end
|
||||
end
|
||||
|
||||
render json: build_create_json(submissions)
|
||||
rescue Submitters::NormalizeValues::BaseError, DownloadUtils::UnableToDownload => e
|
||||
rescue Submitters::NormalizeValues::BaseError, Submissions::CreateFromSubmitters::BaseError,
|
||||
DownloadUtils::UnableToDownload => e
|
||||
Rollbar.warning(e) if defined?(Rollbar)
|
||||
|
||||
render json: { error: e.message }, status: :unprocessable_entity
|
||||
|
||||
@@ -120,6 +120,7 @@ module Api
|
||||
end&.dig('uuid')
|
||||
|
||||
submitter.email = Submissions.normalize_email(attrs[:email]) if attrs.key?(:email)
|
||||
submitter.name = attrs[:name] if attrs.key?(:name)
|
||||
|
||||
if attrs.key?(:phone)
|
||||
submitter.phone = attrs[:phone].to_s.gsub(/[^0-9+]/, '')
|
||||
|
||||
@@ -20,10 +20,7 @@ module Api
|
||||
pdf = HexaPDF::Document.new(io: StringIO.new(file))
|
||||
|
||||
trusted_certs = Accounts.load_trusted_certs(current_account)
|
||||
|
||||
is_checksum_found = ActiveStorage::Attachment.joins(:blob)
|
||||
.where(name: 'documents', record_type: 'Submitter')
|
||||
.exists?(blob: { checksum: Digest::MD5.base64digest(file) })
|
||||
is_checksum_found = CompletedDocument.exists?(sha256: Base64.urlsafe_encode64(Digest::SHA256.digest(file)))
|
||||
|
||||
render json: {
|
||||
checksum_status: is_checksum_found ? 'verified' : 'not_found',
|
||||
|
||||
@@ -38,6 +38,10 @@ class ApplicationController < ActionController::Base
|
||||
end
|
||||
|
||||
def default_url_options
|
||||
if request.domain == 'docuseal.com'
|
||||
return { host: 'docuseal.com', protocol: ENV['FORCE_SSL'].present? ? 'https' : 'http' }
|
||||
end
|
||||
|
||||
Docuseal.default_url_options
|
||||
end
|
||||
|
||||
@@ -62,7 +66,7 @@ class ApplicationController < ActionController::Base
|
||||
end
|
||||
|
||||
def with_browser_locale(&)
|
||||
return yield if I18n.locale != :'en-US'
|
||||
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
|
||||
|
||||
@@ -9,7 +9,7 @@ class EmailSmtpSettingsController < ApplicationController
|
||||
|
||||
def create
|
||||
if @encrypted_config.update(email_configs)
|
||||
SettingsMailer.smtp_successful_setup(@encrypted_config.value['from_email']).deliver_now!
|
||||
SettingsMailer.smtp_successful_setup(@encrypted_config.value['from_email'] || current_user.email).deliver_now!
|
||||
|
||||
redirect_to settings_email_index_path, notice: I18n.t('changes_have_been_saved')
|
||||
else
|
||||
|
||||
@@ -87,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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -9,9 +9,11 @@ class UsersController < ApplicationController
|
||||
def index
|
||||
@users =
|
||||
if params[:status] == 'archived'
|
||||
@users.archived
|
||||
@users.archived.where.not(role: 'integration')
|
||||
elsif params[:status] == 'integration'
|
||||
@users.active.where(role: 'integration')
|
||||
else
|
||||
@users.active
|
||||
@users.active.where.not(role: 'integration')
|
||||
end
|
||||
|
||||
@pagy, @users = pagy(@users.where(account: current_account).order(id: :desc))
|
||||
|
||||
@@ -29,6 +29,7 @@ import SearchInput from './elements/search_input'
|
||||
import ToggleAttribute from './elements/toggle_attribute'
|
||||
import LinkedInput from './elements/linked_input'
|
||||
import CheckboxGroup from './elements/checkbox_group'
|
||||
import MaskedInput from './elements/masked_input'
|
||||
|
||||
import * as TurboInstantClick from './lib/turbo_instant_click'
|
||||
|
||||
@@ -95,6 +96,7 @@ safeRegisterElement('search-input', SearchInput)
|
||||
safeRegisterElement('toggle-attribute', ToggleAttribute)
|
||||
safeRegisterElement('linked-input', LinkedInput)
|
||||
safeRegisterElement('checkbox-group', CheckboxGroup)
|
||||
safeRegisterElement('masked-input', MaskedInput)
|
||||
|
||||
safeRegisterElement('template-builder', class extends HTMLElement {
|
||||
connectedCallback () {
|
||||
|
||||
@@ -130,7 +130,3 @@ button[disabled] .enabled {
|
||||
outline-offset: 3px;
|
||||
outline-color: hsl(var(--bc) / 0.2);
|
||||
}
|
||||
|
||||
select:required:invalid {
|
||||
color: gray !important;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
export default class extends HTMLElement {
|
||||
connectedCallback () {
|
||||
const maskedToken = this.input.value
|
||||
|
||||
this.input.addEventListener('focus', () => {
|
||||
this.input.value = this.dataset.token
|
||||
this.input.select()
|
||||
})
|
||||
|
||||
this.input.addEventListener('focusout', () => {
|
||||
this.input.value = maskedToken
|
||||
})
|
||||
}
|
||||
|
||||
get input () {
|
||||
return this.querySelector('input')
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@ safeRegisterElement('submission-form', class extends HTMLElement {
|
||||
requireSigningReason: this.dataset.requireSigningReason === 'true',
|
||||
withConfetti: this.dataset.withConfetti !== 'false',
|
||||
withDisclosure: this.dataset.withDisclosure === 'true',
|
||||
reuseSignature: this.dataset.reuseSignature !== 'false',
|
||||
withTypedSignature: this.dataset.withTypedSignature !== 'false',
|
||||
authenticityToken: document.querySelector('meta[name="csrf-token"]')?.content,
|
||||
rememberSignature: this.dataset.rememberSignature === 'true',
|
||||
|
||||
@@ -31,10 +31,6 @@ button[disabled] .enabled {
|
||||
display: none;
|
||||
}
|
||||
|
||||
select:required:invalid {
|
||||
@apply text-gray-300;
|
||||
}
|
||||
|
||||
.input-bordered {
|
||||
@apply border-base-content/20;
|
||||
}
|
||||
|
||||
@@ -405,7 +405,8 @@ export default {
|
||||
}
|
||||
|
||||
if (this.field.preferences?.font_size) {
|
||||
style.fontSize = this.field.preferences.font_size + 'pt'
|
||||
style.fontSize = `clamp(4pt, 1.6vw, ${this.field.preferences.font_size}pt)`
|
||||
style.lineHeight = `clamp(6pt, 2.0vw, ${parseInt(this.field.preferences.font_size) + 3}pt)`
|
||||
}
|
||||
|
||||
return style
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<div
|
||||
id="form_completed"
|
||||
class="mx-auto max-w-md flex flex-col"
|
||||
dir="auto"
|
||||
>
|
||||
@@ -75,7 +76,7 @@
|
||||
</a>
|
||||
<a
|
||||
v-if="isDemo"
|
||||
href="https://docuseal.co/sign_up"
|
||||
href="https://docuseal.com/sign_up"
|
||||
class="white-button flex items-center space-x-1 w-full"
|
||||
>
|
||||
<IconLogin />
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
:name="`values[${field.uuid}]`"
|
||||
@keydown.enter="onEnter"
|
||||
@focus="$emit('focus')"
|
||||
@paste="onPaste"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
@@ -98,6 +99,25 @@ export default {
|
||||
this.$emit('submit')
|
||||
}
|
||||
},
|
||||
onPaste (e) {
|
||||
e.preventDefault()
|
||||
|
||||
let pasteData = e.clipboardData.getData('text').trim()
|
||||
|
||||
if (pasteData.match(/^\d{2}\.\d{2}\.\d{4}$/)) {
|
||||
pasteData = pasteData.split('.').reverse().join('-')
|
||||
}
|
||||
|
||||
const parsedDate = new Date(pasteData)
|
||||
|
||||
if (!isNaN(parsedDate)) {
|
||||
const inputEl = this.$refs.input
|
||||
|
||||
inputEl.valueAsDate = new Date(parsedDate.getTime() - parsedDate.getTimezoneOffset() * 60000)
|
||||
|
||||
inputEl.dispatchEvent(new Event('input', { bubbles: true }))
|
||||
}
|
||||
},
|
||||
setCurrentDate () {
|
||||
const inputEl = this.$refs.input
|
||||
|
||||
|
||||
@@ -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) {
|
||||
@@ -1104,6 +1107,10 @@ export default {
|
||||
const currentFieldUuids = this.currentStepFields.map((f) => f.uuid)
|
||||
const currentFieldType = this.currentField.type
|
||||
|
||||
if (!formData && !this.$refs.form.checkValidity()) {
|
||||
return
|
||||
}
|
||||
|
||||
if (this.dryRun) {
|
||||
currentFieldUuids.forEach((fieldUuid) => {
|
||||
this.submittedValues[fieldUuid] = this.values[fieldUuid]
|
||||
@@ -1142,13 +1149,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 +1167,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,7 +1192,7 @@ export default {
|
||||
return Promise.reject(new Error(data.error))
|
||||
}
|
||||
|
||||
const nextStep = (isLastStep && emptyRequiredField) || this.stepFields[this.currentStep + 1]
|
||||
const nextStep = (isLastStep && emptyRequiredField) || this.stepFields[submitStep + 1]
|
||||
|
||||
if (nextStep) {
|
||||
if (this.alwaysMinimize) {
|
||||
|
||||
@@ -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
@@ -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>
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
@@ -693,6 +693,7 @@ export default {
|
||||
'de-DE': 'DD.MM.YYYY',
|
||||
'fr-FR': 'DD/MM/YYYY',
|
||||
'it-IT': 'DD/MM/YYYY',
|
||||
'en-GB': 'DD/MM/YYYY',
|
||||
'es-ES': 'DD/MM/YYYY'
|
||||
}
|
||||
},
|
||||
@@ -1355,7 +1356,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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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) }}
|
||||
@@ -88,8 +90,9 @@
|
||||
</option>
|
||||
</select>
|
||||
<select
|
||||
v-if="conditionField(condition)?.options?.length"
|
||||
v-if="['radio', 'select', 'multiple'].includes(conditionField(condition)?.type) && conditionField(condition)?.options"
|
||||
class="select select-bordered select-sm w-full bg-white h-11 pl-4 text-base font-normal"
|
||||
:class="{ 'text-gray-300': !condition.value }"
|
||||
required
|
||||
@change="condition.value = $event.target.value"
|
||||
>
|
||||
@@ -105,6 +108,7 @@
|
||||
:key="option.uuid"
|
||||
:value="option.uuid"
|
||||
:selected="condition.value === option.uuid"
|
||||
class="text-base-content"
|
||||
>
|
||||
{{ option.value || `${t('option')} ${index + 1}` }}
|
||||
</option>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,8 +103,21 @@ 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',
|
||||
@@ -138,6 +153,8 @@ const en = {
|
||||
}
|
||||
|
||||
const es = {
|
||||
recurrent: 'Recurrente',
|
||||
one_off: 'Único',
|
||||
editable: 'Editable',
|
||||
search_field: 'Campo de búsqueda',
|
||||
field_not_found: 'Campo no encontrado',
|
||||
@@ -176,7 +193,7 @@ const es = {
|
||||
up: 'Arriba',
|
||||
down: 'Abajo',
|
||||
set_signing_date: 'Establecer fecha de firma',
|
||||
are_you_sure: '¿Estás seguro?',
|
||||
are_you_sure_: '¿Estás seguro?',
|
||||
sign_yourself: 'Firma tú mismo',
|
||||
checked: 'Seleccionado',
|
||||
send: 'Enviar',
|
||||
@@ -184,7 +201,7 @@ const es = {
|
||||
save: 'Guardar',
|
||||
cancel: 'Cancelar',
|
||||
or_add_field_without_drawing: 'O añadir campo sin dibujar',
|
||||
draw_field_on_the_document: 'Dibujar campo {field} en el documento',
|
||||
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',
|
||||
@@ -232,13 +249,26 @@ const es = {
|
||||
select: 'Seleccionar',
|
||||
checkbox: 'Casilla',
|
||||
multiple: 'Múltiple',
|
||||
field: 'Campo',
|
||||
group: 'Grupo',
|
||||
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',
|
||||
@@ -277,6 +307,8 @@ const es = {
|
||||
}
|
||||
|
||||
const it = {
|
||||
ricorrente: 'Ricorrente',
|
||||
una_volta: 'Una volta',
|
||||
editable: 'Modificabile',
|
||||
search_field: 'Campo di ricerca',
|
||||
field_not_found: 'Campo non trovato',
|
||||
@@ -307,7 +339,7 @@ const it = {
|
||||
select_value_: 'Seleziona valore...',
|
||||
remove_condition: 'Rimuovi condizione',
|
||||
add_condition: 'Aggiungi condizione',
|
||||
are_you_sure: 'Sei sicuro?',
|
||||
are_you_sure_: 'Sei sicuro?',
|
||||
sign_yourself: 'Firma te stesso',
|
||||
set_signing_date: 'Imposta data di firma',
|
||||
signing_date: 'Data di firma',
|
||||
@@ -324,7 +356,7 @@ const it = {
|
||||
drawn: 'Disegnato',
|
||||
formula: 'Formula',
|
||||
typed: 'Digitato',
|
||||
draw_field_on_the_document: 'Disegna il campo {field} sul documento',
|
||||
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',
|
||||
@@ -372,15 +404,28 @@ const it = {
|
||||
image: 'Immagine',
|
||||
file: 'File',
|
||||
select: 'Seleziona',
|
||||
checkbox: 'Casella di controllo',
|
||||
checkbox: 'Checkbox',
|
||||
multiple: 'Multiplo',
|
||||
radio: 'Radio',
|
||||
cells: 'Celle',
|
||||
stamp: 'Timbro',
|
||||
payment: 'Pagamento',
|
||||
phone: 'Telefono',
|
||||
field: 'Campo',
|
||||
group: 'Gruppo',
|
||||
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',
|
||||
@@ -416,6 +461,8 @@ const it = {
|
||||
}
|
||||
|
||||
const pt = {
|
||||
recurrent: 'Recurrente',
|
||||
one_off: 'Único',
|
||||
editable: 'Editável',
|
||||
search_field: 'Campo de busca',
|
||||
field_not_found: 'Campo não encontrado',
|
||||
@@ -454,7 +501,7 @@ const pt = {
|
||||
up: 'Para cima',
|
||||
down: 'Para baixo',
|
||||
set_signing_date: 'Definir data de assinatura',
|
||||
are_you_sure: 'Tem certeza?',
|
||||
are_you_sure_: 'Tem certeza?',
|
||||
sign_yourself: 'Assine você mesmo',
|
||||
checked: 'Marcado',
|
||||
send: 'Enviar',
|
||||
@@ -462,7 +509,7 @@ const pt = {
|
||||
save: 'Salvar',
|
||||
cancel: 'Cancelar',
|
||||
or_add_field_without_drawing: 'Ou adicione campo sem desenhar',
|
||||
draw_field_on_the_document: 'Desenhar campo {field} no documento',
|
||||
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',
|
||||
@@ -509,14 +556,27 @@ const pt = {
|
||||
file: 'Arquivo',
|
||||
select: 'Selecionar',
|
||||
checkbox: 'Caixa',
|
||||
field: 'Campo',
|
||||
group: 'Grupo',
|
||||
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',
|
||||
@@ -555,6 +615,8 @@ const pt = {
|
||||
}
|
||||
|
||||
const fr = {
|
||||
recurrent: 'Récurrent',
|
||||
one_off: 'Ponctuel',
|
||||
editable: 'Éditable',
|
||||
search_field: 'Champ de recherche',
|
||||
field_not_found: 'Champ non trouvé',
|
||||
@@ -593,14 +655,14 @@ const fr = {
|
||||
up: 'Haut',
|
||||
down: 'Bas',
|
||||
set_signing_date: 'Définir la date de signature',
|
||||
are_you_sure: 'Êtes-vous sûr ?',
|
||||
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: 'Dessinez le champ {field} sur le document',
|
||||
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',
|
||||
@@ -654,8 +716,21 @@ const fr = {
|
||||
stamp: 'Tampon',
|
||||
payment: 'Paiement',
|
||||
phone: 'Téléphone',
|
||||
field: 'Champ',
|
||||
group: 'Groupe',
|
||||
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',
|
||||
@@ -694,6 +769,8 @@ const fr = {
|
||||
}
|
||||
|
||||
const de = {
|
||||
wiederkehrend: 'Wiederkehrend',
|
||||
einmalig: 'Einmalig',
|
||||
editable: 'Bearbeitbar',
|
||||
search_field: 'Suchfeld',
|
||||
field_not_found: 'Feld nicht gefunden',
|
||||
@@ -732,14 +809,14 @@ const de = {
|
||||
up: 'Nach oben',
|
||||
down: 'Nach unten',
|
||||
set_signing_date: 'Unterschriftdatum festlegen',
|
||||
are_you_sure: 'Bist du sicher?',
|
||||
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: 'Feld {field} auf dem Dokument zeichnen',
|
||||
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',
|
||||
@@ -793,8 +870,21 @@ const de = {
|
||||
stamp: 'Stempel',
|
||||
payment: 'Zahlung',
|
||||
phone: 'Telefon',
|
||||
field: 'Feld',
|
||||
group: 'Gruppe',
|
||||
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',
|
||||
|
||||
@@ -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"
|
||||
>
|
||||
@@ -65,6 +66,7 @@
|
||||
: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)"
|
||||
>
|
||||
@@ -93,6 +96,7 @@
|
||||
v-if="column"
|
||||
:value="index"
|
||||
:selected="index === mapping.column_index"
|
||||
class="text-base-content"
|
||||
>
|
||||
{{ column }}
|
||||
</option>
|
||||
|
||||
@@ -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"
|
||||
@@ -74,6 +82,23 @@
|
||||
>
|
||||
{{ 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')"
|
||||
|
||||
@@ -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
|
||||
@@ -6,6 +6,8 @@ class ProcessSubmitterCompletionJob
|
||||
def perform(params = {})
|
||||
submitter = Submitter.find(params['submitter_id'])
|
||||
|
||||
create_completed_submitter!(submitter)
|
||||
|
||||
is_all_completed = !submitter.submission.submitters.exists?(completed_at: nil)
|
||||
|
||||
if !is_all_completed && submitter.submission.submitters_order_preserved?
|
||||
@@ -24,9 +26,42 @@ class ProcessSubmitterCompletionJob
|
||||
enqueue_completed_emails(submitter)
|
||||
end
|
||||
|
||||
create_completed_documents!(submitter)
|
||||
|
||||
enqueue_completed_webhooks(submitter, is_all_completed:)
|
||||
end
|
||||
|
||||
def create_completed_submitter!(submitter)
|
||||
completed_submitter = CompletedSubmitter.find_or_initialize_by(submitter_id: submitter.id)
|
||||
|
||||
return completed_submitter if completed_submitter.persisted?
|
||||
|
||||
submission = submitter.submission
|
||||
|
||||
completed_submitter.assign_attributes(
|
||||
submission_id: submitter.submission_id,
|
||||
account_id: submission.account_id,
|
||||
template_id: submission.template_id,
|
||||
source: submission.source,
|
||||
sms_count: submitter.submission_events.where(event_type: %w[send_sms send_2fa_sms]).count,
|
||||
completed_at: submitter.completed_at
|
||||
)
|
||||
|
||||
completed_submitter.save!
|
||||
|
||||
completed_submitter
|
||||
rescue ActiveRecord::RecordNotUnique
|
||||
retry
|
||||
end
|
||||
|
||||
def create_completed_documents!(submitter)
|
||||
submitter.documents.filter_map do |attachment|
|
||||
next if attachment.metadata['sha256'].blank?
|
||||
|
||||
CompletedDocument.find_or_create_by!(sha256: attachment.metadata['sha256'], submitter_id: submitter.id)
|
||||
end
|
||||
end
|
||||
|
||||
def enqueue_completed_webhooks(submitter, is_all_completed: false)
|
||||
webhook_config = Accounts.load_webhook_config(submitter.account)
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class ApplicationMailer < ActionMailer::Base
|
||||
default from: 'DocuSeal <info@docuseal.co>'
|
||||
default from: 'DocuSeal <info@docuseal.com>'
|
||||
layout 'mailer'
|
||||
|
||||
register_interceptor ActionMailerConfigsInterceptor
|
||||
|
||||
@@ -2,6 +2,6 @@
|
||||
|
||||
class SettingsMailer < ApplicationMailer
|
||||
def smtp_successful_setup(email)
|
||||
mail(to: email, subject: 'SMTP has been configured')
|
||||
mail(to: email, from: email, subject: 'SMTP has been configured')
|
||||
end
|
||||
end
|
||||
@@ -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
|
||||
@@ -28,6 +28,7 @@ class AccountConfig < ApplicationRecord
|
||||
FORCE_MFA = 'force_mfa'
|
||||
ALLOW_TYPED_SIGNATURE = 'allow_typed_signature'
|
||||
ALLOW_TO_RESUBMIT = 'allow_to_resubmit'
|
||||
ALLOW_TO_DECLINE_KEY = 'allow_to_decline'
|
||||
SUBMITTER_REMINDERS = 'submitter_reminders'
|
||||
FORM_COMPLETED_BUTTON_KEY = 'form_completed_button'
|
||||
FORM_COMPLETED_MESSAGE_KEY = 'form_completed_message'
|
||||
@@ -39,7 +40,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 = {
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: completed_documents
|
||||
#
|
||||
# id :bigint not null, primary key
|
||||
# sha256 :string not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# submitter_id :bigint not null
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
# index_completed_documents_on_sha256 (sha256)
|
||||
# index_completed_documents_on_submitter_id (submitter_id)
|
||||
#
|
||||
class CompletedDocument < ApplicationRecord
|
||||
belongs_to :submitter, optional: true
|
||||
|
||||
has_one :completed_submitter, primary_key: :submitter_id, inverse_of: :completed_documents, dependent: :destroy
|
||||
end
|
||||
@@ -0,0 +1,33 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: completed_submitters
|
||||
#
|
||||
# id :bigint not null, primary key
|
||||
# completed_at :datetime not null
|
||||
# sms_count :integer not null
|
||||
# source :string not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# account_id :bigint not null
|
||||
# submission_id :bigint not null
|
||||
# submitter_id :bigint not null
|
||||
# template_id :bigint not null
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
# index_completed_submitters_on_account_id (account_id)
|
||||
# index_completed_submitters_on_submitter_id (submitter_id) UNIQUE
|
||||
#
|
||||
class CompletedSubmitter < ApplicationRecord
|
||||
belongs_to :submitter
|
||||
belongs_to :submission
|
||||
belongs_to :account
|
||||
belongs_to :template
|
||||
|
||||
has_many :completed_documents, dependent: :destroy,
|
||||
primary_key: :submitter_id,
|
||||
foreign_key: :submitter_id,
|
||||
inverse_of: :submitter
|
||||
end
|
||||
@@ -55,6 +55,8 @@ class Submission < ApplicationRecord
|
||||
has_one_attached :audit_trail
|
||||
has_one_attached :combined_document
|
||||
|
||||
has_many_attached :preview_documents
|
||||
|
||||
has_many :template_schema_documents,
|
||||
->(e) { where(uuid: (e.template_schema.presence || e.template.schema).pluck('attachment_uuid')) },
|
||||
through: :template, source: :documents_attachments
|
||||
|
||||
@@ -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) }
|
||||
|
||||
|
||||
@@ -49,6 +49,9 @@ class User < ApplicationRecord
|
||||
|
||||
EMAIL_REGEXP = /[^@;,<>\s]+@[^@;,<>\s]+/
|
||||
|
||||
FULL_EMAIL_REGEXP =
|
||||
/\A[a-z0-9][\.']?(?:(?:[a-z0-9_-]+[\.\+'])*[a-z0-9_-]+)*@(?:[a-z0-9]+[\.-])*[a-z0-9]+\.[a-z]{2,}\z/i
|
||||
|
||||
has_one_attached :signature
|
||||
has_one_attached :initials
|
||||
|
||||
|
||||
@@ -103,6 +103,18 @@
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% account_config = AccountConfig.find_or_initialize_by(account: current_account, key: AccountConfig::ALLOW_TO_DECLINE_KEY) %>
|
||||
<% if can?(:manage, account_config) %>
|
||||
<%= form_for account_config, url: account_configs_path, method: :post do |f| %>
|
||||
<%= f.hidden_field :key %>
|
||||
<div class="flex items-center justify-between py-2.5">
|
||||
<span>
|
||||
<%= t('allow_to_decline_documents') %>
|
||||
</span>
|
||||
<%= f.check_box :value, class: 'toggle', checked: account_config.value != false, onchange: 'this.form.requestSubmit()' %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% account_config = AccountConfig.find_or_initialize_by(account: current_account, key: AccountConfig::FORM_PREFILL_SIGNATURE_KEY) %>
|
||||
<% if can?(:manage, account_config) %>
|
||||
<%= form_for account_config, url: account_configs_path, method: :post do |f| %>
|
||||
|
||||
@@ -10,8 +10,11 @@
|
||||
<label for="api_key" class="text-sm font-semibold">X-Auth-Token</label>
|
||||
<div class="flex flex-col md:flex-row gap-4">
|
||||
<div class="flex w-full space-x-4">
|
||||
<input id="api_key" type="text" value="<%= current_user.access_token.token %>" class="input font-mono input-bordered w-full" autocomplete="off" readonly>
|
||||
<%= render 'shared/clipboard_copy', icon: 'copy', text: current_user.access_token.token, class: 'base-button', icon_class: 'w-6 h-6 text-white', copy_title: t('copy'), copied_title: t('copied') %>
|
||||
<% token = current_user.access_token.token %>
|
||||
<masked-input class="block w-full" data-token="<%= token %>">
|
||||
<input id="api_key" type="text" value="<%= token.sub(token[5..], '*' * token[5..].size) %>" class="input font-mono input-bordered w-full" autocomplete="off" readonly>
|
||||
</masked-input>
|
||||
<%= render 'shared/clipboard_copy', icon: 'copy', text: token, class: 'base-button', icon_class: 'w-6 h-6 text-white', copy_title: t('copy'), copied_title: t('copied') %>
|
||||
</div>
|
||||
<%= button_to button_title(title: t('rotate'), disabled_with: t('rotate'), icon: svg_icon('reload', class: 'w-6 h-6')), settings_api_index_path, class: 'white-button w-full', data: { turbo_confirm: t('remove_existing_api_token_and_generated_a_new_one_are_you_sure_') } %>
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<div class="text-center">
|
||||
<div class="join">
|
||||
<a href="https://docuseal.co<%= request.fullpath.gsub('docuseal.eu', 'docuseal.co') %>" class="btn bg-base-200 join-item w-32 <%= 'bg-base-300' if request.host == 'docuseal.co' %>">
|
||||
<a href="https://docuseal.com<%= request.fullpath.gsub('docuseal.eu', 'docuseal.com') %>" class="btn bg-base-200 join-item w-32 <%= 'bg-base-300' if request.host == 'docuseal.com' %>">
|
||||
<%= svg_icon 'world', class: 'w-5 h-5' %>
|
||||
Global
|
||||
</a>
|
||||
<a href="https://docuseal.eu<%= request.fullpath.gsub('docuseal.co', 'docuseal.eu') %>" class="btn bg-base-200 join-item w-32 <%= 'bg-base-300' if request.host == 'docuseal.eu' %>">
|
||||
<a href="https://docuseal.eu<%= request.fullpath.gsub('docuseal.com', 'docuseal.eu') %>" class="btn bg-base-200 join-item w-32 <%= 'bg-base-300' if request.host == 'docuseal.eu' %>">
|
||||
<%= svg_icon 'eu_flag', class: 'w-5 h-5' %>
|
||||
Europe
|
||||
</a>
|
||||
|
||||
@@ -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 %>
|
||||
@@ -48,7 +48,7 @@
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<%= ff.label :from_email, t('send_from_email'), class: 'label' %>
|
||||
<%= ff.email_field :from_email, value: value['from_email'], required: true, class: 'base-input' %>
|
||||
<%= ff.email_field :from_email, value: value['from_email'], required: !Docuseal.multitenant?, class: 'base-input' %>
|
||||
</div>
|
||||
<% end %>
|
||||
<div class="form-control pt-2">
|
||||
|
||||
@@ -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>
|
||||
@@ -10,7 +10,7 @@
|
||||
<% if signed_in? %>
|
||||
<div class="space-x-4 flex items-center">
|
||||
<% if Docuseal.demo? %>
|
||||
<a href="https://docuseal.co/sign_up" class="btn btn-neutral btn-sm btn-outline inline-flex items-center justify-center" style="height: 37px">
|
||||
<a href="https://docuseal.com/sign_up" class="btn btn-neutral btn-sm btn-outline inline-flex items-center justify-center" style="height: 37px">
|
||||
<%= t('sign_up') %>
|
||||
</a>
|
||||
<span class="hidden sm:inline">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<div class="text-center px-2">
|
||||
<% if local_assigns[:with_counter] %>
|
||||
<% count = Submitter.where.not(completed_at: nil).distinct.count(:submission_id) %>
|
||||
<% count = CompletedSubmitter.distinct.count(:submission_id) %>
|
||||
<% if count > 1 %>
|
||||
<%= t('count_documents_signed_with_html', count:) %>
|
||||
<% else %>
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
</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 !h-10 mt-1.5 w-full" placeholder="<%= "#{t('phone')} (#{t('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>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<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 !h-10 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 %>
|
||||
|
||||
@@ -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,5 +1,5 @@
|
||||
<% align = field.dig('preferences', 'align') %>
|
||||
<field-value dir="auto" class="flex absolute text-[1.6vw] lg:text-base <%= 'font-serif' if field.dig('preferences', 'font') == 'Times' %> <%= align == 'right' ? 'justify-end' : (align == 'center' ? 'justify-center' : '') %>" style="width: <%= area['w'] * 100 %>%; height: <%= area['h'] * 100 %>%; left: <%= area['x'] * 100 %>%; top: <%= area['y'] * 100 %>%; <%= "font-size: #{field['preferences']['font_size']}pt" if field.dig('preferences', 'font_size') %>">
|
||||
<field-value dir="auto" class="flex absolute text-[1.6vw] lg:text-base <%= 'font-serif' if field.dig('preferences', 'font') == 'Times' %> <%= align == 'right' ? 'justify-end' : (align == 'center' ? 'justify-center' : '') %>" style="width: <%= area['w'] * 100 %>%; height: <%= area['h'] * 100 %>%; left: <%= area['x'] * 100 %>%; top: <%= area['y'] * 100 %>%; <%= "font-size: clamp(4pt, 1.6vw, #{field['preferences']['font_size']}pt); line-height: `clamp(6pt, 2.0vw, #{field['preferences']['font_size'].to_i + 3}pt)`" if field.dig('preferences', 'font_size') %>">
|
||||
<% if field['type'] == 'signature' %>
|
||||
<div class="flex flex-col justify-between h-full overflow-hidden">
|
||||
<div class="flex-grow flex overflow-hidden" style="min-height: 50%">
|
||||
|
||||
@@ -17,41 +17,43 @@
|
||||
<% end %>
|
||||
<% if last_submitter = @submission.submitters.to_a.select(&:completed_at?).max_by(&:completed_at) %>
|
||||
<% is_all_completed = @submission.submitters.to_a.all?(&:completed_at?) %>
|
||||
<div class="join relative">
|
||||
<download-button data-src="<%= submitter_download_index_path(last_submitter.slug, { sig: params[:sig], combined: is_combined_enabled }.compact) %>" class="base-button <%= '!rounded-r-none !pr-2' if is_all_completed && !is_combined_enabled %>">
|
||||
<span class="flex items-center justify-center space-x-2" data-target="download-button.defaultButton">
|
||||
<%= svg_icon('download', class: 'w-6 h-6') %>
|
||||
<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 class="hidden md:inline"><%= t('downloading') %></span>
|
||||
</span>
|
||||
</download-button>
|
||||
<% if is_all_completed && !is_combined_enabled %>
|
||||
<div class="dropdown dropdown-end">
|
||||
<label tabindex="0" class="base-button !rounded-l-none !pl-1 !pr-2 !border-l-neutral-500">
|
||||
<span class="text-sm align-text-top">
|
||||
<%= svg_icon('chevron_down', class: 'w-6 h-6 flex-shrink-0 stroke-2') %>
|
||||
</span>
|
||||
</label>
|
||||
<ul tabindex="0" class="z-10 dropdown-content p-2 mt-2 shadow menu text-base bg-base-100 rounded-box text-right">
|
||||
<li>
|
||||
<download-button data-src="<%= submitter_download_index_path(last_submitter.slug, { sig: params[:sig], combined: true }.compact) %>" class="flex items-center">
|
||||
<span class="flex items-center justify-center space-x-2" data-target="download-button.defaultButton">
|
||||
<%= svg_icon('download', class: 'w-6 h-6 flex-shrink-0') %>
|
||||
<span class="whitespace-nowrap"><%= t('download_combined_pdf') %></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>
|
||||
</download-button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% if is_all_completed || !is_combined_enabled %>
|
||||
<div class="join relative">
|
||||
<download-button data-src="<%= submitter_download_index_path(last_submitter.slug, { sig: params[:sig], combined: is_combined_enabled }.compact) %>" class="base-button <%= '!rounded-r-none !pr-2' if is_all_completed && !is_combined_enabled %>">
|
||||
<span class="flex items-center justify-center space-x-2" data-target="download-button.defaultButton">
|
||||
<%= svg_icon('download', class: 'w-6 h-6') %>
|
||||
<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 class="hidden md:inline"><%= t('downloading') %></span>
|
||||
</span>
|
||||
</download-button>
|
||||
<% if is_all_completed && !is_combined_enabled %>
|
||||
<div class="dropdown dropdown-end">
|
||||
<label tabindex="0" class="base-button !rounded-l-none !pl-1 !pr-2 !border-l-neutral-500">
|
||||
<span class="text-sm align-text-top">
|
||||
<%= svg_icon('chevron_down', class: 'w-6 h-6 flex-shrink-0 stroke-2') %>
|
||||
</span>
|
||||
</label>
|
||||
<ul tabindex="0" class="z-10 dropdown-content p-2 mt-2 shadow menu text-base bg-base-100 rounded-box text-right">
|
||||
<li>
|
||||
<download-button data-src="<%= submitter_download_index_path(last_submitter.slug, { sig: params[:sig], combined: true }.compact) %>" class="flex items-center">
|
||||
<span class="flex items-center justify-center space-x-2" data-target="download-button.defaultButton">
|
||||
<%= svg_icon('download', class: 'w-6 h-6 flex-shrink-0') %>
|
||||
<span class="whitespace-nowrap"><%= t('download_combined_pdf') %></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>
|
||||
</download-button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% 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: t('copy_share_link'), copied_title: t('copied_to_clipboard') %>
|
||||
<% end %>
|
||||
@@ -107,12 +109,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">
|
||||
@@ -182,7 +193,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,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] %>" data-language="<%= I18n.locale.to_s.split('-').first %>"></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>
|
||||
@@ -15,18 +15,21 @@
|
||||
<%= @submitter.submission.template.name %>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2" style="margin-left: 20px; flex-shrink: 0">
|
||||
<div>
|
||||
<%= render 'shared/html_modal', title: t(:decline), btn_text: t(:decline), btn_class: 'btn btn-sm !px-5', button_id: 'decline_button' do %>
|
||||
<%= render 'submit_form/decline_form', submitter: @submitter %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% if @form_configs[:with_decline] %>
|
||||
<div>
|
||||
<%= render 'shared/html_modal', title: t(:decline), btn_text: t(:decline), btn_class: 'btn btn-sm !px-5', button_id: 'decline_button' do %>
|
||||
<%= render 'submit_form/decline_form', submitter: @submitter %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<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 +62,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>
|
||||
|
||||
@@ -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 %>
|
||||
@@ -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,22 +41,31 @@
|
||||
</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">
|
||||
<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' if t('download').length < 11 %>" data-target="download-button.defaultButton">
|
||||
<%= svg_icon('download', class: 'w-5 h-5 stroke-2') %>
|
||||
@@ -67,37 +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">
|
||||
<% 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: t('copy_link').length < 10 ? t('copy_link') : t('copy'), 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>
|
||||
@@ -106,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">
|
||||
<%= 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">
|
||||
@@ -123,46 +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">
|
||||
<%= 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">
|
||||
<% 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 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') %>
|
||||
<% 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: t('copy_link'), copy_title_md: t('copy_link').length < 10 ? t('copy_link') : t('copy'), 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 %>
|
||||
@@ -171,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()" class="w-full">
|
||||
<download-button data-src="<%= submitter_download_index_path(latest_submitter.slug) %>" class="btn btn-sm btn-neutral text-white w-full md:w-36">
|
||||
<span class="flex items-center justify-center space-x-1 md:space-x-2" data-target="download-button.defaultButton">
|
||||
<%= 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>
|
||||
@@ -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>
|
||||
@@ -0,0 +1,16 @@
|
||||
<div class="flex items-center space-x-1">
|
||||
<% if %w[archived integration].include?(params[:status]) %>
|
||||
<%= link_to t('view_active'), settings_users_path, class: 'link text-sm' %>
|
||||
<% else %>
|
||||
<% archived_exists = current_account.users.archived.where.not(role: 'integration').exists? %>
|
||||
<% if current_account.users.active.exists?(role: 'integration') %>
|
||||
<%= link_to t('view_embedding_users'), settings_integration_users_path, class: 'link text-sm' %>
|
||||
<% if archived_exists %>
|
||||
<span class="text-neutral-700">|</span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% if archived_exists %>
|
||||
<%= link_to t('view_archived'), settings_archived_users_path, class: 'link text-sm' %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
@@ -1,12 +1,18 @@
|
||||
<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 flex-col md:flex-row gap-2 md:justify-between md:items-end mb-4">
|
||||
<div class="flex flex-col md:flex-row gap-2 md:justify-between md:items-end mb-4 min-h-12">
|
||||
<h1 class="text-4xl font-bold">
|
||||
<%= t('status_users', status: params[:status].to_s.titleize) %>
|
||||
<% if params[:status] == 'archived' %>
|
||||
<%= t('archived_users') %>
|
||||
<% elsif params[:status] == 'integration' %>
|
||||
<%= t('embedding_users') %>
|
||||
<% else %>
|
||||
<%= t('users') %>
|
||||
<% end %>
|
||||
</h1>
|
||||
<div class="flex flex-col md:flex-row gap-y-2 gap-x-4 md:items-center">
|
||||
<% if can?(:create, User.new(account: current_account)) %>
|
||||
<% if params[:status].blank? && can?(:create, User.new(account: current_account)) %>
|
||||
<%= render 'users/extra_buttons' %>
|
||||
<% if content_for(:add_user_button) %>
|
||||
<%= content_for(:add_user_button) %>
|
||||
@@ -57,17 +63,17 @@
|
||||
<%= user.last_sign_in_at ? l(user.last_sign_in_at.in_time_zone(current_account.timezone), format: :short, locale: current_account.locale) : '-' %>
|
||||
</td>
|
||||
<td class="flex items-center space-x-2 justify-end">
|
||||
<% if can?(:update, user) && user.archived_at.blank? %>
|
||||
<% if params[:status].blank? && can?(:update, user) && user.archived_at.blank? %>
|
||||
<%= link_to edit_user_path(user), class: 'btn btn-outline btn-xs', title: t('edit'), data: { turbo_frame: 'modal' } do %>
|
||||
<%= t('edit') %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% if params[:status].blank? && can?(:destroy, user) && user != current_user %>
|
||||
<% if params[:status] != 'archived' && can?(:destroy, user) && user != current_user %>
|
||||
<%= button_to user_path(user), method: :delete, class: 'btn btn-outline btn-error btn-xs', title: t('remove'), data: { turbo_confirm: t('are_you_sure_') } do %>
|
||||
<%= t('remove') %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% if params[:status].present? && can?(:manage, user) && user != current_user %>
|
||||
<% if params[:status] == 'archived' && can?(:manage, user) && user != current_user && user.archived_at? %>
|
||||
<%= button_to user_path(user), method: :put, params: { user: { archived_at: nil } }, class: 'btn btn-outline btn-xs', title: t('unarchive'), data: { turbo_confirm: t('are_you_sure_') } do %>
|
||||
<%= t('unarchive') %>
|
||||
<% end %>
|
||||
@@ -78,22 +84,11 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<% view_archived_html = capture do %>
|
||||
<% if current_account.users.archived.exists? %>
|
||||
<div>
|
||||
<% if params[:status] == 'archived' %>
|
||||
<a href="<%= settings_users_path %>" class="link text-sm"><%= t('view_active') %></a>
|
||||
<% else %>
|
||||
<a href="<%= settings_archived_users_path %>" class="link text-sm"><%= t('view_archived') %></a>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% if @pagy.pages > 1 %>
|
||||
<%= render 'shared/pagination', pagy: @pagy, items_name: 'users', left_additional_html: view_archived_html %>
|
||||
<%= render 'shared/pagination', pagy: @pagy, items_name: 'users', left_additional_html: render('bottom_links') %>
|
||||
<% else %>
|
||||
<div class="mt-2">
|
||||
<%= view_archived_html %>
|
||||
<%= render 'bottom_links' %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
@@ -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])
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,5 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
Rails.configuration.to_prepare do
|
||||
ActiveRecord::Tasks::DatabaseTasks.migrate if ENV['RAILS_ENV'] == 'production'
|
||||
ActiveRecord::Tasks::DatabaseTasks.migrate if ENV['RAILS_ENV'] == 'production' && ENV['RUN_MIGRATIONS'] != 'false'
|
||||
end
|
||||
@@ -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
|
||||
|
||||
+358
-9
@@ -22,6 +22,10 @@ en: &en
|
||||
thanks: Thanks
|
||||
first_party: 'First Party'
|
||||
docuseal_trusted_signature: DocuSeal Trusted Signature
|
||||
hello_name: Hello %{name}
|
||||
you_are_invited_to_product_name: You are invited to %{product_name}
|
||||
you_have_been_invited_to_account_name_product_name_please_sign_up_using_the_link_below_: 'You have been invited to %{account_name} %{product_name}. Please sign up using the link below:'
|
||||
sent_using_product_name_free_document_signing_html: 'Sent using <a href="%{product_url}">%{product_name}</a> free document signing.'
|
||||
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: Sign documents with trusted certificate provided by DocuSeal. Your documents and data are never shared with DocuSeal. PDF checksum is provided to generate a trusted signature.
|
||||
you_have_been_invited_to_submit_the_name_form: 'You have been invited to submit the "%{name}" form.'
|
||||
you_have_been_invited_to_sign_the_name: 'You have been invited to sign the "%{name}".'
|
||||
@@ -130,6 +134,7 @@ en: &en
|
||||
require_signing_reason: Require signing reason
|
||||
allow_typed_text_signatures: Allow typed text signatures
|
||||
allow_to_resubmit_completed_forms: Allow to resubmit completed forms
|
||||
allow_to_decline_documents: Allow to decline documents
|
||||
remember_and_pre_fill_signatures: Remember and pre-fill signatures
|
||||
require_authentication_for_file_download_links: Require authentication for file download links
|
||||
combine_completed_documents_and_audit_log: Combine completed documents and Audit Log
|
||||
@@ -469,7 +474,6 @@ en: &en
|
||||
back: Back
|
||||
add_secret: Add Secret
|
||||
submission_example_payload: Submission example payload
|
||||
status_users: '%{status} Users'
|
||||
there_are_no_signatures: There are no signatures
|
||||
signed_with_trusted_certificate: Signed with trusted certificate
|
||||
signed_with_external_certificate: Signed with external certificate
|
||||
@@ -577,6 +581,61 @@ en: &en
|
||||
opened: Opened
|
||||
sent: Sent
|
||||
awaiting: Awaiting
|
||||
document_id: Document ID
|
||||
envelope_id: Envelope ID
|
||||
event_log: Event Log
|
||||
verify: Verify
|
||||
testing_log_not_for_production_use: Testing Log - Not for Production Use
|
||||
original_sha256: Original SHA256
|
||||
result_sha256: Result SHA256
|
||||
generated_at: Generated at
|
||||
email_verification: Email verification
|
||||
phone_verification: Phone verification
|
||||
session_id: Session ID
|
||||
option: Option
|
||||
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
|
||||
paid_price: 'Paid %{price}'
|
||||
verified: Verified
|
||||
unverified: Unverified
|
||||
document: Document
|
||||
completed_at: Completed At
|
||||
edit_recipient: Edit Recipient
|
||||
update_recipient: Update Recipient
|
||||
use_international_format_1xxx_: 'Use internatioanl format: +1xxx...'
|
||||
submitter_cannot_be_updated: Submitter cannot be updated.
|
||||
at_least_one_field_must_be_filled: At least one field must be filled.
|
||||
archived_users: Archived Users
|
||||
embedding_users: Embedding Users
|
||||
view_embedding_users: View Embedding Users
|
||||
view_users: View Users
|
||||
submission_event_names:
|
||||
send_email_to_html: '<b>Email sent</b> to %{submitter_name}'
|
||||
send_reminder_email_to_html: '<b>Reminder email sent</b> to %{submitter_name}'
|
||||
send_sms_to_html: '<b>SMS sent</b> to %{submitter_name}'
|
||||
send_2fa_sms_to_html: '<b>Verification SMS sent</b> to %{submitter_name}'
|
||||
open_email_by_html: '<b>Email opened</b> by %{submitter_name}'
|
||||
click_email_by_html: '<b>Email link clicked</b> by %{submitter_name}'
|
||||
click_sms_by_html: '<b>SMS link clicked</b> by %{submitter_name}'
|
||||
phone_verified_by_html: '<b>Phone verified</b> by %{submitter_name}'
|
||||
start_form_by_html: '<b>Submission started</b> by %{submitter_name}'
|
||||
view_form_by_html: '<b>Form viewed</b> by %{submitter_name}'
|
||||
invite_party_by_html: '<b>Invited</b> %{invited_submitter_name} by %{submitter_name}'
|
||||
complete_form_by_html: '<b>Submission completed</b> by %{submitter_name}'
|
||||
api_complete_form_by_html: '<b>Submission completed via API</b> by %{submitter_name}'
|
||||
import_list:
|
||||
select_worksheet: Select Worksheet
|
||||
open: Open
|
||||
@@ -603,6 +662,10 @@ es: &es
|
||||
enforce_recipients_order: 'Hacer cumplir el orden de los destinatarios'
|
||||
first_party: 'Primera Parte'
|
||||
docuseal_trusted_signature: Firma de confianza DocuSeal
|
||||
hello_name: Hola %{name}
|
||||
you_are_invited_to_product_name: Estás invitado a %{product_name}
|
||||
you_have_been_invited_to_account_name_product_name_please_sign_up_using_the_link_below_: 'Has sido invitado a %{account_name} %{product_name}. Por favor, regístrate usando el enlace a continuación:'
|
||||
sent_using_product_name_free_document_signing_html: 'Enviado usando la firma de documentos gratuita de <a href="%{product_url}">%{product_name}</a>.'
|
||||
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: Firme documentos con un certificado de confianza proporcionado por DocuSeal. Sus documentos y datos nunca se comparten con DocuSeal. Se proporciona un checksum de PDF para generar una firma de confianza.
|
||||
hi_there: Hola
|
||||
thanks: Gracias
|
||||
@@ -711,6 +774,7 @@ es: &es
|
||||
require_signing_reason: Requerir motivo de firma
|
||||
allow_typed_text_signatures: Permitir firmas de texto mecanografiadas
|
||||
allow_to_resubmit_completed_forms: Permitir reenviar formularios completados
|
||||
allow_to_decline_documents: Permitir rechazar documentos
|
||||
remember_and_pre_fill_signatures: Recordar y prellenar firmas
|
||||
require_authentication_for_file_download_links: Requerir autenticación para enlaces de descarga de archivos
|
||||
combine_completed_documents_and_audit_log: Combinar documentos completados y Registro de Auditoría
|
||||
@@ -1050,7 +1114,6 @@ es: &es
|
||||
back: Atrás
|
||||
add_secret: Agregar secreto
|
||||
submission_example_payload: Ejemplo de payload de envío
|
||||
status_users: '%{status} Usuarios'
|
||||
there_are_no_signatures: No hay firmas
|
||||
signed_with_trusted_certificate: Firmado con certificado de confianza
|
||||
signed_with_external_certificate: Firmado con certificado externo
|
||||
@@ -1158,6 +1221,60 @@ es: &es
|
||||
opened: Abierto
|
||||
sent: Enviado
|
||||
awaiting: Pendiente
|
||||
document_id: ID del Documento
|
||||
envelope_id: ID del Sobre
|
||||
event_log: Registro de Eventos
|
||||
verify: Verificar
|
||||
testing_log_not_for_production_use: Registro de Pruebas - No para Uso en Producción
|
||||
original_sha256: SHA256 Original
|
||||
result_sha256: SHA256 del Resultado
|
||||
generated_at: Generado el
|
||||
email_verification: Verificación de correo electrónico
|
||||
phone_verification: Verificación telefónica
|
||||
session_id: ID de la Sesión
|
||||
option: Opción
|
||||
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
|
||||
multiple_field: Campo Múltiple
|
||||
radio_field: Campo de Grupo Radio
|
||||
radio_field: Campo de Radio
|
||||
cells_field: Campo de Celdas
|
||||
stamp_field: Campo de Sello
|
||||
payment_field: Campo de Pago
|
||||
phone_field: Campo de Teléfono
|
||||
paid_price: 'Pagado %{price}'
|
||||
verified: Verificado
|
||||
unverified: No verificado
|
||||
document: Documento
|
||||
completed_at: Completado el
|
||||
edit_recipient: Editar Destinatario
|
||||
update_recipient: Actualizar Destinatario
|
||||
use_international_format_1xxx_: 'Usa el formato internacional: +1xxx...'
|
||||
submitter_cannot_be_updated: El remitente no puede ser actualizado.
|
||||
at_least_one_field_must_be_filled: Al menos un campo debe estar completo.
|
||||
archived_users: Usuarios Archivados
|
||||
embedding_users: Usuarios Integrados
|
||||
view_embedding_users: Ver Usuarios Integrado
|
||||
submission_event_names:
|
||||
send_email_to_html: '<b>Correo electrónico enviado</b> a %{submitter_name}'
|
||||
send_reminder_email_to_html: '<b>Correo de recordatorio enviado</b> a %{submitter_name}'
|
||||
send_sms_to_html: '<b>SMS enviado</b> a %{submitter_name}'
|
||||
send_2fa_sms_to_html: '<b>SMS de verificación enviado</b> a %{submitter_name}'
|
||||
open_email_by_html: '<b>Correo electrónico abierto</b> por %{submitter_name}'
|
||||
click_email_by_html: '<b>Enlace del correo electrónico clicado</b> por %{submitter_name}'
|
||||
click_sms_by_html: '<b>Enlace del SMS clicado</b> por %{submitter_name}'
|
||||
phone_verified_by_html: '<b>Teléfono verificado</b> por %{submitter_name}'
|
||||
start_form_by_html: '<b>Envío iniciado</b> por %{submitter_name}'
|
||||
view_form_by_html: '<b>Formulario visto</b> por %{submitter_name}'
|
||||
invite_party_by_html: '<b>Invitado</b> %{invited_submitter_name} por %{submitter_name}'
|
||||
complete_form_by_html: '<b>Envío completado</b> por %{submitter_name}'
|
||||
api_complete_form_by_html: '<b>Envío completado vía API</b> por %{submitter_name}'
|
||||
import_list:
|
||||
select_worksheet: Seleccionar hoja de cálculo
|
||||
open: Abrir
|
||||
@@ -1184,6 +1301,10 @@ it: &it
|
||||
enforce_recipients_order: Aplicar el orden de los destinatarios
|
||||
first_party: 'Prima parte'
|
||||
docuseal_trusted_signature: "Firma Fiduciaria DocuSeal"
|
||||
hello_name: Ciao %{name}
|
||||
you_are_invited_to_product_name: Sei stato invitato a %{product_name}
|
||||
you_have_been_invited_to_account_name_product_name_please_sign_up_using_the_link_below_: 'Sei stato invitato a %{account_name} %{product_name}. Registrati utilizzando il link qui sotto:'
|
||||
sent_using_product_name_free_document_signing_html: 'Inviato utilizzando la firma di documenti gratuita di <a href="%{product_url}">%{product_name}</a>.'
|
||||
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: "Firma documenti con un certificato di fiducia fornito da DocuSeal. I tuoi documenti e i tuoi dati non vengono mai condivisi con DocuSeal. Il checksum PDF è fornito per generare una firma di fiducia."
|
||||
hi_there: Ciao
|
||||
thanks: Grazie
|
||||
@@ -1292,6 +1413,7 @@ it: &it
|
||||
require_signing_reason: Richiedere il motivo della firma
|
||||
allow_typed_text_signatures: Permettere firme con testo digitato
|
||||
allow_to_resubmit_completed_forms: Permettere di reinviare i moduli completati
|
||||
allow_to_decline_documents: Permettere di rifiutare i documenti
|
||||
remember_and_pre_fill_signatures: Ricordare e precompilare le firme
|
||||
require_authentication_for_file_download_links: "Richiedere l'autenticazione per i link di download dei file"
|
||||
combine_completed_documents_and_audit_log: Combinare i documenti completati e il Registro di Audit
|
||||
@@ -1631,7 +1753,6 @@ it: &it
|
||||
back: Indietro
|
||||
add_secret: Aggiungi segreto
|
||||
submission_example_payload: Esempio di payload di invio
|
||||
status_users: '%{status} Utenti'
|
||||
there_are_no_signatures: Non ci sono firme
|
||||
signed_with_trusted_certificate: Firmato con certificato affidabile
|
||||
signed_with_external_certificate: Firmato con certificato esterno
|
||||
@@ -1739,6 +1860,60 @@ it: &it
|
||||
opened: Aperto
|
||||
sent: Inviato
|
||||
awaiting: In attesa
|
||||
document_id: ID del Documento
|
||||
envelope_id: ID della Busta
|
||||
event_log: Registro degli Eventi
|
||||
verify: Verifica
|
||||
testing_log_not_for_production_use: Registro di Test - Non per Uso in Produzione
|
||||
original_sha256: SHA256 Originale
|
||||
result_sha256: SHA256 del Risultato
|
||||
generated_at: Generato il
|
||||
email_verification: Verifica e-mail
|
||||
phone_verification: Verifica telefono
|
||||
session_id: ID della Sessione
|
||||
option: Opzione
|
||||
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
|
||||
paid_price: 'Pagato %{price}'
|
||||
verified: Verificato
|
||||
unverified: Non verificato
|
||||
document: Documento
|
||||
completed_at: Completato il
|
||||
edit_recipient: Modifica Destinatario
|
||||
update_recipient: Aggiorna Destinatario
|
||||
use_international_format_1xxx_: 'Utilizza il formato internazionale: +1xxx...'
|
||||
submitter_cannot_be_updated: Il mittente non può essere aggiornato.
|
||||
at_least_one_field_must_be_filled: Almeno un campo deve essere compilato.
|
||||
archived_users: Utenti Archiviati
|
||||
embedding_users: Utenti Incorporati
|
||||
view_embedding_users: Visualizza Utenti Incorporati
|
||||
submission_event_names:
|
||||
send_email_to_html: '<b>E-mail inviato</b> a %{submitter_name}'
|
||||
send_reminder_email_to_html: '<b>E-mail di promemoria inviato</b> a %{submitter_name}'
|
||||
send_sms_to_html: '<b>SMS inviato</b> a %{submitter_name}'
|
||||
send_2fa_sms_to_html: '<b>SMS di verifica inviato</b> a %{submitter_name}'
|
||||
open_email_by_html: '<b>E-mail aperta</b> da %{submitter_name}'
|
||||
click_email_by_html: "<b>Link dell'e-mail cliccato</b> da %{submitter_name}"
|
||||
click_sms_by_html: "<b>Link dell'SMS cliccato</b> da %{submitter_name}"
|
||||
phone_verified_by_html: '<b>Telefono verificato</b> da %{submitter_name}'
|
||||
start_form_by_html: '<b>Invio iniziato</b> da %{submitter_name}'
|
||||
view_form_by_html: '<b>Modulo visualizzato</b> da %{submitter_name}'
|
||||
invite_party_by_html: '<b>Invitato</b> %{invited_submitter_name} da %{submitter_name}'
|
||||
complete_form_by_html: '<b>Invio completato</b> da %{submitter_name}'
|
||||
api_complete_form_by_html: '<b>Invio completato tramite API</b> da %{submitter_name}'
|
||||
import_list:
|
||||
select_worksheet: Seleziona il foglio di lavoro
|
||||
open: Apri
|
||||
@@ -1765,6 +1940,10 @@ fr: &fr
|
||||
enforce_recipients_order: "Respecter l'ordre des destinataires"
|
||||
first_party: 'Première partie'
|
||||
docuseal_trusted_signature: Signature de confiance DocuSeal
|
||||
hello_name: Bonjour %{name}
|
||||
you_are_invited_to_product_name: Vous êtes invité à %{product_name}
|
||||
you_have_been_invited_to_account_name_product_name_please_sign_up_using_the_link_below_: 'Vous avez été invité à %{account_name} %{product_name}. Veuillez vous inscrire en utilisant le lien ci-dessous:'
|
||||
sent_using_product_name_free_document_signing_html: 'Envoyé en utilisant la signature de documents gratuite de <a href="%{product_url}">%{product_name}</a>.'
|
||||
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: Signez des documents avec un certificat de confiance fourni par DocuSeal. Vos documents et données ne sont jamais partagés avec DocuSeal. Un checksum PDF est fourni pour générer une signature de confiance.
|
||||
hi_there: Bonjour
|
||||
thanks: Merci
|
||||
@@ -1874,6 +2053,7 @@ fr: &fr
|
||||
require_signing_reason: Demander une raison pour la signature
|
||||
allow_typed_text_signatures: Autoriser les signatures en texte tapé
|
||||
allow_to_resubmit_completed_forms: Autoriser la resoumission des formulaires complétés
|
||||
allow_to_decline_documents: Autoriser le refus des documents
|
||||
remember_and_pre_fill_signatures: Se souvenir et préremplir les signatures
|
||||
require_authentication_for_file_download_links: Exiger une authentification pour les liens de téléchargement des fichiers
|
||||
combine_completed_documents_and_audit_log: "Combiner les documents complétés et le journal d'audit"
|
||||
@@ -2213,7 +2393,6 @@ fr: &fr
|
||||
back: Retour
|
||||
add_secret: Ajouter un secret
|
||||
submission_example_payload: Exemple de payload de soumission
|
||||
status_users: '%{status} Utilisateurs'
|
||||
there_are_no_signatures: "Il n'y a pas de signatures"
|
||||
signed_with_trusted_certificate: Signé avec un certificat de confiance
|
||||
signed_with_external_certificate: Signé avec un certificat externe
|
||||
@@ -2321,6 +2500,60 @@ fr: &fr
|
||||
opened: Ouvert
|
||||
sent: Envoyé
|
||||
awaiting: En attente
|
||||
document_id: ID du document
|
||||
envelope_id: ID de l'enveloppe
|
||||
event_log: Journal d'événements
|
||||
verify: Vérifier
|
||||
testing_log_not_for_production_use: Journal de test - Ne pas utiliser en production
|
||||
original_sha256: SHA256 original
|
||||
result_sha256: SHA256 du résultat
|
||||
generated_at: Généré le
|
||||
email_verification: Vérification de l'e-mail
|
||||
phone_verification: Vérification du téléphone
|
||||
session_id: ID de session
|
||||
option: Option
|
||||
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
|
||||
paid_price: 'Payé %{price}'
|
||||
verified: Vérifié
|
||||
unverified: Non vérifié
|
||||
document: Document
|
||||
completed_at: Terminé le
|
||||
edit_recipient: Modifica Destinatario
|
||||
update_recipient: Aggiorna Destinatario
|
||||
use_international_format_1xxx_: 'Utilizza il formato internazionale: +1xxx...'
|
||||
submitter_cannot_be_updated: Il mittente non può essere aggiornato.
|
||||
at_least_one_field_must_be_filled: Almeno un campo deve essere compilato.
|
||||
archived_users: Utilisateurs Archivés
|
||||
embedding_users: Utilisateurs Intégrés
|
||||
view_embedding_users: Voir les Utilisateurs Intégrés
|
||||
submission_event_names:
|
||||
send_email_to_html: '<b>E-mail envoyé</b> à %{submitter_name}'
|
||||
send_reminder_email_to_html: '<b>E-mail de rappel envoyé</b> à %{submitter_name}'
|
||||
send_sms_to_html: '<b>SMS envoyé</b> à %{submitter_name}'
|
||||
send_2fa_sms_to_html: '<b>SMS de vérification envoyé</b> à %{submitter_name}'
|
||||
open_email_by_html: '<b>E-mail ouvert</b> par %{submitter_name}'
|
||||
click_email_by_html: "<b>Lien de l'e-mail cliqué</b> par %{submitter_name}"
|
||||
click_sms_by_html: '<b>Lien du SMS cliqué</b> par %{submitter_name}'
|
||||
phone_verified_by_html: '<b>Téléphone vérifié</b> par %{submitter_name}'
|
||||
start_form_by_html: '<b>Soumission commencée</b> par %{submitter_name}'
|
||||
view_form_by_html: '<b>Formulaire consulté</b> par %{submitter_name}'
|
||||
invite_party_by_html: '<b>Invité</b> %{invited_submitter_name} par %{submitter_name}'
|
||||
complete_form_by_html: '<b>Soumission terminée</b> par %{submitter_name}'
|
||||
api_complete_form_by_html: "<b>Soumission terminée via l'API</b> par %{submitter_name}"
|
||||
import_list:
|
||||
select_worksheet: Sélectionner la feuille de calcul
|
||||
open: Ouvrir
|
||||
@@ -2347,6 +2580,10 @@ pt: &pt
|
||||
enforce_recipients_order: 'Forçar a ordem dos recipientes'
|
||||
first_party: 'Primeira Parte'
|
||||
docuseal_trusted_signature: Assinatura Confiável DocuSeal
|
||||
hello_name: Olá %{name}
|
||||
you_are_invited_to_product_name: Você está convidado para %{product_name}
|
||||
you_have_been_invited_to_account_name_product_name_please_sign_up_using_the_link_below_: 'Você foi convidado para %{account_name} %{product_name}. Inscreva-se usando o link abaixo:'
|
||||
sent_using_product_name_free_document_signing_html: 'Enviado usando a assinatura gratuita de documentos de <a href="%{product_url}">%{product_name}</a>.'
|
||||
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: Assine documentos com certificado confiável fornecido pela DocuSeal. Seus documentos e dados nunca são compartilhados com a DocuSeal. O checksum do PDF é fornecido para gerar uma assinatura confiável.
|
||||
hi_there: Olá
|
||||
thanks: Obrigado
|
||||
@@ -2455,6 +2692,7 @@ pt: &pt
|
||||
require_signing_reason: Requerer motivo para assinatura
|
||||
allow_typed_text_signatures: Permitir assinaturas digitadas
|
||||
allow_to_resubmit_completed_forms: Permitir reenviar formulários concluídos
|
||||
allow_to_decline_documents: Permitir recusar documentos
|
||||
remember_and_pre_fill_signatures: Lembrar e preencher assinaturas automaticamente
|
||||
require_authentication_for_file_download_links: Requerer autenticação para links de download de arquivos
|
||||
combine_completed_documents_and_audit_log: Combinar documentos concluídos e log de auditoria
|
||||
@@ -2518,7 +2756,7 @@ pt: &pt
|
||||
removing: Removendo
|
||||
delete: Excluir
|
||||
default: Padrão
|
||||
are_you_sure_: Você tem certeza?
|
||||
are_you_sure_: Tem certeza?
|
||||
timestamp_server: Servidor de carimbo de tempo
|
||||
timeserver_url: URL do servidor de carimbo de tempo
|
||||
url_of_the_trusted_rfc_3161_timeserver_to_be_used_to_generate_timestamp_signatures: URL do servidor de carimbo de tempo RFC 3161 confiável a ser usado para gerar assinaturas de carimbo de tempo.
|
||||
@@ -2696,9 +2934,9 @@ pt: &pt
|
||||
draw_signature: Desenhar assinatura
|
||||
clear: Limpar
|
||||
signature_uploaded: Assinatura enviada
|
||||
submission_deletion_is_irreversible_and_will_permanently_remove_all_associated_signed_documents_with_it_are_you_sure_: 'A exclusão da submissão é irreversível e removerá permanentemente todos os documentos assinados associados a ela. Você tem certeza?'
|
||||
submission_deletion_is_irreversible_and_will_permanently_remove_all_associated_signed_documents_with_it_are_you_sure_: 'A exclusão da submissão é irreversível e removerá permanentemente todos os documentos assinados associados a ela. Tem certeza?'
|
||||
return_back_to_your_desktop_device_to_complete_the_form_or_continue_on_mobile_html: 'Volte para seu dispositivo desktop para concluir o formulário ou <a class="link" href="%{link}">continue no celular</a>'
|
||||
template_deletion_is_irreversible_and_will_permanently_remove_all_associated_signed_documents_with_it_are_you_sure_: A exclusão do modelo é irreversível e removerá permanentemente todos os documentos assinados associados a ele. Você tem certeza?
|
||||
template_deletion_is_irreversible_and_will_permanently_remove_all_associated_signed_documents_with_it_are_you_sure_: A exclusão do modelo é irreversível e removerá permanentemente todos os documentos assinados associados a ele. Tem certeza?
|
||||
rename_folder: Renomear pasta
|
||||
folder_name: Nome da pasta
|
||||
rename: Renomear
|
||||
@@ -2794,7 +3032,6 @@ pt: &pt
|
||||
back: Voltar
|
||||
add_secret: Adicionar segredo
|
||||
submission_example_payload: Exemplo de payload de submissão
|
||||
status_users: '%{status} Usuários'
|
||||
there_are_no_signatures: Não há assinaturas
|
||||
signed_with_trusted_certificate: Assinado com certificado confiável
|
||||
signed_with_external_certificate: Assinado com certificado externo
|
||||
@@ -2902,6 +3139,60 @@ pt: &pt
|
||||
opened: Aberto
|
||||
sent: Enviado
|
||||
awaiting: Aguardando
|
||||
document_id: ID do Documento
|
||||
envelope_id: ID do Envelope
|
||||
event_log: Log de Eventos
|
||||
verify: Verificar
|
||||
testing_log_not_for_production_use: Log de Teste - Não para Uso em Produção
|
||||
original_sha256: SHA256 Original
|
||||
result_sha256: SHA256 do Resultado
|
||||
generated_at: Gerado em
|
||||
email_verification: Verificação de E-mail
|
||||
phone_verification: Verificação de Telefone
|
||||
session_id: ID da Sessão
|
||||
option: Opção
|
||||
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
|
||||
paid_price: 'Pago %{price}'
|
||||
verified: Verificado
|
||||
unverified: Não verificado
|
||||
document: Documento
|
||||
completed_at: Concluído em
|
||||
edit_recipient: Editar Destinatário
|
||||
update_recipient: Atualizar Destinatário
|
||||
use_international_format_1xxx_: 'Use o formato internacional: +1xxx...'
|
||||
submitter_cannot_be_updated: O remetente não pode ser atualizado.
|
||||
at_least_one_field_must_be_filled: Pelo menos um campo deve ser preenchido.
|
||||
archived_users: Usuários Arquivados
|
||||
embedding_users: Usuários Incorporados
|
||||
view_embedding_users: Ver Usuários Incorporados
|
||||
submission_event_names:
|
||||
send_email_to_html: '<b>E-mail enviado</b> para %{submitter_name}'
|
||||
send_reminder_email_to_html: '<b>E-mail de lembrete enviado</b> para %{submitter_name}'
|
||||
send_sms_to_html: '<b>SMS enviado</b> para %{submitter_name}'
|
||||
send_2fa_sms_to_html: '<b>SMS de verificação enviado</b> para %{submitter_name}'
|
||||
open_email_by_html: '<b>E-mail aberto</b> por %{submitter_name}'
|
||||
click_email_by_html: '<b>Link do e-mail clicado</b> por %{submitter_name}'
|
||||
click_sms_by_html: '<b>Link do SMS clicado</b> por %{submitter_name}'
|
||||
phone_verified_by_html: '<b>Telefone verificado</b> por %{submitter_name}'
|
||||
start_form_by_html: '<b>Submissão iniciada</b> por %{submitter_name}'
|
||||
view_form_by_html: '<b>Formulário visualizado</b> por %{submitter_name}'
|
||||
invite_party_by_html: '<b>Convidado</b> %{invited_submitter_name} por %{submitter_name}'
|
||||
complete_form_by_html: '<b>Submissão concluída</b> por %{submitter_name}'
|
||||
api_complete_form_by_html: '<b>Submissão concluída via API</b> por %{submitter_name}'
|
||||
import_list:
|
||||
select_worksheet: Selecionar planilha
|
||||
open: Abrir
|
||||
@@ -2928,6 +3219,10 @@ de: &de
|
||||
enforce_recipients_order: 'Empfängerreihenfolge durchsetzen'
|
||||
first_party: 'Erste Partei'
|
||||
docuseal_trusted_signature: DocuSeal-Vertrauenssignatur
|
||||
hello_name: Hallo %{name}
|
||||
you_are_invited_to_product_name: Sie sind eingeladen zu %{product_name}
|
||||
you_have_been_invited_to_account_name_product_name_please_sign_up_using_the_link_below_: 'Sie wurden zu %{account_name} %{product_name} eingeladen. Bitte registrieren Sie sich über den folgenden Link:'
|
||||
sent_using_product_name_free_document_signing_html: 'Gesendet mit der kostenlosen Dokumentensignierung von <a href="%{product_url}">%{product_name}</a>.'
|
||||
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: Unterzeichnen Sie Dokumente mit einem vertrauenswürdigen Zertifikat von DocuSeal. Ihre Dokumente und Daten werden niemals mit DocuSeal geteilt. Eine PDF-Prüfziffer wird bereitgestellt, um eine vertrauenswürdige Signatur zu generieren.
|
||||
hi_there: Hallo
|
||||
thanks: Danke
|
||||
@@ -3036,6 +3331,7 @@ de: &de
|
||||
require_signing_reason: Grund für die Signatur erforderlich
|
||||
allow_typed_text_signatures: Unterschriften mit getipptem Text zulassen
|
||||
allow_to_resubmit_completed_forms: Erneutes Einreichen abgeschlossener Formulare zulassen
|
||||
allow_to_decline_documents: Erlauben, Dokumente abzulehnen
|
||||
remember_and_pre_fill_signatures: Signaturen merken und vorab ausfüllen
|
||||
require_authentication_for_file_download_links: Authentifizierung für Dateidownload-Links erforderlich
|
||||
combine_completed_documents_and_audit_log: Abgeschlossene Dokumente und Prüfprotokoll kombinieren
|
||||
@@ -3375,7 +3671,6 @@ de: &de
|
||||
back: Zurück
|
||||
add_secret: Geheimnis hinzufügen
|
||||
submission_example_payload: Beispiel-Payload für Einreichung
|
||||
status_users: '%{status} Benutzer'
|
||||
there_are_no_signatures: Es gibt keine Unterschriften
|
||||
signed_with_trusted_certificate: Signiert mit vertrauenswürdigem Zertifikat
|
||||
signed_with_external_certificate: Signiert mit externem Zertifikat
|
||||
@@ -3483,6 +3778,60 @@ de: &de
|
||||
opened: Geöffnet
|
||||
sent: Gesendet
|
||||
awaiting: Ausstehend
|
||||
document_id: Dokumenten-ID
|
||||
envelope_id: Umschlag-ID
|
||||
event_log: Ereignisprotokoll
|
||||
verify: Überprüfen
|
||||
testing_log_not_for_production_use: Testprotokoll - Nicht für den Produktionseinsatz
|
||||
original_sha256: Original SHA256
|
||||
result_sha256: Ergebnis SHA256
|
||||
generated_at: Generiert am
|
||||
email_verification: E-Mail-Verifizierung
|
||||
phone_verification: Telefonverifizierung
|
||||
session_id: Sitzungs-ID
|
||||
option: Option
|
||||
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
|
||||
paid_price: 'Bezahlt %{price}'
|
||||
verified: Verifiziert
|
||||
unverified: Nicht verifiziert
|
||||
document: Dokument
|
||||
completed_at: Abgeschlossen am
|
||||
edit_recipient: Empfänger bearbeiten
|
||||
update_recipient: Empfänger aktualisieren
|
||||
use_international_format_1xxx_: 'Verwenden Sie das internationale Format: +1xxx...'
|
||||
submitter_cannot_be_updated: Der Absender kann nicht aktualisiert werden.
|
||||
at_least_one_field_must_be_filled: Mindestens ein Feld muss ausgefüllt werden.
|
||||
archived_users: Archivierte Benutzer
|
||||
embedding_users: Einbettende Benutzer
|
||||
view_embedding_users: Einbettende Benutzer anzeigen
|
||||
submission_event_names:
|
||||
send_email_to_html: '<b>E-Mail gesendet</b> an %{submitter_name}'
|
||||
send_reminder_email_to_html: '<b>Erinnerungs-E-Mail gesendet</b> an %{submitter_name}'
|
||||
send_sms_to_html: '<b>SMS gesendet</b> an %{submitter_name}'
|
||||
send_2fa_sms_to_html: '<b>Verifizierungs-SMS gesendet</b> an %{submitter_name}'
|
||||
open_email_by_html: '<b>E-Mail geöffnet</b> von %{submitter_name}'
|
||||
click_email_by_html: '<b>E-Mail-Link angeklickt</b> von %{submitter_name}'
|
||||
click_sms_by_html: '<b>SMS-Link angeklickt</b> von %{submitter_name}'
|
||||
phone_verified_by_html: '<b>Telefon verifiziert</b> von %{submitter_name}'
|
||||
start_form_by_html: '<b>Einreichung gestartet</b> von %{submitter_name}'
|
||||
view_form_by_html: '<b>Formular angesehen</b> von %{submitter_name}'
|
||||
invite_party_by_html: '<b>Eingeladen</b> %{invited_submitter_name} von %{submitter_name}'
|
||||
complete_form_by_html: '<b>Einreichung abgeschlossen</b> von %{submitter_name}'
|
||||
api_complete_form_by_html: '<b>Einreichung über API abgeschlossen</b> von %{submitter_name}'
|
||||
import_list:
|
||||
select_worksheet: Arbeitsblatt auswählen
|
||||
open: Öffnen
|
||||
|
||||
+5
-1
@@ -34,6 +34,7 @@ Rails.application.routes.draw do
|
||||
resources :submitter_form_views, only: %i[create]
|
||||
resources :submitters, only: %i[index show update]
|
||||
resources :submissions, only: %i[index show create destroy] do
|
||||
resources :documents, only: %i[index], controller: 'submission_documents'
|
||||
collection do
|
||||
resources :init, only: %i[create], controller: 'submissions'
|
||||
resources :emails, only: %i[create], controller: 'submissions', as: :submissions_emails
|
||||
@@ -69,6 +70,7 @@ Rails.application.routes.draw do
|
||||
resources :submissions_archived, only: %i[index], path: 'submissions/archived'
|
||||
resources :submissions, only: %i[index], controller: 'submissions_dashboard'
|
||||
resources :submissions, only: %i[show destroy]
|
||||
resources :submitters, only: %i[edit update]
|
||||
resources :console_redirect, only: %i[index]
|
||||
resources :upgrade, only: %i[index], controller: 'console_redirect'
|
||||
resources :manage, only: %i[index], controller: 'console_redirect'
|
||||
@@ -151,15 +153,17 @@ Rails.application.routes.draw do
|
||||
scope '/settings', as: :settings do
|
||||
unless Docuseal.multitenant?
|
||||
resources :storage, only: %i[index create], controller: 'storage_settings'
|
||||
resources :email, only: %i[index create], controller: 'email_smtp_settings'
|
||||
resources :sms, only: %i[index], controller: 'sms_settings'
|
||||
end
|
||||
resources :email, only: %i[index create], controller: 'email_smtp_settings'
|
||||
resources :sso, only: %i[index], controller: 'sso_settings'
|
||||
resources :notifications, only: %i[index create], controller: 'notifications_settings'
|
||||
resource :esign, only: %i[show create new update destroy], controller: 'esign_settings'
|
||||
resources :users, only: %i[index]
|
||||
resources :archived_users, only: %i[index], path: 'users/:status', controller: 'users',
|
||||
defaults: { status: :archived }
|
||||
resources :integration_users, only: %i[index], path: 'users/:status', controller: 'users',
|
||||
defaults: { status: :integration }
|
||||
resource :personalization, only: %i[show create], controller: 'personalization_settings'
|
||||
resources :api, only: %i[index create], controller: 'api_settings'
|
||||
resource :webhooks, only: %i[show create update], controller: 'webhook_settings'
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
queues:
|
||||
- [default, 1]
|
||||
- [webhooks, 1]
|
||||
- [images, 1]
|
||||
- [mailers, 1]
|
||||
- [recurrent, 1]
|
||||
- [rollbar, 1]
|
||||
|
||||
@@ -11,6 +11,7 @@ const configs = generateWebpackConfig({
|
||||
},
|
||||
optimization: {
|
||||
runtimeChunk: false,
|
||||
concatenateModules: !process.env.BUNDLE_ANALYZE,
|
||||
splitChunks: {
|
||||
chunks (chunk) {
|
||||
return chunk.name !== 'rollbar'
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class CreateCompletedSubmittersAndDocuments < ActiveRecord::Migration[7.2]
|
||||
def change
|
||||
create_table :completed_submitters do |t|
|
||||
t.bigint :submitter_id, null: false, index: true
|
||||
t.bigint :submission_id, null: false
|
||||
t.bigint :account_id, null: false, index: true
|
||||
t.bigint :template_id, null: false
|
||||
t.string :source, null: false
|
||||
t.integer :sms_count, null: false
|
||||
t.datetime :completed_at, null: false
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
create_table :completed_documents do |t|
|
||||
t.bigint :submitter_id, null: false, index: true
|
||||
t.string :sha256, null: false, index: true
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,79 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class PopulateCompletedSubmittersAndDocuments < ActiveRecord::Migration[7.2]
|
||||
disable_ddl_transaction
|
||||
|
||||
class MigrationSubmitter < ApplicationRecord
|
||||
self.table_name = 'submitters'
|
||||
|
||||
belongs_to :submission, class_name: 'MigrationSubmission'
|
||||
has_many :submission_sms_events, -> { where(event_type: %w[send_sms send_2fa_sms]) },
|
||||
class_name: 'MigrationSubmissionEvent', foreign_key: :submitter_id
|
||||
end
|
||||
|
||||
class MigrationSubmission < ApplicationRecord
|
||||
self.table_name = 'submissions'
|
||||
end
|
||||
|
||||
class MigrationSubmissionEvent < ApplicationRecord
|
||||
self.table_name = 'submission_events'
|
||||
end
|
||||
|
||||
class MigrationCompletedSubmitter < ApplicationRecord
|
||||
self.table_name = 'completed_submitters'
|
||||
end
|
||||
|
||||
class MigrationCompletedDocument < ApplicationRecord
|
||||
self.table_name = 'completed_documents'
|
||||
end
|
||||
|
||||
def up
|
||||
submitters = MigrationSubmitter.where.not(completed_at: nil)
|
||||
.preload(:submission, :submission_sms_events)
|
||||
|
||||
count = submitters.count
|
||||
|
||||
puts "Updating the database - it might take ~#{(count / 1000 * 3) + 1} seconds" if count > 2000
|
||||
|
||||
submitters.find_each do |submitter|
|
||||
completed_submitter = MigrationCompletedSubmitter.find_or_initialize_by(submitter_id: submitter.id)
|
||||
|
||||
next if completed_submitter.persisted?
|
||||
|
||||
submission = submitter.submission
|
||||
|
||||
completed_submitter.assign_attributes(
|
||||
submission_id: submitter.submission_id,
|
||||
account_id: submission.account_id,
|
||||
template_id: submission.template_id,
|
||||
source: submission.source,
|
||||
sms_count: submitter.submission_sms_events.size,
|
||||
completed_at: submitter.completed_at,
|
||||
created_at: submitter.completed_at,
|
||||
updated_at: submitter.completed_at
|
||||
)
|
||||
|
||||
completed_submitter.save!
|
||||
end
|
||||
|
||||
attachments = ActiveStorage::Attachment.where(record_type: 'Submitter', name: 'documents').preload(:blob)
|
||||
|
||||
attachments.find_each do |attachment|
|
||||
sha256 = attachment.metadata['sha256']
|
||||
|
||||
next if sha256.blank?
|
||||
|
||||
completed_document = MigrationCompletedDocument.find_or_initialize_by(submitter_id: attachment.record_id, sha256:)
|
||||
|
||||
next if completed_document.persisted?
|
||||
|
||||
completed_document.assign_attributes(created_at: attachment.created_at, updated_at: attachment.created_at)
|
||||
|
||||
completed_document.save!
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
nil
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,8 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class AddUniqueIndexOnCompletedSubmitters < ActiveRecord::Migration[7.2]
|
||||
def change
|
||||
remove_index :completed_submitters, :submitter_id
|
||||
add_index :completed_submitters, :submitter_id, unique: true
|
||||
end
|
||||
end
|
||||
+24
-1
@@ -10,7 +10,7 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[7.1].define(version: 2024_08_20_180922) do
|
||||
ActiveRecord::Schema[7.2].define(version: 2024_10_26_161207) do
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
|
||||
@@ -89,6 +89,29 @@ ActiveRecord::Schema[7.1].define(version: 2024_08_20_180922) do
|
||||
t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true
|
||||
end
|
||||
|
||||
create_table "completed_documents", force: :cascade do |t|
|
||||
t.bigint "submitter_id", null: false
|
||||
t.string "sha256", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["sha256"], name: "index_completed_documents_on_sha256"
|
||||
t.index ["submitter_id"], name: "index_completed_documents_on_submitter_id"
|
||||
end
|
||||
|
||||
create_table "completed_submitters", force: :cascade do |t|
|
||||
t.bigint "submitter_id", null: false
|
||||
t.bigint "submission_id", null: false
|
||||
t.bigint "account_id", null: false
|
||||
t.bigint "template_id", null: false
|
||||
t.string "source", null: false
|
||||
t.integer "sms_count", null: false
|
||||
t.datetime "completed_at", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["account_id"], name: "index_completed_submitters_on_account_id"
|
||||
t.index ["submitter_id"], name: "index_completed_submitters_on_submitter_id", unique: true
|
||||
end
|
||||
|
||||
create_table "document_generation_events", force: :cascade do |t|
|
||||
t.bigint "submitter_id", null: false
|
||||
t.string "event_name", null: false
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ module Accounts
|
||||
new_user.uuid = SecureRandom.uuid
|
||||
new_user.account = new_account
|
||||
new_user.encrypted_password = SecureRandom.hex
|
||||
new_user.email = "#{SecureRandom.hex}@docuseal.co"
|
||||
new_user.email = "#{SecureRandom.hex}@docuseal.com"
|
||||
|
||||
account.templates.each do |template|
|
||||
new_template = template.dup
|
||||
|
||||
@@ -16,19 +16,27 @@ module ActionMailerConfigsInterceptor
|
||||
end
|
||||
|
||||
if Rails.env.production? && Rails.application.config.action_mailer.delivery_method
|
||||
message.from = ENV.fetch('SMTP_FROM')
|
||||
from = ENV.fetch('SMTP_FROM').to_s.split(',').sample
|
||||
|
||||
if from.match?(User::FULL_EMAIL_REGEXP)
|
||||
message[:from] = message[:from].to_s.sub(User::EMAIL_REGEXP, from)
|
||||
else
|
||||
message.from = from
|
||||
end
|
||||
|
||||
return message
|
||||
end
|
||||
|
||||
email_configs = EncryptedConfig.find_by(key: EncryptedConfig::EMAIL_SMTP_KEY)
|
||||
unless Docuseal.multitenant?
|
||||
email_configs = EncryptedConfig.find_by(key: EncryptedConfig::EMAIL_SMTP_KEY)
|
||||
|
||||
if email_configs
|
||||
message.delivery_method(:smtp, build_smtp_configs_hash(email_configs))
|
||||
if email_configs
|
||||
message.delivery_method(:smtp, build_smtp_configs_hash(email_configs))
|
||||
|
||||
message.from = %("#{email_configs.account.name.to_s.delete('"')}" <#{email_configs.value['from_email']}>)
|
||||
else
|
||||
message.delivery_method(:test)
|
||||
message.from = %("#{email_configs.account.name.to_s.delete('"')}" <#{email_configs.value['from_email']}>)
|
||||
else
|
||||
message.delivery_method(:test)
|
||||
end
|
||||
end
|
||||
|
||||
message
|
||||
@@ -43,9 +51,9 @@ module ActionMailerConfigsInterceptor
|
||||
address: value['host'],
|
||||
port: value['port'],
|
||||
domain: value['domain'],
|
||||
openssl_verify_mode: OpenSSL::SSL::VERIFY_NONE,
|
||||
authentication: value.fetch('authentication', 'plain'),
|
||||
enable_starttls_auto: true,
|
||||
openssl_verify_mode: value['security'] == 'noverify' ? OpenSSL::SSL::VERIFY_NONE : nil,
|
||||
authentication: value['password'].present? ? value.fetch('authentication', 'plain') : nil,
|
||||
enable_starttls_auto: value['security'] != 'tls',
|
||||
open_timeout: OPEN_TIMEOUT,
|
||||
read_timeout: READ_TIMEOUT,
|
||||
ssl: value['security'] == 'ssl',
|
||||
|
||||
@@ -22,7 +22,7 @@ module ActionMailerEventsObserver
|
||||
emailable_type:,
|
||||
event_type: :send,
|
||||
email:,
|
||||
data: { method: mail.delivery_method.class.name.underscore },
|
||||
data: { from: mail.from, method: mail.delivery_method.class.name.underscore },
|
||||
event_datetime: Time.current
|
||||
)
|
||||
end
|
||||
|
||||
+4
-4
@@ -12,7 +12,7 @@ module Docuseal
|
||||
TWITTER_URL = 'https://twitter.com/docusealco'
|
||||
TWITTER_HANDLE = '@docusealco'
|
||||
CHATGPT_URL = 'https://chatgpt.com/g/g-9hg8AAw0r-docuseal'
|
||||
SUPPORT_EMAIL = 'support@docuseal.co'
|
||||
SUPPORT_EMAIL = 'support@docuseal.com'
|
||||
HOST = ENV.fetch('HOST', 'localhost')
|
||||
AATL_CERT_NAME = 'docuseal_aatl'
|
||||
CONSOLE_URL = if Rails.env.development?
|
||||
@@ -20,19 +20,19 @@ module Docuseal
|
||||
elsif ENV['MULTITENANT'] == 'true'
|
||||
"https://console.#{HOST}"
|
||||
else
|
||||
'https://console.docuseal.co'
|
||||
'https://console.docuseal.com'
|
||||
end
|
||||
CLOUD_URL = if Rails.env.development?
|
||||
'http://localhost:3000'
|
||||
else
|
||||
'https://docuseal.co'
|
||||
'https://docuseal.com'
|
||||
end
|
||||
CDN_URL = if Rails.env.development?
|
||||
'http://localhost:3000'
|
||||
elsif ENV['MULTITENANT'] == 'true'
|
||||
"https://cdn.#{HOST}"
|
||||
else
|
||||
'https://cdn.docuseal.co'
|
||||
'https://cdn.docuseal.com'
|
||||
end
|
||||
|
||||
CERTS = JSON.parse(ENV.fetch('CERTS', '{}'))
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
module Params
|
||||
class BaseValidator
|
||||
EMAIL_REGEXP = User::FULL_EMAIL_REGEXP
|
||||
|
||||
InvalidParameterError = Class.new(StandardError)
|
||||
|
||||
def self.call(...)
|
||||
@@ -65,6 +67,28 @@ module Params
|
||||
raise_error(message || "#{key} must follow the #{regexp.source} format")
|
||||
end
|
||||
|
||||
def email_format(params, key, message: nil)
|
||||
return if params.blank?
|
||||
return if params[key].blank?
|
||||
return if params[key].to_s.include?('<')
|
||||
|
||||
if params[key].to_s.strip.split(/\s*[;,]\s*/).compact_blank
|
||||
.all? { |email| EmailTypo::DotCom.call(email).match?(EMAIL_REGEXP) }
|
||||
return
|
||||
end
|
||||
|
||||
raise_error(message || "#{key} must follow the email format: '#{params[key]}'")
|
||||
end
|
||||
|
||||
def unique_value(params, key, message: nil)
|
||||
return unless params.is_a?(Array)
|
||||
return if params.none?
|
||||
return if params.all? { |p| p[key].blank? }
|
||||
return if params.pluck(key).compact_blank.uniq.size == params.pluck(key).compact_blank.size
|
||||
|
||||
raise_error(message || "#{key} must be unique")
|
||||
end
|
||||
|
||||
def in_path(params, path = [])
|
||||
old_path = @current_path
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ module Params
|
||||
required(params, %i[emails email])
|
||||
|
||||
type(params, :emails, String)
|
||||
email_format(params, :emails, message: 'emails are invalid')
|
||||
boolean(params, :send_email)
|
||||
type(params, :message, Hash)
|
||||
|
||||
@@ -43,8 +44,8 @@ module Params
|
||||
type(params, :completed_redirect_url, String)
|
||||
type(params, :bcc_completed, String)
|
||||
type(params, :reply_to, String)
|
||||
format(params, :bcc_completed, /@/, message: 'bcc_completed email is invalid')
|
||||
format(params, :reply_to, /@/, message: 'reply_to email is invalid')
|
||||
email_format(params, :bcc_completed, message: 'bcc_completed email is invalid')
|
||||
email_format(params, :reply_to, message: 'reply_to email is invalid')
|
||||
type(params, :message, Hash)
|
||||
type(params, :submitters, Array)
|
||||
|
||||
@@ -60,6 +61,7 @@ module Params
|
||||
if params[:submitters].present?
|
||||
in_path(params, :submitters) do |submitters_params|
|
||||
type(submitters_params, 0, Hash)
|
||||
unique_value(submitters_params, :role)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -74,8 +76,8 @@ module Params
|
||||
type(submitter_params, :name, String)
|
||||
type(submitter_params, :reply_to, String)
|
||||
type(submitter_params, :email, String)
|
||||
format(submitter_params, :email, /@/, message: 'email is invalid')
|
||||
format(submitter_params, :reply_to, /@/, message: 'reply_to email is invalid')
|
||||
email_format(submitter_params, :email, message: 'email is invalid')
|
||||
email_format(submitter_params, :reply_to, message: 'reply_to email is invalid')
|
||||
type(submitter_params, :phone, String)
|
||||
format(submitter_params, :phone, /\A\+\d+\z/,
|
||||
message: 'phone should start with +<country code> and contain only digits')
|
||||
@@ -105,7 +107,7 @@ module Params
|
||||
type(params, :order, String)
|
||||
type(params, :completed_redirect_url, String)
|
||||
type(params, :bcc_completed, String)
|
||||
format(params, :bcc_completed, /@/, message: 'bcc_completed email is invalid')
|
||||
email_format(params, :bcc_completed, message: 'bcc_completed email is invalid')
|
||||
type(params, :message, Hash)
|
||||
|
||||
in_path(params, :message) do |message_params|
|
||||
|
||||
@@ -23,7 +23,7 @@ module ReplaceEmailVariables
|
||||
|
||||
# rubocop:disable Metrics
|
||||
def call(text, submitter:, tracking_event_type: 'click_email', html_escape: false, sig: nil)
|
||||
text = replace(text, TEMPLATE_NAME, html_escape:) { submitter.template.name }
|
||||
text = replace(text, TEMPLATE_NAME, html_escape:) { (submitter.template || submitter.submission.template).name }
|
||||
text = replace(text, TEMPLATE_ID, html_escape:) { submitter.template.id }
|
||||
text = replace(text, SUBMITTER_ID, html_escape:) { submitter.id }
|
||||
text = replace(text, SUBMITTER_SLUG, html_escape:) { submitter.slug }
|
||||
|
||||
@@ -3,22 +3,6 @@
|
||||
module SubmissionEvents
|
||||
TRACKING_PARAM_LENGTH = 6
|
||||
|
||||
EVENT_NAMES = {
|
||||
send_email: 'Email sent',
|
||||
send_reminder_email: 'Reminder email sent',
|
||||
send_sms: 'SMS sent',
|
||||
send_2fa_sms: 'Verification SMS sent',
|
||||
open_email: 'Email opened',
|
||||
click_email: 'Email link clicked',
|
||||
click_sms: 'SMS link clicked',
|
||||
phone_verified: 'Phone verified',
|
||||
start_form: 'Submission started',
|
||||
view_form: 'Form viewed',
|
||||
invite_party: 'Invited',
|
||||
complete_form: 'Submission completed',
|
||||
api_complete_form: 'Submission completed via API'
|
||||
}.freeze
|
||||
|
||||
module_function
|
||||
|
||||
def build_tracking_param(submitter, event_type = 'click_email')
|
||||
|
||||
@@ -111,6 +111,9 @@ module Submissions
|
||||
return email.downcase if email.to_s.include?('.mm')
|
||||
return email.downcase if email.to_s.include?('.cm')
|
||||
return email.downcase if email.to_s.include?('.et')
|
||||
return email.downcase if email.to_s.include?('.mo')
|
||||
return email.downcase if email.to_s.include?('.nz')
|
||||
return email.downcase if email.to_s.include?('.za')
|
||||
return email.downcase unless email.to_s.include?('.')
|
||||
|
||||
fixed_email = EmailTypo.call(email.delete_prefix('<'))
|
||||
|
||||
Loaded 100 of 124 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user