Compare commits

..
57 Commits
Author SHA1 Message Date
Alex Turchyn 6c416dd76e Merge from docusealco/wip 2026-11-02 14:44:26 +02:00
Pete Matsyburka 1cc4ca6251 adjust events log 2026-11-02 12:28:21 +02:00
Pete Matsyburka a27423c845 update gem 2026-11-02 11:19:14 +02:00
Pete Matsyburka 2692b8a1b5 add index 2026-11-02 10:18:16 +02:00
Pete Matsyburka 7a75a6c887 optimize query 2026-11-02 09:48:37 +02:00
Pete Matsyburka 236b882558 update security.md 2026-11-01 13:42:44 +02:00
Pete Matsyburka 7ee648dacb add index 2026-11-01 12:38:59 +02:00
Pete Matsyburka 9bfa6f7f4a fix rubocop 2026-10-30 11:03:27 +02:00
Pete Matsyburka f9c12b9d78 mobile fixes 2026-10-29 21:24:07 +02:00
Pete Matsyburka 0a3f182e70 fix timezone 2026-10-29 10:51:45 +02:00
Pete Matsyburka 9c5c1f47f8 update gems 2026-10-29 10:36:11 +02:00
Pete Matsyburka 14c0ad2d16 add json log formatter 2026-10-29 09:59:21 +02:00
Pete Matsyburka 6736d8fe68 mobile fixes 2026-10-28 19:01:18 +02:00
Pete Matsyburka 4ecd830587 android fixes 2026-10-28 15:35:43 +02:00
Alex Turchyn a0ecc8e823 support the android app 2026-10-28 10:47:26 +02:00
Pete Matsyburka 8334b612a8 fix enable shared link 2026-10-28 09:33:46 +02:00
Pete Matsyburka 349c0b0537 optimize query 2026-10-27 15:44:08 +02:00
Pete Matsyburka 2af09afc66 add lang 2026-10-27 15:31:15 +02:00
Pete Matsyburka e22a3d9ef3 use uuid v7 2026-10-27 11:24:42 +02:00
Pete Matsyburka d3c92cecfa carry lang param 2026-10-26 17:57:03 +02:00
Pete Matsyburka af4672084d optimize query 2026-10-26 17:14:45 +02:00
Pete Matsyburka b7103cf010 cleanup 2026-10-26 16:33:55 +02:00
Alex Turchyn 13a1835c00 Merge from docusealco/wip 2026-10-26 12:27:46 +02:00
Pete Matsyburka c35407d7a9 cleanup routes 2026-10-25 21:43:38 +02:00
Pete Matsyburka e55ccc0b76 update dep 2026-10-25 16:09:58 +02:00
Pete Matsyburka b8d00870b4 optimize query 2026-10-25 15:02:22 +02:00
Pete Matsyburka 5413fbc248 add template_id to submitters 2026-10-25 09:39:47 +02:00
Pete Matsyburka 2d2eb7ce4d ruby 4.0.9 2026-10-23 23:42:14 +03:00
Pete Matsyburka ed2ddbf74e remove legacy route 2026-10-23 11:10:14 +03:00
Pete Matsyburka b99e5d1262 adjust autosave 2026-10-23 10:02:32 +03:00
Pete Matsyburka 87f6878eda fix brakeman 2026-10-23 08:29:39 +03:00
Pete Matsyburka 99b815eb4b fix generation 2026-10-23 08:25:07 +03:00
Pete Matsyburka e40a29b9ab update certs 2026-10-22 18:47:09 +03:00
Pete Matsyburka 151890bb72 add autosave config 2026-10-22 17:17:47 +03:00
Pete Matsyburka 78edd87cc1 add main tag 2026-10-22 15:01:54 +03:00
Pete Matsyburka 88c99a2b35 fix turbo morph 2026-10-22 14:48:03 +03:00
Pete Matsyburka 282b8cfa73 fix search 2026-10-22 11:02:24 +03:00
Pete Matsyburka e9888c7466 add validation 2026-10-21 15:41:53 +03:00
Pete Matsyburka 0647befb3a escape email variables 2026-10-21 09:54:29 +03:00
Pete Matsyburka 85aa3d0451 mask email field value 2026-10-21 09:35:38 +03:00
Pete Matsyburka 6d2b3bb654 adjust test webhook 2026-10-20 20:15:37 +03:00
Pete Matsyburka b394854e9d adjust send form 2026-10-20 09:50:21 +03:00
Pete Matsyburka 2e586e930e adjust signature validation 2026-10-19 19:03:26 +03:00
Alex Turchyn 8f8f69f0bc Merge from docusealco/wip 2026-10-19 14:57:12 +03:00
Pete Matsyburka 04663d55eb update deps 2026-10-19 11:10:48 +03:00
Pete Matsyburka 93d0b935e2 fix dynamic document 2026-10-17 13:17:52 +03:00
Pete Matsyburka 829b2b2855 disable jemalloc 2026-10-17 10:42:20 +03:00
Alex Turchyn cf6de444f4 disable the review form for native apps 2026-10-16 23:22:32 +03:00
Pete Matsyburka 126f5d58a2 convert docx 2026-10-16 20:18:10 +03:00
Pete Matsyburka 09b93067f4 fix close modal 2026-10-16 19:56:36 +03:00
Pete Matsyburka f14458f238 fix generate 2026-10-16 19:56:36 +03:00
Pete Matsyburka 445139f05c adjust mobile nav 2026-10-16 19:56:36 +03:00
Pete Matsyburka e5e4965f05 adjust kba 2026-10-16 19:56:36 +03:00
Pete Matsyburka 1c8f7b0316 use jemalloc 2026-10-15 15:28:54 +03:00
Alex Turchyn 9fe3656652 fix icons shrinking 2026-10-14 08:11:45 +03:00
Pete Matsyburka 17ae46bd61 make remember signature permanent 2026-10-13 16:45:42 +03:00
Pete Matsyburka a036483938 tenants test flag 2026-10-13 09:23:38 +03:00
143 changed files with 738 additions and 522 deletions

No files matched your search

+4 -4
View File
@@ -12,7 +12,7 @@ jobs:
- name: Install Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 4.0.5
ruby-version: 4.0.9
- name: Cache gems
uses: actions/cache@v4
with:
@@ -37,7 +37,7 @@ jobs:
- name: Install Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 4.0.5
ruby-version: 4.0.9
- name: Cache gems
uses: actions/cache@v4
with:
@@ -89,7 +89,7 @@ jobs:
- name: Install Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 4.0.5
ruby-version: 4.0.9
- name: Cache gems
uses: actions/cache@v4
with:
@@ -113,7 +113,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 15
container:
image: ruby:4.0.5-alpine
image: ruby:4.0.9-alpine
services:
postgres:
+8 -5
View File
@@ -1,4 +1,4 @@
FROM ruby:4.0.5-alpine AS download
FROM ruby:4.0.9-alpine AS download
WORKDIR /fonts
@@ -17,7 +17,7 @@ RUN apk --no-cache add wget unzip && \
mkdir -p /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 NODE_ENV=production
@@ -44,7 +44,7 @@ COPY ./app/views ./app/views
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 BUNDLE_WITHOUT="development:test"
@@ -52,7 +52,8 @@ ENV OPENSSL_CONF=/etc/openssl_legacy.cnf
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
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
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/ && \
chown -R docuseal:docuseal /app/tmp/cache
@@ -100,6 +101,8 @@ ENV HOME=/home/docuseal
ENV WORKDIR=/data/docuseal
ENV VIPS_MAX_COORD=17000
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
CMD ["/app/bin/bundle", "exec", "puma", "-C", "/app/config/puma.rb", "--dir", "/app"]
+2 -1
View File
@@ -2,7 +2,7 @@
source 'https://rubygems.org'
ruby '4.0.5'
ruby '4.0.9'
gem 'addressable'
gem 'arabic-letter-connector', require: false
@@ -45,6 +45,7 @@ gem 'trilogy', require: false
gem 'turbo-rails'
gem 'twitter_cldr', require: false
gem 'tzinfo-data'
gem 'wordcraft', require: false, group: :ce
gem 'xlsxtream', require: false
group :development, :test do
+89 -78
View File
@@ -3,29 +3,29 @@ GEM
specs:
action_text-trix (2.1.19)
railties
actioncable (8.1.3.1)
actionpack (= 8.1.3.1)
activesupport (= 8.1.3.1)
actioncable (8.1.4)
actionpack (= 8.1.4)
activesupport (= 8.1.4)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
zeitwerk (~> 2.6)
actionmailbox (8.1.3.1)
actionpack (= 8.1.3.1)
activejob (= 8.1.3.1)
activerecord (= 8.1.3.1)
activestorage (= 8.1.3.1)
activesupport (= 8.1.3.1)
actionmailbox (8.1.4)
actionpack (= 8.1.4)
activejob (= 8.1.4)
activerecord (= 8.1.4)
activestorage (= 8.1.4)
activesupport (= 8.1.4)
mail (>= 2.8.0)
actionmailer (8.1.3.1)
actionpack (= 8.1.3.1)
actionview (= 8.1.3.1)
activejob (= 8.1.3.1)
activesupport (= 8.1.3.1)
actionmailer (8.1.4)
actionpack (= 8.1.4)
actionview (= 8.1.4)
activejob (= 8.1.4)
activesupport (= 8.1.4)
mail (>= 2.8.0)
rails-dom-testing (~> 2.2)
actionpack (8.1.3.1)
actionview (= 8.1.3.1)
activesupport (= 8.1.3.1)
actionpack (8.1.4)
actionview (= 8.1.4)
activesupport (= 8.1.4)
nokogiri (>= 1.8.5)
rack (>= 2.2.4)
rack-session (>= 1.0.1)
@@ -33,36 +33,36 @@ GEM
rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6)
useragent (~> 0.16)
actiontext (8.1.3.1)
actiontext (8.1.4)
action_text-trix (~> 2.1.15)
actionpack (= 8.1.3.1)
activerecord (= 8.1.3.1)
activestorage (= 8.1.3.1)
activesupport (= 8.1.3.1)
actionpack (= 8.1.4)
activerecord (= 8.1.4)
activestorage (= 8.1.4)
activesupport (= 8.1.4)
globalid (>= 0.6.0)
nokogiri (>= 1.8.5)
actionview (8.1.3.1)
activesupport (= 8.1.3.1)
actionview (8.1.4)
activesupport (= 8.1.4)
builder (~> 3.1)
erubi (~> 1.11)
rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6)
activejob (8.1.3.1)
activesupport (= 8.1.3.1)
activejob (8.1.4)
activesupport (= 8.1.4)
globalid (>= 0.3.6)
activemodel (8.1.3.1)
activesupport (= 8.1.3.1)
activerecord (8.1.3.1)
activemodel (= 8.1.3.1)
activesupport (= 8.1.3.1)
activemodel (8.1.4)
activesupport (= 8.1.4)
activerecord (8.1.4)
activemodel (= 8.1.4)
activesupport (= 8.1.4)
timeout (>= 0.4.0)
activestorage (8.1.3.1)
actionpack (= 8.1.3.1)
activejob (= 8.1.3.1)
activerecord (= 8.1.3.1)
activesupport (= 8.1.3.1)
activestorage (8.1.4)
actionpack (= 8.1.4)
activejob (= 8.1.4)
activerecord (= 8.1.4)
activesupport (= 8.1.4)
marcel (~> 1.0)
activesupport (8.1.3.1)
activesupport (8.1.4)
base64
bigdecimal
concurrent-ruby (~> 1.0, >= 1.3.1)
@@ -116,7 +116,7 @@ GEM
erubi (~> 1.4)
parser (>= 2.4)
smart_properties
bigdecimal (4.1.2)
bigdecimal (4.1.3)
bindex (0.8.1)
bootsnap (1.23.0)
msgpack (~> 1.2)
@@ -149,14 +149,14 @@ GEM
crack (1.0.1)
bigdecimal
rexml
crass (1.0.7)
crass (1.0.8)
csv (3.3.5)
csv-safe (3.3.1)
csv (~> 3.0)
cuprite (0.17)
capybara (~> 3.0)
ferrum (~> 0.17.0)
date (3.5.1)
date (3.6.0)
debug (1.11.1)
irb (~> 1.10)
reline (>= 0.3.8)
@@ -179,7 +179,7 @@ GEM
dotenv (3.2.0)
drb (2.2.3)
email_typo (0.2.3)
erb (6.0.6)
erb (6.0.7)
erb_lint (0.9.0)
activesupport
better_html (>= 2.0.1)
@@ -195,8 +195,8 @@ GEM
railties (>= 6.1.0)
faker (3.6.1)
i18n (>= 1.8.11, < 2)
faraday (2.14.3)
faraday-net_http (>= 2.0, < 3.5)
faraday (2.15.1)
faraday-net_http (>= 2.0, < 3.6)
json
logger
faraday-follow_redirects (0.5.0)
@@ -264,14 +264,14 @@ GEM
strscan (>= 3.1.2)
i18n (1.15.2)
concurrent-ruby (~> 1.0)
io-console (0.8.2)
io-console (0.9.5)
irb (1.18.0)
pp (>= 0.6.0)
prism (>= 1.3.0)
rdoc (>= 4.0.0)
reline (>= 0.4.2)
jmespath (1.6.2)
json (2.21.2)
json (3.1.0)
jwt (3.2.0)
base64
language_server-protocol (3.17.0.5)
@@ -310,17 +310,17 @@ GEM
drb (~> 2.0)
prism (~> 1.5)
msgpack (1.8.4)
multi_json (1.19.1)
multi_json (1.21.2)
net-http (0.9.1)
uri (>= 0.11.1)
net-imap (0.6.6)
net-imap (0.6.8)
date
net-protocol
net-pop (0.1.2)
net-protocol
net-protocol (0.2.2)
net-protocol (0.4.0)
timeout
net-smtp (0.5.1)
net-smtp (0.5.2)
net-protocol
nio4r (2.7.5)
nokogiri (1.19.4-aarch64-linux-gnu)
@@ -361,7 +361,7 @@ GEM
prettyprint
pretender (1.0.0)
actionpack (>= 7.2)
prettyprint (0.2.0)
prettyprint (0.2.1)
prism (1.9.0)
pry (0.16.0)
coderay (~> 1.1)
@@ -373,30 +373,30 @@ GEM
puma (8.0.2)
nio4r (~> 2.0)
racc (1.8.1)
rack (3.2.6)
rack (3.2.7)
rack-proxy (0.7.7)
rack
rack-session (2.1.2)
base64 (>= 0.1.0)
rack (>= 3.0.0)
rack-test (2.2.0)
rack-test (2.3.0)
rack (>= 1.3)
rackup (2.3.1)
rack (>= 3)
rails (8.1.3.1)
actioncable (= 8.1.3.1)
actionmailbox (= 8.1.3.1)
actionmailer (= 8.1.3.1)
actionpack (= 8.1.3.1)
actiontext (= 8.1.3.1)
actionview (= 8.1.3.1)
activejob (= 8.1.3.1)
activemodel (= 8.1.3.1)
activerecord (= 8.1.3.1)
activestorage (= 8.1.3.1)
activesupport (= 8.1.3.1)
rails (8.1.4)
actioncable (= 8.1.4)
actionmailbox (= 8.1.4)
actionmailer (= 8.1.4)
actionpack (= 8.1.4)
actiontext (= 8.1.4)
actionview (= 8.1.4)
activejob (= 8.1.4)
activemodel (= 8.1.4)
activerecord (= 8.1.4)
activestorage (= 8.1.4)
activesupport (= 8.1.4)
bundler (>= 1.15.0)
railties (= 8.1.3.1)
railties (= 8.1.4)
rails-dom-testing (2.3.0)
activesupport (>= 5.0.0)
minitest
@@ -407,9 +407,9 @@ GEM
rails-i18n (8.1.0)
i18n (>= 0.7, < 2)
railties (>= 8.0.0, < 9)
railties (8.1.3.1)
actionpack (= 8.1.3.1)
activesupport (= 8.1.3.1)
railties (8.1.4)
actionpack (= 8.1.4)
activesupport (= 8.1.4)
irb (~> 1.13)
rackup (>= 1.0.0)
rake (>= 12.2)
@@ -418,11 +418,11 @@ GEM
zeitwerk (~> 2.6)
rainbow (3.1.1)
rake (13.4.2)
rbs (4.1.0)
rbs (4.2.0)
logger
prism (>= 1.6.0)
tsort
rdoc (8.0.0)
rdoc (8.1.0)
erb
prism (>= 1.6.0)
rbs (>= 4.0.0)
@@ -430,7 +430,7 @@ GEM
redis-client (0.29.0)
connection_pool
regexp_parser (2.11.3)
reline (0.6.3)
reline (0.7.0)
io-console (~> 0.5)
representable (3.2.0)
declarative (< 0.1.0)
@@ -466,14 +466,14 @@ GEM
rspec-mocks (>= 3.13.0, < 5.0.0)
rspec-support (>= 3.13.0, < 5.0.0)
rspec-support (3.13.7)
rubocop (1.86.0)
json (~> 2.3)
rubocop (1.92.1)
json (>= 2.3)
language_server-protocol (~> 3.17.0.2)
lint_roller (~> 1.1.0)
parallel (~> 1.10)
parallel (>= 1.10)
parser (>= 3.3.0.2)
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)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 4.0)
@@ -555,7 +555,7 @@ GEM
unicode-emoji (~> 4.1)
unicode-emoji (4.2.0)
uniform_notifier (1.18.0)
uri (1.1.1)
uri (1.1.2)
useragent (0.16.11)
warden (1.2.9)
rack (>= 2.0.9)
@@ -572,12 +572,22 @@ GEM
base64
websocket-extensions (>= 0.1.0)
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)
zip_kit (>= 6.2, < 7)
xpath (3.2.0)
nokogiri (~> 1.8)
yaml (0.4.0)
zeitwerk (2.8.2)
zeitwerk (2.8.3)
zip_kit (6.3.4)
PLATFORMS
@@ -650,10 +660,11 @@ DEPENDENCIES
tzinfo-data
web-console
webmock
wordcraft
xlsxtream
RUBY VERSION
ruby 4.0.5
ruby 4.0.9
BUNDLED WITH
4.0.3
+1
View File
@@ -9,6 +9,7 @@ Out of scope vulnerabilities:
- CSRF
- DNSSEC, CAA, CSP headers
- DNS or email security related
- SSRF
- Rate Limiting
Note: We reserve the right to classify any reported vulnerability as out of scope for the bug bounty program.
+1 -2
View File
@@ -54,14 +54,13 @@ class AccountsController < ApplicationController
email: true_user.email.sub('@', '+removed@'))
true_user.account.update!(archived_at: Time.current)
# rubocop:disable Layout/LineLength
# rubocop:disable-next Layout/LineLength
render turbo_stream: turbo_stream.replace(
: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')) +
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'))
)
# rubocop:enable Layout/LineLength
end
private
@@ -11,7 +11,7 @@ module Api
before_action :set_noindex_headers
before_action :set_security_headers
# rubocop:disable Metrics
# rubocop:disable-next Metrics
def show
blob_uuid, purp, exp = ApplicationRecord.signed_id_verifier.verified(params[:signed_uuid])
@@ -53,7 +53,6 @@ module Api
end
end
end
# rubocop:enable Metrics
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
+20 -6
View File
@@ -36,7 +36,7 @@ module Api
render json: Submitters::SerializeForApi.call(@submitter, with_template: true, with_events: true, params:)
end
# rubocop:disable Metrics/MethodLength
# rubocop:disable-next Metrics/MethodLength
def update
submission = @submitter.submission
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
end
# rubocop:enable Metrics/MethodLength
def 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(slug: params[:slug]) if params[:slug].present?
submitters = submitters.where(submission_id: params[:submission_id]) if params[:submission_id].present?
if params[:template_id].present?
submitters = submitters.joins(:submission).where(submissions: { template_id: params[:template_id] })
end
submitters = filter_submitters_by_template_id(submitters, params) if params[:template_id].present?
maybe_filter_by_completed_at(submitters, params)
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)
submitter.external_id = attrs[:application_key] if attrs.key?(:application_key)
submitter.external_id = attrs[:external_id] if attrs.key?(:external_id)
@@ -30,7 +30,7 @@ module Mcp
}
}.freeze
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
# rubocop:disable-next Metrics/AbcSize, Metrics/MethodLength
def call
account = current_user.account
@@ -87,6 +87,5 @@ module Mcp
edit_url: edit_template_url(@template)
)
end
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
end
end
@@ -66,7 +66,7 @@ module Mcp
}
}.freeze
# rubocop:disable Metrics
# rubocop:disable-next Metrics
def call
@template = Template.accessible_by(current_ability).find(mcp_params['template_id'])
@@ -115,6 +115,5 @@ module Mcp
rescue Submissions::CreateFromSubmitters::BaseError => e
render_tool_error(e.message)
end
# rubocop:enable Metrics
end
end
+1 -2
View File
@@ -1,9 +1,8 @@
# frozen_string_literal: true
class PasswordsController < Devise::PasswordsController
# rubocop:disable Rails/LexicallyScopedActionFilter
# rubocop:disable-next Rails/LexicallyScopedActionFilter
skip_before_action :require_no_authentication, only: %i[edit update]
# rubocop:enable Rails/LexicallyScopedActionFilter
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.flock(File::LOCK_EX)
# rubocop:disable Style/ZeroLengthPredicate
# rubocop:disable-next Style/ZeroLengthPredicate
if file.size.zero?
cleanup_stale_tempfiles
@@ -63,7 +63,6 @@ class PreviewDocumentPageController < ActionController::API
else
FileUtils.touch(file_path)
end
# rubocop:enable Style/ZeroLengthPredicate
file.flock(File::LOCK_UN)
@@ -14,7 +14,7 @@ class SendSubmissionEmailController < ApplicationController
template = Template.find_by!(slug: params[:template_slug])
@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]
submission = Submission.find_by!(slug: params[:submission_slug])
+10 -6
View File
@@ -34,7 +34,7 @@ class StartFormController < ApplicationController
@submitter = find_or_initialize_submitter(@template, submitter_params)
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
if Templates.filter_undefined_submitters(@template.submitters).size > 1 && @submitter.new_record?
@error_message = multiple_submitters_error_message
@@ -59,7 +59,7 @@ class StartFormController < ApplicationController
Submitters::StartForm.assign_start_form_cookie(@submitter, request)
redirect_to submit_form_path(@submitter.slug)
redirect_to submit_form_path(@submitter.slug, lang:)
else
render :show, status: :unprocessable_content
end
@@ -80,7 +80,7 @@ class StartFormController < ApplicationController
raise ActionController::RoutingError, I18n.t('not_found') if required_params.any? { |_, v| v.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)
.find_by!(required_params.except('name'))
end
@@ -150,7 +150,7 @@ class StartFormController < ApplicationController
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?)
redirect_to submit_form_path(submitter.slug)
redirect_to submit_form_path(submitter.slug, lang:)
else
if defined?(Rollbar) && template.preferences['shared_link_2fa'] != true
Rollbar.info("2FA link requested: #{submitter.id}")
@@ -167,10 +167,14 @@ class StartFormController < ApplicationController
render :error, status: :unprocessable_content
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
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)
end
def lang
params[:lang].presence
end
end
@@ -16,21 +16,21 @@ class StartFormEmail2faSendController < ApplicationController
.submitters.new(**submitter_params, account_id: @template.account_id)
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
Submitters.send_shared_link_email_verification_code(@submitter, request:)
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
rescue Submitters::BouncedEmail => e
@error_message = e.message
render 'start_form/error', status: :unprocessable_content
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
end
@@ -66,4 +66,8 @@ class StartFormEmail2faSendController < ApplicationController
attrs[:email] = Submissions.normalize_email(attrs[:email])
end
end
def lang
params[:lang].presence
end
end
@@ -25,5 +25,7 @@ class SubmissionsExportController < ApplicationController
end
end
def new; end
def new
render layout: turbo_native_android? ? 'plain' : true
end
end
@@ -14,6 +14,6 @@ class SubmissionsFiltersController < ApplicationController
def show
return head :not_found unless ALLOWED_NAMES.include?(params[:name])
render params[:name]
render params[:name], layout: turbo_native_android? ? 'plain' : true
end
end
+7 -3
View File
@@ -20,7 +20,7 @@ class SubmitFormController < ApplicationController
return render :email_2fa unless Submitters::AuthorizedForForm.pass_email_2fa?(@submitter, request)
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
@form_configs = Submitters::FormConfigs.call(@submitter, CONFIG_KEYS)
@@ -96,7 +96,7 @@ class SubmitFormController < ApplicationController
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
def success; end
@@ -114,7 +114,7 @@ class SubmitFormController < ApplicationController
def maybe_require_link_2fa
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
def maybe_render_locked_page
@@ -144,4 +144,8 @@ class SubmitFormController < ApplicationController
ActiveStorage::Attachment.where(record: submission.submitters, name: :attachments)
.preload(:blob).index_by(&:uuid)
end
def lang
params[:lang].presence
end
end
@@ -20,17 +20,17 @@ class SubmitFormEmail2fasController < ApplicationController
{ value: @submitter.slug, expires: Submitters::StartForm::COOKIES_TTL.from_now,
**Submitters::StartForm::COOKIES_DEFAULTS }
redirect_to submit_form_path(@submitter.slug)
redirect_to submit_form_path(@submitter.slug, lang:)
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
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
def update
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
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)
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
SendSubmitterVerificationEmailJob.perform_async('submitter_id' => @submitter.id, 'locale' => I18n.locale.to_s)
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
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
def load_submitter
@submitter = Submitter.find_by!(slug: params[:submitter_slug])
end
def lang
params[:lang].presence
end
end
@@ -34,7 +34,7 @@ class SubmitFormUploadController < ActionController::API
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)
cookies.permanent.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])
@@ -41,7 +41,9 @@ class TemplateFoldersController < ApplicationController
render_infinite_scroll(@pagy, @templates, @template_folders) if turbo_infinite_scroll?
end
def edit; end
def edit
render layout: turbo_native_android? ? 'plain' : true
end
def update
if @template_folder != current_account.default_template_folder &&
@@ -8,7 +8,9 @@ class TemplateSharingsTestingController < ApplicationController
end
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'
TemplateSharing.create!(ability: :manage, account: testing_account, template: @template)
@@ -7,6 +7,8 @@ class TemplatesCloneController < ApplicationController
authorize!(:create, Template)
@template = Template.new(name: "#{@base_template.name} (#{I18n.t('clone')})")
render layout: turbo_native_android? ? 'plain' : true
end
def create
@@ -3,7 +3,9 @@
class TemplatesFoldersController < ApplicationController
load_and_authorize_resource :template
def edit; end
def edit
render layout: turbo_native_android? ? 'plain' : true
end
def update
authorize!(:update, @template)
+3 -1
View File
@@ -4,7 +4,9 @@ class UserInitialsController < ApplicationController
before_action :load_user_config
authorize_resource :user_config
def edit; end
def edit
render layout: turbo_native_android? ? 'plain' : true
end
def update
file = params[:file]
@@ -4,7 +4,9 @@ class UserSignaturesController < ApplicationController
before_action :load_user_config
authorize_resource :user_config
def edit; end
def edit
render layout: turbo_native_android? ? 'plain' : true
end
def update
file = params[:file]
+6 -2
View File
@@ -31,9 +31,13 @@ class UsersController < ApplicationController
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
existing_user = User.accessible_by(current_ability).find_by(email: @user.email)
@@ -66,7 +66,7 @@ class WebhookSettingsController < ApplicationController
SendTestWebhookRequestJob.perform_async(
'submitter_id' => submitter.id,
'event_uuid' => SecureRandom.uuid,
'event_uuid' => SecureRandom.uuid_v7,
'webhook_url_id' => @webhook_url.id
)
+1
View File
@@ -228,6 +228,7 @@ safeRegisterElement('template-builder', class extends HTMLElement {
template,
customFields: reactive(JSON.parse(this.dataset.customFields || '[]')),
dateFormats: JSON.parse(this.dataset.dateFormats || '[]'),
autosave: this.dataset.autosave !== 'false',
dynamicDocuments: reactive(JSON.parse(this.dataset.dynamicDocuments || '[]')),
backgroundColor: '#faf7f5',
locale: this.dataset.locale,
+1 -1
View File
@@ -22,7 +22,7 @@ export default class extends HTMLElement {
}
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')
if (flash?.dataset.alert) return { alert: flash.dataset.alert }
+1 -1
View File
@@ -55,7 +55,7 @@ export default targetable(class extends HTMLElement {
this.input.files = dataTransfer.files
if (this.input.webkitEntries.length) {
if (this.input.webkitEntries?.length) {
this.input.dataset.file = `${dataTransfer.files[0].name}`
}
+14 -2
View File
@@ -12,7 +12,14 @@ export default class extends HTMLElement {
toggle = (event) => {
if (event?.type === 'input' && event.target.matches(this.fieldSelector('name'))) return
const isNameOnly = (field) => this.isFilled('name') && !this.isFilled(field)
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'))
@@ -29,12 +36,17 @@ export default class extends HTMLElement {
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']
return ['change', 'input', 'linked-input.update', 'focusin', 'focusout']
}
get form () {
+16 -7
View File
@@ -3,11 +3,19 @@
v-if="!error"
class="label text-xl sm:text-2xl py-0 mb-2 sm:mb-3.5 field-name-label"
>
<MarkdownContent
v-if="field.title"
:string="field.title"
/>
<template v-else>{{ field.name || 'Knowledge Based Authentication' }}</template>
<span class="flex items-end">
<MarkdownContent
v-if="field.title"
:string="field.title"
/>
<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
v-if="questions"
class="float-right font-normal text-neutral-500 mt-1 whitespace-nowrap text-right text-sm"
@@ -282,13 +290,14 @@
<script>
import MarkdownContent from './markdown_content'
import { IconInnerShadowTop } from '@tabler/icons-vue'
import { IconInnerShadowTop, IconInfoCircle } from '@tabler/icons-vue'
export default {
name: 'KbaStep',
components: {
MarkdownContent,
IconInnerShadowTop
IconInnerShadowTop,
IconInfoCircle
},
inject: ['baseUrl', 't'],
props: {
+51 -4
View File
@@ -415,9 +415,9 @@
<a
:href="`/templates/${template.id}/form`"
: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"
@click.exact="isPreviewLoading = true"
@click.exact="onSaveAndPreviewClick"
>
<IconInnerShadowTop
v-if="isPreviewLoading"
@@ -640,6 +640,7 @@
:draw-field-type="drawFieldType"
:draw-custom-field="drawCustomField"
:draw-option="drawOption"
:autosave="autosave"
@update="onDynamicDocumentUpdate"
@draw="clearDrawField"
@add-custom-field="addCustomField"
@@ -771,7 +772,7 @@
class="sticky bottom-0 z-20"
>
<native-action
v-if="nativePlatform && withDocumentsList && (isBreakpointLg || isMobile) && sortedDocuments.length && editable"
v-if="nativePlatform && withDocumentsList && (isBreakpointLg || isMobile) && editable"
data-placement="drawer"
data-side="leading"
data-icon="files"
@@ -781,7 +782,7 @@
data-view="documents"
/>
<native-action
v-if="nativePlatform && withFieldsList && isBreakpointLg && sortedDocuments.length && editable"
v-if="nativePlatform && withFieldsList && isBreakpointLg && editable"
data-placement="drawer"
data-side="trailing"
data-icon="plus"
@@ -1379,6 +1380,7 @@ export default {
isPreviewLoading: false,
isLoadingBlankPage: false,
isSaving: false,
hasUnsavedChanges: false,
isDetectingPageFields: false,
detectFieldsQueue: [],
detectingAnalyzingProgress: null,
@@ -1662,6 +1664,11 @@ export default {
window.addEventListener('dragleave', this.onWindowDragLeave)
if (!this.autosave) {
window.addEventListener('beforeunload', this.onBeforeUnload)
document.addEventListener('turbo:before-visit', this.onBeforeVisit)
}
this.$nextTick(() => {
if (document.location.search?.includes('stripe_connect_success')) {
document.querySelector('form[action="/auth/stripe_connect"]')?.closest('.dropdown')?.querySelector('label')?.focus()
@@ -1685,6 +1692,11 @@ export default {
this.resizeObserver.disconnect()
window.removeEventListener('dragleave', this.onWindowDragLeave)
if (!this.autosave) {
window.removeEventListener('beforeunload', this.onBeforeUnload)
document.removeEventListener('turbo:before-visit', this.onBeforeVisit)
}
},
beforeUpdate () {
this.documentRefs = []
@@ -2241,6 +2253,37 @@ export default {
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 () {
document.activeElement.blur()
},
@@ -4036,6 +4079,8 @@ export default {
}
if (!this.autosave && !force) {
this.hasUnsavedChanges = true
return Promise.resolve({})
}
@@ -4058,6 +4103,8 @@ export default {
}),
headers: { 'Content-Type': 'application/json' }
}).then(() => {
this.hasUnsavedChanges = false
if (this.onSave) {
this.onSave(this.template)
}
@@ -7,7 +7,7 @@
class="absolute top-0 bottom-0 right-0 left-0"
@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
v-if="!inline"
class="flex justify-between items-center border-b pb-2 mb-2 font-medium"
@@ -33,7 +33,7 @@
>{{ t('available_in_pro') }}</a>
</div>
<form @submit.prevent="validateSaveAndClose">
<div class="my-4">
<div :class="inline && nativePlatform === 'android' ? 'mt-1 mb-4' : 'my-4'">
<div
v-for="(condition, cindex) in conditions"
:key="cindex"
@@ -168,7 +168,7 @@
<script>
export default {
name: 'ConditionModal',
inject: ['t', 'template', 'withConditions'],
inject: ['t', 'template', 'withConditions', 'nativePlatform'],
props: {
item: {
type: Object,
@@ -7,7 +7,7 @@
class="absolute top-0 bottom-0 right-0 left-0"
@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
v-if="!inline"
class="flex justify-between items-center border-b pb-2 mb-2 font-medium"
@@ -27,7 +27,7 @@
<div>
<label
dir="auto"
class="label text-sm"
:class="['label text-sm', { 'pt-0 mt-1': inline && nativePlatform === 'android' }]"
for="description_field"
>
{{ t('description') }}
@@ -71,7 +71,7 @@
<script>
export default {
name: 'DescriptionModal',
inject: ['t', 'template'],
inject: ['t', 'template', 'nativePlatform'],
props: {
field: {
type: Object,
@@ -36,7 +36,7 @@
<CropView
v-if="cropPageItem"
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"
:page="cropPageItem"
:image-url="thumbUrl(cropPageItem)"
@@ -47,7 +47,7 @@
<RedactView
v-else-if="redactPageItem"
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"
:page="redactPageItem"
:image-url="thumbUrl(redactPageItem)"
@@ -63,14 +63,14 @@
>
<div
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
v-for="(doc, docIndex) in layout"
:key="doc.attachmentUuid"
:data-uuid="doc.attachmentUuid"
class="relative"
:style="inline ? { scrollMarginTop: '86px' } : null"
:style="inline && nativePlatform === 'ios' ? { scrollMarginTop: '86px' } : null"
>
<div
class="flex items-center pb-2"
@@ -191,7 +191,7 @@
:key="page.id"
:data-page-id="page.id"
class="relative min-w-0 cursor-pointer group"
:style="inline ? { scrollMarginTop: '86px' } : null"
:style="inline && nativePlatform === 'ios' ? { scrollMarginTop: '86px' } : null"
draggable="true"
@click="selectedPageId = page.id"
@mousedown="onPageMousedown(page, $event)"
@@ -260,7 +260,7 @@
</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="{ 'pb-3': !inline }"
:class="{ 'pb-3': !inline || nativePlatform === 'android' }"
>
<div class="grid grid-cols-4 gap-2 md:block md:space-y-2">
<button
@@ -419,7 +419,7 @@ export default {
IconEraser,
IconCrop
},
inject: ['t', 'baseFetch'],
inject: ['t', 'baseFetch', 'nativePlatform'],
props: {
template: {
type: Object,
@@ -84,6 +84,11 @@ export default {
required: false,
default: true
},
autosave: {
type: Boolean,
required: false,
default: true
},
selectedSubmitter: {
type: Object,
required: false,
@@ -279,7 +284,7 @@ export default {
this.updatedSectionEditors.set(section.id, editor)
this.saveTimer = setTimeout(() => this.updateSectionsAndSave(), 1000)
this.saveTimer = setTimeout(() => this.autosave ? this.updateSectionsAndSave() : this.updateSections(), 1000)
},
loadListsStyle () {
this.listsStyle = this.bodyDom.querySelector('body > style[data-lists]')?.textContent || null
@@ -299,7 +304,9 @@ export default {
updateVariablesSchema () {
this.document.variables_schema = buildVariablesSchema(this.bodyDom.body)
},
updateSectionsAndSave () {
updateSections () {
delete this.saveTimer
this.updatedSectionEditors.forEach((editor, sectionId) => {
const target = this.bodyDom.getElementById(sectionId)
@@ -315,6 +322,9 @@ export default {
this.updateVariablesSchema()
this.$emit('update', this.document)
},
updateSectionsAndSave () {
this.updateSections()
return this.saveBody()
},
@@ -27,13 +27,17 @@ tiptapStylesheet.replaceSync(
font-feature-settings: "liga" 0;
display: flex;
flex-flow: column nowrap;
min-height: inherit;
flex-grow: 1;
}
.ProseMirror > article {
.ProseMirror > article:last-of-type {
margin-bottom: auto;
}
.ProseMirror p.doc-trailing-break::after {
content: none;
}
.ProseMirror [contenteditable="false"] {
white-space: normal;
}
@@ -608,10 +608,10 @@ export default {
return !['stamp', 'heading', 'strikethrough'].includes(this.field.type)
},
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 () {
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 () {
return this.withRequired && !['phone', 'stamp', 'verification', 'strikethrough', 'heading'].includes(this.field.type)
@@ -484,7 +484,7 @@
</label>
</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"
>
<label
@@ -500,7 +500,7 @@
</label>
</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"
>
<label
+1 -1
View File
@@ -275,7 +275,7 @@
</div>
</button>
<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-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')"
@@ -7,7 +7,7 @@
class="absolute top-0 bottom-0 right-0 left-0"
@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
v-if="!inline"
class="flex justify-between items-center border-b pb-2 mb-2 font-medium"
@@ -21,7 +21,7 @@
@click.prevent="$emit('close')"
>&times;</a>
</div>
<div class="mt-4">
<div :class="inline && nativePlatform === 'android' ? 'mt-1' : 'mt-4'">
<div>
<div class="flex flex-wrap items-center gap-1.5">
<span>
@@ -200,7 +200,7 @@ export default {
components: {
IconChevronDown
},
inject: ['t', 'template'],
inject: ['t', 'template', 'nativePlatform'],
props: {
field: {
type: Object,
@@ -7,7 +7,7 @@
class="absolute top-0 bottom-0 right-0 left-0"
@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
v-if="!inline"
class="flex justify-between items-center border-b pb-2 mb-2 font-medium"
@@ -32,7 +32,7 @@
class="link"
>{{ t('available_in_pro') }}</a>
</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
v-for="f in fields"
:key="f.uuid"
@@ -152,7 +152,7 @@ export default {
IconCodePlus,
IconMathFunction
},
inject: ['t', 'template', 'withFormula'],
inject: ['t', 'template', 'withFormula', 'nativePlatform'],
props: {
field: {
type: Object,
+7
View File
@@ -70,6 +70,7 @@ const en = {
remove_condition: 'Remove condition',
add_condition: 'Add condition',
are_you_sure_: 'Are you sure?',
close_without_saving_changes: 'Close without saving changes?',
sign_yourself: 'Sign Yourself',
set_signing_date: 'Set signing date',
signing_date: 'Signing Date',
@@ -371,6 +372,7 @@ const es = {
down: 'Abajo',
set_signing_date: 'Establecer fecha de firma',
are_you_sure_: '¿Estás seguro?',
close_without_saving_changes: '¿Cerrar sin guardar los cambios?',
sign_yourself: 'Firma tú mismo',
checked: 'Marcado',
current_date: 'Fecha actual',
@@ -652,6 +654,7 @@ const it = {
remove_condition: 'Rimuovi condizione',
add_condition: 'Aggiungi condizione',
are_you_sure_: 'Sei sicuro?',
close_without_saving_changes: 'Chiudere senza salvare le modifiche?',
sign_yourself: 'Firma te stesso',
set_signing_date: 'Imposta data di firma',
signing_date: 'Data di firma',
@@ -953,6 +956,7 @@ const pt = {
down: 'Para baixo',
set_signing_date: 'Definir data de assinatura',
are_you_sure_: 'Tem certeza?',
close_without_saving_changes: 'Fechar sem salvar as alterações?',
sign_yourself: 'Assine você mesmo',
checked: 'Marcado',
current_date: 'Data atual',
@@ -1234,6 +1238,7 @@ const fr = {
remove_condition: 'Supprimer la condition',
add_condition: 'Ajouter une condition',
are_you_sure_: 'Êtes-vous sûr ?',
close_without_saving_changes: 'Fermer sans enregistrer les modifications ?',
sign_yourself: 'Signer vous-même',
set_signing_date: 'Définir la date de signature',
signing_date: 'Date de signature',
@@ -1525,6 +1530,7 @@ const de = {
remove_condition: 'Bedingung entfernen',
add_condition: 'Bedingung hinzufügen',
are_you_sure_: 'Sind Sie sicher?',
close_without_saving_changes: 'Ohne Speichern der Änderungen schließen?',
sign_yourself: 'Selbst unterschreiben',
set_signing_date: 'Unterzeichnungsdatum festlegen',
signing_date: 'Unterzeichnungsdatum',
@@ -1816,6 +1822,7 @@ const nl = {
remove_condition: 'Voorwaarde verwijderen',
add_condition: 'Voorwaarde toevoegen',
are_you_sure_: 'Weet u het zeker?',
close_without_saving_changes: 'Sluiten zonder wijzigingen op te slaan?',
sign_yourself: 'Zelf ondertekenen',
set_signing_date: 'Ondertekeningsdatum instellen',
signing_date: 'Ondertekeningsdatum',
@@ -203,7 +203,7 @@
>{{ t('learn_more') }}</a>
</div>
<li
v-if="withFormula || nativePlatform !== 'ios'"
v-if="withFormula || !nativePlatform"
class="field-settings-formula mb-1"
>
<label
@@ -233,7 +233,7 @@
</label>
</li>
<li
v-if="withCondition && (withConditions || nativePlatform !== 'ios')"
v-if="withCondition && (withConditions || !nativePlatform)"
class="field-settings-condition mt-1"
>
<label
@@ -5,7 +5,7 @@
class="absolute top-0 bottom-0 right-0 left-0"
@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
v-if="!inline"
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 {
name: 'RevisionsModal',
components: { IconUser, IconInnerShadowTop },
inject: ['t', 'baseFetch'],
inject: ['t', 'baseFetch', 'nativePlatform'],
props: {
template: {
type: Object,
@@ -259,7 +259,7 @@ export default {
return true
},
showCondition () {
return this.withCondition && (this.withConditions || this.nativePlatform !== 'ios')
return this.withCondition && (this.withConditions || !this.nativePlatform)
},
fieldNames: FieldType.computed.fieldNames,
fieldLabels: FieldType.computed.fieldLabels
+2 -2
View File
@@ -100,7 +100,7 @@ class ProcessSubmitterCompletionJob
WebhookUrls.for_account_id(submitter.account_id, %w[form.completed submission.completed]).each do |webhook|
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,
'event_uuid' => event_uuids['form.completed'],
@@ -109,7 +109,7 @@ class ProcessSubmitterCompletionJob
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,
'event_uuid' => event_uuids['submission.completed'],
+8 -6
View File
@@ -30,14 +30,16 @@ class SendTestWebhookRequestJob
raise LocalhostError, "Can't send to localhost." if uri.host.in?(SendWebhookRequest::LOCALHOSTS)
end
Faraday.post(webhook_url.url,
{
event_type: 'form.completed',
timestamp: Time.current.iso8601,
data: Submitters::SerializeForWebhook.call(submitter)
}.to_json,
body_json = {
event_type: 'form.completed',
timestamp: Time.current.iso8601,
data: Submitters::SerializeForWebhook.call(submitter)
}.to_json
Faraday.post(webhook_url.url, body_json,
'Content-Type' => 'application/json',
'User-Agent' => USER_AGENT,
'X-Docuseal-Signature' => WebhookUrls::Signatures.sign(webhook_url.hmac_secret, body: body_json),
**webhook_url.secret.to_h)
end
end
+2 -1
View File
@@ -15,7 +15,8 @@
#
# Indexes
#
# index_accounts_on_uuid (uuid) UNIQUE
# index_accounts_on_created_at (created_at)
# index_accounts_on_uuid (uuid) UNIQUE
#
class Account < ApplicationRecord
attribute :uuid, :string, default: -> { SecureRandom.uuid }
+1
View File
@@ -61,6 +61,7 @@ class AccountConfig < ApplicationRecord
DOCUMENT_FILENAME_FORMAT_KEY = 'document_filename_format'
TEMPLATE_CUSTOM_FIELDS_KEY = 'template_custom_fields'
TEMPLATE_DATE_FORMATS_KEY = 'template_date_formats'
TEMPLATE_BUILDER_AUTOSAVE_KEY = 'template_builder_autosave'
POLICY_LINKS_KEY = 'policy_links'
ENABLE_MCP_KEY = 'enable_mcp'
+9 -1
View File
@@ -25,15 +25,17 @@
# account_id :bigint not null
# external_id :string
# submission_id :bigint not null
# template_id :bigint
#
# 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_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_slug (slug) UNIQUE
# index_submitters_on_submission_id (submission_id)
# index_submitters_on_template_id_and_id (template_id,id)
#
# Foreign Keys
#
@@ -68,6 +70,8 @@ class Submitter < ApplicationRecord
scope :completed, -> { where.not(completed_at: nil) }
before_validation :set_template_id, on: :create
after_destroy :anonymize_email_events, if: -> { Docuseal.multitenant? }
def status
@@ -124,6 +128,10 @@ class Submitter < ApplicationRecord
private
def set_template_id
self.template_id = submission&.template_id
end
def anonymize_email_events
email_events.each do |event|
event.update!(email: Digest::MD5.base64digest(event.email))
+1 -1
View File
@@ -22,7 +22,7 @@
# index_webhook_events_on_webhook_url_id_and_id (webhook_url_id,id)
#
class WebhookEvent < ApplicationRecord
attribute :uuid, :string, default: -> { SecureRandom.uuid }
attribute :uuid, :string, default: -> { SecureRandom.uuid_v7 }
belongs_to :webhook_url, optional: true
belongs_to :account, optional: true
+2 -2
View File
@@ -133,7 +133,7 @@
<% end %>
<% end %>
<% 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| %>
<%= f.hidden_field :key %>
<div class="flex items-center justify-between gap-4 py-2.5">
@@ -156,7 +156,7 @@
<% end %>
<% end %>
<% 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| %>
<%= f.hidden_field :key %>
<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">
<td>
<div class="flex items-center space-x-1">
+17
View File
@@ -7,4 +7,21 @@
-webkit-user-drag: 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>
+3 -3
View File
@@ -33,10 +33,10 @@
<% end %>
<turbo-frame id="drawer"></turbo-frame>
<%= render turbo_native_app? ? 'shared/native_navbar' : 'shared/navbar' %>
<% if flash.present? %><%= render 'shared/flash' %><% end %>
<div class="max-w-6xl mx-auto px-4 md:px-2 mb-8">
<main class="max-w-6xl mx-auto px-4 md:px-2 mb-8">
<%= yield %>
</div>
</main>
<% if flash.present? %><%= render 'shared/flash' %><% end %>
<%= render 'shared/body_scripts' %>
</body>
</html>
+3 -1
View File
@@ -13,6 +13,8 @@
<%= stylesheet_pack_tag 'form', media: 'all', data: { turbo_track: 'dynamic' } %>
</head>
<body>
<%= yield %>
<main>
<%= yield %>
</main>
</body>
</html>
+4 -2
View File
@@ -13,8 +13,10 @@
<%= stylesheet_pack_tag 'application', media: 'all', data: { turbo_track: 'dynamic' } %>
</head>
<body>
<% if flash.present? %><%= render 'shared/flash' %><% end %>
<%= yield %>
<main>
<%= yield %>
</main>
<turbo-frame id="modal"></turbo-frame>
<% if flash.present? %><%= render 'shared/flash' %><% end %>
</body>
</html>
@@ -22,7 +22,7 @@
<% end %>
</div>
<%= 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">
<h2 class="text-2xl md:text-3xl font-bold">
<%= t('sign_request_email_reminders') %>
@@ -10,7 +10,7 @@
<%= render 'documents_copy_email_form' %>
<%= render 'submitter_completed_email_form' %>
</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">
<%= t('company_logo') %>
</p>
+10 -8
View File
@@ -1,11 +1,13 @@
<script type="application/json" id="native_path_configuration"><%= render partial: 'layouts/native_path_configuration', formats: :json %></script>
<% if signed_in? %>
<native-action data-placement="profile" data-icon="user">
<%= link_to t('profile'), settings_profile_index_path %>
</native-action>
<native-action data-placement="profile" data-icon="settings">
<%= link_to t('settings'), settings_path %>
</native-action>
<% unless turbo_native_android? %>
<native-action data-placement="profile" data-icon="user">
<%= link_to t('profile'), settings_profile_index_path %>
</native-action>
<native-action data-placement="profile" data-icon="settings">
<%= link_to t('settings'), settings_path %>
</native-action>
<% end %>
<% 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') %>">
<%= button_to '', testing_account_path, method: :delete, form: { data: { turbo_action: 'replace' } } %>
@@ -22,13 +24,13 @@
<% end %>
<% if can?(:create, ::Template) && controller_name.in?(%w[templates_dashboard submissions_dashboard template_folders templates_archived submissions_archived]) %>
<% 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="pencil_plus">
<%= link_to t('create'), new_template_path(folder_name: folder_name), data: { turbo_frame: :modal, native_modal: :medium } %>
</native-action>
<% 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="pencil_plus">
<%= link_to t('create'), new_template_path(folder_name: folder_name), data: { turbo_frame: :modal, native_modal: :medium } %>
+2 -2
View File
@@ -1,5 +1,5 @@
<%= 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">
<a href="<%= root_path %>" class="text-2xl font-bold items-center flex space-x-2">
<%= render 'shared/title' %>
@@ -106,4 +106,4 @@
<% end %>
</div>
<% end %>
</div>
</nav>
+5 -5
View File
@@ -43,7 +43,7 @@
<%= link_to t('storage'), settings_storage_index_path, class: 'text-base hover:bg-base-300', data: nav_link_data %>
</li>
<% 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>
<%= link_to 'SMS', settings_sms_path, class: 'text-base hover:bg-base-300', data: nav_link_data %>
</li>
@@ -84,7 +84,7 @@
</li>
<% 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>
<%= 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') %>
@@ -93,21 +93,21 @@
</li>
<% end %>
<% if !Docuseal.demo? && can?(:manage, EncryptedConfig) && (current_user == true_user || current_account.testing?) %>
<% if Docuseal.multitenant? || !turbo_native_ios? %>
<% unless turbo_native_app? %>
<li>
<%= link_to Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}#{'/test' if current_account.testing?}/api") : "#{Docuseal::CONSOLE_URL}/on_premises", class: 'text-base hover:bg-base-300', data: { turbo: false } do %>
<% if Docuseal.multitenant? %> API <% else %> <%= t('console') %> <% end %>
<% end %>
</li>
<% end %>
<% if Docuseal.multitenant? %>
<% if Docuseal.multitenant? && !turbo_native_app? %>
<li>
<%= link_to console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}#{'/test' if current_account.testing?}/embedding/form"), class: 'text-base hover:bg-base-300', data: { turbo: false } do %>
<%= t('embedding') %>
<% end %>
</li>
<% end %>
<% if (!Docuseal.multitenant? || can?(:manage, :saml_sso)) && can?(:read, EncryptedConfig.new(key: 'saml_configs', account: current_account)) && true_user == current_user && (!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>
<%= link_to 'SSO', settings_sso_index_path, class: 'text-base hover:bg-base-300', data: nav_link_data %>
</li>
@@ -1,7 +1,9 @@
<form action="<%= url_for %>" method="get" class="dropdown dropdown-top hidden md:inline">
<label tabindex="0" class="btn btn-sm h-10">
<%= svg_icon('arrow_sort', class: 'w-5 h-5') %>
</label>
<% unless turbo_native_app? %>
<label tabindex="0" class="btn btn-sm h-10">
<%= 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">
<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">
+1 -1
View File
@@ -2,7 +2,7 @@
<% content_for(:native_modal, 'true') %>
<% content_for(:native_title, local_assigns[:title]) if local_assigns[:title] %>
<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 %>
</native-modal>
</turbo-frame>
+2 -2
View File
@@ -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">
<div class="flex items-center justify-center">
@@ -32,5 +32,5 @@
</toggle-submit>
<% end %>
</div>
</main>
</div>
<%= render 'shared/attribution', link_path: '/start', account: @submitter.account %>
@@ -2,7 +2,7 @@
<% 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)) %>
<% 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">
<div class="flex items-center justify-center">
@@ -23,7 +23,7 @@
<div>
<%= t('we_sent_a_one_time_verification_code_to_your_email_address_please_enter_the_code_below_to_continue') %>
</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">
<%= f.hidden_field 'resubmit', value: params[:resubmit] %>
<%= f.hidden_field 'submitter[name]', value: params[:name] || params.dig(:submitter, :name) %>
@@ -52,7 +52,7 @@
<% if @resubmit_submitter %>
<%= button_to t(:re_send_email), resubmit_form_path, params: { resubmit: @resubmit_submitter.slug }, method: :put, id: 'resend_code', class: 'hidden' %>
<% 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 %>
</div>
</main>
</div>
+2 -2
View File
@@ -2,7 +2,7 @@
<% 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)) %>
<% 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">
<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' %>
</div>
</div>
</main>
</div>
+2 -2
View File
@@ -2,7 +2,7 @@
<% I18n.with_locale(@template.account.locale) do %>
<% content_for(:html_description, t('share_link_is_currently_disabled')) %>
<% 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">
<%= render 'start_form/banner' %>
<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' %>
</toggle-submit>
<% end %>
</main>
</div>
<%= render 'shared/attribution', link_path: '/start', account: @template.account %>
+3 -3
View File
@@ -2,7 +2,7 @@
<% 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)) %>
<% 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">
<div class="text-center w-full space-y-6">
@@ -28,7 +28,7 @@
</div>
</div>
<% 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 %>
<% if @error_message %>
<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 'start_form/policy', account: @template.account %>
</div>
</main>
</div>
+2 -1
View File
@@ -43,7 +43,8 @@
</div>
<p class="text-base-content/80 mt-1">
<% 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')) %>
<%= 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' %>
+1 -1
View File
@@ -1,4 +1,4 @@
<% unless turbo_native_ios? %>
<% unless turbo_native_app? %>
<toggle-on-checked data-names="is_custom_message" class="block">
<div class="form-control">
<%= render 'sms_settings/placeholder' %>
@@ -1,4 +1,4 @@
<% unless turbo_native_ios? %>
<% unless turbo_native_app? %>
<div class="mt-2 mb-1">
<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' %>
+3 -3
View File
@@ -6,14 +6,14 @@
<% recipient_form_fields = Accounts.load_recipient_form_fields(current_account) if prefillable_fields.blank? %>
<% can_send_emails = Accounts.can_send_emails?(current_account) %>
<% 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_phone = !turbo_native_ios? || can?(:manage, :sms_verification) %>
<% with_list = @template.variables_schema.blank? && (!turbo_native_app? || can?(:manage, :bulk_send)) %>
<% with_phone = !turbo_native_app? || can?(:manage, :sms_verification) %>
<% 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'] } %>
<%= 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 %>
<% 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">
<% options.each_with_index do |(label, value), index| %>
<%= content_tag(value == 'list' ? 'span' : 'toggle-cookies', data: { value:, key: 'add_recipients_tab' }) do %>
+13 -13
View File
@@ -11,7 +11,7 @@
<% 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_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' %>
<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? %>
@@ -227,39 +227,39 @@
</div>
<% if submitter&.name.present? %>
<div class="flex items-center space-x-1 mt-1">
<%= svg_icon('user', class: 'w-5 h-5') %>
<span>
<%= svg_icon('user', class: 'w-5 h-5 shrink-0') %>
<span class="truncate">
<%= submitter&.name %>
</span>
</div>
<% end %>
<% if submitter&.email.present? %>
<div class="flex items-center space-x-1 mt-1">
<%= svg_icon('mail', class: 'w-5 h-5') %>
<span>
<%= svg_icon('mail', class: 'w-5 h-5 shrink-0') %>
<span class="truncate">
<%= submitter.email || 'N/A' %>
</span>
</div>
<% end %>
<% if submitter&.phone.present? %>
<div class="flex items-center space-x-1 mt-1">
<%= svg_icon('phone', class: 'w-5 h-5') %>
<span>
<%= svg_icon('phone', class: 'w-5 h-5 shrink-0') %>
<span class="truncate">
<%= submitter.phone %>
</span>
</div>
<% end %>
<div class="flex items-center space-x-1 mt-1">
<% 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? %>
<%= 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? %>
<%= svg_icon('clock_exclamation', class: 'w-5 h-5') %>
<%= svg_icon('clock_exclamation', class: 'w-5 h-5 shrink-0') %>
<% else %>
<%= svg_icon('writing', class: 'w-5 h-5') %>
<%= svg_icon('writing', class: 'w-5 h-5 shrink-0') %>
<% end %>
<span>
<span class="truncate">
<% 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)) %>
<% elsif submitter&.viewer? %>
@@ -394,7 +394,7 @@
</label>
</toggle-visible>
<% end %>
</main>
</div>
<% unless request.headers['HTTP_X_TURBO_REQUEST_ID'] %>
<%= render 'scripts/autosize_field' %>
<% end %>
+1 -1
View File
@@ -1,6 +1,6 @@
<% filter_params = params.permit(:q, :archived, *Submissions::Filter::ALLOWED_PARAMS) %>
<%= 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 %>
<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">
@@ -1,7 +1,7 @@
<% path_uri = Addressable::URI.parse(params[:path].to_s) %>
<% path = url_for(**Rails.application.routes.recognize_path(path_uri.path, method: :get)) %>
<%= render 'shared/turbo_modal', title: local_assigns[:title] do %>
<%= form_for '', url: path, method: :get, data: { turbo_frame: :_top }, html: { autocomplete: :off } do |f| %>
<%= form_for '', url: path, method: :get, data: { turbo_frame: :_top }, html: { autocomplete: :off, class: ('px-4' if turbo_native_android?) } do |f| %>
<%= hidden_field_tag :q, params[:q] if params[:q].present? %>
<%= hidden_field_tag :scope, params[:scope] if params[:scope].present? %>
<%= hidden_field_tag :archived, params[:archived] if params[:archived].present? %>
@@ -3,11 +3,11 @@
<div class="flex flex-col md:flex-row gap-2">
<div class="form-control w-full">
<%= label_tag 'completed_at_from', t('from'), for: 'date_from', class: 'label text-sm' %>
<%= date_field_tag 'completed_at_from', params[:completed_at_from], id: 'date_from', class: 'base-input !h-10', autocomplete: 'off' %>
<%= date_field_tag 'completed_at_from', params[:completed_at_from], id: 'date_from', class: "base-input !h-10 #{'w-full' if turbo_native_android?}", autocomplete: 'off' %>
</div>
<div class="form-control w-full">
<%= label_tag 'completed_at_to', t('to'), for: 'date_to', class: 'label text-sm' %>
<%= date_field_tag 'completed_at_to', params[:completed_at_to], id: 'date_to', class: 'base-input !h-10', autocomplete: 'off' %>
<%= date_field_tag 'completed_at_to', params[:completed_at_to], id: 'date_to', class: "base-input !h-10 #{'w-full' if turbo_native_android?}", autocomplete: 'off' %>
</div>
</div>
<%= render 'date_buttons' %>
@@ -3,11 +3,11 @@
<div class="flex flex-col md:flex-row gap-2">
<div class="form-control w-full">
<%= label_tag 'created_at_from', t('from'), for: 'date_from', class: 'label text-sm' %>
<%= date_field_tag 'created_at_from', params[:created_at_from], id: 'date_from', class: 'base-input !h-10', autocomplete: 'off' %>
<%= date_field_tag 'created_at_from', params[:created_at_from], id: 'date_from', class: "base-input !h-10 #{'w-full' if turbo_native_android?}", autocomplete: 'off' %>
</div>
<div class="form-control w-full">
<%= label_tag 'created_at_to', t('to'), for: 'date_to', class: 'label text-sm' %>
<%= date_field_tag 'created_at_to', params[:created_at_to], id: 'date_to', class: 'base-input !h-10', autocomplete: 'off' %>
<%= date_field_tag 'created_at_to', params[:created_at_to], id: 'date_to', class: "base-input !h-10 #{'w-full' if turbo_native_android?}", autocomplete: 'off' %>
</div>
</div>
<%= render 'date_buttons' %>
+2 -2
View File
@@ -1,5 +1,5 @@
<% completed_at = @submitter.completed_at || @submitter.submission.completed_at %>
<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">
<div class="flex items-center justify-center">
@@ -51,5 +51,5 @@
</toggle-submit>
<% end %>
</div>
</main>
</div>
<%= render 'shared/attribution', link_path: '/start', account: @submitter.account %>
+5 -4
View File
@@ -3,7 +3,7 @@
<% I18n.with_locale(@submitter.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: @submitter.account.name)) %>
<% 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">
<div class="flex items-center justify-center">
@@ -34,6 +34,7 @@
<%= form_for '', url: submit_form_email_2fa_path, method: :post, html: { class: 'space-y-4', id: 'code_form' } do |f| %>
<div dir="auto" class="form-control !mt-0">
<%= f.hidden_field :submitter_slug, value: @submitter.slug %>
<%= f.hidden_field :lang, value: params[:lang] if params[:lang].present? %>
<%= f.text_field :one_time_code, required: true, inputmode: 'numeric', class: 'base-input text-center', placeholder: 'XXXXXX' %>
<div class="flex justify-between items-center mt-1">
<span>
@@ -55,7 +56,7 @@
<%= f.button button_title(title: t('submit')), class: 'base-button' %>
</toggle-submit>
<% end %>
<%= button_to t(:re_send_email), submit_form_email_2fa_path, params: { submitter_slug: @submitter.slug, resend: true }, method: :put, form: { id: 'resend_code_form', class: 'hidden' } %>
<%= button_to t(:re_send_email), submit_form_email_2fa_path, params: { submitter_slug: @submitter.slug, resend: true, lang: params[:lang] }.compact_blank, method: :put, form: { id: 'resend_code_form', class: 'hidden' } %>
<% else %>
<% if params[:t] %>
<fetch-form data-onload="true">
@@ -63,11 +64,11 @@
</fetch-form>
<% end %>
<toggle-submit dir="auto" class="form-control">
<%= button_to button_title(title: t('send_verification_code')), submit_form_email_2fa_path, params: { submitter_slug: @submitter.slug }, method: :put, class: 'base-button w-full' %>
<%= button_to button_title(title: t('send_verification_code')), submit_form_email_2fa_path, params: { submitter_slug: @submitter.slug, lang: params[:lang] }.compact_blank, method: :put, class: 'base-button w-full' %>
</toggle-submit>
<% end %>
<% else %>
<div><%= t('please_contact_the_requester_to_specify_your_email_for_two_factor_authentication') %></div>
<% end %>
</div>
</main>
</div>
+2 -2
View File
@@ -14,7 +14,7 @@
<% delegate_modal_id = nil %>
<% banner_html = turbo_native_app? ? '' : local_assigns[:banner_html] %>
<div style="max-height: -webkit-fill-available;">
<main id="scrollbox">
<div id="scrollbox">
<div id="sr_only_content"></div>
<div class="mx-auto block pb-72" style="max-width: 1000px">
<%# flex block w-full sticky top-0 z-50 space-x-2 items-center bg-yellow-100 p-2 border-y border-yellow-200 transition-transform duration-300 %>
@@ -172,7 +172,7 @@
<% end %>
<% end %>
</div>
</main>
</div>
</div>
<div class="fixed bottom-0 w-full h-0 z-50">
<div class="mx-auto" style="max-width: 1000px">
+2 -2
View File
@@ -1,6 +1,6 @@
<%= render 'shared/turbo_modal', title: t('rename_folder') do %>
<%= form_for @template_folder, url: folder_path(@template_folder), data: { turbo_frame: :_top }, html: { autocomplete: :off } do |f| %>
<div class="form-control my-6">
<%= form_for @template_folder, url: folder_path(@template_folder), data: { turbo_frame: :_top }, html: { autocomplete: :off, class: ('px-4' if turbo_native_android?) } do |f| %>
<div class="form-control <%= turbo_native_android? ? 'mt-1.5 mb-6' : 'my-6' %>">
<%= f.text_field :name, required: true, placeholder: "#{t('folder_name')}...", class: 'base-input w-full', autofocus: true, dir: 'auto' %>
</div>
<div class="form-control">
+1 -1
View File
@@ -31,7 +31,7 @@
</div>
</span>
</div>
<input id="file_dropzone_input" name="files[]" class="hidden" data-action="change:file-dropzone#onSelectFiles" data-target="file-dropzone.input" type="file" accept="image/*, application/pdf, application/zip, application/json<%= ", #{Templates::CreateAttachments::DOCUMENT_EXTENSIONS.join(', ')}" if Docuseal.advanced_formats? %>" multiple>
<input id="file_dropzone_input" name="files[]" class="hidden" data-action="change:file-dropzone#onSelectFiles" data-target="file-dropzone.input" type="file" accept="image/*, application/pdf, application/zip, application/json, <%= Templates::CreateAttachments.document_extensions.join(', ') %>" multiple>
</div>
</label>
</file-dropzone>
+1 -1
View File
@@ -16,7 +16,7 @@
</label>
<input type="hidden" name="form_id" value="<%= form_id %>">
<convert-upload>
<input id="upload_template" name="files[]" class="hidden" type="file" accept="image/*, application/pdf, application/zip, application/json<%= ", #{Templates::CreateAttachments::DOCUMENT_EXTENSIONS.join(', ')}" if Docuseal.advanced_formats? %>" multiple>
<input id="upload_template" name="files[]" class="hidden" type="file" accept="image/*, application/pdf, application/zip, application/json, <%= Templates::CreateAttachments.document_extensions.join(', ') %>" multiple>
</convert-upload>
<input hidden name="folder_name" value="<%= local_assigns[:folder_name] %>">
<% end %>
+3 -2
View File
@@ -7,7 +7,8 @@
<%= button_to nil, user_configs_path, method: :post, params: { user_config: { key: UserConfig::SHOW_APP_TOUR, value: true } }, class: 'hidden', id: 'start_tour_button' %>
<% end %>
<% end %>
<% account_configs = current_account.account_configs.where(key: [AccountConfig::TEMPLATE_CUSTOM_FIELDS_KEY, AccountConfig::TEMPLATE_DATE_FORMATS_KEY]).to_a %>
<% account_configs = current_account.account_configs.where(key: [AccountConfig::TEMPLATE_CUSTOM_FIELDS_KEY, AccountConfig::TEMPLATE_DATE_FORMATS_KEY, AccountConfig::TEMPLATE_BUILDER_AUTOSAVE_KEY]).to_a %>
<% custom_fields = account_configs.find { |e| e.key == AccountConfig::TEMPLATE_CUSTOM_FIELDS_KEY }&.value || [] %>
<% date_formats = account_configs.find { |e| e.key == AccountConfig::TEMPLATE_DATE_FORMATS_KEY }&.value || [] %>
<template-builder data-native-platform="<%= turbo_native_ios? ? 'ios' : (turbo_native_android? ? 'android' : '') %>" class="grid" data-template="<%= @template_data.to_json %>" data-custom-fields="<%= custom_fields.to_json %>" data-date-formats="<%= date_formats.to_json %>" data-with-sign-yourself-button="<%= !@template.archived_at? %>" data-with-fields-detection="true" data-with-send-button="<%= !@template.archived_at? && can?(:create, @template.submissions.new(account: current_account)) %>" data-with-revisions-menu="<%= @template.template_versions.exists? %>" data-locale="<%= I18n.locale %>" data-show-tour-start-form="<%= @show_tour_start_form %>"></template-builder>
<% autosave = account_configs.find { |e| e.key == AccountConfig::TEMPLATE_BUILDER_AUTOSAVE_KEY }&.value != false %>
<template-builder data-native-platform="<%= turbo_native_ios? ? 'ios' : (turbo_native_android? ? 'android' : '') %>" data-accept-file-types="image/*, application/pdf, application/zip, application/json, <%= Templates::CreateAttachments.document_extensions.join(', ') %>" class="grid" data-template="<%= @template_data.to_json %>" data-custom-fields="<%= custom_fields.to_json %>" data-date-formats="<%= date_formats.to_json %>" data-autosave="<%= autosave %>" data-with-sign-yourself-button="<%= !@template.archived_at? %>" data-with-fields-detection="true" data-with-send-button="<%= !@template.archived_at? && can?(:create, @template.submissions.new(account: current_account)) %>" data-with-revisions-menu="<%= @template.template_versions.exists? %>" data-locale="<%= I18n.locale %>" data-show-tour-start-form="<%= @show_tour_start_form %>"></template-builder>
+3 -1
View File
@@ -5,7 +5,9 @@
<% is_show_header = submissions_count != 0 || params[:q].present? || params[:status].present? || filter_params.present? %>
<%= render 'title', template: @template, with_export: is_show_header %>
<% if turbo_native_app? %>
<native-action data-placement="search" data-native="search" data-label="<%= t('search') %>" data-url="<%= search_index_path(filter_params.to_h.merge({ 'scope' => 'submissions', 'template_id' => @template.id, 'archived' => ('true' if @template.archived_at?) }.compact)) %>"></native-action>
<% if !turbo_native_android? || submissions_count >= 5 || params[:q].present? || params[:status].present? || filter_params.present? %>
<native-action data-placement="search" data-native="search" data-label="<%= t('search') %>" data-url="<%= search_index_path(filter_params.to_h.merge({ 'scope' => 'submissions', 'template_id' => @template.id, 'archived' => ('true' if @template.archived_at?) }.compact)) %>"></native-action>
<% end %>
<% if !@template.archived_at? && can?(:create, Submission) %>
<native-action data-placement="footer" data-icon="send" data-haptic="true" data-label="<%= t('send_to_recipients') %>">
<a href="<%= new_template_submission_path(@template) %>" data-turbo-frame="modal" data-native-modal="<%= 'medium' if @template.submitters.size < 3 %>"></a>
+5 -4
View File
@@ -1,11 +1,12 @@
<%= form_for @template, url: template_clone_index_path(@base_template), data: { turbo_frame: :_top }, html: { autocomplete: :off } do |f| %>
<%= form_for @template, url: template_clone_index_path(@base_template), data: { turbo_frame: :_top }, html: { autocomplete: :off, class: ('px-4' if turbo_native_android?) } do |f| %>
<% accounts = Account.accessible_by(true_ability).where.not(id: true_user.account.testing_accounts).where(User.where(User.arel_table[:account_id].eq(Account.arel_table[:id])).arel.exists).active %>
<% if (can?(:manage, :tenants) || true_user != current_user) && accounts.where.not(id: current_account.id).exists? %>
<div class="form-control -mb-2 mt-2">
<% with_accounts = (can?(:manage, :tenants) || true_user != current_user) && accounts.where.not(id: current_account.id).exists? %>
<% if with_accounts %>
<div class="form-control -mb-2 <%= turbo_native_android? ? 'mt-1.5' : 'mt-2' %>">
<%= select_tag :account_id, options_for_select([current_account, *accounts.order(:name)].uniq.map { |e| [e.name, e.id] }, current_account.id), required: true, class: 'base-select' %>
</div>
<% end %>
<div class="form-control mt-4">
<div class="form-control <%= turbo_native_android? && !with_accounts ? 'mt-1.5' : 'mt-4' %>">
<%= f.text_field :name, required: true, placeholder: t('document_name'), class: 'base-input', dir: 'auto' %>
</div>
<div class="mt-3 mb-4 flex items-center justify-between">
@@ -1,4 +1,4 @@
<% unless turbo_native_ios? %>
<% unless turbo_native_app? %>
<div class="alert">
<%= svg_icon('info_circle', class: 'w-6 h-6') %>
<div>
+1 -1
View File
@@ -26,7 +26,7 @@
<%= t('share_template_with_test_mode') %>
</span>
<submit-form data-on="change">
<%= f.check_box :value, class: 'toggle', checked: @template.template_sharings.exists?(account_id: true_user.account.testing_accounts) %>
<%= f.check_box :value, class: 'toggle', checked: @template.template_sharings.exists?(account_id: (can?(:manage, :tenants_test) ? current_account : true_user.account).testing_accounts) %>
</submit-form>
</div>
<% end %>
+1 -1
View File
@@ -127,4 +127,4 @@
<%= render 'shared/pagination', pagy: @related_submissions_pagy, items_name: 'submissions', next_page_path: submissions_path(q: params[:q]) %>
<% end %>
<% end %>
<%= render 'shared/review_form' %>
<%= render 'shared/review_form' unless turbo_native_app? %>
+2 -2
View File
@@ -1,7 +1,7 @@
<% with_subfolder = @template.folder.name != TemplateFolder::DEFAULT_NAME && params[:subfolder] != 'false' %>
<%= render 'shared/turbo_modal', title: t('move_into_folder') do %>
<% if with_subfolder %>
<%= form_for '', url: template_folder_path(@template), method: :put, data: { turbo_frame: :_top }, html: { id: 'subfolder_form', autocomplete: :off } do |f| %>
<%= form_for '', url: template_folder_path(@template), method: :put, data: { turbo_frame: :_top }, html: { id: 'subfolder_form', autocomplete: :off, class: ('px-4' if turbo_native_android?) } do |f| %>
<%= f.hidden_field :parent_name, value: @template.folder.parent_folder&.name || @template.folder.name %>
<toggle-visible data-element-ids="<%= %w[folder_form subfolder_form].to_json %>" class="block relative" data-focus-id="name">
<div class="flex items-center justify-between mb-2.5">
@@ -25,7 +25,7 @@
</div>
<% end %>
<% end %>
<%= form_for '', url: template_folder_path(@template), method: :put, data: { turbo_frame: :_top }, html: { id: 'folder_form', autocomplete: :off, class: "mt-6 #{'hidden' if with_subfolder}" } do |f| %>
<%= form_for '', url: template_folder_path(@template), method: :put, data: { turbo_frame: :_top }, html: { id: 'folder_form', autocomplete: :off, class: "#{turbo_native_android? ? 'mt-1.5 px-4' : 'mt-6'} #{'hidden' if with_subfolder}" } do |f| %>
<div class="form-control mb-6">
<folder-autocomplete class="block" data-submit-on-select="true" data-enabled="<%= params[:autocomplete] != 'false' %>">
<%= f.text_field :name, required: true, placeholder: "#{t('new_folder_name')}...", class: 'base-input w-full', autofocus: true %>
@@ -28,7 +28,7 @@
</submit-form>
</form>
<% end %>
<a href="<%= edit_template_path(@template) %>" class="base-button" data-turbo="false" style="flex-shrink: 0; padding: 0px 24px;">
<a href="<%= edit_template_path(@template) %>" class="base-button" data-turbo-prefetch="false" style="flex-shrink: 0; padding: 0px 24px;">
<%= t('exit_preview') %>
</a>
</div>
@@ -5,7 +5,7 @@
<% options << [t('recipients'), 'recipients'] if show_recipients %>
<% options << [safe_join([tag.span('API', class: 'md:hidden'), tag.span(t('api_and_embedding'), class: 'hidden md:inline')]), 'api'] if show_api %>
<% if options.size > 1 %>
<toggle-visible data-element-ids="<%= options.map(&:last).to_json %>" class="relative text-center block <%= 'mt-3' unless turbo_native_app? %>">
<toggle-visible data-element-ids="<%= options.map(&:last).to_json %>" class="relative text-center block <%= turbo_native_app? ? ('mt-1.5' if turbo_native_android?) : 'mt-3' %>">
<div class="join">
<% options.each_with_index do |(label, value), index| %>
<span>
@@ -194,7 +194,7 @@
<%= t('share_template_with_test_mode') %>
</span>
<submit-form data-on="change" class="flex">
<%= f.check_box :value, class: 'toggle', checked: @template.template_sharings.exists?(account_id: true_user.account.testing_accounts) %>
<%= f.check_box :value, class: 'toggle', checked: @template.template_sharings.exists?(account_id: (can?(:manage, :tenants_test) ? current_account : true_user.account).testing_accounts) %>
</submit-form>
</div>
<% end %>
+4 -4
View File
@@ -1,21 +1,21 @@
<% multiple_submitters = @template.submitters.to_a.length > 1 %>
<% enough_defined_submitters = Templates.filter_undefined_submitters(@template.submitters).size < 2 %>
<%= render 'shared/turbo_modal_large', title: t('share_link') do %>
<div class="mt-2 mb-4 px-5">
<div class="<%= 'mt-2' unless turbo_native_android? %> mb-4 px-5">
<% if @template.variables_schema.present? %>
<div class="flex items-start gap-4 p-4 border border-base-300 rounded-2xl bg-base-100 mt-4">
<%= svg_icon('info_circle', class: 'stroke-current shrink-0 h-6 w-6') %>
<div><%= t('dynamic_template_with_variables_cannot_be_used_via_shared_link') %></div>
</div>
<% end %>
<%= form_for @template, url: template_share_link_path(@template), method: :post, html: { id: 'shared_link_form', autocomplete: 'off', class: 'mt-3' }, data: { close_on_submit: false } do |f| %>
<%= form_for @template, url: template_share_link_path(@template), method: :post, html: { id: 'shared_link_form', autocomplete: 'off', class: turbo_native_android? ? 'mt-1' : 'mt-3' }, data: { close_on_submit: false } do |f| %>
<% if @template.preferences&.dig('require_email_2fa') || @template.preferences&.dig('require_phone_2fa') %>
<label for="template_shared_link" class="tooltip tooltip-bottom tooltip-no-touch flex items-center my-4 justify-between gap-4 p-4 border border-base-300 rounded-2xl bg-base-100" data-tip="<%= t(:templates_that_require_email_or_phone_2fa_cannot_be_used_via_a_shared_link) %>">
<label for="template_shared_link" class="tooltip tooltip-bottom tooltip-no-touch flex items-center <%= turbo_native_android? ? 'mb-4' : 'my-4' %> justify-between gap-4 p-4 border border-base-300 rounded-2xl bg-base-100" data-tip="<%= t(:templates_that_require_email_or_phone_2fa_cannot_be_used_via_a_shared_link) %>">
<span><%= t('enable_shared_link') %></span>
<%= check_box_tag 'shared_link', 'true', false, disabled: true, class: 'toggle' %>
</label>
<% else %>
<label for="template_shared_link" class="flex items-center my-4 justify-between gap-4 p-4 border border-base-300 rounded-2xl bg-base-100">
<label for="template_shared_link" class="flex items-center <%= turbo_native_android? ? 'mb-4' : 'my-4' %> justify-between gap-4 p-4 border border-base-300 rounded-2xl bg-base-100">
<span><%= t('enable_shared_link') %></span>
<submit-form data-on="change" class="flex">
<%= f.check_box :shared_link, { disabled: !can?(:update, @template) || @template.variables_schema.present?, class: 'toggle' }, 'true', 'false' %>
Loaded 100 of 143 files, more files were not shown because too many files have changed in this diff. Show more