mirror of
https://github.com/docusealco/docuseal
synced 2026-11-04 14:42:32 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ac9a5c9556 | ||
|
|
20d09f6dff | ||
|
|
333a758688 | ||
|
|
2c8b4d5816 | ||
|
|
9ce822bce8 | ||
|
|
0a382a688c | ||
|
|
bd3008a35a | ||
|
|
e9bd11cb2e | ||
|
|
ca429bc445 | ||
|
|
21a0c54843 | ||
|
|
2f41d13009 | ||
|
|
f69dcf77de | ||
|
|
32596d738c | ||
|
|
e7e5d0e776 | ||
|
|
bb3b38e76f | ||
|
|
bc4c21f804 | ||
|
|
2fb5de4fa9 | ||
|
|
2e58b2e37b | ||
|
|
780f5512f4 | ||
|
|
2cf4908d53 | ||
|
|
4a9b898713 | ||
|
|
bb552d13d4 | ||
|
|
15d06f4fb0 | ||
|
|
35a2aba887 | ||
|
|
48a82b1d49 | ||
|
|
b8453e14c4 | ||
|
|
29fbdfbe2f | ||
|
|
5340af0bc6 | ||
|
|
1881b6c50f | ||
|
|
b6a2aae970 | ||
|
|
b317094192 | ||
|
|
ab1f4cf297 | ||
|
|
d3033c072a | ||
|
|
f380200e03 | ||
|
|
a4928268ea | ||
|
|
dd18c51384 | ||
|
|
c2014a1549 | ||
|
|
46edc7dc37 | ||
|
|
d62d83ef8e | ||
|
|
79350c8fba | ||
|
|
6702dd52b6 | ||
|
|
c8b74a2c43 | ||
|
|
1746920eb5 | ||
|
|
bacbc4309c | ||
|
|
4429d742b3 | ||
|
|
38b5eccb65 | ||
|
|
d8126c4a47 | ||
|
|
b1c31672ee | ||
|
|
975f3a95fe | ||
|
|
e3bed5ae67 | ||
|
|
4737b103c0 | ||
|
|
c5178b96d3 | ||
|
|
39a2159196 | ||
|
|
5724159b7b | ||
|
|
05b7cc9fa6 | ||
|
|
18b13b5b1e | ||
|
|
d25555b9b0 | ||
|
|
eec266ee83 | ||
|
|
75683631f0 | ||
|
|
36c64d1de4 | ||
|
|
2552b24f21 | ||
|
|
e6ca6dfd85 | ||
|
|
fa2d86b20a | ||
|
|
8e212e4586 | ||
|
|
a59c18f04b | ||
|
|
1ab0f1c90c | ||
|
|
9beaaa0851 | ||
|
|
b86aac86d1 | ||
|
|
c2eb94b571 | ||
|
|
a377e45a63 | ||
|
|
58574abc6b | ||
|
|
cbae888971 | ||
|
|
944e1f31db | ||
|
|
711b3b3190 | ||
|
|
e7abadcefb | ||
|
|
18d60f5522 | ||
|
|
6d5c6e086d | ||
|
|
5a48ab0360 | ||
|
|
599785b6d1 | ||
|
|
f2fd5fb82b | ||
|
|
f2782bd84e | ||
|
|
e52a721fc9 | ||
|
|
b035123ff9 | ||
|
|
ca8de23082 | ||
|
|
f514654c40 | ||
|
|
99bc528a6c | ||
|
|
f3c77d0177 | ||
|
|
db2b5f35bd | ||
|
|
5728381725 | ||
|
|
a7affb1a49 | ||
|
|
21bf5ed9f7 | ||
|
|
8ddd4ee916 | ||
|
|
0e2fd8ee4f | ||
|
|
27901063ed | ||
|
|
ce31ebde1e | ||
|
|
0c2abf211e | ||
|
|
8ecaa4fd7a | ||
|
|
6759c26d86 | ||
|
|
76f1f140ac | ||
|
|
ca7ab5aab7 | ||
|
|
fd3530ac62 | ||
|
|
4ea863592b | ||
|
|
3f87d0336d | ||
|
|
e251482481 | ||
|
|
c79fca75f3 | ||
|
|
0b0f0b497e | ||
|
|
e9034d8da7 | ||
|
|
8fbb8b8f6e | ||
|
|
4635fdd32b | ||
|
|
6dd4382446 | ||
|
|
50acff2e81 | ||
|
|
cb6d24dfca | ||
|
|
ab4a22d8ba | ||
|
|
bc40a15de1 | ||
|
|
2542f26d96 | ||
|
|
84edb6dbda | ||
|
|
88776e7316 | ||
|
|
f669045362 | ||
|
|
68d3efbdba | ||
|
|
22d4833496 | ||
|
|
ff14caa9f7 | ||
|
|
05647135bc | ||
|
|
ae76c2a4a8 | ||
|
|
fb2b3d44fe | ||
|
|
3868557efd | ||
|
|
9bef28c8ba | ||
|
|
f25fd3b03d | ||
|
|
d1d7571ee4 | ||
|
|
6a298a068c | ||
|
|
d4aef79b68 |
No files matched your search
+5
-5
@@ -51,25 +51,25 @@ Metrics/AbcSize:
|
|||||||
- spec/**/*
|
- spec/**/*
|
||||||
|
|
||||||
Metrics/ModuleLength:
|
Metrics/ModuleLength:
|
||||||
Max: 500
|
Max: 1000
|
||||||
|
|
||||||
Metrics/ClassLength:
|
Metrics/ClassLength:
|
||||||
Max: 500
|
Max: 1000
|
||||||
|
|
||||||
RSpec/NestedGroups:
|
RSpec/NestedGroups:
|
||||||
Max: 6
|
Max: 6
|
||||||
|
|
||||||
RSpec/MultipleExpectations:
|
RSpec/MultipleExpectations:
|
||||||
Max: 20
|
Max: 25
|
||||||
|
|
||||||
RSpec/ExampleLength:
|
RSpec/ExampleLength:
|
||||||
Max: 40
|
Max: 50
|
||||||
|
|
||||||
RSpec/MultipleMemoizedHelpers:
|
RSpec/MultipleMemoizedHelpers:
|
||||||
Max: 9
|
Max: 9
|
||||||
|
|
||||||
Metrics/BlockNesting:
|
Metrics/BlockNesting:
|
||||||
Max: 4
|
Max: 5
|
||||||
|
|
||||||
Rails/I18nLocaleTexts:
|
Rails/I18nLocaleTexts:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|||||||
+2
-1
@@ -62,12 +62,13 @@ RUN apk add --no-cache build-base && bundle install && apk del --no-cache build-
|
|||||||
COPY ./bin ./bin
|
COPY ./bin ./bin
|
||||||
COPY ./app ./app
|
COPY ./app ./app
|
||||||
COPY ./config ./config
|
COPY ./config ./config
|
||||||
COPY ./db ./db
|
COPY ./db/migrate ./db/migrate
|
||||||
COPY ./log ./log
|
COPY ./log ./log
|
||||||
COPY ./lib ./lib
|
COPY ./lib ./lib
|
||||||
COPY ./public ./public
|
COPY ./public ./public
|
||||||
COPY ./tmp ./tmp
|
COPY ./tmp ./tmp
|
||||||
COPY LICENSE README.md Rakefile config.ru .version ./
|
COPY LICENSE README.md Rakefile config.ru .version ./
|
||||||
|
COPY .version ./public/version
|
||||||
|
|
||||||
COPY --from=fonts /fonts/GoNotoKurrent-Regular.ttf /fonts/GoNotoKurrent-Bold.ttf /fonts/DancingScript-Regular.otf /fonts/OFL.txt /fonts
|
COPY --from=fonts /fonts/GoNotoKurrent-Regular.ttf /fonts/GoNotoKurrent-Bold.ttf /fonts/DancingScript-Regular.otf /fonts/OFL.txt /fonts
|
||||||
COPY --from=fonts /fonts/FreeSans.ttf /usr/share/fonts/freefont
|
COPY --from=fonts /fonts/FreeSans.ttf /usr/share/fonts/freefont
|
||||||
|
|||||||
+160
-154
@@ -1,67 +1,67 @@
|
|||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
actioncable (7.2.1.1)
|
actioncable (8.0.1)
|
||||||
actionpack (= 7.2.1.1)
|
actionpack (= 8.0.1)
|
||||||
activesupport (= 7.2.1.1)
|
activesupport (= 8.0.1)
|
||||||
nio4r (~> 2.0)
|
nio4r (~> 2.0)
|
||||||
websocket-driver (>= 0.6.1)
|
websocket-driver (>= 0.6.1)
|
||||||
zeitwerk (~> 2.6)
|
zeitwerk (~> 2.6)
|
||||||
actionmailbox (7.2.1.1)
|
actionmailbox (8.0.1)
|
||||||
actionpack (= 7.2.1.1)
|
actionpack (= 8.0.1)
|
||||||
activejob (= 7.2.1.1)
|
activejob (= 8.0.1)
|
||||||
activerecord (= 7.2.1.1)
|
activerecord (= 8.0.1)
|
||||||
activestorage (= 7.2.1.1)
|
activestorage (= 8.0.1)
|
||||||
activesupport (= 7.2.1.1)
|
activesupport (= 8.0.1)
|
||||||
mail (>= 2.8.0)
|
mail (>= 2.8.0)
|
||||||
actionmailer (7.2.1.1)
|
actionmailer (8.0.1)
|
||||||
actionpack (= 7.2.1.1)
|
actionpack (= 8.0.1)
|
||||||
actionview (= 7.2.1.1)
|
actionview (= 8.0.1)
|
||||||
activejob (= 7.2.1.1)
|
activejob (= 8.0.1)
|
||||||
activesupport (= 7.2.1.1)
|
activesupport (= 8.0.1)
|
||||||
mail (>= 2.8.0)
|
mail (>= 2.8.0)
|
||||||
rails-dom-testing (~> 2.2)
|
rails-dom-testing (~> 2.2)
|
||||||
actionpack (7.2.1.1)
|
actionpack (8.0.1)
|
||||||
actionview (= 7.2.1.1)
|
actionview (= 8.0.1)
|
||||||
activesupport (= 7.2.1.1)
|
activesupport (= 8.0.1)
|
||||||
nokogiri (>= 1.8.5)
|
nokogiri (>= 1.8.5)
|
||||||
racc
|
rack (>= 2.2.4)
|
||||||
rack (>= 2.2.4, < 3.2)
|
|
||||||
rack-session (>= 1.0.1)
|
rack-session (>= 1.0.1)
|
||||||
rack-test (>= 0.6.3)
|
rack-test (>= 0.6.3)
|
||||||
rails-dom-testing (~> 2.2)
|
rails-dom-testing (~> 2.2)
|
||||||
rails-html-sanitizer (~> 1.6)
|
rails-html-sanitizer (~> 1.6)
|
||||||
useragent (~> 0.16)
|
useragent (~> 0.16)
|
||||||
actiontext (7.2.1.1)
|
actiontext (8.0.1)
|
||||||
actionpack (= 7.2.1.1)
|
actionpack (= 8.0.1)
|
||||||
activerecord (= 7.2.1.1)
|
activerecord (= 8.0.1)
|
||||||
activestorage (= 7.2.1.1)
|
activestorage (= 8.0.1)
|
||||||
activesupport (= 7.2.1.1)
|
activesupport (= 8.0.1)
|
||||||
globalid (>= 0.6.0)
|
globalid (>= 0.6.0)
|
||||||
nokogiri (>= 1.8.5)
|
nokogiri (>= 1.8.5)
|
||||||
actionview (7.2.1.1)
|
actionview (8.0.1)
|
||||||
activesupport (= 7.2.1.1)
|
activesupport (= 8.0.1)
|
||||||
builder (~> 3.1)
|
builder (~> 3.1)
|
||||||
erubi (~> 1.11)
|
erubi (~> 1.11)
|
||||||
rails-dom-testing (~> 2.2)
|
rails-dom-testing (~> 2.2)
|
||||||
rails-html-sanitizer (~> 1.6)
|
rails-html-sanitizer (~> 1.6)
|
||||||
activejob (7.2.1.1)
|
activejob (8.0.1)
|
||||||
activesupport (= 7.2.1.1)
|
activesupport (= 8.0.1)
|
||||||
globalid (>= 0.3.6)
|
globalid (>= 0.3.6)
|
||||||
activemodel (7.2.1.1)
|
activemodel (8.0.1)
|
||||||
activesupport (= 7.2.1.1)
|
activesupport (= 8.0.1)
|
||||||
activerecord (7.2.1.1)
|
activerecord (8.0.1)
|
||||||
activemodel (= 7.2.1.1)
|
activemodel (= 8.0.1)
|
||||||
activesupport (= 7.2.1.1)
|
activesupport (= 8.0.1)
|
||||||
timeout (>= 0.4.0)
|
timeout (>= 0.4.0)
|
||||||
activestorage (7.2.1.1)
|
activestorage (8.0.1)
|
||||||
actionpack (= 7.2.1.1)
|
actionpack (= 8.0.1)
|
||||||
activejob (= 7.2.1.1)
|
activejob (= 8.0.1)
|
||||||
activerecord (= 7.2.1.1)
|
activerecord (= 8.0.1)
|
||||||
activesupport (= 7.2.1.1)
|
activesupport (= 8.0.1)
|
||||||
marcel (~> 1.0)
|
marcel (~> 1.0)
|
||||||
activesupport (7.2.1.1)
|
activesupport (8.0.1)
|
||||||
base64
|
base64
|
||||||
|
benchmark (>= 0.3)
|
||||||
bigdecimal
|
bigdecimal
|
||||||
concurrent-ruby (~> 1.0, >= 1.3.1)
|
concurrent-ruby (~> 1.0, >= 1.3.1)
|
||||||
connection_pool (>= 2.2.5)
|
connection_pool (>= 2.2.5)
|
||||||
@@ -71,31 +71,32 @@ GEM
|
|||||||
minitest (>= 5.1)
|
minitest (>= 5.1)
|
||||||
securerandom (>= 0.3)
|
securerandom (>= 0.3)
|
||||||
tzinfo (~> 2.0, >= 2.0.5)
|
tzinfo (~> 2.0, >= 2.0.5)
|
||||||
|
uri (>= 0.13.1)
|
||||||
addressable (2.8.7)
|
addressable (2.8.7)
|
||||||
public_suffix (>= 2.0.2, < 7.0)
|
public_suffix (>= 2.0.2, < 7.0)
|
||||||
annotate (3.2.0)
|
annotate (2.6.5)
|
||||||
activerecord (>= 3.2, < 8.0)
|
activerecord (>= 2.3.0)
|
||||||
rake (>= 10.4, < 14.0)
|
rake (>= 0.8.7)
|
||||||
arabic-letter-connector (0.1.1)
|
arabic-letter-connector (0.1.1)
|
||||||
ast (2.4.2)
|
ast (2.4.2)
|
||||||
aws-eventstream (1.3.0)
|
aws-eventstream (1.3.0)
|
||||||
aws-partitions (1.985.0)
|
aws-partitions (1.1027.0)
|
||||||
aws-sdk-core (3.209.1)
|
aws-sdk-core (3.214.0)
|
||||||
aws-eventstream (~> 1, >= 1.3.0)
|
aws-eventstream (~> 1, >= 1.3.0)
|
||||||
aws-partitions (~> 1, >= 1.651.0)
|
aws-partitions (~> 1, >= 1.992.0)
|
||||||
aws-sigv4 (~> 1.9)
|
aws-sigv4 (~> 1.9)
|
||||||
jmespath (~> 1, >= 1.6.1)
|
jmespath (~> 1, >= 1.6.1)
|
||||||
aws-sdk-kms (1.94.0)
|
aws-sdk-kms (1.96.0)
|
||||||
aws-sdk-core (~> 3, >= 3.207.0)
|
aws-sdk-core (~> 3, >= 3.210.0)
|
||||||
aws-sigv4 (~> 1.5)
|
aws-sigv4 (~> 1.5)
|
||||||
aws-sdk-s3 (1.167.0)
|
aws-sdk-s3 (1.176.1)
|
||||||
aws-sdk-core (~> 3, >= 3.207.0)
|
aws-sdk-core (~> 3, >= 3.210.0)
|
||||||
aws-sdk-kms (~> 1)
|
aws-sdk-kms (~> 1)
|
||||||
aws-sigv4 (~> 1.5)
|
aws-sigv4 (~> 1.5)
|
||||||
aws-sdk-secretsmanager (1.108.0)
|
aws-sdk-secretsmanager (1.110.0)
|
||||||
aws-sdk-core (~> 3, >= 3.207.0)
|
aws-sdk-core (~> 3, >= 3.210.0)
|
||||||
aws-sigv4 (~> 1.5)
|
aws-sigv4 (~> 1.5)
|
||||||
aws-sigv4 (1.10.0)
|
aws-sigv4 (1.10.1)
|
||||||
aws-eventstream (~> 1, >= 1.0.2)
|
aws-eventstream (~> 1, >= 1.0.2)
|
||||||
azure-storage-blob (2.0.3)
|
azure-storage-blob (2.0.3)
|
||||||
azure-storage-common (~> 2.0)
|
azure-storage-common (~> 2.0)
|
||||||
@@ -107,6 +108,7 @@ GEM
|
|||||||
nokogiri (~> 1, >= 1.10.8)
|
nokogiri (~> 1, >= 1.10.8)
|
||||||
base64 (0.2.0)
|
base64 (0.2.0)
|
||||||
bcrypt (3.1.20)
|
bcrypt (3.1.20)
|
||||||
|
benchmark (0.4.0)
|
||||||
better_html (2.1.1)
|
better_html (2.1.1)
|
||||||
actionview (>= 6.0)
|
actionview (>= 6.0)
|
||||||
activesupport (>= 6.0)
|
activesupport (>= 6.0)
|
||||||
@@ -119,7 +121,7 @@ GEM
|
|||||||
bootsnap (1.18.4)
|
bootsnap (1.18.4)
|
||||||
msgpack (~> 1.2)
|
msgpack (~> 1.2)
|
||||||
builder (3.3.0)
|
builder (3.3.0)
|
||||||
bullet (7.2.0)
|
bullet (8.0.0)
|
||||||
activesupport (>= 3.0.0)
|
activesupport (>= 3.0.0)
|
||||||
uniform_notifier (~> 1.11)
|
uniform_notifier (~> 1.11)
|
||||||
camertron-eprun (1.1.1)
|
camertron-eprun (1.1.1)
|
||||||
@@ -145,14 +147,14 @@ GEM
|
|||||||
bigdecimal
|
bigdecimal
|
||||||
rexml
|
rexml
|
||||||
crass (1.0.6)
|
crass (1.0.6)
|
||||||
css_parser (1.19.0)
|
css_parser (1.21.0)
|
||||||
addressable
|
addressable
|
||||||
csv (3.3.0)
|
csv (3.3.2)
|
||||||
cuprite (0.15.1)
|
cuprite (0.15.1)
|
||||||
capybara (~> 3.0)
|
capybara (~> 3.0)
|
||||||
ferrum (~> 0.15.0)
|
ferrum (~> 0.15.0)
|
||||||
date (3.3.4)
|
date (3.4.1)
|
||||||
debug (1.9.2)
|
debug (1.10.0)
|
||||||
irb (~> 1.10)
|
irb (~> 1.10)
|
||||||
reline (>= 0.3.8)
|
reline (>= 0.3.8)
|
||||||
declarative (0.0.20)
|
declarative (0.0.20)
|
||||||
@@ -162,32 +164,32 @@ GEM
|
|||||||
railties (>= 4.1.0)
|
railties (>= 4.1.0)
|
||||||
responders
|
responders
|
||||||
warden (~> 1.2.3)
|
warden (~> 1.2.3)
|
||||||
devise-two-factor (6.0.0)
|
devise-two-factor (6.1.0)
|
||||||
activesupport (~> 7.0)
|
activesupport (>= 7.0, < 8.1)
|
||||||
devise (~> 4.0)
|
devise (~> 4.0)
|
||||||
railties (~> 7.0)
|
railties (>= 7.0, < 8.1)
|
||||||
rotp (~> 6.0)
|
rotp (~> 6.0)
|
||||||
diff-lcs (1.5.1)
|
diff-lcs (1.5.1)
|
||||||
digest-crc (0.6.5)
|
digest-crc (0.6.5)
|
||||||
rake (>= 12.0.0, < 14.0.0)
|
rake (>= 12.0.0, < 14.0.0)
|
||||||
docile (1.4.1)
|
docile (1.4.1)
|
||||||
dotenv (3.1.4)
|
dotenv (3.1.7)
|
||||||
drb (2.2.1)
|
drb (2.2.1)
|
||||||
email_typo (0.2.3)
|
email_typo (0.2.3)
|
||||||
erb_lint (0.6.0)
|
erb_lint (0.7.0)
|
||||||
activesupport
|
activesupport
|
||||||
better_html (>= 2.0.1)
|
better_html (>= 2.0.1)
|
||||||
parser (>= 2.7.1.4)
|
parser (>= 2.7.1.4)
|
||||||
rainbow
|
rainbow
|
||||||
rubocop (>= 1)
|
rubocop (>= 1)
|
||||||
smart_properties
|
smart_properties
|
||||||
erubi (1.13.0)
|
erubi (1.13.1)
|
||||||
factory_bot (6.5.0)
|
factory_bot (6.5.0)
|
||||||
activesupport (>= 5.0.0)
|
activesupport (>= 5.0.0)
|
||||||
factory_bot_rails (6.4.3)
|
factory_bot_rails (6.4.4)
|
||||||
factory_bot (~> 6.4)
|
factory_bot (~> 6.5)
|
||||||
railties (>= 5.0.0)
|
railties (>= 5.0.0)
|
||||||
faker (3.4.2)
|
faker (3.5.1)
|
||||||
i18n (>= 1.8.11, < 2)
|
i18n (>= 1.8.11, < 2)
|
||||||
faraday (1.10.4)
|
faraday (1.10.4)
|
||||||
faraday-em_http (~> 1.0)
|
faraday-em_http (~> 1.0)
|
||||||
@@ -207,8 +209,8 @@ GEM
|
|||||||
faraday-follow_redirects (0.3.0)
|
faraday-follow_redirects (0.3.0)
|
||||||
faraday (>= 1, < 3)
|
faraday (>= 1, < 3)
|
||||||
faraday-httpclient (1.0.1)
|
faraday-httpclient (1.0.1)
|
||||||
faraday-multipart (1.0.4)
|
faraday-multipart (1.1.0)
|
||||||
multipart-post (~> 2)
|
multipart-post (~> 2.0)
|
||||||
faraday-net_http (1.0.2)
|
faraday-net_http (1.0.2)
|
||||||
faraday-net_http_persistent (1.2.0)
|
faraday-net_http_persistent (1.2.0)
|
||||||
faraday-patron (1.0.0)
|
faraday-patron (1.0.0)
|
||||||
@@ -235,9 +237,9 @@ GEM
|
|||||||
mutex_m
|
mutex_m
|
||||||
representable (~> 3.0)
|
representable (~> 3.0)
|
||||||
retriable (>= 2.0, < 4.a)
|
retriable (>= 2.0, < 4.a)
|
||||||
google-apis-iamcredentials_v1 (0.21.0)
|
google-apis-iamcredentials_v1 (0.22.0)
|
||||||
google-apis-core (>= 0.15.0, < 2.a)
|
google-apis-core (>= 0.15.0, < 2.a)
|
||||||
google-apis-storage_v1 (0.46.0)
|
google-apis-storage_v1 (0.49.0)
|
||||||
google-apis-core (>= 0.15.0, < 2.a)
|
google-apis-core (>= 0.15.0, < 2.a)
|
||||||
google-cloud-core (1.7.1)
|
google-cloud-core (1.7.1)
|
||||||
google-cloud-env (>= 1.0, < 3.a)
|
google-cloud-env (>= 1.0, < 3.a)
|
||||||
@@ -245,7 +247,7 @@ GEM
|
|||||||
google-cloud-env (2.2.1)
|
google-cloud-env (2.2.1)
|
||||||
faraday (>= 1.0, < 3.a)
|
faraday (>= 1.0, < 3.a)
|
||||||
google-cloud-errors (1.4.0)
|
google-cloud-errors (1.4.0)
|
||||||
google-cloud-storage (1.52.0)
|
google-cloud-storage (1.54.0)
|
||||||
addressable (~> 2.8)
|
addressable (~> 2.8)
|
||||||
digest-crc (~> 0.4)
|
digest-crc (~> 0.4)
|
||||||
google-apis-core (~> 0.13)
|
google-apis-core (~> 0.13)
|
||||||
@@ -254,15 +256,17 @@ GEM
|
|||||||
google-cloud-core (~> 1.6)
|
google-cloud-core (~> 1.6)
|
||||||
googleauth (~> 1.9)
|
googleauth (~> 1.9)
|
||||||
mini_mime (~> 1.0)
|
mini_mime (~> 1.0)
|
||||||
googleauth (1.11.1)
|
google-logging-utils (0.1.0)
|
||||||
|
googleauth (1.12.2)
|
||||||
faraday (>= 1.0, < 3.a)
|
faraday (>= 1.0, < 3.a)
|
||||||
google-cloud-env (~> 2.1)
|
google-cloud-env (~> 2.2)
|
||||||
|
google-logging-utils (~> 0.1)
|
||||||
jwt (>= 1.4, < 3.0)
|
jwt (>= 1.4, < 3.0)
|
||||||
multi_json (~> 1.11)
|
multi_json (~> 1.11)
|
||||||
os (>= 0.9, < 2.0)
|
os (>= 0.9, < 2.0)
|
||||||
signet (>= 0.16, < 2.a)
|
signet (>= 0.16, < 2.a)
|
||||||
hashdiff (1.1.1)
|
hashdiff (1.1.2)
|
||||||
hexapdf (0.47.0)
|
hexapdf (1.0.3)
|
||||||
cmdparse (~> 3.0, >= 3.0.3)
|
cmdparse (~> 3.0, >= 3.0.3)
|
||||||
geom2d (~> 0.4, >= 0.4.1)
|
geom2d (~> 0.4, >= 0.4.1)
|
||||||
openssl (>= 2.2.1)
|
openssl (>= 2.2.1)
|
||||||
@@ -273,12 +277,12 @@ GEM
|
|||||||
image_processing (1.13.0)
|
image_processing (1.13.0)
|
||||||
mini_magick (>= 4.9.5, < 5)
|
mini_magick (>= 4.9.5, < 5)
|
||||||
ruby-vips (>= 2.0.17, < 3)
|
ruby-vips (>= 2.0.17, < 3)
|
||||||
io-console (0.7.2)
|
io-console (0.8.0)
|
||||||
irb (1.14.1)
|
irb (1.14.3)
|
||||||
rdoc (>= 4.0.0)
|
rdoc (>= 4.0.0)
|
||||||
reline (>= 0.4.2)
|
reline (>= 0.4.2)
|
||||||
jmespath (1.6.2)
|
jmespath (1.6.2)
|
||||||
json (2.7.2)
|
json (2.9.1)
|
||||||
jwt (2.9.3)
|
jwt (2.9.3)
|
||||||
base64
|
base64
|
||||||
language_server-protocol (3.17.0.3)
|
language_server-protocol (3.17.0.3)
|
||||||
@@ -292,13 +296,13 @@ GEM
|
|||||||
letter_opener (~> 1.9)
|
letter_opener (~> 1.9)
|
||||||
railties (>= 6.1)
|
railties (>= 6.1)
|
||||||
rexml
|
rexml
|
||||||
logger (1.6.1)
|
logger (1.6.4)
|
||||||
lograge (0.14.0)
|
lograge (0.14.0)
|
||||||
actionpack (>= 4)
|
actionpack (>= 4)
|
||||||
activesupport (>= 4)
|
activesupport (>= 4)
|
||||||
railties (>= 4)
|
railties (>= 4)
|
||||||
request_store (~> 1.0)
|
request_store (~> 1.0)
|
||||||
loofah (2.22.0)
|
loofah (2.23.1)
|
||||||
crass (~> 1.0.2)
|
crass (~> 1.0.2)
|
||||||
nokogiri (>= 1.12.0)
|
nokogiri (>= 1.12.0)
|
||||||
mail (2.8.1)
|
mail (2.8.1)
|
||||||
@@ -311,16 +315,16 @@ GEM
|
|||||||
method_source (1.1.0)
|
method_source (1.1.0)
|
||||||
mini_magick (4.13.2)
|
mini_magick (4.13.2)
|
||||||
mini_mime (1.1.5)
|
mini_mime (1.1.5)
|
||||||
mini_portile2 (2.8.7)
|
mini_portile2 (2.8.8)
|
||||||
minitest (5.25.1)
|
minitest (5.25.4)
|
||||||
msgpack (1.7.3)
|
msgpack (1.7.5)
|
||||||
multi_json (1.15.0)
|
multi_json (1.15.0)
|
||||||
multipart-post (2.4.1)
|
multipart-post (2.4.1)
|
||||||
mutex_m (0.2.0)
|
mutex_m (0.3.0)
|
||||||
mysql2 (0.5.6)
|
mysql2 (0.5.6)
|
||||||
net-http-persistent (4.0.4)
|
net-http-persistent (4.0.5)
|
||||||
connection_pool (~> 2.2)
|
connection_pool (~> 2.2)
|
||||||
net-imap (0.4.17)
|
net-imap (0.5.3)
|
||||||
date
|
date
|
||||||
net-protocol
|
net-protocol
|
||||||
net-pop (0.1.2)
|
net-pop (0.1.2)
|
||||||
@@ -329,28 +333,28 @@ GEM
|
|||||||
timeout
|
timeout
|
||||||
net-smtp (0.5.0)
|
net-smtp (0.5.0)
|
||||||
net-protocol
|
net-protocol
|
||||||
nio4r (2.7.3)
|
nio4r (2.7.4)
|
||||||
nokogiri (1.16.7)
|
nokogiri (1.17.2)
|
||||||
mini_portile2 (~> 2.8.2)
|
mini_portile2 (~> 2.8.2)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
nokogiri (1.16.7-aarch64-linux)
|
nokogiri (1.17.2-aarch64-linux)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
nokogiri (1.16.7-x86_64-linux)
|
nokogiri (1.17.2-x86_64-linux)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
oj (3.16.6)
|
oj (3.16.8)
|
||||||
bigdecimal (>= 3.0)
|
bigdecimal (>= 3.0)
|
||||||
ostruct (>= 0.2)
|
ostruct (>= 0.2)
|
||||||
openssl (3.2.0)
|
openssl (3.3.0)
|
||||||
orm_adapter (0.5.0)
|
orm_adapter (0.5.0)
|
||||||
os (1.1.4)
|
os (1.1.4)
|
||||||
ostruct (0.6.0)
|
ostruct (0.6.1)
|
||||||
package_json (0.1.0)
|
package_json (0.1.0)
|
||||||
pagy (9.1.0)
|
pagy (9.3.3)
|
||||||
parallel (1.26.3)
|
parallel (1.26.3)
|
||||||
parser (3.3.5.0)
|
parser (3.3.6.0)
|
||||||
ast (~> 2.4.1)
|
ast (~> 2.4.1)
|
||||||
racc
|
racc
|
||||||
pg (1.5.8)
|
pg (1.5.9)
|
||||||
premailer (1.27.0)
|
premailer (1.27.0)
|
||||||
addressable
|
addressable
|
||||||
css_parser (>= 1.19.0)
|
css_parser (>= 1.19.0)
|
||||||
@@ -361,15 +365,16 @@ GEM
|
|||||||
premailer (~> 1.7, >= 1.7.9)
|
premailer (~> 1.7, >= 1.7.9)
|
||||||
pretender (0.5.0)
|
pretender (0.5.0)
|
||||||
actionpack (>= 6.1)
|
actionpack (>= 6.1)
|
||||||
pry (0.14.2)
|
pry (0.15.0)
|
||||||
coderay (~> 1.1)
|
coderay (~> 1.1)
|
||||||
method_source (~> 1.0)
|
method_source (~> 1.0)
|
||||||
pry-rails (0.3.11)
|
pry-rails (0.3.11)
|
||||||
pry (>= 0.13.0)
|
pry (>= 0.13.0)
|
||||||
psych (5.1.2)
|
psych (5.2.2)
|
||||||
|
date
|
||||||
stringio
|
stringio
|
||||||
public_suffix (6.0.1)
|
public_suffix (6.0.1)
|
||||||
puma (6.4.3)
|
puma (6.5.0)
|
||||||
nio4r (~> 2.0)
|
nio4r (~> 2.0)
|
||||||
racc (1.8.1)
|
racc (1.8.1)
|
||||||
rack (3.1.8)
|
rack (3.1.8)
|
||||||
@@ -379,40 +384,39 @@ GEM
|
|||||||
rack (>= 3.0.0)
|
rack (>= 3.0.0)
|
||||||
rack-test (2.1.0)
|
rack-test (2.1.0)
|
||||||
rack (>= 1.3)
|
rack (>= 1.3)
|
||||||
rackup (2.1.0)
|
rackup (2.2.1)
|
||||||
rack (>= 3)
|
rack (>= 3)
|
||||||
webrick (~> 1.8)
|
rails (8.0.1)
|
||||||
rails (7.2.1.1)
|
actioncable (= 8.0.1)
|
||||||
actioncable (= 7.2.1.1)
|
actionmailbox (= 8.0.1)
|
||||||
actionmailbox (= 7.2.1.1)
|
actionmailer (= 8.0.1)
|
||||||
actionmailer (= 7.2.1.1)
|
actionpack (= 8.0.1)
|
||||||
actionpack (= 7.2.1.1)
|
actiontext (= 8.0.1)
|
||||||
actiontext (= 7.2.1.1)
|
actionview (= 8.0.1)
|
||||||
actionview (= 7.2.1.1)
|
activejob (= 8.0.1)
|
||||||
activejob (= 7.2.1.1)
|
activemodel (= 8.0.1)
|
||||||
activemodel (= 7.2.1.1)
|
activerecord (= 8.0.1)
|
||||||
activerecord (= 7.2.1.1)
|
activestorage (= 8.0.1)
|
||||||
activestorage (= 7.2.1.1)
|
activesupport (= 8.0.1)
|
||||||
activesupport (= 7.2.1.1)
|
|
||||||
bundler (>= 1.15.0)
|
bundler (>= 1.15.0)
|
||||||
railties (= 7.2.1.1)
|
railties (= 8.0.1)
|
||||||
rails-dom-testing (2.2.0)
|
rails-dom-testing (2.2.0)
|
||||||
activesupport (>= 5.0.0)
|
activesupport (>= 5.0.0)
|
||||||
minitest
|
minitest
|
||||||
nokogiri (>= 1.6)
|
nokogiri (>= 1.6)
|
||||||
rails-html-sanitizer (1.6.0)
|
rails-html-sanitizer (1.6.2)
|
||||||
loofah (~> 2.21)
|
loofah (~> 2.21)
|
||||||
nokogiri (~> 1.14)
|
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
|
||||||
rails-i18n (7.0.9)
|
rails-i18n (8.0.1)
|
||||||
i18n (>= 0.7, < 2)
|
i18n (>= 0.7, < 2)
|
||||||
railties (>= 6.0.0, < 8)
|
railties (>= 8.0.0, < 9)
|
||||||
rails_autolink (1.1.8)
|
rails_autolink (1.1.8)
|
||||||
actionview (> 3.1)
|
actionview (> 3.1)
|
||||||
activesupport (> 3.1)
|
activesupport (> 3.1)
|
||||||
railties (> 3.1)
|
railties (> 3.1)
|
||||||
railties (7.2.1.1)
|
railties (8.0.1)
|
||||||
actionpack (= 7.2.1.1)
|
actionpack (= 8.0.1)
|
||||||
activesupport (= 7.2.1.1)
|
activesupport (= 8.0.1)
|
||||||
irb (~> 1.13)
|
irb (~> 1.13)
|
||||||
rackup (>= 1.0.0)
|
rackup (>= 1.0.0)
|
||||||
rake (>= 12.2)
|
rake (>= 12.2)
|
||||||
@@ -420,12 +424,12 @@ GEM
|
|||||||
zeitwerk (~> 2.6)
|
zeitwerk (~> 2.6)
|
||||||
rainbow (3.1.1)
|
rainbow (3.1.1)
|
||||||
rake (13.2.1)
|
rake (13.2.1)
|
||||||
rdoc (6.7.0)
|
rdoc (6.10.0)
|
||||||
psych (>= 4.0.0)
|
psych (>= 4.0.0)
|
||||||
redis-client (0.22.2)
|
redis-client (0.23.0)
|
||||||
connection_pool
|
connection_pool
|
||||||
regexp_parser (2.9.2)
|
regexp_parser (2.9.3)
|
||||||
reline (0.5.10)
|
reline (0.6.0)
|
||||||
io-console (~> 0.5)
|
io-console (~> 0.5)
|
||||||
representable (3.2.0)
|
representable (3.2.0)
|
||||||
declarative (< 0.1.0)
|
declarative (< 0.1.0)
|
||||||
@@ -437,13 +441,13 @@ GEM
|
|||||||
actionpack (>= 5.2)
|
actionpack (>= 5.2)
|
||||||
railties (>= 5.2)
|
railties (>= 5.2)
|
||||||
retriable (3.1.2)
|
retriable (3.1.2)
|
||||||
rexml (3.3.8)
|
rexml (3.4.0)
|
||||||
rotp (6.3.0)
|
rotp (6.3.0)
|
||||||
rqrcode (2.2.0)
|
rqrcode (2.2.0)
|
||||||
chunky_png (~> 1.0)
|
chunky_png (~> 1.0)
|
||||||
rqrcode_core (~> 1.0)
|
rqrcode_core (~> 1.0)
|
||||||
rqrcode_core (1.2.0)
|
rqrcode_core (1.2.0)
|
||||||
rspec-core (3.13.1)
|
rspec-core (3.13.2)
|
||||||
rspec-support (~> 3.13.0)
|
rspec-support (~> 3.13.0)
|
||||||
rspec-expectations (3.13.3)
|
rspec-expectations (3.13.3)
|
||||||
diff-lcs (>= 1.2.0, < 2.0)
|
diff-lcs (>= 1.2.0, < 2.0)
|
||||||
@@ -451,7 +455,7 @@ GEM
|
|||||||
rspec-mocks (3.13.2)
|
rspec-mocks (3.13.2)
|
||||||
diff-lcs (>= 1.2.0, < 2.0)
|
diff-lcs (>= 1.2.0, < 2.0)
|
||||||
rspec-support (~> 3.13.0)
|
rspec-support (~> 3.13.0)
|
||||||
rspec-rails (7.0.1)
|
rspec-rails (7.1.0)
|
||||||
actionpack (>= 7.0)
|
actionpack (>= 7.0)
|
||||||
activesupport (>= 7.0)
|
activesupport (>= 7.0)
|
||||||
railties (>= 7.0)
|
railties (>= 7.0)
|
||||||
@@ -459,48 +463,47 @@ GEM
|
|||||||
rspec-expectations (~> 3.13)
|
rspec-expectations (~> 3.13)
|
||||||
rspec-mocks (~> 3.13)
|
rspec-mocks (~> 3.13)
|
||||||
rspec-support (~> 3.13)
|
rspec-support (~> 3.13)
|
||||||
rspec-support (3.13.1)
|
rspec-support (3.13.2)
|
||||||
rubocop (1.66.1)
|
rubocop (1.69.2)
|
||||||
json (~> 2.3)
|
json (~> 2.3)
|
||||||
language_server-protocol (>= 3.17.0)
|
language_server-protocol (>= 3.17.0)
|
||||||
parallel (~> 1.10)
|
parallel (~> 1.10)
|
||||||
parser (>= 3.3.0.2)
|
parser (>= 3.3.0.2)
|
||||||
rainbow (>= 2.2.2, < 4.0)
|
rainbow (>= 2.2.2, < 4.0)
|
||||||
regexp_parser (>= 2.4, < 3.0)
|
regexp_parser (>= 2.9.3, < 3.0)
|
||||||
rubocop-ast (>= 1.32.2, < 2.0)
|
rubocop-ast (>= 1.36.2, < 2.0)
|
||||||
ruby-progressbar (~> 1.7)
|
ruby-progressbar (~> 1.7)
|
||||||
unicode-display_width (>= 2.4.0, < 3.0)
|
unicode-display_width (>= 2.4.0, < 4.0)
|
||||||
rubocop-ast (1.32.3)
|
rubocop-ast (1.37.0)
|
||||||
parser (>= 3.3.1.0)
|
parser (>= 3.3.1.0)
|
||||||
rubocop-performance (1.22.1)
|
rubocop-performance (1.23.0)
|
||||||
rubocop (>= 1.48.1, < 2.0)
|
rubocop (>= 1.48.1, < 2.0)
|
||||||
rubocop-ast (>= 1.31.1, < 2.0)
|
rubocop-ast (>= 1.31.1, < 2.0)
|
||||||
rubocop-rails (2.26.2)
|
rubocop-rails (2.27.0)
|
||||||
activesupport (>= 4.2.0)
|
activesupport (>= 4.2.0)
|
||||||
rack (>= 1.1)
|
rack (>= 1.1)
|
||||||
rubocop (>= 1.52.0, < 2.0)
|
rubocop (>= 1.52.0, < 2.0)
|
||||||
rubocop-ast (>= 1.31.1, < 2.0)
|
rubocop-ast (>= 1.31.1, < 2.0)
|
||||||
rubocop-rspec (3.1.0)
|
rubocop-rspec (3.3.0)
|
||||||
rubocop (~> 1.61)
|
rubocop (~> 1.61)
|
||||||
ruby-progressbar (1.13.0)
|
ruby-progressbar (1.13.0)
|
||||||
ruby-vips (2.2.2)
|
ruby-vips (2.2.2)
|
||||||
ffi (~> 1.12)
|
ffi (~> 1.12)
|
||||||
logger
|
logger
|
||||||
ruby2_keywords (0.0.5)
|
ruby2_keywords (0.0.5)
|
||||||
rubyXL (3.4.27)
|
rubyXL (3.4.33)
|
||||||
nokogiri (>= 1.10.8)
|
nokogiri (>= 1.10.8)
|
||||||
rubyzip (>= 1.3.0)
|
rubyzip (>= 1.3.0)
|
||||||
rubyzip (2.3.2)
|
rubyzip (2.3.2)
|
||||||
securerandom (0.3.1)
|
securerandom (0.4.1)
|
||||||
semantic_range (3.0.0)
|
semantic_range (3.1.0)
|
||||||
shakapacker (8.0.2)
|
shakapacker (8.0.2)
|
||||||
activesupport (>= 5.2)
|
activesupport (>= 5.2)
|
||||||
package_json
|
package_json
|
||||||
rack-proxy (>= 0.6.1)
|
rack-proxy (>= 0.6.1)
|
||||||
railties (>= 5.2)
|
railties (>= 5.2)
|
||||||
semantic_range (>= 2.3.0)
|
semantic_range (>= 2.3.0)
|
||||||
sidekiq (7.3.2)
|
sidekiq (7.3.7)
|
||||||
concurrent-ruby (< 2)
|
|
||||||
connection_pool (>= 2.3.0)
|
connection_pool (>= 2.3.0)
|
||||||
logger
|
logger
|
||||||
rack (>= 2.2.4)
|
rack (>= 2.2.4)
|
||||||
@@ -517,17 +520,17 @@ GEM
|
|||||||
simplecov-html (0.13.1)
|
simplecov-html (0.13.1)
|
||||||
simplecov_json_formatter (0.1.4)
|
simplecov_json_formatter (0.1.4)
|
||||||
smart_properties (1.17.0)
|
smart_properties (1.17.0)
|
||||||
sqlite3 (2.1.0)
|
sqlite3 (2.4.1)
|
||||||
mini_portile2 (~> 2.8.0)
|
mini_portile2 (~> 2.8.0)
|
||||||
sqlite3 (2.1.0-aarch64-linux-gnu)
|
sqlite3 (2.4.1-aarch64-linux-gnu)
|
||||||
sqlite3 (2.1.0-x86_64-linux-gnu)
|
sqlite3 (2.4.1-x86_64-linux-gnu)
|
||||||
stringio (3.1.1)
|
stringio (3.1.2)
|
||||||
strip_attributes (1.13.0)
|
strip_attributes (1.14.1)
|
||||||
activemodel (>= 3.0, < 8.0)
|
activemodel (>= 3.0, < 9.0)
|
||||||
thor (1.3.2)
|
thor (1.3.2)
|
||||||
timeout (0.4.1)
|
timeout (0.4.3)
|
||||||
trailblazer-option (0.1.2)
|
trailblazer-option (0.1.2)
|
||||||
turbo-rails (2.0.10)
|
turbo-rails (2.0.11)
|
||||||
actionpack (>= 6.0.0)
|
actionpack (>= 6.0.0)
|
||||||
railties (>= 6.0.0)
|
railties (>= 6.0.0)
|
||||||
twitter_cldr (6.12.1)
|
twitter_cldr (6.12.1)
|
||||||
@@ -539,9 +542,12 @@ GEM
|
|||||||
tzinfo-data (1.2024.2)
|
tzinfo-data (1.2024.2)
|
||||||
tzinfo (>= 1.0.0)
|
tzinfo (>= 1.0.0)
|
||||||
uber (0.1.0)
|
uber (0.1.0)
|
||||||
unicode-display_width (2.6.0)
|
unicode-display_width (3.1.2)
|
||||||
|
unicode-emoji (~> 4.0, >= 4.0.4)
|
||||||
|
unicode-emoji (4.0.4)
|
||||||
uniform_notifier (1.16.0)
|
uniform_notifier (1.16.0)
|
||||||
useragent (0.16.10)
|
uri (1.0.2)
|
||||||
|
useragent (0.16.11)
|
||||||
warden (1.2.9)
|
warden (1.2.9)
|
||||||
rack (>= 2.0.9)
|
rack (>= 2.0.9)
|
||||||
web-console (4.2.1)
|
web-console (4.2.1)
|
||||||
@@ -553,13 +559,13 @@ GEM
|
|||||||
addressable (>= 2.8.0)
|
addressable (>= 2.8.0)
|
||||||
crack (>= 0.3.2)
|
crack (>= 0.3.2)
|
||||||
hashdiff (>= 0.4.0, < 2.0.0)
|
hashdiff (>= 0.4.0, < 2.0.0)
|
||||||
webrick (1.8.2)
|
webrick (1.9.1)
|
||||||
websocket-driver (0.7.6)
|
websocket-driver (0.7.6)
|
||||||
websocket-extensions (>= 0.1.0)
|
websocket-extensions (>= 0.1.0)
|
||||||
websocket-extensions (0.1.5)
|
websocket-extensions (0.1.5)
|
||||||
xpath (3.2.0)
|
xpath (3.2.0)
|
||||||
nokogiri (~> 1.8)
|
nokogiri (~> 1.8)
|
||||||
zeitwerk (2.7.0)
|
zeitwerk (2.7.1)
|
||||||
|
|
||||||
PLATFORMS
|
PLATFORMS
|
||||||
aarch64-linux
|
aarch64-linux
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<h1 align="center" style="border-bottom: none">
|
<h1 align="center" style="border-bottom: none">
|
||||||
<div>
|
<div>
|
||||||
<a href="https://www.docuseal.co">
|
<a href="https://www.docuseal.com">
|
||||||
<img alt="DocuSeal" src="https://github.com/docusealco/docuseal/assets/5418788/c12cd051-81cd-4402-bc3a-92f2cfdc1b06" width="80" />
|
<img alt="DocuSeal" src="https://github.com/docusealco/docuseal/assets/5418788/c12cd051-81cd-4402-bc3a-92f2cfdc1b06" width="80" />
|
||||||
<br>
|
<br>
|
||||||
</a>
|
</a>
|
||||||
@@ -54,11 +54,11 @@ DocuSeal is an open source platform that provides secure and efficient digital d
|
|||||||
- Conditional fields and formulas
|
- Conditional fields and formulas
|
||||||
- Bulk send with CSV, XLSX spreadsheet import
|
- Bulk send with CSV, XLSX spreadsheet import
|
||||||
- SSO / SAML
|
- SSO / SAML
|
||||||
- Template creation with HTML API ([Guide](https://www.docuseal.co/guides/create-pdf-document-fillable-form-with-html-api))
|
- Template creation with HTML API ([Guide](https://www.docuseal.com/guides/create-pdf-document-fillable-form-with-html-api))
|
||||||
- Template creation with PDF or DOCX and field tags API ([Guide](https://www.docuseal.co/guides/use-embedded-text-field-tags-in-the-pdf-to-create-a-fillable-form))
|
- Template creation with PDF or DOCX and field tags API ([Guide](https://www.docuseal.com/guides/use-embedded-text-field-tags-in-the-pdf-to-create-a-fillable-form))
|
||||||
- Embedded signing form ([React](https://github.com/docusealco/docuseal-react), [Vue](https://github.com/docusealco/docuseal-vue), [Angular](https://github.com/docusealco/docuseal-angular) or [JavaScript](https://www.docuseal.co/docs/embedded))
|
- Embedded signing form ([React](https://github.com/docusealco/docuseal-react), [Vue](https://github.com/docusealco/docuseal-vue), [Angular](https://github.com/docusealco/docuseal-angular) or [JavaScript](https://www.docuseal.com/docs/embedded))
|
||||||
- Embedded document form builder ([React](https://github.com/docusealco/docuseal-react), [Vue](https://github.com/docusealco/docuseal-vue), [Angular](https://github.com/docusealco/docuseal-angular) or [JavaScript](https://www.docuseal.co/docs/embedded))
|
- Embedded document form builder ([React](https://github.com/docusealco/docuseal-react), [Vue](https://github.com/docusealco/docuseal-vue), [Angular](https://github.com/docusealco/docuseal-angular) or [JavaScript](https://www.docuseal.com/docs/embedded))
|
||||||
- [Learn more](https://www.docuseal.co/pricing)
|
- [Learn more](https://www.docuseal.com/pricing)
|
||||||
|
|
||||||
## Deploy
|
## Deploy
|
||||||
|
|
||||||
@@ -93,7 +93,7 @@ sudo HOST=your-domain-name.com docker compose up
|
|||||||
|
|
||||||
At DocuSeal we have expertise and technologies to make documents creation, filling, signing and processing seamlessly integrated with your product. We specialize in working with various industries, including **Banking, Healthcare, Transport, Real Estate, eCommerce, KYC, CRM, and other software products** that require bulk document signing. By leveraging DocuSeal, we can assist in reducing the overall cost of developing and processing electronic documents while ensuring security and compliance with local electronic document laws.
|
At DocuSeal we have expertise and technologies to make documents creation, filling, signing and processing seamlessly integrated with your product. We specialize in working with various industries, including **Banking, Healthcare, Transport, Real Estate, eCommerce, KYC, CRM, and other software products** that require bulk document signing. By leveraging DocuSeal, we can assist in reducing the overall cost of developing and processing electronic documents while ensuring security and compliance with local electronic document laws.
|
||||||
|
|
||||||
[Book a Meeting](https://www.docuseal.co/contact)
|
[Book a Meeting](https://www.docuseal.com/contact)
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
@@ -102,6 +102,6 @@ Unless otherwise noted, all files © 2023 DocuSeal LLC.
|
|||||||
|
|
||||||
## Tools
|
## Tools
|
||||||
|
|
||||||
- [Signature Maker](https://www.docuseal.co/online-signature)
|
- [Signature Maker](https://www.docuseal.com/online-signature)
|
||||||
- [Sign Document Online](https://www.docuseal.co/sign-documents-online)
|
- [Sign Document Online](https://www.docuseal.com/sign-documents-online)
|
||||||
- [Fill PDF Online](https://www.docuseal.co/fill-pdf)
|
- [Fill PDF Online](https://www.docuseal.com/fill-pdf)
|
||||||
@@ -17,7 +17,8 @@ class AccountConfigsController < ApplicationController
|
|||||||
AccountConfig::FLATTEN_RESULT_PDF_KEY,
|
AccountConfig::FLATTEN_RESULT_PDF_KEY,
|
||||||
AccountConfig::WITH_SIGNATURE_ID,
|
AccountConfig::WITH_SIGNATURE_ID,
|
||||||
AccountConfig::COMBINE_PDF_RESULT_KEY,
|
AccountConfig::COMBINE_PDF_RESULT_KEY,
|
||||||
AccountConfig::REQUIRE_SIGNING_REASON_KEY
|
AccountConfig::REQUIRE_SIGNING_REASON_KEY,
|
||||||
|
AccountConfig::DOCUMENT_FILENAME_FORMAT_KEY
|
||||||
].freeze
|
].freeze
|
||||||
|
|
||||||
InvalidKey = Class.new(StandardError)
|
InvalidKey = Class.new(StandardError)
|
||||||
|
|||||||
@@ -67,15 +67,20 @@ module Api
|
|||||||
|
|
||||||
submissions = create_submissions(@template, params)
|
submissions = create_submissions(@template, params)
|
||||||
|
|
||||||
submissions.each do |submission|
|
WebhookUrls.for_account_id(@template.account_id, 'submission.created').each do |webhook_url|
|
||||||
SendSubmissionCreatedWebhookRequestJob.perform_async({ 'submission_id' => submission.id })
|
submissions.each do |submission|
|
||||||
|
SendSubmissionCreatedWebhookRequestJob.perform_async('submission_id' => submission.id,
|
||||||
|
'webhook_url_id' => webhook_url.id)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
Submissions.send_signature_requests(submissions)
|
Submissions.send_signature_requests(submissions)
|
||||||
|
|
||||||
submissions.each do |submission|
|
submissions.each do |submission|
|
||||||
submission.submitters.each do |submitter|
|
submission.submitters.each do |submitter|
|
||||||
ProcessSubmitterCompletionJob.perform_async({ 'submitter_id' => submitter.id }) if submitter.completed_at?
|
next unless submitter.completed_at?
|
||||||
|
|
||||||
|
ProcessSubmitterCompletionJob.perform_async('submitter_id' => submitter.id, 'send_invitation_email' => false)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -88,12 +93,15 @@ module Api
|
|||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
if params[:permanently] == 'true'
|
if params[:permanently].in?(['true', true])
|
||||||
@submission.destroy!
|
@submission.destroy!
|
||||||
else
|
else
|
||||||
@submission.update!(archived_at: Time.current)
|
@submission.update!(archived_at: Time.current)
|
||||||
|
|
||||||
SendSubmissionArchivedWebhookRequestJob.perform_async('submission_id' => @submission.id)
|
WebhookUrls.for_account_id(@submission.account_id, 'submission.archived').each do |webhook_url|
|
||||||
|
SendSubmissionArchivedWebhookRequestJob.perform_async('submission_id' => @submission.id,
|
||||||
|
'webhook_url_id' => webhook_url.id)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
render json: @submission.as_json(only: %i[id archived_at])
|
render json: @submission.as_json(only: %i[id archived_at])
|
||||||
|
|||||||
@@ -13,7 +13,10 @@ module Api
|
|||||||
|
|
||||||
SubmissionEvents.create_with_tracking_data(submitter, 'view_form', request)
|
SubmissionEvents.create_with_tracking_data(submitter, 'view_form', request)
|
||||||
|
|
||||||
SendFormViewedWebhookRequestJob.perform_async({ 'submitter_id' => submitter.id })
|
WebhookUrls.for_account_id(submitter.account_id, 'form.viewed').each do |webhook_url|
|
||||||
|
SendFormViewedWebhookRequestJob.perform_async('submitter_id' => submitter.id,
|
||||||
|
'webhook_url_id' => webhook_url.id)
|
||||||
|
end
|
||||||
|
|
||||||
render json: {}
|
render json: {}
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ module Api
|
|||||||
end
|
end
|
||||||
|
|
||||||
if @submitter.completed_at?
|
if @submitter.completed_at?
|
||||||
ProcessSubmitterCompletionJob.perform_async({ 'submitter_id' => @submitter.id })
|
ProcessSubmitterCompletionJob.perform_async('submitter_id' => @submitter.id)
|
||||||
elsif normalized_params[:send_email] || normalized_params[:send_sms]
|
elsif normalized_params[:send_email] || normalized_params[:send_sms]
|
||||||
Submitters.send_signature_requests([@submitter])
|
Submitters.send_signature_requests([@submitter])
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -23,9 +23,14 @@ module Api
|
|||||||
cloned_template.source = :api
|
cloned_template.source = :api
|
||||||
cloned_template.save!
|
cloned_template.save!
|
||||||
|
|
||||||
schema_documents = Templates::CloneAttachments.call(template: cloned_template, original_template: @template)
|
schema_documents = Templates::CloneAttachments.call(template: cloned_template,
|
||||||
|
original_template: @template,
|
||||||
|
documents: params[:documents])
|
||||||
|
|
||||||
SendTemplateCreatedWebhookRequestJob.perform_async('template_id' => cloned_template.id)
|
WebhookUrls.for_account_id(cloned_template.account_id, 'template.created').each do |webhook_url|
|
||||||
|
SendTemplateCreatedWebhookRequestJob.perform_async('template_id' => cloned_template.id,
|
||||||
|
'webhook_url_id' => webhook_url.id)
|
||||||
|
end
|
||||||
|
|
||||||
render json: Templates::SerializeForApi.call(cloned_template, schema_documents)
|
render json: Templates::SerializeForApi.call(cloned_template, schema_documents)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -65,13 +65,16 @@ module Api
|
|||||||
|
|
||||||
@template.update!(template_params)
|
@template.update!(template_params)
|
||||||
|
|
||||||
SendTemplateUpdatedWebhookRequestJob.perform_async('template_id' => @template.id)
|
WebhookUrls.for_account_id(@template.account_id, 'template.updated').each do |webhook_url|
|
||||||
|
SendTemplateUpdatedWebhookRequestJob.perform_async('template_id' => @template.id,
|
||||||
|
'webhook_url_id' => webhook_url.id)
|
||||||
|
end
|
||||||
|
|
||||||
render json: @template.as_json(only: %i[id updated_at])
|
render json: @template.as_json(only: %i[id updated_at])
|
||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
if params[:permanently] == 'true'
|
if params[:permanently].in?(['true', true])
|
||||||
@template.destroy!
|
@template.destroy!
|
||||||
else
|
else
|
||||||
@template.update!(archived_at: Time.current)
|
@template.update!(archived_at: Time.current)
|
||||||
@@ -84,7 +87,7 @@ module Api
|
|||||||
|
|
||||||
def filter_templates(templates, params)
|
def filter_templates(templates, params)
|
||||||
templates = Templates.search(templates, params[:q])
|
templates = Templates.search(templates, params[:q])
|
||||||
templates = params[:archived] ? templates.archived : templates.active
|
templates = params[:archived].in?(['true', true]) ? templates.archived : templates.active
|
||||||
templates = templates.where(external_id: params[:application_key]) if params[:application_key].present?
|
templates = templates.where(external_id: params[:application_key]) if params[:application_key].present?
|
||||||
templates = templates.where(external_id: params[:external_id]) if params[:external_id].present?
|
templates = templates.where(external_id: params[:external_id]) if params[:external_id].present?
|
||||||
templates = templates.joins(:folder).where(folder: { name: params[:folder] }) if params[:folder].present?
|
templates = templates.joins(:folder).where(folder: { name: params[:folder] }) if params[:folder].present?
|
||||||
@@ -97,12 +100,12 @@ module Api
|
|||||||
:name,
|
:name,
|
||||||
:external_id,
|
:external_id,
|
||||||
{
|
{
|
||||||
submitters: [%i[name uuid is_requester invite_by_uuid linked_to_uuid email]],
|
submitters: [%i[name uuid is_requester invite_by_uuid optional_invite_by_uuid linked_to_uuid email]],
|
||||||
fields: [[:uuid, :submitter_uuid, :name, :type,
|
fields: [[:uuid, :submitter_uuid, :name, :type,
|
||||||
:required, :readonly, :default_value,
|
:required, :readonly, :default_value,
|
||||||
:title, :description,
|
:title, :description,
|
||||||
{ preferences: {},
|
{ preferences: {},
|
||||||
conditions: [%i[field_uuid value action]],
|
conditions: [%i[field_uuid value action operation]],
|
||||||
options: [%i[value uuid]],
|
options: [%i[value uuid]],
|
||||||
validation: %i[message pattern],
|
validation: %i[message pattern],
|
||||||
areas: [%i[x y w h cell_w attachment_uuid option_uuid page]] }]]
|
areas: [%i[x y w h cell_w attachment_uuid option_uuid page]] }]]
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ class ApplicationController < ActionController::Base
|
|||||||
|
|
||||||
helper_method :button_title,
|
helper_method :button_title,
|
||||||
:current_account,
|
:current_account,
|
||||||
|
:form_link_host,
|
||||||
:svg_icon
|
:svg_icon
|
||||||
|
|
||||||
impersonates :user, with: ->(uuid) { User.find_by(uuid:) }
|
impersonates :user, with: ->(uuid) { User.find_by(uuid:) }
|
||||||
@@ -104,4 +105,14 @@ class ApplicationController < ActionController::Base
|
|||||||
def svg_icon(icon_name, class: '')
|
def svg_icon(icon_name, class: '')
|
||||||
render_to_string(partial: "icons/#{icon_name}", locals: { class: })
|
render_to_string(partial: "icons/#{icon_name}", locals: { class: })
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def form_link_host
|
||||||
|
Docuseal.default_url_options[:host]
|
||||||
|
end
|
||||||
|
|
||||||
|
def maybe_redirect_com
|
||||||
|
return if request.domain != 'docuseal.co'
|
||||||
|
|
||||||
|
redirect_to request.url.gsub('.co/', '.com/'), allow_other_host: true, status: :moved_permanently
|
||||||
|
end
|
||||||
end
|
end
|
||||||
@@ -6,7 +6,7 @@ class ConsoleRedirectController < ApplicationController
|
|||||||
|
|
||||||
def index
|
def index
|
||||||
if request.path == '/upgrade'
|
if request.path == '/upgrade'
|
||||||
params[:redir] = Docuseal.multitenant? ? "#{Docuseal::CONSOLE_URL}/plans" : "#{Docuseal::CONSOLE_URL}/on_premise"
|
params[:redir] = Docuseal.multitenant? ? "#{Docuseal::CONSOLE_URL}/plans" : "#{Docuseal::CONSOLE_URL}/on_premises"
|
||||||
end
|
end
|
||||||
|
|
||||||
params[:redir] = "#{Docuseal::CONSOLE_URL}/manage" if request.path == '/manage'
|
params[:redir] = "#{Docuseal::CONSOLE_URL}/manage" if request.path == '/manage'
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ class EmbedScriptsController < ActionController::Metal
|
|||||||
<h2>Upgrade to Pro</h2>
|
<h2>Upgrade to Pro</h2>
|
||||||
<p>Unlock embedded components by upgrading to Pro</p>
|
<p>Unlock embedded components by upgrading to Pro</p>
|
||||||
<div style="margin-top: 40px;">
|
<div style="margin-top: 40px;">
|
||||||
<a href="#{Docuseal::CONSOLE_URL}/on_premise" target="_blank" style="padding: 15px 25px; background-color: #222; color: white; text-decoration: none; border-radius: 5px; font-size: 16px; cursor: pointer;">
|
<a href="#{Docuseal::CONSOLE_URL}/on_premises" target="_blank" style="padding: 15px 25px; background-color: #222; color: white; text-decoration: none; border-radius: 5px; font-size: 16px; cursor: pointer;">
|
||||||
Learn More
|
Learn More
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
class ErrorsController < ActionController::Base
|
class ErrorsController < ActionController::Base
|
||||||
ENTERPRISE_FEATURE_MESSAGE =
|
ENTERPRISE_FEATURE_MESSAGE =
|
||||||
'This feature is available in Pro Edition: https://www.docuseal.co/pricing'
|
'This feature is available in Pro Edition: https://www.docuseal.com/pricing'
|
||||||
|
|
||||||
ENTERPRISE_PATHS = [
|
ENTERPRISE_PATHS = [
|
||||||
'/templates/html',
|
'/templates/html',
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ class EsignSettingsController < ApplicationController
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
prepend_before_action :maybe_redirect_com, only: %i[show]
|
||||||
|
|
||||||
before_action :load_encrypted_config
|
before_action :load_encrypted_config
|
||||||
authorize_resource :encrypted_config, parent: false, only: %i[new create]
|
authorize_resource :encrypted_config, parent: false, only: %i[new create]
|
||||||
authorize_resource :encrypted_config, only: %i[update destroy show]
|
authorize_resource :encrypted_config, only: %i[update destroy show]
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ class PersonalizationSettingsController < ApplicationController
|
|||||||
AccountConfig::SUBMITTER_INVITATION_EMAIL_KEY,
|
AccountConfig::SUBMITTER_INVITATION_EMAIL_KEY,
|
||||||
AccountConfig::SUBMITTER_DOCUMENTS_COPY_EMAIL_KEY,
|
AccountConfig::SUBMITTER_DOCUMENTS_COPY_EMAIL_KEY,
|
||||||
AccountConfig::SUBMITTER_COMPLETED_EMAIL_KEY,
|
AccountConfig::SUBMITTER_COMPLETED_EMAIL_KEY,
|
||||||
AccountConfig::FORM_COMPLETED_MESSAGE_KEY
|
AccountConfig::FORM_COMPLETED_MESSAGE_KEY,
|
||||||
|
*(Docuseal.multitenant? ? [] : [AccountConfig::POLICY_LINKS_KEY])
|
||||||
].freeze
|
].freeze
|
||||||
|
|
||||||
InvalidKey = Class.new(StandardError)
|
InvalidKey = Class.new(StandardError)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ class StartFormController < ApplicationController
|
|||||||
skip_authorization_check
|
skip_authorization_check
|
||||||
|
|
||||||
around_action :with_browser_locale, only: %i[show completed]
|
around_action :with_browser_locale, only: %i[show completed]
|
||||||
|
before_action :maybe_redirect_com, only: %i[show completed]
|
||||||
before_action :load_template
|
before_action :load_template
|
||||||
|
|
||||||
def show
|
def show
|
||||||
@@ -37,7 +38,10 @@ class StartFormController < ApplicationController
|
|||||||
|
|
||||||
if @submitter.save
|
if @submitter.save
|
||||||
if is_new_record
|
if is_new_record
|
||||||
SendSubmissionCreatedWebhookRequestJob.perform_async({ 'submission_id' => @submitter.submission.id })
|
WebhookUrls.for_account_id(@submitter.account_id, 'submission.created').each do |webhook_url|
|
||||||
|
SendSubmissionCreatedWebhookRequestJob.perform_async('submission_id' => @submitter.submission_id,
|
||||||
|
'webhook_url_id' => webhook_url.id)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
redirect_to submit_form_path(@submitter.slug)
|
redirect_to submit_form_path(@submitter.slug)
|
||||||
|
|||||||
@@ -8,8 +8,18 @@ class SubmissionsArchivedController < ApplicationController
|
|||||||
@submissions = @submissions.where.not(archived_at: nil)
|
@submissions = @submissions.where.not(archived_at: nil)
|
||||||
.or(@submissions.where.not(templates: { archived_at: nil }))
|
.or(@submissions.where.not(templates: { archived_at: nil }))
|
||||||
.preload(:created_by_user, template: :author)
|
.preload(:created_by_user, template: :author)
|
||||||
@submissions = Submissions.search(@submissions, params[:q], search_template: true)
|
|
||||||
|
|
||||||
@pagy, @submissions = pagy(@submissions.preload(:submitters).order(id: :desc))
|
@submissions = @submissions.preload(:template_accesses) unless current_user.role.in?(%w[admin superadmin])
|
||||||
|
|
||||||
|
@submissions = Submissions.search(@submissions, params[:q], search_template: true)
|
||||||
|
@submissions = Submissions::Filter.call(@submissions, current_user, params)
|
||||||
|
|
||||||
|
@submissions = if params[:completed_at_from].present? || params[:completed_at_to].present?
|
||||||
|
@submissions.order(Submitter.arel_table[:completed_at].maximum.desc)
|
||||||
|
else
|
||||||
|
@submissions.order(id: :desc)
|
||||||
|
end
|
||||||
|
|
||||||
|
@pagy, @submissions = pagy(@submissions.preload(submitters: :start_form_submission_events))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -6,6 +6,8 @@ class SubmissionsController < ApplicationController
|
|||||||
|
|
||||||
load_and_authorize_resource :submission, only: %i[show destroy]
|
load_and_authorize_resource :submission, only: %i[show destroy]
|
||||||
|
|
||||||
|
prepend_before_action :maybe_redirect_com, only: %i[show]
|
||||||
|
|
||||||
def show
|
def show
|
||||||
@submission = Submissions.preload_with_pages(@submission)
|
@submission = Submissions.preload_with_pages(@submission)
|
||||||
|
|
||||||
@@ -50,9 +52,7 @@ class SubmissionsController < ApplicationController
|
|||||||
params: params.merge('send_completed_email' => true))
|
params: params.merge('send_completed_email' => true))
|
||||||
end
|
end
|
||||||
|
|
||||||
submissions.each do |submission|
|
enqueue_submission_created_webhooks(@template, submissions)
|
||||||
SendSubmissionCreatedWebhookRequestJob.perform_async({ 'submission_id' => submission.id })
|
|
||||||
end
|
|
||||||
|
|
||||||
Submissions.send_signature_requests(submissions)
|
Submissions.send_signature_requests(submissions)
|
||||||
|
|
||||||
@@ -61,14 +61,17 @@ class SubmissionsController < ApplicationController
|
|||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
notice =
|
notice =
|
||||||
if params[:permanently].present?
|
if params[:permanently].in?(['true', true])
|
||||||
@submission.destroy!
|
@submission.destroy!
|
||||||
|
|
||||||
I18n.t('submission_has_been_removed')
|
I18n.t('submission_has_been_removed')
|
||||||
else
|
else
|
||||||
@submission.update!(archived_at: Time.current)
|
@submission.update!(archived_at: Time.current)
|
||||||
|
|
||||||
SendSubmissionArchivedWebhookRequestJob.perform_async('submission_id' => @submission.id)
|
WebhookUrls.for_account_id(@submission.account_id, 'submission.archived').each do |webhook_url|
|
||||||
|
SendSubmissionArchivedWebhookRequestJob.perform_async('submission_id' => @submission.id,
|
||||||
|
'webhook_url_id' => webhook_url.id)
|
||||||
|
end
|
||||||
|
|
||||||
I18n.t('submission_has_been_archived')
|
I18n.t('submission_has_been_archived')
|
||||||
end
|
end
|
||||||
@@ -85,6 +88,15 @@ class SubmissionsController < ApplicationController
|
|||||||
template.save!
|
template.save!
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def enqueue_submission_created_webhooks(template, submissions)
|
||||||
|
WebhookUrls.for_account_id(template.account_id, 'submission.created').each do |webhook_url|
|
||||||
|
submissions.each do |submission|
|
||||||
|
SendSubmissionCreatedWebhookRequestJob.perform_async('submission_id' => submission.id,
|
||||||
|
'webhook_url_id' => webhook_url.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def submissions_params
|
def submissions_params
|
||||||
params.permit(submission: { submitters: [%i[uuid email phone name]] })
|
params.permit(submission: { submitters: [%i[uuid email phone name]] })
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -10,11 +10,17 @@ class SubmissionsDashboardController < ApplicationController
|
|||||||
.where(templates: { archived_at: nil })
|
.where(templates: { archived_at: nil })
|
||||||
.preload(:created_by_user, template: :author)
|
.preload(:created_by_user, template: :author)
|
||||||
|
|
||||||
|
@submissions = @submissions.preload(:template_accesses) unless current_user.role.in?(%w[admin superadmin])
|
||||||
|
|
||||||
@submissions = Submissions.search(@submissions, params[:q], search_template: true)
|
@submissions = Submissions.search(@submissions, params[:q], search_template: true)
|
||||||
|
@submissions = Submissions::Filter.call(@submissions, current_user, params)
|
||||||
|
|
||||||
@submissions = @submissions.pending if params[:status] == 'pending'
|
@submissions = if params[:completed_at_from].present? || params[:completed_at_to].present?
|
||||||
@submissions = @submissions.completed if params[:status] == 'completed'
|
@submissions.order(Submitter.arel_table[:completed_at].maximum.desc)
|
||||||
|
else
|
||||||
|
@submissions.order(id: :desc)
|
||||||
|
end
|
||||||
|
|
||||||
@pagy, @submissions = pagy(@submissions.preload(submitters: :start_form_submission_events).order(id: :desc))
|
@pagy, @submissions = pagy(@submissions.preload(submitters: :start_form_submission_events))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -53,8 +53,15 @@ class SubmissionsDownloadController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def build_urls(submitter)
|
def build_urls(submitter)
|
||||||
|
filename_format = AccountConfig.find_or_initialize_by(account_id: submitter.account_id,
|
||||||
|
key: AccountConfig::DOCUMENT_FILENAME_FORMAT_KEY)&.value
|
||||||
|
|
||||||
Submitters.select_attachments_for_download(submitter).map do |attachment|
|
Submitters.select_attachments_for_download(submitter).map do |attachment|
|
||||||
ActiveStorage::Blob.proxy_url(attachment.blob, expires_at: FILES_TTL.from_now.to_i)
|
ActiveStorage::Blob.proxy_url(
|
||||||
|
attachment.blob,
|
||||||
|
expires_at: FILES_TTL.from_now.to_i,
|
||||||
|
filename: Submitters.build_document_filename(submitter, attachment.blob, filename_format)
|
||||||
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -65,6 +72,13 @@ class SubmissionsDownloadController < ApplicationController
|
|||||||
attachment = submitter.submission.combined_document_attachment
|
attachment = submitter.submission.combined_document_attachment
|
||||||
attachment ||= Submissions::GenerateCombinedAttachment.call(submitter)
|
attachment ||= Submissions::GenerateCombinedAttachment.call(submitter)
|
||||||
|
|
||||||
ActiveStorage::Blob.proxy_url(attachment.blob, expires_at: FILES_TTL.from_now.to_i)
|
filename_format = AccountConfig.find_or_initialize_by(account_id: submitter.account_id,
|
||||||
|
key: AccountConfig::DOCUMENT_FILENAME_FORMAT_KEY)&.value
|
||||||
|
|
||||||
|
ActiveStorage::Blob.proxy_url(
|
||||||
|
attachment.blob,
|
||||||
|
expires_at: FILES_TTL.from_now.to_i,
|
||||||
|
filename: Submitters.build_document_filename(submitter, attachment.blob, filename_format)
|
||||||
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class SubmissionsFiltersController < ApplicationController
|
||||||
|
ALLOWED_NAMES = %w[
|
||||||
|
author
|
||||||
|
completed_at
|
||||||
|
status
|
||||||
|
created_at
|
||||||
|
].freeze
|
||||||
|
|
||||||
|
skip_authorization_check
|
||||||
|
|
||||||
|
def show
|
||||||
|
return head :not_found unless ALLOWED_NAMES.include?(params[:name])
|
||||||
|
|
||||||
|
render params[:name]
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -4,6 +4,8 @@ class SubmissionsPreviewController < ApplicationController
|
|||||||
skip_before_action :authenticate_user!
|
skip_before_action :authenticate_user!
|
||||||
skip_authorization_check
|
skip_authorization_check
|
||||||
|
|
||||||
|
prepend_before_action :maybe_redirect_com, only: %i[show completed]
|
||||||
|
|
||||||
TTL = 40.minutes
|
TTL = 40.minutes
|
||||||
|
|
||||||
def show
|
def show
|
||||||
@@ -18,7 +20,7 @@ class SubmissionsPreviewController < ApplicationController
|
|||||||
|
|
||||||
@submission ||= Submission.find_by!(slug: params[:slug])
|
@submission ||= Submission.find_by!(slug: params[:slug])
|
||||||
|
|
||||||
if !@submission.submitters.all?(&:completed_at?) && current_user.blank?
|
if @submission.account.archived_at? || (!@submission.submitters.all?(&:completed_at?) && current_user.blank?)
|
||||||
raise ActionController::RoutingError, I18n.t('not_found')
|
raise ActionController::RoutingError, I18n.t('not_found')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class SubmissionsUnarchiveController < ApplicationController
|
||||||
|
load_and_authorize_resource :submission
|
||||||
|
|
||||||
|
def create
|
||||||
|
@submission.update!(archived_at: nil)
|
||||||
|
|
||||||
|
redirect_to submission_path(@submission), notice: I18n.t('submission_has_been_unarchived')
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -15,7 +15,9 @@ class SubmitFormController < ApplicationController
|
|||||||
submission = @submitter.submission
|
submission = @submitter.submission
|
||||||
|
|
||||||
return redirect_to submit_form_completed_path(@submitter.slug) if @submitter.completed_at?
|
return redirect_to submit_form_completed_path(@submitter.slug) if @submitter.completed_at?
|
||||||
return render :archived if submission.template.archived_at? || submission.archived_at?
|
return render :archived if submission.template.archived_at? ||
|
||||||
|
submission.archived_at? ||
|
||||||
|
@submitter.account.archived_at?
|
||||||
return render :expired if submission.expired?
|
return render :expired if submission.expired?
|
||||||
return render :declined if @submitter.declined_at?
|
return render :declined if @submitter.declined_at?
|
||||||
return render :awaiting if submission.template.preferences['submitters_order'] == 'preserved' &&
|
return render :awaiting if submission.template.preferences['submitters_order'] == 'preserved' &&
|
||||||
@@ -25,8 +27,7 @@ class SubmitFormController < ApplicationController
|
|||||||
|
|
||||||
Submitters::MaybeUpdateDefaultValues.call(@submitter, current_user)
|
Submitters::MaybeUpdateDefaultValues.call(@submitter, current_user)
|
||||||
|
|
||||||
@attachments_index = ActiveStorage::Attachment.where(record: submission.submitters, name: :attachments)
|
@attachments_index = build_attachments_index(submission)
|
||||||
.preload(:blob).index_by(&:uuid)
|
|
||||||
|
|
||||||
@form_configs = Submitters::FormConfigs.call(@submitter, CONFIG_KEYS)
|
@form_configs = Submitters::FormConfigs.call(@submitter, CONFIG_KEYS)
|
||||||
|
|
||||||
@@ -78,4 +79,11 @@ class SubmitFormController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def success; end
|
def success; end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def build_attachments_index(submission)
|
||||||
|
ActiveStorage::Attachment.where(record: submission.submitters, name: :attachments)
|
||||||
|
.preload(:blob).index_by(&:uuid)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
@@ -21,8 +21,14 @@ class SubmitFormDeclineController < ApplicationController
|
|||||||
|
|
||||||
user = submitter.submission.created_by_user || submitter.template.author
|
user = submitter.submission.created_by_user || submitter.template.author
|
||||||
|
|
||||||
SubmitterMailer.declined_email(submitter, user).deliver_later!
|
if user.user_configs.find_by(key: UserConfig::RECEIVE_DECLINED_EMAIL)&.value != false
|
||||||
SendFormDeclinedWebhookRequestJob.perform_async('submitter_id' => submitter.id)
|
SubmitterMailer.declined_email(submitter, user).deliver_later!
|
||||||
|
end
|
||||||
|
|
||||||
|
WebhookUrls.for_account_id(submitter.account_id, 'form.declined').each do |webhook_url|
|
||||||
|
SendFormDeclinedWebhookRequestJob.perform_async('submitter_id' => submitter.id,
|
||||||
|
'webhook_url_id' => webhook_url.id)
|
||||||
|
end
|
||||||
|
|
||||||
redirect_to submit_form_path(submitter.slug)
|
redirect_to submit_form_path(submitter.slug)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -9,13 +9,15 @@ class SubmitFormInviteController < ApplicationController
|
|||||||
|
|
||||||
return head :unprocessable_entity unless can_invite?(submitter)
|
return head :unprocessable_entity unless can_invite?(submitter)
|
||||||
|
|
||||||
invite_submitters = filter_invite_submitters(submitter)
|
invite_submitters = filter_invite_submitters(submitter, 'invite_by_uuid')
|
||||||
|
optional_invite_submitters = filter_invite_submitters(submitter, 'optional_invite_by_uuid')
|
||||||
|
|
||||||
ApplicationRecord.transaction do
|
ApplicationRecord.transaction do
|
||||||
invite_submitters.each do |item|
|
(invite_submitters + optional_invite_submitters).each do |item|
|
||||||
attrs = submitters_attributes.find { |e| e[:uuid] == item['uuid'] }
|
attrs = submitters_attributes.find { |e| e[:uuid] == item['uuid'] }
|
||||||
|
|
||||||
next unless attrs
|
next unless attrs
|
||||||
|
next if attrs[:email].blank?
|
||||||
|
|
||||||
submitter.submission.submitters.create!(**attrs, account_id: submitter.account_id)
|
submitter.submission.submitters.create!(**attrs, account_id: submitter.account_id)
|
||||||
|
|
||||||
@@ -46,9 +48,9 @@ class SubmitFormInviteController < ApplicationController
|
|||||||
!submitter.submission.template.archived_at?
|
!submitter.submission.template.archived_at?
|
||||||
end
|
end
|
||||||
|
|
||||||
def filter_invite_submitters(submitter)
|
def filter_invite_submitters(submitter, key = 'invite_by_uuid')
|
||||||
(submitter.submission.template_submitters || submitter.submission.template.submitters).select do |s|
|
(submitter.submission.template_submitters || submitter.submission.template.submitters).select do |s|
|
||||||
s['invite_by_uuid'] == submitter.uuid && submitter.submission.submitters.none? { |e| e.uuid == s['uuid'] }
|
s[key] == submitter.uuid && submitter.submission.submitters.none? { |e| e.uuid == s['uuid'] }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ class SubmittersAutocompleteController < ApplicationController
|
|||||||
def index
|
def index
|
||||||
submitters = search_submitters(@submitters)
|
submitters = search_submitters(@submitters)
|
||||||
|
|
||||||
values = submitters.limit(LIMIT).group(SELECT_COLUMNS.join(', ')).pluck(SELECT_COLUMNS.join(', '))
|
arel_columns = SELECT_COLUMNS.map { |col| Submitter.arel_table[col] }
|
||||||
|
values = submitters.limit(LIMIT).group(arel_columns).pluck(arel_columns)
|
||||||
|
|
||||||
attrs = values.map { |row| SELECT_COLUMNS.zip(row).to_h }
|
attrs = values.map { |row| SELECT_COLUMNS.zip(row).to_h }
|
||||||
attrs = attrs.uniq { |e| e[params[:field]] } if params[:field].present?
|
attrs = attrs.uniq { |e| e[params[:field]] } if params[:field].present?
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ class SubmittersSendEmailController < ApplicationController
|
|||||||
def create
|
def create
|
||||||
if Docuseal.multitenant? && SubmissionEvent.exists?(submitter: @submitter,
|
if Docuseal.multitenant? && SubmissionEvent.exists?(submitter: @submitter,
|
||||||
event_type: 'send_email',
|
event_type: 'send_email',
|
||||||
created_at: 24.hours.ago..Time.current)
|
created_at: 10.hours.ago..Time.current)
|
||||||
Rollbar.warning("Already sent: #{@submitter.id}") if defined?(Rollbar)
|
Rollbar.warning("Already sent: #{@submitter.id}") if defined?(Rollbar)
|
||||||
|
|
||||||
return redirect_back(fallback_location: submission_path(@submitter.submission),
|
return redirect_back(fallback_location: submission_path(@submitter.submission),
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ class TemplateFoldersController < ApplicationController
|
|||||||
load_and_authorize_resource :template_folder
|
load_and_authorize_resource :template_folder
|
||||||
|
|
||||||
def show
|
def show
|
||||||
@templates = @template_folder.templates.active.preload(:author).order(id: :desc)
|
@templates = @template_folder.templates.active.accessible_by(current_ability)
|
||||||
|
.preload(:author, :template_accesses).order(id: :desc)
|
||||||
@templates = Templates.search(@templates, params[:q])
|
@templates = Templates.search(@templates, params[:q])
|
||||||
|
|
||||||
@pagy, @templates = pagy(@templates, limit: 12)
|
@pagy, @templates = pagy(@templates, limit: 12)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ class TemplatesArchivedController < ApplicationController
|
|||||||
load_and_authorize_resource :template, parent: false
|
load_and_authorize_resource :template, parent: false
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@templates = @templates.where.not(archived_at: nil).preload(:author, :folder).order(id: :desc)
|
@templates = @templates.where.not(archived_at: nil).preload(:author, :folder, :template_accesses).order(id: :desc)
|
||||||
@templates = Templates.search(@templates, params[:q])
|
@templates = Templates.search(@templates, params[:q])
|
||||||
|
|
||||||
@pagy, @templates = pagy(@templates, limit: 12)
|
@pagy, @templates = pagy(@templates, limit: 12)
|
||||||
|
|||||||
@@ -7,8 +7,15 @@ class TemplatesArchivedSubmissionsController < ApplicationController
|
|||||||
def index
|
def index
|
||||||
@submissions = @submissions.where.not(archived_at: nil)
|
@submissions = @submissions.where.not(archived_at: nil)
|
||||||
@submissions = Submissions.search(@submissions, params[:q], search_values: true)
|
@submissions = Submissions.search(@submissions, params[:q], search_values: true)
|
||||||
|
@submissions = Submissions::Filter.call(@submissions, current_user, params)
|
||||||
|
|
||||||
@pagy, @submissions = pagy(@submissions.preload(:submitters).order(id: :desc))
|
@submissions = if params[:completed_at_from].present? || params[:completed_at_to].present?
|
||||||
|
@submissions.order(Submitter.arel_table[:completed_at].maximum.desc)
|
||||||
|
else
|
||||||
|
@submissions.order(id: :desc)
|
||||||
|
end
|
||||||
|
|
||||||
|
@pagy, @submissions = pagy(@submissions.preload(submitters: :start_form_submission_events))
|
||||||
rescue ActiveRecord::RecordNotFound
|
rescue ActiveRecord::RecordNotFound
|
||||||
redirect_to root_path
|
redirect_to root_path
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -9,13 +9,21 @@ class TemplatesController < ApplicationController
|
|||||||
submissions = @template.submissions.accessible_by(current_ability)
|
submissions = @template.submissions.accessible_by(current_ability)
|
||||||
submissions = submissions.active if @template.archived_at.blank?
|
submissions = submissions.active if @template.archived_at.blank?
|
||||||
submissions = Submissions.search(submissions, params[:q], search_values: true)
|
submissions = Submissions.search(submissions, params[:q], search_values: true)
|
||||||
|
submissions = Submissions::Filter.call(submissions, current_user, params.except(:status))
|
||||||
|
|
||||||
@base_submissions = submissions
|
@base_submissions = submissions
|
||||||
|
|
||||||
submissions = submissions.pending if params[:status] == 'pending'
|
submissions = Submissions::Filter.filter_by_status(submissions, params)
|
||||||
submissions = submissions.completed if params[:status] == 'completed'
|
|
||||||
|
|
||||||
@pagy, @submissions = pagy(submissions.preload(submitters: :start_form_submission_events).order(id: :desc))
|
submissions = if params[:completed_at_from].present? || params[:completed_at_to].present?
|
||||||
|
submissions.order(Submitter.arel_table[:completed_at].maximum.desc)
|
||||||
|
else
|
||||||
|
submissions.order(id: :desc)
|
||||||
|
end
|
||||||
|
|
||||||
|
submissions = submissions.preload(:template_accesses) unless current_user.role.in?(%w[admin superadmin])
|
||||||
|
|
||||||
|
@pagy, @submissions = pagy(submissions.preload(submitters: :start_form_submission_events))
|
||||||
rescue ActiveRecord::RecordNotFound
|
rescue ActiveRecord::RecordNotFound
|
||||||
redirect_to root_path
|
redirect_to root_path
|
||||||
end
|
end
|
||||||
@@ -66,7 +74,7 @@ class TemplatesController < ApplicationController
|
|||||||
if @template.save
|
if @template.save
|
||||||
Templates::CloneAttachments.call(template: @template, original_template: @base_template) if @base_template
|
Templates::CloneAttachments.call(template: @template, original_template: @base_template) if @base_template
|
||||||
|
|
||||||
SendTemplateUpdatedWebhookRequestJob.perform_async('template_id' => @template.id)
|
enqueue_template_created_webhooks(@template)
|
||||||
|
|
||||||
maybe_redirect_to_template(@template)
|
maybe_redirect_to_template(@template)
|
||||||
else
|
else
|
||||||
@@ -77,14 +85,14 @@ class TemplatesController < ApplicationController
|
|||||||
def update
|
def update
|
||||||
@template.update!(template_params)
|
@template.update!(template_params)
|
||||||
|
|
||||||
SendTemplateUpdatedWebhookRequestJob.perform_async('template_id' => @template.id)
|
enqueue_template_updated_webhooks(@template)
|
||||||
|
|
||||||
head :ok
|
head :ok
|
||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
notice =
|
notice =
|
||||||
if params[:permanently].present?
|
if params[:permanently].in?(['true', true])
|
||||||
@template.destroy!
|
@template.destroy!
|
||||||
|
|
||||||
I18n.t('template_has_been_removed')
|
I18n.t('template_has_been_removed')
|
||||||
@@ -102,13 +110,13 @@ class TemplatesController < ApplicationController
|
|||||||
def template_params
|
def template_params
|
||||||
params.require(:template).permit(
|
params.require(:template).permit(
|
||||||
:name,
|
:name,
|
||||||
{ schema: [%i[attachment_uuid name]],
|
{ schema: [[:attachment_uuid, :name, { conditions: [%i[field_uuid value action operation]] }]],
|
||||||
submitters: [%i[name uuid is_requester linked_to_uuid invite_by_uuid email]],
|
submitters: [%i[name uuid is_requester linked_to_uuid invite_by_uuid optional_invite_by_uuid email]],
|
||||||
fields: [[:uuid, :submitter_uuid, :name, :type,
|
fields: [[:uuid, :submitter_uuid, :name, :type,
|
||||||
:required, :readonly, :default_value,
|
:required, :readonly, :default_value,
|
||||||
:title, :description,
|
:title, :description,
|
||||||
{ preferences: {},
|
{ preferences: {},
|
||||||
conditions: [%i[field_uuid value action]],
|
conditions: [%i[field_uuid value action operation]],
|
||||||
options: [%i[value uuid]],
|
options: [%i[value uuid]],
|
||||||
validation: %i[message pattern],
|
validation: %i[message pattern],
|
||||||
areas: [%i[x y w h cell_w attachment_uuid option_uuid page]] }]] }
|
areas: [%i[x y w h cell_w attachment_uuid option_uuid page]] }]] }
|
||||||
@@ -128,6 +136,20 @@ class TemplatesController < ApplicationController
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def enqueue_template_created_webhooks(template)
|
||||||
|
WebhookUrls.for_account_id(template.account_id, 'template.created').each do |webhook_url|
|
||||||
|
SendTemplateCreatedWebhookRequestJob.perform_async('template_id' => template.id,
|
||||||
|
'webhook_url_id' => webhook_url.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def enqueue_template_updated_webhooks(template)
|
||||||
|
WebhookUrls.for_account_id(template.account_id, 'template.updated').each do |webhook_url|
|
||||||
|
SendTemplateUpdatedWebhookRequestJob.perform_async('template_id' => template.id,
|
||||||
|
'webhook_url_id' => webhook_url.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def load_base_template
|
def load_base_template
|
||||||
return if params[:base_template_id].blank?
|
return if params[:base_template_id].blank?
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ class TemplatesDashboardController < ApplicationController
|
|||||||
FOLDERS_PER_PAGE = 18
|
FOLDERS_PER_PAGE = 18
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@template_folders = filter_template_folders(@template_folders)
|
@template_folders = @template_folders.where(id: @templates.active.select(:folder_id)).order(id: :desc)
|
||||||
|
|
||||||
|
@template_folders = TemplateFolders.search(@template_folders, params[:q])
|
||||||
|
|
||||||
@pagy, @template_folders = pagy(
|
@pagy, @template_folders = pagy(
|
||||||
@template_folders,
|
@template_folders,
|
||||||
@@ -36,16 +38,8 @@ class TemplatesDashboardController < ApplicationController
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def filter_template_folders(template_folders)
|
|
||||||
rel = template_folders.joins(:active_templates)
|
|
||||||
.order(id: :desc)
|
|
||||||
.distinct
|
|
||||||
|
|
||||||
TemplateFolders.search(rel, params[:q])
|
|
||||||
end
|
|
||||||
|
|
||||||
def filter_templates(templates)
|
def filter_templates(templates)
|
||||||
rel = templates.active.preload(:author).order(id: :desc)
|
rel = templates.active.preload(:author, :template_accesses).order(id: :desc)
|
||||||
|
|
||||||
if params[:q].blank?
|
if params[:q].blank?
|
||||||
if Docuseal.multitenant? && !current_account.testing?
|
if Docuseal.multitenant? && !current_account.testing?
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ class TemplatesPreferencesController < ApplicationController
|
|||||||
preferences: %i[bcc_completed request_email_subject request_email_body
|
preferences: %i[bcc_completed request_email_subject request_email_body
|
||||||
documents_copy_email_subject documents_copy_email_body
|
documents_copy_email_subject documents_copy_email_body
|
||||||
documents_copy_email_enabled documents_copy_email_attach_audit
|
documents_copy_email_enabled documents_copy_email_attach_audit
|
||||||
|
documents_copy_email_attach_documents
|
||||||
completed_notification_email_attach_documents
|
completed_notification_email_attach_documents
|
||||||
completed_redirect_url
|
completed_redirect_url
|
||||||
submitters_order
|
submitters_order
|
||||||
|
|||||||
@@ -17,38 +17,47 @@ class TemplatesRecipientsController < ApplicationController
|
|||||||
private
|
private
|
||||||
|
|
||||||
def submitters_params
|
def submitters_params
|
||||||
params.require(:template).permit(
|
permit_params = { submitters: [%i[name uuid is_requester optional_invite_by_uuid
|
||||||
submitters: [%i[name uuid is_requester invite_by_uuid linked_to_uuid email option]]
|
invite_by_uuid linked_to_uuid email option]] }
|
||||||
).fetch(:submitters, {}).values.filter_map do |s|
|
|
||||||
|
params.require(:template).permit(permit_params).fetch(:submitters, {}).values.filter_map do |s|
|
||||||
next if s[:uuid].blank?
|
next if s[:uuid].blank?
|
||||||
|
|
||||||
if s[:is_requester] == '1' && s[:invite_by_uuid].blank?
|
if s[:is_requester] == '1' && s[:invite_by_uuid].blank? && s[:optional_invite_by_uuid].blank?
|
||||||
s[:is_requester] = true
|
s[:is_requester] = true
|
||||||
else
|
else
|
||||||
s.delete(:is_requester)
|
s.delete(:is_requester)
|
||||||
end
|
end
|
||||||
|
|
||||||
s.delete(:invite_by_uuid) if s[:invite_by_uuid].blank?
|
s.delete(:invite_by_uuid) if s[:invite_by_uuid].blank?
|
||||||
|
s.delete(:optional_invite_by_uuid) if s[:optional_invite_by_uuid].blank?
|
||||||
|
|
||||||
option = s.delete(:option)
|
normalize_option_value(s)
|
||||||
|
|
||||||
if option.present?
|
|
||||||
case option
|
|
||||||
when 'is_requester'
|
|
||||||
s[:is_requester] = true
|
|
||||||
when 'not_set'
|
|
||||||
s.delete(:is_requester)
|
|
||||||
s.delete(:email)
|
|
||||||
s.delete(:linked_to_uuid)
|
|
||||||
s.delete(:invite_by_uuid)
|
|
||||||
when /\Alinked_to_(.*)\z/
|
|
||||||
s[:linked_to_uuid] = ::Regexp.last_match(-1)
|
|
||||||
when /\Ainvite_by_(.*)\z/
|
|
||||||
s[:invite_by_uuid] = ::Regexp.last_match(-1)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
s
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def normalize_option_value(attrs)
|
||||||
|
option = attrs.delete(:option)
|
||||||
|
|
||||||
|
if option.present?
|
||||||
|
case option
|
||||||
|
when 'is_requester'
|
||||||
|
attrs[:is_requester] = true
|
||||||
|
when 'not_set'
|
||||||
|
attrs.delete(:is_requester)
|
||||||
|
attrs.delete(:email)
|
||||||
|
attrs.delete(:linked_to_uuid)
|
||||||
|
attrs.delete(:invite_by_uuid)
|
||||||
|
attrs.delete(:optional_invite_by_uuid)
|
||||||
|
when /\Alinked_to_(.*)\z/
|
||||||
|
attrs[:linked_to_uuid] = ::Regexp.last_match(-1)
|
||||||
|
when /\Aoptional_invite_by_(.*)\z/
|
||||||
|
attrs[:optional_invite_by_uuid] = ::Regexp.last_match(-1)
|
||||||
|
when /\Ainvite_by_(.*)\z/
|
||||||
|
attrs[:invite_by_uuid] = ::Regexp.last_match(-1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
attrs
|
||||||
|
end
|
||||||
end
|
end
|
||||||
@@ -23,7 +23,7 @@ class TemplatesUploadsController < ApplicationController
|
|||||||
|
|
||||||
@template.update!(schema:)
|
@template.update!(schema:)
|
||||||
|
|
||||||
SendTemplateCreatedWebhookRequestJob.perform_async('template_id' => @template.id)
|
enqueue_template_created_webhooks(@template)
|
||||||
|
|
||||||
redirect_to edit_template_path(@template)
|
redirect_to edit_template_path(@template)
|
||||||
rescue Templates::CreateAttachments::PdfEncrypted
|
rescue Templates::CreateAttachments::PdfEncrypted
|
||||||
@@ -55,14 +55,22 @@ class TemplatesUploadsController < ApplicationController
|
|||||||
tempfile.write(DownloadUtils.call(params[:url]).body)
|
tempfile.write(DownloadUtils.call(params[:url]).body)
|
||||||
tempfile.rewind
|
tempfile.rewind
|
||||||
|
|
||||||
|
filename = URI.decode_www_form_component(params[:filename]) if params[:filename].present?
|
||||||
|
filename ||= File.basename(URI.decode_www_form_component(params[:url]))
|
||||||
|
|
||||||
file = ActionDispatch::Http::UploadedFile.new(
|
file = ActionDispatch::Http::UploadedFile.new(
|
||||||
tempfile:,
|
tempfile:,
|
||||||
filename: File.basename(
|
filename:,
|
||||||
URI.decode_www_form_component(params[:filename].presence || params[:url]), '.*'
|
|
||||||
),
|
|
||||||
type: Marcel::MimeType.for(tempfile)
|
type: Marcel::MimeType.for(tempfile)
|
||||||
)
|
)
|
||||||
|
|
||||||
{ files: [file] }
|
{ files: [file] }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def enqueue_template_created_webhooks(template)
|
||||||
|
WebhookUrls.for_account_id(template.account_id, 'template.created').each do |webhook_url|
|
||||||
|
SendTemplateCreatedWebhookRequestJob.perform_async('template_id' => template.id,
|
||||||
|
'webhook_url_id' => webhook_url.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
@@ -4,9 +4,8 @@ class TestingApiSettingsController < ApplicationController
|
|||||||
def index
|
def index
|
||||||
authorize!(:manage, current_user.access_token)
|
authorize!(:manage, current_user.access_token)
|
||||||
|
|
||||||
@webhook_config =
|
@webhook_url = current_account.webhook_urls.first_or_initialize
|
||||||
current_account.encrypted_configs.find_or_initialize_by(key: EncryptedConfig::WEBHOOK_URL_KEY)
|
|
||||||
|
|
||||||
authorize!(:manage, @webhook_config)
|
authorize!(:manage, @webhook_url)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -24,16 +24,14 @@ class UsersController < ApplicationController
|
|||||||
def edit; end
|
def edit; end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
existing_user = User.accessible_by(current_ability).find_by(email: @user.email)
|
if User.accessible_by(current_ability).exists?(email: @user.email)
|
||||||
|
@user.errors.add(:email, I18n.t('already_exists'))
|
||||||
|
|
||||||
if existing_user
|
return render turbo_stream: turbo_stream.replace(:modal, template: 'users/new'), status: :unprocessable_entity
|
||||||
existing_user.archived_at = nil
|
|
||||||
existing_user.assign_attributes(user_params)
|
|
||||||
existing_user.account = current_account
|
|
||||||
|
|
||||||
@user = existing_user
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@user.role = User::ADMIN_ROLE unless role_valid?(@user.role)
|
||||||
|
|
||||||
if @user.save
|
if @user.save
|
||||||
UserMailer.invitation_email(@user).deliver_later!
|
UserMailer.invitation_email(@user).deliver_later!
|
||||||
|
|
||||||
@@ -81,14 +79,7 @@ class UsersController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def build_user
|
def build_user
|
||||||
@user = current_account.users.find_by(email: user_params[:email])&.tap do |user|
|
@user = current_account.users.new(user_params)
|
||||||
user.assign_attributes(user_params)
|
|
||||||
user.archived_at = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
@user ||= current_account.users.new(user_params)
|
|
||||||
|
|
||||||
@user
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def user_params
|
def user_params
|
||||||
|
|||||||
@@ -1,37 +1,22 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class WebhookPreferencesController < ApplicationController
|
class WebhookPreferencesController < ApplicationController
|
||||||
EVENTS = %w[
|
load_and_authorize_resource :webhook_url, parent: false
|
||||||
form.viewed
|
|
||||||
form.started
|
|
||||||
form.completed
|
|
||||||
form.declined
|
|
||||||
template.created
|
|
||||||
template.updated
|
|
||||||
submission.created
|
|
||||||
submission.archived
|
|
||||||
].freeze
|
|
||||||
|
|
||||||
before_action :load_account_config
|
def update
|
||||||
authorize_resource :account_config, parent: false
|
webhook_preferences_params[:events].each do |event, val|
|
||||||
|
@webhook_url.events.delete(event) if val == '0'
|
||||||
|
@webhook_url.events.push(event) if val == '1' && @webhook_url.events.exclude?(event)
|
||||||
|
end
|
||||||
|
|
||||||
def create
|
@webhook_url.save!
|
||||||
@account_config.value[account_config_params[:event]] = account_config_params[:value] == '1'
|
|
||||||
|
|
||||||
@account_config.save!
|
|
||||||
|
|
||||||
head :ok
|
head :ok
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def load_account_config
|
def webhook_preferences_params
|
||||||
@account_config =
|
params.require(:webhook_url).permit(events: {})
|
||||||
current_account.account_configs.find_or_initialize_by(key: AccountConfig::WEBHOOK_PREFERENCES_KEY)
|
|
||||||
@account_config.value ||= {}
|
|
||||||
end
|
|
||||||
|
|
||||||
def account_config_params
|
|
||||||
params.permit(:event, :value)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -1,29 +1,22 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class WebhookSecretController < ApplicationController
|
class WebhookSecretController < ApplicationController
|
||||||
before_action :load_encrypted_config
|
load_and_authorize_resource :webhook_url, parent: false
|
||||||
authorize_resource :encrypted_config, parent: false
|
|
||||||
|
|
||||||
def index; end
|
def show; end
|
||||||
|
|
||||||
def create
|
def update
|
||||||
@encrypted_config.assign_attributes(value: {
|
@webhook_url.update!(secret: {
|
||||||
encrypted_config_params[:key] => encrypted_config_params[:value]
|
webhook_secret_params[:key] => webhook_secret_params[:value]
|
||||||
}.compact_blank)
|
}.compact_blank)
|
||||||
|
|
||||||
@encrypted_config.value.present? ? @encrypted_config.save! : @encrypted_config.delete
|
redirect_back(fallback_location: settings_webhook_path(@webhook_url),
|
||||||
|
notice: I18n.t('webhook_secret_has_been_saved'))
|
||||||
redirect_back(fallback_location: settings_webhooks_path, notice: I18n.t('webhook_secret_has_been_saved'))
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def load_encrypted_config
|
def webhook_secret_params
|
||||||
@encrypted_config =
|
params.require(:webhook_url).permit(secret: %i[key value]).fetch(:secret, {})
|
||||||
current_account.encrypted_configs.find_or_initialize_by(key: EncryptedConfig::WEBHOOK_SECRET_KEY)
|
|
||||||
end
|
|
||||||
|
|
||||||
def encrypted_config_params
|
|
||||||
params.require(:encrypted_config).permit(value: %i[key value]).fetch(:value, {})
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -1,36 +1,60 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class WebhookSettingsController < ApplicationController
|
class WebhookSettingsController < ApplicationController
|
||||||
before_action :load_encrypted_config
|
load_and_authorize_resource :webhook_url, parent: false, only: %i[index show new create update destroy]
|
||||||
authorize_resource :encrypted_config, parent: false
|
load_and_authorize_resource :webhook_url, only: %i[resend], id_param: :webhook_id
|
||||||
|
|
||||||
|
def index
|
||||||
|
@webhook_urls = @webhook_urls.order(id: :desc)
|
||||||
|
@webhook_url = @webhook_urls.first_or_initialize
|
||||||
|
|
||||||
|
render @webhook_urls.size > 1 ? 'index' : 'show'
|
||||||
|
end
|
||||||
|
|
||||||
def show; end
|
def show; end
|
||||||
|
|
||||||
|
def new; end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
@encrypted_config.assign_attributes(encrypted_config_params)
|
@webhook_url.save!
|
||||||
|
|
||||||
@encrypted_config.value.present? ? @encrypted_config.save! : @encrypted_config.delete
|
redirect_to settings_webhooks_path, notice: I18n.t('webhook_url_has_been_saved')
|
||||||
|
|
||||||
redirect_back(fallback_location: settings_webhooks_path, notice: I18n.t('webhook_url_has_been_saved'))
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
|
@webhook_url.update!(update_params)
|
||||||
|
|
||||||
|
redirect_back(fallback_location: settings_webhook_path(@webhook_url),
|
||||||
|
notice: I18n.t('webhook_url_has_been_updated'))
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
@webhook_url.destroy!
|
||||||
|
|
||||||
|
redirect_to settings_webhooks_path, notice: I18n.t('webhook_url_has_been_deleted')
|
||||||
|
end
|
||||||
|
|
||||||
|
def resend
|
||||||
submitter = current_account.submitters.where.not(completed_at: nil).order(:id).last
|
submitter = current_account.submitters.where.not(completed_at: nil).order(:id).last
|
||||||
|
|
||||||
SendFormCompletedWebhookRequestJob.perform_async({ 'submitter_id' => submitter.id,
|
if submitter.blank? || @webhook_url.blank?
|
||||||
'encrypted_config_id' => @encrypted_config.id })
|
return redirect_back(fallback_location: settings_webhooks_path,
|
||||||
|
alert: I18n.t('unable_to_resend_webhook_request'))
|
||||||
|
end
|
||||||
|
|
||||||
|
SendFormCompletedWebhookRequestJob.perform_async('submitter_id' => submitter.id,
|
||||||
|
'webhook_url_id' => @webhook_url.id)
|
||||||
|
|
||||||
redirect_back(fallback_location: settings_webhooks_path, notice: I18n.t('webhook_request_has_been_sent'))
|
redirect_back(fallback_location: settings_webhooks_path, notice: I18n.t('webhook_request_has_been_sent'))
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def load_encrypted_config
|
def create_params
|
||||||
@encrypted_config =
|
params.require(:webhook_url).permit(:url, events: []).reverse_merge(events: [])
|
||||||
current_account.encrypted_configs.find_or_initialize_by(key: EncryptedConfig::WEBHOOK_URL_KEY)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def encrypted_config_params
|
def update_params
|
||||||
params.require(:encrypted_config).permit(:value)
|
params.require(:webhook_url).permit(:url)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -30,6 +30,8 @@ import ToggleAttribute from './elements/toggle_attribute'
|
|||||||
import LinkedInput from './elements/linked_input'
|
import LinkedInput from './elements/linked_input'
|
||||||
import CheckboxGroup from './elements/checkbox_group'
|
import CheckboxGroup from './elements/checkbox_group'
|
||||||
import MaskedInput from './elements/masked_input'
|
import MaskedInput from './elements/masked_input'
|
||||||
|
import SetDateButton from './elements/set_date_button'
|
||||||
|
import IndeterminateCheckbox from './elements/indeterminate_checkbox'
|
||||||
|
|
||||||
import * as TurboInstantClick from './lib/turbo_instant_click'
|
import * as TurboInstantClick from './lib/turbo_instant_click'
|
||||||
|
|
||||||
@@ -97,6 +99,8 @@ safeRegisterElement('toggle-attribute', ToggleAttribute)
|
|||||||
safeRegisterElement('linked-input', LinkedInput)
|
safeRegisterElement('linked-input', LinkedInput)
|
||||||
safeRegisterElement('checkbox-group', CheckboxGroup)
|
safeRegisterElement('checkbox-group', CheckboxGroup)
|
||||||
safeRegisterElement('masked-input', MaskedInput)
|
safeRegisterElement('masked-input', MaskedInput)
|
||||||
|
safeRegisterElement('set-date-button', SetDateButton)
|
||||||
|
safeRegisterElement('indeterminate-checkbox', IndeterminateCheckbox)
|
||||||
|
|
||||||
safeRegisterElement('template-builder', class extends HTMLElement {
|
safeRegisterElement('template-builder', class extends HTMLElement {
|
||||||
connectedCallback () {
|
connectedCallback () {
|
||||||
@@ -111,6 +115,7 @@ safeRegisterElement('template-builder', class extends HTMLElement {
|
|||||||
backgroundColor: '#faf7f5',
|
backgroundColor: '#faf7f5',
|
||||||
locale: this.dataset.locale,
|
locale: this.dataset.locale,
|
||||||
withPhone: this.dataset.withPhone === 'true',
|
withPhone: this.dataset.withPhone === 'true',
|
||||||
|
withVerification: ['true', 'false'].includes(this.dataset.withVerification) ? this.dataset.withVerification === 'true' : null,
|
||||||
withLogo: this.dataset.withLogo !== 'false',
|
withLogo: this.dataset.withLogo !== 'false',
|
||||||
editable: this.dataset.editable !== 'false',
|
editable: this.dataset.editable !== 'false',
|
||||||
authenticityToken: document.querySelector('meta[name="csrf-token"]')?.content,
|
authenticityToken: document.querySelector('meta[name="csrf-token"]')?.content,
|
||||||
|
|||||||
@@ -97,6 +97,23 @@ button[disabled] .enabled {
|
|||||||
bottom: auto;
|
bottom: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tooltip-bottom-start:before {
|
||||||
|
transform: translateX(-31%);
|
||||||
|
top: var(--tooltip-offset);
|
||||||
|
left: 100%;
|
||||||
|
right: auto;
|
||||||
|
bottom: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip-bottom-start:after {
|
||||||
|
transform: translateX(-25%);
|
||||||
|
border-color: transparent transparent var(--tooltip-color) transparent;
|
||||||
|
top: var(--tooltip-tail-offset);
|
||||||
|
left: 50%;
|
||||||
|
right: auto;
|
||||||
|
bottom: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.autocomplete {
|
.autocomplete {
|
||||||
background: white;
|
background: white;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
|
|||||||
@@ -12,6 +12,10 @@ window.customElements.define('draw-signature', class extends HTMLElement {
|
|||||||
|
|
||||||
this.pad = new SignaturePad(this.canvas)
|
this.pad = new SignaturePad(this.canvas)
|
||||||
|
|
||||||
|
if (this.dataset.color) {
|
||||||
|
this.pad.penColor = this.dataset.color
|
||||||
|
}
|
||||||
|
|
||||||
this.pad.addEventListener('endStroke', () => {
|
this.pad.addEventListener('endStroke', () => {
|
||||||
this.updateSubmitButtonVisibility()
|
this.updateSubmitButtonVisibility()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
export default class extends HTMLElement {
|
||||||
|
connectedCallback () {
|
||||||
|
if (this.dataset.indeterminate === 'true') {
|
||||||
|
this.checkbox.indeterminate = true
|
||||||
|
this.checkbox.readOnly = true
|
||||||
|
}
|
||||||
|
|
||||||
|
this.checkbox.addEventListener('click', () => {
|
||||||
|
this.checkbox.setAttribute('name', this.dataset.name)
|
||||||
|
|
||||||
|
if (this.showIndeterminateEl) {
|
||||||
|
this.showIndeterminateEl.classList.add('hidden')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.checkbox.readOnly) {
|
||||||
|
this.checkbox.checked = this.checkbox.readOnly = false
|
||||||
|
} else if (!this.checkbox.checked) {
|
||||||
|
if (this.showIndeterminateEl) {
|
||||||
|
this.showIndeterminateEl.classList.remove('hidden')
|
||||||
|
}
|
||||||
|
|
||||||
|
this.checkbox.setAttribute('name', this.dataset.indeterminateName)
|
||||||
|
this.checkbox.checked = this.checkbox.readOnly = this.checkbox.indeterminate = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
get checkbox () {
|
||||||
|
return this.querySelector('input[type="checkbox"]')
|
||||||
|
}
|
||||||
|
|
||||||
|
get showIndeterminateEl () {
|
||||||
|
return document.getElementById(this.dataset.showIndeterminateId)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
export default class extends HTMLElement {
|
||||||
|
connectedCallback () {
|
||||||
|
this.button.addEventListener('click', () => {
|
||||||
|
this.fromInput.value = this.dataset.fromValue || ''
|
||||||
|
this.toInput.value = this.dataset.toValue || ''
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
get button () {
|
||||||
|
return this.querySelector('button')
|
||||||
|
}
|
||||||
|
|
||||||
|
get fromInput () {
|
||||||
|
return document.getElementById(this.dataset.fromId)
|
||||||
|
}
|
||||||
|
|
||||||
|
get toInput () {
|
||||||
|
return document.getElementById(this.dataset.toId)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,6 +15,8 @@ safeRegisterElement('submission-form', class extends HTMLElement {
|
|||||||
this.app = createApp(Form, {
|
this.app = createApp(Form, {
|
||||||
submitter: JSON.parse(this.dataset.submitter),
|
submitter: JSON.parse(this.dataset.submitter),
|
||||||
inviteSubmitters: JSON.parse(this.dataset.inviteSubmitters),
|
inviteSubmitters: JSON.parse(this.dataset.inviteSubmitters),
|
||||||
|
optionalInviteSubmitters: JSON.parse(this.dataset.optionalInviteSubmitters),
|
||||||
|
schema: JSON.parse(this.dataset.schema),
|
||||||
canSendEmail: this.dataset.canSendEmail === 'true',
|
canSendEmail: this.dataset.canSendEmail === 'true',
|
||||||
previousSignatureValue: this.dataset.previousSignatureValue,
|
previousSignatureValue: this.dataset.previousSignatureValue,
|
||||||
goToLast: this.dataset.goToLast === 'true',
|
goToLast: this.dataset.goToLast === 'true',
|
||||||
|
|||||||
@@ -39,7 +39,6 @@
|
|||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="isActive"
|
|
||||||
ref="scrollToElem"
|
ref="scrollToElem"
|
||||||
class="absolute"
|
class="absolute"
|
||||||
:style="{ top: scrollPadding }"
|
:style="{ top: scrollPadding }"
|
||||||
@@ -75,7 +74,7 @@
|
|||||||
ID: {{ signature.uuid }}
|
ID: {{ signature.uuid }}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{{ t('reason') }}: {{ values[field.preferences?.reason_field_uuid] || t('digitally_signed_by') }} {{ submitter.name }}
|
<span v-if="values[field.preferences?.reason_field_uuid]">{{ t('reason') }}: </span>{{ values[field.preferences?.reason_field_uuid] || t('digitally_signed_by') }} {{ submitter.name }}
|
||||||
<template v-if="submitter.email">
|
<template v-if="submitter.email">
|
||||||
<{{ submitter.email }}>
|
<{{ submitter.email }}>
|
||||||
</template>
|
</template>
|
||||||
@@ -169,6 +168,7 @@
|
|||||||
<div
|
<div
|
||||||
v-else-if="field.type === 'cells'"
|
v-else-if="field.type === 'cells'"
|
||||||
class="w-full flex items-center"
|
class="w-full flex items-center"
|
||||||
|
:class="{ 'justify-end': field.preferences?.align === 'right' }"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-for="(char, index) in modelValue"
|
v-for="(char, index) in modelValue"
|
||||||
@@ -210,7 +210,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { IconTextSize, IconWritingSign, IconCalendarEvent, IconPhoto, IconCheckbox, IconPaperclip, IconSelect, IconCircleDot, IconChecks, IconCheck, IconColumns3, IconPhoneCheck, IconLetterCaseUpper, IconCreditCard, IconRubberStamp, IconSquareNumber1 } from '@tabler/icons-vue'
|
import { IconTextSize, IconWritingSign, IconCalendarEvent, IconPhoto, IconCheckbox, IconPaperclip, IconSelect, IconCircleDot, IconChecks, IconCheck, IconColumns3, IconPhoneCheck, IconLetterCaseUpper, IconCreditCard, IconRubberStamp, IconSquareNumber1, IconId } from '@tabler/icons-vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'FieldArea',
|
name: 'FieldArea',
|
||||||
@@ -312,7 +312,8 @@ export default {
|
|||||||
cells: this.t('cells'),
|
cells: this.t('cells'),
|
||||||
stamp: this.t('stamp'),
|
stamp: this.t('stamp'),
|
||||||
payment: this.t('payment'),
|
payment: this.t('payment'),
|
||||||
phone: this.t('phone')
|
phone: this.t('phone'),
|
||||||
|
verification: this.t('verify_id')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
alignClasses () {
|
alignClasses () {
|
||||||
@@ -341,7 +342,8 @@ export default {
|
|||||||
cells: IconColumns3,
|
cells: IconColumns3,
|
||||||
multiple: IconChecks,
|
multiple: IconChecks,
|
||||||
phone: IconPhoneCheck,
|
phone: IconPhoneCheck,
|
||||||
payment: IconCreditCard
|
payment: IconCreditCard,
|
||||||
|
verification: IconId
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
image () {
|
image () {
|
||||||
@@ -405,8 +407,12 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.field.preferences?.font_size) {
|
if (this.field.preferences?.font_size) {
|
||||||
style.fontSize = `clamp(4pt, 1.6vw, ${this.field.preferences.font_size}pt)`
|
style.fontSize = `clamp(4pt, 1.6vw, ${parseInt(this.field.preferences.font_size) * 1.23}pt)`
|
||||||
style.lineHeight = `clamp(6pt, 2.0vw, ${parseInt(this.field.preferences.font_size) + 3}pt)`
|
style.lineHeight = `clamp(6pt, 2.0vw, ${parseInt(this.field.preferences.font_size) * 1.23 + 3}pt)`
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.field.preferences?.color) {
|
||||||
|
style.color = this.field.preferences.color
|
||||||
}
|
}
|
||||||
|
|
||||||
return style
|
return style
|
||||||
|
|||||||
@@ -91,7 +91,7 @@
|
|||||||
>
|
>
|
||||||
{{ t('powered_by') }}
|
{{ t('powered_by') }}
|
||||||
<a
|
<a
|
||||||
href="https://www.docuseal.co/start"
|
href="https://www.docuseal.com/start"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
class="underline"
|
class="underline"
|
||||||
>DocuSeal</a> - {{ t('open_source_documents_software') }}
|
>DocuSeal</a> - {{ t('open_source_documents_software') }}
|
||||||
|
|||||||
@@ -37,6 +37,7 @@
|
|||||||
<AppearsOn :field="field" />
|
<AppearsOn :field="field" />
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<input
|
<input
|
||||||
|
:id="field.uuid"
|
||||||
ref="input"
|
ref="input"
|
||||||
v-model="value"
|
v-model="value"
|
||||||
class="base-input !text-2xl text-center w-full"
|
class="base-input !text-2xl text-center w-full"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
|
id="dropzone"
|
||||||
class="flex h-32 w-full"
|
class="flex h-32 w-full"
|
||||||
@dragover.prevent
|
@dragover.prevent
|
||||||
@drop.prevent="onDropFiles"
|
@drop.prevent="onDropFiles"
|
||||||
|
|||||||
@@ -11,12 +11,13 @@
|
|||||||
:with-label="!isAnonymousChecboxes && showFieldNames"
|
:with-label="!isAnonymousChecboxes && showFieldNames"
|
||||||
:current-step="currentStepFields"
|
:current-step="currentStepFields"
|
||||||
:scroll-padding="scrollPadding"
|
:scroll-padding="scrollPadding"
|
||||||
@focus-step="[saveStep(), currentField.type !== 'checkbox' ? isFormVisible = true : '', goToStep($event, false, true)]"
|
@focus-step="[saveStep(), goToStep($event, false, true), currentField.type !== 'checkbox' ? isFormVisible = true : '']"
|
||||||
/>
|
/>
|
||||||
<FieldAreas
|
<FieldAreas
|
||||||
:steps="readonlyConditionalFields.map((e) => [e])"
|
:steps="readonlyConditionalFields.map((e) => [e])"
|
||||||
:values="readonlyConditionalFields.reduce((acc, f) => { acc[f.uuid] = f.default_value; return acc }, {})"
|
:values="readonlyConditionalFields.reduce((acc, f) => { acc[f.uuid] = (values[f.uuid] || f.default_value); return acc }, {})"
|
||||||
:submitter="submitter"
|
:submitter="submitter"
|
||||||
|
:attachments-index="attachmentsIndex"
|
||||||
:submittable="false"
|
:submittable="false"
|
||||||
/>
|
/>
|
||||||
<FormulaFieldAreas
|
<FormulaFieldAreas
|
||||||
@@ -24,6 +25,44 @@
|
|||||||
:fields="formulaFields"
|
:fields="formulaFields"
|
||||||
:values="values"
|
:values="values"
|
||||||
/>
|
/>
|
||||||
|
<Teleport
|
||||||
|
v-if="completeButtonToRef"
|
||||||
|
:to="completeButtonToRef"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
v-if="(emptyValueRequiredStep && ((stepFields.length - 1) !== currentStep || currentStepFields !== emptyValueRequiredStep)) || isCompleted"
|
||||||
|
class="tooltip-left"
|
||||||
|
:class="{ tooltip: !isCompleted }"
|
||||||
|
:data-tip="t('fill_all_required_fields_to_complete')"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="btn btn-sm btn-neutral text-white px-4 w-full flex justify-center btn-disabled pointer-events-auto"
|
||||||
|
@click="[isFormVisible = true, !isCompleted && goToStep(stepFields.indexOf(emptyValueRequiredStep), true, false)]"
|
||||||
|
>
|
||||||
|
{{ t('complete') }}
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
v-else
|
||||||
|
id="complete_form_button"
|
||||||
|
class="btn btn-sm btn-neutral text-white px-4 w-full flex justify-center"
|
||||||
|
form="steps_form"
|
||||||
|
type="submit"
|
||||||
|
name="completed"
|
||||||
|
value="true"
|
||||||
|
:disabled="isSubmittingComplete"
|
||||||
|
>
|
||||||
|
<span class="flex items-center">
|
||||||
|
<IconInnerShadowTop
|
||||||
|
v-if="isSubmittingComplete"
|
||||||
|
class="mr-1 animate-spin w-5 h-5"
|
||||||
|
/>
|
||||||
|
<span>
|
||||||
|
{{ t('complete') }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</Teleport>
|
||||||
<button
|
<button
|
||||||
v-if="!isFormVisible"
|
v-if="!isFormVisible"
|
||||||
id="expand_form_button"
|
id="expand_form_button"
|
||||||
@@ -74,6 +113,7 @@
|
|||||||
>
|
>
|
||||||
<form
|
<form
|
||||||
v-if="!isCompleted && !isInvite"
|
v-if="!isCompleted && !isInvite"
|
||||||
|
id="steps_form"
|
||||||
ref="form"
|
ref="form"
|
||||||
:action="submitPath"
|
:action="submitPath"
|
||||||
method="post"
|
method="post"
|
||||||
@@ -409,9 +449,22 @@
|
|||||||
@focus="scrollIntoField(currentField)"
|
@focus="scrollIntoField(currentField)"
|
||||||
@submit="!isSubmitting && submitStep()"
|
@submit="!isSubmitting && submitStep()"
|
||||||
/>
|
/>
|
||||||
|
<VerificationStep
|
||||||
|
v-else-if="currentField.type === 'verification'"
|
||||||
|
ref="currentStep"
|
||||||
|
:key="currentField.uuid"
|
||||||
|
:locale="language?.toLowerCase() || browserLanguage"
|
||||||
|
:submitter="submitter"
|
||||||
|
:empty-value-required-step="emptyValueRequiredStep"
|
||||||
|
:field="currentField"
|
||||||
|
:submitter-slug="submitterSlug"
|
||||||
|
:values="values"
|
||||||
|
@focus="scrollIntoField(currentField)"
|
||||||
|
@submit="!isSubmitting && submitStep()"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="currentField.type !== 'payment' || submittedValues[currentField.uuid]"
|
v-if="(currentField.type !== 'payment' && currentField.type !== 'verification') || submittedValues[currentField.uuid]"
|
||||||
:class="currentField.type === 'signature' ? 'mt-2' : 'mt-6 md:mt-8'"
|
:class="currentField.type === 'signature' ? 'mt-2' : 'mt-6 md:mt-8'"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
@@ -445,6 +498,7 @@
|
|||||||
<InviteForm
|
<InviteForm
|
||||||
v-else-if="isInvite"
|
v-else-if="isInvite"
|
||||||
:submitters="inviteSubmitters"
|
:submitters="inviteSubmitters"
|
||||||
|
:optional-submitters="optionalInviteSubmitters"
|
||||||
:submitter-slug="submitterSlug"
|
:submitter-slug="submitterSlug"
|
||||||
:authenticity-token="authenticityToken"
|
:authenticity-token="authenticityToken"
|
||||||
:url="baseUrl + submitPath + '/invite'"
|
:url="baseUrl + submitPath + '/invite'"
|
||||||
@@ -498,6 +552,7 @@ import AttachmentStep from './attachment_step'
|
|||||||
import MultiSelectStep from './multi_select_step'
|
import MultiSelectStep from './multi_select_step'
|
||||||
import PhoneStep from './phone_step'
|
import PhoneStep from './phone_step'
|
||||||
import PaymentStep from './payment_step'
|
import PaymentStep from './payment_step'
|
||||||
|
import VerificationStep from './verification_step'
|
||||||
import TextStep from './text_step'
|
import TextStep from './text_step'
|
||||||
import NumberStep from './number_step'
|
import NumberStep from './number_step'
|
||||||
import DateStep from './date_step'
|
import DateStep from './date_step'
|
||||||
@@ -540,6 +595,7 @@ export default {
|
|||||||
IconWritingSign,
|
IconWritingSign,
|
||||||
AttachmentStep,
|
AttachmentStep,
|
||||||
InitialsStep,
|
InitialsStep,
|
||||||
|
VerificationStep,
|
||||||
InviteForm,
|
InviteForm,
|
||||||
MultiSelectStep,
|
MultiSelectStep,
|
||||||
IconInnerShadowTop,
|
IconInnerShadowTop,
|
||||||
@@ -572,6 +628,11 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: () => []
|
default: () => []
|
||||||
},
|
},
|
||||||
|
optionalInviteSubmitters: {
|
||||||
|
type: Array,
|
||||||
|
required: false,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
withSignatureId: {
|
withSignatureId: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -582,6 +643,11 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: '-80px'
|
default: '-80px'
|
||||||
},
|
},
|
||||||
|
orderAsOnPage: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
requireSigningReason: {
|
requireSigningReason: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -592,6 +658,16 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
|
completeButtonToRef: {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
type: Array,
|
||||||
|
required: false,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
attachments: {
|
attachments: {
|
||||||
type: Array,
|
type: Array,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -772,6 +848,7 @@ export default {
|
|||||||
phoneVerifiedValues: {},
|
phoneVerifiedValues: {},
|
||||||
orientation: screen?.orientation?.type,
|
orientation: screen?.orientation?.type,
|
||||||
isSubmitting: false,
|
isSubmitting: false,
|
||||||
|
isSubmittingComplete: false,
|
||||||
submittedValues: {},
|
submittedValues: {},
|
||||||
recalculateButtonDisabledKey: ''
|
recalculateButtonDisabledKey: ''
|
||||||
}
|
}
|
||||||
@@ -780,6 +857,28 @@ export default {
|
|||||||
isMobile () {
|
isMobile () {
|
||||||
return /android|iphone|ipad/i.test(navigator.userAgent)
|
return /android|iphone|ipad/i.test(navigator.userAgent)
|
||||||
},
|
},
|
||||||
|
attachmentConditionsIndex () {
|
||||||
|
return this.schema.reduce((acc, item) => {
|
||||||
|
if (item.conditions?.length) {
|
||||||
|
if (item.conditions.every((c) => this.fieldsUuidIndex[c.field_uuid])) {
|
||||||
|
acc[item.attachment_uuid] = this.checkFieldConditions(item)
|
||||||
|
} else {
|
||||||
|
acc[item.attachment_uuid] = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
acc[item.attachment_uuid] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
},
|
||||||
|
emptyValueRequiredStep () {
|
||||||
|
return this.stepFields.find((fields, index) => {
|
||||||
|
return fields.some((f) => {
|
||||||
|
return f.required && isEmpty(this.values[f.uuid])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
submitButtonText () {
|
submitButtonText () {
|
||||||
if (this.alwaysMinimize) {
|
if (this.alwaysMinimize) {
|
||||||
return this.t('submit')
|
return this.t('submit')
|
||||||
@@ -853,13 +952,61 @@ export default {
|
|||||||
return this.currentStepFields[0]
|
return this.currentStepFields[0]
|
||||||
},
|
},
|
||||||
readonlyConditionalFields () {
|
readonlyConditionalFields () {
|
||||||
return this.fields.filter((f) => f.readonly && f.conditions?.length && this.checkFieldConditions(f))
|
return this.fields.filter((f) => f.readonly && f.conditions?.length && this.checkFieldConditions(f) && this.checkFieldDocumentsConditions(f))
|
||||||
},
|
},
|
||||||
stepFields () {
|
stepFields () {
|
||||||
return this.fields.filter((f) => !f.readonly).reduce((acc, f) => {
|
const verificationFields = []
|
||||||
|
|
||||||
|
const sortedFields = this.fields.reduce((acc, f) => {
|
||||||
|
if (f.type === 'verification') {
|
||||||
|
verificationFields.push(f)
|
||||||
|
} else if (!f.readonly) {
|
||||||
|
acc.push(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
if (this.orderAsOnPage) {
|
||||||
|
const fieldAreasIndex = {}
|
||||||
|
const attachmentUuids = Object.keys(this.attachmentConditionsIndex)
|
||||||
|
|
||||||
|
const sortArea = (aArea, bArea) => {
|
||||||
|
if (aArea.attachment_uuid === bArea.attachment_uuid) {
|
||||||
|
if (aArea.page === bArea.page) {
|
||||||
|
if (Math.abs(aArea.y - bArea.y) < 0.01) {
|
||||||
|
if (aArea.x === bArea.x) {
|
||||||
|
return 0
|
||||||
|
} else {
|
||||||
|
return aArea.x - bArea.x
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return aArea.y - bArea.y
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return aArea.page - bArea.page
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return attachmentUuids.indexOf(aArea.attachment_uuid) - attachmentUuids.indexOf(bArea.attachment_uuid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sortedFields.sort((aField, bField) => {
|
||||||
|
const aArea = (fieldAreasIndex[aField.uuid] ||= [...(aField.areas || [])].sort(sortArea)[0])
|
||||||
|
const bArea = (fieldAreasIndex[bField.uuid] ||= [...(bField.areas || [])].sort(sortArea)[0])
|
||||||
|
|
||||||
|
return sortArea(aArea, bArea)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (verificationFields.length) {
|
||||||
|
sortedFields.push(verificationFields.pop())
|
||||||
|
}
|
||||||
|
|
||||||
|
return sortedFields.reduce((acc, f) => {
|
||||||
const prevStep = acc[acc.length - 1]
|
const prevStep = acc[acc.length - 1]
|
||||||
|
|
||||||
if (this.checkFieldConditions(f)) {
|
if (this.checkFieldConditions(f) && this.checkFieldDocumentsConditions(f)) {
|
||||||
if (f.type === 'checkbox' && Array.isArray(prevStep) && prevStep[0].type === 'checkbox' && !f.description) {
|
if (f.type === 'checkbox' && Array.isArray(prevStep) && prevStep[0].type === 'checkbox' && !f.description) {
|
||||||
prevStep.push(f)
|
prevStep.push(f)
|
||||||
} else {
|
} else {
|
||||||
@@ -892,6 +1039,23 @@ export default {
|
|||||||
if (isEmpty(value) && this.currentStep > 0) {
|
if (isEmpty(value) && this.currentStep > 0) {
|
||||||
this.currentStep -= 1
|
this.currentStep -= 1
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
attachmentConditionsIndex: {
|
||||||
|
deep: true,
|
||||||
|
immediate: true,
|
||||||
|
handler (value) {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
const root = this.$root.$el.parentNode.getRootNode()
|
||||||
|
|
||||||
|
for (const key in value) {
|
||||||
|
const doc = root.querySelector(`[id="document-${key}"`)
|
||||||
|
|
||||||
|
if (doc) {
|
||||||
|
doc.classList.toggle('hidden', !value[key])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
beforeUnmount () {
|
beforeUnmount () {
|
||||||
@@ -973,33 +1137,57 @@ export default {
|
|||||||
onOrientationChange (event) {
|
onOrientationChange (event) {
|
||||||
this.orientation = event.target.type
|
this.orientation = event.target.type
|
||||||
},
|
},
|
||||||
|
checkFieldDocumentsConditions (field) {
|
||||||
|
if (field.areas?.length) {
|
||||||
|
return field.areas.some((area) => {
|
||||||
|
return this.attachmentConditionsIndex[area.attachment_uuid]
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
},
|
||||||
checkFieldConditions (field) {
|
checkFieldConditions (field) {
|
||||||
if (field.conditions?.length) {
|
if (field.conditions?.length) {
|
||||||
return field.conditions.reduce((acc, c) => {
|
const result = field.conditions.reduce((acc, cond) => {
|
||||||
const field = this.fieldsUuidIndex[c.field_uuid]
|
if (cond.operation === 'or') {
|
||||||
|
acc.push(acc.pop() || this.checkFieldCondition(cond))
|
||||||
if (['not_empty', 'checked', 'equal', 'contains'].includes(c.action) && field && !this.checkFieldConditions(field)) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (['empty', 'unchecked'].includes(c.action)) {
|
|
||||||
return acc && isEmpty(this.values[c.field_uuid])
|
|
||||||
} else if (['not_empty', 'checked'].includes(c.action)) {
|
|
||||||
return acc && !isEmpty(this.values[c.field_uuid])
|
|
||||||
} else if (['equal', 'contains'].includes(c.action) && field) {
|
|
||||||
const option = field.options.find((o) => o.uuid === c.value)
|
|
||||||
const values = [this.values[c.field_uuid]].flat()
|
|
||||||
|
|
||||||
return acc && values.includes(this.optionValue(option, field.options.indexOf(option)))
|
|
||||||
} else if (['not_equal', 'does_not_contain'].includes(c.action) && field) {
|
|
||||||
const option = field.options.find((o) => o.uuid === c.value)
|
|
||||||
const values = [this.values[c.field_uuid]].flat()
|
|
||||||
|
|
||||||
return acc && !values.includes(this.optionValue(option, field.options.indexOf(option)))
|
|
||||||
} else {
|
} else {
|
||||||
return acc
|
acc.push(this.checkFieldCondition(cond))
|
||||||
}
|
}
|
||||||
}, true)
|
|
||||||
|
return acc
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return !result.includes(false)
|
||||||
|
} else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
checkFieldCondition (condition) {
|
||||||
|
const field = this.fieldsUuidIndex[condition.field_uuid]
|
||||||
|
|
||||||
|
if (['not_empty', 'checked', 'equal', 'contains'].includes(condition.action) && field && !this.checkFieldConditions(field)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (['empty', 'unchecked'].includes(condition.action)) {
|
||||||
|
return isEmpty(this.values[condition.field_uuid])
|
||||||
|
} else if (['not_empty', 'checked'].includes(condition.action)) {
|
||||||
|
return !isEmpty(this.values[condition.field_uuid])
|
||||||
|
} else if (['equal', 'contains'].includes(condition.action) && field) {
|
||||||
|
if (field.options) {
|
||||||
|
const option = field.options.find((o) => o.uuid === condition.value)
|
||||||
|
const values = [this.values[condition.field_uuid]].flat()
|
||||||
|
|
||||||
|
return values.includes(this.optionValue(option, field.options.indexOf(option)))
|
||||||
|
} else {
|
||||||
|
return [this.values[condition.field_uuid]].flat().includes(condition.value)
|
||||||
|
}
|
||||||
|
} else if (['not_equal', 'does_not_contain'].includes(condition.action) && field) {
|
||||||
|
const option = field.options.find((o) => o.uuid === condition.value)
|
||||||
|
const values = [this.values[condition.field_uuid]].flat()
|
||||||
|
|
||||||
|
return !values.includes(this.optionValue(option, field.options.indexOf(option)))
|
||||||
} else {
|
} else {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -1146,18 +1334,24 @@ export default {
|
|||||||
scrollIntoArea (area) {
|
scrollIntoArea (area) {
|
||||||
return this.$refs.areas.scrollIntoArea(area)
|
return this.$refs.areas.scrollIntoArea(area)
|
||||||
},
|
},
|
||||||
async submitStep () {
|
async submitStep (e) {
|
||||||
this.isSubmitting = true
|
this.isSubmitting = true
|
||||||
|
|
||||||
|
const forceComplete = e?.submitter?.getAttribute('name') === 'completed'
|
||||||
|
|
||||||
|
if (forceComplete) {
|
||||||
|
this.isSubmittingComplete = true
|
||||||
|
}
|
||||||
|
|
||||||
const submitStep = this.currentStep
|
const submitStep = this.currentStep
|
||||||
|
|
||||||
const stepPromise = ['signature', 'phone', 'initials', 'payment'].includes(this.currentField.type)
|
const stepPromise = ['signature', 'phone', 'initials', 'payment', 'verification'].includes(this.currentField.type)
|
||||||
? this.$refs.currentStep.submit
|
? this.$refs.currentStep.submit
|
||||||
: () => Promise.resolve({})
|
: () => Promise.resolve({})
|
||||||
|
|
||||||
stepPromise().then(async () => {
|
stepPromise().then(async () => {
|
||||||
const emptyRequiredField = this.stepFields.find((fields, index) => {
|
const emptyRequiredField = this.stepFields.find((fields, index) => {
|
||||||
if (index >= submitStep) {
|
if (forceComplete ? index === submitStep : index >= submitStep) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1167,9 +1361,9 @@ export default {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const formData = new FormData(this.$refs.form)
|
const formData = new FormData(this.$refs.form)
|
||||||
const isLastStep = submitStep === this.stepFields.length - 1
|
const isLastStep = (submitStep === this.stepFields.length - 1) || forceComplete
|
||||||
|
|
||||||
if (isLastStep && !emptyRequiredField && !this.inviteSubmitters.length) {
|
if (isLastStep && !emptyRequiredField && !this.inviteSubmitters.length && !this.optionalInviteSubmitters.length) {
|
||||||
formData.append('completed', 'true')
|
formData.append('completed', 'true')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1185,14 +1379,18 @@ export default {
|
|||||||
if (response.status === 422 || response.status === 500) {
|
if (response.status === 422 || response.status === 500) {
|
||||||
const data = await response.json()
|
const data = await response.json()
|
||||||
|
|
||||||
const i18nError = data.error ? this.t(data.error.replace(/\s+/g, '_').toLowerCase()) : ''
|
if (data.error) {
|
||||||
|
const i18nKey = data.error.replace(/\s+/g, '_').toLowerCase()
|
||||||
|
|
||||||
alert(i18nError !== data.error ? i18nError : (data.error || this.t('value_is_invalid')))
|
alert(this.t(i18nKey) !== i18nKey ? this.t(i18nKey) : data.error)
|
||||||
|
} else {
|
||||||
|
alert(this.t('value_is_invalid'))
|
||||||
|
}
|
||||||
|
|
||||||
return Promise.reject(new Error(data.error))
|
return Promise.reject(new Error(data.error))
|
||||||
}
|
}
|
||||||
|
|
||||||
const nextStep = (isLastStep && emptyRequiredField) || this.stepFields[submitStep + 1]
|
const nextStep = (isLastStep && emptyRequiredField) || (forceComplete ? null : this.stepFields[submitStep + 1])
|
||||||
|
|
||||||
if (nextStep) {
|
if (nextStep) {
|
||||||
if (this.alwaysMinimize) {
|
if (this.alwaysMinimize) {
|
||||||
@@ -1204,7 +1402,7 @@ export default {
|
|||||||
if (emptyRequiredField === nextStep) {
|
if (emptyRequiredField === nextStep) {
|
||||||
this.showFillAllRequiredFields = true
|
this.showFillAllRequiredFields = true
|
||||||
}
|
}
|
||||||
} else if (this.inviteSubmitters.length) {
|
} else if (this.inviteSubmitters.length || this.optionalInviteSubmitters.length) {
|
||||||
this.isInvite = true
|
this.isInvite = true
|
||||||
} else {
|
} else {
|
||||||
this.performComplete(response)
|
this.performComplete(response)
|
||||||
@@ -1213,6 +1411,7 @@ export default {
|
|||||||
console.error(error)
|
console.error(error)
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
this.isSubmitting = false
|
this.isSubmitting = false
|
||||||
|
this.isSubmittingComplete = false
|
||||||
})
|
})
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
if (error?.message === 'Image too small') {
|
if (error?.message === 'Image too small') {
|
||||||
@@ -1222,6 +1421,7 @@ export default {
|
|||||||
}
|
}
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
this.isSubmitting = false
|
this.isSubmitting = false
|
||||||
|
this.isSubmittingComplete = false
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
minimizeForm () {
|
minimizeForm () {
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
const en = {
|
const en = {
|
||||||
|
complete_all_required_fields_to_proceed_with_identity_verification: 'Complete all required fields to proceed with identity verification.',
|
||||||
|
verify_id: 'Verify ID',
|
||||||
|
identity_verification: 'Identity verification',
|
||||||
complete: 'Complete',
|
complete: 'Complete',
|
||||||
|
fill_all_required_fields_to_complete: 'Fill all required fields to complete',
|
||||||
sign_and_complete: 'Sign and Complete',
|
sign_and_complete: 'Sign and Complete',
|
||||||
text: 'Text',
|
text: 'Text',
|
||||||
by_clicking_you_agree_to_the: 'By clicking "{button}", you agree to the',
|
by_clicking_you_agree_to_the: 'By clicking "{button}", you agree to the',
|
||||||
@@ -66,7 +70,7 @@ const en = {
|
|||||||
documents_have_been_signed: 'Documents have been signed!',
|
documents_have_been_signed: 'Documents have been signed!',
|
||||||
create_a_free_account: 'Create a Free Account',
|
create_a_free_account: 'Create a Free Account',
|
||||||
powered_by: 'Powered by',
|
powered_by: 'Powered by',
|
||||||
please_check_the_box_to_continue: 'Please check the box to continue',
|
please_check_the_box_to_continue: 'Please check the box to continue.',
|
||||||
open_source_documents_software: 'open source documents software',
|
open_source_documents_software: 'open source documents software',
|
||||||
verified_phone_number: 'Verify Phone Number',
|
verified_phone_number: 'Verify Phone Number',
|
||||||
use_international_format: 'Use international format: +1xxx',
|
use_international_format: 'Use international format: +1xxx',
|
||||||
@@ -88,11 +92,17 @@ const en = {
|
|||||||
pay_with_strip: 'Pay with Stripe',
|
pay_with_strip: 'Pay with Stripe',
|
||||||
reupload: 'Reupload',
|
reupload: 'Reupload',
|
||||||
upload: 'Upload',
|
upload: 'Upload',
|
||||||
files: 'Files'
|
files: 'Files',
|
||||||
|
signature_is_too_small_please_redraw: 'Signature is too small. Please redraw.',
|
||||||
|
wait_countdown_seconds: 'Wait {countdown} seconds'
|
||||||
}
|
}
|
||||||
|
|
||||||
const es = {
|
const es = {
|
||||||
|
complete_all_required_fields_to_proceed_with_identity_verification: 'Complete todos los campos requeridos para continuar con la verificación de identidad.',
|
||||||
|
verify_id: 'Verificar ID',
|
||||||
|
identity_verification: 'Verificación de identidad',
|
||||||
complete: 'Completar',
|
complete: 'Completar',
|
||||||
|
fill_all_required_fields_to_complete: 'Complete todos los campos requeridos para finalizar',
|
||||||
sign_and_complete: 'Firmar y Completar',
|
sign_and_complete: 'Firmar y Completar',
|
||||||
invite: 'Invitar',
|
invite: 'Invitar',
|
||||||
email: 'Correo electrónico',
|
email: 'Correo electrónico',
|
||||||
@@ -158,7 +168,7 @@ const es = {
|
|||||||
documents_have_been_signed: '¡Los documentos han sido firmados!',
|
documents_have_been_signed: '¡Los documentos han sido firmados!',
|
||||||
create_a_free_account: 'Crear una Cuenta Gratuita',
|
create_a_free_account: 'Crear una Cuenta Gratuita',
|
||||||
powered_by: 'Desarrollado por',
|
powered_by: 'Desarrollado por',
|
||||||
please_check_the_box_to_continue: 'Por favor marque la casilla para continuar',
|
please_check_the_box_to_continue: 'Por favor marque la casilla para continuar.',
|
||||||
open_source_documents_software: 'software de documentos de código abierto',
|
open_source_documents_software: 'software de documentos de código abierto',
|
||||||
verified_phone_number: 'Verificar número de teléfono',
|
verified_phone_number: 'Verificar número de teléfono',
|
||||||
use_international_format: 'Usar formato internacional: +1xxx',
|
use_international_format: 'Usar formato internacional: +1xxx',
|
||||||
@@ -180,11 +190,17 @@ const es = {
|
|||||||
pay_with_strip: 'Pagar con Stripe',
|
pay_with_strip: 'Pagar con Stripe',
|
||||||
reupload: 'Volver a subir',
|
reupload: 'Volver a subir',
|
||||||
upload: 'Subir',
|
upload: 'Subir',
|
||||||
files: 'Archivos'
|
files: 'Archivos',
|
||||||
|
signature_is_too_small_please_redraw: 'La firma es demasiado pequeña. Por favor, dibújala de nuevo.',
|
||||||
|
wait_countdown_seconds: 'Espera {countdown} segundos'
|
||||||
}
|
}
|
||||||
|
|
||||||
const it = {
|
const it = {
|
||||||
|
complete_all_required_fields_to_proceed_with_identity_verification: "Compila tutti i campi obbligatori per procedere con la verifica dell'identità.",
|
||||||
|
verify_id: 'Verifica ID',
|
||||||
|
identity_verification: "Verifica dell'identità",
|
||||||
complete: 'Completa',
|
complete: 'Completa',
|
||||||
|
fill_all_required_fields_to_complete: 'Compila tutti i campi obbligatori per completare',
|
||||||
sign_and_complete: 'Firma e Completa',
|
sign_and_complete: 'Firma e Completa',
|
||||||
invite: 'Invita',
|
invite: 'Invita',
|
||||||
email: 'Email',
|
email: 'Email',
|
||||||
@@ -250,7 +266,7 @@ const it = {
|
|||||||
documents_have_been_signed: 'I documenti sono stati firmati!',
|
documents_have_been_signed: 'I documenti sono stati firmati!',
|
||||||
create_a_free_account: 'Crea un Account Gratuito',
|
create_a_free_account: 'Crea un Account Gratuito',
|
||||||
powered_by: 'Desarrollado por',
|
powered_by: 'Desarrollado por',
|
||||||
please_check_the_box_to_continue: 'Si prega di spuntare la casella per continuare',
|
please_check_the_box_to_continue: 'Si prega di spuntare la casella per continuare.',
|
||||||
open_source_documents_software: 'software di documenti open source',
|
open_source_documents_software: 'software di documenti open source',
|
||||||
verified_phone_number: 'Verifica numero di telefono',
|
verified_phone_number: 'Verifica numero di telefono',
|
||||||
use_international_format: 'Usa formato internazionale: +1xxx',
|
use_international_format: 'Usa formato internazionale: +1xxx',
|
||||||
@@ -272,11 +288,17 @@ const it = {
|
|||||||
pay_with_strip: 'Paga con Stripe',
|
pay_with_strip: 'Paga con Stripe',
|
||||||
reupload: 'Ricarica',
|
reupload: 'Ricarica',
|
||||||
upload: 'Carica',
|
upload: 'Carica',
|
||||||
files: 'File'
|
files: 'File',
|
||||||
|
signature_is_too_small_please_redraw: 'La firma è troppo piccola. Ridisegnala per favore.',
|
||||||
|
wait_countdown_seconds: 'Attendi {countdown} secondi'
|
||||||
}
|
}
|
||||||
|
|
||||||
const de = {
|
const de = {
|
||||||
|
complete_all_required_fields_to_proceed_with_identity_verification: 'Vervollständigen Sie alle erforderlichen Felder, um mit der Identitätsverifizierung fortzufahren.',
|
||||||
|
verify_id: 'ID überprüfen',
|
||||||
|
identity_verification: 'Identitätsüberprüfung',
|
||||||
complete: 'Abschließen',
|
complete: 'Abschließen',
|
||||||
|
fill_all_required_fields_to_complete: 'Alle erforderlichen Felder ausfüllen, um abzuschließen',
|
||||||
sign_and_complete: 'Signieren und Abschließen',
|
sign_and_complete: 'Signieren und Abschließen',
|
||||||
invite: 'Einladen',
|
invite: 'Einladen',
|
||||||
email: 'E-Mail',
|
email: 'E-Mail',
|
||||||
@@ -342,7 +364,7 @@ const de = {
|
|||||||
documents_have_been_signed: 'Dokumente wurden unterschrieben!',
|
documents_have_been_signed: 'Dokumente wurden unterschrieben!',
|
||||||
create_a_free_account: 'Kostenloses Konto erstellen',
|
create_a_free_account: 'Kostenloses Konto erstellen',
|
||||||
powered_by: 'Bereitgestellt von',
|
powered_by: 'Bereitgestellt von',
|
||||||
please_check_the_box_to_continue: 'Bitte setzen Sie das Häkchen, um fortzufahren',
|
please_check_the_box_to_continue: 'Bitte setzen Sie das Häkchen, um fortzufahren.',
|
||||||
open_source_documents_software: 'Open-Source-Dokumentensoftware',
|
open_source_documents_software: 'Open-Source-Dokumentensoftware',
|
||||||
verified_phone_number: 'Telefonnummer überprüfen',
|
verified_phone_number: 'Telefonnummer überprüfen',
|
||||||
use_international_format: 'Internationales Format verwenden: +1xxx',
|
use_international_format: 'Internationales Format verwenden: +1xxx',
|
||||||
@@ -364,11 +386,17 @@ const de = {
|
|||||||
pay_with_strip: 'Mit Stripe bezahlen',
|
pay_with_strip: 'Mit Stripe bezahlen',
|
||||||
reupload: 'Erneut hochladen',
|
reupload: 'Erneut hochladen',
|
||||||
upload: 'Hochladen',
|
upload: 'Hochladen',
|
||||||
files: 'Dateien'
|
files: 'Dateien',
|
||||||
|
signature_is_too_small_please_redraw: 'Die Unterschrift ist zu klein. Bitte erneut zeichnen.',
|
||||||
|
wait_countdown_seconds: 'Warte {countdown} Sekunden'
|
||||||
}
|
}
|
||||||
|
|
||||||
const fr = {
|
const fr = {
|
||||||
|
complete_all_required_fields_to_proceed_with_identity_verification: "Veuillez remplir tous les champs obligatoires pour continuer la vérification de l'identité.",
|
||||||
|
verif_id: "Vérification de l'ID",
|
||||||
|
verif_identite: "Vérification de l'identité",
|
||||||
complete: 'Terminer',
|
complete: 'Terminer',
|
||||||
|
fill_all_required_fields_to_complete: 'Veuillez remplir tous les champs obligatoires pour compléter',
|
||||||
sign_and_complete: 'Signer et Terminer',
|
sign_and_complete: 'Signer et Terminer',
|
||||||
invite: 'Inviter',
|
invite: 'Inviter',
|
||||||
email: 'Courriel',
|
email: 'Courriel',
|
||||||
@@ -434,7 +462,7 @@ const fr = {
|
|||||||
documents_have_been_signed: 'Les documents ont été signés!',
|
documents_have_been_signed: 'Les documents ont été signés!',
|
||||||
create_a_free_account: 'Créer un Compte Gratuit',
|
create_a_free_account: 'Créer un Compte Gratuit',
|
||||||
powered_by: 'Propulsé par',
|
powered_by: 'Propulsé par',
|
||||||
please_check_the_box_to_continue: 'Veuillez cocher la case pour continuer',
|
please_check_the_box_to_continue: 'Veuillez cocher la case pour continuer.',
|
||||||
open_source_documents_software: 'logiciel de documents open source',
|
open_source_documents_software: 'logiciel de documents open source',
|
||||||
verified_phone_number: 'Vérifier le numéro de téléphone',
|
verified_phone_number: 'Vérifier le numéro de téléphone',
|
||||||
use_international_format: 'Utiliser le format international : +1xxx',
|
use_international_format: 'Utiliser le format international : +1xxx',
|
||||||
@@ -456,11 +484,17 @@ const fr = {
|
|||||||
pay_with_strip: 'Paiement avec Stripe',
|
pay_with_strip: 'Paiement avec Stripe',
|
||||||
reupload: 'Recharger',
|
reupload: 'Recharger',
|
||||||
upload: 'Télécharger',
|
upload: 'Télécharger',
|
||||||
files: 'Fichiers'
|
files: 'Fichiers',
|
||||||
|
signature_is_too_small_please_redraw: 'La signature est trop petite. Veuillez la redessiner.',
|
||||||
|
wait_countdown_seconds: 'Attendez {countdown} secondes'
|
||||||
}
|
}
|
||||||
|
|
||||||
const pl = {
|
const pl = {
|
||||||
|
complete_all_required_fields_to_proceed_with_identity_verification: 'Uzupełnij wszystkie wymagane pola, aby kontynuować weryfikację tożsamości.',
|
||||||
|
verify_id: 'Zweryfikuj ID',
|
||||||
|
identity_verification: 'Weryfikacja tożsamości',
|
||||||
complete: 'Zakończ',
|
complete: 'Zakończ',
|
||||||
|
fill_all_required_fields_to_complete: 'Uzupełnij wszystkie wymagane pola, aby zakończyć',
|
||||||
sign_and_complete: 'Podpisz i zakończ',
|
sign_and_complete: 'Podpisz i zakończ',
|
||||||
invite: 'Zaproś',
|
invite: 'Zaproś',
|
||||||
email: 'E-mail',
|
email: 'E-mail',
|
||||||
@@ -526,7 +560,7 @@ const pl = {
|
|||||||
documents_have_been_signed: 'Dokumenty zostały podpisane!',
|
documents_have_been_signed: 'Dokumenty zostały podpisane!',
|
||||||
create_a_free_account: 'Utwórz darmowe konto',
|
create_a_free_account: 'Utwórz darmowe konto',
|
||||||
powered_by: 'Napędzany przez',
|
powered_by: 'Napędzany przez',
|
||||||
please_check_the_box_to_continue: 'Proszę zaznaczyć pole, aby kontynuować',
|
please_check_the_box_to_continue: 'Proszę zaznaczyć pole, aby kontynuować.',
|
||||||
open_source_documents_software: 'oprogramowanie do dokumentów open source',
|
open_source_documents_software: 'oprogramowanie do dokumentów open source',
|
||||||
verified_phone_number: 'Zweryfikuj numer telefonu',
|
verified_phone_number: 'Zweryfikuj numer telefonu',
|
||||||
use_international_format: 'Użyj międzynarodowego formatu: +1xxx',
|
use_international_format: 'Użyj międzynarodowego formatu: +1xxx',
|
||||||
@@ -548,11 +582,16 @@ const pl = {
|
|||||||
pay_with_strip: 'Płatność za pomocą Stripe',
|
pay_with_strip: 'Płatność za pomocą Stripe',
|
||||||
reupload: 'Ponowne przesłanie',
|
reupload: 'Ponowne przesłanie',
|
||||||
upload: 'Przesyłanie',
|
upload: 'Przesyłanie',
|
||||||
files: 'Pliki'
|
files: 'Pliki',
|
||||||
|
signature_is_too_small_please_redraw: 'Podpis jest zbyt mały. Proszę narysować go ponownie.'
|
||||||
}
|
}
|
||||||
|
|
||||||
const uk = {
|
const uk = {
|
||||||
|
complete_all_required_fields_to_proceed_with_identity_verification: "Заповніть всі обов'язкові поля, щоб продовжити перевірку особи.",
|
||||||
|
verify_id: 'Підтвердження ідентичності',
|
||||||
|
identity_verification: 'Ідентифікація особи',
|
||||||
complete: 'Завершити',
|
complete: 'Завершити',
|
||||||
|
fill_all_required_fields_to_complete: "Заповніть всі обов'язкові поля для завершення",
|
||||||
sign_and_complete: 'Підписати і завершити',
|
sign_and_complete: 'Підписати і завершити',
|
||||||
invite: 'Запросити',
|
invite: 'Запросити',
|
||||||
email: 'Електронна пошта',
|
email: 'Електронна пошта',
|
||||||
@@ -618,7 +657,7 @@ const uk = {
|
|||||||
documents_have_been_signed: 'Документи були підписані!',
|
documents_have_been_signed: 'Документи були підписані!',
|
||||||
create_a_free_account: 'Створити безкоштовний обліковий запис',
|
create_a_free_account: 'Створити безкоштовний обліковий запис',
|
||||||
powered_by: 'Працює на базі',
|
powered_by: 'Працює на базі',
|
||||||
please_check_the_box_to_continue: 'Будь ласка, позначте прапорець, щоб продовжити',
|
please_check_the_box_to_continue: 'Будь ласка, позначте прапорець, щоб продовжити.',
|
||||||
open_source_documents_software: 'відкритий програмний засіб для документів',
|
open_source_documents_software: 'відкритий програмний засіб для документів',
|
||||||
verified_phone_number: 'Підтвердіть номер телефону',
|
verified_phone_number: 'Підтвердіть номер телефону',
|
||||||
use_international_format: 'Використовуйте міжнародний формат: +1xxx',
|
use_international_format: 'Використовуйте міжнародний формат: +1xxx',
|
||||||
@@ -640,11 +679,17 @@ const uk = {
|
|||||||
pay_with_strip: 'Сплатити за допомогою Stripe',
|
pay_with_strip: 'Сплатити за допомогою Stripe',
|
||||||
reupload: 'Перезавантажити',
|
reupload: 'Перезавантажити',
|
||||||
upload: 'Завантажити',
|
upload: 'Завантажити',
|
||||||
files: 'Файли'
|
files: 'Файли',
|
||||||
|
signature_is_too_small_please_redraw: 'Підпис занадто малий. Будь ласка, перемалюйте його.',
|
||||||
|
wait_countdown_seconds: 'Зачекайте {countdown} секунд'
|
||||||
}
|
}
|
||||||
|
|
||||||
const cs = {
|
const cs = {
|
||||||
|
complete_all_required_fields_to_proceed_with_identity_verification: 'Vyplňte všechna povinná pole, abyste mohli pokračovat v ověření identity.',
|
||||||
|
verify_id: 'Ověřit ID',
|
||||||
|
identity_verification: 'Ověření identity',
|
||||||
complete: 'Dokončit',
|
complete: 'Dokončit',
|
||||||
|
fill_all_required_fields_to_complete: 'Please complete all mandatory fields',
|
||||||
sign_and_complete: 'Podepsat a dokončit',
|
sign_and_complete: 'Podepsat a dokončit',
|
||||||
invite: 'Pozvat',
|
invite: 'Pozvat',
|
||||||
email: 'E-mail',
|
email: 'E-mail',
|
||||||
@@ -710,7 +755,7 @@ const cs = {
|
|||||||
documents_have_been_signed: 'Dokumenty byly podepsány!',
|
documents_have_been_signed: 'Dokumenty byly podepsány!',
|
||||||
create_a_free_account: 'Vytvořit bezplatný účet',
|
create_a_free_account: 'Vytvořit bezplatný účet',
|
||||||
powered_by: 'Poháněno',
|
powered_by: 'Poháněno',
|
||||||
please_check_the_box_to_continue: 'Prosím, zaškrtněte políčko pro pokračování',
|
please_check_the_box_to_continue: 'Prosím, zaškrtněte políčko pro pokračování.',
|
||||||
open_source_documents_software: 'open source software pro dokumenty',
|
open_source_documents_software: 'open source software pro dokumenty',
|
||||||
verified_phone_number: 'Ověřte telefonní číslo',
|
verified_phone_number: 'Ověřte telefonní číslo',
|
||||||
use_international_format: 'Použijte mezinárodní formát: +1xxx',
|
use_international_format: 'Použijte mezinárodní formát: +1xxx',
|
||||||
@@ -732,11 +777,17 @@ const cs = {
|
|||||||
pay_with_strip: 'Zaplacení přes Stripe',
|
pay_with_strip: 'Zaplacení přes Stripe',
|
||||||
reupload: 'Znovu nahrát',
|
reupload: 'Znovu nahrát',
|
||||||
upload: 'Nahrát',
|
upload: 'Nahrát',
|
||||||
files: 'Soubory'
|
files: 'Soubory',
|
||||||
|
signature_is_too_small_please_redraw: 'Podpis je příliš malý. Prosím, překreslete ho.',
|
||||||
|
wait_countdown_seconds: 'Počkejte {countdown} sekund'
|
||||||
}
|
}
|
||||||
|
|
||||||
const pt = {
|
const pt = {
|
||||||
|
complete_all_required_fields_to_proceed_with_identity_verification: 'Preencha todos os campos obrigatórios para prosseguir com a verificação de identidade.',
|
||||||
|
verify_id: 'Verificar ID',
|
||||||
|
identity_verification: 'Verificação de identidade',
|
||||||
complete: 'Completar',
|
complete: 'Completar',
|
||||||
|
fill_all_required_fields_to_complete: 'Preencher todos os campos obrigatórios para concluir',
|
||||||
sign_and_complete: 'Assinar e Completar',
|
sign_and_complete: 'Assinar e Completar',
|
||||||
invite: 'Convidar',
|
invite: 'Convidar',
|
||||||
email: 'E-mail',
|
email: 'E-mail',
|
||||||
@@ -802,7 +853,7 @@ const pt = {
|
|||||||
documents_have_been_signed: 'Os documentos foram assinados!',
|
documents_have_been_signed: 'Os documentos foram assinados!',
|
||||||
create_a_free_account: 'Criar uma Conta Gratuita',
|
create_a_free_account: 'Criar uma Conta Gratuita',
|
||||||
powered_by: 'Desenvolvido por',
|
powered_by: 'Desenvolvido por',
|
||||||
please_check_the_box_to_continue: 'Por favor, marque a caixa para continuar',
|
please_check_the_box_to_continue: 'Por favor, marque a caixa para continuar.',
|
||||||
open_source_documents_software: 'software de documentos de código aberto',
|
open_source_documents_software: 'software de documentos de código aberto',
|
||||||
verified_phone_number: 'Verificar Número de Telefone',
|
verified_phone_number: 'Verificar Número de Telefone',
|
||||||
use_international_format: 'Use formato internacional: +1xxx',
|
use_international_format: 'Use formato internacional: +1xxx',
|
||||||
@@ -824,11 +875,17 @@ const pt = {
|
|||||||
pay_with_strip: 'Pagar com Stripe',
|
pay_with_strip: 'Pagar com Stripe',
|
||||||
reupload: 'Reenviar',
|
reupload: 'Reenviar',
|
||||||
upload: 'Carregar',
|
upload: 'Carregar',
|
||||||
files: 'Arquivos'
|
files: 'Arquivos',
|
||||||
|
signature_is_too_small_please_redraw: 'A assinatura é muito pequena. Por favor, redesenhe-a.',
|
||||||
|
wait_countdown_seconds: 'Aguarde {countdown} segundos'
|
||||||
}
|
}
|
||||||
|
|
||||||
const he = {
|
const he = {
|
||||||
|
complete_all_required_fields_to_proceed_with_identity_verification: 'מלא את כל השדות הנדרשים כדי להמשיך עם אימות זהות.',
|
||||||
|
verify_id: 'אמת מזהה',
|
||||||
|
identity_verification: 'אימות זהות',
|
||||||
complete: 'השלם',
|
complete: 'השלם',
|
||||||
|
fill_all_required_fields_to_complete: 'נא למלא את כל השדות הנדרשים להשלמה',
|
||||||
sign_and_complete: 'חתום והשלם',
|
sign_and_complete: 'חתום והשלם',
|
||||||
invite: 'הזמן',
|
invite: 'הזמן',
|
||||||
email: 'דוא"ל',
|
email: 'דוא"ל',
|
||||||
@@ -895,7 +952,7 @@ const he = {
|
|||||||
documents_have_been_signed: 'המסמכים נחתמו!',
|
documents_have_been_signed: 'המסמכים נחתמו!',
|
||||||
create_a_free_account: 'צור חשבון חינם',
|
create_a_free_account: 'צור חשבון חינם',
|
||||||
powered_by: 'מופעל על ידי',
|
powered_by: 'מופעל על ידי',
|
||||||
please_check_the_box_to_continue: 'אנא סמן את התיבה כדי להמשיך',
|
please_check_the_box_to_continue: 'אנא סמן את התיבה כדי להמשיך.',
|
||||||
open_source_documents_software: 'תוכנה פתוחה למסמכים',
|
open_source_documents_software: 'תוכנה פתוחה למסמכים',
|
||||||
verified_phone_number: 'אימות מספר טלפון',
|
verified_phone_number: 'אימות מספר טלפון',
|
||||||
use_international_format: 'השתמש בפורמט בינלאומי: +1xxx',
|
use_international_format: 'השתמש בפורמט בינלאומי: +1xxx',
|
||||||
@@ -917,11 +974,17 @@ const he = {
|
|||||||
pay_with_strip: 'שלם עם סטרייפ',
|
pay_with_strip: 'שלם עם סטרייפ',
|
||||||
reupload: 'העלה שוב',
|
reupload: 'העלה שוב',
|
||||||
upload: 'העלאה',
|
upload: 'העלאה',
|
||||||
files: 'קבצים'
|
files: 'קבצים',
|
||||||
|
signature_is_too_small_please_redraw: 'החתימה קטנה מדי. אנא צייר מחדש.',
|
||||||
|
wait_countdown_seconds: 'המתן {countdown} שניות'
|
||||||
}
|
}
|
||||||
|
|
||||||
const nl = {
|
const nl = {
|
||||||
|
complete_all_required_fields_to_proceed_with_identity_verification: 'Vul alle verplichte velden in om door te gaan met de identiteitsverificatie.',
|
||||||
|
verify_id: 'Verifiëren ID',
|
||||||
|
identity_verification: 'Identiteitsverificatie',
|
||||||
complete: 'Voltooien',
|
complete: 'Voltooien',
|
||||||
|
fill_all_required_fields_to_complete: 'Vul alle verplichte velden in om te voltooien',
|
||||||
sign_and_complete: 'Ondertekenen en voltooien',
|
sign_and_complete: 'Ondertekenen en voltooien',
|
||||||
invite: 'Uitnodigen',
|
invite: 'Uitnodigen',
|
||||||
email: 'E-mail',
|
email: 'E-mail',
|
||||||
@@ -988,7 +1051,7 @@ const nl = {
|
|||||||
documents_have_been_signed: 'De documenten zijn ondertekend!',
|
documents_have_been_signed: 'De documenten zijn ondertekend!',
|
||||||
create_a_free_account: 'Maak een gratis account aan',
|
create_a_free_account: 'Maak een gratis account aan',
|
||||||
powered_by: 'Aangedreven door',
|
powered_by: 'Aangedreven door',
|
||||||
please_check_the_box_to_continue: 'Vink het vakje aan om door te gaan',
|
please_check_the_box_to_continue: 'Vink het vakje aan om door te gaan.',
|
||||||
open_source_documents_software: 'Open source documenten software',
|
open_source_documents_software: 'Open source documenten software',
|
||||||
verified_phone_number: 'Geverifieerd telefoonnummer',
|
verified_phone_number: 'Geverifieerd telefoonnummer',
|
||||||
use_international_format: 'Gebruik internationaal formaat: +1xxx',
|
use_international_format: 'Gebruik internationaal formaat: +1xxx',
|
||||||
@@ -1010,11 +1073,17 @@ const nl = {
|
|||||||
pay_with_strip: 'Betalen met Stripe',
|
pay_with_strip: 'Betalen met Stripe',
|
||||||
reupload: 'Opnieuw uploaden',
|
reupload: 'Opnieuw uploaden',
|
||||||
upload: 'Uploaden',
|
upload: 'Uploaden',
|
||||||
files: 'Bestanden'
|
files: 'Bestanden',
|
||||||
|
signature_is_too_small_please_redraw: 'De handtekening is te klein. Teken deze opnieuw, alstublieft.',
|
||||||
|
wait_countdown_seconds: 'Wacht {countdown} seconden'
|
||||||
}
|
}
|
||||||
|
|
||||||
const ar = {
|
const ar = {
|
||||||
|
complete_all_required_fields_to_proceed_with_identity_verification: 'أكمل جميع الحقول المطلوبة للمتابعة في التحقق من الهوية.',
|
||||||
|
verify_id: 'تحقق من الهوية',
|
||||||
|
identity_verification: 'التحقق من الهوية',
|
||||||
complete: 'اكتمال',
|
complete: 'اكتمال',
|
||||||
|
fill_all_required_fields_to_complete: 'يرجى ملء جميع الحقول المطلوبة لإكمال',
|
||||||
sign_and_complete: 'التوقيع والاكتمال',
|
sign_and_complete: 'التوقيع والاكتمال',
|
||||||
invite: 'دعوة',
|
invite: 'دعوة',
|
||||||
email: 'البريد الإلكتروني',
|
email: 'البريد الإلكتروني',
|
||||||
@@ -1080,7 +1149,7 @@ const ar = {
|
|||||||
documents_have_been_signed: 'تم توقيع الوثائق!',
|
documents_have_been_signed: 'تم توقيع الوثائق!',
|
||||||
create_a_free_account: 'إنشاء حساب مجاني',
|
create_a_free_account: 'إنشاء حساب مجاني',
|
||||||
powered_by: 'مدعوم من',
|
powered_by: 'مدعوم من',
|
||||||
please_check_the_box_to_continue: 'الرجاء التحقق من الخانة للمتابعة',
|
please_check_the_box_to_continue: 'الرجاء التحقق من الخانة للمتابعة.',
|
||||||
open_source_documents_software: 'برنامج وثائق مفتوح المصدر',
|
open_source_documents_software: 'برنامج وثائق مفتوح المصدر',
|
||||||
verified_phone_number: 'تحقق من رقم الهاتف',
|
verified_phone_number: 'تحقق من رقم الهاتف',
|
||||||
use_international_format: 'استخدم الشكل الدولي: +1xxx',
|
use_international_format: 'استخدم الشكل الدولي: +1xxx',
|
||||||
@@ -1102,11 +1171,17 @@ const ar = {
|
|||||||
pay_with_strip: 'الدفع بواسطة Stripe',
|
pay_with_strip: 'الدفع بواسطة Stripe',
|
||||||
reupload: 'إعادة التحميل',
|
reupload: 'إعادة التحميل',
|
||||||
upload: 'تحميل',
|
upload: 'تحميل',
|
||||||
files: 'الملفات'
|
files: 'الملفات',
|
||||||
|
signature_is_too_small_please_redraw: 'التوقيع صغير جدًا. يرجى إعادة الرسم.',
|
||||||
|
wait_countdown_seconds: 'انتظر {countdown} ثانية'
|
||||||
}
|
}
|
||||||
|
|
||||||
const ko = {
|
const ko = {
|
||||||
|
complete_all_required_fields_to_proceed_with_identity_verification: '신원 확인을 진행하려면 모든 필수 필드를 작성하십시오.',
|
||||||
|
verify_id: '아이디 확인',
|
||||||
|
identity_verification: '신원 확인',
|
||||||
complete: '완료',
|
complete: '완료',
|
||||||
|
fill_all_required_fields_to_complete: '모든 필수 필드를 작성하여 완료하세요',
|
||||||
sign_and_complete: '서명하고 완료하기',
|
sign_and_complete: '서명하고 완료하기',
|
||||||
invite: '초대하기',
|
invite: '초대하기',
|
||||||
email: '이메일',
|
email: '이메일',
|
||||||
@@ -1171,7 +1246,7 @@ const ko = {
|
|||||||
documents_have_been_signed: '문서가 서명되었습니다!',
|
documents_have_been_signed: '문서가 서명되었습니다!',
|
||||||
create_a_free_account: '무료 계정 생성',
|
create_a_free_account: '무료 계정 생성',
|
||||||
powered_by: '구동',
|
powered_by: '구동',
|
||||||
please_check_the_box_to_continue: '계속하려면 확인란을 선택하십시오',
|
please_check_the_box_to_continue: '계속하려면 확인란을 선택하십시오.',
|
||||||
open_source_documents_software: '오픈 소스 문서 소프트웨어',
|
open_source_documents_software: '오픈 소스 문서 소프트웨어',
|
||||||
verified_phone_number: '전화번호 확인됨',
|
verified_phone_number: '전화번호 확인됨',
|
||||||
use_international_format: '국제 포맷 사용: +1xxx',
|
use_international_format: '국제 포맷 사용: +1xxx',
|
||||||
@@ -1193,7 +1268,9 @@ const ko = {
|
|||||||
pay_with_strip: '스트라이프로 결제',
|
pay_with_strip: '스트라이프로 결제',
|
||||||
reupload: '다시 업로드',
|
reupload: '다시 업로드',
|
||||||
upload: '업로드',
|
upload: '업로드',
|
||||||
files: '파일'
|
files: '파일',
|
||||||
|
signature_is_too_small_please_redraw: '서명이 너무 작습니다. 다시 그려주세요.',
|
||||||
|
wait_countdown_seconds: '{countdown}초 기다리세요'
|
||||||
}
|
}
|
||||||
|
|
||||||
const i18n = { en, es, it, de, fr, pl, uk, cs, pt, he, nl, ar, ko }
|
const i18n = { en, es, it, de, fr, pl, uk, cs, pt, he, nl, ar, ko }
|
||||||
|
|||||||
@@ -226,6 +226,10 @@ export default {
|
|||||||
if (this.$refs.canvas) {
|
if (this.$refs.canvas) {
|
||||||
this.pad = new SignaturePad(this.$refs.canvas)
|
this.pad = new SignaturePad(this.$refs.canvas)
|
||||||
|
|
||||||
|
if (this.field.preferences?.color) {
|
||||||
|
this.pad.penColor = this.field.preferences.color
|
||||||
|
}
|
||||||
|
|
||||||
this.pad.addEventListener('beginStroke', () => {
|
this.pad.addEventListener('beginStroke', () => {
|
||||||
this.isInitialsStarted = true
|
this.isInitialsStarted = true
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
:value="authenticityToken"
|
:value="authenticityToken"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-for="(submitter, index) in submitters"
|
v-for="(submitter, index) in [...submitters, ...optionalSubmitters]"
|
||||||
:key="submitter.uuid"
|
:key="submitter.uuid"
|
||||||
:class="{ 'mt-4': index !== 0 }"
|
:class="{ 'mt-4': index !== 0 }"
|
||||||
>
|
>
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
dir="auto"
|
dir="auto"
|
||||||
class="label text-2xl"
|
class="label text-2xl"
|
||||||
>
|
>
|
||||||
{{ t('invite') }} {{ submitter.name }}
|
{{ t('invite') }} {{ submitter.name }} <template v-if="!submitters.includes(submitter)">({{ t('optional') }})</template>
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
:id="submitter.uuid"
|
:id="submitter.uuid"
|
||||||
@@ -34,7 +34,7 @@
|
|||||||
class="base-input !text-2xl w-full"
|
class="base-input !text-2xl w-full"
|
||||||
:placeholder="t('email')"
|
:placeholder="t('email')"
|
||||||
type="email"
|
type="email"
|
||||||
required
|
:required="submitters.includes(submitter)"
|
||||||
autofocus="true"
|
autofocus="true"
|
||||||
name="submission[submitters][][email]"
|
name="submission[submitters][][email]"
|
||||||
>
|
>
|
||||||
@@ -53,7 +53,7 @@
|
|||||||
class="mr-1 animate-spin"
|
class="mr-1 animate-spin"
|
||||||
/>
|
/>
|
||||||
<span>
|
<span>
|
||||||
{{ t('submit') }}
|
{{ t('complete') }}
|
||||||
</span><span
|
</span><span
|
||||||
v-if="isSubmitting"
|
v-if="isSubmitting"
|
||||||
class="w-6 flex justify-start mr-1"
|
class="w-6 flex justify-start mr-1"
|
||||||
@@ -78,6 +78,11 @@ export default {
|
|||||||
type: Array,
|
type: Array,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
optionalSubmitters: {
|
||||||
|
type: Array,
|
||||||
|
required: false,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
url: {
|
url: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<label
|
<label
|
||||||
v-if="!modelValue && !sessionId"
|
v-if="!modelValue && !sessionId"
|
||||||
:for="field.uuid"
|
|
||||||
class="label text-2xl mb-2"
|
class="label text-2xl mb-2"
|
||||||
>
|
>
|
||||||
<MarkdownContent
|
<MarkdownContent
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
@@ -52,7 +52,14 @@
|
|||||||
>
|
>
|
||||||
{{ t('change_phone_number') }}
|
{{ t('change_phone_number') }}
|
||||||
</a>
|
</a>
|
||||||
|
<span
|
||||||
|
v-if="resendCodeCountdown > 0"
|
||||||
|
class="link"
|
||||||
|
>
|
||||||
|
{{ t('wait_countdown_seconds').replace('{countdown}', resendCodeCountdown) }}
|
||||||
|
</span>
|
||||||
<a
|
<a
|
||||||
|
v-else
|
||||||
href="#"
|
href="#"
|
||||||
class="link"
|
class="link"
|
||||||
@click.prevent="resendCode"
|
@click.prevent="resendCode"
|
||||||
@@ -65,11 +72,15 @@
|
|||||||
v-show="!isCodeSent"
|
v-show="!isCodeSent"
|
||||||
class="flex w-full rounded-full outline-neutral-content outline-2 outline-offset-2 focus-within:outline"
|
class="flex w-full rounded-full outline-neutral-content outline-2 outline-offset-2 focus-within:outline"
|
||||||
>
|
>
|
||||||
<div class="relative inline-block">
|
<div
|
||||||
|
id="country_code"
|
||||||
|
class="relative inline-block"
|
||||||
|
>
|
||||||
<div class="btn bg-base-200 border border-neutral-300 text-2xl whitespace-nowrap font-normal rounded-l-full">
|
<div class="btn bg-base-200 border border-neutral-300 text-2xl whitespace-nowrap font-normal rounded-l-full">
|
||||||
{{ selectedCountry.flag }} +{{ selectedCountry.dial }}
|
{{ selectedCountry.flag }} +{{ selectedCountry.dial }}
|
||||||
</div>
|
</div>
|
||||||
<select
|
<select
|
||||||
|
id="country_code_select"
|
||||||
class="absolute top-0 bottom-0 right-0 left-0 opacity-0 w-full h-full cursor-pointer"
|
class="absolute top-0 bottom-0 right-0 left-0 opacity-0 w-full h-full cursor-pointer"
|
||||||
:disabled="!!defaultValue"
|
:disabled="!!defaultValue"
|
||||||
@change="onCountrySelect(countries.find((country) => country.flag === $event.target.value))"
|
@change="onCountrySelect(countries.find((country) => country.flag === $event.target.value))"
|
||||||
@@ -169,6 +180,8 @@ export default {
|
|||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
isCodeSent: false,
|
isCodeSent: false,
|
||||||
|
codeSentAt: null,
|
||||||
|
resendCodeCountdown: 0,
|
||||||
isResendLoading: false,
|
isResendLoading: false,
|
||||||
phoneValue: this.modelValue || this.defaultValue || '',
|
phoneValue: this.modelValue || this.defaultValue || '',
|
||||||
selectedCountry: {}
|
selectedCountry: {}
|
||||||
@@ -176,8 +189,8 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
countries () {
|
countries () {
|
||||||
return phoneData.map(([name, dial, flag, tz]) => {
|
return phoneData.map(([iso, name, dial, flag, tz]) => {
|
||||||
return { name, dial, flag, tz }
|
return { iso, name, dial, flag, tz }
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
countriesDialIndex () {
|
countriesDialIndex () {
|
||||||
@@ -216,6 +229,11 @@ export default {
|
|||||||
this.selectedCountry = this.countries.find((country) => country.tz.includes(tz)) || this.countries[0]
|
this.selectedCountry = this.countries.find((country) => country.tz.includes(tz)) || this.countries[0]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
beforeUnmount () {
|
||||||
|
if (this.interval) {
|
||||||
|
clearInterval(this.interval)
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
emitSubmit: throttle(function (e) {
|
emitSubmit: throttle(function (e) {
|
||||||
this.$emit('submit')
|
this.$emit('submit')
|
||||||
@@ -242,13 +260,28 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
resendCode () {
|
resendCode () {
|
||||||
this.isResendLoading = true
|
if (this.codeSentAt && Date.now() - this.codeSentAt < 15000) {
|
||||||
|
this.startResendCodeCountdown()
|
||||||
|
} else {
|
||||||
|
this.isResendLoading = true
|
||||||
|
|
||||||
this.sendVerificationCode().finally(() => {
|
this.sendVerificationCode().then(() => {
|
||||||
alert(this.t('verification_code_has_been_resent'))
|
alert(this.t('verification_code_has_been_resent'))
|
||||||
|
}).finally(() => {
|
||||||
|
this.isResendLoading = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
startResendCodeCountdown () {
|
||||||
|
this.resendCodeCountdown = 15 - parseInt((Date.now() - this.codeSentAt) / 1000)
|
||||||
|
|
||||||
this.isResendLoading = false
|
this.interval = setInterval(() => {
|
||||||
})
|
this.resendCodeCountdown--
|
||||||
|
|
||||||
|
if (this.resendCodeCountdown <= 0) {
|
||||||
|
clearInterval(this.interval)
|
||||||
|
}
|
||||||
|
}, 1000)
|
||||||
},
|
},
|
||||||
sendVerificationCode () {
|
sendVerificationCode () {
|
||||||
return fetch(this.baseUrl + '/api/send_phone_verification_code', {
|
return fetch(this.baseUrl + '/api/send_phone_verification_code', {
|
||||||
@@ -260,12 +293,20 @@ export default {
|
|||||||
}),
|
}),
|
||||||
headers: { 'Content-Type': 'application/json' }
|
headers: { 'Content-Type': 'application/json' }
|
||||||
}).then(async (resp) => {
|
}).then(async (resp) => {
|
||||||
if (resp.status === 422) {
|
if ([422, 429].includes(resp.status)) {
|
||||||
const data = await resp.json()
|
const data = await resp.json()
|
||||||
|
|
||||||
alert(this.t('number_phone_is_invalid').replace('{number}', this.fullInternationalPhoneValue))
|
if (resp.status === 422) {
|
||||||
|
alert(this.t('number_phone_is_invalid').replace('{number}', this.fullInternationalPhoneValue))
|
||||||
|
} else if (resp.status === 429) {
|
||||||
|
alert(data.error)
|
||||||
|
}
|
||||||
|
|
||||||
return Promise.reject(new Error(data.error))
|
return Promise.reject(new Error(data.error))
|
||||||
|
} else if (resp.ok) {
|
||||||
|
this.codeSentAt = Date.now()
|
||||||
|
|
||||||
|
return resp
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
</label>
|
</label>
|
||||||
<div class="space-x-2 flex">
|
<div class="space-x-2 flex">
|
||||||
<span
|
<span
|
||||||
v-if="isTextSignature && field.preferences?.format !== 'typed'"
|
v-if="isTextSignature && format !== 'typed' && format !== 'upload'"
|
||||||
class="tooltip"
|
class="tooltip"
|
||||||
:data-tip="t('draw_signature')"
|
:data-tip="t('draw_signature')"
|
||||||
>
|
>
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
v-else-if="withTypedSignature && field.preferences?.format !== 'typed' && field.preferences?.format !== 'drawn'"
|
v-else-if="withTypedSignature && format !== 'typed' && format !== 'drawn' && format !== 'upload'"
|
||||||
class="tooltip ml-2"
|
class="tooltip ml-2"
|
||||||
:data-tip="t('type_text')"
|
:data-tip="t('type_text')"
|
||||||
>
|
>
|
||||||
@@ -54,7 +54,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
v-if="field.preferences?.format !== 'typed' && field.preferences?.format !== 'drawn'"
|
v-if="format !== 'typed' && format !== 'drawn' && format !== 'upload' && format !== 'drawn_or_typed'"
|
||||||
class="tooltip"
|
class="tooltip"
|
||||||
:data-tip="t('take_photo')"
|
:data-tip="t('take_photo')"
|
||||||
>
|
>
|
||||||
@@ -81,10 +81,10 @@
|
|||||||
@click.prevent="remove"
|
@click.prevent="remove"
|
||||||
>
|
>
|
||||||
<IconReload :width="16" />
|
<IconReload :width="16" />
|
||||||
{{ t('redraw') }}
|
{{ t(format === 'upload' ? 'reupload' : 'redraw') }}
|
||||||
</a>
|
</a>
|
||||||
<span
|
<span
|
||||||
v-if="withQrButton && !modelValue && !computedPreviousValue && field.preferences?.format !== 'typed'"
|
v-if="withQrButton && !modelValue && !computedPreviousValue && format !== 'typed' && format !== 'upload'"
|
||||||
class=" tooltip"
|
class=" tooltip"
|
||||||
:data-tip="t('drawn_signature_on_a_touchscreen_device')"
|
:data-tip="t('drawn_signature_on_a_touchscreen_device')"
|
||||||
>
|
>
|
||||||
@@ -131,7 +131,18 @@
|
|||||||
:src="attachmentsIndex[modelValue || computedPreviousValue].url"
|
:src="attachmentsIndex[modelValue || computedPreviousValue].url"
|
||||||
class="mx-auto bg-white border border-base-300 rounded max-h-44"
|
class="mx-auto bg-white border border-base-300 rounded max-h-44"
|
||||||
>
|
>
|
||||||
<div class="relative">
|
<FileDropzone
|
||||||
|
v-if="format === 'upload' && !modelValue"
|
||||||
|
:message="`${t('upload')} ${field.name || t('signature')}`"
|
||||||
|
:submitter-slug="submitterSlug"
|
||||||
|
:dry-run="dryRun"
|
||||||
|
:accept="'image/*'"
|
||||||
|
@upload="[$emit('attached', $event[0]), $emit('update:model-value', $event[0].uuid)]"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
class="relative"
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
v-if="!modelValue && !computedPreviousValue && !isShowQr && !isTextSignature && isSignatureStarted"
|
v-if="!modelValue && !computedPreviousValue && !isShowQr && !isTextSignature && isSignatureStarted"
|
||||||
class="absolute top-0.5 right-0.5"
|
class="absolute top-0.5 right-0.5"
|
||||||
@@ -145,6 +156,10 @@
|
|||||||
{{ t('clear') }}
|
{{ t('clear') }}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="isTextSignature"
|
||||||
|
class="absolute top-0 right-0 left-0 bottom-0"
|
||||||
|
/>
|
||||||
<canvas
|
<canvas
|
||||||
v-show="!modelValue && !computedPreviousValue"
|
v-show="!modelValue && !computedPreviousValue"
|
||||||
ref="canvas"
|
ref="canvas"
|
||||||
@@ -255,7 +270,7 @@
|
|||||||
class="text-base-content/60 text-xs text-center w-full mt-1"
|
class="text-base-content/60 text-xs text-center w-full mt-1"
|
||||||
>
|
>
|
||||||
{{ t('by_clicking_you_agree_to_the').replace('{button}', buttonText.charAt(0).toUpperCase() + buttonText.slice(1)) }} <a
|
{{ t('by_clicking_you_agree_to_the').replace('{button}', buttonText.charAt(0).toUpperCase() + buttonText.slice(1)) }} <a
|
||||||
href="https://www.docuseal.co/esign-disclosure"
|
href="https://www.docuseal.com/esign-disclosure"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
<span class="inline md:hidden">
|
<span class="inline md:hidden">
|
||||||
@@ -278,6 +293,7 @@ import { IconReload, IconCamera, IconSignature, IconTextSize, IconArrowsDiagonal
|
|||||||
import { cropCanvasAndExportToPNG } from './crop_canvas'
|
import { cropCanvasAndExportToPNG } from './crop_canvas'
|
||||||
import SignaturePad from 'signature_pad'
|
import SignaturePad from 'signature_pad'
|
||||||
import AppearsOn from './appears_on'
|
import AppearsOn from './appears_on'
|
||||||
|
import FileDropzone from './dropzone'
|
||||||
import MarkdownContent from './markdown_content'
|
import MarkdownContent from './markdown_content'
|
||||||
import { v4 } from 'uuid'
|
import { v4 } from 'uuid'
|
||||||
|
|
||||||
@@ -290,6 +306,7 @@ export default {
|
|||||||
components: {
|
components: {
|
||||||
AppearsOn,
|
AppearsOn,
|
||||||
IconReload,
|
IconReload,
|
||||||
|
FileDropzone,
|
||||||
IconCamera,
|
IconCamera,
|
||||||
IconQrcode,
|
IconQrcode,
|
||||||
MarkdownContent,
|
MarkdownContent,
|
||||||
@@ -384,6 +401,9 @@ export default {
|
|||||||
submitterSlug () {
|
submitterSlug () {
|
||||||
return this.submitter.slug
|
return this.submitter.slug
|
||||||
},
|
},
|
||||||
|
format () {
|
||||||
|
return this.field.preferences?.format
|
||||||
|
},
|
||||||
defaultReasons () {
|
defaultReasons () {
|
||||||
return {
|
return {
|
||||||
[this.t('approved_by')]: this.t('approved'),
|
[this.t('approved_by')]: this.t('approved'),
|
||||||
@@ -419,6 +439,10 @@ export default {
|
|||||||
if (this.$refs.canvas) {
|
if (this.$refs.canvas) {
|
||||||
this.pad = new SignaturePad(this.$refs.canvas)
|
this.pad = new SignaturePad(this.$refs.canvas)
|
||||||
|
|
||||||
|
if (this.field.preferences?.color) {
|
||||||
|
this.pad.penColor = this.field.preferences.color
|
||||||
|
}
|
||||||
|
|
||||||
this.pad.addEventListener('endStroke', () => {
|
this.pad.addEventListener('endStroke', () => {
|
||||||
this.isSignatureStarted = true
|
this.isSignatureStarted = true
|
||||||
|
|
||||||
|
|||||||
@@ -114,7 +114,9 @@ export default {
|
|||||||
const area = this.field.areas?.[0]
|
const area = this.field.areas?.[0]
|
||||||
|
|
||||||
if (area) {
|
if (area) {
|
||||||
return parseInt(area.w / area.cell_w) + 1
|
const num = area.w / area.cell_w
|
||||||
|
|
||||||
|
return (num % 1) > 0.2 ? parseInt(num) + 1 : parseInt(num)
|
||||||
} else {
|
} else {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,169 @@
|
|||||||
|
<template>
|
||||||
|
<label
|
||||||
|
class="label text-2xl mb-2"
|
||||||
|
>
|
||||||
|
<MarkdownContent
|
||||||
|
v-if="field.title"
|
||||||
|
:string="field.title"
|
||||||
|
/>
|
||||||
|
<template v-else>{{ field.name || t('identity_verification') }}</template>
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
v-if="field.description"
|
||||||
|
dir="auto"
|
||||||
|
class="mb-4 px-1"
|
||||||
|
>
|
||||||
|
<MarkdownContent :string="field.description" />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="emptyValueRequiredStep && emptyValueRequiredStep[0] !== field"
|
||||||
|
class="px-1"
|
||||||
|
>
|
||||||
|
{{ t('complete_all_required_fields_to_proceed_with_identity_verification') }}
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<div
|
||||||
|
v-if="isLoading"
|
||||||
|
class="w-full flex space-x-2 justify-center mb-2"
|
||||||
|
>
|
||||||
|
<IconInnerShadowTop
|
||||||
|
width="40"
|
||||||
|
class="animate-spin h-10"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
ref="widgetContainer"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import MarkdownContent from './markdown_content'
|
||||||
|
import { IconInnerShadowTop } from '@tabler/icons-vue'
|
||||||
|
import phoneData from './phone_data'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'VerificationStep',
|
||||||
|
components: {
|
||||||
|
MarkdownContent,
|
||||||
|
IconInnerShadowTop
|
||||||
|
},
|
||||||
|
inject: ['baseUrl', 't'],
|
||||||
|
props: {
|
||||||
|
modelValue: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
submitter: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
field: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
emptyValueRequiredStep: {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
values: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
locale: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: 'en'
|
||||||
|
},
|
||||||
|
submitterSlug: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emits: ['focus', 'submit', 'update:model-value', 'attached'],
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
isCreatingCheckout: false,
|
||||||
|
isMathLoaded: false,
|
||||||
|
isLoading: false,
|
||||||
|
eidEasyData: {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
countryCode () {
|
||||||
|
const browserTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone
|
||||||
|
const browserTz = browserTimeZone.split('/')[1]
|
||||||
|
const country = phoneData.find(([a, b, c, e, tz]) => tz.includes(browserTz))
|
||||||
|
|
||||||
|
return country[0]
|
||||||
|
},
|
||||||
|
browserCountry () {
|
||||||
|
return (navigator.language || navigator.userLanguage || 'en').split('-')[1]
|
||||||
|
},
|
||||||
|
widgetSettings () {
|
||||||
|
return {
|
||||||
|
clientId: this.eidEasyData.client_id,
|
||||||
|
docId: this.eidEasyData.doc_id,
|
||||||
|
language: this.locale,
|
||||||
|
countryCode: this.countryCode,
|
||||||
|
enabledMethods: {
|
||||||
|
signature: this.eidEasyData.available_methods
|
||||||
|
},
|
||||||
|
selectedMethod: null,
|
||||||
|
enabledCountries: 'all',
|
||||||
|
onSuccess: (data) => {
|
||||||
|
this.$emit('submit')
|
||||||
|
},
|
||||||
|
onFail: (error) => {
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async mounted () {
|
||||||
|
this.isLoading = true
|
||||||
|
|
||||||
|
Promise.all([
|
||||||
|
import('@eid-easy/eideasy-widget'),
|
||||||
|
this.start()
|
||||||
|
]).finally(() => {
|
||||||
|
this.isLoading = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
start () {
|
||||||
|
return fetch(this.baseUrl + `/api/identity_verification/${this.field.uuid}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
body: JSON.stringify({
|
||||||
|
submitter_slug: this.submitterSlug
|
||||||
|
}),
|
||||||
|
headers: { 'Content-Type': 'application/json' }
|
||||||
|
}).then(async (resp) => {
|
||||||
|
this.eidEasyData = await resp.json()
|
||||||
|
|
||||||
|
const eidEasyWidget = document.createElement('eideasy-widget')
|
||||||
|
|
||||||
|
for (const key in this.widgetSettings) {
|
||||||
|
eidEasyWidget[key] = this.widgetSettings[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$refs.widgetContainer.innerHTML = ''
|
||||||
|
this.$refs.widgetContainer.appendChild(eidEasyWidget)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
async submit () {
|
||||||
|
return fetch(this.baseUrl + '/api/identity_verification', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
submitter_slug: this.submitterSlug
|
||||||
|
}),
|
||||||
|
headers: { 'Content-Type': 'application/json' }
|
||||||
|
}).then(async (resp) => {
|
||||||
|
return resp
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -175,6 +175,7 @@
|
|||||||
<div
|
<div
|
||||||
ref="textContainer"
|
ref="textContainer"
|
||||||
class="flex items-center px-0.5"
|
class="flex items-center px-0.5"
|
||||||
|
:style="{ color: field.preferences?.color }"
|
||||||
:class="{ 'w-full h-full': ['cells', 'checkbox'].includes(field.type) }"
|
:class="{ 'w-full h-full': ['cells', 'checkbox'].includes(field.type) }"
|
||||||
>
|
>
|
||||||
<IconCheck
|
<IconCheck
|
||||||
@@ -236,7 +237,7 @@
|
|||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
v-if="field?.type && editable"
|
v-if="field?.type && editable"
|
||||||
class="h-4 w-4 md:h-2.5 md:w-2.5 -right-1 rounded-full -bottom-1 border-gray-400 bg-white shadow-md border absolute cursor-nwse-resize"
|
class="h-4 w-4 lg:h-2.5 lg:w-2.5 -right-1 rounded-full -bottom-1 border-gray-400 bg-white shadow-md border absolute cursor-nwse-resize"
|
||||||
@mousedown.stop="startResize"
|
@mousedown.stop="startResize"
|
||||||
@touchstart="startTouchResize"
|
@touchstart="startTouchResize"
|
||||||
/>
|
/>
|
||||||
@@ -256,7 +257,7 @@
|
|||||||
:to="modalContainerEl"
|
:to="modalContainerEl"
|
||||||
>
|
>
|
||||||
<ConditionsModal
|
<ConditionsModal
|
||||||
:field="field"
|
:item="field"
|
||||||
:build-default-name="buildDefaultName"
|
:build-default-name="buildDefaultName"
|
||||||
@close="isShowConditionsModal = false"
|
@close="isShowConditionsModal = false"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
style="max-width: 1600px"
|
style="max-width: 1600px"
|
||||||
class="mx-auto pl-3 md:pl-4 h-full"
|
class="mx-auto pl-3 h-full"
|
||||||
|
:class="isMobile ? 'pl-4' : 'md:pl-4'"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-if="pendingFieldAttachmentUuids.length"
|
v-if="pendingFieldAttachmentUuids.length && editable"
|
||||||
class="top-1.5 sticky h-0 z-20 max-w-2xl mx-auto"
|
class="top-1.5 sticky h-0 z-20 max-w-2xl mx-auto"
|
||||||
>
|
>
|
||||||
<div class="alert border-base-content/30 py-2 px-2.5">
|
<div class="alert border-base-content/30 py-2 px-2.5">
|
||||||
@@ -163,7 +164,7 @@
|
|||||||
<div
|
<div
|
||||||
id="main_container"
|
id="main_container"
|
||||||
class="flex"
|
class="flex"
|
||||||
:class="$slots.buttons || withTitle ? 'md:max-h-[calc(100%_-_60px)]' : 'md:max-h-[100%]'"
|
:class="$slots.buttons || withTitle ? (isMobile ? 'max-h-[calc(100%_-_60px)]' : 'md:max-h-[calc(100%_-_60px)]') : (isMobile ? 'max-h-[100%]' : 'md:max-h-[100%]')"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-if="withDocumentsList"
|
v-if="withDocumentsList"
|
||||||
@@ -219,7 +220,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
id="pages_container"
|
id="pages_container"
|
||||||
class="w-full overflow-y-hidden md:overflow-y-auto overflow-x-hidden mt-0.5 pt-0.5"
|
class="w-full overflow-y-hidden overflow-x-hidden mt-0.5 pt-0.5"
|
||||||
|
:class="isMobile ? 'overflow-y-auto' : 'md:overflow-y-auto'"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
ref="documents"
|
ref="documents"
|
||||||
@@ -320,7 +322,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="withFieldsList"
|
v-if="withFieldsList && !isMobile"
|
||||||
id="fields_list_container"
|
id="fields_list_container"
|
||||||
class="relative w-80 flex-none mt-1 pr-4 pl-0.5 hidden md:block"
|
class="relative w-80 flex-none mt-1 pr-4 pl-0.5 hidden md:block"
|
||||||
:class="drawField ? 'overflow-hidden' : 'overflow-y-auto overflow-x-hidden'"
|
:class="drawField ? 'overflow-hidden' : 'overflow-y-auto overflow-x-hidden'"
|
||||||
@@ -362,6 +364,7 @@
|
|||||||
:default-submitters="defaultSubmitters"
|
:default-submitters="defaultSubmitters"
|
||||||
:draw-field-type="drawFieldType"
|
:draw-field-type="drawFieldType"
|
||||||
:default-fields="[...defaultRequiredFields, ...defaultFields]"
|
:default-fields="[...defaultRequiredFields, ...defaultFields]"
|
||||||
|
:template="template"
|
||||||
:default-required-fields="defaultRequiredFields"
|
:default-required-fields="defaultRequiredFields"
|
||||||
:field-types="fieldTypes"
|
:field-types="fieldTypes"
|
||||||
:with-sticky-submitters="withStickySubmitters"
|
:with-sticky-submitters="withStickySubmitters"
|
||||||
@@ -378,14 +381,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="sticky bottom-0">
|
<div class="sticky bottom-0 z-10">
|
||||||
<MobileDrawField
|
<MobileDrawField
|
||||||
v-if="drawField && isBreakpointLg"
|
v-if="drawField && (isBreakpointLg || isMobile)"
|
||||||
:draw-field="drawField"
|
:draw-field="drawField"
|
||||||
:fields="template.fields"
|
:fields="template.fields"
|
||||||
:submitters="template.submitters"
|
:submitters="template.submitters"
|
||||||
:selected-submitter="selectedSubmitter"
|
:selected-submitter="selectedSubmitter"
|
||||||
class="md:hidden"
|
:class="{ 'md:hidden': !isMobile }"
|
||||||
:editable="editable"
|
:editable="editable"
|
||||||
@cancel="[drawField = null, drawOption = null]"
|
@cancel="[drawField = null, drawOption = null]"
|
||||||
@change-submitter="[selectedSubmitter = $event, drawField.submitter_uuid = $event.uuid]"
|
@change-submitter="[selectedSubmitter = $event, drawField.submitter_uuid = $event.uuid]"
|
||||||
@@ -396,6 +399,7 @@
|
|||||||
:default-fields="[...defaultRequiredFields, ...defaultFields]"
|
:default-fields="[...defaultRequiredFields, ...defaultFields]"
|
||||||
:default-required-fields="defaultRequiredFields"
|
:default-required-fields="defaultRequiredFields"
|
||||||
:field-types="fieldTypes"
|
:field-types="fieldTypes"
|
||||||
|
:class="{ 'md:hidden': !isMobile }"
|
||||||
:selected-submitter="selectedSubmitter"
|
:selected-submitter="selectedSubmitter"
|
||||||
@select="startFieldDraw($event)"
|
@select="startFieldDraw($event)"
|
||||||
/>
|
/>
|
||||||
@@ -415,6 +419,7 @@ import Contenteditable from './contenteditable'
|
|||||||
import DocumentPreview from './preview'
|
import DocumentPreview from './preview'
|
||||||
import DocumentControls from './controls'
|
import DocumentControls from './controls'
|
||||||
import MobileFields from './mobile_fields'
|
import MobileFields from './mobile_fields'
|
||||||
|
import FieldSubmitter from './field_submitter'
|
||||||
import { IconPlus, IconUsersPlus, IconDeviceFloppy, IconChevronDown, IconEye, IconWritingSign, IconInnerShadowTop, IconInfoCircle, IconAdjustments } from '@tabler/icons-vue'
|
import { IconPlus, IconUsersPlus, IconDeviceFloppy, IconChevronDown, IconEye, IconWritingSign, IconInnerShadowTop, IconInfoCircle, IconAdjustments } from '@tabler/icons-vue'
|
||||||
import { v4 } from 'uuid'
|
import { v4 } from 'uuid'
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
@@ -454,6 +459,7 @@ export default {
|
|||||||
fieldTypes: this.fieldTypes,
|
fieldTypes: this.fieldTypes,
|
||||||
backgroundColor: this.backgroundColor,
|
backgroundColor: this.backgroundColor,
|
||||||
withPhone: this.withPhone,
|
withPhone: this.withPhone,
|
||||||
|
withVerification: this.withVerification,
|
||||||
withPayment: this.withPayment,
|
withPayment: this.withPayment,
|
||||||
isPaymentConnected: this.isPaymentConnected,
|
isPaymentConnected: this.isPaymentConnected,
|
||||||
withFormula: this.withFormula,
|
withFormula: this.withFormula,
|
||||||
@@ -558,6 +564,11 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: () => []
|
default: () => []
|
||||||
},
|
},
|
||||||
|
defineSubmitters: {
|
||||||
|
type: Array,
|
||||||
|
required: false,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
acceptFileTypes: {
|
acceptFileTypes: {
|
||||||
type: String,
|
type: String,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -629,6 +640,11 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
|
withVerification: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
withPayment: {
|
withPayment: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -677,11 +693,15 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
submitterDefaultNames: FieldSubmitter.computed.names,
|
||||||
selectedAreaRef: () => ref(),
|
selectedAreaRef: () => ref(),
|
||||||
fieldsDragFieldRef: () => ref(),
|
fieldsDragFieldRef: () => ref(),
|
||||||
language () {
|
language () {
|
||||||
return this.locale.split('-')[0].toLowerCase()
|
return this.locale.split('-')[0].toLowerCase()
|
||||||
},
|
},
|
||||||
|
isMobile () {
|
||||||
|
return /android|iphone|ipad/i.test(navigator.userAgent)
|
||||||
|
},
|
||||||
defaultDateFormat () {
|
defaultDateFormat () {
|
||||||
const isUsBrowser = Intl.DateTimeFormat().resolvedOptions().locale.endsWith('-US')
|
const isUsBrowser = Intl.DateTimeFormat().resolvedOptions().locale.endsWith('-US')
|
||||||
const isUsTimezone = new Intl.DateTimeFormat('en-US', { timeZoneName: 'short' }).format(new Date()).match(/\s(?:CST|CDT|PST|PDT|EST|EDT)$/)
|
const isUsTimezone = new Intl.DateTimeFormat('en-US', { timeZoneName: 'short' }).format(new Date()).match(/\s(?:CST|CDT|PST|PDT|EST|EDT)$/)
|
||||||
@@ -749,6 +769,18 @@ export default {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
this.defineSubmitters.forEach((name, index) => {
|
||||||
|
const submitter = (this.template.submitters[index] ||= {})
|
||||||
|
|
||||||
|
submitter.name = name || this.submitterDefaultNames[index]
|
||||||
|
|
||||||
|
if (existingSubmittersUuids.filter(Boolean).length) {
|
||||||
|
submitter.uuid = existingSubmittersUuids[index] || submitter.uuid || v4()
|
||||||
|
} else {
|
||||||
|
submitter.uuid ||= v4()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
this.selectedSubmitter = this.template.submitters[0]
|
this.selectedSubmitter = this.template.submitters[0]
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
@@ -998,6 +1030,22 @@ export default {
|
|||||||
|
|
||||||
if (!field.areas.length) {
|
if (!field.areas.length) {
|
||||||
this.template.fields.splice(this.template.fields.indexOf(field), 1)
|
this.template.fields.splice(this.template.fields.indexOf(field), 1)
|
||||||
|
|
||||||
|
this.template.fields.forEach((f) => {
|
||||||
|
(f.conditions || []).forEach((c) => {
|
||||||
|
if (c.field_uuid === field.uuid) {
|
||||||
|
f.conditions.splice(f.conditions.indexOf(c), 1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
this.template.schema.forEach((item) => {
|
||||||
|
(item.conditions || []).forEach((c) => {
|
||||||
|
if (c.field_uuid === field.uuid) {
|
||||||
|
item.conditions.splice(item.conditions.indexOf(c), 1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
this.save()
|
this.save()
|
||||||
@@ -1056,7 +1104,7 @@ export default {
|
|||||||
} else if (type === 'image') {
|
} else if (type === 'image') {
|
||||||
area.w = pageMask.clientWidth / 5 / pageMask.clientWidth
|
area.w = pageMask.clientWidth / 5 / pageMask.clientWidth
|
||||||
area.h = (pageMask.clientWidth / 5 / pageMask.clientWidth) * (pageMask.clientWidth / pageMask.clientHeight)
|
area.h = (pageMask.clientWidth / 5 / pageMask.clientWidth) * (pageMask.clientWidth / pageMask.clientHeight)
|
||||||
} else if (type === 'signature' || type === 'stamp') {
|
} else if (type === 'signature' || type === 'stamp' || type === 'verification') {
|
||||||
area.w = pageMask.clientWidth / 5 / pageMask.clientWidth
|
area.w = pageMask.clientWidth / 5 / pageMask.clientWidth
|
||||||
area.h = (pageMask.clientWidth / 5 / pageMask.clientWidth) * (pageMask.clientWidth / pageMask.clientHeight) / 2
|
area.h = (pageMask.clientWidth / 5 / pageMask.clientWidth) * (pageMask.clientWidth / pageMask.clientHeight) / 2
|
||||||
} else if (type === 'initials') {
|
} else if (type === 'initials') {
|
||||||
@@ -1220,7 +1268,7 @@ export default {
|
|||||||
w: area.maskW / 5 / area.maskW,
|
w: area.maskW / 5 / area.maskW,
|
||||||
h: (area.maskW / 5 / area.maskW) * (area.maskW / area.maskH)
|
h: (area.maskW / 5 / area.maskW) * (area.maskW / area.maskH)
|
||||||
}
|
}
|
||||||
} else if (field.type === 'signature' || field.type === 'stamp') {
|
} else if (field.type === 'signature' || field.type === 'stamp' || field.type === 'verification') {
|
||||||
baseArea = {
|
baseArea = {
|
||||||
w: area.maskW / 5 / area.maskW,
|
w: area.maskW / 5 / area.maskW,
|
||||||
h: (area.maskW / 5 / area.maskW) * (area.maskW / area.maskH) / 2
|
h: (area.maskW / 5 / area.maskW) * (area.maskW / area.maskH) / 2
|
||||||
@@ -1256,6 +1304,16 @@ export default {
|
|||||||
fieldArea.h = lastArea.h
|
fieldArea.h = lastArea.h
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (field.width) {
|
||||||
|
fieldArea.w = field.width / area.maskW
|
||||||
|
delete field.width
|
||||||
|
}
|
||||||
|
|
||||||
|
if (field.height) {
|
||||||
|
fieldArea.h = field.height / area.maskH
|
||||||
|
delete field.height
|
||||||
|
}
|
||||||
|
|
||||||
field.areas.push(fieldArea)
|
field.areas.push(fieldArea)
|
||||||
|
|
||||||
this.selectedAreaRef.value = fieldArea
|
this.selectedAreaRef.value = fieldArea
|
||||||
@@ -1323,7 +1381,9 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.$refs.previews.scrollTop = this.$refs.previews.scrollHeight
|
if (this.$refs.previews) {
|
||||||
|
this.$refs.previews.scrollTop = this.$refs.previews.scrollHeight
|
||||||
|
}
|
||||||
|
|
||||||
this.scrollIntoDocument(data.schema[0])
|
this.scrollIntoDocument(data.schema[0])
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
<div class="modal-box pt-4 pb-6 px-6 mt-20 max-h-none w-full max-w-xl">
|
<div class="modal-box pt-4 pb-6 px-6 mt-20 max-h-none w-full max-w-xl">
|
||||||
<div class="flex justify-between items-center border-b pb-2 mb-2 font-medium">
|
<div class="flex justify-between items-center border-b pb-2 mb-2 font-medium">
|
||||||
<span>
|
<span>
|
||||||
{{ t('condition') }} - {{ field.name || buildDefaultName(field, template.fields) }}
|
{{ t('condition') }} - {{ item.name || buildDefaultName(item, template.fields) }}
|
||||||
</span>
|
</span>
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
@@ -23,21 +23,32 @@
|
|||||||
class="bg-base-300 rounded-xl py-2 px-3 text-center"
|
class="bg-base-300 rounded-xl py-2 px-3 text-center"
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
href="https://www.docuseal.co/pricing"
|
href="https://www.docuseal.com/pricing"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
class="link"
|
class="link"
|
||||||
>{{ t('available_in_pro') }}</a>
|
>{{ t('available_in_pro') }}</a>
|
||||||
</div>
|
</div>
|
||||||
<form @submit.prevent="validateSaveAndClose">
|
<form @submit.prevent="validateSaveAndClose">
|
||||||
<div class="my-4 space-y-5">
|
<div class="my-4">
|
||||||
<div
|
<div
|
||||||
v-for="(condition, cindex) in conditions"
|
v-for="(condition, cindex) in conditions"
|
||||||
:key="cindex"
|
:key="cindex"
|
||||||
class="space-y-4 relative"
|
class="space-y-4 relative"
|
||||||
>
|
>
|
||||||
|
<div
|
||||||
|
v-if="cindex > 0"
|
||||||
|
class="divider -mb-2 mx-1"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="btn btn-xs btn-primary w-24"
|
||||||
|
@click.prevent="condition.operation === 'or' ? delete condition.operation : condition.operation = 'or'"
|
||||||
|
>
|
||||||
|
{{ condition.operation === 'or' ? t('or') : t('and') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="conditions.length > 1"
|
v-if="conditions.length > 1"
|
||||||
class="flex justify-between border-b mx-1 -mb-1 pb-1"
|
class="flex justify-between mx-1"
|
||||||
>
|
>
|
||||||
<span class="text-sm">
|
<span class="text-sm">
|
||||||
{{ t('condition') }} {{ cindex + 1 }}
|
{{ t('condition') }} {{ cindex + 1 }}
|
||||||
@@ -127,12 +138,12 @@
|
|||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
<div
|
<div
|
||||||
v-if="field.conditions?.[0]?.field_uuid"
|
v-if="item.conditions?.[0]?.field_uuid"
|
||||||
class="text-center w-full mt-4"
|
class="text-center w-full mt-4"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
class="link"
|
class="link"
|
||||||
@click="[conditions = [], delete field.conditions, validateSaveAndClose()]"
|
@click="[conditions = [], delete item.conditions, validateSaveAndClose()]"
|
||||||
>
|
>
|
||||||
{{ t('remove_condition') }}
|
{{ t('remove_condition') }}
|
||||||
</button>
|
</button>
|
||||||
@@ -147,7 +158,7 @@ export default {
|
|||||||
name: 'ConditionModal',
|
name: 'ConditionModal',
|
||||||
inject: ['t', 'save', 'template', 'withConditions'],
|
inject: ['t', 'save', 'template', 'withConditions'],
|
||||||
props: {
|
props: {
|
||||||
field: {
|
item: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
@@ -159,22 +170,26 @@ export default {
|
|||||||
emits: ['close'],
|
emits: ['close'],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
conditions: this.field.conditions?.[0] ? JSON.parse(JSON.stringify(this.field.conditions)) : [{}]
|
conditions: this.item.conditions?.[0] ? JSON.parse(JSON.stringify(this.item.conditions)) : [{}]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
fields () {
|
fields () {
|
||||||
return this.template.fields.reduce((acc, f) => {
|
if (this.item.submitter_uuid) {
|
||||||
if (f !== this.field && f.submitter_uuid === this.field.submitter_uuid) {
|
return this.template.fields.reduce((acc, f) => {
|
||||||
acc.push(f)
|
if (f !== this.item && f.submitter_uuid === this.item.submitter_uuid) {
|
||||||
}
|
acc.push(f)
|
||||||
|
}
|
||||||
|
|
||||||
return acc
|
return acc
|
||||||
}, [])
|
}, [])
|
||||||
|
} else {
|
||||||
|
return this.template.fields
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created () {
|
created () {
|
||||||
this.field.conditions ||= []
|
this.item.conditions ||= []
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
conditionField (condition) {
|
conditionField (condition) {
|
||||||
@@ -208,9 +223,9 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.conditions.find((f) => f.field_uuid)) {
|
if (this.conditions.find((f) => f.field_uuid)) {
|
||||||
this.field.conditions = this.conditions
|
this.item.conditions = this.conditions
|
||||||
} else {
|
} else {
|
||||||
delete this.field.conditions
|
delete this.item.conditions
|
||||||
}
|
}
|
||||||
|
|
||||||
this.save()
|
this.save()
|
||||||
|
|||||||
@@ -236,7 +236,7 @@
|
|||||||
:to="modalContainerEl"
|
:to="modalContainerEl"
|
||||||
>
|
>
|
||||||
<ConditionsModal
|
<ConditionsModal
|
||||||
:field="field"
|
:item="field"
|
||||||
:build-default-name="buildDefaultName"
|
:build-default-name="buildDefaultName"
|
||||||
@close="isShowConditionsModal = false"
|
@close="isShowConditionsModal = false"
|
||||||
/>
|
/>
|
||||||
@@ -365,7 +365,7 @@ export default {
|
|||||||
} else {
|
} else {
|
||||||
const typeIndex = fields.filter((f) => f.type === field.type).indexOf(field)
|
const typeIndex = fields.filter((f) => f.type === field.type).indexOf(field)
|
||||||
|
|
||||||
if (this.field.type === 'heading') {
|
if (field.type === 'heading') {
|
||||||
return `${this.fieldNames[field.type]} ${typeIndex + 1}`
|
return `${this.fieldNames[field.type]} ${typeIndex + 1}`
|
||||||
} else {
|
} else {
|
||||||
return `${this.fieldLabels[field.type]} ${typeIndex + 1}`
|
return `${this.fieldLabels[field.type]} ${typeIndex + 1}`
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="['number'].includes(field.type)"
|
v-if="['number', 'cells'].includes(field.type)"
|
||||||
class="py-1.5 px-1 relative"
|
class="py-1.5 px-1 relative"
|
||||||
@click.stop
|
@click.stop
|
||||||
>
|
>
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
@change="[field.preferences ||= {}, field.preferences.align = $event.target.value, save()]"
|
@change="[field.preferences ||= {}, field.preferences.align = $event.target.value, save()]"
|
||||||
>
|
>
|
||||||
<option
|
<option
|
||||||
v-for="value in ['left', 'right', 'center']"
|
v-for="value in ['left', 'right', field.type === 'cells' ? null : 'center'].filter(Boolean)"
|
||||||
:key="value"
|
:key="value"
|
||||||
:selected="field.preferences?.align ? value === field.preferences.align : value === 'left'"
|
:selected="field.preferences?.align ? value === field.preferences.align : value === 'left'"
|
||||||
:value="value"
|
:value="value"
|
||||||
@@ -212,16 +212,12 @@
|
|||||||
{{ t('any') }}
|
{{ t('any') }}
|
||||||
</option>
|
</option>
|
||||||
<option
|
<option
|
||||||
value="drawn"
|
v-for="type in ['drawn', 'typed', 'drawn_or_typed', 'upload']"
|
||||||
:selected="field.preferences?.format === 'drawn'"
|
:key="type"
|
||||||
|
:value="type"
|
||||||
|
:selected="field.preferences?.format === type"
|
||||||
>
|
>
|
||||||
{{ t('drawn') }}
|
{{ t(type) }}
|
||||||
</option>
|
|
||||||
<option
|
|
||||||
value="typed"
|
|
||||||
:selected="field.preferences?.format === 'typed'"
|
|
||||||
>
|
|
||||||
{{ t('typed') }}
|
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
<label
|
<label
|
||||||
@@ -240,7 +236,7 @@
|
|||||||
<input
|
<input
|
||||||
v-model="field.required"
|
v-model="field.required"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
:disabled="!editable || defaultField"
|
:disabled="!editable || (defaultField && [true, false].includes(defaultField.required))"
|
||||||
class="toggle toggle-xs"
|
class="toggle toggle-xs"
|
||||||
@update:model-value="save"
|
@update:model-value="save"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -79,7 +79,7 @@
|
|||||||
:stroke-width="1.6"
|
:stroke-width="1.6"
|
||||||
/>
|
/>
|
||||||
<span class="py-1">
|
<span class="py-1">
|
||||||
{{ t('add') }} {{ names[submitters.length] }}
|
{{ t('add') }} {{ names[lastPartyIndex] }}
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
@@ -202,7 +202,7 @@
|
|||||||
:stroke-width="1.6"
|
:stroke-width="1.6"
|
||||||
/>
|
/>
|
||||||
<span class="py-1">
|
<span class="py-1">
|
||||||
{{ t('add') }} {{ names[submitters.length] }}
|
{{ t('add') }} {{ names[lastPartyIndex] }}
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
@@ -321,6 +321,15 @@ export default {
|
|||||||
this.t('twentieth_party')
|
this.t('twentieth_party')
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
lastPartyIndex () {
|
||||||
|
const index = Math.max(...this.submitters.map((s) => this.names.indexOf(s.name)))
|
||||||
|
|
||||||
|
if (index !== -1) {
|
||||||
|
return index + 1
|
||||||
|
} else {
|
||||||
|
return this.submitters.length
|
||||||
|
}
|
||||||
|
},
|
||||||
selectedSubmitter () {
|
selectedSubmitter () {
|
||||||
return this.submitters.find((e) => e.uuid === this.modelValue)
|
return this.submitters.find((e) => e.uuid === this.modelValue)
|
||||||
}
|
}
|
||||||
@@ -353,7 +362,7 @@ export default {
|
|||||||
},
|
},
|
||||||
addSubmitter () {
|
addSubmitter () {
|
||||||
const newSubmitter = {
|
const newSubmitter = {
|
||||||
name: this.names[this.submitters.length],
|
name: this.names[this.lastPartyIndex],
|
||||||
uuid: v4()
|
uuid: v4()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
v-for="(icon, type) in fieldIconsSorted"
|
v-for="(icon, type) in fieldIconsSorted"
|
||||||
:key="type"
|
:key="type"
|
||||||
>
|
>
|
||||||
<li v-if="(fieldTypes.length === 0 || fieldTypes.includes(type)) && (withPhone || type != 'phone') && (withPayment || type != 'payment')">
|
<li v-if="(fieldTypes.length === 0 || fieldTypes.includes(type)) && (withPhone || type != 'phone') && (withPayment || type != 'payment') && (withVerification || type != 'verification')">
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
class="text-sm py-1 px-2"
|
class="text-sm py-1 px-2"
|
||||||
@@ -51,11 +51,11 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { IconTextSize, IconWritingSign, IconCalendarEvent, IconPhoto, IconCheckbox, IconPaperclip, IconSelect, IconCircleDot, IconChecks, IconColumns3, IconPhoneCheck, IconLetterCaseUpper, IconCreditCard, IconRubberStamp, IconSquareNumber1, IconHeading } from '@tabler/icons-vue'
|
import { IconTextSize, IconWritingSign, IconCalendarEvent, IconPhoto, IconCheckbox, IconPaperclip, IconSelect, IconCircleDot, IconChecks, IconColumns3, IconPhoneCheck, IconLetterCaseUpper, IconCreditCard, IconRubberStamp, IconSquareNumber1, IconHeading, IconId } from '@tabler/icons-vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'FiledTypeDropdown',
|
name: 'FiledTypeDropdown',
|
||||||
inject: ['withPhone', 'withPayment', 't', 'fieldTypes'],
|
inject: ['withPhone', 'withPayment', 'withVerification', 't', 'fieldTypes'],
|
||||||
props: {
|
props: {
|
||||||
modelValue: {
|
modelValue: {
|
||||||
type: String,
|
type: String,
|
||||||
@@ -111,7 +111,8 @@ export default {
|
|||||||
cells: this.t('cells'),
|
cells: this.t('cells'),
|
||||||
stamp: this.t('stamp'),
|
stamp: this.t('stamp'),
|
||||||
payment: this.t('payment'),
|
payment: this.t('payment'),
|
||||||
phone: this.t('phone')
|
phone: this.t('phone'),
|
||||||
|
verification: this.t('verify_id')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
fieldLabels () {
|
fieldLabels () {
|
||||||
@@ -130,7 +131,8 @@ export default {
|
|||||||
cells: this.t('cells_field'),
|
cells: this.t('cells_field'),
|
||||||
stamp: this.t('stamp_field'),
|
stamp: this.t('stamp_field'),
|
||||||
payment: this.t('payment_field'),
|
payment: this.t('payment_field'),
|
||||||
phone: this.t('phone_field')
|
phone: this.t('phone_field'),
|
||||||
|
verification: this.t('verify_id')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
fieldIcons () {
|
fieldIcons () {
|
||||||
@@ -150,7 +152,8 @@ export default {
|
|||||||
cells: IconColumns3,
|
cells: IconColumns3,
|
||||||
stamp: IconRubberStamp,
|
stamp: IconRubberStamp,
|
||||||
payment: IconCreditCard,
|
payment: IconCreditCard,
|
||||||
phone: IconPhoneCheck
|
phone: IconPhoneCheck,
|
||||||
|
verification: IconId
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
fieldIconsSorted () {
|
fieldIconsSorted () {
|
||||||
|
|||||||
@@ -111,14 +111,14 @@
|
|||||||
:key="type"
|
:key="type"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
v-if="(fieldTypes.length === 0 || fieldTypes.includes(type)) && (withPhone || type != 'phone') && (withPayment || type != 'payment')"
|
v-if="(fieldTypes.length === 0 || fieldTypes.includes(type)) && (withPhone || type != 'phone') && (withPayment || type != 'payment') && (withVerification || type != 'verification')"
|
||||||
draggable="true"
|
draggable="true"
|
||||||
class="field-type-button group flex items-center justify-center border border-dashed w-full rounded relative"
|
class="field-type-button group flex items-center justify-center border border-dashed w-full rounded relative"
|
||||||
:style="{ backgroundColor }"
|
:style="{ backgroundColor }"
|
||||||
:class="drawFieldType === type ? 'border-base-content/40' : 'border-base-300 hover:border-base-content/20'"
|
:class="drawFieldType === type ? 'border-base-content/40' : 'border-base-300 hover:border-base-content/20'"
|
||||||
@dragstart="onDragstart({ type: type })"
|
@dragstart="onDragstart({ type: type })"
|
||||||
@dragend="$emit('drag-end')"
|
@dragend="$emit('drag-end')"
|
||||||
@click="['file', 'payment'].includes(type) ? $emit('add-field', type) : $emit('set-draw-type', type)"
|
@click="['file', 'payment', 'verification'].includes(type) ? $emit('add-field', type) : $emit('set-draw-type', type)"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="flex items-console transition-all cursor-grab h-full absolute left-0"
|
class="flex items-console transition-all cursor-grab h-full absolute left-0"
|
||||||
@@ -140,7 +140,33 @@
|
|||||||
:data-tip="t('unlock_sms_verified_phone_number_field_with_paid_plan_use_text_field_for_phone_numbers_without_verification')"
|
:data-tip="t('unlock_sms_verified_phone_number_field_with_paid_plan_use_text_field_for_phone_numbers_without_verification')"
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
href="https://www.docuseal.co/pricing"
|
href="https://www.docuseal.com/pricing"
|
||||||
|
target="_blank"
|
||||||
|
class="opacity-50 flex items-center justify-center border border-dashed border-base-300 w-full rounded relative"
|
||||||
|
:style="{ backgroundColor }"
|
||||||
|
>
|
||||||
|
<div class="w-0 absolute left-0">
|
||||||
|
<IconLock
|
||||||
|
width="18"
|
||||||
|
height="18"
|
||||||
|
stroke-width="1.5"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center flex-col px-2 py-2">
|
||||||
|
<component :is="icon" />
|
||||||
|
<span class="text-xs mt-1">
|
||||||
|
{{ fieldNames[type] }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-else-if="withVerification === false && type == 'verification' && (fieldTypes.length === 0 || fieldTypes.includes(type))"
|
||||||
|
class="tooltip tooltip-bottom flex tooltip-bottom-start"
|
||||||
|
:data-tip="t('obtain_qualified_electronic_signature_with_the_trusted_provider_click_to_learn_more')"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="https://www.docuseal.com/contact"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
class="opacity-50 flex items-center justify-center border border-dashed border-base-300 w-full rounded relative"
|
class="opacity-50 flex items-center justify-center border border-dashed border-base-300 w-full rounded relative"
|
||||||
:style="{ backgroundColor }"
|
:style="{ backgroundColor }"
|
||||||
@@ -197,12 +223,16 @@ export default {
|
|||||||
IconDrag,
|
IconDrag,
|
||||||
IconLock
|
IconLock
|
||||||
},
|
},
|
||||||
inject: ['save', 'backgroundColor', 'withPhone', 'withPayment', 't', 'fieldsDragFieldRef'],
|
inject: ['save', 'backgroundColor', 'withPhone', 'withVerification', 'withPayment', 't', 'fieldsDragFieldRef'],
|
||||||
props: {
|
props: {
|
||||||
fields: {
|
fields: {
|
||||||
type: Array,
|
type: Array,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
template: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
withHelp: {
|
withHelp: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -354,6 +384,14 @@ export default {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
this.template.schema.forEach((item) => {
|
||||||
|
(item.conditions || []).forEach((c) => {
|
||||||
|
if (c.field_uuid === field.uuid) {
|
||||||
|
item.conditions.splice(item.conditions.indexOf(c), 1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
this.save()
|
this.save()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
class="bg-base-300 rounded-xl py-2 px-3 text-center"
|
class="bg-base-300 rounded-xl py-2 px-3 text-center"
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
href="https://www.docuseal.co/pricing"
|
href="https://www.docuseal.com/pricing"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
class="link"
|
class="link"
|
||||||
>{{ t('available_in_pro') }}</a>
|
>{{ t('available_in_pro') }}</a>
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
const en = {
|
const en = {
|
||||||
|
verify_id: 'Verify ID',
|
||||||
|
obtain_qualified_electronic_signature_with_the_trusted_provider_click_to_learn_more: 'Obtain qualified electronic signature (QeS) with the trusted provider. Click to learn more.',
|
||||||
editable: 'Editable',
|
editable: 'Editable',
|
||||||
recurrent: 'Recurrent',
|
recurrent: 'Recurrent',
|
||||||
one_off: 'One-off',
|
one_off: 'One-off',
|
||||||
@@ -46,6 +48,8 @@ const en = {
|
|||||||
cancel: 'Cancel',
|
cancel: 'Cancel',
|
||||||
any: 'Any',
|
any: 'Any',
|
||||||
drawn: 'Drawn',
|
drawn: 'Drawn',
|
||||||
|
drawn_or_typed: 'Drawn or Typed',
|
||||||
|
upload: 'Upload',
|
||||||
formula: 'Formula',
|
formula: 'Formula',
|
||||||
typed: 'Typed',
|
typed: 'Typed',
|
||||||
draw_field_on_the_document: 'Draw a field on the document',
|
draw_field_on_the_document: 'Draw a field on the document',
|
||||||
@@ -126,7 +130,6 @@ const en = {
|
|||||||
unlock_sms_verified_phone_number_field_with_paid_plan_use_text_field_for_phone_numbers_without_verification: 'Unlock SMS-verified phone number field with paid plan. Use text field for phone numbers without verification.',
|
unlock_sms_verified_phone_number_field_with_paid_plan_use_text_field_for_phone_numbers_without_verification: 'Unlock SMS-verified phone number field with paid plan. Use text field for phone numbers without verification.',
|
||||||
available_only_in_pro: 'Available only in Pro',
|
available_only_in_pro: 'Available only in Pro',
|
||||||
failed_to_download_files: 'Failed to download files',
|
failed_to_download_files: 'Failed to download files',
|
||||||
signature_is_too_small_please_redraw: 'Signature is too small - please redraw.',
|
|
||||||
please_add_fields_for_the_submitter_name_or_remove_the_submitter_name_if_not_needed: 'Please add fields for the {submitter_name}. Or, remove the {submitter_name} if not needed.',
|
please_add_fields_for_the_submitter_name_or_remove_the_submitter_name_if_not_needed: 'Please add fields for the {submitter_name}. Or, remove the {submitter_name} if not needed.',
|
||||||
draw_field: 'Draw {field} Field',
|
draw_field: 'Draw {field} Field',
|
||||||
replace: 'Replace',
|
replace: 'Replace',
|
||||||
@@ -149,10 +152,14 @@ const en = {
|
|||||||
preferences: 'Preferences',
|
preferences: 'Preferences',
|
||||||
available_in_pro: 'Available in Pro',
|
available_in_pro: 'Available in Pro',
|
||||||
some_fields_are_missing_in_the_formula: 'Some fields are missing in the formula.',
|
some_fields_are_missing_in_the_formula: 'Some fields are missing in the formula.',
|
||||||
learn_more: 'Learn more'
|
learn_more: 'Learn more',
|
||||||
|
and: 'and',
|
||||||
|
or: 'or'
|
||||||
}
|
}
|
||||||
|
|
||||||
const es = {
|
const es = {
|
||||||
|
verify_id: 'Verificar ID',
|
||||||
|
obtain_qualified_electronic_signature_with_the_trusted_provider_click_to_learn_more: 'Obtenga una firma electrónica cualificada (QeS) con el proveedor de confianza. Haga clic para obtener más información.',
|
||||||
recurrent: 'Recurrente',
|
recurrent: 'Recurrente',
|
||||||
one_off: 'Único',
|
one_off: 'Único',
|
||||||
editable: 'Editable',
|
editable: 'Editable',
|
||||||
@@ -277,7 +284,6 @@ const es = {
|
|||||||
unlock_sms_verified_phone_number_field_with_paid_plan_use_text_field_for_phone_numbers_without_verification: 'Desbloquea el campo de número de teléfono verificado por SMS con un plan pago. Usa el campo de texto para números de teléfono sin verificación.',
|
unlock_sms_verified_phone_number_field_with_paid_plan_use_text_field_for_phone_numbers_without_verification: 'Desbloquea el campo de número de teléfono verificado por SMS con un plan pago. Usa el campo de texto para números de teléfono sin verificación.',
|
||||||
available_only_in_pro: 'Disponible solo en Pro',
|
available_only_in_pro: 'Disponible solo en Pro',
|
||||||
failed_to_download_files: 'Error al descargar los archivos',
|
failed_to_download_files: 'Error al descargar los archivos',
|
||||||
signature_is_too_small_please_redraw: 'La firma es demasiado pequeña. Por favor, dibújala de nuevo.',
|
|
||||||
please_add_fields_for_the_submitter_name_or_remove_the_submitter_name_if_not_needed: 'Por favor, añade campos para {submitter_name} o elimina {submitter_name} si no es necesario.',
|
please_add_fields_for_the_submitter_name_or_remove_the_submitter_name_if_not_needed: 'Por favor, añade campos para {submitter_name} o elimina {submitter_name} si no es necesario.',
|
||||||
draw_field: 'Dibujar campo {field}',
|
draw_field: 'Dibujar campo {field}',
|
||||||
replace: 'Reemplazar',
|
replace: 'Reemplazar',
|
||||||
@@ -285,6 +291,8 @@ const es = {
|
|||||||
add_document: 'Subir',
|
add_document: 'Subir',
|
||||||
any: 'Cualquier',
|
any: 'Cualquier',
|
||||||
drawn: 'Dibujado',
|
drawn: 'Dibujado',
|
||||||
|
drawn_or_typed: 'Dibujado o Escrito',
|
||||||
|
upload: 'Subir',
|
||||||
typed: 'Escrito',
|
typed: 'Escrito',
|
||||||
none: 'Ninguno',
|
none: 'Ninguno',
|
||||||
ssn: 'SSN',
|
ssn: 'SSN',
|
||||||
@@ -303,10 +311,14 @@ const es = {
|
|||||||
preferences: 'Preferencias',
|
preferences: 'Preferencias',
|
||||||
available_in_pro: 'Disponible en Pro',
|
available_in_pro: 'Disponible en Pro',
|
||||||
some_fields_are_missing_in_the_formula: 'Faltan algunos campos en la fórmula.',
|
some_fields_are_missing_in_the_formula: 'Faltan algunos campos en la fórmula.',
|
||||||
learn_more: 'Aprende más'
|
learn_more: 'Aprende más',
|
||||||
|
and: 'y',
|
||||||
|
or: 'o'
|
||||||
}
|
}
|
||||||
|
|
||||||
const it = {
|
const it = {
|
||||||
|
verify_id: 'Verifica ID',
|
||||||
|
obtain_qualified_electronic_signature_with_the_trusted_provider_click_to_learn_more: 'Ottieni una firma elettronica qualificata (QeS) con il fornitore di fiducia. Clicca per saperne di più.',
|
||||||
ricorrente: 'Ricorrente',
|
ricorrente: 'Ricorrente',
|
||||||
una_volta: 'Una volta',
|
una_volta: 'Una volta',
|
||||||
editable: 'Modificabile',
|
editable: 'Modificabile',
|
||||||
@@ -354,6 +366,8 @@ const it = {
|
|||||||
cancel: 'Annulla',
|
cancel: 'Annulla',
|
||||||
any: 'Qualsiasi',
|
any: 'Qualsiasi',
|
||||||
drawn: 'Disegnato',
|
drawn: 'Disegnato',
|
||||||
|
drawn_or_typed: 'Disegnato o Digitato',
|
||||||
|
upload: 'Caricare',
|
||||||
formula: 'Formula',
|
formula: 'Formula',
|
||||||
typed: 'Digitato',
|
typed: 'Digitato',
|
||||||
draw_field_on_the_document: 'Disegnare un campo sul documento',
|
draw_field_on_the_document: 'Disegnare un campo sul documento',
|
||||||
@@ -434,7 +448,6 @@ const it = {
|
|||||||
unlock_sms_verified_phone_number_field_with_paid_plan_use_text_field_for_phone_numbers_without_verification: 'Sblocca il campo numero di telefono verificato tramite SMS con un piano a pagamento. Usa il campo di testo per numeri di telefono senza verifica.',
|
unlock_sms_verified_phone_number_field_with_paid_plan_use_text_field_for_phone_numbers_without_verification: 'Sblocca il campo numero di telefono verificato tramite SMS con un piano a pagamento. Usa il campo di testo per numeri di telefono senza verifica.',
|
||||||
available_only_in_pro: 'Disponibile solo in Pro',
|
available_only_in_pro: 'Disponibile solo in Pro',
|
||||||
failed_to_download_files: 'Impossibile scaricare i file',
|
failed_to_download_files: 'Impossibile scaricare i file',
|
||||||
signature_is_too_small_please_redraw: 'La firma è troppo piccola. Ridisegnala per favore.',
|
|
||||||
please_add_fields_for_the_submitter_name_or_remove_the_submitter_name_if_not_needed: 'Aggiungi campi per {submitter_name} o rimuovi {submitter_name} se non necessario.',
|
please_add_fields_for_the_submitter_name_or_remove_the_submitter_name_if_not_needed: 'Aggiungi campi per {submitter_name} o rimuovi {submitter_name} se non necessario.',
|
||||||
draw_field: 'Disegna il campo {field}',
|
draw_field: 'Disegna il campo {field}',
|
||||||
replace: 'Sostituisci',
|
replace: 'Sostituisci',
|
||||||
@@ -457,10 +470,14 @@ const it = {
|
|||||||
preferences: 'Preferenze',
|
preferences: 'Preferenze',
|
||||||
available_in_pro: 'Disponibile in Pro',
|
available_in_pro: 'Disponibile in Pro',
|
||||||
some_fields_are_missing_in_the_formula: 'Alcuni campi mancano nella formula.',
|
some_fields_are_missing_in_the_formula: 'Alcuni campi mancano nella formula.',
|
||||||
learn_more: 'Scopri di più'
|
learn_more: 'Scopri di più',
|
||||||
|
and: 'e',
|
||||||
|
or: 'o'
|
||||||
}
|
}
|
||||||
|
|
||||||
const pt = {
|
const pt = {
|
||||||
|
verify_id: 'Verificar ID',
|
||||||
|
obtain_qualified_electronic_signature_with_the_trusted_provider_click_to_learn_more: 'Obtenha a assinatura eletrônica qualificada (QeS) com o provedor confiável. Clique para saber mais.',
|
||||||
recurrent: 'Recurrente',
|
recurrent: 'Recurrente',
|
||||||
one_off: 'Único',
|
one_off: 'Único',
|
||||||
editable: 'Editável',
|
editable: 'Editável',
|
||||||
@@ -585,7 +602,6 @@ const pt = {
|
|||||||
unlock_sms_verified_phone_number_field_with_paid_plan_use_text_field_for_phone_numbers_without_verification: 'Desbloqueie o campo de número de telefone verificado por SMS com um plano pago. Use o campo de texto para números de telefone sem verificação.',
|
unlock_sms_verified_phone_number_field_with_paid_plan_use_text_field_for_phone_numbers_without_verification: 'Desbloqueie o campo de número de telefone verificado por SMS com um plano pago. Use o campo de texto para números de telefone sem verificação.',
|
||||||
available_only_in_pro: 'Disponível apenas no Pro',
|
available_only_in_pro: 'Disponível apenas no Pro',
|
||||||
failed_to_download_files: 'Falha ao baixar arquivos',
|
failed_to_download_files: 'Falha ao baixar arquivos',
|
||||||
signature_is_too_small_please_redraw: 'A assinatura é muito pequena. Por favor, redesenhe.',
|
|
||||||
please_add_fields_for_the_submitter_name_or_remove_the_submitter_name_if_not_needed: 'Adicione campos para {submitter_name} ou remova {submitter_name} se não for necessário.',
|
please_add_fields_for_the_submitter_name_or_remove_the_submitter_name_if_not_needed: 'Adicione campos para {submitter_name} ou remova {submitter_name} se não for necessário.',
|
||||||
draw_field: 'Desenhar campo {field}',
|
draw_field: 'Desenhar campo {field}',
|
||||||
replace: 'Substituir',
|
replace: 'Substituir',
|
||||||
@@ -593,6 +609,8 @@ const pt = {
|
|||||||
add_document: 'Enviar',
|
add_document: 'Enviar',
|
||||||
any: 'Qualquer',
|
any: 'Qualquer',
|
||||||
drawn: 'Desenhado',
|
drawn: 'Desenhado',
|
||||||
|
drawn_or_typed: 'Desenhado ou Digitado',
|
||||||
|
upload: 'Carregar',
|
||||||
typed: 'Digitado',
|
typed: 'Digitado',
|
||||||
none: 'Nenhum',
|
none: 'Nenhum',
|
||||||
ssn: 'SSN',
|
ssn: 'SSN',
|
||||||
@@ -611,10 +629,14 @@ const pt = {
|
|||||||
preferences: 'Preferências',
|
preferences: 'Preferências',
|
||||||
available_in_pro: 'Disponível no Pro',
|
available_in_pro: 'Disponível no Pro',
|
||||||
some_fields_are_missing_in_the_formula: 'Faltam alguns campos na fórmula.',
|
some_fields_are_missing_in_the_formula: 'Faltam alguns campos na fórmula.',
|
||||||
learn_more: 'Saiba mais'
|
learn_more: 'Saiba mais',
|
||||||
|
and: 'e',
|
||||||
|
or: 'ou'
|
||||||
}
|
}
|
||||||
|
|
||||||
const fr = {
|
const fr = {
|
||||||
|
verify_id: "Vérifier l'ID",
|
||||||
|
obtain_qualified_electronic_signature_with_the_trusted_provider_click_to_learn_more: 'Obtenez une signature électronique qualifiée (QeS) avec le fournisseur de confiance. Cliquez pour en savoir plus.',
|
||||||
recurrent: 'Récurrent',
|
recurrent: 'Récurrent',
|
||||||
one_off: 'Ponctuel',
|
one_off: 'Ponctuel',
|
||||||
editable: 'Éditable',
|
editable: 'Éditable',
|
||||||
@@ -739,7 +761,6 @@ const fr = {
|
|||||||
unlock_sms_verified_phone_number_field_with_paid_plan_use_text_field_for_phone_numbers_without_verification: 'Débloquez le champ de numéro de téléphone vérifié par SMS avec un plan payant. Utilisez un champ texte pour les numéros de téléphone sans vérification.',
|
unlock_sms_verified_phone_number_field_with_paid_plan_use_text_field_for_phone_numbers_without_verification: 'Débloquez le champ de numéro de téléphone vérifié par SMS avec un plan payant. Utilisez un champ texte pour les numéros de téléphone sans vérification.',
|
||||||
available_only_in_pro: 'Disponible uniquement en Pro',
|
available_only_in_pro: 'Disponible uniquement en Pro',
|
||||||
failed_to_download_files: 'Échec du téléchargement des fichiers',
|
failed_to_download_files: 'Échec du téléchargement des fichiers',
|
||||||
signature_is_too_small_please_redraw: 'La signature est trop petite. Veuillez la redessiner.',
|
|
||||||
please_add_fields_for_the_submitter_name_or_remove_the_submitter_name_if_not_needed: 'Veuillez ajouter des champs pour {submitter_name} ou retirer {submitter_name} si ce n\'est pas nécessaire.',
|
please_add_fields_for_the_submitter_name_or_remove_the_submitter_name_if_not_needed: 'Veuillez ajouter des champs pour {submitter_name} ou retirer {submitter_name} si ce n\'est pas nécessaire.',
|
||||||
draw_field: 'Dessiner le champ {field}',
|
draw_field: 'Dessiner le champ {field}',
|
||||||
replace: 'Remplacer',
|
replace: 'Remplacer',
|
||||||
@@ -747,6 +768,8 @@ const fr = {
|
|||||||
add_document: 'Télécharger',
|
add_document: 'Télécharger',
|
||||||
any: 'Tout',
|
any: 'Tout',
|
||||||
drawn: 'Dessiné',
|
drawn: 'Dessiné',
|
||||||
|
drawn_or_typed: 'Dessiné ou Tapé',
|
||||||
|
upload: 'Téléverser',
|
||||||
typed: 'Tapé',
|
typed: 'Tapé',
|
||||||
none: 'Aucun',
|
none: 'Aucun',
|
||||||
ssn: 'SSN',
|
ssn: 'SSN',
|
||||||
@@ -765,10 +788,14 @@ const fr = {
|
|||||||
preferences: 'Préférences',
|
preferences: 'Préférences',
|
||||||
available_in_pro: 'Disponible en version Pro',
|
available_in_pro: 'Disponible en version Pro',
|
||||||
some_fields_are_missing_in_the_formula: 'Certains champs manquent dans la formule.',
|
some_fields_are_missing_in_the_formula: 'Certains champs manquent dans la formule.',
|
||||||
learn_more: 'En savoir plus'
|
learn_more: 'En savoir plus',
|
||||||
|
and: 'et',
|
||||||
|
or: 'ou'
|
||||||
}
|
}
|
||||||
|
|
||||||
const de = {
|
const de = {
|
||||||
|
verify_id: 'ID überprüfen',
|
||||||
|
obtain_qualified_electronic_signature_with_the_trusted_provider_click_to_learn_more: 'Erhalten Sie eine qualifizierte elektronische Signatur (QeS) beim vertrauenswürdigen Anbieter. Klicken Sie hier, um mehr zu erfahren.',
|
||||||
wiederkehrend: 'Wiederkehrend',
|
wiederkehrend: 'Wiederkehrend',
|
||||||
einmalig: 'Einmalig',
|
einmalig: 'Einmalig',
|
||||||
editable: 'Bearbeitbar',
|
editable: 'Bearbeitbar',
|
||||||
@@ -893,7 +920,6 @@ const de = {
|
|||||||
unlock_sms_verified_phone_number_field_with_paid_plan_use_text_field_for_phone_numbers_without_verification: 'Schalte das SMS-verifizierte Telefonnummernfeld mit einem kostenpflichtigen Plan frei. Verwende das Textfeld für Telefonnummern ohne Verifizierung.',
|
unlock_sms_verified_phone_number_field_with_paid_plan_use_text_field_for_phone_numbers_without_verification: 'Schalte das SMS-verifizierte Telefonnummernfeld mit einem kostenpflichtigen Plan frei. Verwende das Textfeld für Telefonnummern ohne Verifizierung.',
|
||||||
available_only_in_pro: 'Nur in Pro verfügbar',
|
available_only_in_pro: 'Nur in Pro verfügbar',
|
||||||
failed_to_download_files: 'Fehler beim Herunterladen der Dateien',
|
failed_to_download_files: 'Fehler beim Herunterladen der Dateien',
|
||||||
signature_is_too_small_please_redraw: 'Die Unterschrift ist zu klein. Bitte erneut zeichnen.',
|
|
||||||
please_add_fields_for_the_submitter_name_or_remove_the_submitter_name_if_not_needed: 'Bitte füge Felder für {submitter_name} hinzu oder entferne {submitter_name}, falls nicht erforderlich.',
|
please_add_fields_for_the_submitter_name_or_remove_the_submitter_name_if_not_needed: 'Bitte füge Felder für {submitter_name} hinzu oder entferne {submitter_name}, falls nicht erforderlich.',
|
||||||
draw_field: 'Feld {field} zeichnen',
|
draw_field: 'Feld {field} zeichnen',
|
||||||
replace: 'Ersetzen',
|
replace: 'Ersetzen',
|
||||||
@@ -901,6 +927,8 @@ const de = {
|
|||||||
add_document: 'Hochladen',
|
add_document: 'Hochladen',
|
||||||
any: 'Jede',
|
any: 'Jede',
|
||||||
drawn: 'Gezeichnet',
|
drawn: 'Gezeichnet',
|
||||||
|
drawn_or_typed: 'Gezeichnet oder getippt',
|
||||||
|
upload: 'Upload',
|
||||||
typed: 'Getippt',
|
typed: 'Getippt',
|
||||||
none: 'Keine',
|
none: 'Keine',
|
||||||
ssn: 'SSN',
|
ssn: 'SSN',
|
||||||
@@ -919,7 +947,9 @@ const de = {
|
|||||||
preferences: 'Einstellungen',
|
preferences: 'Einstellungen',
|
||||||
available_in_pro: 'In Pro verfügbar',
|
available_in_pro: 'In Pro verfügbar',
|
||||||
some_fields_are_missing_in_the_formula: 'Einige Felder fehlen in der Formel.',
|
some_fields_are_missing_in_the_formula: 'Einige Felder fehlen in der Formel.',
|
||||||
learn_more: 'Erfahren Sie mehr'
|
learn_more: 'Erfahren Sie mehr',
|
||||||
|
and: 'und',
|
||||||
|
or: 'oder'
|
||||||
}
|
}
|
||||||
|
|
||||||
export { en, es, it, pt, fr, de }
|
export { en, es, it, pt, fr, de }
|
||||||
@@ -77,7 +77,7 @@
|
|||||||
<div class="w-full relative">
|
<div class="w-full relative">
|
||||||
<select
|
<select
|
||||||
class="base-select !select-sm !h-10"
|
class="base-select !select-sm !h-10"
|
||||||
:class="{ '!text-gray-300': !mapping.column_index }"
|
:class="{ '!text-gray-300': !mapping.column_index && mapping.column_index != 0 }"
|
||||||
required
|
required
|
||||||
@change="mapping.column_index = parseInt($event.target.value)"
|
@change="mapping.column_index = parseInt($event.target.value)"
|
||||||
>
|
>
|
||||||
@@ -279,13 +279,17 @@ export default {
|
|||||||
|
|
||||||
this.mappings.forEach((mapping) => {
|
this.mappings.forEach((mapping) => {
|
||||||
if (mapping.field_name && mapping.column_index != null) {
|
if (mapping.field_name && mapping.column_index != null) {
|
||||||
submittersIndex[mapping.submitter_uuid] ||= { uuid: mapping.submitter_uuid, fields: [] }
|
submittersIndex[mapping.submitter_uuid] ||= {
|
||||||
|
uuid: mapping.submitter_uuid,
|
||||||
|
role: this.submitters.find((s) => s.uuid === mapping.submitter_uuid).name,
|
||||||
|
fields: []
|
||||||
|
}
|
||||||
|
|
||||||
if (['name', 'email', 'phone', 'external_id'].includes(mapping.field_name.toLowerCase())) {
|
if (['name', 'email', 'phone', 'external_id'].includes(mapping.field_name.toLowerCase())) {
|
||||||
submittersIndex[mapping.submitter_uuid][mapping.field_name.toLowerCase()] = row[mapping.column_index]
|
submittersIndex[mapping.submitter_uuid][mapping.field_name.toLowerCase()] = row[mapping.column_index]
|
||||||
}
|
}
|
||||||
|
|
||||||
const fieldType = this.fieldTypesIndex[mapping.submitter_uuid][mapping.field_name]
|
const fieldType = this.fieldTypesIndex[mapping.submitter_uuid]?.[mapping.field_name]
|
||||||
|
|
||||||
if (fieldType && fieldType !== 'phone') {
|
if (fieldType && fieldType !== 'phone') {
|
||||||
submittersIndex[mapping.submitter_uuid].fields.push({
|
submittersIndex[mapping.submitter_uuid].fields.push({
|
||||||
@@ -296,7 +300,7 @@ export default {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (Object.keys(submittersIndex).length !== 0) {
|
if (Object.keys(submittersIndex).length !== 0) {
|
||||||
submissions.push({ submitters: Object.values(submittersIndex) })
|
submissions.push({ submitters: this.submitters.map((s) => submittersIndex[s.uuid]).filter(Boolean) })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -342,7 +346,7 @@ export default {
|
|||||||
return this.$el.closest('form')
|
return this.$el.closest('form')
|
||||||
},
|
},
|
||||||
fieldTypes () {
|
fieldTypes () {
|
||||||
return ['text', 'cells', 'date', 'number', 'radio', 'select', 'checkbox']
|
return ['text', 'cells', 'date', 'number', 'radio', 'select', 'checkbox', 'image']
|
||||||
},
|
},
|
||||||
defaultFields () {
|
defaultFields () {
|
||||||
return [
|
return [
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="absolute text-center w-full bottom-0 pr-3 mb-4">
|
<div class="absolute text-center w-full bottom-0 pr-3 mb-4">
|
||||||
<span class="w-full bg-base-200 px-4 py-2 rounded-md inline-flex space-x-2 mx-auto items-center justify-between mb-2 z-20 md:hidden">
|
<span class="w-full bg-base-200 px-4 py-2 rounded-md inline-flex space-x-2 mx-auto items-center justify-between mb-2 z-20">
|
||||||
<div class="flex items-center space-x-2">
|
<div class="flex items-center space-x-2">
|
||||||
<component
|
<component
|
||||||
:is="fieldIcons[drawField.type]"
|
:is="fieldIcons[drawField.type]"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<span
|
<span
|
||||||
class="dropdown dropdown-top dropdown-end absolute bottom-4 right-4 z-10 md:hidden"
|
class="dropdown dropdown-top dropdown-end absolute bottom-4 right-4 z-10"
|
||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
class="btn btn-neutral text-white btn-circle btn-lg group"
|
class="btn btn-neutral text-white btn-circle btn-lg group"
|
||||||
@@ -12,9 +12,10 @@
|
|||||||
height="28"
|
height="28"
|
||||||
/>
|
/>
|
||||||
<IconX
|
<IconX
|
||||||
class="hidden group-focus:inline"
|
class="hidden group-focus:inline p-3"
|
||||||
width="28"
|
width="64"
|
||||||
height="28"
|
height="50"
|
||||||
|
@click="closeDropdown"
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<ul
|
<ul
|
||||||
@@ -55,7 +56,7 @@
|
|||||||
v-for="(icon, type) in fieldIconsSorted"
|
v-for="(icon, type) in fieldIconsSorted"
|
||||||
:key="type"
|
:key="type"
|
||||||
>
|
>
|
||||||
<li v-if="(fieldTypes.length === 0 || fieldTypes.includes(type)) && (withPhone || type != 'phone') && (withPayment || type != 'payment')">
|
<li v-if="(fieldTypes.length === 0 || fieldTypes.includes(type)) && (withPhone || type != 'phone') && (withPayment || type != 'payment') && (withVerification || type != 'verification')">
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
class="text-sm py-1 px-2"
|
class="text-sm py-1 px-2"
|
||||||
@@ -85,7 +86,7 @@ export default {
|
|||||||
IconPlus,
|
IconPlus,
|
||||||
IconX
|
IconX
|
||||||
},
|
},
|
||||||
inject: ['withPhone', 'withPayment', 'backgroundColor', 't'],
|
inject: ['withPhone', 'withPayment', 'withVerification', 'backgroundColor', 't'],
|
||||||
props: {
|
props: {
|
||||||
modelValue: {
|
modelValue: {
|
||||||
type: String,
|
type: String,
|
||||||
|
|||||||
@@ -178,7 +178,7 @@
|
|||||||
<a
|
<a
|
||||||
v-if="!isConnected"
|
v-if="!isConnected"
|
||||||
class="block link text-center mt-1"
|
class="block link text-center mt-1"
|
||||||
href="https://www.docuseal.co/blog/accept-payments-and-request-signatures-with-ease"
|
href="https://www.docuseal.com/blog/accept-payments-and-request-signatures-with-ease"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
data-turbo="false"
|
data-turbo="false"
|
||||||
>{{ t('learn_more') }}</a>
|
>{{ t('learn_more') }}</a>
|
||||||
|
|||||||
@@ -9,14 +9,30 @@
|
|||||||
loading="lazy"
|
loading="lazy"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="group flex justify-end cursor-pointer top-0 bottom-0 left-0 right-0 absolute p-1"
|
class="group flex justify-end cursor-pointer top-0 bottom-0 left-0 right-0 absolute p-1 hover:bg-black/10 transition-colors"
|
||||||
@click="$emit('scroll-to', item)"
|
@click="$emit('scroll-to', item)"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-if="editable"
|
v-if="editable"
|
||||||
class="flex justify-between w-full"
|
class="flex justify-between w-full"
|
||||||
>
|
>
|
||||||
<div style="width: 26px" />
|
<div
|
||||||
|
style="width: 26px"
|
||||||
|
class="flex flex-col justify-between group-hover:opacity-100"
|
||||||
|
:class="{'opacity-0': !item.conditions?.length }"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
class="btn border-base-200 bg-white text-base-content btn-xs rounded hover:text-base-100 hover:bg-base-content hover:border-base-content w-full transition-colors p-0"
|
||||||
|
@click.stop="isShowConditionsModal = true"
|
||||||
|
>
|
||||||
|
<IconRouteAltLeft
|
||||||
|
:width="14"
|
||||||
|
:stroke-width="1.6"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="">
|
<div class="">
|
||||||
<ReplaceButton
|
<ReplaceButton
|
||||||
v-if="withReplaceButton"
|
v-if="withReplaceButton"
|
||||||
@@ -73,19 +89,36 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<Teleport
|
||||||
|
v-if="isShowConditionsModal"
|
||||||
|
:to="modalContainerEl"
|
||||||
|
>
|
||||||
|
<ConditionsModal
|
||||||
|
:item="item"
|
||||||
|
:build-default-name="buildDefaultName"
|
||||||
|
@close="isShowConditionsModal = false"
|
||||||
|
/>
|
||||||
|
</Teleport>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Contenteditable from './contenteditable'
|
import Contenteditable from './contenteditable'
|
||||||
import Upload from './upload'
|
import Upload from './upload'
|
||||||
|
import { IconRouteAltLeft } from '@tabler/icons-vue'
|
||||||
|
import ConditionsModal from './conditions_modal'
|
||||||
import ReplaceButton from './replace'
|
import ReplaceButton from './replace'
|
||||||
|
import Field from './field'
|
||||||
|
import FieldType from './field_type'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'DocumentPreview',
|
name: 'DocumentPreview',
|
||||||
components: {
|
components: {
|
||||||
Contenteditable,
|
Contenteditable,
|
||||||
|
IconRouteAltLeft,
|
||||||
|
ConditionsModal,
|
||||||
ReplaceButton
|
ReplaceButton
|
||||||
},
|
},
|
||||||
|
inject: ['t'],
|
||||||
props: {
|
props: {
|
||||||
item: {
|
item: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@@ -121,13 +154,24 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits: ['scroll-to', 'change', 'remove', 'up', 'down', 'replace'],
|
emits: ['scroll-to', 'change', 'remove', 'up', 'down', 'replace'],
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
isShowConditionsModal: false
|
||||||
|
}
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
fieldNames: FieldType.computed.fieldNames,
|
||||||
|
fieldLabels: FieldType.computed.fieldLabels,
|
||||||
previewImage () {
|
previewImage () {
|
||||||
return [...this.document.preview_images].sort((a, b) => parseInt(a.filename) - parseInt(b.filename))[0]
|
return [...this.document.preview_images].sort((a, b) => parseInt(a.filename) - parseInt(b.filename))[0]
|
||||||
|
},
|
||||||
|
modalContainerEl () {
|
||||||
|
return this.$el.getRootNode().querySelector('#docuseal_modal_container')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
upload: Upload.methods.upload,
|
upload: Upload.methods.upload,
|
||||||
|
buildDefaultName: Field.methods.buildDefaultName,
|
||||||
onUpdateName (value) {
|
onUpdateName (value) {
|
||||||
this.item.name = value
|
this.item.name = value
|
||||||
|
|
||||||
|
|||||||
@@ -10,10 +10,6 @@ class ProcessSubmitterCompletionJob
|
|||||||
|
|
||||||
is_all_completed = !submitter.submission.submitters.exists?(completed_at: nil)
|
is_all_completed = !submitter.submission.submitters.exists?(completed_at: nil)
|
||||||
|
|
||||||
if !is_all_completed && submitter.submission.submitters_order_preserved?
|
|
||||||
enqueue_next_submitter_request_notification(submitter)
|
|
||||||
end
|
|
||||||
|
|
||||||
Submissions::EnsureResultGenerated.call(submitter)
|
Submissions::EnsureResultGenerated.call(submitter)
|
||||||
|
|
||||||
if is_all_completed && submitter.completed_at == submitter.submission.submitters.maximum(:completed_at)
|
if is_all_completed && submitter.completed_at == submitter.submission.submitters.maximum(:completed_at)
|
||||||
@@ -28,6 +24,10 @@ class ProcessSubmitterCompletionJob
|
|||||||
|
|
||||||
create_completed_documents!(submitter)
|
create_completed_documents!(submitter)
|
||||||
|
|
||||||
|
if !is_all_completed && submitter.submission.submitters_order_preserved? && params['send_invitation_email'] != false
|
||||||
|
enqueue_next_submitter_request_notification(submitter)
|
||||||
|
end
|
||||||
|
|
||||||
enqueue_completed_webhooks(submitter, is_all_completed:)
|
enqueue_completed_webhooks(submitter, is_all_completed:)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -63,32 +63,15 @@ class ProcessSubmitterCompletionJob
|
|||||||
end
|
end
|
||||||
|
|
||||||
def enqueue_completed_webhooks(submitter, is_all_completed: false)
|
def enqueue_completed_webhooks(submitter, is_all_completed: false)
|
||||||
webhook_config = Accounts.load_webhook_config(submitter.account)
|
WebhookUrls.for_account_id(submitter.account_id, %w[form.completed submission.completed]).each do |webhook|
|
||||||
|
|
||||||
if webhook_config
|
|
||||||
SendFormCompletedWebhookRequestJob.perform_async({ 'submitter_id' => submitter.id,
|
|
||||||
'encrypted_config_id' => webhook_config.id })
|
|
||||||
end
|
|
||||||
|
|
||||||
webhook_urls = submitter.account.webhook_urls
|
|
||||||
|
|
||||||
webhook_urls = webhook_urls.where(
|
|
||||||
Arel::Table.new(:webhook_urls)[:events].matches('%"form.completed"%')
|
|
||||||
).or(
|
|
||||||
webhook_urls.where(
|
|
||||||
Arel::Table.new(:webhook_urls)[:events].matches('%"submission.completed"%')
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
webhook_urls.each do |webhook|
|
|
||||||
if webhook.events.include?('form.completed')
|
if webhook.events.include?('form.completed')
|
||||||
SendFormCompletedWebhookRequestJob.perform_async({ 'submitter_id' => submitter.id,
|
SendFormCompletedWebhookRequestJob.perform_async('submitter_id' => submitter.id,
|
||||||
'webhook_url_id' => webhook.id })
|
'webhook_url_id' => webhook.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
if webhook.events.include?('submission.completed') && is_all_completed
|
if webhook.events.include?('submission.completed') && is_all_completed
|
||||||
SendSubmissionCompletedWebhookRequestJob.perform_async({ 'submission_id' => submitter.submission_id,
|
SendSubmissionCompletedWebhookRequestJob.perform_async('submission_id' => submitter.submission_id,
|
||||||
'webhook_url_id' => webhook.id })
|
'webhook_url_id' => webhook.id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -5,33 +5,32 @@ class SendFormCompletedWebhookRequestJob
|
|||||||
|
|
||||||
sidekiq_options queue: :webhooks
|
sidekiq_options queue: :webhooks
|
||||||
|
|
||||||
USER_AGENT = 'DocuSeal.co Webhook'
|
USER_AGENT = 'DocuSeal.com Webhook'
|
||||||
|
|
||||||
MAX_ATTEMPTS = 10
|
MAX_ATTEMPTS = 10
|
||||||
|
|
||||||
def perform(params = {})
|
def perform(params = {})
|
||||||
submitter = Submitter.find(params['submitter_id'])
|
submitter = Submitter.find(params['submitter_id'])
|
||||||
|
webhook_url = WebhookUrl.find(params['webhook_url_id'])
|
||||||
|
|
||||||
attempt = params['attempt'].to_i
|
attempt = params['attempt'].to_i
|
||||||
|
|
||||||
url, secret = load_url_and_secret(submitter, params)
|
return if webhook_url.url.blank? || webhook_url.events.exclude?('form.completed')
|
||||||
|
|
||||||
return if url.blank?
|
|
||||||
|
|
||||||
Submissions::EnsureResultGenerated.call(submitter)
|
Submissions::EnsureResultGenerated.call(submitter)
|
||||||
|
|
||||||
ActiveStorage::Current.url_options = Docuseal.default_url_options
|
ActiveStorage::Current.url_options = Docuseal.default_url_options
|
||||||
|
|
||||||
resp = begin
|
resp = begin
|
||||||
Faraday.post(url,
|
Faraday.post(webhook_url.url,
|
||||||
{
|
{
|
||||||
event_type: 'form.completed',
|
event_type: 'form.completed',
|
||||||
timestamp: Time.current,
|
timestamp: Time.current,
|
||||||
data: Submitters::SerializeForWebhook.call(submitter)
|
data: Submitters::SerializeForWebhook.call(submitter)
|
||||||
}.to_json,
|
}.to_json,
|
||||||
**secret.to_h,
|
|
||||||
'Content-Type' => 'application/json',
|
'Content-Type' => 'application/json',
|
||||||
'User-Agent' => USER_AGENT)
|
'User-Agent' => USER_AGENT,
|
||||||
|
**webhook_url.secret.to_h)
|
||||||
rescue Faraday::Error
|
rescue Faraday::Error
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
@@ -45,27 +44,4 @@ class SendFormCompletedWebhookRequestJob
|
|||||||
})
|
})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def load_url_and_secret(submitter, params)
|
|
||||||
if params['encrypted_config_id']
|
|
||||||
config = EncryptedConfig.find(params['encrypted_config_id'])
|
|
||||||
|
|
||||||
url = config.value
|
|
||||||
|
|
||||||
return if url.blank?
|
|
||||||
|
|
||||||
preferences = Accounts.load_webhook_preferences(submitter.submission.account)
|
|
||||||
|
|
||||||
return if preferences['form.completed'] == false
|
|
||||||
|
|
||||||
secret = EncryptedConfig.find_or_initialize_by(account_id: config.account_id,
|
|
||||||
key: EncryptedConfig::WEBHOOK_SECRET_KEY)&.value.to_h
|
|
||||||
|
|
||||||
[url, secret]
|
|
||||||
elsif params['webhook_url_id']
|
|
||||||
webhook_url = submitter.account.webhook_urls.find(params['webhook_url_id'])
|
|
||||||
|
|
||||||
webhook_url.url if webhook_url.events.include?('form.completed')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
@@ -5,34 +5,28 @@ class SendFormDeclinedWebhookRequestJob
|
|||||||
|
|
||||||
sidekiq_options queue: :webhooks
|
sidekiq_options queue: :webhooks
|
||||||
|
|
||||||
USER_AGENT = 'DocuSeal.co Webhook'
|
USER_AGENT = 'DocuSeal.com Webhook'
|
||||||
|
|
||||||
MAX_ATTEMPTS = 10
|
MAX_ATTEMPTS = 10
|
||||||
|
|
||||||
def perform(params = {})
|
def perform(params = {})
|
||||||
submitter = Submitter.find(params['submitter_id'])
|
submitter = Submitter.find(params['submitter_id'])
|
||||||
|
webhook_url = WebhookUrl.find(params['webhook_url_id'])
|
||||||
|
|
||||||
attempt = params['attempt'].to_i
|
attempt = params['attempt'].to_i
|
||||||
config = Accounts.load_webhook_config(submitter.submission.account)
|
|
||||||
url = config&.value.presence
|
|
||||||
|
|
||||||
return if url.blank?
|
return if webhook_url.url.blank? || webhook_url.events.exclude?('form.declined')
|
||||||
|
|
||||||
preferences = Accounts.load_webhook_preferences(submitter.submission.account)
|
|
||||||
|
|
||||||
return if preferences['form.declined'] == false
|
|
||||||
|
|
||||||
ActiveStorage::Current.url_options = Docuseal.default_url_options
|
ActiveStorage::Current.url_options = Docuseal.default_url_options
|
||||||
|
|
||||||
resp = begin
|
resp = begin
|
||||||
Faraday.post(url,
|
Faraday.post(webhook_url.url,
|
||||||
{
|
{
|
||||||
event_type: 'form.declined',
|
event_type: 'form.declined',
|
||||||
timestamp: Time.current,
|
timestamp: Time.current,
|
||||||
data: Submitters::SerializeForWebhook.call(submitter)
|
data: Submitters::SerializeForWebhook.call(submitter)
|
||||||
}.to_json,
|
}.to_json,
|
||||||
**EncryptedConfig.find_or_initialize_by(account_id: config.account_id,
|
**webhook_url.secret.to_h,
|
||||||
key: EncryptedConfig::WEBHOOK_SECRET_KEY)&.value.to_h,
|
|
||||||
'Content-Type' => 'application/json',
|
'Content-Type' => 'application/json',
|
||||||
'User-Agent' => USER_AGENT)
|
'User-Agent' => USER_AGENT)
|
||||||
rescue Faraday::Error
|
rescue Faraday::Error
|
||||||
@@ -43,6 +37,7 @@ class SendFormDeclinedWebhookRequestJob
|
|||||||
(!Docuseal.multitenant? || submitter.account.account_configs.exists?(key: :plan))
|
(!Docuseal.multitenant? || submitter.account.account_configs.exists?(key: :plan))
|
||||||
SendFormDeclinedWebhookRequestJob.perform_in((2**attempt).minutes, {
|
SendFormDeclinedWebhookRequestJob.perform_in((2**attempt).minutes, {
|
||||||
'submitter_id' => submitter.id,
|
'submitter_id' => submitter.id,
|
||||||
|
'webhook_url_id' => webhook_url.id,
|
||||||
'attempt' => attempt + 1,
|
'attempt' => attempt + 1,
|
||||||
'last_status' => resp&.status.to_i
|
'last_status' => resp&.status.to_i
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -5,34 +5,28 @@ class SendFormStartedWebhookRequestJob
|
|||||||
|
|
||||||
sidekiq_options queue: :webhooks
|
sidekiq_options queue: :webhooks
|
||||||
|
|
||||||
USER_AGENT = 'DocuSeal.co Webhook'
|
USER_AGENT = 'DocuSeal.com Webhook'
|
||||||
|
|
||||||
MAX_ATTEMPTS = 10
|
MAX_ATTEMPTS = 10
|
||||||
|
|
||||||
def perform(params = {})
|
def perform(params = {})
|
||||||
submitter = Submitter.find(params['submitter_id'])
|
submitter = Submitter.find(params['submitter_id'])
|
||||||
|
webhook_url = WebhookUrl.find(params['webhook_url_id'])
|
||||||
|
|
||||||
attempt = params['attempt'].to_i
|
attempt = params['attempt'].to_i
|
||||||
config = Accounts.load_webhook_config(submitter.submission.account)
|
|
||||||
url = config&.value.presence
|
|
||||||
|
|
||||||
return if url.blank?
|
return if webhook_url.url.blank? || webhook_url.events.exclude?('form.started')
|
||||||
|
|
||||||
preferences = Accounts.load_webhook_preferences(submitter.submission.account)
|
|
||||||
|
|
||||||
return if preferences['form.started'] == false
|
|
||||||
|
|
||||||
ActiveStorage::Current.url_options = Docuseal.default_url_options
|
ActiveStorage::Current.url_options = Docuseal.default_url_options
|
||||||
|
|
||||||
resp = begin
|
resp = begin
|
||||||
Faraday.post(url,
|
Faraday.post(webhook_url.url,
|
||||||
{
|
{
|
||||||
event_type: 'form.started',
|
event_type: 'form.started',
|
||||||
timestamp: Time.current,
|
timestamp: Time.current,
|
||||||
data: Submitters::SerializeForWebhook.call(submitter)
|
data: Submitters::SerializeForWebhook.call(submitter)
|
||||||
}.to_json,
|
}.to_json,
|
||||||
**EncryptedConfig.find_or_initialize_by(account_id: config.account_id,
|
**webhook_url.secret.to_h,
|
||||||
key: EncryptedConfig::WEBHOOK_SECRET_KEY)&.value.to_h,
|
|
||||||
'Content-Type' => 'application/json',
|
'Content-Type' => 'application/json',
|
||||||
'User-Agent' => USER_AGENT)
|
'User-Agent' => USER_AGENT)
|
||||||
rescue Faraday::Error
|
rescue Faraday::Error
|
||||||
@@ -43,6 +37,7 @@ class SendFormStartedWebhookRequestJob
|
|||||||
(!Docuseal.multitenant? || submitter.account.account_configs.exists?(key: :plan))
|
(!Docuseal.multitenant? || submitter.account.account_configs.exists?(key: :plan))
|
||||||
SendFormStartedWebhookRequestJob.perform_in((2**attempt).minutes, {
|
SendFormStartedWebhookRequestJob.perform_in((2**attempt).minutes, {
|
||||||
'submitter_id' => submitter.id,
|
'submitter_id' => submitter.id,
|
||||||
|
'webhook_url_id' => webhook_url.id,
|
||||||
'attempt' => attempt + 1,
|
'attempt' => attempt + 1,
|
||||||
'last_status' => resp&.status.to_i
|
'last_status' => resp&.status.to_i
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -5,34 +5,28 @@ class SendFormViewedWebhookRequestJob
|
|||||||
|
|
||||||
sidekiq_options queue: :webhooks
|
sidekiq_options queue: :webhooks
|
||||||
|
|
||||||
USER_AGENT = 'DocuSeal.co Webhook'
|
USER_AGENT = 'DocuSeal.com Webhook'
|
||||||
|
|
||||||
MAX_ATTEMPTS = 10
|
MAX_ATTEMPTS = 10
|
||||||
|
|
||||||
def perform(params = {})
|
def perform(params = {})
|
||||||
submitter = Submitter.find(params['submitter_id'])
|
submitter = Submitter.find(params['submitter_id'])
|
||||||
|
webhook_url = WebhookUrl.find(params['webhook_url_id'])
|
||||||
|
|
||||||
attempt = params['attempt'].to_i
|
attempt = params['attempt'].to_i
|
||||||
config = Accounts.load_webhook_config(submitter.submission.account)
|
|
||||||
url = config&.value.presence
|
|
||||||
|
|
||||||
return if url.blank?
|
return if webhook_url.url.blank? || webhook_url.events.exclude?('form.viewed')
|
||||||
|
|
||||||
preferences = Accounts.load_webhook_preferences(submitter.submission.account)
|
|
||||||
|
|
||||||
return if preferences['form.viewed'] == false
|
|
||||||
|
|
||||||
ActiveStorage::Current.url_options = Docuseal.default_url_options
|
ActiveStorage::Current.url_options = Docuseal.default_url_options
|
||||||
|
|
||||||
resp = begin
|
resp = begin
|
||||||
Faraday.post(url,
|
Faraday.post(webhook_url.url,
|
||||||
{
|
{
|
||||||
event_type: 'form.viewed',
|
event_type: 'form.viewed',
|
||||||
timestamp: Time.current,
|
timestamp: Time.current,
|
||||||
data: Submitters::SerializeForWebhook.call(submitter)
|
data: Submitters::SerializeForWebhook.call(submitter)
|
||||||
}.to_json,
|
}.to_json,
|
||||||
**EncryptedConfig.find_or_initialize_by(account_id: config.account_id,
|
**webhook_url.secret.to_h,
|
||||||
key: EncryptedConfig::WEBHOOK_SECRET_KEY)&.value.to_h,
|
|
||||||
'Content-Type' => 'application/json',
|
'Content-Type' => 'application/json',
|
||||||
'User-Agent' => USER_AGENT)
|
'User-Agent' => USER_AGENT)
|
||||||
rescue Faraday::Error
|
rescue Faraday::Error
|
||||||
@@ -43,6 +37,7 @@ class SendFormViewedWebhookRequestJob
|
|||||||
(!Docuseal.multitenant? || submitter.account.account_configs.exists?(key: :plan))
|
(!Docuseal.multitenant? || submitter.account.account_configs.exists?(key: :plan))
|
||||||
SendFormViewedWebhookRequestJob.perform_in((2**attempt).minutes, {
|
SendFormViewedWebhookRequestJob.perform_in((2**attempt).minutes, {
|
||||||
'submitter_id' => submitter.id,
|
'submitter_id' => submitter.id,
|
||||||
|
'webhook_url_id' => webhook_url.id,
|
||||||
'attempt' => attempt + 1,
|
'attempt' => attempt + 1,
|
||||||
'last_status' => resp&.status.to_i
|
'last_status' => resp&.status.to_i
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -5,33 +5,26 @@ class SendSubmissionArchivedWebhookRequestJob
|
|||||||
|
|
||||||
sidekiq_options queue: :webhooks
|
sidekiq_options queue: :webhooks
|
||||||
|
|
||||||
USER_AGENT = 'DocuSeal.co Webhook'
|
USER_AGENT = 'DocuSeal.com Webhook'
|
||||||
|
|
||||||
MAX_ATTEMPTS = 10
|
MAX_ATTEMPTS = 10
|
||||||
|
|
||||||
def perform(params = {})
|
def perform(params = {})
|
||||||
submission = Submission.find(params['submission_id'])
|
submission = Submission.find(params['submission_id'])
|
||||||
|
webhook_url = WebhookUrl.find(params['webhook_url_id'])
|
||||||
|
|
||||||
attempt = params['attempt'].to_i
|
attempt = params['attempt'].to_i
|
||||||
|
|
||||||
config = Accounts.load_webhook_config(submission.account)
|
return if webhook_url.url.blank? || webhook_url.events.exclude?('submission.archived')
|
||||||
url = config&.value.presence
|
|
||||||
|
|
||||||
return if url.blank?
|
|
||||||
|
|
||||||
preferences = Accounts.load_webhook_preferences(submission.account)
|
|
||||||
|
|
||||||
return if preferences['submission.archived'].blank?
|
|
||||||
|
|
||||||
resp = begin
|
resp = begin
|
||||||
Faraday.post(url,
|
Faraday.post(webhook_url.url,
|
||||||
{
|
{
|
||||||
event_type: 'submission.archived',
|
event_type: 'submission.archived',
|
||||||
timestamp: Time.current,
|
timestamp: Time.current,
|
||||||
data: submission.as_json(only: %i[id archived_at])
|
data: submission.as_json(only: %i[id archived_at])
|
||||||
}.to_json,
|
}.to_json,
|
||||||
**EncryptedConfig.find_or_initialize_by(account_id: config.account_id,
|
**webhook_url.secret.to_h,
|
||||||
key: EncryptedConfig::WEBHOOK_SECRET_KEY)&.value.to_h,
|
|
||||||
'Content-Type' => 'application/json',
|
'Content-Type' => 'application/json',
|
||||||
'User-Agent' => USER_AGENT)
|
'User-Agent' => USER_AGENT)
|
||||||
rescue Faraday::Error
|
rescue Faraday::Error
|
||||||
@@ -42,6 +35,7 @@ class SendSubmissionArchivedWebhookRequestJob
|
|||||||
(!Docuseal.multitenant? || submission.account.account_configs.exists?(key: :plan))
|
(!Docuseal.multitenant? || submission.account.account_configs.exists?(key: :plan))
|
||||||
SendSubmissionArchivedWebhookRequestJob.perform_in((2**attempt).minutes, {
|
SendSubmissionArchivedWebhookRequestJob.perform_in((2**attempt).minutes, {
|
||||||
'submission_id' => submission.id,
|
'submission_id' => submission.id,
|
||||||
|
'webhook_url_id' => webhook_url.id,
|
||||||
'attempt' => attempt + 1,
|
'attempt' => attempt + 1,
|
||||||
'last_status' => resp&.status.to_i
|
'last_status' => resp&.status.to_i
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -5,32 +5,28 @@ class SendSubmissionCompletedWebhookRequestJob
|
|||||||
|
|
||||||
sidekiq_options queue: :webhooks
|
sidekiq_options queue: :webhooks
|
||||||
|
|
||||||
USER_AGENT = 'DocuSeal.co Webhook'
|
USER_AGENT = 'DocuSeal.com Webhook'
|
||||||
|
|
||||||
MAX_ATTEMPTS = 10
|
MAX_ATTEMPTS = 10
|
||||||
|
|
||||||
def perform(params = {})
|
def perform(params = {})
|
||||||
submission = Submission.find(params['submission_id'])
|
submission = Submission.find(params['submission_id'])
|
||||||
|
webhook_url = WebhookUrl.find(params['webhook_url_id'])
|
||||||
|
|
||||||
attempt = params['attempt'].to_i
|
attempt = params['attempt'].to_i
|
||||||
|
|
||||||
webhook_url = submission.account.webhook_urls.find(params['webhook_url_id'])
|
return if webhook_url.url.blank? || webhook_url.events.exclude?('submission.completed')
|
||||||
|
|
||||||
url = webhook_url.url if webhook_url.events.include?('submission.completed')
|
|
||||||
|
|
||||||
return if url.blank?
|
|
||||||
|
|
||||||
resp = begin
|
resp = begin
|
||||||
Faraday.post(url,
|
Faraday.post(webhook_url.url,
|
||||||
{
|
{
|
||||||
event_type: 'submission.completed',
|
event_type: 'submission.completed',
|
||||||
timestamp: Time.current,
|
timestamp: Time.current,
|
||||||
data: Submissions::SerializeForApi.call(submission)
|
data: Submissions::SerializeForApi.call(submission)
|
||||||
}.to_json,
|
}.to_json,
|
||||||
**EncryptedConfig.find_or_initialize_by(account_id: submission.account_id,
|
|
||||||
key: EncryptedConfig::WEBHOOK_SECRET_KEY)&.value.to_h,
|
|
||||||
'Content-Type' => 'application/json',
|
'Content-Type' => 'application/json',
|
||||||
'User-Agent' => USER_AGENT)
|
'User-Agent' => USER_AGENT,
|
||||||
|
**webhook_url.secret.to_h)
|
||||||
rescue Faraday::Error
|
rescue Faraday::Error
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -5,33 +5,26 @@ class SendSubmissionCreatedWebhookRequestJob
|
|||||||
|
|
||||||
sidekiq_options queue: :webhooks
|
sidekiq_options queue: :webhooks
|
||||||
|
|
||||||
USER_AGENT = 'DocuSeal.co Webhook'
|
USER_AGENT = 'DocuSeal.com Webhook'
|
||||||
|
|
||||||
MAX_ATTEMPTS = 10
|
MAX_ATTEMPTS = 10
|
||||||
|
|
||||||
def perform(params = {})
|
def perform(params = {})
|
||||||
submission = Submission.find(params['submission_id'])
|
submission = Submission.find(params['submission_id'])
|
||||||
|
webhook_url = WebhookUrl.find(params['webhook_url_id'])
|
||||||
|
|
||||||
attempt = params['attempt'].to_i
|
attempt = params['attempt'].to_i
|
||||||
|
|
||||||
config = Accounts.load_webhook_config(submission.account)
|
return if webhook_url.url.blank? || webhook_url.events.exclude?('submission.created')
|
||||||
url = config&.value.presence
|
|
||||||
|
|
||||||
return if url.blank?
|
|
||||||
|
|
||||||
preferences = Accounts.load_webhook_preferences(submission.account)
|
|
||||||
|
|
||||||
return if preferences['submission.created'].blank?
|
|
||||||
|
|
||||||
resp = begin
|
resp = begin
|
||||||
Faraday.post(url,
|
Faraday.post(webhook_url.url,
|
||||||
{
|
{
|
||||||
event_type: 'submission.created',
|
event_type: 'submission.created',
|
||||||
timestamp: Time.current,
|
timestamp: Time.current,
|
||||||
data: Submissions::SerializeForApi.call(submission)
|
data: Submissions::SerializeForApi.call(submission)
|
||||||
}.to_json,
|
}.to_json,
|
||||||
**EncryptedConfig.find_or_initialize_by(account_id: config.account_id,
|
**webhook_url.secret.to_h,
|
||||||
key: EncryptedConfig::WEBHOOK_SECRET_KEY)&.value.to_h,
|
|
||||||
'Content-Type' => 'application/json',
|
'Content-Type' => 'application/json',
|
||||||
'User-Agent' => USER_AGENT)
|
'User-Agent' => USER_AGENT)
|
||||||
rescue Faraday::Error
|
rescue Faraday::Error
|
||||||
@@ -42,6 +35,7 @@ class SendSubmissionCreatedWebhookRequestJob
|
|||||||
(!Docuseal.multitenant? || submission.account.account_configs.exists?(key: :plan))
|
(!Docuseal.multitenant? || submission.account.account_configs.exists?(key: :plan))
|
||||||
SendSubmissionCreatedWebhookRequestJob.perform_in((2**attempt).minutes, {
|
SendSubmissionCreatedWebhookRequestJob.perform_in((2**attempt).minutes, {
|
||||||
'submission_id' => submission.id,
|
'submission_id' => submission.id,
|
||||||
|
'webhook_url_id' => webhook_url.id,
|
||||||
'attempt' => attempt + 1,
|
'attempt' => attempt + 1,
|
||||||
'last_status' => resp&.status.to_i
|
'last_status' => resp&.status.to_i
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -5,33 +5,26 @@ class SendTemplateCreatedWebhookRequestJob
|
|||||||
|
|
||||||
sidekiq_options queue: :webhooks
|
sidekiq_options queue: :webhooks
|
||||||
|
|
||||||
USER_AGENT = 'DocuSeal.co Webhook'
|
USER_AGENT = 'DocuSeal.com Webhook'
|
||||||
|
|
||||||
MAX_ATTEMPTS = 10
|
MAX_ATTEMPTS = 10
|
||||||
|
|
||||||
def perform(params = {})
|
def perform(params = {})
|
||||||
template = Template.find(params['template_id'])
|
template = Template.find(params['template_id'])
|
||||||
|
webhook_url = WebhookUrl.find(params['webhook_url_id'])
|
||||||
|
|
||||||
attempt = params['attempt'].to_i
|
attempt = params['attempt'].to_i
|
||||||
|
|
||||||
config = Accounts.load_webhook_config(template.account)
|
return if webhook_url.url.blank? || webhook_url.events.exclude?('template.created')
|
||||||
url = config&.value.presence
|
|
||||||
|
|
||||||
return if url.blank?
|
|
||||||
|
|
||||||
preferences = Accounts.load_webhook_preferences(template.account)
|
|
||||||
|
|
||||||
return if preferences['template.created'].blank?
|
|
||||||
|
|
||||||
resp = begin
|
resp = begin
|
||||||
Faraday.post(url,
|
Faraday.post(webhook_url.url,
|
||||||
{
|
{
|
||||||
event_type: 'template.created',
|
event_type: 'template.created',
|
||||||
timestamp: Time.current,
|
timestamp: Time.current,
|
||||||
data: Templates::SerializeForApi.call(template)
|
data: Templates::SerializeForApi.call(template)
|
||||||
}.to_json,
|
}.to_json,
|
||||||
**EncryptedConfig.find_or_initialize_by(account_id: config.account_id,
|
**webhook_url.secret.to_h,
|
||||||
key: EncryptedConfig::WEBHOOK_SECRET_KEY)&.value.to_h,
|
|
||||||
'Content-Type' => 'application/json',
|
'Content-Type' => 'application/json',
|
||||||
'User-Agent' => USER_AGENT)
|
'User-Agent' => USER_AGENT)
|
||||||
rescue Faraday::Error
|
rescue Faraday::Error
|
||||||
@@ -42,6 +35,7 @@ class SendTemplateCreatedWebhookRequestJob
|
|||||||
(!Docuseal.multitenant? || template.account.account_configs.exists?(key: :plan))
|
(!Docuseal.multitenant? || template.account.account_configs.exists?(key: :plan))
|
||||||
SendTemplateCreatedWebhookRequestJob.perform_in((2**attempt).minutes, {
|
SendTemplateCreatedWebhookRequestJob.perform_in((2**attempt).minutes, {
|
||||||
'template_id' => template.id,
|
'template_id' => template.id,
|
||||||
|
'webhook_url_id' => webhook_url.id,
|
||||||
'attempt' => attempt + 1,
|
'attempt' => attempt + 1,
|
||||||
'last_status' => resp&.status.to_i
|
'last_status' => resp&.status.to_i
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -5,33 +5,26 @@ class SendTemplateUpdatedWebhookRequestJob
|
|||||||
|
|
||||||
sidekiq_options queue: :webhooks
|
sidekiq_options queue: :webhooks
|
||||||
|
|
||||||
USER_AGENT = 'DocuSeal.co Webhook'
|
USER_AGENT = 'DocuSeal.com Webhook'
|
||||||
|
|
||||||
MAX_ATTEMPTS = 10
|
MAX_ATTEMPTS = 10
|
||||||
|
|
||||||
def perform(params = {})
|
def perform(params = {})
|
||||||
template = Template.find(params['template_id'])
|
template = Template.find(params['template_id'])
|
||||||
|
webhook_url = WebhookUrl.find(params['webhook_url_id'])
|
||||||
|
|
||||||
attempt = params['attempt'].to_i
|
attempt = params['attempt'].to_i
|
||||||
|
|
||||||
config = Accounts.load_webhook_config(template.account)
|
return if webhook_url.url.blank? || webhook_url.events.exclude?('template.updated')
|
||||||
url = config&.value.presence
|
|
||||||
|
|
||||||
return if url.blank?
|
|
||||||
|
|
||||||
preferences = Accounts.load_webhook_preferences(template.account)
|
|
||||||
|
|
||||||
return if preferences['template.updated'].blank?
|
|
||||||
|
|
||||||
resp = begin
|
resp = begin
|
||||||
Faraday.post(url,
|
Faraday.post(webhook_url.url,
|
||||||
{
|
{
|
||||||
event_type: 'template.updated',
|
event_type: 'template.updated',
|
||||||
timestamp: Time.current,
|
timestamp: Time.current,
|
||||||
data: Templates::SerializeForApi.call(template)
|
data: Templates::SerializeForApi.call(template)
|
||||||
}.to_json,
|
}.to_json,
|
||||||
**EncryptedConfig.find_or_initialize_by(account_id: config.account_id,
|
**webhook_url.secret.to_h,
|
||||||
key: EncryptedConfig::WEBHOOK_SECRET_KEY)&.value.to_h,
|
|
||||||
'Content-Type' => 'application/json',
|
'Content-Type' => 'application/json',
|
||||||
'User-Agent' => USER_AGENT)
|
'User-Agent' => USER_AGENT)
|
||||||
rescue Faraday::Error
|
rescue Faraday::Error
|
||||||
@@ -42,6 +35,7 @@ class SendTemplateUpdatedWebhookRequestJob
|
|||||||
(!Docuseal.multitenant? || template.account.account_configs.exists?(key: :plan))
|
(!Docuseal.multitenant? || template.account.account_configs.exists?(key: :plan))
|
||||||
SendTemplateUpdatedWebhookRequestJob.perform_in((2**attempt).minutes, {
|
SendTemplateUpdatedWebhookRequestJob.perform_in((2**attempt).minutes, {
|
||||||
'template_id' => template.id,
|
'template_id' => template.id,
|
||||||
|
'webhook_url_id' => webhook_url.id,
|
||||||
'attempt' => attempt + 1,
|
'attempt' => attempt + 1,
|
||||||
'last_status' => resp&.status.to_i
|
'last_status' => resp&.status.to_i
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ class ApplicationMailer < ActionMailer::Base
|
|||||||
after_action :set_message_uuid
|
after_action :set_message_uuid
|
||||||
|
|
||||||
def default_url_options
|
def default_url_options
|
||||||
Docuseal.default_url_options
|
Docuseal.default_url_options.merge(host: ENV.fetch('EMAIL_HOST', Docuseal.default_url_options[:host]))
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_message_metadata
|
def set_message_metadata
|
||||||
|
|||||||
@@ -106,8 +106,11 @@ class SubmitterMailer < ApplicationMailer
|
|||||||
|
|
||||||
@email_config = AccountConfigs.find_for_account(@current_account, AccountConfig::SUBMITTER_DOCUMENTS_COPY_EMAIL_KEY)
|
@email_config = AccountConfigs.find_for_account(@current_account, AccountConfig::SUBMITTER_DOCUMENTS_COPY_EMAIL_KEY)
|
||||||
|
|
||||||
@documents = add_completed_email_attachments!(
|
add_completed_email_attachments!(
|
||||||
submitter, with_audit_log: @submitter.template.preferences['documents_copy_email_attach_audit'] != false &&
|
submitter,
|
||||||
|
with_documents: @submitter.template.preferences['documents_copy_email_attach_documents'] != false &&
|
||||||
|
(@email_config.nil? || @email_config.value['attach_documents'] != false),
|
||||||
|
with_audit_log: @submitter.template.preferences['documents_copy_email_attach_audit'] != false &&
|
||||||
(@email_config.nil? || @email_config.value['attach_audit_log'] != false)
|
(@email_config.nil? || @email_config.value['attach_audit_log'] != false)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -138,9 +141,11 @@ class SubmitterMailer < ApplicationMailer
|
|||||||
private
|
private
|
||||||
|
|
||||||
def build_submitter_reply_to(submitter)
|
def build_submitter_reply_to(submitter)
|
||||||
reply_to =
|
reply_to = submitter.preferences['reply_to'].presence
|
||||||
submitter.preferences['reply_to'].presence ||
|
|
||||||
(submitter.submission.created_by_user || submitter.template.author)&.friendly_name&.sub(/\+\w+@/, '@')
|
if reply_to.blank? && (submitter.submission.created_by_user || submitter.template.author)&.email != submitter.email
|
||||||
|
reply_to = (submitter.submission.created_by_user || submitter.template.author)&.friendly_name&.sub(/\+\w+@/, '@')
|
||||||
|
end
|
||||||
|
|
||||||
return nil if reply_to.to_s.match?(NO_REPLY_REGEXP)
|
return nil if reply_to.to_s.match?(NO_REPLY_REGEXP)
|
||||||
|
|
||||||
@@ -150,6 +155,9 @@ class SubmitterMailer < ApplicationMailer
|
|||||||
def add_completed_email_attachments!(submitter, with_audit_log: true, with_documents: true)
|
def add_completed_email_attachments!(submitter, with_audit_log: true, with_documents: true)
|
||||||
documents = with_documents ? Submitters.select_attachments_for_download(submitter) : []
|
documents = with_documents ? Submitters.select_attachments_for_download(submitter) : []
|
||||||
|
|
||||||
|
filename_format = AccountConfig.find_or_initialize_by(account_id: submitter.account_id,
|
||||||
|
key: AccountConfig::DOCUMENT_FILENAME_FORMAT_KEY)&.value
|
||||||
|
|
||||||
total_size = 0
|
total_size = 0
|
||||||
audit_trail_data = nil
|
audit_trail_data = nil
|
||||||
|
|
||||||
@@ -159,9 +167,14 @@ class SubmitterMailer < ApplicationMailer
|
|||||||
total_size = audit_trail_data.size
|
total_size = audit_trail_data.size
|
||||||
end
|
end
|
||||||
|
|
||||||
total_size = add_attachments_with_size_limit(documents, total_size)
|
total_size = add_attachments_with_size_limit(submitter, documents, total_size, filename_format)
|
||||||
|
|
||||||
attachments[submitter.submission.audit_trail.filename.to_s.tr('"', "'")] = audit_trail_data if audit_trail_data
|
if audit_trail_data
|
||||||
|
audit_trail_filename =
|
||||||
|
Submitters.build_document_filename(submitter, submitter.submission.audit_trail.blob, filename_format)
|
||||||
|
|
||||||
|
attachments[audit_trail_filename.tr('"', "'")] = audit_trail_data
|
||||||
|
end
|
||||||
|
|
||||||
if with_documents
|
if with_documents
|
||||||
file_fields = submitter.submission.template_fields.select { |e| e['type'].in?(%w[file payment]) }
|
file_fields = submitter.submission.template_fields.select { |e| e['type'].in?(%w[file payment]) }
|
||||||
@@ -170,7 +183,7 @@ class SubmitterMailer < ApplicationMailer
|
|||||||
storage_attachments =
|
storage_attachments =
|
||||||
submitter.attachments.where(uuid: submitter.values.values_at(*file_fields.pluck('uuid')).flatten)
|
submitter.attachments.where(uuid: submitter.values.values_at(*file_fields.pluck('uuid')).flatten)
|
||||||
|
|
||||||
add_attachments_with_size_limit(storage_attachments, total_size)
|
add_attachments_with_size_limit(submitter, storage_attachments, total_size)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -181,7 +194,7 @@ class SubmitterMailer < ApplicationMailer
|
|||||||
user.role == 'integration' ? user.friendly_name.sub(/\+\w+@/, '@') : user.friendly_name
|
user.role == 'integration' ? user.friendly_name.sub(/\+\w+@/, '@') : user.friendly_name
|
||||||
end
|
end
|
||||||
|
|
||||||
def add_attachments_with_size_limit(storage_attachments, current_size)
|
def add_attachments_with_size_limit(submitter, storage_attachments, current_size, filename_format = nil)
|
||||||
total_size = current_size
|
total_size = current_size
|
||||||
|
|
||||||
storage_attachments.each do |attachment|
|
storage_attachments.each do |attachment|
|
||||||
@@ -189,7 +202,8 @@ class SubmitterMailer < ApplicationMailer
|
|||||||
|
|
||||||
break if total_size >= MAX_ATTACHMENTS_SIZE
|
break if total_size >= MAX_ATTACHMENTS_SIZE
|
||||||
|
|
||||||
attachments[attachment.filename.to_s.tr('"', "'")] = attachment.download
|
filename = Submitters.build_document_filename(submitter, attachment.blob, filename_format)
|
||||||
|
attachments[filename.to_s.tr('"', "'")] = attachment.download
|
||||||
end
|
end
|
||||||
|
|
||||||
total_size
|
total_size
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ class AccountConfig < ApplicationRecord
|
|||||||
FORM_WITH_CONFETTI_KEY = 'form_with_confetti'
|
FORM_WITH_CONFETTI_KEY = 'form_with_confetti'
|
||||||
FORM_PREFILL_SIGNATURE_KEY = 'form_prefill_signature'
|
FORM_PREFILL_SIGNATURE_KEY = 'form_prefill_signature'
|
||||||
ESIGNING_PREFERENCE_KEY = 'esigning_preference'
|
ESIGNING_PREFERENCE_KEY = 'esigning_preference'
|
||||||
WEBHOOK_PREFERENCES_KEY = 'webhook_preferences'
|
|
||||||
DOWNLOAD_LINKS_AUTH_KEY = 'download_links_auth'
|
DOWNLOAD_LINKS_AUTH_KEY = 'download_links_auth'
|
||||||
FORCE_SSO_AUTH_KEY = 'force_sso_auth'
|
FORCE_SSO_AUTH_KEY = 'force_sso_auth'
|
||||||
FLATTEN_RESULT_PDF_KEY = 'flatten_result_pdf'
|
FLATTEN_RESULT_PDF_KEY = 'flatten_result_pdf'
|
||||||
@@ -44,6 +43,8 @@ class AccountConfig < ApplicationRecord
|
|||||||
REQUIRE_SIGNING_REASON_KEY = 'require_signing_reason'
|
REQUIRE_SIGNING_REASON_KEY = 'require_signing_reason'
|
||||||
REUSE_SIGNATURE_KEY = 'reuse_signature'
|
REUSE_SIGNATURE_KEY = 'reuse_signature'
|
||||||
COMBINE_PDF_RESULT_KEY = 'combine_pdf_result_key'
|
COMBINE_PDF_RESULT_KEY = 'combine_pdf_result_key'
|
||||||
|
DOCUMENT_FILENAME_FORMAT_KEY = 'document_filename_format'
|
||||||
|
POLICY_LINKS_KEY = 'policy_links'
|
||||||
|
|
||||||
DEFAULT_VALUES = {
|
DEFAULT_VALUES = {
|
||||||
SUBMITTER_INVITATION_EMAIL_KEY => lambda {
|
SUBMITTER_INVITATION_EMAIL_KEY => lambda {
|
||||||
|
|||||||
@@ -26,9 +26,7 @@ class EncryptedConfig < ApplicationRecord
|
|||||||
EMAIL_SMTP_KEY = 'action_mailer_smtp',
|
EMAIL_SMTP_KEY = 'action_mailer_smtp',
|
||||||
ESIGN_CERTS_KEY = 'esign_certs',
|
ESIGN_CERTS_KEY = 'esign_certs',
|
||||||
TIMESTAMP_SERVER_URL_KEY = 'timestamp_server_url',
|
TIMESTAMP_SERVER_URL_KEY = 'timestamp_server_url',
|
||||||
APP_URL_KEY = 'app_url',
|
APP_URL_KEY = 'app_url'
|
||||||
WEBHOOK_URL_KEY = 'webhook_url',
|
|
||||||
WEBHOOK_SECRET_KEY = 'webhook_secret'
|
|
||||||
].freeze
|
].freeze
|
||||||
|
|
||||||
belongs_to :account
|
belongs_to :account
|
||||||
|
|||||||
@@ -57,13 +57,20 @@ class Submission < ApplicationRecord
|
|||||||
|
|
||||||
has_many_attached :preview_documents
|
has_many_attached :preview_documents
|
||||||
|
|
||||||
|
has_many :template_accesses, primary_key: :template_id, foreign_key: :template_id, dependent: nil, inverse_of: false
|
||||||
|
|
||||||
has_many :template_schema_documents,
|
has_many :template_schema_documents,
|
||||||
->(e) { where(uuid: (e.template_schema.presence || e.template.schema).pluck('attachment_uuid')) },
|
->(e) { where(uuid: (e.template_schema.presence || e.template.schema).pluck('attachment_uuid')) },
|
||||||
through: :template, source: :documents_attachments
|
through: :template, source: :documents_attachments
|
||||||
|
|
||||||
scope :active, -> { where(archived_at: nil) }
|
scope :active, -> { where(archived_at: nil) }
|
||||||
scope :pending, -> { joins(:submitters).where(submitters: { completed_at: nil }).distinct }
|
scope :pending, -> { joins(:submitters).where(submitters: { completed_at: nil }).group(:id) }
|
||||||
scope :completed, -> { where.not(id: pending.select(:submission_id)) }
|
scope :completed, lambda {
|
||||||
|
where.not(Submitter.where(Submitter.arel_table[:submission_id].eq(Submission.arel_table[:id])
|
||||||
|
.and(Submitter.arel_table[:completed_at].eq(nil))).select(1).arel.exists)
|
||||||
|
}
|
||||||
|
scope :declined, -> { joins(:submitters).where.not(submitters: { declined_at: nil }).group(:id) }
|
||||||
|
scope :expired, -> { where(expire_at: ..Time.current) }
|
||||||
|
|
||||||
enum :source, {
|
enum :source, {
|
||||||
invite: 'invite',
|
invite: 'invite',
|
||||||
|
|||||||
@@ -46,6 +46,8 @@ class SubmissionEvent < ApplicationRecord
|
|||||||
click_sms: 'click_sms',
|
click_sms: 'click_sms',
|
||||||
phone_verified: 'phone_verified',
|
phone_verified: 'phone_verified',
|
||||||
start_form: 'start_form',
|
start_form: 'start_form',
|
||||||
|
start_verification: 'start_verification',
|
||||||
|
complete_verification: 'complete_verification',
|
||||||
view_form: 'view_form',
|
view_form: 'view_form',
|
||||||
invite_party: 'invite_party',
|
invite_party: 'invite_party',
|
||||||
complete_form: 'complete_form',
|
complete_form: 'complete_form',
|
||||||
|
|||||||
@@ -53,6 +53,8 @@ class Submitter < ApplicationRecord
|
|||||||
|
|
||||||
has_many_attached :documents
|
has_many_attached :documents
|
||||||
has_many_attached :attachments
|
has_many_attached :attachments
|
||||||
|
has_many_attached :preview_documents
|
||||||
|
has_many :template_accesses, through: :template
|
||||||
|
|
||||||
has_many :document_generation_events, dependent: :destroy
|
has_many :document_generation_events, dependent: :destroy
|
||||||
has_many :submission_events, dependent: :destroy
|
has_many :submission_events, dependent: :destroy
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ class Template < ApplicationRecord
|
|||||||
|
|
||||||
has_many :submissions, dependent: :destroy
|
has_many :submissions, dependent: :destroy
|
||||||
has_many :template_sharings, dependent: :destroy
|
has_many :template_sharings, dependent: :destroy
|
||||||
|
has_many :template_accesses, dependent: :destroy
|
||||||
|
|
||||||
scope :active, -> { where(archived_at: nil) }
|
scope :active, -> { where(archived_at: nil) }
|
||||||
scope :archived, -> { where.not(archived_at: nil) }
|
scope :archived, -> { where.not(archived_at: nil) }
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# == Schema Information
|
||||||
|
#
|
||||||
|
# Table name: template_accesses
|
||||||
|
#
|
||||||
|
# id :bigint not null, primary key
|
||||||
|
# created_at :datetime not null
|
||||||
|
# updated_at :datetime not null
|
||||||
|
# template_id :bigint not null
|
||||||
|
# user_id :bigint not null
|
||||||
|
#
|
||||||
|
# Indexes
|
||||||
|
#
|
||||||
|
# index_template_accesses_on_template_id_and_user_id (template_id,user_id) UNIQUE
|
||||||
|
#
|
||||||
|
# Foreign Keys
|
||||||
|
#
|
||||||
|
# fk_rails_... (template_id => templates.id)
|
||||||
|
#
|
||||||
|
class TemplateAccess < ApplicationRecord
|
||||||
|
ADMIN_USER_ID = -1
|
||||||
|
|
||||||
|
belongs_to :template
|
||||||
|
belongs_to :user, optional: true
|
||||||
|
end
|
||||||
@@ -57,6 +57,7 @@ class User < ApplicationRecord
|
|||||||
|
|
||||||
belongs_to :account
|
belongs_to :account
|
||||||
has_one :access_token, dependent: :destroy
|
has_one :access_token, dependent: :destroy
|
||||||
|
has_many :access_tokens, dependent: :destroy
|
||||||
has_many :templates, dependent: :destroy, foreign_key: :author_id, inverse_of: :author
|
has_many :templates, dependent: :destroy, foreign_key: :author_id, inverse_of: :author
|
||||||
has_many :template_folders, dependent: :destroy, foreign_key: :author_id, inverse_of: :author
|
has_many :template_folders, dependent: :destroy, foreign_key: :author_id, inverse_of: :author
|
||||||
has_many :user_configs, dependent: :destroy
|
has_many :user_configs, dependent: :destroy
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ class UserConfig < ApplicationRecord
|
|||||||
SIGNATURE_KEY = 'signature'
|
SIGNATURE_KEY = 'signature'
|
||||||
INITIALS_KEY = 'initials'
|
INITIALS_KEY = 'initials'
|
||||||
RECEIVE_COMPLETED_EMAIL = 'receive_completed_email'
|
RECEIVE_COMPLETED_EMAIL = 'receive_completed_email'
|
||||||
|
RECEIVE_DECLINED_EMAIL = 'receive_declined_email'
|
||||||
|
|
||||||
belongs_to :user
|
belongs_to :user
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#
|
#
|
||||||
# id :bigint not null, primary key
|
# id :bigint not null, primary key
|
||||||
# events :text not null
|
# events :text not null
|
||||||
|
# secret :text not null
|
||||||
# sha1 :string not null
|
# sha1 :string not null
|
||||||
# url :text not null
|
# url :text not null
|
||||||
# created_at :datetime not null
|
# created_at :datetime not null
|
||||||
@@ -22,15 +23,28 @@
|
|||||||
# fk_rails_... (account_id => accounts.id)
|
# fk_rails_... (account_id => accounts.id)
|
||||||
#
|
#
|
||||||
class WebhookUrl < ApplicationRecord
|
class WebhookUrl < ApplicationRecord
|
||||||
|
EVENTS = %w[
|
||||||
|
form.viewed
|
||||||
|
form.started
|
||||||
|
form.completed
|
||||||
|
form.declined
|
||||||
|
template.created
|
||||||
|
template.updated
|
||||||
|
submission.created
|
||||||
|
submission.archived
|
||||||
|
].freeze
|
||||||
|
|
||||||
belongs_to :account
|
belongs_to :account
|
||||||
|
|
||||||
attribute :events, :string, default: -> { [] }
|
attribute :events, :string, default: -> { %w[form.viewed form.started form.completed form.declined] }
|
||||||
|
attribute :secret, :string, default: -> { {} }
|
||||||
|
|
||||||
serialize :events, coder: JSON
|
serialize :events, coder: JSON
|
||||||
|
serialize :secret, coder: JSON
|
||||||
|
|
||||||
before_validation :set_sha1
|
before_validation :set_sha1
|
||||||
|
|
||||||
encrypts :url
|
encrypts :url, :secret
|
||||||
|
|
||||||
def set_sha1
|
def set_sha1
|
||||||
self.sha1 = Digest::SHA1.hexdigest(url)
|
self.sha1 = Digest::SHA1.hexdigest(url)
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
<%= content_for(:canonical_url, new_user_session_url) %>
|
||||||
<div class="max-w-lg mx-auto px-2">
|
<div class="max-w-lg mx-auto px-2">
|
||||||
<%= render 'devise/shared/select_server' if Docuseal.multitenant? %>
|
<%= render 'devise/shared/select_server' if Docuseal.multitenant? %>
|
||||||
<h1 class="text-4xl font-bold text-center mt-8"><%= t('sign_in') %></h1>
|
<h1 class="text-4xl font-bold text-center mt-8"><%= t('sign_in') %></h1>
|
||||||
|
|||||||
Loaded 100 of 233 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user