mirror of
https://github.com/docusealco/docuseal
synced 2026-11-04 14:42:32 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2508b5fc8a | ||
|
|
4bd1ef0da3 | ||
|
|
4efd748571 | ||
|
|
859892d1fd | ||
|
|
7adeaf93cd | ||
|
|
5dbe155ecb | ||
|
|
667d065bee | ||
|
|
bb613059de | ||
|
|
1b2dec4c2a | ||
|
|
2c2ef97c30 | ||
|
|
6df1d68237 | ||
|
|
20c9faedff | ||
|
|
d931a40ea9 | ||
|
|
1097b98b85 | ||
|
|
2e597fcf43 | ||
|
|
bb2fa6a261 | ||
|
|
1dc8369487 | ||
|
|
e6d60f1985 | ||
|
|
82e3cb8a4d | ||
|
|
aeec0cbf8e | ||
|
|
7a2d1fc6c5 | ||
|
|
68033ab7d2 | ||
|
|
7595c60ea8 | ||
|
|
d9318c04f8 | ||
|
|
2d76198290 | ||
|
|
02a7b6ea55 | ||
|
|
ab57076b8f | ||
|
|
82fba47c98 | ||
|
|
5f32e2a5af | ||
|
|
70012fbd7c | ||
|
|
668f6ca975 | ||
|
|
e78dd301ef | ||
|
|
f8ec3daf10 | ||
|
|
75c929639d | ||
|
|
e9c1242b8d | ||
|
|
e7eb1d4f12 | ||
|
|
abbd3e809a | ||
|
|
f430b4f5a6 | ||
|
|
e65234b4c8 | ||
|
|
fe04d22c94 | ||
|
|
44a7a5ebdc | ||
|
|
119ef0d0c7 | ||
|
|
cdb8eb7931 | ||
|
|
aef176c0cc | ||
|
|
0c21b55865 | ||
|
|
964b1444a5 | ||
|
|
48de343948 | ||
|
|
2640934c15 | ||
|
|
442dd4d6db | ||
|
|
3168feb3d6 | ||
|
|
fbae4933f6 | ||
|
|
78bb66c344 | ||
|
|
5af65ef74f | ||
|
|
4d354ae9ff | ||
|
|
1e57c1ace7 | ||
|
|
fefd138dfd | ||
|
|
16a77d3536 | ||
|
|
e03857bdb7 | ||
|
|
fc679bcd72 | ||
|
|
652b0da021 | ||
|
|
2d54b0db2f | ||
|
|
7a29894926 | ||
|
|
f9ae79dd5b | ||
|
|
cadb78ca35 | ||
|
|
36a6b7ff1d | ||
|
|
efee46f113 | ||
|
|
b6639738c3 | ||
|
|
aab07c5f49 | ||
|
|
33c0784bc6 | ||
|
|
6f5ebfc8a6 | ||
|
|
811091ff14 | ||
|
|
6666f39928 | ||
|
|
f67db9fe5f | ||
|
|
45f002803e | ||
|
|
8c19f90e88 | ||
|
|
a8f5d5cea7 | ||
|
|
6788071eec | ||
|
|
0c690b3033 | ||
|
|
dcb963b623 | ||
|
|
04e30dbdd6 |
No files matched your search
@@ -11,7 +11,7 @@ jobs:
|
||||
- name: Install Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: 3.2.2
|
||||
ruby-version: 3.3.1
|
||||
- name: Cache gems
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
- name: Install Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: 3.2.2
|
||||
ruby-version: 3.3.1
|
||||
- name: Cache gems
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
@@ -100,7 +100,7 @@ jobs:
|
||||
- name: Install Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: 3.2.2
|
||||
ruby-version: 3.3.1
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ AllCops:
|
||||
- node_modules/**/*
|
||||
- bin/*
|
||||
- vendor/**/*
|
||||
TargetRubyVersion: '3.2'
|
||||
TargetRubyVersion: '3.3'
|
||||
|
||||
Metrics/BlockLength:
|
||||
Exclude:
|
||||
|
||||
+7
-7
@@ -1,4 +1,4 @@
|
||||
FROM ruby:3.2.2-alpine3.18 as fonts
|
||||
FROM ruby:3.3.1-alpine3.18 as fonts
|
||||
|
||||
WORKDIR /fonts
|
||||
|
||||
@@ -6,7 +6,7 @@ RUN apk --no-cache add fontforge wget && wget https://github.com/satbyy/go-noto-
|
||||
|
||||
RUN fontforge -lang=py -c 'font1 = fontforge.open("FreeSans.ttf"); font2 = fontforge.open("NotoSansSymbols2-Regular.ttf"); font1.mergeFonts(font2); font1.generate("FreeSans.ttf")'
|
||||
|
||||
FROM ruby:3.2.2-alpine3.18 as webpack
|
||||
FROM ruby:3.3.1-alpine3.18 as webpack
|
||||
|
||||
ENV RAILS_ENV=production
|
||||
ENV NODE_ENV=production
|
||||
@@ -23,16 +23,16 @@ RUN yarn install --network-timeout 1000000
|
||||
COPY ./bin/shakapacker ./bin/shakapacker
|
||||
COPY ./config/webpack ./config/webpack
|
||||
COPY ./config/shakapacker.yml ./config/shakapacker.yml
|
||||
COPY ./postcss.config.js ./postcss.config.js ./
|
||||
COPY ./tailwind.config.js ./tailwind.config.js ./
|
||||
COPY ./tailwind.form.config.js ./tailwind.form.config.js ./
|
||||
COPY ./tailwind.application.config.js ./tailwind.application.config.js ./
|
||||
COPY ./postcss.config.js ./postcss.config.js
|
||||
COPY ./tailwind.config.js ./tailwind.config.js
|
||||
COPY ./tailwind.form.config.js ./tailwind.form.config.js
|
||||
COPY ./tailwind.application.config.js ./tailwind.application.config.js
|
||||
COPY ./app/javascript ./app/javascript
|
||||
COPY ./app/views ./app/views
|
||||
|
||||
RUN echo "gem 'shakapacker'" > Gemfile && ./bin/shakapacker
|
||||
|
||||
FROM ruby:3.2.2-alpine3.18 as app
|
||||
FROM ruby:3.3.1-alpine3.18 as app
|
||||
|
||||
ENV RAILS_ENV=production
|
||||
ENV BUNDLE_WITHOUT="development:test"
|
||||
|
||||
@@ -2,13 +2,14 @@
|
||||
|
||||
source 'https://rubygems.org'
|
||||
|
||||
ruby '3.2.2'
|
||||
ruby '3.3.1'
|
||||
|
||||
gem 'arabic-letter-connector', require: 'arabic-letter-connector/logic'
|
||||
gem 'aws-sdk-s3', require: false
|
||||
gem 'azure-storage-blob', require: false
|
||||
gem 'bootsnap', require: false
|
||||
gem 'cancancan'
|
||||
gem 'csv'
|
||||
gem 'devise'
|
||||
gem 'devise-two-factor'
|
||||
gem 'dotenv', require: false
|
||||
|
||||
+178
-165
@@ -2,35 +2,35 @@ GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
Ascii85 (1.1.0)
|
||||
actioncable (7.1.2)
|
||||
actionpack (= 7.1.2)
|
||||
activesupport (= 7.1.2)
|
||||
actioncable (7.1.3.2)
|
||||
actionpack (= 7.1.3.2)
|
||||
activesupport (= 7.1.3.2)
|
||||
nio4r (~> 2.0)
|
||||
websocket-driver (>= 0.6.1)
|
||||
zeitwerk (~> 2.6)
|
||||
actionmailbox (7.1.2)
|
||||
actionpack (= 7.1.2)
|
||||
activejob (= 7.1.2)
|
||||
activerecord (= 7.1.2)
|
||||
activestorage (= 7.1.2)
|
||||
activesupport (= 7.1.2)
|
||||
actionmailbox (7.1.3.2)
|
||||
actionpack (= 7.1.3.2)
|
||||
activejob (= 7.1.3.2)
|
||||
activerecord (= 7.1.3.2)
|
||||
activestorage (= 7.1.3.2)
|
||||
activesupport (= 7.1.3.2)
|
||||
mail (>= 2.7.1)
|
||||
net-imap
|
||||
net-pop
|
||||
net-smtp
|
||||
actionmailer (7.1.2)
|
||||
actionpack (= 7.1.2)
|
||||
actionview (= 7.1.2)
|
||||
activejob (= 7.1.2)
|
||||
activesupport (= 7.1.2)
|
||||
actionmailer (7.1.3.2)
|
||||
actionpack (= 7.1.3.2)
|
||||
actionview (= 7.1.3.2)
|
||||
activejob (= 7.1.3.2)
|
||||
activesupport (= 7.1.3.2)
|
||||
mail (~> 2.5, >= 2.5.4)
|
||||
net-imap
|
||||
net-pop
|
||||
net-smtp
|
||||
rails-dom-testing (~> 2.2)
|
||||
actionpack (7.1.2)
|
||||
actionview (= 7.1.2)
|
||||
activesupport (= 7.1.2)
|
||||
actionpack (7.1.3.2)
|
||||
actionview (= 7.1.3.2)
|
||||
activesupport (= 7.1.3.2)
|
||||
nokogiri (>= 1.8.5)
|
||||
racc
|
||||
rack (>= 2.2.4)
|
||||
@@ -38,35 +38,35 @@ GEM
|
||||
rack-test (>= 0.6.3)
|
||||
rails-dom-testing (~> 2.2)
|
||||
rails-html-sanitizer (~> 1.6)
|
||||
actiontext (7.1.2)
|
||||
actionpack (= 7.1.2)
|
||||
activerecord (= 7.1.2)
|
||||
activestorage (= 7.1.2)
|
||||
activesupport (= 7.1.2)
|
||||
actiontext (7.1.3.2)
|
||||
actionpack (= 7.1.3.2)
|
||||
activerecord (= 7.1.3.2)
|
||||
activestorage (= 7.1.3.2)
|
||||
activesupport (= 7.1.3.2)
|
||||
globalid (>= 0.6.0)
|
||||
nokogiri (>= 1.8.5)
|
||||
actionview (7.1.2)
|
||||
activesupport (= 7.1.2)
|
||||
actionview (7.1.3.2)
|
||||
activesupport (= 7.1.3.2)
|
||||
builder (~> 3.1)
|
||||
erubi (~> 1.11)
|
||||
rails-dom-testing (~> 2.2)
|
||||
rails-html-sanitizer (~> 1.6)
|
||||
activejob (7.1.2)
|
||||
activesupport (= 7.1.2)
|
||||
activejob (7.1.3.2)
|
||||
activesupport (= 7.1.3.2)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (7.1.2)
|
||||
activesupport (= 7.1.2)
|
||||
activerecord (7.1.2)
|
||||
activemodel (= 7.1.2)
|
||||
activesupport (= 7.1.2)
|
||||
activemodel (7.1.3.2)
|
||||
activesupport (= 7.1.3.2)
|
||||
activerecord (7.1.3.2)
|
||||
activemodel (= 7.1.3.2)
|
||||
activesupport (= 7.1.3.2)
|
||||
timeout (>= 0.4.0)
|
||||
activestorage (7.1.2)
|
||||
actionpack (= 7.1.2)
|
||||
activejob (= 7.1.2)
|
||||
activerecord (= 7.1.2)
|
||||
activesupport (= 7.1.2)
|
||||
activestorage (7.1.3.2)
|
||||
actionpack (= 7.1.3.2)
|
||||
activejob (= 7.1.3.2)
|
||||
activerecord (= 7.1.3.2)
|
||||
activesupport (= 7.1.3.2)
|
||||
marcel (~> 1.0)
|
||||
activesupport (7.1.2)
|
||||
activesupport (7.1.3.2)
|
||||
base64
|
||||
bigdecimal
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
@@ -85,17 +85,17 @@ GEM
|
||||
arabic-letter-connector (0.1.1)
|
||||
ast (2.4.2)
|
||||
aws-eventstream (1.3.0)
|
||||
aws-partitions (1.874.0)
|
||||
aws-sdk-core (3.190.1)
|
||||
aws-partitions (1.913.0)
|
||||
aws-sdk-core (3.191.6)
|
||||
aws-eventstream (~> 1, >= 1.3.0)
|
||||
aws-partitions (~> 1, >= 1.651.0)
|
||||
aws-sigv4 (~> 1.8)
|
||||
jmespath (~> 1, >= 1.6.1)
|
||||
aws-sdk-kms (1.75.0)
|
||||
aws-sdk-core (~> 3, >= 3.188.0)
|
||||
aws-sdk-kms (1.79.0)
|
||||
aws-sdk-core (~> 3, >= 3.191.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-s3 (1.142.0)
|
||||
aws-sdk-core (~> 3, >= 3.189.0)
|
||||
aws-sdk-s3 (1.146.1)
|
||||
aws-sdk-core (~> 3, >= 3.191.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.8)
|
||||
aws-sigv4 (1.8.0)
|
||||
@@ -110,52 +110,55 @@ GEM
|
||||
nokogiri (~> 1, >= 1.10.8)
|
||||
base64 (0.2.0)
|
||||
bcrypt (3.1.20)
|
||||
better_html (2.0.2)
|
||||
better_html (2.1.1)
|
||||
actionview (>= 6.0)
|
||||
activesupport (>= 6.0)
|
||||
ast (~> 2.0)
|
||||
erubi (~> 1.4)
|
||||
parser (>= 2.4)
|
||||
smart_properties
|
||||
bigdecimal (3.1.5)
|
||||
bigdecimal (3.1.7)
|
||||
bindex (0.8.1)
|
||||
bootsnap (1.17.0)
|
||||
bootsnap (1.18.3)
|
||||
msgpack (~> 1.2)
|
||||
builder (3.2.4)
|
||||
bullet (7.1.4)
|
||||
bullet (7.1.6)
|
||||
activesupport (>= 3.0.0)
|
||||
uniform_notifier (~> 1.11)
|
||||
camertron-eprun (1.1.1)
|
||||
cancancan (3.5.0)
|
||||
capybara (3.39.2)
|
||||
capybara (3.40.0)
|
||||
addressable
|
||||
matrix
|
||||
mini_mime (>= 0.1.3)
|
||||
nokogiri (~> 1.8)
|
||||
nokogiri (~> 1.11)
|
||||
rack (>= 1.6.0)
|
||||
rack-test (>= 0.6.3)
|
||||
regexp_parser (>= 1.5, < 3.0)
|
||||
xpath (~> 3.2)
|
||||
childprocess (5.0.0)
|
||||
chunky_png (1.4.0)
|
||||
cldr-plurals-runtime-rb (1.1.0)
|
||||
cmdparse (3.0.7)
|
||||
coderay (1.1.3)
|
||||
concurrent-ruby (1.2.2)
|
||||
concurrent-ruby (1.2.3)
|
||||
connection_pool (2.4.1)
|
||||
crack (0.4.5)
|
||||
crack (1.0.0)
|
||||
bigdecimal
|
||||
rexml
|
||||
crass (1.0.6)
|
||||
css_parser (1.16.0)
|
||||
css_parser (1.17.1)
|
||||
addressable
|
||||
csv (3.3.0)
|
||||
cuprite (0.15)
|
||||
capybara (~> 3.0)
|
||||
ferrum (~> 0.14.0)
|
||||
date (3.3.4)
|
||||
debug (1.9.1)
|
||||
debug (1.9.2)
|
||||
irb (~> 1.10)
|
||||
reline (>= 0.3.8)
|
||||
declarative (0.0.20)
|
||||
devise (4.9.3)
|
||||
devise (4.9.4)
|
||||
bcrypt (~> 3.0)
|
||||
orm_adapter (~> 0.1)
|
||||
railties (>= 4.1.0)
|
||||
@@ -166,13 +169,12 @@ GEM
|
||||
devise (~> 4.0)
|
||||
railties (~> 7.0)
|
||||
rotp (~> 6.0)
|
||||
diff-lcs (1.5.0)
|
||||
diff-lcs (1.5.1)
|
||||
digest-crc (0.6.5)
|
||||
rake (>= 12.0.0, < 14.0.0)
|
||||
docile (1.4.0)
|
||||
dotenv (2.8.1)
|
||||
drb (2.2.0)
|
||||
ruby2_keywords
|
||||
dotenv (3.1.0)
|
||||
drb (2.2.1)
|
||||
email_typo (0.2.3)
|
||||
erb_lint (0.5.0)
|
||||
activesupport
|
||||
@@ -182,12 +184,12 @@ GEM
|
||||
rubocop
|
||||
smart_properties
|
||||
erubi (1.12.0)
|
||||
factory_bot (6.4.4)
|
||||
factory_bot (6.4.6)
|
||||
activesupport (>= 5.0.0)
|
||||
factory_bot_rails (6.4.2)
|
||||
factory_bot_rails (6.4.3)
|
||||
factory_bot (~> 6.4)
|
||||
railties (>= 5.0.0)
|
||||
faker (3.2.2)
|
||||
faker (3.3.1)
|
||||
i18n (>= 1.8.11, < 2)
|
||||
faraday (1.10.3)
|
||||
faraday-em_http (~> 1.0)
|
||||
@@ -225,34 +227,34 @@ GEM
|
||||
geom2d (0.4.1)
|
||||
globalid (1.2.1)
|
||||
activesupport (>= 6.1)
|
||||
google-apis-core (0.11.2)
|
||||
google-apis-core (0.14.1)
|
||||
addressable (~> 2.5, >= 2.5.1)
|
||||
googleauth (>= 0.16.2, < 2.a)
|
||||
googleauth (~> 1.9)
|
||||
httpclient (>= 2.8.1, < 3.a)
|
||||
mini_mime (~> 1.0)
|
||||
representable (~> 3.0)
|
||||
retriable (>= 2.0, < 4.a)
|
||||
rexml
|
||||
webrick
|
||||
google-apis-iamcredentials_v1 (0.17.0)
|
||||
google-apis-core (>= 0.11.0, < 2.a)
|
||||
google-apis-storage_v1 (0.29.0)
|
||||
google-apis-core (>= 0.11.0, < 2.a)
|
||||
google-cloud-core (1.6.1)
|
||||
google-apis-iamcredentials_v1 (0.20.0)
|
||||
google-apis-core (>= 0.14.0, < 2.a)
|
||||
google-apis-storage_v1 (0.37.0)
|
||||
google-apis-core (>= 0.14.0, < 2.a)
|
||||
google-cloud-core (1.7.0)
|
||||
google-cloud-env (>= 1.0, < 3.a)
|
||||
google-cloud-errors (~> 1.0)
|
||||
google-cloud-env (2.1.0)
|
||||
google-cloud-env (2.1.1)
|
||||
faraday (>= 1.0, < 3.a)
|
||||
google-cloud-errors (1.3.1)
|
||||
google-cloud-storage (1.45.0)
|
||||
google-cloud-errors (1.4.0)
|
||||
google-cloud-storage (1.49.0)
|
||||
addressable (~> 2.8)
|
||||
digest-crc (~> 0.4)
|
||||
google-apis-iamcredentials_v1 (~> 0.1)
|
||||
google-apis-storage_v1 (~> 0.29.0)
|
||||
google-apis-core (~> 0.13)
|
||||
google-apis-iamcredentials_v1 (~> 0.18)
|
||||
google-apis-storage_v1 (~> 0.33)
|
||||
google-cloud-core (~> 1.6)
|
||||
googleauth (>= 0.16.2, < 2.a)
|
||||
googleauth (~> 1.9)
|
||||
mini_mime (~> 1.0)
|
||||
googleauth (1.9.1)
|
||||
googleauth (1.11.0)
|
||||
faraday (>= 1.0, < 3.a)
|
||||
google-cloud-env (~> 2.1)
|
||||
jwt (>= 1.4, < 3.0)
|
||||
@@ -261,29 +263,31 @@ GEM
|
||||
signet (>= 0.16, < 2.a)
|
||||
hashdiff (1.1.0)
|
||||
hashery (2.1.2)
|
||||
hexapdf (0.37.1)
|
||||
hexapdf (0.40.0)
|
||||
cmdparse (~> 3.0, >= 3.0.3)
|
||||
geom2d (~> 0.4, >= 0.4.1)
|
||||
openssl (>= 2.2.1)
|
||||
htmlentities (4.3.4)
|
||||
httpclient (2.8.3)
|
||||
i18n (1.14.1)
|
||||
i18n (1.14.4)
|
||||
concurrent-ruby (~> 1.0)
|
||||
image_processing (1.12.2)
|
||||
mini_magick (>= 4.9.5, < 5)
|
||||
ruby-vips (>= 2.0.17, < 3)
|
||||
io-console (0.7.1)
|
||||
irb (1.11.0)
|
||||
io-console (0.7.2)
|
||||
irb (1.12.0)
|
||||
rdoc
|
||||
reline (>= 0.3.8)
|
||||
reline (>= 0.4.2)
|
||||
jmespath (1.6.2)
|
||||
json (2.7.1)
|
||||
jwt (2.7.1)
|
||||
json (2.7.2)
|
||||
jwt (2.8.1)
|
||||
base64
|
||||
language_server-protocol (3.17.0.3)
|
||||
launchy (2.5.2)
|
||||
launchy (3.0.0)
|
||||
addressable (~> 2.8)
|
||||
letter_opener (1.8.1)
|
||||
launchy (>= 2.2, < 3)
|
||||
childprocess (~> 5.0)
|
||||
letter_opener (1.10.0)
|
||||
launchy (>= 2.2, < 4)
|
||||
letter_opener_web (2.0.0)
|
||||
actionmailer (>= 5.2)
|
||||
letter_opener (~> 1.7)
|
||||
@@ -302,21 +306,21 @@ GEM
|
||||
net-imap
|
||||
net-pop
|
||||
net-smtp
|
||||
marcel (1.0.2)
|
||||
marcel (1.0.4)
|
||||
matrix (0.4.2)
|
||||
method_source (1.0.0)
|
||||
method_source (1.1.0)
|
||||
mini_magick (4.12.0)
|
||||
mini_mime (1.1.5)
|
||||
mini_portile2 (2.8.5)
|
||||
minitest (5.20.0)
|
||||
mini_portile2 (2.8.6)
|
||||
minitest (5.22.3)
|
||||
msgpack (1.7.2)
|
||||
multi_json (1.15.0)
|
||||
multipart-post (2.3.0)
|
||||
multipart-post (2.4.0)
|
||||
mutex_m (0.2.0)
|
||||
mysql2 (0.5.5)
|
||||
mysql2 (0.5.6)
|
||||
net-http-persistent (4.0.2)
|
||||
connection_pool (~> 2.2)
|
||||
net-imap (0.4.9)
|
||||
net-imap (0.4.10)
|
||||
date
|
||||
net-protocol
|
||||
net-pop (0.1.2)
|
||||
@@ -325,18 +329,21 @@ GEM
|
||||
timeout
|
||||
net-smtp (0.4.0)
|
||||
net-protocol
|
||||
nio4r (2.7.0)
|
||||
nokogiri (1.15.5)
|
||||
nio4r (2.7.1)
|
||||
nokogiri (1.16.4)
|
||||
mini_portile2 (~> 2.8.2)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.16.4-arm64-darwin)
|
||||
racc (~> 1.4)
|
||||
oj (3.16.3)
|
||||
bigdecimal (>= 3.0)
|
||||
openssl (3.2.0)
|
||||
orm_adapter (0.5.0)
|
||||
os (1.1.4)
|
||||
pagy (6.2.0)
|
||||
package_json (0.1.0)
|
||||
pagy (8.1.2)
|
||||
parallel (1.24.0)
|
||||
parser (3.2.2.4)
|
||||
parser (3.3.1.0)
|
||||
ast (~> 2.4.1)
|
||||
racc
|
||||
pdf-reader (2.12.0)
|
||||
@@ -345,8 +352,8 @@ GEM
|
||||
hashery (~> 2.0)
|
||||
ruby-rc4
|
||||
ttfunk
|
||||
pg (1.5.4)
|
||||
premailer (1.21.0)
|
||||
pg (1.5.6)
|
||||
premailer (1.23.0)
|
||||
addressable
|
||||
css_parser (>= 1.12.0)
|
||||
htmlentities (>= 4.0.0)
|
||||
@@ -363,11 +370,11 @@ GEM
|
||||
pry (>= 0.10.4)
|
||||
psych (5.1.2)
|
||||
stringio
|
||||
public_suffix (5.0.4)
|
||||
puma (6.4.0)
|
||||
public_suffix (5.0.5)
|
||||
puma (6.4.2)
|
||||
nio4r (~> 2.0)
|
||||
racc (1.7.3)
|
||||
rack (2.2.8)
|
||||
rack (2.2.9)
|
||||
rack-proxy (0.7.7)
|
||||
rack
|
||||
rack-session (1.0.2)
|
||||
@@ -377,20 +384,20 @@ GEM
|
||||
rackup (1.0.0)
|
||||
rack (< 3)
|
||||
webrick
|
||||
rails (7.1.2)
|
||||
actioncable (= 7.1.2)
|
||||
actionmailbox (= 7.1.2)
|
||||
actionmailer (= 7.1.2)
|
||||
actionpack (= 7.1.2)
|
||||
actiontext (= 7.1.2)
|
||||
actionview (= 7.1.2)
|
||||
activejob (= 7.1.2)
|
||||
activemodel (= 7.1.2)
|
||||
activerecord (= 7.1.2)
|
||||
activestorage (= 7.1.2)
|
||||
activesupport (= 7.1.2)
|
||||
rails (7.1.3.2)
|
||||
actioncable (= 7.1.3.2)
|
||||
actionmailbox (= 7.1.3.2)
|
||||
actionmailer (= 7.1.3.2)
|
||||
actionpack (= 7.1.3.2)
|
||||
actiontext (= 7.1.3.2)
|
||||
actionview (= 7.1.3.2)
|
||||
activejob (= 7.1.3.2)
|
||||
activemodel (= 7.1.3.2)
|
||||
activerecord (= 7.1.3.2)
|
||||
activestorage (= 7.1.3.2)
|
||||
activesupport (= 7.1.3.2)
|
||||
bundler (>= 1.15.0)
|
||||
railties (= 7.1.2)
|
||||
railties (= 7.1.3.2)
|
||||
rails-dom-testing (2.2.0)
|
||||
activesupport (>= 5.0.0)
|
||||
minitest
|
||||
@@ -398,35 +405,35 @@ GEM
|
||||
rails-html-sanitizer (1.6.0)
|
||||
loofah (~> 2.21)
|
||||
nokogiri (~> 1.14)
|
||||
rails-i18n (7.0.8)
|
||||
rails-i18n (7.0.9)
|
||||
i18n (>= 0.7, < 2)
|
||||
railties (>= 6.0.0, < 8)
|
||||
rails_autolink (1.1.8)
|
||||
actionview (> 3.1)
|
||||
activesupport (> 3.1)
|
||||
railties (> 3.1)
|
||||
railties (7.1.2)
|
||||
actionpack (= 7.1.2)
|
||||
activesupport (= 7.1.2)
|
||||
railties (7.1.3.2)
|
||||
actionpack (= 7.1.3.2)
|
||||
activesupport (= 7.1.3.2)
|
||||
irb
|
||||
rackup (>= 1.0.0)
|
||||
rake (>= 12.2)
|
||||
thor (~> 1.0, >= 1.2.2)
|
||||
zeitwerk (~> 2.6)
|
||||
rainbow (3.1.1)
|
||||
rake (13.1.0)
|
||||
rdoc (6.6.2)
|
||||
rake (13.2.1)
|
||||
rdoc (6.6.3.1)
|
||||
psych (>= 4.0.0)
|
||||
redis-client (0.19.1)
|
||||
redis-client (0.22.0)
|
||||
connection_pool
|
||||
regexp_parser (2.8.3)
|
||||
reline (0.4.1)
|
||||
regexp_parser (2.9.0)
|
||||
reline (0.5.1)
|
||||
io-console (~> 0.5)
|
||||
representable (3.2.0)
|
||||
declarative (< 0.1.0)
|
||||
trailblazer-option (>= 0.1.1, < 0.2.0)
|
||||
uber (< 0.2.0)
|
||||
request_store (1.5.1)
|
||||
request_store (1.6.0)
|
||||
rack (>= 1.4)
|
||||
responders (3.1.1)
|
||||
actionpack (>= 5.2)
|
||||
@@ -438,73 +445,77 @@ GEM
|
||||
chunky_png (~> 1.0)
|
||||
rqrcode_core (~> 1.0)
|
||||
rqrcode_core (1.2.0)
|
||||
rspec-core (3.12.2)
|
||||
rspec-support (~> 3.12.0)
|
||||
rspec-expectations (3.12.3)
|
||||
rspec-core (3.13.0)
|
||||
rspec-support (~> 3.13.0)
|
||||
rspec-expectations (3.13.0)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.12.0)
|
||||
rspec-mocks (3.12.6)
|
||||
rspec-support (~> 3.13.0)
|
||||
rspec-mocks (3.13.0)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.12.0)
|
||||
rspec-rails (6.1.0)
|
||||
rspec-support (~> 3.13.0)
|
||||
rspec-rails (6.1.2)
|
||||
actionpack (>= 6.1)
|
||||
activesupport (>= 6.1)
|
||||
railties (>= 6.1)
|
||||
rspec-core (~> 3.12)
|
||||
rspec-expectations (~> 3.12)
|
||||
rspec-mocks (~> 3.12)
|
||||
rspec-support (~> 3.12)
|
||||
rspec-support (3.12.1)
|
||||
rubocop (1.59.0)
|
||||
rspec-core (~> 3.13)
|
||||
rspec-expectations (~> 3.13)
|
||||
rspec-mocks (~> 3.13)
|
||||
rspec-support (~> 3.13)
|
||||
rspec-support (3.13.1)
|
||||
rubocop (1.63.3)
|
||||
json (~> 2.3)
|
||||
language_server-protocol (>= 3.17.0)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 3.2.2.4)
|
||||
parser (>= 3.3.0.2)
|
||||
rainbow (>= 2.2.2, < 4.0)
|
||||
regexp_parser (>= 1.8, < 3.0)
|
||||
rexml (>= 3.2.5, < 4.0)
|
||||
rubocop-ast (>= 1.30.0, < 2.0)
|
||||
rubocop-ast (>= 1.31.1, < 2.0)
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (>= 2.4.0, < 3.0)
|
||||
rubocop-ast (1.30.0)
|
||||
parser (>= 3.2.1.0)
|
||||
rubocop-capybara (2.19.0)
|
||||
rubocop-ast (1.31.2)
|
||||
parser (>= 3.3.0.4)
|
||||
rubocop-capybara (2.20.0)
|
||||
rubocop (~> 1.41)
|
||||
rubocop-factory_bot (2.24.0)
|
||||
rubocop (~> 1.33)
|
||||
rubocop-performance (1.20.1)
|
||||
rubocop-factory_bot (2.25.1)
|
||||
rubocop (~> 1.41)
|
||||
rubocop-performance (1.21.0)
|
||||
rubocop (>= 1.48.1, < 2.0)
|
||||
rubocop-ast (>= 1.30.0, < 2.0)
|
||||
rubocop-rails (2.23.1)
|
||||
rubocop-ast (>= 1.31.1, < 2.0)
|
||||
rubocop-rails (2.24.1)
|
||||
activesupport (>= 4.2.0)
|
||||
rack (>= 1.1)
|
||||
rubocop (>= 1.33.0, < 2.0)
|
||||
rubocop-ast (>= 1.30.0, < 2.0)
|
||||
rubocop-rspec (2.25.0)
|
||||
rubocop-ast (>= 1.31.1, < 2.0)
|
||||
rubocop-rspec (2.29.1)
|
||||
rubocop (~> 1.40)
|
||||
rubocop-capybara (~> 2.17)
|
||||
rubocop-factory_bot (~> 2.22)
|
||||
rubocop-rspec_rails (~> 2.28)
|
||||
rubocop-rspec_rails (2.28.3)
|
||||
rubocop (~> 1.40)
|
||||
ruby-progressbar (1.13.0)
|
||||
ruby-rc4 (0.1.5)
|
||||
ruby-vips (2.2.0)
|
||||
ruby-vips (2.2.1)
|
||||
ffi (~> 1.12)
|
||||
ruby2_keywords (0.0.5)
|
||||
rubyXL (3.4.25)
|
||||
rubyXL (3.4.27)
|
||||
nokogiri (>= 1.10.8)
|
||||
rubyzip (>= 1.3.0)
|
||||
rubyzip (2.3.2)
|
||||
semantic_range (3.0.0)
|
||||
shakapacker (7.1.0)
|
||||
shakapacker (7.2.3)
|
||||
activesupport (>= 5.2)
|
||||
package_json
|
||||
rack-proxy (>= 0.6.1)
|
||||
railties (>= 5.2)
|
||||
semantic_range (>= 2.3.0)
|
||||
sidekiq (7.2.0)
|
||||
sidekiq (7.2.2)
|
||||
concurrent-ruby (< 2)
|
||||
connection_pool (>= 2.3.0)
|
||||
rack (>= 2.2.4)
|
||||
redis-client (>= 0.14.0)
|
||||
signet (0.18.0)
|
||||
redis-client (>= 0.19.0)
|
||||
signet (0.19.0)
|
||||
addressable (~> 2.8)
|
||||
faraday (>= 0.17.5, < 3.a)
|
||||
jwt (>= 1.5, < 3.0)
|
||||
@@ -516,17 +527,18 @@ GEM
|
||||
simplecov-html (0.12.3)
|
||||
simplecov_json_formatter (0.1.4)
|
||||
smart_properties (1.17.0)
|
||||
sqlite3 (1.7.0)
|
||||
sqlite3 (1.7.3)
|
||||
mini_portile2 (~> 2.8.0)
|
||||
sqlite3 (1.7.0-arm64-darwin)
|
||||
sqlite3 (1.7.3-arm64-darwin)
|
||||
stringio (3.1.0)
|
||||
strip_attributes (1.13.0)
|
||||
activemodel (>= 3.0, < 8.0)
|
||||
thor (1.3.0)
|
||||
thor (1.3.1)
|
||||
timeout (0.4.1)
|
||||
trailblazer-option (0.1.2)
|
||||
ttfunk (1.7.0)
|
||||
turbo-rails (1.5.0)
|
||||
ttfunk (1.8.0)
|
||||
bigdecimal (~> 3.1)
|
||||
turbo-rails (2.0.5)
|
||||
actionpack (>= 6.0.0)
|
||||
activejob (>= 6.0.0)
|
||||
railties (>= 6.0.0)
|
||||
@@ -536,7 +548,7 @@ GEM
|
||||
tzinfo
|
||||
tzinfo (2.0.6)
|
||||
concurrent-ruby (~> 1.0)
|
||||
tzinfo-data (1.2023.4)
|
||||
tzinfo-data (1.2024.1)
|
||||
tzinfo (>= 1.0.0)
|
||||
uber (0.1.0)
|
||||
unicode-display_width (2.5.0)
|
||||
@@ -548,7 +560,7 @@ GEM
|
||||
activemodel (>= 6.0.0)
|
||||
bindex (>= 0.4.0)
|
||||
railties (>= 6.0.0)
|
||||
webmock (3.19.1)
|
||||
webmock (3.23.0)
|
||||
addressable (>= 2.8.0)
|
||||
crack (>= 0.3.2)
|
||||
hashdiff (>= 0.4.0, < 2.0.0)
|
||||
@@ -558,7 +570,7 @@ GEM
|
||||
websocket-extensions (0.1.5)
|
||||
xpath (3.2.0)
|
||||
nokogiri (~> 1.8)
|
||||
zeitwerk (2.6.12)
|
||||
zeitwerk (2.6.13)
|
||||
|
||||
PLATFORMS
|
||||
arm64-darwin-22
|
||||
@@ -574,6 +586,7 @@ DEPENDENCIES
|
||||
bullet
|
||||
cancancan
|
||||
capybara
|
||||
csv
|
||||
cuprite
|
||||
debug
|
||||
devise
|
||||
@@ -625,7 +638,7 @@ DEPENDENCIES
|
||||
webmock
|
||||
|
||||
RUBY VERSION
|
||||
ruby 3.2.2p53
|
||||
ruby 3.3.1p55
|
||||
|
||||
BUNDLED WITH
|
||||
2.5.3
|
||||
@@ -9,7 +9,10 @@ class AccountConfigsController < ApplicationController
|
||||
AccountConfig::FORCE_MFA,
|
||||
AccountConfig::ALLOW_TO_RESUBMIT,
|
||||
AccountConfig::ESIGNING_PREFERENCE_KEY,
|
||||
AccountConfig::FORM_WITH_CONFETTI_KEY
|
||||
AccountConfig::FORM_WITH_CONFETTI_KEY,
|
||||
AccountConfig::DOWNLOAD_LINKS_AUTH_KEY,
|
||||
AccountConfig::FORCE_SSO_AUTH_KEY,
|
||||
AccountConfig::FLATTEN_RESULT_PDF_KEY
|
||||
].freeze
|
||||
|
||||
InvalidKey = Class.new(StandardError)
|
||||
|
||||
@@ -8,6 +8,7 @@ module Api
|
||||
skip_authorization_check
|
||||
|
||||
before_action :set_cors_headers
|
||||
before_action :set_noindex_headers
|
||||
|
||||
def show
|
||||
blob_uuid, purp, exp = ApplicationRecord.signed_id_verifier.verified(params[:signed_uuid])
|
||||
@@ -20,6 +21,8 @@ module Api
|
||||
|
||||
blob = ActiveStorage::Blob.find_by!(uuid: blob_uuid)
|
||||
|
||||
authorization_check!(blob) if exp.blank?
|
||||
|
||||
if request.headers['Range'].present?
|
||||
send_blob_byte_range_data blob, request.headers['Range']
|
||||
else
|
||||
@@ -31,5 +34,22 @@ module Api
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def authorization_check!(blob)
|
||||
is_authorized =
|
||||
blob.attachments.all? do |a|
|
||||
a.name.in?(%w[logo preview_images]) ||
|
||||
(current_user && a.record.account.id == current_user.account_id) ||
|
||||
!a.record.account.account_configs.find_or_initialize_by(key: AccountConfig::DOWNLOAD_LINKS_AUTH_KEY).value
|
||||
end
|
||||
|
||||
return if is_authorized
|
||||
|
||||
Rollbar.error('Blob aunauthorized') if defined?(Rollbar)
|
||||
|
||||
raise CanCan::AccessDenied
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -7,6 +7,9 @@ module Api
|
||||
skip_before_action :authenticate_user!
|
||||
skip_authorization_check
|
||||
|
||||
before_action :set_cors_headers
|
||||
before_action :set_noindex_headers
|
||||
|
||||
# rubocop:disable Metrics
|
||||
def show
|
||||
Rollbar.info('Blob legacy') if defined?(Rollbar)
|
||||
|
||||
@@ -50,20 +50,26 @@ module Api
|
||||
end
|
||||
|
||||
def authenticate_user!
|
||||
@current_user ||=
|
||||
if request.headers['X-Auth-Token'].present?
|
||||
sha256 = Digest::SHA256.hexdigest(request.headers['X-Auth-Token'])
|
||||
|
||||
User.joins(:access_token).active.find_by(access_token: { sha256: })
|
||||
end
|
||||
|
||||
render json: { error: 'Not authenticated' }, status: :unauthorized unless current_user
|
||||
end
|
||||
|
||||
def current_user
|
||||
super || @current_user ||=
|
||||
if request.headers['X-Auth-Token'].present?
|
||||
sha256 = Digest::SHA256.hexdigest(request.headers['X-Auth-Token'])
|
||||
|
||||
User.joins(:access_token).active.find_by(access_token: { sha256: })
|
||||
end
|
||||
end
|
||||
|
||||
def current_account
|
||||
current_user&.account
|
||||
end
|
||||
|
||||
def set_noindex_headers
|
||||
headers['X-Robots-Tag'] = 'noindex'
|
||||
end
|
||||
|
||||
def set_cors_headers
|
||||
headers['Access-Control-Allow-Origin'] = '*'
|
||||
headers['Access-Control-Allow-Methods'] = 'POST, GET, PUT, PATCH, DELETE, OPTIONS'
|
||||
|
||||
@@ -17,7 +17,8 @@ module Api
|
||||
submissions = submissions.joins(template: :folder).where(folder: { name: params[:template_folder] })
|
||||
end
|
||||
|
||||
submissions = paginate(submissions.preload(:created_by_user, :template, :submitters,
|
||||
submissions = paginate(submissions.preload(:created_by_user, :submitters,
|
||||
template: :folder,
|
||||
audit_trail_attachment: :blob))
|
||||
|
||||
render json: {
|
||||
@@ -69,7 +70,7 @@ module Api
|
||||
end
|
||||
|
||||
def create
|
||||
Params::SubmissionCreateValidator.call(params, dry_run: !Docuseal.multitenant?)
|
||||
Params::SubmissionCreateValidator.call(params)
|
||||
|
||||
return render json: { error: 'Template not found' }, status: :unprocessable_entity if @template.nil?
|
||||
|
||||
@@ -92,7 +93,13 @@ module Api
|
||||
end
|
||||
end
|
||||
|
||||
render json: submissions.flat_map(&:submitters)
|
||||
json = submissions.flat_map do |submission|
|
||||
submission.submitters.map do |s|
|
||||
Submitters::SerializeForApi.call(s, with_documents: false, with_urls: true)
|
||||
end
|
||||
end
|
||||
|
||||
render json:
|
||||
rescue Submitters::NormalizeValues::BaseError => e
|
||||
Rollbar.warning(e) if defined?(Rollbar)
|
||||
|
||||
@@ -102,6 +109,8 @@ module Api
|
||||
def destroy
|
||||
@submission.update!(archived_at: Time.current)
|
||||
|
||||
SendSubmissionArchivedWebhookRequestJob.perform_later(@submission)
|
||||
|
||||
render json: @submission.as_json(only: %i[id], methods: %i[archived_at])
|
||||
end
|
||||
|
||||
@@ -153,7 +162,8 @@ module Api
|
||||
completed_at opened_at sent_at
|
||||
created_at updated_at external_id metadata],
|
||||
methods: %i[status application_key] },
|
||||
template: { only: %i[id name created_at updated_at] },
|
||||
template: { only: %i[id name external_id created_at updated_at],
|
||||
methods: %i[folder_name] },
|
||||
created_by_user: { only: %i[id email first_name last_name] }
|
||||
}
|
||||
}
|
||||
@@ -161,15 +171,15 @@ module Api
|
||||
|
||||
def submissions_params
|
||||
permitted_attrs = [
|
||||
:send_email, :send_sms, :bcc_completed, :completed_redirect_url, :reply_to,
|
||||
:send_email, :send_sms, :bcc_completed, :completed_redirect_url, :reply_to, :go_to_last,
|
||||
{
|
||||
message: %i[subject body],
|
||||
submitters: [[:send_email, :send_sms, :completed_redirect_url, :uuid, :name, :email, :role,
|
||||
:completed, :phone, :application_key, :external_id, :reply_to,
|
||||
:completed, :phone, :application_key, :external_id, :reply_to, :go_to_last,
|
||||
{ metadata: {}, values: {}, readonly_fields: [], message: %i[subject body],
|
||||
fields: [:name, :default_value, :title, :description,
|
||||
:readonly, :validation_pattern, :invalid_message,
|
||||
{ default_value: [] }] }]]
|
||||
fields: [:name, :uuid, :default_value, :value, :title, :description,
|
||||
:readonly, :redacted, :validation_pattern, :invalid_message,
|
||||
{ default_value: [], value: [] }] }]]
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
@@ -10,6 +10,11 @@ module Api
|
||||
submitters = submitters.where(external_id: params[:application_key]) if params[:application_key].present?
|
||||
submitters = submitters.where(external_id: params[:external_id]) if params[:external_id].present?
|
||||
submitters = submitters.where(submission_id: params[:submission_id]) if params[:submission_id].present?
|
||||
|
||||
if params[:template_id].present?
|
||||
submitters = submitters.joins(:submission).where(submission: { template_id: params[:template_id] })
|
||||
end
|
||||
|
||||
submitters = maybe_filder_by_completed_at(submitters, params)
|
||||
|
||||
submitters = paginate(
|
||||
@@ -66,18 +71,21 @@ module Api
|
||||
Submitters.send_signature_requests([@submitter])
|
||||
end
|
||||
|
||||
render json: Submitters::SerializeForApi.call(@submitter, with_template: false, with_events: false)
|
||||
render json: Submitters::SerializeForApi.call(@submitter, with_template: false,
|
||||
with_urls: true,
|
||||
with_events: false)
|
||||
end
|
||||
|
||||
def submitter_params
|
||||
submitter_params = params.key?(:submitter) ? params.require(:submitter) : params
|
||||
|
||||
submitter_params.permit(
|
||||
:send_email, :send_sms, :uuid, :name, :email, :role,
|
||||
:completed, :phone, :application_key, :external_id,
|
||||
:send_email, :send_sms, :reply_to, :completed_redirect_url, :uuid, :name, :email, :role,
|
||||
:completed, :phone, :application_key, :external_id, :go_to_last,
|
||||
{ metadata: {}, values: {}, readonly_fields: [], message: %i[subject body],
|
||||
fields: [[:name, :default_value, :readonly, :validation_pattern, :invalid_message,
|
||||
{ default_value: [] }]] }
|
||||
fields: [[:name, :uuid, :default_value, :value,
|
||||
:readonly, :redacted, :validation_pattern, :invalid_message,
|
||||
{ default_value: [], value: [] }]] }
|
||||
)
|
||||
end
|
||||
|
||||
@@ -125,6 +133,14 @@ module Api
|
||||
end
|
||||
|
||||
submitter.preferences['send_sms'] = submitter_preferences['send_sms'] if submitter_preferences.key?('send_sms')
|
||||
submitter.preferences['reply_to'] = submitter_preferences['reply_to'] if submitter_preferences.key?('reply_to')
|
||||
if submitter_preferences.key?('go_to_last')
|
||||
submitter.preferences['go_to_last'] = submitter_preferences['go_to_last']
|
||||
end
|
||||
|
||||
if submitter_preferences.key?('completed_redirect_url')
|
||||
submitter.preferences['completed_redirect_url'] = submitter_preferences['completed_redirect_url']
|
||||
end
|
||||
|
||||
return unless submitter_preferences.key?('email_message_uuid')
|
||||
|
||||
|
||||
@@ -91,10 +91,25 @@ module Api
|
||||
end
|
||||
|
||||
def template_params
|
||||
permitted_params = [
|
||||
:name,
|
||||
{
|
||||
submitters: [%i[name uuid]],
|
||||
fields: [[:uuid, :submitter_uuid, :name, :type,
|
||||
:required, :readonly, :default_value,
|
||||
:title, :description,
|
||||
{ preferences: {},
|
||||
conditions: [%i[field_uuid value action]],
|
||||
options: [%i[value uuid]],
|
||||
validation: %i[message pattern],
|
||||
areas: [%i[x y w h cell_w attachment_uuid option_uuid page]] }]]
|
||||
}
|
||||
]
|
||||
|
||||
if params.key?(:template)
|
||||
params.require(:template).permit(:name, submitters: [%i[name uuid]])
|
||||
params.require(:template).permit(permitted_params)
|
||||
else
|
||||
params.permit(:name, submitters: [%i[name uuid]])
|
||||
params.permit(permitted_params)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -15,6 +15,11 @@ class ErrorsController < ActionController::Base
|
||||
'/api/templates/docx'
|
||||
].freeze
|
||||
|
||||
SAFE_ERROR_MESSAGE_CLASSES = [
|
||||
ActionDispatch::Http::Parameters::ParseError,
|
||||
JSON::ParserError
|
||||
].freeze
|
||||
|
||||
def show
|
||||
if request.original_fullpath.in?(ENTERPRISE_PATHS) && error_status_code == 404
|
||||
return render json: { status: 404, message: ENTERPRISE_FEATURE_MESSAGE }, status: :not_found
|
||||
@@ -24,7 +29,11 @@ class ErrorsController < ActionController::Base
|
||||
f.json do
|
||||
set_cors_headers
|
||||
|
||||
render json: { status: error_status_code }, status: error_status_code
|
||||
exception = request.env['action_dispatch.exception']
|
||||
|
||||
error = exception.message if exception.class.in?(SAFE_ERROR_MESSAGE_CLASSES)
|
||||
|
||||
render json: { status: error_status_code, error: }.compact, status: error_status_code
|
||||
end
|
||||
|
||||
f.html { render error_status_code.to_s, status: error_status_code }
|
||||
|
||||
@@ -35,12 +35,12 @@ class NotificationsSettingsController < ApplicationController
|
||||
|
||||
def load_reminder_config
|
||||
@reminder_config =
|
||||
AccountConfig.find_or_initialize_by(account: current_account, key: AccountConfig::SUBMITTER_REMAILERS)
|
||||
AccountConfig.find_or_initialize_by(account: current_account, key: AccountConfig::SUBMITTER_REMINDERS)
|
||||
end
|
||||
|
||||
def email_config_params
|
||||
params.require(:account_config).permit!.tap do |attrs|
|
||||
attrs[:key] = nil unless attrs[:key].in?([AccountConfig::BCC_EMAILS, AccountConfig::SUBMITTER_REMAILERS])
|
||||
attrs[:key] = nil unless attrs[:key].in?([AccountConfig::BCC_EMAILS, AccountConfig::SUBMITTER_REMINDERS])
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -18,6 +18,12 @@ class PersonalizationSettingsController < ApplicationController
|
||||
end
|
||||
|
||||
def create
|
||||
if @account_config.value.is_a?(Hash)
|
||||
@account_config.value = @account_config.value.reject do |_, v|
|
||||
v.blank? && v != false
|
||||
end
|
||||
end
|
||||
|
||||
if @account_config.value != false && @account_config.value.blank?
|
||||
@account_config.destroy!
|
||||
else
|
||||
|
||||
@@ -7,6 +7,8 @@ class SessionsController < Devise::SessionsController
|
||||
email = sign_in_params[:email].to_s.downcase
|
||||
|
||||
if Docuseal.multitenant? && !User.exists?(email:)
|
||||
Rollbar.warning('Sign in new user') if defined?(Rollbar)
|
||||
|
||||
return redirect_to new_registration_path(sign_up: true, user: sign_in_params.slice(:email)),
|
||||
notice: 'Create a new account'
|
||||
end
|
||||
|
||||
@@ -19,6 +19,8 @@ class SubmissionsController < ApplicationController
|
||||
def create
|
||||
authorize!(:create, Submission)
|
||||
|
||||
save_template_message(@template, params) if params[:save_message] == '1'
|
||||
|
||||
if params[:is_custom_message] != '1'
|
||||
params.delete(:subject)
|
||||
params.delete(:body)
|
||||
@@ -50,11 +52,20 @@ class SubmissionsController < ApplicationController
|
||||
def destroy
|
||||
@submission.update!(archived_at: Time.current)
|
||||
|
||||
SendSubmissionArchivedWebhookRequestJob.perform_later(@submission)
|
||||
|
||||
redirect_back(fallback_location: template_path(@submission.template), notice: 'Submission has been archived')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def save_template_message(template, params)
|
||||
template.preferences['request_email_subject'] = params[:subject] if params[:subject].present?
|
||||
template.preferences['request_email_body'] = params[:body] if params[:body].present?
|
||||
|
||||
template.save!
|
||||
end
|
||||
|
||||
def submissions_params
|
||||
params.permit(submission: { submitters: [%i[uuid email phone name]] })
|
||||
end
|
||||
|
||||
@@ -44,7 +44,7 @@ class SubmissionsDownloadController < ApplicationController
|
||||
|
||||
def build_urls(submitter)
|
||||
Submitters.select_attachments_for_download(submitter).map do |attachment|
|
||||
ActiveStorage::Blob.proxy_url(attachment.blob, expires_at: FILES_TTL.minutes.from_now.to_i)
|
||||
ActiveStorage::Blob.proxy_url(attachment.blob, expires_at: FILES_TTL.from_now.to_i)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -55,7 +55,7 @@ class SubmitFormController < ApplicationController
|
||||
end
|
||||
|
||||
def completed
|
||||
@submitter = Submitter.find_by!(slug: params[:submit_form_slug])
|
||||
@submitter = Submitter.completed.find_by!(slug: params[:submit_form_slug])
|
||||
end
|
||||
|
||||
def success; end
|
||||
|
||||
@@ -7,6 +7,8 @@ class SubmittersSendEmailController < ApplicationController
|
||||
if Docuseal.multitenant? && SubmissionEvent.exists?(submitter: @submitter,
|
||||
event_type: 'send_email',
|
||||
created_at: 24.hours.ago..Time.current)
|
||||
Rollbar.warning("Already sent: #{@submitter.id}") if defined?(Rollbar)
|
||||
|
||||
return redirect_back(fallback_location: submission_path(@submitter.submission),
|
||||
alert: 'Email has been sent already.')
|
||||
end
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class TemplatesPreferencesController < ApplicationController
|
||||
load_and_authorize_resource :template
|
||||
|
||||
def show; end
|
||||
|
||||
def create
|
||||
authorize!(:update, @template)
|
||||
|
||||
@template.preferences = @template.preferences.merge(template_params[:preferences])
|
||||
@template.save!
|
||||
|
||||
head :ok
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def template_params
|
||||
params.require(:template).permit(preferences: %i[bcc_completed request_email_subject request_email_body])
|
||||
end
|
||||
end
|
||||
@@ -1,13 +1,20 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class UsersController < ApplicationController
|
||||
load_and_authorize_resource :user, only: %i[index edit new update destroy]
|
||||
load_and_authorize_resource :user, only: %i[index edit update destroy]
|
||||
|
||||
before_action :build_user, only: :create
|
||||
authorize_resource :user, only: :create
|
||||
before_action :build_user, only: %i[new create]
|
||||
authorize_resource :user, only: %i[new create]
|
||||
|
||||
def index
|
||||
@pagy, @users = pagy(@users.active.order(id: :desc))
|
||||
@users =
|
||||
if params[:status] == 'archived'
|
||||
@users.archived
|
||||
else
|
||||
@users.active
|
||||
end
|
||||
|
||||
@pagy, @users = pagy(@users.where(account: current_account).order(id: :desc))
|
||||
end
|
||||
|
||||
def new; end
|
||||
@@ -15,10 +22,19 @@ class UsersController < ApplicationController
|
||||
def edit; end
|
||||
|
||||
def create
|
||||
existing_user = User.accessible_by(current_ability).find_by(email: @user.email)
|
||||
|
||||
if existing_user
|
||||
existing_user.archived_at = nil
|
||||
existing_user.assign_attributes(user_params)
|
||||
|
||||
@user = existing_user
|
||||
end
|
||||
|
||||
if @user.save
|
||||
UserMailer.invitation_email(@user).deliver_later!
|
||||
|
||||
redirect_to settings_users_path, notice: 'User has been invited'
|
||||
redirect_back fallback_location: settings_users_path, notice: 'User has been invited'
|
||||
else
|
||||
render turbo_stream: turbo_stream.replace(:modal, template: 'users/new'), status: :unprocessable_entity
|
||||
end
|
||||
@@ -27,11 +43,11 @@ class UsersController < ApplicationController
|
||||
def update
|
||||
return redirect_to settings_users_path, notice: 'Unable to update user.' if Docuseal.demo?
|
||||
|
||||
attrs = user_params.compact_blank
|
||||
attrs = user_params.compact_blank.merge(user_params.slice(:archived_at))
|
||||
attrs.delete(:role) if !role_valid?(attrs[:role]) || current_user == @user
|
||||
|
||||
if @user.update(attrs)
|
||||
redirect_to settings_users_path, notice: 'User has been updated'
|
||||
redirect_back fallback_location: settings_users_path, notice: 'User has been updated'
|
||||
else
|
||||
render turbo_stream: turbo_stream.replace(:modal, template: 'users/edit'), status: :unprocessable_entity
|
||||
end
|
||||
@@ -44,7 +60,7 @@ class UsersController < ApplicationController
|
||||
|
||||
@user.update!(archived_at: Time.current)
|
||||
|
||||
redirect_to settings_users_path, notice: 'User has been removed'
|
||||
redirect_back fallback_location: settings_users_path, notice: 'User has been removed'
|
||||
end
|
||||
|
||||
private
|
||||
@@ -65,6 +81,11 @@ class UsersController < ApplicationController
|
||||
end
|
||||
|
||||
def user_params
|
||||
params.require(:user).permit(:email, :first_name, :last_name, :password, :role)
|
||||
if params.key?(:user)
|
||||
params.require(:user).permit(:email, :first_name, :last_name, :password,
|
||||
:role, :archived_at, :account_id)
|
||||
else
|
||||
{}
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,33 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class WebhookPreferencesController < ApplicationController
|
||||
EVENTS = %w[
|
||||
form.viewed
|
||||
form.started
|
||||
form.completed
|
||||
submission.archived
|
||||
].freeze
|
||||
|
||||
before_action :load_account_config
|
||||
authorize_resource :account_config, parent: false
|
||||
|
||||
def create
|
||||
@account_config.value[account_config_params[:event]] = account_config_params[:value] == '1'
|
||||
|
||||
@account_config.save!
|
||||
|
||||
head :ok
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_account_config
|
||||
@account_config =
|
||||
current_account.account_configs.find_or_initialize_by(key: AccountConfig::WEBHOOK_PREFERENCES_KEY)
|
||||
@account_config.value ||= {}
|
||||
end
|
||||
|
||||
def account_config_params
|
||||
params.permit(:event, :value)
|
||||
end
|
||||
end
|
||||
@@ -23,6 +23,7 @@ import SignatureForm from './elements/signature_form'
|
||||
import SubmitForm from './elements/submit_form'
|
||||
import PromptPassword from './elements/prompt_password'
|
||||
import EmailsTextarea from './elements/emails_textarea'
|
||||
import ToggleOnSubmit from './elements/toggle_on_submit'
|
||||
|
||||
import * as TurboInstantClick from './lib/turbo_instant_click'
|
||||
|
||||
@@ -58,6 +59,7 @@ window.customElements.define('submit-form', SubmitForm)
|
||||
window.customElements.define('prompt-password', PromptPassword)
|
||||
window.customElements.define('emails-textarea', EmailsTextarea)
|
||||
window.customElements.define('toggle-cookies', ToggleCookies)
|
||||
window.customElements.define('toggle-on-submit', ToggleOnSubmit)
|
||||
|
||||
document.addEventListener('turbo:before-fetch-request', encodeMethodIntoRequestBody)
|
||||
document.addEventListener('turbo:submit-end', async (event) => {
|
||||
|
||||
@@ -7,7 +7,7 @@ export default class extends HTMLElement {
|
||||
|
||||
resize () {
|
||||
if (this.textarea.clientHeight < this.textarea.scrollHeight) {
|
||||
this.textarea.style.height = `${this.textarea.scrollHeight}px`
|
||||
this.textarea.style.height = `${this.textarea.scrollHeight + 5}px`
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
export default class extends HTMLElement {
|
||||
connectedCallback () {
|
||||
document.addEventListener('turbo:submit-end', this.onSubmitEnd)
|
||||
|
||||
this.form.addEventListener('submit', this.onSubmit)
|
||||
}
|
||||
|
||||
disconnectedCallback () {
|
||||
document.removeEventListener('turbo:submit-end', this.onSubmitEnd)
|
||||
|
||||
this.form.removeEventListener('submit', this.onSubmit)
|
||||
}
|
||||
|
||||
onSubmit = () => {
|
||||
this.element.classList.add('invisible')
|
||||
}
|
||||
|
||||
onSubmitEnd = (event) => {
|
||||
if (event.target === this.form) {
|
||||
const resp = event.detail?.formSubmission?.result?.fetchResponse?.response
|
||||
|
||||
if (resp?.status / 100 === 2) {
|
||||
this.element.classList.remove('invisible')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get element () {
|
||||
return document.getElementById(this.dataset.elementId)
|
||||
}
|
||||
|
||||
get form () {
|
||||
return this.closest('form')
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div
|
||||
class="flex absolute lg:text-base -outline-offset-1"
|
||||
class="field-area flex absolute lg:text-base -outline-offset-1"
|
||||
dir="auto"
|
||||
:style="computedStyle"
|
||||
:class="{ 'text-[1.5vw] lg:text-base': !textOverflowChars, 'text-[1.0vw] lg:text-xs': textOverflowChars, 'cursor-default': !submittable, 'border border-red-100 bg-red-100 cursor-pointer': submittable, 'border border-red-100': !isActive && submittable, 'bg-opacity-80': !isActive && !isValueSet && submittable, 'outline-red-500 outline-dashed outline-2 z-10': isActive && submittable, 'bg-opacity-40': (isActive || isValueSet) && submittable }"
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
</label>
|
||||
<button
|
||||
class="btn btn-outline btn-sm !normal-case font-normal"
|
||||
@click.prevent="setCurrentDate"
|
||||
@click.prevent="[setCurrentDate(), $emit('focus')]"
|
||||
>
|
||||
<IconCalendarCheck :width="16" />
|
||||
{{ t('set_today') }}
|
||||
|
||||
@@ -16,13 +16,17 @@
|
||||
<button
|
||||
v-if="!isFormVisible"
|
||||
id="expand_form_button"
|
||||
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 bottom-0 w-full mb-3"
|
||||
style="width: 96%; margin-left: 2%"
|
||||
@click.prevent="[isFormVisible = true, scrollIntoField(currentField)]"
|
||||
>
|
||||
<template v-if="['initials', 'signature'].includes(currentField.type)">
|
||||
<IconWritingSign stroke-width="1.5" />
|
||||
{{ t('sign_now') }}
|
||||
</template>
|
||||
<template v-else-if="alwaysMinimize">
|
||||
{{ t('next') }}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ t('submit_form') }}
|
||||
</template>
|
||||
@@ -356,7 +360,9 @@
|
||||
:key="currentField.uuid"
|
||||
v-model="values[currentField.uuid]"
|
||||
:field="currentField"
|
||||
:locale="language?.toLowerCase() || browserLanguage"
|
||||
:show-field-names="showFieldNames"
|
||||
:verified-value="phoneVerifiedValues[currentField.uuid]"
|
||||
:default-value="submitter.phone"
|
||||
:submitter-slug="submitterSlug"
|
||||
@focus="scrollIntoField(currentField)"
|
||||
@@ -390,7 +396,7 @@
|
||||
v-if="isSubmitting"
|
||||
class="mr-1 animate-spin"
|
||||
/>
|
||||
<span v-if="stepFields.length === currentStep + 1">
|
||||
<span v-if="alwaysMinimize || stepFields.length === currentStep + 1">
|
||||
{{ t('submit') }}
|
||||
</span>
|
||||
<span v-else>
|
||||
@@ -466,10 +472,14 @@ import i18n from './i18n'
|
||||
const isEmpty = (obj) => {
|
||||
if (obj == null) return true
|
||||
|
||||
if (Array.isArray(obj) || typeof obj === 'string') {
|
||||
if (Array.isArray(obj)) {
|
||||
return obj.length === 0
|
||||
}
|
||||
|
||||
if (typeof obj === 'string') {
|
||||
return obj.trim().length === 0
|
||||
}
|
||||
|
||||
if (typeof obj === 'object') {
|
||||
return Object.keys(obj).length === 0
|
||||
}
|
||||
@@ -539,11 +549,26 @@ export default {
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
minimize: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
reuseSignature: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
withConfetti: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
autoscrollFields: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
showFieldNames: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
@@ -646,12 +671,20 @@ export default {
|
||||
isFormVisible: this.expand !== false,
|
||||
showFillAllRequiredFields: false,
|
||||
currentStep: 0,
|
||||
phoneVerifiedValues: {},
|
||||
orientation: screen?.orientation?.type,
|
||||
isSubmitting: false,
|
||||
submittedValues: {},
|
||||
recalculateButtonDisabledKey: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isMobile () {
|
||||
return /android|iphone|ipad/i.test(navigator.userAgent)
|
||||
},
|
||||
alwaysMinimize () {
|
||||
return this.minimize || (this.orientation?.includes('landscape') && this.isMobile && parseInt(window.innerHeight) < 550)
|
||||
},
|
||||
currentStepFields () {
|
||||
return this.stepFields[this.currentStep]
|
||||
},
|
||||
@@ -672,9 +705,13 @@ export default {
|
||||
}, {})
|
||||
},
|
||||
previousInitialsValue () {
|
||||
const initialsField = [...this.fields].reverse().find((field) => field.type === 'initials' && !!this.values[field.uuid])
|
||||
if (this.reuseSignature !== false) {
|
||||
const initialsField = [...this.fields].reverse().find((field) => field.type === 'initials' && !!this.values[field.uuid])
|
||||
|
||||
return this.values[initialsField?.uuid]
|
||||
return this.values[initialsField?.uuid]
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
},
|
||||
isAnonymousChecboxes () {
|
||||
return this.currentField.type === 'checkbox' && this.currentStepFields.every((e) => !e.name && !e.required) && this.currentStepFields.length > 4
|
||||
@@ -724,11 +761,21 @@ export default {
|
||||
watch: {
|
||||
expand (value) {
|
||||
this.isFormVisible = value
|
||||
},
|
||||
currentStepFields (value) {
|
||||
if (!value) {
|
||||
this.currentStep -= 1
|
||||
}
|
||||
}
|
||||
},
|
||||
beforeUnmount () {
|
||||
screen?.orientation?.removeEventListener('change', this.onOrientationChange)
|
||||
},
|
||||
mounted () {
|
||||
this.submittedValues = JSON.parse(JSON.stringify(this.values))
|
||||
|
||||
screen?.orientation.addEventListener('change', this.onOrientationChange)
|
||||
|
||||
this.fields.forEach((field) => {
|
||||
if (field.default_value && !field.readonly) {
|
||||
this.values[field.uuid] ||= field.default_value
|
||||
@@ -762,6 +809,10 @@ export default {
|
||||
this.isFormVisible = false
|
||||
}
|
||||
|
||||
if (this.alwaysMinimize) {
|
||||
this.isFormVisible = false
|
||||
}
|
||||
|
||||
if (/iPhone|iPad|iPod/i.test(navigator.userAgent)) {
|
||||
this.$nextTick(() => {
|
||||
const root = this.$root.$el.parentNode.getRootNode()
|
||||
@@ -791,6 +842,9 @@ export default {
|
||||
t (key) {
|
||||
return this.i18n[key] || i18n[this.language?.toLowerCase()]?.[key] || i18n[this.browserLanguage]?.[key] || i18n.en[key] || key
|
||||
},
|
||||
onOrientationChange (event) {
|
||||
this.orientation = event.target.type
|
||||
},
|
||||
checkFieldConditions (field) {
|
||||
if (field.conditions?.length) {
|
||||
return field.conditions.reduce((acc, c) => {
|
||||
@@ -885,11 +939,15 @@ export default {
|
||||
})
|
||||
},
|
||||
previousSignatureValueFor (field) {
|
||||
const signatureField = [...this.fields].reverse().find((f) =>
|
||||
f.type === 'signature' && field.preferences?.format === f.preferences?.format && !!this.values[f.uuid]
|
||||
)
|
||||
if (this.reuseSignature !== false) {
|
||||
const signatureField = [...this.fields].reverse().find((f) =>
|
||||
f.type === 'signature' && field.preferences?.format === f.preferences?.format && !!this.values[f.uuid]
|
||||
)
|
||||
|
||||
return this.values[signatureField?.uuid]
|
||||
return this.values[signatureField?.uuid]
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
},
|
||||
goToStep (step, scrollToArea = false, clickUpload = false) {
|
||||
this.currentStep = this.stepFields.indexOf(step)
|
||||
@@ -900,9 +958,9 @@ export default {
|
||||
|
||||
if (scrollToArea) {
|
||||
this.scrollIntoField(step[0])
|
||||
}
|
||||
|
||||
this.$refs.form.querySelector('input[type="date"], input[type="number"], input[type="text"], select')?.focus()
|
||||
this.$refs.form.querySelector('input[type="date"], input[type="number"], input[type="text"], select')?.focus()
|
||||
}
|
||||
|
||||
if (clickUpload && !this.values[this.currentField.uuid] && ['file', 'image'].includes(this.currentField.type)) {
|
||||
this.$refs.form.querySelector('input[type="file"]')?.click()
|
||||
@@ -911,6 +969,7 @@ export default {
|
||||
},
|
||||
saveStep (formData) {
|
||||
const currentFieldUuids = this.currentStepFields.map((f) => f.uuid)
|
||||
const currentFieldType = this.currentField.type
|
||||
|
||||
if (this.isCompleted) {
|
||||
return Promise.resolve({})
|
||||
@@ -922,6 +981,10 @@ export default {
|
||||
if (response.status === 200) {
|
||||
currentFieldUuids.forEach((fieldUuid) => {
|
||||
this.submittedValues[fieldUuid] = this.values[fieldUuid]
|
||||
|
||||
if (currentFieldType === 'phone') {
|
||||
this.phoneVerifiedValues[fieldUuid] = this.values[fieldUuid]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -960,7 +1023,15 @@ export default {
|
||||
formData.append('completed', 'true')
|
||||
}
|
||||
|
||||
await this.saveStep(formData).then(async (response) => {
|
||||
let saveStepRequest
|
||||
|
||||
if (!isLastStep && this.phoneVerifiedValues[this.currentField.uuid] && this.phoneVerifiedValues[this.currentField.uuid] === this.values[this.currentField.uuid]) {
|
||||
saveStepRequest = Promise.resolve({})
|
||||
} else {
|
||||
saveStepRequest = this.saveStep(formData)
|
||||
}
|
||||
|
||||
await saveStepRequest.then(async (response) => {
|
||||
if (response.status === 422 || response.status === 500) {
|
||||
const data = await response.json()
|
||||
|
||||
@@ -976,7 +1047,11 @@ export default {
|
||||
const nextStep = (isLastStep && emptyRequiredField) || this.stepFields[this.currentStep + 1]
|
||||
|
||||
if (nextStep) {
|
||||
this.goToStep(nextStep, true)
|
||||
if (this.alwaysMinimize) {
|
||||
this.isFormVisible = false
|
||||
}
|
||||
|
||||
this.goToStep(nextStep, this.autoscrollFields)
|
||||
|
||||
if (emptyRequiredField === nextStep) {
|
||||
this.showFillAllRequiredFields = true
|
||||
|
||||
@@ -24,7 +24,7 @@ const en = {
|
||||
appears_on: 'Appears on',
|
||||
page: 'Page',
|
||||
select_your_option: 'Select your option',
|
||||
complete_hightlighted_checkboxes_and_click: 'Complete hightlighted checkboxes and click',
|
||||
complete_hightlighted_checkboxes_and_click: 'Complete highlighted checkboxes and click',
|
||||
submit: 'submit',
|
||||
next: 'next',
|
||||
click_to_upload: 'Click to upload',
|
||||
@@ -42,7 +42,7 @@ const en = {
|
||||
please_check_the_box_to_continue: 'Please check the box to continue',
|
||||
open_source_documents_software: 'open source documents software',
|
||||
verified_phone_number: 'Verify Phone Number',
|
||||
use_international_format: 'Use internatioanl format: +1xxx',
|
||||
use_international_format: 'Use international format: +1xxx',
|
||||
six_digits_code: '6-digit code',
|
||||
change_phone_number: 'Change phone number',
|
||||
sending: 'Sending...',
|
||||
|
||||
@@ -193,8 +193,6 @@ export default {
|
||||
|
||||
this.$refs.canvas.getContext('2d').scale(scale, scale)
|
||||
}
|
||||
|
||||
this.$refs.textInput?.focus()
|
||||
})
|
||||
|
||||
if (this.$refs.canvas) {
|
||||
|
||||
@@ -108,10 +108,20 @@ export default {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
verifiedValue: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: ''
|
||||
},
|
||||
submitterSlug: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
locale: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'en'
|
||||
},
|
||||
showFieldNames: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
@@ -158,12 +168,17 @@ export default {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
submitter_slug: this.submitterSlug,
|
||||
locale: this.locale,
|
||||
phone: this.$refs.phone.value
|
||||
}),
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
})
|
||||
},
|
||||
async submit () {
|
||||
if (this.verifiedValue && this.verifiedValue === this.modelValue) {
|
||||
return Promise.resolve({})
|
||||
}
|
||||
|
||||
if (!this.$refs.phone.value.toString().startsWith('+')) {
|
||||
alert(this.t('use_international_format'))
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
@keydown.enter.prevent="onNameEnter"
|
||||
@focus="onNameFocus"
|
||||
@blur="onNameBlur"
|
||||
>{{ optionIndexText }} {{ field.name || defaultName }}</span>
|
||||
>{{ optionIndexText }} {{ (defaultField ? (field.title || field.name) : field.name) || defaultName }}</span>
|
||||
<div
|
||||
v-if="isNameFocus && !['checkbox', 'phone'].includes(field.type)"
|
||||
class="flex items-center ml-1.5"
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
class="btn btn-primary btn-ghost text-base hidden md:flex"
|
||||
:target="template.submitters.length > 1 ? '' : '_blank'"
|
||||
:data-turbo-frame="template.submitters.length > 1 ? 'modal' : ''"
|
||||
@click="maybeShowEmptyTemplateAlert"
|
||||
@click="maybeShowErrorTemplateAlert"
|
||||
>
|
||||
<IconWritingSign
|
||||
width="22"
|
||||
@@ -51,7 +51,7 @@
|
||||
:href="`/templates/${template.id}/submissions/new?with_link=true`"
|
||||
data-turbo-frame="modal"
|
||||
class="white-button md:!px-6"
|
||||
@click="maybeShowEmptyTemplateAlert"
|
||||
@click="maybeShowErrorTemplateAlert"
|
||||
>
|
||||
<IconUsersPlus
|
||||
width="20"
|
||||
@@ -159,7 +159,7 @@
|
||||
:selected-submitter="selectedSubmitter"
|
||||
:document="document"
|
||||
:is-drag="!!dragField"
|
||||
:default-fields="defaultFields"
|
||||
:default-fields="[...defaultRequiredFields, ...defaultFields]"
|
||||
:allow-draw="!onlyDefinedFields"
|
||||
:default-submitters="defaultSubmitters"
|
||||
:draw-field="drawField"
|
||||
@@ -242,7 +242,8 @@
|
||||
:with-help="withHelp"
|
||||
:default-submitters="defaultSubmitters"
|
||||
:draw-field-type="drawFieldType"
|
||||
:default-fields="defaultFields"
|
||||
:default-fields="[...defaultRequiredFields, ...defaultFields]"
|
||||
:default-required-fields="defaultRequiredFields"
|
||||
:field-types="fieldTypes"
|
||||
:with-sticky-submitters="withStickySubmitters"
|
||||
:only-defined-fields="onlyDefinedFields"
|
||||
@@ -273,7 +274,8 @@
|
||||
<MobileFields
|
||||
v-if="sortedDocuments.length && !drawField && editable"
|
||||
:fields="template.fields"
|
||||
:default-fields="defaultFields"
|
||||
:default-fields="[...defaultRequiredFields, ...defaultFields]"
|
||||
:default-required-fields="defaultRequiredFields"
|
||||
:field-types="fieldTypes"
|
||||
:selected-submitter="selectedSubmitter"
|
||||
@select="startFieldDraw($event)"
|
||||
@@ -371,6 +373,11 @@ export default {
|
||||
required: false,
|
||||
default: () => []
|
||||
},
|
||||
defaultRequiredFields: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => []
|
||||
},
|
||||
withSelectedFieldType: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
@@ -425,6 +432,13 @@ export default {
|
||||
return () => {}
|
||||
}
|
||||
},
|
||||
onChange: {
|
||||
type: Function,
|
||||
required: false,
|
||||
default () {
|
||||
return () => {}
|
||||
}
|
||||
},
|
||||
withStickySubmitters: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
@@ -522,6 +536,11 @@ export default {
|
||||
|
||||
return areas
|
||||
},
|
||||
isAllRequiredFieldsAdded () {
|
||||
return !this.defaultRequiredFields?.some((f) => {
|
||||
return !this.template.fields?.some((field) => field.name === f.name)
|
||||
})
|
||||
},
|
||||
selectedField () {
|
||||
return this.template.fields.find((f) => f.areas?.includes(this.selectedAreaRef.value))
|
||||
},
|
||||
@@ -1093,7 +1112,13 @@ export default {
|
||||
|
||||
this.save()
|
||||
},
|
||||
maybeShowEmptyTemplateAlert (e) {
|
||||
maybeShowErrorTemplateAlert (e) {
|
||||
if (!this.isAllRequiredFieldsAdded) {
|
||||
e.preventDefault()
|
||||
|
||||
return alert(this.t('add_all_required_fields_to_continue'))
|
||||
}
|
||||
|
||||
if (!this.template.fields.length) {
|
||||
e.preventDefault()
|
||||
|
||||
@@ -1101,6 +1126,10 @@ export default {
|
||||
}
|
||||
},
|
||||
onSaveClick () {
|
||||
if (!this.isAllRequiredFieldsAdded) {
|
||||
return alert(this.t('add_all_required_fields_to_continue'))
|
||||
}
|
||||
|
||||
if (this.template.fields.length) {
|
||||
this.isSaving = true
|
||||
|
||||
@@ -1131,6 +1160,10 @@ export default {
|
||||
})
|
||||
},
|
||||
save ({ force } = { force: false }) {
|
||||
if (this.onChange) {
|
||||
this.onChange(this.template)
|
||||
}
|
||||
|
||||
if (!this.autosave && !force) {
|
||||
return Promise.resolve({})
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
v-model="description"
|
||||
dir="auto"
|
||||
class="base-textarea !text-base w-full"
|
||||
:readonly="!editable"
|
||||
@input="resizeTextarea"
|
||||
/>
|
||||
</div>
|
||||
@@ -49,6 +50,7 @@
|
||||
id="title_field"
|
||||
v-model="title"
|
||||
dir="auto"
|
||||
:readonly="!editable"
|
||||
class="base-input !text-base w-full"
|
||||
>
|
||||
</div>
|
||||
@@ -73,6 +75,11 @@ export default {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
editable: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
buildDefaultName: {
|
||||
type: Function,
|
||||
required: true
|
||||
|
||||
@@ -16,12 +16,14 @@
|
||||
v-model="field.type"
|
||||
:editable="editable && !defaultField"
|
||||
:button-width="20"
|
||||
:menu-classes="'mt-1.5'"
|
||||
:menu-style="{ backgroundColor: dropdownBgColor }"
|
||||
@update:model-value="[maybeUpdateOptions(), save()]"
|
||||
@click="scrollToFirstArea"
|
||||
/>
|
||||
<Contenteditable
|
||||
ref="name"
|
||||
:model-value="field.name || defaultName"
|
||||
:model-value="(defaultField ? (field.title || field.name) : field.name) || defaultName"
|
||||
:editable="editable && !defaultField"
|
||||
:icon-inline="true"
|
||||
:icon-width="18"
|
||||
@@ -110,7 +112,8 @@
|
||||
<ul
|
||||
v-if="renderDropdown"
|
||||
tabindex="0"
|
||||
class="mt-1.5 dropdown-content menu menu-xs p-2 shadow bg-base-100 rounded-box w-52 z-10"
|
||||
class="mt-1.5 dropdown-content menu menu-xs p-2 shadow rounded-box w-52 z-10"
|
||||
:style="{ backgroundColor: dropdownBgColor }"
|
||||
draggable="true"
|
||||
@dragstart.prevent.stop
|
||||
@click="closeDropdown"
|
||||
@@ -121,7 +124,7 @@
|
||||
@click.stop
|
||||
>
|
||||
<select
|
||||
class="select select-bordered select-xs w-full max-w-xs h-7 !outline-0 font-normal"
|
||||
class="select select-bordered select-xs w-full max-w-xs h-7 !outline-0 font-normal bg-transparent"
|
||||
@change="[field.preferences ||= {}, field.preferences.align = $event.target.value, save()]"
|
||||
>
|
||||
<option
|
||||
@@ -134,7 +137,7 @@
|
||||
</option>
|
||||
</select>
|
||||
<label
|
||||
:style="{ backgroundColor: backgroundColor }"
|
||||
:style="{ backgroundColor: dropdownBgColor }"
|
||||
class="absolute -top-1 left-2.5 px-1 h-4"
|
||||
style="font-size: 8px"
|
||||
>
|
||||
@@ -151,12 +154,12 @@
|
||||
:placeholder="t('default_value')"
|
||||
dir="auto"
|
||||
:type="field.type"
|
||||
class="input input-bordered input-xs w-full max-w-xs h-7 !outline-0"
|
||||
class="input input-bordered input-xs w-full max-w-xs h-7 !outline-0 bg-transparent"
|
||||
@blur="save"
|
||||
>
|
||||
<label
|
||||
v-if="field.default_value"
|
||||
:style="{ backgroundColor: backgroundColor }"
|
||||
:style="{ backgroundColor: dropdownBgColor }"
|
||||
class="absolute -top-1 left-2.5 px-1 h-4"
|
||||
style="font-size: 8px"
|
||||
>
|
||||
@@ -171,7 +174,7 @@
|
||||
<select
|
||||
v-model="field.preferences.format"
|
||||
:placeholder="t('format')"
|
||||
class="select select-bordered select-xs font-normal w-full max-w-xs !h-7 !outline-0"
|
||||
class="select select-bordered select-xs font-normal w-full max-w-xs !h-7 !outline-0 bg-transparent"
|
||||
@change="save"
|
||||
>
|
||||
<option
|
||||
@@ -183,7 +186,7 @@
|
||||
</option>
|
||||
</select>
|
||||
<label
|
||||
:style="{ backgroundColor: backgroundColor }"
|
||||
:style="{ backgroundColor: dropdownBgColor }"
|
||||
class="absolute -top-1 left-2.5 px-1 h-4"
|
||||
style="font-size: 8px"
|
||||
>
|
||||
@@ -197,7 +200,7 @@
|
||||
>
|
||||
<select
|
||||
:placeholder="t('format')"
|
||||
class="select select-bordered select-xs font-normal w-full max-w-xs !h-7 !outline-0"
|
||||
class="select select-bordered select-xs font-normal w-full max-w-xs !h-7 !outline-0 bg-transparent"
|
||||
@change="[field.preferences.format = $event.target.value, save()]"
|
||||
>
|
||||
<option
|
||||
@@ -220,7 +223,7 @@
|
||||
</option>
|
||||
</select>
|
||||
<label
|
||||
:style="{ backgroundColor: backgroundColor }"
|
||||
:style="{ backgroundColor: dropdownBgColor }"
|
||||
class="absolute -top-1 left-2.5 px-1 h-4"
|
||||
style="font-size: 8px"
|
||||
>
|
||||
@@ -235,6 +238,7 @@
|
||||
<input
|
||||
v-model="field.required"
|
||||
type="checkbox"
|
||||
:disabled="!editable || defaultField"
|
||||
class="toggle toggle-xs"
|
||||
@update:model-value="save"
|
||||
>
|
||||
@@ -481,6 +485,7 @@
|
||||
>
|
||||
<FormulaModal
|
||||
:field="field"
|
||||
:editable="editable && !defaultField"
|
||||
:build-default-name="buildDefaultName"
|
||||
@close="isShowFormulaModal = false"
|
||||
/>
|
||||
@@ -501,6 +506,7 @@
|
||||
>
|
||||
<DescriptionModal
|
||||
:field="field"
|
||||
:editable="editable && !defaultField"
|
||||
:build-default-name="buildDefaultName"
|
||||
@close="isShowDescriptionModal = false"
|
||||
/>
|
||||
@@ -566,6 +572,9 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
fieldNames: FieldType.computed.fieldNames,
|
||||
dropdownBgColor () {
|
||||
return ['', null, 'transparent'].includes(this.backgroundColor) ? 'white' : this.backgroundColor
|
||||
},
|
||||
schemaAttachmentsIndexes () {
|
||||
return (this.template.schema || []).reduce((acc, item, index) => {
|
||||
acc[item.attachment_uuid] = index
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
tabindex="0"
|
||||
class="rounded-md min-w-max mb-2"
|
||||
:class="menuClasses"
|
||||
:style="menuStyle"
|
||||
@click="closeDropdown"
|
||||
>
|
||||
<li
|
||||
@@ -136,6 +137,7 @@
|
||||
v-if="(editable || !compact) && renderDropdown"
|
||||
tabindex="0"
|
||||
:class="menuClasses"
|
||||
:style="menuStyle"
|
||||
@click="closeDropdown"
|
||||
>
|
||||
<li
|
||||
@@ -253,10 +255,15 @@ export default {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
menuStyle: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => ({})
|
||||
},
|
||||
menuClasses: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'dropdown-content menu p-2 shadow bg-base-100 rounded-box w-full z-10'
|
||||
default: 'dropdown-content menu p-2 shadow rounded-box w-full z-10'
|
||||
}
|
||||
},
|
||||
emits: ['update:model-value', 'remove', 'new-submitter', 'name-change'],
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
v-if="editable && renderDropdown"
|
||||
tabindex="0"
|
||||
class="dropdown-content menu menu-xs p-2 shadow rounded-box w-52 z-10 mb-3"
|
||||
:style="menuStyle"
|
||||
:class="menuClasses"
|
||||
@click="closeDropdown"
|
||||
>
|
||||
@@ -65,6 +66,11 @@ export default {
|
||||
required: false,
|
||||
default: 'mt-1.5 bg-base-100'
|
||||
},
|
||||
menuStyle: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => ({})
|
||||
},
|
||||
buttonClasses: {
|
||||
type: String,
|
||||
required: false,
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
<FieldSubmitter
|
||||
:model-value="selectedSubmitter.uuid"
|
||||
class="roles-dropdown w-full rounded-lg"
|
||||
:class="{ 'bg-base-100': withStickySubmitters }"
|
||||
:style="withStickySubmitters ? { backgroundColor } : {}"
|
||||
:submitters="submitters"
|
||||
:menu-style="{ backgroundColor: ['', null, 'transparent'].includes(backgroundColor) ? 'white' : backgroundColor }"
|
||||
:editable="editable && !defaultSubmitters.length"
|
||||
@new-submitter="save"
|
||||
@remove="removeSubmitter"
|
||||
@@ -71,7 +72,7 @@
|
||||
:key="field.name"
|
||||
>
|
||||
<div
|
||||
:style="{ backgroundColor: backgroundColor }"
|
||||
:style="{ backgroundColor }"
|
||||
draggable="true"
|
||||
class="default-field border border-base-300 rounded rounded-tr-none relative group mb-2"
|
||||
@dragstart="onDragstart({ type: 'text', ...field })"
|
||||
@@ -86,9 +87,16 @@
|
||||
:button-width="20"
|
||||
/>
|
||||
<span class="block pl-0.5">
|
||||
{{ field.name }}
|
||||
{{ field.title || field.name }}
|
||||
</span>
|
||||
</div>
|
||||
<span
|
||||
v-if="defaultRequiredFields.includes(field)"
|
||||
:data-tip="t('required')"
|
||||
class="text-red-400 text-3xl pr-1.5 tooltip tooltip-left h-8"
|
||||
>
|
||||
*
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -106,7 +114,7 @@
|
||||
v-if="(fieldTypes.length === 0 || fieldTypes.includes(type)) && (withPhone || type != 'phone') && (withPayment || type != 'payment')"
|
||||
draggable="true"
|
||||
class="field-type-button group flex items-center justify-center border border-dashed w-full rounded relative"
|
||||
:style="{ backgroundColor: backgroundColor }"
|
||||
:style="{ backgroundColor }"
|
||||
:class="drawFieldType === type ? 'border-base-content/40' : 'border-base-300 hover:border-base-content/20'"
|
||||
@dragstart="onDragstart({ type: type })"
|
||||
@dragend="$emit('drag-end')"
|
||||
@@ -135,7 +143,7 @@
|
||||
href="https://www.docuseal.co/pricing"
|
||||
target="_blank"
|
||||
class="opacity-50 flex items-center justify-center border border-dashed border-base-300 w-full rounded relative"
|
||||
:style="{ backgroundColor: backgroundColor }"
|
||||
:style="{ backgroundColor }"
|
||||
>
|
||||
<div class="w-0 absolute left-0">
|
||||
<IconLock
|
||||
@@ -210,6 +218,11 @@ export default {
|
||||
required: false,
|
||||
default: () => []
|
||||
},
|
||||
defaultRequiredFields: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => []
|
||||
},
|
||||
onlyDefinedFields: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
ref="textarea"
|
||||
v-model="formula"
|
||||
class="base-textarea !rounded-xl !text-base font-mono w-full !outline-0 !ring-0 !px-3"
|
||||
:readonly="!editable"
|
||||
required="true"
|
||||
@input="resizeTextarea"
|
||||
/>
|
||||
@@ -134,6 +135,11 @@ export default {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
editable: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
buildDefaultName: {
|
||||
type: Function,
|
||||
required: true
|
||||
|
||||
@@ -3,6 +3,7 @@ const en = {
|
||||
field_not_found: 'Field not found',
|
||||
clear: 'Clear',
|
||||
align: 'Align',
|
||||
add_all_required_fields_to_continue: 'Add all required fields to continue',
|
||||
left: 'Left',
|
||||
right: 'Right',
|
||||
center: 'Center',
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
:submitters="submitters"
|
||||
:editable="editable"
|
||||
:mobile-view="true"
|
||||
:menu-style="{ backgroundColor: ['', null, 'transparent'].includes(backgroundColor) ? 'white' : backgroundColor }"
|
||||
@new-submitter="save"
|
||||
@remove="removeSubmitter"
|
||||
@name-change="save"
|
||||
@@ -43,7 +44,7 @@ export default {
|
||||
Field,
|
||||
FieldSubmitter
|
||||
},
|
||||
inject: ['save', 't'],
|
||||
inject: ['save', 't', 'backgroundColor'],
|
||||
props: {
|
||||
drawField: {
|
||||
type: Object,
|
||||
|
||||
@@ -38,7 +38,14 @@
|
||||
:stroke-width="1.6"
|
||||
:width="20"
|
||||
/>
|
||||
{{ field.name }}
|
||||
{{ field.title || field.name }}
|
||||
<span
|
||||
v-if="defaultRequiredFields.includes(field)"
|
||||
:data-tip="t('required')"
|
||||
class="text-red-400 text-2xl tooltip tooltip-left h-6"
|
||||
>
|
||||
*
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
</template>
|
||||
@@ -99,6 +106,11 @@ export default {
|
||||
required: false,
|
||||
default: () => []
|
||||
},
|
||||
defaultRequiredFields: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => []
|
||||
},
|
||||
defaultFields: {
|
||||
type: Array,
|
||||
required: false,
|
||||
|
||||
@@ -219,7 +219,7 @@ export default {
|
||||
this.newArea.y = e.offsetY / this.$refs.mask.clientHeight
|
||||
}
|
||||
|
||||
if (this.drawField?.type === 'cells') {
|
||||
if ((this.drawField?.type || this.drawFieldType) === 'cells') {
|
||||
this.newArea.cell_w = this.newArea.h * (this.$refs.mask.clientHeight / this.$refs.mask.clientWidth)
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ class ProcessSubmitterCompletionJob < ApplicationJob
|
||||
enqueue_completed_emails(submitter)
|
||||
end
|
||||
|
||||
return if Accounts.load_webhook_configs(submitter.account).blank?
|
||||
return if Accounts.load_webhook_url(submitter.account).blank?
|
||||
|
||||
SendFormCompletedWebhookRequestJob.perform_later(submitter)
|
||||
end
|
||||
@@ -34,6 +34,7 @@ class ProcessSubmitterCompletionJob < ApplicationJob
|
||||
end
|
||||
|
||||
bcc = submission.preferences['bcc_completed'].presence ||
|
||||
submission.template.preferences['bcc_completed'].presence ||
|
||||
submission.account.account_configs
|
||||
.find_by(key: AccountConfig::BCC_EMAILS)&.value
|
||||
|
||||
|
||||
@@ -7,16 +7,20 @@ class SendFormCompletedWebhookRequestJob < ApplicationJob
|
||||
|
||||
def perform(submitter, params = {})
|
||||
attempt = params[:attempt].to_i
|
||||
config = Accounts.load_webhook_configs(submitter.submission.account)
|
||||
url = Accounts.load_webhook_url(submitter.submission.account)
|
||||
|
||||
return if config.blank? || config.value.blank?
|
||||
return if url.blank?
|
||||
|
||||
preferences = Accounts.load_webhook_preferences(submitter.submission.account)
|
||||
|
||||
return if preferences['form.completed'] == false
|
||||
|
||||
Submissions::EnsureResultGenerated.call(submitter)
|
||||
|
||||
ActiveStorage::Current.url_options = Docuseal.default_url_options
|
||||
|
||||
resp = begin
|
||||
Faraday.post(config.value,
|
||||
Faraday.post(url,
|
||||
{
|
||||
event_type: 'form.completed',
|
||||
timestamp: Time.current,
|
||||
|
||||
@@ -7,14 +7,18 @@ class SendFormStartedWebhookRequestJob < ApplicationJob
|
||||
|
||||
def perform(submitter, params = {})
|
||||
attempt = params[:attempt].to_i
|
||||
config = Accounts.load_webhook_configs(submitter.submission.account)
|
||||
url = Accounts.load_webhook_url(submitter.submission.account)
|
||||
|
||||
return if config.blank? || config.value.blank?
|
||||
return if url.blank?
|
||||
|
||||
preferences = Accounts.load_webhook_preferences(submitter.submission.account)
|
||||
|
||||
return if preferences['form.started'] == false
|
||||
|
||||
ActiveStorage::Current.url_options = Docuseal.default_url_options
|
||||
|
||||
resp = begin
|
||||
Faraday.post(config.value,
|
||||
Faraday.post(url,
|
||||
{
|
||||
event_type: 'form.started',
|
||||
timestamp: Time.current,
|
||||
|
||||
@@ -7,14 +7,18 @@ class SendFormViewedWebhookRequestJob < ApplicationJob
|
||||
|
||||
def perform(submitter, params = {})
|
||||
attempt = params[:attempt].to_i
|
||||
config = Accounts.load_webhook_configs(submitter.submission.account)
|
||||
url = Accounts.load_webhook_url(submitter.submission.account)
|
||||
|
||||
return if config.blank? || config.value.blank?
|
||||
return if url.blank?
|
||||
|
||||
preferences = Accounts.load_webhook_preferences(submitter.submission.account)
|
||||
|
||||
return if preferences['form.viewed'] == false
|
||||
|
||||
ActiveStorage::Current.url_options = Docuseal.default_url_options
|
||||
|
||||
resp = begin
|
||||
Faraday.post(config.value,
|
||||
Faraday.post(url,
|
||||
{
|
||||
event_type: 'form.viewed',
|
||||
timestamp: Time.current,
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class SendSubmissionArchivedWebhookRequestJob < ApplicationJob
|
||||
USER_AGENT = 'DocuSeal.co Webhook'
|
||||
|
||||
MAX_ATTEMPTS = 10
|
||||
|
||||
def perform(submission, params = {})
|
||||
attempt = params[:attempt].to_i
|
||||
url = Accounts.load_webhook_url(submission.account)
|
||||
|
||||
return if url.blank?
|
||||
|
||||
preferences = Accounts.load_webhook_preferences(submission.account)
|
||||
|
||||
return if preferences['submission.archived'].blank?
|
||||
|
||||
resp = begin
|
||||
Faraday.post(url,
|
||||
{
|
||||
event_type: 'submission.archived',
|
||||
timestamp: Time.current,
|
||||
data: submission.as_json(only: %i[id archived_at])
|
||||
}.to_json,
|
||||
'Content-Type' => 'application/json',
|
||||
'User-Agent' => USER_AGENT)
|
||||
rescue Faraday::Error
|
||||
nil
|
||||
end
|
||||
|
||||
if (resp.nil? || resp.status.to_i >= 400) && attempt <= MAX_ATTEMPTS &&
|
||||
(!Docuseal.multitenant? || submitter.account.account_configs.exists?(key: :plan))
|
||||
SendSubmissionArchivedWebhookRequestJob.set(wait: (2**attempt).minutes)
|
||||
.perform_later(submitter, {
|
||||
attempt: attempt + 1,
|
||||
last_status: resp&.status.to_i
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -4,7 +4,7 @@ class SendSubmitterInvitationEmailJob < ApplicationJob
|
||||
def perform(params = {})
|
||||
submitter = Submitter.find(params['submitter_id'])
|
||||
|
||||
if submitter.submission.source == 'invite' && !Accounts.can_send_emails?(submitter.account)
|
||||
if submitter.submission.source == 'invite' && !Accounts.can_send_emails?(submitter.account, on_events: true)
|
||||
Rollbar.error("Skip email: #{submitter.id}") if defined?(Rollbar)
|
||||
|
||||
return
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class ApplicationMailer < ActionMailer::Base
|
||||
default from: 'DocuSeal <hi@docuseal.co>'
|
||||
default from: 'DocuSeal <info@docuseal.co>'
|
||||
layout 'mailer'
|
||||
|
||||
register_interceptor ActionMailerConfigsInterceptor
|
||||
|
||||
@@ -14,8 +14,8 @@ class SubmitterMailer < ApplicationMailer
|
||||
@email_message = submitter.account.email_messages.find_by(uuid: submitter.preferences['email_message_uuid'])
|
||||
end
|
||||
|
||||
@body = @email_message&.body.presence
|
||||
@subject = @email_message&.subject.presence
|
||||
@body = @email_message&.body.presence || @submitter.template.preferences['request_email_body'].presence
|
||||
@subject = @email_message&.subject.presence || @submitter.template.preferences['request_email_subject'].presence
|
||||
|
||||
@email_config = AccountConfigs.find_for_account(@current_account, AccountConfig::SUBMITTER_INVITATION_EMAIL_KEY)
|
||||
|
||||
|
||||
+10
-7
@@ -4,13 +4,14 @@
|
||||
#
|
||||
# Table name: accounts
|
||||
#
|
||||
# id :bigint not null, primary key
|
||||
# locale :string not null
|
||||
# name :string not null
|
||||
# timezone :string not null
|
||||
# uuid :string not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# id :bigint not null, primary key
|
||||
# archived_at :datetime
|
||||
# locale :string not null
|
||||
# name :string not null
|
||||
# timezone :string not null
|
||||
# uuid :string not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
@@ -49,6 +50,8 @@ class Account < ApplicationRecord
|
||||
attribute :timezone, :string, default: 'UTC'
|
||||
attribute :locale, :string, default: 'en-US'
|
||||
|
||||
scope :active, -> { where(archived_at: nil) }
|
||||
|
||||
def testing?
|
||||
linked_account_account&.testing?
|
||||
end
|
||||
|
||||
@@ -28,11 +28,15 @@ class AccountConfig < ApplicationRecord
|
||||
FORCE_MFA = 'force_mfa'
|
||||
ALLOW_TYPED_SIGNATURE = 'allow_typed_signature'
|
||||
ALLOW_TO_RESUBMIT = 'allow_to_resubmit'
|
||||
SUBMITTER_REMAILERS = 'submitter_reminders'
|
||||
SUBMITTER_REMINDERS = 'submitter_reminders'
|
||||
FORM_COMPLETED_BUTTON_KEY = 'form_completed_button'
|
||||
FORM_COMPLETED_MESSAGE_KEY = 'form_completed_message'
|
||||
FORM_WITH_CONFETTI_KEY = 'form_with_confetti'
|
||||
ESIGNING_PREFERENCE_KEY = 'esigning_preference'
|
||||
WEBHOOK_PREFERENCES_KEY = 'webhook_preferences'
|
||||
DOWNLOAD_LINKS_AUTH_KEY = 'download_links_auth'
|
||||
FORCE_SSO_AUTH_KEY = 'force_sso_auth'
|
||||
FLATTEN_RESULT_PDF_KEY = 'flatten_result_pdf'
|
||||
|
||||
DEFAULT_VALUES = {
|
||||
SUBMITTER_INVITATION_EMAIL_KEY => {
|
||||
|
||||
@@ -63,6 +63,7 @@ class Submission < ApplicationRecord
|
||||
|
||||
enum :source, {
|
||||
invite: 'invite',
|
||||
bulk: 'bulk',
|
||||
api: 'api',
|
||||
embed: 'embed',
|
||||
link: 'link'
|
||||
|
||||
@@ -53,6 +53,8 @@ class Submitter < ApplicationRecord
|
||||
has_many :document_generation_events, dependent: :destroy
|
||||
has_many :submission_events, dependent: :destroy
|
||||
|
||||
scope :completed, -> { where.not(completed_at: nil) }
|
||||
|
||||
def status
|
||||
if completed_at?
|
||||
'completed'
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
# archived_at :datetime
|
||||
# fields :text not null
|
||||
# name :string not null
|
||||
# preferences :text not null
|
||||
# schema :text not null
|
||||
# slug :string not null
|
||||
# source :text not null
|
||||
@@ -41,12 +42,14 @@ class Template < ApplicationRecord
|
||||
|
||||
before_validation :maybe_set_default_folder, on: :create
|
||||
|
||||
attribute :preferences, :string, default: -> { {} }
|
||||
attribute :fields, :string, default: -> { [] }
|
||||
attribute :schema, :string, default: -> { [] }
|
||||
attribute :submitters, :string, default: -> { [{ name: DEFAULT_SUBMITTER_NAME, uuid: SecureRandom.uuid }] }
|
||||
attribute :slug, :string, default: -> { SecureRandom.base58(14) }
|
||||
attribute :source, :string, default: 'native'
|
||||
|
||||
serialize :preferences, coder: JSON
|
||||
serialize :fields, coder: JSON
|
||||
serialize :schema, coder: JSON
|
||||
serialize :submitters, coder: JSON
|
||||
@@ -62,6 +65,8 @@ class Template < ApplicationRecord
|
||||
scope :active, -> { where(archived_at: nil) }
|
||||
scope :archived, -> { where.not(archived_at: nil) }
|
||||
|
||||
delegate :name, to: :folder, prefix: true
|
||||
|
||||
def application_key
|
||||
external_id
|
||||
end
|
||||
|
||||
+2
-1
@@ -65,6 +65,7 @@ class User < ApplicationRecord
|
||||
attribute :uuid, :string, default: -> { SecureRandom.uuid }
|
||||
|
||||
scope :active, -> { where(archived_at: nil) }
|
||||
scope :archived, -> { where.not(archived_at: nil) }
|
||||
scope :admins, -> { where(role: ADMIN_ROLE) }
|
||||
|
||||
def access_token
|
||||
@@ -72,7 +73,7 @@ class User < ApplicationRecord
|
||||
end
|
||||
|
||||
def active_for_authentication?
|
||||
super && !archived_at?
|
||||
super && !archived_at? && !account.archived_at?
|
||||
end
|
||||
|
||||
def remember_me
|
||||
|
||||
@@ -75,6 +75,18 @@
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% account_config = AccountConfig.find_or_initialize_by(account: current_account, key: AccountConfig::DOWNLOAD_LINKS_AUTH_KEY) %>
|
||||
<% if can?(:manage, account_config) %>
|
||||
<%= form_for account_config, url: account_configs_path, method: :post do |f| %>
|
||||
<%= f.hidden_field :key %>
|
||||
<div class="flex items-center justify-between py-2.5">
|
||||
<span>
|
||||
Require authentication for file download links
|
||||
</span>
|
||||
<%= f.check_box :value, class: 'toggle', checked: account_config.value, onchange: 'this.form.requestSubmit()' %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if can?(:manage, current_account) && Docuseal.multitenant? && true_user == current_user %>
|
||||
|
||||
@@ -140,6 +140,18 @@
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% account_config = AccountConfig.find_or_initialize_by(account: current_account, key: AccountConfig::FLATTEN_RESULT_PDF_KEY) %>
|
||||
<% if can?(:manage, account_config) %>
|
||||
<%= form_for account_config, url: account_configs_path, method: :post do |f| %>
|
||||
<%= f.hidden_field :key %>
|
||||
<div class="flex items-center justify-between py-2.5">
|
||||
<span>
|
||||
Remove PDF form fillable fields from the signed PDF (flatten form)
|
||||
</span>
|
||||
<%= f.check_box :value, { class: 'toggle', checked: account_config.value != false, onchange: 'this.form.requestSubmit()' } %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="<%= local_assigns[:class] %>" width="44" height="44" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path d="M14 6m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0" />
|
||||
<path d="M4 6l8 0" />
|
||||
<path d="M16 6l4 0" />
|
||||
<path d="M8 12m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0" />
|
||||
<path d="M4 12l2 0" />
|
||||
<path d="M10 12l10 0" />
|
||||
<path d="M17 18m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0" />
|
||||
<path d="M4 18l11 0" />
|
||||
<path d="M19 18l1 0" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 602 B |
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="<%= local_assigns[:class] %>" width="44" height="44" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path d="M12 3a9 9 0 1 0 0 18a9 9 0 0 0 0 -18" />
|
||||
<path d="M16 12l-4 -4" />
|
||||
<path d="M16 12h-8" />
|
||||
<path d="M12 16l4 -4" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 415 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="<%= local_assigns[:class] %>" aria-hidden="true" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 338 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="<%= local_assigns[:class] %>" width="16" height="16" viewBox="0 0 16 16" fill="none">
|
||||
<path d="M6.06004 6.00016C6.21678 5.55461 6.52614 5.1789 6.93334 4.93958C7.34055 4.70027 7.8193 4.61279 8.28483 4.69264C8.75035 4.77249 9.17259 5.01451 9.47676 5.37585C9.78093 5.73718 9.94741 6.19451 9.94671 6.66683C9.94671 8.00016 7.94671 8.66683 7.94671 8.66683M8.00004 11.3335H8.00671M14.6667 8.00016C14.6667 11.6821 11.6819 14.6668 8.00004 14.6668C4.31814 14.6668 1.33337 11.6821 1.33337 8.00016C1.33337 4.31826 4.31814 1.3335 8.00004 1.3335C11.6819 1.3335 14.6667 4.31826 14.6667 8.00016Z" stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 730 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 d="M6 6v6a3 3 0 0 0 3 3h10l-4 -4m0 8l4 -4" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 337 B |
@@ -0,0 +1,5 @@
|
||||
<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" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path d="M10.325 4.317c.426 -1.756 2.924 -1.756 3.35 0a1.724 1.724 0 0 0 2.573 1.066c1.543 -.94 3.31 .826 2.37 2.37a1.724 1.724 0 0 0 1.065 2.572c1.756 .426 1.756 2.924 0 3.35a1.724 1.724 0 0 0 -1.066 2.573c.94 1.543 -.826 3.31 -2.37 2.37a1.724 1.724 0 0 0 -2.572 1.065c-.426 1.756 -2.924 1.756 -3.35 0a1.724 1.724 0 0 0 -2.573 -1.066c-1.543 .94 -3.31 -.826 -2.37 -2.37a1.724 1.724 0 0 0 -1.065 -2.572c-1.756 -.426 -1.756 -2.924 0 -3.35a1.724 1.724 0 0 0 1.066 -2.573c-.94 -1.543 .826 -3.31 2.37 -2.37c1 .608 2.296 .07 2.572 -1.065z" />
|
||||
<path d="M9 12a3 3 0 1 0 6 0a3 3 0 0 0 -6 0" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 872 B |
@@ -12,6 +12,7 @@
|
||||
</head>
|
||||
<body>
|
||||
<turbo-frame id="modal"></turbo-frame>
|
||||
<turbo-frame id="drawer"></turbo-frame>
|
||||
<%= render 'shared/navbar' %>
|
||||
<% if flash.present? %><%= render 'shared/flash' %><% end %>
|
||||
<div class="max-w-6xl mx-auto px-4 md:px-2 mb-8">
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
</span>
|
||||
</span>
|
||||
<% end %>
|
||||
<%= f.email_field :value, autocomplete: 'off', class: 'base-input' %>
|
||||
<%= tag.input type: 'email', multiple: true, name: 'account_config[value]', autocomplete: 'off', class: 'base-input', value: f.object.value %>
|
||||
</div>
|
||||
<div class="form-control pt-2">
|
||||
<%= f.button button_title(title: 'Save', disabled_with: 'Updating'), class: 'base-button' %>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<% link = pagy_link_proc(@pagy) %>
|
||||
<% link = pagy_anchor(@pagy) %>
|
||||
<% if @pagy.pages > 1 %>
|
||||
<div class="flex my-6 justify-center md:justify-between">
|
||||
<div class="hidden md:block text-sm">
|
||||
@@ -7,7 +7,7 @@
|
||||
</div>
|
||||
<div class="join">
|
||||
<% if @pagy.prev %>
|
||||
<%== link.call(@pagy.prev, '«', 'class="join-item btn min-h-full h-10"') %>
|
||||
<%== link.call(@pagy.prev, '«', classes: 'join-item btn min-h-full h-10') %>
|
||||
<% else %>
|
||||
<span class="join-item btn btn-disabled !bg-base-200 min-h-full h-10">«</span>
|
||||
<% end %>
|
||||
@@ -15,7 +15,7 @@
|
||||
Page <%= @pagy.page %>
|
||||
</span>
|
||||
<% if @pagy.next %>
|
||||
<%== link.call(@pagy.next, '»', 'class="join-item btn min-h-full h-10"') %>
|
||||
<%== link.call(@pagy.next, '»', classes: 'join-item btn min-h-full h-10') %>
|
||||
<% else %>
|
||||
<span class="join-item btn btn-disabled !bg-base-200 min-h-full h-10">»</span>
|
||||
<% end %>
|
||||
|
||||
@@ -37,11 +37,17 @@
|
||||
<%= link_to 'E-Signature', settings_esign_path, class: 'text-base hover:bg-base-300' %>
|
||||
</li>
|
||||
<% end %>
|
||||
<% if can?(:read, AccountConfig) %>
|
||||
<li>
|
||||
<%= link_to 'Personalization', settings_personalization_path, class: 'text-base hover:bg-base-300' %>
|
||||
</li>
|
||||
<% end %>
|
||||
<% if can?(:read, User) %>
|
||||
<li>
|
||||
<%= link_to 'Users', settings_users_path, class: 'text-base hover:bg-base-300' %>
|
||||
</li>
|
||||
<% end %>
|
||||
<%= render 'shared/settings_nav_extra' %>
|
||||
<% if Docuseal.demo? || !Docuseal.multitenant? %>
|
||||
<% if can?(:read, AccessToken) %>
|
||||
<li>
|
||||
@@ -54,12 +60,6 @@
|
||||
</li>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% if can?(:read, AccountConfig) %>
|
||||
<li>
|
||||
<%= link_to 'Personalization', settings_personalization_path, class: 'text-base hover:bg-base-300' %>
|
||||
</li>
|
||||
<% end %>
|
||||
<%= render 'shared/settings_nav_extra' %>
|
||||
<% if !Docuseal.demo? && can?(:manage, EncryptedConfig) && (current_user != true_user || !current_account.testing?) %>
|
||||
<li>
|
||||
<%= link_to Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premise" }.to_query}", class: 'text-base hover:bg-base-300', data: { prefetch: false } do %>
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
<turbo-frame id="drawer">
|
||||
<turbo-modal class="modal modal-open items-start !animate-none justify-end overflow-y-auto !justify-normal md:!justify-end" data-close-after-submit="<%= local_assigns.key?(:close_after_submit) ? local_assigns[:close_after_submit] : true %>">
|
||||
<div class="absolute top-0 bottom-0 right-0 left-0" data-action="click:turbo-modal#close"></div>
|
||||
<div class="bg-base-100 min-h-screen max-h-screen relative w-full relative overflow-y-auto">
|
||||
<% if local_assigns[:title] %>
|
||||
<div class="flex justify-between bg-base-100 py-2 px-4 items-center border-b pb-2 font-medium">
|
||||
<span>
|
||||
<%= local_assigns[:title] %>
|
||||
</span>
|
||||
<a href="#" class="text-xl" data-action="click:turbo-modal#close">×</a>
|
||||
</div>
|
||||
<% else %>
|
||||
<span data-action="click:turbo-modal#close" class="absolute btn border border-base-100 bg-base-100 hover:bg-base-300 hover:border-base-300 btn-primary !text-base btn-sm btn-circle" style="left: -40px; top: 6px">
|
||||
×
|
||||
</span>
|
||||
<% end %>
|
||||
<div class="w-full md:w-[620px] overflow-y-auto" style="max-height: calc(100vh - <%= local_assigns[:title] ? '45px' : '0px' %>)">
|
||||
<%= yield %>
|
||||
</div>
|
||||
</div>
|
||||
</turbo-modal>
|
||||
</turbo-frame>
|
||||
@@ -0,0 +1,18 @@
|
||||
<turbo-frame id="modal">
|
||||
<turbo-modal class="modal modal-open items-start !animate-none overflow-y-auto !justify-normal md:!justify-center" data-close-after-submit="<%= local_assigns.key?(:close_after_submit) ? local_assigns[:close_after_submit] : true %>">
|
||||
<div class="absolute top-0 bottom-0 right-0 left-0" data-action="click:turbo-modal#close"></div>
|
||||
<div class="bg-base-100 min-h-screen max-h-screen md:min-h-fit md:mt-3 md:rounded-2xl relative w-full relative overflow-y-auto">
|
||||
<% if local_assigns[:title] %>
|
||||
<div class="flex justify-between bg-base-100 py-2 px-5 items-center border-b pb-2 font-medium mt-0.5">
|
||||
<span>
|
||||
<%= local_assigns[:title] %>
|
||||
</span>
|
||||
<a href="#" class="text-xl" data-action="click:turbo-modal#close">×</a>
|
||||
</div>
|
||||
<% end %>
|
||||
<div class="w-full md:w-[590px] overflow-y-auto" style="max-height: calc(100vh - 14px - 45px)">
|
||||
<%= yield %>
|
||||
</div>
|
||||
</div>
|
||||
</turbo-modal>
|
||||
</turbo-frame>
|
||||
@@ -48,6 +48,8 @@
|
||||
<%= render('submitters_order', f:, template:) if Accounts.can_send_emails?(current_account) %>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<%= f.button button_title(title: 'Add Recipients'), class: 'base-button' %>
|
||||
<%= content_for(:submit_button) || capture do %>
|
||||
<%= f.button button_title(title: 'Add Recipients'), class: 'base-button' %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
@@ -35,7 +35,7 @@
|
||||
<div class="form-control space-y-2">
|
||||
<div class="form-control">
|
||||
<%= f.label :subject, class: 'label' %>
|
||||
<%= f.text_field :subject, value: config.value['subject'], required: true, class: '!text-sm base-input w-full', dir: 'auto' %>
|
||||
<%= f.text_field :subject, value: @template.preferences['request_email_subject'].presence || config.value['subject'], required: true, class: '!text-sm base-input w-full', dir: 'auto' %>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<div class="flex items-center">
|
||||
@@ -45,8 +45,12 @@
|
||||
</span>
|
||||
</div>
|
||||
<autoresize-textarea>
|
||||
<%= f.text_area :body, value: config.value['body'], required: true, class: 'base-textarea w-full', rows: 10, dir: 'auto' %>
|
||||
<%= f.text_area :body, value: @template.preferences['request_email_body'].presence || config.value['body'], required: true, class: 'base-textarea w-full', rows: 10, dir: 'auto' %>
|
||||
</autoresize-textarea>
|
||||
<label for="<%= uuid = SecureRandom.uuid %>" class="flex items-center cursor-pointer">
|
||||
<%= check_box_tag :save_message, id: uuid, class: 'base-checkbox', checked: false %>
|
||||
<span class="label">Save as default template message</span>
|
||||
</label>
|
||||
</div>
|
||||
<%= render 'message_fields' %>
|
||||
</div>
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
</div>
|
||||
<div>
|
||||
<p dir="auto" class="text-lg font-bold mb-1"><%= @submission.template.name %></p>
|
||||
<% if submitter = @submission.submitters.map(&:completed_at).max %>
|
||||
<p dir="auto" class="text-sm"><%= t('signed_on_time', time: l(submitter.to_date, format: :long)) %></p>
|
||||
<% if completed_at = @submission.submitters.map(&:completed_at).compact.max %>
|
||||
<p dir="auto" class="text-sm"><%= t('signed_on_time', time: l(completed_at.to_date, format: :long)) %></p>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<% 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 %>
|
||||
<% configs = Submitters::FormConfigs.call(submitter) %>
|
||||
<submission-form data-is-demo="<%= Docuseal.demo? %>" data-with-confetti="<%= configs[:with_confetti] %>" data-completed-redirect-url="<%= submitter.preferences['completed_redirect_url'] %>" data-completed-message="<%= configs[:completed_message].to_json %>" data-completed-button="<%= configs[:completed_button].to_json %>" data-go-to-last="<%= submitter.opened_at? %>" data-submitter="<%= submitter.to_json(only: %i[uuid slug name phone email]) %>" data-can-send-email="<%= Accounts.can_send_emails?(submitter.submission.account) %>" data-attachments="<%= data_attachments %>" data-fields="<%= data_fields %>" data-values="<%= submitter.values.to_json %>" data-with-typed-signature="<%= configs[:with_typed_signature] %>"></submission-form>
|
||||
<submission-form data-is-demo="<%= Docuseal.demo? %>" data-with-confetti="<%= configs[:with_confetti] %>" data-completed-redirect-url="<%= submitter.preferences['completed_redirect_url'] %>" data-completed-message="<%= configs[:completed_message].to_json %>" data-completed-button="<%= configs[:completed_button].to_json %>" data-go-to-last="<%= submitter.preferences.key?('go_to_last') ? submitter.preferences['go_to_last'] : submitter.opened_at? %>" data-submitter="<%= submitter.to_json(only: %i[uuid slug name phone email]) %>" data-can-send-email="<%= Accounts.can_send_emails?(submitter.submission.account) %>" data-attachments="<%= data_attachments %>" data-fields="<%= data_fields %>" data-values="<%= submitter.values.to_json %>" data-with-typed-signature="<%= configs[:with_typed_signature] %>"></submission-form>
|
||||
@@ -24,6 +24,7 @@
|
||||
<% value = values[field['uuid']].presence || (field['default_value'].present? ? Submitters::SubmitValues.template_default_value_for_submitter(field['default_value'], @submitter.submission.submitters.find { |e| e.uuid == field['submitter_uuid'] }, with_time: false) : nil) %>
|
||||
<% next if value.blank? %>
|
||||
<% next if !field['readonly'] && field['submitter_uuid'] == @submitter.uuid %>
|
||||
<% next if field['redacted'] && field['submitter_uuid'] != @submitter.uuid %>
|
||||
<% next if value == '{{date}}' && field['submitter_uuid'] != @submitter.uuid %>
|
||||
<% next if field.dig('preferences', 'formula') && field['submitter_uuid'] == @submitter.uuid %>
|
||||
<%= render 'submissions/value', area:, field:, attachments_index:, value:, locale: @submitter.account.locale, timezone: @submitter.account.timezone %>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<toggle-visible data-element-ids="["js_1","react_1","vue_1"]" class="block relative" data-catalyst="">
|
||||
<toggle-visible data-element-ids="["js_1","react_1","vue_1","angular_1"]" class="block relative">
|
||||
<ul class="items-center w-full text-sm font-medium text-gray-900 my-4 space-y-2 sm:space-y-0 sm:flex sm:space-x-2">
|
||||
<li class="w-full h-10 text-sm font-medium flex items-center relative group py-3.5">
|
||||
<input type="radio" name="option_1" id="js_radio_1" value="js_1" data-action="change:toggle-visible#trigger" class="relative peer z-10 hidden" checked="checked">
|
||||
@@ -33,6 +33,23 @@
|
||||
<span>Vue</span>
|
||||
</label>
|
||||
</li>
|
||||
<li class="w-full h-10 text-sm font-medium flex items-center relative group py-3.5">
|
||||
<input type="radio" name="option_1" id="angular_radio_1" value="angular_1" data-action="change:toggle-visible#trigger" class="relative peer z-10 hidden">
|
||||
<label for="angular_radio_1" class="absolute border-neutral-focus space-x-2 border rounded-xl left-0 right-0 top-0 bottom-0 flex items-center justify-center group-hover:bg-neutral group-hover:text-white peer-checked:btn-neutral">
|
||||
<span><svg class="h-5 w-5" width="800px" height="800px" viewBox="-8 0 272 272" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
|
||||
<g>
|
||||
<path d="M0.0996108949,45.522179 L125.908171,0.697276265 L255.103502,44.7252918 L234.185214,211.175097 L125.908171,271.140856 L19.3245136,211.971984 L0.0996108949,45.522179 Z" fill="#E23237">
|
||||
</path>
|
||||
<path d="M255.103502,44.7252918 L125.908171,0.697276265 L125.908171,271.140856 L234.185214,211.274708 L255.103502,44.7252918 L255.103502,44.7252918 Z" fill="#B52E31">
|
||||
</path>
|
||||
<path d="M126.107393,32.27393 L126.107393,32.27393 L47.7136187,206.692607 L76.9992218,206.194553 L92.7377432,166.848249 L126.207004,166.848249 L126.306615,166.848249 L163.063035,166.848249 L180.29572,206.692607 L208.286381,207.190661 L126.107393,32.27393 L126.107393,32.27393 Z M126.306615,88.155642 L152.803113,143.5393 L127.402335,143.5393 L126.107393,143.5393 L102.997665,143.5393 L126.306615,88.155642 L126.306615,88.155642 Z" fill="#FFFFFF">
|
||||
</path>
|
||||
</g>
|
||||
</svg>
|
||||
</span>
|
||||
<span>Angular</span>
|
||||
</label>
|
||||
</li>
|
||||
</ul>
|
||||
</toggle-visible>
|
||||
<div id="js_1" class="block my-4">
|
||||
@@ -173,3 +190,61 @@ export default {
|
||||
</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
<div id="angular_1" class="block my-4 hidden">
|
||||
<div class="mockup-code overflow-hidden pb-0 mt-4">
|
||||
<span class="top-0 right-0 absolute flex">
|
||||
<%= link_to 'Learn More', console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/embedding/form"), target: '_blank', data: { turbo: false }, class: 'btn btn-ghost text-gray-100 flex', rel: 'noopener' %>
|
||||
<clipboard-copy data-text="import { Component } from '@angular/core';
|
||||
import { DocusealFormComponent } from '@docuseal/angular';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
standalone: true,
|
||||
imports: [DocusealFormComponent],
|
||||
template: `
|
||||
<div class="app">
|
||||
<docuseal-form
|
||||
[src]="'<%= start_form_url(slug: template.slug) %>'">
|
||||
</docuseal-form>
|
||||
</div>
|
||||
`
|
||||
})
|
||||
export class AppComponent {}
|
||||
">
|
||||
<label class="btn btn-ghost text-white">
|
||||
<input type="radio" class="peer hidden">
|
||||
<span class="peer-checked:hidden flex items-center space-x-2">
|
||||
<%= svg_icon('copy', class: 'w-6 h-6 text-white') %>
|
||||
<span class="hidden md:inline">
|
||||
Copy
|
||||
</span>
|
||||
</span>
|
||||
<span class="hidden peer-checked:flex items-center space-x-2">
|
||||
<%= svg_icon('clipboard_copy', class: 'w-6 h-6 text-white') %>
|
||||
<span class="hidden md:inline">
|
||||
Copied
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
</clipboard-copy>
|
||||
|
||||
</span>
|
||||
<pre class="before:!m-0 pl-6 pb-4 overflow-auto"><code class="overflow-hidden w-full"><span style="color: #aa759f">import</span> <span style="color: #d0d0d0">{</span> <span style="color: #d0d0d0;background-color: #151515">Component</span> <span style="color: #d0d0d0">}</span> <span style="color: #aa759f">from</span> <span style="color: #90a959">'</span><span style="color: #90a959">@angular/core</span><span style="color: #90a959">'</span><span style="color: #d0d0d0">;</span>
|
||||
<span style="color: #aa759f">import</span> <span style="color: #d0d0d0">{</span> <span style="color: #d0d0d0;background-color: #151515">DocusealFormComponent</span> <span style="color: #d0d0d0">}</span> <span style="color: #aa759f">from</span> <span style="color: #90a959">'</span><span style="color: #90a959">@docuseal/angular</span><span style="color: #90a959">'</span><span style="color: #d0d0d0">;</span>
|
||||
|
||||
<span style="color: #d0d0d0">@</span><span style="color: #d0d0d0;background-color: #151515">Component</span><span style="color: #d0d0d0">({</span>
|
||||
<span style="color: #6a9fb5">selector</span><span style="color: #d0d0d0">:</span> <span style="color: #90a959">'</span><span style="color: #90a959">app-root</span><span style="color: #90a959">'</span><span style="color: #d0d0d0">,</span>
|
||||
<span style="color: #6a9fb5">standalone</span><span style="color: #d0d0d0">:</span> <span style="color: #d28445">true</span><span style="color: #d0d0d0">,</span>
|
||||
<span style="color: #6a9fb5">imports</span><span style="color: #d0d0d0">:</span> <span style="color: #d0d0d0">[</span><span style="color: #d0d0d0;background-color: #151515">DocusealFormComponent</span><span style="color: #d0d0d0">],</span>
|
||||
<span style="color: #6a9fb5">template</span><span style="color: #d0d0d0">:</span> <span style="color: #90a959">`
|
||||
<div class="app">
|
||||
<docuseal-form
|
||||
[src]="'<%= start_form_url(slug: template.slug) %>'">
|
||||
</docuseal-form>
|
||||
</div>
|
||||
`</span>
|
||||
<span style="color: #d0d0d0">})</span>
|
||||
<span style="color: #aa759f">export</span> <span style="color: #d28445">class</span> <span style="color: #f4bf75">AppComponent</span> <span style="color: #d0d0d0">{}</span>
|
||||
</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
@@ -61,7 +61,7 @@
|
||||
</download-button>
|
||||
</button>
|
||||
</form>
|
||||
<% else %>
|
||||
<% elsif !submission.archived_at? && !template.archived_at? %>
|
||||
<% if current_user.email == submitter.email %>
|
||||
<form data-turbo="false" method="get" action="<%= submit_form_url(slug: submitter.slug) %>" class="flex-1 md:flex-none">
|
||||
<button onclick="event.stopPropagation()" class="btn btn-sm btn-neutral btn-outline bg-white w-full md:w-36 flex">
|
||||
@@ -80,7 +80,7 @@
|
||||
<div class="flex-1 md:flex-none">
|
||||
<span class="btn btn-outline btn-sm w-full md:w-24">View</span>
|
||||
</div>
|
||||
<% if !submission.archived_at? && can?(:destroy, submission) %>
|
||||
<% if !submission.archived_at? && !template.archived_at? && can?(:destroy, submission) %>
|
||||
<span data-tip="Archive" class="sm:tooltip tooltip-top">
|
||||
<%= button_to button_title(title: nil, disabled_with: 'Arch', icon: svg_icon('archive', class: 'w-6 h-6')), submission_path(submission), class: 'btn btn-outline btn-sm w-full md:w-fit', form: { class: 'flex' }, title: 'Archive', method: :delete, data: { turbo_confirm: 'Are you sure?' }, onclick: 'event.stopPropagation()' %>
|
||||
</span>
|
||||
@@ -127,7 +127,7 @@
|
||||
</download-button>
|
||||
</button>
|
||||
</form>
|
||||
<% elsif !is_submission_completed %>
|
||||
<% elsif !template.archived_at? && !submission.archived_at? && !is_submission_completed %>
|
||||
<div class="relative flex items-center space-x-3">
|
||||
<% if current_user.email == submitter.email %>
|
||||
<form data-turbo="false" method="get" action="<%= submit_form_url(slug: submitter.slug) %>">
|
||||
@@ -171,7 +171,7 @@
|
||||
<div class="flex-1 md:flex-none">
|
||||
<span class="btn btn-outline btn-sm w-full md:w-24">View</span>
|
||||
</div>
|
||||
<% unless submission.archived_at? %>
|
||||
<% if !submission.archived_at? && !template.archived_at? %>
|
||||
<span data-tip="Archive" class="sm:tooltip tooltip-top">
|
||||
<%= button_to button_title(title: nil, disabled_with: 'Arch', icon: svg_icon('archive', class: 'w-6 h-6')), submission_path(submission), class: 'btn btn-outline btn-sm w-full md:w-fit', form: { class: 'flex' }, title: 'Archive', method: :delete, data: { turbo_confirm: 'Are you sure?' }, onclick: 'event.stopPropagation()' %>
|
||||
</span>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<h1 class="text-4xl font-semibold" style="overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;">
|
||||
<% template.name.split(/(_)/).each do |item| %><%= item %><wbr><% end %>
|
||||
<% if template.archived_at? %>
|
||||
<span class="badge badge-outline badge-lg align-middle">Archived</span>
|
||||
<span class="ml-1 badge badge-outline badge-lg align-middle">Archived</span>
|
||||
<% end %>
|
||||
</h1>
|
||||
<% if @template.account_id == current_account.id %>
|
||||
@@ -37,11 +37,11 @@
|
||||
</div>
|
||||
<% if !template.archived_at? %>
|
||||
<div class="flex items-center space-x-2">
|
||||
<% if can?(:update, template) && (Docuseal.multitenant? || current_account.testing? || current_account.id == 1) %>
|
||||
<div class="tooltip" data-tip="API and Embedding">
|
||||
<%= link_to template_code_modal_path(template), class: 'btn btn-ghost btn-sm flex-1 hidden md:flex', data: { turbo_frame: :modal } do %>
|
||||
<% if can?(:update, template) %>
|
||||
<div class="tooltip" data-tip="Preferences">
|
||||
<%= link_to template_preferences_path(template), class: 'btn border border-base-200 bg-base-200 hover:bg-base-300 hover:border-base-300 btn-sm flex-1 hidden md:flex', data: { turbo_frame: :modal } do %>
|
||||
<span class="flex items-center justify-center space-x-2">
|
||||
<%= svg_icon('code', class: 'w-6 h-6') %>
|
||||
<%= svg_icon('adjustments_horizontal', class: 'w-6 h-6') %>
|
||||
</span>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
<% if @base_template %>
|
||||
<%= hidden_field_tag :base_template_id, @base_template.id %>
|
||||
<% end %>
|
||||
<% if @base_template && (can?(:manage, :tenants) || true_user != current_user) && true_user.account.account_linked_accounts.exists? %>
|
||||
<% if @base_template && (can?(:manage, :tenants) || true_user != current_user) && true_user.account.linked_accounts.active.exists? %>
|
||||
<div class="form-control -mb-2 mt-2">
|
||||
<%= select_tag :account_id, options_for_select([true_user.account, *true_user.account.linked_accounts].uniq.map { |e| [e.name, e.id] }, current_account.id), required: true, class: 'base-select' %>
|
||||
<%= select_tag :account_id, options_for_select([true_user.account, *true_user.account.linked_accounts.active].uniq.map { |e| [e.name, e.id] }, current_account.id), required: true, class: 'base-select' %>
|
||||
</div>
|
||||
<% end %>
|
||||
<div class="form-control mt-6">
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
<% end %>
|
||||
<% if @submissions.present? %>
|
||||
<div class="space-y-4">
|
||||
<%= render partial: 'submission', collection: @submissions %>
|
||||
<%= render partial: 'submission', collection: @submissions, locals: { template: @template } %>
|
||||
</div>
|
||||
<% view_archived_html = capture do %>
|
||||
<% if @template.submissions.where.not(archived_at: nil).exists? && !@template.archived_at? %>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
</div>
|
||||
<% if @pagy.count > 0 %>
|
||||
<div class="space-y-4">
|
||||
<%= render partial: 'templates/submission', collection: @submissions %>
|
||||
<%= render partial: 'templates/submission', collection: @submissions, locals: { template: @template } %>
|
||||
</div>
|
||||
<% elsif params[:q].present? %>
|
||||
<div class="text-center">
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
<%= render 'shared/turbo_modal_large', title: 'Preferences', close_after_submit: false do %>
|
||||
<% show_api = Docuseal.multitenant? || current_account.testing? || !current_account.linked_account_account %>
|
||||
<% options = [%w[General general]] %>
|
||||
<% options << ['API and Embedding', 'api'] if show_api %>
|
||||
<% if options.size > 1 %>
|
||||
<toggle-visible data-element-ids="<%= options.map(&:last).to_json %>" class="relative text-center mt-3 block">
|
||||
<div class="join">
|
||||
<% options.each_with_index do |(label, value), index| %>
|
||||
<span>
|
||||
<%= radio_button_tag 'option', value, value == 'general', class: 'peer hidden', data: { action: 'change:toggle-visible#trigger' } %>
|
||||
<label for="option_<%= value %>" class="<%= '!rounded-s-full' if index.zero? %> btn btn-focus btn-sm join-item md:w-48 peer-checked:btn-active normal-case <%= 'px-8 md:px-0' if value == 'general' %>">
|
||||
<%= label %>
|
||||
</label>
|
||||
</span>
|
||||
<% end %>
|
||||
</div>
|
||||
</toggle-visible>
|
||||
<% end %>
|
||||
<div id="general" class="px-5 mb-4">
|
||||
<%= form_for @template, url: template_preferences_path(@template), method: :post, html: { autocomplete: 'off', class: 'mt-1' } do |f| %>
|
||||
<toggle-on-submit data-element-id="email_saved_alert"></toggle-on-submit>
|
||||
<%= f.fields_for :preferences, Struct.new(:request_email_subject, :request_email_body).new(*(@template.preferences.values_at('request_email_subject', 'request_email_body').compact_blank.presence || AccountConfigs.find_or_initialize_for_key(current_account, AccountConfig::SUBMITTER_INVITATION_EMAIL_KEY).value.values_at('subject', 'body'))) do |ff| %>
|
||||
<div class="form-control">
|
||||
<%= ff.label :request_email_subject, 'Email subject', class: 'label' %>
|
||||
<%= ff.text_field :request_email_subject, required: true, class: 'base-input', dir: 'auto' %>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<div class="flex items-center">
|
||||
<%= ff.label :request_email_body, 'Email body', class: 'label' %>
|
||||
<span class="tooltip tooltip-right" data-tip="Use following placeholders text: <%= AccountConfig::DEFAULT_VALUES.dig(AccountConfig::SUBMITTER_INVITATION_EMAIL_KEY, 'body').scan(/{{.*?}}/).join(', ') %>">
|
||||
<%= svg_icon('info_circle', class: 'w-4 h-4') %>
|
||||
</span>
|
||||
</div>
|
||||
<autoresize-textarea>
|
||||
<%= ff.text_area :request_email_body, required: true, class: 'base-input w-full py-2', dir: 'auto' %>
|
||||
</autoresize-textarea>
|
||||
</div>
|
||||
<% end %>
|
||||
<div class="form-control pt-2">
|
||||
<%= f.button button_title(title: 'Save', disabled_with: 'Saving'), class: 'base-button' %>
|
||||
<div class="flex justify-center">
|
||||
<span id="email_saved_alert" class="text-sm invisible font-normal mt-0.5">Changes have been saved</span>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<%= form_for @template, url: template_preferences_path(@template), method: :post, html: { autocomplete: 'off', class: 'mt-2' } do |f| %>
|
||||
<toggle-on-submit data-element-id="bcc_saved_alert"></toggle-on-submit>
|
||||
<%= f.fields_for :preferences, Struct.new(:bcc_completed).new(@template.preferences['bcc_completed']) do |ff| %>
|
||||
<div class="form-control">
|
||||
<%= ff.label :bcc_completed, class: 'label' do %>
|
||||
<span class="flex items-center space-x-1 justify-between w-full">
|
||||
<span>
|
||||
Completed documents BCC address
|
||||
</span>
|
||||
</span>
|
||||
<% end %>
|
||||
<%= tag.input type: 'email', multiple: true, name: 'template[preferences][bcc_completed]', autocomplete: 'off', class: 'base-input', value: ff.object.bcc_completed %>
|
||||
</div>
|
||||
<% end %>
|
||||
<div class="form-control pt-3">
|
||||
<%= f.button button_title(title: 'Save', disabled_with: 'Updating'), class: 'base-button' %>
|
||||
<div class="flex justify-center">
|
||||
<span id="bcc_saved_alert" class="text-sm invisible font-normal mt-0.5">Changes have been saved</span>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<%= render 'templates_code_modal/preferences' %>
|
||||
</div>
|
||||
<% if show_api %>
|
||||
<div id="api" class="hidden mt-2 mb-4 px-5">
|
||||
<div>
|
||||
<label class="text-sm font-semibold" for="template_id">
|
||||
Template ID
|
||||
</label>
|
||||
<div class="flex gap-2 mb-4 mt-2">
|
||||
<input id="template_id" type="text" value="<%= @template.id %>" class="base-input w-full" autocomplete="off" readonly>
|
||||
<%= render 'shared/clipboard_copy', icon: 'copy', text: @template.id, class: 'base-button', icon_class: 'w-6 h-6 text-white', copy_title: 'Copy', copied_title: 'Copied' %>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-sm font-semibold" for="embedding_url">
|
||||
Embedding URL
|
||||
</label>
|
||||
<div class="flex gap-2 mb-4 mt-2">
|
||||
<input id="embedding_url" type="text" value="<%= start_form_url(slug: @template.slug) %>" class="base-input w-full" autocomplete="off" readonly>
|
||||
<%= render 'shared/clipboard_copy', icon: 'copy', text: start_form_url(slug: @template.slug), class: 'base-button', icon_class: 'w-6 h-6 text-white', copy_title: 'Copy', copied_title: 'Copied' %>
|
||||
</div>
|
||||
</div>
|
||||
<%= render 'templates_code_modal/placeholder' %>
|
||||
<%= render 'templates/embedding', template: @template %>
|
||||
<% if can?(:manage, TemplateSharing.new(template: @template)) %>
|
||||
<%= form_for '', url: template_sharings_testing_index_path, method: :post, html: { class: 'mt-1' } do |f| %>
|
||||
<%= f.hidden_field :template_id, value: @template.id %>
|
||||
<div class="flex items-center justify-between">
|
||||
<span>
|
||||
Share template with Test Environment
|
||||
</span>
|
||||
<%= f.check_box :value, class: 'toggle', checked: @template.template_sharings.exists?(account_id: current_account.testing_accounts), onchange: 'this.form.requestSubmit()' %>
|
||||
</div>
|
||||
<% end %>
|
||||
<div class="mb-4">
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
@@ -2,7 +2,7 @@
|
||||
<%= render 'shared/settings_nav' %>
|
||||
<div class="md:flex-grow">
|
||||
<div class="flex justify-between mb-4">
|
||||
<h1 class="text-4xl font-bold">Users</h1>
|
||||
<h1 class="text-4xl font-bold"><%= params[:status].to_s.titleize %> Users</h1>
|
||||
<div class="flex items-center space-x-4">
|
||||
<% if can?(:create, User.new(account: current_account)) %>
|
||||
<%= link_to new_user_path, class: 'btn btn-primary btn-md gap-2', data: { turbo_frame: 'modal' } do %>
|
||||
@@ -55,17 +55,39 @@
|
||||
Edit
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% if can?(:destroy, user) && user != current_user %>
|
||||
<% if params[:status].blank? && can?(:destroy, user) && user != current_user %>
|
||||
<%= 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
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% if params[:status].present? && can?(:manage, user) && user != current_user %>
|
||||
<%= button_to user_path(user), method: :put, params: { user: { archived_at: nil } }, class: 'btn btn-outline btn-secondary btn-xs', title: 'Unarchive', data: { turbo_confirm: 'Are you sure?' } do %>
|
||||
Unarchive
|
||||
<% end %>
|
||||
<% end %>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<%= render 'shared/pagination', pagy: @pagy, items_name: 'users' %>
|
||||
<% view_archived_html = capture do %>
|
||||
<% if current_account.users.archived.exists? %>
|
||||
<div>
|
||||
<% if params[:status] == 'archived' %>
|
||||
<a href="<%= settings_users_path %>" class="link text-sm">View Active</a>
|
||||
<% else %>
|
||||
<a href="<%= settings_archived_users_path %>" class="link text-sm">View Archived</a>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% if @pagy.pages > 1 %>
|
||||
<%= render 'shared/pagination', pagy: @pagy, items_name: 'users', left_additional_html: view_archived_html %>
|
||||
<% else %>
|
||||
<div class="mt-2">
|
||||
<%= view_archived_html %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
@@ -14,6 +14,23 @@
|
||||
<%= f.button button_title(title: 'Save', disabled_with: 'Saving'), class: 'base-button w-full md:w-32' %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% preference = current_account.account_configs.find_by(key: AccountConfig::WEBHOOK_PREFERENCES_KEY)&.value || {} %>
|
||||
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-5 mt-2 gap-y-2">
|
||||
<% WebhookPreferencesController::EVENTS.each do |event| %>
|
||||
<%= form_for '', url: webhook_preferences_path, method: :post do |f| %>
|
||||
<%= f.hidden_field :event, value: event %>
|
||||
<% uuid = SecureRandom.uuid %>
|
||||
<div class="flex">
|
||||
<label for="<%= uuid %>" class="flex items-center space-x-2">
|
||||
<%= f.check_box :value, class: 'base-checkbox', checked: preference[event] || (!preference.key?(event) && event.starts_with?('form.')), onchange: 'this.form.requestSubmit()', id: uuid %>
|
||||
<span>
|
||||
<%= event %>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% submitter = current_account.submitters.where.not(completed_at: nil).order(:id).last %>
|
||||
|
||||
@@ -128,14 +128,22 @@ Rails.application.configure do
|
||||
config.lograge.formatter = ->(data) { data.except(:path, :location).to_json }
|
||||
|
||||
config.lograge.custom_payload do |controller|
|
||||
params = controller.request.try(:params) || {}
|
||||
params =
|
||||
begin
|
||||
controller.request.try(:params) || {}
|
||||
rescue StandardError
|
||||
{}
|
||||
end
|
||||
|
||||
{
|
||||
fwd: controller.request.ip,
|
||||
params: {
|
||||
id: params[:id],
|
||||
sig: (params[:signed_uuid] || params[:signed_id]).to_s.split('--').first,
|
||||
slug: (params[:slug] || params[:submission_slug] || params[:template_slug]).to_s.last(5)
|
||||
slug: (params[:slug] ||
|
||||
params[:submitter_slug] ||
|
||||
params[:submission_slug] ||
|
||||
params[:template_slug]).to_s.last(5)
|
||||
}.compact_blank,
|
||||
host: controller.request.host,
|
||||
uid: controller.instance_variable_get(:@current_user).try(:id)
|
||||
|
||||
@@ -30,7 +30,7 @@ Rails.application.configure do
|
||||
config.cache_store = :null_store
|
||||
|
||||
# Raise exceptions instead of rendering exception templates.
|
||||
config.action_dispatch.show_exceptions = false
|
||||
config.action_dispatch.show_exceptions = :none
|
||||
|
||||
# Disable request forgery protection in test environment.
|
||||
config.action_controller.allow_forgery_protection = false
|
||||
|
||||
@@ -2,6 +2,14 @@
|
||||
|
||||
Devise.otp_allowed_drift = 60.seconds
|
||||
|
||||
class FailureApp < Devise::FailureApp
|
||||
def respond
|
||||
Rollbar.warning('Invalid password') if defined?(Rollbar) && warden_message == :invalid
|
||||
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
# Assuming you have not yet modified this file, each configuration option below
|
||||
# is set to its default value. Note that some are commented out while others
|
||||
# are not: uncommented lines are intended to protect your configuration from
|
||||
@@ -277,6 +285,7 @@ Devise.setup do |config|
|
||||
config.warden do |manager|
|
||||
# manager.intercept_401 = false
|
||||
# manager.default_strategies(scope: :user).unshift(:auth_token)
|
||||
manager.failure_app = FailureApp
|
||||
end
|
||||
|
||||
# ==> Mountable engine configurations
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
en: &en
|
||||
email: Email
|
||||
verification_code_code: 'Verification code: %{code}'
|
||||
digitally_signed_by: Digitally signed by
|
||||
role: Role
|
||||
provide_your_email_to_start: Provide your email to start
|
||||
@@ -21,6 +22,7 @@ en: &en
|
||||
|
||||
es: &es
|
||||
role: Rol
|
||||
verification_code_code: 'Código de verificación: %{code}'
|
||||
email: Correo electrónico
|
||||
digitally_signed_by: Firmado digitalmente por
|
||||
role: Rol
|
||||
@@ -42,6 +44,7 @@ es: &es
|
||||
|
||||
it:
|
||||
role: Rôle
|
||||
verification_code_code: 'Codice di verifica: %{code}'
|
||||
email: Email
|
||||
digitally_signed_by: Firmato digitalmente da
|
||||
role: Ruolo
|
||||
@@ -63,6 +66,7 @@ it:
|
||||
|
||||
fr: &fr
|
||||
email: Email
|
||||
verification_code_code: 'Code de vérification: %{code}'
|
||||
digitally_signed_by: Signé numériquement par
|
||||
role: Rôle
|
||||
provide_your_email_to_start: Entrez votre adresse email pour commencer
|
||||
@@ -84,6 +88,7 @@ fr: &fr
|
||||
pt: &pt
|
||||
role: Função
|
||||
email: Email
|
||||
verification_code_code: 'Código de verificação: %{code}'
|
||||
digitally_signed_by: Assinado digitalmente por
|
||||
role: Função
|
||||
provide_your_email_to_start: Forneça o seu email para começar
|
||||
@@ -104,6 +109,7 @@ pt: &pt
|
||||
|
||||
de: &de
|
||||
role: Rolle
|
||||
verification_code_code: 'Verifizierungscode: %{code}'
|
||||
email: E-Mail
|
||||
digitally_signed_by: Digital signiert von
|
||||
role: Rolle
|
||||
@@ -125,6 +131,7 @@ de: &de
|
||||
|
||||
pl:
|
||||
email: Email
|
||||
verification_code_code: 'Kod weryfikacyjny: %{code}'
|
||||
digitally_signed_by: Podpis cyfrowy przez
|
||||
role: Rola
|
||||
provide_your_email_to_start: Podaj swój adres email, aby rozpocząć
|
||||
@@ -146,6 +153,7 @@ pl:
|
||||
uk:
|
||||
email: Email
|
||||
digitally_signed_by: Цифровий підпис від
|
||||
verification_code_code: 'Код підтвердження: %{code}'
|
||||
role: Роль
|
||||
provide_your_email_to_start: Введіть свій email, щоб почати
|
||||
start: Почати
|
||||
@@ -166,6 +174,7 @@ uk:
|
||||
cs:
|
||||
email: Email
|
||||
digitally_signed_by: Digitálně podepsáno uživatelem
|
||||
verification_code_code: 'Ověřovací kód: %{code}'
|
||||
role: Role
|
||||
provide_your_email_to_start: Zadejte svůj email pro zahájení
|
||||
start: Zahájit
|
||||
@@ -187,6 +196,7 @@ he:
|
||||
email: דוא"ל
|
||||
digitally_signed_by: חתום דיגיטלית על ידי
|
||||
role: תפקיד
|
||||
verification_code_code: 'קוד אימות: %{code}'
|
||||
provide_your_email_to_start: ספק את כתובת הדוא"ל שלך כדי להתחיל
|
||||
start: התחל
|
||||
starting: מתחיל
|
||||
@@ -207,6 +217,7 @@ nl:
|
||||
email: E-mail
|
||||
digitally_signed_by: Digitaal ondertekend door
|
||||
role: Rol
|
||||
verification_code_code: 'Verificatiecode: %{code}'
|
||||
provide_your_email_to_start: Geef uw e-mailadres om te beginnen
|
||||
start: Start
|
||||
starting: Starten
|
||||
@@ -231,6 +242,7 @@ ar:
|
||||
provide_your_email_to_start: قدم بريدك الإلكتروني للبدء
|
||||
start: بدء
|
||||
starting: بداية
|
||||
verification_code_code: 'رمز التحقق: %{code}'
|
||||
form_has_been_deleted_by_html: 'تم حذف الاستمارة بواسطة <span class="font-semibold">%{name}</span>.'
|
||||
invited_by_html: 'تمت الدعوة بواسطة <span class="font-semibold">%{name}</span>'
|
||||
you_have_been_invited_to_submit_a_form: تمت دعوتك لتقديم استمارة
|
||||
@@ -255,6 +267,7 @@ ko:
|
||||
form_has_been_deleted_by_html: '<span class="font-semibold">%{name}</span>에 의해 양식이 삭제되었습니다.'
|
||||
invited_by_html: '<span class="font-semibold">%{name}</span>에 의해 초대되었습니다.'
|
||||
you_have_been_invited_to_submit_a_form: 양식을 제출하도록 초대되었습니다.
|
||||
verification_code_code: '확인 코드: %{code}'
|
||||
signed_on_time: '%{time}에 서명됨'
|
||||
form_has_been_submitted_already: 양식이 이미 제출되었습니다.
|
||||
send_copy_to_email: 이메일로 사본 보내기
|
||||
|
||||
@@ -59,6 +59,7 @@ Rails.application.routes.draw do
|
||||
resources :testing_api_settings, only: %i[index]
|
||||
resources :submitters_autocomplete, only: %i[index]
|
||||
resources :template_folders_autocomplete, only: %i[index]
|
||||
resources :webhook_preferences, only: %i[create]
|
||||
resource :templates_upload, only: %i[create]
|
||||
authenticated do
|
||||
resource :templates_upload, only: %i[show], path: 'new'
|
||||
@@ -75,9 +76,12 @@ Rails.application.routes.draw do
|
||||
resource :folder, only: %i[edit update], controller: 'templates_folders'
|
||||
resource :preview, only: %i[show], controller: 'templates_preview'
|
||||
resource :code_modal, only: %i[show], controller: 'templates_code_modal'
|
||||
resource :preferences, only: %i[show create], controller: 'templates_preferences'
|
||||
resources :submissions_export, only: %i[index new]
|
||||
end
|
||||
resources :preview_document_page, only: %i[show], path: '/preview/:signed_uuid'
|
||||
resource :blobs_proxy, only: %i[show], path: '/file/:signed_uuid/*filename',
|
||||
controller: 'api/active_storage_blobs_proxy'
|
||||
resource :blobs_proxy, only: %i[show], path: '/blobs_proxy/:signed_uuid/*filename',
|
||||
controller: 'api/active_storage_blobs_proxy'
|
||||
|
||||
@@ -127,6 +131,8 @@ Rails.application.routes.draw do
|
||||
resources :notifications, only: %i[index create], controller: 'notifications_settings'
|
||||
resource :esign, only: %i[show create new update destroy], controller: 'esign_settings'
|
||||
resources :users, only: %i[index]
|
||||
resources :archived_users, only: %i[index], path: 'users/:status', controller: 'users',
|
||||
defaults: { status: :archived }
|
||||
resource :personalization, only: %i[show create], controller: 'personalization_settings'
|
||||
resources :api, only: %i[index create], controller: 'api_settings'
|
||||
resource :webhooks, only: %i[show create update], controller: 'webhook_settings'
|
||||
|
||||
@@ -19,7 +19,7 @@ development:
|
||||
compiler_strategy: mtime
|
||||
|
||||
dev_server:
|
||||
https: false
|
||||
server: 'http'
|
||||
host: localhost
|
||||
port: 3035
|
||||
hmr: false
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class AddPreferencesToTemplates < ActiveRecord::Migration[7.1]
|
||||
class MigrationTemplate < ApplicationRecord
|
||||
self.table_name = 'templates'
|
||||
end
|
||||
|
||||
def change
|
||||
add_column :templates, :preferences, :text
|
||||
|
||||
MigrationTemplate.where(preferences: nil).update_all(preferences: '{}')
|
||||
|
||||
change_column_null :templates, :preferences, false
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class AddArchivedAtToAccounts < ActiveRecord::Migration[7.1]
|
||||
def change
|
||||
add_column :accounts, :archived_at, :datetime
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,28 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class PopulateAccountLinkedAccounts < ActiveRecord::Migration[7.1]
|
||||
class MigrationAccount < ApplicationRecord
|
||||
self.table_name = 'accounts'
|
||||
end
|
||||
|
||||
class MigrationAccountLinkedAccount < ApplicationRecord
|
||||
self.table_name = 'account_linked_accounts'
|
||||
end
|
||||
|
||||
def up
|
||||
return if Docuseal.multitenant?
|
||||
|
||||
MigrationAccount.order(:id).each do |account|
|
||||
next if account.id == 1
|
||||
next if MigrationAccountLinkedAccount.exists?(linked_account_id: account.id)
|
||||
|
||||
MigrationAccountLinkedAccount.create!(account_id: 1,
|
||||
linked_account_id: account.id,
|
||||
account_type: :linked)
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
nil
|
||||
end
|
||||
end
|
||||
+3
-1
@@ -10,7 +10,7 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[7.1].define(version: 2024_04_05_165329) do
|
||||
ActiveRecord::Schema[7.1].define(version: 2024_04_28_112400) do
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
|
||||
@@ -52,6 +52,7 @@ ActiveRecord::Schema[7.1].define(version: 2024_04_05_165329) do
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.string "uuid", null: false
|
||||
t.datetime "archived_at"
|
||||
t.index ["uuid"], name: "index_accounts_on_uuid", unique: true
|
||||
end
|
||||
|
||||
@@ -222,6 +223,7 @@ ActiveRecord::Schema[7.1].define(version: 2024_04_05_165329) do
|
||||
t.text "source", null: false
|
||||
t.bigint "folder_id", null: false
|
||||
t.string "external_id"
|
||||
t.text "preferences", null: false
|
||||
t.index ["account_id"], name: "index_templates_on_account_id"
|
||||
t.index ["author_id"], name: "index_templates_on_author_id"
|
||||
t.index ["folder_id"], name: "index_templates_on_folder_id"
|
||||
|
||||
+13
-3
@@ -67,14 +67,24 @@ module Accounts
|
||||
new_template
|
||||
end
|
||||
|
||||
def load_webhook_configs(account)
|
||||
def load_webhook_url(account)
|
||||
configs = account.encrypted_configs.find_by(key: EncryptedConfig::WEBHOOK_URL_KEY)
|
||||
|
||||
unless Docuseal.multitenant?
|
||||
configs ||= Account.order(:id).first.encrypted_configs.find_by(key: EncryptedConfig::WEBHOOK_URL_KEY)
|
||||
end
|
||||
|
||||
configs
|
||||
configs&.value.presence
|
||||
end
|
||||
|
||||
def load_webhook_preferences(account)
|
||||
configs = account.account_configs.find_by(key: AccountConfig::WEBHOOK_PREFERENCES_KEY)
|
||||
|
||||
unless Docuseal.multitenant?
|
||||
configs ||= Account.order(:id).first.account_configs.find_by(key: AccountConfig::WEBHOOK_PREFERENCES_KEY)
|
||||
end
|
||||
|
||||
configs&.value.presence || {}
|
||||
end
|
||||
|
||||
def load_signing_pkcs(account)
|
||||
@@ -108,7 +118,7 @@ module Accounts
|
||||
end.presence
|
||||
end
|
||||
|
||||
def can_send_emails?(_account)
|
||||
def can_send_emails?(_account, **_params)
|
||||
return true if Docuseal.multitenant?
|
||||
return true if ENV['SMTP_ADDRESS'].present?
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ module LoadActiveStorageConfigs
|
||||
return if Docuseal.multitenant?
|
||||
return if IS_ENV_CONFIGURED
|
||||
return if Rails.env.test?
|
||||
return if Rails.env.development?
|
||||
|
||||
encrypted_config = EncryptedConfig.find_by(key: EncryptedConfig::FILES_STORAGE_KEY)
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ module Params
|
||||
type(submitter_params, :fields, Array)
|
||||
|
||||
in_path_each(submitter_params, :fields) do |field_params|
|
||||
required(field_params, :name)
|
||||
required(field_params, %i[name uuid])
|
||||
|
||||
type(field_params, :name, String)
|
||||
type(field_params, :validation_pattern, String)
|
||||
|
||||
@@ -26,12 +26,13 @@ module SubmissionEvents
|
||||
).first(TRACKING_PARAM_LENGTH)
|
||||
end
|
||||
|
||||
def create_with_tracking_data(submitter, event_type, request)
|
||||
def create_with_tracking_data(submitter, event_type, request, data = {})
|
||||
SubmissionEvent.create!(submitter:, event_type:, data: {
|
||||
ip: request.remote_ip,
|
||||
ua: request.user_agent,
|
||||
sid: request.session.id.to_s,
|
||||
uid: request.env['warden'].user(:user)&.id
|
||||
uid: request.env['warden'].user(:user)&.id,
|
||||
**data
|
||||
}.compact_blank)
|
||||
end
|
||||
end
|
||||
Loaded 100 of 117 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user