mirror of
https://github.com/docusealco/docuseal
synced 2026-11-04 06:42:32 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6c416dd76e | ||
|
|
1cc4ca6251 | ||
|
|
a27423c845 | ||
|
|
2692b8a1b5 | ||
|
|
7a75a6c887 | ||
|
|
236b882558 | ||
|
|
7ee648dacb | ||
|
|
9bfa6f7f4a | ||
|
|
f9c12b9d78 | ||
|
|
0a3f182e70 | ||
|
|
9c5c1f47f8 | ||
|
|
14c0ad2d16 | ||
|
|
6736d8fe68 | ||
|
|
4ecd830587 | ||
|
|
a0ecc8e823 | ||
|
|
8334b612a8 | ||
|
|
349c0b0537 | ||
|
|
2af09afc66 | ||
|
|
e22a3d9ef3 | ||
|
|
d3c92cecfa | ||
|
|
af4672084d | ||
|
|
b7103cf010 | ||
|
|
13a1835c00 | ||
|
|
c35407d7a9 | ||
|
|
e55ccc0b76 | ||
|
|
b8d00870b4 | ||
|
|
5413fbc248 | ||
|
|
2d2eb7ce4d | ||
|
|
ed2ddbf74e | ||
|
|
b99e5d1262 | ||
|
|
87f6878eda | ||
|
|
99b815eb4b | ||
|
|
e40a29b9ab | ||
|
|
151890bb72 | ||
|
|
78edd87cc1 | ||
|
|
88c99a2b35 | ||
|
|
282b8cfa73 | ||
|
|
e9888c7466 | ||
|
|
0647befb3a | ||
|
|
85aa3d0451 | ||
|
|
6d2b3bb654 | ||
|
|
b394854e9d | ||
|
|
2e586e930e | ||
|
|
8f8f69f0bc | ||
|
|
04663d55eb | ||
|
|
93d0b935e2 | ||
|
|
829b2b2855 | ||
|
|
cf6de444f4 | ||
|
|
126f5d58a2 | ||
|
|
09b93067f4 | ||
|
|
f14458f238 | ||
|
|
445139f05c | ||
|
|
e5e4965f05 | ||
|
|
1c8f7b0316 | ||
|
|
9fe3656652 | ||
|
|
17ae46bd61 | ||
|
|
a036483938 | ||
|
|
3c65b0e4ba | ||
|
|
ced5ab67ab | ||
|
|
28c4177128 | ||
|
|
5d86a08b5f | ||
|
|
a69fb81429 | ||
|
|
e6ac3380d4 | ||
|
|
3a535e7112 | ||
|
|
7464f9cfad | ||
|
|
58e24ef758 | ||
|
|
c1d809f989 | ||
|
|
80b24f0e2a | ||
|
|
2e253904a3 | ||
|
|
a7f381caf0 |
No files matched your search
@@ -12,7 +12,7 @@ jobs:
|
|||||||
- name: Install Ruby
|
- name: Install Ruby
|
||||||
uses: ruby/setup-ruby@v1
|
uses: ruby/setup-ruby@v1
|
||||||
with:
|
with:
|
||||||
ruby-version: 4.0.5
|
ruby-version: 4.0.9
|
||||||
- name: Cache gems
|
- name: Cache gems
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
@@ -37,7 +37,7 @@ jobs:
|
|||||||
- name: Install Ruby
|
- name: Install Ruby
|
||||||
uses: ruby/setup-ruby@v1
|
uses: ruby/setup-ruby@v1
|
||||||
with:
|
with:
|
||||||
ruby-version: 4.0.5
|
ruby-version: 4.0.9
|
||||||
- name: Cache gems
|
- name: Cache gems
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
@@ -89,7 +89,7 @@ jobs:
|
|||||||
- name: Install Ruby
|
- name: Install Ruby
|
||||||
uses: ruby/setup-ruby@v1
|
uses: ruby/setup-ruby@v1
|
||||||
with:
|
with:
|
||||||
ruby-version: 4.0.5
|
ruby-version: 4.0.9
|
||||||
- name: Cache gems
|
- name: Cache gems
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
@@ -113,7 +113,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 15
|
timeout-minutes: 15
|
||||||
container:
|
container:
|
||||||
image: ruby:4.0.5-alpine
|
image: ruby:4.0.9-alpine
|
||||||
|
|
||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
|
|||||||
+8
-5
@@ -1,4 +1,4 @@
|
|||||||
FROM ruby:4.0.5-alpine AS download
|
FROM ruby:4.0.9-alpine AS download
|
||||||
|
|
||||||
WORKDIR /fonts
|
WORKDIR /fonts
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@ RUN apk --no-cache add wget unzip && \
|
|||||||
mkdir -p /pdfium-linux && \
|
mkdir -p /pdfium-linux && \
|
||||||
unzip -q pdfium-linux.zip -d /pdfium-linux
|
unzip -q pdfium-linux.zip -d /pdfium-linux
|
||||||
|
|
||||||
FROM ruby:4.0.5-alpine AS webpack
|
FROM ruby:4.0.9-alpine AS webpack
|
||||||
|
|
||||||
ENV RAILS_ENV=production
|
ENV RAILS_ENV=production
|
||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
@@ -44,7 +44,7 @@ COPY ./app/views ./app/views
|
|||||||
|
|
||||||
RUN echo "gem 'shakapacker'" > Gemfile && ./bin/shakapacker
|
RUN echo "gem 'shakapacker'" > Gemfile && ./bin/shakapacker
|
||||||
|
|
||||||
FROM ruby:4.0.5-alpine AS app
|
FROM ruby:4.0.9-alpine AS app
|
||||||
|
|
||||||
ENV RAILS_ENV=production
|
ENV RAILS_ENV=production
|
||||||
ENV BUNDLE_WITHOUT="development:test"
|
ENV BUNDLE_WITHOUT="development:test"
|
||||||
@@ -52,7 +52,8 @@ ENV OPENSSL_CONF=/etc/openssl_legacy.cnf
|
|||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
RUN apk add --no-cache libpq vips redis onnxruntime leptonica && \
|
RUN apk upgrade --no-cache ca-certificates-bundle && \
|
||||||
|
apk add --no-cache libpq vips redis onnxruntime leptonica jemalloc font-liberation font-carlito && \
|
||||||
rm -f /usr/bin/onnx_test_runner /usr/bin/onnxruntime_test
|
rm -f /usr/bin/onnx_test_runner /usr/bin/onnxruntime_test
|
||||||
|
|
||||||
RUN addgroup -g 2000 docuseal && adduser -u 2000 -G docuseal -s /bin/sh -D -h /home/docuseal docuseal
|
RUN addgroup -g 2000 docuseal && adduser -u 2000 -G docuseal -s /bin/sh -D -h /home/docuseal docuseal
|
||||||
@@ -91,7 +92,7 @@ COPY --chown=docuseal:docuseal --from=download /model.onnx /app/tmp/model.onnx
|
|||||||
COPY --chown=docuseal:docuseal --from=webpack /app/public/packs ./public/packs
|
COPY --chown=docuseal:docuseal --from=webpack /app/public/packs ./public/packs
|
||||||
|
|
||||||
RUN mkdir -p /app/public/fonts && ln -s /fonts/DancingScript-Regular.otf /app/public/fonts/ && \
|
RUN mkdir -p /app/public/fonts && ln -s /fonts/DancingScript-Regular.otf /app/public/fonts/ && \
|
||||||
mkdir -p /usr/share/fonts/noto && ln -s /fonts/GoNotoKurrent-Regular.ttf /usr/share/fonts/noto/ && ln -s /fonts/GoNotoKurrent-Bold.ttf /usr/share/fonts/noto/ && fc-cache -f && \
|
mkdir -p /usr/share/fonts/noto/bold && ln -s /fonts/GoNotoKurrent-Regular.ttf /usr/share/fonts/noto/ && ln -s /fonts/GoNotoKurrent-Bold.ttf /usr/share/fonts/noto/bold/ && fc-cache -f && \
|
||||||
bundle exec bootsnap precompile -j 1 --gemfile app/ lib/ && \
|
bundle exec bootsnap precompile -j 1 --gemfile app/ lib/ && \
|
||||||
chown -R docuseal:docuseal /app/tmp/cache
|
chown -R docuseal:docuseal /app/tmp/cache
|
||||||
|
|
||||||
@@ -100,6 +101,8 @@ ENV HOME=/home/docuseal
|
|||||||
ENV WORKDIR=/data/docuseal
|
ENV WORKDIR=/data/docuseal
|
||||||
ENV VIPS_MAX_COORD=17000
|
ENV VIPS_MAX_COORD=17000
|
||||||
ENV VIPS_BLOCK_UNTRUSTED=1
|
ENV VIPS_BLOCK_UNTRUSTED=1
|
||||||
|
# ENV LD_PRELOAD=/usr/lib/libjemalloc.so.2
|
||||||
|
ENV MALLOC_CONF=dirty_decay_ms:1000,narenas:2,background_thread:true
|
||||||
|
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
CMD ["/app/bin/bundle", "exec", "puma", "-C", "/app/config/puma.rb", "--dir", "/app"]
|
CMD ["/app/bin/bundle", "exec", "puma", "-C", "/app/config/puma.rb", "--dir", "/app"]
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
source 'https://rubygems.org'
|
source 'https://rubygems.org'
|
||||||
|
|
||||||
ruby '4.0.5'
|
ruby '4.0.9'
|
||||||
|
|
||||||
gem 'addressable'
|
gem 'addressable'
|
||||||
gem 'arabic-letter-connector', require: false
|
gem 'arabic-letter-connector', require: false
|
||||||
@@ -45,6 +45,7 @@ gem 'trilogy', require: false
|
|||||||
gem 'turbo-rails'
|
gem 'turbo-rails'
|
||||||
gem 'twitter_cldr', require: false
|
gem 'twitter_cldr', require: false
|
||||||
gem 'tzinfo-data'
|
gem 'tzinfo-data'
|
||||||
|
gem 'wordcraft', require: false, group: :ce
|
||||||
gem 'xlsxtream', require: false
|
gem 'xlsxtream', require: false
|
||||||
|
|
||||||
group :development, :test do
|
group :development, :test do
|
||||||
|
|||||||
+94
-83
@@ -3,29 +3,29 @@ GEM
|
|||||||
specs:
|
specs:
|
||||||
action_text-trix (2.1.19)
|
action_text-trix (2.1.19)
|
||||||
railties
|
railties
|
||||||
actioncable (8.1.3.1)
|
actioncable (8.1.4)
|
||||||
actionpack (= 8.1.3.1)
|
actionpack (= 8.1.4)
|
||||||
activesupport (= 8.1.3.1)
|
activesupport (= 8.1.4)
|
||||||
nio4r (~> 2.0)
|
nio4r (~> 2.0)
|
||||||
websocket-driver (>= 0.6.1)
|
websocket-driver (>= 0.6.1)
|
||||||
zeitwerk (~> 2.6)
|
zeitwerk (~> 2.6)
|
||||||
actionmailbox (8.1.3.1)
|
actionmailbox (8.1.4)
|
||||||
actionpack (= 8.1.3.1)
|
actionpack (= 8.1.4)
|
||||||
activejob (= 8.1.3.1)
|
activejob (= 8.1.4)
|
||||||
activerecord (= 8.1.3.1)
|
activerecord (= 8.1.4)
|
||||||
activestorage (= 8.1.3.1)
|
activestorage (= 8.1.4)
|
||||||
activesupport (= 8.1.3.1)
|
activesupport (= 8.1.4)
|
||||||
mail (>= 2.8.0)
|
mail (>= 2.8.0)
|
||||||
actionmailer (8.1.3.1)
|
actionmailer (8.1.4)
|
||||||
actionpack (= 8.1.3.1)
|
actionpack (= 8.1.4)
|
||||||
actionview (= 8.1.3.1)
|
actionview (= 8.1.4)
|
||||||
activejob (= 8.1.3.1)
|
activejob (= 8.1.4)
|
||||||
activesupport (= 8.1.3.1)
|
activesupport (= 8.1.4)
|
||||||
mail (>= 2.8.0)
|
mail (>= 2.8.0)
|
||||||
rails-dom-testing (~> 2.2)
|
rails-dom-testing (~> 2.2)
|
||||||
actionpack (8.1.3.1)
|
actionpack (8.1.4)
|
||||||
actionview (= 8.1.3.1)
|
actionview (= 8.1.4)
|
||||||
activesupport (= 8.1.3.1)
|
activesupport (= 8.1.4)
|
||||||
nokogiri (>= 1.8.5)
|
nokogiri (>= 1.8.5)
|
||||||
rack (>= 2.2.4)
|
rack (>= 2.2.4)
|
||||||
rack-session (>= 1.0.1)
|
rack-session (>= 1.0.1)
|
||||||
@@ -33,36 +33,36 @@ GEM
|
|||||||
rails-dom-testing (~> 2.2)
|
rails-dom-testing (~> 2.2)
|
||||||
rails-html-sanitizer (~> 1.6)
|
rails-html-sanitizer (~> 1.6)
|
||||||
useragent (~> 0.16)
|
useragent (~> 0.16)
|
||||||
actiontext (8.1.3.1)
|
actiontext (8.1.4)
|
||||||
action_text-trix (~> 2.1.15)
|
action_text-trix (~> 2.1.15)
|
||||||
actionpack (= 8.1.3.1)
|
actionpack (= 8.1.4)
|
||||||
activerecord (= 8.1.3.1)
|
activerecord (= 8.1.4)
|
||||||
activestorage (= 8.1.3.1)
|
activestorage (= 8.1.4)
|
||||||
activesupport (= 8.1.3.1)
|
activesupport (= 8.1.4)
|
||||||
globalid (>= 0.6.0)
|
globalid (>= 0.6.0)
|
||||||
nokogiri (>= 1.8.5)
|
nokogiri (>= 1.8.5)
|
||||||
actionview (8.1.3.1)
|
actionview (8.1.4)
|
||||||
activesupport (= 8.1.3.1)
|
activesupport (= 8.1.4)
|
||||||
builder (~> 3.1)
|
builder (~> 3.1)
|
||||||
erubi (~> 1.11)
|
erubi (~> 1.11)
|
||||||
rails-dom-testing (~> 2.2)
|
rails-dom-testing (~> 2.2)
|
||||||
rails-html-sanitizer (~> 1.6)
|
rails-html-sanitizer (~> 1.6)
|
||||||
activejob (8.1.3.1)
|
activejob (8.1.4)
|
||||||
activesupport (= 8.1.3.1)
|
activesupport (= 8.1.4)
|
||||||
globalid (>= 0.3.6)
|
globalid (>= 0.3.6)
|
||||||
activemodel (8.1.3.1)
|
activemodel (8.1.4)
|
||||||
activesupport (= 8.1.3.1)
|
activesupport (= 8.1.4)
|
||||||
activerecord (8.1.3.1)
|
activerecord (8.1.4)
|
||||||
activemodel (= 8.1.3.1)
|
activemodel (= 8.1.4)
|
||||||
activesupport (= 8.1.3.1)
|
activesupport (= 8.1.4)
|
||||||
timeout (>= 0.4.0)
|
timeout (>= 0.4.0)
|
||||||
activestorage (8.1.3.1)
|
activestorage (8.1.4)
|
||||||
actionpack (= 8.1.3.1)
|
actionpack (= 8.1.4)
|
||||||
activejob (= 8.1.3.1)
|
activejob (= 8.1.4)
|
||||||
activerecord (= 8.1.3.1)
|
activerecord (= 8.1.4)
|
||||||
activesupport (= 8.1.3.1)
|
activesupport (= 8.1.4)
|
||||||
marcel (~> 1.0)
|
marcel (~> 1.0)
|
||||||
activesupport (8.1.3.1)
|
activesupport (8.1.4)
|
||||||
base64
|
base64
|
||||||
bigdecimal
|
bigdecimal
|
||||||
concurrent-ruby (~> 1.0, >= 1.3.1)
|
concurrent-ruby (~> 1.0, >= 1.3.1)
|
||||||
@@ -116,7 +116,7 @@ GEM
|
|||||||
erubi (~> 1.4)
|
erubi (~> 1.4)
|
||||||
parser (>= 2.4)
|
parser (>= 2.4)
|
||||||
smart_properties
|
smart_properties
|
||||||
bigdecimal (4.1.2)
|
bigdecimal (4.1.3)
|
||||||
bindex (0.8.1)
|
bindex (0.8.1)
|
||||||
bootsnap (1.23.0)
|
bootsnap (1.23.0)
|
||||||
msgpack (~> 1.2)
|
msgpack (~> 1.2)
|
||||||
@@ -149,14 +149,14 @@ GEM
|
|||||||
crack (1.0.1)
|
crack (1.0.1)
|
||||||
bigdecimal
|
bigdecimal
|
||||||
rexml
|
rexml
|
||||||
crass (1.0.7)
|
crass (1.0.8)
|
||||||
csv (3.3.5)
|
csv (3.3.5)
|
||||||
csv-safe (3.3.1)
|
csv-safe (3.3.1)
|
||||||
csv (~> 3.0)
|
csv (~> 3.0)
|
||||||
cuprite (0.17)
|
cuprite (0.17)
|
||||||
capybara (~> 3.0)
|
capybara (~> 3.0)
|
||||||
ferrum (~> 0.17.0)
|
ferrum (~> 0.17.0)
|
||||||
date (3.5.1)
|
date (3.6.0)
|
||||||
debug (1.11.1)
|
debug (1.11.1)
|
||||||
irb (~> 1.10)
|
irb (~> 1.10)
|
||||||
reline (>= 0.3.8)
|
reline (>= 0.3.8)
|
||||||
@@ -179,7 +179,7 @@ GEM
|
|||||||
dotenv (3.2.0)
|
dotenv (3.2.0)
|
||||||
drb (2.2.3)
|
drb (2.2.3)
|
||||||
email_typo (0.2.3)
|
email_typo (0.2.3)
|
||||||
erb (6.0.6)
|
erb (6.0.7)
|
||||||
erb_lint (0.9.0)
|
erb_lint (0.9.0)
|
||||||
activesupport
|
activesupport
|
||||||
better_html (>= 2.0.1)
|
better_html (>= 2.0.1)
|
||||||
@@ -195,8 +195,8 @@ GEM
|
|||||||
railties (>= 6.1.0)
|
railties (>= 6.1.0)
|
||||||
faker (3.6.1)
|
faker (3.6.1)
|
||||||
i18n (>= 1.8.11, < 2)
|
i18n (>= 1.8.11, < 2)
|
||||||
faraday (2.14.3)
|
faraday (2.15.1)
|
||||||
faraday-net_http (>= 2.0, < 3.5)
|
faraday-net_http (>= 2.0, < 3.6)
|
||||||
json
|
json
|
||||||
logger
|
logger
|
||||||
faraday-follow_redirects (0.5.0)
|
faraday-follow_redirects (0.5.0)
|
||||||
@@ -264,14 +264,14 @@ GEM
|
|||||||
strscan (>= 3.1.2)
|
strscan (>= 3.1.2)
|
||||||
i18n (1.15.2)
|
i18n (1.15.2)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
io-console (0.8.2)
|
io-console (0.9.5)
|
||||||
irb (1.18.0)
|
irb (1.18.0)
|
||||||
pp (>= 0.6.0)
|
pp (>= 0.6.0)
|
||||||
prism (>= 1.3.0)
|
prism (>= 1.3.0)
|
||||||
rdoc (>= 4.0.0)
|
rdoc (>= 4.0.0)
|
||||||
reline (>= 0.4.2)
|
reline (>= 0.4.2)
|
||||||
jmespath (1.6.2)
|
jmespath (1.6.2)
|
||||||
json (2.21.2)
|
json (3.1.0)
|
||||||
jwt (3.2.0)
|
jwt (3.2.0)
|
||||||
base64
|
base64
|
||||||
language_server-protocol (3.17.0.5)
|
language_server-protocol (3.17.0.5)
|
||||||
@@ -310,17 +310,17 @@ GEM
|
|||||||
drb (~> 2.0)
|
drb (~> 2.0)
|
||||||
prism (~> 1.5)
|
prism (~> 1.5)
|
||||||
msgpack (1.8.4)
|
msgpack (1.8.4)
|
||||||
multi_json (1.19.1)
|
multi_json (1.21.2)
|
||||||
net-http (0.9.1)
|
net-http (0.9.1)
|
||||||
uri (>= 0.11.1)
|
uri (>= 0.11.1)
|
||||||
net-imap (0.6.6)
|
net-imap (0.6.8)
|
||||||
date
|
date
|
||||||
net-protocol
|
net-protocol
|
||||||
net-pop (0.1.2)
|
net-pop (0.1.2)
|
||||||
net-protocol
|
net-protocol
|
||||||
net-protocol (0.2.2)
|
net-protocol (0.4.0)
|
||||||
timeout
|
timeout
|
||||||
net-smtp (0.5.1)
|
net-smtp (0.5.2)
|
||||||
net-protocol
|
net-protocol
|
||||||
nio4r (2.7.5)
|
nio4r (2.7.5)
|
||||||
nokogiri (1.19.4-aarch64-linux-gnu)
|
nokogiri (1.19.4-aarch64-linux-gnu)
|
||||||
@@ -361,7 +361,7 @@ GEM
|
|||||||
prettyprint
|
prettyprint
|
||||||
pretender (1.0.0)
|
pretender (1.0.0)
|
||||||
actionpack (>= 7.2)
|
actionpack (>= 7.2)
|
||||||
prettyprint (0.2.0)
|
prettyprint (0.2.1)
|
||||||
prism (1.9.0)
|
prism (1.9.0)
|
||||||
pry (0.16.0)
|
pry (0.16.0)
|
||||||
coderay (~> 1.1)
|
coderay (~> 1.1)
|
||||||
@@ -373,30 +373,30 @@ GEM
|
|||||||
puma (8.0.2)
|
puma (8.0.2)
|
||||||
nio4r (~> 2.0)
|
nio4r (~> 2.0)
|
||||||
racc (1.8.1)
|
racc (1.8.1)
|
||||||
rack (3.2.6)
|
rack (3.2.7)
|
||||||
rack-proxy (0.7.7)
|
rack-proxy (0.7.7)
|
||||||
rack
|
rack
|
||||||
rack-session (2.1.2)
|
rack-session (2.1.2)
|
||||||
base64 (>= 0.1.0)
|
base64 (>= 0.1.0)
|
||||||
rack (>= 3.0.0)
|
rack (>= 3.0.0)
|
||||||
rack-test (2.2.0)
|
rack-test (2.3.0)
|
||||||
rack (>= 1.3)
|
rack (>= 1.3)
|
||||||
rackup (2.3.1)
|
rackup (2.3.1)
|
||||||
rack (>= 3)
|
rack (>= 3)
|
||||||
rails (8.1.3.1)
|
rails (8.1.4)
|
||||||
actioncable (= 8.1.3.1)
|
actioncable (= 8.1.4)
|
||||||
actionmailbox (= 8.1.3.1)
|
actionmailbox (= 8.1.4)
|
||||||
actionmailer (= 8.1.3.1)
|
actionmailer (= 8.1.4)
|
||||||
actionpack (= 8.1.3.1)
|
actionpack (= 8.1.4)
|
||||||
actiontext (= 8.1.3.1)
|
actiontext (= 8.1.4)
|
||||||
actionview (= 8.1.3.1)
|
actionview (= 8.1.4)
|
||||||
activejob (= 8.1.3.1)
|
activejob (= 8.1.4)
|
||||||
activemodel (= 8.1.3.1)
|
activemodel (= 8.1.4)
|
||||||
activerecord (= 8.1.3.1)
|
activerecord (= 8.1.4)
|
||||||
activestorage (= 8.1.3.1)
|
activestorage (= 8.1.4)
|
||||||
activesupport (= 8.1.3.1)
|
activesupport (= 8.1.4)
|
||||||
bundler (>= 1.15.0)
|
bundler (>= 1.15.0)
|
||||||
railties (= 8.1.3.1)
|
railties (= 8.1.4)
|
||||||
rails-dom-testing (2.3.0)
|
rails-dom-testing (2.3.0)
|
||||||
activesupport (>= 5.0.0)
|
activesupport (>= 5.0.0)
|
||||||
minitest
|
minitest
|
||||||
@@ -407,9 +407,9 @@ GEM
|
|||||||
rails-i18n (8.1.0)
|
rails-i18n (8.1.0)
|
||||||
i18n (>= 0.7, < 2)
|
i18n (>= 0.7, < 2)
|
||||||
railties (>= 8.0.0, < 9)
|
railties (>= 8.0.0, < 9)
|
||||||
railties (8.1.3.1)
|
railties (8.1.4)
|
||||||
actionpack (= 8.1.3.1)
|
actionpack (= 8.1.4)
|
||||||
activesupport (= 8.1.3.1)
|
activesupport (= 8.1.4)
|
||||||
irb (~> 1.13)
|
irb (~> 1.13)
|
||||||
rackup (>= 1.0.0)
|
rackup (>= 1.0.0)
|
||||||
rake (>= 12.2)
|
rake (>= 12.2)
|
||||||
@@ -418,11 +418,11 @@ GEM
|
|||||||
zeitwerk (~> 2.6)
|
zeitwerk (~> 2.6)
|
||||||
rainbow (3.1.1)
|
rainbow (3.1.1)
|
||||||
rake (13.4.2)
|
rake (13.4.2)
|
||||||
rbs (4.1.0)
|
rbs (4.2.0)
|
||||||
logger
|
logger
|
||||||
prism (>= 1.6.0)
|
prism (>= 1.6.0)
|
||||||
tsort
|
tsort
|
||||||
rdoc (8.0.0)
|
rdoc (8.1.0)
|
||||||
erb
|
erb
|
||||||
prism (>= 1.6.0)
|
prism (>= 1.6.0)
|
||||||
rbs (>= 4.0.0)
|
rbs (>= 4.0.0)
|
||||||
@@ -430,7 +430,7 @@ GEM
|
|||||||
redis-client (0.29.0)
|
redis-client (0.29.0)
|
||||||
connection_pool
|
connection_pool
|
||||||
regexp_parser (2.11.3)
|
regexp_parser (2.11.3)
|
||||||
reline (0.6.3)
|
reline (0.7.0)
|
||||||
io-console (~> 0.5)
|
io-console (~> 0.5)
|
||||||
representable (3.2.0)
|
representable (3.2.0)
|
||||||
declarative (< 0.1.0)
|
declarative (< 0.1.0)
|
||||||
@@ -466,14 +466,14 @@ GEM
|
|||||||
rspec-mocks (>= 3.13.0, < 5.0.0)
|
rspec-mocks (>= 3.13.0, < 5.0.0)
|
||||||
rspec-support (>= 3.13.0, < 5.0.0)
|
rspec-support (>= 3.13.0, < 5.0.0)
|
||||||
rspec-support (3.13.7)
|
rspec-support (3.13.7)
|
||||||
rubocop (1.86.0)
|
rubocop (1.92.1)
|
||||||
json (~> 2.3)
|
json (>= 2.3)
|
||||||
language_server-protocol (~> 3.17.0.2)
|
language_server-protocol (~> 3.17.0.2)
|
||||||
lint_roller (~> 1.1.0)
|
lint_roller (~> 1.1.0)
|
||||||
parallel (~> 1.10)
|
parallel (>= 1.10)
|
||||||
parser (>= 3.3.0.2)
|
parser (>= 3.3.0.2)
|
||||||
rainbow (>= 2.2.2, < 4.0)
|
rainbow (>= 2.2.2, < 4.0)
|
||||||
regexp_parser (>= 2.9.3, < 3.0)
|
regexp_parser (>= 2.11.3, < 3.0)
|
||||||
rubocop-ast (>= 1.49.0, < 2.0)
|
rubocop-ast (>= 1.49.0, < 2.0)
|
||||||
ruby-progressbar (~> 1.7)
|
ruby-progressbar (~> 1.7)
|
||||||
unicode-display_width (>= 2.4.0, < 4.0)
|
unicode-display_width (>= 2.4.0, < 4.0)
|
||||||
@@ -524,11 +524,11 @@ GEM
|
|||||||
simplecov-html (0.13.2)
|
simplecov-html (0.13.2)
|
||||||
simplecov_json_formatter (0.1.4)
|
simplecov_json_formatter (0.1.4)
|
||||||
smart_properties (1.17.0)
|
smart_properties (1.17.0)
|
||||||
sqlite3 (2.9.5-aarch64-linux-gnu)
|
sqlite3 (2.9.6-aarch64-linux-gnu)
|
||||||
sqlite3 (2.9.5-aarch64-linux-musl)
|
sqlite3 (2.9.6-aarch64-linux-musl)
|
||||||
sqlite3 (2.9.5-arm64-darwin)
|
sqlite3 (2.9.6-arm64-darwin)
|
||||||
sqlite3 (2.9.5-x86_64-linux-gnu)
|
sqlite3 (2.9.6-x86_64-linux-gnu)
|
||||||
sqlite3 (2.9.5-x86_64-linux-musl)
|
sqlite3 (2.9.6-x86_64-linux-musl)
|
||||||
strip_attributes (2.0.1)
|
strip_attributes (2.0.1)
|
||||||
activemodel (>= 3.0, < 9.0)
|
activemodel (>= 3.0, < 9.0)
|
||||||
strscan (3.1.8)
|
strscan (3.1.8)
|
||||||
@@ -555,7 +555,7 @@ GEM
|
|||||||
unicode-emoji (~> 4.1)
|
unicode-emoji (~> 4.1)
|
||||||
unicode-emoji (4.2.0)
|
unicode-emoji (4.2.0)
|
||||||
uniform_notifier (1.18.0)
|
uniform_notifier (1.18.0)
|
||||||
uri (1.1.1)
|
uri (1.1.2)
|
||||||
useragent (0.16.11)
|
useragent (0.16.11)
|
||||||
warden (1.2.9)
|
warden (1.2.9)
|
||||||
rack (>= 2.0.9)
|
rack (>= 2.0.9)
|
||||||
@@ -572,12 +572,22 @@ GEM
|
|||||||
base64
|
base64
|
||||||
websocket-extensions (>= 0.1.0)
|
websocket-extensions (>= 0.1.0)
|
||||||
websocket-extensions (0.1.5)
|
websocket-extensions (0.1.5)
|
||||||
|
wordcraft (0.11.0-aarch64-linux-gnu)
|
||||||
|
ffi (>= 1.15)
|
||||||
|
wordcraft (0.11.0-aarch64-linux-musl)
|
||||||
|
ffi (>= 1.15)
|
||||||
|
wordcraft (0.11.0-arm64-darwin)
|
||||||
|
ffi (>= 1.15)
|
||||||
|
wordcraft (0.11.0-x86_64-linux-gnu)
|
||||||
|
ffi (>= 1.15)
|
||||||
|
wordcraft (0.11.0-x86_64-linux-musl)
|
||||||
|
ffi (>= 1.15)
|
||||||
xlsxtream (3.1.0)
|
xlsxtream (3.1.0)
|
||||||
zip_kit (>= 6.2, < 7)
|
zip_kit (>= 6.2, < 7)
|
||||||
xpath (3.2.0)
|
xpath (3.2.0)
|
||||||
nokogiri (~> 1.8)
|
nokogiri (~> 1.8)
|
||||||
yaml (0.4.0)
|
yaml (0.4.0)
|
||||||
zeitwerk (2.8.2)
|
zeitwerk (2.8.3)
|
||||||
zip_kit (6.3.4)
|
zip_kit (6.3.4)
|
||||||
|
|
||||||
PLATFORMS
|
PLATFORMS
|
||||||
@@ -650,10 +660,11 @@ DEPENDENCIES
|
|||||||
tzinfo-data
|
tzinfo-data
|
||||||
web-console
|
web-console
|
||||||
webmock
|
webmock
|
||||||
|
wordcraft
|
||||||
xlsxtream
|
xlsxtream
|
||||||
|
|
||||||
RUBY VERSION
|
RUBY VERSION
|
||||||
ruby 4.0.5
|
ruby 4.0.9
|
||||||
|
|
||||||
BUNDLED WITH
|
BUNDLED WITH
|
||||||
4.0.3
|
4.0.3
|
||||||
@@ -9,6 +9,7 @@ Out of scope vulnerabilities:
|
|||||||
- CSRF
|
- CSRF
|
||||||
- DNSSEC, CAA, CSP headers
|
- DNSSEC, CAA, CSP headers
|
||||||
- DNS or email security related
|
- DNS or email security related
|
||||||
|
- SSRF
|
||||||
- Rate Limiting
|
- Rate Limiting
|
||||||
|
|
||||||
Note: We reserve the right to classify any reported vulnerability as out of scope for the bug bounty program.
|
Note: We reserve the right to classify any reported vulnerability as out of scope for the bug bounty program.
|
||||||
@@ -54,14 +54,13 @@ class AccountsController < ApplicationController
|
|||||||
email: true_user.email.sub('@', '+removed@'))
|
email: true_user.email.sub('@', '+removed@'))
|
||||||
true_user.account.update!(archived_at: Time.current)
|
true_user.account.update!(archived_at: Time.current)
|
||||||
|
|
||||||
# rubocop:disable Layout/LineLength
|
# rubocop:disable-next Layout/LineLength
|
||||||
render turbo_stream: turbo_stream.replace(
|
render turbo_stream: turbo_stream.replace(
|
||||||
:account_delete_button,
|
:account_delete_button,
|
||||||
html: helpers.tag.p(I18n.t('your_account_will_be_permanently_deleted_within_2_months_please_contact_us_if_you_want_to_keep_your_account')) +
|
html: helpers.tag.p(I18n.t('your_account_will_be_permanently_deleted_within_2_months_please_contact_us_if_you_want_to_keep_your_account')) +
|
||||||
helpers.tag.br +
|
helpers.tag.br +
|
||||||
helpers.tag.p(I18n.t('your_email_address_has_been_released_immediately_you_can_now_be_added_to_your_company_team_without_waiting_for_the_deletion_period_to_end'))
|
helpers.tag.p(I18n.t('your_email_address_has_been_released_immediately_you_can_now_be_added_to_your_company_team_without_waiting_for_the_deletion_period_to_end'))
|
||||||
)
|
)
|
||||||
# rubocop:enable Layout/LineLength
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ module Api
|
|||||||
before_action :set_noindex_headers
|
before_action :set_noindex_headers
|
||||||
before_action :set_security_headers
|
before_action :set_security_headers
|
||||||
|
|
||||||
# rubocop:disable Metrics
|
# rubocop:disable-next Metrics
|
||||||
def show
|
def show
|
||||||
blob_uuid, purp, exp = ApplicationRecord.signed_id_verifier.verified(params[:signed_uuid])
|
blob_uuid, purp, exp = ApplicationRecord.signed_id_verifier.verified(params[:signed_uuid])
|
||||||
|
|
||||||
@@ -53,7 +53,6 @@ module Api
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
# rubocop:enable Metrics
|
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module Api
|
|
||||||
class ActiveStorageBlobsProxyLegacyController < ApiBaseController
|
|
||||||
include ActiveStorage::Streaming
|
|
||||||
|
|
||||||
skip_before_action :authenticate_user!
|
|
||||||
skip_authorization_check
|
|
||||||
|
|
||||||
before_action :set_cors_headers
|
|
||||||
before_action :set_noindex_headers
|
|
||||||
before_action :set_security_headers
|
|
||||||
|
|
||||||
# rubocop:disable Metrics
|
|
||||||
def show
|
|
||||||
Rollbar.info('Blob legacy') if defined?(Rollbar)
|
|
||||||
|
|
||||||
blob = ActiveStorage::Blob.find_signed(params[:signed_blob_id] || params[:signed_id])
|
|
||||||
|
|
||||||
return head :not_found unless blob
|
|
||||||
|
|
||||||
if FilenameUtils.dangerous_extension(blob.filename)
|
|
||||||
Rollbar.error('Dangerous extension') if defined?(Rollbar)
|
|
||||||
|
|
||||||
return head :unprocessable_content
|
|
||||||
end
|
|
||||||
|
|
||||||
is_permitted = blob.attachments.any? do |a|
|
|
||||||
(current_user && a.record.account.id == current_user.account_id) ||
|
|
||||||
a.record.account.account_configs.any? { |e| e.key == 'legacy_blob_proxy' } ||
|
|
||||||
a.name == 'logo'
|
|
||||||
end
|
|
||||||
|
|
||||||
unless is_permitted
|
|
||||||
Rollbar.error("Blob account not found: #{blob.id}") if defined?(Rollbar)
|
|
||||||
|
|
||||||
return render json: { error: 'Not authenticated' }, status: :unauthorized
|
|
||||||
end
|
|
||||||
|
|
||||||
if request.headers['Range'].present?
|
|
||||||
send_blob_byte_range_data blob, request.headers['Range']
|
|
||||||
else
|
|
||||||
http_cache_forever public: true do
|
|
||||||
response.headers['Accept-Ranges'] = 'bytes'
|
|
||||||
response.headers['Content-Length'] = blob.byte_size.to_s
|
|
||||||
|
|
||||||
send_blob_stream blob, disposition: params[:disposition]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
# rubocop:enable Metrics
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module Api
|
|
||||||
class AttachmentsController < ActionController::API
|
|
||||||
include ActionController::Cookies
|
|
||||||
include ActiveStorage::SetCurrent
|
|
||||||
|
|
||||||
COOKIE_STORE_LIMIT = 10
|
|
||||||
|
|
||||||
def create
|
|
||||||
@submitter = Submitter.find_by!(slug: params[:submitter_slug])
|
|
||||||
|
|
||||||
unless can_upload?(@submitter)
|
|
||||||
return render json: { error: I18n.t('form_has_been_archived') }, status: :unprocessable_content
|
|
||||||
end
|
|
||||||
|
|
||||||
file = params[:file]
|
|
||||||
|
|
||||||
if params[:type].in?(%w[initials signature])
|
|
||||||
image = ImageUtils.load_vips(file.read, content_type: file.content_type)
|
|
||||||
|
|
||||||
if ImageUtils.blank?(image)
|
|
||||||
Rollbar.error("Empty signature: #{@submitter.id}") if defined?(Rollbar)
|
|
||||||
|
|
||||||
return render json: { error: "#{params[:type]} is empty" }, status: :unprocessable_content
|
|
||||||
end
|
|
||||||
|
|
||||||
if ImageUtils.error?(image)
|
|
||||||
Rollbar.error("Error signature: #{@submitter.id}") if defined?(Rollbar)
|
|
||||||
|
|
||||||
return render json: { error: "#{params[:type]} error, try to sign on another device" },
|
|
||||||
status: :unprocessable_content
|
|
||||||
end
|
|
||||||
|
|
||||||
metadata = { analyzed: true, identified: true, width: image.width, height: image.height }
|
|
||||||
end
|
|
||||||
|
|
||||||
attachment = Submitters.create_attachment!(@submitter, file, metadata:)
|
|
||||||
|
|
||||||
if params[:remember_signature] == 'true' && @submitter.email.present?
|
|
||||||
cookies.encrypted[:signature_uuids] = build_new_cookie_signatures_json(@submitter, attachment)
|
|
||||||
end
|
|
||||||
|
|
||||||
render json: attachment.as_json(only: %i[uuid created_at], methods: %i[url filename content_type])
|
|
||||||
rescue Submitters::MaliciousFileExtension => e
|
|
||||||
Rollbar.error(e) if defined?(Rollbar)
|
|
||||||
|
|
||||||
render json: { error: e.message }, status: :unprocessable_content
|
|
||||||
end
|
|
||||||
|
|
||||||
def can_upload?(submitter)
|
|
||||||
!submitter.declined_at? &&
|
|
||||||
!submitter.completed_at? &&
|
|
||||||
!submitter.submission.archived_at? &&
|
|
||||||
!submitter.submission.expired? &&
|
|
||||||
!submitter.submission.template&.archived_at?
|
|
||||||
end
|
|
||||||
|
|
||||||
def build_new_cookie_signatures_json(submitter, attachment)
|
|
||||||
values =
|
|
||||||
begin
|
|
||||||
JSON.parse(cookies.encrypted[:signature_uuids].presence || '{}')
|
|
||||||
rescue JSON::ParserError
|
|
||||||
{}
|
|
||||||
end
|
|
||||||
|
|
||||||
values[submitter.email] = attachment.uuid
|
|
||||||
|
|
||||||
values = values.to_a.last(COOKIE_STORE_LIMIT).to_h if values.size > COOKIE_STORE_LIMIT
|
|
||||||
|
|
||||||
values.to_json
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module Api
|
|
||||||
class SubmitterEmailClicksController < ActionController::API
|
|
||||||
def create
|
|
||||||
@submitter = Submitter.find_by!(slug: params[:submitter_slug])
|
|
||||||
|
|
||||||
if params[:t] == SubmissionEvents.build_tracking_param(@submitter, 'click_email')
|
|
||||||
SubmissionEvents.create_with_tracking_data(@submitter, 'click_email', request)
|
|
||||||
end
|
|
||||||
|
|
||||||
render json: {}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -36,7 +36,7 @@ module Api
|
|||||||
render json: Submitters::SerializeForApi.call(@submitter, with_template: true, with_events: true, params:)
|
render json: Submitters::SerializeForApi.call(@submitter, with_template: true, with_events: true, params:)
|
||||||
end
|
end
|
||||||
|
|
||||||
# rubocop:disable Metrics/MethodLength
|
# rubocop:disable-next Metrics/MethodLength
|
||||||
def update
|
def update
|
||||||
submission = @submitter.submission
|
submission = @submitter.submission
|
||||||
role = submission.template_submitters.find { |e| e['uuid'] == @submitter.uuid }['name']
|
role = submission.template_submitters.find { |e| e['uuid'] == @submitter.uuid }['name']
|
||||||
@@ -84,7 +84,6 @@ module Api
|
|||||||
|
|
||||||
render json: { error: e.message }, status: :unprocessable_content
|
render json: { error: e.message }, status: :unprocessable_content
|
||||||
end
|
end
|
||||||
# rubocop:enable Metrics/MethodLength
|
|
||||||
|
|
||||||
def submitter_params
|
def submitter_params
|
||||||
submitter_params = params.key?(:submitter) ? params.require(:submitter) : params
|
submitter_params = params.key?(:submitter) ? params.require(:submitter) : params
|
||||||
@@ -179,14 +178,29 @@ module Api
|
|||||||
submitters = submitters.where(external_id: params[:external_id]) if params[:external_id].present?
|
submitters = submitters.where(external_id: params[:external_id]) if params[:external_id].present?
|
||||||
submitters = submitters.where(slug: params[:slug]) if params[:slug].present?
|
submitters = submitters.where(slug: params[:slug]) if params[:slug].present?
|
||||||
submitters = submitters.where(submission_id: params[:submission_id]) if params[:submission_id].present?
|
submitters = submitters.where(submission_id: params[:submission_id]) if params[:submission_id].present?
|
||||||
|
submitters = filter_submitters_by_template_id(submitters, params) if params[:template_id].present?
|
||||||
if params[:template_id].present?
|
|
||||||
submitters = submitters.joins(:submission).where(submissions: { template_id: params[:template_id] })
|
|
||||||
end
|
|
||||||
|
|
||||||
maybe_filter_by_completed_at(submitters, params)
|
maybe_filter_by_completed_at(submitters, params)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def filter_submitters_by_template_id(submitters, params)
|
||||||
|
return submitters.where(template_id: params[:template_id]) if params[:template_id].is_a?(Array)
|
||||||
|
|
||||||
|
template = Template.find_by(id: params[:template_id])
|
||||||
|
|
||||||
|
return submitters.none if template.nil? || !can?(:read, template)
|
||||||
|
|
||||||
|
if template.account_id == current_user.account_id &&
|
||||||
|
params[:completed_after].blank? && params[:completed_before].blank?
|
||||||
|
account_id_bind = Submitter.predicate_builder.build_bind_attribute('account_id', current_user.account_id)
|
||||||
|
|
||||||
|
submitters = submitters.unscope(where: :account_id)
|
||||||
|
.where((Submitter.arel_table[:account_id] + 0).eq(account_id_bind))
|
||||||
|
end
|
||||||
|
|
||||||
|
submitters.where(template_id: template.id)
|
||||||
|
end
|
||||||
|
|
||||||
def assign_external_id(submitter, attrs)
|
def assign_external_id(submitter, attrs)
|
||||||
submitter.external_id = attrs[:application_key] if attrs.key?(:application_key)
|
submitter.external_id = attrs[:application_key] if attrs.key?(:application_key)
|
||||||
submitter.external_id = attrs[:external_id] if attrs.key?(:external_id)
|
submitter.external_id = attrs[:external_id] if attrs.key?(:external_id)
|
||||||
|
|||||||
@@ -118,7 +118,12 @@ class ApplicationController < ActionController::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
def sign_in_for_demo
|
def sign_in_for_demo
|
||||||
sign_in(User.active.order('random()').take) unless signed_in?
|
return if signed_in?
|
||||||
|
|
||||||
|
account = Account.create!(name: "Demo #{SecureRandom.hex(3)}")
|
||||||
|
|
||||||
|
sign_in(account.users.create!(first_name: 'Demo', last_name: 'User',
|
||||||
|
email: "#{SecureRandom.hex}@docuseal.com", password: SecureRandom.hex))
|
||||||
end
|
end
|
||||||
|
|
||||||
def current_account
|
def current_account
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ module Mcp
|
|||||||
}
|
}
|
||||||
}.freeze
|
}.freeze
|
||||||
|
|
||||||
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
# rubocop:disable-next Metrics/AbcSize, Metrics/MethodLength
|
||||||
def call
|
def call
|
||||||
account = current_user.account
|
account = current_user.account
|
||||||
|
|
||||||
@@ -87,6 +87,5 @@ module Mcp
|
|||||||
edit_url: edit_template_url(@template)
|
edit_url: edit_template_url(@template)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -66,7 +66,7 @@ module Mcp
|
|||||||
}
|
}
|
||||||
}.freeze
|
}.freeze
|
||||||
|
|
||||||
# rubocop:disable Metrics
|
# rubocop:disable-next Metrics
|
||||||
def call
|
def call
|
||||||
@template = Template.accessible_by(current_ability).find(mcp_params['template_id'])
|
@template = Template.accessible_by(current_ability).find(mcp_params['template_id'])
|
||||||
|
|
||||||
@@ -115,6 +115,5 @@ module Mcp
|
|||||||
rescue Submissions::CreateFromSubmitters::BaseError => e
|
rescue Submissions::CreateFromSubmitters::BaseError => e
|
||||||
render_tool_error(e.message)
|
render_tool_error(e.message)
|
||||||
end
|
end
|
||||||
# rubocop:enable Metrics
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class PasswordsController < Devise::PasswordsController
|
class PasswordsController < Devise::PasswordsController
|
||||||
# rubocop:disable Rails/LexicallyScopedActionFilter
|
# rubocop:disable-next Rails/LexicallyScopedActionFilter
|
||||||
skip_before_action :require_no_authentication, only: %i[edit update]
|
skip_before_action :require_no_authentication, only: %i[edit update]
|
||||||
# rubocop:enable Rails/LexicallyScopedActionFilter
|
|
||||||
|
|
||||||
around_action :with_browser_locale
|
around_action :with_browser_locale
|
||||||
|
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ class PreviewDocumentPageController < ActionController::API
|
|||||||
File.open(file_path, File::RDWR | File::CREAT, 0o644) do |file|
|
File.open(file_path, File::RDWR | File::CREAT, 0o644) do |file|
|
||||||
file.flock(File::LOCK_EX)
|
file.flock(File::LOCK_EX)
|
||||||
|
|
||||||
# rubocop:disable Style/ZeroLengthPredicate
|
# rubocop:disable-next Style/ZeroLengthPredicate
|
||||||
if file.size.zero?
|
if file.size.zero?
|
||||||
cleanup_stale_tempfiles
|
cleanup_stale_tempfiles
|
||||||
|
|
||||||
@@ -63,7 +63,6 @@ class PreviewDocumentPageController < ActionController::API
|
|||||||
else
|
else
|
||||||
FileUtils.touch(file_path)
|
FileUtils.touch(file_path)
|
||||||
end
|
end
|
||||||
# rubocop:enable Style/ZeroLengthPredicate
|
|
||||||
|
|
||||||
file.flock(File::LOCK_UN)
|
file.flock(File::LOCK_UN)
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ class SendSubmissionEmailController < ApplicationController
|
|||||||
template = Template.find_by!(slug: params[:template_slug])
|
template = Template.find_by!(slug: params[:template_slug])
|
||||||
|
|
||||||
@submitter =
|
@submitter =
|
||||||
Submitter.completed.where(submission: template.submissions).find_by(email: params[:email].to_s.downcase)
|
Submitter.completed.where(template_id: template.id).find_by(email: params[:email].to_s.downcase)
|
||||||
elsif params[:submission_slug]
|
elsif params[:submission_slug]
|
||||||
submission = Submission.find_by!(slug: params[:submission_slug])
|
submission = Submission.find_by!(slug: params[:submission_slug])
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ class StartFormController < ApplicationController
|
|||||||
@submitter = find_or_initialize_submitter(@template, submitter_params)
|
@submitter = find_or_initialize_submitter(@template, submitter_params)
|
||||||
|
|
||||||
if @submitter.completed_at?
|
if @submitter.completed_at?
|
||||||
redirect_to start_form_completed_path(@template.slug, submitter_params.compact_blank)
|
redirect_to start_form_completed_path(@template.slug, submitter_params.compact_blank.merge(lang:))
|
||||||
else
|
else
|
||||||
if Templates.filter_undefined_submitters(@template.submitters).size > 1 && @submitter.new_record?
|
if Templates.filter_undefined_submitters(@template.submitters).size > 1 && @submitter.new_record?
|
||||||
@error_message = multiple_submitters_error_message
|
@error_message = multiple_submitters_error_message
|
||||||
@@ -59,7 +59,7 @@ class StartFormController < ApplicationController
|
|||||||
|
|
||||||
Submitters::StartForm.assign_start_form_cookie(@submitter, request)
|
Submitters::StartForm.assign_start_form_cookie(@submitter, request)
|
||||||
|
|
||||||
redirect_to submit_form_path(@submitter.slug)
|
redirect_to submit_form_path(@submitter.slug, lang:)
|
||||||
else
|
else
|
||||||
render :show, status: :unprocessable_content
|
render :show, status: :unprocessable_content
|
||||||
end
|
end
|
||||||
@@ -80,7 +80,7 @@ class StartFormController < ApplicationController
|
|||||||
raise ActionController::RoutingError, I18n.t('not_found') if required_params.any? { |_, v| v.blank? } ||
|
raise ActionController::RoutingError, I18n.t('not_found') if required_params.any? { |_, v| v.blank? } ||
|
||||||
required_params.except('name').compact_blank.blank?
|
required_params.except('name').compact_blank.blank?
|
||||||
|
|
||||||
@submitter = Submitter.where(submission: @template.submissions)
|
@submitter = Submitter.where(template_id: @template.id)
|
||||||
.where.not(completed_at: nil)
|
.where.not(completed_at: nil)
|
||||||
.find_by!(required_params.except('name'))
|
.find_by!(required_params.except('name'))
|
||||||
end
|
end
|
||||||
@@ -150,7 +150,7 @@ class StartFormController < ApplicationController
|
|||||||
return render :show, status: :unprocessable_content if submitter.errors.present?
|
return render :show, status: :unprocessable_content if submitter.errors.present?
|
||||||
|
|
||||||
if Submitters::StartForm.verify_2fa_and_save_submitter(submitter, request, is_new_record: submitter.new_record?)
|
if Submitters::StartForm.verify_2fa_and_save_submitter(submitter, request, is_new_record: submitter.new_record?)
|
||||||
redirect_to submit_form_path(submitter.slug)
|
redirect_to submit_form_path(submitter.slug, lang:)
|
||||||
else
|
else
|
||||||
if defined?(Rollbar) && template.preferences['shared_link_2fa'] != true
|
if defined?(Rollbar) && template.preferences['shared_link_2fa'] != true
|
||||||
Rollbar.info("2FA link requested: #{submitter.id}")
|
Rollbar.info("2FA link requested: #{submitter.id}")
|
||||||
@@ -167,10 +167,14 @@ class StartFormController < ApplicationController
|
|||||||
|
|
||||||
render :error, status: :unprocessable_content
|
render :error, status: :unprocessable_content
|
||||||
rescue Submitters::UnableToSendCode, Submitters::InvalidOtp => e
|
rescue Submitters::UnableToSendCode, Submitters::InvalidOtp => e
|
||||||
redirect_to start_form_path(template.slug, params: submitter_params.merge(email_verification: true)),
|
redirect_to start_form_path(template.slug, lang:, params: submitter_params.merge(email_verification: true)),
|
||||||
alert: e.message
|
alert: e.message
|
||||||
rescue RateLimit::LimitApproached
|
rescue RateLimit::LimitApproached
|
||||||
redirect_to start_form_path(template.slug, params: submitter_params.merge(email_verification: true)),
|
redirect_to start_form_path(template.slug, lang:, params: submitter_params.merge(email_verification: true)),
|
||||||
alert: I18n.t(:too_many_attempts)
|
alert: I18n.t(:too_many_attempts)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def lang
|
||||||
|
params[:lang].presence
|
||||||
|
end
|
||||||
end
|
end
|
||||||
@@ -16,21 +16,21 @@ class StartFormEmail2faSendController < ApplicationController
|
|||||||
.submitters.new(**submitter_params, account_id: @template.account_id)
|
.submitters.new(**submitter_params, account_id: @template.account_id)
|
||||||
|
|
||||||
if @submitter.email.blank?
|
if @submitter.email.blank?
|
||||||
return redirect_to start_form_path(@template.slug), alert: I18n.t(:provide_your_email_to_start)
|
return redirect_to start_form_path(@template.slug, lang:), alert: I18n.t(:provide_your_email_to_start)
|
||||||
end
|
end
|
||||||
|
|
||||||
Submitters.send_shared_link_email_verification_code(@submitter, request:)
|
Submitters.send_shared_link_email_verification_code(@submitter, request:)
|
||||||
|
|
||||||
redir_params = { notice: I18n.t(:code_has_been_resent) } if params[:resend]
|
redir_params = { notice: I18n.t(:code_has_been_resent) } if params[:resend]
|
||||||
|
|
||||||
redirect_to start_form_path(@template.slug, params: submitter_params.merge(email_verification: true)),
|
redirect_to start_form_path(@template.slug, lang:, params: submitter_params.merge(email_verification: true)),
|
||||||
**redir_params
|
**redir_params
|
||||||
rescue Submitters::BouncedEmail => e
|
rescue Submitters::BouncedEmail => e
|
||||||
@error_message = e.message
|
@error_message = e.message
|
||||||
|
|
||||||
render 'start_form/error', status: :unprocessable_content
|
render 'start_form/error', status: :unprocessable_content
|
||||||
rescue Submitters::UnableToSendCode => e
|
rescue Submitters::UnableToSendCode => e
|
||||||
redirect_to start_form_path(@template.slug, params: submitter_params.merge(email_verification: true)),
|
redirect_to start_form_path(@template.slug, lang:, params: submitter_params.merge(email_verification: true)),
|
||||||
alert: e.message
|
alert: e.message
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -66,4 +66,8 @@ class StartFormEmail2faSendController < ApplicationController
|
|||||||
attrs[:email] = Submissions.normalize_email(attrs[:email])
|
attrs[:email] = Submissions.normalize_email(attrs[:email])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def lang
|
||||||
|
params[:lang].presence
|
||||||
|
end
|
||||||
end
|
end
|
||||||
@@ -66,6 +66,8 @@ class SubmissionsController < ApplicationController
|
|||||||
create_submissions(@template, submissions_params, params)
|
create_submissions(@template, submissions_params, params)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
raise Submissions::CreateFromSubmitters::BaseError, 'Invalid email address' if submissions.blank?
|
||||||
|
|
||||||
WebhookUrls.enqueue_events(submissions, 'submission.created')
|
WebhookUrls.enqueue_events(submissions, 'submission.created')
|
||||||
|
|
||||||
Submissions.send_signature_requests(submissions)
|
Submissions.send_signature_requests(submissions)
|
||||||
|
|||||||
@@ -25,5 +25,7 @@ class SubmissionsExportController < ApplicationController
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def new; end
|
def new
|
||||||
|
render layout: turbo_native_android? ? 'plain' : true
|
||||||
|
end
|
||||||
end
|
end
|
||||||
@@ -14,6 +14,6 @@ class SubmissionsFiltersController < ApplicationController
|
|||||||
def show
|
def show
|
||||||
return head :not_found unless ALLOWED_NAMES.include?(params[:name])
|
return head :not_found unless ALLOWED_NAMES.include?(params[:name])
|
||||||
|
|
||||||
render params[:name]
|
render params[:name], layout: turbo_native_android? ? 'plain' : true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -20,7 +20,7 @@ class SubmitFormController < ApplicationController
|
|||||||
return render :email_2fa unless Submitters::AuthorizedForForm.pass_email_2fa?(@submitter, request)
|
return render :email_2fa unless Submitters::AuthorizedForForm.pass_email_2fa?(@submitter, request)
|
||||||
|
|
||||||
if @submitter.completed_at? || submission.completed_at?
|
if @submitter.completed_at? || submission.completed_at?
|
||||||
return redirect_to submit_form_completed_path(@submitter.slug)
|
return redirect_to submit_form_completed_path(@submitter.slug, lang:)
|
||||||
end
|
end
|
||||||
|
|
||||||
@form_configs = Submitters::FormConfigs.call(@submitter, CONFIG_KEYS)
|
@form_configs = Submitters::FormConfigs.call(@submitter, CONFIG_KEYS)
|
||||||
@@ -96,7 +96,7 @@ class SubmitFormController < ApplicationController
|
|||||||
|
|
||||||
return if Submitters::AuthorizedForForm.call(@submitter, current_user, request)
|
return if Submitters::AuthorizedForForm.call(@submitter, current_user, request)
|
||||||
|
|
||||||
redirect_to submit_form_path(params[:submit_form_slug])
|
redirect_to submit_form_path(params[:submit_form_slug], lang:)
|
||||||
end
|
end
|
||||||
|
|
||||||
def success; end
|
def success; end
|
||||||
@@ -114,7 +114,7 @@ class SubmitFormController < ApplicationController
|
|||||||
def maybe_require_link_2fa
|
def maybe_require_link_2fa
|
||||||
return if Submitters::AuthorizedForForm.pass_link_2fa?(@submitter, current_user, request)
|
return if Submitters::AuthorizedForForm.pass_link_2fa?(@submitter, current_user, request)
|
||||||
|
|
||||||
redirect_to start_form_path(@submitter.submission.template.slug)
|
redirect_to start_form_path(@submitter.submission.template.slug, lang:)
|
||||||
end
|
end
|
||||||
|
|
||||||
def maybe_render_locked_page
|
def maybe_render_locked_page
|
||||||
@@ -144,4 +144,8 @@ class SubmitFormController < ApplicationController
|
|||||||
ActiveStorage::Attachment.where(record: submission.submitters, name: :attachments)
|
ActiveStorage::Attachment.where(record: submission.submitters, name: :attachments)
|
||||||
.preload(:blob).index_by(&:uuid)
|
.preload(:blob).index_by(&:uuid)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def lang
|
||||||
|
params[:lang].presence
|
||||||
|
end
|
||||||
end
|
end
|
||||||
@@ -20,17 +20,17 @@ class SubmitFormEmail2fasController < ApplicationController
|
|||||||
{ value: @submitter.slug, expires: Submitters::StartForm::COOKIES_TTL.from_now,
|
{ value: @submitter.slug, expires: Submitters::StartForm::COOKIES_TTL.from_now,
|
||||||
**Submitters::StartForm::COOKIES_DEFAULTS }
|
**Submitters::StartForm::COOKIES_DEFAULTS }
|
||||||
|
|
||||||
redirect_to submit_form_path(@submitter.slug)
|
redirect_to submit_form_path(@submitter.slug, lang:)
|
||||||
else
|
else
|
||||||
redirect_to submit_form_path(@submitter.slug, status: :error), alert: I18n.t(:invalid_code)
|
redirect_to submit_form_path(@submitter.slug, status: :error, lang:), alert: I18n.t(:invalid_code)
|
||||||
end
|
end
|
||||||
rescue RateLimit::LimitApproached
|
rescue RateLimit::LimitApproached
|
||||||
redirect_to submit_form_path(@submitter.slug, status: :error), alert: I18n.t(:too_many_attempts)
|
redirect_to submit_form_path(@submitter.slug, status: :error, lang:), alert: I18n.t(:too_many_attempts)
|
||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
if @submitter.submission_events.where(event_type: 'send_2fa_email').exists?(created_at: 15.seconds.ago..)
|
if @submitter.submission_events.where(event_type: 'send_2fa_email').exists?(created_at: 15.seconds.ago..)
|
||||||
return redirect_to submit_form_path(@submitter.slug, status: :error), alert: I18n.t(:rate_limit_exceeded)
|
return redirect_to submit_form_path(@submitter.slug, status: :error, lang:), alert: I18n.t(:rate_limit_exceeded)
|
||||||
end
|
end
|
||||||
|
|
||||||
RateLimit.call("send-email-code-#{@submitter.id}", limit: 2, ttl: 45.seconds, enabled: true)
|
RateLimit.call("send-email-code-#{@submitter.id}", limit: 2, ttl: 45.seconds, enabled: true)
|
||||||
@@ -38,19 +38,24 @@ class SubmitFormEmail2fasController < ApplicationController
|
|||||||
if Docuseal.multitenant? && Submitters.email_bounced_recently?(@submitter.email)
|
if Docuseal.multitenant? && Submitters.email_bounced_recently?(@submitter.email)
|
||||||
Rollbar.warning("Bounced OTP email for submitter: #{@submitter.id}") if defined?(Rollbar)
|
Rollbar.warning("Bounced OTP email for submitter: #{@submitter.id}") if defined?(Rollbar)
|
||||||
|
|
||||||
return redirect_to submit_form_path(@submitter.slug, status: :error), alert: I18n.t(:verification_email_bounced)
|
return redirect_to submit_form_path(@submitter.slug, status: :error, lang:),
|
||||||
|
alert: I18n.t(:verification_email_bounced)
|
||||||
end
|
end
|
||||||
|
|
||||||
SendSubmitterVerificationEmailJob.perform_async('submitter_id' => @submitter.id, 'locale' => I18n.locale.to_s)
|
SendSubmitterVerificationEmailJob.perform_async('submitter_id' => @submitter.id, 'locale' => I18n.locale.to_s)
|
||||||
|
|
||||||
redir_params = params[:resend] ? { alert: I18n.t(:code_has_been_resent) } : {}
|
redir_params = params[:resend] ? { alert: I18n.t(:code_has_been_resent) } : {}
|
||||||
|
|
||||||
redirect_to submit_form_path(@submitter.slug, status: :sent), **redir_params
|
redirect_to submit_form_path(@submitter.slug, status: :sent, lang:), **redir_params
|
||||||
rescue RateLimit::LimitApproached
|
rescue RateLimit::LimitApproached
|
||||||
redirect_to submit_form_path(@submitter.slug, status: :error), alert: I18n.t(:too_many_attempts)
|
redirect_to submit_form_path(@submitter.slug, status: :error, lang:), alert: I18n.t(:too_many_attempts)
|
||||||
end
|
end
|
||||||
|
|
||||||
def load_submitter
|
def load_submitter
|
||||||
@submitter = Submitter.find_by!(slug: params[:submitter_slug])
|
@submitter = Submitter.find_by!(slug: params[:submitter_slug])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def lang
|
||||||
|
params[:lang].presence
|
||||||
|
end
|
||||||
end
|
end
|
||||||
@@ -34,7 +34,7 @@ class SubmitFormUploadController < ActionController::API
|
|||||||
attachment = Submitters.create_attachment!(@submitter, file, metadata:)
|
attachment = Submitters.create_attachment!(@submitter, file, metadata:)
|
||||||
|
|
||||||
if params[:remember_signature] == 'true' && @submitter.email.present?
|
if params[:remember_signature] == 'true' && @submitter.email.present?
|
||||||
cookies.encrypted[:signature_uuids] = build_new_cookie_signatures_json(@submitter, attachment)
|
cookies.permanent.encrypted[:signature_uuids] = build_new_cookie_signatures_json(@submitter, attachment)
|
||||||
end
|
end
|
||||||
|
|
||||||
render json: attachment.as_json(only: %i[uuid created_at], methods: %i[url filename content_type])
|
render json: attachment.as_json(only: %i[uuid created_at], methods: %i[url filename content_type])
|
||||||
|
|||||||
@@ -81,9 +81,26 @@ class SubmittersController < ApplicationController
|
|||||||
|
|
||||||
submitter.name = attrs[:name] if attrs.key?(:name)
|
submitter.name = attrs[:name] if attrs.key?(:name)
|
||||||
|
|
||||||
|
if submitter_changed?(submitter)
|
||||||
|
submitter.submitter_versions.new(slug: submitter.slug, email: submitter.email_was,
|
||||||
|
name: submitter.name_was, phone: submitter.phone_was)
|
||||||
|
|
||||||
|
submitter.slug = SecureRandom.base58(14)
|
||||||
|
submitter.values = Submitters.fetch_values_for_delegate(submitter)
|
||||||
|
end
|
||||||
|
|
||||||
submitter
|
submitter
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def submitter_changed?(submitter)
|
||||||
|
if submitter.email_was.blank? && submitter.phone_was.blank?
|
||||||
|
submitter.name_was.present? && submitter.name_changed?
|
||||||
|
else
|
||||||
|
(submitter.email_was.present? && submitter.email_changed?) ||
|
||||||
|
(submitter.phone_was.present? && submitter.phone_changed?)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def submitter_params
|
def submitter_params
|
||||||
params.require(:submitter).permit(:email, :name, :phone).transform_values(&:strip)
|
params.require(:submitter).permit(:email, :name, :phone).transform_values(&:strip)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -41,7 +41,9 @@ class TemplateFoldersController < ApplicationController
|
|||||||
render_infinite_scroll(@pagy, @templates, @template_folders) if turbo_infinite_scroll?
|
render_infinite_scroll(@pagy, @templates, @template_folders) if turbo_infinite_scroll?
|
||||||
end
|
end
|
||||||
|
|
||||||
def edit; end
|
def edit
|
||||||
|
render layout: turbo_native_android? ? 'plain' : true
|
||||||
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
if @template_folder != current_account.default_template_folder &&
|
if @template_folder != current_account.default_template_folder &&
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ class TemplateSharingsTestingController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
testing_account = Accounts.find_or_create_testing_user(true_user.account).account
|
account = can?(:manage, :tenants_test) ? current_account : true_user.account
|
||||||
|
|
||||||
|
testing_account = Accounts.find_or_create_testing_user(account).account
|
||||||
|
|
||||||
if params[:value] == '1'
|
if params[:value] == '1'
|
||||||
TemplateSharing.create!(ability: :manage, account: testing_account, template: @template)
|
TemplateSharing.create!(ability: :manage, account: testing_account, template: @template)
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ class TemplatesCloneController < ApplicationController
|
|||||||
authorize!(:create, Template)
|
authorize!(:create, Template)
|
||||||
|
|
||||||
@template = Template.new(name: "#{@base_template.name} (#{I18n.t('clone')})")
|
@template = Template.new(name: "#{@base_template.name} (#{I18n.t('clone')})")
|
||||||
|
|
||||||
|
render layout: turbo_native_android? ? 'plain' : true
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
|
|||||||
@@ -3,7 +3,9 @@
|
|||||||
class TemplatesFoldersController < ApplicationController
|
class TemplatesFoldersController < ApplicationController
|
||||||
load_and_authorize_resource :template
|
load_and_authorize_resource :template
|
||||||
|
|
||||||
def edit; end
|
def edit
|
||||||
|
render layout: turbo_native_android? ? 'plain' : true
|
||||||
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
authorize!(:update, @template)
|
authorize!(:update, @template)
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ class UserInitialsController < ApplicationController
|
|||||||
before_action :load_user_config
|
before_action :load_user_config
|
||||||
authorize_resource :user_config
|
authorize_resource :user_config
|
||||||
|
|
||||||
def edit; end
|
def edit
|
||||||
|
render layout: turbo_native_android? ? 'plain' : true
|
||||||
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
file = params[:file]
|
file = params[:file]
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ class UserSignaturesController < ApplicationController
|
|||||||
before_action :load_user_config
|
before_action :load_user_config
|
||||||
authorize_resource :user_config
|
authorize_resource :user_config
|
||||||
|
|
||||||
def edit; end
|
def edit
|
||||||
|
render layout: turbo_native_android? ? 'plain' : true
|
||||||
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
file = params[:file]
|
file = params[:file]
|
||||||
|
|||||||
@@ -31,9 +31,13 @@ class UsersController < ApplicationController
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def new; end
|
def new
|
||||||
|
render layout: turbo_native_android? ? 'plain' : true
|
||||||
|
end
|
||||||
|
|
||||||
def edit; end
|
def edit
|
||||||
|
render layout: turbo_native_android? ? 'plain' : true
|
||||||
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
existing_user = User.accessible_by(current_ability).find_by(email: @user.email)
|
existing_user = User.accessible_by(current_ability).find_by(email: @user.email)
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ class WebhookSettingsController < ApplicationController
|
|||||||
|
|
||||||
SendTestWebhookRequestJob.perform_async(
|
SendTestWebhookRequestJob.perform_async(
|
||||||
'submitter_id' => submitter.id,
|
'submitter_id' => submitter.id,
|
||||||
'event_uuid' => SecureRandom.uuid,
|
'event_uuid' => SecureRandom.uuid_v7,
|
||||||
'webhook_url_id' => @webhook_url.id
|
'webhook_url_id' => @webhook_url.id
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,9 @@ import PromptPassword from './elements/prompt_password'
|
|||||||
import EmailsTextarea from './elements/emails_textarea'
|
import EmailsTextarea from './elements/emails_textarea'
|
||||||
import ToggleSubmit from './elements/toggle_submit'
|
import ToggleSubmit from './elements/toggle_submit'
|
||||||
import ToggleOnSubmit from './elements/toggle_on_submit'
|
import ToggleOnSubmit from './elements/toggle_on_submit'
|
||||||
|
import ToggleOnChecked from './elements/toggle_on_checked'
|
||||||
|
import ToggleSendTitle from './elements/toggle_send_title'
|
||||||
|
import AppendTemplate from './elements/append_template'
|
||||||
import CheckOnClick from './elements/check_on_click'
|
import CheckOnClick from './elements/check_on_click'
|
||||||
import PasswordInput from './elements/password_input'
|
import PasswordInput from './elements/password_input'
|
||||||
import SearchInput from './elements/search_input'
|
import SearchInput from './elements/search_input'
|
||||||
@@ -159,6 +162,9 @@ safeRegisterElement('emails-textarea', EmailsTextarea)
|
|||||||
safeRegisterElement('toggle-cookies', ToggleCookies)
|
safeRegisterElement('toggle-cookies', ToggleCookies)
|
||||||
safeRegisterElement('toggle-submit', ToggleSubmit)
|
safeRegisterElement('toggle-submit', ToggleSubmit)
|
||||||
safeRegisterElement('toggle-on-submit', ToggleOnSubmit)
|
safeRegisterElement('toggle-on-submit', ToggleOnSubmit)
|
||||||
|
safeRegisterElement('toggle-on-checked', ToggleOnChecked)
|
||||||
|
safeRegisterElement('toggle-send-title', ToggleSendTitle)
|
||||||
|
safeRegisterElement('append-template', AppendTemplate)
|
||||||
safeRegisterElement('password-input', PasswordInput)
|
safeRegisterElement('password-input', PasswordInput)
|
||||||
safeRegisterElement('search-input', SearchInput)
|
safeRegisterElement('search-input', SearchInput)
|
||||||
safeRegisterElement('toggle-attribute', ToggleAttribute)
|
safeRegisterElement('toggle-attribute', ToggleAttribute)
|
||||||
@@ -222,6 +228,7 @@ safeRegisterElement('template-builder', class extends HTMLElement {
|
|||||||
template,
|
template,
|
||||||
customFields: reactive(JSON.parse(this.dataset.customFields || '[]')),
|
customFields: reactive(JSON.parse(this.dataset.customFields || '[]')),
|
||||||
dateFormats: JSON.parse(this.dataset.dateFormats || '[]'),
|
dateFormats: JSON.parse(this.dataset.dateFormats || '[]'),
|
||||||
|
autosave: this.dataset.autosave !== 'false',
|
||||||
dynamicDocuments: reactive(JSON.parse(this.dataset.dynamicDocuments || '[]')),
|
dynamicDocuments: reactive(JSON.parse(this.dataset.dynamicDocuments || '[]')),
|
||||||
backgroundColor: '#faf7f5',
|
backgroundColor: '#faf7f5',
|
||||||
locale: this.dataset.locale,
|
locale: this.dataset.locale,
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
export default class extends HTMLElement {
|
||||||
|
connectedCallback () {
|
||||||
|
this.addEventListener('click', this.onClick)
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectedCallback () {
|
||||||
|
this.removeEventListener('click', this.onClick)
|
||||||
|
}
|
||||||
|
|
||||||
|
onClick = (e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
const target = document.getElementById(this.dataset.targetId)
|
||||||
|
const item = this.querySelector('template').content.firstElementChild.cloneNode(true)
|
||||||
|
|
||||||
|
item.querySelector('a').addEventListener('click', (e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
item.remove()
|
||||||
|
|
||||||
|
target.classList.toggle('hidden', !target.childElementCount)
|
||||||
|
})
|
||||||
|
|
||||||
|
target.append(item)
|
||||||
|
target.classList.remove('hidden')
|
||||||
|
|
||||||
|
item.querySelector('input').focus()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ import { actionable } from '@github/catalyst/lib/actionable'
|
|||||||
import { targets, targetable } from '@github/catalyst/lib/targetable'
|
import { targets, targetable } from '@github/catalyst/lib/targetable'
|
||||||
|
|
||||||
export default actionable(targetable(class extends HTMLElement {
|
export default actionable(targetable(class extends HTMLElement {
|
||||||
static [targets.static] = ['items']
|
static [targets.static] = ['items', 'groups']
|
||||||
|
|
||||||
addItem (e) {
|
addItem (e) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
@@ -24,11 +24,28 @@ export default actionable(targetable(class extends HTMLElement {
|
|||||||
duplicateItem.querySelectorAll('a.hidden').forEach((button) => button.classList.toggle('hidden'))
|
duplicateItem.querySelectorAll('a.hidden').forEach((button) => button.classList.toggle('hidden'))
|
||||||
|
|
||||||
originalItem.parentNode.append(duplicateItem)
|
originalItem.parentNode.append(duplicateItem)
|
||||||
|
|
||||||
|
this.toggleMultipleClasses()
|
||||||
|
|
||||||
|
duplicateItem.querySelector("input:not([type='hidden'])")?.focus()
|
||||||
}
|
}
|
||||||
|
|
||||||
removeItem (e) {
|
removeItem (e) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
this.items.find((item) => item.contains(e.target))?.remove()
|
this.items.find((item) => item.contains(e.target))?.remove()
|
||||||
|
|
||||||
|
this.toggleMultipleClasses()
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleMultipleClasses () {
|
||||||
|
const isMultiple = this.items.length > 1
|
||||||
|
const elements = [...this.items, ...this.groups]
|
||||||
|
|
||||||
|
elements.forEach((element) => {
|
||||||
|
const classes = element.dataset.multipleClasses || this.dataset.multipleClasses
|
||||||
|
|
||||||
|
classes?.split(' ').forEach((cls) => element.classList.toggle(cls, isMultiple))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
@@ -22,7 +22,7 @@ export default class extends HTMLElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async flashFrom (fetchResponse) {
|
async flashFrom (fetchResponse) {
|
||||||
const html = await fetchResponse?.responseHTML
|
const html = await fetchResponse?.responseHTML.catch(() => null)
|
||||||
const flash = html && new DOMParser().parseFromString(html, 'text/html').getElementById('native_flash')
|
const flash = html && new DOMParser().parseFromString(html, 'text/html').getElementById('native_flash')
|
||||||
|
|
||||||
if (flash?.dataset.alert) return { alert: flash.dataset.alert }
|
if (flash?.dataset.alert) return { alert: flash.dataset.alert }
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ export default targetable(class extends HTMLElement {
|
|||||||
|
|
||||||
this.input.files = dataTransfer.files
|
this.input.files = dataTransfer.files
|
||||||
|
|
||||||
if (this.input.webkitEntries.length) {
|
if (this.input.webkitEntries?.length) {
|
||||||
this.input.dataset.file = `${dataTransfer.files[0].name}`
|
this.input.dataset.file = `${dataTransfer.files[0].name}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
export default class extends HTMLElement {
|
||||||
|
connectedCallback () {
|
||||||
|
this.form.addEventListener('change', this.toggle)
|
||||||
|
|
||||||
|
this.toggle()
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectedCallback () {
|
||||||
|
this.form.removeEventListener('change', this.toggle)
|
||||||
|
}
|
||||||
|
|
||||||
|
toggle = () => {
|
||||||
|
const selector = this.dataset.names.split(' ').map((name) => `[name="${name}"]:checked:enabled`).join(', ')
|
||||||
|
const isChecked = !!this.form.querySelector(selector)
|
||||||
|
|
||||||
|
this.firstElementChild.classList.toggle('hidden', !isChecked)
|
||||||
|
this.lastElementChild.classList.toggle('hidden', isChecked)
|
||||||
|
}
|
||||||
|
|
||||||
|
get form () {
|
||||||
|
return this.closest('form')
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
export default class extends HTMLElement {
|
||||||
|
connectedCallback () {
|
||||||
|
this.events.forEach((event) => this.form.addEventListener(event, this.toggle))
|
||||||
|
|
||||||
|
this.toggle()
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectedCallback () {
|
||||||
|
this.events.forEach((event) => this.form.removeEventListener(event, this.toggle))
|
||||||
|
}
|
||||||
|
|
||||||
|
toggle = (event) => {
|
||||||
|
if (event?.type === 'input' && event.target.matches(this.fieldSelector('name'))) return
|
||||||
|
|
||||||
|
if (event?.type === 'focusin' && !this.isKeepSendUsed && this.isFocusFromName(event)) {
|
||||||
|
this.isKeepSendUsed = true
|
||||||
|
this.keepSendInput = event.target
|
||||||
|
} else if (event?.type === 'focusout' && event.target === this.keepSendInput) {
|
||||||
|
this.keepSendInput = null
|
||||||
|
}
|
||||||
|
|
||||||
|
const isNameOnly = (field) => this.isFilled('name') && !this.isFilled(field) && !this.keepSendInput?.matches(this.fieldSelector(field))
|
||||||
|
|
||||||
|
const isSend = (this.isChecked('send_email') && !isNameOnly('email')) ||
|
||||||
|
(this.isChecked('send_sms') && !isNameOnly('phone'))
|
||||||
|
|
||||||
|
this.firstElementChild.classList.toggle('hidden', !isSend)
|
||||||
|
this.lastElementChild.classList.toggle('hidden', isSend)
|
||||||
|
}
|
||||||
|
|
||||||
|
isChecked (name) {
|
||||||
|
return !!this.form.querySelector(`[name="${name}"]:checked:enabled`)
|
||||||
|
}
|
||||||
|
|
||||||
|
isFilled (field) {
|
||||||
|
return [...this.form.querySelectorAll(this.fieldSelector(field))].some((input) => input.value.trim())
|
||||||
|
}
|
||||||
|
|
||||||
|
isFocusFromName ({ target, relatedTarget }) {
|
||||||
|
return !!relatedTarget?.matches(this.fieldSelector('name')) && !!relatedTarget.value.trim() &&
|
||||||
|
['email', 'phone'].some((field) => target.matches(this.fieldSelector(field)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldSelector (field) {
|
||||||
|
return `[name$="[][${field}]"]`
|
||||||
|
}
|
||||||
|
|
||||||
|
get events () {
|
||||||
|
return ['change', 'input', 'linked-input.update', 'focusin', 'focusout']
|
||||||
|
}
|
||||||
|
|
||||||
|
get form () {
|
||||||
|
return this.closest('form')
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,6 +24,9 @@ const ICONS = {
|
|||||||
user: 'person',
|
user: 'person',
|
||||||
users: 'person.2',
|
users: 'person.2',
|
||||||
users_plus: 'person.badge.plus',
|
users_plus: 'person.badge.plus',
|
||||||
|
user_plus: 'person.badge.plus',
|
||||||
|
eye_plus: 'eye',
|
||||||
|
mail_plus: 'envelope',
|
||||||
photo: 'photo',
|
photo: 'photo',
|
||||||
settings: 'gearshape',
|
settings: 'gearshape',
|
||||||
logout: 'rectangle.portrait.and.arrow.right',
|
logout: 'rectangle.portrait.and.arrow.right',
|
||||||
|
|||||||
@@ -3,11 +3,19 @@
|
|||||||
v-if="!error"
|
v-if="!error"
|
||||||
class="label text-xl sm:text-2xl py-0 mb-2 sm:mb-3.5 field-name-label"
|
class="label text-xl sm:text-2xl py-0 mb-2 sm:mb-3.5 field-name-label"
|
||||||
>
|
>
|
||||||
<MarkdownContent
|
<span class="flex items-end">
|
||||||
v-if="field.title"
|
<MarkdownContent
|
||||||
:string="field.title"
|
v-if="field.title"
|
||||||
/>
|
:string="field.title"
|
||||||
<template v-else>{{ field.name || 'Knowledge Based Authentication' }}</template>
|
/>
|
||||||
|
<template v-else>{{ field.name || 'Knowledge Based Authentication' }}</template>
|
||||||
|
<span
|
||||||
|
class="tooltip tooltip-bottom ml-2 mb-1"
|
||||||
|
data-tip="To verify your identity, the details you enter are checked against third-party records, which may include credit bureau data, and used to generate questions based on those records. Only these identity details are shared with the identity verification provider; this document and your tax return information are not shared with it or with any credit reporting company. If the check is recorded on your credit report, it appears as a soft inquiry, which does not affect your credit score and is not shown to lenders. This check does not give the sender of this document or the IRS access to your credit report."
|
||||||
|
>
|
||||||
|
<IconInfoCircle class="w-4 h-4 text-neutral-500" />
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
<span
|
<span
|
||||||
v-if="questions"
|
v-if="questions"
|
||||||
class="float-right font-normal text-neutral-500 mt-1 whitespace-nowrap text-right text-sm"
|
class="float-right font-normal text-neutral-500 mt-1 whitespace-nowrap text-right text-sm"
|
||||||
@@ -282,13 +290,14 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import MarkdownContent from './markdown_content'
|
import MarkdownContent from './markdown_content'
|
||||||
import { IconInnerShadowTop } from '@tabler/icons-vue'
|
import { IconInnerShadowTop, IconInfoCircle } from '@tabler/icons-vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'KbaStep',
|
name: 'KbaStep',
|
||||||
components: {
|
components: {
|
||||||
MarkdownContent,
|
MarkdownContent,
|
||||||
IconInnerShadowTop
|
IconInnerShadowTop,
|
||||||
|
IconInfoCircle
|
||||||
},
|
},
|
||||||
inject: ['baseUrl', 't'],
|
inject: ['baseUrl', 't'],
|
||||||
props: {
|
props: {
|
||||||
|
|||||||
@@ -319,7 +319,7 @@ export default {
|
|||||||
default: false
|
default: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits: ['start-resize', 'stop-resize', 'start-drag', 'stop-drag', 'remove', 'scroll-to', 'add-custom-field', 'click-title', 'multi-select'],
|
emits: ['start-resize', 'stop-resize', 'start-drag', 'stop-drag', 'remove', 'scroll-to', 'add-custom-field', 'click-title'],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
isContenteditable: false,
|
isContenteditable: false,
|
||||||
@@ -798,8 +798,6 @@ export default {
|
|||||||
this.selectedAreasRef.value.splice(this.selectedAreasRef.value.indexOf(this.area), 1)
|
this.selectedAreasRef.value.splice(this.selectedAreasRef.value.indexOf(this.area), 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$emit('multi-select', e)
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,10 @@
|
|||||||
ref="dragContainer"
|
ref="dragContainer"
|
||||||
style="max-width: 1600px"
|
style="max-width: 1600px"
|
||||||
:class="nativeView ? (isSheetView ? 'bg-base-100' : 'px-4') : 'mx-auto pl-3 md:pl-4 h-full'"
|
:class="nativeView ? (isSheetView ? 'bg-base-100' : 'px-4') : 'mx-auto pl-3 md:pl-4 h-full'"
|
||||||
|
@dragstart="isInternalDrag = true"
|
||||||
|
@dragend="isInternalDrag = false"
|
||||||
@dragover="onDragover"
|
@dragover="onDragover"
|
||||||
@drop="isDragFile = false"
|
@drop="[isDragFile = false, isInternalDrag = false]"
|
||||||
>
|
>
|
||||||
<HoverDropzone
|
<HoverDropzone
|
||||||
v-if="sortedDocuments.length && withUploadButton && editable && !isSheetView"
|
v-if="sortedDocuments.length && withUploadButton && editable && !isSheetView"
|
||||||
@@ -413,9 +415,9 @@
|
|||||||
<a
|
<a
|
||||||
:href="`/templates/${template.id}/form`"
|
:href="`/templates/${template.id}/form`"
|
||||||
:data-native-actions="template.submitters.length > 1 ? 'users' : null"
|
:data-native-actions="template.submitters.length > 1 ? 'users' : null"
|
||||||
data-turbo="false"
|
data-turbo-prefetch="false"
|
||||||
class="flex items-center justify-center space-x-2"
|
class="flex items-center justify-center space-x-2"
|
||||||
@click.exact="isPreviewLoading = true"
|
@click.exact="onSaveAndPreviewClick"
|
||||||
>
|
>
|
||||||
<IconInnerShadowTop
|
<IconInnerShadowTop
|
||||||
v-if="isPreviewLoading"
|
v-if="isPreviewLoading"
|
||||||
@@ -638,6 +640,7 @@
|
|||||||
:draw-field-type="drawFieldType"
|
:draw-field-type="drawFieldType"
|
||||||
:draw-custom-field="drawCustomField"
|
:draw-custom-field="drawCustomField"
|
||||||
:draw-option="drawOption"
|
:draw-option="drawOption"
|
||||||
|
:autosave="autosave"
|
||||||
@update="onDynamicDocumentUpdate"
|
@update="onDynamicDocumentUpdate"
|
||||||
@draw="clearDrawField"
|
@draw="clearDrawField"
|
||||||
@add-custom-field="addCustomField"
|
@add-custom-field="addCustomField"
|
||||||
@@ -769,7 +772,7 @@
|
|||||||
class="sticky bottom-0 z-20"
|
class="sticky bottom-0 z-20"
|
||||||
>
|
>
|
||||||
<native-action
|
<native-action
|
||||||
v-if="nativePlatform && withDocumentsList && (isBreakpointLg || isMobile) && sortedDocuments.length && editable"
|
v-if="nativePlatform && withDocumentsList && (isBreakpointLg || isMobile) && editable"
|
||||||
data-placement="drawer"
|
data-placement="drawer"
|
||||||
data-side="leading"
|
data-side="leading"
|
||||||
data-icon="files"
|
data-icon="files"
|
||||||
@@ -779,7 +782,7 @@
|
|||||||
data-view="documents"
|
data-view="documents"
|
||||||
/>
|
/>
|
||||||
<native-action
|
<native-action
|
||||||
v-if="nativePlatform && withFieldsList && isBreakpointLg && sortedDocuments.length && editable"
|
v-if="nativePlatform && withFieldsList && isBreakpointLg && editable"
|
||||||
data-placement="drawer"
|
data-placement="drawer"
|
||||||
data-side="trailing"
|
data-side="trailing"
|
||||||
data-icon="plus"
|
data-icon="plus"
|
||||||
@@ -1377,6 +1380,7 @@ export default {
|
|||||||
isPreviewLoading: false,
|
isPreviewLoading: false,
|
||||||
isLoadingBlankPage: false,
|
isLoadingBlankPage: false,
|
||||||
isSaving: false,
|
isSaving: false,
|
||||||
|
hasUnsavedChanges: false,
|
||||||
isDetectingPageFields: false,
|
isDetectingPageFields: false,
|
||||||
detectFieldsQueue: [],
|
detectFieldsQueue: [],
|
||||||
detectingAnalyzingProgress: null,
|
detectingAnalyzingProgress: null,
|
||||||
@@ -1390,6 +1394,7 @@ export default {
|
|||||||
drawOption: null,
|
drawOption: null,
|
||||||
dragField: null,
|
dragField: null,
|
||||||
isDragFile: false,
|
isDragFile: false,
|
||||||
|
isInternalDrag: false,
|
||||||
isMathLoaded: false,
|
isMathLoaded: false,
|
||||||
isRevisionsModalOpen: false,
|
isRevisionsModalOpen: false,
|
||||||
isDocumentsDrawerOpen: false,
|
isDocumentsDrawerOpen: false,
|
||||||
@@ -1659,6 +1664,11 @@ export default {
|
|||||||
|
|
||||||
window.addEventListener('dragleave', this.onWindowDragLeave)
|
window.addEventListener('dragleave', this.onWindowDragLeave)
|
||||||
|
|
||||||
|
if (!this.autosave) {
|
||||||
|
window.addEventListener('beforeunload', this.onBeforeUnload)
|
||||||
|
document.addEventListener('turbo:before-visit', this.onBeforeVisit)
|
||||||
|
}
|
||||||
|
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
if (document.location.search?.includes('stripe_connect_success')) {
|
if (document.location.search?.includes('stripe_connect_success')) {
|
||||||
document.querySelector('form[action="/auth/stripe_connect"]')?.closest('.dropdown')?.querySelector('label')?.focus()
|
document.querySelector('form[action="/auth/stripe_connect"]')?.closest('.dropdown')?.querySelector('label')?.focus()
|
||||||
@@ -1682,6 +1692,11 @@ export default {
|
|||||||
|
|
||||||
this.resizeObserver.disconnect()
|
this.resizeObserver.disconnect()
|
||||||
window.removeEventListener('dragleave', this.onWindowDragLeave)
|
window.removeEventListener('dragleave', this.onWindowDragLeave)
|
||||||
|
|
||||||
|
if (!this.autosave) {
|
||||||
|
window.removeEventListener('beforeunload', this.onBeforeUnload)
|
||||||
|
document.removeEventListener('turbo:before-visit', this.onBeforeVisit)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
beforeUpdate () {
|
beforeUpdate () {
|
||||||
this.documentRefs = []
|
this.documentRefs = []
|
||||||
@@ -2039,8 +2054,18 @@ export default {
|
|||||||
|
|
||||||
ref.x = e.clientX - ref.offsetX
|
ref.x = e.clientX - ref.offsetX
|
||||||
ref.y = e.clientY - ref.offsetY
|
ref.y = e.clientY - ref.offsetY
|
||||||
} else if (e.dataTransfer?.types?.includes('Files') && !this.editModalDocumentUuid) {
|
} else if (e.dataTransfer?.types?.includes('Files') && !this.editModalDocumentUuid && !this.isInternalDrag) {
|
||||||
this.isDragFile = true
|
const items = Array.from(e.dataTransfer.items)
|
||||||
|
|
||||||
|
if (this.dynamicDocuments.length && items.length && items.every((item) => item.type.startsWith('image/'))) {
|
||||||
|
if (!e.defaultPrevented) {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
e.dataTransfer.dropEffect = 'none'
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.isDragFile = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onWindowDragLeave (event) {
|
onWindowDragLeave (event) {
|
||||||
@@ -2228,6 +2253,37 @@ export default {
|
|||||||
this.insertField(field)
|
this.insertField(field)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onSaveAndPreviewClick (event) {
|
||||||
|
this.isPreviewLoading = true
|
||||||
|
|
||||||
|
if (!this.autosave) {
|
||||||
|
event.preventDefault()
|
||||||
|
|
||||||
|
const dynamicDocumentRefs = this.documentRefs.filter((ref) => ref.isDynamic)
|
||||||
|
|
||||||
|
dynamicDocumentRefs.forEach((ref) => ref.update())
|
||||||
|
|
||||||
|
this.rebuildVariablesSchema({ disable: false })
|
||||||
|
|
||||||
|
Promise.all([this.save({ force: true }), ...dynamicDocumentRefs.map((ref) => ref.saveBody())]).then(() => {
|
||||||
|
window.Turbo.visit(`/templates/${this.template.id}/form`)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onBeforeUnload (event) {
|
||||||
|
if (this.hasUnsavedChanges) {
|
||||||
|
event.preventDefault()
|
||||||
|
|
||||||
|
event.returnValue = ''
|
||||||
|
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onBeforeVisit (event) {
|
||||||
|
if (this.hasUnsavedChanges && !window.confirm(this.t('close_without_saving_changes'))) {
|
||||||
|
event.preventDefault()
|
||||||
|
}
|
||||||
|
},
|
||||||
closeDropdown () {
|
closeDropdown () {
|
||||||
document.activeElement.blur()
|
document.activeElement.blur()
|
||||||
},
|
},
|
||||||
@@ -4023,6 +4079,8 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!this.autosave && !force) {
|
if (!this.autosave && !force) {
|
||||||
|
this.hasUnsavedChanges = true
|
||||||
|
|
||||||
return Promise.resolve({})
|
return Promise.resolve({})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4045,6 +4103,8 @@ export default {
|
|||||||
}),
|
}),
|
||||||
headers: { 'Content-Type': 'application/json' }
|
headers: { 'Content-Type': 'application/json' }
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
|
this.hasUnsavedChanges = false
|
||||||
|
|
||||||
if (this.onSave) {
|
if (this.onSave) {
|
||||||
this.onSave(this.template)
|
this.onSave(this.template)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
class="absolute top-0 bottom-0 right-0 left-0"
|
class="absolute top-0 bottom-0 right-0 left-0"
|
||||||
@click.prevent="$emit('close')"
|
@click.prevent="$emit('close')"
|
||||||
/>
|
/>
|
||||||
<div :class="inline ? 'px-4 pt-3 pb-6' : 'modal-box pt-4 pb-6 px-6 mt-20 w-full max-w-xl'">
|
<div :class="inline ? (nativePlatform === 'android' ? 'px-4 pt-1.5 pb-6' : 'px-4 pt-3 pb-6') : 'modal-box pt-4 pb-6 px-6 mt-20 w-full max-w-xl'">
|
||||||
<div
|
<div
|
||||||
v-if="!inline"
|
v-if="!inline"
|
||||||
class="flex justify-between items-center border-b pb-2 mb-2 font-medium"
|
class="flex justify-between items-center border-b pb-2 mb-2 font-medium"
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
>{{ t('available_in_pro') }}</a>
|
>{{ t('available_in_pro') }}</a>
|
||||||
</div>
|
</div>
|
||||||
<form @submit.prevent="validateSaveAndClose">
|
<form @submit.prevent="validateSaveAndClose">
|
||||||
<div class="my-4">
|
<div :class="inline && nativePlatform === 'android' ? 'mt-1 mb-4' : 'my-4'">
|
||||||
<div
|
<div
|
||||||
v-for="(condition, cindex) in conditions"
|
v-for="(condition, cindex) in conditions"
|
||||||
:key="cindex"
|
:key="cindex"
|
||||||
@@ -168,7 +168,7 @@
|
|||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: 'ConditionModal',
|
name: 'ConditionModal',
|
||||||
inject: ['t', 'template', 'withConditions'],
|
inject: ['t', 'template', 'withConditions', 'nativePlatform'],
|
||||||
props: {
|
props: {
|
||||||
item: {
|
item: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
class="absolute top-0 bottom-0 right-0 left-0"
|
class="absolute top-0 bottom-0 right-0 left-0"
|
||||||
@click.prevent="$emit('close')"
|
@click.prevent="$emit('close')"
|
||||||
/>
|
/>
|
||||||
<div :class="inline ? 'px-4 pt-3 pb-6' : 'modal-box pt-4 pb-6 px-6 mt-20 max-h-none w-full max-w-xl'">
|
<div :class="inline ? (nativePlatform === 'android' ? 'px-4 pt-1.5 pb-6' : 'px-4 pt-3 pb-6') : 'modal-box pt-4 pb-6 px-6 mt-20 max-h-none w-full max-w-xl'">
|
||||||
<div
|
<div
|
||||||
v-if="!inline"
|
v-if="!inline"
|
||||||
class="flex justify-between items-center border-b pb-2 mb-2 font-medium"
|
class="flex justify-between items-center border-b pb-2 mb-2 font-medium"
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<label
|
<label
|
||||||
dir="auto"
|
dir="auto"
|
||||||
class="label text-sm"
|
:class="['label text-sm', { 'pt-0 mt-1': inline && nativePlatform === 'android' }]"
|
||||||
for="description_field"
|
for="description_field"
|
||||||
>
|
>
|
||||||
{{ t('description') }}
|
{{ t('description') }}
|
||||||
@@ -71,7 +71,7 @@
|
|||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: 'DescriptionModal',
|
name: 'DescriptionModal',
|
||||||
inject: ['t', 'template'],
|
inject: ['t', 'template', 'nativePlatform'],
|
||||||
props: {
|
props: {
|
||||||
field: {
|
field: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
<CropView
|
<CropView
|
||||||
v-if="cropPageItem"
|
v-if="cropPageItem"
|
||||||
class="absolute inset-0 z-10 bg-base-100"
|
class="absolute inset-0 z-10 bg-base-100"
|
||||||
:style="inline ? { paddingTop: '70px' } : null"
|
:style="inline && nativePlatform === 'ios' ? { paddingTop: '70px' } : null"
|
||||||
:template-id="template.id"
|
:template-id="template.id"
|
||||||
:page="cropPageItem"
|
:page="cropPageItem"
|
||||||
:image-url="thumbUrl(cropPageItem)"
|
:image-url="thumbUrl(cropPageItem)"
|
||||||
@@ -47,7 +47,7 @@
|
|||||||
<RedactView
|
<RedactView
|
||||||
v-else-if="redactPageItem"
|
v-else-if="redactPageItem"
|
||||||
class="absolute inset-0 z-10 bg-base-100"
|
class="absolute inset-0 z-10 bg-base-100"
|
||||||
:style="inline ? { paddingTop: '70px' } : null"
|
:style="inline && nativePlatform === 'ios' ? { paddingTop: '70px' } : null"
|
||||||
:template-id="template.id"
|
:template-id="template.id"
|
||||||
:page="redactPageItem"
|
:page="redactPageItem"
|
||||||
:image-url="thumbUrl(redactPageItem)"
|
:image-url="thumbUrl(redactPageItem)"
|
||||||
@@ -63,14 +63,14 @@
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="flex-1 overflow-y-auto overflow-x-hidden px-4 md:px-6 py-4 space-y-4"
|
class="flex-1 overflow-y-auto overflow-x-hidden px-4 md:px-6 py-4 space-y-4"
|
||||||
:style="inline ? { paddingTop: '86px' } : null"
|
:style="inline && nativePlatform === 'ios' ? { paddingTop: '86px' } : null"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-for="(doc, docIndex) in layout"
|
v-for="(doc, docIndex) in layout"
|
||||||
:key="doc.attachmentUuid"
|
:key="doc.attachmentUuid"
|
||||||
:data-uuid="doc.attachmentUuid"
|
:data-uuid="doc.attachmentUuid"
|
||||||
class="relative"
|
class="relative"
|
||||||
:style="inline ? { scrollMarginTop: '86px' } : null"
|
:style="inline && nativePlatform === 'ios' ? { scrollMarginTop: '86px' } : null"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="flex items-center pb-2"
|
class="flex items-center pb-2"
|
||||||
@@ -191,7 +191,7 @@
|
|||||||
:key="page.id"
|
:key="page.id"
|
||||||
:data-page-id="page.id"
|
:data-page-id="page.id"
|
||||||
class="relative min-w-0 cursor-pointer group"
|
class="relative min-w-0 cursor-pointer group"
|
||||||
:style="inline ? { scrollMarginTop: '86px' } : null"
|
:style="inline && nativePlatform === 'ios' ? { scrollMarginTop: '86px' } : null"
|
||||||
draggable="true"
|
draggable="true"
|
||||||
@click="selectedPageId = page.id"
|
@click="selectedPageId = page.id"
|
||||||
@mousedown="onPageMousedown(page, $event)"
|
@mousedown="onPageMousedown(page, $event)"
|
||||||
@@ -260,7 +260,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="flex-none px-4 border-t pt-3 flex flex-col gap-3 md:w-56 md:border-t-0 md:border-l md:py-4 md:gap-0"
|
class="flex-none px-4 border-t pt-3 flex flex-col gap-3 md:w-56 md:border-t-0 md:border-l md:py-4 md:gap-0"
|
||||||
:class="{ 'pb-3': !inline }"
|
:class="{ 'pb-3': !inline || nativePlatform === 'android' }"
|
||||||
>
|
>
|
||||||
<div class="grid grid-cols-4 gap-2 md:block md:space-y-2">
|
<div class="grid grid-cols-4 gap-2 md:block md:space-y-2">
|
||||||
<button
|
<button
|
||||||
@@ -419,7 +419,7 @@ export default {
|
|||||||
IconEraser,
|
IconEraser,
|
||||||
IconCrop
|
IconCrop
|
||||||
},
|
},
|
||||||
inject: ['t', 'baseFetch'],
|
inject: ['t', 'baseFetch', 'nativePlatform'],
|
||||||
props: {
|
props: {
|
||||||
template: {
|
template: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
|||||||
@@ -84,6 +84,11 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: true
|
default: true
|
||||||
},
|
},
|
||||||
|
autosave: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
selectedSubmitter: {
|
selectedSubmitter: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -279,7 +284,7 @@ export default {
|
|||||||
|
|
||||||
this.updatedSectionEditors.set(section.id, editor)
|
this.updatedSectionEditors.set(section.id, editor)
|
||||||
|
|
||||||
this.saveTimer = setTimeout(() => this.updateSectionsAndSave(), 1000)
|
this.saveTimer = setTimeout(() => this.autosave ? this.updateSectionsAndSave() : this.updateSections(), 1000)
|
||||||
},
|
},
|
||||||
loadListsStyle () {
|
loadListsStyle () {
|
||||||
this.listsStyle = this.bodyDom.querySelector('body > style[data-lists]')?.textContent || null
|
this.listsStyle = this.bodyDom.querySelector('body > style[data-lists]')?.textContent || null
|
||||||
@@ -299,7 +304,9 @@ export default {
|
|||||||
updateVariablesSchema () {
|
updateVariablesSchema () {
|
||||||
this.document.variables_schema = buildVariablesSchema(this.bodyDom.body)
|
this.document.variables_schema = buildVariablesSchema(this.bodyDom.body)
|
||||||
},
|
},
|
||||||
updateSectionsAndSave () {
|
updateSections () {
|
||||||
|
delete this.saveTimer
|
||||||
|
|
||||||
this.updatedSectionEditors.forEach((editor, sectionId) => {
|
this.updatedSectionEditors.forEach((editor, sectionId) => {
|
||||||
const target = this.bodyDom.getElementById(sectionId)
|
const target = this.bodyDom.getElementById(sectionId)
|
||||||
|
|
||||||
@@ -315,6 +322,9 @@ export default {
|
|||||||
this.updateVariablesSchema()
|
this.updateVariablesSchema()
|
||||||
|
|
||||||
this.$emit('update', this.document)
|
this.$emit('update', this.document)
|
||||||
|
},
|
||||||
|
updateSectionsAndSave () {
|
||||||
|
this.updateSections()
|
||||||
|
|
||||||
return this.saveBody()
|
return this.saveBody()
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -27,13 +27,17 @@ tiptapStylesheet.replaceSync(
|
|||||||
font-feature-settings: "liga" 0;
|
font-feature-settings: "liga" 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: column nowrap;
|
flex-flow: column nowrap;
|
||||||
min-height: inherit;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ProseMirror > article {
|
.ProseMirror > article:last-of-type {
|
||||||
margin-bottom: auto;
|
margin-bottom: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ProseMirror p.doc-trailing-break::after {
|
||||||
|
content: none;
|
||||||
|
}
|
||||||
|
|
||||||
.ProseMirror [contenteditable="false"] {
|
.ProseMirror [contenteditable="false"] {
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -639,6 +639,20 @@ export default {
|
|||||||
|
|
||||||
if (!file) return
|
if (!file) return
|
||||||
|
|
||||||
|
const image = await this.uploadImage(file)
|
||||||
|
|
||||||
|
if (image) {
|
||||||
|
this.editor.chain().focus().insertContent(image).run()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async insertImageFiles (files, pos) {
|
||||||
|
const images = (await Promise.all(files.map(this.uploadImage))).filter(Boolean)
|
||||||
|
|
||||||
|
if (images.length) {
|
||||||
|
this.editor.chain().focus().insertContentAt(pos, images).run()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async uploadImage (file) {
|
||||||
const formData = new FormData()
|
const formData = new FormData()
|
||||||
|
|
||||||
formData.append('file', file)
|
formData.append('file', file)
|
||||||
@@ -656,9 +670,7 @@ export default {
|
|||||||
|
|
||||||
await this.$nextTick()
|
await this.$nextTick()
|
||||||
|
|
||||||
this.editor.chain().focus()
|
return { type: 'image', attrs: { htmlAttrs: { src: `blob:${data.uuid}`, style: 'max-width: 100%' } } }
|
||||||
.insertContent({ type: 'image', attrs: { htmlAttrs: { src: `blob:${data.uuid}`, style: 'max-width: 100%' } } })
|
|
||||||
.run()
|
|
||||||
},
|
},
|
||||||
setDynamicMenuCoords (editor) {
|
setDynamicMenuCoords (editor) {
|
||||||
const { from, to } = editor.state.selection
|
const { from, to } = editor.state.selection
|
||||||
@@ -975,7 +987,7 @@ export default {
|
|||||||
onSelectedAreaChange () {
|
onSelectedAreaChange () {
|
||||||
this.save()
|
this.save()
|
||||||
},
|
},
|
||||||
onFieldDrop (view, event, _slice, moved) {
|
onFieldDrop (view, event, slice, moved) {
|
||||||
this.isAreaDrag = false
|
this.isAreaDrag = false
|
||||||
|
|
||||||
if (moved) {
|
if (moved) {
|
||||||
@@ -983,8 +995,9 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const draggedField = this.fieldsDragFieldRef?.value || this.customDragFieldRef?.value || this.dragField
|
const draggedField = this.fieldsDragFieldRef?.value || this.customDragFieldRef?.value || this.dragField
|
||||||
|
const imageFiles = slice.size ? [] : Array.from(event.dataTransfer.files).filter((file) => file.type.startsWith('image/'))
|
||||||
|
|
||||||
if (!draggedField) return false
|
if (!draggedField && !imageFiles.length) return false
|
||||||
|
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|
||||||
@@ -992,14 +1005,18 @@ export default {
|
|||||||
|
|
||||||
if (!pos) return false
|
if (!pos) return false
|
||||||
|
|
||||||
this.insertFieldAtRange({
|
if (draggedField) {
|
||||||
sourceField: draggedField,
|
this.insertFieldAtRange({
|
||||||
existingField: this.fieldsDragFieldRef?.value,
|
sourceField: draggedField,
|
||||||
from: pos.pos
|
existingField: this.fieldsDragFieldRef?.value,
|
||||||
})
|
from: pos.pos
|
||||||
|
})
|
||||||
|
|
||||||
this.fieldsDragFieldRef.value = null
|
this.fieldsDragFieldRef.value = null
|
||||||
this.customDragFieldRef.value = null
|
this.customDragFieldRef.value = null
|
||||||
|
} else {
|
||||||
|
this.insertImageFiles(imageFiles, pos.pos)
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -608,10 +608,10 @@ export default {
|
|||||||
return !['stamp', 'heading', 'strikethrough'].includes(this.field.type)
|
return !['stamp', 'heading', 'strikethrough'].includes(this.field.type)
|
||||||
},
|
},
|
||||||
showCondition () {
|
showCondition () {
|
||||||
return this.withCondition && (this.withConditions || this.nativePlatform !== 'ios') && !['stamp', 'heading'].includes(this.field.type)
|
return this.withCondition && (this.withConditions || !this.nativePlatform) && !['stamp', 'heading'].includes(this.field.type)
|
||||||
},
|
},
|
||||||
showFormula () {
|
showFormula () {
|
||||||
return (this.field.type === 'number' || this.field.type === 'date' || this.field.type === 'payment' || !!this.field.preferences?.formula) && (this.withFormula || this.nativePlatform !== 'ios')
|
return (this.field.type === 'number' || this.field.type === 'date' || this.field.type === 'payment' || !!this.field.preferences?.formula) && (this.withFormula || !this.nativePlatform)
|
||||||
},
|
},
|
||||||
showRequired () {
|
showRequired () {
|
||||||
return this.withRequired && !['phone', 'stamp', 'verification', 'strikethrough', 'heading'].includes(this.field.type)
|
return this.withRequired && !['phone', 'stamp', 'verification', 'strikethrough', 'heading'].includes(this.field.type)
|
||||||
|
|||||||
@@ -484,7 +484,7 @@
|
|||||||
</label>
|
</label>
|
||||||
</li>
|
</li>
|
||||||
<li
|
<li
|
||||||
v-if="withCondition && (withConditions || nativePlatform !== 'ios') && field.type != 'stamp' && field.type != 'heading'"
|
v-if="withCondition && (withConditions || !nativePlatform) && field.type != 'stamp' && field.type != 'heading'"
|
||||||
class="field-settings-condition"
|
class="field-settings-condition"
|
||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
@@ -500,7 +500,7 @@
|
|||||||
</label>
|
</label>
|
||||||
</li>
|
</li>
|
||||||
<li
|
<li
|
||||||
v-if="(field.type == 'number' || field.type == 'date' || field.preferences?.formula) && (withFormula || nativePlatform !== 'ios')"
|
v-if="(field.type == 'number' || field.type == 'date' || field.preferences?.formula) && (withFormula || !nativePlatform)"
|
||||||
class="field-settings-formula"
|
class="field-settings-formula"
|
||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
|
|||||||
@@ -275,7 +275,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
<div
|
<div
|
||||||
v-else-if="type == 'phone' && nativePlatform !== 'ios' && (fieldTypes.length === 0 || fieldTypes.includes(type))"
|
v-else-if="type == 'phone' && !nativePlatform && (fieldTypes.length === 0 || fieldTypes.includes(type))"
|
||||||
class="tooltip tooltip-bottom flex"
|
class="tooltip tooltip-bottom flex"
|
||||||
:class="{'tooltip-bottom-end': withPayment, 'tooltip-bottom': !withPayment }"
|
:class="{'tooltip-bottom-end': withPayment, 'tooltip-bottom': !withPayment }"
|
||||||
:data-tip="t('unlock_sms_verified_phone_number_field_with_paid_plan_use_text_field_for_phone_numbers_without_verification')"
|
:data-tip="t('unlock_sms_verified_phone_number_field_with_paid_plan_use_text_field_for_phone_numbers_without_verification')"
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
class="absolute top-0 bottom-0 right-0 left-0"
|
class="absolute top-0 bottom-0 right-0 left-0"
|
||||||
@click.prevent="$emit('close')"
|
@click.prevent="$emit('close')"
|
||||||
/>
|
/>
|
||||||
<div :class="inline ? 'px-4 pt-3 pb-6' : 'modal-box pt-4 pb-6 px-6 mt-20 max-h-none w-full max-w-xl'">
|
<div :class="inline ? (nativePlatform === 'android' ? 'px-4 pt-1.5 pb-6' : 'px-4 pt-3 pb-6') : 'modal-box pt-4 pb-6 px-6 mt-20 max-h-none w-full max-w-xl'">
|
||||||
<div
|
<div
|
||||||
v-if="!inline"
|
v-if="!inline"
|
||||||
class="flex justify-between items-center border-b pb-2 mb-2 font-medium"
|
class="flex justify-between items-center border-b pb-2 mb-2 font-medium"
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
@click.prevent="$emit('close')"
|
@click.prevent="$emit('close')"
|
||||||
>×</a>
|
>×</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-4">
|
<div :class="inline && nativePlatform === 'android' ? 'mt-1' : 'mt-4'">
|
||||||
<div>
|
<div>
|
||||||
<div class="flex flex-wrap items-center gap-1.5">
|
<div class="flex flex-wrap items-center gap-1.5">
|
||||||
<span>
|
<span>
|
||||||
@@ -200,7 +200,7 @@ export default {
|
|||||||
components: {
|
components: {
|
||||||
IconChevronDown
|
IconChevronDown
|
||||||
},
|
},
|
||||||
inject: ['t', 'template'],
|
inject: ['t', 'template', 'nativePlatform'],
|
||||||
props: {
|
props: {
|
||||||
field: {
|
field: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
class="absolute top-0 bottom-0 right-0 left-0"
|
class="absolute top-0 bottom-0 right-0 left-0"
|
||||||
@click.prevent="$emit('close')"
|
@click.prevent="$emit('close')"
|
||||||
/>
|
/>
|
||||||
<div :class="inline ? 'px-4 pt-3 pb-6' : 'modal-box pt-4 pb-6 px-6 mt-20 max-h-none w-full max-w-xl'">
|
<div :class="inline ? (nativePlatform === 'android' ? 'px-4 pt-1.5 pb-6' : 'px-4 pt-3 pb-6') : 'modal-box pt-4 pb-6 px-6 mt-20 max-h-none w-full max-w-xl'">
|
||||||
<div
|
<div
|
||||||
v-if="!inline"
|
v-if="!inline"
|
||||||
class="flex justify-between items-center border-b pb-2 mb-2 font-medium"
|
class="flex justify-between items-center border-b pb-2 mb-2 font-medium"
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
class="link"
|
class="link"
|
||||||
>{{ t('available_in_pro') }}</a>
|
>{{ t('available_in_pro') }}</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-wrap mb-2 gap-y-1 pt-1">
|
<div :class="['flex flex-wrap mb-2 gap-y-1', inline && nativePlatform === 'android' ? 'mt-1' : 'pt-1']">
|
||||||
<button
|
<button
|
||||||
v-for="f in fields"
|
v-for="f in fields"
|
||||||
:key="f.uuid"
|
:key="f.uuid"
|
||||||
@@ -152,7 +152,7 @@ export default {
|
|||||||
IconCodePlus,
|
IconCodePlus,
|
||||||
IconMathFunction
|
IconMathFunction
|
||||||
},
|
},
|
||||||
inject: ['t', 'template', 'withFormula'],
|
inject: ['t', 'template', 'withFormula', 'nativePlatform'],
|
||||||
props: {
|
props: {
|
||||||
field: {
|
field: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ const en = {
|
|||||||
remove_condition: 'Remove condition',
|
remove_condition: 'Remove condition',
|
||||||
add_condition: 'Add condition',
|
add_condition: 'Add condition',
|
||||||
are_you_sure_: 'Are you sure?',
|
are_you_sure_: 'Are you sure?',
|
||||||
|
close_without_saving_changes: 'Close without saving changes?',
|
||||||
sign_yourself: 'Sign Yourself',
|
sign_yourself: 'Sign Yourself',
|
||||||
set_signing_date: 'Set signing date',
|
set_signing_date: 'Set signing date',
|
||||||
signing_date: 'Signing Date',
|
signing_date: 'Signing Date',
|
||||||
@@ -371,6 +372,7 @@ const es = {
|
|||||||
down: 'Abajo',
|
down: 'Abajo',
|
||||||
set_signing_date: 'Establecer fecha de firma',
|
set_signing_date: 'Establecer fecha de firma',
|
||||||
are_you_sure_: '¿Estás seguro?',
|
are_you_sure_: '¿Estás seguro?',
|
||||||
|
close_without_saving_changes: '¿Cerrar sin guardar los cambios?',
|
||||||
sign_yourself: 'Firma tú mismo',
|
sign_yourself: 'Firma tú mismo',
|
||||||
checked: 'Marcado',
|
checked: 'Marcado',
|
||||||
current_date: 'Fecha actual',
|
current_date: 'Fecha actual',
|
||||||
@@ -652,6 +654,7 @@ const it = {
|
|||||||
remove_condition: 'Rimuovi condizione',
|
remove_condition: 'Rimuovi condizione',
|
||||||
add_condition: 'Aggiungi condizione',
|
add_condition: 'Aggiungi condizione',
|
||||||
are_you_sure_: 'Sei sicuro?',
|
are_you_sure_: 'Sei sicuro?',
|
||||||
|
close_without_saving_changes: 'Chiudere senza salvare le modifiche?',
|
||||||
sign_yourself: 'Firma te stesso',
|
sign_yourself: 'Firma te stesso',
|
||||||
set_signing_date: 'Imposta data di firma',
|
set_signing_date: 'Imposta data di firma',
|
||||||
signing_date: 'Data di firma',
|
signing_date: 'Data di firma',
|
||||||
@@ -953,6 +956,7 @@ const pt = {
|
|||||||
down: 'Para baixo',
|
down: 'Para baixo',
|
||||||
set_signing_date: 'Definir data de assinatura',
|
set_signing_date: 'Definir data de assinatura',
|
||||||
are_you_sure_: 'Tem certeza?',
|
are_you_sure_: 'Tem certeza?',
|
||||||
|
close_without_saving_changes: 'Fechar sem salvar as alterações?',
|
||||||
sign_yourself: 'Assine você mesmo',
|
sign_yourself: 'Assine você mesmo',
|
||||||
checked: 'Marcado',
|
checked: 'Marcado',
|
||||||
current_date: 'Data atual',
|
current_date: 'Data atual',
|
||||||
@@ -1234,6 +1238,7 @@ const fr = {
|
|||||||
remove_condition: 'Supprimer la condition',
|
remove_condition: 'Supprimer la condition',
|
||||||
add_condition: 'Ajouter une condition',
|
add_condition: 'Ajouter une condition',
|
||||||
are_you_sure_: 'Êtes-vous sûr ?',
|
are_you_sure_: 'Êtes-vous sûr ?',
|
||||||
|
close_without_saving_changes: 'Fermer sans enregistrer les modifications ?',
|
||||||
sign_yourself: 'Signer vous-même',
|
sign_yourself: 'Signer vous-même',
|
||||||
set_signing_date: 'Définir la date de signature',
|
set_signing_date: 'Définir la date de signature',
|
||||||
signing_date: 'Date de signature',
|
signing_date: 'Date de signature',
|
||||||
@@ -1525,6 +1530,7 @@ const de = {
|
|||||||
remove_condition: 'Bedingung entfernen',
|
remove_condition: 'Bedingung entfernen',
|
||||||
add_condition: 'Bedingung hinzufügen',
|
add_condition: 'Bedingung hinzufügen',
|
||||||
are_you_sure_: 'Sind Sie sicher?',
|
are_you_sure_: 'Sind Sie sicher?',
|
||||||
|
close_without_saving_changes: 'Ohne Speichern der Änderungen schließen?',
|
||||||
sign_yourself: 'Selbst unterschreiben',
|
sign_yourself: 'Selbst unterschreiben',
|
||||||
set_signing_date: 'Unterzeichnungsdatum festlegen',
|
set_signing_date: 'Unterzeichnungsdatum festlegen',
|
||||||
signing_date: 'Unterzeichnungsdatum',
|
signing_date: 'Unterzeichnungsdatum',
|
||||||
@@ -1816,6 +1822,7 @@ const nl = {
|
|||||||
remove_condition: 'Voorwaarde verwijderen',
|
remove_condition: 'Voorwaarde verwijderen',
|
||||||
add_condition: 'Voorwaarde toevoegen',
|
add_condition: 'Voorwaarde toevoegen',
|
||||||
are_you_sure_: 'Weet u het zeker?',
|
are_you_sure_: 'Weet u het zeker?',
|
||||||
|
close_without_saving_changes: 'Sluiten zonder wijzigingen op te slaan?',
|
||||||
sign_yourself: 'Zelf ondertekenen',
|
sign_yourself: 'Zelf ondertekenen',
|
||||||
set_signing_date: 'Ondertekeningsdatum instellen',
|
set_signing_date: 'Ondertekeningsdatum instellen',
|
||||||
signing_date: 'Ondertekeningsdatum',
|
signing_date: 'Ondertekeningsdatum',
|
||||||
|
|||||||
@@ -57,7 +57,6 @@
|
|||||||
@scroll-to="$emit('scroll-to', $event)"
|
@scroll-to="$emit('scroll-to', $event)"
|
||||||
@add-custom-field="$emit('add-custom-field', $event)"
|
@add-custom-field="$emit('add-custom-field', $event)"
|
||||||
@contextmenu="openAreaContextMenu($event, item.area, item.field)"
|
@contextmenu="openAreaContextMenu($event, item.area, item.field)"
|
||||||
@multi-select="openMultiSelectContextMenu"
|
|
||||||
@click-title="closeContextMenu"
|
@click-title="closeContextMenu"
|
||||||
/>
|
/>
|
||||||
<FieldArea
|
<FieldArea
|
||||||
@@ -450,13 +449,6 @@ export default {
|
|||||||
areas: this.selectedAreasRef.value
|
areas: this.selectedAreasRef.value
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
openMultiSelectContextMenu (event) {
|
|
||||||
if (this.selectedAreasRef.value.length >= 2) {
|
|
||||||
this.openSelectionContextMenu(event)
|
|
||||||
} else {
|
|
||||||
this.closeContextMenu()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handleSelectionCopy () {
|
handleSelectionCopy () {
|
||||||
this.$emit('copy-selected-areas')
|
this.$emit('copy-selected-areas')
|
||||||
|
|
||||||
|
|||||||
@@ -203,7 +203,7 @@
|
|||||||
>{{ t('learn_more') }}</a>
|
>{{ t('learn_more') }}</a>
|
||||||
</div>
|
</div>
|
||||||
<li
|
<li
|
||||||
v-if="withFormula || nativePlatform !== 'ios'"
|
v-if="withFormula || !nativePlatform"
|
||||||
class="field-settings-formula mb-1"
|
class="field-settings-formula mb-1"
|
||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
@@ -233,7 +233,7 @@
|
|||||||
</label>
|
</label>
|
||||||
</li>
|
</li>
|
||||||
<li
|
<li
|
||||||
v-if="withCondition && (withConditions || nativePlatform !== 'ios')"
|
v-if="withCondition && (withConditions || !nativePlatform)"
|
||||||
class="field-settings-condition mt-1"
|
class="field-settings-condition mt-1"
|
||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
class="absolute top-0 bottom-0 right-0 left-0"
|
class="absolute top-0 bottom-0 right-0 left-0"
|
||||||
@click.prevent="$emit('close')"
|
@click.prevent="$emit('close')"
|
||||||
/>
|
/>
|
||||||
<div :class="inline ? 'px-4 pt-3 pb-6' : 'modal-box pt-4 pb-6 mt-20 w-full'">
|
<div :class="inline ? (nativePlatform === 'android' ? 'px-4 pt-1.5 pb-6' : 'px-4 pt-3 pb-6') : 'modal-box pt-4 pb-6 mt-20 w-full'">
|
||||||
<div
|
<div
|
||||||
v-if="!inline"
|
v-if="!inline"
|
||||||
class="flex justify-between items-center border-b pb-2 mb-3 font-medium"
|
class="flex justify-between items-center border-b pb-2 mb-3 font-medium"
|
||||||
@@ -63,7 +63,7 @@ import { IconUser, IconInnerShadowTop } from '@tabler/icons-vue'
|
|||||||
export default {
|
export default {
|
||||||
name: 'RevisionsModal',
|
name: 'RevisionsModal',
|
||||||
components: { IconUser, IconInnerShadowTop },
|
components: { IconUser, IconInnerShadowTop },
|
||||||
inject: ['t', 'baseFetch'],
|
inject: ['t', 'baseFetch', 'nativePlatform'],
|
||||||
props: {
|
props: {
|
||||||
template: {
|
template: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
|||||||
@@ -259,7 +259,7 @@ export default {
|
|||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
showCondition () {
|
showCondition () {
|
||||||
return this.withCondition && (this.withConditions || this.nativePlatform !== 'ios')
|
return this.withCondition && (this.withConditions || !this.nativePlatform)
|
||||||
},
|
},
|
||||||
fieldNames: FieldType.computed.fieldNames,
|
fieldNames: FieldType.computed.fieldNames,
|
||||||
fieldLabels: FieldType.computed.fieldLabels
|
fieldLabels: FieldType.computed.fieldLabels
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ class ProcessSubmitterCompletionJob
|
|||||||
|
|
||||||
WebhookUrls.for_account_id(submitter.account_id, %w[form.completed submission.completed]).each do |webhook|
|
WebhookUrls.for_account_id(submitter.account_id, %w[form.completed submission.completed]).each do |webhook|
|
||||||
if webhook.events.include?('form.completed')
|
if webhook.events.include?('form.completed')
|
||||||
event_uuids['form.completed'] ||= SecureRandom.uuid
|
event_uuids['form.completed'] ||= SecureRandom.uuid_v7
|
||||||
|
|
||||||
SendFormCompletedWebhookRequestJob.perform_async('submitter_id' => submitter.id,
|
SendFormCompletedWebhookRequestJob.perform_async('submitter_id' => submitter.id,
|
||||||
'event_uuid' => event_uuids['form.completed'],
|
'event_uuid' => event_uuids['form.completed'],
|
||||||
@@ -109,7 +109,7 @@ class ProcessSubmitterCompletionJob
|
|||||||
|
|
||||||
next unless webhook.events.include?('submission.completed') && is_last
|
next unless webhook.events.include?('submission.completed') && is_last
|
||||||
|
|
||||||
event_uuids['submission.completed'] ||= SecureRandom.uuid
|
event_uuids['submission.completed'] ||= SecureRandom.uuid_v7
|
||||||
|
|
||||||
SendSubmissionCompletedWebhookRequestJob.perform_async('submission_id' => submitter.submission_id,
|
SendSubmissionCompletedWebhookRequestJob.perform_async('submission_id' => submitter.submission_id,
|
||||||
'event_uuid' => event_uuids['submission.completed'],
|
'event_uuid' => event_uuids['submission.completed'],
|
||||||
|
|||||||
@@ -30,14 +30,16 @@ class SendTestWebhookRequestJob
|
|||||||
raise LocalhostError, "Can't send to localhost." if uri.host.in?(SendWebhookRequest::LOCALHOSTS)
|
raise LocalhostError, "Can't send to localhost." if uri.host.in?(SendWebhookRequest::LOCALHOSTS)
|
||||||
end
|
end
|
||||||
|
|
||||||
Faraday.post(webhook_url.url,
|
body_json = {
|
||||||
{
|
event_type: 'form.completed',
|
||||||
event_type: 'form.completed',
|
timestamp: Time.current.iso8601,
|
||||||
timestamp: Time.current.iso8601,
|
data: Submitters::SerializeForWebhook.call(submitter)
|
||||||
data: Submitters::SerializeForWebhook.call(submitter)
|
}.to_json
|
||||||
}.to_json,
|
|
||||||
|
Faraday.post(webhook_url.url, body_json,
|
||||||
'Content-Type' => 'application/json',
|
'Content-Type' => 'application/json',
|
||||||
'User-Agent' => USER_AGENT,
|
'User-Agent' => USER_AGENT,
|
||||||
|
'X-Docuseal-Signature' => WebhookUrls::Signatures.sign(webhook_url.hmac_secret, body: body_json),
|
||||||
**webhook_url.secret.to_h)
|
**webhook_url.secret.to_h)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -15,7 +15,8 @@
|
|||||||
#
|
#
|
||||||
# Indexes
|
# Indexes
|
||||||
#
|
#
|
||||||
# index_accounts_on_uuid (uuid) UNIQUE
|
# index_accounts_on_created_at (created_at)
|
||||||
|
# index_accounts_on_uuid (uuid) UNIQUE
|
||||||
#
|
#
|
||||||
class Account < ApplicationRecord
|
class Account < ApplicationRecord
|
||||||
attribute :uuid, :string, default: -> { SecureRandom.uuid }
|
attribute :uuid, :string, default: -> { SecureRandom.uuid }
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ class AccountConfig < ApplicationRecord
|
|||||||
DOCUMENT_FILENAME_FORMAT_KEY = 'document_filename_format'
|
DOCUMENT_FILENAME_FORMAT_KEY = 'document_filename_format'
|
||||||
TEMPLATE_CUSTOM_FIELDS_KEY = 'template_custom_fields'
|
TEMPLATE_CUSTOM_FIELDS_KEY = 'template_custom_fields'
|
||||||
TEMPLATE_DATE_FORMATS_KEY = 'template_date_formats'
|
TEMPLATE_DATE_FORMATS_KEY = 'template_date_formats'
|
||||||
|
TEMPLATE_BUILDER_AUTOSAVE_KEY = 'template_builder_autosave'
|
||||||
POLICY_LINKS_KEY = 'policy_links'
|
POLICY_LINKS_KEY = 'policy_links'
|
||||||
ENABLE_MCP_KEY = 'enable_mcp'
|
ENABLE_MCP_KEY = 'enable_mcp'
|
||||||
|
|
||||||
|
|||||||
@@ -25,15 +25,17 @@
|
|||||||
# account_id :bigint not null
|
# account_id :bigint not null
|
||||||
# external_id :string
|
# external_id :string
|
||||||
# submission_id :bigint not null
|
# submission_id :bigint not null
|
||||||
|
# template_id :bigint
|
||||||
#
|
#
|
||||||
# Indexes
|
# Indexes
|
||||||
#
|
#
|
||||||
# index_submitters_on_account_id_and_completed_at (account_id,completed_at) WHERE (completed_at IS NOT NULL)
|
# index_submitters_on_account_id_and_completed_at (account_id,completed_at) WHERE (completed_at IS NOT NULL)
|
||||||
# index_submitters_on_account_id_and_id (account_id,id)
|
# index_submitters_on_account_id_and_id (account_id,id)
|
||||||
# index_submitters_on_email (email)
|
# index_submitters_on_email_and_template_id (email,template_id) WHERE (email IS NOT NULL)
|
||||||
# index_submitters_on_external_id (external_id)
|
# index_submitters_on_external_id (external_id)
|
||||||
# index_submitters_on_slug (slug) UNIQUE
|
# index_submitters_on_slug (slug) UNIQUE
|
||||||
# index_submitters_on_submission_id (submission_id)
|
# index_submitters_on_submission_id (submission_id)
|
||||||
|
# index_submitters_on_template_id_and_id (template_id,id)
|
||||||
#
|
#
|
||||||
# Foreign Keys
|
# Foreign Keys
|
||||||
#
|
#
|
||||||
@@ -68,6 +70,8 @@ class Submitter < ApplicationRecord
|
|||||||
|
|
||||||
scope :completed, -> { where.not(completed_at: nil) }
|
scope :completed, -> { where.not(completed_at: nil) }
|
||||||
|
|
||||||
|
before_validation :set_template_id, on: :create
|
||||||
|
|
||||||
after_destroy :anonymize_email_events, if: -> { Docuseal.multitenant? }
|
after_destroy :anonymize_email_events, if: -> { Docuseal.multitenant? }
|
||||||
|
|
||||||
def status
|
def status
|
||||||
@@ -124,6 +128,10 @@ class Submitter < ApplicationRecord
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def set_template_id
|
||||||
|
self.template_id = submission&.template_id
|
||||||
|
end
|
||||||
|
|
||||||
def anonymize_email_events
|
def anonymize_email_events
|
||||||
email_events.each do |event|
|
email_events.each do |event|
|
||||||
event.update!(email: Digest::MD5.base64digest(event.email))
|
event.update!(email: Digest::MD5.base64digest(event.email))
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
# index_webhook_events_on_webhook_url_id_and_id (webhook_url_id,id)
|
# index_webhook_events_on_webhook_url_id_and_id (webhook_url_id,id)
|
||||||
#
|
#
|
||||||
class WebhookEvent < ApplicationRecord
|
class WebhookEvent < ApplicationRecord
|
||||||
attribute :uuid, :string, default: -> { SecureRandom.uuid }
|
attribute :uuid, :string, default: -> { SecureRandom.uuid_v7 }
|
||||||
|
|
||||||
belongs_to :webhook_url, optional: true
|
belongs_to :webhook_url, optional: true
|
||||||
belongs_to :account, optional: true
|
belongs_to :account, optional: true
|
||||||
|
|||||||
@@ -133,7 +133,7 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% account_config = AccountConfig.find_or_initialize_by(account: current_account, key: AccountConfig::ALLOW_TO_DECLINE_KEY) %>
|
<% account_config = AccountConfig.find_or_initialize_by(account: current_account, key: AccountConfig::ALLOW_TO_DECLINE_KEY) %>
|
||||||
<% if can?(:manage, account_config) && (!turbo_native_ios? || !Docuseal.multitenant? || can?(:manage, :disable_decline)) %>
|
<% if can?(:manage, account_config) && (!turbo_native_app? || !Docuseal.multitenant? || can?(:manage, :disable_decline)) %>
|
||||||
<%= form_for account_config, url: account_configs_path, method: :post do |f| %>
|
<%= form_for account_config, url: account_configs_path, method: :post do |f| %>
|
||||||
<%= f.hidden_field :key %>
|
<%= f.hidden_field :key %>
|
||||||
<div class="flex items-center justify-between gap-4 py-2.5">
|
<div class="flex items-center justify-between gap-4 py-2.5">
|
||||||
@@ -156,7 +156,7 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% account_config = AccountConfig.find_or_initialize_by(account: current_account, key: AccountConfig::ALLOW_TO_DELEGATE_KEY) %>
|
<% account_config = AccountConfig.find_or_initialize_by(account: current_account, key: AccountConfig::ALLOW_TO_DELEGATE_KEY) %>
|
||||||
<% if can?(:manage, account_config) && (!turbo_native_ios? || !Docuseal.multitenant? || can?(:manage, :delegate_form)) %>
|
<% if can?(:manage, account_config) && (!turbo_native_app? || !Docuseal.multitenant? || can?(:manage, :delegate_form)) %>
|
||||||
<%= form_for account_config, url: account_configs_path, method: :post do |f| %>
|
<%= form_for account_config, url: account_configs_path, method: :post do |f| %>
|
||||||
<%= f.hidden_field :key %>
|
<%= f.hidden_field :key %>
|
||||||
<div class="flex items-center justify-between gap-4 py-2.5">
|
<div class="flex items-center justify-between gap-4 py-2.5">
|
||||||
@@ -196,7 +196,7 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% account_config = AccountConfig.find_or_initialize_by(account: current_account, key: AccountConfig::DOWNLOAD_LINKS_EXPIRE_KEY) %>
|
<% account_config = AccountConfig.find_or_initialize_by(account: current_account, key: AccountConfig::DOWNLOAD_LINKS_EXPIRE_KEY) %>
|
||||||
<% if can?(:manage, account_config) %>
|
<% if !Docuseal.demo? && can?(:manage, account_config) %>
|
||||||
<%= form_for account_config, url: account_configs_path, method: :post do |f| %>
|
<%= form_for account_config, url: account_configs_path, method: :post do |f| %>
|
||||||
<%= f.hidden_field :key %>
|
<%= f.hidden_field :key %>
|
||||||
<div class="flex items-center justify-between gap-4 py-2.5">
|
<div class="flex items-center justify-between gap-4 py-2.5">
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<% unless turbo_native_ios? %>
|
<% unless turbo_native_app? %>
|
||||||
<tr class="group">
|
<tr class="group">
|
||||||
<td>
|
<td>
|
||||||
<div class="flex items-center space-x-1">
|
<div class="flex items-center space-x-1">
|
||||||
|
|||||||
@@ -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="M10 12a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" />
|
||||||
|
<path d="M12 18c-3.6 0 -6.6 -2 -9 -6c2.4 -4 5.4 -6 9 -6c3.6 0 6.6 2 9 6" />
|
||||||
|
<path d="M16 19h6" />
|
||||||
|
<path d="M19 16v6" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 460 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 19h-7a2 2 0 0 1 -2 -2v-10a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v5.5" />
|
||||||
|
<path d="M16 19h6" />
|
||||||
|
<path d="M19 16v6" />
|
||||||
|
<path d="M3 7l9 6l9 -6" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 441 B |
@@ -1,8 +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">
|
<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 stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
<path d="M5 7a4 4 0 1 0 8 0a4 4 0 0 0 -8 0" />
|
<path d="M8 7a4 4 0 1 0 8 0a4 4 0 0 0 -8 0" />
|
||||||
<path d="M3 21v-2a4 4 0 0 1 4 -4h4c.96 0 1.84 .338 2.53 .901" />
|
|
||||||
<path d="M16 3.13a4 4 0 0 1 0 7.75" />
|
|
||||||
<path d="M16 19h6" />
|
<path d="M16 19h6" />
|
||||||
<path d="M19 16v6" />
|
<path d="M19 16v6" />
|
||||||
|
<path d="M6 21v-2a4 4 0 0 1 4 -4h4" />
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 488 B After Width: | Height: | Size: 421 B |
@@ -7,4 +7,21 @@
|
|||||||
-webkit-user-drag: none;
|
-webkit-user-drag: none;
|
||||||
-webkit-touch-callout: none;
|
-webkit-touch-callout: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<% if turbo_native_android? %>
|
||||||
|
input[type="date"] {
|
||||||
|
appearance: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="date"]::-webkit-calendar-picker-indicator {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
div:has(> #account_settings_menu) + * {
|
||||||
|
margin-top: 0 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
<% end %>
|
||||||
</style>
|
</style>
|
||||||
@@ -33,10 +33,10 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
<turbo-frame id="drawer"></turbo-frame>
|
<turbo-frame id="drawer"></turbo-frame>
|
||||||
<%= render turbo_native_app? ? 'shared/native_navbar' : 'shared/navbar' %>
|
<%= render turbo_native_app? ? 'shared/native_navbar' : 'shared/navbar' %>
|
||||||
<% if flash.present? %><%= render 'shared/flash' %><% end %>
|
<main class="max-w-6xl mx-auto px-4 md:px-2 mb-8">
|
||||||
<div class="max-w-6xl mx-auto px-4 md:px-2 mb-8">
|
|
||||||
<%= yield %>
|
<%= yield %>
|
||||||
</div>
|
</main>
|
||||||
|
<% if flash.present? %><%= render 'shared/flash' %><% end %>
|
||||||
<%= render 'shared/body_scripts' %>
|
<%= render 'shared/body_scripts' %>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -13,6 +13,8 @@
|
|||||||
<%= stylesheet_pack_tag 'form', media: 'all', data: { turbo_track: 'dynamic' } %>
|
<%= stylesheet_pack_tag 'form', media: 'all', data: { turbo_track: 'dynamic' } %>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<%= yield %>
|
<main>
|
||||||
|
<%= yield %>
|
||||||
|
</main>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -13,8 +13,10 @@
|
|||||||
<%= stylesheet_pack_tag 'application', media: 'all', data: { turbo_track: 'dynamic' } %>
|
<%= stylesheet_pack_tag 'application', media: 'all', data: { turbo_track: 'dynamic' } %>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<% if flash.present? %><%= render 'shared/flash' %><% end %>
|
<main>
|
||||||
<%= yield %>
|
<%= yield %>
|
||||||
|
</main>
|
||||||
<turbo-frame id="modal"></turbo-frame>
|
<turbo-frame id="modal"></turbo-frame>
|
||||||
|
<% if flash.present? %><%= render 'shared/flash' %><% end %>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<%= render 'bcc_form', config: @bcc_config %>
|
<%= render 'bcc_form', config: @bcc_config %>
|
||||||
<% unless can?(:manage, :personalization_restricted) || (turbo_native_ios? && !can?(:manage, :email_reminders)) %>
|
<% unless can?(:manage, :personalization_restricted) || (turbo_native_app? && !can?(:manage, :email_reminders)) %>
|
||||||
<div class="flex justify-between items-end mb-4 mt-8">
|
<div class="flex justify-between items-end mb-4 mt-8">
|
||||||
<h2 class="text-2xl md:text-3xl font-bold">
|
<h2 class="text-2xl md:text-3xl font-bold">
|
||||||
<%= t('sign_request_email_reminders') %>
|
<%= t('sign_request_email_reminders') %>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
<%= render 'documents_copy_email_form' %>
|
<%= render 'documents_copy_email_form' %>
|
||||||
<%= render 'submitter_completed_email_form' %>
|
<%= render 'submitter_completed_email_form' %>
|
||||||
</div>
|
</div>
|
||||||
<% if !turbo_native_ios? || can?(:manage, :custom_logo) %>
|
<% if !turbo_native_app? || can?(:manage, :custom_logo) %>
|
||||||
<p class="text-3xl md:text-4xl font-bold mb-4 mt-8">
|
<p class="text-3xl md:text-4xl font-bold mb-4 mt-8">
|
||||||
<%= t('company_logo') %>
|
<%= t('company_logo') %>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<div class="w-full text-center">
|
<div class="w-full text-center">
|
||||||
<span class="font-bold"><%= t('demo_environment') %></span>
|
<span class="font-bold"><%= t('demo_environment') %></span>
|
||||||
<br>
|
<br>
|
||||||
<%= t('create_a_new_template_document_form_or_submit_the_existing_one_html', new_template_link: new_template_path, start_form_link: start_form_url(slug: ::Template.first&.slug)) %> 😊
|
<%= t('create_a_new_template_document_form_html', new_template_link: new_template_path) %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
<script type="application/json" id="native_path_configuration"><%= render partial: 'layouts/native_path_configuration', formats: :json %></script>
|
<script type="application/json" id="native_path_configuration"><%= render partial: 'layouts/native_path_configuration', formats: :json %></script>
|
||||||
<% if signed_in? %>
|
<% if signed_in? %>
|
||||||
<native-action data-placement="profile" data-icon="user">
|
<% unless turbo_native_android? %>
|
||||||
<%= link_to t('profile'), settings_profile_index_path %>
|
<native-action data-placement="profile" data-icon="user">
|
||||||
</native-action>
|
<%= link_to t('profile'), settings_profile_index_path %>
|
||||||
<native-action data-placement="profile" data-icon="settings">
|
</native-action>
|
||||||
<%= link_to t('settings'), settings_path %>
|
<native-action data-placement="profile" data-icon="settings">
|
||||||
</native-action>
|
<%= link_to t('settings'), settings_path %>
|
||||||
|
</native-action>
|
||||||
|
<% end %>
|
||||||
<% if current_account.testing? && (can?(:manage, EncryptedConfig) || current_user != true_user) %>
|
<% if current_account.testing? && (can?(:manage, EncryptedConfig) || current_user != true_user) %>
|
||||||
<native-action data-placement="test_mode" data-icon="code" data-label="<%= t('exit_test_mode') %>">
|
<native-action data-placement="test_mode" data-icon="code" data-label="<%= t('exit_test_mode') %>">
|
||||||
<%= button_to '', testing_account_path, method: :delete, form: { data: { turbo_action: 'replace' } } %>
|
<%= button_to '', testing_account_path, method: :delete, form: { data: { turbo_action: 'replace' } } %>
|
||||||
@@ -22,13 +24,13 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
<% if can?(:create, ::Template) && controller_name.in?(%w[templates_dashboard submissions_dashboard template_folders templates_archived submissions_archived]) %>
|
<% if can?(:create, ::Template) && controller_name.in?(%w[templates_dashboard submissions_dashboard template_folders templates_archived submissions_archived]) %>
|
||||||
<% folder_name = @template_folder&.full_name %>
|
<% folder_name = @template_folder&.full_name %>
|
||||||
<native-action data-placement="create" data-icon="upload" data-native="upload" data-label="<%= t('upload') %>" data-url="<%= templates_upload_path(folder_name: folder_name) %>" data-accept="image/*, application/pdf, application/zip, application/json<%= ", #{Templates::CreateAttachments::DOCUMENT_EXTENSIONS.join(', ')}" if Docuseal.advanced_formats? %>"></native-action>
|
<native-action data-placement="create" data-icon="upload" data-native="upload" data-label="<%= t('upload') %>" data-url="<%= templates_upload_path(folder_name: folder_name) %>" data-accept="image/*, application/pdf, application/zip, application/json, <%= Templates::CreateAttachments.document_extensions.join(', ') %>"></native-action>
|
||||||
<native-action data-placement="create" data-icon="scan" data-native="scan" data-label="<%= t('scan') %>" data-url="<%= templates_upload_path(folder_name: folder_name) %>"></native-action>
|
<native-action data-placement="create" data-icon="scan" data-native="scan" data-label="<%= t('scan') %>" data-url="<%= templates_upload_path(folder_name: folder_name) %>"></native-action>
|
||||||
<native-action data-placement="create" data-icon="pencil_plus">
|
<native-action data-placement="create" data-icon="pencil_plus">
|
||||||
<%= link_to t('create'), new_template_path(folder_name: folder_name), data: { turbo_frame: :modal, native_modal: :medium } %>
|
<%= link_to t('create'), new_template_path(folder_name: folder_name), data: { turbo_frame: :modal, native_modal: :medium } %>
|
||||||
</native-action>
|
</native-action>
|
||||||
<% unless controller_name.in?(%w[templates_archived submissions_archived]) %>
|
<% unless controller_name.in?(%w[templates_archived submissions_archived]) %>
|
||||||
<native-action data-placement="menu" data-menu="<%= t('new') %>" data-menu-icon="pencil_plus" data-icon="upload" data-native="upload" data-label="<%= t('upload') %>" data-url="<%= templates_upload_path(folder_name: folder_name) %>" data-accept="image/*, application/pdf, application/zip, application/json<%= ", #{Templates::CreateAttachments::DOCUMENT_EXTENSIONS.join(', ')}" if Docuseal.advanced_formats? %>"></native-action>
|
<native-action data-placement="menu" data-menu="<%= t('new') %>" data-menu-icon="pencil_plus" data-icon="upload" data-native="upload" data-label="<%= t('upload') %>" data-url="<%= templates_upload_path(folder_name: folder_name) %>" data-accept="image/*, application/pdf, application/zip, application/json, <%= Templates::CreateAttachments.document_extensions.join(', ') %>"></native-action>
|
||||||
<native-action data-placement="menu" data-menu="<%= t('new') %>" data-menu-icon="pencil_plus" data-icon="scan" data-native="scan" data-label="<%= t('scan') %>" data-url="<%= templates_upload_path(folder_name: folder_name) %>"></native-action>
|
<native-action data-placement="menu" data-menu="<%= t('new') %>" data-menu-icon="pencil_plus" data-icon="scan" data-native="scan" data-label="<%= t('scan') %>" data-url="<%= templates_upload_path(folder_name: folder_name) %>"></native-action>
|
||||||
<native-action data-placement="menu" data-menu="<%= t('new') %>" data-menu-icon="pencil_plus" data-icon="pencil_plus">
|
<native-action data-placement="menu" data-menu="<%= t('new') %>" data-menu-icon="pencil_plus" data-icon="pencil_plus">
|
||||||
<%= link_to t('create'), new_template_path(folder_name: folder_name), data: { turbo_frame: :modal, native_modal: :medium } %>
|
<%= link_to t('create'), new_template_path(folder_name: folder_name), data: { turbo_frame: :modal, native_modal: :medium } %>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<%= render 'shared/navbar_warning' %>
|
<%= render 'shared/navbar_warning' %>
|
||||||
<div class="max-w-6xl mb-4 mx-auto px-4 md:px-2 py-3 flex items-center justify-between">
|
<nav class="max-w-6xl mb-4 mx-auto px-4 md:px-2 py-3 flex items-center justify-between">
|
||||||
<div class="flex items-center space-x-4">
|
<div class="flex items-center space-x-4">
|
||||||
<a href="<%= root_path %>" class="text-2xl font-bold items-center flex space-x-2">
|
<a href="<%= root_path %>" class="text-2xl font-bold items-center flex space-x-2">
|
||||||
<%= render 'shared/title' %>
|
<%= render 'shared/title' %>
|
||||||
@@ -106,4 +106,4 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</nav>
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
<%= link_to t('storage'), settings_storage_index_path, class: 'text-base hover:bg-base-300', data: nav_link_data %>
|
<%= link_to t('storage'), settings_storage_index_path, class: 'text-base hover:bg-base-300', data: nav_link_data %>
|
||||||
</li>
|
</li>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if can?(:read, EncryptedConfig.new(key: 'submitter_invitation_sms', account: current_account)) && true_user == current_user && (!turbo_native_ios? || can?(:manage, :sms_verification)) %>
|
<% if can?(:read, EncryptedConfig.new(key: 'submitter_invitation_sms', account: current_account)) && true_user == current_user && (!turbo_native_app? || can?(:manage, :sms_verification)) %>
|
||||||
<li>
|
<li>
|
||||||
<%= link_to 'SMS', settings_sms_path, class: 'text-base hover:bg-base-300', data: nav_link_data %>
|
<%= link_to 'SMS', settings_sms_path, class: 'text-base hover:bg-base-300', data: nav_link_data %>
|
||||||
</li>
|
</li>
|
||||||
@@ -84,7 +84,7 @@
|
|||||||
</li>
|
</li>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if !Docuseal.demo? && !turbo_native_ios? && can?(:manage, EncryptedConfig) && (current_user != true_user || !current_account.linked_account_account) %>
|
<% if !Docuseal.demo? && !turbo_native_app? && can?(:manage, EncryptedConfig) && (current_user != true_user || !current_account.linked_account_account) %>
|
||||||
<li>
|
<li>
|
||||||
<%= content_for(:pro_link) || link_to(Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premises" }.to_query}", class: 'text-base hover:bg-base-300', data: { turbo: false }) do %>
|
<%= content_for(:pro_link) || link_to(Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premises" }.to_query}", class: 'text-base hover:bg-base-300', data: { turbo: false }) do %>
|
||||||
<%= t('plans') %>
|
<%= t('plans') %>
|
||||||
@@ -93,21 +93,21 @@
|
|||||||
</li>
|
</li>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if !Docuseal.demo? && can?(:manage, EncryptedConfig) && (current_user == true_user || current_account.testing?) %>
|
<% if !Docuseal.demo? && can?(:manage, EncryptedConfig) && (current_user == true_user || current_account.testing?) %>
|
||||||
<% if Docuseal.multitenant? || !turbo_native_ios? %>
|
<% unless turbo_native_app? %>
|
||||||
<li>
|
<li>
|
||||||
<%= link_to Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}#{'/test' if current_account.testing?}/api") : "#{Docuseal::CONSOLE_URL}/on_premises", class: 'text-base hover:bg-base-300', data: { turbo: false } do %>
|
<%= link_to Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}#{'/test' if current_account.testing?}/api") : "#{Docuseal::CONSOLE_URL}/on_premises", class: 'text-base hover:bg-base-300', data: { turbo: false } do %>
|
||||||
<% if Docuseal.multitenant? %> API <% else %> <%= t('console') %> <% end %>
|
<% if Docuseal.multitenant? %> API <% else %> <%= t('console') %> <% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</li>
|
</li>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if Docuseal.multitenant? %>
|
<% if Docuseal.multitenant? && !turbo_native_app? %>
|
||||||
<li>
|
<li>
|
||||||
<%= link_to console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}#{'/test' if current_account.testing?}/embedding/form"), class: 'text-base hover:bg-base-300', data: { turbo: false } do %>
|
<%= link_to console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}#{'/test' if current_account.testing?}/embedding/form"), class: 'text-base hover:bg-base-300', data: { turbo: false } do %>
|
||||||
<%= t('embedding') %>
|
<%= t('embedding') %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</li>
|
</li>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if (!Docuseal.multitenant? || can?(:manage, :saml_sso)) && can?(:read, EncryptedConfig.new(key: 'saml_configs', account: current_account)) && true_user == current_user && (!turbo_native_ios? || can?(:manage, :saml_sso)) %>
|
<% if (!Docuseal.multitenant? || can?(:manage, :saml_sso)) && can?(:read, EncryptedConfig.new(key: 'saml_configs', account: current_account)) && true_user == current_user && (!turbo_native_app? || can?(:manage, :saml_sso)) %>
|
||||||
<li>
|
<li>
|
||||||
<%= link_to 'SSO', settings_sso_index_path, class: 'text-base hover:bg-base-300', data: nav_link_data %>
|
<%= link_to 'SSO', settings_sso_index_path, class: 'text-base hover:bg-base-300', data: nav_link_data %>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
<form action="<%= url_for %>" method="get" class="dropdown dropdown-top hidden md:inline">
|
<form action="<%= url_for %>" method="get" class="dropdown dropdown-top hidden md:inline">
|
||||||
<label tabindex="0" class="btn btn-sm h-10">
|
<% unless turbo_native_app? %>
|
||||||
<%= svg_icon('arrow_sort', class: 'w-5 h-5') %>
|
<label tabindex="0" class="btn btn-sm h-10">
|
||||||
</label>
|
<%= svg_icon('arrow_sort', class: 'w-5 h-5') %>
|
||||||
|
</label>
|
||||||
|
<% end %>
|
||||||
<ul tabindex="0" class="dropdown-content z-[10] menu p-2 shadow bg-base-100 rounded-box mb-1 min-w-48">
|
<ul tabindex="0" class="dropdown-content z-[10] menu p-2 shadow bg-base-100 rounded-box mb-1 min-w-48">
|
||||||
<native-action class="contents" data-menu="<%= t('sort_by') %>" data-menu-icon="arrow_sort" data-icon="sort_descending_numbers" data-selected="<%= selected_order == 'created_at' %>">
|
<native-action class="contents" data-menu="<%= t('sort_by') %>" data-menu-icon="arrow_sort" data-icon="sort_descending_numbers" data-selected="<%= selected_order == 'created_at' %>">
|
||||||
<toggle-cookies data-value="created_at" data-key="dashboard_templates_order">
|
<toggle-cookies data-value="created_at" data-key="dashboard_templates_order">
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<% content_for(:native_modal, 'true') %>
|
<% content_for(:native_modal, 'true') %>
|
||||||
<% content_for(:native_title, local_assigns[:title]) if local_assigns[:title] %>
|
<% content_for(:native_title, local_assigns[:title]) if local_assigns[:title] %>
|
||||||
<turbo-frame id="modal">
|
<turbo-frame id="modal">
|
||||||
<native-modal class="block max-w-lg mx-auto pb-6" data-close-after-submit="<%= local_assigns.key?(:close_after_submit) ? local_assigns[:close_after_submit] : true %>">
|
<native-modal class="block max-w-lg mx-auto <%= turbo_native_android? ? 'pb-4' : 'pb-6' %>" data-close-after-submit="<%= local_assigns.key?(:close_after_submit) ? local_assigns[:close_after_submit] : true %>">
|
||||||
<%= yield %>
|
<%= yield %>
|
||||||
</native-modal>
|
</native-modal>
|
||||||
</turbo-frame>
|
</turbo-frame>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<main class="max-w-md mx-auto px-2 mt-12 mb-4">
|
<div class="max-w-md mx-auto px-2 mt-12 mb-4">
|
||||||
<div class="space-y-6 mx-auto">
|
<div class="space-y-6 mx-auto">
|
||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
<div class="flex items-center justify-center">
|
<div class="flex items-center justify-center">
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<p dir="auto" class="text-lg font-bold mb-1"><%= @submitter.submission.template&.name %></p>
|
<p dir="auto" class="text-lg font-bold mb-1"><%= @submitter.submission.template&.name %></p>
|
||||||
<p dir="auto" class="text-sm">
|
<p dir="auto" class="text-sm">
|
||||||
<%= t(@submitter.with_signature_fields? ? 'signed_on_time' : 'completed_on_time', time: l(@submitter.completed_at.to_date, format: :long)) %>
|
<%= t(@submitter.with_signature_fields? ? 'signed_on_time' : 'completed_on_time', time: l(@submitter.completed_at.in_time_zone(@submitter.account.timezone).to_date, format: :long)) %>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -32,5 +32,5 @@
|
|||||||
</toggle-submit>
|
</toggle-submit>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</div>
|
||||||
<%= render 'shared/attribution', link_path: '/start', account: @submitter.account %>
|
<%= render 'shared/attribution', link_path: '/start', account: @submitter.account %>
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
<% I18n.with_locale(@template.account.locale) do %>
|
<% I18n.with_locale(@template.account.locale) do %>
|
||||||
<% content_for(:html_description, t('account_name_has_invited_you_to_fill_and_sign_documents_online_effortlessly_with_a_secure_fast_and_user_friendly_digital_document_signing_solution', account_name: @template.account.name)) %>
|
<% content_for(:html_description, t('account_name_has_invited_you_to_fill_and_sign_documents_online_effortlessly_with_a_secure_fast_and_user_friendly_digital_document_signing_solution', account_name: @template.account.name)) %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<main class="max-w-md mx-auto px-2 mt-12 mb-4">
|
<div class="max-w-md mx-auto px-2 mt-12 mb-4">
|
||||||
<div class="space-y-6 mx-auto">
|
<div class="space-y-6 mx-auto">
|
||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
<div class="flex items-center justify-center">
|
<div class="flex items-center justify-center">
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<%= t('we_sent_a_one_time_verification_code_to_your_email_address_please_enter_the_code_below_to_continue') %>
|
<%= t('we_sent_a_one_time_verification_code_to_your_email_address_please_enter_the_code_below_to_continue') %>
|
||||||
</div>
|
</div>
|
||||||
<%= form_for '', url: @resubmit_submitter ? resubmit_form_path : start_form_path(@template.slug), method: :put, html: { class: 'space-y-4', id: 'code_form' } do |f| %>
|
<%= form_for '', url: @resubmit_submitter ? resubmit_form_path : start_form_path(@template.slug, lang: params[:lang].presence), method: :put, html: { class: 'space-y-4', id: 'code_form' } do |f| %>
|
||||||
<div dir="auto" class="form-control !mt-0">
|
<div dir="auto" class="form-control !mt-0">
|
||||||
<%= f.hidden_field 'resubmit', value: params[:resubmit] %>
|
<%= f.hidden_field 'resubmit', value: params[:resubmit] %>
|
||||||
<%= f.hidden_field 'submitter[name]', value: params[:name] || params.dig(:submitter, :name) %>
|
<%= f.hidden_field 'submitter[name]', value: params[:name] || params.dig(:submitter, :name) %>
|
||||||
@@ -52,7 +52,7 @@
|
|||||||
<% if @resubmit_submitter %>
|
<% if @resubmit_submitter %>
|
||||||
<%= button_to t(:re_send_email), resubmit_form_path, params: { resubmit: @resubmit_submitter.slug }, method: :put, id: 'resend_code', class: 'hidden' %>
|
<%= button_to t(:re_send_email), resubmit_form_path, params: { resubmit: @resubmit_submitter.slug }, method: :put, id: 'resend_code', class: 'hidden' %>
|
||||||
<% else %>
|
<% else %>
|
||||||
<%= button_to t(:re_send_email), start_form_email_2fa_send_index_path, params: { slug: @template.slug, resend: true, submitter: { name: params[:name] || params.dig(:submitter, :name), email: params[:email] || params.dig(:submitter, :email), phone: params[:phone] || params.dig(:submitter, :phone) } }.compact, method: :post, id: 'resend_code', class: 'hidden' %>
|
<%= button_to t(:re_send_email), start_form_email_2fa_send_index_path, params: { slug: @template.slug, resend: true, submitter: { name: params[:name] || params.dig(:submitter, :name), email: params[:email] || params.dig(:submitter, :email), phone: params[:phone] || params.dig(:submitter, :phone) }, lang: params[:lang] }.compact, method: :post, id: 'resend_code', class: 'hidden' %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</div>
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
<% I18n.with_locale(@template.account.locale) do %>
|
<% I18n.with_locale(@template.account.locale) do %>
|
||||||
<% content_for(:html_description, t('account_name_has_invited_you_to_fill_and_sign_documents_online_effortlessly_with_a_secure_fast_and_user_friendly_digital_document_signing_solution', account_name: @template.account.name)) %>
|
<% content_for(:html_description, t('account_name_has_invited_you_to_fill_and_sign_documents_online_effortlessly_with_a_secure_fast_and_user_friendly_digital_document_signing_solution', account_name: @template.account.name)) %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<main class="max-w-md mx-auto px-2 mt-12 mb-4">
|
<div class="max-w-md mx-auto px-2 mt-12 mb-4">
|
||||||
<div class="space-y-6 mx-auto">
|
<div class="space-y-6 mx-auto">
|
||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
<div class="flex items-center justify-center">
|
<div class="flex items-center justify-center">
|
||||||
@@ -28,4 +28,4 @@
|
|||||||
<%= link_to t('back'), @resubmit_submitter ? submit_form_path(@resubmit_submitter.slug) : start_form_path(@template.slug, @submitter.slice(:name, :email, :phone).compact_blank), class: 'base-button' %>
|
<%= link_to t('back'), @resubmit_submitter ? submit_form_path(@resubmit_submitter.slug) : start_form_path(@template.slug, @submitter.slice(:name, :email, :phone).compact_blank), class: 'base-button' %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</div>
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
<% I18n.with_locale(@template.account.locale) do %>
|
<% I18n.with_locale(@template.account.locale) do %>
|
||||||
<% content_for(:html_description, t('share_link_is_currently_disabled')) %>
|
<% content_for(:html_description, t('share_link_is_currently_disabled')) %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<main class="max-w-md space-y-6 mx-auto px-2 mt-12 mb-4">
|
<div class="max-w-md space-y-6 mx-auto px-2 mt-12 mb-4">
|
||||||
<div class="text-center w-full space-y-6">
|
<div class="text-center w-full space-y-6">
|
||||||
<%= render 'start_form/banner' %>
|
<%= render 'start_form/banner' %>
|
||||||
<p class="text-xl font-semibold text-center">
|
<p class="text-xl font-semibold text-center">
|
||||||
@@ -27,5 +27,5 @@
|
|||||||
<%= button_to button_title(title: t('enable_shared_link'), icon: svg_icon('lock_open', class: 'w-6 h-6')), template_share_link_path(@template), params: { template: { shared_link: true }, redir: start_form_path(slug: @template.slug) }, method: :post, class: 'white-button w-full' %>
|
<%= button_to button_title(title: t('enable_shared_link'), icon: svg_icon('lock_open', class: 'w-6 h-6')), template_share_link_path(@template), params: { template: { shared_link: true }, redir: start_form_path(slug: @template.slug) }, method: :post, class: 'white-button w-full' %>
|
||||||
</toggle-submit>
|
</toggle-submit>
|
||||||
<% end %>
|
<% end %>
|
||||||
</main>
|
</div>
|
||||||
<%= render 'shared/attribution', link_path: '/start', account: @template.account %>
|
<%= render 'shared/attribution', link_path: '/start', account: @template.account %>
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
<% I18n.with_locale(@template.account.locale) do %>
|
<% I18n.with_locale(@template.account.locale) do %>
|
||||||
<% content_for(:html_description, t('account_name_has_invited_you_to_fill_and_sign_documents_online_effortlessly_with_a_secure_fast_and_user_friendly_digital_document_signing_solution', account_name: @template.account.name)) %>
|
<% content_for(:html_description, t('account_name_has_invited_you_to_fill_and_sign_documents_online_effortlessly_with_a_secure_fast_and_user_friendly_digital_document_signing_solution', account_name: @template.account.name)) %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<main class="max-w-md mx-auto px-2 mt-12 mb-4">
|
<div class="max-w-md mx-auto px-2 mt-12 mb-4">
|
||||||
<div class="space-y-6 mx-auto">
|
<div class="space-y-6 mx-auto">
|
||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
<div class="text-center w-full space-y-6">
|
<div class="text-center w-full space-y-6">
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% if !@template.archived_at? && !@template.account.archived_at? %>
|
<% if !@template.archived_at? && !@template.account.archived_at? %>
|
||||||
<%= form_for @submitter, url: @resubmit_submitter ? resubmit_form_path : start_form_path(@template.slug), data: { turbo_frame: :_top }, method: :put, html: { class: 'space-y-4' } do |f| %>
|
<%= form_for @submitter, url: @resubmit_submitter ? resubmit_form_path : start_form_path(@template.slug, lang: params[:lang].presence), data: { turbo_frame: :_top }, method: :put, html: { class: 'space-y-4' } do |f| %>
|
||||||
<%= hidden_field_tag :resubmit, @resubmit_submitter.slug if @resubmit_submitter %>
|
<%= hidden_field_tag :resubmit, @resubmit_submitter.slug if @resubmit_submitter %>
|
||||||
<% if @error_message %>
|
<% if @error_message %>
|
||||||
<div class="alert items-start bg-base-100 border border-red-400 text-red-400">
|
<div class="alert items-start bg-base-100 border border-red-400 text-red-400">
|
||||||
@@ -66,4 +66,4 @@
|
|||||||
<%= render 'shared/attribution', link_path: '/start', account: @template.account %>
|
<%= render 'shared/attribution', link_path: '/start', account: @template.account %>
|
||||||
<%= render 'start_form/policy', account: @template.account %>
|
<%= render 'start_form/policy', account: @template.account %>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</div>
|
||||||
@@ -43,7 +43,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<p class="text-base-content/80 mt-1">
|
<p class="text-base-content/80 mt-1">
|
||||||
<% if event.event_type == 'complete_verification' %>
|
<% if event.event_type == 'complete_verification' %>
|
||||||
<%= t('submission_event_names.complete_verification_by_html', provider: event.data['method'], submitter_name:) %>
|
<% identity_name = [event.data['firstname'], event.data['lastname']].compact_blank.join(' ') %>
|
||||||
|
<%= t('submission_event_names.complete_verification_by_html', provider: event.data['method'], submitter_name: identity_name.presence || submitter_name) %>
|
||||||
<% elsif event.event_type == 'invite_party' && (invited_submitter = @submission.submitters.find { |e| e.uuid == event.data['uuid'] }) && (name = @submission.template_submitters.find { |e| e['uuid'] == event.data['uuid'] }&.dig('name')) %>
|
<% elsif event.event_type == 'invite_party' && (invited_submitter = @submission.submitters.find { |e| e.uuid == event.data['uuid'] }) && (name = @submission.template_submitters.find { |e| e['uuid'] == event.data['uuid'] }&.dig('name')) %>
|
||||||
<%= t('submission_event_names.invite_party_by_html', invited_submitter_name: [invited_submitter.name || invited_submitter.email || invited_submitter.phone, name].join(' '), submitter_name:) %>
|
<%= t('submission_event_names.invite_party_by_html', invited_submitter_name: [invited_submitter.name || invited_submitter.email || invited_submitter.phone, name].join(' '), submitter_name:) %>
|
||||||
<% elsif event.event_type == 'delegate_form' %>
|
<% elsif event.event_type == 'delegate_form' %>
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
<% fields_id = "add_new_fields_#{SecureRandom.hex(3)}" %>
|
||||||
|
<div id="<%= fields_id %>" class="space-y-3 mt-4 hidden" data-targets="dynamic-list.groups" data-multiple-classes="px-3 pt-3 pb-4 rounded-xl border border-dashed border-base-300"></div>
|
||||||
|
<native-menu class="<%= turbo_native_app? ? 'block' : 'dropdown block max-sm:hidden' %> mt-4 -mb-2">
|
||||||
|
<label tabindex="0" class="btn btn-ghost btn-sm normal-case font-normal w-full flex items-center justify-center border-dashed border-base-300 hover:border-base-content/20 !bg-transparent">
|
||||||
|
<%= svg_icon('users_plus', class: 'w-4 h-4 stroke-2') %>
|
||||||
|
<span><%= t('add_new') %></span>
|
||||||
|
</label>
|
||||||
|
<ul tabindex="0" class="<%= turbo_native_app? ? 'hidden' : 'z-10 dropdown-content p-2 mt-1 shadow menu bg-base-100 rounded-box' %>" style="left: 50%; --tw-translate-x: -50%">
|
||||||
|
<li>
|
||||||
|
<native-menu-action data-icon="user_plus" data-label="<%= t('recipients') %>" class="grid-cols-1 p-0">
|
||||||
|
<a href="#" class="flex items-center gap-2 px-4 py-2" data-action="click:dynamic-list#addItem">
|
||||||
|
<%= svg_icon('user_plus', class: 'w-4 h-4 stroke-2') %>
|
||||||
|
<span class="whitespace-nowrap"><%= t('recipients') %></span>
|
||||||
|
</a>
|
||||||
|
</native-menu-action>
|
||||||
|
</li>
|
||||||
|
<append-template class="contents" data-target-id="<%= fields_id %>">
|
||||||
|
<li>
|
||||||
|
<native-menu-action data-icon="eye_plus" data-label="<%= t('cc_view_only') %>" class="grid-cols-1 p-0">
|
||||||
|
<a href="#" class="flex items-center gap-2 px-4 py-2">
|
||||||
|
<%= svg_icon('eye_plus', class: 'w-4 h-4 stroke-2') %>
|
||||||
|
<span class="whitespace-nowrap"><%= t('cc_view_only') %></span>
|
||||||
|
</a>
|
||||||
|
</native-menu-action>
|
||||||
|
</li>
|
||||||
|
<template>
|
||||||
|
<submitter-item class="form-control">
|
||||||
|
<label class="label pt-0 pb-1">
|
||||||
|
<span class="label-text"><%= t('cc_view_only') %></span>
|
||||||
|
</label>
|
||||||
|
<div class="relative">
|
||||||
|
<submitters-autocomplete data-field="email">
|
||||||
|
<input type="email" name="cc_recipients[]" autocomplete="off" class="base-input !h-10 w-full pr-10" placeholder="<%= t('email') %>">
|
||||||
|
</submitters-autocomplete>
|
||||||
|
<a href="#" class="absolute right-2 top-2 block w-6 h-6 text-neutral-700 p-1 hover:text-base-content">
|
||||||
|
<%= svg_icon('trash', class: 'w-4 h-4') %>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</submitter-item>
|
||||||
|
</template>
|
||||||
|
</append-template>
|
||||||
|
<append-template class="contents" data-target-id="<%= fields_id %>">
|
||||||
|
<li>
|
||||||
|
<native-menu-action data-icon="mail_plus" data-label="<%= t('bcc_completed') %>" class="grid-cols-1 p-0">
|
||||||
|
<a href="#" class="flex items-center gap-2 px-4 py-2">
|
||||||
|
<%= svg_icon('mail_plus', class: 'w-4 h-4 stroke-2') %>
|
||||||
|
<span class="whitespace-nowrap"><%= t('bcc_completed') %></span>
|
||||||
|
</a>
|
||||||
|
</native-menu-action>
|
||||||
|
</li>
|
||||||
|
<template>
|
||||||
|
<div class="form-control">
|
||||||
|
<label class="label pt-0 pb-1">
|
||||||
|
<span class="label-text"><%= t('bcc_completed') %></span>
|
||||||
|
</label>
|
||||||
|
<div class="relative">
|
||||||
|
<input type="email" name="bcc_completed[]" autocomplete="off" class="base-input !h-10 w-full pr-10" placeholder="<%= t('email') %>">
|
||||||
|
<a href="#" class="absolute right-2 top-2 block w-6 h-6 text-neutral-700 p-1 hover:text-base-content">
|
||||||
|
<%= svg_icon('trash', class: 'w-4 h-4') %>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<% default_bcc_completed = template.preferences['bcc_completed'].presence || current_account.account_configs.find_by(key: AccountConfig::BCC_EMAILS)&.value %>
|
||||||
|
<% if default_bcc_completed.present? %>
|
||||||
|
<input type="hidden" name="bcc_completed[]" value="<%= default_bcc_completed %>">
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</append-template>
|
||||||
|
</ul>
|
||||||
|
</native-menu>
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
<% has_phone_field = false %>
|
<% has_phone_field = false %>
|
||||||
<%= form_for '', url: template_submissions_path(template), html: { class: 'space-y-4', autocomplete: 'off' }, data: { turbo_frame: :_top } do |f| %>
|
<%= form_for '', url: template_submissions_path(template), html: { autocomplete: 'off' }, data: { turbo_frame: :_top } do |f| %>
|
||||||
<% submitters = template.submitters.reject { |e| e['invite_by_uuid'].present? || e['optional_invite_by_uuid'].present? || e['invite_via_field_uuid'].present? } %>
|
<% submitters = template.submitters.reject { |e| e['invite_by_uuid'].present? || e['optional_invite_by_uuid'].present? || e['invite_via_field_uuid'].present? } %>
|
||||||
<dynamic-list class="space-y-4">
|
<dynamic-list class="block" data-multiple-classes="px-3 <%= submitters.size > 1 ? 'pt-3 pb-4' : 'py-4' %> rounded-xl border border-dashed border-base-300">
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div class="relative <%= 'p-4 rounded-xl bg-base-300/40 max-sm:p-0 max-sm:bg-transparent' unless local_assigns[:variables_form] %>" data-targets="dynamic-list.items">
|
<div class="relative" data-targets="dynamic-list.items">
|
||||||
<div>
|
<div>
|
||||||
<div class="absolute right-4 top-5">
|
<div class="absolute top-5" style="right: 12px">
|
||||||
<a href="#" data-action="click:dynamic-list#removeItem" class="<%= submitters.size == 1 ? 'right-2 top-1' : '-top-3' %> relative block w-6 h-6 rounded-lg text-neutral-700 text-center bg-base-300 p-1 hidden hover:bg-neutral hover:text-white">
|
<a href="#" data-action="click:dynamic-list#removeItem" class="<%= submitters.size == 1 ? 'right-2 top-1' : '-top-3' %> relative block w-6 h-6 text-neutral-700 p-1 hidden hover:text-base-content">
|
||||||
<%= svg_icon('trash', class: 'w-4 h-4') %>
|
<%= svg_icon('trash', class: 'w-4 h-4') %>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -86,14 +86,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% if params[:selfsign].blank? && local_assigns[:prefillable_fields].blank? && local_assigns[:recipient_form_fields].blank? && local_assigns[:variables_form].blank? %>
|
<% if params[:selfsign].blank? && local_assigns[:prefillable_fields].blank? && local_assigns[:recipient_form_fields].blank? && local_assigns[:variables_form].blank? %>
|
||||||
<a href="#" class="btn btn-primary btn-sm w-full flex items-center justify-center max-sm:hidden" data-action="click:dynamic-list#addItem">
|
<%= render 'add_new', template: %>
|
||||||
<%= svg_icon('user_plus', class: 'w-4 h-4 stroke-2') %>
|
|
||||||
<span><%= t('add_new') %></span>
|
|
||||||
</a>
|
|
||||||
<% end %>
|
<% end %>
|
||||||
</dynamic-list>
|
</dynamic-list>
|
||||||
<%= local_assigns[:variables_form] %>
|
<%= local_assigns[:variables_form] %>
|
||||||
<div>
|
<div class="mt-4">
|
||||||
<%= render('submitters_order', f:, template:) if can_send_emails %>
|
<%= render('submitters_order', f:, template:) if can_send_emails %>
|
||||||
<%= render 'send_email', f:, template:, can_send_emails:, viewer_submitter_uuids: local_assigns[:viewer_submitter_uuids] %>
|
<%= render 'send_email', f:, template:, can_send_emails:, viewer_submitter_uuids: local_assigns[:viewer_submitter_uuids] %>
|
||||||
<% if has_phone_field %>
|
<% if has_phone_field %>
|
||||||
@@ -101,7 +98,12 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
<%= render 'extra_fields', f: %>
|
<%= render 'extra_fields', f: %>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-control">
|
<div class="form-control mt-4">
|
||||||
<%= f.button button_title(title: t('add_recipients')), class: 'base-button' %>
|
<%= f.button class: 'base-button' do %>
|
||||||
|
<toggle-send-title>
|
||||||
|
<span><%= button_title(title: t('send'), disabled_with: t('sending')) %></span>
|
||||||
|
<span class="hidden"><%= button_title(title: t('add_recipients')) %></span>
|
||||||
|
</toggle-send-title>
|
||||||
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<%= form_for '', url: template_submissions_path(template), html: { class: 'space-y-4', autocomplete: 'off' }, data: { turbo_frame: :_top } do |f| %>
|
<%= form_for '', url: template_submissions_path(template), html: { autocomplete: 'off' }, data: { turbo_frame: :_top } do |f| %>
|
||||||
<% submitters = template.submitters.reject { |e| e['invite_by_uuid'].present? || e['optional_invite_by_uuid'].present? || e['invite_via_field_uuid'].present? } %>
|
<% submitters = template.submitters.reject { |e| e['invite_by_uuid'].present? || e['optional_invite_by_uuid'].present? || e['invite_via_field_uuid'].present? } %>
|
||||||
<% if submitters.size == 1 && local_assigns[:variables_form] %>
|
<% if submitters.size == 1 && local_assigns[:variables_form] %>
|
||||||
<% item = submitters.first %>
|
<% item = submitters.first %>
|
||||||
@@ -26,18 +26,18 @@
|
|||||||
<emails-textarea data-bulk-enabled="<%= Docuseal.demo? || !Docuseal.multitenant? || can?(:manage, :bulk_send) %>" data-limit="<%= Docuseal.multitenant? ? (can?(:manage, :bulk_send) ? 40 : 1) : nil %>">
|
<emails-textarea data-bulk-enabled="<%= Docuseal.demo? || !Docuseal.multitenant? || can?(:manage, :bulk_send) %>" data-limit="<%= Docuseal.multitenant? ? (can?(:manage, :bulk_send) ? 40 : 1) : nil %>">
|
||||||
<submitters-autocomplete data-field="email" class="block relative">
|
<submitters-autocomplete data-field="email" class="block relative">
|
||||||
<autoresize-textarea>
|
<autoresize-textarea>
|
||||||
<%= f.text_area :emails, required: true, class: 'base-textarea w-full !text-lg', placeholder: "#{t('type_emails_here')}...", rows: 2 %>
|
<%= f.text_area :emails, required: true, class: 'base-textarea block w-full !text-lg', placeholder: "#{t('type_emails_here')}...", rows: 2 %>
|
||||||
</autoresize-textarea>
|
</autoresize-textarea>
|
||||||
</submitters-autocomplete>
|
</submitters-autocomplete>
|
||||||
</emails-textarea>
|
</emails-textarea>
|
||||||
</submitter-item>
|
</submitter-item>
|
||||||
<% else %>
|
<% else %>
|
||||||
<dynamic-list class="space-y-4">
|
<dynamic-list class="block" data-multiple-classes="px-3 pt-3 pb-4 rounded-xl border border-dashed border-base-300">
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div class="relative <%= 'p-4 rounded-xl bg-base-300/40 max-sm:p-0 max-sm:bg-transparent' unless local_assigns[:variables_form] %>" data-targets="dynamic-list.items">
|
<div class="relative" data-targets="dynamic-list.items">
|
||||||
<div>
|
<div>
|
||||||
<div class="absolute right-4 top-5">
|
<div class="absolute top-5" style="right: 12px">
|
||||||
<a href="#" data-action="click:dynamic-list#removeItem" class="-top-3 relative block w-6 h-6 rounded-lg text-neutral-700 text-center bg-base-300 p-1 hidden hover:bg-neutral hover:text-white">
|
<a href="#" data-action="click:dynamic-list#removeItem" class="-top-3 relative block w-6 h-6 text-neutral-700 p-1 hidden hover:text-base-content">
|
||||||
<%= svg_icon('trash', class: 'w-4 h-4') %>
|
<%= svg_icon('trash', class: 'w-4 h-4') %>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -60,22 +60,24 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% if params[:selfsign].blank? && local_assigns[:variables_form].blank? %>
|
<% if params[:selfsign].blank? && local_assigns[:variables_form].blank? %>
|
||||||
<a href="#" class="btn btn-primary btn-sm w-full flex items-center justify-center max-sm:hidden" data-action="click:dynamic-list#addItem">
|
<%= render 'add_new', template: %>
|
||||||
<%= svg_icon('user_plus', class: 'w-4 h-4 stroke-2') %>
|
|
||||||
<span><%= t('add_new') %></span>
|
|
||||||
</a>
|
|
||||||
<% end %>
|
<% end %>
|
||||||
</dynamic-list>
|
</dynamic-list>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= local_assigns[:variables_form] %>
|
<%= local_assigns[:variables_form] %>
|
||||||
<div>
|
<div class="mt-4">
|
||||||
<%= render('submitters_order', f:, template:) if can_send_emails %>
|
<%= render('submitters_order', f:, template:) if can_send_emails %>
|
||||||
<%= render 'send_email', f:, template:, can_send_emails:, viewer_submitter_uuids: local_assigns[:viewer_submitter_uuids] %>
|
<%= render 'send_email', f:, template:, can_send_emails:, viewer_submitter_uuids: local_assigns[:viewer_submitter_uuids] %>
|
||||||
<%= render 'extra_fields', f: %>
|
<%= render 'extra_fields', f: %>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-control">
|
<div class="form-control mt-4">
|
||||||
<%= content_for(:submit_button) || capture do %>
|
<%= content_for(:submit_button) || capture do %>
|
||||||
<%= f.button button_title(title: t('add_recipients')), class: 'base-button' %>
|
<%= f.button class: 'base-button' do %>
|
||||||
|
<toggle-on-checked data-names="send_email send_sms">
|
||||||
|
<span><%= button_title(title: t('send'), disabled_with: t('sending')) %></span>
|
||||||
|
<span class="hidden"><%= button_title(title: t('add_recipients')) %></span>
|
||||||
|
</toggle-on-checked>
|
||||||
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
<%= form_for '', url: template_submissions_path(template), html: { class: 'space-y-4', autocomplete: 'off' }, data: { turbo_frame: :_top } do |f| %>
|
<%= form_for '', url: template_submissions_path(template), html: { autocomplete: 'off' }, data: { turbo_frame: :_top } do |f| %>
|
||||||
<% submitters = template.submitters.reject { |e| e['invite_by_uuid'].present? || e['optional_invite_by_uuid'].present? || e['invite_via_field_uuid'].present? } %>
|
<% submitters = template.submitters.reject { |e| e['invite_by_uuid'].present? || e['optional_invite_by_uuid'].present? || e['invite_via_field_uuid'].present? } %>
|
||||||
<dynamic-list class="space-y-4">
|
<dynamic-list class="block" data-multiple-classes="px-3 <%= submitters.size > 1 ? 'pt-3 pb-4' : 'py-4' %> rounded-xl border border-dashed border-base-300">
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div class="relative <%= 'p-4 rounded-xl bg-base-300/40 max-sm:p-0 max-sm:bg-transparent' unless local_assigns[:variables_form] %>" data-targets="dynamic-list.items">
|
<div class="relative" data-targets="dynamic-list.items">
|
||||||
<div>
|
<div>
|
||||||
<div class="absolute right-4 top-5">
|
<div class="absolute top-5" style="right: 12px">
|
||||||
<a href="#" data-action="click:dynamic-list#removeItem" class="<%= submitters.size == 1 ? 'right-2 top-1' : '-top-3' %> relative block w-6 h-6 rounded-lg text-neutral-700 text-center bg-base-300 p-1 hidden hover:bg-neutral hover:text-white">
|
<a href="#" data-action="click:dynamic-list#removeItem" class="<%= submitters.size == 1 ? 'right-2 top-1' : '-top-3' %> relative block w-6 h-6 text-neutral-700 p-1 hidden hover:text-base-content">
|
||||||
<%= svg_icon('trash', class: 'w-4 h-4') %>
|
<%= svg_icon('trash', class: 'w-4 h-4') %>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -48,19 +48,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% if params[:selfsign].blank? && local_assigns[:variables_form].blank? %>
|
<% if params[:selfsign].blank? && local_assigns[:variables_form].blank? %>
|
||||||
<a href="#" class="btn btn-primary btn-sm w-full flex items-center justify-center max-sm:hidden" data-action="click:dynamic-list#addItem">
|
<%= render 'add_new', template: %>
|
||||||
<%= svg_icon('user_plus', class: 'w-4 h-4 stroke-2') %>
|
|
||||||
<span><%= t('add_new') %></span>
|
|
||||||
</a>
|
|
||||||
<% end %>
|
<% end %>
|
||||||
</dynamic-list>
|
</dynamic-list>
|
||||||
<%= local_assigns[:variables_form] %>
|
<%= local_assigns[:variables_form] %>
|
||||||
<div>
|
<div class="mt-4">
|
||||||
<%= render 'submitters_order', f:, template: %>
|
<%= render 'submitters_order', f:, template: %>
|
||||||
<%= render 'send_sms', f:, checked: true %>
|
<%= render 'send_sms', f:, checked: true %>
|
||||||
<%= render 'extra_phone_fields', f: %>
|
<%= render 'extra_phone_fields', f: %>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-control">
|
<div class="form-control mt-4">
|
||||||
<%= f.button button_title(title: t('add_recipients')), class: 'base-button' %>
|
<%= f.button class: 'base-button' do %>
|
||||||
|
<toggle-on-checked data-names="send_email send_sms">
|
||||||
|
<span><%= button_title(title: t('send'), disabled_with: t('sending')) %></span>
|
||||||
|
<span class="hidden"><%= button_title(title: t('add_recipients')) %></span>
|
||||||
|
</toggle-on-checked>
|
||||||
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
<toggle-visible data-element-ids="<%= [message_field_id].to_json %>" class="flex">
|
<toggle-visible data-element-ids="<%= [message_field_id].to_json %>" class="flex">
|
||||||
<label>
|
<label>
|
||||||
<%= f.check_box :is_custom_message, checked: false, class: 'hidden peer', data: { action: 'change:toggle-visible#trigger', type: 'checkbox' } %>
|
<%= f.check_box :is_custom_message, checked: false, class: 'hidden peer', data: { action: 'change:toggle-visible#trigger', type: 'checkbox' } %>
|
||||||
<span class="link peer-checked:hidden"><%= t('edit_message') %></span>
|
<span class="link link-hover peer-checked:hidden"><%= t('edit_message') %></span>
|
||||||
</label>
|
</label>
|
||||||
</toggle-visible>
|
</toggle-visible>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
<% unless turbo_native_ios? %>
|
<% unless turbo_native_app? %>
|
||||||
<div class="form-control">
|
<toggle-on-checked data-names="is_custom_message" class="block">
|
||||||
<%= render 'sms_settings/placeholder' %>
|
<div class="form-control">
|
||||||
</div>
|
<%= render 'sms_settings/placeholder' %>
|
||||||
|
</div>
|
||||||
|
</toggle-on-checked>
|
||||||
<% end %>
|
<% end %>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<% unless turbo_native_ios? %>
|
<% unless turbo_native_app? %>
|
||||||
<div class="mt-2 mb-1">
|
<div class="mt-2 mb-1">
|
||||||
<div class="tooltip tooltip-no-touch w-full" data-tip="<%= t('unlock_with_docuseal_pro') %>">
|
<div class="tooltip tooltip-no-touch w-full" data-tip="<%= t('unlock_with_docuseal_pro') %>">
|
||||||
<%= link_to submitter.sent_at? ? t('re_send_sms') : t('send_sms'), Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premises" }.to_query}", class: 'btn btn-sm btn-primary text-gray-400 w-full' %>
|
<%= link_to submitter.sent_at? ? t('re_send_sms') : t('send_sms'), Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premises" }.to_query}", class: 'btn btn-sm btn-primary text-gray-400 w-full' %>
|
||||||
|
|||||||
@@ -6,14 +6,14 @@
|
|||||||
<% recipient_form_fields = Accounts.load_recipient_form_fields(current_account) if prefillable_fields.blank? %>
|
<% recipient_form_fields = Accounts.load_recipient_form_fields(current_account) if prefillable_fields.blank? %>
|
||||||
<% can_send_emails = Accounts.can_send_emails?(current_account) %>
|
<% can_send_emails = Accounts.can_send_emails?(current_account) %>
|
||||||
<% only_detailed = require_phone_2fa || require_email_2fa || prefillable_fields.present? || recipient_form_fields.present? %>
|
<% only_detailed = require_phone_2fa || require_email_2fa || prefillable_fields.present? || recipient_form_fields.present? %>
|
||||||
<% with_list = @template.variables_schema.blank? && (!turbo_native_ios? || can?(:manage, :bulk_send)) %>
|
<% with_list = @template.variables_schema.blank? && (!turbo_native_app? || can?(:manage, :bulk_send)) %>
|
||||||
<% with_phone = !turbo_native_ios? || can?(:manage, :sms_verification) %>
|
<% with_phone = !turbo_native_app? || can?(:manage, :sms_verification) %>
|
||||||
<% default_tab = 'email' if default_tab == 'phone' && !with_phone %>
|
<% default_tab = 'email' if default_tab == 'phone' && !with_phone %>
|
||||||
<% variables_form = render 'variables_form', schema: @template.variables_schema if @template.variables_schema.present? && @template.variables_schema.any? { |_, v| !v['disabled'] } %>
|
<% variables_form = render 'variables_form', schema: @template.variables_schema if @template.variables_schema.present? && @template.variables_schema.any? { |_, v| !v['disabled'] } %>
|
||||||
<%= render 'shared/turbo_modal_large', title: params[:selfsign] ? t('add_recipients') : t('add_new_recipients') do %>
|
<%= render 'shared/turbo_modal_large', title: params[:selfsign] ? t('add_recipients') : t('add_new_recipients') do %>
|
||||||
<% options = [only_detailed ? nil : [t('via_email'), 'email'], only_detailed || !with_phone ? nil : [t('via_phone'), 'phone'], [t('detailed'), 'detailed'], with_list ? [t('upload_list'), 'list'] : nil].compact %>
|
<% options = [only_detailed ? nil : [t('via_email'), 'email'], only_detailed || !with_phone ? nil : [t('via_phone'), 'phone'], [t('detailed'), 'detailed'], with_list ? [t('upload_list'), 'list'] : nil].compact %>
|
||||||
<% if options.size > 1 %>
|
<% if options.size > 1 %>
|
||||||
<toggle-visible data-element-ids="<%= options.map(&:last).to_json %>" class="relative text-center px-2 block <%= 'mt-4' unless turbo_native_app? %>">
|
<toggle-visible data-element-ids="<%= options.map(&:last).to_json %>" class="relative text-center px-2 block <%= turbo_native_app? ? ('mt-1.5' if turbo_native_android?) : 'mt-4' %> <%= 'max-sm:hidden' if options.one? { |_, value| value != 'list' } %>">
|
||||||
<div class="flex justify-center">
|
<div class="flex justify-center">
|
||||||
<% options.each_with_index do |(label, value), index| %>
|
<% options.each_with_index do |(label, value), index| %>
|
||||||
<%= content_tag(value == 'list' ? 'span' : 'toggle-cookies', data: { value:, key: 'add_recipients_tab' }) do %>
|
<%= content_tag(value == 'list' ? 'span' : 'toggle-cookies', data: { value:, key: 'add_recipients_tab' }) do %>
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</toggle-visible>
|
</toggle-visible>
|
||||||
<% end %>
|
<% end %>
|
||||||
<div class="px-5 mb-5 mt-4">
|
<div class="px-5 mb-5 mt-5">
|
||||||
<% unless only_detailed %>
|
<% unless only_detailed %>
|
||||||
<div id="email" class="<%= 'hidden' if default_tab != 'email' %>">
|
<div id="email" class="<%= 'hidden' if default_tab != 'email' %>">
|
||||||
<%= render 'email_form', template: @template, variables_form:, can_send_emails:, viewer_submitter_uuids: %>
|
<%= render 'email_form', template: @template, variables_form:, can_send_emails:, viewer_submitter_uuids: %>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<% with_timestamp_seconds = configs.find { |e| e.key == AccountConfig::WITH_TIMESTAMP_SECONDS_KEY }&.value == true %>
|
<% with_timestamp_seconds = configs.find { |e| e.key == AccountConfig::WITH_TIMESTAMP_SECONDS_KEY }&.value == true %>
|
||||||
<% with_signature_id_reason = configs.find { |e| e.key == AccountConfig::WITH_SIGNATURE_ID_REASON_KEY }&.value != false %>
|
<% with_signature_id_reason = configs.find { |e| e.key == AccountConfig::WITH_SIGNATURE_ID_REASON_KEY }&.value != false %>
|
||||||
<% with_signature_id_completed_at = configs.find { |e| e.key == AccountConfig::WITH_SIGNATURE_ID_COMPLETED_AT_KEY }&.value == true %>
|
<% with_signature_id_completed_at = configs.find { |e| e.key == AccountConfig::WITH_SIGNATURE_ID_COMPLETED_AT_KEY }&.value == true %>
|
||||||
<main style="max-width: 1600px" class="mx-auto pl-4">
|
<div style="max-width: 1600px" class="mx-auto pl-4">
|
||||||
<% unless native_tab == 'recipients' %>
|
<% unless native_tab == 'recipients' %>
|
||||||
<div class="flex justify-between items-center pr-4 sticky top-0 md:relative z-10 bg-base-100 <%= 'py-1.5' unless turbo_native_app? %>">
|
<div class="flex justify-between items-center pr-4 sticky top-0 md:relative z-10 bg-base-100 <%= 'py-1.5' unless turbo_native_app? %>">
|
||||||
<% unless turbo_native_app? %>
|
<% unless turbo_native_app? %>
|
||||||
@@ -54,7 +54,8 @@
|
|||||||
<% if @submission.submitters.to_a.any?(&:completed_at?) %>
|
<% if @submission.submitters.to_a.any?(&:completed_at?) %>
|
||||||
<% show_combined_download = @submission.completed_at? && !is_combined_enabled %>
|
<% show_combined_download = @submission.completed_at? && !is_combined_enabled %>
|
||||||
<% show_unarchive = signed_in? && can?(:destroy, @submission) && @submission.archived_at? && @submission.completed_at? %>
|
<% show_unarchive = signed_in? && can?(:destroy, @submission) && @submission.archived_at? && @submission.completed_at? %>
|
||||||
<% show_download_dropdown = show_combined_download || show_unarchive %>
|
<% show_event_log_download = signed_in? && can?(:manage, :event_log_download) && can?(:read, @submission) %>
|
||||||
|
<% show_download_dropdown = show_combined_download || show_event_log_download || show_unarchive %>
|
||||||
<% if @submission.completed_at? || !is_combined_enabled %>
|
<% if @submission.completed_at? || !is_combined_enabled %>
|
||||||
<div class="join relative">
|
<div class="join relative">
|
||||||
<native-action data-label="<%= t('download') %>" data-icon="download">
|
<native-action data-label="<%= t('download') %>" data-icon="download">
|
||||||
@@ -95,6 +96,9 @@
|
|||||||
</li>
|
</li>
|
||||||
</native-action>
|
</native-action>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
<% if show_event_log_download %>
|
||||||
|
<%= render 'submissions/download_event_log' %>
|
||||||
|
<% end %>
|
||||||
<% if show_unarchive %>
|
<% if show_unarchive %>
|
||||||
<native-action data-label="<%= t('unarchive') %>" data-icon="rotate" data-section="archive">
|
<native-action data-label="<%= t('unarchive') %>" data-icon="rotate" data-section="archive">
|
||||||
<li>
|
<li>
|
||||||
@@ -163,6 +167,7 @@
|
|||||||
<% value = values[field['uuid']].presence || (field['default_value'] != '{{date}}' && field['readonly'] == true && field['conditions'].blank? && field['default_value'].present? ? Submitters::SubmitValues.template_default_value_for_submitter(field['default_value'], @submission.submitters.find { |e| e.uuid == field['submitter_uuid'] }, with_time: false) : nil) %>
|
<% value = values[field['uuid']].presence || (field['default_value'] != '{{date}}' && field['readonly'] == true && field['conditions'].blank? && field['default_value'].present? ? Submitters::SubmitValues.template_default_value_for_submitter(field['default_value'], @submission.submitters.find { |e| e.uuid == field['submitter_uuid'] }, with_time: false) : nil) %>
|
||||||
<% value ||= field['default_value'] if field['type'] == 'heading' %>
|
<% value ||= field['default_value'] if field['type'] == 'heading' %>
|
||||||
<% submitter = submitters_index[field['submitter_uuid']] %>
|
<% submitter = submitters_index[field['submitter_uuid']] %>
|
||||||
|
<% value = nil if field['type'] == 'signature' && submitter && !submitter.completed_at? %>
|
||||||
<% if value.present? %>
|
<% if value.present? %>
|
||||||
<% if (mask = field.dig('preferences', 'mask').presence) && signed_in? && can?(:read, @submission) %>
|
<% if (mask = field.dig('preferences', 'mask').presence) && signed_in? && can?(:read, @submission) %>
|
||||||
<span class="group">
|
<span class="group">
|
||||||
@@ -222,39 +227,39 @@
|
|||||||
</div>
|
</div>
|
||||||
<% if submitter&.name.present? %>
|
<% if submitter&.name.present? %>
|
||||||
<div class="flex items-center space-x-1 mt-1">
|
<div class="flex items-center space-x-1 mt-1">
|
||||||
<%= svg_icon('user', class: 'w-5 h-5') %>
|
<%= svg_icon('user', class: 'w-5 h-5 shrink-0') %>
|
||||||
<span>
|
<span class="truncate">
|
||||||
<%= submitter&.name %>
|
<%= submitter&.name %>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if submitter&.email.present? %>
|
<% if submitter&.email.present? %>
|
||||||
<div class="flex items-center space-x-1 mt-1">
|
<div class="flex items-center space-x-1 mt-1">
|
||||||
<%= svg_icon('mail', class: 'w-5 h-5') %>
|
<%= svg_icon('mail', class: 'w-5 h-5 shrink-0') %>
|
||||||
<span>
|
<span class="truncate">
|
||||||
<%= submitter.email || 'N/A' %>
|
<%= submitter.email || 'N/A' %>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if submitter&.phone.present? %>
|
<% if submitter&.phone.present? %>
|
||||||
<div class="flex items-center space-x-1 mt-1">
|
<div class="flex items-center space-x-1 mt-1">
|
||||||
<%= svg_icon('phone', class: 'w-5 h-5') %>
|
<%= svg_icon('phone', class: 'w-5 h-5 shrink-0') %>
|
||||||
<span>
|
<span class="truncate">
|
||||||
<%= submitter.phone %>
|
<%= submitter.phone %>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<div class="flex items-center space-x-1 mt-1">
|
<div class="flex items-center space-x-1 mt-1">
|
||||||
<% if submitter&.viewer? %>
|
<% if submitter&.viewer? %>
|
||||||
<%= svg_icon(submitter.opened_at? ? 'file_check' : 'file_unknown', class: 'w-5 h-5') %>
|
<%= svg_icon(submitter.opened_at? ? 'file_check' : 'file_unknown', class: 'w-5 h-5 shrink-0') %>
|
||||||
<% elsif submitter && !submitter.completed_at? && @submission.completed_at? %>
|
<% elsif submitter && !submitter.completed_at? && @submission.completed_at? %>
|
||||||
<%= svg_icon('writing_off', class: 'w-5 h-5') %>
|
<%= svg_icon('writing_off', class: 'w-5 h-5 shrink-0') %>
|
||||||
<% elsif @submission.expire_at? && submitter && !submitter.completed_at? %>
|
<% elsif @submission.expire_at? && submitter && !submitter.completed_at? %>
|
||||||
<%= svg_icon('clock_exclamation', class: 'w-5 h-5') %>
|
<%= svg_icon('clock_exclamation', class: 'w-5 h-5 shrink-0') %>
|
||||||
<% else %>
|
<% else %>
|
||||||
<%= svg_icon('writing', class: 'w-5 h-5') %>
|
<%= svg_icon('writing', class: 'w-5 h-5 shrink-0') %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<span>
|
<span class="truncate">
|
||||||
<% if submitter&.declined_at? %>
|
<% if submitter&.declined_at? %>
|
||||||
<%= t('declined_on_time', time: l(submitter.declined_at.in_time_zone(@submission.account.timezone), format: :short, locale: @submission.account.locale)) %>
|
<%= t('declined_on_time', time: l(submitter.declined_at.in_time_zone(@submission.account.timezone), format: :short, locale: @submission.account.locale)) %>
|
||||||
<% elsif submitter&.viewer? %>
|
<% elsif submitter&.viewer? %>
|
||||||
@@ -320,6 +325,7 @@
|
|||||||
<% value = values[field['uuid']].presence || (field['default_value'] != '{{date}}' && field['readonly'] == true && field['default_value'].present? ? Submitters::SubmitValues.template_default_value_for_submitter(field['default_value'], @submission.submitters.find { |e| e.uuid == field['submitter_uuid'] }, with_time: false) : nil) %>
|
<% value = values[field['uuid']].presence || (field['default_value'] != '{{date}}' && field['readonly'] == true && field['default_value'].present? ? Submitters::SubmitValues.template_default_value_for_submitter(field['default_value'], @submission.submitters.find { |e| e.uuid == field['submitter_uuid'] }, with_time: false) : nil) %>
|
||||||
<% next if value.blank? %>
|
<% next if value.blank? %>
|
||||||
<% next if field['type'] == 'heading' || field['type'] == 'strikethrough' %>
|
<% next if field['type'] == 'heading' || field['type'] == 'strikethrough' %>
|
||||||
|
<% next if field['type'] == 'signature' && submitter && !submitter.completed_at? %>
|
||||||
<div class="pt-2.5 border-b border-base-300">
|
<div class="pt-2.5 border-b border-base-300">
|
||||||
<div class="text-xs font-medium uppercase mb-0.5" dir="auto">
|
<div class="text-xs font-medium uppercase mb-0.5" dir="auto">
|
||||||
<%= field['name'].presence || "#{t("#{field['type']}_field")} #{submitter_field_counters[field['type']]}" %>
|
<%= field['name'].presence || "#{t("#{field['type']}_field")} #{submitter_field_counters[field['type']]}" %>
|
||||||
@@ -388,7 +394,7 @@
|
|||||||
</label>
|
</label>
|
||||||
</toggle-visible>
|
</toggle-visible>
|
||||||
<% end %>
|
<% end %>
|
||||||
</main>
|
</div>
|
||||||
<% unless request.headers['HTTP_X_TURBO_REQUEST_ID'] %>
|
<% unless request.headers['HTTP_X_TURBO_REQUEST_ID'] %>
|
||||||
<%= render 'scripts/autosize_field' %>
|
<%= render 'scripts/autosize_field' %>
|
||||||
<% end %>
|
<% end %>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<% filter_params = params.permit(:q, :archived, *Submissions::Filter::ALLOWED_PARAMS) %>
|
<% filter_params = params.permit(:q, :archived, *Submissions::Filter::ALLOWED_PARAMS) %>
|
||||||
<%= render 'shared/turbo_modal', title: t('export'), close_after_submit: false do %>
|
<%= render 'shared/turbo_modal', title: t('export'), close_after_submit: false do %>
|
||||||
<div class="space-y-2">
|
<div class="space-y-2 <%= 'mt-1.5 px-4' if turbo_native_android? %>">
|
||||||
<%= button_to template_submissions_export_index_path(@template), params: { format: :xlsx, **filter_params }, method: :get, data: { turbo_frame: :_top } do %>
|
<%= button_to template_submissions_export_index_path(@template), params: { format: :xlsx, **filter_params }, method: :get, data: { turbo_frame: :_top } do %>
|
||||||
<div class="flex items-center p-4 text-left rounded-2xl border border-neutral-300 hover:cursor-pointer hover:bg-neutral hover:text-gray-300">
|
<div class="flex items-center p-4 text-left rounded-2xl border border-neutral-300 hover:cursor-pointer hover:bg-neutral hover:text-gray-300">
|
||||||
<div class="enabled">
|
<div class="enabled">
|
||||||
|
|||||||
Loaded 100 of 169 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user