mirror of
https://github.com/docusealco/docuseal.git
synced 2026-08-07 07:14:43 +00:00
Compare commits
75 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a2d8b85549 | |||
| 4d68783b82 | |||
| b05c3b2030 | |||
| 784ae9aa4b | |||
| 03b5956cbc | |||
| 2876c9a18e | |||
| c4b7be3925 | |||
| e2ffbdac1d | |||
| 0d197bb2eb | |||
| 55089bee7f | |||
| b96fe74db1 | |||
| 2fc1ba3037 | |||
| c67d37207e | |||
| dba50cd1bf | |||
| edf2c0d9ed | |||
| 35f7c36f28 | |||
| 3a24ecac63 | |||
| 50ec7682b5 | |||
| 3667993caf | |||
| 68e8cd1188 | |||
| 3eebf6d2cf | |||
| 34d0e00910 | |||
| b8ab7190d2 | |||
| 59fe8a7917 | |||
| d56070827b | |||
| 74dbd4bed3 | |||
| 807e82b21e | |||
| 66862e93c8 | |||
| 270461ad3b | |||
| 3edd1ab32d | |||
| e77998e53c | |||
| 1421bc2bce | |||
| cbbbfdda80 | |||
| 2d510081be | |||
| 704893ee1d | |||
| 88612913a6 | |||
| ce6823ed38 | |||
| 9074d23b3c | |||
| 76d0e76fd5 | |||
| 5ec8e4ccc7 | |||
| b8c29324b8 | |||
| 972f38242e | |||
| 7ce6c29f4d | |||
| 2c6ddd7f90 | |||
| ae50b2e323 | |||
| 152cbc11a6 | |||
| 3a406de655 | |||
| 2e31cda08c | |||
| 094b7f47bd | |||
| 99ac349ecc | |||
| 8830e03cf3 | |||
| 706f3d6d65 | |||
| 12b49f5fa7 | |||
| 85327760b9 | |||
| c94ed9c3d4 | |||
| bc50735d2a | |||
| d05e125fa9 | |||
| 48d595ca81 | |||
| a156de5e75 | |||
| a7003bf4d0 | |||
| 5fe75c84ff | |||
| 4549a04fc5 | |||
| 0ea6b912a3 | |||
| 9b86fa3d40 | |||
| 374f66c59f | |||
| da43d881f1 | |||
| 9cbfa3a0ba | |||
| 77a925f33b | |||
| 3f7d7beedd | |||
| 40d91df732 | |||
| c8e783a210 | |||
| 61fc24d35d | |||
| 73734b0de7 | |||
| 09bd831d54 | |||
| 2c9f1b999e |
@@ -30,7 +30,9 @@ jobs:
|
|||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
- name: Create .version file
|
- name: Create .version file
|
||||||
run: echo ${{ github.ref_name }} > .version
|
env:
|
||||||
|
REF_NAME: ${{ github.ref_name }}
|
||||||
|
run: echo "$REF_NAME" > .version
|
||||||
|
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
|
|||||||
+83
-82
@@ -1,31 +1,31 @@
|
|||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
action_text-trix (2.1.18)
|
action_text-trix (2.1.19)
|
||||||
railties
|
railties
|
||||||
actioncable (8.1.3)
|
actioncable (8.1.3.1)
|
||||||
actionpack (= 8.1.3)
|
actionpack (= 8.1.3.1)
|
||||||
activesupport (= 8.1.3)
|
activesupport (= 8.1.3.1)
|
||||||
nio4r (~> 2.0)
|
nio4r (~> 2.0)
|
||||||
websocket-driver (>= 0.6.1)
|
websocket-driver (>= 0.6.1)
|
||||||
zeitwerk (~> 2.6)
|
zeitwerk (~> 2.6)
|
||||||
actionmailbox (8.1.3)
|
actionmailbox (8.1.3.1)
|
||||||
actionpack (= 8.1.3)
|
actionpack (= 8.1.3.1)
|
||||||
activejob (= 8.1.3)
|
activejob (= 8.1.3.1)
|
||||||
activerecord (= 8.1.3)
|
activerecord (= 8.1.3.1)
|
||||||
activestorage (= 8.1.3)
|
activestorage (= 8.1.3.1)
|
||||||
activesupport (= 8.1.3)
|
activesupport (= 8.1.3.1)
|
||||||
mail (>= 2.8.0)
|
mail (>= 2.8.0)
|
||||||
actionmailer (8.1.3)
|
actionmailer (8.1.3.1)
|
||||||
actionpack (= 8.1.3)
|
actionpack (= 8.1.3.1)
|
||||||
actionview (= 8.1.3)
|
actionview (= 8.1.3.1)
|
||||||
activejob (= 8.1.3)
|
activejob (= 8.1.3.1)
|
||||||
activesupport (= 8.1.3)
|
activesupport (= 8.1.3.1)
|
||||||
mail (>= 2.8.0)
|
mail (>= 2.8.0)
|
||||||
rails-dom-testing (~> 2.2)
|
rails-dom-testing (~> 2.2)
|
||||||
actionpack (8.1.3)
|
actionpack (8.1.3.1)
|
||||||
actionview (= 8.1.3)
|
actionview (= 8.1.3.1)
|
||||||
activesupport (= 8.1.3)
|
activesupport (= 8.1.3.1)
|
||||||
nokogiri (>= 1.8.5)
|
nokogiri (>= 1.8.5)
|
||||||
rack (>= 2.2.4)
|
rack (>= 2.2.4)
|
||||||
rack-session (>= 1.0.1)
|
rack-session (>= 1.0.1)
|
||||||
@@ -33,36 +33,36 @@ GEM
|
|||||||
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 (8.1.3)
|
actiontext (8.1.3.1)
|
||||||
action_text-trix (~> 2.1.15)
|
action_text-trix (~> 2.1.15)
|
||||||
actionpack (= 8.1.3)
|
actionpack (= 8.1.3.1)
|
||||||
activerecord (= 8.1.3)
|
activerecord (= 8.1.3.1)
|
||||||
activestorage (= 8.1.3)
|
activestorage (= 8.1.3.1)
|
||||||
activesupport (= 8.1.3)
|
activesupport (= 8.1.3.1)
|
||||||
globalid (>= 0.6.0)
|
globalid (>= 0.6.0)
|
||||||
nokogiri (>= 1.8.5)
|
nokogiri (>= 1.8.5)
|
||||||
actionview (8.1.3)
|
actionview (8.1.3.1)
|
||||||
activesupport (= 8.1.3)
|
activesupport (= 8.1.3.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 (8.1.3)
|
activejob (8.1.3.1)
|
||||||
activesupport (= 8.1.3)
|
activesupport (= 8.1.3.1)
|
||||||
globalid (>= 0.3.6)
|
globalid (>= 0.3.6)
|
||||||
activemodel (8.1.3)
|
activemodel (8.1.3.1)
|
||||||
activesupport (= 8.1.3)
|
activesupport (= 8.1.3.1)
|
||||||
activerecord (8.1.3)
|
activerecord (8.1.3.1)
|
||||||
activemodel (= 8.1.3)
|
activemodel (= 8.1.3.1)
|
||||||
activesupport (= 8.1.3)
|
activesupport (= 8.1.3.1)
|
||||||
timeout (>= 0.4.0)
|
timeout (>= 0.4.0)
|
||||||
activestorage (8.1.3)
|
activestorage (8.1.3.1)
|
||||||
actionpack (= 8.1.3)
|
actionpack (= 8.1.3.1)
|
||||||
activejob (= 8.1.3)
|
activejob (= 8.1.3.1)
|
||||||
activerecord (= 8.1.3)
|
activerecord (= 8.1.3.1)
|
||||||
activesupport (= 8.1.3)
|
activesupport (= 8.1.3.1)
|
||||||
marcel (~> 1.0)
|
marcel (~> 1.0)
|
||||||
activesupport (8.1.3)
|
activesupport (8.1.3.1)
|
||||||
base64
|
base64
|
||||||
bigdecimal
|
bigdecimal
|
||||||
concurrent-ruby (~> 1.0, >= 1.3.1)
|
concurrent-ruby (~> 1.0, >= 1.3.1)
|
||||||
@@ -144,12 +144,12 @@ GEM
|
|||||||
cldr-plurals-runtime-rb (1.1.0)
|
cldr-plurals-runtime-rb (1.1.0)
|
||||||
cmdparse (3.0.7)
|
cmdparse (3.0.7)
|
||||||
coderay (1.1.3)
|
coderay (1.1.3)
|
||||||
concurrent-ruby (1.3.7)
|
concurrent-ruby (1.3.8)
|
||||||
connection_pool (3.0.2)
|
connection_pool (3.0.2)
|
||||||
crack (1.0.1)
|
crack (1.0.1)
|
||||||
bigdecimal
|
bigdecimal
|
||||||
rexml
|
rexml
|
||||||
crass (1.0.6)
|
crass (1.0.7)
|
||||||
csv (3.3.5)
|
csv (3.3.5)
|
||||||
csv-safe (3.3.1)
|
csv-safe (3.3.1)
|
||||||
csv (~> 3.0)
|
csv (~> 3.0)
|
||||||
@@ -179,7 +179,7 @@ GEM
|
|||||||
dotenv (3.2.0)
|
dotenv (3.2.0)
|
||||||
drb (2.2.3)
|
drb (2.2.3)
|
||||||
email_typo (0.2.3)
|
email_typo (0.2.3)
|
||||||
erb (6.0.4)
|
erb (6.0.6)
|
||||||
erb_lint (0.9.0)
|
erb_lint (0.9.0)
|
||||||
activesupport
|
activesupport
|
||||||
better_html (>= 2.0.1)
|
better_html (>= 2.0.1)
|
||||||
@@ -217,7 +217,7 @@ GEM
|
|||||||
foreman (0.90.0)
|
foreman (0.90.0)
|
||||||
thor (~> 1.4)
|
thor (~> 1.4)
|
||||||
geom2d (0.4.1)
|
geom2d (0.4.1)
|
||||||
globalid (1.3.0)
|
globalid (1.4.0)
|
||||||
activesupport (>= 6.1)
|
activesupport (>= 6.1)
|
||||||
google-apis-core (1.0.2)
|
google-apis-core (1.0.2)
|
||||||
addressable (~> 2.8, >= 2.8.7)
|
addressable (~> 2.8, >= 2.8.7)
|
||||||
@@ -262,7 +262,7 @@ GEM
|
|||||||
geom2d (~> 0.4, >= 0.4.1)
|
geom2d (~> 0.4, >= 0.4.1)
|
||||||
openssl (>= 2.2.1)
|
openssl (>= 2.2.1)
|
||||||
strscan (>= 3.1.2)
|
strscan (>= 3.1.2)
|
||||||
i18n (1.14.8)
|
i18n (1.15.2)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
io-console (0.8.2)
|
io-console (0.8.2)
|
||||||
irb (1.18.0)
|
irb (1.18.0)
|
||||||
@@ -271,7 +271,7 @@ GEM
|
|||||||
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.19.9)
|
json (2.21.1)
|
||||||
jwt (3.2.0)
|
jwt (3.2.0)
|
||||||
base64
|
base64
|
||||||
language_server-protocol (3.17.0.5)
|
language_server-protocol (3.17.0.5)
|
||||||
@@ -293,27 +293,27 @@ GEM
|
|||||||
activesupport (>= 4)
|
activesupport (>= 4)
|
||||||
railties (>= 4)
|
railties (>= 4)
|
||||||
request_store (~> 1.0)
|
request_store (~> 1.0)
|
||||||
loofah (2.25.1)
|
loofah (2.25.2)
|
||||||
crass (~> 1.0.2)
|
crass (~> 1.0.2)
|
||||||
nokogiri (>= 1.12.0)
|
nokogiri (>= 1.12.0)
|
||||||
mail (2.9.0)
|
mail (2.9.1)
|
||||||
logger
|
logger
|
||||||
mini_mime (>= 0.1.1)
|
mini_mime (>= 0.1.1)
|
||||||
net-imap
|
net-imap
|
||||||
net-pop
|
net-pop
|
||||||
net-smtp
|
net-smtp
|
||||||
marcel (1.1.0)
|
marcel (1.2.1)
|
||||||
matrix (0.4.3)
|
matrix (0.4.3)
|
||||||
method_source (1.1.0)
|
method_source (1.1.0)
|
||||||
mini_mime (1.1.5)
|
mini_mime (1.1.5)
|
||||||
minitest (6.0.6)
|
minitest (6.0.6)
|
||||||
drb (~> 2.0)
|
drb (~> 2.0)
|
||||||
prism (~> 1.5)
|
prism (~> 1.5)
|
||||||
msgpack (1.8.0)
|
msgpack (1.8.4)
|
||||||
multi_json (1.19.1)
|
multi_json (1.19.1)
|
||||||
net-http (0.9.1)
|
net-http (0.9.1)
|
||||||
uri (>= 0.11.1)
|
uri (>= 0.11.1)
|
||||||
net-imap (0.6.4.1)
|
net-imap (0.6.6)
|
||||||
date
|
date
|
||||||
net-protocol
|
net-protocol
|
||||||
net-pop (0.1.2)
|
net-pop (0.1.2)
|
||||||
@@ -344,7 +344,7 @@ GEM
|
|||||||
orm_adapter (0.5.0)
|
orm_adapter (0.5.0)
|
||||||
os (1.1.4)
|
os (1.1.4)
|
||||||
package_json (0.2.0)
|
package_json (0.2.0)
|
||||||
pagy (43.4.4)
|
pagy (43.6.1)
|
||||||
json
|
json
|
||||||
uri
|
uri
|
||||||
yaml
|
yaml
|
||||||
@@ -357,7 +357,7 @@ GEM
|
|||||||
pg (1.6.3-arm64-darwin)
|
pg (1.6.3-arm64-darwin)
|
||||||
pg (1.6.3-x86_64-linux)
|
pg (1.6.3-x86_64-linux)
|
||||||
pg (1.6.3-x86_64-linux-musl)
|
pg (1.6.3-x86_64-linux-musl)
|
||||||
pp (0.6.3)
|
pp (0.6.4)
|
||||||
prettyprint
|
prettyprint
|
||||||
pretender (1.0.0)
|
pretender (1.0.0)
|
||||||
actionpack (>= 7.2)
|
actionpack (>= 7.2)
|
||||||
@@ -369,9 +369,6 @@ GEM
|
|||||||
reline (>= 0.6.0)
|
reline (>= 0.6.0)
|
||||||
pry-rails (0.3.11)
|
pry-rails (0.3.11)
|
||||||
pry (>= 0.13.0)
|
pry (>= 0.13.0)
|
||||||
psych (5.3.1)
|
|
||||||
date
|
|
||||||
stringio
|
|
||||||
public_suffix (7.0.5)
|
public_suffix (7.0.5)
|
||||||
puma (8.0.2)
|
puma (8.0.2)
|
||||||
nio4r (~> 2.0)
|
nio4r (~> 2.0)
|
||||||
@@ -386,33 +383,33 @@ GEM
|
|||||||
rack (>= 1.3)
|
rack (>= 1.3)
|
||||||
rackup (2.3.1)
|
rackup (2.3.1)
|
||||||
rack (>= 3)
|
rack (>= 3)
|
||||||
rails (8.1.3)
|
rails (8.1.3.1)
|
||||||
actioncable (= 8.1.3)
|
actioncable (= 8.1.3.1)
|
||||||
actionmailbox (= 8.1.3)
|
actionmailbox (= 8.1.3.1)
|
||||||
actionmailer (= 8.1.3)
|
actionmailer (= 8.1.3.1)
|
||||||
actionpack (= 8.1.3)
|
actionpack (= 8.1.3.1)
|
||||||
actiontext (= 8.1.3)
|
actiontext (= 8.1.3.1)
|
||||||
actionview (= 8.1.3)
|
actionview (= 8.1.3.1)
|
||||||
activejob (= 8.1.3)
|
activejob (= 8.1.3.1)
|
||||||
activemodel (= 8.1.3)
|
activemodel (= 8.1.3.1)
|
||||||
activerecord (= 8.1.3)
|
activerecord (= 8.1.3.1)
|
||||||
activestorage (= 8.1.3)
|
activestorage (= 8.1.3.1)
|
||||||
activesupport (= 8.1.3)
|
activesupport (= 8.1.3.1)
|
||||||
bundler (>= 1.15.0)
|
bundler (>= 1.15.0)
|
||||||
railties (= 8.1.3)
|
railties (= 8.1.3.1)
|
||||||
rails-dom-testing (2.3.0)
|
rails-dom-testing (2.3.0)
|
||||||
activesupport (>= 5.0.0)
|
activesupport (>= 5.0.0)
|
||||||
minitest
|
minitest
|
||||||
nokogiri (>= 1.6)
|
nokogiri (>= 1.6)
|
||||||
rails-html-sanitizer (1.7.0)
|
rails-html-sanitizer (1.7.1)
|
||||||
loofah (~> 2.25)
|
loofah (~> 2.25, >= 2.25.2)
|
||||||
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)
|
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 (8.1.0)
|
rails-i18n (8.1.0)
|
||||||
i18n (>= 0.7, < 2)
|
i18n (>= 0.7, < 2)
|
||||||
railties (>= 8.0.0, < 9)
|
railties (>= 8.0.0, < 9)
|
||||||
railties (8.1.3)
|
railties (8.1.3.1)
|
||||||
actionpack (= 8.1.3)
|
actionpack (= 8.1.3.1)
|
||||||
activesupport (= 8.1.3)
|
activesupport (= 8.1.3.1)
|
||||||
irb (~> 1.13)
|
irb (~> 1.13)
|
||||||
rackup (>= 1.0.0)
|
rackup (>= 1.0.0)
|
||||||
rake (>= 12.2)
|
rake (>= 12.2)
|
||||||
@@ -421,9 +418,14 @@ GEM
|
|||||||
zeitwerk (~> 2.6)
|
zeitwerk (~> 2.6)
|
||||||
rainbow (3.1.1)
|
rainbow (3.1.1)
|
||||||
rake (13.4.2)
|
rake (13.4.2)
|
||||||
rdoc (7.2.0)
|
rbs (4.1.0)
|
||||||
|
logger
|
||||||
|
prism (>= 1.6.0)
|
||||||
|
tsort
|
||||||
|
rdoc (8.0.0)
|
||||||
erb
|
erb
|
||||||
psych (>= 4.0.0)
|
prism (>= 1.6.0)
|
||||||
|
rbs (>= 4.0.0)
|
||||||
tsort
|
tsort
|
||||||
redis-client (0.29.0)
|
redis-client (0.29.0)
|
||||||
connection_pool
|
connection_pool
|
||||||
@@ -525,12 +527,11 @@ GEM
|
|||||||
simplecov-html (0.13.2)
|
simplecov-html (0.13.2)
|
||||||
simplecov_json_formatter (0.1.4)
|
simplecov_json_formatter (0.1.4)
|
||||||
smart_properties (1.17.0)
|
smart_properties (1.17.0)
|
||||||
sqlite3 (2.9.2-aarch64-linux-gnu)
|
sqlite3 (2.9.5-aarch64-linux-gnu)
|
||||||
sqlite3 (2.9.2-aarch64-linux-musl)
|
sqlite3 (2.9.5-aarch64-linux-musl)
|
||||||
sqlite3 (2.9.2-arm64-darwin)
|
sqlite3 (2.9.5-arm64-darwin)
|
||||||
sqlite3 (2.9.2-x86_64-linux-gnu)
|
sqlite3 (2.9.5-x86_64-linux-gnu)
|
||||||
sqlite3 (2.9.2-x86_64-linux-musl)
|
sqlite3 (2.9.5-x86_64-linux-musl)
|
||||||
stringio (3.2.0)
|
|
||||||
strip_attributes (2.0.1)
|
strip_attributes (2.0.1)
|
||||||
activemodel (>= 3.0, < 9.0)
|
activemodel (>= 3.0, < 9.0)
|
||||||
strscan (3.1.8)
|
strscan (3.1.8)
|
||||||
@@ -570,14 +571,14 @@ GEM
|
|||||||
crack (>= 0.3.2)
|
crack (>= 0.3.2)
|
||||||
hashdiff (>= 0.4.0, < 2.0.0)
|
hashdiff (>= 0.4.0, < 2.0.0)
|
||||||
webrick (1.9.2)
|
webrick (1.9.2)
|
||||||
websocket-driver (0.8.0)
|
websocket-driver (0.8.2)
|
||||||
base64
|
base64
|
||||||
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)
|
||||||
yaml (0.4.0)
|
yaml (0.4.0)
|
||||||
zeitwerk (2.7.5)
|
zeitwerk (2.8.2)
|
||||||
|
|
||||||
PLATFORMS
|
PLATFORMS
|
||||||
aarch64-linux
|
aarch64-linux
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ class AccountConfigsController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
raise InvalidKey unless allowed_keys.include?(@account_config.key)
|
raise InvalidKey unless allowed_destroy_keys.include?(@account_config.key)
|
||||||
|
|
||||||
@account_config.destroy!
|
@account_config.destroy!
|
||||||
|
|
||||||
@@ -50,6 +50,10 @@ class AccountConfigsController < ApplicationController
|
|||||||
ALLOWED_KEYS
|
ALLOWED_KEYS
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def allowed_destroy_keys
|
||||||
|
ALLOWED_KEYS
|
||||||
|
end
|
||||||
|
|
||||||
def load_account_config
|
def load_account_config
|
||||||
raise InvalidKey unless allowed_keys.include?(account_config_params[:key])
|
raise InvalidKey unless allowed_keys.include?(account_config_params[:key])
|
||||||
|
|
||||||
|
|||||||
@@ -14,8 +14,7 @@ class EsignSettingsController < ApplicationController
|
|||||||
prepend_before_action :maybe_redirect_com, only: %i[show]
|
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
|
||||||
authorize_resource :encrypted_config, only: %i[update destroy show]
|
|
||||||
|
|
||||||
def show
|
def show
|
||||||
cert_data = @encrypted_config.value || {}
|
cert_data = @encrypted_config.value || {}
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Mcp
|
||||||
|
class CreateTemplateController < McpBaseController
|
||||||
|
SCHEMA = {
|
||||||
|
name: 'create_template',
|
||||||
|
title: 'Create Template',
|
||||||
|
description: 'Create a document template. Provide a URL to upload a PDF/DOCX file, or provide only a name ' \
|
||||||
|
'to create an empty template and receive an edit URL where the file can be uploaded via the UI.',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
name: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Template name (used as the template name and required when url is not provided)'
|
||||||
|
},
|
||||||
|
url: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Optional URL of a PDF or DOCX file to upload. If omitted, an empty template is ' \
|
||||||
|
'created and the returned edit_url can be used to upload a file via the UI.'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: %w[name]
|
||||||
|
},
|
||||||
|
annotations: {
|
||||||
|
readOnlyHint: false,
|
||||||
|
destructiveHint: false,
|
||||||
|
idempotentHint: false,
|
||||||
|
openWorldHint: true
|
||||||
|
}
|
||||||
|
}.freeze
|
||||||
|
|
||||||
|
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
||||||
|
def call
|
||||||
|
account = current_user.account
|
||||||
|
|
||||||
|
@template = Template.new(
|
||||||
|
account:,
|
||||||
|
author: current_user,
|
||||||
|
folder: account.default_template_folder,
|
||||||
|
source: :mcp,
|
||||||
|
name: mcp_params['name'].to_s.presence || 'New Template',
|
||||||
|
fields: [],
|
||||||
|
schema: []
|
||||||
|
)
|
||||||
|
|
||||||
|
authorize!(:create, @template)
|
||||||
|
|
||||||
|
if mcp_params['url'].present?
|
||||||
|
tempfile = Tempfile.new
|
||||||
|
tempfile.binmode
|
||||||
|
tempfile.write(DownloadUtils.call(mcp_params['url'], validate: true).body)
|
||||||
|
tempfile.rewind
|
||||||
|
|
||||||
|
filename = File.basename(URI.decode_www_form_component(mcp_params['url']))
|
||||||
|
|
||||||
|
file = ActionDispatch::Http::UploadedFile.new(
|
||||||
|
tempfile:,
|
||||||
|
filename:,
|
||||||
|
type: Marcel::MimeType.for(tempfile)
|
||||||
|
)
|
||||||
|
|
||||||
|
@template.name = mcp_params['name'].presence || File.basename(filename, '.*')
|
||||||
|
@template.save!
|
||||||
|
|
||||||
|
documents, = Templates::CreateAttachments.call(@template, { files: [file] }, extract_fields: true)
|
||||||
|
schema = documents.map { |doc| { attachment_uuid: doc.uuid, name: doc.filename.base } }
|
||||||
|
|
||||||
|
if @template.fields.blank?
|
||||||
|
@template.fields = Templates::ProcessDocument.normalize_attachment_fields(@template, documents)
|
||||||
|
end
|
||||||
|
|
||||||
|
@template.update!(schema:)
|
||||||
|
else
|
||||||
|
@template.save!
|
||||||
|
end
|
||||||
|
|
||||||
|
WebhookUrls.enqueue_events(@template, 'template.created')
|
||||||
|
|
||||||
|
SearchEntries.enqueue_reindex(@template)
|
||||||
|
|
||||||
|
render_tool_result(
|
||||||
|
id: @template.id,
|
||||||
|
name: @template.name,
|
||||||
|
edit_url: edit_template_url(@template)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Mcp
|
||||||
|
class LoadTemplateController < McpBaseController
|
||||||
|
SCHEMA = {
|
||||||
|
name: 'load_template',
|
||||||
|
title: 'Load Template',
|
||||||
|
description: 'Load a template with its fields. Each field includes name, type, and the signing role name.',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
template_id: {
|
||||||
|
type: 'integer',
|
||||||
|
description: 'Template identifier'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: %w[template_id]
|
||||||
|
},
|
||||||
|
annotations: {
|
||||||
|
readOnlyHint: true,
|
||||||
|
destructiveHint: false,
|
||||||
|
idempotentHint: true,
|
||||||
|
openWorldHint: false
|
||||||
|
}
|
||||||
|
}.freeze
|
||||||
|
|
||||||
|
def call
|
||||||
|
@template = Template.accessible_by(current_ability).find(mcp_params['template_id'])
|
||||||
|
|
||||||
|
authorize!(:read, @template)
|
||||||
|
|
||||||
|
submitters_index = @template.submitters.index_by { |s| s['uuid'] }
|
||||||
|
|
||||||
|
roles = @template.submitters.pluck('name')
|
||||||
|
|
||||||
|
fields = @template.fields.filter_map do |field|
|
||||||
|
next if field['name'].blank?
|
||||||
|
|
||||||
|
{
|
||||||
|
name: field['name'],
|
||||||
|
type: field['type'],
|
||||||
|
role: submitters_index[field['submitter_uuid']]&.dig('name')
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
render_tool_result(
|
||||||
|
id: @template.id,
|
||||||
|
name: @template.name,
|
||||||
|
roles: roles,
|
||||||
|
fields: fields
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Mcp
|
||||||
|
class McpBaseController < ActionController::API
|
||||||
|
wrap_parameters false
|
||||||
|
|
||||||
|
before_action :authenticate_user!
|
||||||
|
before_action :verify_mcp_enabled!
|
||||||
|
check_authorization
|
||||||
|
|
||||||
|
before_action do
|
||||||
|
raise CanCan::AccessDenied unless can?(:manage, :mcp)
|
||||||
|
end
|
||||||
|
|
||||||
|
rescue_from CanCan::AccessDenied do
|
||||||
|
render_error(-32_603, 'Forbidden', status: :forbidden)
|
||||||
|
end
|
||||||
|
|
||||||
|
rescue_from ActiveRecord::RecordNotFound do
|
||||||
|
render_tool_error('Not found')
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def default_url_options
|
||||||
|
Docuseal.default_url_options
|
||||||
|
end
|
||||||
|
|
||||||
|
def mcp_body
|
||||||
|
request.request_parameters
|
||||||
|
end
|
||||||
|
|
||||||
|
def mcp_params
|
||||||
|
mcp_body.dig('params', 'arguments') || {}
|
||||||
|
end
|
||||||
|
|
||||||
|
def render_result(result)
|
||||||
|
render json: { jsonrpc: '2.0', id: mcp_body['id'], result: }
|
||||||
|
end
|
||||||
|
|
||||||
|
def render_error(code, message, id: nil, status: :ok)
|
||||||
|
render json: { jsonrpc: '2.0', id:, error: { code:, message: } }, status:
|
||||||
|
end
|
||||||
|
|
||||||
|
def render_tool_result(data)
|
||||||
|
render_result(content: [{ type: 'text', text: data.to_json }])
|
||||||
|
end
|
||||||
|
|
||||||
|
def render_tool_error(message)
|
||||||
|
render_result(content: [{ type: 'text', text: message }], isError: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
def authenticate_user!
|
||||||
|
render json: { error: 'Not authenticated' }, status: :unauthorized unless current_user
|
||||||
|
end
|
||||||
|
|
||||||
|
def verify_mcp_enabled!
|
||||||
|
return if Docuseal.multitenant?
|
||||||
|
|
||||||
|
return if AccountConfig.exists?(account_id: current_user.account_id,
|
||||||
|
key: AccountConfig::ENABLE_MCP_KEY,
|
||||||
|
value: true)
|
||||||
|
|
||||||
|
render json: { error: 'MCP is disabled' }, status: :forbidden
|
||||||
|
end
|
||||||
|
|
||||||
|
def current_user
|
||||||
|
@current_user ||= user_from_api_key
|
||||||
|
end
|
||||||
|
|
||||||
|
def user_from_api_key
|
||||||
|
token = request.headers['Authorization'].to_s[/\ABearer\s+(.+)\z/, 1]
|
||||||
|
|
||||||
|
return if token.blank?
|
||||||
|
|
||||||
|
sha256 = Digest::SHA256.hexdigest(token)
|
||||||
|
|
||||||
|
User.joins(:mcp_tokens).active.find_by(mcp_tokens: { sha256:, archived_at: nil })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Mcp
|
||||||
|
class ProtocolController < McpBaseController
|
||||||
|
skip_authorization_check
|
||||||
|
|
||||||
|
def ok
|
||||||
|
head :ok
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize_request
|
||||||
|
render_result(
|
||||||
|
protocolVersion: '2025-11-25',
|
||||||
|
serverInfo: {
|
||||||
|
name: 'DocuSeal',
|
||||||
|
version: Docuseal.version.to_s
|
||||||
|
},
|
||||||
|
capabilities: {
|
||||||
|
tools: {
|
||||||
|
listChanged: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialized_notification
|
||||||
|
head :accepted
|
||||||
|
end
|
||||||
|
|
||||||
|
def ping
|
||||||
|
render_result({})
|
||||||
|
end
|
||||||
|
|
||||||
|
def tools_list
|
||||||
|
render_result(tools: McpController::TOOLS)
|
||||||
|
end
|
||||||
|
|
||||||
|
def method_not_found
|
||||||
|
render_error(-32_601, "Method not found: #{mcp_body['method']}", id: mcp_body['id'])
|
||||||
|
end
|
||||||
|
|
||||||
|
def tool_not_found
|
||||||
|
render_error(-32_602, "Unknown tool: #{mcp_body.dig('params', 'name')}", id: mcp_body['id'])
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse_error
|
||||||
|
render_error(-32_700, 'Parse error', status: :bad_request)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Mcp
|
||||||
|
class SearchDocumentsController < McpBaseController
|
||||||
|
SCHEMA = {
|
||||||
|
name: 'search_documents',
|
||||||
|
title: 'Search Documents',
|
||||||
|
description: 'Search signed or pending documents by submitter name, email, phone, or template name',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
q: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Search by submitter name, email, phone, or template name'
|
||||||
|
},
|
||||||
|
limit: {
|
||||||
|
type: 'integer',
|
||||||
|
description: 'The number of results to return (default 10)'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: %w[q]
|
||||||
|
},
|
||||||
|
annotations: {
|
||||||
|
readOnlyHint: true,
|
||||||
|
destructiveHint: false,
|
||||||
|
idempotentHint: true,
|
||||||
|
openWorldHint: false
|
||||||
|
}
|
||||||
|
}.freeze
|
||||||
|
|
||||||
|
def call
|
||||||
|
authorize!(:read, Submission)
|
||||||
|
|
||||||
|
submissions = Submissions.search(current_user, Submission.accessible_by(current_ability).active,
|
||||||
|
mcp_params['q'], search_template: true)
|
||||||
|
|
||||||
|
limit = mcp_params.fetch('limit', 10).to_i
|
||||||
|
limit = 10 if limit <= 0
|
||||||
|
limit = [limit, 100].min
|
||||||
|
submissions = submissions.preload(:submitters, :template)
|
||||||
|
.order(id: :desc)
|
||||||
|
.limit(limit)
|
||||||
|
|
||||||
|
data = submissions.map do |submission|
|
||||||
|
{
|
||||||
|
id: submission.id,
|
||||||
|
template_name: submission.template&.name,
|
||||||
|
status: Submissions::SerializeForApi.build_status(submission, submission.submitters),
|
||||||
|
submitters: submission.submitters.map do |s|
|
||||||
|
{ email: s.email, name: s.name, phone: s.phone, status: s.status }
|
||||||
|
end,
|
||||||
|
documents_url: submission_url(submission.id)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
render_tool_result(data)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Mcp
|
||||||
|
class SearchTemplatesController < McpBaseController
|
||||||
|
SCHEMA = {
|
||||||
|
name: 'search_templates',
|
||||||
|
title: 'Search Templates',
|
||||||
|
description: 'Search document templates by name',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
q: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Search query to filter templates by name'
|
||||||
|
},
|
||||||
|
limit: {
|
||||||
|
type: 'integer',
|
||||||
|
description: 'The number of templates to return (default 10)'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: %w[q]
|
||||||
|
},
|
||||||
|
annotations: {
|
||||||
|
readOnlyHint: true,
|
||||||
|
destructiveHint: false,
|
||||||
|
idempotentHint: true,
|
||||||
|
openWorldHint: false
|
||||||
|
}
|
||||||
|
}.freeze
|
||||||
|
|
||||||
|
def call
|
||||||
|
authorize!(:read, Template)
|
||||||
|
|
||||||
|
templates = Templates.search(current_user, Template.accessible_by(current_ability).active, mcp_params['q'])
|
||||||
|
|
||||||
|
limit = mcp_params.fetch('limit', 10).to_i
|
||||||
|
limit = 10 if limit <= 0
|
||||||
|
limit = [limit, 100].min
|
||||||
|
templates = templates.order(id: :desc).limit(limit)
|
||||||
|
|
||||||
|
render_tool_result(templates.map { |t| { id: t.id, name: t.name } })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Mcp
|
||||||
|
class SendDocumentsController < McpBaseController
|
||||||
|
SCHEMA = {
|
||||||
|
name: 'send_documents',
|
||||||
|
title: 'Send Documents',
|
||||||
|
description: 'Send a document template for signing to specified submitters',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
template_id: {
|
||||||
|
type: 'integer',
|
||||||
|
description: 'Template identifier'
|
||||||
|
},
|
||||||
|
submitters: {
|
||||||
|
type: 'array',
|
||||||
|
description: 'The list of submitters (signers)',
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
email: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Submitter email address'
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Submitter name'
|
||||||
|
},
|
||||||
|
phone: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Submitter phone number in E.164 format'
|
||||||
|
},
|
||||||
|
role: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Signing role name from the template'
|
||||||
|
},
|
||||||
|
fields: {
|
||||||
|
type: 'array',
|
||||||
|
description: 'Prefill field values for this submitter (fields become readonly)',
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
name: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Field name'
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
description: 'Prefilled value for the field'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: %w[name value]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: %w[template_id submitters]
|
||||||
|
},
|
||||||
|
annotations: {
|
||||||
|
readOnlyHint: false,
|
||||||
|
destructiveHint: true,
|
||||||
|
idempotentHint: false,
|
||||||
|
openWorldHint: true
|
||||||
|
}
|
||||||
|
}.freeze
|
||||||
|
|
||||||
|
# rubocop:disable Metrics
|
||||||
|
def call
|
||||||
|
@template = Template.accessible_by(current_ability).find(mcp_params['template_id'])
|
||||||
|
|
||||||
|
authorize!(:read, @template)
|
||||||
|
|
||||||
|
return render_tool_error('Template has been archived') if @template.archived_at?
|
||||||
|
|
||||||
|
authorize!(:create, Submission.new(template: @template, account_id: current_user.account_id))
|
||||||
|
|
||||||
|
return render_tool_error('Template has no fields') if @template.fields.blank?
|
||||||
|
|
||||||
|
submitters = (mcp_params['submitters'] || []).map do |s|
|
||||||
|
attrs = s.slice('email', 'name', 'role', 'phone').compact_blank
|
||||||
|
|
||||||
|
fields = Array.wrap(s['fields']).filter_map do |f|
|
||||||
|
next if f['name'].blank?
|
||||||
|
|
||||||
|
{ 'name' => f['name'], 'default_value' => f['value'], 'readonly' => true }
|
||||||
|
end
|
||||||
|
|
||||||
|
attrs['fields'] = fields if fields.present?
|
||||||
|
|
||||||
|
attrs.with_indifferent_access
|
||||||
|
end
|
||||||
|
|
||||||
|
submissions = Submissions.create_from_submitters(
|
||||||
|
template: @template,
|
||||||
|
user: current_user,
|
||||||
|
source: :mcp,
|
||||||
|
submitters_order: @template.preferences['submitters_order'].presence || 'random',
|
||||||
|
submissions_attrs: { submitters: },
|
||||||
|
params: { 'send_email' => true, 'submitters' => submitters }
|
||||||
|
)
|
||||||
|
|
||||||
|
return render_tool_error('No valid submitters provided') if submissions.blank?
|
||||||
|
|
||||||
|
WebhookUrls.enqueue_events(submissions, 'submission.created')
|
||||||
|
|
||||||
|
Submissions.send_signature_requests(submissions)
|
||||||
|
|
||||||
|
SearchEntries.enqueue_reindex(submissions)
|
||||||
|
|
||||||
|
submission = submissions.first
|
||||||
|
|
||||||
|
render_tool_result(id: submission.id, status: 'pending')
|
||||||
|
rescue Submissions::CreateFromSubmitters::BaseError => e
|
||||||
|
render_tool_error(e.message)
|
||||||
|
end
|
||||||
|
# rubocop:enable Metrics
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,58 +1,44 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class McpController < ActionController::API
|
class McpController < ActionController::Metal
|
||||||
before_action :authenticate_user!
|
TOOL_CONTROLLERS = {
|
||||||
before_action :verify_mcp_enabled!
|
'search_templates' => Mcp::SearchTemplatesController,
|
||||||
|
'load_template' => Mcp::LoadTemplateController,
|
||||||
|
'create_template' => Mcp::CreateTemplateController,
|
||||||
|
'send_documents' => Mcp::SendDocumentsController,
|
||||||
|
'search_documents' => Mcp::SearchDocumentsController
|
||||||
|
}.freeze
|
||||||
|
|
||||||
before_action do
|
TOOLS = TOOL_CONTROLLERS.map { |_, controller| controller::SCHEMA }.freeze
|
||||||
authorize!(:manage, :mcp)
|
|
||||||
end
|
|
||||||
|
|
||||||
def call
|
def call
|
||||||
return head :ok if request.raw_post.blank?
|
return Mcp::ProtocolController.dispatch(:ok, request, response) if request.raw_post.blank?
|
||||||
|
|
||||||
body = JSON.parse(request.raw_post)
|
body = JSON.parse(request.raw_post)
|
||||||
|
body = nil unless body.is_a?(Hash)
|
||||||
|
|
||||||
result = Mcp::HandleRequest.call(body, current_user, current_ability)
|
request.request_parameters = body || {}
|
||||||
|
|
||||||
if result
|
action =
|
||||||
render json: result
|
case body&.dig('method')
|
||||||
else
|
when 'initialize' then :initialize_request
|
||||||
head :accepted
|
when 'notifications/initialized' then :initialized_notification
|
||||||
end
|
when 'ping' then :ping
|
||||||
rescue CanCan::AccessDenied
|
when 'tools/list' then :tools_list
|
||||||
render json: { jsonrpc: '2.0', id: nil, error: { code: -32_603, message: 'Forbidden' } }, status: :forbidden
|
when 'tools/call'
|
||||||
|
tool = TOOL_CONTROLLERS[body.dig('params', 'name')]
|
||||||
|
|
||||||
|
return tool.dispatch(:call, request, response) if tool
|
||||||
|
|
||||||
|
:tool_not_found
|
||||||
|
else
|
||||||
|
:method_not_found
|
||||||
|
end
|
||||||
|
|
||||||
|
Mcp::ProtocolController.dispatch(action, request, response)
|
||||||
rescue JSON::ParserError
|
rescue JSON::ParserError
|
||||||
render json: { jsonrpc: '2.0', id: nil, error: { code: -32_700, message: 'Parse error' } }, status: :bad_request
|
request.request_parameters = {}
|
||||||
end
|
|
||||||
|
|
||||||
private
|
Mcp::ProtocolController.dispatch(:parse_error, request, response)
|
||||||
|
|
||||||
def authenticate_user!
|
|
||||||
render json: { error: 'Not authenticated' }, status: :unauthorized unless current_user
|
|
||||||
end
|
|
||||||
|
|
||||||
def verify_mcp_enabled!
|
|
||||||
return if Docuseal.multitenant?
|
|
||||||
|
|
||||||
return if AccountConfig.exists?(account_id: current_user.account_id,
|
|
||||||
key: AccountConfig::ENABLE_MCP_KEY,
|
|
||||||
value: true)
|
|
||||||
|
|
||||||
render json: { error: 'MCP is disabled' }, status: :forbidden
|
|
||||||
end
|
|
||||||
|
|
||||||
def current_user
|
|
||||||
@current_user ||= user_from_api_key
|
|
||||||
end
|
|
||||||
|
|
||||||
def user_from_api_key
|
|
||||||
token = request.headers['Authorization'].to_s[/\ABearer\s+(.+)\z/, 1]
|
|
||||||
|
|
||||||
return if token.blank?
|
|
||||||
|
|
||||||
sha256 = Digest::SHA256.hexdigest(token)
|
|
||||||
|
|
||||||
User.joins(:mcp_tokens).active.find_by(mcp_tokens: { sha256:, archived_at: nil })
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ class MfaSetupController < ApplicationController
|
|||||||
|
|
||||||
redirect_to settings_profile_index_path, notice: I18n.t('2fa_has_been_configured')
|
redirect_to settings_profile_index_path, notice: I18n.t('2fa_has_been_configured')
|
||||||
else
|
else
|
||||||
|
RateLimit.call("mfa-setup-otp-#{current_user.id}", limit: 5, ttl: 5.minutes, enabled: true)
|
||||||
|
|
||||||
@provision_url = current_user.otp_provisioning_uri(current_user.email, issuer: Docuseal.product_name)
|
@provision_url = current_user.otp_provisioning_uri(current_user.email, issuer: Docuseal.product_name)
|
||||||
|
|
||||||
@error_message = I18n.t('code_is_invalid')
|
@error_message = I18n.t('code_is_invalid')
|
||||||
@@ -34,6 +36,8 @@ class MfaSetupController < ApplicationController
|
|||||||
|
|
||||||
redirect_to settings_profile_index_path, notice: I18n.t('2fa_has_been_removed')
|
redirect_to settings_profile_index_path, notice: I18n.t('2fa_has_been_removed')
|
||||||
else
|
else
|
||||||
|
RateLimit.call("mfa-setup-otp-#{current_user.id}", limit: 5, ttl: 5.minutes, enabled: true)
|
||||||
|
|
||||||
@error_message = I18n.t('code_is_invalid')
|
@error_message = I18n.t('code_is_invalid')
|
||||||
|
|
||||||
render turbo_stream: turbo_stream.replace(:modal, template: 'mfa_setup/edit'), status: :unprocessable_content
|
render turbo_stream: turbo_stream.replace(:modal, template: 'mfa_setup/edit'), status: :unprocessable_content
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class SettingsController < ApplicationController
|
||||||
|
skip_authorization_check
|
||||||
|
|
||||||
|
def index; end
|
||||||
|
end
|
||||||
@@ -24,5 +24,7 @@ class SubmissionEventsController < ApplicationController
|
|||||||
|
|
||||||
load_and_authorize_resource :submission
|
load_and_authorize_resource :submission
|
||||||
|
|
||||||
def index; end
|
def index
|
||||||
|
render :index, layout: 'plain'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ class SubmissionsController < ApplicationController
|
|||||||
|
|
||||||
def new
|
def new
|
||||||
authorize!(:new, Submission)
|
authorize!(:new, Submission)
|
||||||
|
|
||||||
|
render :new, layout: 'plain'
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ class SubmittersController < ApplicationController
|
|||||||
if @submitter.preferences['email_message_uuid'].present?
|
if @submitter.preferences['email_message_uuid'].present?
|
||||||
@submitter.account.email_messages.find_by(uuid: @submitter.preferences['email_message_uuid'])
|
@submitter.account.email_messages.find_by(uuid: @submitter.preferences['email_message_uuid'])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
render :edit, layout: 'plain'
|
||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
|
|||||||
@@ -25,7 +25,9 @@ class TemplatesController < ApplicationController
|
|||||||
redirect_to root_path
|
redirect_to root_path
|
||||||
end
|
end
|
||||||
|
|
||||||
def new; end
|
def new
|
||||||
|
render :new, layout: 'plain'
|
||||||
|
end
|
||||||
|
|
||||||
def edit
|
def edit
|
||||||
@template_data = Templates.serialize_for_builder(@template)
|
@template_data = Templates.serialize_for_builder(@template)
|
||||||
|
|||||||
@@ -13,11 +13,13 @@ class TemplatesFormPreviewController < ApplicationController
|
|||||||
|
|
||||||
@submitter.submission.submitters = @template.submitters.map { |item| Submitter.new(uuid: item['uuid']) }
|
@submitter.submission.submitters = @template.submitters.map { |item| Submitter.new(uuid: item['uuid']) }
|
||||||
|
|
||||||
|
Submissions::CreateFromSubmitters.maybe_set_dynamic_documents(@submitter.submission, preview: true)
|
||||||
|
|
||||||
Submissions.preload_with_pages(@submitter.submission)
|
Submissions.preload_with_pages(@submitter.submission)
|
||||||
|
|
||||||
@attachments_index = ActiveStorage::Attachment.where(record: @submitter.submission.submitters, name: :attachments)
|
@attachments_index = ActiveStorage::Attachment.where(record: @submitter.submission.submitters, name: :attachments)
|
||||||
.preload(:blob).index_by(&:uuid)
|
.preload(:blob).index_by(&:uuid)
|
||||||
|
|
||||||
@form_configs = Submitters::FormConfigs.call(@submitter)
|
@form_configs = Submitters::FormConfigs.call(@submitter, SubmitFormController::CONFIG_KEYS)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -14,7 +14,9 @@ class TemplatesPreferencesController < ApplicationController
|
|||||||
completed_notification_email_body]
|
completed_notification_email_body]
|
||||||
}.freeze
|
}.freeze
|
||||||
|
|
||||||
def show; end
|
def show
|
||||||
|
render :show, layout: 'plain'
|
||||||
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
authorize!(:update, @template)
|
authorize!(:update, @template)
|
||||||
|
|||||||
@@ -3,7 +3,9 @@
|
|||||||
class TemplatesShareLinkController < ApplicationController
|
class TemplatesShareLinkController < ApplicationController
|
||||||
load_and_authorize_resource :template
|
load_and_authorize_resource :template
|
||||||
|
|
||||||
def show; end
|
def show
|
||||||
|
render :show, layout: 'plain'
|
||||||
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
authorize!(:update, @template)
|
authorize!(:update, @template)
|
||||||
|
|||||||
@@ -56,6 +56,9 @@ import OpenModal from './elements/open_modal'
|
|||||||
import BarChart from './elements/bar_chart'
|
import BarChart from './elements/bar_chart'
|
||||||
import FieldCondition from './elements/field_condition'
|
import FieldCondition from './elements/field_condition'
|
||||||
import ConfirmUpload from './elements/confirm_upload'
|
import ConfirmUpload from './elements/confirm_upload'
|
||||||
|
import ScrollFade from './elements/scroll_fade'
|
||||||
|
import OpenModalMobile from './elements/open_modal_mobile'
|
||||||
|
import HistoryBack from './elements/history_back'
|
||||||
|
|
||||||
import * as TurboInstantClick from './lib/turbo_instant_click'
|
import * as TurboInstantClick from './lib/turbo_instant_click'
|
||||||
|
|
||||||
@@ -150,10 +153,14 @@ safeRegisterElement('open-modal', OpenModal)
|
|||||||
safeRegisterElement('bar-chart', BarChart)
|
safeRegisterElement('bar-chart', BarChart)
|
||||||
safeRegisterElement('field-condition', FieldCondition)
|
safeRegisterElement('field-condition', FieldCondition)
|
||||||
safeRegisterElement('confirm-upload', ConfirmUpload)
|
safeRegisterElement('confirm-upload', ConfirmUpload)
|
||||||
|
safeRegisterElement('scroll-fade', ScrollFade)
|
||||||
|
safeRegisterElement('open-modal-mobile', OpenModalMobile)
|
||||||
|
safeRegisterElement('history-back', HistoryBack)
|
||||||
|
|
||||||
safeRegisterElement('template-builder', class extends HTMLElement {
|
safeRegisterElement('template-builder', class extends HTMLElement {
|
||||||
connectedCallback () {
|
connectedCallback () {
|
||||||
document.addEventListener('turbo:submit-end', this.onSubmit)
|
document.addEventListener('turbo:submit-end', this.onSubmit)
|
||||||
|
document.addEventListener('turbo:before-cache', this.onBeforeCache)
|
||||||
|
|
||||||
this.appElem = document.createElement('div')
|
this.appElem = document.createElement('div')
|
||||||
|
|
||||||
@@ -164,6 +171,7 @@ safeRegisterElement('template-builder', class extends HTMLElement {
|
|||||||
this.app = createApp(TemplateBuilder, {
|
this.app = createApp(TemplateBuilder, {
|
||||||
template,
|
template,
|
||||||
customFields: reactive(JSON.parse(this.dataset.customFields || '[]')),
|
customFields: reactive(JSON.parse(this.dataset.customFields || '[]')),
|
||||||
|
dateFormats: JSON.parse(this.dataset.dateFormats || '[]'),
|
||||||
dynamicDocuments: reactive(JSON.parse(this.dataset.dynamicDocuments || '[]')),
|
dynamicDocuments: reactive(JSON.parse(this.dataset.dynamicDocuments || '[]')),
|
||||||
backgroundColor: '#faf7f5',
|
backgroundColor: '#faf7f5',
|
||||||
locale: this.dataset.locale,
|
locale: this.dataset.locale,
|
||||||
@@ -180,7 +188,10 @@ safeRegisterElement('template-builder', class extends HTMLElement {
|
|||||||
authenticityToken: document.querySelector('meta[name="csrf-token"]')?.content,
|
authenticityToken: document.querySelector('meta[name="csrf-token"]')?.content,
|
||||||
withCustomFields: true,
|
withCustomFields: true,
|
||||||
withPayment: this.dataset.withPayment === 'true',
|
withPayment: this.dataset.withPayment === 'true',
|
||||||
isPaymentConnected: this.dataset.isPaymentConnected === 'true',
|
isStripeConnected: this.dataset.isPaymentConnected === 'true' || this.dataset.isStripeConnected === 'true',
|
||||||
|
withStripe: this.dataset.withStripe !== 'false',
|
||||||
|
withPaypal: this.dataset.withPaypal === 'true',
|
||||||
|
isPaypalConnected: this.dataset.isPaypalConnected === 'true',
|
||||||
withFormula: this.dataset.withFormula === 'true',
|
withFormula: this.dataset.withFormula === 'true',
|
||||||
withSendButton: this.dataset.withSendButton !== 'false',
|
withSendButton: this.dataset.withSendButton !== 'false',
|
||||||
withSignYourselfButton: this.dataset.withSignYourselfButton !== 'false',
|
withSignYourselfButton: this.dataset.withSignYourselfButton !== 'false',
|
||||||
@@ -233,8 +244,14 @@ safeRegisterElement('template-builder', class extends HTMLElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onBeforeCache = () => {
|
||||||
|
this.app?.unmount()
|
||||||
|
this.appElem?.remove()
|
||||||
|
}
|
||||||
|
|
||||||
disconnectedCallback () {
|
disconnectedCallback () {
|
||||||
document.removeEventListener('turbo:submit-end', this.onSubmit)
|
document.removeEventListener('turbo:submit-end', this.onSubmit)
|
||||||
|
document.removeEventListener('turbo:before-cache', this.onBeforeCache)
|
||||||
|
|
||||||
this.app?.unmount()
|
this.app?.unmount()
|
||||||
this.appElem?.remove()
|
this.appElem?.remove()
|
||||||
@@ -243,6 +260,8 @@ safeRegisterElement('template-builder', class extends HTMLElement {
|
|||||||
|
|
||||||
safeRegisterElement('import-list', class extends HTMLElement {
|
safeRegisterElement('import-list', class extends HTMLElement {
|
||||||
connectedCallback () {
|
connectedCallback () {
|
||||||
|
document.addEventListener('turbo:before-cache', this.onBeforeCache)
|
||||||
|
|
||||||
this.appElem = document.createElement('div')
|
this.appElem = document.createElement('div')
|
||||||
|
|
||||||
this.app = createApp(ImportList, {
|
this.app = createApp(ImportList, {
|
||||||
@@ -257,7 +276,14 @@ safeRegisterElement('import-list', class extends HTMLElement {
|
|||||||
this.appendChild(this.appElem)
|
this.appendChild(this.appElem)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onBeforeCache = () => {
|
||||||
|
this.app?.unmount()
|
||||||
|
this.appElem?.remove()
|
||||||
|
}
|
||||||
|
|
||||||
disconnectedCallback () {
|
disconnectedCallback () {
|
||||||
|
document.removeEventListener('turbo:before-cache', this.onBeforeCache)
|
||||||
|
|
||||||
this.app?.unmount()
|
this.app?.unmount()
|
||||||
this.appElem?.remove()
|
this.appElem?.remove()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,6 +114,13 @@ button[disabled] .enabled, button.btn-disabled .enabled {
|
|||||||
bottom: auto;
|
bottom: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (hover: none), (pointer: coarse) {
|
||||||
|
.tooltip-no-touch:before,
|
||||||
|
.tooltip-no-touch:after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.autocomplete {
|
.autocomplete {
|
||||||
background: white;
|
background: white;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
export default class extends HTMLElement {
|
||||||
|
connectedCallback () {
|
||||||
|
this.addEventListener('click', this.onClick)
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectedCallback () {
|
||||||
|
this.removeEventListener('click', this.onClick)
|
||||||
|
}
|
||||||
|
|
||||||
|
onClick = (e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
window.history.back()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -106,11 +106,29 @@ img.ProseMirror-separator {
|
|||||||
}
|
}
|
||||||
`)
|
`)
|
||||||
|
|
||||||
|
const DROP_ATTRS = [
|
||||||
|
'srcdoc', 'xlink:href', 'srcset', 'action', 'formaction', 'poster',
|
||||||
|
'background', 'data', 'cite', 'ping', 'longdesc', 'manifest', 'profile'
|
||||||
|
]
|
||||||
|
|
||||||
|
const SAFE_URL_REGEXP = /^(?:https?:\/\/|data:image\/|blob:|mailto:|tel:|\{|#)/i
|
||||||
|
|
||||||
|
function isSafeAttr (name, value) {
|
||||||
|
const lowerName = name.toLowerCase()
|
||||||
|
|
||||||
|
if (lowerName.startsWith('on') || DROP_ATTRS.includes(lowerName)) return false
|
||||||
|
if ((lowerName === 'href' || lowerName === 'src') && !SAFE_URL_REGEXP.test(value.trim())) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
function collectDomAttrs (dom) {
|
function collectDomAttrs (dom) {
|
||||||
const attrs = {}
|
const attrs = {}
|
||||||
|
|
||||||
for (let i = 0; i < dom.attributes.length; i++) {
|
for (let i = 0; i < dom.attributes.length; i++) {
|
||||||
attrs[dom.attributes[i].name] = dom.attributes[i].value
|
const { name, value } = dom.attributes[i]
|
||||||
|
|
||||||
|
if (isSafeAttr(name, value)) attrs[name] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
return { htmlAttrs: attrs }
|
return { htmlAttrs: attrs }
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
export default class extends HTMLElement {
|
||||||
|
connectedCallback () {
|
||||||
|
if (this.dataset.disabled === 'true') return
|
||||||
|
if (!this.isMobile()) return
|
||||||
|
if (window.innerWidth >= 768) return
|
||||||
|
|
||||||
|
this.querySelectorAll('a[data-turbo-frame]').forEach((link) => {
|
||||||
|
link.dataset.turboFrame = '_top'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
isMobile () {
|
||||||
|
const isTouchWebkit = 'ontouchstart' in window && navigator.maxTouchPoints > 0 && /AppleWebKit|android/i.test(navigator.userAgent)
|
||||||
|
|
||||||
|
return isTouchWebkit || /iPhone|iPad|iPod/i.test(navigator.userAgent)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
export default class extends HTMLElement {
|
||||||
|
connectedCallback () {
|
||||||
|
this.addEventListener('scroll', this.updateFade, { passive: true })
|
||||||
|
window.addEventListener('resize', this.updateFade)
|
||||||
|
|
||||||
|
this.updateFade()
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectedCallback () {
|
||||||
|
window.removeEventListener('resize', this.updateFade)
|
||||||
|
}
|
||||||
|
|
||||||
|
updateFade = () => {
|
||||||
|
const maxScroll = this.scrollWidth - this.clientWidth
|
||||||
|
const fadeGradient = {
|
||||||
|
right: 'linear-gradient(to right, black calc(100% - 24px), transparent)',
|
||||||
|
left: 'linear-gradient(to right, transparent, black 24px)',
|
||||||
|
both: 'linear-gradient(to right, transparent, black 24px, black calc(100% - 24px), transparent)'
|
||||||
|
}
|
||||||
|
|
||||||
|
let state = 'none'
|
||||||
|
|
||||||
|
if (maxScroll > 4) {
|
||||||
|
if (this.scrollLeft <= 4) {
|
||||||
|
state = 'right'
|
||||||
|
} else if (this.scrollLeft >= maxScroll - 4) {
|
||||||
|
state = 'left'
|
||||||
|
} else {
|
||||||
|
state = 'both'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.applyFade(fadeGradient[state] || '')
|
||||||
|
}
|
||||||
|
|
||||||
|
applyFade (gradient) {
|
||||||
|
this.style.maskImage = gradient
|
||||||
|
this.style.webkitMaskImage = gradient
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,19 +1,5 @@
|
|||||||
export default class extends HTMLElement {
|
export default class extends HTMLElement {
|
||||||
connectedCallback () {
|
connectedCallback () {
|
||||||
this.input.addEventListener('focus', () => {
|
|
||||||
if (this.title) {
|
|
||||||
this.title.classList.add('hidden', 'md:block')
|
|
||||||
this.input.classList.add('w-60')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
this.input.addEventListener('blur', (e) => {
|
|
||||||
if (this.title && !e.target.value) {
|
|
||||||
this.title.classList.remove('hidden')
|
|
||||||
this.input.classList.remove('w-60')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
this.button.addEventListener('click', (event) => {
|
this.button.addEventListener('click', (event) => {
|
||||||
if (!this.input.value && document.activeElement !== this.input) {
|
if (!this.input.value && document.activeElement !== this.input) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
@@ -21,16 +7,22 @@ export default class extends HTMLElement {
|
|||||||
this.input.focus()
|
this.input.focus()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
document.addEventListener('turbo:before-cache', this.onBeforeCache)
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectedCallback () {
|
||||||
|
document.removeEventListener('turbo:before-cache', this.onBeforeCache)
|
||||||
|
}
|
||||||
|
|
||||||
|
onBeforeCache = () => {
|
||||||
|
this.input.value = this.input.getAttribute('value') || ''
|
||||||
}
|
}
|
||||||
|
|
||||||
get input () {
|
get input () {
|
||||||
return this.querySelector('input')
|
return this.querySelector('input')
|
||||||
}
|
}
|
||||||
|
|
||||||
get title () {
|
|
||||||
return document.querySelector(this.dataset.title)
|
|
||||||
}
|
|
||||||
|
|
||||||
get button () {
|
get button () {
|
||||||
return this.querySelector('button')
|
return this.querySelector('button')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ export default actionable(class extends HTMLElement {
|
|||||||
|
|
||||||
document.addEventListener('keyup', this.onEscKey)
|
document.addEventListener('keyup', this.onEscKey)
|
||||||
|
|
||||||
document.addEventListener('turbo:before-cache', this.close)
|
document.addEventListener('turbo:before-cache', this.onBeforeCache)
|
||||||
|
|
||||||
if (this.dataset.closeAfterSubmit !== 'false') {
|
if (this.dataset.closeAfterSubmit !== 'false') {
|
||||||
document.addEventListener('turbo:submit-end', this.onSubmit)
|
document.addEventListener('turbo:submit-end', this.onSubmit)
|
||||||
@@ -18,7 +18,7 @@ export default actionable(class extends HTMLElement {
|
|||||||
|
|
||||||
document.removeEventListener('keyup', this.onEscKey)
|
document.removeEventListener('keyup', this.onEscKey)
|
||||||
document.removeEventListener('turbo:submit-end', this.onSubmit)
|
document.removeEventListener('turbo:submit-end', this.onSubmit)
|
||||||
document.removeEventListener('turbo:before-cache', this.close)
|
document.removeEventListener('turbo:before-cache', this.onBeforeCache)
|
||||||
}
|
}
|
||||||
|
|
||||||
onSubmit = (e) => {
|
onSubmit = (e) => {
|
||||||
@@ -33,6 +33,12 @@ export default actionable(class extends HTMLElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onBeforeCache = () => {
|
||||||
|
if (this.closest('turbo-frame')?.src) {
|
||||||
|
this.remove()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
close = (e) => {
|
close = (e) => {
|
||||||
e?.preventDefault()
|
e?.preventDefault()
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ safeRegisterElement('submission-form', class extends HTMLElement {
|
|||||||
scrollPadding: this.dataset.scrollPadding || '-80px',
|
scrollPadding: this.dataset.scrollPadding || '-80px',
|
||||||
signatureText: this.dataset.signatureText,
|
signatureText: this.dataset.signatureText,
|
||||||
language: this.dataset.language,
|
language: this.dataset.language,
|
||||||
|
paymentProvider: this.dataset.paymentProvider,
|
||||||
dryRun: this.dataset.dryRun === 'true',
|
dryRun: this.dataset.dryRun === 'true',
|
||||||
expand: ['true', 'false'].includes(this.dataset.expand) ? this.dataset.expand === 'true' : null,
|
expand: ['true', 'false'].includes(this.dataset.expand) ? this.dataset.expand === 'true' : null,
|
||||||
withSignatureId: this.dataset.withSignatureId === 'true',
|
withSignatureId: this.dataset.withSignatureId === 'true',
|
||||||
|
|||||||
@@ -69,8 +69,8 @@
|
|||||||
:attachments-index="attachmentsIndex"
|
:attachments-index="attachmentsIndex"
|
||||||
/>
|
/>
|
||||||
<FieldArea
|
<FieldArea
|
||||||
v-else-if="item.type === 'formula_area' && isMathLoaded"
|
v-else-if="item.type === 'formula_area' && (isMathLoaded || item.field.type === 'text')"
|
||||||
:model-value="calculateFormula(item.field)"
|
:model-value="item.field.type === 'text' ? evalTextFormula(item.field) : calculateFormula(item.field)"
|
||||||
:is-inline-size="isInlineSize"
|
:is-inline-size="isInlineSize"
|
||||||
:field="item.field"
|
:field="item.field"
|
||||||
:area="item.area"
|
:area="item.area"
|
||||||
@@ -289,6 +289,7 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
normalizeFormula: FormulaAreas.methods.normalizeFormula,
|
normalizeFormula: FormulaAreas.methods.normalizeFormula,
|
||||||
calculateFormula: FormulaAreas.methods.calculateFormula,
|
calculateFormula: FormulaAreas.methods.calculateFormula,
|
||||||
|
evalTextFormula: FormulaAreas.methods.evalTextFormula,
|
||||||
scrollInContainer: FieldAreas.methods.scrollInContainer,
|
scrollInContainer: FieldAreas.methods.scrollInContainer,
|
||||||
scrollIntoArea: FieldAreas.methods.scrollIntoArea,
|
scrollIntoArea: FieldAreas.methods.scrollIntoArea,
|
||||||
scrollIntoField: FieldAreas.methods.scrollIntoField,
|
scrollIntoField: FieldAreas.methods.scrollIntoField,
|
||||||
|
|||||||
@@ -560,6 +560,7 @@
|
|||||||
:fields="formulaFields"
|
:fields="formulaFields"
|
||||||
:values="values"
|
:values="values"
|
||||||
:readonly-values="readonlyFieldValues"
|
:readonly-values="readonlyFieldValues"
|
||||||
|
:provider="paymentProvider"
|
||||||
@attached="attachments.push($event)"
|
@attached="attachments.push($event)"
|
||||||
@focus="scrollIntoField(currentField)"
|
@focus="scrollIntoField(currentField)"
|
||||||
@submit="!isSubmitting && submitStep()"
|
@submit="!isSubmitting && submitStep()"
|
||||||
@@ -1000,6 +1001,11 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: ''
|
default: ''
|
||||||
},
|
},
|
||||||
|
paymentProvider: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
values: {
|
values: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -1575,6 +1581,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
goToStep (stepIndex, scrollToArea = false, clickUpload = false) {
|
goToStep (stepIndex, scrollToArea = false, clickUpload = false) {
|
||||||
|
this.isInvite = false
|
||||||
this.currentStep = stepIndex
|
this.currentStep = stepIndex
|
||||||
this.showFillAllRequiredFields = false
|
this.showFillAllRequiredFields = false
|
||||||
|
|
||||||
@@ -1599,6 +1606,10 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
saveStep (formData) {
|
saveStep (formData) {
|
||||||
|
if (!formData && !this.$refs.form) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const currentFieldUuids = this.currentStepFields.map((f) => f.uuid)
|
const currentFieldUuids = this.currentStepFields.map((f) => f.uuid)
|
||||||
const currentFieldType = this.currentField.type
|
const currentFieldType = this.currentField.type
|
||||||
|
|
||||||
|
|||||||
@@ -12,8 +12,8 @@
|
|||||||
:to="findPageElementForArea(area)"
|
:to="findPageElementForArea(area)"
|
||||||
>
|
>
|
||||||
<FieldArea
|
<FieldArea
|
||||||
v-if="isMathLoaded"
|
v-if="isMathLoaded || field.type === 'text'"
|
||||||
:model-value="calculateFormula(field)"
|
:model-value="field.type === 'text' ? evalTextFormula(field) : calculateFormula(field)"
|
||||||
:is-inline-size="isInlineSize"
|
:is-inline-size="isInlineSize"
|
||||||
:field="field"
|
:field="field"
|
||||||
:area="area"
|
:area="area"
|
||||||
@@ -95,6 +95,25 @@ export default {
|
|||||||
})
|
})
|
||||||
|
|
||||||
return this.math.evaluate(transformedFormula.toLowerCase())
|
return this.math.evaluate(transformedFormula.toLowerCase())
|
||||||
|
},
|
||||||
|
evalTextFormula (field, depth = 0) {
|
||||||
|
if (depth > 10) return ''
|
||||||
|
|
||||||
|
return field.preferences.formula.replace(/{{(.*?)}}/g, (match, uuid) => {
|
||||||
|
const formulaField = this.fieldsUuidIndex[uuid]
|
||||||
|
|
||||||
|
if (formulaField?.preferences?.formula) {
|
||||||
|
if (formulaField.type === 'text') {
|
||||||
|
return this.evalTextFormula(formulaField, depth + 1)
|
||||||
|
} else if (this.isMathLoaded) {
|
||||||
|
return this.calculateFormula(formulaField)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = this.readonlyValues[uuid] ?? this.values[uuid]
|
||||||
|
|
||||||
|
return Array.isArray(value) ? value.join(', ') : (value ?? '')
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -107,6 +107,7 @@ const en = {
|
|||||||
email_has_been_sent: 'Email has been sent',
|
email_has_been_sent: 'Email has been sent',
|
||||||
processing: 'Processing',
|
processing: 'Processing',
|
||||||
pay_with_stripe: 'Pay with Stripe',
|
pay_with_stripe: 'Pay with Stripe',
|
||||||
|
pay_with_paypal: 'Pay with PayPal',
|
||||||
reupload: 'Reupload',
|
reupload: 'Reupload',
|
||||||
upload: 'Upload',
|
upload: 'Upload',
|
||||||
files: 'Files',
|
files: 'Files',
|
||||||
@@ -225,6 +226,7 @@ const es = {
|
|||||||
email_has_been_sent: 'El correo electrónico ha sido enviado',
|
email_has_been_sent: 'El correo electrónico ha sido enviado',
|
||||||
processing: 'Procesando',
|
processing: 'Procesando',
|
||||||
pay_with_stripe: 'Pagar con Stripe',
|
pay_with_stripe: 'Pagar con Stripe',
|
||||||
|
pay_with_paypal: 'Pagar con PayPal',
|
||||||
reupload: 'Volver a subir',
|
reupload: 'Volver a subir',
|
||||||
upload: 'Subir',
|
upload: 'Subir',
|
||||||
files: 'Archivos',
|
files: 'Archivos',
|
||||||
@@ -343,6 +345,7 @@ const it = {
|
|||||||
email_has_been_sent: "L'email è stata inviata",
|
email_has_been_sent: "L'email è stata inviata",
|
||||||
processing: 'Elaborazione',
|
processing: 'Elaborazione',
|
||||||
pay_with_stripe: 'Paga con Stripe',
|
pay_with_stripe: 'Paga con Stripe',
|
||||||
|
pay_with_paypal: 'Paga con PayPal',
|
||||||
reupload: 'Ricarica',
|
reupload: 'Ricarica',
|
||||||
upload: 'Carica',
|
upload: 'Carica',
|
||||||
files: 'File',
|
files: 'File',
|
||||||
@@ -461,6 +464,7 @@ const de = {
|
|||||||
email_has_been_sent: 'E-Mail wurde gesendet',
|
email_has_been_sent: 'E-Mail wurde gesendet',
|
||||||
processing: 'Verarbeitung',
|
processing: 'Verarbeitung',
|
||||||
pay_with_stripe: 'Mit Stripe bezahlen',
|
pay_with_stripe: 'Mit Stripe bezahlen',
|
||||||
|
pay_with_paypal: 'Mit PayPal bezahlen',
|
||||||
reupload: 'Erneut hochladen',
|
reupload: 'Erneut hochladen',
|
||||||
upload: 'Hochladen',
|
upload: 'Hochladen',
|
||||||
files: 'Dateien',
|
files: 'Dateien',
|
||||||
@@ -579,6 +583,7 @@ const fr = {
|
|||||||
email_has_been_sent: "L'e-mail a été envoyé",
|
email_has_been_sent: "L'e-mail a été envoyé",
|
||||||
processing: 'Traitement en cours',
|
processing: 'Traitement en cours',
|
||||||
pay_with_stripe: 'Payer avec Stripe',
|
pay_with_stripe: 'Payer avec Stripe',
|
||||||
|
pay_with_paypal: 'Payer avec PayPal',
|
||||||
reupload: 'Retéléverser',
|
reupload: 'Retéléverser',
|
||||||
upload: 'Téléverser',
|
upload: 'Téléverser',
|
||||||
files: 'Fichiers',
|
files: 'Fichiers',
|
||||||
@@ -697,6 +702,7 @@ const pl = {
|
|||||||
email_has_been_sent: 'E-mail został wysłany',
|
email_has_been_sent: 'E-mail został wysłany',
|
||||||
processing: 'Przetwarzanie',
|
processing: 'Przetwarzanie',
|
||||||
pay_with_stripe: 'Zapłać za pomocą Stripe',
|
pay_with_stripe: 'Zapłać za pomocą Stripe',
|
||||||
|
pay_with_paypal: 'Zapłać za pomocą PayPal',
|
||||||
reupload: 'Ponowne przesłanie',
|
reupload: 'Ponowne przesłanie',
|
||||||
upload: 'Przesyłanie',
|
upload: 'Przesyłanie',
|
||||||
files: 'Pliki',
|
files: 'Pliki',
|
||||||
@@ -815,6 +821,7 @@ const uk = {
|
|||||||
email_has_been_sent: 'Електронний лист був відправлений',
|
email_has_been_sent: 'Електронний лист був відправлений',
|
||||||
processing: 'Обробка',
|
processing: 'Обробка',
|
||||||
pay_with_stripe: 'Сплатити за допомогою Stripe',
|
pay_with_stripe: 'Сплатити за допомогою Stripe',
|
||||||
|
pay_with_paypal: 'Сплатити за допомогою PayPal',
|
||||||
reupload: 'Перезавантажити',
|
reupload: 'Перезавантажити',
|
||||||
upload: 'Завантажити',
|
upload: 'Завантажити',
|
||||||
files: 'Файли',
|
files: 'Файли',
|
||||||
@@ -933,6 +940,7 @@ const cs = {
|
|||||||
email_has_been_sent: 'E-mail byl odeslán',
|
email_has_been_sent: 'E-mail byl odeslán',
|
||||||
processing: 'Zpracování',
|
processing: 'Zpracování',
|
||||||
pay_with_stripe: 'Zaplatit přes Stripe',
|
pay_with_stripe: 'Zaplatit přes Stripe',
|
||||||
|
pay_with_paypal: 'Zaplatit přes PayPal',
|
||||||
reupload: 'Znovu nahrát',
|
reupload: 'Znovu nahrát',
|
||||||
upload: 'Nahrát',
|
upload: 'Nahrát',
|
||||||
files: 'Soubory',
|
files: 'Soubory',
|
||||||
@@ -1051,6 +1059,7 @@ const pt = {
|
|||||||
email_has_been_sent: 'Email enviado',
|
email_has_been_sent: 'Email enviado',
|
||||||
processing: 'Processamento',
|
processing: 'Processamento',
|
||||||
pay_with_stripe: 'Pagar com Stripe',
|
pay_with_stripe: 'Pagar com Stripe',
|
||||||
|
pay_with_paypal: 'Pagar com PayPal',
|
||||||
reupload: 'Recarregar',
|
reupload: 'Recarregar',
|
||||||
upload: 'Carregar',
|
upload: 'Carregar',
|
||||||
files: 'Arquivos',
|
files: 'Arquivos',
|
||||||
@@ -1169,6 +1178,7 @@ const he = {
|
|||||||
email_has_been_sent: 'האימייל נשלח',
|
email_has_been_sent: 'האימייל נשלח',
|
||||||
processing: 'מעבד',
|
processing: 'מעבד',
|
||||||
pay_with_stripe: 'שלם עם סטרייפ',
|
pay_with_stripe: 'שלם עם סטרייפ',
|
||||||
|
pay_with_paypal: 'שלם עם פייפאל',
|
||||||
reupload: 'העלה שוב',
|
reupload: 'העלה שוב',
|
||||||
upload: 'העלאה',
|
upload: 'העלאה',
|
||||||
files: 'קבצים',
|
files: 'קבצים',
|
||||||
@@ -1287,6 +1297,7 @@ const nl = {
|
|||||||
email_has_been_sent: 'E-mail is verzonden',
|
email_has_been_sent: 'E-mail is verzonden',
|
||||||
processing: 'Verwerken',
|
processing: 'Verwerken',
|
||||||
pay_with_stripe: 'Betalen met Stripe',
|
pay_with_stripe: 'Betalen met Stripe',
|
||||||
|
pay_with_paypal: 'Betalen met PayPal',
|
||||||
reupload: 'Opnieuw uploaden',
|
reupload: 'Opnieuw uploaden',
|
||||||
upload: 'Uploaden',
|
upload: 'Uploaden',
|
||||||
files: 'Bestanden',
|
files: 'Bestanden',
|
||||||
@@ -1405,6 +1416,7 @@ const ar = {
|
|||||||
email_has_been_sent: 'تم إرسال البريد الإلكتروني',
|
email_has_been_sent: 'تم إرسال البريد الإلكتروني',
|
||||||
processing: 'جارٍ المعالجة',
|
processing: 'جارٍ المعالجة',
|
||||||
pay_with_stripe: 'الدفع بواسطة Stripe',
|
pay_with_stripe: 'الدفع بواسطة Stripe',
|
||||||
|
pay_with_paypal: 'الدفع بواسطة PayPal',
|
||||||
reupload: 'إعادة التحميل',
|
reupload: 'إعادة التحميل',
|
||||||
upload: 'تحميل',
|
upload: 'تحميل',
|
||||||
files: 'الملفات',
|
files: 'الملفات',
|
||||||
@@ -1523,6 +1535,7 @@ const ko = {
|
|||||||
email_has_been_sent: '이메일이 전송되었습니다',
|
email_has_been_sent: '이메일이 전송되었습니다',
|
||||||
processing: '처리 중',
|
processing: '처리 중',
|
||||||
pay_with_stripe: '스트라이프로 결제',
|
pay_with_stripe: '스트라이프로 결제',
|
||||||
|
pay_with_paypal: 'PayPal로 결제',
|
||||||
reupload: '다시 업로드',
|
reupload: '다시 업로드',
|
||||||
upload: '업로드',
|
upload: '업로드',
|
||||||
files: '파일',
|
files: '파일',
|
||||||
@@ -1641,6 +1654,7 @@ const ja = {
|
|||||||
email_has_been_sent: 'メールが送信されました',
|
email_has_been_sent: 'メールが送信されました',
|
||||||
processing: '処理中',
|
processing: '処理中',
|
||||||
pay_with_stripe: 'Stripeで支払う',
|
pay_with_stripe: 'Stripeで支払う',
|
||||||
|
pay_with_paypal: 'PayPalで支払う',
|
||||||
reupload: '再アップロード',
|
reupload: '再アップロード',
|
||||||
upload: 'アップロード',
|
upload: 'アップロード',
|
||||||
files: 'ファイル',
|
files: 'ファイル',
|
||||||
|
|||||||
@@ -169,7 +169,7 @@
|
|||||||
class="base-input !text-2xl w-full mt-6 text-center"
|
class="base-input !text-2xl w-full mt-6 text-center"
|
||||||
:required="field.required && !isInitialsStarted"
|
:required="field.required && !isInitialsStarted"
|
||||||
:aria-label="field.name || t('initials')"
|
:aria-label="field.name || t('initials')"
|
||||||
:placeholder="`${t('type_initial_here')}...`"
|
:placeholder="`${t('type_initial_here')}${field.required ? '...' : ' (' + t('optional') + ')'}`"
|
||||||
type="text"
|
type="text"
|
||||||
@focus="$emit('focus')"
|
@focus="$emit('focus')"
|
||||||
@input="updateWrittenInitials"
|
@input="updateWrittenInitials"
|
||||||
@@ -293,7 +293,7 @@ export default {
|
|||||||
|
|
||||||
if (!this.isDrawInitials) {
|
if (!this.isDrawInitials) {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
if (this.$refs.textInput) {
|
if (this.$refs.textInput && this.field.required === true) {
|
||||||
this.initTextInitial()
|
this.initTextInitial()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<label
|
<label
|
||||||
v-if="!modelValue && !sessionId"
|
v-if="!modelValue && !isProcessing"
|
||||||
class="label text-xl sm:text-2xl py-0 mb-2 sm:mb-3.5 field-name-label"
|
class="label text-xl sm:text-2xl py-0 mb-2 sm:mb-3.5 field-name-label"
|
||||||
>
|
>
|
||||||
<MarkdownContent
|
<MarkdownContent
|
||||||
@@ -25,14 +25,14 @@
|
|||||||
class="hidden"
|
class="hidden"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-if="modelValue && !sessionId"
|
v-if="modelValue && !isProcessing"
|
||||||
class=" text-2xl mb-2"
|
class=" text-2xl mb-2"
|
||||||
>
|
>
|
||||||
{{ t('already_paid') }}
|
{{ t('already_paid') }}
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<button
|
<button
|
||||||
v-if="sessionId"
|
v-if="isProcessing"
|
||||||
type="button"
|
type="button"
|
||||||
disabled
|
disabled
|
||||||
class="base-button w-full modal-save-button"
|
class="base-button w-full modal-save-button"
|
||||||
@@ -49,8 +49,8 @@
|
|||||||
v-else
|
v-else
|
||||||
:id="field.uuid"
|
:id="field.uuid"
|
||||||
type="button"
|
type="button"
|
||||||
class="btn bg-[#7B73FF] text-white hover:bg-[#0A2540] text-lg w-full"
|
class="btn text-white text-lg w-full"
|
||||||
:class="{ disabled: isCreatingCheckout }"
|
:class="[isPaypal ? 'bg-[#003087] hover:bg-[#012169]' : 'bg-[#7B73FF] hover:bg-[#0A2540]', { disabled: isCreatingCheckout }]"
|
||||||
:disabled="isCreatingCheckout"
|
:disabled="isCreatingCheckout"
|
||||||
@click.prevent="postCheckout"
|
@click.prevent="postCheckout"
|
||||||
>
|
>
|
||||||
@@ -59,12 +59,16 @@
|
|||||||
width="22"
|
width="22"
|
||||||
class="animate-spin"
|
class="animate-spin"
|
||||||
/>
|
/>
|
||||||
|
<IconBrandPaypal
|
||||||
|
v-else-if="isPaypal"
|
||||||
|
width="22"
|
||||||
|
/>
|
||||||
<IconBrandStripe
|
<IconBrandStripe
|
||||||
v-else
|
v-else
|
||||||
width="22"
|
width="22"
|
||||||
/>
|
/>
|
||||||
<span>
|
<span>
|
||||||
{{ t('pay_with_stripe') }}
|
{{ isPaypal ? t('pay_with_paypal') : t('pay_with_stripe') }}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -72,13 +76,14 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { IconBrandStripe, IconInnerShadowTop, IconLoader } from '@tabler/icons-vue'
|
import { IconBrandStripe, IconBrandPaypal, IconInnerShadowTop, IconLoader } from '@tabler/icons-vue'
|
||||||
import MarkdownContent from './markdown_content'
|
import MarkdownContent from './markdown_content'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'PaymentStep',
|
name: 'PaymentStep',
|
||||||
components: {
|
components: {
|
||||||
IconBrandStripe,
|
IconBrandStripe,
|
||||||
|
IconBrandPaypal,
|
||||||
MarkdownContent,
|
MarkdownContent,
|
||||||
IconInnerShadowTop,
|
IconInnerShadowTop,
|
||||||
IconLoader
|
IconLoader
|
||||||
@@ -111,12 +116,18 @@ export default {
|
|||||||
submitterSlug: {
|
submitterSlug: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true
|
||||||
|
},
|
||||||
|
provider: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits: ['focus', 'submit', 'update:model-value', 'attached'],
|
emits: ['focus', 'submit', 'update:model-value', 'attached'],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
isCreatingCheckout: false,
|
isCreatingCheckout: false,
|
||||||
|
isProcessing: false,
|
||||||
isMathLoaded: false
|
isMathLoaded: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -131,8 +142,14 @@ export default {
|
|||||||
queryParams () {
|
queryParams () {
|
||||||
return new URLSearchParams(window.location.search)
|
return new URLSearchParams(window.location.search)
|
||||||
},
|
},
|
||||||
|
isPaypal () {
|
||||||
|
return this.provider === 'paypal'
|
||||||
|
},
|
||||||
|
paymentEndpoint () {
|
||||||
|
return this.isPaypal ? '/api/paypal_payments' : '/api/stripe_payments'
|
||||||
|
},
|
||||||
sessionId () {
|
sessionId () {
|
||||||
return this.queryParams.get('stripe_session_id')
|
return this.queryParams.get('stripe_session_id') || this.queryParams.get('paypal_order_id') || (this.isPaypal && this.queryParams.get('token'))
|
||||||
},
|
},
|
||||||
defaultName () {
|
defaultName () {
|
||||||
if (this.field.preferences?.price_id || this.field.preferences?.payment_link_id) {
|
if (this.field.preferences?.price_id || this.field.preferences?.payment_link_id) {
|
||||||
@@ -159,6 +176,8 @@ export default {
|
|||||||
},
|
},
|
||||||
async mounted () {
|
async mounted () {
|
||||||
if (this.sessionId) {
|
if (this.sessionId) {
|
||||||
|
this.isProcessing = true
|
||||||
|
|
||||||
this.$emit('submit')
|
this.$emit('submit')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,6 +194,21 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
normalizedPaymentUrl () {
|
||||||
|
const url = new URL(window.location.href)
|
||||||
|
const keys = ['stripe_session_id', 'paypal_order_id', 'field_uuid']
|
||||||
|
|
||||||
|
if (this.isPaypal) {
|
||||||
|
keys.push('token', 'PayerID')
|
||||||
|
}
|
||||||
|
|
||||||
|
keys.forEach((key) => url.searchParams.delete(key))
|
||||||
|
|
||||||
|
return url.toString()
|
||||||
|
},
|
||||||
|
normalizedPaymentParams () {
|
||||||
|
window.history.replaceState({}, document.title, this.normalizedPaymentUrl())
|
||||||
|
},
|
||||||
calculateFormula () {
|
calculateFormula () {
|
||||||
const transformedFormula = this.normalizeFormula(this.field.preferences.formula).replace(/{{(.*?)}}/g, (match, uuid) => {
|
const transformedFormula = this.normalizeFormula(this.field.preferences.formula).replace(/{{(.*?)}}/g, (match, uuid) => {
|
||||||
return this.readonlyValues[uuid] || this.values[uuid] || 0.0
|
return this.readonlyValues[uuid] || this.values[uuid] || 0.0
|
||||||
@@ -195,7 +229,7 @@ export default {
|
|||||||
},
|
},
|
||||||
async submit () {
|
async submit () {
|
||||||
if (this.sessionId) {
|
if (this.sessionId) {
|
||||||
return fetch(this.baseUrl + '/api/stripe_payments/' + this.sessionId, {
|
return fetch(this.baseUrl + this.paymentEndpoint + '/' + this.sessionId, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
submitter_slug: this.submitterSlug
|
submitter_slug: this.submitterSlug
|
||||||
@@ -203,16 +237,22 @@ export default {
|
|||||||
headers: { 'Content-Type': 'application/json' }
|
headers: { 'Content-Type': 'application/json' }
|
||||||
}).then(async (resp) => {
|
}).then(async (resp) => {
|
||||||
if (resp.status === 422 || resp.status === 500) {
|
if (resp.status === 422 || resp.status === 500) {
|
||||||
|
this.isProcessing = false
|
||||||
|
|
||||||
const data = await resp.json()
|
const data = await resp.json()
|
||||||
|
|
||||||
alert(data.error || 'Unexpected error')
|
if (data.error) {
|
||||||
|
alert(data.error)
|
||||||
|
}
|
||||||
|
|
||||||
return Promise.reject(new Error(data.error))
|
return Promise.reject(new Error(data.error))
|
||||||
}
|
}
|
||||||
|
|
||||||
const attachment = await resp.json()
|
const attachment = await resp.json()
|
||||||
|
|
||||||
window.history.replaceState({}, document.title, window.location.pathname)
|
this.isProcessing = false
|
||||||
|
|
||||||
|
this.normalizedPaymentParams()
|
||||||
|
|
||||||
this.$emit('update:model-value', attachment.uuid)
|
this.$emit('update:model-value', attachment.uuid)
|
||||||
this.$emit('attached', attachment)
|
this.$emit('attached', attachment)
|
||||||
@@ -226,13 +266,13 @@ export default {
|
|||||||
postCheckout ({ checkStatus } = {}) {
|
postCheckout ({ checkStatus } = {}) {
|
||||||
this.isCreatingCheckout = true
|
this.isCreatingCheckout = true
|
||||||
|
|
||||||
fetch(this.baseUrl + '/api/stripe_payments', {
|
fetch(this.baseUrl + this.paymentEndpoint, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
submitter_slug: this.submitterSlug,
|
submitter_slug: this.submitterSlug,
|
||||||
field_uuid: this.field.uuid,
|
field_uuid: this.field.uuid,
|
||||||
check_status: checkStatus,
|
check_status: checkStatus,
|
||||||
referer: document.location.href
|
referer: this.normalizedPaymentUrl()
|
||||||
}),
|
}),
|
||||||
headers: { 'Content-Type': 'application/json' }
|
headers: { 'Content-Type': 'application/json' }
|
||||||
}).then(async (resp) => {
|
}).then(async (resp) => {
|
||||||
|
|||||||
@@ -39,7 +39,7 @@
|
|||||||
required
|
required
|
||||||
maxlength="6"
|
maxlength="6"
|
||||||
autofocus
|
autofocus
|
||||||
inputmode="decimal"
|
inputmode="numeric"
|
||||||
@input="onInputCode"
|
@input="onInputCode"
|
||||||
>
|
>
|
||||||
<div class="flex justify-between mt-2 -mb-2 md:-mb-4">
|
<div class="flex justify-between mt-2 -mb-2 md:-mb-4">
|
||||||
|
|||||||
@@ -247,7 +247,7 @@
|
|||||||
class="base-input !text-2xl w-full mt-6"
|
class="base-input !text-2xl w-full mt-6"
|
||||||
:required="field.required && !isSignatureStarted"
|
:required="field.required && !isSignatureStarted"
|
||||||
:aria-label="field.name || t('signature')"
|
:aria-label="field.name || t('signature')"
|
||||||
:placeholder="`${t('type_signature_here')}...`"
|
:placeholder="`${t('type_signature_here')}${field.required ? '...' : ' (' + t('optional') + ')'}`"
|
||||||
type="text"
|
type="text"
|
||||||
@input="updateWrittenSignature"
|
@input="updateWrittenSignature"
|
||||||
>
|
>
|
||||||
@@ -535,7 +535,7 @@ export default {
|
|||||||
this.$nextTick(() => this.drawSignatureSrc())
|
this.$nextTick(() => this.drawSignatureSrc())
|
||||||
} else if (this.isTextSignature) {
|
} else if (this.isTextSignature) {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
if (this.$refs.textInput) {
|
if (this.$refs.textInput && this.field.required === true) {
|
||||||
this.initTypedSignature()
|
this.initTypedSignature()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -319,7 +319,7 @@ export default {
|
|||||||
default: false
|
default: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits: ['start-resize', 'stop-resize', 'start-drag', 'stop-drag', 'remove', 'scroll-to', 'add-custom-field', 'click-title'],
|
emits: ['start-resize', 'stop-resize', 'start-drag', 'stop-drag', 'remove', 'scroll-to', 'add-custom-field', 'click-title', 'multi-select'],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
isContenteditable: false,
|
isContenteditable: false,
|
||||||
@@ -798,6 +798,8 @@ export default {
|
|||||||
this.selectedAreasRef.value.splice(this.selectedAreasRef.value.indexOf(this.area), 1)
|
this.selectedAreasRef.value.splice(this.selectedAreasRef.value.indexOf(this.area), 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.$emit('multi-select', e)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -164,7 +164,7 @@
|
|||||||
v-if="withSendButton"
|
v-if="withSendButton"
|
||||||
id="send_button"
|
id="send_button"
|
||||||
:href="`/templates/${template.id}/submissions/new?with_link=true`"
|
:href="`/templates/${template.id}/submissions/new?with_link=true`"
|
||||||
data-turbo-frame="modal"
|
:data-turbo-frame="isMobile && isBreakpointLg ? '_top' : 'modal'"
|
||||||
class="white-button md:!px-6"
|
class="white-button md:!px-6"
|
||||||
@click="maybeShowErrorTemplateAlert"
|
@click="maybeShowErrorTemplateAlert"
|
||||||
>
|
>
|
||||||
@@ -202,7 +202,7 @@
|
|||||||
</button>
|
</button>
|
||||||
<div
|
<div
|
||||||
class="dropdown dropdown-end"
|
class="dropdown dropdown-end"
|
||||||
:class="{ 'dropdown-open': isDownloading }"
|
:class="{ 'dropdown-open': isDownloading || isPreviewLoading }"
|
||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
@@ -221,15 +221,23 @@
|
|||||||
:href="`/templates/${template.id}/form`"
|
:href="`/templates/${template.id}/form`"
|
||||||
data-turbo="false"
|
data-turbo="false"
|
||||||
class="flex items-center justify-center space-x-2"
|
class="flex items-center justify-center space-x-2"
|
||||||
|
@click.exact="isPreviewLoading = true"
|
||||||
>
|
>
|
||||||
<IconEye class="w-6 h-6 flex-shrink-0" />
|
<IconInnerShadowTop
|
||||||
|
v-if="isPreviewLoading"
|
||||||
|
class="animate-spin w-6 h-6 flex-shrink-0"
|
||||||
|
/>
|
||||||
|
<IconEye
|
||||||
|
v-else
|
||||||
|
class="w-6 h-6 flex-shrink-0"
|
||||||
|
/>
|
||||||
<span class="whitespace-nowrap">{{ t('save_and_preview') }}</span>
|
<span class="whitespace-nowrap">{{ t('save_and_preview') }}</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
:href="`/templates/${template.id}/preferences`"
|
:href="`/templates/${template.id}/preferences`"
|
||||||
data-turbo-frame="modal"
|
:data-turbo-frame="isMobile && isBreakpointLg ? '_top' : 'modal'"
|
||||||
class="flex space-x-2"
|
class="flex space-x-2"
|
||||||
@click="closeDropdown"
|
@click="closeDropdown"
|
||||||
>
|
>
|
||||||
@@ -281,6 +289,10 @@
|
|||||||
:href="`/templates/${template.id}`"
|
:href="`/templates/${template.id}`"
|
||||||
class="base-button"
|
class="base-button"
|
||||||
>
|
>
|
||||||
|
<IconX
|
||||||
|
width="20"
|
||||||
|
class="md:hidden stroke-2"
|
||||||
|
/>
|
||||||
<span class="hidden md:inline">
|
<span class="hidden md:inline">
|
||||||
{{ t('back') }}
|
{{ t('back') }}
|
||||||
</span>
|
</span>
|
||||||
@@ -779,7 +791,10 @@ export default {
|
|||||||
withVerification: this.withVerification,
|
withVerification: this.withVerification,
|
||||||
withKba: this.withKba,
|
withKba: this.withKba,
|
||||||
withPayment: this.withPayment,
|
withPayment: this.withPayment,
|
||||||
isPaymentConnected: this.isPaymentConnected,
|
isStripeConnected: this.isStripeConnected,
|
||||||
|
withStripe: this.withStripe,
|
||||||
|
withPaypal: this.withPaypal,
|
||||||
|
isPaypalConnected: this.isPaypalConnected,
|
||||||
withFormula: this.withFormula,
|
withFormula: this.withFormula,
|
||||||
withConditions: this.withConditions,
|
withConditions: this.withConditions,
|
||||||
withCustomFields: this.withCustomFields,
|
withCustomFields: this.withCustomFields,
|
||||||
@@ -1049,7 +1064,22 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
isPaymentConnected: {
|
isStripeConnected: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
withStripe: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
withPaypal: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
isPaypalConnected: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
default: false
|
default: false
|
||||||
@@ -1100,6 +1130,7 @@ export default {
|
|||||||
documentRefs: [],
|
documentRefs: [],
|
||||||
isBreakpointLg: false,
|
isBreakpointLg: false,
|
||||||
isDownloading: false,
|
isDownloading: false,
|
||||||
|
isPreviewLoading: false,
|
||||||
isLoadingBlankPage: false,
|
isLoadingBlankPage: false,
|
||||||
isSaving: false,
|
isSaving: false,
|
||||||
isDetectingPageFields: false,
|
isDetectingPageFields: false,
|
||||||
@@ -1241,7 +1272,7 @@ export default {
|
|||||||
|
|
||||||
return formulaFields.reduce((acc, f) => {
|
return formulaFields.reduce((acc, f) => {
|
||||||
if (this.conditionalFieldIndex[f.uuid] !== false) {
|
if (this.conditionalFieldIndex[f.uuid] !== false) {
|
||||||
acc[f.uuid] = this.calculateFormula(f)
|
acc[f.uuid] = f.type === 'text' ? this.evalTextFormula(f) : this.calculateFormula(f)
|
||||||
}
|
}
|
||||||
|
|
||||||
return acc
|
return acc
|
||||||
@@ -1363,6 +1394,10 @@ export default {
|
|||||||
if (document.location.search?.includes('stripe_connect_success')) {
|
if (document.location.search?.includes('stripe_connect_success')) {
|
||||||
document.querySelector('form[action="/auth/stripe_connect"]')?.closest('.dropdown')?.querySelector('label')?.focus()
|
document.querySelector('form[action="/auth/stripe_connect"]')?.closest('.dropdown')?.querySelector('label')?.focus()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (document.location.search?.includes('paypal_connect_success')) {
|
||||||
|
document.querySelector('a[href^="/auth/paypal_connect"]')?.closest('.dropdown')?.querySelector('label')?.focus()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
this.template.schema.forEach((item) => {
|
this.template.schema.forEach((item) => {
|
||||||
@@ -1504,6 +1539,25 @@ export default {
|
|||||||
|
|
||||||
return this.math.evaluate(transformedFormula.toLowerCase())
|
return this.math.evaluate(transformedFormula.toLowerCase())
|
||||||
},
|
},
|
||||||
|
evalTextFormula (field, depth = 0) {
|
||||||
|
if (depth > 10) return ''
|
||||||
|
|
||||||
|
return field.preferences.formula.replace(/{{(.*?)}}/g, (match, uuid) => {
|
||||||
|
const formulaField = this.fieldsUuidIndex[uuid]
|
||||||
|
|
||||||
|
if (formulaField?.preferences?.formula) {
|
||||||
|
if (formulaField.type === 'text') {
|
||||||
|
return this.evalTextFormula(formulaField, depth + 1)
|
||||||
|
} else if (this.isMathLoaded) {
|
||||||
|
return this.calculateFormula(formulaField)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = formulaField?.default_value
|
||||||
|
|
||||||
|
return Array.isArray(value) ? value.join(', ') : (value ?? '')
|
||||||
|
})
|
||||||
|
},
|
||||||
hasFormulaDependencyValue (field) {
|
hasFormulaDependencyValue (field) {
|
||||||
const normalized = this.normalizeFormula(field.preferences.formula)
|
const normalized = this.normalizeFormula(field.preferences.formula)
|
||||||
|
|
||||||
|
|||||||
@@ -98,11 +98,29 @@ dynamic-variable {
|
|||||||
overflow-wrap: anywhere;
|
overflow-wrap: anywhere;
|
||||||
}`)
|
}`)
|
||||||
|
|
||||||
|
const DROP_ATTRS = [
|
||||||
|
'srcdoc', 'xlink:href', 'srcset', 'action', 'formaction', 'poster',
|
||||||
|
'background', 'data', 'cite', 'ping', 'longdesc', 'manifest', 'profile'
|
||||||
|
]
|
||||||
|
|
||||||
|
const SAFE_URL_REGEXP = /^(?:https?:\/\/|data:image\/|blob:|mailto:|tel:|\{|#)/i
|
||||||
|
|
||||||
|
function isSafeAttr (name, value) {
|
||||||
|
const lowerName = name.toLowerCase()
|
||||||
|
|
||||||
|
if (lowerName.startsWith('on') || DROP_ATTRS.includes(lowerName)) return false
|
||||||
|
if ((lowerName === 'href' || lowerName === 'src') && !SAFE_URL_REGEXP.test(value.trim())) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
function collectDomAttrs (dom) {
|
function collectDomAttrs (dom) {
|
||||||
const attrs = {}
|
const attrs = {}
|
||||||
|
|
||||||
for (let i = 0; i < dom.attributes.length; i++) {
|
for (let i = 0; i < dom.attributes.length; i++) {
|
||||||
attrs[dom.attributes[i].name] = dom.attributes[i].value
|
const { name, value } = dom.attributes[i]
|
||||||
|
|
||||||
|
if (isSafeAttr(name, value)) attrs[name] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
return { htmlAttrs: attrs }
|
return { htmlAttrs: attrs }
|
||||||
|
|||||||
@@ -646,9 +646,7 @@ export default {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const container = document.createElement('div')
|
const container = new DOMParser().parseFromString(clipboardHtml, 'text/html').body
|
||||||
|
|
||||||
container.innerHTML = clipboardHtml
|
|
||||||
|
|
||||||
const fieldNodes = [...container.querySelectorAll('dynamic-field[data-field][data-area]')]
|
const fieldNodes = [...container.querySelectorAll('dynamic-field[data-field][data-area]')]
|
||||||
|
|
||||||
|
|||||||
@@ -494,6 +494,7 @@ import FieldType from './field_type.vue'
|
|||||||
import FieldSettings from './field_settings.vue'
|
import FieldSettings from './field_settings.vue'
|
||||||
import ContextSubmenu from './field_context_submenu.vue'
|
import ContextSubmenu from './field_context_submenu.vue'
|
||||||
import ContextModal from './field_context_modal.vue'
|
import ContextModal from './field_context_modal.vue'
|
||||||
|
import { stripeConnectedRef } from './payment_settings'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'FieldContextMenu',
|
name: 'FieldContextMenu',
|
||||||
@@ -610,7 +611,7 @@ export default {
|
|||||||
return this.withCondition && !['stamp', 'heading'].includes(this.field.type)
|
return this.withCondition && !['stamp', 'heading'].includes(this.field.type)
|
||||||
},
|
},
|
||||||
showFormula () {
|
showFormula () {
|
||||||
return this.field.type === 'number' || this.field.type === 'payment'
|
return this.field.type === 'number' || this.field.type === 'payment' || !!this.field.preferences?.formula
|
||||||
},
|
},
|
||||||
showRequired () {
|
showRequired () {
|
||||||
return this.withRequired && !['phone', 'stamp', 'verification', 'strikethrough', 'heading'].includes(this.field.type)
|
return this.withRequired && !['phone', 'stamp', 'verification', 'strikethrough', 'heading'].includes(this.field.type)
|
||||||
@@ -716,12 +717,18 @@ export default {
|
|||||||
currentCurrency () {
|
currentCurrency () {
|
||||||
return this.field.preferences?.currency || 'USD'
|
return this.field.preferences?.currency || 'USD'
|
||||||
},
|
},
|
||||||
|
isStripeConnected: () => stripeConnectedRef.value,
|
||||||
priceTypeOptions () {
|
priceTypeOptions () {
|
||||||
return [
|
const options = [
|
||||||
{ value: 'one_off', label: this.t('fixed') },
|
{ value: 'one_off', label: this.t('fixed') },
|
||||||
{ value: 'formula', label: this.t('formula') },
|
{ value: 'formula', label: this.t('formula') }
|
||||||
{ value: 'payment_link', label: this.t('payment_link') }
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if (this.isStripeConnected) {
|
||||||
|
options.push({ value: 'payment_link', label: this.t('payment_link') })
|
||||||
|
}
|
||||||
|
|
||||||
|
return options
|
||||||
},
|
},
|
||||||
currentPriceType () {
|
currentPriceType () {
|
||||||
if (this.field.preferences?.formula) {
|
if (this.field.preferences?.formula) {
|
||||||
|
|||||||
@@ -500,7 +500,7 @@
|
|||||||
</label>
|
</label>
|
||||||
</li>
|
</li>
|
||||||
<li
|
<li
|
||||||
v-if="field.type == 'number'"
|
v-if="field.type == 'number' || field.preferences?.formula"
|
||||||
class="field-settings-formula"
|
class="field-settings-formula"
|
||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
|
|||||||
@@ -61,7 +61,10 @@
|
|||||||
@input="resizeTextarea"
|
@input="resizeTextarea"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3 mt-1">
|
<div
|
||||||
|
v-if="field.type !== 'text'"
|
||||||
|
class="mb-3 mt-1"
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
target="blank"
|
target="blank"
|
||||||
class="text-sm mb-2 inline space-x-2 font-mono"
|
class="text-sm mb-2 inline space-x-2 font-mono"
|
||||||
@@ -161,7 +164,9 @@ export default {
|
|||||||
computed: {
|
computed: {
|
||||||
fields () {
|
fields () {
|
||||||
return this.template.fields.reduce((acc, f) => {
|
return this.template.fields.reduce((acc, f) => {
|
||||||
if (f !== this.field && this.isNumberField(f) && (!f.preferences?.formula || !f.preferences.formula.includes(this.field.uuid))) {
|
const isAllowed = this.field.type === 'text' ? this.isTextField(f) : this.isNumberField(f)
|
||||||
|
|
||||||
|
if (f !== this.field && isAllowed && (!f.preferences?.formula || !f.preferences.formula.includes(this.field.uuid))) {
|
||||||
acc.push(f)
|
acc.push(f)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,6 +184,9 @@ export default {
|
|||||||
isNumberField (field) {
|
isNumberField (field) {
|
||||||
return field.type === 'number' || (['radio', 'select'].includes(field.type) && field.options?.every((o) => String(o.value).match(/^[\d.-]+$/)))
|
return field.type === 'number' || (['radio', 'select'].includes(field.type) && field.options?.every((o) => String(o.value).match(/^[\d.-]+$/)))
|
||||||
},
|
},
|
||||||
|
isTextField (field) {
|
||||||
|
return ['text', 'number', 'select', 'radio', 'cells', 'phone'].includes(field.type)
|
||||||
|
},
|
||||||
humanizeFormula (text) {
|
humanizeFormula (text) {
|
||||||
return text.replace(/{{(.*?)}}/g, (match, uuid) => {
|
return text.replace(/{{(.*?)}}/g, (match, uuid) => {
|
||||||
const foundField = this.template.fields.find((f) => f.uuid === uuid)
|
const foundField = this.template.fields.find((f) => f.uuid === uuid)
|
||||||
|
|||||||
@@ -57,6 +57,7 @@
|
|||||||
@scroll-to="$emit('scroll-to', $event)"
|
@scroll-to="$emit('scroll-to', $event)"
|
||||||
@add-custom-field="$emit('add-custom-field', $event)"
|
@add-custom-field="$emit('add-custom-field', $event)"
|
||||||
@contextmenu="openAreaContextMenu($event, item.area, item.field)"
|
@contextmenu="openAreaContextMenu($event, item.area, item.field)"
|
||||||
|
@multi-select="openMultiSelectContextMenu"
|
||||||
@click-title="closeContextMenu"
|
@click-title="closeContextMenu"
|
||||||
/>
|
/>
|
||||||
<FieldArea
|
<FieldArea
|
||||||
@@ -408,6 +409,12 @@ export default {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.selectedAreasRef.value.length >= 2) {
|
||||||
|
this.openSelectionContextMenu(event)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
|
|
||||||
@@ -443,6 +450,13 @@ export default {
|
|||||||
areas: this.selectedAreasRef.value
|
areas: this.selectedAreasRef.value
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
openMultiSelectContextMenu (event) {
|
||||||
|
if (this.selectedAreasRef.value.length >= 2) {
|
||||||
|
this.openSelectionContextMenu(event)
|
||||||
|
} else {
|
||||||
|
this.closeContextMenu()
|
||||||
|
}
|
||||||
|
},
|
||||||
handleSelectionCopy () {
|
handleSelectionCopy () {
|
||||||
this.$emit('copy-selected-areas')
|
this.$emit('copy-selected-areas')
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<span
|
<span
|
||||||
class="dropdown dropdown-end field-settings-dropdown"
|
class="dropdown dropdown-end field-settings-dropdown"
|
||||||
:class="{ 'dropdown-open': withForceOpen && ((!field.preferences?.price && !field.preferences?.formula && !field.preferences?.price_id && !field.preferences?.payment_link_id) || !isConnected) && !isLoading }"
|
:class="{ 'dropdown-open': withForceOpen && !field.preferences?.price && !field.preferences?.formula && !field.preferences?.price_id && !field.preferences?.payment_link_id && !isLoading }"
|
||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
@click="closeDropdown"
|
@click="closeDropdown"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-if="!('price_id' in field.preferences) && !('payment_link_id' in field.preferences)"
|
v-if="isPaypal || (!('price_id' in field.preferences) && !('payment_link_id' in field.preferences))"
|
||||||
class="field-settings-currency py-1.5 px-1 relative"
|
class="field-settings-currency py-1.5 px-1 relative"
|
||||||
@click.stop
|
@click.stop
|
||||||
>
|
>
|
||||||
@@ -52,14 +52,14 @@
|
|||||||
@click.stop
|
@click.stop
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
v-if="'payment_link_id' in field.preferences"
|
v-if="isStripe && 'payment_link_id' in field.preferences"
|
||||||
v-model="field.preferences.payment_link_id"
|
v-model="field.preferences.payment_link_id"
|
||||||
placeholder="plink_XXXXX"
|
placeholder="plink_XXXXX"
|
||||||
class="input input-bordered input-xs w-full max-w-xs h-7 !outline-0"
|
class="input input-bordered input-xs w-full max-w-xs h-7 !outline-0"
|
||||||
@blur="save"
|
@blur="save"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
v-else-if="'price_id' in field.preferences"
|
v-else-if="isStripe && 'price_id' in field.preferences"
|
||||||
v-model="field.preferences.price_id"
|
v-model="field.preferences.price_id"
|
||||||
placeholder="Price ID: price_XXXXX"
|
placeholder="Price ID: price_XXXXX"
|
||||||
class="input input-bordered input-xs w-full max-w-xs h-7 !outline-0"
|
class="input input-bordered input-xs w-full max-w-xs h-7 !outline-0"
|
||||||
@@ -89,7 +89,10 @@
|
|||||||
>
|
>
|
||||||
{{ 'payment_link_id' in field.preferences ? t('payment_link') : t('price') }}
|
{{ 'payment_link_id' in field.preferences ? t('payment_link') : t('price') }}
|
||||||
</label>
|
</label>
|
||||||
<div class="flex items-center justify-center">
|
<div
|
||||||
|
v-if="isStripe && stripeConnected"
|
||||||
|
class="flex items-center justify-center"
|
||||||
|
>
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
class="hover:underline"
|
class="hover:underline"
|
||||||
@@ -120,21 +123,22 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="!isConnected || isOauthSuccess"
|
v-if="!stripeConnected && !paypalConnected"
|
||||||
class="field-settings-stripe-connect py-1.5 px-1 relative"
|
class="flex flex-col gap-1 py-1.5 px-1"
|
||||||
@click.stop
|
@click.stop
|
||||||
>
|
>
|
||||||
<div
|
<a
|
||||||
v-if="isConnected && isOauthSuccess"
|
v-if="withPaypal"
|
||||||
class="text-sm text-center"
|
:href="paypalConnectUrl"
|
||||||
|
target="_blank"
|
||||||
|
data-turbo="false"
|
||||||
|
class="btn bg-[#003087] hover:bg-[#012169] btn-sm text-white w-full"
|
||||||
>
|
>
|
||||||
<IconCircleCheck
|
<IconBrandPaypal class="w-4 h-4 inline" />
|
||||||
class="inline text-green-600 w-4 h-4"
|
<span>Connect PayPal</span>
|
||||||
/>
|
</a>
|
||||||
Stripe Connected
|
|
||||||
</div>
|
|
||||||
<form
|
<form
|
||||||
v-if="!isConnected"
|
v-if="withStripe"
|
||||||
data-turbo="false"
|
data-turbo="false"
|
||||||
action="/auth/stripe_connect"
|
action="/auth/stripe_connect"
|
||||||
accept-charset="UTF-8"
|
accept-charset="UTF-8"
|
||||||
@@ -170,33 +174,25 @@
|
|||||||
:disabled="isLoading"
|
:disabled="isLoading"
|
||||||
class="btn bg-[#7B73FF] hover:bg-[#0A2540] btn-sm text-white w-full"
|
class="btn bg-[#7B73FF] hover:bg-[#0A2540] btn-sm text-white w-full"
|
||||||
>
|
>
|
||||||
<span
|
<IconInnerShadowTop
|
||||||
v-if="isLoading"
|
v-if="isLoading"
|
||||||
class="flex items-center space-x-1"
|
class="w-4 h-4 animate-spin inline"
|
||||||
>
|
/>
|
||||||
<IconInnerShadowTop
|
<IconBrandStripe
|
||||||
class="w-4 h-4 animate-spin inline"
|
|
||||||
/>
|
|
||||||
<span>
|
|
||||||
Connect Stripe
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
v-else
|
v-else
|
||||||
class="flex items-center space-x-1"
|
class="w-4 h-4 inline"
|
||||||
>
|
/>
|
||||||
<IconBrandStripe
|
<span>Connect Stripe</span>
|
||||||
class="w-4 h-4 inline"
|
|
||||||
/>
|
|
||||||
<span>
|
|
||||||
Connect Stripe
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="isStripe && !stripeConnected"
|
||||||
|
class="px-1 pb-1.5"
|
||||||
|
@click.stop
|
||||||
|
>
|
||||||
<a
|
<a
|
||||||
v-if="!isConnected"
|
class="block link text-center text-xs"
|
||||||
class="block link text-center mt-1"
|
|
||||||
href="https://www.docuseal.com/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"
|
||||||
@@ -272,24 +268,25 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { IconMathFunction, IconSettings, IconCircleCheck, IconInfoCircle, IconBrandStripe, IconInnerShadowTop, IconRouteAltLeft, IconForms } from '@tabler/icons-vue'
|
import { IconMathFunction, IconSettings, IconInfoCircle, IconBrandStripe, IconBrandPaypal, IconInnerShadowTop, IconRouteAltLeft, IconForms } from '@tabler/icons-vue'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
|
|
||||||
const isConnected = ref(false)
|
export const stripeConnectedRef = ref(false)
|
||||||
|
export const paypalConnectedRef = ref(false)
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'PaymentSettings',
|
name: 'PaymentSettings',
|
||||||
components: {
|
components: {
|
||||||
IconSettings,
|
IconSettings,
|
||||||
IconCircleCheck,
|
|
||||||
IconRouteAltLeft,
|
IconRouteAltLeft,
|
||||||
IconInfoCircle,
|
IconInfoCircle,
|
||||||
IconForms,
|
IconForms,
|
||||||
IconMathFunction,
|
IconMathFunction,
|
||||||
IconInnerShadowTop,
|
IconInnerShadowTop,
|
||||||
IconBrandStripe
|
IconBrandStripe,
|
||||||
|
IconBrandPaypal
|
||||||
},
|
},
|
||||||
inject: ['backgroundColor', 'save', 'currencies', 't', 'isPaymentConnected'],
|
inject: ['backgroundColor', 'save', 'currencies', 't', 'isStripeConnected', 'isPaypalConnected', 'withStripe', 'withPaypal'],
|
||||||
props: {
|
props: {
|
||||||
field: {
|
field: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@@ -318,13 +315,27 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
isConnected: () => isConnected.value,
|
stripeConnected: () => stripeConnectedRef.value,
|
||||||
isOauthSuccess () {
|
paypalConnected: () => paypalConnectedRef.value,
|
||||||
return document.location.search?.includes('stripe_connect_success')
|
provider () {
|
||||||
|
if (this.paypalConnected && !this.stripeConnected) return 'paypal'
|
||||||
|
if (this.stripeConnected && !this.paypalConnected) return 'stripe'
|
||||||
|
if (this.withPaypal && !this.withStripe) return 'paypal'
|
||||||
|
|
||||||
|
return 'stripe'
|
||||||
|
},
|
||||||
|
isStripe () {
|
||||||
|
return this.provider === 'stripe'
|
||||||
|
},
|
||||||
|
isPaypal () {
|
||||||
|
return this.provider === 'paypal'
|
||||||
},
|
},
|
||||||
redirectUri () {
|
redirectUri () {
|
||||||
return document.location.origin + '/auth/stripe_connect/callback'
|
return document.location.origin + '/auth/stripe_connect/callback'
|
||||||
},
|
},
|
||||||
|
paypalConnectUrl () {
|
||||||
|
return `/auth/paypal_connect?redir=${encodeURIComponent(document.location.href)}`
|
||||||
|
},
|
||||||
defaultCurrencies () {
|
defaultCurrencies () {
|
||||||
return ['USD', 'EUR', 'GBP', 'CAD', 'AUD']
|
return ['USD', 'EUR', 'GBP', 'CAD', 'AUD']
|
||||||
},
|
},
|
||||||
@@ -363,26 +374,53 @@ export default {
|
|||||||
mounted () {
|
mounted () {
|
||||||
this.field.preferences.currency ||= this.defaultCurrency
|
this.field.preferences.currency ||= this.defaultCurrency
|
||||||
|
|
||||||
isConnected.value ||= this.isPaymentConnected
|
stripeConnectedRef.value ||= this.isStripeConnected
|
||||||
|
paypalConnectedRef.value ||= this.isPaypalConnected
|
||||||
|
|
||||||
if (!this.isConnected) {
|
if (this.withStripe && !this.stripeConnected && !this.paypalConnected) {
|
||||||
this.checkStatus()
|
this.checkStripeStatus()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.withPaypal && !this.paypalConnected && !this.stripeConnected) {
|
||||||
|
this.checkPaypalStatus()
|
||||||
|
window.addEventListener('focus', this.onWindowFocus)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
beforeUnmount () {
|
||||||
|
window.removeEventListener('focus', this.onWindowFocus)
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
checkStatus () {
|
onWindowFocus () {
|
||||||
|
if (this.withPaypal && !this.paypalConnected) {
|
||||||
|
this.checkPaypalStatus()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
checkStripeStatus () {
|
||||||
this.isLoading = true
|
this.isLoading = true
|
||||||
|
|
||||||
fetch('/api/stripe_connect').then(async (resp) => {
|
fetch('/api/stripe_connect').then(async (resp) => {
|
||||||
const { status } = await resp.json()
|
const { status } = await resp.json()
|
||||||
|
|
||||||
if (status === 'connected') {
|
if (status === 'connected') {
|
||||||
isConnected.value = true
|
stripeConnectedRef.value = true
|
||||||
|
|
||||||
|
window.removeEventListener('focus', this.onWindowFocus)
|
||||||
}
|
}
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
this.isLoading = false
|
this.isLoading = false
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
checkPaypalStatus () {
|
||||||
|
fetch('/api/paypal_connect').then(async (resp) => {
|
||||||
|
const { status } = await resp.json()
|
||||||
|
|
||||||
|
if (status === 'connected') {
|
||||||
|
paypalConnectedRef.value = true
|
||||||
|
|
||||||
|
window.removeEventListener('focus', this.onWindowFocus)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
closeDropdown () {
|
closeDropdown () {
|
||||||
document.activeElement.blur()
|
document.activeElement.blur()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,62 @@
|
|||||||
@mousedown.stop
|
@mousedown.stop
|
||||||
@pointerdown.stop
|
@pointerdown.stop
|
||||||
>
|
>
|
||||||
|
<label
|
||||||
|
v-if="requiredFields.length"
|
||||||
|
class="field-settings-required w-full px-2 py-1 rounded-md hover:bg-neutral-100 flex items-center space-x-2 text-sm cursor-pointer"
|
||||||
|
@click.stop
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
:checked="isAllRequired"
|
||||||
|
:indeterminate="isMixedRequired"
|
||||||
|
type="checkbox"
|
||||||
|
class="toggle toggle-xs"
|
||||||
|
:disabled="!editable"
|
||||||
|
@change="handleToggleRequired($event.target.checked)"
|
||||||
|
@click.stop
|
||||||
|
>
|
||||||
|
<span>{{ t('required') }}</span>
|
||||||
|
</label>
|
||||||
|
<label
|
||||||
|
v-if="readOnlyFields.length"
|
||||||
|
class="field-settings-read-only w-full px-2 py-1 rounded-md hover:bg-neutral-100 flex items-center space-x-2 text-sm cursor-pointer"
|
||||||
|
@click.stop
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
:checked="isAllReadOnly"
|
||||||
|
:indeterminate="isMixedReadOnly"
|
||||||
|
type="checkbox"
|
||||||
|
class="toggle toggle-xs"
|
||||||
|
:disabled="!editable"
|
||||||
|
@change="handleToggleReadOnly($event.target.checked)"
|
||||||
|
@click.stop
|
||||||
|
>
|
||||||
|
<span>{{ t('read_only') }}</span>
|
||||||
|
</label>
|
||||||
|
<hr
|
||||||
|
v-if="requiredFields.length || readOnlyFields.length"
|
||||||
|
class="my-1 border-neutral-200"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
v-if="showFont"
|
||||||
|
class="w-full px-2 py-1 rounded-md hover:bg-neutral-100 flex items-center space-x-2 text-sm"
|
||||||
|
@click.stop="openFontModal"
|
||||||
|
>
|
||||||
|
<IconTypography class="w-4 h-4" />
|
||||||
|
<span>{{ t('font') }}</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
v-if="showCondition"
|
||||||
|
class="w-full px-2 py-1 rounded-md hover:bg-neutral-100 flex items-center space-x-2 text-sm"
|
||||||
|
@click.stop="openConditionModal"
|
||||||
|
>
|
||||||
|
<IconRouteAltLeft class="w-4 h-4" />
|
||||||
|
<span>{{ t('condition') }}</span>
|
||||||
|
</button>
|
||||||
|
<hr
|
||||||
|
v-if="showFont || showCondition"
|
||||||
|
class="my-1 border-neutral-200"
|
||||||
|
>
|
||||||
<ContextSubmenu
|
<ContextSubmenu
|
||||||
:icon="IconLayoutAlignMiddle"
|
:icon="IconLayoutAlignMiddle"
|
||||||
:label="t('align')"
|
:label="t('align')"
|
||||||
@@ -61,26 +117,6 @@
|
|||||||
<span>{{ t('height') }}</span>
|
<span>{{ t('height') }}</span>
|
||||||
</button>
|
</button>
|
||||||
</ContextSubmenu>
|
</ContextSubmenu>
|
||||||
<hr
|
|
||||||
v-if="showFont || showCondition"
|
|
||||||
class="my-1 border-neutral-200"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
v-if="showFont"
|
|
||||||
class="w-full px-2 py-1 rounded-md hover:bg-neutral-100 flex items-center space-x-2 text-sm"
|
|
||||||
@click.stop="openFontModal"
|
|
||||||
>
|
|
||||||
<IconTypography class="w-4 h-4" />
|
|
||||||
<span>{{ t('font') }}</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
v-if="showCondition"
|
|
||||||
class="w-full px-2 py-1 rounded-md hover:bg-neutral-100 flex items-center space-x-2 text-sm"
|
|
||||||
@click.stop="openConditionModal"
|
|
||||||
>
|
|
||||||
<IconRouteAltLeft class="w-4 h-4" />
|
|
||||||
<span>{{ t('condition') }}</span>
|
|
||||||
</button>
|
|
||||||
<hr class="my-1 border-neutral-200">
|
<hr class="my-1 border-neutral-200">
|
||||||
<button
|
<button
|
||||||
class="w-full px-2 py-1 rounded-md hover:bg-neutral-100 flex items-center justify-between text-sm"
|
class="w-full px-2 py-1 rounded-md hover:bg-neutral-100 flex items-center justify-between text-sm"
|
||||||
@@ -192,6 +228,24 @@ export default {
|
|||||||
return this.template.fields.find((f) => f.areas?.includes(area))
|
return this.template.fields.find((f) => f.areas?.includes(area))
|
||||||
}).filter(Boolean)
|
}).filter(Boolean)
|
||||||
},
|
},
|
||||||
|
requiredFields () {
|
||||||
|
return this.selectedFields.filter((f) => !['phone', 'stamp', 'verification', 'strikethrough', 'heading'].includes(f.type))
|
||||||
|
},
|
||||||
|
readOnlyFields () {
|
||||||
|
return this.selectedFields.filter((f) => ['text', 'number', 'radio', 'multiple', 'select'].includes(f.type))
|
||||||
|
},
|
||||||
|
isAllRequired () {
|
||||||
|
return this.requiredFields.every((f) => f.required)
|
||||||
|
},
|
||||||
|
isMixedRequired () {
|
||||||
|
return !this.isAllRequired && this.requiredFields.some((f) => f.required)
|
||||||
|
},
|
||||||
|
isAllReadOnly () {
|
||||||
|
return this.readOnlyFields.every((f) => f.readonly)
|
||||||
|
},
|
||||||
|
isMixedReadOnly () {
|
||||||
|
return !this.isAllReadOnly && this.readOnlyFields.some((f) => f.readonly)
|
||||||
|
},
|
||||||
isMac () {
|
isMac () {
|
||||||
return (navigator.userAgentData?.platform || navigator.platform)?.toLowerCase()?.includes('mac')
|
return (navigator.userAgentData?.platform || navigator.platform)?.toLowerCase()?.includes('mac')
|
||||||
},
|
},
|
||||||
@@ -234,6 +288,16 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
handleToggleRequired (value) {
|
||||||
|
this.requiredFields.forEach((field) => { field.required = value })
|
||||||
|
|
||||||
|
this.save()
|
||||||
|
},
|
||||||
|
handleToggleReadOnly (value) {
|
||||||
|
this.readOnlyFields.forEach((field) => { field.readonly = value })
|
||||||
|
|
||||||
|
this.save()
|
||||||
|
},
|
||||||
onKeyDown (event) {
|
onKeyDown (event) {
|
||||||
if (event.key === 'Escape') {
|
if (event.key === 'Escape') {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|||||||
@@ -3,6 +3,9 @@
|
|||||||
class ProcessSubmitterCompletionJob
|
class ProcessSubmitterCompletionJob
|
||||||
include Sidekiq::Job
|
include Sidekiq::Job
|
||||||
|
|
||||||
|
BCC_LIMIT = 14
|
||||||
|
TooManyBcc = Class.new(StandardError)
|
||||||
|
|
||||||
def perform(params = {})
|
def perform(params = {})
|
||||||
submitter = Submitter.find(params['submitter_id'])
|
submitter = Submitter.find(params['submitter_id'])
|
||||||
submission = submitter.submission
|
submission = submitter.submission
|
||||||
@@ -133,16 +136,24 @@ class ProcessSubmitterCompletionJob
|
|||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
build_bcc_addresses(submission).each do |to|
|
enqueue_bcc_completed_emails(submitter, user, is_sent_to_user)
|
||||||
next if is_sent_to_user && to == user.email
|
|
||||||
|
|
||||||
SubmitterMailer.completed_email(submitter, user, to:).deliver_later!
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
maybe_enqueue_copy_emails(submitter)
|
maybe_enqueue_copy_emails(submitter)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def enqueue_bcc_completed_emails(submitter, user, is_sent_to_user)
|
||||||
|
bcc_addresses = build_bcc_addresses(submitter.submission)
|
||||||
|
|
||||||
|
raise TooManyBcc, submitter.account_id if Docuseal.multitenant? && bcc_addresses.size > BCC_LIMIT
|
||||||
|
|
||||||
|
bcc_addresses.each do |to|
|
||||||
|
next if is_sent_to_user && to == user.email
|
||||||
|
|
||||||
|
SubmitterMailer.completed_email(submitter, user, to:).deliver_later!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def maybe_enqueue_copy_emails(submitter)
|
def maybe_enqueue_copy_emails(submitter)
|
||||||
return if submitter.template&.preferences&.dig('documents_copy_email_enabled') == false
|
return if submitter.template&.preferences&.dig('documents_copy_email_enabled') == false
|
||||||
|
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ class AccountConfig < ApplicationRecord
|
|||||||
WITH_SIGNATURE_ID = 'with_signature_id'
|
WITH_SIGNATURE_ID = 'with_signature_id'
|
||||||
WITH_FILE_LINKS_KEY = 'with_file_links'
|
WITH_FILE_LINKS_KEY = 'with_file_links'
|
||||||
WITH_SIGNATURE_ID_REASON_KEY = 'with_signature_id_reason'
|
WITH_SIGNATURE_ID_REASON_KEY = 'with_signature_id_reason'
|
||||||
|
WITH_SIGNATURE_ID_COMPLETED_AT_KEY = 'with_signature_id_completed_at'
|
||||||
RECIPIENT_FORM_FIELDS_KEY = 'recipient_form_fields'
|
RECIPIENT_FORM_FIELDS_KEY = 'recipient_form_fields'
|
||||||
WITH_AUDIT_VALUES_KEY = 'with_audit_values'
|
WITH_AUDIT_VALUES_KEY = 'with_audit_values'
|
||||||
WITH_AUDIT_SENDER_KEY = 'with_audit_sender'
|
WITH_AUDIT_SENDER_KEY = 'with_audit_sender'
|
||||||
@@ -59,6 +60,7 @@ class AccountConfig < ApplicationRecord
|
|||||||
COMBINE_PDF_RESULT_KEY = 'combine_pdf_result_key'
|
COMBINE_PDF_RESULT_KEY = 'combine_pdf_result_key'
|
||||||
DOCUMENT_FILENAME_FORMAT_KEY = 'document_filename_format'
|
DOCUMENT_FILENAME_FORMAT_KEY = 'document_filename_format'
|
||||||
TEMPLATE_CUSTOM_FIELDS_KEY = 'template_custom_fields'
|
TEMPLATE_CUSTOM_FIELDS_KEY = 'template_custom_fields'
|
||||||
|
TEMPLATE_DATE_FORMATS_KEY = 'template_date_formats'
|
||||||
POLICY_LINKS_KEY = 'policy_links'
|
POLICY_LINKS_KEY = 'policy_links'
|
||||||
ENABLE_MCP_KEY = 'enable_mcp'
|
ENABLE_MCP_KEY = 'enable_mcp'
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<div class="flex flex-wrap space-y-4 md:flex-nowrap md:space-y-0">
|
<div class="flex flex-wrap space-y-4 md:flex-nowrap md:space-y-0">
|
||||||
<%= render 'shared/settings_nav' %>
|
<%= render 'shared/settings_nav' %>
|
||||||
<div class="flex-grow max-w-xl mx-auto">
|
<div class="flex-grow max-w-xl mx-auto">
|
||||||
<h1 class="text-4xl font-bold mb-4">
|
<h1 class="text-3xl md:text-4xl font-bold mb-4">
|
||||||
<%= t('account') %>
|
<%= t('account') %>
|
||||||
</h1>
|
</h1>
|
||||||
<%= form_for '', url: settings_account_path, method: :patch, html: { autocomplete: 'off', class: 'space-y-4' } do |f| %>
|
<%= form_for '', url: settings_account_path, method: :patch, html: { autocomplete: 'off', class: 'space-y-4' } do |f| %>
|
||||||
@@ -53,8 +53,8 @@
|
|||||||
<div class="flex items-center justify-between gap-4 py-2.5">
|
<div class="flex items-center justify-between gap-4 py-2.5">
|
||||||
<div class="flex items-center space-x-1">
|
<div class="flex items-center space-x-1">
|
||||||
<span class="text-left"><%= t('force_2fa_with_authenticator_app') %></span>
|
<span class="text-left"><%= t('force_2fa_with_authenticator_app') %></span>
|
||||||
<span class="tooltip tooltip-top flex cursor-pointer" data-tip="<%= t('require_two_factor_authentication_2fa_with_an_authenticator_app_e_g_google_authenticator_authy_all_users_signing_documents_must_pass_the_second_factor_verification_using_a_secure_code_in_addition_to_their_password') %>">
|
<span class="tooltip tooltip-top hidden md:flex cursor-pointer" data-tip="<%= t('require_two_factor_authentication_2fa_with_an_authenticator_app_e_g_google_authenticator_authy_all_users_signing_documents_must_pass_the_second_factor_verification_using_a_secure_code_in_addition_to_their_password') %>">
|
||||||
<%= svg_icon('info_circle', class: 'hidden md:inline-block w-4 h-4 shrink-0') %>
|
<%= svg_icon('info_circle', class: 'w-4 h-4 shrink-0') %>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<submit-form data-on="change" class="flex">
|
<submit-form data-on="change" class="flex">
|
||||||
@@ -70,8 +70,8 @@
|
|||||||
<div class="flex items-center justify-between gap-4 py-2.5">
|
<div class="flex items-center justify-between gap-4 py-2.5">
|
||||||
<div class="flex items-center space-x-1">
|
<div class="flex items-center space-x-1">
|
||||||
<span class="text-left"><%= t('add_signature_id_to_the_documents') %></span>
|
<span class="text-left"><%= t('add_signature_id_to_the_documents') %></span>
|
||||||
<span class="tooltip tooltip-top flex cursor-pointer" data-tip="<%= t('add_a_unique_signature_id_and_timestamp_to_each_signature_for_audit_and_traceability_purposes_along_with_the_timestamp_part_of_docuseals_21_cfr_part_11_compliance_settings') %>">
|
<span class="tooltip tooltip-top hidden md:flex cursor-pointer" data-tip="<%= t('add_a_unique_signature_id_and_timestamp_to_each_signature_for_audit_and_traceability_purposes_along_with_the_timestamp_part_of_docuseals_21_cfr_part_11_compliance_settings') %>">
|
||||||
<%= svg_icon('info_circle', class: 'hidden md:inline-block w-4 h-4 shrink-0') %>
|
<%= svg_icon('info_circle', class: 'w-4 h-4 shrink-0') %>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<submit-form data-on="change" class="flex">
|
<submit-form data-on="change" class="flex">
|
||||||
@@ -87,8 +87,8 @@
|
|||||||
<div class="flex items-center justify-between gap-4 py-2.5">
|
<div class="flex items-center justify-between gap-4 py-2.5">
|
||||||
<div class="flex items-center space-x-1">
|
<div class="flex items-center space-x-1">
|
||||||
<span class="text-left"><%= t('require_signing_reason') %></span>
|
<span class="text-left"><%= t('require_signing_reason') %></span>
|
||||||
<span class="tooltip tooltip-top flex cursor-pointer" data-tip="<%= t('require_signer_to_provide_a_reason_for_signing_before_completing_their_signature_e_g_approvals_certifications_part_of_docuseals_21_cfr_part_11_compliance_settings') %>">
|
<span class="tooltip tooltip-top hidden md:flex cursor-pointer" data-tip="<%= t('require_signer_to_provide_a_reason_for_signing_before_completing_their_signature_e_g_approvals_certifications_part_of_docuseals_21_cfr_part_11_compliance_settings') %>">
|
||||||
<%= svg_icon('info_circle', class: 'hidden md:inline-block w-4 h-4 shrink-0') %>
|
<%= svg_icon('info_circle', class: 'w-4 h-4 shrink-0') %>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<submit-form data-on="change" class="flex">
|
<submit-form data-on="change" class="flex">
|
||||||
@@ -104,8 +104,8 @@
|
|||||||
<div class="flex items-center justify-between gap-4 py-2.5">
|
<div class="flex items-center justify-between gap-4 py-2.5">
|
||||||
<div class="flex items-center space-x-1">
|
<div class="flex items-center space-x-1">
|
||||||
<span class="text-left"><%= t('allow_typed_text_signatures') %></span>
|
<span class="text-left"><%= t('allow_typed_text_signatures') %></span>
|
||||||
<span class="tooltip tooltip-top flex cursor-pointer" data-tip="<%= t('allow_signers_to_create_signatures_by_typing_their_name_instead_of_drawing_or_uploading_one') %>">
|
<span class="tooltip tooltip-top hidden md:flex cursor-pointer" data-tip="<%= t('allow_signers_to_create_signatures_by_typing_their_name_instead_of_drawing_or_uploading_one') %>">
|
||||||
<%= svg_icon('info_circle', class: 'hidden md:inline-block w-4 h-4 shrink-0') %>
|
<%= svg_icon('info_circle', class: 'w-4 h-4 shrink-0') %>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<submit-form data-on="change" class="flex">
|
<submit-form data-on="change" class="flex">
|
||||||
@@ -121,8 +121,8 @@
|
|||||||
<div class="flex items-center justify-between gap-4 py-2.5">
|
<div class="flex items-center justify-between gap-4 py-2.5">
|
||||||
<div class="flex items-center space-x-1">
|
<div class="flex items-center space-x-1">
|
||||||
<span class="text-left"><%= t('allow_to_resubmit_completed_forms') %></span>
|
<span class="text-left"><%= t('allow_to_resubmit_completed_forms') %></span>
|
||||||
<span class="tooltip tooltip-top flex cursor-pointer" data-tip="<%= t('allow_signers_to_resubmit_forms_after_completion_useful_when_corrections_or_multiple_submissions_are_needed') %>">
|
<span class="tooltip tooltip-top hidden md:flex cursor-pointer" data-tip="<%= t('allow_signers_to_resubmit_forms_after_completion_useful_when_corrections_or_multiple_submissions_are_needed') %>">
|
||||||
<%= svg_icon('info_circle', class: 'hidden md:inline-block w-4 h-4 shrink-0') %>
|
<%= svg_icon('info_circle', class: 'w-4 h-4 shrink-0') %>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<submit-form data-on="change" class="flex">
|
<submit-form data-on="change" class="flex">
|
||||||
@@ -138,8 +138,8 @@
|
|||||||
<div class="flex items-center justify-between gap-4 py-2.5">
|
<div class="flex items-center justify-between gap-4 py-2.5">
|
||||||
<div class="flex items-center space-x-1">
|
<div class="flex items-center space-x-1">
|
||||||
<span class="text-left"><%= t('allow_to_decline_documents') %></span>
|
<span class="text-left"><%= t('allow_to_decline_documents') %></span>
|
||||||
<span class="tooltip tooltip-top flex cursor-pointer" data-tip="<%= t('allow_recipients_to_decline_signing_a_document_the_decline_reason_notification_will_be_sent_to_the_signature_requester') %>">
|
<span class="tooltip tooltip-top hidden md:flex cursor-pointer" data-tip="<%= t('allow_recipients_to_decline_signing_a_document_the_decline_reason_notification_will_be_sent_to_the_signature_requester') %>">
|
||||||
<%= svg_icon('info_circle', class: 'hidden md:inline-block w-4 h-4 shrink-0') %>
|
<%= svg_icon('info_circle', class: 'w-4 h-4 shrink-0') %>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<% if !Docuseal.multitenant? || can?(:manage, :disable_decline) %>
|
<% if !Docuseal.multitenant? || can?(:manage, :disable_decline) %>
|
||||||
@@ -147,7 +147,7 @@
|
|||||||
<%= f.check_box :value, class: 'toggle', checked: account_config.value != false %>
|
<%= f.check_box :value, class: 'toggle', checked: account_config.value != false %>
|
||||||
</submit-form>
|
</submit-form>
|
||||||
<% else %>
|
<% else %>
|
||||||
<a href="<%= console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") %>" data-turbo="false" data-tip="<%= I18n.t('unlock_with_docuseal_pro') %>" data-on="change" class="flex tooltip">
|
<a href="<%= console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") %>" data-turbo="false" data-tip="<%= I18n.t('unlock_with_docuseal_pro') %>" data-on="change" class="flex tooltip tooltip-no-touch">
|
||||||
<%= f.check_box :value, class: 'toggle pointer-events-none', checked: account_config.value != false, disabled: true %>
|
<%= f.check_box :value, class: 'toggle pointer-events-none', checked: account_config.value != false, disabled: true %>
|
||||||
</a>
|
</a>
|
||||||
<% end %>
|
<% end %>
|
||||||
@@ -161,8 +161,8 @@
|
|||||||
<div class="flex items-center justify-between gap-4 py-2.5">
|
<div class="flex items-center justify-between gap-4 py-2.5">
|
||||||
<div class="flex items-center space-x-1">
|
<div class="flex items-center space-x-1">
|
||||||
<span class="text-left"><%= t('allow_to_delegate_documents') %></span>
|
<span class="text-left"><%= t('allow_to_delegate_documents') %></span>
|
||||||
<span class="tooltip tooltip-top flex cursor-pointer" data-tip="<%= t('allow_recipients_to_delegate_documents') %>">
|
<span class="tooltip tooltip-top hidden md:flex cursor-pointer" data-tip="<%= t('allow_recipients_to_delegate_documents') %>">
|
||||||
<%= svg_icon('info_circle', class: 'hidden md:inline-block w-4 h-4 shrink-0') %>
|
<%= svg_icon('info_circle', class: 'w-4 h-4 shrink-0') %>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<% if !Docuseal.multitenant? || can?(:manage, :delegate_form) %>
|
<% if !Docuseal.multitenant? || can?(:manage, :delegate_form) %>
|
||||||
@@ -170,7 +170,7 @@
|
|||||||
<%= f.check_box :value, class: 'toggle', checked: account_config.value == true %>
|
<%= f.check_box :value, class: 'toggle', checked: account_config.value == true %>
|
||||||
</submit-form>
|
</submit-form>
|
||||||
<% else %>
|
<% else %>
|
||||||
<a href="<%= console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") %>" data-turbo="false" data-tip="<%= I18n.t('unlock_with_docuseal_pro') %>" class="flex tooltip">
|
<a href="<%= console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") %>" data-turbo="false" data-tip="<%= I18n.t('unlock_with_docuseal_pro') %>" class="flex tooltip tooltip-no-touch">
|
||||||
<%= f.check_box :value, class: 'toggle pointer-events-none', checked: account_config.value == true, disabled: true %>
|
<%= f.check_box :value, class: 'toggle pointer-events-none', checked: account_config.value == true, disabled: true %>
|
||||||
</a>
|
</a>
|
||||||
<% end %>
|
<% end %>
|
||||||
@@ -184,8 +184,8 @@
|
|||||||
<div class="flex items-center justify-between gap-4 py-2.5">
|
<div class="flex items-center justify-between gap-4 py-2.5">
|
||||||
<div class="flex items-center space-x-1">
|
<div class="flex items-center space-x-1">
|
||||||
<span class="text-left"><%= t('remember_and_pre_fill_signatures') %></span>
|
<span class="text-left"><%= t('remember_and_pre_fill_signatures') %></span>
|
||||||
<span class="tooltip tooltip-top flex cursor-pointer" data-tip="<%= t('save_a_users_signature_and_automatically_pre_fill_it_in_future_signing_sessions') %>">
|
<span class="tooltip tooltip-top hidden md:flex cursor-pointer" data-tip="<%= t('save_a_users_signature_and_automatically_pre_fill_it_in_future_signing_sessions') %>">
|
||||||
<%= svg_icon('info_circle', class: 'hidden md:inline-block w-4 h-4 shrink-0') %>
|
<%= svg_icon('info_circle', class: 'w-4 h-4 shrink-0') %>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<submit-form data-on="change" class="flex">
|
<submit-form data-on="change" class="flex">
|
||||||
@@ -201,8 +201,8 @@
|
|||||||
<div class="flex items-center justify-between gap-4 py-2.5">
|
<div class="flex items-center justify-between gap-4 py-2.5">
|
||||||
<div class="flex items-center space-x-1">
|
<div class="flex items-center space-x-1">
|
||||||
<span class="text-left"><%= t('expirable_file_download_links') %></span>
|
<span class="text-left"><%= t('expirable_file_download_links') %></span>
|
||||||
<span class="tooltip tooltip-top flex cursor-pointer" data-tip="<%= t('make_document_download_links_expire_after_40_minutes_to_prevent_long_term_access_and_enhance_security') %>">
|
<span class="tooltip tooltip-top hidden md:flex cursor-pointer" data-tip="<%= t('make_document_download_links_expire_after_40_minutes_to_prevent_long_term_access_and_enhance_security') %>">
|
||||||
<%= svg_icon('info_circle', class: 'hidden md:inline-block w-4 h-4 shrink-0') %>
|
<%= svg_icon('info_circle', class: 'w-4 h-4 shrink-0') %>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<submit-form data-on="change" class="flex">
|
<submit-form data-on="change" class="flex">
|
||||||
@@ -218,8 +218,8 @@
|
|||||||
<div class="flex items-center justify-between gap-4 py-2.5">
|
<div class="flex items-center justify-between gap-4 py-2.5">
|
||||||
<div class="flex items-center space-x-1">
|
<div class="flex items-center space-x-1">
|
||||||
<span class="text-left"><%= t('require_authentication_for_file_download_links') %></span>
|
<span class="text-left"><%= t('require_authentication_for_file_download_links') %></span>
|
||||||
<span class="tooltip tooltip-top flex cursor-pointer" data-tip="<%= t('require_authentication_with_user_login_or_api_key_to_access_the_document_download_links') %>">
|
<span class="tooltip tooltip-top hidden md:flex cursor-pointer" data-tip="<%= t('require_authentication_with_user_login_or_api_key_to_access_the_document_download_links') %>">
|
||||||
<%= svg_icon('info_circle', class: 'hidden md:inline-block w-4 h-4 shrink-0') %>
|
<%= svg_icon('info_circle', class: 'w-4 h-4 shrink-0') %>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<submit-form data-on="change" class="flex">
|
<submit-form data-on="change" class="flex">
|
||||||
@@ -235,8 +235,8 @@
|
|||||||
<div class="flex items-center justify-between gap-4 py-2.5">
|
<div class="flex items-center justify-between gap-4 py-2.5">
|
||||||
<div class="flex items-center space-x-1">
|
<div class="flex items-center space-x-1">
|
||||||
<span class="text-left"><%= t('combine_completed_documents_and_audit_log') %></span>
|
<span class="text-left"><%= t('combine_completed_documents_and_audit_log') %></span>
|
||||||
<span class="tooltip tooltip-top flex cursor-pointer" data-tip="<%= t('combine_signed_documents_and_the_audit_log_into_a_single_pdf_file_for_easier_recordkeeping_and_compliance') %>">
|
<span class="tooltip tooltip-top hidden md:flex cursor-pointer" data-tip="<%= t('combine_signed_documents_and_the_audit_log_into_a_single_pdf_file_for_easier_recordkeeping_and_compliance') %>">
|
||||||
<%= svg_icon('info_circle', class: 'hidden md:inline-block w-4 h-4 shrink-0') %>
|
<%= svg_icon('info_circle', class: 'w-4 h-4 shrink-0') %>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<submit-form data-on="change" class="flex">
|
<submit-form data-on="change" class="flex">
|
||||||
@@ -253,8 +253,8 @@
|
|||||||
<div class="flex items-center justify-between gap-4 py-2.5">
|
<div class="flex items-center justify-between gap-4 py-2.5">
|
||||||
<div class="flex items-center space-x-1">
|
<div class="flex items-center space-x-1">
|
||||||
<span class="text-left"><%= t('always_enforce_signing_order') %></span>
|
<span class="text-left"><%= t('always_enforce_signing_order') %></span>
|
||||||
<span class="tooltip tooltip-top flex cursor-pointer" data-tip="<%= t('make_the_recipients_signing_order_always_enforced_so_that_the_second_signer_can_start_signing_their_part_only_after_the_first_signer_has_completed_signing') %>">
|
<span class="tooltip tooltip-top hidden md:flex cursor-pointer" data-tip="<%= t('make_the_recipients_signing_order_always_enforced_so_that_the_second_signer_can_start_signing_their_part_only_after_the_first_signer_has_completed_signing') %>">
|
||||||
<%= svg_icon('info_circle', class: 'hidden md:inline-block w-4 h-4 shrink-0') %>
|
<%= svg_icon('info_circle', class: 'w-4 h-4 shrink-0') %>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<submit-form data-on="change" class="flex">
|
<submit-form data-on="change" class="flex">
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<%= render 'shared/settings_nav' %>
|
<%= render 'shared/settings_nav' %>
|
||||||
<div class="flex-grow">
|
<div class="flex-grow">
|
||||||
<div class="flex flex-col gap-2 md:flex-row md:justify-between md:items-end mb-4">
|
<div class="flex flex-col gap-2 md:flex-row md:justify-between md:items-end mb-4">
|
||||||
<h1 class="text-4xl font-bold">API</h1>
|
<h1 class="text-3xl md:text-4xl font-bold">API</h1>
|
||||||
<%= render 'shared/test_mode_toggle' %>
|
<%= render 'shared/test_mode_toggle' %>
|
||||||
</div>
|
</div>
|
||||||
<div class="card bg-base-200">
|
<div class="card bg-base-200">
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<form action="<%= root_path %>" method="get" id="templates_submissions_toggle" class="bg-base-200 px-1.5 rounded-xl py-1 whitespace-nowrap">
|
<form action="<%= root_path %>" method="get" id="templates_submissions_toggle" class="bg-base-200 px-1.5 rounded-xl py-1 whitespace-nowrap">
|
||||||
<toggle-cookies data-value="templates" data-key="dashboard_view" class="sm:tooltip tooltip-top" data-tip="<%= t('templates') %>">
|
<toggle-cookies data-value="templates" data-key="dashboard_view" class="tooltip tooltip-top tooltip-no-touch" data-tip="<%= t('templates') %>">
|
||||||
<button class="<%= local_assigns[:selected] == 'submissions' ? 'btn !border !rounded-lg btn-square !p-0 !btn-sm !h-8 !w-9' : 'btn btn-neutral !rounded-lg btn-square !p-0 hover:text-neutral-300 !btn-sm !h-8 !w-9 disabled:btn-neutral' %>">
|
<button class="<%= local_assigns[:selected] == 'submissions' ? 'btn !border !rounded-lg btn-square !p-0 !btn-sm !h-8 !w-9' : 'btn btn-neutral !rounded-lg btn-square !p-0 hover:text-neutral-300 !btn-sm !h-8 !w-9 disabled:btn-neutral' %>">
|
||||||
<%= svg_icon('layout_grid', class: 'w-6 h-6 stroke-2') %>
|
<%= svg_icon('layout_grid', class: 'w-6 h-6 stroke-2') %>
|
||||||
</button>
|
</button>
|
||||||
</toggle-cookies>
|
</toggle-cookies>
|
||||||
<toggle-cookies data-value="submissions" data-key="dashboard_view" class="sm:tooltip tooltip-top" data-tip="<%= t('submissions') %>">
|
<toggle-cookies data-value="submissions" data-key="dashboard_view" class="tooltip tooltip-top tooltip-no-touch" data-tip="<%= t('submissions') %>">
|
||||||
<button class="<%= local_assigns[:selected] == 'submissions' ? 'btn btn-neutral !rounded-lg btn-square !p-0 hover:text-neutral-300 !btn-sm !h-8 !w-9' : 'btn !border !rounded-lg btn-square !p-0 !btn-sm !h-8 !w-9 disabled:btn-neutral' %>">
|
<button class="<%= local_assigns[:selected] == 'submissions' ? 'btn btn-neutral !rounded-lg btn-square !p-0 hover:text-neutral-300 !btn-sm !h-8 !w-9' : 'btn !border !rounded-lg btn-square !p-0 !btn-sm !h-8 !w-9 disabled:btn-neutral' %>">
|
||||||
<%= svg_icon('layout_list', class: 'w-6 h-6 stroke-2') %>
|
<%= svg_icon('layout_list', class: 'w-6 h-6 stroke-2') %>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<%= label_tag 'user[otp_attempt]', t('two_factor_code_from_authenticator_app'), class: 'label' %>
|
<%= label_tag 'user[otp_attempt]', t('two_factor_code_from_authenticator_app'), class: 'label' %>
|
||||||
<%= text_field_tag 'user[otp_attempt]', nil, autofocus: true, autocomplete: 'off', placeholder: 'XXX-XXX', required: true, class: 'base-input' %>
|
<%= text_field_tag 'user[otp_attempt]', nil, autofocus: true, autocomplete: 'off', inputmode: 'numeric', placeholder: 'XXXXXX', required: true, class: 'base-input' %>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<div class="flex flex-wrap space-y-4 md:flex-nowrap md:space-y-0">
|
<div class="flex flex-wrap space-y-4 md:flex-nowrap md:space-y-0">
|
||||||
<%= render 'shared/settings_nav' %>
|
<%= render 'shared/settings_nav' %>
|
||||||
<div class="flex-grow max-w-xl mx-auto">
|
<div class="flex-grow max-w-xl mx-auto min-w-0">
|
||||||
<h1 class="text-4xl font-bold mb-4">Email SMTP</h1>
|
<h1 class="text-3xl md:text-4xl font-bold mb-4">Email SMTP</h1>
|
||||||
<% value = @encrypted_config.value || {} %>
|
<% value = @encrypted_config.value || {} %>
|
||||||
<%= form_for @encrypted_config, url: settings_email_index_path, method: :post, html: { autocomplete: 'off', class: 'space-y-4' } do |f| %>
|
<%= form_for @encrypted_config, url: settings_email_index_path, method: :post, html: { autocomplete: 'off', class: 'space-y-4' } do |f| %>
|
||||||
<%= f.fields_for :value do |ff| %>
|
<%= f.fields_for :value do |ff| %>
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
<% if !Docuseal.multitenant? || can?(:manage, :personalization_advanced) %>
|
<% if !Docuseal.multitenant? || can?(:manage, :personalization_advanced) %>
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<%= ff.label :security_label, 'SMTP Security', class: 'label' %>
|
<%= ff.label :security_label, 'SMTP Security', class: 'label' %>
|
||||||
<div class="flex items-center space-x-6">
|
<div class="flex flex-wrap items-center gap-x-6">
|
||||||
<% [%w[STARTTLS none], %w[TLS tls], %w[SSL ssl], %w[Noverify noverify]].each do |(label, val)| %>
|
<% [%w[STARTTLS none], %w[TLS tls], %w[SSL ssl], %w[Noverify noverify]].each do |(label, val)| %>
|
||||||
<%= ff.label :security, value: val, for: "#{val}_radio", class: 'label' do %>
|
<%= ff.label :security, value: val, for: "#{val}_radio", class: 'label' do %>
|
||||||
<%= ff.radio_button :security, val, checked: (value['security'].blank? && val == 'none') || value['security'] == val, id: "#{val}_radio", class: 'base-radio mr-2' %>
|
<%= ff.radio_button :security, val, checked: (value['security'].blank? && val == 'none') || value['security'] == val, id: "#{val}_radio", class: 'base-radio mr-2' %>
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
<tr scope="row" class="group">
|
<tr class="group">
|
||||||
<td class="flex items-center space-x-1">
|
<td class="flex items-center space-x-1">
|
||||||
<%= svg_icon('discount_check_filled', class: 'w-6 h-6 text-green-500') %>
|
<%= svg_icon('discount_check_filled', class: 'w-6 h-6 shrink-0 text-green-500') %>
|
||||||
<span class="flex items-center">
|
<span class="flex items-center">
|
||||||
<%= t('docuseal_trusted_signature') %>
|
<span class="whitespace-nowrap"><%= t('docuseal_trusted_signature') %></span>
|
||||||
<div class="tooltip ml-1" data-tip="<%= t('sign_documents_with_trusted_certificate_provided_by_docu_seal_your_documents_and_data_are_never_shared_with_docu_seal_p_d_f_checksum_is_provided_to_generate_a_trusted_signature') %>">
|
<div class="tooltip tooltip-top ml-1 shrink-0" data-tip="<%= t('sign_documents_with_trusted_certificate_provided_by_docu_seal_your_documents_and_data_are_never_shared_with_docu_seal_p_d_f_checksum_is_provided_to_generate_a_trusted_signature') %>">
|
||||||
<%= svg_icon('circle_question', class: 'w-4 h-4 stroke-1') %>
|
<%= svg_icon('circle_question', class: 'w-4 h-4 stroke-1 shrink-0') %>
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="<%= "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premises" }.to_query}" %>" class="btn btn-neutral btn-sm text-white">
|
<a href="<%= "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premises" }.to_query}" %>" class="btn btn-neutral btn-sm text-white whitespace-nowrap">
|
||||||
<%= t('unlock_with_docuseal_pro') %>
|
<%= t('unlock_with_docuseal_pro') %>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<%= render 'shared/settings_nav' %>
|
<%= render 'shared/settings_nav' %>
|
||||||
<div class="md:flex-grow">
|
<div class="md:flex-grow">
|
||||||
<div class="max-w-xl">
|
<div class="max-w-xl">
|
||||||
<h1 class="text-4xl font-bold mb-4">
|
<h1 class="text-3xl md:text-4xl font-bold mb-4">
|
||||||
<%= t('pdf_signature') %>
|
<%= t('pdf_signature') %>
|
||||||
</h1>
|
</h1>
|
||||||
<div id="result">
|
<div id="result">
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col md:flex-row gap-2 md:justify-between md:items-end mb-4 mt-8">
|
<div class="flex flex-col md:flex-row gap-2 md:justify-between md:items-end mb-4 mt-8">
|
||||||
<h2 class="text-3xl font-bold">
|
<h2 class="text-2xl md:text-3xl font-bold">
|
||||||
<%= t('signing_certificates') %>
|
<%= t('signing_certificates') %>
|
||||||
</h2>
|
</h2>
|
||||||
<% if can?(:create, @encrypted_config) %>
|
<% if can?(:create, @encrypted_config) %>
|
||||||
@@ -72,7 +72,7 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
<% @pkcs_list.each do |item| %>
|
<% @pkcs_list.each do |item| %>
|
||||||
<% next if item['pkcs'].blank? %>
|
<% next if item['pkcs'].blank? %>
|
||||||
<tr scope="row" class="group">
|
<tr class="group">
|
||||||
<td>
|
<td>
|
||||||
<%= item['name'] %>
|
<%= item['name'] %>
|
||||||
</td>
|
</td>
|
||||||
@@ -109,7 +109,7 @@
|
|||||||
<% if !Docuseal.multitenant? && can?(:manage, encrypted_config) %>
|
<% if !Docuseal.multitenant? && can?(:manage, encrypted_config) %>
|
||||||
<div class="flex-grow max-w-xl">
|
<div class="flex-grow max-w-xl">
|
||||||
<div class="flex justify-between items-end mb-4 mt-8">
|
<div class="flex justify-between items-end mb-4 mt-8">
|
||||||
<h2 class="text-3xl font-bold">
|
<h2 class="text-2xl md:text-3xl font-bold">
|
||||||
<%= t('timestamp_server') %>
|
<%= t('timestamp_server') %>
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
@@ -137,7 +137,7 @@
|
|||||||
<% if can?(:manage, account_config) %>
|
<% if can?(:manage, account_config) %>
|
||||||
<div class="px-1 mt-8 max-w-xl">
|
<div class="px-1 mt-8 max-w-xl">
|
||||||
<div class="flex justify-between items-end mb-4 mt-8">
|
<div class="flex justify-between items-end mb-4 mt-8">
|
||||||
<h2 class="text-3xl font-bold">
|
<h2 class="text-2xl md:text-3xl font-bold">
|
||||||
<%= t('preferences') %>
|
<%= t('preferences') %>
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
<svg class="<%= local_assigns[:class] %>" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-brand-google-drive"><path stroke="none" d="M0 0h24v24H0z" fill="none" /><path d="M12 10l-6 10l-3 -5l6 -10z" /><path d="M9 15h12l-3 5h-12" /><path d="M15 15l-6 -10h6l6 10z" /></svg>
|
<svg class="<%= local_assigns[:class] %>" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none" /><path d="M12 10l-6 10l-3 -5l6 -10z" /><path d="M9 15h12l-3 5h-12" /><path d="M15 15l-6 -10h6l6 10z" /></svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 458 B After Width: | Height: | Size: 381 B |
@@ -1,8 +1,7 @@
|
|||||||
<svg class="<%= local_assigns[:class] %>" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
<svg class="<%= local_assigns[:class] %>" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
<path d="M3 5a1 1 0 0 1 1 -1h16a1 1 0 0 1 1 1v10a1 1 0 0 1 -1 1h-16a1 1 0 0 1 -1 -1v-10z" />
|
<path d="M3 5a1 1 0 0 1 1 -1h16a1 1 0 0 1 1 1v10a1 1 0 0 1 -1 1h-16a1 1 0 0 1 -1 -1v-10z" />
|
||||||
<path d="M7 20h10" /><path d="M9 16v4" />
|
<path d="M7 20h10" />
|
||||||
<path d="M15 16v4" /></svg>
|
|
||||||
<path d="M9 16v4" />
|
<path d="M9 16v4" />
|
||||||
<path d="M15 16v4" />
|
<path d="M15 16v4" />
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 497 B After Width: | Height: | Size: 447 B |
@@ -0,0 +1,6 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="<%= local_assigns[:class] %>" width="44" height="44" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<path d="M8 9h8" />
|
||||||
|
<path d="M8 13h6" />
|
||||||
|
<path d="M18 4a3 3 0 0 1 3 3v8a3 3 0 0 1 -3 3h-5l-5 3v-3h-2a3 3 0 0 1 -3 -3v-8a3 3 0 0 1 3 -3h12" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 431 B |
@@ -2,7 +2,7 @@
|
|||||||
<%= render 'shared/settings_nav' %>
|
<%= render 'shared/settings_nav' %>
|
||||||
<div class="md:flex-grow">
|
<div class="md:flex-grow">
|
||||||
<div class="flex flex-col md:flex-row md:flex-wrap gap-2 md:justify-between md:items-end mb-4 min-h-12">
|
<div class="flex flex-col md:flex-row md:flex-wrap gap-2 md:justify-between md:items-end mb-4 min-h-12">
|
||||||
<h1 class="text-4xl font-bold">
|
<h1 class="text-3xl md:text-4xl font-bold">
|
||||||
<%= t('mcp_server') %>
|
<%= t('mcp_server') %>
|
||||||
</h1>
|
</h1>
|
||||||
<div class="flex flex-col md:flex-row gap-y-2 gap-x-4 md:items-center">
|
<div class="flex flex-col md:flex-row gap-y-2 gap-x-4 md:items-center">
|
||||||
@@ -67,7 +67,7 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<% @mcp_tokens.each do |mcp_token| %>
|
<% @mcp_tokens.each do |mcp_token| %>
|
||||||
<tr scope="row">
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<%= mcp_token.name %>
|
<%= mcp_token.name %>
|
||||||
</td>
|
</td>
|
||||||
@@ -98,8 +98,8 @@
|
|||||||
<div class="flex items-center gap-4 py-2.5">
|
<div class="flex items-center gap-4 py-2.5">
|
||||||
<div class="flex items-center space-x-1">
|
<div class="flex items-center space-x-1">
|
||||||
<span class="text-left"><%= t('enable_mcp_server') %></span>
|
<span class="text-left"><%= t('enable_mcp_server') %></span>
|
||||||
<span class="tooltip tooltip-top flex cursor-pointer" data-tip="<%= t('all_existing_mcp_connections_will_be_stopped_immediately_when_this_setting_is_disabled') %>">
|
<span class="tooltip tooltip-top hidden md:flex cursor-pointer" data-tip="<%= t('all_existing_mcp_connections_will_be_stopped_immediately_when_this_setting_is_disabled') %>">
|
||||||
<%= svg_icon('info_circle', class: 'hidden md:inline-block w-4 h-4 shrink-0') %>
|
<%= svg_icon('info_circle', class: 'w-4 h-4 shrink-0') %>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<submit-form data-on="change" class="flex">
|
<submit-form data-on="change" class="flex">
|
||||||
|
|||||||
@@ -3,10 +3,10 @@
|
|||||||
<%= t('use_an_authenticator_mobile_app_like_google_authenticator_or_1password_to_scan_the_qr_code_below') %>
|
<%= t('use_an_authenticator_mobile_app_like_google_authenticator_or_1password_to_scan_the_qr_code_below') %>
|
||||||
</p>
|
</p>
|
||||||
<div>
|
<div>
|
||||||
<%== RQRCode::QRCode.new(@provision_url).as_svg(viewbox: true, svg_attributes: { class: 'md:w-80 md:h-80 my-4 mx-auto' }) %>
|
<%== RQRCode::QRCode.new(@provision_url).as_svg(viewbox: true, svg_attributes: { class: 'md:w-80 md:h-80 my-4 mx-auto' }).sub(/\A<\?xml[^>]*\?>/, '') %>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-control my-6 space-y-2">
|
<div class="form-control my-6 space-y-2">
|
||||||
<%= f.text_field :otp_attempt, required: true, placeholder: 'XXX-XXX', class: 'base-input text-center' %>
|
<%= f.text_field :otp_attempt, required: true, inputmode: 'numeric', placeholder: 'XXXXXX', class: 'base-input text-center' %>
|
||||||
<span>
|
<span>
|
||||||
<%= @error_message %>
|
<%= @error_message %>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<%= render 'shared/turbo_modal', title: t('remove_2fa') do %>
|
<%= render 'shared/turbo_modal', title: t('remove_2fa') do %>
|
||||||
<%= form_for '', url: mfa_setup_path, method: :delete, data: { turbo_frame: :_top } do |f| %>
|
<%= form_for '', url: mfa_setup_path, method: :delete, data: { turbo_frame: :_top } do |f| %>
|
||||||
<div class="form-control my-6 space-y-2">
|
<div class="form-control my-6 space-y-2">
|
||||||
<%= f.text_field :otp_attempt, required: true, placeholder: 'XXX-XXX', class: 'base-input text-center' %>
|
<%= f.text_field :otp_attempt, required: true, inputmode: 'numeric', placeholder: 'XXXXXX', class: 'base-input text-center' %>
|
||||||
<span>
|
<span>
|
||||||
<%= @error_message %>
|
<%= @error_message %>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<span>
|
<span>
|
||||||
<%= t('completed_documents_notification_bcc_address') %>
|
<%= t('completed_documents_notification_bcc_address') %>
|
||||||
</span>
|
</span>
|
||||||
<span class="tooltip" data-tip="<%= t('send_email_copy_with_completed_documents_to_a_specified_bcc_address') %>">
|
<span class="tooltip hidden md:inline-flex" data-tip="<%= t('send_email_copy_with_completed_documents_to_a_specified_bcc_address') %>">
|
||||||
<%= svg_icon('info_circle', class: 'w-4 h-4') %>
|
<%= svg_icon('info_circle', class: 'w-4 h-4') %>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<div class="flex flex-wrap space-y-4 md:flex-nowrap md:space-y-0">
|
<div class="flex flex-wrap space-y-4 md:flex-nowrap md:space-y-0">
|
||||||
<%= render 'shared/settings_nav' %>
|
<%= render 'shared/settings_nav' %>
|
||||||
<div class="flex-grow max-w-xl mx-auto">
|
<div class="flex-grow max-w-xl mx-auto">
|
||||||
<h1 class="text-4xl font-bold mb-4">
|
<h1 class="text-3xl md:text-4xl font-bold mb-4">
|
||||||
<%= t('email_notifications') %>
|
<%= t('email_notifications') %>
|
||||||
</h1>
|
</h1>
|
||||||
<div class="mt-2 mb-1">
|
<div class="mt-2 mb-1">
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<%= render 'bcc_form', config: @bcc_config %>
|
<%= render 'bcc_form', config: @bcc_config %>
|
||||||
<div class="flex justify-between items-end mb-4 mt-8">
|
<div class="flex justify-between items-end mb-4 mt-8">
|
||||||
<h2 class="text-3xl font-bold">
|
<h2 class="text-2xl md:text-3xl font-bold">
|
||||||
<%= t('sign_request_email_reminders') %>
|
<%= t('sign_request_email_reminders') %>
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
<div class="flex items-center px-2 py-2 border-b" style="height: 42px;">
|
<div class="flex items-center px-2 py-2 border-b" style="height: 42px;">
|
||||||
<div class="flex items-center gap-1">
|
<div class="flex items-center gap-1">
|
||||||
<div class="tooltip tooltip-top before:text-xs" data-tip="<%= t('bold') %> (Ctrl+B)">
|
<div class="tooltip tooltip-top tooltip-no-touch before:text-xs" data-tip="<%= t('bold') %> (Ctrl+B)">
|
||||||
<button type="button" data-action="click:<%= editor_tag %>#bold" data-target="<%= editor_tag %>.boldButton" aria-label="<%= t('bold') %>" class="flex items-center px-1 w-6 h-6 rounded hover:bg-base-300">
|
<button type="button" data-action="click:<%= editor_tag %>#bold" data-target="<%= editor_tag %>.boldButton" aria-label="<%= t('bold') %>" class="flex items-center px-1 w-6 h-6 rounded hover:bg-base-300">
|
||||||
<%= svg_icon('bold', class: 'w-4 h-4') %>
|
<%= svg_icon('bold', class: 'w-4 h-4') %>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="tooltip tooltip-top before:text-xs" data-tip="<%= t('italic') %> (Ctrl+I)">
|
<div class="tooltip tooltip-top tooltip-no-touch before:text-xs" data-tip="<%= t('italic') %> (Ctrl+I)">
|
||||||
<button type="button" data-action="click:<%= editor_tag %>#italic" data-target="<%= editor_tag %>.italicButton" aria-label="<%= t('italic') %>" class="flex items-center px-1 w-6 h-6 rounded hover:bg-base-300">
|
<button type="button" data-action="click:<%= editor_tag %>#italic" data-target="<%= editor_tag %>.italicButton" aria-label="<%= t('italic') %>" class="flex items-center px-1 w-6 h-6 rounded hover:bg-base-300">
|
||||||
<%= svg_icon('italic', class: 'w-4 h-4') %>
|
<%= svg_icon('italic', class: 'w-4 h-4') %>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="tooltip tooltip-top before:text-xs" data-tip="<%= t('underline') %> (Ctrl+U)">
|
<div class="tooltip tooltip-top tooltip-no-touch before:text-xs" data-tip="<%= t('underline') %> (Ctrl+U)">
|
||||||
<button type="button" data-action="click:<%= editor_tag %>#underline" data-target="<%= editor_tag %>.underlineButton" aria-label="<%= t('underline') %>" class="flex items-center px-1 w-6 h-6 rounded hover:bg-base-300">
|
<button type="button" data-action="click:<%= editor_tag %>#underline" data-target="<%= editor_tag %>.underlineButton" aria-label="<%= t('underline') %>" class="flex items-center px-1 w-6 h-6 rounded hover:bg-base-300">
|
||||||
<%= svg_icon('underline', class: 'w-4 h-4') %>
|
<%= svg_icon('underline', class: 'w-4 h-4') %>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="tooltip tooltip-top before:text-xs" data-tip="<%= t('link') %> (Ctrl+K)">
|
<div class="tooltip tooltip-top tooltip-no-touch before:text-xs" data-tip="<%= t('link') %> (Ctrl+K)">
|
||||||
<button type="button" data-action="click:<%= editor_tag %>#linkSelection" data-target="<%= editor_tag %>.linkButton" aria-label="<%= t('link') %>" class="flex items-center px-1 w-6 h-6 rounded hover:bg-base-300">
|
<button type="button" data-action="click:<%= editor_tag %>#linkSelection" data-target="<%= editor_tag %>.linkButton" aria-label="<%= t('link') %>" class="flex items-center px-1 w-6 h-6 rounded hover:bg-base-300">
|
||||||
<%= svg_icon('link', class: 'w-4 h-4') %>
|
<%= svg_icon('link', class: 'w-4 h-4') %>
|
||||||
</button>
|
</button>
|
||||||
@@ -23,12 +23,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="mx-2 h-5 border-l border-base-content/20"></div>
|
<div class="mx-2 h-5 border-l border-base-content/20"></div>
|
||||||
<div class="flex items-center gap-1">
|
<div class="flex items-center gap-1">
|
||||||
<div class="tooltip tooltip-top before:text-xs" data-tip="<%= t('undo') %> (Ctrl+Z)">
|
<div class="tooltip tooltip-top tooltip-no-touch before:text-xs" data-tip="<%= t('undo') %> (Ctrl+Z)">
|
||||||
<button type="button" data-action="click:<%= editor_tag %>#undo" data-target="<%= editor_tag %>.undoButton" aria-label="<%= t('undo') %>" class="flex items-center px-1 w-6 h-6 rounded hover:bg-base-300">
|
<button type="button" data-action="click:<%= editor_tag %>#undo" data-target="<%= editor_tag %>.undoButton" aria-label="<%= t('undo') %>" class="flex items-center px-1 w-6 h-6 rounded hover:bg-base-300">
|
||||||
<%= svg_icon('arrow_back_up', class: 'w-4 h-4') %>
|
<%= svg_icon('arrow_back_up', class: 'w-4 h-4') %>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="tooltip tooltip-top before:text-xs" data-tip="<%= t('redo') %> (Ctrl+Shift+Z)">
|
<div class="tooltip tooltip-top tooltip-no-touch before:text-xs" data-tip="<%= t('redo') %> (Ctrl+Shift+Z)">
|
||||||
<button type="button" data-action="click:<%= editor_tag %>#redo" data-target="<%= editor_tag %>.redoButton" aria-label="<%= t('redo') %>" class="flex items-center px-1 w-6 h-6 rounded hover:bg-base-300">
|
<button type="button" data-action="click:<%= editor_tag %>#redo" data-target="<%= editor_tag %>.redoButton" aria-label="<%= t('redo') %>" class="flex items-center px-1 w-6 h-6 rounded hover:bg-base-300">
|
||||||
<%= svg_icon('arrow_forward_up', class: 'w-4 h-4') %>
|
<%= svg_icon('arrow_forward_up', class: 'w-4 h-4') %>
|
||||||
</button>
|
</button>
|
||||||
@@ -37,8 +37,9 @@
|
|||||||
<% if local_assigns[:variables]&.any? %>
|
<% if local_assigns[:variables]&.any? %>
|
||||||
<% variable_labels = { 'account.name' => t('variables.account_name'), 'submitter.link' => t('variables.submitter_link'), 'template.name' => t('variables.template_name'), 'submission.submitters' => t('variables.submission_submitters'), 'submission.link' => t('variables.submission_link'), 'documents.link' => t('variables.documents_link') } %>
|
<% variable_labels = { 'account.name' => t('variables.account_name'), 'submitter.link' => t('variables.submitter_link'), 'template.name' => t('variables.template_name'), 'submission.submitters' => t('variables.submission_submitters'), 'submission.link' => t('variables.submission_link'), 'documents.link' => t('variables.documents_link') } %>
|
||||||
<div class="dropdown dropdown-end ml-auto">
|
<div class="dropdown dropdown-end ml-auto">
|
||||||
<label tabindex="0" class="flex items-center gap-1 text-sm px-2 py-1 rounded hover:bg-base-200 cursor-pointer">
|
<label tabindex="0" class="flex items-center gap-1 text-sm px-2 py-1 rounded hover:bg-base-200 cursor-pointer whitespace-nowrap">
|
||||||
<%= t('add_variable') %>
|
<span class="hidden sm:inline"><%= t('add_variable') %></span>
|
||||||
|
<span class="sm:hidden"><%= t('variable') %></span>
|
||||||
<%= svg_icon('chevron_down', class: 'w-3.5 h-3.5') %>
|
<%= svg_icon('chevron_down', class: 'w-3.5 h-3.5') %>
|
||||||
</label>
|
</label>
|
||||||
<div tabindex="0" class="dropdown-content right-0 top-full mt-1 p-1 bg-white border border-neutral-200 rounded-lg shadow-lg z-50">
|
<div tabindex="0" class="dropdown-content right-0 top-full mt-1 p-1 bg-white border border-neutral-200 rounded-lg shadow-lg z-50">
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<div class="flex flex-wrap space-y-4 md:flex-nowrap md:space-y-0">
|
<div class="flex flex-wrap space-y-4 md:flex-nowrap md:space-y-0">
|
||||||
<%= render 'shared/settings_nav' %>
|
<%= render 'shared/settings_nav' %>
|
||||||
<div class="flex-grow max-w-xl mx-auto">
|
<div class="flex-grow max-w-xl mx-auto">
|
||||||
<p class="text-4xl font-bold mb-4">
|
<p class="text-3xl md:text-4xl font-bold mb-4">
|
||||||
<%= t('email_templates') %>
|
<%= t('email_templates') %>
|
||||||
</p>
|
</p>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
@@ -9,11 +9,11 @@
|
|||||||
<%= render 'documents_copy_email_form' %>
|
<%= render 'documents_copy_email_form' %>
|
||||||
<%= render 'submitter_completed_email_form' %>
|
<%= render 'submitter_completed_email_form' %>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-4xl font-bold mb-4 mt-8">
|
<p class="text-3xl md:text-4xl font-bold mb-4 mt-8">
|
||||||
<%= t('company_logo') %>
|
<%= t('company_logo') %>
|
||||||
</p>
|
</p>
|
||||||
<%= render 'logo_form' %>
|
<%= render 'logo_form' %>
|
||||||
<p class="text-4xl font-bold mb-4 mt-8">
|
<p class="text-3xl md:text-4xl font-bold mb-4 mt-8">
|
||||||
<%= t('submission_form') %>
|
<%= t('submission_form') %>
|
||||||
</p>
|
</p>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<div class="flex flex-wrap space-y-4 md:flex-nowrap md:space-y-0">
|
<div class="flex flex-wrap space-y-4 md:flex-nowrap md:space-y-0">
|
||||||
<%= render 'shared/settings_nav' %>
|
<%= render 'shared/settings_nav' %>
|
||||||
<div class="flex-grow max-w-xl mx-auto">
|
<div class="flex-grow max-w-xl mx-auto">
|
||||||
<h1 class="text-4xl font-bold mb-4">
|
<h1 class="text-3xl md:text-4xl font-bold mb-4">
|
||||||
<%= t('profile') %>
|
<%= t('profile') %>
|
||||||
</h1>
|
</h1>
|
||||||
<%= form_for current_user, url: update_contact_settings_profile_index_path, method: :patch, html: { autocomplete: 'off', class: 'space-y-4' } do |f| %>
|
<%= form_for current_user, url: update_contact_settings_profile_index_path, method: :patch, html: { autocomplete: 'off', class: 'space-y-4' } do |f| %>
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<div class="mb-2 w-full">
|
||||||
|
<a href="<%= root_path %>" class="flex items-center space-x-0.5 font-medium">
|
||||||
|
<%= svg_icon('arrow_left', class: 'w-4 h-4 stroke-2') %>
|
||||||
|
<span><%= t('back') %></span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<%= render 'shared/settings_nav', expanded: true %>
|
||||||
@@ -1 +1 @@
|
|||||||
<% 'stats stat stat-figure stat-title stat-value text-accent w-fit hover:bg-white dropdown-open border-base-content/30 before:border-base-content/30' %>
|
<% 'stats stat stat-figure stat-title stat-value text-accent w-fit hover:bg-white dropdown-open border-base-content/30 before:border-base-content/30 order-1 order-2 order-3 order-4 order-5 lg:inline lg:hidden' %>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<a target="_blank" href="<%= Docuseal::GITHUB_URL %>" rel="noopener noreferrer nofollow" class="relative flex items-center rounded-full px-2 py-0.5 text-xs leading-4 mt-1 text-base-content border border-base-300 tooltip tooltip-bottom" data-tip="Give a star on GitHub">
|
<a target="_blank" href="<%= Docuseal::GITHUB_URL %>" rel="noopener noreferrer nofollow" class="relative flex items-center rounded-full px-2 py-0.5 text-xs leading-4 mt-1 text-base-content border border-base-300 tooltip tooltip-bottom tooltip-no-touch" data-tip="Give a star on GitHub">
|
||||||
<span class="flex items-center justify-between space-x-0.5 font-medium">
|
<span class="flex items-center justify-between space-x-0.5 font-medium">
|
||||||
<%= svg_icon('start', class: 'h-3 w-3') %>
|
<%= svg_icon('start', class: 'h-3 w-3') %>
|
||||||
<span>17k</span>
|
<span>18k</span>
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -28,7 +28,13 @@
|
|||||||
<span class="text-sm align-text-top"><%= current_user.initials %></span>
|
<span class="text-sm align-text-top"><%= current_user.initials %></span>
|
||||||
</label>
|
</label>
|
||||||
<ul tabindex="0" class="z-10 dropdown-content p-2 mt-2 shadow menu text-base bg-base-100 rounded-box min-w-[160px] text-right">
|
<ul tabindex="0" class="z-10 dropdown-content p-2 mt-2 shadow menu text-base bg-base-100 rounded-box min-w-[160px] text-right">
|
||||||
<li>
|
<li class="md:hidden">
|
||||||
|
<%= link_to settings_path, class: 'flex items-center' do %>
|
||||||
|
<%= svg_icon('adjustments', class: 'w-5 h-5 flex-shrink-0 stroke-2') %>
|
||||||
|
<span class="mr-1"><%= t('settings') %></span>
|
||||||
|
<% end %>
|
||||||
|
</li>
|
||||||
|
<li class="hidden md:list-item">
|
||||||
<%= link_to settings_profile_index_path, class: 'flex items-center' do %>
|
<%= link_to settings_profile_index_path, class: 'flex items-center' do %>
|
||||||
<%= svg_icon('adjustments', class: 'w-5 h-5 flex-shrink-0 stroke-2') %>
|
<%= svg_icon('adjustments', class: 'w-5 h-5 flex-shrink-0 stroke-2') %>
|
||||||
<span class="mr-1"><%= t('profile') %></span>
|
<span class="mr-1"><%= t('profile') %></span>
|
||||||
@@ -75,7 +81,7 @@
|
|||||||
<%= svg_icon('logout', class: 'w-5 h-5 flex-shrink-0 stroke-2 inline') %>
|
<%= svg_icon('logout', class: 'w-5 h-5 flex-shrink-0 stroke-2 inline') %>
|
||||||
<span class="whitespace-nowrap"><%= t('sign_out') %></span>
|
<span class="whitespace-nowrap"><%= t('sign_out') %></span>
|
||||||
</button>
|
</button>
|
||||||
<%= button_to '', destroy_user_session_path, method: :delete, data: { turbo: false }, form: { id: 'destroy_user_session_form' }, form_class: 'hidden' %>
|
<%= button_to '', destroy_user_session_path, method: :delete, form: { id: 'destroy_user_session_form', data: { turbo: false } }, form_class: 'hidden' %>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,14 +1,17 @@
|
|||||||
<% if pagy.pages > 1 %>
|
<% if pagy.pages > 1 %>
|
||||||
<div class="flex my-6 justify-center md:justify-between">
|
<% left_additional_html = local_assigns[:left_additional_html] %>
|
||||||
<div class="hidden md:block text-sm">
|
<div class="flex flex-col md:flex-row md:items-center <%= left_additional_html.present? ? 'mt-3' : 'mt-6' %> mb-6 md:my-6 justify-center md:justify-between">
|
||||||
<% if pagy.count.nil? %>
|
<div class="text-sm <%= 'mb-2 md:mb-0' if left_additional_html.present? %>">
|
||||||
<%= t("pagination.#{local_assigns.fetch(:items_name, 'items')}.range_without_total", from: local_assigns.fetch(:from, pagy.from), to: local_assigns.fetch(:to, pagy.to)) %>
|
<span class="hidden md:inline">
|
||||||
<% else %>
|
<% if pagy.count.nil? %>
|
||||||
<%= t("pagination.#{local_assigns.fetch(:items_name, 'items')}.range_with_total", from: local_assigns.fetch(:from, pagy.from), to: local_assigns.fetch(:to, pagy.to), count: local_assigns.fetch(:count, pagy.count)) %>
|
<%= t("pagination.#{local_assigns.fetch(:items_name, 'items')}.range_without_total", from: local_assigns.fetch(:from, pagy.from), to: local_assigns.fetch(:to, pagy.to)) %>
|
||||||
<% end %>
|
<% else %>
|
||||||
<%= local_assigns[:left_additional_html] %>
|
<%= t("pagination.#{local_assigns.fetch(:items_name, 'items')}.range_with_total", from: local_assigns.fetch(:from, pagy.from), to: local_assigns.fetch(:to, pagy.to), count: local_assigns.fetch(:count, pagy.count)) %>
|
||||||
|
<% end %>
|
||||||
|
</span>
|
||||||
|
<%= left_additional_html %>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center space-x-1.5">
|
<div class="flex items-center space-x-1.5 justify-center md:justify-end">
|
||||||
<%= local_assigns[:right_additional_html] %>
|
<%= local_assigns[:right_additional_html] %>
|
||||||
<div class="join">
|
<div class="join">
|
||||||
<% if pagy.previous %>
|
<% if pagy.previous %>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<form action="<%= url_for %>" method="get" class="items-center flex">
|
<form action="<%= url_for %>" method="get" class="items-center flex max-md:focus-within:flex-1 <%= 'max-md:flex-1' if params[:q].present? %>">
|
||||||
<% Submissions::Filter::ALLOWED_PARAMS.each do |key| %>
|
<% Submissions::Filter::ALLOWED_PARAMS.each do |key| %>
|
||||||
<% if params[key].present? %>
|
<% if params[key].present? %>
|
||||||
<input name="<%= key %>" value="<%= params[key] %>" class="hidden">
|
<input name="<%= key %>" value="<%= params[key] %>" class="hidden">
|
||||||
@@ -14,8 +14,8 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<search-input data-title="<%= local_assigns[:title_selector] || 'h1' %>" class="flex items-center">
|
<search-input class="group flex items-center max-md:focus-within:flex-1 <%= 'max-md:flex-1' if params[:q].present? %>">
|
||||||
<input id="search" name="q" value="<%= params[:q] %>" class="input text-lg pr-10 -mr-12 w-0 md:w-60 <%= 'pl-8 input-outlined w-60' if params[:q].present? %>" placeholder="<%= local_assigns[:placeholder] %>">
|
<input id="search" name="q" value="<%= params[:q] %>" enterkeyhint="search" class="input text-lg pr-10 -mr-12 w-0 md:w-60 max-md:group-focus-within:w-full <%= 'pl-8 input-outlined w-full' if params[:q].present? %>" placeholder="<%= local_assigns[:placeholder] %>">
|
||||||
<button type="submit" title="<%= t('search') %>" class="btn btn-ghost btn-circle">
|
<button type="submit" title="<%= t('search') %>" class="btn btn-ghost btn-circle">
|
||||||
<span class="enabled">
|
<span class="enabled">
|
||||||
<%= svg_icon('search', class: 'w-6 h-6 stroke-2') %>
|
<%= svg_icon('search', class: 'w-6 h-6 stroke-2') %>
|
||||||
|
|||||||
@@ -1,150 +1,168 @@
|
|||||||
<div class="block w-full md:w-52 flex-none">
|
<% expanded = local_assigns[:expanded] %>
|
||||||
<menu-active>
|
<div class="<%= expanded ? 'block -mx-3 md:mx-0' : 'w-full md:w-52 flex-none' %>">
|
||||||
<ul id="account_settings_menu" class="menu px-0">
|
<% unless expanded %>
|
||||||
<li class="menu-title py-0 !bg-transparent mb-3 -mt-5"><a href="<%= '/' %>" class="!bg-transparent !text-neutral font-medium flex items-center space-x-0.5"><%= svg_icon('arrow_left', class: 'w-4 h-4 stroke-2') %><span><%= t('back') %></span></a></li>
|
<div class="md:hidden w-full">
|
||||||
<li class="menu-title py-0 !bg-transparent">
|
<a href="<%= settings_path %>" class="flex items-center space-x-0.5 font-medium">
|
||||||
<span class="!bg-transparent"><%= t('settings') %></span>
|
<%= svg_icon('arrow_left', class: 'w-4 h-4 stroke-2') %>
|
||||||
</li>
|
<span><%= t('settings') %></span>
|
||||||
<li></li>
|
</a>
|
||||||
<li>
|
</div>
|
||||||
<%= link_to t('profile'), settings_profile_index_path, class: 'text-base hover:bg-base-300' %>
|
<% end %>
|
||||||
</li>
|
<div class="<%= 'hidden md:block' unless expanded %>">
|
||||||
<li>
|
<menu-active>
|
||||||
<%= link_to t('account'), settings_account_path, class: 'text-base hover:bg-base-300' %>
|
<ul id="account_settings_menu" class="menu px-0">
|
||||||
</li>
|
<% unless expanded %>
|
||||||
<% if (!Docuseal.multitenant? || EncryptedConfig.exists?(key: EncryptedConfig::EMAIL_SMTP_KEY, account: current_account)) && can?(:read, EncryptedConfig.new(key: EncryptedConfig::EMAIL_SMTP_KEY, account: current_account)) && ENV['SMTP_ADDRESS'].blank? && true_user == current_user %>
|
<li class="menu-title py-0 !bg-transparent mb-3 -mt-5">
|
||||||
<li>
|
<a href="<%= root_path %>" class="!bg-transparent !text-neutral font-medium flex items-center space-x-0.5">
|
||||||
<%= link_to t('email'), settings_email_index_path, class: 'text-base hover:bg-base-300' %>
|
<%= svg_icon('arrow_left', class: 'w-4 h-4 stroke-2') %>
|
||||||
</li>
|
<span><%= t('back') %></span>
|
||||||
<% end %>
|
</a>
|
||||||
<% unless Docuseal.multitenant? %>
|
|
||||||
<% if can?(:read, EncryptedConfig.new(key: EncryptedConfig::FILES_STORAGE_KEY, account: current_account)) && true_user == current_user && ENV['S3_ATTACHMENTS_BUCKET'].blank? && ENV['GCS_BUCKET'].blank? && ENV['AZURE_CONTAINER'].blank? %>
|
|
||||||
<li>
|
|
||||||
<%= link_to t('storage'), settings_storage_index_path, class: 'text-base hover:bg-base-300' %>
|
|
||||||
</li>
|
</li>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if can?(:read, EncryptedConfig.new(key: 'submitter_invitation_sms', account: current_account)) && true_user == current_user %>
|
<li class="menu-title py-0 !bg-transparent">
|
||||||
|
<span class="!bg-transparent"><%= t('settings') %></span>
|
||||||
|
</li>
|
||||||
|
<li></li>
|
||||||
|
<li>
|
||||||
|
<%= link_to t('profile'), settings_profile_index_path, class: 'text-base hover:bg-base-300' %>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<%= link_to t('account'), settings_account_path, class: 'text-base hover:bg-base-300' %>
|
||||||
|
</li>
|
||||||
|
<% if (!Docuseal.multitenant? || EncryptedConfig.exists?(key: EncryptedConfig::EMAIL_SMTP_KEY, account: current_account)) && can?(:read, EncryptedConfig.new(key: EncryptedConfig::EMAIL_SMTP_KEY, account: current_account)) && ENV['SMTP_ADDRESS'].blank? && true_user == current_user %>
|
||||||
<li>
|
<li>
|
||||||
<%= link_to 'SMS', settings_sms_path, class: 'text-base hover:bg-base-300' %>
|
<%= link_to t('email'), settings_email_index_path, class: 'text-base hover:bg-base-300' %>
|
||||||
</li>
|
</li>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% unless Docuseal.multitenant? %>
|
||||||
<% if can?(:read, AccountConfig) %>
|
<% if can?(:read, EncryptedConfig.new(key: EncryptedConfig::FILES_STORAGE_KEY, account: current_account)) && true_user == current_user && ENV['S3_ATTACHMENTS_BUCKET'].blank? && ENV['GCS_BUCKET'].blank? && ENV['AZURE_CONTAINER'].blank? %>
|
||||||
<li>
|
|
||||||
<%= link_to t('notifications'), settings_notifications_path, class: 'text-base hover:bg-base-300' %>
|
|
||||||
</li>
|
|
||||||
<% end %>
|
|
||||||
<% if can?(:read, EncryptedConfig.new(key: EncryptedConfig::ESIGN_CERTS_KEY, account: current_account)) %>
|
|
||||||
<li>
|
|
||||||
<%= link_to t('e_signature'), settings_esign_path, class: 'text-base hover:bg-base-300' %>
|
|
||||||
</li>
|
|
||||||
<% end %>
|
|
||||||
<% if can?(:read, AccountConfig) %>
|
|
||||||
<li>
|
|
||||||
<%= link_to t('personalization'), settings_personalization_path, class: 'text-base hover:bg-base-300' %>
|
|
||||||
</li>
|
|
||||||
<% end %>
|
|
||||||
<% if can?(:read, User) %>
|
|
||||||
<li>
|
|
||||||
<%= link_to t('users'), settings_users_path, class: 'text-base hover:bg-base-300' %>
|
|
||||||
</li>
|
|
||||||
<% end %>
|
|
||||||
<%= render 'shared/settings_nav_extra' %>
|
|
||||||
<% if Docuseal.demo? || !Docuseal.multitenant? %>
|
|
||||||
<% if can?(:read, AccessToken) %>
|
|
||||||
<li>
|
|
||||||
<%= link_to 'API', settings_api_index_path, class: 'text-base hover:bg-base-300' %>
|
|
||||||
</li>
|
|
||||||
<% end %>
|
|
||||||
<% end %>
|
|
||||||
<% if Docuseal.demo? || !Docuseal.multitenant? || (current_user != true_user && !current_account.testing?) %>
|
|
||||||
<% if can?(:read, WebhookUrl) %>
|
|
||||||
<li>
|
|
||||||
<%= link_to 'Webhooks', settings_webhooks_path, class: 'text-base hover:bg-base-300' %>
|
|
||||||
</li>
|
|
||||||
<% end %>
|
|
||||||
<% end %>
|
|
||||||
<% if !Docuseal.demo? && can?(:manage, EncryptedConfig) && (current_user != true_user || !current_account.linked_account_account) %>
|
|
||||||
<li>
|
|
||||||
<%= content_for(:pro_link) || link_to(Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premises" }.to_query}", class: 'text-base hover:bg-base-300', data: { turbo: false }) do %>
|
|
||||||
<%= t('plans') %>
|
|
||||||
<span class="badge badge-warning"><%= t('pro') %></span>
|
|
||||||
<% end %>
|
|
||||||
</li>
|
|
||||||
<% end %>
|
|
||||||
<% if !Docuseal.demo? && can?(:manage, EncryptedConfig) && (current_user == true_user || current_account.testing?) %>
|
|
||||||
<li>
|
|
||||||
<%= link_to Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}#{'/test' if current_account.testing?}/api") : "#{Docuseal::CONSOLE_URL}/on_premises", class: 'text-base hover:bg-base-300', data: { turbo: false } do %>
|
|
||||||
<% if Docuseal.multitenant? %> API <% else %> <%= t('console') %> <% end %>
|
|
||||||
<% end %>
|
|
||||||
</li>
|
|
||||||
<% if Docuseal.multitenant? %>
|
|
||||||
<li>
|
|
||||||
<%= link_to console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}#{'/test' if current_account.testing?}/embedding/form"), class: 'text-base hover:bg-base-300', data: { turbo: false } do %>
|
|
||||||
<%= t('embedding') %>
|
|
||||||
<% end %>
|
|
||||||
</li>
|
|
||||||
<% end %>
|
|
||||||
<% if (!Docuseal.multitenant? || can?(:manage, :saml_sso)) && can?(:read, EncryptedConfig.new(key: 'saml_configs', account: current_account)) && true_user == current_user %>
|
|
||||||
<li>
|
|
||||||
<%= link_to 'SSO', settings_sso_index_path, class: 'text-base hover:bg-base-300' %>
|
|
||||||
</li>
|
|
||||||
<% end %>
|
|
||||||
<% if !Docuseal.multitenant? && can?(:read, McpToken) && can?(:manage, :mcp) %>
|
|
||||||
<li>
|
|
||||||
<%= link_to 'MCP', settings_mcp_index_path, class: 'text-base hover:bg-base-300' %>
|
|
||||||
</li>
|
|
||||||
<% end %>
|
|
||||||
<%= render 'shared/settings_nav_extra2' %>
|
|
||||||
<% if (can?(:manage, EncryptedConfig) && current_user == true_user) || (current_user != true_user && current_account.testing?) %>
|
|
||||||
<%= form_for '', url: testing_account_path, method: current_account.testing? ? :delete : :post, html: { class: 'w-full' } do |f| %>
|
|
||||||
<li>
|
<li>
|
||||||
<label class="flex items-center text-base hover:bg-base-300 w-full justify-between" for="testing_toggle">
|
<%= link_to t('storage'), settings_storage_index_path, class: 'text-base hover:bg-base-300' %>
|
||||||
<span class="mr-2 w-full">
|
</li>
|
||||||
<%= t('test_mode') %>
|
<% end %>
|
||||||
</span>
|
<% if can?(:read, EncryptedConfig.new(key: 'submitter_invitation_sms', account: current_account)) && true_user == current_user %>
|
||||||
<submit-form data-on="change" class="flex">
|
<li>
|
||||||
<%= f.check_box :testing_toggle, class: 'toggle toggle-sm', checked: current_account.testing? %>
|
<%= link_to 'SMS', settings_sms_path, class: 'text-base hover:bg-base-300' %>
|
||||||
</submit-form>
|
|
||||||
</label>
|
|
||||||
</li>
|
</li>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% if can?(:read, AccountConfig) %>
|
||||||
</ul>
|
<li>
|
||||||
</menu-active>
|
<%= link_to t('notifications'), settings_notifications_path, class: 'text-base hover:bg-base-300' %>
|
||||||
<% if Docuseal.multitenant? || cannot?(:manage, :tenants) %>
|
</li>
|
||||||
<div id="support_channels" class="mx-4 border-t border-base-300 hidden md:block">
|
<% end %>
|
||||||
<div class="text-sm mt-3">
|
<% if can?(:read, EncryptedConfig.new(key: EncryptedConfig::ESIGN_CERTS_KEY, account: current_account)) %>
|
||||||
<%= t('need_help_ask_a_question_') %>
|
<li>
|
||||||
</div>
|
<%= link_to t('e_signature'), settings_esign_path, class: 'text-base hover:bg-base-300' %>
|
||||||
<div class="flex mt-3 space-x-3">
|
</li>
|
||||||
<div class="tooltip" data-tip="GitHub">
|
<% end %>
|
||||||
<a href="<%= Docuseal::GITHUB_URL %>" target="_blank" class="btn btn-circle btn-primary btn-md">
|
<% if can?(:read, AccountConfig) %>
|
||||||
<%= svg_icon('brand_github', class: 'w-8 h-8') %>
|
<li>
|
||||||
</a>
|
<%= link_to t('personalization'), settings_personalization_path, class: 'text-base hover:bg-base-300' %>
|
||||||
|
</li>
|
||||||
|
<% end %>
|
||||||
|
<% if can?(:read, User) %>
|
||||||
|
<li>
|
||||||
|
<%= link_to t('users'), settings_users_path, class: 'text-base hover:bg-base-300' %>
|
||||||
|
</li>
|
||||||
|
<% end %>
|
||||||
|
<%= render 'shared/settings_nav_extra' %>
|
||||||
|
<% if Docuseal.demo? || !Docuseal.multitenant? %>
|
||||||
|
<% if can?(:read, AccessToken) %>
|
||||||
|
<li>
|
||||||
|
<%= link_to 'API', settings_api_index_path, class: 'text-base hover:bg-base-300' %>
|
||||||
|
</li>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
<% if Docuseal.demo? || !Docuseal.multitenant? || (current_user != true_user && !current_account.testing?) %>
|
||||||
|
<% if can?(:read, WebhookUrl) %>
|
||||||
|
<li>
|
||||||
|
<%= link_to 'Webhooks', settings_webhooks_path, class: 'text-base hover:bg-base-300' %>
|
||||||
|
</li>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
<% if !Docuseal.demo? && can?(:manage, EncryptedConfig) && (current_user != true_user || !current_account.linked_account_account) %>
|
||||||
|
<li>
|
||||||
|
<%= content_for(:pro_link) || link_to(Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premises" }.to_query}", class: 'text-base hover:bg-base-300', data: { turbo: false }) do %>
|
||||||
|
<%= t('plans') %>
|
||||||
|
<span class="badge badge-warning"><%= t('pro') %></span>
|
||||||
|
<% end %>
|
||||||
|
</li>
|
||||||
|
<% end %>
|
||||||
|
<% if !Docuseal.demo? && can?(:manage, EncryptedConfig) && (current_user == true_user || current_account.testing?) %>
|
||||||
|
<li>
|
||||||
|
<%= link_to Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}#{'/test' if current_account.testing?}/api") : "#{Docuseal::CONSOLE_URL}/on_premises", class: 'text-base hover:bg-base-300', data: { turbo: false } do %>
|
||||||
|
<% if Docuseal.multitenant? %> API <% else %> <%= t('console') %> <% end %>
|
||||||
|
<% end %>
|
||||||
|
</li>
|
||||||
|
<% if Docuseal.multitenant? %>
|
||||||
|
<li>
|
||||||
|
<%= link_to console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}#{'/test' if current_account.testing?}/embedding/form"), class: 'text-base hover:bg-base-300', data: { turbo: false } do %>
|
||||||
|
<%= t('embedding') %>
|
||||||
|
<% end %>
|
||||||
|
</li>
|
||||||
|
<% end %>
|
||||||
|
<% if (!Docuseal.multitenant? || can?(:manage, :saml_sso)) && can?(:read, EncryptedConfig.new(key: 'saml_configs', account: current_account)) && true_user == current_user %>
|
||||||
|
<li>
|
||||||
|
<%= link_to 'SSO', settings_sso_index_path, class: 'text-base hover:bg-base-300' %>
|
||||||
|
</li>
|
||||||
|
<% end %>
|
||||||
|
<% if !Docuseal.multitenant? && can?(:read, McpToken) && can?(:manage, :mcp) %>
|
||||||
|
<li>
|
||||||
|
<%= link_to 'MCP', settings_mcp_index_path, class: 'text-base hover:bg-base-300' %>
|
||||||
|
</li>
|
||||||
|
<% end %>
|
||||||
|
<%= render 'shared/settings_nav_extra2' %>
|
||||||
|
<% if (can?(:manage, EncryptedConfig) && current_user == true_user) || (current_user != true_user && current_account.testing?) %>
|
||||||
|
<%= form_for '', url: testing_account_path, method: current_account.testing? ? :delete : :post, html: { class: 'w-full' } do |f| %>
|
||||||
|
<li>
|
||||||
|
<label class="flex items-center text-base hover:bg-base-300 w-full justify-between" for="testing_toggle">
|
||||||
|
<span class="mr-2 w-full">
|
||||||
|
<%= t('test_mode') %>
|
||||||
|
</span>
|
||||||
|
<submit-form data-on="change" class="flex">
|
||||||
|
<%= f.check_box :testing_toggle, class: 'toggle toggle-sm', checked: current_account.testing? %>
|
||||||
|
</submit-form>
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
</ul>
|
||||||
|
</menu-active>
|
||||||
|
<% if Docuseal.multitenant? || cannot?(:manage, :tenants) %>
|
||||||
|
<div id="support_channels" class="mx-4 border-t border-base-300 <%= 'hidden md:block' unless expanded %>">
|
||||||
|
<div class="text-sm mt-3">
|
||||||
|
<%= t('need_help_ask_a_question_') %>
|
||||||
</div>
|
</div>
|
||||||
<div class="tooltip" data-tip="<%= t('discord_community') %>">
|
<div class="flex mt-3 space-x-3">
|
||||||
<a href="<%= Docuseal::DISCORD_URL %>" target="_blank" class="btn btn-circle btn-primary btn-md">
|
<div class="tooltip tooltip-no-touch" data-tip="GitHub">
|
||||||
<%= svg_icon('brand_discord', class: 'w-8 h-8') %>
|
<a href="<%= Docuseal::GITHUB_URL %>" target="_blank" class="btn btn-circle btn-primary btn-md">
|
||||||
</a>
|
<%= svg_icon('brand_github', class: 'w-8 h-8') %>
|
||||||
</div>
|
|
||||||
<%= capture do %>
|
|
||||||
<div class="tooltip" data-tip="<%= t('ai_assistant') %>">
|
|
||||||
<a href="<%= Docuseal::CHATGPT_URL %>" target="_blank" class="btn btn-circle btn-primary btn-md">
|
|
||||||
<%= svg_icon('brand_openai', class: 'w-8 h-8') %>
|
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="tooltip tooltip-no-touch" data-tip="<%= t('discord_community') %>">
|
||||||
|
<a href="<%= Docuseal::DISCORD_URL %>" target="_blank" class="btn btn-circle btn-primary btn-md">
|
||||||
|
<%= svg_icon('brand_discord', class: 'w-8 h-8') %>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<%= capture do %>
|
||||||
|
<div class="tooltip tooltip-no-touch" data-tip="<%= t('ai_assistant') %>">
|
||||||
|
<a href="<%= Docuseal::CHATGPT_URL %>" target="_blank" class="btn btn-circle btn-primary btn-md">
|
||||||
|
<%= svg_icon('message', class: 'w-8 h-8') %>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<a href="mailto:<%= Docuseal::SUPPORT_EMAIL %>" target="_blank" class="w-full block mt-4 underline">
|
||||||
|
<%= Docuseal::SUPPORT_EMAIL %>
|
||||||
|
</a>
|
||||||
|
<% if Docuseal.version.present? && !Docuseal.multitenant? && can?(:manage, EncryptedConfig) %>
|
||||||
|
<a href="https://github.com/docusealco/docuseal/releases" target="_blank" class="badge badge-outline text-xs block mx-auto mt-4">
|
||||||
|
v<%= Docuseal.version %>
|
||||||
|
</a>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<a href="mailto:<%= Docuseal::SUPPORT_EMAIL %>" target="_blank" class="w-full block mt-4 underline text-center">
|
<% end %>
|
||||||
<%= Docuseal::SUPPORT_EMAIL %>
|
</div>
|
||||||
</a>
|
|
||||||
<% if Docuseal.version.present? && !Docuseal.multitenant? && can?(:manage, EncryptedConfig) %>
|
|
||||||
<a href="https://github.com/docusealco/docuseal/releases" target="_blank" class="badge badge-outline text-xs block mx-auto mt-4">
|
|
||||||
v<%= Docuseal.version %>
|
|
||||||
</a>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
|
<% offset = local_assigns[:title] ? '45px' : '0px' %>
|
||||||
<turbo-frame id="drawer">
|
<turbo-frame id="drawer">
|
||||||
<turbo-modal class="modal modal-open items-start !animate-none justify-end overflow-y-auto !justify-normal md:!justify-end" data-close-after-submit="<%= local_assigns.key?(:close_after_submit) ? local_assigns[:close_after_submit] : true %>">
|
<turbo-modal class="modal modal-open items-start !animate-none justify-end overflow-y-auto !justify-normal md:!justify-end" data-close-after-submit="<%= local_assigns.key?(:close_after_submit) ? local_assigns[:close_after_submit] : true %>">
|
||||||
<div class="absolute top-0 bottom-0 right-0 left-0" data-action="click:turbo-modal#close"></div>
|
<div class="absolute top-0 bottom-0 right-0 left-0" data-action="click:turbo-modal#close"></div>
|
||||||
<div class="bg-base-100 min-h-screen max-h-screen relative w-full relative">
|
<div class="bg-base-100 min-h-screen max-h-screen relative w-full min-w-0">
|
||||||
<% if local_assigns[:title] %>
|
<% if local_assigns[:title] %>
|
||||||
<div class="flex justify-between bg-base-100 py-2 px-4 items-center border-b pb-2 font-medium">
|
<div class="flex justify-between bg-base-100 py-2 px-4 items-center border-b pb-2 font-medium">
|
||||||
<span>
|
<span>
|
||||||
@@ -14,7 +15,7 @@
|
|||||||
×
|
×
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
<div class="w-screen md:w-[620px] overflow-y-auto" style="max-height: calc(100vh - <%= local_assigns[:title] ? '45px' : '0px' %>)">
|
<div class="md:w-[620px] overflow-y-auto" style="max-height: calc(100vh - <%= offset %>); max-height: calc(100dvh - <%= offset %>)">
|
||||||
<%= yield %>
|
<%= yield %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,18 +1,34 @@
|
|||||||
<turbo-frame id="modal">
|
<% if turbo_frame_request? %>
|
||||||
<turbo-modal class="modal modal-open items-start !animate-none overflow-y-auto !justify-normal md:!justify-center" data-close-after-submit="<%= local_assigns.key?(:close_after_submit) ? local_assigns[:close_after_submit] : true %>">
|
<turbo-frame id="modal">
|
||||||
<div class="absolute top-0 bottom-0 right-0 left-0" data-action="click:turbo-modal#close"></div>
|
<turbo-modal class="modal modal-open items-start !animate-none overflow-y-auto !justify-normal md:!justify-center" data-close-after-submit="<%= local_assigns.key?(:close_after_submit) ? local_assigns[:close_after_submit] : true %>">
|
||||||
<div class="bg-base-100 min-h-screen max-h-screen md:min-h-fit md:mt-3 md:rounded-2xl relative w-full relative overflow-y-auto">
|
<div class="absolute top-0 bottom-0 right-0 left-0" data-action="click:turbo-modal#close"></div>
|
||||||
<% if local_assigns[:title] %>
|
<div class="bg-base-100 min-h-screen max-h-screen md:min-h-fit md:mt-3 md:rounded-2xl relative w-full relative overflow-y-auto">
|
||||||
<div class="flex justify-between bg-base-100 py-2 px-5 items-center border-b pb-2 font-medium mt-0.5">
|
<% if local_assigns[:title] %>
|
||||||
<span>
|
<div class="flex justify-between bg-base-100 py-2 px-5 items-center border-b pb-2 font-medium mt-0.5">
|
||||||
<%= local_assigns[:title] %>
|
<span>
|
||||||
</span>
|
<%= local_assigns[:title] %>
|
||||||
<a href="#" class="text-xl" data-action="click:turbo-modal#close">×</a>
|
</span>
|
||||||
|
<a href="#" class="text-xl" data-action="click:turbo-modal#close">×</a>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
<div class="w-full md:w-[590px] overflow-y-auto" style="max-height: calc(100vh - 14px - 45px); max-height: calc(100dvh - 14px - 45px)">
|
||||||
|
<%= yield %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
|
||||||
<div class="w-full md:w-[590px] overflow-y-auto" style="max-height: calc(100vh - 14px - 45px)">
|
|
||||||
<%= yield %>
|
|
||||||
</div>
|
</div>
|
||||||
|
</turbo-modal>
|
||||||
|
</turbo-frame>
|
||||||
|
<% else %>
|
||||||
|
<% if local_assigns[:title] %>
|
||||||
|
<div class="sticky top-0 z-10 flex justify-between bg-base-100 py-2 px-5 items-center border-b pb-2 font-medium">
|
||||||
|
<span>
|
||||||
|
<%= local_assigns[:title] %>
|
||||||
|
</span>
|
||||||
|
<history-back>
|
||||||
|
<a href="#" class="text-xl block py-2 px-5 -my-2 -mr-5">×</a>
|
||||||
|
</history-back>
|
||||||
</div>
|
</div>
|
||||||
</turbo-modal>
|
<% end %>
|
||||||
</turbo-frame>
|
<div class="w-full">
|
||||||
|
<%= yield %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<div class="flex flex-wrap space-y-4 md:flex-nowrap md:space-y-0">
|
<div class="flex flex-wrap space-y-4 md:flex-nowrap md:space-y-0">
|
||||||
<%= render 'shared/settings_nav' %>
|
<%= render 'shared/settings_nav' %>
|
||||||
<div class="flex-grow max-w-xl mx-auto">
|
<div class="flex-grow max-w-xl mx-auto">
|
||||||
<h1 class="text-4xl font-bold mb-4">SMS</h1>
|
<h1 class="text-3xl md:text-4xl font-bold mb-4">SMS</h1>
|
||||||
<%= render 'placeholder' %>
|
<%= render 'placeholder' %>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-0 md:w-52"></div>
|
<div class="w-0 md:w-52"></div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<div class="flex flex-wrap space-y-4 md:flex-nowrap md:space-y-0">
|
<div class="flex flex-wrap space-y-4 md:flex-nowrap md:space-y-0">
|
||||||
<%= render 'shared/settings_nav' %>
|
<%= render 'shared/settings_nav' %>
|
||||||
<div class="flex-grow max-w-xl mx-auto">
|
<div class="flex-grow max-w-xl mx-auto">
|
||||||
<h1 class="text-4xl font-bold mb-4">SAML SSO</h1>
|
<h1 class="text-3xl md:text-4xl font-bold mb-4">SAML SSO</h1>
|
||||||
<%= render 'placeholder' %>
|
<%= render 'placeholder' %>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-0 md:w-52"></div>
|
<div class="w-0 md:w-52"></div>
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
<%= f.hidden_field 'submitter[name]', value: params[:name] || @submitter&.name %>
|
<%= f.hidden_field 'submitter[name]', value: params[:name] || @submitter&.name %>
|
||||||
<%= f.hidden_field 'submitter[email]', value: params[:email] || @submitter&.email %>
|
<%= f.hidden_field 'submitter[email]', value: params[:email] || @submitter&.email %>
|
||||||
<%= f.hidden_field 'submitter[phone]', value: params[:phone] || @submitter&.phone %>
|
<%= f.hidden_field 'submitter[phone]', value: params[:phone] || @submitter&.phone %>
|
||||||
<%= f.text_field :one_time_code, required: true, class: 'base-input text-center', placeholder: 'XXX-XXX' %>
|
<%= f.text_field :one_time_code, required: true, inputmode: 'numeric', class: 'base-input text-center', placeholder: 'XXXXXX' %>
|
||||||
<div class="flex justify-between items-center mt-1">
|
<div class="flex justify-between items-center mt-1">
|
||||||
<span>
|
<span>
|
||||||
<% if flash[:alert] %>
|
<% if flash[:alert] %>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<div class="flex flex-wrap space-y-4 md:flex-nowrap md:space-y-0">
|
<div class="flex flex-wrap space-y-4 md:flex-nowrap md:space-y-0">
|
||||||
<%= render 'shared/settings_nav' %>
|
<%= render 'shared/settings_nav' %>
|
||||||
<div class="flex-grow max-w-xl mx-auto">
|
<div class="flex-grow max-w-xl mx-auto">
|
||||||
<h1 class="text-4xl font-bold mb-4">
|
<h1 class="text-3xl md:text-4xl font-bold mb-4">
|
||||||
<%= t('storage') %>
|
<%= t('storage') %>
|
||||||
</h1>
|
</h1>
|
||||||
<% value = @encrypted_config.value || { 'service' => 'disk' } %>
|
<% value = @encrypted_config.value || { 'service' => 'disk' } %>
|
||||||
|
|||||||
@@ -45,9 +45,9 @@
|
|||||||
<% default_subject = template&.preferences&.dig('request_email_subject').presence || config.value['subject'] %>
|
<% default_subject = template&.preferences&.dig('request_email_subject').presence || config.value['subject'] %>
|
||||||
<% default_body = template&.preferences&.dig('request_email_body').presence || config_body %>
|
<% default_body = template&.preferences&.dig('request_email_body').presence || config_body %>
|
||||||
<% is_edit_viewer = local_assigns[:submitter] && local_assigns[:viewer_submitter_uuids].include?(local_assigns[:submitter].uuid) %>
|
<% is_edit_viewer = local_assigns[:submitter] && local_assigns[:viewer_submitter_uuids].include?(local_assigns[:submitter].uuid) %>
|
||||||
<div id="<%= message_field_id %>" class="card card-compact bg-base-300/40 hidden">
|
<div id="<%= message_field_id %>" class="relative flex flex-col rounded-2xl md:bg-base-300/40 hidden">
|
||||||
<div class="card-body">
|
<div class="flex flex-auto flex-col gap-2 md:text-sm md:p-4">
|
||||||
<%= tag.input id: toggle_uuid = SecureRandom.uuid, value: '1', name: 'request_email_per_submitter', class: 'peer', type: 'checkbox', hidden: true, checked: local_assigns[:message_per_submitter] != false && template_submitters.size < 5 && template&.preferences&.dig('submitters').to_a.size > 1 %>
|
<%= tag.input id: toggle_uuid = SecureRandom.uuid, value: '1', name: 'request_email_per_submitter', class: 'peer', type: 'checkbox', hidden: true, checked: local_assigns[:message_per_submitter] != false && template_submitters.size < 11 && template&.preferences&.dig('submitters').to_a.size > 1 %>
|
||||||
<% if local_assigns[:viewer_submitter_uuids].present? && local_assigns[:submitter].blank? %>
|
<% if local_assigns[:viewer_submitter_uuids].present? && local_assigns[:submitter].blank? %>
|
||||||
<% (template_submitters.pluck('uuid') - local_assigns[:viewer_submitter_uuids].to_a).each do |signer_uuid| %>
|
<% (template_submitters.pluck('uuid') - local_assigns[:viewer_submitter_uuids].to_a).each do |signer_uuid| %>
|
||||||
<%= hidden_field_tag 'email_message_submitter_uuids[]', signer_uuid %>
|
<%= hidden_field_tag 'email_message_submitter_uuids[]', signer_uuid %>
|
||||||
@@ -57,7 +57,7 @@
|
|||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<%= f.label :subject, t('subject'), class: 'label' %>
|
<%= f.label :subject, t('subject'), class: 'label' %>
|
||||||
<% if template_submitters.size > 1 && template_submitters.size < 5 && local_assigns[:message_per_submitter] != false %>
|
<% if template_submitters.size > 1 && template_submitters.size < 11 && local_assigns[:message_per_submitter] != false %>
|
||||||
<label for="<%= toggle_uuid %>" class="label underline">
|
<label for="<%= toggle_uuid %>" class="label underline">
|
||||||
<%= t('edit_per_party') %>
|
<%= t('edit_per_party') %>
|
||||||
</label>
|
</label>
|
||||||
@@ -78,16 +78,18 @@
|
|||||||
</div>
|
</div>
|
||||||
<%= render 'submissions/message_fields' %>
|
<%= render 'submissions/message_fields' %>
|
||||||
</div>
|
</div>
|
||||||
<% if template_submitters.size > 1 && template_submitters.size < 5 && local_assigns[:message_per_submitter] != false %>
|
<% if template_submitters.size > 1 && template_submitters.size < 11 && local_assigns[:message_per_submitter] != false %>
|
||||||
<div class="hidden peer-checked:block form-control space-y-2">
|
<div class="hidden peer-checked:block form-control space-y-2">
|
||||||
<% uuid = SecureRandom.uuid %>
|
<% uuid = SecureRandom.uuid %>
|
||||||
<% options = template_submitters.map { |e| [e['name'], "request_email_#{uuid}_#{e['uuid']}"] } %>
|
<% options = template_submitters.map { |e| [e['name'], "request_email_#{uuid}_#{e['uuid']}"] } %>
|
||||||
<toggle-visible data-element-ids="<%= options.map(&:last).to_json %>" class="flex relative px-1">
|
<toggle-visible data-element-ids="<%= options.map(&:last).to_json %>" class="flex relative px-1">
|
||||||
<ul class="tabs w-full flex flex-nowrap">
|
<ul class="tabs w-full min-w-0 flex flex-nowrap">
|
||||||
<% options.each_with_index do |(label, val), index| %>
|
<% options.each_with_index do |(label, val), index| %>
|
||||||
<div class="w-full">
|
<div class="w-full min-w-0 has-[:checked]:min-w-fit">
|
||||||
<%= f.radio_button :selected, val, checked: index.zero?, id: "#{val}_radio", data: { action: 'click:toggle-visible#trigger' }, class: 'hidden peer' %>
|
<%= f.radio_button :selected, val, checked: index.zero?, id: "#{val}_radio", data: { action: 'click:toggle-visible#trigger' }, class: 'hidden peer' %>
|
||||||
<%= f.label :selected, label, value: val, for: "#{val}_radio", class: 'tab w-full tab-lifted peer-checked:tab-active !bg-transparent' %>
|
<%= f.label :selected, value: val, for: "#{val}_radio", class: 'tab w-full tab-lifted peer-checked:tab-active !bg-transparent !px-2' do %>
|
||||||
|
<span class="truncate" title="<%= label %>"><%= label %></span>
|
||||||
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<div class="mt-2 mb-1">
|
<div class="mt-2 mb-1">
|
||||||
<div class="tooltip w-full" data-tip="<%= t('unlock_with_docuseal_pro') %>">
|
<div class="tooltip tooltip-no-touch w-full" data-tip="<%= t('unlock_with_docuseal_pro') %>">
|
||||||
<%= link_to submitter.sent_at? ? t('re_send_sms') : t('send_sms'), Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premises" }.to_query}", class: 'btn btn-sm btn-primary text-gray-400 w-full' %>
|
<%= link_to submitter.sent_at? ? t('re_send_sms') : t('send_sms'), Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premises" }.to_query}", class: 'btn btn-sm btn-primary text-gray-400 w-full' %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -29,7 +29,8 @@
|
|||||||
<div>
|
<div>
|
||||||
<% timezone = local_assigns[:with_submitter_timezone] ? (submitter.timezone || local_assigns[:timezone]) : local_assigns[:timezone] %>
|
<% timezone = local_assigns[:with_submitter_timezone] ? (submitter.timezone || local_assigns[:timezone]) : local_assigns[:timezone] %>
|
||||||
<% time_format = local_assigns[:with_timestamp_seconds] ? :detailed : :long %>
|
<% time_format = local_assigns[:with_timestamp_seconds] ? :detailed : :long %>
|
||||||
<%= l(attachment.created_at.in_time_zone(timezone), format: time_format, locale: local_assigns[:locale]) %> <%= TimeUtils.timezone_abbr(timezone, attachment.created_at) %>
|
<% signature_timestamp = (local_assigns[:with_signature_id_completed_at] ? submitter.completed_at : nil) || attachment.created_at %>
|
||||||
|
<%= l(signature_timestamp.in_time_zone(timezone), format: time_format, locale: local_assigns[:locale]) %> <%= TimeUtils.timezone_abbr(timezone, signature_timestamp) %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -2,12 +2,13 @@
|
|||||||
<%= render 'submissions/preview_tags' %>
|
<%= render 'submissions/preview_tags' %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% font_scale = 1040.0 / PdfUtils::US_LETTER_W %>
|
<% font_scale = 1040.0 / PdfUtils::US_LETTER_W %>
|
||||||
<% configs = AccountConfig.where(account_id: @submission.account_id, key: [AccountConfig::COMBINE_PDF_RESULT_KEY, AccountConfig::WITH_SIGNATURE_ID, AccountConfig::WITH_SUBMITTER_TIMEZONE_KEY, AccountConfig::WITH_SIGNATURE_ID_REASON_KEY, AccountConfig::WITH_TIMESTAMP_SECONDS_KEY]) %>
|
<% configs = AccountConfig.where(account_id: @submission.account_id, key: [AccountConfig::COMBINE_PDF_RESULT_KEY, AccountConfig::WITH_SIGNATURE_ID, AccountConfig::WITH_SUBMITTER_TIMEZONE_KEY, AccountConfig::WITH_SIGNATURE_ID_REASON_KEY, AccountConfig::WITH_TIMESTAMP_SECONDS_KEY, AccountConfig::WITH_SIGNATURE_ID_COMPLETED_AT_KEY]) %>
|
||||||
<% with_signature_id = configs.find { |e| e.key == AccountConfig::WITH_SIGNATURE_ID }&.value == true %>
|
<% with_signature_id = configs.find { |e| e.key == AccountConfig::WITH_SIGNATURE_ID }&.value == true %>
|
||||||
<% is_combined_enabled = configs.find { |e| e.key == AccountConfig::COMBINE_PDF_RESULT_KEY }&.value == true && !@submission.template_fields&.any? { |f| f['type'] == 'verification' } %>
|
<% is_combined_enabled = configs.find { |e| e.key == AccountConfig::COMBINE_PDF_RESULT_KEY }&.value == true && !@submission.template_fields&.any? { |f| f['type'] == 'verification' } %>
|
||||||
<% with_submitter_timezone = configs.find { |e| e.key == AccountConfig::WITH_SUBMITTER_TIMEZONE_KEY }&.value == true %>
|
<% with_submitter_timezone = configs.find { |e| e.key == AccountConfig::WITH_SUBMITTER_TIMEZONE_KEY }&.value == true %>
|
||||||
<% with_timestamp_seconds = configs.find { |e| e.key == AccountConfig::WITH_TIMESTAMP_SECONDS_KEY }&.value == true %>
|
<% with_timestamp_seconds = configs.find { |e| e.key == AccountConfig::WITH_TIMESTAMP_SECONDS_KEY }&.value == true %>
|
||||||
<% with_signature_id_reason = configs.find { |e| e.key == AccountConfig::WITH_SIGNATURE_ID_REASON_KEY }&.value != false %>
|
<% with_signature_id_reason = configs.find { |e| e.key == AccountConfig::WITH_SIGNATURE_ID_REASON_KEY }&.value != false %>
|
||||||
|
<% with_signature_id_completed_at = configs.find { |e| e.key == AccountConfig::WITH_SIGNATURE_ID_COMPLETED_AT_KEY }&.value == true %>
|
||||||
<main style="max-width: 1600px" class="mx-auto pl-4">
|
<main style="max-width: 1600px" class="mx-auto pl-4">
|
||||||
<div class="flex justify-between py-1.5 items-center pr-4 sticky top-0 md:relative z-10 bg-base-100">
|
<div class="flex justify-between py-1.5 items-center pr-4 sticky top-0 md:relative z-10 bg-base-100">
|
||||||
<a href="<%= signed_in? && @submission.account_id == current_account&.id && @submission.template ? template_path(@submission.template) : '/' %>" class="flex items-center space-x-3 py-1">
|
<a href="<%= signed_in? && @submission.account_id == current_account&.id && @submission.template ? template_path(@submission.template) : '/' %>" class="flex items-center space-x-3 py-1">
|
||||||
@@ -24,10 +25,12 @@
|
|||||||
<span class="hidden md:inline"><%= t('audit_log') %></span>
|
<span class="hidden md:inline"><%= t('audit_log') %></span>
|
||||||
</a>
|
</a>
|
||||||
<% elsif signed_in? %>
|
<% elsif signed_in? %>
|
||||||
<%= link_to submission_events_path(@submission), class: 'white-button', data: { turbo_frame: :modal } do %>
|
<open-modal-mobile class="flex">
|
||||||
<%= svg_icon('logs', class: 'w-6 h-6') %>
|
<%= link_to submission_events_path(@submission), class: 'white-button', data: { turbo_frame: :modal } do %>
|
||||||
<span class="hidden md:block"><%= t('event_log') %></span>
|
<%= svg_icon('logs', class: 'w-6 h-6') %>
|
||||||
<% end %>
|
<span class="hidden md:block"><%= t('event_log') %></span>
|
||||||
|
<% end %>
|
||||||
|
</open-modal-mobile>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if signed_in? && !@submission.completed_at? && can?(:manage, :resend_all) && @submission.submitters.to_a.size > 3 && !@submission.archived_at? && !@submission.template&.archived_at? && !@submission.expired? && can?(:update, @submission) %>
|
<% if signed_in? && !@submission.completed_at? && can?(:manage, :resend_all) && @submission.submitters.to_a.size > 3 && !@submission.archived_at? && !@submission.template&.archived_at? && !@submission.expired? && can?(:update, @submission) %>
|
||||||
<% pending_submitters_count = @submission.submitters.to_a.count { |s| !s.completed_at? && s.email.present? && !s.declined_at? } %>
|
<% pending_submitters_count = @submission.submitters.to_a.count { |s| !s.completed_at? && s.email.present? && !s.declined_at? } %>
|
||||||
@@ -144,7 +147,7 @@
|
|||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<% else %>
|
<% else %>
|
||||||
<%= render 'submissions/value', page_width: width, page_height: height, font_scale:, area:, field:, attachments_index:, value: mask.present? ? Array.wrap(value).map { |e| TextUtils.mask_value(e, mask) }.join(', ') : value, locale: @submission.account.locale, timezone: @submission.account.timezone, submitter:, with_signature_id:, with_submitter_timezone:, with_timestamp_seconds:, with_signature_id_reason: %>
|
<%= render 'submissions/value', page_width: width, page_height: height, font_scale:, area:, field:, attachments_index:, value: mask.present? ? Array.wrap(value).map { |e| TextUtils.mask_value(e, mask) }.join(', ') : value, locale: @submission.account.locale, timezone: @submission.account.timezone, submitter:, with_signature_id:, with_submitter_timezone:, with_timestamp_seconds:, with_signature_id_reason:, with_signature_id_completed_at: %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% elsif field['readonly'] != true && submitter && !submitter.completed_at? %>
|
<% elsif field['readonly'] != true && submitter && !submitter.completed_at? %>
|
||||||
<% submitters_order_index ||= (@submission.template_submitters || @submission.template.submitters).each_with_index.to_h { |s, i| [s['uuid'], i] } %>
|
<% submitters_order_index ||= (@submission.template_submitters || @submission.template.submitters).each_with_index.to_h { |s, i| [s['uuid'], i] } %>
|
||||||
@@ -179,10 +182,12 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<% if signed_in? && can?(:update, @submission) && submitter && !submitter.completed_at? && !@submission.completed_at? && !submitter.declined_at? && !@submission.archived_at? && !@submission.template&.archived_at? && !@submission.expired? && !submitter.start_form_submission_events.any? %>
|
<% if signed_in? && can?(:update, @submission) && submitter && !submitter.completed_at? && !@submission.completed_at? && !submitter.declined_at? && !@submission.archived_at? && !@submission.template&.archived_at? && !@submission.expired? && !submitter.start_form_submission_events.any? %>
|
||||||
<span class="tooltip tooltip-left" data-tip="<%= t('edit') %>">
|
<span class="tooltip tooltip-left tooltip-no-touch" data-tip="<%= t('edit') %>">
|
||||||
<%= link_to edit_submitter_path(submitter), class: 'shrink-0 inline md:hidden md:group-hover:inline', data: { turbo_frame: 'modal' } do %>
|
<open-modal-mobile>
|
||||||
<%= svg_icon('pencil', class: 'w-5 h-5') %>
|
<%= link_to edit_submitter_path(submitter), class: 'shrink-0 inline md:hidden md:group-hover:inline', data: { turbo_frame: 'modal' } do %>
|
||||||
<% end %>
|
<%= svg_icon('pencil', class: 'w-5 h-5') %>
|
||||||
|
<% end %>
|
||||||
|
</open-modal-mobile>
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,23 +1,23 @@
|
|||||||
<% filter_params = params.permit(Submissions::Filter::ALLOWED_PARAMS).compact_blank %>
|
<% filter_params = params.permit(Submissions::Filter::ALLOWED_PARAMS).compact_blank %>
|
||||||
<div>
|
<div>
|
||||||
<%= link_to root_path, class: 'flex items-center' do %>
|
<%= link_to root_path, class: 'flex items-center mb-1 md:mb-0' do %>
|
||||||
<%= svg_icon('chevron_left', class: 'w-5 h-5') %>
|
<%= svg_icon('chevron_left', class: 'w-5 h-5') %>
|
||||||
<span style="margin-left: 3px"><%= t('back_to_active') %></span>
|
<span style="margin-left: 3px"><%= t('back_to_active') %></span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col md:flex-row md:items-center mb-4 gap-3">
|
<div class="flex flex-col md:flex-row md:items-center mb-4 gap-3">
|
||||||
<div class="flex w-full justify-between items-center">
|
<div class="group/header flex w-full justify-between items-center">
|
||||||
<div>
|
<div class="max-md:group-has-[search-input:focus-within]/header:hidden">
|
||||||
<h1 class="text-2xl md:text-3xl font-bold md:block <%= 'hidden' if params[:q].present? %>"><%= t('submissions') %> <span class="badge badge-outline badge-lg align-middle"><%= t('archived') %></span></h1>
|
<h1 class="text-2xl md:text-3xl font-bold md:block <%= 'hidden' if params[:q].present? %>"><%= t('submissions') %> <span class="badge badge-outline badge-lg align-middle"><%= t('archived') %></span></h1>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div class="max-md:has-[search-input:focus-within]:grow <%= 'max-md:grow' if params[:q].present? %>">
|
||||||
<% if params[:q].present? || @pagy.pages > 1 || filter_params.present? %>
|
<% if params[:q].present? || @pagy.pages > 1 || filter_params.present? %>
|
||||||
<%= render 'shared/search_input', placeholder: "#{t('search')}..." %>
|
<%= render 'shared/search_input', placeholder: "#{t('search')}..." %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col items-end md:flex-row gap-2">
|
<div class="flex items-center md:items-end gap-2">
|
||||||
<%= render 'submissions_filters/applied_filters', filter_params: %>
|
<%= render 'submissions_filters/applied_filters', filter_params:, with_status: true %>
|
||||||
<%= render 'submissions_filters/filter_button', filter_params: %>
|
<%= render 'submissions_filters/filter_button', filter_params: %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<% filter_params = params.permit(Submissions::Filter::ALLOWED_PARAMS).compact_blank %>
|
<% filter_params = params.permit(Submissions::Filter::ALLOWED_PARAMS).compact_blank %>
|
||||||
<% is_show_tabs = (@pagy.count.nil? || @pagy.count >= 5) || params[:status].present? || filter_params.present? %>
|
<% is_show_tabs = (@pagy.count.nil? || @pagy.count >= 5) || params[:status].present? || filter_params.present? %>
|
||||||
<% if Docuseal.demo? %><%= render 'shared/demo_alert' %><% end %>
|
<% if Docuseal.demo? %><%= render 'shared/demo_alert' %><% end %>
|
||||||
<div class="flex justify-between items-center w-full mb-4">
|
<div class="group/header flex justify-between items-center w-full mb-4">
|
||||||
<div class="flex items-center flex-grow min-w-0">
|
<div class="flex items-center flex-grow min-w-0 max-md:group-has-[search-input:focus-within]/header:hidden <%= 'max-md:hidden' if params[:q].present? %>">
|
||||||
<div class="mr-2">
|
<div class="mr-2">
|
||||||
<%= render 'dashboard/toggle_view', selected: 'submissions' %>
|
<%= render 'dashboard/toggle_view', selected: 'submissions' %>
|
||||||
</div>
|
</div>
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
<%= t('submissions') %>
|
<%= t('submissions') %>
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex space-x-2">
|
<div class="flex space-x-2 max-md:has-[search-input:focus-within]:grow <%= 'max-md:grow' if params[:q].present? %>">
|
||||||
<% if params[:q].present? || @pagy.pages > 1 || filter_params.present? %>
|
<% if params[:q].present? || @pagy.pages > 1 || filter_params.present? %>
|
||||||
<%= render 'shared/search_input' %>
|
<%= render 'shared/search_input' %>
|
||||||
<% end %>
|
<% end %>
|
||||||
@@ -18,10 +18,12 @@
|
|||||||
<span class="hidden sm:block">
|
<span class="hidden sm:block">
|
||||||
<%= render 'templates/upload_button' %>
|
<%= render 'templates/upload_button' %>
|
||||||
</span>
|
</span>
|
||||||
<%= link_to new_template_path, class: 'white-button !border gap-2', data: { turbo_frame: :modal } do %>
|
<open-modal-mobile data-disabled="<%= !Docuseal.multitenant? %>" class="flex">
|
||||||
<%= svg_icon('plus', class: 'w-6 h-6 stroke-2') %>
|
<%= link_to new_template_path, class: 'white-button !border gap-2', data: { turbo_frame: :modal } do %>
|
||||||
<span class="hidden md:block"><%= t('create') %></span>
|
<%= svg_icon('plus', class: 'w-6 h-6 stroke-2') %>
|
||||||
<% end %>
|
<span class="hidden md:block"><%= t('create') %></span>
|
||||||
|
<% end %>
|
||||||
|
</open-modal-mobile>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -34,7 +36,7 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
<% if is_show_tabs %>
|
<% if is_show_tabs %>
|
||||||
<div class="flex items-center flex-col md:flex-row md:flex-wrap gap-2 mb-4">
|
<div class="flex items-center flex-col md:flex-row md:flex-wrap gap-2 mb-4">
|
||||||
<div class="flex items-center md:items-end flex-col md:flex-row gap-2 w-full md:w-fit">
|
<div class="hidden md:flex md:items-end gap-2 md:w-fit">
|
||||||
<a href="<%= url_for(params: request.query_parameters.except('status')) %>" class="<%= params[:status].blank? ? 'border-neutral-700' : 'border-neutral-300' %> flex h-10 px-2 py-1 text-lg items-center justify-between border text-center text-neutral font-semibold rounded-xl w-full md:w-48 hover:border-neutral-600">
|
<a href="<%= url_for(params: request.query_parameters.except('status')) %>" class="<%= params[:status].blank? ? 'border-neutral-700' : 'border-neutral-300' %> flex h-10 px-2 py-1 text-lg items-center justify-between border text-center text-neutral font-semibold rounded-xl w-full md:w-48 hover:border-neutral-600">
|
||||||
<div class="flex items-center space-x-1">
|
<div class="flex items-center space-x-1">
|
||||||
<%= svg_icon('list', class: 'w-5 h-5') %>
|
<%= svg_icon('list', class: 'w-5 h-5') %>
|
||||||
@@ -54,8 +56,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-end flex-col md:flex-row gap-2 w-full md:w-fit">
|
<div class="flex items-center md:items-end gap-2 w-full md:w-fit">
|
||||||
<%= render 'submissions_filters/applied_filters', filter_params: %>
|
<%= render 'submissions_filters/applied_filters', filter_params:, with_status: true %>
|
||||||
<%= render 'submissions_filters/filter_button', filter_params: %>
|
<%= render 'submissions_filters/filter_button', filter_params: %>
|
||||||
<%= render 'submissions_filters/actions', filter_params: %>
|
<%= render 'submissions_filters/actions', filter_params: %>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,72 +1,136 @@
|
|||||||
<% query_params = params.permit(:q).merge(filter_params) %>
|
<% query_params = params.permit(:q).merge(filter_params) %>
|
||||||
<% if icon = { 'declined' => 'x_circle', 'expired' => 'clock_cancel', 'partially_completed' => 'clock_edit', 'sent' => 'send', 'opened' => 'mail_opened' }[params[:status]] %>
|
<% filter_names = %w[status folder author completed_at created_at] %>
|
||||||
<div class="flex h-10 px-2 py-1 text-lg items-center justify-between border text-center text-neutral font-semibold rounded-xl w-full md:w-34 border-neutral-700">
|
<% ordered_filters = request.query_parameters.keys.filter_map { |key| filter_names.find { |name| key.start_with?(name) } }.uniq %>
|
||||||
<%= link_to submissions_filter_path('status', query_params.merge(path: url_for, with_remove: true)), data: { turbo_frame: 'modal' }, class: 'flex items-center space-x-1 w-full pr-1 md:max-w-[140px]' do %>
|
<% chip_order = ordered_filters.reverse.each_with_index.to_h { |name, index| [name, index + 1] } %>
|
||||||
<%= svg_icon(icon, class: 'w-5 h-5 shrink-0') %>
|
<% chip_order.default = ordered_filters.size + 1 %>
|
||||||
<span class="font-normal truncate"><%= t(params[:status]) %></span>
|
<% status_icon = { 'declined' => 'x_circle', 'expired' => 'clock_cancel', 'partially_completed' => 'clock_edit', 'sent' => 'send', 'opened' => 'mail_opened' }[params[:status]] %>
|
||||||
<% end %>
|
<% status_icons = { 'all' => 'list', 'pending' => 'clock', 'completed' => 'circle_check' } %>
|
||||||
<%= link_to url_for(params: request.query_parameters.except('status')), class: 'rounded-lg ml-1 hover:bg-base-content hover:text-white' do %>
|
<% current_status = status_icons.key?(params[:status].to_s) ? params[:status].to_s : 'all' %>
|
||||||
<%= svg_icon('x', class: 'w-5 h-5') %>
|
<% with_status_button = local_assigns[:with_status] && status_icon.blank? %>
|
||||||
<% end %>
|
<% chips_html = capture do %>
|
||||||
</div>
|
<% if status_icon %>
|
||||||
|
<div class="order-none flex h-10 px-2 py-1 text-base md:text-lg items-center justify-between border text-center text-neutral font-semibold rounded-xl shrink-0 max-w-[60vw] md:w-36 border-neutral-700">
|
||||||
|
<%= link_to submissions_filter_path('status', query_params.merge(path: url_for, with_remove: true)), data: { turbo_frame: 'modal' }, class: 'flex items-center space-x-1 flex-1 min-w-0 pr-1' do %>
|
||||||
|
<%= svg_icon(status_icon, class: 'w-5 h-5 shrink-0') %>
|
||||||
|
<span class="font-normal truncate"><%= t(params[:status]) %></span>
|
||||||
|
<% end %>
|
||||||
|
<%= link_to url_for(params: request.query_parameters.except('status')), class: 'rounded-lg ml-1 shrink-0 hover:bg-base-content hover:text-white' do %>
|
||||||
|
<%= svg_icon('x', class: 'w-5 h-5') %>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
<% if params[:folder].present? %>
|
||||||
|
<div class="order-<%= chip_order['folder'] %> md:order-none md:tooltip tooltip-no-touch tooltip-bottom flex md:flex h-10 px-2 py-1 text-base md:text-lg items-center justify-between border text-center text-neutral font-semibold rounded-xl shrink-0 max-w-[60vw] md:w-36 border-neutral-700" data-tip="<%= t('folder') %>">
|
||||||
|
<%= link_to submissions_filter_path('folder', query_params.merge(path: url_for, with_remove: true)), data: { turbo_frame: 'modal' }, class: 'flex items-center space-x-1 flex-1 min-w-0 pr-1' do %>
|
||||||
|
<%= svg_icon('folder', class: 'w-5 h-5 shrink-0') %>
|
||||||
|
<span class="font-normal truncate"><%= params[:folder] %></span>
|
||||||
|
<% end %>
|
||||||
|
<%= link_to url_for(params: request.query_parameters.except('folder')), class: 'rounded-lg ml-1 shrink-0 hover:bg-base-content hover:text-white' do %>
|
||||||
|
<%= svg_icon('x', class: 'w-5 h-5') %>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
<% if params[:author].present? %>
|
||||||
|
<div class="order-<%= chip_order['author'] %> md:order-none md:tooltip tooltip-no-touch tooltip-bottom flex md:flex h-10 px-2 py-1 text-base md:text-lg items-center justify-between border text-center text-neutral font-semibold rounded-xl shrink-0 max-w-[60vw] md:w-36 border-neutral-700" data-tip="<%= t('author') %>">
|
||||||
|
<%= link_to submissions_filter_path('author', query_params.merge(path: url_for, with_remove: true)), data: { turbo_frame: 'modal' }, class: 'flex items-center space-x-1 flex-1 min-w-0 pr-1' do %>
|
||||||
|
<%= svg_icon('user', class: 'w-5 h-5 shrink-0') %>
|
||||||
|
<span class="font-normal truncate"><%= current_account.users.accessible_by(current_ability).where(account: current_account).find_by(email: params[:author])&.full_name || 'NA' %></span>
|
||||||
|
<% end %>
|
||||||
|
<%= link_to url_for(params: request.query_parameters.except('author')), class: 'rounded-lg ml-1 shrink-0 hover:bg-base-content hover:text-white' do %>
|
||||||
|
<%= svg_icon('x', class: 'w-5 h-5') %>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
<% if query_params[:completed_at_from].present? || query_params[:completed_at_to].present? %>
|
||||||
|
<div class="order-<%= chip_order['completed_at'] %> md:order-none md:tooltip tooltip-no-touch tooltip-bottom flex md:flex h-10 px-2 py-1 text-base md:text-lg items-center justify-between border text-center text-neutral font-semibold rounded-xl shrink-0 max-w-[60vw] md:w-36 border-neutral-700" data-tip="<%= t('completed_at') %>">
|
||||||
|
<%= link_to submissions_filter_path('completed_at', query_params.merge(path: url_for, with_remove: true)), data: { turbo_frame: 'modal' }, class: 'flex items-center space-x-1 flex-1 min-w-0 pr-1' do %>
|
||||||
|
<%= svg_icon('calendar_check', class: 'w-5 h-5 shrink-0') %>
|
||||||
|
<span class="flex flex-col font-normal text-left md:text-center text-xs">
|
||||||
|
<% if query_params[:completed_at_from] == query_params[:completed_at_to] %>
|
||||||
|
<span><%= l(Date.parse(query_params[:completed_at_from]), locale: current_account.locale) %></span>
|
||||||
|
<% else %>
|
||||||
|
<span><%= query_params[:completed_at_from].present? ? l(Date.parse(query_params[:completed_at_from]), locale: current_account.locale) : '∞' %></span>
|
||||||
|
<span><%= query_params[:completed_at_to].present? ? l(Date.parse(query_params[:completed_at_to]), locale: current_account.locale) : t('today') %></span>
|
||||||
|
<% end %>
|
||||||
|
</span>
|
||||||
|
<% end %>
|
||||||
|
<%= link_to url_for(params: request.query_parameters.except('completed_at_from', 'completed_at_to')), class: 'rounded-lg ml-1 shrink-0 hover:bg-base-content hover:text-white' do %>
|
||||||
|
<%= svg_icon('x', class: 'w-5 h-5') %>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
<% if query_params[:created_at_from].present? || query_params[:created_at_to].present? %>
|
||||||
|
<div class="order-<%= chip_order['created_at'] %> md:order-none md:tooltip tooltip-no-touch tooltip-bottom flex md:flex h-10 px-2 py-1 text-base md:text-lg items-center justify-between border text-center text-neutral font-semibold rounded-xl shrink-0 max-w-[60vw] md:w-36 border-neutral-700" data-tip="<%= t('created_at') %>">
|
||||||
|
<%= link_to submissions_filter_path('created_at', query_params.merge(path: url_for, with_remove: true)), data: { turbo_frame: 'modal' }, class: 'flex items-center space-x-1 flex-1 min-w-0 pr-1' do %>
|
||||||
|
<%= svg_icon('calendar', class: 'w-5 h-5 shrink-0') %>
|
||||||
|
<span class="flex flex-col font-normal text-left md:text-center text-xs">
|
||||||
|
<% if query_params[:created_at_from] == query_params[:created_at_to] %>
|
||||||
|
<span><%= l(Date.parse(query_params[:created_at_from]), locale: current_account.locale) %></span>
|
||||||
|
<% else %>
|
||||||
|
<span><%= query_params[:created_at_from].present? ? l(Date.parse(query_params[:created_at_from]), locale: current_account.locale) : '∞' %></span>
|
||||||
|
<span><%= query_params[:created_at_to].present? ? l(Date.parse(query_params[:created_at_to]), locale: current_account.locale) : t('today') %></span>
|
||||||
|
<% end %>
|
||||||
|
</span>
|
||||||
|
<% end %>
|
||||||
|
<%= link_to url_for(params: request.query_parameters.except('created_at_to', 'created_at_from')), class: 'rounded-lg ml-1 shrink-0 hover:bg-base-content hover:text-white' do %>
|
||||||
|
<%= svg_icon('x', class: 'w-5 h-5') %>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if params[:folder].present? %>
|
<% if with_status_button && chips_html.blank? %>
|
||||||
<div class="tooltip tooltip-bottom flex h-10 px-2 py-1 text-lg items-center justify-between border text-center text-neutral font-semibold rounded-xl w-full md:w-34 border-neutral-700" data-tip="<%= t('folder') %>">
|
<div class="dropdown md:hidden shrink-0">
|
||||||
<%= link_to submissions_filter_path('folder', query_params.merge(path: url_for, with_remove: true)), data: { turbo_frame: 'modal' }, class: 'flex items-center space-x-1 w-full pr-1 md:max-w-[140px]' do %>
|
<label tabindex="0" class="cursor-pointer flex h-10 px-2 py-1 space-x-1 text-base items-center border text-neutral rounded-xl <%= current_status == 'all' ? 'border-neutral-300' : 'border-neutral-700' %>">
|
||||||
<%= svg_icon('folder', class: 'w-5 h-5 shrink-0') %>
|
<%= svg_icon(status_icons[current_status], class: 'w-5 h-5 shrink-0') %>
|
||||||
<span class="font-normal truncate"><%= params[:folder] %></span>
|
<span><%= t(current_status) %></span>
|
||||||
<% end %>
|
<%= svg_icon('chevron_down', class: 'w-4 h-4 shrink-0') %>
|
||||||
<%= link_to url_for(params: request.query_parameters.except('folder')), class: 'rounded-lg ml-1 hover:bg-base-content hover:text-white' do %>
|
</label>
|
||||||
<%= svg_icon('x', class: 'w-5 h-5') %>
|
<ul tabindex="0" class="z-10 dropdown-content p-2 mt-2 shadow menu text-base bg-base-100 rounded-box" style="min-width: 180px;">
|
||||||
<% end %>
|
<li>
|
||||||
</div>
|
<%= link_to url_for(params: request.query_parameters.except('status')), class: ('bg-base-200' if current_status == 'all') do %>
|
||||||
<% end %>
|
<%= svg_icon('list', class: 'w-5 h-5 shrink-0') %>
|
||||||
<% if params[:author].present? %>
|
<span><%= t('all') %></span>
|
||||||
<div class="tooltip tooltip-bottom flex h-10 px-2 py-1 text-lg items-center justify-between border text-center text-neutral font-semibold rounded-xl w-full md:w-34 border-neutral-700" data-tip="<%= t('author') %>">
|
<% if local_assigns[:all_count] %>
|
||||||
<%= link_to submissions_filter_path('author', query_params.merge(path: url_for, with_remove: true)), data: { turbo_frame: 'modal' }, class: 'flex items-center space-x-1 w-full pr-1 md:max-w-[140px]' do %>
|
<span class="badge badge-sm badge-ghost font-medium px-2 ml-auto">
|
||||||
<%= svg_icon('user', class: 'w-5 h-5 shrink-0') %>
|
<%= local_assigns[:all_count] %>
|
||||||
<span class="font-normal truncate"><%= current_account.users.accessible_by(current_ability).where(account: current_account).find_by(email: params[:author])&.full_name || 'NA' %></span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= link_to url_for(params: request.query_parameters.except('author')), class: 'rounded-lg ml-1 hover:bg-base-content hover:text-white' do %>
|
|
||||||
<%= svg_icon('x', class: 'w-5 h-5') %>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
<% if query_params[:completed_at_from].present? || query_params[:completed_at_to].present? %>
|
|
||||||
<div class="tooltip tooltip-bottom flex h-10 px-2 py-1 text-lg items-center justify-between border text-center text-neutral font-semibold rounded-xl w-full md:w-34 border-neutral-700" data-tip="<%= t('completed_at') %>">
|
|
||||||
<%= link_to submissions_filter_path('completed_at', query_params.merge(path: url_for, with_remove: true)), data: { turbo_frame: 'modal' }, class: 'flex items-center space-x-1 w-full pr-1 md:max-w-[140px]' do %>
|
|
||||||
<%= svg_icon('calendar_check', class: 'w-5 h-5 shrink-0') %>
|
|
||||||
<span class="flex flex-row md:flex-col font-normal text-left md:text-center md:text-xs">
|
|
||||||
<% if query_params[:completed_at_from] == query_params[:completed_at_to] %>
|
|
||||||
<span><%= l(Date.parse(query_params[:completed_at_from]), locale: current_account.locale) %></span>
|
|
||||||
<% else %>
|
|
||||||
<span><%= query_params[:completed_at_from].present? ? l(Date.parse(query_params[:completed_at_from]), locale: current_account.locale) : '∞' %></span>
|
|
||||||
<span class="px-1 md:px-0 md:hidden">-</span>
|
|
||||||
<span><%= query_params[:completed_at_to].present? ? l(Date.parse(query_params[:completed_at_to]), locale: current_account.locale) : t('today') %></span>
|
|
||||||
<% end %>
|
<% end %>
|
||||||
</span>
|
</li>
|
||||||
<% end %>
|
<li>
|
||||||
<%= link_to url_for(params: request.query_parameters.except('completed_at_from', 'completed_at_to')), class: 'rounded-lg ml-1 hover:bg-base-content hover:text-white' do %>
|
<%= link_to url_for(params: request.query_parameters.merge('status' => 'pending')), class: ('bg-base-200' if current_status == 'pending') do %>
|
||||||
<%= svg_icon('x', class: 'w-5 h-5') %>
|
<%= svg_icon('clock', class: 'w-5 h-5 shrink-0') %>
|
||||||
<% end %>
|
<span><%= t('pending') %></span>
|
||||||
</div>
|
<% if local_assigns[:pending_count] %>
|
||||||
<% end %>
|
<span class="badge badge-sm badge-ghost font-medium px-2 ml-auto">
|
||||||
<% if query_params[:created_at_from].present? || query_params[:created_at_to].present? %>
|
<%= local_assigns[:pending_count] %>
|
||||||
<div class="tooltip tooltip-bottom flex h-10 px-2 py-1 text-lg items-center justify-between border text-center text-neutral font-semibold rounded-xl w-full md:w-34 border-neutral-700" data-tip="<%= t('created_at') %>">
|
</span>
|
||||||
<%= link_to submissions_filter_path('created_at', query_params.merge(path: url_for, with_remove: true)), data: { turbo_frame: 'modal' }, class: 'flex items-center space-x-1 w-full pr-1 md:max-w-[140px]' do %>
|
<% end %>
|
||||||
<%= svg_icon('calendar', class: 'w-5 h-5 shrink-0') %>
|
|
||||||
<span class="flex flex-row md:flex-col font-normal text-left md:text-center md:text-xs">
|
|
||||||
<% if query_params[:created_at_from] == query_params[:created_at_to] %>
|
|
||||||
<span><%= l(Date.parse(query_params[:created_at_from]), locale: current_account.locale) %></span>
|
|
||||||
<% else %>
|
|
||||||
<span><%= query_params[:created_at_from].present? ? l(Date.parse(query_params[:created_at_from]), locale: current_account.locale) : '∞' %></span>
|
|
||||||
<span class="px-1 md:px-0 md:hidden">-</span>
|
|
||||||
<span><%= query_params[:created_at_to].present? ? l(Date.parse(query_params[:created_at_to]), locale: current_account.locale) : t('today') %></span>
|
|
||||||
<% end %>
|
<% end %>
|
||||||
</span>
|
</li>
|
||||||
<% end %>
|
<li>
|
||||||
<%= link_to url_for(params: request.query_parameters.except('created_at_to', 'created_at_from')), class: 'rounded-lg ml-1 hover:bg-base-content hover:text-white' do %>
|
<%= link_to url_for(params: request.query_parameters.merge('status' => 'completed')), class: ('bg-base-200' if current_status == 'completed') do %>
|
||||||
<%= svg_icon('x', class: 'w-5 h-5') %>
|
<%= svg_icon('circle_check', class: 'w-5 h-5 shrink-0') %>
|
||||||
<% end %>
|
<span><%= t('completed') %></span>
|
||||||
|
<% if local_assigns[:completed_count] %>
|
||||||
|
<span class="badge badge-sm badge-ghost font-medium px-2 ml-auto">
|
||||||
|
<%= local_assigns[:completed_count] %>
|
||||||
|
</span>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
<% if chips_html.present? %>
|
||||||
|
<scroll-fade class="flex items-center gap-2 overflow-x-auto flex-1 min-w-0 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden [&::-webkit-scrollbar]:h-0 [&::-webkit-scrollbar]:w-0 md:contents">
|
||||||
|
<% if with_status_button %>
|
||||||
|
<a href="<%= submissions_filter_path('status', query_params.merge(path: url_for)) %>" data-turbo-frame="modal" class="order-none md:hidden flex h-10 px-2 py-1 space-x-1 text-base items-center border text-neutral rounded-xl shrink-0 <%= current_status == 'all' ? 'border-neutral-300' : 'border-neutral-700' %>">
|
||||||
|
<%= svg_icon(status_icons[current_status], class: 'w-5 h-5 shrink-0') %>
|
||||||
|
<span><%= t(current_status) %></span>
|
||||||
|
<%= svg_icon('chevron_down', class: 'w-4 h-4 shrink-0') %>
|
||||||
|
</a>
|
||||||
|
<% end %>
|
||||||
|
<%= chips_html %>
|
||||||
|
</scroll-fade>
|
||||||
|
<% end %>
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
<% query_params = params.permit(:q).merge(filter_params) %>
|
<% query_params = params.permit(:q).merge(filter_params) %>
|
||||||
<div class="dropdown dropdown-end">
|
<% advanced_filters = filter_params.then { |f| f[:status].in?(%w[pending completed]) ? f.except(:status) : f } %>
|
||||||
<label tabindex="0" class="cursor-pointer flex h-10 px-3 py-1 space-x-1 text-lg items-center justify-between border text-center text-neutral rounded-xl border-neutral-300 hover:border-neutral-700">
|
<div class="dropdown dropdown-end shrink-0">
|
||||||
|
<label tabindex="0" class="cursor-pointer flex h-10 py-1 text-lg items-center border text-center text-neutral rounded-xl hover:border-neutral-700 <%= advanced_filters.present? ? 'w-10 md:w-auto px-0 md:px-3 space-x-0 md:space-x-1 justify-center md:justify-between border-neutral-700 md:border-neutral-300' : 'px-3 space-x-1 justify-between border-neutral-300' %>">
|
||||||
<%= svg_icon('filter', class: 'w-5 h-5 flex-shrink-0 stroke-2') %>
|
<%= svg_icon('filter', class: 'w-5 h-5 flex-shrink-0 stroke-2') %>
|
||||||
<span class="<%= filter_params.then { |f| f[:status].in?(%w[pending completed]) ? f.except(:status) : f }.present? ? 'md:hidden' : '' %>">Filter</span>
|
<% if advanced_filters.blank? %>
|
||||||
|
<span>Filter</span>
|
||||||
|
<% end %>
|
||||||
</label>
|
</label>
|
||||||
<ul tabindex="0" class="z-10 dropdown-content p-2 mt-2 shadow menu text-base bg-base-100 rounded-box min-w-[180px] text-right">
|
<ul tabindex="0" class="z-10 dropdown-content p-2 mt-2 shadow menu text-base bg-base-100 rounded-box text-right" style="min-width: 180px;">
|
||||||
<li class="flex">
|
<li class="flex">
|
||||||
<%= link_to submissions_filter_path('completed_at', query_params.merge(path: url_for)), data: { turbo_frame: 'modal' } do %>
|
<%= link_to submissions_filter_path('completed_at', query_params.merge(path: url_for)), data: { turbo_frame: 'modal' } do %>
|
||||||
<%= svg_icon('calendar_check', class: 'w-5 h-5 flex-shrink-0 stroke-2') %>
|
<%= svg_icon('calendar_check', class: 'w-5 h-5 flex-shrink-0 stroke-2') %>
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
<div class="form-control w-full">
|
<div class="form-control w-full">
|
||||||
<div id="status" class="radio-select grid grid-cols-2 gap-2 px-1">
|
<div id="status" class="radio-select grid grid-cols-2 gap-2 px-1">
|
||||||
<% ['', 'pending', 'completed', 'partially_completed', 'sent', 'opened', 'declined', 'expired'].each do |status| %>
|
<% ['', 'pending', 'completed', 'partially_completed', 'sent', 'opened', 'declined', 'expired'].each do |status| %>
|
||||||
<label class="radio-label cursor-pointer inline-flex items-center space-x-2">
|
<label class="radio-label cursor-pointer inline-flex items-center space-x-2 min-w-0">
|
||||||
<%= radio_button_tag 'status', status, params[:status] == status || (status == '' && params[:status].blank?), class: 'base-radio' %>
|
<%= radio_button_tag 'status', status, params[:status] == status || (status == '' && params[:status].blank?), class: 'base-radio shrink-0' %>
|
||||||
<span><%= t(status.presence || 'all') %></span>
|
<span class="truncate"><%= t(status.presence || 'all') %></span>
|
||||||
</label>
|
</label>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
<%= form_for '', url: submit_form_email_2fa_path, method: :post, html: { class: 'space-y-4', id: 'code_form' } do |f| %>
|
<%= form_for '', url: submit_form_email_2fa_path, method: :post, html: { class: 'space-y-4', id: 'code_form' } do |f| %>
|
||||||
<div dir="auto" class="form-control !mt-0">
|
<div dir="auto" class="form-control !mt-0">
|
||||||
<%= f.hidden_field :submitter_slug, value: @submitter.slug %>
|
<%= f.hidden_field :submitter_slug, value: @submitter.slug %>
|
||||||
<%= f.text_field :one_time_code, required: true, class: 'base-input text-center', placeholder: 'XXX-XXX' %>
|
<%= f.text_field :one_time_code, required: true, inputmode: 'numeric', class: 'base-input text-center', placeholder: 'XXXXXX' %>
|
||||||
<div class="flex justify-between items-center mt-1">
|
<div class="flex justify-between items-center mt-1">
|
||||||
<span>
|
<span>
|
||||||
<% if flash[:alert] %>
|
<% if flash[:alert] %>
|
||||||
|
|||||||
@@ -152,7 +152,7 @@
|
|||||||
<% next if field['conditions'].present? && values[field['uuid']].blank? && field['submitter_uuid'] != @submitter.uuid %>
|
<% next if field['conditions'].present? && values[field['uuid']].blank? && field['submitter_uuid'] != @submitter.uuid %>
|
||||||
<% next if field['conditions'].present? && field['submitter_uuid'] == @submitter.uuid %>
|
<% next if field['conditions'].present? && field['submitter_uuid'] == @submitter.uuid %>
|
||||||
<% next if field.dig('preferences', 'formula').present? && field['submitter_uuid'] == @submitter.uuid %>
|
<% next if field.dig('preferences', 'formula').present? && field['submitter_uuid'] == @submitter.uuid %>
|
||||||
<%= render 'submissions/value', page_width: width, page_height: height, font_scale:, area:, field:, attachments_index: @attachments_index, value: field.dig('preferences', 'mask').present? ? TextUtils.mask_value(value, field.dig('preferences', 'mask')) : value, locale: @submitter.account.locale, timezone: @submitter.account.timezone, submitter: submitters_index[field['submitter_uuid']], with_signature_id: @form_configs[:with_signature_id], with_submitter_timezone: @form_configs[:with_submitter_timezone], with_timestamp_seconds: @form_configs[:with_timestamp_seconds], with_signature_id_reason: @form_configs[:with_signature_id_reason] %>
|
<%= render 'submissions/value', page_width: width, page_height: height, font_scale:, area:, field:, attachments_index: @attachments_index, value: field.dig('preferences', 'mask').present? ? TextUtils.mask_value(value, field.dig('preferences', 'mask')) : value, locale: @submitter.account.locale, timezone: @submitter.account.timezone, submitter: submitters_index[field['submitter_uuid']], with_signature_id: @form_configs[:with_signature_id], with_submitter_timezone: @form_configs[:with_submitter_timezone], with_timestamp_seconds: @form_configs[:with_timestamp_seconds], with_signature_id_reason: @form_configs[:with_signature_id_reason], with_signature_id_completed_at: @form_configs[:with_signature_id_completed_at] %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</page-container>
|
</page-container>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<dashboard-dropzone>
|
<dashboard-dropzone>
|
||||||
<div class="relative flex justify-between items-center w-full mb-4">
|
<div class="group/header relative flex justify-between items-center w-full mb-4">
|
||||||
<%= form_for '', url: '', id: form_id = SecureRandom.uuid, method: :post, class: 'hidden', data: { target: 'dashboard-dropzone.form' }, html: { enctype: 'multipart/form-data' } do %>
|
<%= form_for '', url: '', id: form_id = SecureRandom.uuid, method: :post, class: 'hidden', data: { target: 'dashboard-dropzone.form' }, html: { enctype: 'multipart/form-data' } do %>
|
||||||
<input name="form_id" value="<%= form_id %>">
|
<input name="form_id" value="<%= form_id %>">
|
||||||
<input name="folder_name" value="<%= @template_folder.full_name %>">
|
<input name="folder_name" value="<%= @template_folder.full_name %>">
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
<% unless @template_folder.parent_folder %>
|
<% unless @template_folder.parent_folder %>
|
||||||
<%= render 'templates/dashboard_folder_dropzone', style: 'height: 137px' %>
|
<%= render 'templates/dashboard_folder_dropzone', style: 'height: 137px' %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<h1 class="text-2xl truncate md:text-3xl font-bold flex items-center flex-grow min-w-0 space-x-2 md:flex <%= 'hidden' if params[:q].present? %>">
|
<h1 class="text-2xl truncate md:text-3xl font-bold flex items-center flex-grow min-w-0 space-x-2 md:flex max-md:group-has-[search-input:focus-within]/header:hidden <%= 'hidden' if params[:q].present? %>">
|
||||||
<%= svg_icon('folder', class: 'w-9 h-9 flex-shrink-0') %>
|
<%= svg_icon('folder', class: 'w-9 h-9 flex-shrink-0') %>
|
||||||
<span class="peer truncate">
|
<span class="peer truncate">
|
||||||
<%= @template_folder.name %>
|
<%= @template_folder.name %>
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</h1>
|
</h1>
|
||||||
<div class="flex space-x-2">
|
<div class="flex space-x-2 max-md:has-[search-input:focus-within]:grow <%= 'max-md:grow' if params[:q].present? %>">
|
||||||
<% if params[:q].present? || @pagy.count.nil? || @pagy.count > 0 || @template_folders.present? %>
|
<% if params[:q].present? || @pagy.count.nil? || @pagy.count > 0 || @template_folders.present? %>
|
||||||
<%= render 'shared/search_input' %>
|
<%= render 'shared/search_input' %>
|
||||||
<% end %>
|
<% end %>
|
||||||
@@ -37,10 +37,12 @@
|
|||||||
<span class="hidden sm:block">
|
<span class="hidden sm:block">
|
||||||
<%= render 'templates/upload_button', folder_name: @template_folder.full_name %>
|
<%= render 'templates/upload_button', folder_name: @template_folder.full_name %>
|
||||||
</span>
|
</span>
|
||||||
<%= link_to new_template_path(folder_name: @template_folder.full_name), class: 'white-button !border gap-2', data: { turbo_frame: :modal } do %>
|
<open-modal-mobile data-disabled="<%= !Docuseal.multitenant? %>" class="flex">
|
||||||
<%= svg_icon('plus', class: 'w-6 h-6 stroke-2') %>
|
<%= link_to new_template_path(folder_name: @template_folder.full_name), class: 'white-button !border gap-2', data: { turbo_frame: :modal } do %>
|
||||||
<span class="hidden md:block"><%= t('create') %></span>
|
<%= svg_icon('plus', class: 'w-6 h-6 stroke-2') %>
|
||||||
<% end %>
|
<span class="hidden md:block"><%= t('create') %></span>
|
||||||
|
<% end %>
|
||||||
|
</open-modal-mobile>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<% status_badges = { 'awaiting' => 'badge-info', 'sent' => 'badge-info', 'completed' => 'badge-success', 'opened' => 'badge-warning', 'declined' => 'badge-error' } %>
|
<% status_badges = { 'awaiting' => 'badge-info', 'sent' => 'badge-info', 'completed' => 'badge-success', 'opened' => 'badge-warning', 'declined' => 'badge-error' } %>
|
||||||
<div class="bg-base-200 rounded-2xl flex flex-col sm:flex-row items-strech">
|
<div class="bg-base-200 rounded-2xl flex flex-col sm:flex-row">
|
||||||
<% if local_assigns[:with_template] %>
|
<% if local_assigns[:with_template] %>
|
||||||
<% template = submission.template %>
|
<% template = submission.template %>
|
||||||
<a href="<%= template ? template_path(template) : submission_path(submission) %>" class="px-5 sm:pr-3 py-3 group sm:rounded-l-2xl sm:rounded-tr-none rounded-t-2xl flex sm:flex-col justify-between sm:w-52 w-full flex-shrink-0 bg-base-300/60 space-x-2 sm:space-x-0">
|
<a href="<%= template ? template_path(template) : submission_path(submission) %>" class="px-5 sm:pr-3 py-3 group sm:rounded-l-2xl sm:rounded-tr-none rounded-t-2xl flex sm:flex-col justify-between sm:w-52 w-full flex-shrink-0 bg-base-300/60 space-x-2 sm:space-x-0">
|
||||||
@@ -52,10 +52,12 @@
|
|||||||
<%= submitter.name || submitter.email || submitter.phone %>
|
<%= submitter.name || submitter.email || submitter.phone %>
|
||||||
</a>
|
</a>
|
||||||
<% if !submitter.completed_at? && !submission.completed_at? && can?(:update, submission) && !submitter.start_form_submission_events.any? && !submission.archived_at? && !submission.expired? && !submitter.declined_at? %>
|
<% if !submitter.completed_at? && !submission.completed_at? && can?(:update, submission) && !submitter.start_form_submission_events.any? && !submission.archived_at? && !submission.expired? && !submitter.declined_at? %>
|
||||||
<span class="pl-0.5 tooltip tooltip-top md:opacity-0 md:hover:opacity-100 md:peer-hover:opacity-100" data-tip="<%= t('edit') %>">
|
<span class="pl-0.5 tooltip tooltip-top tooltip-no-touch md:opacity-0 md:hover:opacity-100 md:peer-hover:opacity-100" data-tip="<%= t('edit') %>">
|
||||||
<%= link_to edit_submitter_path(submitter), class: 'shrink-0', data: { turbo_frame: 'modal' } do %>
|
<open-modal-mobile>
|
||||||
<%= svg_icon('pencil', class: 'w-5 h-5') %>
|
<%= link_to edit_submitter_path(submitter), class: 'shrink-0', data: { turbo_frame: 'modal' } do %>
|
||||||
<% end %>
|
<%= svg_icon('pencil', class: 'w-5 h-5') %>
|
||||||
|
<% end %>
|
||||||
|
</open-modal-mobile>
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</span>
|
</span>
|
||||||
@@ -102,12 +104,12 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<% if !submission.archived_at? && !template&.archived_at? && can?(:destroy, submission) %>
|
<% if !submission.archived_at? && !template&.archived_at? && can?(:destroy, submission) %>
|
||||||
<span data-tip="<%= t('archive') %>" class="sm:tooltip tooltip-top">
|
<span data-tip="<%= t('archive') %>" class="tooltip tooltip-top tooltip-no-touch">
|
||||||
<%= button_to button_title(title: nil, disabled_with: nil, icon: svg_icon('archive', class: 'w-6 h-6'), icon_disabled: svg_icon('loader', class: 'w-6 h-6 animate-spin')), submission_path(submission), class: 'btn btn-outline btn-sm w-full md:w-fit', form: { class: 'flex' }, title: t('archive'), method: :delete %>
|
<%= button_to button_title(title: nil, disabled_with: nil, icon: svg_icon('archive', class: 'w-6 h-6'), icon_disabled: svg_icon('loader', class: 'w-6 h-6 animate-spin')), submission_path(submission), class: 'btn btn-outline btn-sm w-full md:w-fit', form: { class: 'flex' }, title: t('archive'), method: :delete %>
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if local_assigns[:archived] && can?(:destroy, submission) %>
|
<% if local_assigns[:archived] && can?(:destroy, submission) %>
|
||||||
<span data-tip="<%= t('remove') %>" class="sm:tooltip tooltip-top">
|
<span data-tip="<%= t('remove') %>" class="tooltip tooltip-top tooltip-no-touch">
|
||||||
<%= button_to button_title(title: nil, disabled_with: nil, icon: svg_icon('trash', class: 'w-6 h-6'), icon_disabled: svg_icon('loader', class: 'w-6 h-6 animate-spin')), submission_path(submission, permanently: true), class: 'btn btn-outline btn-sm w-full md:w-fit', form: { class: 'flex' }, title: t('remove'), method: :delete, data: { turbo_confirm: t('submission_deletion_is_irreversible_and_will_permanently_remove_all_associated_signed_documents_with_it_are_you_sure_') } %>
|
<%= button_to button_title(title: nil, disabled_with: nil, icon: svg_icon('trash', class: 'w-6 h-6'), icon_disabled: svg_icon('loader', class: 'w-6 h-6 animate-spin')), submission_path(submission, permanently: true), class: 'btn btn-outline btn-sm w-full md:w-fit', form: { class: 'flex' }, title: t('remove'), method: :delete, data: { turbo_confirm: t('submission_deletion_is_irreversible_and_will_permanently_remove_all_associated_signed_documents_with_it_are_you_sure_') } %>
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
@@ -117,7 +119,7 @@
|
|||||||
<div class="flex flex-col md:flex-row md:items-center gap-3">
|
<div class="flex flex-col md:flex-row md:items-center gap-3">
|
||||||
<% if is_submission_completed %>
|
<% if is_submission_completed %>
|
||||||
<% latest_submitter = submitters.select(&:completed_at?).max_by(&:completed_at) %>
|
<% latest_submitter = submitters.select(&:completed_at?).max_by(&:completed_at) %>
|
||||||
<a href="<%= submission_path(submission) %>" class="z-[1] tooltip flex" data-tip="<%= l(latest_submitter.status_event_at.in_time_zone(current_account.timezone), format: :short, locale: current_account.locale) %>">
|
<a href="<%= submission_path(submission) %>" class="z-[1] tooltip tooltip-no-touch flex" data-tip="<%= l(latest_submitter.status_event_at.in_time_zone(current_account.timezone), format: :short, locale: current_account.locale) %>">
|
||||||
<span class="badge <%= status_badges[latest_submitter.status] %> md:w-32 bg-opacity-50 badge-lg uppercase text-sm font-semibold">
|
<span class="badge <%= status_badges[latest_submitter.status] %> md:w-32 bg-opacity-50 badge-lg uppercase text-sm font-semibold">
|
||||||
<%= t(latest_submitter.status) %>
|
<%= t(latest_submitter.status) %>
|
||||||
</span>
|
</span>
|
||||||
@@ -134,7 +136,7 @@
|
|||||||
<div class="relative flex justify-between items-start md:items-center space-x-3">
|
<div class="relative flex justify-between items-start md:items-center space-x-3">
|
||||||
<span class="flex flex-col md:flex-row md:items-center gap-2">
|
<span class="flex flex-col md:flex-row md:items-center gap-2">
|
||||||
<% if !is_submission_completed && !submission.expired? %>
|
<% if !is_submission_completed && !submission.expired? %>
|
||||||
<a href="<%= submission_path(submission) %>" class="z-[1] tooltip flex" data-tip="<%= l(submitter.status_event_at.in_time_zone(current_account.timezone), format: :short, locale: current_account.locale) %>">
|
<a href="<%= submission_path(submission) %>" class="z-[1] tooltip tooltip-no-touch flex" data-tip="<%= l(submitter.status_event_at.in_time_zone(current_account.timezone), format: :short, locale: current_account.locale) %>">
|
||||||
<span class="badge md:w-24 <%= status_badges[submitter.status] %> bg-opacity-50 uppercase text-xs font-semibold">
|
<span class="badge md:w-24 <%= status_badges[submitter.status] %> bg-opacity-50 uppercase text-xs font-semibold">
|
||||||
<%= t(submitter.status) %>
|
<%= t(submitter.status) %>
|
||||||
</span>
|
</span>
|
||||||
@@ -145,10 +147,12 @@
|
|||||||
<%= submitter.name || submitter.email || submitter.phone %>
|
<%= submitter.name || submitter.email || submitter.phone %>
|
||||||
</a>
|
</a>
|
||||||
<% if !submitter.completed_at? && !submission.completed_at? && can?(:update, submission) && !submitter.start_form_submission_events.any? && !submission.archived_at? && !submission.expired? && !submitter.declined_at? %>
|
<% if !submitter.completed_at? && !submission.completed_at? && can?(:update, submission) && !submitter.start_form_submission_events.any? && !submission.archived_at? && !submission.expired? && !submitter.declined_at? %>
|
||||||
<span class="pl-0.5 tooltip tooltip-top md:opacity-0 md:hover:opacity-100 md:peer-hover:opacity-100" data-tip="<%= t('edit') %>">
|
<span class="pl-0.5 tooltip tooltip-top tooltip-no-touch md:opacity-0 md:hover:opacity-100 md:peer-hover:opacity-100" data-tip="<%= t('edit') %>">
|
||||||
<%= link_to edit_submitter_path(submitter), class: 'shrink-0', data: { turbo_frame: 'modal' } do %>
|
<open-modal-mobile>
|
||||||
<%= svg_icon('pencil', class: 'w-5 h-5') %>
|
<%= link_to edit_submitter_path(submitter), class: 'shrink-0', data: { turbo_frame: 'modal' } do %>
|
||||||
<% end %>
|
<%= svg_icon('pencil', class: 'w-5 h-5') %>
|
||||||
|
<% end %>
|
||||||
|
</open-modal-mobile>
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</span>
|
</span>
|
||||||
@@ -209,12 +213,12 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<% if !submission.archived_at? && !template&.archived_at? %>
|
<% if !submission.archived_at? && !template&.archived_at? %>
|
||||||
<span data-tip="<%= t('archive') %>" class="sm:tooltip tooltip-top">
|
<span data-tip="<%= t('archive') %>" class="tooltip tooltip-top tooltip-no-touch">
|
||||||
<%= button_to button_title(title: nil, disabled_with: nil, icon: svg_icon('archive', class: 'w-6 h-6'), icon_disabled: svg_icon('loader', class: 'w-6 h-6 animate-spin')), submission_path(submission), class: 'btn btn-outline btn-sm w-full md:w-fit', form: { class: 'flex' }, title: t('archive'), method: :delete %>
|
<%= button_to button_title(title: nil, disabled_with: nil, icon: svg_icon('archive', class: 'w-6 h-6'), icon_disabled: svg_icon('loader', class: 'w-6 h-6 animate-spin')), submission_path(submission), class: 'btn btn-outline btn-sm w-full md:w-fit', form: { class: 'flex' }, title: t('archive'), method: :delete %>
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if local_assigns[:archived] && can?(:destroy, submission) %>
|
<% if local_assigns[:archived] && can?(:destroy, submission) %>
|
||||||
<span data-tip="<%= t('remove') %>" class="sm:tooltip tooltip-top">
|
<span data-tip="<%= t('remove') %>" class="tooltip tooltip-top tooltip-no-touch">
|
||||||
<%= button_to button_title(title: nil, disabled_with: nil, icon: svg_icon('trash', class: 'w-6 h-6'), icon_disabled: svg_icon('loader', class: 'w-6 h-6 animate-spin')), submission_path(submission, permanently: true), class: 'btn btn-outline btn-sm w-full md:w-fit', form: { class: 'flex' }, title: t('remove'), method: :delete, data: { turbo_confirm: t('submission_deletion_is_irreversible_and_will_permanently_remove_all_associated_signed_documents_with_it_are_you_sure_') } %>
|
<%= button_to button_title(title: nil, disabled_with: nil, icon: svg_icon('trash', class: 'w-6 h-6'), icon_disabled: svg_icon('loader', class: 'w-6 h-6 animate-spin')), submission_path(submission, permanently: true), class: 'btn btn-outline btn-sm w-full md:w-fit', form: { class: 'flex' }, title: t('remove'), method: :delete, data: { turbo_confirm: t('submission_deletion_is_irreversible_and_will_permanently_remove_all_associated_signed_documents_with_it_are_you_sure_') } %>
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -15,12 +15,12 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
</p>
|
</p>
|
||||||
<p class="flex text-xs text-base-content/60">
|
<p class="flex text-xs text-base-content/60">
|
||||||
<span class="flex items-center space-x-1 w-1/2">
|
<span class="flex items-center space-x-1 w-1/2 md:w-2/3 lg:w-1/2 whitespace-nowrap">
|
||||||
<%= svg_icon('calendar', class: 'w-4 h-4') %>
|
<%= svg_icon('calendar', class: 'w-4 h-4 flex-shrink-0') %>
|
||||||
<span><%= l(template.created_at.in_time_zone(current_account.timezone), format: :short, locale: current_account.locale) %></span>
|
<span><%= l(template.created_at.in_time_zone(current_account.timezone), format: :short, locale: current_account.locale) %></span>
|
||||||
</span>
|
</span>
|
||||||
<% if local_assigns[:with_folder] %>
|
<% if local_assigns[:with_folder] %>
|
||||||
<span class="flex items-center space-x-1 w-1/2">
|
<span class="flex items-center space-x-1 w-1/2 md:w-1/3 lg:w-1/2">
|
||||||
<%= svg_icon('folder', class: 'w-4 h-4 flex-shrink-0') %>
|
<%= svg_icon('folder', class: 'w-4 h-4 flex-shrink-0') %>
|
||||||
<span class="truncate"><%= template.folder.full_name %></span>
|
<span class="truncate"><%= template.folder.full_name %></span>
|
||||||
</span>
|
</span>
|
||||||
@@ -31,36 +31,36 @@
|
|||||||
<div class="absolute top-0 bottom-0 w-0 items-center hidden md:group-hover:flex" style="right: 37px" data-targets="dashboard-dropzone.hiddenOnDrag">
|
<div class="absolute top-0 bottom-0 w-0 items-center hidden md:group-hover:flex" style="right: 37px" data-targets="dashboard-dropzone.hiddenOnDrag">
|
||||||
<div class="space-y-1">
|
<div class="space-y-1">
|
||||||
<% if can?(:update, template) && !template.archived_at? && template.account_id == current_account.id %>
|
<% if can?(:update, template) && !template.archived_at? && template.account_id == current_account.id %>
|
||||||
<span class="tooltip tooltip-left" data-tip="<%= t('move') %>">
|
<span class="tooltip tooltip-left tooltip-no-touch" data-tip="<%= t('move') %>">
|
||||||
<a href="<%= edit_template_folder_path(template.id) %>" data-turbo-frame="modal" class="btn btn-xs hover:btn-outline bg-base-200 btn-circle">
|
<a href="<%= edit_template_folder_path(template.id) %>" data-turbo-frame="modal" class="btn btn-xs hover:btn-outline bg-base-200 btn-circle">
|
||||||
<%= svg_icon('folder_share', class: 'w-4 h-4') %>
|
<%= svg_icon('folder_share', class: 'w-4 h-4') %>
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if template.archived_at? && can?(:destroy, template) %>
|
<% if template.archived_at? && can?(:destroy, template) %>
|
||||||
<span class="tooltip tooltip-left" data-tip="<%= t('restore') %>">
|
<span class="tooltip tooltip-left tooltip-no-touch" data-tip="<%= t('restore') %>">
|
||||||
<%= button_to template_restore_index_path(template), class: 'btn btn-xs hover:btn-outline bg-base-200 btn-circle' do %>
|
<%= button_to template_restore_index_path(template), class: 'btn btn-xs hover:btn-outline bg-base-200 btn-circle' do %>
|
||||||
<%= svg_icon('rotate', class: 'w-4 h-4 enabled') %>
|
<%= svg_icon('rotate', class: 'w-4 h-4 enabled') %>
|
||||||
<%= svg_icon('loader', class: 'w-4 h-4 animate-spin disabled') %>
|
<%= svg_icon('loader', class: 'w-4 h-4 animate-spin disabled') %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</span>
|
</span>
|
||||||
<% elsif !template.archived_at? && can?(:update, template) %>
|
<% elsif !template.archived_at? && can?(:update, template) %>
|
||||||
<span class="tooltip tooltip-left" data-tip="<%= t('edit') %>">
|
<span class="tooltip tooltip-left tooltip-no-touch" data-tip="<%= t('edit') %>">
|
||||||
<a href="<%= edit_template_path(template) %>" class="btn btn-xs hover:btn-outline bg-base-200 btn-circle">
|
<a href="<%= edit_template_path(template) %>" class="btn btn-xs hover:btn-outline bg-base-200 btn-circle">
|
||||||
<%= svg_icon('pencil', class: 'w-4 h-4') %>
|
<%= svg_icon('pencil', class: 'w-4 h-4') %>
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if can?(:create, template) %>
|
<% if can?(:create, template) %>
|
||||||
<span class="tooltip tooltip-left" data-tip="<%= t('clone') %>">
|
<span class="tooltip tooltip-left tooltip-no-touch" data-tip="<%= t('clone') %>">
|
||||||
<a href="<%= new_template_clone_path(template) %>" data-turbo-frame="modal" class="btn btn-xs hover:btn-outline bg-base-200 btn-circle">
|
<a href="<%= new_template_clone_path(template) %>" data-turbo-frame="modal" class="btn btn-xs hover:btn-outline bg-base-200 btn-circle">
|
||||||
<%= svg_icon('copy', class: 'w-4 h-4') %>
|
<%= svg_icon('copy', class: 'w-4 h-4') %>
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if can?(:destroy, template) %>
|
<% if can?(:destroy, template) %>
|
||||||
<span class="tooltip tooltip-left" data-tip="<%= template.archived_at? ? t('delete') : t('archive') %>">
|
<span class="tooltip tooltip-left tooltip-no-touch" data-tip="<%= template.archived_at? ? t('delete') : t('archive') %>">
|
||||||
<%= button_to template_path(template), data: template.archived_at? ? { turbo_confirm: t('template_deletion_is_irreversible_and_will_permanently_remove_all_associated_signed_documents_with_it_are_you_sure_') } : {}, params: { permanently: template.archived_at? }.compact_blank, method: :delete, class: 'btn btn-xs hover:btn-outline bg-base-200 btn-circle', aria_label: t('restore') do %>
|
<%= button_to template_path(template), data: template.archived_at? ? { turbo_confirm: t('template_deletion_is_irreversible_and_will_permanently_remove_all_associated_signed_documents_with_it_are_you_sure_') } : {}, params: { permanently: template.archived_at? }.compact_blank, method: :delete, class: 'btn btn-xs hover:btn-outline bg-base-200 btn-circle', aria: { label: template.archived_at? ? t('delete') : t('archive') } do %>
|
||||||
<%= svg_icon(template.archived_at? ? 'trash' : 'archive', class: 'w-4 h-4 enabled') %>
|
<%= svg_icon(template.archived_at? ? 'trash' : 'archive', class: 'w-4 h-4 enabled') %>
|
||||||
<%= svg_icon('loader', class: 'w-4 h-4 animate-spin disabled') %>
|
<%= svg_icon('loader', class: 'w-4 h-4 animate-spin disabled') %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
<% if can?(:update, template) %>
|
<% if can?(:update, template) %>
|
||||||
<span class="pl-1 tooltip tooltip-right md:opacity-0 hover:opacity-100 peer-hover:opacity-100" data-tip="<%= t('move') %>">
|
<span class="pl-1 tooltip tooltip-right tooltip-no-touch md:opacity-0 hover:opacity-100 peer-hover:opacity-100" data-tip="<%= t('move') %>">
|
||||||
<a href="<%= edit_template_folder_path(template.id, subfolder: @template.folder.parent_folder.present?) %>" data-turbo-frame="modal">
|
<a href="<%= edit_template_folder_path(template.id, subfolder: @template.folder.parent_folder.present?) %>" data-turbo-frame="modal">
|
||||||
<%= svg_icon('pencil_share', class: 'w-5 h-5') %>
|
<%= svg_icon('pencil_share', class: 'w-5 h-5') %>
|
||||||
</a>
|
</a>
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
<% if !template.archived_at? %>
|
<% if !template.archived_at? %>
|
||||||
<div class="flex items-center space-x-2">
|
<div class="flex items-center space-x-2">
|
||||||
<% if can?(:update, template) %>
|
<% if can?(:update, template) %>
|
||||||
<div class="tooltip" data-tip="<%= t('preferences') %>">
|
<div class="tooltip tooltip-no-touch hidden md:block" data-tip="<%= t('preferences') %>">
|
||||||
<%= link_to template_preferences_path(template), class: 'btn border border-base-200 bg-base-200 hover:bg-base-300 hover:border-base-300 btn-sm flex-1 hidden md:flex', data: { turbo_frame: :modal } do %>
|
<%= link_to template_preferences_path(template), class: 'btn border border-base-200 bg-base-200 hover:bg-base-300 hover:border-base-300 btn-sm flex-1 hidden md:flex', data: { turbo_frame: :modal } do %>
|
||||||
<span class="flex items-center justify-center space-x-2">
|
<span class="flex items-center justify-center space-x-2">
|
||||||
<%= svg_icon('adjustments_horizontal', class: 'w-6 h-6') %>
|
<%= svg_icon('adjustments_horizontal', class: 'w-6 h-6') %>
|
||||||
@@ -52,12 +52,14 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= link_to template_share_link_path(template), class: 'absolute md:relative bottom-0 right-0 btn btn-xs md:btn-sm whitespace-nowrap btn-neutral text-white mt-1 px-2', data: { turbo_frame: :modal } do %>
|
<open-modal-mobile class="absolute md:relative bottom-0 right-0 mt-1">
|
||||||
<span class="flex items-center justify-center space-x-2">
|
<%= link_to template_share_link_path(template), class: 'btn btn-xs md:btn-sm whitespace-nowrap btn-neutral text-white px-2', data: { turbo_frame: :modal } do %>
|
||||||
<%= svg_icon('link', class: 'w-4 h-4 md:w-6 md:h-6') %>
|
<span class="flex items-center justify-center space-x-2">
|
||||||
<span><%= t('link') %></span>
|
<%= svg_icon('link', class: 'w-4 h-4 md:w-6 md:h-6') %>
|
||||||
</span>
|
<span><%= t('link') %></span>
|
||||||
<% end %>
|
</span>
|
||||||
|
<% end %>
|
||||||
|
</open-modal-mobile>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
@@ -66,40 +68,48 @@
|
|||||||
<%= button_to button_title(title: t('archive'), disabled_with: t('archiving')[..-4], title_class: 'inline', icon: svg_icon('archive', class: 'w-6 h-6')), template_path(template), class: 'btn btn-outline btn-sm w-full', form_class: 'flex-1', method: :delete %>
|
<%= button_to button_title(title: t('archive'), disabled_with: t('archiving')[..-4], title_class: 'inline', icon: svg_icon('archive', class: 'w-6 h-6')), template_path(template), class: 'btn btn-outline btn-sm w-full', form_class: 'flex-1', method: :delete %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if can?(:create, current_account.templates.new(author: current_user)) %>
|
<% if can?(:create, current_account.templates.new(author: current_user)) %>
|
||||||
<%= link_to new_template_clone_path(template), class: 'btn btn-outline btn-sm flex-1', data: { turbo_frame: :modal } do %>
|
<div class="flex-1">
|
||||||
<span class="flex items-center justify-center space-x-2">
|
<%= link_to new_template_clone_path(template), class: 'btn btn-outline btn-sm w-full', data: { turbo_frame: :modal } do %>
|
||||||
<%= svg_icon('copy', class: 'w-6 h-6') %>
|
<span class="flex items-center justify-center space-x-2">
|
||||||
<span class="inline"><%= t('clone') %></span>
|
<%= svg_icon('copy', class: 'w-6 h-6') %>
|
||||||
</span>
|
<span class="inline"><%= t('clone') %></span>
|
||||||
<% end %>
|
</span>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if !template.archived_at? %>
|
<% if !template.archived_at? %>
|
||||||
<% if can?(:update, template) %>
|
<% if can?(:update, template) %>
|
||||||
<%= link_to edit_template_path(template), class: 'btn btn-outline btn-sm flex-1' do %>
|
<div class="flex-1">
|
||||||
<span class="flex items-center justify-center space-x-2">
|
<%= link_to edit_template_path(template), class: 'btn btn-outline btn-sm w-full' do %>
|
||||||
<%= svg_icon('pencil', class: 'w-6 h-6') %>
|
<span class="flex items-center justify-center space-x-2">
|
||||||
<span><%= t('edit') %></span>
|
<%= svg_icon('pencil', class: 'w-6 h-6') %>
|
||||||
</span>
|
<span><%= t('edit') %></span>
|
||||||
<% end %>
|
</span>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
<% elsif can?(:read, template) && !template.archived_at? %>
|
<% elsif can?(:read, template) && !template.archived_at? %>
|
||||||
<%= link_to template_preview_path(template), class: 'btn btn-outline btn-sm flex-1' do %>
|
<div class="flex-1">
|
||||||
|
<%= link_to template_preview_path(template), class: 'btn btn-outline btn-sm w-full' do %>
|
||||||
|
<span class="flex items-center justify-center space-x-2">
|
||||||
|
<%= svg_icon('file_text', class: 'w-6 h-6') %>
|
||||||
|
<span><%= t('preview') %></span>
|
||||||
|
</span>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
<% if template.archived_at? %>
|
||||||
|
<% if can?(:destroy, template) %>
|
||||||
|
<%= button_to button_title(title: t('restore'), disabled_with: t('restoring')[..-4], icon: svg_icon('rotate', class: 'w-6 h-6')), template_restore_index_path(template), class: 'btn btn-outline btn-sm w-full', form_class: 'flex-1' %>
|
||||||
|
<% end %>
|
||||||
|
<div class="flex-1">
|
||||||
|
<%= link_to template_preview_path(template), class: 'btn btn-outline btn-sm w-full' do %>
|
||||||
<span class="flex items-center justify-center space-x-2">
|
<span class="flex items-center justify-center space-x-2">
|
||||||
<%= svg_icon('file_text', class: 'w-6 h-6') %>
|
<%= svg_icon('file_text', class: 'w-6 h-6') %>
|
||||||
<span><%= t('preview') %></span>
|
<span><%= t('preview') %></span>
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
</div>
|
||||||
<% end %>
|
|
||||||
<% if template.archived_at? %>
|
|
||||||
<% if can?(:destroy, template) %>
|
|
||||||
<%= button_to button_title(title: t('restore'), disabled_with: t('restoring')[..-4], icon: svg_icon('rotate', class: 'w-6 h-6')), template_restore_index_path(template), class: 'btn btn-outline btn-sm flex-1' %>
|
|
||||||
<% end %>
|
|
||||||
<%= link_to template_preview_path(template), class: 'btn btn-outline btn-sm flex-1' do %>
|
|
||||||
<span class="flex items-center justify-center space-x-2">
|
|
||||||
<%= svg_icon('file_text', class: 'w-6 h-6') %>
|
|
||||||
<span><%= t('preview') %></span>
|
|
||||||
</span>
|
|
||||||
<% end %>
|
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,4 +6,7 @@
|
|||||||
<%= button_to nil, user_configs_path, method: :post, params: { user_config: { key: UserConfig::SHOW_APP_TOUR, value: true } }, class: 'hidden', id: 'start_tour_button' %>
|
<%= button_to nil, user_configs_path, method: :post, params: { user_config: { key: UserConfig::SHOW_APP_TOUR, value: true } }, class: 'hidden', id: 'start_tour_button' %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<template-builder class="grid" data-template="<%= @template_data.to_json %>" data-custom-fields="<%= (current_account.account_configs.find_or_initialize_by(key: AccountConfig::TEMPLATE_CUSTOM_FIELDS_KEY).value || []).to_json %>" data-with-sign-yourself-button="<%= !@template.archived_at? %>" data-with-fields-detection="true" data-with-send-button="<%= !@template.archived_at? && can?(:create, @template.submissions.new(account: current_account)) %>" data-with-revisions-menu="<%= @template.template_versions.exists? %>" data-locale="<%= I18n.locale %>" data-show-tour-start-form="<%= @show_tour_start_form %>"></template-builder>
|
<% account_configs = current_account.account_configs.where(key: [AccountConfig::TEMPLATE_CUSTOM_FIELDS_KEY, AccountConfig::TEMPLATE_DATE_FORMATS_KEY]).to_a %>
|
||||||
|
<% custom_fields = account_configs.find { |e| e.key == AccountConfig::TEMPLATE_CUSTOM_FIELDS_KEY }&.value || [] %>
|
||||||
|
<% date_formats = account_configs.find { |e| e.key == AccountConfig::TEMPLATE_DATE_FORMATS_KEY }&.value || [] %>
|
||||||
|
<template-builder class="grid" data-template="<%= @template_data.to_json %>" data-custom-fields="<%= custom_fields.to_json %>" data-date-formats="<%= date_formats.to_json %>" data-with-sign-yourself-button="<%= !@template.archived_at? %>" data-with-fields-detection="true" data-with-send-button="<%= !@template.archived_at? && can?(:create, @template.submissions.new(account: current_account)) %>" data-with-revisions-menu="<%= @template.template_versions.exists? %>" data-locale="<%= I18n.locale %>" data-show-tour-start-form="<%= @show_tour_start_form %>"></template-builder>
|
||||||
|
|||||||
@@ -3,25 +3,27 @@
|
|||||||
<% is_show_tabs = @pagy.pages > 1 || params[:q].present? || params[:status].present? || filter_params.present? %>
|
<% is_show_tabs = @pagy.pages > 1 || params[:q].present? || params[:status].present? || filter_params.present? %>
|
||||||
<% if @pagy.count.nil? || !@pagy.count.zero? || params[:q].present? || params[:status].present? || filter_params.present? %>
|
<% if @pagy.count.nil? || !@pagy.count.zero? || params[:q].present? || params[:status].present? || filter_params.present? %>
|
||||||
<div class="<%= is_show_tabs ? 'mb-4' : 'mb-6' %>">
|
<div class="<%= is_show_tabs ? 'mb-4' : 'mb-6' %>">
|
||||||
<div class="flex justify-between items-center md:items-end">
|
<div class="group/header flex justify-between items-center md:items-end">
|
||||||
<div>
|
<div class="max-md:group-has-[search-input:focus-within]/header:hidden">
|
||||||
<h2 class="text-3xl font-bold md:block <%= 'hidden' if params[:q].present? %>">
|
<h2 class="text-3xl font-bold md:block <%= 'hidden' if params[:q].present? %>">
|
||||||
<%= t('submissions') %>
|
<%= t('submissions') %>
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-end space-x-2">
|
<div class="flex justify-end space-x-2 max-md:has-[search-input:focus-within]:grow <%= 'max-md:grow' if params[:q].present? %>">
|
||||||
<% if params[:q].present? || params[:status].present? || filter_params.present? || @pagy.pages > 1 %>
|
<% if params[:q].present? || params[:status].present? || filter_params.present? || @pagy.pages > 1 %>
|
||||||
<%= render 'shared/search_input', title_selector: 'h2' %>
|
<%= render 'shared/search_input' %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= link_to new_template_submissions_export_path(@template, params.permit(:q, *Submissions::Filter::ALLOWED_PARAMS)), class: 'hidden md:flex btn btn-ghost text-base', data: { turbo_frame: 'modal' } do %>
|
<%= link_to new_template_submissions_export_path(@template, params.permit(:q, *Submissions::Filter::ALLOWED_PARAMS)), class: 'hidden md:flex btn btn-ghost text-base', data: { turbo_frame: 'modal' } do %>
|
||||||
<%= svg_icon('download', class: 'w-6 h-6 stroke-2') %>
|
<%= svg_icon('download', class: 'w-6 h-6 stroke-2') %>
|
||||||
<span><%= t('export') %></span>
|
<span><%= t('export') %></span>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if !@template.archived_at? && can?(:create, Submission) %>
|
<% if !@template.archived_at? && can?(:create, Submission) %>
|
||||||
<%= link_to new_template_submission_path(@template), id: 'add_recipients_button', class: 'white-button !border', data: { turbo_frame: 'modal' } do %>
|
<open-modal-mobile class="flex">
|
||||||
<%= svg_icon('plus', class: 'w-6 h-6 stroke-2') %>
|
<%= link_to new_template_submission_path(@template), id: 'add_recipients_button', class: 'white-button !border', data: { turbo_frame: 'modal' } do %>
|
||||||
<%= t('add_recipients_html') %>
|
<%= svg_icon('plus', class: 'w-6 h-6 stroke-2') %>
|
||||||
<% end %>
|
<%= t('add_recipients_html') %>
|
||||||
|
<% end %>
|
||||||
|
</open-modal-mobile>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -29,7 +31,7 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
<% if is_show_tabs %>
|
<% if is_show_tabs %>
|
||||||
<div class="flex items-center flex-col md:flex-row md:flex-wrap gap-2 mb-4">
|
<div class="flex items-center flex-col md:flex-row md:flex-wrap gap-2 mb-4">
|
||||||
<div class="flex items-center md:items-end flex-col md:flex-row gap-2 w-full md:w-fit">
|
<div class="hidden md:flex md:items-end gap-2 md:w-fit">
|
||||||
<a href="<%= url_for(params: request.query_parameters.except('status')) %>" class="<%= params[:status].blank? ? 'border-neutral-700' : 'border-neutral-300' %> flex h-10 px-2 py-1 text-lg items-center justify-between border text-center text-neutral font-semibold rounded-xl w-full md:w-48 hover:border-neutral-700">
|
<a href="<%= url_for(params: request.query_parameters.except('status')) %>" class="<%= params[:status].blank? ? 'border-neutral-700' : 'border-neutral-300' %> flex h-10 px-2 py-1 text-lg items-center justify-between border text-center text-neutral font-semibold rounded-xl w-full md:w-48 hover:border-neutral-700">
|
||||||
<div class="flex items-center space-x-1">
|
<div class="flex items-center space-x-1">
|
||||||
<%= svg_icon('list', class: 'w-5 h-5') %>
|
<%= svg_icon('list', class: 'w-5 h-5') %>
|
||||||
@@ -37,7 +39,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<% unless can?(:manage, :countless) %>
|
<% unless can?(:manage, :countless) %>
|
||||||
<div class="badge badge-neutral badge-outline font-medium">
|
<div class="badge badge-neutral badge-outline font-medium">
|
||||||
<%= params[:status].blank? && filter_params.blank? ? @pagy.count : @base_submissions.count %>
|
<%= all_count = params[:status].blank? && filter_params.blank? ? @pagy.count : @base_submissions.count %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</a>
|
</a>
|
||||||
@@ -48,7 +50,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<% unless can?(:manage, :countless) %>
|
<% unless can?(:manage, :countless) %>
|
||||||
<div class="badge badge-neutral badge-outline font-medium">
|
<div class="badge badge-neutral badge-outline font-medium">
|
||||||
<%= params[:status] == 'pending' && filter_params.blank? ? @pagy.count : @base_submissions.pending.count %>
|
<%= pending_count = params[:status] == 'pending' && filter_params.blank? ? @pagy.count : @base_submissions.pending.count %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</a>
|
</a>
|
||||||
@@ -59,13 +61,13 @@
|
|||||||
</div>
|
</div>
|
||||||
<% unless can?(:manage, :countless) %>
|
<% unless can?(:manage, :countless) %>
|
||||||
<div class="badge badge-neutral badge-outline font-medium">
|
<div class="badge badge-neutral badge-outline font-medium">
|
||||||
<%= params[:status] == 'completed' && filter_params.blank? ? @pagy.count : @base_submissions.completed.count %>
|
<%= completed_count = params[:status] == 'completed' && filter_params.blank? ? @pagy.count : @base_submissions.completed.count %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-end flex-col md:flex-row gap-2 w-full md:w-fit">
|
<div class="flex items-center md:items-end gap-2 w-full md:w-fit">
|
||||||
<%= render 'submissions_filters/applied_filters', filter_params: %>
|
<%= render 'submissions_filters/applied_filters', filter_params:, with_status: true, all_count:, pending_count:, completed_count: %>
|
||||||
<%= render 'submissions_filters/filter_button', filter_params: %>
|
<%= render 'submissions_filters/filter_button', filter_params: %>
|
||||||
<% unless @template.archived_at? %>
|
<% unless @template.archived_at? %>
|
||||||
<%= render 'submissions_filters/actions', filter_params: filter_params.merge(template_id: @template.id) %>
|
<%= render 'submissions_filters/actions', filter_params: filter_params.merge(template_id: @template.id) %>
|
||||||
@@ -89,10 +91,12 @@
|
|||||||
<p><%= t('send_an_invitation_to_fill_and_complete_the_form') %></p>
|
<p><%= t('send_an_invitation_to_fill_and_complete_the_form') %></p>
|
||||||
<div class="space-y-2 flex flex-col">
|
<div class="space-y-2 flex flex-col">
|
||||||
<% if can?(:create, Submission) %>
|
<% if can?(:create, Submission) %>
|
||||||
<%= link_to new_template_submission_path(@template, with_link: true), id: 'send_to_recipients_button', class: 'base-button mt-6', data: { turbo_frame: 'modal' } do %>
|
<open-modal-mobile class="flex flex-col mt-6">
|
||||||
<%= svg_icon('plus', class: 'w-6 h-6 stroke-2') %>
|
<%= link_to new_template_submission_path(@template, with_link: true), id: 'send_to_recipients_button', class: 'base-button', data: { turbo_frame: 'modal' } do %>
|
||||||
<span class="mr-1"><%= t('send_to_recipients') %></span>
|
<%= svg_icon('plus', class: 'w-6 h-6 stroke-2') %>
|
||||||
<% end %>
|
<span class="mr-1"><%= t('send_to_recipients') %></span>
|
||||||
|
<% end %>
|
||||||
|
</open-modal-mobile>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if Templates.filter_undefined_submitters(@template.submitters).size == 1 && @template.variables_schema.blank? %>
|
<% if Templates.filter_undefined_submitters(@template.submitters).size == 1 && @template.variables_schema.blank? %>
|
||||||
<%= button_to start_form_path(@template.slug), params: { selfsign: true }, method: :put, class: 'white-button w-full', form: { style: 'display: inline', target: '_blank', data: { turbo: false } } do %>
|
<%= button_to start_form_path(@template.slug), params: { selfsign: true }, method: :put, class: 'white-button w-full', form: { style: 'display: inline', target: '_blank', data: { turbo: false } } do %>
|
||||||
@@ -100,10 +104,12 @@
|
|||||||
<span class="mr-1"><%= t('sign_it_yourself') %></span>
|
<span class="mr-1"><%= t('sign_it_yourself') %></span>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% else %>
|
<% else %>
|
||||||
<%= link_to new_template_submission_path(@template, selfsign: true), id: 'sign_yourself_button', class: 'white-button mt-6', data: { turbo_frame: 'modal' } do %>
|
<open-modal-mobile class="flex flex-col mt-6">
|
||||||
<%= svg_icon('writing', class: 'w-6 h-6') %>
|
<%= link_to new_template_submission_path(@template, selfsign: true), id: 'sign_yourself_button', class: 'white-button', data: { turbo_frame: 'modal' } do %>
|
||||||
<span class="mr-1"><%= t('sign_it_yourself') %></span>
|
<%= svg_icon('writing', class: 'w-6 h-6') %>
|
||||||
<% end %>
|
<span class="mr-1"><%= t('sign_it_yourself') %></span>
|
||||||
|
<% end %>
|
||||||
|
</open-modal-mobile>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<div>
|
<div>
|
||||||
<%= link_to root_path, class: 'flex items-center' do %>
|
<%= link_to root_path, class: 'flex items-center mb-1 md:mb-0' do %>
|
||||||
<%= svg_icon('chevron_left', class: 'w-5 h-5') %>
|
<%= svg_icon('chevron_left', class: 'w-5 h-5') %>
|
||||||
<span style="margin-left: 3px"><%= t('back_to_active') %></span>
|
<span style="margin-left: 3px"><%= t('back_to_active') %></span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between mb-4 items-center">
|
<div class="group/header flex justify-between mb-4 items-center">
|
||||||
<div>
|
<div class="max-md:group-has-[search-input:focus-within]/header:hidden">
|
||||||
<h1 class="text-2xl md:text-3xl font-bold md:block <%= 'hidden' if params[:q].present? %>"><%= t('document_templates_html') %> <span class="badge badge-outline badge-lg align-middle"><%= t('archived') %></span></h1>
|
<h1 class="text-2xl md:text-3xl font-bold md:block <%= 'hidden' if params[:q].present? %>"><%= t('document_templates_html') %> <span class="badge badge-outline badge-lg align-middle"><%= t('archived') %></span></h1>
|
||||||
</div>
|
</div>
|
||||||
<% if params[:q].present? || @pagy.pages > 1 %>
|
<% if params[:q].present? || @pagy.pages > 1 %>
|
||||||
|
|||||||
@@ -1,33 +1,33 @@
|
|||||||
<% filter_params = params.permit(Submissions::Filter::ALLOWED_PARAMS).compact_blank %>
|
<% filter_params = params.permit(Submissions::Filter::ALLOWED_PARAMS).compact_blank %>
|
||||||
<% with_filters = @pagy.pages > 1 || params[:q].present? || filter_params.present? %>
|
<% with_filters = @pagy.pages > 1 || params[:q].present? || filter_params.present? %>
|
||||||
<%= render 'templates/title', template: @template %>
|
<%= render 'templates/title', template: @template %>
|
||||||
<div class="flex flex-col md:flex-row md:items-center mb-6 gap-3">
|
<div>
|
||||||
<div class="flex w-full justify-between md:items-end items-end">
|
<%= link_to template_path(@template), class: 'flex items-center mb-1 md:mb-0' do %>
|
||||||
<div>
|
<%= svg_icon('chevron_left', class: 'w-5 h-5') %>
|
||||||
<div>
|
<span style="margin-left: 3px"><%= t('back_to_active') %></span>
|
||||||
<%= link_to template_path(@template), class: 'flex items-center' do %>
|
<% end %>
|
||||||
<%= svg_icon('chevron_left', class: 'w-5 h-5') %>
|
</div>
|
||||||
<span style="margin-left: 3px"><%= t('back_to_active') %></span>
|
<div class="flex flex-col md:flex-row md:items-center mb-4 gap-3">
|
||||||
<% end %>
|
<div class="group/header flex w-full md:flex-1 min-w-0 justify-between items-center">
|
||||||
</div>
|
<div class="max-md:group-has-[search-input:focus-within]/header:hidden">
|
||||||
<h1 class="text-3xl font-bold md:block"><%= t('submissions') %> <span class="badge badge-outline badge-lg align-middle"><%= t('archived') %></span></h1>
|
<h2 class="text-2xl md:text-3xl font-bold md:block"><%= t('submissions') %> <span class="badge badge-outline badge-lg align-middle"><%= t('archived') %></span></h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex space-x-2 justify-end">
|
<div class="max-md:has-[search-input:focus-within]:grow <%= 'max-md:grow' if params[:q].present? %>">
|
||||||
<% if with_filters %>
|
<% if with_filters %>
|
||||||
<%= render 'shared/search_input' %>
|
<%= render 'shared/search_input' %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= link_to new_template_submissions_export_path(@template, archived: true), class: 'btn btn-ghost text-base', data: { turbo_frame: 'modal' } do %>
|
|
||||||
<%= svg_icon('download', class: 'w-6 h-6 stroke-2') %>
|
|
||||||
<span><%= t('export') %></span>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% if with_filters %>
|
<div class="flex flex-nowrap md:flex-wrap md:justify-end items-center gap-2">
|
||||||
<div class="flex flex-col items-end md:flex-row gap-2">
|
<%= render 'submissions_filters/applied_filters', filter_params:, with_status: true %>
|
||||||
<%= render 'submissions_filters/applied_filters', filter_params: %>
|
<div class="hidden md:block shrink-0 md:order-first">
|
||||||
<%= render 'submissions_filters/filter_button', filter_params: %>
|
<%= link_to new_template_submissions_export_path(@template, archived: true), class: 'btn btn-ghost text-base h-10 min-h-10', data: { turbo_frame: 'modal' } do %>
|
||||||
|
<%= svg_icon('download', class: 'w-6 h-6 stroke-2') %>
|
||||||
|
<span class="hidden md:inline"><%= t('export') %></span>
|
||||||
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<%= render 'submissions_filters/filter_button', filter_params: %>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% if @pagy.count.nil? || @pagy.count > 0 %>
|
<% if @pagy.count.nil? || @pagy.count > 0 %>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user