mirror of
https://github.com/docusealco/docuseal
synced 2026-11-04 22:42:32 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
14bd039a88 | ||
|
|
8c8c34cb75 | ||
|
|
0cc53066eb | ||
|
|
3d13d28e05 | ||
|
|
a80e20234f | ||
|
|
b1b46cf998 | ||
|
|
52ef0ae929 | ||
|
|
c4daf8ed49 | ||
|
|
c098e61947 | ||
|
|
ceb052b158 | ||
|
|
61b79d5b24 | ||
|
|
5a9cffc6fc | ||
|
|
08447ec4cf | ||
|
|
d3a5f36555 | ||
|
|
c9f39e7b1a | ||
|
|
1060aa84ba | ||
|
|
b6f68ddde3 | ||
|
|
12dd96e34c | ||
|
|
52ba415429 | ||
|
|
601c984f3f | ||
|
|
9d90304d9a | ||
|
|
788493c0a6 | ||
|
|
e1342860c4 | ||
|
|
88968897e9 | ||
|
|
966b786a30 | ||
|
|
bc3b1b477e | ||
|
|
cbc6f4497a | ||
|
|
6b4e7c2264 | ||
|
|
8e6639d891 | ||
|
|
8e39f0078d | ||
|
|
46f350217a |
No files matched your search
+5
-2
@@ -53,10 +53,13 @@ RSpec/NestedGroups:
|
|||||||
Max: 6
|
Max: 6
|
||||||
|
|
||||||
RSpec/MultipleExpectations:
|
RSpec/MultipleExpectations:
|
||||||
Max: 7
|
Max: 20
|
||||||
|
|
||||||
RSpec/ExampleLength:
|
RSpec/ExampleLength:
|
||||||
Max: 15
|
Max: 40
|
||||||
|
|
||||||
|
RSpec/MultipleMemoizedHelpers:
|
||||||
|
Max: 6
|
||||||
|
|
||||||
Rails/I18nLocaleTexts:
|
Rails/I18nLocaleTexts:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|||||||
+1
-1
@@ -31,7 +31,7 @@ ENV BUNDLE_WITHOUT="development:test"
|
|||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
RUN apk add --no-cache build-base sqlite-dev libpq-dev mariadb-dev vips-dev vips-poppler vips-heif libc6-compat ttf-freefont
|
RUN apk add --no-cache build-base sqlite-dev libpq-dev mariadb-dev vips-dev vips-poppler vips-heif libc6-compat ttf-freefont ttf-liberation && cp /usr/share/fonts/liberation/LiberationSans-Regular.ttf /LiberationSans-Regular.ttf && apk del ttf-liberation
|
||||||
|
|
||||||
COPY ./Gemfile ./Gemfile.lock ./
|
COPY ./Gemfile ./Gemfile.lock ./
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ gem 'oj'
|
|||||||
gem 'omniauth-google-oauth2'
|
gem 'omniauth-google-oauth2'
|
||||||
gem 'omniauth-rails_csrf_protection'
|
gem 'omniauth-rails_csrf_protection'
|
||||||
gem 'pagy'
|
gem 'pagy'
|
||||||
|
gem 'pdf-reader'
|
||||||
gem 'pg', require: false
|
gem 'pg', require: false
|
||||||
gem 'premailer-rails'
|
gem 'premailer-rails'
|
||||||
gem 'puma'
|
gem 'puma'
|
||||||
@@ -28,6 +29,7 @@ gem 'rails_autolink'
|
|||||||
gem 'rails-i18n'
|
gem 'rails-i18n'
|
||||||
gem 'rollbar', require: ENV.key?('ROLLBAR_ACCESS_TOKEN')
|
gem 'rollbar', require: ENV.key?('ROLLBAR_ACCESS_TOKEN')
|
||||||
gem 'ruby-vips'
|
gem 'ruby-vips'
|
||||||
|
gem 'rubyXL'
|
||||||
gem 'shakapacker'
|
gem 'shakapacker'
|
||||||
gem 'sidekiq', require: ENV.key?('REDIS_URL')
|
gem 'sidekiq', require: ENV.key?('REDIS_URL')
|
||||||
gem 'sqlite3', require: false
|
gem 'sqlite3', require: false
|
||||||
|
|||||||
+127
-102
@@ -1,92 +1,94 @@
|
|||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
actioncable (7.0.5)
|
Ascii85 (1.1.0)
|
||||||
actionpack (= 7.0.5)
|
actioncable (7.0.7)
|
||||||
activesupport (= 7.0.5)
|
actionpack (= 7.0.7)
|
||||||
|
activesupport (= 7.0.7)
|
||||||
nio4r (~> 2.0)
|
nio4r (~> 2.0)
|
||||||
websocket-driver (>= 0.6.1)
|
websocket-driver (>= 0.6.1)
|
||||||
actionmailbox (7.0.5)
|
actionmailbox (7.0.7)
|
||||||
actionpack (= 7.0.5)
|
actionpack (= 7.0.7)
|
||||||
activejob (= 7.0.5)
|
activejob (= 7.0.7)
|
||||||
activerecord (= 7.0.5)
|
activerecord (= 7.0.7)
|
||||||
activestorage (= 7.0.5)
|
activestorage (= 7.0.7)
|
||||||
activesupport (= 7.0.5)
|
activesupport (= 7.0.7)
|
||||||
mail (>= 2.7.1)
|
mail (>= 2.7.1)
|
||||||
net-imap
|
net-imap
|
||||||
net-pop
|
net-pop
|
||||||
net-smtp
|
net-smtp
|
||||||
actionmailer (7.0.5)
|
actionmailer (7.0.7)
|
||||||
actionpack (= 7.0.5)
|
actionpack (= 7.0.7)
|
||||||
actionview (= 7.0.5)
|
actionview (= 7.0.7)
|
||||||
activejob (= 7.0.5)
|
activejob (= 7.0.7)
|
||||||
activesupport (= 7.0.5)
|
activesupport (= 7.0.7)
|
||||||
mail (~> 2.5, >= 2.5.4)
|
mail (~> 2.5, >= 2.5.4)
|
||||||
net-imap
|
net-imap
|
||||||
net-pop
|
net-pop
|
||||||
net-smtp
|
net-smtp
|
||||||
rails-dom-testing (~> 2.0)
|
rails-dom-testing (~> 2.0)
|
||||||
actionpack (7.0.5)
|
actionpack (7.0.7)
|
||||||
actionview (= 7.0.5)
|
actionview (= 7.0.7)
|
||||||
activesupport (= 7.0.5)
|
activesupport (= 7.0.7)
|
||||||
rack (~> 2.0, >= 2.2.4)
|
rack (~> 2.0, >= 2.2.4)
|
||||||
rack-test (>= 0.6.3)
|
rack-test (>= 0.6.3)
|
||||||
rails-dom-testing (~> 2.0)
|
rails-dom-testing (~> 2.0)
|
||||||
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
||||||
actiontext (7.0.5)
|
actiontext (7.0.7)
|
||||||
actionpack (= 7.0.5)
|
actionpack (= 7.0.7)
|
||||||
activerecord (= 7.0.5)
|
activerecord (= 7.0.7)
|
||||||
activestorage (= 7.0.5)
|
activestorage (= 7.0.7)
|
||||||
activesupport (= 7.0.5)
|
activesupport (= 7.0.7)
|
||||||
globalid (>= 0.6.0)
|
globalid (>= 0.6.0)
|
||||||
nokogiri (>= 1.8.5)
|
nokogiri (>= 1.8.5)
|
||||||
actionview (7.0.5)
|
actionview (7.0.7)
|
||||||
activesupport (= 7.0.5)
|
activesupport (= 7.0.7)
|
||||||
builder (~> 3.1)
|
builder (~> 3.1)
|
||||||
erubi (~> 1.4)
|
erubi (~> 1.4)
|
||||||
rails-dom-testing (~> 2.0)
|
rails-dom-testing (~> 2.0)
|
||||||
rails-html-sanitizer (~> 1.1, >= 1.2.0)
|
rails-html-sanitizer (~> 1.1, >= 1.2.0)
|
||||||
activejob (7.0.5)
|
activejob (7.0.7)
|
||||||
activesupport (= 7.0.5)
|
activesupport (= 7.0.7)
|
||||||
globalid (>= 0.3.6)
|
globalid (>= 0.3.6)
|
||||||
activemodel (7.0.5)
|
activemodel (7.0.7)
|
||||||
activesupport (= 7.0.5)
|
activesupport (= 7.0.7)
|
||||||
activerecord (7.0.5)
|
activerecord (7.0.7)
|
||||||
activemodel (= 7.0.5)
|
activemodel (= 7.0.7)
|
||||||
activesupport (= 7.0.5)
|
activesupport (= 7.0.7)
|
||||||
activestorage (7.0.5)
|
activestorage (7.0.7)
|
||||||
actionpack (= 7.0.5)
|
actionpack (= 7.0.7)
|
||||||
activejob (= 7.0.5)
|
activejob (= 7.0.7)
|
||||||
activerecord (= 7.0.5)
|
activerecord (= 7.0.7)
|
||||||
activesupport (= 7.0.5)
|
activesupport (= 7.0.7)
|
||||||
marcel (~> 1.0)
|
marcel (~> 1.0)
|
||||||
mini_mime (>= 1.1.0)
|
mini_mime (>= 1.1.0)
|
||||||
activesupport (7.0.5)
|
activesupport (7.0.7)
|
||||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||||
i18n (>= 1.6, < 2)
|
i18n (>= 1.6, < 2)
|
||||||
minitest (>= 5.1)
|
minitest (>= 5.1)
|
||||||
tzinfo (~> 2.0)
|
tzinfo (~> 2.0)
|
||||||
addressable (2.8.4)
|
addressable (2.8.5)
|
||||||
public_suffix (>= 2.0.2, < 6.0)
|
public_suffix (>= 2.0.2, < 6.0)
|
||||||
|
afm (0.2.2)
|
||||||
annotate (3.2.0)
|
annotate (3.2.0)
|
||||||
activerecord (>= 3.2, < 8.0)
|
activerecord (>= 3.2, < 8.0)
|
||||||
rake (>= 10.4, < 14.0)
|
rake (>= 10.4, < 14.0)
|
||||||
ast (2.4.2)
|
ast (2.4.2)
|
||||||
aws-eventstream (1.2.0)
|
aws-eventstream (1.2.0)
|
||||||
aws-partitions (1.781.0)
|
aws-partitions (1.807.0)
|
||||||
aws-sdk-core (3.175.0)
|
aws-sdk-core (3.180.3)
|
||||||
aws-eventstream (~> 1, >= 1.0.2)
|
aws-eventstream (~> 1, >= 1.0.2)
|
||||||
aws-partitions (~> 1, >= 1.651.0)
|
aws-partitions (~> 1, >= 1.651.0)
|
||||||
aws-sigv4 (~> 1.5)
|
aws-sigv4 (~> 1.5)
|
||||||
jmespath (~> 1, >= 1.6.1)
|
jmespath (~> 1, >= 1.6.1)
|
||||||
aws-sdk-kms (1.67.0)
|
aws-sdk-kms (1.71.0)
|
||||||
aws-sdk-core (~> 3, >= 3.174.0)
|
aws-sdk-core (~> 3, >= 3.177.0)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.1)
|
||||||
aws-sdk-s3 (1.126.0)
|
aws-sdk-s3 (1.132.1)
|
||||||
aws-sdk-core (~> 3, >= 3.174.0)
|
aws-sdk-core (~> 3, >= 3.179.0)
|
||||||
aws-sdk-kms (~> 1)
|
aws-sdk-kms (~> 1)
|
||||||
aws-sigv4 (~> 1.4)
|
aws-sigv4 (~> 1.6)
|
||||||
aws-sigv4 (1.5.2)
|
aws-sigv4 (1.6.0)
|
||||||
aws-eventstream (~> 1, >= 1.0.2)
|
aws-eventstream (~> 1, >= 1.0.2)
|
||||||
azure-storage-blob (2.0.3)
|
azure-storage-blob (2.0.3)
|
||||||
azure-storage-common (~> 2.0)
|
azure-storage-common (~> 2.0)
|
||||||
@@ -96,8 +98,9 @@ GEM
|
|||||||
faraday_middleware (~> 1.0, >= 1.0.0.rc1)
|
faraday_middleware (~> 1.0, >= 1.0.0.rc1)
|
||||||
net-http-persistent (~> 4.0)
|
net-http-persistent (~> 4.0)
|
||||||
nokogiri (~> 1, >= 1.10.8)
|
nokogiri (~> 1, >= 1.10.8)
|
||||||
|
base64 (0.1.1)
|
||||||
bcrypt (3.1.19)
|
bcrypt (3.1.19)
|
||||||
better_html (2.0.1)
|
better_html (2.0.2)
|
||||||
actionview (>= 6.0)
|
actionview (>= 6.0)
|
||||||
activesupport (>= 6.0)
|
activesupport (>= 6.0)
|
||||||
ast (~> 2.0)
|
ast (~> 2.0)
|
||||||
@@ -105,7 +108,7 @@ GEM
|
|||||||
parser (>= 2.4)
|
parser (>= 2.4)
|
||||||
smart_properties
|
smart_properties
|
||||||
bindex (0.8.1)
|
bindex (0.8.1)
|
||||||
bootsnap (1.15.0)
|
bootsnap (1.16.0)
|
||||||
msgpack (~> 1.2)
|
msgpack (~> 1.2)
|
||||||
builder (3.2.4)
|
builder (3.2.4)
|
||||||
bullet (7.0.7)
|
bullet (7.0.7)
|
||||||
@@ -144,7 +147,7 @@ GEM
|
|||||||
responders
|
responders
|
||||||
warden (~> 1.2.3)
|
warden (~> 1.2.3)
|
||||||
diff-lcs (1.5.0)
|
diff-lcs (1.5.0)
|
||||||
digest-crc (0.6.4)
|
digest-crc (0.6.5)
|
||||||
rake (>= 12.0.0, < 14.0.0)
|
rake (>= 12.0.0, < 14.0.0)
|
||||||
docile (1.4.0)
|
docile (1.4.0)
|
||||||
dotenv (2.8.1)
|
dotenv (2.8.1)
|
||||||
@@ -161,7 +164,7 @@ GEM
|
|||||||
factory_bot_rails (6.2.0)
|
factory_bot_rails (6.2.0)
|
||||||
factory_bot (~> 6.2.0)
|
factory_bot (~> 6.2.0)
|
||||||
railties (>= 5.0.0)
|
railties (>= 5.0.0)
|
||||||
faker (3.2.0)
|
faker (3.2.1)
|
||||||
i18n (>= 1.8.11, < 2)
|
i18n (>= 1.8.11, < 2)
|
||||||
faraday (1.10.3)
|
faraday (1.10.3)
|
||||||
faraday-em_http (~> 1.0)
|
faraday-em_http (~> 1.0)
|
||||||
@@ -194,10 +197,10 @@ GEM
|
|||||||
webrick (~> 1.7)
|
webrick (~> 1.7)
|
||||||
websocket-driver (>= 0.6, < 0.8)
|
websocket-driver (>= 0.6, < 0.8)
|
||||||
ffi (1.15.5)
|
ffi (1.15.5)
|
||||||
geom2d (0.3.1)
|
geom2d (0.4.1)
|
||||||
globalid (1.1.0)
|
globalid (1.1.0)
|
||||||
activesupport (>= 5.0)
|
activesupport (>= 5.0)
|
||||||
google-apis-core (0.11.0)
|
google-apis-core (0.11.1)
|
||||||
addressable (~> 2.5, >= 2.5.1)
|
addressable (~> 2.5, >= 2.5.1)
|
||||||
googleauth (>= 0.16.2, < 2.a)
|
googleauth (>= 0.16.2, < 2.a)
|
||||||
httpclient (>= 2.8.1, < 3.a)
|
httpclient (>= 2.8.1, < 3.a)
|
||||||
@@ -224,7 +227,7 @@ GEM
|
|||||||
google-cloud-core (~> 1.6)
|
google-cloud-core (~> 1.6)
|
||||||
googleauth (>= 0.16.2, < 2.a)
|
googleauth (>= 0.16.2, < 2.a)
|
||||||
mini_mime (~> 1.0)
|
mini_mime (~> 1.0)
|
||||||
googleauth (1.6.0)
|
googleauth (1.7.0)
|
||||||
faraday (>= 0.17.3, < 3.a)
|
faraday (>= 0.17.3, < 3.a)
|
||||||
jwt (>= 1.4, < 3.0)
|
jwt (>= 1.4, < 3.0)
|
||||||
memoist (~> 0.16)
|
memoist (~> 0.16)
|
||||||
@@ -232,10 +235,11 @@ GEM
|
|||||||
os (>= 0.9, < 2.0)
|
os (>= 0.9, < 2.0)
|
||||||
signet (>= 0.16, < 2.a)
|
signet (>= 0.16, < 2.a)
|
||||||
hashdiff (1.0.1)
|
hashdiff (1.0.1)
|
||||||
|
hashery (2.1.2)
|
||||||
hashie (5.0.0)
|
hashie (5.0.0)
|
||||||
hexapdf (0.32.2)
|
hexapdf (0.33.0)
|
||||||
cmdparse (~> 3.0, >= 3.0.3)
|
cmdparse (~> 3.0, >= 3.0.3)
|
||||||
geom2d (~> 0.3)
|
geom2d (~> 0.4, >= 0.4.1)
|
||||||
openssl (>= 2.2.1)
|
openssl (>= 2.2.1)
|
||||||
htmlentities (4.3.4)
|
htmlentities (4.3.4)
|
||||||
httpclient (2.8.3)
|
httpclient (2.8.3)
|
||||||
@@ -245,8 +249,8 @@ GEM
|
|||||||
mini_magick (>= 4.9.5, < 5)
|
mini_magick (>= 4.9.5, < 5)
|
||||||
ruby-vips (>= 2.0.17, < 3)
|
ruby-vips (>= 2.0.17, < 3)
|
||||||
io-console (0.6.0)
|
io-console (0.6.0)
|
||||||
irb (1.7.0)
|
irb (1.7.4)
|
||||||
reline (>= 0.3.0)
|
reline (>= 0.3.6)
|
||||||
jmespath (1.6.2)
|
jmespath (1.6.2)
|
||||||
json (2.6.3)
|
json (2.6.3)
|
||||||
jwt (2.7.1)
|
jwt (2.7.1)
|
||||||
@@ -260,7 +264,7 @@ GEM
|
|||||||
letter_opener (~> 1.7)
|
letter_opener (~> 1.7)
|
||||||
railties (>= 5.2)
|
railties (>= 5.2)
|
||||||
rexml
|
rexml
|
||||||
lograge (0.12.0)
|
lograge (0.13.0)
|
||||||
actionpack (>= 4)
|
actionpack (>= 4)
|
||||||
activesupport (>= 4)
|
activesupport (>= 4)
|
||||||
railties (>= 4)
|
railties (>= 4)
|
||||||
@@ -278,17 +282,17 @@ GEM
|
|||||||
memoist (0.16.2)
|
memoist (0.16.2)
|
||||||
method_source (1.0.0)
|
method_source (1.0.0)
|
||||||
mini_magick (4.12.0)
|
mini_magick (4.12.0)
|
||||||
mini_mime (1.1.2)
|
mini_mime (1.1.5)
|
||||||
mini_portile2 (2.8.2)
|
mini_portile2 (2.8.4)
|
||||||
minitest (5.18.1)
|
minitest (5.19.0)
|
||||||
msgpack (1.7.1)
|
msgpack (1.7.2)
|
||||||
multi_json (1.15.0)
|
multi_json (1.15.0)
|
||||||
multi_xml (0.6.0)
|
multi_xml (0.6.0)
|
||||||
multipart-post (2.3.0)
|
multipart-post (2.3.0)
|
||||||
mysql2 (0.5.5)
|
mysql2 (0.5.5)
|
||||||
net-http-persistent (4.0.2)
|
net-http-persistent (4.0.2)
|
||||||
connection_pool (~> 2.2)
|
connection_pool (~> 2.2)
|
||||||
net-imap (0.3.6)
|
net-imap (0.3.7)
|
||||||
date
|
date
|
||||||
net-protocol
|
net-protocol
|
||||||
net-pop (0.1.2)
|
net-pop (0.1.2)
|
||||||
@@ -298,7 +302,10 @@ GEM
|
|||||||
net-smtp (0.3.3)
|
net-smtp (0.3.3)
|
||||||
net-protocol
|
net-protocol
|
||||||
nio4r (2.5.9)
|
nio4r (2.5.9)
|
||||||
nokogiri (1.15.2-arm64-darwin)
|
nokogiri (1.15.4)
|
||||||
|
mini_portile2 (~> 2.8.2)
|
||||||
|
racc (~> 1.4)
|
||||||
|
nokogiri (1.15.4-arm64-darwin)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
oauth2 (2.0.9)
|
oauth2 (2.0.9)
|
||||||
faraday (>= 0.17.3, < 3.0)
|
faraday (>= 0.17.3, < 3.0)
|
||||||
@@ -307,7 +314,7 @@ GEM
|
|||||||
rack (>= 1.2, < 4)
|
rack (>= 1.2, < 4)
|
||||||
snaky_hash (~> 2.0)
|
snaky_hash (~> 2.0)
|
||||||
version_gem (~> 1.1)
|
version_gem (~> 1.1)
|
||||||
oj (3.15.0)
|
oj (3.16.0)
|
||||||
omniauth (2.1.1)
|
omniauth (2.1.1)
|
||||||
hashie (>= 3.4.6)
|
hashie (>= 3.4.6)
|
||||||
rack (>= 2.2.3)
|
rack (>= 2.2.3)
|
||||||
@@ -331,6 +338,12 @@ GEM
|
|||||||
parser (3.2.2.3)
|
parser (3.2.2.3)
|
||||||
ast (~> 2.4.1)
|
ast (~> 2.4.1)
|
||||||
racc
|
racc
|
||||||
|
pdf-reader (2.11.0)
|
||||||
|
Ascii85 (~> 1.0)
|
||||||
|
afm (~> 0.2.1)
|
||||||
|
hashery (~> 2.0)
|
||||||
|
ruby-rc4
|
||||||
|
ttfunk
|
||||||
pg (1.5.3)
|
pg (1.5.3)
|
||||||
premailer (1.21.0)
|
premailer (1.21.0)
|
||||||
addressable
|
addressable
|
||||||
@@ -345,57 +358,58 @@ GEM
|
|||||||
method_source (~> 1.0)
|
method_source (~> 1.0)
|
||||||
pry-rails (0.3.9)
|
pry-rails (0.3.9)
|
||||||
pry (>= 0.10.4)
|
pry (>= 0.10.4)
|
||||||
public_suffix (5.0.1)
|
public_suffix (5.0.3)
|
||||||
puma (6.3.0)
|
puma (6.3.1)
|
||||||
nio4r (~> 2.0)
|
nio4r (~> 2.0)
|
||||||
racc (1.7.1)
|
racc (1.7.1)
|
||||||
rack (2.2.7)
|
rack (2.2.8)
|
||||||
rack-protection (3.0.6)
|
rack-protection (3.1.0)
|
||||||
rack
|
rack (~> 2.2, >= 2.2.4)
|
||||||
rack-proxy (0.7.6)
|
rack-proxy (0.7.6)
|
||||||
rack
|
rack
|
||||||
rack-test (2.1.0)
|
rack-test (2.1.0)
|
||||||
rack (>= 1.3)
|
rack (>= 1.3)
|
||||||
rails (7.0.5)
|
rails (7.0.7)
|
||||||
actioncable (= 7.0.5)
|
actioncable (= 7.0.7)
|
||||||
actionmailbox (= 7.0.5)
|
actionmailbox (= 7.0.7)
|
||||||
actionmailer (= 7.0.5)
|
actionmailer (= 7.0.7)
|
||||||
actionpack (= 7.0.5)
|
actionpack (= 7.0.7)
|
||||||
actiontext (= 7.0.5)
|
actiontext (= 7.0.7)
|
||||||
actionview (= 7.0.5)
|
actionview (= 7.0.7)
|
||||||
activejob (= 7.0.5)
|
activejob (= 7.0.7)
|
||||||
activemodel (= 7.0.5)
|
activemodel (= 7.0.7)
|
||||||
activerecord (= 7.0.5)
|
activerecord (= 7.0.7)
|
||||||
activestorage (= 7.0.5)
|
activestorage (= 7.0.7)
|
||||||
activesupport (= 7.0.5)
|
activesupport (= 7.0.7)
|
||||||
bundler (>= 1.15.0)
|
bundler (>= 1.15.0)
|
||||||
railties (= 7.0.5)
|
railties (= 7.0.7)
|
||||||
rails-dom-testing (2.0.3)
|
rails-dom-testing (2.2.0)
|
||||||
activesupport (>= 4.2.0)
|
activesupport (>= 5.0.0)
|
||||||
|
minitest
|
||||||
nokogiri (>= 1.6)
|
nokogiri (>= 1.6)
|
||||||
rails-html-sanitizer (1.6.0)
|
rails-html-sanitizer (1.6.0)
|
||||||
loofah (~> 2.21)
|
loofah (~> 2.21)
|
||||||
nokogiri (~> 1.14)
|
nokogiri (~> 1.14)
|
||||||
rails-i18n (7.0.7)
|
rails-i18n (7.0.8)
|
||||||
i18n (>= 0.7, < 2)
|
i18n (>= 0.7, < 2)
|
||||||
railties (>= 6.0.0, < 8)
|
railties (>= 6.0.0, < 8)
|
||||||
rails_autolink (1.1.8)
|
rails_autolink (1.1.8)
|
||||||
actionview (> 3.1)
|
actionview (> 3.1)
|
||||||
activesupport (> 3.1)
|
activesupport (> 3.1)
|
||||||
railties (> 3.1)
|
railties (> 3.1)
|
||||||
railties (7.0.5)
|
railties (7.0.7)
|
||||||
actionpack (= 7.0.5)
|
actionpack (= 7.0.7)
|
||||||
activesupport (= 7.0.5)
|
activesupport (= 7.0.7)
|
||||||
method_source
|
method_source
|
||||||
rake (>= 12.2)
|
rake (>= 12.2)
|
||||||
thor (~> 1.0)
|
thor (~> 1.0)
|
||||||
zeitwerk (~> 2.5)
|
zeitwerk (~> 2.5)
|
||||||
rainbow (3.1.1)
|
rainbow (3.1.1)
|
||||||
rake (13.0.6)
|
rake (13.0.6)
|
||||||
redis-client (0.14.1)
|
redis-client (0.16.0)
|
||||||
connection_pool
|
connection_pool
|
||||||
regexp_parser (2.8.1)
|
regexp_parser (2.8.1)
|
||||||
reline (0.3.5)
|
reline (0.3.8)
|
||||||
io-console (~> 0.5)
|
io-console (~> 0.5)
|
||||||
representable (3.2.0)
|
representable (3.2.0)
|
||||||
declarative (< 0.1.0)
|
declarative (< 0.1.0)
|
||||||
@@ -407,14 +421,14 @@ GEM
|
|||||||
actionpack (>= 5.2)
|
actionpack (>= 5.2)
|
||||||
railties (>= 5.2)
|
railties (>= 5.2)
|
||||||
retriable (3.1.2)
|
retriable (3.1.2)
|
||||||
rexml (3.2.5)
|
rexml (3.2.6)
|
||||||
rollbar (3.4.0)
|
rollbar (3.4.0)
|
||||||
rspec-core (3.12.2)
|
rspec-core (3.12.2)
|
||||||
rspec-support (~> 3.12.0)
|
rspec-support (~> 3.12.0)
|
||||||
rspec-expectations (3.12.3)
|
rspec-expectations (3.12.3)
|
||||||
diff-lcs (>= 1.2.0, < 2.0)
|
diff-lcs (>= 1.2.0, < 2.0)
|
||||||
rspec-support (~> 3.12.0)
|
rspec-support (~> 3.12.0)
|
||||||
rspec-mocks (3.12.5)
|
rspec-mocks (3.12.6)
|
||||||
diff-lcs (>= 1.2.0, < 2.0)
|
diff-lcs (>= 1.2.0, < 2.0)
|
||||||
rspec-support (~> 3.12.0)
|
rspec-support (~> 3.12.0)
|
||||||
rspec-rails (6.0.3)
|
rspec-rails (6.0.3)
|
||||||
@@ -425,8 +439,9 @@ GEM
|
|||||||
rspec-expectations (~> 3.12)
|
rspec-expectations (~> 3.12)
|
||||||
rspec-mocks (~> 3.12)
|
rspec-mocks (~> 3.12)
|
||||||
rspec-support (~> 3.12)
|
rspec-support (~> 3.12)
|
||||||
rspec-support (3.12.0)
|
rspec-support (3.12.1)
|
||||||
rubocop (1.53.0)
|
rubocop (1.56.1)
|
||||||
|
base64 (~> 0.1.1)
|
||||||
json (~> 2.3)
|
json (~> 2.3)
|
||||||
language_server-protocol (>= 3.17.0)
|
language_server-protocol (>= 3.17.0)
|
||||||
parallel (~> 1.10)
|
parallel (~> 1.10)
|
||||||
@@ -434,7 +449,7 @@ GEM
|
|||||||
rainbow (>= 2.2.2, < 4.0)
|
rainbow (>= 2.2.2, < 4.0)
|
||||||
regexp_parser (>= 1.8, < 3.0)
|
regexp_parser (>= 1.8, < 3.0)
|
||||||
rexml (>= 3.2.5, < 4.0)
|
rexml (>= 3.2.5, < 4.0)
|
||||||
rubocop-ast (>= 1.28.0, < 2.0)
|
rubocop-ast (>= 1.28.1, < 2.0)
|
||||||
ruby-progressbar (~> 1.7)
|
ruby-progressbar (~> 1.7)
|
||||||
unicode-display_width (>= 2.4.0, < 3.0)
|
unicode-display_width (>= 2.4.0, < 3.0)
|
||||||
rubocop-ast (1.29.0)
|
rubocop-ast (1.29.0)
|
||||||
@@ -443,23 +458,28 @@ GEM
|
|||||||
rubocop (~> 1.41)
|
rubocop (~> 1.41)
|
||||||
rubocop-factory_bot (2.23.1)
|
rubocop-factory_bot (2.23.1)
|
||||||
rubocop (~> 1.33)
|
rubocop (~> 1.33)
|
||||||
rubocop-performance (1.18.0)
|
rubocop-performance (1.19.0)
|
||||||
rubocop (>= 1.7.0, < 2.0)
|
rubocop (>= 1.7.0, < 2.0)
|
||||||
rubocop-ast (>= 0.4.0)
|
rubocop-ast (>= 0.4.0)
|
||||||
rubocop-rails (2.20.2)
|
rubocop-rails (2.20.2)
|
||||||
activesupport (>= 4.2.0)
|
activesupport (>= 4.2.0)
|
||||||
rack (>= 1.1)
|
rack (>= 1.1)
|
||||||
rubocop (>= 1.33.0, < 2.0)
|
rubocop (>= 1.33.0, < 2.0)
|
||||||
rubocop-rspec (2.22.0)
|
rubocop-rspec (2.23.2)
|
||||||
rubocop (~> 1.33)
|
rubocop (~> 1.33)
|
||||||
rubocop-capybara (~> 2.17)
|
rubocop-capybara (~> 2.17)
|
||||||
rubocop-factory_bot (~> 2.22)
|
rubocop-factory_bot (~> 2.22)
|
||||||
ruby-progressbar (1.13.0)
|
ruby-progressbar (1.13.0)
|
||||||
|
ruby-rc4 (0.1.5)
|
||||||
ruby-vips (2.1.4)
|
ruby-vips (2.1.4)
|
||||||
ffi (~> 1.12)
|
ffi (~> 1.12)
|
||||||
ruby2_keywords (0.0.5)
|
ruby2_keywords (0.0.5)
|
||||||
|
rubyXL (3.4.25)
|
||||||
|
nokogiri (>= 1.10.8)
|
||||||
|
rubyzip (>= 1.3.0)
|
||||||
|
rubyzip (2.3.2)
|
||||||
semantic_range (3.0.0)
|
semantic_range (3.0.0)
|
||||||
shakapacker (7.0.0)
|
shakapacker (7.0.3)
|
||||||
activesupport (>= 5.2)
|
activesupport (>= 5.2)
|
||||||
rack-proxy (>= 0.6.1)
|
rack-proxy (>= 0.6.1)
|
||||||
railties (>= 5.2)
|
railties (>= 5.2)
|
||||||
@@ -484,13 +504,15 @@ GEM
|
|||||||
snaky_hash (2.0.1)
|
snaky_hash (2.0.1)
|
||||||
hashie
|
hashie
|
||||||
version_gem (~> 1.1, >= 1.1.1)
|
version_gem (~> 1.1, >= 1.1.1)
|
||||||
sqlite3 (1.5.4)
|
sqlite3 (1.6.3)
|
||||||
mini_portile2 (~> 2.8.0)
|
mini_portile2 (~> 2.8.0)
|
||||||
|
sqlite3 (1.6.3-arm64-darwin)
|
||||||
strip_attributes (1.13.0)
|
strip_attributes (1.13.0)
|
||||||
activemodel (>= 3.0, < 8.0)
|
activemodel (>= 3.0, < 8.0)
|
||||||
thor (1.2.2)
|
thor (1.2.2)
|
||||||
timeout (0.4.0)
|
timeout (0.4.0)
|
||||||
trailblazer-option (0.1.2)
|
trailblazer-option (0.1.2)
|
||||||
|
ttfunk (1.7.0)
|
||||||
turbo-rails (1.4.0)
|
turbo-rails (1.4.0)
|
||||||
actionpack (>= 6.0.0)
|
actionpack (>= 6.0.0)
|
||||||
activejob (>= 6.0.0)
|
activejob (>= 6.0.0)
|
||||||
@@ -515,15 +537,16 @@ 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.8.1)
|
webrick (1.8.1)
|
||||||
websocket-driver (0.7.5)
|
websocket-driver (0.7.6)
|
||||||
websocket-extensions (>= 0.1.0)
|
websocket-extensions (>= 0.1.0)
|
||||||
websocket-extensions (0.1.5)
|
websocket-extensions (0.1.5)
|
||||||
xpath (3.2.0)
|
xpath (3.2.0)
|
||||||
nokogiri (~> 1.8)
|
nokogiri (~> 1.8)
|
||||||
zeitwerk (2.6.8)
|
zeitwerk (2.6.11)
|
||||||
|
|
||||||
PLATFORMS
|
PLATFORMS
|
||||||
arm64-darwin-22
|
arm64-darwin-22
|
||||||
|
ruby
|
||||||
|
|
||||||
DEPENDENCIES
|
DEPENDENCIES
|
||||||
annotate
|
annotate
|
||||||
@@ -552,6 +575,7 @@ DEPENDENCIES
|
|||||||
omniauth-google-oauth2
|
omniauth-google-oauth2
|
||||||
omniauth-rails_csrf_protection
|
omniauth-rails_csrf_protection
|
||||||
pagy
|
pagy
|
||||||
|
pdf-reader
|
||||||
pg
|
pg
|
||||||
premailer-rails
|
premailer-rails
|
||||||
pry-rails
|
pry-rails
|
||||||
@@ -566,6 +590,7 @@ DEPENDENCIES
|
|||||||
rubocop-rails
|
rubocop-rails
|
||||||
rubocop-rspec
|
rubocop-rspec
|
||||||
ruby-vips
|
ruby-vips
|
||||||
|
rubyXL
|
||||||
shakapacker
|
shakapacker
|
||||||
sidekiq
|
sidekiq
|
||||||
simplecov
|
simplecov
|
||||||
|
|||||||
@@ -9,11 +9,11 @@ module Api
|
|||||||
template = current_account.templates.find(params[:template_id])
|
template = current_account.templates.find(params[:template_id])
|
||||||
|
|
||||||
submissions =
|
submissions =
|
||||||
if params[:emails].present?
|
if (params[:emails] || params[:email]).present?
|
||||||
Submissions.create_from_emails(template:,
|
Submissions.create_from_emails(template:,
|
||||||
user: current_user,
|
user: current_user,
|
||||||
source: :api,
|
source: :api,
|
||||||
send_email: params[:send_email] != 'false',
|
mark_as_sent: params[:send_email] != 'false',
|
||||||
emails: params[:emails] || params[:email])
|
emails: params[:emails] || params[:email])
|
||||||
else
|
else
|
||||||
submissions_attrs = normalize_submissions_params!(submissions_params[:submission], template)
|
submissions_attrs = normalize_submissions_params!(submissions_params[:submission], template)
|
||||||
@@ -21,13 +21,13 @@ module Api
|
|||||||
Submissions.create_from_submitters(template:,
|
Submissions.create_from_submitters(template:,
|
||||||
user: current_user,
|
user: current_user,
|
||||||
source: :api,
|
source: :api,
|
||||||
send_email: params[:send_email] != 'false',
|
mark_as_sent: params[:send_email] != 'false',
|
||||||
submissions_attrs:)
|
submissions_attrs:)
|
||||||
end
|
end
|
||||||
|
|
||||||
submitters = submissions.flat_map(&:submitters)
|
submitters = submissions.flat_map(&:submitters)
|
||||||
|
|
||||||
send_invitation_emails(submitters) if params[:send_email] != 'false'
|
Submitters.send_signature_requests(submitters, send_email: params[:send_email] != 'false')
|
||||||
|
|
||||||
render json: submitters
|
render json: submitters
|
||||||
rescue UnknownFieldName, UnknownSubmitterName => e
|
rescue UnknownFieldName, UnknownSubmitterName => e
|
||||||
@@ -36,14 +36,8 @@ module Api
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def send_invitation_emails(submitters)
|
|
||||||
submitters.each do |submitter|
|
|
||||||
SubmitterMailer.invitation_email(submitter, message: params[:message]).deliver_later!
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def submissions_params
|
def submissions_params
|
||||||
params.permit(submission: [{ submitters: [[:uuid, :name, :email, { values: {} }]] }])
|
params.permit(submission: [{ submitters: [[:uuid, :name, :email, :role, :phone, { values: {} }]] }])
|
||||||
end
|
end
|
||||||
|
|
||||||
def normalize_submissions_params!(submissions_params, template)
|
def normalize_submissions_params!(submissions_params, template)
|
||||||
@@ -53,7 +47,8 @@ module Api
|
|||||||
|
|
||||||
submitter[:values] =
|
submitter[:values] =
|
||||||
normalize_submitter_values(template,
|
normalize_submitter_values(template,
|
||||||
submitter[:values], submitter[:name] || template.submitters[index]['name'])
|
submitter[:values],
|
||||||
|
submitter[:role] || template.submitters[index]['name'])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -7,12 +7,7 @@ module Api
|
|||||||
|
|
||||||
@template = current_account.templates.find(params[:template_id])
|
@template = current_account.templates.find(params[:template_id])
|
||||||
|
|
||||||
documents =
|
documents = Templates::CreateAttachments.call(@template, params)
|
||||||
find_or_create_blobs.map do |blob|
|
|
||||||
document = @template.documents.create!(blob:)
|
|
||||||
|
|
||||||
Templates::ProcessDocument.call(document)
|
|
||||||
end
|
|
||||||
|
|
||||||
schema = documents.map do |doc|
|
schema = documents.map do |doc|
|
||||||
{ attachment_uuid: doc.uuid, name: doc.filename.base }
|
{ attachment_uuid: doc.uuid, name: doc.filename.base }
|
||||||
@@ -27,19 +22,5 @@ module Api
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def find_or_create_blobs
|
|
||||||
blobs = params[:blobs]&.map do |attrs|
|
|
||||||
ActiveStorage::Blob.find_signed(attrs[:signed_id])
|
|
||||||
end
|
|
||||||
|
|
||||||
blobs || params[:files].map do |file|
|
|
||||||
ActiveStorage::Blob.create_and_upload!(io: file.open,
|
|
||||||
filename: file.original_filename,
|
|
||||||
content_type: file.content_type)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -1,11 +1,17 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class ConsoleRedirectController < ApplicationController
|
class ConsoleRedirectController < ApplicationController
|
||||||
|
skip_before_action :authenticate_user!
|
||||||
|
|
||||||
def index
|
def index
|
||||||
|
return redirect_to(new_user_session_path({ redir: params[:redir] }.compact)) if current_user.blank?
|
||||||
|
|
||||||
auth = JsonWebToken.encode(uuid: current_user.uuid,
|
auth = JsonWebToken.encode(uuid: current_user.uuid,
|
||||||
scope: :console,
|
scope: :console,
|
||||||
exp: 1.minute.from_now.to_i)
|
exp: 1.minute.from_now.to_i)
|
||||||
|
|
||||||
redirect_to("#{Docuseal::CONSOLE_URL}?#{{ auth: }.to_query}", allow_other_host: true)
|
path = Addressable::URI.parse(params[:redir]).path if params[:redir].to_s.starts_with?(Docuseal::CONSOLE_URL)
|
||||||
|
|
||||||
|
redirect_to("#{Docuseal::CONSOLE_URL}#{path}?#{{ auth: }.to_query}", allow_other_host: true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -6,7 +6,7 @@ class EmailSettingsController < ApplicationController
|
|||||||
def index; end
|
def index; end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
if @encrypted_config.update(storage_configs)
|
if @encrypted_config.update(email_configs)
|
||||||
SettingsMailer.smtp_successful_setup(@encrypted_config.value['from_email']).deliver_now!
|
SettingsMailer.smtp_successful_setup(@encrypted_config.value['from_email']).deliver_now!
|
||||||
|
|
||||||
redirect_to settings_email_index_path, notice: 'Changes have been saved'
|
redirect_to settings_email_index_path, notice: 'Changes have been saved'
|
||||||
@@ -26,7 +26,7 @@ class EmailSettingsController < ApplicationController
|
|||||||
EncryptedConfig.find_or_initialize_by(account: current_account, key: EncryptedConfig::EMAIL_SMTP_KEY)
|
EncryptedConfig.find_or_initialize_by(account: current_account, key: EncryptedConfig::EMAIL_SMTP_KEY)
|
||||||
end
|
end
|
||||||
|
|
||||||
def storage_configs
|
def email_configs
|
||||||
params.require(:encrypted_config).permit(value: {}).tap do |e|
|
params.require(:encrypted_config).permit(value: {}).tap do |e|
|
||||||
e[:value].compact_blank!
|
e[:value].compact_blank!
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,19 +1,111 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class EsignSettingsController < ApplicationController
|
class EsignSettingsController < ApplicationController
|
||||||
|
DEFAULT_CERT_NAME = 'DocuSeal Self-Host Autogenerated'
|
||||||
|
|
||||||
|
CertFormRecord = Struct.new(:name, :file, :password, keyword_init: true) do
|
||||||
|
include ActiveModel::Validations
|
||||||
|
|
||||||
|
def to_key
|
||||||
|
[]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def show
|
||||||
|
cert_data = EncryptedConfig.find_by(account: current_account,
|
||||||
|
key: EncryptedConfig::ESIGN_CERTS_KEY)&.value || {}
|
||||||
|
|
||||||
|
default_pkcs = GenerateCertificate.load_pkcs(cert_data) if cert_data['cert'].present?
|
||||||
|
|
||||||
|
custom_pkcs_list = (cert_data['custom'] || []).map do |e|
|
||||||
|
{ 'pkcs' => OpenSSL::PKCS12.new(Base64.urlsafe_decode64(e['data']), e['password'].to_s),
|
||||||
|
'name' => e['name'],
|
||||||
|
'status' => e['status'] }
|
||||||
|
end
|
||||||
|
|
||||||
|
@pkcs_list = [
|
||||||
|
if default_pkcs
|
||||||
|
{
|
||||||
|
'pkcs' => default_pkcs,
|
||||||
|
'name' => DEFAULT_CERT_NAME,
|
||||||
|
'status' => custom_pkcs_list.any? { |e| e['status'] == 'default' } ? 'validate' : 'default'
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
*custom_pkcs_list
|
||||||
|
].compact.reverse
|
||||||
|
end
|
||||||
|
|
||||||
|
def new
|
||||||
|
@cert_record = CertFormRecord.new
|
||||||
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
pdfs =
|
@cert_record = CertFormRecord.new(**cert_params)
|
||||||
params[:files].map do |file|
|
|
||||||
HexaPDF::Document.new(io: file.open)
|
|
||||||
end
|
|
||||||
|
|
||||||
certs = Accounts.load_signing_certs(current_account)
|
cert_configs = EncryptedConfig.find_or_initialize_by(account: current_account,
|
||||||
|
key: EncryptedConfig::ESIGN_CERTS_KEY)
|
||||||
|
|
||||||
trusted_certs = [certs[:cert], certs[:sub_ca], certs[:root_ca]]
|
if (cert_configs.value && cert_configs.value['custom']&.any? { |e| e['name'] == @cert_record.name }) ||
|
||||||
|
@cert_record.name == DEFAULT_CERT_NAME
|
||||||
|
|
||||||
render turbo_stream: turbo_stream.replace('result', partial: 'result',
|
@cert_record.errors.add(:name, 'already exists')
|
||||||
locals: { pdfs:, files: params[:files], trusted_certs: })
|
|
||||||
rescue HexaPDF::MalformedPDFError
|
return render turbo_stream: turbo_stream.replace(:modal, template: 'esign_settings/new'),
|
||||||
render turbo_stream: turbo_stream.replace('result', html: helpers.tag.div('Invalid PDF', id: 'result'))
|
status: :unprocessable_entity
|
||||||
|
end
|
||||||
|
|
||||||
|
save_new_cert!(cert_configs, @cert_record)
|
||||||
|
|
||||||
|
redirect_to settings_esign_path, notice: 'Certificate has been successfully added!'
|
||||||
|
rescue OpenSSL::PKCS12::PKCS12Error
|
||||||
|
@cert_record.errors.add(:password, "is invalid. Make sure you're uploading a valid .p12 file")
|
||||||
|
|
||||||
|
render turbo_stream: turbo_stream.replace(:modal, template: 'esign_settings/new'), status: :unprocessable_entity
|
||||||
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
cert_configs = EncryptedConfig.find_by(account: current_account, key: EncryptedConfig::ESIGN_CERTS_KEY)
|
||||||
|
|
||||||
|
cert_configs.value['custom'].each { |e| e['status'] = 'validate' }
|
||||||
|
custom_cert_data = cert_configs.value['custom'].find { |e| e['name'] == params[:name] }
|
||||||
|
custom_cert_data['status'] = 'default' if custom_cert_data
|
||||||
|
|
||||||
|
cert_configs.save!
|
||||||
|
|
||||||
|
redirect_to settings_esign_path, notice: 'Default certificate has been selected'
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
cert_configs = EncryptedConfig.find_by(account: current_account, key: EncryptedConfig::ESIGN_CERTS_KEY)
|
||||||
|
|
||||||
|
cert_configs.value['custom'].reject! { |e| e['name'] == params[:name] }
|
||||||
|
|
||||||
|
cert_configs.save!
|
||||||
|
|
||||||
|
redirect_to settings_esign_path, notice: 'Certificate has been removed'
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def save_new_cert!(cert_configs, cert_record)
|
||||||
|
pkcs = OpenSSL::PKCS12.new(cert_record.file.read, cert_record.password)
|
||||||
|
|
||||||
|
cert_configs.value ||= {}
|
||||||
|
cert_configs.value['custom'] ||= []
|
||||||
|
cert_configs.value['custom'].each { |e| e['status'] = 'validate' }
|
||||||
|
cert_configs.value['custom'] << {
|
||||||
|
data: Base64.urlsafe_encode64(pkcs.to_der),
|
||||||
|
password: cert_record.password,
|
||||||
|
name: cert_record.name,
|
||||||
|
status: 'default'
|
||||||
|
}
|
||||||
|
|
||||||
|
cert_configs.save!
|
||||||
|
end
|
||||||
|
|
||||||
|
def cert_params
|
||||||
|
return {} if params[:esign_settings_controller_cert_form_record].blank?
|
||||||
|
|
||||||
|
params.require(:esign_settings_controller_cert_form_record).permit(:name, :file, :password)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
class RegistrationsController < Devise::RegistrationsController
|
class RegistrationsController < Devise::RegistrationsController
|
||||||
prepend_before_action :require_no_authentication, only: [:show]
|
prepend_before_action :require_no_authentication, only: [:show]
|
||||||
|
prepend_before_action :maybe_redirect_if_signed_in, only: [:show]
|
||||||
|
|
||||||
def show; end
|
def show; end
|
||||||
|
|
||||||
@@ -13,6 +14,23 @@ class RegistrationsController < Devise::RegistrationsController
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def after_sign_up_path_for(...)
|
||||||
|
if params[:redir].present?
|
||||||
|
return console_redirect_index_path(redir: params[:redir]) if params[:redir].starts_with?(Docuseal::CONSOLE_URL)
|
||||||
|
|
||||||
|
return params[:redir]
|
||||||
|
end
|
||||||
|
|
||||||
|
super
|
||||||
|
end
|
||||||
|
|
||||||
|
def maybe_redirect_if_signed_in
|
||||||
|
return unless signed_in?
|
||||||
|
return if params[:redir].blank?
|
||||||
|
|
||||||
|
redirect_to after_sign_up_path_for(current_user), allow_other_host: true
|
||||||
|
end
|
||||||
|
|
||||||
def require_no_authentication
|
def require_no_authentication
|
||||||
super
|
super
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,16 @@ class SessionsController < Devise::SessionsController
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def after_sign_in_path_for(...)
|
||||||
|
if params[:redir].present?
|
||||||
|
return console_redirect_index_path(redir: params[:redir]) if params[:redir].starts_with?(Docuseal::CONSOLE_URL)
|
||||||
|
|
||||||
|
return params[:redir]
|
||||||
|
end
|
||||||
|
|
||||||
|
super
|
||||||
|
end
|
||||||
|
|
||||||
def require_no_authentication
|
def require_no_authentication
|
||||||
super
|
super
|
||||||
|
|
||||||
|
|||||||
@@ -16,16 +16,17 @@ class SetupController < ApplicationController
|
|||||||
def create
|
def create
|
||||||
@account = Account.new(account_params)
|
@account = Account.new(account_params)
|
||||||
@account.timezone = Accounts.normalize_timezone(@account.timezone)
|
@account.timezone = Accounts.normalize_timezone(@account.timezone)
|
||||||
|
|
||||||
@user = @account.users.new(user_params)
|
@user = @account.users.new(user_params)
|
||||||
|
@encrypted_config = EncryptedConfig.new(encrypted_config_params)
|
||||||
|
|
||||||
unless URI.parse(encrypted_config_params[:value].to_s).class.in?([URI::HTTP, URI::HTTPS])
|
unless URI.parse(encrypted_config_params[:value].to_s).class.in?([URI::HTTP, URI::HTTPS])
|
||||||
@encrypted_config = EncryptedConfig.new(encrypted_config_params)
|
|
||||||
@encrypted_config.errors.add(:value, 'should be a valid URL')
|
@encrypted_config.errors.add(:value, 'should be a valid URL')
|
||||||
|
|
||||||
return render :index, status: :unprocessable_entity
|
return render :index, status: :unprocessable_entity
|
||||||
end
|
end
|
||||||
|
|
||||||
|
return render :index, status: :unprocessable_entity unless @account.valid?
|
||||||
|
|
||||||
if @user.save
|
if @user.save
|
||||||
encrypted_configs = [
|
encrypted_configs = [
|
||||||
{ key: EncryptedConfig::APP_URL_KEY, value: encrypted_config_params[:value] },
|
{ key: EncryptedConfig::APP_URL_KEY, value: encrypted_config_params[:value] },
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class SmsSettingsController < ApplicationController
|
||||||
|
def index; end
|
||||||
|
end
|
||||||
@@ -6,7 +6,7 @@ class SubmissionsController < ApplicationController
|
|||||||
def show
|
def show
|
||||||
@submission =
|
@submission =
|
||||||
Submission.joins(:template).where(template: { account_id: current_account.id })
|
Submission.joins(:template).where(template: { account_id: current_account.id })
|
||||||
.preload(template: { documents_attachments: { preview_images_attachments: :blob } })
|
.preload(:template, template_schema_documents: [:blob, { preview_images_attachments: :blob }])
|
||||||
.find(params[:id])
|
.find(params[:id])
|
||||||
|
|
||||||
render :show, layout: 'plain'
|
render :show, layout: 'plain'
|
||||||
@@ -20,23 +20,19 @@ class SubmissionsController < ApplicationController
|
|||||||
Submissions.create_from_emails(template: @template,
|
Submissions.create_from_emails(template: @template,
|
||||||
user: current_user,
|
user: current_user,
|
||||||
source: :invite,
|
source: :invite,
|
||||||
send_email: params[:send_email] == '1',
|
mark_as_sent: params[:send_email] == '1',
|
||||||
emails: params[:emails])
|
emails: params[:emails])
|
||||||
else
|
else
|
||||||
Submissions.create_from_submitters(template: @template,
|
Submissions.create_from_submitters(template: @template,
|
||||||
user: current_user,
|
user: current_user,
|
||||||
source: :invite,
|
source: :invite,
|
||||||
send_email: params[:send_email] == '1',
|
mark_as_sent: params[:send_email] == '1',
|
||||||
submissions_attrs: submissions_params[:submission].to_h.values)
|
submissions_attrs: submissions_params[:submission].to_h.values)
|
||||||
end
|
end
|
||||||
|
|
||||||
submitters = submissions.flat_map(&:submitters)
|
submitters = submissions.flat_map(&:submitters)
|
||||||
|
|
||||||
if params[:send_email] == '1'
|
Submitters.send_signature_requests(submitters, params)
|
||||||
submitters.each do |submitter|
|
|
||||||
SubmitterMailer.invitation_email(submitter, message: params[:message]).deliver_later!
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
redirect_to template_path(@template),
|
redirect_to template_path(@template),
|
||||||
notice: "#{submitters.size} #{'recipient'.pluralize(submitters.size)} added"
|
notice: "#{submitters.size} #{'recipient'.pluralize(submitters.size)} added"
|
||||||
@@ -54,7 +50,7 @@ class SubmissionsController < ApplicationController
|
|||||||
private
|
private
|
||||||
|
|
||||||
def submissions_params
|
def submissions_params
|
||||||
params.permit(submission: { submitters: [%i[uuid email]] })
|
params.permit(submission: { submitters: [%i[uuid email phone name]] })
|
||||||
end
|
end
|
||||||
|
|
||||||
def load_template
|
def load_template
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class SubmissionsExportController < ApplicationController
|
||||||
|
before_action :load_template
|
||||||
|
|
||||||
|
def index
|
||||||
|
submissions = @template.submissions.active
|
||||||
|
.preload(submitters: { documents_attachments: :blob,
|
||||||
|
attachments_attachments: :blob })
|
||||||
|
.order(id: :asc)
|
||||||
|
|
||||||
|
if params[:format] == 'csv'
|
||||||
|
send_data Submissions::GenerateExportFiles.call(submissions, format: params[:format]),
|
||||||
|
filename: "#{@template.name}.csv"
|
||||||
|
elsif params[:format] == 'xlsx'
|
||||||
|
send_data Submissions::GenerateExportFiles.call(submissions, format: params[:format]),
|
||||||
|
filename: "#{@template.name}.xlsx"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def new; end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def load_template
|
||||||
|
@template = current_account.templates.find(params[:template_id])
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -7,7 +7,9 @@ class SubmitFormController < ApplicationController
|
|||||||
|
|
||||||
def show
|
def show
|
||||||
@submitter =
|
@submitter =
|
||||||
Submitter.preload(submission: { template: { documents_attachments: { preview_images_attachments: :blob } } })
|
Submitter.preload(submission: [
|
||||||
|
:template, { template_schema_documents: [:blob, { preview_images_attachments: :blob }] }
|
||||||
|
])
|
||||||
.find_by!(slug: params[:slug])
|
.find_by!(slug: params[:slug])
|
||||||
|
|
||||||
return redirect_to submit_form_completed_path(@submitter.slug) if @submitter.completed_at?
|
return redirect_to submit_form_completed_path(@submitter.slug) if @submitter.completed_at?
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class SubmittersSendEmailController < ApplicationController
|
||||||
|
def create
|
||||||
|
submitter = Submitter.joins(:template)
|
||||||
|
.where(template: { account_id: current_account.id })
|
||||||
|
.find_by!(slug: params[:submitter_slug])
|
||||||
|
|
||||||
|
SubmitterMailer.invitation_email(submitter).deliver_later!
|
||||||
|
|
||||||
|
submitter.sent_at ||= Time.current
|
||||||
|
submitter.save!
|
||||||
|
|
||||||
|
redirect_back(fallback_location: submission_path(submitter.submission), notice: 'Email has been sent')
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -17,7 +17,7 @@ class TemplatesController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def edit
|
def edit
|
||||||
@template = current_account.templates.preload(documents_attachments: { preview_images_attachments: :blob })
|
@template = current_account.templates.preload(schema_documents: { preview_images_attachments: :blob })
|
||||||
.find(params[:id])
|
.find(params[:id])
|
||||||
|
|
||||||
render :edit, layout: 'plain'
|
render :edit, layout: 'plain'
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class VerifyPdfSignatureController < ApplicationController
|
||||||
|
def create
|
||||||
|
pdfs =
|
||||||
|
params[:files].map do |file|
|
||||||
|
HexaPDF::Document.new(io: file.open)
|
||||||
|
end
|
||||||
|
|
||||||
|
cert_data = if Docuseal.multitenant?
|
||||||
|
Docuseal::CERTS
|
||||||
|
else
|
||||||
|
EncryptedConfig.find_by(account: current_account, key: EncryptedConfig::ESIGN_CERTS_KEY)&.value || {}
|
||||||
|
end
|
||||||
|
|
||||||
|
default_pkcs = GenerateCertificate.load_pkcs(cert_data)
|
||||||
|
|
||||||
|
custom_certs = cert_data.fetch('custom', []).map do |e|
|
||||||
|
OpenSSL::PKCS12.new(Base64.urlsafe_decode64(e['data']), e['password'].to_s)
|
||||||
|
end
|
||||||
|
|
||||||
|
trusted_certs = [default_pkcs.certificate,
|
||||||
|
*default_pkcs.ca_certs,
|
||||||
|
*custom_certs.map(&:certificate),
|
||||||
|
*custom_certs.flat_map(&:ca_certs).compact]
|
||||||
|
|
||||||
|
render turbo_stream: turbo_stream.replace('result', partial: 'result',
|
||||||
|
locals: { pdfs:, files: params[:files], trusted_certs: })
|
||||||
|
rescue HexaPDF::MalformedPDFError
|
||||||
|
render turbo_stream: turbo_stream.replace('result', html: helpers.tag.div('Invalid PDF', id: 'result'))
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -45,13 +45,36 @@ window.customElements.define('set-timezone', SetTimezone)
|
|||||||
window.customElements.define('autoresize-textarea', AutoresizeTextarea)
|
window.customElements.define('autoresize-textarea', AutoresizeTextarea)
|
||||||
|
|
||||||
document.addEventListener('turbo:before-fetch-request', encodeMethodIntoRequestBody)
|
document.addEventListener('turbo:before-fetch-request', encodeMethodIntoRequestBody)
|
||||||
|
document.addEventListener('turbo:submit-end', async (event) => {
|
||||||
|
const resp = event.detail?.formSubmission?.result?.fetchResponse?.response
|
||||||
|
|
||||||
|
if (!resp?.headers?.get('content-disposition')?.includes('attachment')) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = URL.createObjectURL(await resp.blob())
|
||||||
|
const link = document.createElement('a')
|
||||||
|
|
||||||
|
link.href = url
|
||||||
|
link.setAttribute('download', decodeURIComponent(resp.headers.get('content-disposition').split('"')[1]))
|
||||||
|
|
||||||
|
document.body.appendChild(link)
|
||||||
|
|
||||||
|
link.click()
|
||||||
|
|
||||||
|
document.body.removeChild(link)
|
||||||
|
|
||||||
|
URL.revokeObjectURL(url)
|
||||||
|
})
|
||||||
|
|
||||||
window.customElements.define('template-builder', class extends HTMLElement {
|
window.customElements.define('template-builder', class extends HTMLElement {
|
||||||
connectedCallback () {
|
connectedCallback () {
|
||||||
this.appElem = document.createElement('div')
|
this.appElem = document.createElement('div')
|
||||||
|
this.appElem.classList.add('max-h-screen')
|
||||||
|
|
||||||
this.app = createApp(TemplateBuilder, {
|
this.app = createApp(TemplateBuilder, {
|
||||||
template: reactive(JSON.parse(this.dataset.template)),
|
template: reactive(JSON.parse(this.dataset.template)),
|
||||||
|
backgroundColor: '#faf7f5',
|
||||||
isDirectUpload: this.dataset.isDirectUpload === 'true'
|
isDirectUpload: this.dataset.isDirectUpload === 'true'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,11 @@ export default actionable(targetable(class extends HTMLElement {
|
|||||||
this.uploadFiles(this.input.files)
|
this.uploadFiles(this.input.files)
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleLoading = () => {
|
toggleLoading = (e) => {
|
||||||
|
if (e && e.target && !e.target.contains(this)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
this.loading.classList.toggle('hidden')
|
this.loading.classList.toggle('hidden')
|
||||||
this.icon.classList.toggle('hidden')
|
this.icon.classList.toggle('hidden')
|
||||||
this.classList.toggle('opacity-50')
|
this.classList.toggle('opacity-50')
|
||||||
|
|||||||
@@ -5,8 +5,12 @@ export default actionable(class extends HTMLElement {
|
|||||||
document.body.classList.add('overflow-hidden')
|
document.body.classList.add('overflow-hidden')
|
||||||
|
|
||||||
document.addEventListener('keyup', this.onEscKey)
|
document.addEventListener('keyup', this.onEscKey)
|
||||||
document.addEventListener('turbo:submit-end', this.onSubmit)
|
|
||||||
document.addEventListener('turbo:before-cache', this.close)
|
document.addEventListener('turbo:before-cache', this.close)
|
||||||
|
|
||||||
|
if (this.dataset.closeAfterSubmit !== 'false') {
|
||||||
|
document.addEventListener('turbo:submit-end', this.onSubmit)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnectedCallback () {
|
disconnectedCallback () {
|
||||||
|
|||||||
@@ -9,12 +9,12 @@ window.customElements.define('submission-form', class extends HTMLElement {
|
|||||||
this.appElem = document.createElement('div')
|
this.appElem = document.createElement('div')
|
||||||
|
|
||||||
this.app = createApp(Form, {
|
this.app = createApp(Form, {
|
||||||
submitterSlug: this.dataset.submitterSlug,
|
submitter: JSON.parse(this.dataset.submitter),
|
||||||
submitterUuid: this.dataset.submitterUuid,
|
|
||||||
authenticityToken: this.dataset.authenticityToken,
|
authenticityToken: this.dataset.authenticityToken,
|
||||||
canSendEmail: this.dataset.canSendEmail === 'true',
|
canSendEmail: this.dataset.canSendEmail === 'true',
|
||||||
isDirectUpload: this.dataset.isDirectUpload === 'true',
|
isDirectUpload: this.dataset.isDirectUpload === 'true',
|
||||||
isDemo: this.dataset.isDemo === 'true',
|
isDemo: this.dataset.isDemo === 'true',
|
||||||
|
attribution: this.dataset.attribution !== 'false',
|
||||||
withConfetti: true,
|
withConfetti: true,
|
||||||
values: reactive(JSON.parse(this.dataset.values)),
|
values: reactive(JSON.parse(this.dataset.values)),
|
||||||
attachments: reactive(JSON.parse(this.dataset.attachments)),
|
attachments: reactive(JSON.parse(this.dataset.attachments)),
|
||||||
|
|||||||
@@ -7,13 +7,13 @@
|
|||||||
:height="30"
|
:height="30"
|
||||||
/>
|
/>
|
||||||
<span>
|
<span>
|
||||||
Form has been completed!
|
{{ t('form_has_been_completed') }}
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
<div class="space-y-3 mt-5">
|
<div class="space-y-3 mt-5">
|
||||||
<button
|
<button
|
||||||
v-if="canSendEmail && !isDemo"
|
v-if="canSendEmail && !isDemo"
|
||||||
class="white-button flex items-center space-x-1 w-full"
|
class="white-button !h-auto flex items-center space-x-1 w-full"
|
||||||
:disabled="isSendingCopy"
|
:disabled="isSendingCopy"
|
||||||
@click.prevent="sendCopyToEmail"
|
@click.prevent="sendCopyToEmail"
|
||||||
>
|
>
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
/>
|
/>
|
||||||
<IconMail v-else />
|
<IconMail v-else />
|
||||||
<span>
|
<span>
|
||||||
Send copy via email
|
{{ t('send_copy_via_email') }}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
/>
|
/>
|
||||||
<IconDownload v-else />
|
<IconDownload v-else />
|
||||||
<span>
|
<span>
|
||||||
Download
|
{{ t('download') }}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
<a
|
<a
|
||||||
@@ -58,17 +58,20 @@
|
|||||||
>
|
>
|
||||||
<IconLogin />
|
<IconLogin />
|
||||||
<span>
|
<span>
|
||||||
Create a Free Account
|
{{ t('create_a_free_account') }}
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-center mt-4">
|
<div
|
||||||
Signed with
|
v-if="attribution"
|
||||||
|
class="text-center mt-4"
|
||||||
|
>
|
||||||
|
{{ t('signed_with') }}
|
||||||
<a
|
<a
|
||||||
href="https://www.docuseal.co"
|
href="https://www.docuseal.co"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
class="underline"
|
class="underline"
|
||||||
>DocuSeal</a> - open source documents software
|
>DocuSeal</a> - {{ t('open_source_documents_software') }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -86,7 +89,7 @@ export default {
|
|||||||
IconLogin,
|
IconLogin,
|
||||||
IconDownload
|
IconDownload
|
||||||
},
|
},
|
||||||
inject: ['baseUrl'],
|
inject: ['baseUrl', 't'],
|
||||||
props: {
|
props: {
|
||||||
submitterSlug: {
|
submitterSlug: {
|
||||||
type: String,
|
type: String,
|
||||||
@@ -97,6 +100,11 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
|
attribution: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
withConfetti: {
|
withConfetti: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
{{ message }}
|
{{ message }}
|
||||||
</div>
|
</div>
|
||||||
<div class="text-xs">
|
<div class="text-xs">
|
||||||
<span class="font-medium">Click to upload</span> or drag and drop files
|
<span class="font-medium">{{ t('click_to_upload') }}</span> {{ t('or_drag_and_drop_files') }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -55,7 +55,7 @@ export default {
|
|||||||
IconCloudUpload,
|
IconCloudUpload,
|
||||||
IconInnerShadowTop
|
IconInnerShadowTop
|
||||||
},
|
},
|
||||||
inject: ['baseUrl'],
|
inject: ['baseUrl', 't'],
|
||||||
props: {
|
props: {
|
||||||
message: {
|
message: {
|
||||||
type: String,
|
type: String,
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
class="btn btn-neutral flex text-white absolute rounded-none border-x-0 md:border md:rounded-full bottom-0 w-full md:mb-4 text-base"
|
class="btn btn-neutral flex text-white absolute rounded-none border-x-0 md:border md:rounded-full bottom-0 w-full md:mb-4 text-base"
|
||||||
@click.prevent="isFormVisible = true"
|
@click.prevent="isFormVisible = true"
|
||||||
>
|
>
|
||||||
Submit Form
|
{{ t('submit_form') }}
|
||||||
<IconArrowsDiagonal
|
<IconArrowsDiagonal
|
||||||
class="absolute right-0 mr-4"
|
class="absolute right-0 mr-4"
|
||||||
:width="20"
|
:width="20"
|
||||||
@@ -24,6 +24,7 @@
|
|||||||
v-show="isFormVisible"
|
v-show="isFormVisible"
|
||||||
id="form_container"
|
id="form_container"
|
||||||
class="shadow-md bg-base-100 absolute bottom-0 md:bottom-4 w-full border-base-200 border p-4 rounded"
|
class="shadow-md bg-base-100 absolute bottom-0 md:bottom-4 w-full border-base-200 border p-4 rounded"
|
||||||
|
:style="{ backgroundColor: backgroundColor }"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
v-if="!isCompleted"
|
v-if="!isCompleted"
|
||||||
@@ -62,7 +63,7 @@
|
|||||||
:for="currentField.uuid"
|
:for="currentField.uuid"
|
||||||
class="label text-2xl mb-2"
|
class="label text-2xl mb-2"
|
||||||
>{{ currentField.name }}
|
>{{ currentField.name }}
|
||||||
<template v-if="!currentField.required">(optional)</template>
|
<template v-if="!currentField.required">({{ t('optional') }})</template>
|
||||||
</label>
|
</label>
|
||||||
<div
|
<div
|
||||||
v-else
|
v-else
|
||||||
@@ -74,7 +75,7 @@
|
|||||||
v-model="values[currentField.uuid]"
|
v-model="values[currentField.uuid]"
|
||||||
class="base-input !text-2xl w-full"
|
class="base-input !text-2xl w-full"
|
||||||
:required="currentField.required"
|
:required="currentField.required"
|
||||||
:placeholder="`Type here...${currentField.required ? '' : ' (optional)'}`"
|
:placeholder="`${t('type_here')}...${currentField.required ? '' : ` (${t('optional')})`}`"
|
||||||
type="text"
|
type="text"
|
||||||
:name="`values[${currentField.uuid}]`"
|
:name="`values[${currentField.uuid}]`"
|
||||||
@focus="$refs.areas.scrollIntoField(currentField)"
|
@focus="$refs.areas.scrollIntoField(currentField)"
|
||||||
@@ -87,7 +88,7 @@
|
|||||||
:for="currentField.uuid"
|
:for="currentField.uuid"
|
||||||
class="label text-2xl mb-2"
|
class="label text-2xl mb-2"
|
||||||
>{{ currentField.name }}
|
>{{ currentField.name }}
|
||||||
<template v-if="!currentField.required">(optional)</template>
|
<template v-if="!currentField.required">({{ t('optional') }})</template>
|
||||||
</label>
|
</label>
|
||||||
<div
|
<div
|
||||||
v-else
|
v-else
|
||||||
@@ -111,7 +112,7 @@
|
|||||||
:for="currentField.uuid"
|
:for="currentField.uuid"
|
||||||
class="label text-2xl mb-2"
|
class="label text-2xl mb-2"
|
||||||
>{{ currentField.name }}
|
>{{ currentField.name }}
|
||||||
<template v-if="!currentField.required">(optional)</template>
|
<template v-if="!currentField.required">({{ t('optional') }})</template>
|
||||||
</label>
|
</label>
|
||||||
<div
|
<div
|
||||||
v-else
|
v-else
|
||||||
@@ -129,7 +130,7 @@
|
|||||||
value=""
|
value=""
|
||||||
:selected="!values[currentField.uuid]"
|
:selected="!values[currentField.uuid]"
|
||||||
>
|
>
|
||||||
Select your option
|
{{ t('select_your_option') }}
|
||||||
</option>
|
</option>
|
||||||
<option
|
<option
|
||||||
v-for="(option, index) in currentField.options"
|
v-for="(option, index) in currentField.options"
|
||||||
@@ -147,7 +148,7 @@
|
|||||||
:for="currentField.uuid"
|
:for="currentField.uuid"
|
||||||
class="label text-2xl mb-2"
|
class="label text-2xl mb-2"
|
||||||
>{{ currentField.name }}
|
>{{ currentField.name }}
|
||||||
<template v-if="!currentField.required">(optional)</template>
|
<template v-if="!currentField.required">({{ t('optional') }})</template>
|
||||||
</label>
|
</label>
|
||||||
<div class="flex w-full">
|
<div class="flex w-full">
|
||||||
<div class="space-y-3.5 mx-auto">
|
<div class="space-y-3.5 mx-auto">
|
||||||
@@ -195,7 +196,7 @@
|
|||||||
>
|
>
|
||||||
<template v-if="isAnonymousChecboxes">
|
<template v-if="isAnonymousChecboxes">
|
||||||
<span class="text-xl">
|
<span class="text-xl">
|
||||||
Complete hightlighted checkboxes and click <span class="font-semibold">{{ stepFields.length === currentStep + 1 ? 'submit' : 'next' }}</span>.
|
{{ t('complete_hightlighted_checkboxes_and_click') }} <span class="font-semibold">{{ stepFields.length === currentStep + 1 ? t('submit') : t('next') }}</span>.
|
||||||
</span>
|
</span>
|
||||||
<input
|
<input
|
||||||
v-for="field in currentStepFields"
|
v-for="field in currentStepFields"
|
||||||
@@ -277,10 +278,10 @@
|
|||||||
class="mr-1 animate-spin"
|
class="mr-1 animate-spin"
|
||||||
/>
|
/>
|
||||||
<span v-if="stepFields.length === currentStep + 1">
|
<span v-if="stepFields.length === currentStep + 1">
|
||||||
Submit
|
{{ t('submit') }}
|
||||||
</span>
|
</span>
|
||||||
<span v-else>
|
<span v-else>
|
||||||
Next
|
{{ t('next') }}
|
||||||
</span><span
|
</span><span
|
||||||
v-if="isSubmitting"
|
v-if="isSubmitting"
|
||||||
class="w-6 flex justify-start mr-1"
|
class="w-6 flex justify-start mr-1"
|
||||||
@@ -292,8 +293,9 @@
|
|||||||
<FormCompleted
|
<FormCompleted
|
||||||
v-else
|
v-else
|
||||||
:is-demo="isDemo"
|
:is-demo="isDemo"
|
||||||
|
:attribution="attribution"
|
||||||
:with-confetti="withConfetti"
|
:with-confetti="withConfetti"
|
||||||
:can-send-email="canSendEmail"
|
:can-send-email="canSendEmail && submitter.email"
|
||||||
:submitter-slug="submitterSlug"
|
:submitter-slug="submitterSlug"
|
||||||
/>
|
/>
|
||||||
<div class="flex justify-center">
|
<div class="flex justify-center">
|
||||||
@@ -320,6 +322,7 @@ import AttachmentStep from './attachment_step'
|
|||||||
import MultiSelectStep from './multi_select_step'
|
import MultiSelectStep from './multi_select_step'
|
||||||
import FormCompleted from './completed'
|
import FormCompleted from './completed'
|
||||||
import { IconInnerShadowTop, IconArrowsDiagonal, IconArrowsDiagonalMinimize2 } from '@tabler/icons-vue'
|
import { IconInnerShadowTop, IconArrowsDiagonal, IconArrowsDiagonalMinimize2 } from '@tabler/icons-vue'
|
||||||
|
import { t } from './i18n'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'SubmissionForm',
|
name: 'SubmissionForm',
|
||||||
@@ -336,12 +339,13 @@ export default {
|
|||||||
},
|
},
|
||||||
provide () {
|
provide () {
|
||||||
return {
|
return {
|
||||||
baseUrl: this.baseUrl
|
baseUrl: this.baseUrl,
|
||||||
|
t: this.t
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
submitterSlug: {
|
submitter: {
|
||||||
type: String,
|
type: Object,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
canSendEmail: {
|
canSendEmail: {
|
||||||
@@ -349,10 +353,6 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
submitterUuid: {
|
|
||||||
type: String,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
attachments: {
|
attachments: {
|
||||||
type: Array,
|
type: Array,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -378,6 +378,11 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: ''
|
default: ''
|
||||||
},
|
},
|
||||||
|
backgroundColor: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
isDirectUpload: {
|
isDirectUpload: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -388,6 +393,11 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
|
attribution: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
values: {
|
values: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -406,6 +416,9 @@ export default {
|
|||||||
currentStepFields () {
|
currentStepFields () {
|
||||||
return this.stepFields[this.currentStep]
|
return this.stepFields[this.currentStep]
|
||||||
},
|
},
|
||||||
|
submitterSlug () {
|
||||||
|
return this.submitter.slug
|
||||||
|
},
|
||||||
isAnonymousChecboxes () {
|
isAnonymousChecboxes () {
|
||||||
return this.currentField.type === 'checkbox' && this.currentStepFields.every((e) => !e.name) && this.currentStepFields.length > 4
|
return this.currentField.type === 'checkbox' && this.currentStepFields.every((e) => !e.name) && this.currentStepFields.length > 4
|
||||||
},
|
},
|
||||||
@@ -462,6 +475,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
t,
|
||||||
goToStep (step, scrollToArea = false, clickUpload = false) {
|
goToStep (step, scrollToArea = false, clickUpload = false) {
|
||||||
this.currentStep = this.stepFields.indexOf(step)
|
this.currentStep = this.stepFields.indexOf(step)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,152 @@
|
|||||||
|
const en = {
|
||||||
|
submit_form: 'Submit Form',
|
||||||
|
type_here: 'Type here',
|
||||||
|
optional: 'optional',
|
||||||
|
select_your_option: 'Select your option',
|
||||||
|
complete_hightlighted_checkboxes_and_click: 'Complete hightlighted checkboxes and click',
|
||||||
|
submit: 'submit',
|
||||||
|
next: 'next',
|
||||||
|
click_to_upload: 'Click to upload',
|
||||||
|
or_drag_and_drop_files: 'or drag and drop files',
|
||||||
|
send_copy_via_email: 'Send copy via email',
|
||||||
|
download: 'Download',
|
||||||
|
form_has_been_completed: 'Form has been completed!',
|
||||||
|
create_a_free_account: 'Create a Free Account',
|
||||||
|
signed_with: 'Signed with',
|
||||||
|
open_source_documents_software: 'open source documents software'
|
||||||
|
}
|
||||||
|
|
||||||
|
const es = {
|
||||||
|
submit_form: 'Enviar Formulario',
|
||||||
|
type_here: 'Escribe aquí',
|
||||||
|
optional: 'opcional',
|
||||||
|
select_your_option: 'Selecciona tu opción',
|
||||||
|
complete_hightlighted_checkboxes_and_click: 'Completa las casillas resaltadas y haz clic',
|
||||||
|
submit: 'enviar',
|
||||||
|
next: 'siguiente',
|
||||||
|
click_to_upload: 'Haz clic para cargar',
|
||||||
|
or_drag_and_drop_files: 'o arrastra y suelta archivos',
|
||||||
|
send_copy_via_email: 'Enviar copia por correo electrónico',
|
||||||
|
download: 'Descargar',
|
||||||
|
form_has_been_completed: '¡El formulario ha sido completado!',
|
||||||
|
create_a_free_account: 'Crear una Cuenta Gratuita',
|
||||||
|
signed_with: 'Firmado con',
|
||||||
|
open_source_documents_software: 'software de documentos de código abierto'
|
||||||
|
}
|
||||||
|
|
||||||
|
const it = {
|
||||||
|
submit_form: 'Invia Modulo',
|
||||||
|
type_here: 'Digita qui',
|
||||||
|
optional: 'opzionale',
|
||||||
|
select_your_option: 'Seleziona la tua opzione',
|
||||||
|
complete_hightlighted_checkboxes_and_click: 'Completa le caselle evidenziate e fai clic',
|
||||||
|
submit: 'invia',
|
||||||
|
next: 'avanti',
|
||||||
|
click_to_upload: 'Clicca per caricare',
|
||||||
|
or_drag_and_drop_files: 'oppure trascina e rilascia i file',
|
||||||
|
send_copy_via_email: 'Invia copia via email',
|
||||||
|
download: 'Scarica',
|
||||||
|
form_has_been_completed: 'Il modulo è stato completato!',
|
||||||
|
create_a_free_account: 'Crea un Account Gratuito',
|
||||||
|
signed_with: 'Firmato con',
|
||||||
|
open_source_documents_software: 'software di documenti open source'
|
||||||
|
}
|
||||||
|
|
||||||
|
const de = {
|
||||||
|
submit_form: 'Formular absenden',
|
||||||
|
type_here: 'Hier eingeben',
|
||||||
|
optional: 'optional',
|
||||||
|
select_your_option: 'Wähle deine Option',
|
||||||
|
complete_hightlighted_checkboxes_and_click: 'Markierte Kontrollkästchen ausfüllen und klicken',
|
||||||
|
submit: 'absenden',
|
||||||
|
next: 'weiter',
|
||||||
|
click_to_upload: 'Klicken zum Hochladen',
|
||||||
|
or_drag_and_drop_files: 'oder Dateien hierher ziehen und ablegen',
|
||||||
|
send_copy_via_email: 'Kopie per E-Mail senden',
|
||||||
|
download: 'Herunterladen',
|
||||||
|
form_has_been_completed: 'Formular wurde ausgefüllt!',
|
||||||
|
create_a_free_account: 'Kostenloses Konto erstellen',
|
||||||
|
signed_with: 'Unterschrieben mit',
|
||||||
|
open_source_documents_software: 'Open-Source-Dokumentensoftware'
|
||||||
|
}
|
||||||
|
|
||||||
|
const fr = {
|
||||||
|
submit_form: 'Envoyer le Formulaire',
|
||||||
|
type_here: 'Tapez ici',
|
||||||
|
optional: 'facultatif',
|
||||||
|
select_your_option: 'Sélectionnez votre option',
|
||||||
|
complete_hightlighted_checkboxes_and_click: 'Complétez les cases à cocher en surbrillance et cliquez',
|
||||||
|
submit: 'envoyer',
|
||||||
|
next: 'suivant',
|
||||||
|
click_to_upload: 'Cliquez pour télécharger',
|
||||||
|
or_drag_and_drop_files: 'ou faites glisser-déposer les fichiers',
|
||||||
|
send_copy_via_email: 'Envoyer une copie par e-mail',
|
||||||
|
download: 'Télécharger',
|
||||||
|
form_has_been_completed: 'Le formulaire a été complété !',
|
||||||
|
create_a_free_account: 'Créer un Compte Gratuit',
|
||||||
|
signed_with: 'Signé avec',
|
||||||
|
open_source_documents_software: 'logiciel de documents open source'
|
||||||
|
}
|
||||||
|
|
||||||
|
const pl = {
|
||||||
|
submit_form: 'Wyślij Formularz',
|
||||||
|
type_here: 'Wpisz tutaj',
|
||||||
|
optional: 'opcjonalny',
|
||||||
|
select_your_option: 'Wybierz swoją opcję',
|
||||||
|
complete_hightlighted_checkboxes_and_click: 'Wypełnij zaznaczone pola wyboru i kliknij',
|
||||||
|
submit: 'wyślij',
|
||||||
|
next: 'dalej',
|
||||||
|
click_to_upload: 'Kliknij, aby przesłać',
|
||||||
|
or_drag_and_drop_files: 'lub przeciągnij i upuść pliki',
|
||||||
|
send_copy_via_email: 'Wyślij kopię drogą mailową',
|
||||||
|
download: 'Pobierz',
|
||||||
|
form_has_been_completed: 'Formularz został wypełniony!',
|
||||||
|
create_a_free_account: 'Utwórz darmowe konto',
|
||||||
|
signed_with: 'Podpisane za pomocą',
|
||||||
|
open_source_documents_software: 'oprogramowanie do dokumentów open source'
|
||||||
|
}
|
||||||
|
|
||||||
|
const uk = {
|
||||||
|
submit_form: 'Надіслати Форму',
|
||||||
|
type_here: 'Введіть тут',
|
||||||
|
optional: 'необов’язково',
|
||||||
|
select_your_option: 'Виберіть варіант',
|
||||||
|
complete_hightlighted_checkboxes_and_click: 'Заповніть позначені прапорці та натисніть',
|
||||||
|
submit: 'надіслати',
|
||||||
|
next: 'далі',
|
||||||
|
click_to_upload: 'Клацніть, щоб завантажити',
|
||||||
|
or_drag_and_drop_files: 'або перетягніть файли сюди',
|
||||||
|
send_copy_via_email: 'Надіслати копію електронною поштою',
|
||||||
|
download: 'Завантажити',
|
||||||
|
form_has_been_completed: 'Форму заповнено!',
|
||||||
|
create_a_free_account: 'Створити безкоштовний обліковий запис',
|
||||||
|
signed_with: 'Підписано за допомогою',
|
||||||
|
open_source_documents_software: 'відкритий програмний засіб для документів'
|
||||||
|
}
|
||||||
|
|
||||||
|
const cs = {
|
||||||
|
submit_form: 'Odeslat formulář',
|
||||||
|
type_here: 'Zadejte zde',
|
||||||
|
optional: 'volitelné',
|
||||||
|
select_your_option: 'Vyberte svou volbu',
|
||||||
|
complete_hightlighted_checkboxes_and_click: 'Označte zvýrazněné zaškrtávací políčka a klikněte na',
|
||||||
|
submit: 'odeslat',
|
||||||
|
next: 'další',
|
||||||
|
click_to_upload: 'Klikněte pro nahrání',
|
||||||
|
or_drag_and_drop_files: 'nebo přetáhněte soubory sem',
|
||||||
|
send_copy_via_email: 'Odeslat kopii e-mailem',
|
||||||
|
download: 'Stáhnout',
|
||||||
|
form_has_been_completed: 'Formulář byl dokončen!',
|
||||||
|
create_a_free_account: 'Vytvořit bezplatný účet',
|
||||||
|
signed_with: 'Podepsáno pomocí',
|
||||||
|
open_source_documents_software: 'open source software pro dokumenty'
|
||||||
|
}
|
||||||
|
|
||||||
|
const i18n = { en, es, it, de, fr, pl, uk, cs }
|
||||||
|
|
||||||
|
const browserLanguage = (navigator.language || navigator.userLanguage || 'en').split('-')[0]
|
||||||
|
|
||||||
|
const t = (key) => i18n[browserLanguage][key] || i18n.en[key] || key
|
||||||
|
|
||||||
|
export default i18n
|
||||||
|
export { t }
|
||||||
@@ -10,6 +10,7 @@
|
|||||||
data-tip="Type text"
|
data-tip="Type text"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
|
id="type_text_button"
|
||||||
class="btn btn-sm btn-circle"
|
class="btn btn-sm btn-circle"
|
||||||
:class="{ 'btn-neutral': isTextSignature, 'btn-outline': !isTextSignature }"
|
:class="{ 'btn-neutral': isTextSignature, 'btn-outline': !isTextSignature }"
|
||||||
@click.prevent="toggleTextInput"
|
@click.prevent="toggleTextInput"
|
||||||
@@ -78,6 +79,7 @@
|
|||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
v-if="isTextSignature"
|
v-if="isTextSignature"
|
||||||
|
id="signature_text_input"
|
||||||
ref="textInput"
|
ref="textInput"
|
||||||
class="base-input !text-2xl w-full mt-6"
|
class="base-input !text-2xl w-full mt-6"
|
||||||
:required="field.required"
|
:required="field.required"
|
||||||
|
|||||||
@@ -226,14 +226,14 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
startResizeCell (e) {
|
startResizeCell (e) {
|
||||||
document.addEventListener('mousemove', this.onResizeCell)
|
this.$el.getRootNode().addEventListener('mousemove', this.onResizeCell)
|
||||||
document.addEventListener('mouseup', this.stopResizeCell)
|
this.$el.getRootNode().addEventListener('mouseup', this.stopResizeCell)
|
||||||
|
|
||||||
this.$emit('start-resize', 'ew')
|
this.$emit('start-resize', 'ew')
|
||||||
},
|
},
|
||||||
stopResizeCell (e) {
|
stopResizeCell (e) {
|
||||||
document.removeEventListener('mousemove', this.onResizeCell)
|
this.$el.getRootNode().removeEventListener('mousemove', this.onResizeCell)
|
||||||
document.removeEventListener('mouseup', this.stopResizeCell)
|
this.$el.getRootNode().removeEventListener('mouseup', this.stopResizeCell)
|
||||||
|
|
||||||
this.$emit('stop-resize')
|
this.$emit('stop-resize')
|
||||||
|
|
||||||
@@ -266,11 +266,13 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
onNameBlur (e) {
|
onNameBlur (e) {
|
||||||
|
const text = this.$refs.name.innerText.trim()
|
||||||
|
|
||||||
this.isNameFocus = false
|
this.isNameFocus = false
|
||||||
this.$refs.name.style.minWidth = ''
|
this.$refs.name.style.minWidth = ''
|
||||||
|
|
||||||
if (e.target.innerText.trim()) {
|
if (text) {
|
||||||
this.field.name = e.target.innerText.trim()
|
this.field.name = text
|
||||||
} else {
|
} else {
|
||||||
this.field.name = ''
|
this.field.name = ''
|
||||||
this.$refs.name.innerText = this.defaultName
|
this.$refs.name.innerText = this.defaultName
|
||||||
@@ -302,14 +304,14 @@ export default {
|
|||||||
|
|
||||||
this.dragFrom = { x: e.clientX - rect.left, y: e.clientY - rect.top }
|
this.dragFrom = { x: e.clientX - rect.left, y: e.clientY - rect.top }
|
||||||
|
|
||||||
document.addEventListener('mousemove', this.drag)
|
this.$el.getRootNode().addEventListener('mousemove', this.drag)
|
||||||
document.addEventListener('mouseup', this.stopDrag)
|
this.$el.getRootNode().addEventListener('mouseup', this.stopDrag)
|
||||||
|
|
||||||
this.$emit('start-drag')
|
this.$emit('start-drag')
|
||||||
},
|
},
|
||||||
stopDrag () {
|
stopDrag () {
|
||||||
document.removeEventListener('mousemove', this.drag)
|
this.$el.getRootNode().removeEventListener('mousemove', this.drag)
|
||||||
document.removeEventListener('mouseup', this.stopDrag)
|
this.$el.getRootNode().removeEventListener('mouseup', this.stopDrag)
|
||||||
|
|
||||||
if (this.isDragged) {
|
if (this.isDragged) {
|
||||||
this.save()
|
this.save()
|
||||||
@@ -322,14 +324,14 @@ export default {
|
|||||||
startResize () {
|
startResize () {
|
||||||
this.selectedAreaRef.value = this.area
|
this.selectedAreaRef.value = this.area
|
||||||
|
|
||||||
document.addEventListener('mousemove', this.resize)
|
this.$el.getRootNode().addEventListener('mousemove', this.resize)
|
||||||
document.addEventListener('mouseup', this.stopResize)
|
this.$el.getRootNode().addEventListener('mouseup', this.stopResize)
|
||||||
|
|
||||||
this.$emit('start-resize', 'nwse')
|
this.$emit('start-resize', 'nwse')
|
||||||
},
|
},
|
||||||
stopResize () {
|
stopResize () {
|
||||||
document.removeEventListener('mousemove', this.resize)
|
this.$el.getRootNode().removeEventListener('mousemove', this.resize)
|
||||||
document.removeEventListener('mouseup', this.stopResize)
|
this.$el.getRootNode().removeEventListener('mouseup', this.stopResize)
|
||||||
|
|
||||||
this.$emit('stop-resize')
|
this.$emit('stop-resize')
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,17 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
style="max-width: 1600px"
|
style="max-width: 1600px"
|
||||||
class="mx-auto pl-4"
|
class="mx-auto pl-4 h-full"
|
||||||
>
|
>
|
||||||
<div class="flex justify-between py-1.5 items-center pr-4">
|
<div class="flex justify-between py-1.5 items-center pr-4">
|
||||||
<div class="flex space-x-3">
|
<div class="flex space-x-3">
|
||||||
<a href="/">
|
<a
|
||||||
|
v-if="withLogoLink"
|
||||||
|
href="/"
|
||||||
|
>
|
||||||
<Logo />
|
<Logo />
|
||||||
</a>
|
</a>
|
||||||
|
<Logo v-else />
|
||||||
<Contenteditable
|
<Contenteditable
|
||||||
:model-value="template.name"
|
:model-value="template.name"
|
||||||
class="text-3xl font-semibold focus:text-clip"
|
class="text-3xl font-semibold focus:text-clip"
|
||||||
@@ -16,46 +20,53 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="space-x-3 flex items-center">
|
<div class="space-x-3 flex items-center">
|
||||||
<a
|
<slot
|
||||||
:href="`/templates/${template.id}/submissions/new`"
|
v-if="$slots.buttons"
|
||||||
data-turbo-frame="modal"
|
name="buttons"
|
||||||
class="btn btn-primary"
|
/>
|
||||||
>
|
<template v-else>
|
||||||
<IconUsersPlus
|
<a
|
||||||
width="20"
|
:href="`/templates/${template.id}/submissions/new`"
|
||||||
class="inline"
|
data-turbo-frame="modal"
|
||||||
/>
|
class="btn btn-primary"
|
||||||
<span class="hidden md:inline">
|
>
|
||||||
Recipients
|
<IconUsersPlus
|
||||||
</span>
|
width="20"
|
||||||
</a>
|
class="inline"
|
||||||
<button
|
/>
|
||||||
class="base-button"
|
<span class="hidden md:inline">
|
||||||
:class="{ disabled: isSaving }"
|
Recipients
|
||||||
v-bind="isSaving ? { disabled: true } : {}"
|
</span>
|
||||||
@click.prevent="onSaveClick"
|
</a>
|
||||||
>
|
<button
|
||||||
<IconInnerShadowTop
|
class="base-button"
|
||||||
v-if="isSaving"
|
:class="{ disabled: isSaving }"
|
||||||
width="20"
|
v-bind="isSaving ? { disabled: true } : {}"
|
||||||
class="animate-spin"
|
@click.prevent="onSaveClick"
|
||||||
/>
|
>
|
||||||
<IconDeviceFloppy
|
<IconInnerShadowTop
|
||||||
v-else
|
v-if="isSaving"
|
||||||
width="20"
|
width="20"
|
||||||
/>
|
class="animate-spin"
|
||||||
<span class="hidden md:inline">
|
/>
|
||||||
Save
|
<IconDeviceFloppy
|
||||||
</span>
|
v-else
|
||||||
</button>
|
width="20"
|
||||||
|
/>
|
||||||
|
<span class="hidden md:inline">
|
||||||
|
Save
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="flex"
|
class="flex"
|
||||||
style="max-height: calc(100vh - 60px)"
|
style="max-height: calc(100% - 60px)"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
ref="previews"
|
ref="previews"
|
||||||
|
:style="{ 'display': isBreakpointLg ? 'none' : 'initial' }"
|
||||||
class="overflow-y-auto overflow-x-hidden w-52 flex-none pr-3 mt-0.5 pt-0.5 hidden lg:block"
|
class="overflow-y-auto overflow-x-hidden w-52 flex-none pr-3 mt-0.5 pt-0.5 hidden lg:block"
|
||||||
>
|
>
|
||||||
<DocumentPreview
|
<DocumentPreview
|
||||||
@@ -73,7 +84,10 @@
|
|||||||
@down="moveDocument(item, 1)"
|
@down="moveDocument(item, 1)"
|
||||||
@change="save"
|
@change="save"
|
||||||
/>
|
/>
|
||||||
<div class="sticky bottom-0 bg-base-100 py-2">
|
<div
|
||||||
|
class="sticky bottom-0 py-2"
|
||||||
|
:class="{ 'bg-base-100': withStickySubmitters }"
|
||||||
|
>
|
||||||
<Upload
|
<Upload
|
||||||
v-if="sortedDocuments.length"
|
v-if="sortedDocuments.length"
|
||||||
:template-id="template.id"
|
:template-id="template.id"
|
||||||
@@ -94,19 +108,46 @@
|
|||||||
@success="updateFromUpload"
|
@success="updateFromUpload"
|
||||||
/>
|
/>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<Document
|
<template
|
||||||
v-for="document in sortedDocuments"
|
v-for="document in sortedDocuments"
|
||||||
:key="document.uuid"
|
:key="document.uuid"
|
||||||
:ref="setDocumentRefs"
|
>
|
||||||
:areas-index="fieldAreasIndex[document.uuid]"
|
<Document
|
||||||
:selected-submitter="selectedSubmitter"
|
:ref="setDocumentRefs"
|
||||||
:document="document"
|
:areas-index="fieldAreasIndex[document.uuid]"
|
||||||
:is-drag="!!dragFieldType"
|
:selected-submitter="selectedSubmitter"
|
||||||
:draw-field="drawField"
|
:document="document"
|
||||||
@draw="onDraw"
|
:is-drag="!!dragFieldType"
|
||||||
@drop-field="onDropfield"
|
:draw-field="drawField"
|
||||||
@remove-area="removeArea"
|
@draw="onDraw"
|
||||||
/>
|
@drop-field="onDropfield"
|
||||||
|
@remove-area="removeArea"
|
||||||
|
/>
|
||||||
|
<DocumentControls
|
||||||
|
v-if="isBreakpointLg"
|
||||||
|
:with-arrows="template.schema.length > 1"
|
||||||
|
:item="template.schema.find((item) => item.attachment_uuid === document.uuid)"
|
||||||
|
:document="document"
|
||||||
|
:template="template"
|
||||||
|
:is-direct-upload="isDirectUpload"
|
||||||
|
class="pb-2 mb-2 border-b border-base-300 border-dashed"
|
||||||
|
@remove="onDocumentRemove"
|
||||||
|
@replace="onDocumentReplace"
|
||||||
|
@up="moveDocument(template.schema.find((item) => item.attachment_uuid === document.uuid), -1)"
|
||||||
|
@down="moveDocument(template.schema.find((item) => item.attachment_uuid === document.uuid), 1)"
|
||||||
|
@change="save"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<div
|
||||||
|
v-if="sortedDocuments.length && isBreakpointLg"
|
||||||
|
class="pb-4"
|
||||||
|
>
|
||||||
|
<Upload
|
||||||
|
:template-id="template.id"
|
||||||
|
:is-direct-upload="isDirectUpload"
|
||||||
|
@success="updateFromUpload"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -122,7 +163,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="relative w-80 flex-none pt-0.5 pr-4 pl-0.5 hidden md:block"
|
class="relative w-80 flex-none mt-1 pr-4 pl-0.5 hidden md:block"
|
||||||
:class="drawField ? 'overflow-hidden' : 'overflow-auto'"
|
:class="drawField ? 'overflow-hidden' : 'overflow-auto'"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@@ -149,6 +190,7 @@
|
|||||||
:fields="template.fields"
|
:fields="template.fields"
|
||||||
:submitters="template.submitters"
|
:submitters="template.submitters"
|
||||||
:selected-submitter="selectedSubmitter"
|
:selected-submitter="selectedSubmitter"
|
||||||
|
:with-sticky-submitters="withStickySubmitters"
|
||||||
@set-draw="drawField = $event"
|
@set-draw="drawField = $event"
|
||||||
@set-drag="dragFieldType = $event"
|
@set-drag="dragFieldType = $event"
|
||||||
@change-submitter="selectedSubmitter = $event"
|
@change-submitter="selectedSubmitter = $event"
|
||||||
@@ -169,6 +211,7 @@ import Document from './document'
|
|||||||
import Logo from './logo'
|
import Logo from './logo'
|
||||||
import Contenteditable from './contenteditable'
|
import Contenteditable from './contenteditable'
|
||||||
import DocumentPreview from './preview'
|
import DocumentPreview from './preview'
|
||||||
|
import DocumentControls from './controls'
|
||||||
import { IconUsersPlus, IconDeviceFloppy, IconInnerShadowTop } from '@tabler/icons-vue'
|
import { IconUsersPlus, IconDeviceFloppy, IconInnerShadowTop } from '@tabler/icons-vue'
|
||||||
import { v4 } from 'uuid'
|
import { v4 } from 'uuid'
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
@@ -182,6 +225,7 @@ export default {
|
|||||||
Logo,
|
Logo,
|
||||||
Dropzone,
|
Dropzone,
|
||||||
DocumentPreview,
|
DocumentPreview,
|
||||||
|
DocumentControls,
|
||||||
IconInnerShadowTop,
|
IconInnerShadowTop,
|
||||||
Contenteditable,
|
Contenteditable,
|
||||||
IconUsersPlus,
|
IconUsersPlus,
|
||||||
@@ -191,6 +235,8 @@ export default {
|
|||||||
return {
|
return {
|
||||||
template: this.template,
|
template: this.template,
|
||||||
save: this.save,
|
save: this.save,
|
||||||
|
baseFetch: this.baseFetch,
|
||||||
|
backgroundColor: this.backgroundColor,
|
||||||
selectedAreaRef: computed(() => this.selectedAreaRef)
|
selectedAreaRef: computed(() => this.selectedAreaRef)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -201,13 +247,39 @@ export default {
|
|||||||
},
|
},
|
||||||
isDirectUpload: {
|
isDirectUpload: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: true,
|
required: false,
|
||||||
default: false
|
default: false
|
||||||
|
},
|
||||||
|
backgroundColor: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
baseUrl: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
withLogoLink: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
withStickySubmitters: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
fetchOptions: {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
default: () => ({ headers: {} })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
documentRefs: [],
|
documentRefs: [],
|
||||||
|
isBreakpointLg: false,
|
||||||
isSaving: false,
|
isSaving: false,
|
||||||
selectedSubmitter: null,
|
selectedSubmitter: null,
|
||||||
drawField: null,
|
drawField: null,
|
||||||
@@ -244,15 +316,28 @@ export default {
|
|||||||
this.selectedSubmitter = this.template.submitters[0]
|
this.selectedSubmitter = this.template.submitters[0]
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.onWindowResize()
|
||||||
|
})
|
||||||
|
|
||||||
document.addEventListener('keyup', this.onKeyUp)
|
document.addEventListener('keyup', this.onKeyUp)
|
||||||
|
|
||||||
|
window.addEventListener('resize', this.onWindowResize)
|
||||||
},
|
},
|
||||||
unmounted () {
|
unmounted () {
|
||||||
document.removeEventListener('keyup', this.onKeyUp)
|
document.removeEventListener('keyup', this.onKeyUp)
|
||||||
|
|
||||||
|
window.removeEventListener('resize', this.onWindowResize)
|
||||||
},
|
},
|
||||||
beforeUpdate () {
|
beforeUpdate () {
|
||||||
this.documentRefs = []
|
this.documentRefs = []
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
onWindowResize (e) {
|
||||||
|
const breakpointLg = 1024
|
||||||
|
|
||||||
|
this.isBreakpointLg = this.$el.getRootNode().querySelector('div').offsetWidth < breakpointLg
|
||||||
|
},
|
||||||
setDocumentRefs (el) {
|
setDocumentRefs (el) {
|
||||||
if (el) {
|
if (el) {
|
||||||
this.documentRefs.push(el)
|
this.documentRefs.push(el)
|
||||||
@@ -474,10 +559,18 @@ export default {
|
|||||||
|
|
||||||
this.selectedAreaRef.value = area
|
this.selectedAreaRef.value = area
|
||||||
},
|
},
|
||||||
|
baseFetch (path, options = {}) {
|
||||||
|
return fetch(this.baseUrl + path, {
|
||||||
|
...options,
|
||||||
|
headers: { ...this.fetchOptions.headers, ...options.headers }
|
||||||
|
})
|
||||||
|
},
|
||||||
save () {
|
save () {
|
||||||
this.$el.closest('template-builder').dataset.template = JSON.stringify(this.template)
|
if (this.$el.closest('template-builder')) {
|
||||||
|
this.$el.closest('template-builder').dataset.template = JSON.stringify(this.template)
|
||||||
|
}
|
||||||
|
|
||||||
return fetch(`/api/templates/${this.template.id}`, {
|
return this.baseFetch(`/api/templates/${this.template.id}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
template: {
|
template: {
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex space-x-2">
|
||||||
|
<Contenteditable
|
||||||
|
class="w-full block mr-6"
|
||||||
|
:model-value="item.name"
|
||||||
|
:icon-width="16"
|
||||||
|
@update:model-value="onUpdateName"
|
||||||
|
/>
|
||||||
|
<ReplaceButton
|
||||||
|
:is-direct-upload="isDirectUpload"
|
||||||
|
:template-id="template.id"
|
||||||
|
@click.stop
|
||||||
|
@success="$emit('replace', { replaceSchemaItem: item, ...$event })"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
v-if="withArrows"
|
||||||
|
class="btn border-base-200 bg-white text-base-content btn-xs rounded hover:text-base-100 hover:bg-base-content hover:border-base-content w-full transition-colors"
|
||||||
|
style="width: 24px; height: 24px"
|
||||||
|
@click.stop="$emit('up', item)"
|
||||||
|
>
|
||||||
|
↑
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
v-if="withArrows"
|
||||||
|
class="btn border-base-200 bg-white text-base-content btn-xs rounded hover:text-base-100 hover:bg-base-content hover:border-base-content w-full transition-colors"
|
||||||
|
style="width: 24px; height: 24px"
|
||||||
|
@click.stop="$emit('down', item)"
|
||||||
|
>
|
||||||
|
↓
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="btn border-base-200 bg-white text-base-content btn-xs rounded hover:text-base-100 hover:bg-base-content hover:border-base-content w-full transition-colors"
|
||||||
|
style="width: 24px; height: 24px"
|
||||||
|
@click.stop="$emit('remove', item)"
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Contenteditable from './contenteditable'
|
||||||
|
import Upload from './upload'
|
||||||
|
import ReplaceButton from './replace'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'DocumentControls',
|
||||||
|
components: {
|
||||||
|
Contenteditable,
|
||||||
|
ReplaceButton
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
item: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
template: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
document: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
isDirectUpload: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
withArrows: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emits: ['change', 'remove', 'up', 'down', 'replace'],
|
||||||
|
mounted () {
|
||||||
|
if (this.isDirectUpload) {
|
||||||
|
import('@rails/activestorage')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
upload: Upload.methods.upload,
|
||||||
|
onUpdateName (value) {
|
||||||
|
this.item.name = value
|
||||||
|
|
||||||
|
this.$emit('change')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -61,6 +61,7 @@ export default {
|
|||||||
IconCloudUpload,
|
IconCloudUpload,
|
||||||
IconInnerShadowTop
|
IconInnerShadowTop
|
||||||
},
|
},
|
||||||
|
inject: ['baseFetch'],
|
||||||
props: {
|
props: {
|
||||||
templateId: {
|
templateId: {
|
||||||
type: [Number, String],
|
type: [Number, String],
|
||||||
|
|||||||
@@ -58,7 +58,7 @@
|
|||||||
<label
|
<label
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
title="Areas"
|
title="Areas"
|
||||||
class="cursor-pointer text-base-100 group-hover:text-base-content"
|
class="cursor-pointer text-transparent group-hover:text-base-content"
|
||||||
>
|
>
|
||||||
<IconShape
|
<IconShape
|
||||||
:width="18"
|
:width="18"
|
||||||
@@ -104,7 +104,7 @@
|
|||||||
<button
|
<button
|
||||||
v-else
|
v-else
|
||||||
title="Areas"
|
title="Areas"
|
||||||
class="relative cursor-pointer text-base-100 group-hover:text-base-content"
|
class="relative cursor-pointer text-transparent group-hover:text-base-content"
|
||||||
@click="$emit('set-draw', field)"
|
@click="$emit('set-draw', field)"
|
||||||
>
|
>
|
||||||
<IconShape
|
<IconShape
|
||||||
@@ -113,7 +113,7 @@
|
|||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="relative text-base-100 group-hover:text-base-content"
|
class="relative text-transparent group-hover:text-base-content"
|
||||||
title="Remove"
|
title="Remove"
|
||||||
@click="$emit('remove', field)"
|
@click="$emit('remove', field)"
|
||||||
>
|
>
|
||||||
@@ -122,7 +122,7 @@
|
|||||||
:stroke-width="1.6"
|
:stroke-width="1.6"
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
<div class="flex flex-col pr-1 text-base-100 group-hover:text-base-content">
|
<div class="flex flex-col pr-1 text-transparent group-hover:text-base-content">
|
||||||
<button
|
<button
|
||||||
title="Up"
|
title="Up"
|
||||||
class="relative"
|
class="relative"
|
||||||
@@ -156,7 +156,7 @@
|
|||||||
</span>
|
</span>
|
||||||
<input
|
<input
|
||||||
v-model="field.options[index]"
|
v-model="field.options[index]"
|
||||||
class="w-full input input-primary input-xs text-sm"
|
class="w-full input input-primary input-xs text-sm bg-transparent"
|
||||||
type="text"
|
type="text"
|
||||||
required
|
required
|
||||||
@blur="save"
|
@blur="save"
|
||||||
@@ -254,8 +254,10 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
onNameBlur (e) {
|
onNameBlur (e) {
|
||||||
if (e.target.innerText.trim()) {
|
const text = this.$refs.name.$refs.contenteditable.innerText.trim()
|
||||||
this.field.name = e.target.innerText.trim()
|
|
||||||
|
if (text) {
|
||||||
|
this.field.name = text
|
||||||
} else {
|
} else {
|
||||||
this.field.name = ''
|
this.field.name = ''
|
||||||
this.$refs.name.$refs.contenteditable.innerText = this.defaultName
|
this.$refs.name.$refs.contenteditable.innerText = this.defaultName
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="sticky -top-1 bg-base-100 pt-1 -mt-1 z-10">
|
<div :class="withStickySubmitters ? 'sticky top-0 z-10' : ''">
|
||||||
<FieldSubmitter
|
<FieldSubmitter
|
||||||
:model-value="selectedSubmitter.uuid"
|
:model-value="selectedSubmitter.uuid"
|
||||||
class="w-full bg-base-100"
|
class="w-full rounded-lg"
|
||||||
|
:class="{ 'bg-base-100': withStickySubmitters }"
|
||||||
:submitters="submitters"
|
:submitters="submitters"
|
||||||
@new-submitter="save"
|
@new-submitter="save"
|
||||||
@remove="removeSubmitter"
|
@remove="removeSubmitter"
|
||||||
@@ -28,7 +29,8 @@
|
|||||||
v-for="(icon, type) in fieldIcons"
|
v-for="(icon, type) in fieldIcons"
|
||||||
:key="type"
|
:key="type"
|
||||||
draggable="true"
|
draggable="true"
|
||||||
class="flex items-center justify-center border border-dashed border-base-300 bg-base-100 w-full rounded relative"
|
class="flex items-center justify-center border border-dashed border-base-300 w-full rounded relative"
|
||||||
|
:style="{ backgroundColor: backgroundColor }"
|
||||||
@dragstart="onDragstart(type)"
|
@dragstart="onDragstart(type)"
|
||||||
@dragend="$emit('drag-end')"
|
@dragend="$emit('drag-end')"
|
||||||
@click="addField(type)"
|
@click="addField(type)"
|
||||||
@@ -100,12 +102,17 @@ export default {
|
|||||||
Field,
|
Field,
|
||||||
FieldSubmitter
|
FieldSubmitter
|
||||||
},
|
},
|
||||||
inject: ['save'],
|
inject: ['save', 'backgroundColor'],
|
||||||
props: {
|
props: {
|
||||||
fields: {
|
fields: {
|
||||||
type: Array,
|
type: Array,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
withStickySubmitters: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
submitters: {
|
submitters: {
|
||||||
type: Array,
|
type: Array,
|
||||||
required: true
|
required: true
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
ref="image"
|
ref="image"
|
||||||
:src="image.url"
|
:src="image.url"
|
||||||
:width="width"
|
:width="width"
|
||||||
class="shadow-md mb-4"
|
class="border rounded mb-4"
|
||||||
:height="height"
|
:height="height"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import Upload from './upload'
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ReplaceDocument',
|
name: 'ReplaceDocument',
|
||||||
|
inject: ['baseFetch'],
|
||||||
props: {
|
props: {
|
||||||
templateId: {
|
templateId: {
|
||||||
type: [Number, String],
|
type: [Number, String],
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ export default {
|
|||||||
IconUpload,
|
IconUpload,
|
||||||
IconInnerShadowTop
|
IconInnerShadowTop
|
||||||
},
|
},
|
||||||
|
inject: ['baseFetch'],
|
||||||
props: {
|
props: {
|
||||||
templateId: {
|
templateId: {
|
||||||
type: [Number, String],
|
type: [Number, String],
|
||||||
@@ -113,7 +114,7 @@ export default {
|
|||||||
|
|
||||||
this.isProcessing = true
|
this.isProcessing = true
|
||||||
|
|
||||||
fetch(`/api/templates/${this.templateId}/documents`, {
|
this.baseFetch(`/api/templates/${this.templateId}/documents`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify({ blobs }),
|
body: JSON.stringify({ blobs }),
|
||||||
headers: { 'Content-Type': 'application/json' }
|
headers: { 'Content-Type': 'application/json' }
|
||||||
@@ -124,7 +125,7 @@ export default {
|
|||||||
this.isProcessing = false
|
this.isProcessing = false
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
fetch(`/api/templates/${this.templateId}/documents`, {
|
this.baseFetch(`/api/templates/${this.templateId}/documents`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: new FormData(this.$refs.form)
|
body: new FormData(this.$refs.form)
|
||||||
}).then(resp => resp.json()).then((data) => {
|
}).then(resp => resp.json()).then((data) => {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ class SubmitterMailer < ApplicationMailer
|
|||||||
end
|
end
|
||||||
|
|
||||||
mail(to: @submitter.email,
|
mail(to: @submitter.email,
|
||||||
|
from: from_address_for_submitter(submitter),
|
||||||
subject:,
|
subject:,
|
||||||
reply_to: submitter.submission.created_by_user&.friendly_name)
|
reply_to: submitter.submission.created_by_user&.friendly_name)
|
||||||
end
|
end
|
||||||
@@ -27,8 +28,18 @@ class SubmitterMailer < ApplicationMailer
|
|||||||
@submitter = submitter
|
@submitter = submitter
|
||||||
@user = user
|
@user = user
|
||||||
|
|
||||||
mail(to: user.email,
|
@email_config = @current_account.account_configs.find_by(key: AccountConfig::SUBMITTER_COMPLETED_EMAIL_KEY)
|
||||||
subject: %(#{submitter.email} has completed the "#{submitter.submission.template.name}" form))
|
|
||||||
|
subject =
|
||||||
|
if @email_config
|
||||||
|
ReplaceEmailVariables.call(@email_config.value['subject'], submitter:)
|
||||||
|
else
|
||||||
|
%(#{submitter.email} has completed the "#{submitter.submission.template.name}" form)
|
||||||
|
end
|
||||||
|
|
||||||
|
mail(from: from_address_for_submitter(submitter),
|
||||||
|
to: user.email,
|
||||||
|
subject:)
|
||||||
end
|
end
|
||||||
|
|
||||||
def documents_copy_email(submitter)
|
def documents_copy_email(submitter)
|
||||||
@@ -43,6 +54,23 @@ class SubmitterMailer < ApplicationMailer
|
|||||||
attachments[attachment.filename.to_s] = attachment.download
|
attachments[attachment.filename.to_s] = attachment.download
|
||||||
end
|
end
|
||||||
|
|
||||||
mail(to: submitter.email, subject: 'Your copy of documents')
|
@email_config = @current_account.account_configs.find_by(key: AccountConfig::SUBMITTER_DOCUMENTS_COPY_EMAIL_KEY)
|
||||||
|
|
||||||
|
subject =
|
||||||
|
if @email_config
|
||||||
|
ReplaceEmailVariables.call(@email_config.value['subject'], submitter:)
|
||||||
|
else
|
||||||
|
'Your copy of documents'
|
||||||
|
end
|
||||||
|
|
||||||
|
mail(from: from_address_for_submitter(submitter),
|
||||||
|
to: submitter.email,
|
||||||
|
subject:)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def from_address_for_submitter(submitter)
|
||||||
|
submitter.submission.created_by_user&.friendly_name || submitter.submission.template.author.friendly_name
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -22,6 +22,8 @@
|
|||||||
#
|
#
|
||||||
class AccountConfig < ApplicationRecord
|
class AccountConfig < ApplicationRecord
|
||||||
SUBMITTER_INVITATION_EMAIL_KEY = 'submitter_invitation_email'
|
SUBMITTER_INVITATION_EMAIL_KEY = 'submitter_invitation_email'
|
||||||
|
SUBMITTER_COMPLETED_EMAIL_KEY = 'submitter_completed_email'
|
||||||
|
SUBMITTER_DOCUMENTS_COPY_EMAIL_KEY = 'submitter_documents_copy_email'
|
||||||
|
|
||||||
DEFAULT_VALUES = {
|
DEFAULT_VALUES = {
|
||||||
SUBMITTER_INVITATION_EMAIL_KEY => {
|
SUBMITTER_INVITATION_EMAIL_KEY => {
|
||||||
@@ -32,6 +34,23 @@ class AccountConfig < ApplicationRecord
|
|||||||
"Please contact us by replying to this email if you didn't request this.\n\n" \
|
"Please contact us by replying to this email if you didn't request this.\n\n" \
|
||||||
"Thanks,\n" \
|
"Thanks,\n" \
|
||||||
'{{account.name}}'
|
'{{account.name}}'
|
||||||
|
},
|
||||||
|
SUBMITTER_COMPLETED_EMAIL_KEY => {
|
||||||
|
'subject' => '{{submitter.email}} has completed the "{{template.name}}" form',
|
||||||
|
'body' => "Hi,\n\n" \
|
||||||
|
"{{submitter.email}} has completed the \"{{template.name}}\" form:\n\n" \
|
||||||
|
"{{submission.link}}\n\n" \
|
||||||
|
"Thanks,\n" \
|
||||||
|
'{{account.name}}'
|
||||||
|
},
|
||||||
|
SUBMITTER_DOCUMENTS_COPY_EMAIL_KEY => {
|
||||||
|
'subject' => 'Your copy of documents',
|
||||||
|
'body' => "Hi there,\n\n" \
|
||||||
|
"Please check the copy of your \"{{template.name}}\" submission in the email attachments.\n" \
|
||||||
|
"Alternatively, you can download the copy using:\n\n" \
|
||||||
|
"{{documents.links}}\n\n" \
|
||||||
|
"Thanks,\n" \
|
||||||
|
'{{account.name}}'
|
||||||
}
|
}
|
||||||
}.freeze
|
}.freeze
|
||||||
|
|
||||||
|
|||||||
@@ -21,11 +21,13 @@
|
|||||||
# fk_rails_... (account_id => accounts.id)
|
# fk_rails_... (account_id => accounts.id)
|
||||||
#
|
#
|
||||||
class EncryptedConfig < ApplicationRecord
|
class EncryptedConfig < ApplicationRecord
|
||||||
FILES_STORAGE_KEY = 'active_storage'
|
CONFIG_KEYS = [
|
||||||
EMAIL_SMTP_KEY = 'action_mailer_smtp'
|
FILES_STORAGE_KEY = 'active_storage',
|
||||||
ESIGN_CERTS_KEY = 'esign_certs'
|
EMAIL_SMTP_KEY = 'action_mailer_smtp',
|
||||||
APP_URL_KEY = 'app_url'
|
ESIGN_CERTS_KEY = 'esign_certs',
|
||||||
WEBHOOK_URL_KEY = 'webhook_url'
|
APP_URL_KEY = 'app_url',
|
||||||
|
WEBHOOK_URL_KEY = 'webhook_url'
|
||||||
|
].freeze
|
||||||
|
|
||||||
belongs_to :account
|
belongs_to :account
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,10 @@ class Submission < ApplicationRecord
|
|||||||
|
|
||||||
attribute :source, :string, default: 'link'
|
attribute :source, :string, default: 'link'
|
||||||
|
|
||||||
|
has_many :template_schema_documents,
|
||||||
|
->(e) { where(uuid: (e.template_schema.presence || e.template.schema).pluck('attachment_uuid')) },
|
||||||
|
through: :template, source: :documents_attachments
|
||||||
|
|
||||||
scope :active, -> { where(deleted_at: nil) }
|
scope :active, -> { where(deleted_at: nil) }
|
||||||
|
|
||||||
enum :source, {
|
enum :source, {
|
||||||
|
|||||||
@@ -6,9 +6,11 @@
|
|||||||
#
|
#
|
||||||
# id :bigint not null, primary key
|
# id :bigint not null, primary key
|
||||||
# completed_at :datetime
|
# completed_at :datetime
|
||||||
# email :string not null
|
# email :string
|
||||||
# ip :string
|
# ip :string
|
||||||
|
# name :string
|
||||||
# opened_at :datetime
|
# opened_at :datetime
|
||||||
|
# phone :string
|
||||||
# sent_at :datetime
|
# sent_at :datetime
|
||||||
# slug :string not null
|
# slug :string not null
|
||||||
# ua :string
|
# ua :string
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
# name :string not null
|
# name :string not null
|
||||||
# schema :text not null
|
# schema :text not null
|
||||||
# slug :string not null
|
# slug :string not null
|
||||||
|
# source :text not null
|
||||||
# submitters :text not null
|
# submitters :text not null
|
||||||
# created_at :datetime not null
|
# created_at :datetime not null
|
||||||
# updated_at :datetime not null
|
# updated_at :datetime not null
|
||||||
@@ -37,6 +38,7 @@ class Template < ApplicationRecord
|
|||||||
attribute :schema, :string, default: -> { [] }
|
attribute :schema, :string, default: -> { [] }
|
||||||
attribute :submitters, :string, default: -> { [{ name: DEFAULT_SUBMITTER_NAME, uuid: SecureRandom.uuid }] }
|
attribute :submitters, :string, default: -> { [{ name: DEFAULT_SUBMITTER_NAME, uuid: SecureRandom.uuid }] }
|
||||||
attribute :slug, :string, default: -> { SecureRandom.base58(14) }
|
attribute :slug, :string, default: -> { SecureRandom.base58(14) }
|
||||||
|
attribute :source, :string, default: 'native'
|
||||||
|
|
||||||
serialize :fields, JSON
|
serialize :fields, JSON
|
||||||
serialize :schema, JSON
|
serialize :schema, JSON
|
||||||
|
|||||||
+15
-7
@@ -11,8 +11,8 @@
|
|||||||
# email :string not null
|
# email :string not null
|
||||||
# encrypted_password :string not null
|
# encrypted_password :string not null
|
||||||
# failed_attempts :integer default(0), not null
|
# failed_attempts :integer default(0), not null
|
||||||
# first_name :string not null
|
# first_name :string
|
||||||
# last_name :string not null
|
# last_name :string
|
||||||
# last_sign_in_at :datetime
|
# last_sign_in_at :datetime
|
||||||
# last_sign_in_ip :string
|
# last_sign_in_ip :string
|
||||||
# locked_at :datetime
|
# locked_at :datetime
|
||||||
@@ -40,20 +40,24 @@
|
|||||||
# fk_rails_... (account_id => accounts.id)
|
# fk_rails_... (account_id => accounts.id)
|
||||||
#
|
#
|
||||||
class User < ApplicationRecord
|
class User < ApplicationRecord
|
||||||
ROLES = %w[admin].freeze
|
ROLES = [
|
||||||
|
ADMIN_ROLE = 'admin'
|
||||||
|
].freeze
|
||||||
|
|
||||||
EMAIL_REGEXP = /[^@,\s]+@[^@,\s]+/
|
EMAIL_REGEXP = /[^@,\s]+@[^@,\s]+/
|
||||||
|
|
||||||
belongs_to :account
|
belongs_to :account
|
||||||
has_one :access_token, dependent: :destroy
|
has_one :access_token, dependent: :destroy
|
||||||
|
has_many :templates, dependent: :destroy, foreign_key: :author_id, inverse_of: :author
|
||||||
|
|
||||||
devise :database_authenticatable, :recoverable, :rememberable, :validatable, :trackable
|
devise :database_authenticatable, :recoverable, :rememberable, :validatable, :trackable
|
||||||
devise :registerable, :omniauthable, omniauth_providers: [:google_oauth2] if Docuseal.multitenant?
|
devise :registerable, :omniauthable, omniauth_providers: [:google_oauth2] if Docuseal.multitenant?
|
||||||
|
|
||||||
attribute :role, :string, default: 'admin'
|
attribute :role, :string, default: ADMIN_ROLE
|
||||||
attribute :uuid, :string, default: -> { SecureRandom.uuid }
|
attribute :uuid, :string, default: -> { SecureRandom.uuid }
|
||||||
|
|
||||||
scope :active, -> { where(deleted_at: nil) }
|
scope :active, -> { where(deleted_at: nil) }
|
||||||
|
scope :admins, -> { where(role: ADMIN_ROLE) }
|
||||||
|
|
||||||
def access_token
|
def access_token
|
||||||
super || build_access_token.tap(&:save!)
|
super || build_access_token.tap(&:save!)
|
||||||
@@ -68,14 +72,18 @@ class User < ApplicationRecord
|
|||||||
end
|
end
|
||||||
|
|
||||||
def initials
|
def initials
|
||||||
[first_name.first, last_name.first].join.upcase
|
[first_name&.first, last_name&.first].compact_blank.join.upcase
|
||||||
end
|
end
|
||||||
|
|
||||||
def full_name
|
def full_name
|
||||||
[first_name, last_name].join(' ')
|
[first_name, last_name].compact_blank.join(' ')
|
||||||
end
|
end
|
||||||
|
|
||||||
def friendly_name
|
def friendly_name
|
||||||
"#{full_name} <#{email}>"
|
if full_name.present?
|
||||||
|
"#{full_name} <#{email}>"
|
||||||
|
else
|
||||||
|
email
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -59,7 +59,8 @@
|
|||||||
{
|
{
|
||||||
"submitters": [
|
"submitters": [
|
||||||
{
|
{
|
||||||
"name": "<%= current_account.templates.last ? current_account.templates.last.submitters.first['name'] : 'First Submitter' %>",
|
"name": "John Doe",
|
||||||
|
"role": "<%= current_account.templates.last ? current_account.templates.last.submitters.first['name'] : 'First Submitter' %>",
|
||||||
"email": "<%= current_user.email.sub('@', '+test@') %>",
|
"email": "<%= current_user.email.sub('@', '+test@') %>",
|
||||||
"values": {
|
"values": {
|
||||||
"Form Text Field Name": "Default Value"
|
"Form Text Field Name": "Default Value"
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
<div class="pb-6 pt-1 space-y-1">
|
<div class="pb-6 pt-1 space-y-1">
|
||||||
<p class="flex items-center space-x-1 text-xs text-base-content/60">
|
<p class="flex items-center space-x-1 text-xs text-base-content/60">
|
||||||
<%= svg_icon('user', class: 'w-4 h-4') %>
|
<%= svg_icon('user', class: 'w-4 h-4') %>
|
||||||
<span><%= template.author.full_name %></span>
|
<span><%= template.author.full_name.presence || template.author.email %></span>
|
||||||
</p>
|
</p>
|
||||||
<p class="flex items-center space-x-1 text-xs text-base-content/60">
|
<p class="flex items-center space-x-1 text-xs text-base-content/60">
|
||||||
<%= svg_icon('calendar', class: 'w-4 h-4') %>
|
<%= svg_icon('calendar', class: 'w-4 h-4') %>
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
<div class="max-w-xl mx-auto px-2">
|
<div class="max-w-xl mx-auto px-2">
|
||||||
<h1 class="text-3xl font-bold text-center my-8">Profile Details</h1>
|
<h1 class="text-3xl font-bold text-center my-8">Profile Details</h1>
|
||||||
<%= form_for('', as: resource_name, html: { class: 'space-y-6' }, url: new_registration_path) do |f| %>
|
<%= form_for('', as: resource_name, html: { class: 'space-y-6' }, data: { turbo: params[:redir].blank? }, url: new_registration_path) do |f| %>
|
||||||
|
<% if params[:redir].present? %>
|
||||||
|
<%= hidden_field_tag :redir, params[:redir] %>
|
||||||
|
<% end %>
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<%= render 'devise/shared/error_messages', resource: %>
|
<%= render 'devise/shared/error_messages', resource: %>
|
||||||
<%= f.fields_for resource do |ff| %>
|
<%= f.fields_for resource do |ff| %>
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
<div class="max-w-xl mx-auto px-2">
|
<div class="max-w-xl mx-auto px-2">
|
||||||
|
<%= render 'devise/shared/select_server' if Docuseal.multitenant? %>
|
||||||
<h1 class="text-3xl font-bold text-center my-8">Create Free Account</h1>
|
<h1 class="text-3xl font-bold text-center my-8">Create Free Account</h1>
|
||||||
<%= form_for(User.new, html: { class: 'space-y-6' }, url: new_registration_path, method: :get) do |f| %>
|
<%= form_for(User.new, html: { class: 'space-y-6' }, url: new_registration_path, data: { turbo: params[:redir].blank? }, method: :get) do |f| %>
|
||||||
|
<% if params[:redir].present? %>
|
||||||
|
<%= hidden_field_tag :redir, params[:redir] %>
|
||||||
|
<% end %>
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<%= f.label :email, class: 'label' %>
|
<%= f.label :email, class: 'label' %>
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
<div class="max-w-xl mx-auto px-2">
|
<div class="max-w-xl mx-auto px-2">
|
||||||
|
<%= render 'devise/shared/select_server' if Docuseal.multitenant? %>
|
||||||
<h1 class="text-4xl font-bold text-center my-8">Log In</h1>
|
<h1 class="text-4xl font-bold text-center my-8">Log In</h1>
|
||||||
<%= form_for(resource, as: resource_name, html: { class: 'space-y-6' }, url: session_path(resource_name)) do |f| %>
|
<%= form_for(resource, as: resource_name, html: { class: 'space-y-6' }, data: { turbo: params[:redir].blank? }, url: session_path(resource_name)) do |f| %>
|
||||||
|
<% if params[:redir].present? %>
|
||||||
|
<%= hidden_field_tag :redir, params[:redir] %>
|
||||||
|
<% end %>
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<%= f.label :email, class: 'label' %>
|
<%= f.label :email, class: 'label' %>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<%= link_to 'Log in', new_session_path(resource_name), class: 'link link-hover' %>
|
<%= link_to 'Log in', new_session_path(resource_name), class: 'link link-hover' %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%- if devise_mapping.registerable? && controller_name != 'registrations' %>
|
<%- if devise_mapping.registerable? && controller_name != 'registrations' %>
|
||||||
<%= link_to 'Create free account', registration_path, class: 'link link-hover' %>
|
<%= link_to 'Create free account', registration_path({ redir: params[:redir] }.compact_blank), class: 'link link-hover' %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %>
|
<%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %>
|
||||||
<%= link_to 'Forgot your password?', new_password_path(resource_name), class: 'link link-hover' %>
|
<%= link_to 'Forgot your password?', new_password_path(resource_name), class: 'link link-hover' %>
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<div class="text-center">
|
||||||
|
<div class="join">
|
||||||
|
<a href="https://docuseal.co<%= request.fullpath.gsub('docuseal.eu', 'docuseal.co') %>" class="btn bg-base-200 join-item w-32 <%= 'bg-base-300' if request.host == 'docuseal.co' %>">
|
||||||
|
🌎 Global
|
||||||
|
</a>
|
||||||
|
<a href="https://docuseal.eu<%= request.fullpath.gsub('docuseal.co', 'docuseal.eu') %>" class="btn bg-base-200 join-item w-32 <%= 'bg-base-300' if request.host == 'docuseal.eu' %>">
|
||||||
|
🇪🇺 Europe
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
Whitespace-only changes.
@@ -1,41 +0,0 @@
|
|||||||
<div class="flex flex-wrap space-y-4 md:flex-nowrap md:space-y-0">
|
|
||||||
<%= render 'shared/settings_nav' %>
|
|
||||||
<div class="flex-grow max-w-xl mx-auto">
|
|
||||||
<h1 class="text-4xl font-bold mb-4">PDF Signature</h1>
|
|
||||||
<div id="result">
|
|
||||||
<p class="mb-2">
|
|
||||||
Upload signed PDF file to validate its signature:
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<%= form_for '', url: settings_esign_index_path, method: :post, html: { enctype: 'multipart/form-data' } do |f| %>
|
|
||||||
<%= f.button type: 'submit', class: 'flex' do %>
|
|
||||||
<div class="disabled mb-3">
|
|
||||||
<%= svg_icon('loader', class: 'w-5 h-5 animate-spin inline') %>
|
|
||||||
Analyzing...
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
<file-dropzone data-is-direct-upload="false" data-name="verify_attachments" data-submit-on-upload="true" class="w-full">
|
|
||||||
<label for="file" class="w-full block h-32 relative bg-base-200 hover:bg-base-200/70 rounded-md border border-base-content border-dashed">
|
|
||||||
<div class="absolute top-0 right-0 left-0 bottom-0 flex items-center justify-center">
|
|
||||||
<div class="flex flex-col items-center">
|
|
||||||
<span data-target="file-dropzone.icon">
|
|
||||||
<%= svg_icon('cloud_upload', class: 'w-10 h-10') %>
|
|
||||||
</span>
|
|
||||||
<span data-target="file-dropzone.loading" class="hidden">
|
|
||||||
<%= svg_icon('loader', class: 'w-10 h-10 animate-spin') %>
|
|
||||||
</span>
|
|
||||||
<div class="font-medium mb-1">
|
|
||||||
Verify Signed PDF
|
|
||||||
</div>
|
|
||||||
<div class="text-xs">
|
|
||||||
<span class="font-medium">Click to upload</span> or drag and drop files
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<input id="file" name="files[]" class="hidden" data-action="change:file-dropzone#onSelectFiles" data-target="file-dropzone.input" type="file" accept="application/pdf" multiple>
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
</file-dropzone>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
<div class="w-0 md:w-52"></div>
|
|
||||||
</div>
|
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<%= render 'shared/turbo_modal', title: 'Upload Certificate' do %>
|
||||||
|
<%= form_for @cert_record, url: settings_esign_path, html: { class: 'space-y-4', enctype: 'multipart/form-data' }, data: { turbo_frame: :_top } do |f| %>
|
||||||
|
<div class="space-y-2">
|
||||||
|
<div class="form-control">
|
||||||
|
<%= f.label :name, class: 'label' %>
|
||||||
|
<%= f.text_field :name, required: true, class: 'base-input' %>
|
||||||
|
</div>
|
||||||
|
<div class="form-control">
|
||||||
|
<%= f.label :file, class: 'label' %>
|
||||||
|
<%= f.file_field :file, required: true %>
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text-alt">Use a valid .der, .p12 or .pfx file.</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-control">
|
||||||
|
<%= f.label :password, 'Password (optional)', class: 'label' %>
|
||||||
|
<%= f.text_field :password, class: 'base-input' %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-control pt-2">
|
||||||
|
<%= f.button button_title, class: 'base-button' %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
<div class="flex flex-wrap space-y-4 md:flex-nowrap md:space-y-0 md:space-x-10">
|
||||||
|
<%= render 'shared/settings_nav' %>
|
||||||
|
<div class="flex-grow">
|
||||||
|
<div class="max-w-xl">
|
||||||
|
<h1 class="text-4xl font-bold mb-4">PDF Signature</h1>
|
||||||
|
<div id="result">
|
||||||
|
<p class="mb-2">
|
||||||
|
Upload signed PDF file to validate its signature:
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<%= form_for '', url: verify_pdf_signature_index_path, method: :post, html: { enctype: 'multipart/form-data' } do |f| %>
|
||||||
|
<%= f.button type: 'submit', class: 'flex' do %>
|
||||||
|
<div class="disabled mb-3">
|
||||||
|
<%= svg_icon('loader', class: 'w-5 h-5 animate-spin inline') %>
|
||||||
|
Analyzing...
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
<file-dropzone data-is-direct-upload="false" data-name="verify_attachments" data-submit-on-upload="true" class="w-full">
|
||||||
|
<label for="file" class="w-full block h-32 relative bg-base-200 hover:bg-base-200/70 rounded-md border border-base-content border-dashed">
|
||||||
|
<div class="absolute top-0 right-0 left-0 bottom-0 flex items-center justify-center">
|
||||||
|
<div class="flex flex-col items-center">
|
||||||
|
<span data-target="file-dropzone.icon">
|
||||||
|
<%= svg_icon('cloud_upload', class: 'w-10 h-10') %>
|
||||||
|
</span>
|
||||||
|
<span data-target="file-dropzone.loading" class="hidden">
|
||||||
|
<%= svg_icon('loader', class: 'w-10 h-10 animate-spin') %>
|
||||||
|
</span>
|
||||||
|
<div class="font-medium mb-1">
|
||||||
|
Verify Signed PDF
|
||||||
|
</div>
|
||||||
|
<div class="text-xs">
|
||||||
|
<span class="font-medium">Click to upload</span> or drag and drop files
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<input id="file" name="files[]" class="hidden" data-action="change:file-dropzone#onSelectFiles" data-target="file-dropzone.input" type="file" accept="application/pdf" multiple>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</file-dropzone>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between items-end mb-4 mt-8">
|
||||||
|
<h2 class="text-3xl font-bold">Signing Certificates</h2>
|
||||||
|
<%= link_to new_settings_esign_path, class: 'btn btn-primary btn-md', data: { turbo_frame: 'modal' } do %>
|
||||||
|
<%= svg_icon('plus', class: 'w-6 h-6') %>
|
||||||
|
<span>Upload Cert</span>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<%= render 'alert' %>
|
||||||
|
<div class="overflow-x-auto">
|
||||||
|
<table class="table w-full table-lg rounded-b-none overflow-hidden">
|
||||||
|
<thead class="bg-base-200">
|
||||||
|
<tr class="text-neutral uppercase">
|
||||||
|
<th>
|
||||||
|
Name
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
Valid To
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
Status
|
||||||
|
</th>
|
||||||
|
<th class="text-right" width="1px">
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<% @pkcs_list.each do |item| %>
|
||||||
|
<tr scope="row" class="group">
|
||||||
|
<td>
|
||||||
|
<%= item['name'] %>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<%= l(item['pkcs'].certificate.not_after.to_date, format: :long, locale: current_account.locale) %>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<% if item['status'] == 'default' %>
|
||||||
|
<span class="badge badge-lg badge-info badge-outline">
|
||||||
|
<%= item['status'] %>
|
||||||
|
</span>
|
||||||
|
<% else %>
|
||||||
|
<%= button_to settings_esign_path, method: :put, params: { name: item['name'] }, class: 'btn btn-outline btn-neutral btn-xs whitespace-nowrap', title: 'Delete', data: { turbo_confirm: 'Are you sure?' } do %>
|
||||||
|
Make Default
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<% if item['name'] != EsignSettingsController::DEFAULT_CERT_NAME && item['status'] != 'default' %>
|
||||||
|
<%= button_to settings_esign_path, params: { name: item['name'] }, method: :delete, class: 'btn btn-outline btn-error btn-xs', title: 'Delete', data: { turbo_confirm: 'Are you sure?' } do %>
|
||||||
|
Remove
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<% end %>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -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>
|
||||||
|
<path d="M12 12m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0"></path>
|
||||||
|
<path d="M12 19m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0"></path>
|
||||||
|
<path d="M12 5m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0"></path>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 470 B |
@@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="<%= local_assigns[:class] %>" width="44" height="44" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||||
|
<path d="M5 4h4l2 5l-2.5 1.5a11 11 0 0 0 5 5l1.5 -2.5l5 2v4a2 2 0 0 1 -2 2a16 16 0 0 1 -15 -15a2 2 0 0 1 2 -2"></path>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 409 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="#000000" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<path d="M8 9l3 3l-3 3" />
|
||||||
|
<path d="M13 15l3 0" />
|
||||||
|
<path d="M3 4m0 2a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2z" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 428 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="#000000" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<path d="M10 10m-7 0a7 7 0 1 0 14 0a7 7 0 1 0 -14 0" />
|
||||||
|
<path d="M21 21l-6 -6" />
|
||||||
|
<path d="M7 10l2 2l4 -4" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 394 B |
@@ -0,0 +1,28 @@
|
|||||||
|
<div class="collapse collapse-plus bg-base-200">
|
||||||
|
<input type="checkbox">
|
||||||
|
<div class="collapse-title text-xl font-medium">
|
||||||
|
<div>
|
||||||
|
Documents Copy Email
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="collapse-content">
|
||||||
|
<%= form_for AccountConfigs.find_or_initialize_for_key(current_account, AccountConfig::SUBMITTER_DOCUMENTS_COPY_EMAIL_KEY), url: settings_personalization_path, method: :post, html: { autocomplete: 'off', class: 'space-y-4' } do |f| %>
|
||||||
|
<%= f.hidden_field :key %>
|
||||||
|
<%= f.fields_for :value, Struct.new(:subject, :body).new(*f.object.value.values_at('subject', 'body')) do |ff| %>
|
||||||
|
<div class="form-control">
|
||||||
|
<%= ff.label :subject, class: 'label' %>
|
||||||
|
<%= ff.text_field :subject, required: true, class: 'base-input' %>
|
||||||
|
</div>
|
||||||
|
<div class="form-control">
|
||||||
|
<%= ff.label :body, class: 'label' %>
|
||||||
|
<autoresize-textarea>
|
||||||
|
<%= ff.text_area :body, required: true, class: 'base-input w-full py-2' %>
|
||||||
|
</autoresize-textarea>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
<div class="form-control pt-2">
|
||||||
|
<%= f.button button_title(title: 'Save', disabled_with: 'Saving'), class: 'base-button' %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
<div class="alert my-4">
|
<div class="alert my-4">
|
||||||
<%= svg_icon('info_circle', class: 'w-6 h-6') %>
|
<%= svg_icon('info_circle', class: 'w-6 h-6') %>
|
||||||
<div>
|
<div>
|
||||||
<p class="font-bold">Unlock with DocuSeal Enterprise</p>
|
<p class="font-bold">Unlock with DocuSeal Pro</p>
|
||||||
<p>
|
<p>
|
||||||
Display your company name and logo when signing documents.
|
Display your company name and logo when signing documents.
|
||||||
<br>
|
<br>
|
||||||
<a class="link font-medium" target="_blank" href="<%= "#{Docuseal::PRODUCT_URL}/pricing" %>">Learn More</a>
|
<a class="link font-medium" target="_blank" href="<%= "#{Docuseal::CONSOLE_URL}/#{Docuseal.multitenant? ? 'plans' : 'on_premise'}" %>">Learn More</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<div class="collapse collapse-plus bg-base-200">
|
||||||
|
<input type="checkbox">
|
||||||
|
<div class="collapse-title text-xl font-medium">
|
||||||
|
<div>
|
||||||
|
Signature Request Email
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="collapse-content">
|
||||||
|
<%= form_for AccountConfigs.find_or_initialize_for_key(current_account, AccountConfig::SUBMITTER_INVITATION_EMAIL_KEY), url: settings_personalization_path, method: :post, html: { autocomplete: 'off', class: 'space-y-4' } do |f| %>
|
||||||
|
<%= f.hidden_field :key %>
|
||||||
|
<%= f.fields_for :value, Struct.new(:subject, :body).new(*f.object.value.values_at('subject', 'body')) do |ff| %>
|
||||||
|
<div class="form-control">
|
||||||
|
<%= ff.label :subject, class: 'label' %>
|
||||||
|
<%= ff.text_field :subject, required: true, class: 'base-input' %>
|
||||||
|
</div>
|
||||||
|
<div class="form-control">
|
||||||
|
<%= ff.label :body, class: 'label' %>
|
||||||
|
<autoresize-textarea>
|
||||||
|
<%= ff.text_area :body, required: true, class: 'base-input w-full py-2' %>
|
||||||
|
</autoresize-textarea>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
<div class="form-control pt-2">
|
||||||
|
<%= f.button button_title(title: 'Save', disabled_with: 'Saving'), class: 'base-button' %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<div class="collapse collapse-plus bg-base-200">
|
||||||
|
<input type="checkbox">
|
||||||
|
<div class="collapse-title text-xl font-medium">
|
||||||
|
<div>
|
||||||
|
Form Completed Email
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="collapse-content">
|
||||||
|
<%= form_for AccountConfigs.find_or_initialize_for_key(current_account, AccountConfig::SUBMITTER_COMPLETED_EMAIL_KEY), url: settings_personalization_path, method: :post, html: { autocomplete: 'off', class: 'space-y-4' } do |f| %>
|
||||||
|
<%= f.hidden_field :key %>
|
||||||
|
<%= f.fields_for :value, Struct.new(:subject, :body).new(*f.object.value.values_at('subject', 'body')) do |ff| %>
|
||||||
|
<div class="form-control">
|
||||||
|
<%= ff.label :subject, class: 'label' %>
|
||||||
|
<%= ff.text_field :subject, required: true, class: 'base-input' %>
|
||||||
|
</div>
|
||||||
|
<div class="form-control">
|
||||||
|
<%= ff.label :body, class: 'label' %>
|
||||||
|
<autoresize-textarea>
|
||||||
|
<%= ff.text_area :body, required: true, class: 'base-input w-full py-2' %>
|
||||||
|
</autoresize-textarea>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
<div class="form-control pt-2">
|
||||||
|
<%= f.button button_title(title: 'Save', disabled_with: 'Saving'), class: 'base-button' %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -1,25 +1,12 @@
|
|||||||
<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">Signature Request Email</p>
|
<p class="text-4xl font-bold mb-4">Email Templates</p>
|
||||||
<%= form_for AccountConfigs.find_or_initialize_for_key(current_account, AccountConfig::SUBMITTER_INVITATION_EMAIL_KEY), url: settings_personalization_path, method: :post, html: { autocomplete: 'off', class: 'space-y-4' } do |f| %>
|
<div class="space-y-4">
|
||||||
<%= f.hidden_field :key %>
|
<%= render 'signature_request_email_form' %>
|
||||||
<%= f.fields_for :value, Struct.new(:subject, :body).new(*f.object.value.values_at('subject', 'body')) do |ff| %>
|
<%= render 'submitter_completed_email_form' %>
|
||||||
<div class="form-control">
|
<%= render 'documents_copy_email_form' %>
|
||||||
<%= ff.label :subject, class: 'label' %>
|
</div>
|
||||||
<%= ff.text_field :subject, required: true, class: 'base-input' %>
|
|
||||||
</div>
|
|
||||||
<div class="form-control">
|
|
||||||
<%= ff.label :body, class: 'label' %>
|
|
||||||
<autoresize-textarea>
|
|
||||||
<%= ff.text_area :body, required: true, class: 'base-input w-full py-2' %>
|
|
||||||
</autoresize-textarea>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
<div class="form-control pt-2">
|
|
||||||
<%= f.button button_title(title: 'Save', disabled_with: 'Saving'), class: 'base-button' %>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
<p class="text-4xl font-bold mb-4 mt-8">Company Logo</p>
|
<p class="text-4xl font-bold mb-4 mt-8">Company Logo</p>
|
||||||
<%= render 'logo_form' %>
|
<%= render 'logo_form' %>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1 @@
|
|||||||
<div class="text-center px-2">
|
<%= render 'shared/powered_by' %>
|
||||||
Powered by
|
|
||||||
<a href="<%= Docuseal::PRODUCT_URL %>" class="underline"><%= Docuseal::PRODUCT_NAME %></a> - open source documents software
|
|
||||||
</div>
|
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
</span>
|
</span>
|
||||||
<span class="disabled">
|
<span class="disabled">
|
||||||
<span class="flex items-center justify-center space-x-2">
|
<span class="flex items-center justify-center space-x-2">
|
||||||
<%= local_assigns[:icon_disabled] || svg_icon('loader', class: 'w-6 h-6 animate-spin') %>
|
<%= local_assigns[:icon_disabled] || svg_icon('loader', class: 'w-5 h-5 animate-spin') %>
|
||||||
<span><%= disabled_with %>...</span>
|
<span><%= disabled_with %>...</span>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
@@ -6,12 +6,22 @@
|
|||||||
<span class="hidden md:inline">
|
<span class="hidden md:inline">
|
||||||
<%= local_assigns[:copy_title] || 'Copy' %>
|
<%= local_assigns[:copy_title] || 'Copy' %>
|
||||||
</span>
|
</span>
|
||||||
|
<% if local_assigns[:copy_title_md] %>
|
||||||
|
<span class="inline md:hidden">
|
||||||
|
<%= local_assigns[:copy_title_md] %>
|
||||||
|
</span>
|
||||||
|
<% end %>
|
||||||
</span>
|
</span>
|
||||||
<span class="hidden peer-checked:flex items-center space-x-2">
|
<span class="hidden peer-checked:flex items-center space-x-2">
|
||||||
<%= svg_icon(local_assigns[:copied_icon] || 'clipboard_copy', class: local_assigns[:icon_class] || 'w-6 h-6 text-white') %>
|
<%= svg_icon(local_assigns[:copied_icon] || 'clipboard_copy', class: local_assigns[:icon_class] || 'w-6 h-6 text-white') %>
|
||||||
<span class="hidden md:inline">
|
<span class="hidden md:inline">
|
||||||
<%= local_assigns[:copied_title] || 'Copied' %>
|
<%= local_assigns[:copied_title] || 'Copied' %>
|
||||||
</span>
|
</span>
|
||||||
|
<% if local_assigns[:copied_title_md] %>
|
||||||
|
<span class="inline md:hidden">
|
||||||
|
<%= local_assigns[:copied_title_md] %>
|
||||||
|
</span>
|
||||||
|
<% end %>
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
</clipboard-copy>
|
</clipboard-copy>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<div id="flash" class="absolute top-0 w-full h-0">
|
<div id="flash" class="absolute top-0 w-full h-0 z-20">
|
||||||
<div class="max-w-xl mx-auto mt-1.5">
|
<div class="max-w-xl mx-auto mt-1.5">
|
||||||
<div class="px-4 py-3 rounded-2xl bg-base-200 flex items-center justify-between mx-4 md:mx-0">
|
<div class="px-4 py-3 rounded-2xl bg-base-200 flex items-center justify-between mx-4 md:mx-0">
|
||||||
<div class="flex items-center space-x-3">
|
<div class="flex items-center space-x-3">
|
||||||
|
|||||||
@@ -15,7 +15,14 @@
|
|||||||
<% if Docuseal.demo? %>
|
<% if Docuseal.demo? %>
|
||||||
<%= render 'shared/github_button' %>
|
<%= render 'shared/github_button' %>
|
||||||
<% else %>
|
<% else %>
|
||||||
<%= link_to 'Settings', settings_profile_index_path, class: 'hidden md:inline font-medium text-lg mr-2' %>
|
<div class="flex items-center justify-center space-x-4 mr-1">
|
||||||
|
<%= link_to Docuseal.multitenant? ? console_redirect_index_path : Docuseal::CONSOLE_URL, class: 'hidden md:inline-flex items-center font-medium text-lg', data: { prefetch: false } do %>
|
||||||
|
Console
|
||||||
|
<span class="badge badge-warning ml-1">New</span>
|
||||||
|
<% end %>
|
||||||
|
<span class="hidden md:inline-flex h-3 border-r border-base-content"></span>
|
||||||
|
<%= link_to 'Settings', settings_profile_index_path, class: 'hidden md:inline-flex font-medium text-lg' %>
|
||||||
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<div class="dropdown dropdown-end">
|
<div class="dropdown dropdown-end">
|
||||||
<label tabindex="0" class="cursor-pointer bg-base-content text-purple-300 rounded-full p-2 w-9 justify-center flex">
|
<label tabindex="0" class="cursor-pointer bg-base-content text-purple-300 rounded-full p-2 w-9 justify-center flex">
|
||||||
@@ -23,14 +30,28 @@
|
|||||||
</label>
|
</label>
|
||||||
<ul tabindex="0" class="z-10 dropdown-content p-2 mt-2 shadow menu text-base bg-base-100 rounded-box whitespace-nowrap text-right">
|
<ul tabindex="0" class="z-10 dropdown-content p-2 mt-2 shadow menu text-base bg-base-100 rounded-box whitespace-nowrap text-right">
|
||||||
<li>
|
<li>
|
||||||
<%= link_to settings_profile_index_path do %>
|
<%= link_to settings_profile_index_path, class: 'flex items-center' do %>
|
||||||
<%= svg_icon('adjustments', class: 'w-5 h-5 stroke-2') %>
|
<%= svg_icon('adjustments', class: 'w-5 h-5 stroke-2') %>
|
||||||
<span class="mr-1">Profile</span>
|
<span class="mr-1">Profile</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</li>
|
</li>
|
||||||
|
<% unless Docuseal.demo? %>
|
||||||
|
<li>
|
||||||
|
<%= link_to Docuseal.multitenant? ? console_redirect_index_path : Docuseal::CONSOLE_URL, data: { prefetch: false }, class: 'flex items-center' do %>
|
||||||
|
<%= svg_icon('terminal', class: 'w-5 h-5 stroke-2') %>
|
||||||
|
Console
|
||||||
|
<% end %>
|
||||||
|
</li>
|
||||||
|
<% end %>
|
||||||
<li>
|
<li>
|
||||||
<%= button_to destroy_user_session_path, method: :delete, data: { turbo: false } do %>
|
<%= link_to settings_esign_path, class: 'flex items-center' do %>
|
||||||
<%= svg_icon('logout', class: 'w-5 h-5 stroke-2 inline') %>
|
<%= svg_icon('zoom_check', class: 'w-5 h-5 stroke-2') %>
|
||||||
|
<span class="mr-1">Verify PDF</span>
|
||||||
|
<% end %>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<%= button_to destroy_user_session_path, method: :delete, data: { turbo: false }, class: 'flex items-center' do %>
|
||||||
|
<%= svg_icon('logout', class: 'w-5 h-5 stroke-2 mr-2 inline') %>
|
||||||
<span class="mr-1">Sign out</span>
|
<span class="mr-1">Sign out</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<div class="text-center px-2">
|
||||||
|
Powered by
|
||||||
|
<a href="<%= Docuseal::PRODUCT_URL %>" class="underline"><%= Docuseal::PRODUCT_NAME %></a> - open source documents software
|
||||||
|
</div>
|
||||||
@@ -16,9 +16,12 @@
|
|||||||
<li>
|
<li>
|
||||||
<%= link_to 'Storage', settings_storage_index_path, class: 'text-base hover:bg-base-300' %>
|
<%= link_to 'Storage', settings_storage_index_path, class: 'text-base hover:bg-base-300' %>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<%= link_to 'SMS', settings_sms_path, class: 'text-base hover:bg-base-300' %>
|
||||||
|
</li>
|
||||||
<% end %>
|
<% end %>
|
||||||
<li>
|
<li>
|
||||||
<%= link_to 'Signature', settings_esign_index_path, class: 'text-base hover:bg-base-300' %>
|
<%= link_to 'E-Signature', settings_esign_path, class: 'text-base hover:bg-base-300' %>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<%= link_to 'Team', settings_users_path, class: 'text-base hover:bg-base-300' %>
|
<%= link_to 'Team', settings_users_path, class: 'text-base hover:bg-base-300' %>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<turbo-frame id="modal">
|
<turbo-frame id="modal">
|
||||||
<turbo-modal class="modal modal-open items-start !animate-none overflow-y-auto">
|
<turbo-modal class="modal modal-open items-start !animate-none overflow-y-auto" 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="modal-box pt-4 pb-6 px-6 mt-20 max-h-none">
|
<div class="modal-box pt-4 pb-6 px-6 mt-20 max-h-none">
|
||||||
<% if local_assigns[:title] %>
|
<% if local_assigns[:title] %>
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<div class="alert">
|
||||||
|
<%= svg_icon('info_circle', class: 'w-6 h-6') %>
|
||||||
|
<div>
|
||||||
|
<p class="font-bold">Send signature requests via SMS</p>
|
||||||
|
<p class="text-gray-700">
|
||||||
|
Unlock with DocuSeal Enterprise
|
||||||
|
<br>
|
||||||
|
<a class="link font-medium" target="_blank" href="<%= "#{Docuseal::CONSOLE_URL}/#{Docuseal.multitenant? ? 'plans' : 'on_premise'}" %>">Learn More</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<div class="flex flex-wrap space-y-4 md:flex-nowrap md:space-y-0">
|
||||||
|
<%= render 'shared/settings_nav' %>
|
||||||
|
<div class="flex-grow max-w-xl mx-auto">
|
||||||
|
<h1 class="text-4xl font-bold mb-4">SMS</h1>
|
||||||
|
<%= render 'placeholder' %>
|
||||||
|
</div>
|
||||||
|
<div class="w-0 md:w-52"></div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<a href="<%= annot['value'] %>" rel="noopener noreferrer nofollow" target="_blank" class="flex absolute" style="width: <%= annot['w'] * 100 %>%; height: <%= annot['h'] * 100 %>%; left: <%= annot['x'] * 100 %>%; top: <%= annot['y'] * 100 %>%"></a>
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
<%= form_for '', url: template_submissions_path(template), html: { class: 'space-y-4', autocomplete: 'off' }, data: { turbo_frame: :_top } do |f| %>
|
||||||
|
<dynamic-list class="space-y-4">
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div class="card card-compact bg-base-200" data-targets="dynamic-list.items">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="absolute right-4 top-5">
|
||||||
|
<a href="#" data-action="click:dynamic-list#removeItem" class="<%= template.submitters.size == 1 ? 'right-2' : '-top-3' %> relative block w-6 h-6 rounded-lg text-neutral-700 text-center bg-base-300 p-1 hidden hover:bg-neutral hover:text-white">
|
||||||
|
<%= svg_icon('trash', class: 'w-4 h-4') %>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="grid <%= 'md:grid-cols-2' if template.submitters.size > 1 %> gap-4">
|
||||||
|
<% template.submitters.each do |item| %>
|
||||||
|
<div class="form-control">
|
||||||
|
<% if template.submitters.size > 1 %>
|
||||||
|
<label class="label pt-0 pb-1 text-xs">
|
||||||
|
<span class="label-text"> <%= item['name'] %></span>
|
||||||
|
</label>
|
||||||
|
<% end %>
|
||||||
|
<input type="hidden" name="submission[1][submitters][][uuid]" value="<%= item['uuid'] %>">
|
||||||
|
<input type="text" name="submission[1][submitters][][name]" autocomplete="off" class="input input-sm input-bordered" placeholder="Name" required>
|
||||||
|
<div class="grid <%= 'md:grid-cols-2 gap-1' if template.submitters.size == 1 %>">
|
||||||
|
<input type="email" name="submission[1][submitters][][email]" autocomplete="off" class="input input-sm input-bordered mt-1.5" placeholder="Email (optional)">
|
||||||
|
<input type="tel" pattern="^\+[0-9\s\-]+$" oninvalid="this.value ? this.setCustomValidity('Use internatioanl format: +1xxx...') : ''" oninput="this.setCustomValidity('')" name="submission[1][submitters][][phone]" autocomplete="off" class="input input-sm input-bordered mt-1.5" placeholder="Phone (optional)">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<a href="#" class="btn btn-primary btn-sm w-full flex items-center justify-center" data-action="click:dynamic-list#addItem">
|
||||||
|
<%= svg_icon('user_plus', class: 'w-4 h-4 stroke-2') %>
|
||||||
|
<span>Add Recipient</span>
|
||||||
|
</a>
|
||||||
|
</dynamic-list>
|
||||||
|
<div>
|
||||||
|
<%= render 'send_email', f:, template: %>
|
||||||
|
<%= render 'send_sms', f: %>
|
||||||
|
</div>
|
||||||
|
<div class="form-control">
|
||||||
|
<%= f.button button_title(title: 'Add Recipients'), class: 'base-button' %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
<%= form_for '', url: template_submissions_path(template), html: { class: 'space-y-4', autocomplete: 'off' }, data: { turbo_frame: :_top } do |f| %>
|
||||||
|
<% if template.submitters.size == 1 %>
|
||||||
|
<div class="form-control">
|
||||||
|
<autoresize-textarea>
|
||||||
|
<%= f.text_area :emails, required: true, class: 'base-textarea w-full', placeholder: 'Type emails here...' %>
|
||||||
|
</autoresize-textarea>
|
||||||
|
</div>
|
||||||
|
<% else %>
|
||||||
|
<dynamic-list class="space-y-4">
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div class="card card-compact bg-base-200" data-targets="dynamic-list.items">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="absolute right-4 top-5">
|
||||||
|
<a href="#" data-action="click:dynamic-list#removeItem" class="-top-3 relative block w-6 h-6 rounded-lg text-neutral-700 text-center bg-base-300 p-1 hidden hover:bg-neutral hover:text-white">
|
||||||
|
<%= svg_icon('trash', class: 'w-4 h-4') %>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="grid md:grid-cols-2 gap-4">
|
||||||
|
<% template.submitters.each do |item| %>
|
||||||
|
<div class="form-control">
|
||||||
|
<label class="label pt-0 pb-1 text-xs">
|
||||||
|
<span class="label-text"> <%= item['name'] %></span>
|
||||||
|
</label>
|
||||||
|
<input type="hidden" name="submission[1][submitters][][uuid]" value="<%= item['uuid'] %>">
|
||||||
|
<input type="email" name="submission[1][submitters][][email]" autocomplete="off" class="input input-sm input-bordered" placeholder="Email" required>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<a href="#" class="btn btn-primary btn-sm w-full flex items-center justify-center" data-action="click:dynamic-list#addItem">
|
||||||
|
<%= svg_icon('user_plus', class: 'w-4 h-4 stroke-2') %>
|
||||||
|
<span>Add Recipient</span>
|
||||||
|
</a>
|
||||||
|
</dynamic-list>
|
||||||
|
<% end %>
|
||||||
|
<%= render 'send_email', f:, template: %>
|
||||||
|
<div class="form-control">
|
||||||
|
<%= f.button button_title(title: 'Add Recipients'), class: 'base-button' %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
<%= form_for '', url: template_submissions_path(template), html: { class: 'space-y-4', autocomplete: 'off' }, data: { turbo_frame: :_top } do |f| %>
|
||||||
|
<dynamic-list class="space-y-4">
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div class="card card-compact bg-base-200" data-targets="dynamic-list.items">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="absolute right-4 top-5">
|
||||||
|
<a href="#" data-action="click:dynamic-list#removeItem" class="<%= template.submitters.size == 1 ? 'right-2' : '-top-3' %> relative block w-6 h-6 rounded-lg text-neutral-700 text-center bg-base-300 p-1 hidden hover:bg-neutral hover:text-white">
|
||||||
|
<%= svg_icon('trash', class: 'w-4 h-4') %>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="grid md:grid-cols-2 <%= template.submitters.size > 1 ? 'gap-4' : 'gap-1' %>">
|
||||||
|
<% template.submitters.each do |item| %>
|
||||||
|
<div class="form-control">
|
||||||
|
<% if template.submitters.size > 1 %>
|
||||||
|
<label class="label pt-0 pb-1 text-xs">
|
||||||
|
<span class="label-text"> <%= item['name'] %></span>
|
||||||
|
</label>
|
||||||
|
<% end %>
|
||||||
|
<input type="hidden" name="submission[1][submitters][][uuid]" value="<%= item['uuid'] %>">
|
||||||
|
<input type="tel" pattern="^\+[0-9\s\-]+$" oninvalid="this.value ? this.setCustomValidity('Use internatioanl format: +1xxx...') : ''" oninput="this.setCustomValidity('')" name="submission[1][submitters][][phone]" autocomplete="off" class="input input-sm input-bordered" placeholder="Phone" required>
|
||||||
|
<% if template.submitters.size > 1 %>
|
||||||
|
<input type="text" name="submission[1][submitters][][name]" autocomplete="off" class="input input-sm input-bordered mt-1.5" placeholder="Name (optional)">
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<% if template.submitters.size == 1 %>
|
||||||
|
<div class="form-control flex">
|
||||||
|
<input type="text" name="submission[1][submitters][][name]" autocomplete="off" class="input input-sm input-bordered" placeholder="Name (optional)">
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<a href="#" class="btn btn-primary btn-sm w-full flex items-center justify-center" data-action="click:dynamic-list#addItem">
|
||||||
|
<%= svg_icon('user_plus', class: 'w-4 h-4 stroke-2') %>
|
||||||
|
<span>Add Recipient</span>
|
||||||
|
</a>
|
||||||
|
</dynamic-list>
|
||||||
|
<%= render 'send_sms', f: %>
|
||||||
|
<div class="form-control">
|
||||||
|
<%= f.button button_title(title: 'Add Recipients'), class: 'base-button' %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
<div class="form-control">
|
||||||
|
<% is_smtp_configured = Accounts.can_send_emails?(current_account) %>
|
||||||
|
<%= f.label :send_email, for: uuid = SecureRandom.uuid, class: 'flex items-center cursor-pointer' do %>
|
||||||
|
<%= f.check_box :send_email, id: uuid, class: 'base-checkbox', disabled: !is_smtp_configured, onchange: "window.message_field && message_field.classList.toggle('hidden', !event.currentTarget.checked)" %>
|
||||||
|
<span class="label">Send Email</span>
|
||||||
|
<% end %>
|
||||||
|
<% unless is_smtp_configured %>
|
||||||
|
<div class="alert my-4">
|
||||||
|
<%= svg_icon('info_circle', class: 'w-6 h-6') %>
|
||||||
|
<div>
|
||||||
|
<p class="font-bold">SMTP not Configured</p>
|
||||||
|
<p class="text-gray-700">
|
||||||
|
Configure SMTP settings in order to send emails:
|
||||||
|
<br>
|
||||||
|
<a class="link font-medium" data-turbo-frame="_top" href="<%= settings_email_index_path %>">Go to SMTP settings</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<% unless AccountConfig.exists?(key: AccountConfig::SUBMITTER_INVITATION_EMAIL_KEY) %>
|
||||||
|
<div id="message_field" class="card card-compact bg-base-200 hidden">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="form-control space-y-2">
|
||||||
|
<span class="label-text">Hi there,</span>
|
||||||
|
<autoresize-textarea>
|
||||||
|
<%= f.text_area :message, value: format(SubmitterMailer::DEFAULT_MESSAGE, name: template.name), required: true, class: 'base-textarea !rounded-lg w-full' %>
|
||||||
|
</autoresize-textarea>
|
||||||
|
<span class="label-text">
|
||||||
|
Thanks,
|
||||||
|
<br>
|
||||||
|
<%= current_account.name %>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<div class="form-control">
|
||||||
|
<%= render 'sms_settings/placeholder' %>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<div class="mt-2 mb-1">
|
||||||
|
<div class="tooltip w-full" data-tip="Unlock with Enterpise">
|
||||||
|
<%= link_to submitter.sent_at? ? 'Re-send SMS' : 'Send SMS', "#{Docuseal::CONSOLE_URL}/#{Docuseal.multitenant? ? 'plans' : 'on_premise'}", class: 'btn btn-sm btn-primary text-gray-400 w-full' %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -1,81 +1,26 @@
|
|||||||
<%= render 'shared/turbo_modal', title: 'New Recipients' do %>
|
<%= render 'shared/turbo_modal', title: 'Add New Recipients' do %>
|
||||||
<%= form_for '', url: template_submissions_path(@template), html: { class: 'space-y-4', autocomplete: 'off' }, data: { turbo_frame: :_top } do |f| %>
|
<% options = [['via Email', 'email'], ['via Phone', 'phone'], ['Detailed', 'detailed']] %>
|
||||||
<% if @template.submitters.size == 1 %>
|
<toggle-visible data-element-ids="<%= options.map(&:last).to_json %>" class="relative text-center mt-4 block">
|
||||||
<div class="form-control">
|
<div class="join">
|
||||||
<%= f.label :emails, class: 'label' %>
|
<% options.each_with_index do |(label, value), index| %>
|
||||||
<autoresize-textarea>
|
<span>
|
||||||
<%= f.text_area :emails, required: true, class: 'base-textarea w-full' %>
|
<%= radio_button_tag 'option', value, value == 'email', class: 'peer hidden', data: { action: 'change:toggle-visible#trigger' } %>
|
||||||
</autoresize-textarea>
|
<label for="option_<%= value %>" class="<%= '!rounded-s-full' if index.zero? %> btn btn-focus btn-sm join-item w-28 peer-checked:btn-active normal-case">
|
||||||
</div>
|
<%= label %>
|
||||||
<% else %>
|
</label>
|
||||||
<dynamic-list class="space-y-4">
|
</span>
|
||||||
<div class="space-y-4">
|
|
||||||
<div class="card card-compact bg-base-200" data-targets="dynamic-list.items">
|
|
||||||
<div class="card-body">
|
|
||||||
<div class="absolute right-4 top-5">
|
|
||||||
<a href="#" data-action="click:dynamic-list#removeItem" class="-top-3 relative block w-6 h-6 rounded-lg text-neutral-700 text-center bg-base-300 p-1 hidden hover:bg-neutral hover:text-white">
|
|
||||||
<%= svg_icon('trash', class: 'w-4 h-4') %>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div class="grid md:grid-cols-2 gap-4">
|
|
||||||
<% @template.submitters.each do |item| %>
|
|
||||||
<div class="form-control">
|
|
||||||
<label class="label pt-0 pb-1 text-xs">
|
|
||||||
<span class="label-text"> <%= item['name'] %></span>
|
|
||||||
</label>
|
|
||||||
<input type="hidden" name="submission[1][submitters][][uuid]" value="<%= item['uuid'] %>">
|
|
||||||
<input type="email" name="submission[1][submitters][][email]" value="<%= item['email'] %>" autocomplete="off" class="input input-sm input-bordered" placeholder="Email" required>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<a href="#" class="btn btn-primary btn-sm w-full flex items-center justify-center" data-action="click:dynamic-list#addItem">
|
|
||||||
<%= svg_icon('user_plus', class: 'w-4 h-4 stroke-2') %>
|
|
||||||
<span>Add Recipient</span>
|
|
||||||
</a>
|
|
||||||
</dynamic-list>
|
|
||||||
<% end %>
|
|
||||||
<div class="form-control">
|
|
||||||
<% is_smtp_configured = Accounts.can_send_emails?(current_account) %>
|
|
||||||
<%= f.label :send_email, class: 'flex items-center cursor-pointer' do %>
|
|
||||||
<%= f.check_box :send_email, class: 'base-checkbox', disabled: !is_smtp_configured, onchange: "window.message_field && message_field.classList.toggle('hidden', !event.currentTarget.checked)" %>
|
|
||||||
<span class="label">Send Email</span>
|
|
||||||
<% end %>
|
|
||||||
<% unless is_smtp_configured %>
|
|
||||||
<div class="alert my-4">
|
|
||||||
<%= svg_icon('info_circle', class: 'w-6 h-6') %>
|
|
||||||
<div>
|
|
||||||
<p class="font-bold">SMTP not Configured</p>
|
|
||||||
<p class="text-gray-700">
|
|
||||||
Configure SMTP settings in order to send emails:
|
|
||||||
<br>
|
|
||||||
<a class="link font-medium" data-turbo-frame="_top" href="<%= settings_email_index_path %>">Go to SMTP settings</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% unless AccountConfig.exists?(key: AccountConfig::SUBMITTER_INVITATION_EMAIL_KEY) %>
|
</toggle-visible>
|
||||||
<div id="message_field" class="card card-compact bg-base-200 hidden">
|
<div class="mt-4">
|
||||||
<div class="card-body">
|
<div id="email">
|
||||||
<div class="form-control space-y-2">
|
<%= render 'email_form', template: @template %>
|
||||||
<span class="label-text">Hi there,</span>
|
|
||||||
<autoresize-textarea>
|
|
||||||
<%= f.text_area :message, value: format(SubmitterMailer::DEFAULT_MESSAGE, name: @template.name), required: true, class: 'base-textarea !rounded-lg w-full' %>
|
|
||||||
</autoresize-textarea>
|
|
||||||
<span class="label-text">
|
|
||||||
Thanks,
|
|
||||||
<br>
|
|
||||||
<%= current_account.name %>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
<div class="form-control">
|
|
||||||
<%= f.button button_title(title: 'Add Recipients'), class: 'base-button' %>
|
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<div id="phone" class="hidden">
|
||||||
|
<%= render 'phone_form', template: @template %>
|
||||||
|
</div>
|
||||||
|
<div id="detailed" class="hidden">
|
||||||
|
<%= render 'detailed_form', template: @template %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
<div class="flex md:max-h-[calc(100vh-60px)]">
|
<div class="flex md:max-h-[calc(100vh-60px)]">
|
||||||
<div class="overflow-y-auto overflow-x-hidden hidden lg:block w-52 flex-none pr-3 mt-0.5 pt-0.5">
|
<div class="overflow-y-auto overflow-x-hidden hidden lg:block w-52 flex-none pr-3 mt-0.5 pt-0.5">
|
||||||
<% (@submission.template_schema || @submission.template.schema).each do |item| %>
|
<% (@submission.template_schema || @submission.template.schema).each do |item| %>
|
||||||
<% document = @submission.template.documents.find { |a| item['attachment_uuid'] == a.uuid } %>
|
<% document = @submission.template_schema_documents.find { |a| item['attachment_uuid'] == a.uuid } %>
|
||||||
<a href="#<%= "page-#{document.uuid}-0" %>" onclick="[event.preventDefault(), window[event.target.closest('a').href.split('#')[1]].scrollIntoView({ behavior: 'smooth', block: 'start' })]" class="block cursor-pointer">
|
<a href="#<%= "page-#{document.uuid}-0" %>" onclick="[event.preventDefault(), window[event.target.closest('a').href.split('#')[1]].scrollIntoView({ behavior: 'smooth', block: 'start' })]" class="block cursor-pointer">
|
||||||
<img src="<%= document.preview_images.first.url %>" width="<%= document.preview_images.first.metadata['width'] %>" height="<%= document.preview_images.first.metadata['height'] %>" class="rounded border" loading="lazy">
|
<img src="<%= document.preview_images.first.url %>" width="<%= document.preview_images.first.metadata['width'] %>" height="<%= document.preview_images.first.metadata['height'] %>" class="rounded border" loading="lazy">
|
||||||
<div class="pb-2 pt-1.5 text-center">
|
<div class="pb-2 pt-1.5 text-center">
|
||||||
@@ -39,11 +39,15 @@
|
|||||||
<% values = @submission.submitters.reduce({}) { |acc, sub| acc.merge(sub.values) } %>
|
<% values = @submission.submitters.reduce({}) { |acc, sub| acc.merge(sub.values) } %>
|
||||||
<% attachments_index = ActiveStorage::Attachment.where(record: @submission.submitters, name: :attachments).preload(:blob).index_by(&:uuid) %>
|
<% attachments_index = ActiveStorage::Attachment.where(record: @submission.submitters, name: :attachments).preload(:blob).index_by(&:uuid) %>
|
||||||
<% (@submission.template_schema || @submission.template.schema).each do |item| %>
|
<% (@submission.template_schema || @submission.template.schema).each do |item| %>
|
||||||
<% document = @submission.template.documents.find { |e| e.uuid == item['attachment_uuid'] } %>
|
<% document = @submission.template_schema_documents.find { |e| e.uuid == item['attachment_uuid'] } %>
|
||||||
|
<% document_annots_index = document.metadata.dig('pdf', 'annotations')&.group_by { |e| e['page'] } || {} %>
|
||||||
<% document.preview_images.sort_by { |a| a.filename.base.to_i }.each_with_index do |page, index| %>
|
<% document.preview_images.sort_by { |a| a.filename.base.to_i }.each_with_index do |page, index| %>
|
||||||
<div id="<%= "page-#{document.uuid}-#{index}" %>" class="relative">
|
<div id="<%= "page-#{document.uuid}-#{index}" %>" class="relative">
|
||||||
<img src="<%= page.url %>" width="<%= page.metadata['width'] %>" class="shadow-md mb-4" height="<%= page.metadata['height'] %>" loading="lazy">
|
<img src="<%= page.url %>" width="<%= page.metadata['width'] %>" class="shadow-md mb-4" height="<%= page.metadata['height'] %>" loading="lazy">
|
||||||
<div class="top-0 bottom-0 left-0 right-0 absolute">
|
<div class="top-0 bottom-0 left-0 right-0 absolute">
|
||||||
|
<% document_annots_index[index]&.each do |annot| %>
|
||||||
|
<%= render 'submissions/annotation', annot: %>
|
||||||
|
<% end %>
|
||||||
<% fields_index.dig(document.uuid, index)&.each do |(area, field)| %>
|
<% fields_index.dig(document.uuid, index)&.each do |(area, field)| %>
|
||||||
<% value = values[field['uuid']] %>
|
<% value = values[field['uuid']] %>
|
||||||
<% next if value.blank? %>
|
<% next if value.blank? %>
|
||||||
@@ -68,18 +72,44 @@
|
|||||||
<%= (@submission.template_submitters || @submission.template.submitters).find { |e| e['uuid'] == submitter&.uuid }&.dig('name') || "#{(index + 1).ordinalize} Submitter" %>
|
<%= (@submission.template_submitters || @submission.template.submitters).find { |e| e['uuid'] == submitter&.uuid }&.dig('name') || "#{(index + 1).ordinalize} Submitter" %>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center space-x-1 mt-1">
|
<% if submitter&.name.present? %>
|
||||||
<%= svg_icon('mail', class: 'w-5 h-5') %>
|
<div class="flex items-center space-x-1 mt-1">
|
||||||
<span>
|
<%= svg_icon('user', class: 'w-5 h-5') %>
|
||||||
<%= submitter&.email || 'N/A' %>
|
<span>
|
||||||
</span>
|
<%= submitter&.name %>
|
||||||
</div>
|
</span>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
<% if submitter&.email.present? %>
|
||||||
|
<div class="flex items-center space-x-1 mt-1">
|
||||||
|
<%= svg_icon('mail', class: 'w-5 h-5') %>
|
||||||
|
<span>
|
||||||
|
<%= submitter.email || 'N/A' %>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
<% if submitter&.phone.present? %>
|
||||||
|
<div class="flex items-center space-x-1 mt-1">
|
||||||
|
<%= svg_icon('phone', class: 'w-5 h-5') %>
|
||||||
|
<span>
|
||||||
|
<%= submitter.phone %>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
<div class="flex items-center space-x-1 mt-1">
|
<div class="flex items-center space-x-1 mt-1">
|
||||||
<%= svg_icon('writing', class: 'w-5 h-5') %>
|
<%= svg_icon('writing', class: 'w-5 h-5') %>
|
||||||
<span>
|
<span>
|
||||||
<%= submitter&.completed_at? ? l(submitter.completed_at.in_time_zone(current_account.timezone), format: :long, locale: current_account.locale) : 'Not completed yet' %>
|
<%= submitter&.completed_at? ? l(submitter.completed_at.in_time_zone(current_account.timezone), format: :long, locale: current_account.locale) : 'Not completed yet' %>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<% if submitter && submitter.email && !submitter.completed_at %>
|
||||||
|
<div class="mt-2 mb-1">
|
||||||
|
<%= button_to button_title(title: submitter.sent_at? ? 'Re-send Email' : 'Send Email', disabled_with: 'Sending'), submitter_send_email_index_path(submitter_slug: submitter.slug), class: 'btn btn-sm btn-primary w-full' %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
<% if submitter && submitter.phone && !submitter.completed_at %>
|
||||||
|
<%= render 'send_sms_button', submitter: %>
|
||||||
|
<% end %>
|
||||||
<% if submitter && !submitter.completed_at? %>
|
<% if submitter && !submitter.completed_at? %>
|
||||||
<div class="mt-2 mb-1">
|
<div class="mt-2 mb-1">
|
||||||
<a class="btn btn-sm btn-primary w-full" target="_blank" href="<%= submit_form_path(slug: submitter.slug) %>">
|
<a class="btn btn-sm btn-primary w-full" target="_blank" href="<%= submit_form_path(slug: submitter.slug) %>">
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<%= render 'shared/turbo_modal', title: 'Export', close_after_submit: false do %>
|
||||||
|
<div class="space-y-2">
|
||||||
|
<%= button_to template_submissions_export_index_path(@template), params: { format: :xlsx }, method: :get, data: { turbo_frame: :_top } do %>
|
||||||
|
<div class="flex items-center p-4 text-left rounded-2xl border border-neutral-300 hover:cursor-pointer hover:bg-neutral hover:text-gray-300">
|
||||||
|
<div class="enabled">
|
||||||
|
<%= svg_icon('download', class: 'w-12 h-12 stroke-2 mr-2') %>
|
||||||
|
</div>
|
||||||
|
<div class="disabled">
|
||||||
|
<%= svg_icon('loader', class: 'w-12 h-12 stroke-2 mr-2 animate-spin') %>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<span class="mb-1 text-lg font-semibold">XLSX</span>
|
||||||
|
<p class="text-sm"> Primarily opened with Microsoft Excel. Other options include Google Sheets, LibreOffice Calc, and OpenOffice Calc.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
<%= button_to template_submissions_export_index_path(@template), params: { format: :csv }, method: :get, data: { turbo_frame: :_top } do %>
|
||||||
|
<div class="flex items-center text-left p-4 rounded-2xl border border-neutral-300 hover:cursor-pointer hover:bg-neutral hover:text-gray-300">
|
||||||
|
<div class="enabled">
|
||||||
|
<%= svg_icon('download', class: 'w-12 h-12 stroke-2 mr-2') %>
|
||||||
|
</div>
|
||||||
|
<div class="disabled">
|
||||||
|
<%= svg_icon('loader', class: 'w-12 h-12 stroke-2 mr-2 animate-spin') %>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<span class="mb-1 text-lg font-semibold">CSV</span>
|
||||||
|
<p class="text-sm">Can be opened with Microsoft Excel, Google Sheets, or any text editor like Notepad.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<% data_attachments = attachments_index.values.select { |e| e.record_id == submitter.id }.to_json(only: %i[uuid], methods: %i[url filename content_type]) %>
|
||||||
|
<% data_fields = (submitter.submission.template_fields || submitter.submission.template.fields).select { |f| f['submitter_uuid'] == submitter.uuid }.to_json %>
|
||||||
|
<submission-form data-is-demo="<%= Docuseal.demo? %>" data-is-direct-upload="<%= Docuseal.active_storage_public? %>" data-submitter="<%= submitter.to_json(only: %i[uuid slug email]) %>" data-can-send-email="<%= Accounts.can_send_emails?(Struct.new(:id).new(@submitter.submission.template.account_id)) %>" data-attachments="<%= data_attachments %>" data-fields="<%= data_fields %>" data-authenticity-token="<%= form_authenticity_token %>" data-values="<%= submitter.values.to_json %>"></submission-form>
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<% if Accounts.can_send_emails?(@submitter.submission.template.account) %>
|
<% if Accounts.can_send_emails?(@submitter.submission.template.account) && @submitter.email.present? %>
|
||||||
<%= button_to button_title(title: 'Send copy to Email', disabled_with: 'Sending', icon: svg_icon('mail_forward', class: 'w-6 h-6')), send_submission_email_index_path, params: { submitter_slug: @submitter.slug }, form: { onsubmit: 'event.submitter.disabled = true' }, class: 'white-button w-full' %>
|
<%= button_to button_title(title: 'Send copy to Email', disabled_with: 'Sending', icon: svg_icon('mail_forward', class: 'w-6 h-6')), send_submission_email_index_path, params: { submitter_slug: @submitter.slug }, form: { onsubmit: 'event.submitter.disabled = true' }, class: 'white-button w-full' %>
|
||||||
<div class="divider">OR</div>
|
<div class="divider">OR</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -8,11 +8,15 @@
|
|||||||
<%= render 'banner' %>
|
<%= render 'banner' %>
|
||||||
</div>
|
</div>
|
||||||
<% (@submitter.submission.template_schema || @submitter.submission.template.schema).each do |item| %>
|
<% (@submitter.submission.template_schema || @submitter.submission.template.schema).each do |item| %>
|
||||||
<% document = @submitter.submission.template.documents.find { |a| a.uuid == item['attachment_uuid'] } %>
|
<% document = @submitter.submission.template_schema_documents.find { |a| a.uuid == item['attachment_uuid'] } %>
|
||||||
|
<% document_annots_index = document.metadata.dig('pdf', 'annotations')&.group_by { |e| e['page'] } || {} %>
|
||||||
<% document.preview_images.sort_by { |a| a.filename.base.to_i }.each_with_index do |page, index| %>
|
<% document.preview_images.sort_by { |a| a.filename.base.to_i }.each_with_index do |page, index| %>
|
||||||
<div class="relative my-4 shadow-md">
|
<div class="relative my-4 shadow-md">
|
||||||
<img src="<%= page.url %>" width="<%= page.metadata['width'] %>" height="<%= page.metadata['height'] %>" loading="lazy">
|
<img src="<%= page.url %>" width="<%= page.metadata['width'] %>" height="<%= page.metadata['height'] %>" loading="lazy">
|
||||||
<div id="page-<%= [document.uuid, index].join('-') %>" class="top-0 bottom-0 left-0 right-0 absolute">
|
<div id="page-<%= [document.uuid, index].join('-') %>" class="top-0 bottom-0 left-0 right-0 absolute">
|
||||||
|
<% document_annots_index[index]&.each do |annot| %>
|
||||||
|
<%= render 'submissions/annotation', annot: %>
|
||||||
|
<% end %>
|
||||||
<% fields_index.dig(document.uuid, index)&.each do |(area, field)| %>
|
<% fields_index.dig(document.uuid, index)&.each do |(area, field)| %>
|
||||||
<% value = values[field['uuid']] %>
|
<% value = values[field['uuid']] %>
|
||||||
<% next if value.blank? %>
|
<% next if value.blank? %>
|
||||||
@@ -29,7 +33,7 @@
|
|||||||
<div class="fixed bottom-0 w-full h-0 z-20">
|
<div class="fixed bottom-0 w-full h-0 z-20">
|
||||||
<div class="mx-auto" style="max-width: 1000px">
|
<div class="mx-auto" style="max-width: 1000px">
|
||||||
<div class="relative md:mx-32">
|
<div class="relative md:mx-32">
|
||||||
<submission-form data-is-demo="<%= Docuseal.demo? %>" data-is-direct-upload="<%= Docuseal.active_storage_public? %>" data-submitter-uuid="<%= @submitter.uuid %>" data-submitter-slug="<%= @submitter.slug %>" data-can-send-email="<%= Accounts.can_send_emails?(Struct.new(:id).new(@submitter.submission.template.account_id)) %>" data-attachments="<%= attachments_index.values.select { |e| e.record_id == @submitter.id }.to_json(only: %i[uuid], methods: %i[url filename content_type]) %>" data-fields="<%= (@submitter.submission.template_fields || @submitter.submission.template.fields).select { |f| f['submitter_uuid'] == @submitter.uuid }.to_json %>" data-values="<%= @submitter.values.to_json %>" data-authenticity-token="<%= form_authenticity_token %>"></submission-form>
|
<%= render 'submission_form', attachments_index:, submitter: @submitter %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1,3 +1,7 @@
|
|||||||
<p>Hi <%= @user.first_name %>,</p>
|
<% if @email_config %>
|
||||||
<p><%= @submitter.email %> has completed the "<%= @submitter.submission.template.name %>" form.</p>
|
<%= auto_link(simple_format(h(ReplaceEmailVariables.call(@email_config.value['body'], submitter: @submitter)))) %>
|
||||||
<p><%= link_to 'View Submission', submission_url(@submitter.submission) %></p>
|
<% else %>
|
||||||
|
<p>Hi <%= @user.first_name %>,</p>
|
||||||
|
<p><%= @submitter.email %> has completed the "<%= @submitter.submission.template.name %>" form.</p>
|
||||||
|
<p><%= link_to 'View Submission', submission_url(@submitter.submission) %></p>
|
||||||
|
<% end %>
|
||||||
@@ -1,13 +1,17 @@
|
|||||||
<p>Hi there,</p>
|
<% if @email_config %>
|
||||||
<p>Please check the copy of your "<%= @submitter.submission.template.name %>" submission in the email attachments.</p>
|
<%= auto_link(simple_format(h(ReplaceEmailVariables.call(@email_config.value['body'], submitter: @submitter)))) %>
|
||||||
<p>Alternatively, you can download the copy using:</p>
|
<% else %>
|
||||||
<% @documents.each do |document| %>
|
<p>Hi there,</p>
|
||||||
<ul>
|
<p>Please check the copy of your "<%= @submitter.submission.template.name %>" submission in the email attachments.</p>
|
||||||
<li>
|
<p>Alternatively, you can download the copy using:</p>
|
||||||
<%= link_to document.filename.to_s, rails_blob_url(document) %>
|
<% @documents.each do |document| %>
|
||||||
</li>
|
<ul>
|
||||||
</ul>
|
<li>
|
||||||
|
<%= link_to document.filename.to_s, rails_blob_url(document) %>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<% end %>
|
||||||
|
<p>
|
||||||
|
Thanks,<br><%= @current_account.name %>
|
||||||
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
<p>
|
|
||||||
Thanks,<br><%= @current_account.name %>
|
|
||||||
</p>
|
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
<% status_badges = { 'awaiting' => 'badge-info', 'sent' => 'badge-info', 'completed' => 'badge-success', 'opened' => 'badge-warning' } %>
|
||||||
|
<a href="<%= submission_path(submission) %>" class="bg-base-200 w-full flex flex-col md:flex-row space-y-4 md:space-y-0 md:justify-between rounded-2xl px-6 py-5 md:items-center">
|
||||||
|
<% submitters = (submission.template_submitters || submission.template.submitters).filter_map { |item| submission.submitters.find { |e| e.uuid == item['uuid'] } } %>
|
||||||
|
<% is_submission_completed = submitters.all?(&:completed_at?) %>
|
||||||
|
<% if submitters.size == 1 %>
|
||||||
|
<div>
|
||||||
|
<% submitter = submitters.first %>
|
||||||
|
<div class="flex items-center space-x-4">
|
||||||
|
<span class="flex items-center space-x-3">
|
||||||
|
<div class="tooltip flex" data-tip="<%= l(submitter.status_event_at.in_time_zone(current_account.timezone), format: :short, locale: current_account.locale) %>">
|
||||||
|
<span class="badge <%= status_badges[submitter.status] %> md:w-32 badge-lg bg-opacity-50 uppercase text-sm font-semibold">
|
||||||
|
<%= submitter.status %>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<span class="text-lg break-all flex items-center">
|
||||||
|
<%= submitter.name || submitter.email || submitter.phone %>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex space-x-2 items-center">
|
||||||
|
<% if submitter.completed_at? %>
|
||||||
|
<form onsubmit="event.preventDefault()">
|
||||||
|
<button onclick="event.stopPropagation()">
|
||||||
|
<download-button data-src="<%= submitter_download_index_path(submitter.slug) %>" class="btn btn-sm btn-neutral text-white md:w-36">
|
||||||
|
<span class="flex items-center justify-center space-x-2" data-target="download-button.defaultButton">
|
||||||
|
<%= svg_icon('download', class: 'w-5 h-5 stroke-2') %>
|
||||||
|
<span class="inline">Download</span>
|
||||||
|
</span>
|
||||||
|
<span class="flex items-center justify-center space-x-2 hidden" data-target="download-button.loadingButton">
|
||||||
|
<%= svg_icon('loader', class: 'w-5 h-5 animate-spin') %>
|
||||||
|
<span class="inline">Downloa...</span>
|
||||||
|
</span>
|
||||||
|
</download-button>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
<% else %>
|
||||||
|
<%= render 'shared/clipboard_copy', text: submit_form_url(slug: submitter.slug), class: 'btn btn-sm btn-neutral text-white md:w-36 flex', icon_class: 'w-6 h-6 text-white', copy_title: 'Copy Link', copy_title_md: 'Copy', copied_title_md: 'Copied' %>
|
||||||
|
<% end %>
|
||||||
|
<span class="btn btn-outline btn-sm w-20 md:w-24">View</span>
|
||||||
|
<%= button_to button_title(title: nil, disabled_with: 'Remov', icon: svg_icon('trash', class: 'w-6 h-6')), submission_path(submission), class: 'btn btn-outline btn-sm', title: 'Delete', method: :delete, data: { turbo_confirm: 'Are you sure?' }, onclick: 'event.stopPropagation()' %>
|
||||||
|
</div>
|
||||||
|
<% else %>
|
||||||
|
<div class="space-y-1 w-full md:mr-2">
|
||||||
|
<div class="flex items-center space-x-3">
|
||||||
|
<% if is_submission_completed %>
|
||||||
|
<% latest_submitter = submitters.select(&:completed_at?).max_by(&:completed_at) %>
|
||||||
|
<div class="tooltip flex" data-tip="<%= l(latest_submitter.status_event_at.in_time_zone(current_account.timezone), format: :short, locale: current_account.locale) %>">
|
||||||
|
<span class="badge <%= status_badges[latest_submitter.status] %> md:w-32 bg-opacity-50 badge-lg uppercase text-sm font-semibold">
|
||||||
|
<%= latest_submitter.status %>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
<div class="w-full">
|
||||||
|
<% submitters.each_with_index do |submitter, index| %>
|
||||||
|
<div class="flex justify-between items-center space-x-3">
|
||||||
|
<span class="flex items-center space-x-3">
|
||||||
|
<% unless is_submission_completed %>
|
||||||
|
<div class="tooltip flex" data-tip="<%= l(submitter.status_event_at.in_time_zone(current_account.timezone), format: :short, locale: current_account.locale) %>">
|
||||||
|
<span class="badge md:w-24 <%= status_badges[submitter.status] %> bg-opacity-50 uppercase text-xs font-semibold">
|
||||||
|
<%= submitter.status %>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
<span class="text-lg break-all">
|
||||||
|
<%= submitter.name || submitter.email || submitter.phone %>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<% if submitter.completed_at? && !is_submission_completed %>
|
||||||
|
<form onsubmit="event.preventDefault()">
|
||||||
|
<button onclick="event.stopPropagation()">
|
||||||
|
<download-button data-src="<%= submitter_download_index_path(submitter.slug) %>" class="btn btn-xs btn-neutral text-white md:w-36">
|
||||||
|
<span class="flex items-center justify-center space-x-2" data-target="download-button.defaultButton">
|
||||||
|
<%= svg_icon('download', class: 'w-4 h-4 stroke-2') %>
|
||||||
|
<span class="hidden md:inline">Download</span>
|
||||||
|
</span>
|
||||||
|
<span class="flex items-center justify-center space-x-2 hidden" data-target="download-button.loadingButton">
|
||||||
|
<%= svg_icon('loader', class: 'w-4 h-4 animate-spin') %>
|
||||||
|
<span class="hidden md:inline">Downloa...</span>
|
||||||
|
</span>
|
||||||
|
</download-button>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
<% elsif !is_submission_completed %>
|
||||||
|
<div class="flex items-center space-x-3">
|
||||||
|
<%= render 'shared/clipboard_copy', text: submit_form_url(slug: submitter.slug), class: 'btn btn-xs text-xs btn-neutral text-white md:w-36 flex', icon_class: 'w-4 h-4 text-white', copy_title: 'Copy Link' %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex space-x-2 items-center">
|
||||||
|
<% if is_submission_completed %>
|
||||||
|
<% latest_submitter = submitters.select(&:completed_at?).max_by(&:completed_at) %>
|
||||||
|
<form onsubmit="event.preventDefault()">
|
||||||
|
<button onclick="event.stopPropagation()">
|
||||||
|
<download-button data-src="<%= submitter_download_index_path(latest_submitter.slug) %>" class="btn btn-sm btn-neutral text-white md:w-36">
|
||||||
|
<span class="flex items-center justify-center space-x-2" data-target="download-button.defaultButton">
|
||||||
|
<%= svg_icon('download', class: 'w-5 h-5 stroke-2') %>
|
||||||
|
<span class="inline">Download</span>
|
||||||
|
</span>
|
||||||
|
<span class="flex items-center justify-center space-x-2 hidden" data-target="download-button.loadingButton">
|
||||||
|
<%= svg_icon('loader', class: 'w-5 h-5 animate-spin') %>
|
||||||
|
<span class="inline">Downloa...</span>
|
||||||
|
</span>
|
||||||
|
</download-button>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
<% end %>
|
||||||
|
<span class="btn btn-outline btn-sm w-20 md:w-24">View</span>
|
||||||
|
<%= button_to button_title(title: nil, disabled_with: 'Remov', icon: svg_icon('trash', class: 'w-6 h-6')), submission_path(submission), class: 'btn btn-outline btn-sm', title: 'Delete', method: :delete, data: { turbo_confirm: 'Are you sure?' }, onclick: 'event.stopPropagation()' %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
</a>
|
||||||
@@ -1 +1 @@
|
|||||||
<template-builder data-is-direct-upload="<%= Docuseal.active_storage_public? %>" data-template="<%= @template.as_json.merge(documents: @template.schema_documents.as_json(include: { preview_images: { methods: %i[url metadata filename] } })).to_json %>"></template-builder>
|
<template-builder class="grid" data-is-direct-upload="<%= Docuseal.active_storage_public? %>" data-template="<%= @template.as_json.merge(documents: @template.schema_documents.as_json(include: { preview_images: { methods: %i[url metadata filename] } })).to_json %>"></template-builder>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<div class="flex flex-col md:flex-row space-y-2 md:space-y-0 md:justify-between items-start mb-8">
|
<div class="flex flex-col items-start md:flex-row space-y-2 md:space-y-0 md:justify-between md:items-center mb-8">
|
||||||
<h1 class="text-4xl font-semibold mr-4" style="overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;">
|
<h1 class="text-4xl font-semibold mr-4" style="overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;">
|
||||||
<%= @template.name %>
|
<%= @template.name %>
|
||||||
</h1>
|
</h1>
|
||||||
@@ -17,141 +17,30 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% if !@pagy.count.zero? || @template.submitters.to_a.size == 1 %>
|
<% if !@pagy.count.zero? || @template.submitters.to_a.size == 1 %>
|
||||||
<div class="flex justify-between mb-6 items-end">
|
<div class="flex justify-between mb-6 md:items-end flex-col md:flex-row">
|
||||||
<p class="text-3xl font-bold">Submissions</p>
|
<p class="text-3xl font-bold">Submissions</p>
|
||||||
<div class="flex space-x-2">
|
<div class="flex space-x-2 mt-3 md:mt-0">
|
||||||
|
<%= link_to new_template_submissions_export_path(@template), class: 'order-3 md:order-1 btn btn-ghost text-base', data: { turbo_frame: 'modal' } do %>
|
||||||
|
<%= svg_icon('download', class: 'w-6 h-6 stroke-2') %>
|
||||||
|
<span>Export</span>
|
||||||
|
<% end %>
|
||||||
<% if @template.submitters.to_a.size == 1 %>
|
<% if @template.submitters.to_a.size == 1 %>
|
||||||
<%= render 'shared/clipboard_copy', text: start_form_url(slug: @template.slug), class: 'base-button', icon_class: 'w-6 h-6 text-white', copy_title: 'Copy Share Link', copied_title: 'Copied to Clipboard' %>
|
<span class="order-1">
|
||||||
|
<%= render 'shared/clipboard_copy', text: start_form_url(slug: @template.slug), class: 'base-button', icon_class: 'w-6 h-6 text-white', copy_title: 'Copy Share Link', copied_title: 'Copied to Clipboard', copy_title_md: 'Copy', copied_title_md: 'Copied' %>
|
||||||
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% unless @pagy.count.zero? %>
|
<% unless @pagy.count.zero? %>
|
||||||
<%= link_to new_template_submission_path(@template), class: 'btn btn-primary text-base', data: { turbo_frame: 'modal' } do %>
|
<%= link_to new_template_submission_path(@template), class: 'order-1 btn btn-primary text-base', data: { turbo_frame: 'modal' } do %>
|
||||||
<%= svg_icon('plus', class: 'w-6 h-6 stroke-2') %>
|
<%= svg_icon('plus', class: 'w-6 h-6 stroke-2') %>
|
||||||
<span class="hidden md:block">Add Recipients</span>
|
<span>Add <span class="hidden md:inline">Recipients</span></span>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% status_badges = { 'awaiting' => 'badge-info', 'sent' => 'badge-info', 'completed' => 'badge-success', 'opened' => 'badge-warning' } %>
|
|
||||||
<% if @submissions.present? %>
|
<% if @submissions.present? %>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<% @submissions.each do |submission| %>
|
<%= render partial: 'submission', collection: @submissions %>
|
||||||
<a href="<%= submission_path(submission) %>" class="bg-base-200 w-full flex flex-col md:flex-row space-y-4 md:space-y-0 md:justify-between rounded-2xl px-6 py-5 md:items-center">
|
|
||||||
<% submitters = (submission.template_submitters || submission.template.submitters).filter_map { |item| submission.submitters.find { |e| e.uuid == item['uuid'] } } %>
|
|
||||||
<% is_submission_complated = submitters.all?(&:completed_at?) %>
|
|
||||||
<% if submitters.size == 1 %>
|
|
||||||
<div>
|
|
||||||
<% submitter = submitters.first %>
|
|
||||||
<div class="flex items-center space-x-4">
|
|
||||||
<span class="flex items-center space-x-3">
|
|
||||||
<div class="tooltip flex" data-tip="<%= l(submitter.status_event_at.in_time_zone(current_account.timezone), format: :short, locale: current_account.locale) %>">
|
|
||||||
<span class="badge <%= status_badges[submitter.status] %> md:w-32 badge-lg bg-opacity-50 uppercase text-sm font-semibold">
|
|
||||||
<%= submitter.status %>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<span class="text-lg break-all">
|
|
||||||
<%= submitter.email %>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex space-x-2 items-center">
|
|
||||||
<% if submitter.completed_at? %>
|
|
||||||
<form onsubmit="event.preventDefault()">
|
|
||||||
<button onclick="event.stopPropagation()">
|
|
||||||
<download-button data-src="<%= submitter_download_index_path(submitter.slug) %>" class="btn btn-sm btn-neutral text-white md:w-36">
|
|
||||||
<span class="flex items-center justify-center space-x-2" data-target="download-button.defaultButton">
|
|
||||||
<%= svg_icon('download', class: 'w-5 h-5 stroke-2') %>
|
|
||||||
<span class="inline">Download</span>
|
|
||||||
</span>
|
|
||||||
<span class="flex items-center justify-center space-x-2 hidden" data-target="download-button.loadingButton">
|
|
||||||
<%= svg_icon('loader', class: 'w-5 h-5 animate-spin') %>
|
|
||||||
<span class="inline">Downloa...</span>
|
|
||||||
</span>
|
|
||||||
</download-button>
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
<% else %>
|
|
||||||
<%= render 'shared/clipboard_copy', text: submit_form_url(slug: submitter.slug), class: 'btn btn-sm btn-neutral text-white md:w-36 flex', icon_class: 'w-6 h-6 text-white', copy_title: 'Copy Link' %>
|
|
||||||
<% end %>
|
|
||||||
<span class="btn btn-outline btn-sm w-20 md:w-24">View</span>
|
|
||||||
<%= button_to button_title(title: nil, disabled_with: 'Remov', icon: svg_icon('trash', class: 'w-6 h-6')), submission_path(submission), class: 'btn btn-outline btn-sm', title: 'Delete', method: :delete, data: { turbo_confirm: 'Are you sure?' }, onclick: 'event.stopPropagation()' %>
|
|
||||||
</div>
|
|
||||||
<% else %>
|
|
||||||
<div class="space-y-1 w-full md:mr-2">
|
|
||||||
<div class="flex items-center space-x-3">
|
|
||||||
<% if is_submission_complated %>
|
|
||||||
<% latest_submitter = submitters.select(&:completed_at?).max_by(&:completed_at) %>
|
|
||||||
<div class="tooltip flex" data-tip="<%= l(latest_submitter.status_event_at.in_time_zone(current_account.timezone), format: :short, locale: current_account.locale) %>">
|
|
||||||
<span class="badge <%= status_badges[latest_submitter.status] %> md:w-32 bg-opacity-50 badge-lg uppercase text-sm font-semibold">
|
|
||||||
<%= latest_submitter.status %>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
<div class="w-full">
|
|
||||||
<% submitters.each_with_index do |submitter, index| %>
|
|
||||||
<div class="flex justify-between items-center space-x-3">
|
|
||||||
<span class="flex items-center space-x-3">
|
|
||||||
<% unless is_submission_complated %>
|
|
||||||
<div class="tooltip flex" data-tip="<%= l(submitter.status_event_at.in_time_zone(current_account.timezone), format: :short, locale: current_account.locale) %>">
|
|
||||||
<span class="badge md:w-24 <%= status_badges[submitter.status] %> bg-opacity-50 uppercase text-xs font-semibold">
|
|
||||||
<%= submitter.status %>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
<span class="text-lg break-all">
|
|
||||||
<%= submitter.email %>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
<% if submitter.completed_at? && !is_submission_complated %>
|
|
||||||
<form onsubmit="event.preventDefault()">
|
|
||||||
<button onclick="event.stopPropagation()">
|
|
||||||
<download-button data-src="<%= submitter_download_index_path(submitter.slug) %>" class="btn btn-xs btn-neutral text-white md:w-36">
|
|
||||||
<span class="flex items-center justify-center space-x-2" data-target="download-button.defaultButton">
|
|
||||||
<%= svg_icon('download', class: 'w-4 h-4 stroke-2') %>
|
|
||||||
<span class="hidden md:inline">Download</span>
|
|
||||||
</span>
|
|
||||||
<span class="flex items-center justify-center space-x-2 hidden" data-target="download-button.loadingButton">
|
|
||||||
<%= svg_icon('loader', class: 'w-4 h-4 animate-spin') %>
|
|
||||||
<span class="hidden md:inline">Downloa...</span>
|
|
||||||
</span>
|
|
||||||
</download-button>
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
<% elsif !is_submission_complated %>
|
|
||||||
<div class="flex items-center space-x-3">
|
|
||||||
<%= render 'shared/clipboard_copy', text: submit_form_url(slug: submitter.slug), class: 'btn btn-xs text-xs btn-neutral text-white md:w-36 flex', icon_class: 'w-4 h-4 text-white', copy_title: 'Copy Link' %>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex space-x-2 items-center">
|
|
||||||
<% if is_submission_complated %>
|
|
||||||
<% latest_submitter = submitters.select(&:completed_at?).max_by(&:completed_at) %>
|
|
||||||
<form onsubmit="event.preventDefault()">
|
|
||||||
<button onclick="event.stopPropagation()">
|
|
||||||
<download-button data-src="<%= submitter_download_index_path(latest_submitter.slug) %>" class="btn btn-sm btn-neutral text-white md:w-36">
|
|
||||||
<span class="flex items-center justify-center space-x-2" data-target="download-button.defaultButton">
|
|
||||||
<%= svg_icon('download', class: 'w-5 h-5 stroke-2') %>
|
|
||||||
<span class="inline">Download</span>
|
|
||||||
</span>
|
|
||||||
<span class="flex items-center justify-center space-x-2 hidden" data-target="download-button.loadingButton">
|
|
||||||
<%= svg_icon('loader', class: 'w-5 h-5 animate-spin') %>
|
|
||||||
<span class="inline">Downloa...</span>
|
|
||||||
</span>
|
|
||||||
</download-button>
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
<% end %>
|
|
||||||
<span class="btn btn-outline btn-sm w-20 md:w-24">View</span>
|
|
||||||
<%= button_to button_title(title: nil, disabled_with: 'Remov', icon: svg_icon('trash', class: 'w-6 h-6')), submission_path(submission), class: 'btn btn-outline btn-sm', title: 'Delete', method: :delete, data: { turbo_confirm: 'Are you sure?' }, onclick: 'event.stopPropagation()' %>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
</a>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
</div>
|
||||||
<%= render 'shared/pagination', pagy: @pagy, items_name: 'submissions' %>
|
<%= render 'shared/pagination', pagy: @pagy, items_name: 'submissions' %>
|
||||||
<% else %>
|
<% else %>
|
||||||
|
|||||||
@@ -49,7 +49,7 @@
|
|||||||
<%= link_to edit_user_path(user), class: 'btn btn-outline btn-xs', title: 'Edit', data: { turbo_frame: 'modal' } do %>
|
<%= link_to edit_user_path(user), class: 'btn btn-outline btn-xs', title: 'Edit', data: { turbo_frame: 'modal' } do %>
|
||||||
Edit
|
Edit
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= link_to user_path(user), class: 'btn btn-outline btn-error btn-xs', title: 'Delete', data: { turbo_method: :delete, turbo_confirm: 'Are you sure?' } do %>
|
<%= button_to user_path(user), method: :delete, class: 'btn btn-outline btn-error btn-xs', title: 'Delete', data: { turbo_confirm: 'Are you sure?' } do %>
|
||||||
Remove
|
Remove
|
||||||
<% end %>
|
<% end %>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
+3
-3
@@ -29,10 +29,10 @@
|
|||||||
</p>
|
</p>
|
||||||
<% if message == 'Signature valid' %>
|
<% if message == 'Signature valid' %>
|
||||||
<p class="flex space-x-1 items-center">
|
<p class="flex space-x-1 items-center">
|
||||||
<% if signature.signature_handler.signer_certificate.public_key.to_der == trusted_certs.first.public_key.to_der %>
|
<% if trusted_certs.any? { |e| e.public_key.to_der == signature.signature_handler.signer_certificate.public_key.to_der } %>
|
||||||
<%= svg_icon('circle_check', class: 'w-6 h-6 text-green-500') %>
|
<%= svg_icon('circle_check', class: 'w-6 h-6 text-green-500') %>
|
||||||
<span>
|
<span>
|
||||||
Signed with DocuSeal certificate
|
Signed with trusted certificate
|
||||||
</span>
|
</span>
|
||||||
<% else %>
|
<% else %>
|
||||||
<%= svg_icon('x_circle', class: 'w-6 h-6 text-red-500') %>
|
<%= svg_icon('x_circle', class: 'w-6 h-6 text-red-500') %>
|
||||||
@@ -54,7 +54,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex items-center space-x-1">
|
<div class="flex items-center space-x-1">
|
||||||
<%= svg_icon('certificate', class: 'w-5 h-5 inline') %>
|
<%= svg_icon('certificate', class: 'w-5 h-5 inline') %>
|
||||||
<span><%= signature.signer_name %></span>
|
<span><%= signature.signer_name.force_encoding('UTF-8') %></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center space-x-1">
|
<div class="flex items-center space-x-1">
|
||||||
<%= svg_icon('lock_access', class: 'w-5 h-5 inline') %>
|
<%= svg_icon('lock_access', class: 'w-5 h-5 inline') %>
|
||||||
@@ -44,6 +44,7 @@ Rails.application.configure do
|
|||||||
# The :test delivery method accumulates sent emails in the
|
# The :test delivery method accumulates sent emails in the
|
||||||
# ActionMailer::Base.deliveries array.
|
# ActionMailer::Base.deliveries array.
|
||||||
config.action_mailer.delivery_method = :test
|
config.action_mailer.delivery_method = :test
|
||||||
|
config.active_job.queue_adapter = :test
|
||||||
|
|
||||||
# Print deprecation notices to the stderr.
|
# Print deprecation notices to the stderr.
|
||||||
config.active_support.deprecation = :stderr
|
config.active_support.deprecation = :stderr
|
||||||
@@ -59,4 +60,10 @@ Rails.application.configure do
|
|||||||
|
|
||||||
# Annotate rendered view with file names.
|
# Annotate rendered view with file names.
|
||||||
# config.action_view.annotate_rendered_view_with_filenames = true
|
# config.action_view.annotate_rendered_view_with_filenames = true
|
||||||
|
|
||||||
|
config.active_record.encryption = {
|
||||||
|
primary_key: 'test master key',
|
||||||
|
deterministic_key: 'test deterministic key',
|
||||||
|
key_derivation_salt: 'test key derivation salt'
|
||||||
|
}
|
||||||
end
|
end
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'csv'
|
||||||
+10
-4
@@ -17,8 +17,10 @@ Rails.application.routes.draw do
|
|||||||
devise_scope :user do
|
devise_scope :user do
|
||||||
if Docuseal.multitenant?
|
if Docuseal.multitenant?
|
||||||
resource :registration, only: %i[show], path: 'sign_up'
|
resource :registration, only: %i[show], path: 'sign_up'
|
||||||
resource :registration, only: %i[create], path: 'new' do
|
unauthenticated do
|
||||||
get '' => :new, as: :new
|
resource :registration, only: %i[create], path: 'new' do
|
||||||
|
get '' => :new, as: :new
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -32,10 +34,11 @@ Rails.application.routes.draw do
|
|||||||
resources :submissions, only: %i[create]
|
resources :submissions, only: %i[create]
|
||||||
resources :templates, only: %i[update show index] do
|
resources :templates, only: %i[update show index] do
|
||||||
resources :submissions, only: %i[create]
|
resources :submissions, only: %i[create]
|
||||||
resources :documents, only: %i[create destroy], controller: 'templates_documents'
|
resources :documents, only: %i[create], controller: 'templates_documents'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
resources :verify_pdf_signature, only: %i[create]
|
||||||
resources :dashboard, only: %i[index]
|
resources :dashboard, only: %i[index]
|
||||||
resources :setup, only: %i[index create]
|
resources :setup, only: %i[index create]
|
||||||
resource :newsletter, only: %i[show update]
|
resource :newsletter, only: %i[show update]
|
||||||
@@ -44,6 +47,7 @@ Rails.application.routes.draw do
|
|||||||
resources :console_redirect, only: %i[index]
|
resources :console_redirect, only: %i[index]
|
||||||
resources :templates, only: %i[new create edit show destroy] do
|
resources :templates, only: %i[new create edit show destroy] do
|
||||||
resources :submissions, only: %i[new create]
|
resources :submissions, only: %i[new create]
|
||||||
|
resources :submissions_export, only: %i[index new]
|
||||||
end
|
end
|
||||||
|
|
||||||
resources :start_form, only: %i[show update], path: 'd', param: 'slug' do
|
resources :start_form, only: %i[show update], path: 'd', param: 'slug' do
|
||||||
@@ -60,6 +64,7 @@ Rails.application.routes.draw do
|
|||||||
|
|
||||||
resources :submitters, only: %i[], param: 'slug' do
|
resources :submitters, only: %i[], param: 'slug' do
|
||||||
resources :download, only: %i[index], controller: 'submissions_download'
|
resources :download, only: %i[index], controller: 'submissions_download'
|
||||||
|
resources :send_email, only: %i[create], controller: 'submitters_send_email'
|
||||||
resources :debug, only: %i[index], controller: 'submissions_debug' if Rails.env.development?
|
resources :debug, only: %i[index], controller: 'submissions_debug' if Rails.env.development?
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -67,8 +72,9 @@ Rails.application.routes.draw do
|
|||||||
unless Docuseal.multitenant?
|
unless Docuseal.multitenant?
|
||||||
resources :storage, only: %i[index create], controller: 'storage_settings'
|
resources :storage, only: %i[index create], controller: 'storage_settings'
|
||||||
resources :email, only: %i[index create], controller: 'email_settings'
|
resources :email, only: %i[index create], controller: 'email_settings'
|
||||||
|
resources :sms, only: %i[index], controller: 'sms_settings'
|
||||||
end
|
end
|
||||||
resources :esign, only: %i[index create], controller: 'esign_settings'
|
resource :esign, only: %i[show create new update destroy], controller: 'esign_settings'
|
||||||
resources :users, only: %i[index]
|
resources :users, only: %i[index]
|
||||||
resource :personalization, only: %i[show create], controller: 'personalization_settings'
|
resource :personalization, only: %i[show create], controller: 'personalization_settings'
|
||||||
if !Docuseal.multitenant? || Docuseal.demo?
|
if !Docuseal.multitenant? || Docuseal.demo?
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ default: &default
|
|||||||
compiler_strategy: digest
|
compiler_strategy: digest
|
||||||
cache_manifest: false
|
cache_manifest: false
|
||||||
ensure_consistent_versioning: false
|
ensure_consistent_versioning: false
|
||||||
useContentHash: false
|
|
||||||
|
|
||||||
development:
|
development:
|
||||||
<<: *default
|
<<: *default
|
||||||
|
|||||||
@@ -27,3 +27,8 @@ azure:
|
|||||||
storage_access_key: <%= ENV['AZURE_STORAGE_ACCESS_KEY'] %>
|
storage_access_key: <%= ENV['AZURE_STORAGE_ACCESS_KEY'] %>
|
||||||
container: <%= ENV['AZURE_CONTAINER'] %>
|
container: <%= ENV['AZURE_CONTAINER'] %>
|
||||||
public: <%= ENV['ACTIVE_STORAGE_PUBLIC'] == 'true' %>
|
public: <%= ENV['ACTIVE_STORAGE_PUBLIC'] == 'true' %>
|
||||||
|
|
||||||
|
test:
|
||||||
|
service: Disk
|
||||||
|
root: <%= Rails.root.join("tmp/storage") %>
|
||||||
|
public: true
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class RemoveUserFirstLastNameNotNull < ActiveRecord::Migration[7.0]
|
||||||
|
def change
|
||||||
|
change_column_null :users, :first_name, true
|
||||||
|
change_column_null :users, :last_name, true
|
||||||
|
end
|
||||||
|
end
|
||||||
Loaded 100 of 142 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user