diff --git a/.rubocop.yml b/.rubocop.yml index 499a63e66..a679c86a1 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -69,7 +69,7 @@ RSpec/MultipleMemoizedHelpers: Max: 9 Metrics/BlockNesting: - Max: 4 + Max: 5 Rails/I18nLocaleTexts: Enabled: false diff --git a/Dockerfile b/Dockerfile index d9ea5e456..fed215116 100644 --- a/Dockerfile +++ b/Dockerfile @@ -62,7 +62,7 @@ RUN apk add --no-cache build-base && bundle install && apk del --no-cache build- COPY ./bin ./bin COPY ./app ./app COPY ./config ./config -COPY ./db ./db +COPY ./db/migrate ./db/migrate COPY ./log ./log COPY ./lib ./lib COPY ./public ./public diff --git a/Gemfile.lock b/Gemfile.lock index 7e35ba9ff..f32faeb7b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,67 +1,67 @@ GEM remote: https://rubygems.org/ specs: - actioncable (7.2.1.1) - actionpack (= 7.2.1.1) - activesupport (= 7.2.1.1) + actioncable (8.0.1) + actionpack (= 8.0.1) + activesupport (= 8.0.1) nio4r (~> 2.0) websocket-driver (>= 0.6.1) zeitwerk (~> 2.6) - actionmailbox (7.2.1.1) - actionpack (= 7.2.1.1) - activejob (= 7.2.1.1) - activerecord (= 7.2.1.1) - activestorage (= 7.2.1.1) - activesupport (= 7.2.1.1) + actionmailbox (8.0.1) + actionpack (= 8.0.1) + activejob (= 8.0.1) + activerecord (= 8.0.1) + activestorage (= 8.0.1) + activesupport (= 8.0.1) mail (>= 2.8.0) - actionmailer (7.2.1.1) - actionpack (= 7.2.1.1) - actionview (= 7.2.1.1) - activejob (= 7.2.1.1) - activesupport (= 7.2.1.1) + actionmailer (8.0.1) + actionpack (= 8.0.1) + actionview (= 8.0.1) + activejob (= 8.0.1) + activesupport (= 8.0.1) mail (>= 2.8.0) rails-dom-testing (~> 2.2) - actionpack (7.2.1.1) - actionview (= 7.2.1.1) - activesupport (= 7.2.1.1) + actionpack (8.0.1) + actionview (= 8.0.1) + activesupport (= 8.0.1) nokogiri (>= 1.8.5) - racc - rack (>= 2.2.4, < 3.2) + rack (>= 2.2.4) rack-session (>= 1.0.1) rack-test (>= 0.6.3) rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) useragent (~> 0.16) - actiontext (7.2.1.1) - actionpack (= 7.2.1.1) - activerecord (= 7.2.1.1) - activestorage (= 7.2.1.1) - activesupport (= 7.2.1.1) + actiontext (8.0.1) + actionpack (= 8.0.1) + activerecord (= 8.0.1) + activestorage (= 8.0.1) + activesupport (= 8.0.1) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (7.2.1.1) - activesupport (= 7.2.1.1) + actionview (8.0.1) + activesupport (= 8.0.1) builder (~> 3.1) erubi (~> 1.11) rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) - activejob (7.2.1.1) - activesupport (= 7.2.1.1) + activejob (8.0.1) + activesupport (= 8.0.1) globalid (>= 0.3.6) - activemodel (7.2.1.1) - activesupport (= 7.2.1.1) - activerecord (7.2.1.1) - activemodel (= 7.2.1.1) - activesupport (= 7.2.1.1) + activemodel (8.0.1) + activesupport (= 8.0.1) + activerecord (8.0.1) + activemodel (= 8.0.1) + activesupport (= 8.0.1) timeout (>= 0.4.0) - activestorage (7.2.1.1) - actionpack (= 7.2.1.1) - activejob (= 7.2.1.1) - activerecord (= 7.2.1.1) - activesupport (= 7.2.1.1) + activestorage (8.0.1) + actionpack (= 8.0.1) + activejob (= 8.0.1) + activerecord (= 8.0.1) + activesupport (= 8.0.1) marcel (~> 1.0) - activesupport (7.2.1.1) + activesupport (8.0.1) base64 + benchmark (>= 0.3) bigdecimal concurrent-ruby (~> 1.0, >= 1.3.1) connection_pool (>= 2.2.5) @@ -71,31 +71,32 @@ GEM minitest (>= 5.1) securerandom (>= 0.3) tzinfo (~> 2.0, >= 2.0.5) + uri (>= 0.13.1) addressable (2.8.7) public_suffix (>= 2.0.2, < 7.0) - annotate (3.2.0) - activerecord (>= 3.2, < 8.0) - rake (>= 10.4, < 14.0) + annotate (2.6.5) + activerecord (>= 2.3.0) + rake (>= 0.8.7) arabic-letter-connector (0.1.1) ast (2.4.2) aws-eventstream (1.3.0) - aws-partitions (1.985.0) - aws-sdk-core (3.209.1) + aws-partitions (1.1027.0) + aws-sdk-core (3.214.0) aws-eventstream (~> 1, >= 1.3.0) - aws-partitions (~> 1, >= 1.651.0) + aws-partitions (~> 1, >= 1.992.0) aws-sigv4 (~> 1.9) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.94.0) - aws-sdk-core (~> 3, >= 3.207.0) + aws-sdk-kms (1.96.0) + aws-sdk-core (~> 3, >= 3.210.0) aws-sigv4 (~> 1.5) - aws-sdk-s3 (1.167.0) - aws-sdk-core (~> 3, >= 3.207.0) + aws-sdk-s3 (1.176.1) + aws-sdk-core (~> 3, >= 3.210.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.5) - aws-sdk-secretsmanager (1.108.0) - aws-sdk-core (~> 3, >= 3.207.0) + aws-sdk-secretsmanager (1.110.0) + aws-sdk-core (~> 3, >= 3.210.0) aws-sigv4 (~> 1.5) - aws-sigv4 (1.10.0) + aws-sigv4 (1.10.1) aws-eventstream (~> 1, >= 1.0.2) azure-storage-blob (2.0.3) azure-storage-common (~> 2.0) @@ -107,6 +108,7 @@ GEM nokogiri (~> 1, >= 1.10.8) base64 (0.2.0) bcrypt (3.1.20) + benchmark (0.4.0) better_html (2.1.1) actionview (>= 6.0) activesupport (>= 6.0) @@ -119,7 +121,7 @@ GEM bootsnap (1.18.4) msgpack (~> 1.2) builder (3.3.0) - bullet (7.2.0) + bullet (8.0.0) activesupport (>= 3.0.0) uniform_notifier (~> 1.11) camertron-eprun (1.1.1) @@ -145,14 +147,14 @@ GEM bigdecimal rexml crass (1.0.6) - css_parser (1.19.0) + css_parser (1.21.0) addressable - csv (3.3.0) + csv (3.3.2) cuprite (0.15.1) capybara (~> 3.0) ferrum (~> 0.15.0) - date (3.3.4) - debug (1.9.2) + date (3.4.1) + debug (1.10.0) irb (~> 1.10) reline (>= 0.3.8) declarative (0.0.20) @@ -162,32 +164,32 @@ GEM railties (>= 4.1.0) responders warden (~> 1.2.3) - devise-two-factor (6.0.0) - activesupport (~> 7.0) + devise-two-factor (6.1.0) + activesupport (>= 7.0, < 8.1) devise (~> 4.0) - railties (~> 7.0) + railties (>= 7.0, < 8.1) rotp (~> 6.0) diff-lcs (1.5.1) digest-crc (0.6.5) rake (>= 12.0.0, < 14.0.0) docile (1.4.1) - dotenv (3.1.4) + dotenv (3.1.7) drb (2.2.1) email_typo (0.2.3) - erb_lint (0.6.0) + erb_lint (0.7.0) activesupport better_html (>= 2.0.1) parser (>= 2.7.1.4) rainbow rubocop (>= 1) smart_properties - erubi (1.13.0) + erubi (1.13.1) factory_bot (6.5.0) activesupport (>= 5.0.0) - factory_bot_rails (6.4.3) - factory_bot (~> 6.4) + factory_bot_rails (6.4.4) + factory_bot (~> 6.5) railties (>= 5.0.0) - faker (3.4.2) + faker (3.5.1) i18n (>= 1.8.11, < 2) faraday (1.10.4) faraday-em_http (~> 1.0) @@ -207,8 +209,8 @@ GEM faraday-follow_redirects (0.3.0) faraday (>= 1, < 3) faraday-httpclient (1.0.1) - faraday-multipart (1.0.4) - multipart-post (~> 2) + faraday-multipart (1.1.0) + multipart-post (~> 2.0) faraday-net_http (1.0.2) faraday-net_http_persistent (1.2.0) faraday-patron (1.0.0) @@ -235,9 +237,9 @@ GEM mutex_m representable (~> 3.0) retriable (>= 2.0, < 4.a) - google-apis-iamcredentials_v1 (0.21.0) + google-apis-iamcredentials_v1 (0.22.0) google-apis-core (>= 0.15.0, < 2.a) - google-apis-storage_v1 (0.46.0) + google-apis-storage_v1 (0.49.0) google-apis-core (>= 0.15.0, < 2.a) google-cloud-core (1.7.1) google-cloud-env (>= 1.0, < 3.a) @@ -245,7 +247,7 @@ GEM google-cloud-env (2.2.1) faraday (>= 1.0, < 3.a) google-cloud-errors (1.4.0) - google-cloud-storage (1.52.0) + google-cloud-storage (1.54.0) addressable (~> 2.8) digest-crc (~> 0.4) google-apis-core (~> 0.13) @@ -254,15 +256,17 @@ GEM google-cloud-core (~> 1.6) googleauth (~> 1.9) mini_mime (~> 1.0) - googleauth (1.11.1) + google-logging-utils (0.1.0) + googleauth (1.12.2) faraday (>= 1.0, < 3.a) - google-cloud-env (~> 2.1) + google-cloud-env (~> 2.2) + google-logging-utils (~> 0.1) jwt (>= 1.4, < 3.0) multi_json (~> 1.11) os (>= 0.9, < 2.0) signet (>= 0.16, < 2.a) - hashdiff (1.1.1) - hexapdf (0.47.0) + hashdiff (1.1.2) + hexapdf (1.0.3) cmdparse (~> 3.0, >= 3.0.3) geom2d (~> 0.4, >= 0.4.1) openssl (>= 2.2.1) @@ -273,12 +277,12 @@ GEM image_processing (1.13.0) mini_magick (>= 4.9.5, < 5) ruby-vips (>= 2.0.17, < 3) - io-console (0.7.2) - irb (1.14.1) + io-console (0.8.0) + irb (1.14.3) rdoc (>= 4.0.0) reline (>= 0.4.2) jmespath (1.6.2) - json (2.7.2) + json (2.9.1) jwt (2.9.3) base64 language_server-protocol (3.17.0.3) @@ -292,7 +296,7 @@ GEM letter_opener (~> 1.9) railties (>= 6.1) rexml - logger (1.6.1) + logger (1.6.4) lograge (0.14.0) actionpack (>= 4) activesupport (>= 4) @@ -312,15 +316,15 @@ GEM mini_magick (4.13.2) mini_mime (1.1.5) mini_portile2 (2.8.8) - minitest (5.25.1) - msgpack (1.7.3) + minitest (5.25.4) + msgpack (1.7.5) multi_json (1.15.0) multipart-post (2.4.1) - mutex_m (0.2.0) + mutex_m (0.3.0) mysql2 (0.5.6) - net-http-persistent (4.0.4) + net-http-persistent (4.0.5) connection_pool (~> 2.2) - net-imap (0.4.17) + net-imap (0.5.3) date net-protocol net-pop (0.1.2) @@ -329,28 +333,28 @@ GEM timeout net-smtp (0.5.0) net-protocol - nio4r (2.7.3) - nokogiri (1.16.8) + nio4r (2.7.4) + nokogiri (1.17.2) mini_portile2 (~> 2.8.2) racc (~> 1.4) - nokogiri (1.16.8-aarch64-linux) + nokogiri (1.17.2-aarch64-linux) racc (~> 1.4) - nokogiri (1.16.8-x86_64-linux) + nokogiri (1.17.2-x86_64-linux) racc (~> 1.4) - oj (3.16.6) + oj (3.16.8) bigdecimal (>= 3.0) ostruct (>= 0.2) - openssl (3.2.0) + openssl (3.3.0) orm_adapter (0.5.0) os (1.1.4) - ostruct (0.6.0) + ostruct (0.6.1) package_json (0.1.0) - pagy (9.1.0) + pagy (9.3.3) parallel (1.26.3) - parser (3.3.5.0) + parser (3.3.6.0) ast (~> 2.4.1) racc - pg (1.5.8) + pg (1.5.9) premailer (1.27.0) addressable css_parser (>= 1.19.0) @@ -361,15 +365,16 @@ GEM premailer (~> 1.7, >= 1.7.9) pretender (0.5.0) actionpack (>= 6.1) - pry (0.14.2) + pry (0.15.0) coderay (~> 1.1) method_source (~> 1.0) pry-rails (0.3.11) pry (>= 0.13.0) - psych (5.1.2) + psych (5.2.2) + date stringio public_suffix (6.0.1) - puma (6.4.3) + puma (6.5.0) nio4r (~> 2.0) racc (1.8.1) rack (3.1.8) @@ -379,40 +384,39 @@ GEM rack (>= 3.0.0) rack-test (2.1.0) rack (>= 1.3) - rackup (2.1.0) + rackup (2.2.1) rack (>= 3) - webrick (~> 1.8) - rails (7.2.1.1) - actioncable (= 7.2.1.1) - actionmailbox (= 7.2.1.1) - actionmailer (= 7.2.1.1) - actionpack (= 7.2.1.1) - actiontext (= 7.2.1.1) - actionview (= 7.2.1.1) - activejob (= 7.2.1.1) - activemodel (= 7.2.1.1) - activerecord (= 7.2.1.1) - activestorage (= 7.2.1.1) - activesupport (= 7.2.1.1) + rails (8.0.1) + actioncable (= 8.0.1) + actionmailbox (= 8.0.1) + actionmailer (= 8.0.1) + actionpack (= 8.0.1) + actiontext (= 8.0.1) + actionview (= 8.0.1) + activejob (= 8.0.1) + activemodel (= 8.0.1) + activerecord (= 8.0.1) + activestorage (= 8.0.1) + activesupport (= 8.0.1) bundler (>= 1.15.0) - railties (= 7.2.1.1) + railties (= 8.0.1) rails-dom-testing (2.2.0) activesupport (>= 5.0.0) minitest nokogiri (>= 1.6) - rails-html-sanitizer (1.6.1) + rails-html-sanitizer (1.6.2) loofah (~> 2.21) nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) - rails-i18n (7.0.9) + rails-i18n (8.0.1) i18n (>= 0.7, < 2) - railties (>= 6.0.0, < 8) + railties (>= 8.0.0, < 9) rails_autolink (1.1.8) actionview (> 3.1) activesupport (> 3.1) railties (> 3.1) - railties (7.2.1.1) - actionpack (= 7.2.1.1) - activesupport (= 7.2.1.1) + railties (8.0.1) + actionpack (= 8.0.1) + activesupport (= 8.0.1) irb (~> 1.13) rackup (>= 1.0.0) rake (>= 12.2) @@ -420,12 +424,12 @@ GEM zeitwerk (~> 2.6) rainbow (3.1.1) rake (13.2.1) - rdoc (6.7.0) + rdoc (6.10.0) psych (>= 4.0.0) - redis-client (0.22.2) + redis-client (0.23.0) connection_pool - regexp_parser (2.9.2) - reline (0.5.10) + regexp_parser (2.9.3) + reline (0.6.0) io-console (~> 0.5) representable (3.2.0) declarative (< 0.1.0) @@ -437,13 +441,13 @@ GEM actionpack (>= 5.2) railties (>= 5.2) retriable (3.1.2) - rexml (3.3.9) + rexml (3.4.0) rotp (6.3.0) rqrcode (2.2.0) chunky_png (~> 1.0) rqrcode_core (~> 1.0) rqrcode_core (1.2.0) - rspec-core (3.13.1) + rspec-core (3.13.2) rspec-support (~> 3.13.0) rspec-expectations (3.13.3) diff-lcs (>= 1.2.0, < 2.0) @@ -451,7 +455,7 @@ GEM rspec-mocks (3.13.2) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) - rspec-rails (7.0.1) + rspec-rails (7.1.0) actionpack (>= 7.0) activesupport (>= 7.0) railties (>= 7.0) @@ -459,48 +463,47 @@ GEM rspec-expectations (~> 3.13) rspec-mocks (~> 3.13) rspec-support (~> 3.13) - rspec-support (3.13.1) - rubocop (1.66.1) + rspec-support (3.13.2) + rubocop (1.69.2) json (~> 2.3) language_server-protocol (>= 3.17.0) parallel (~> 1.10) parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) - regexp_parser (>= 2.4, < 3.0) - rubocop-ast (>= 1.32.2, < 2.0) + regexp_parser (>= 2.9.3, < 3.0) + rubocop-ast (>= 1.36.2, < 2.0) ruby-progressbar (~> 1.7) - unicode-display_width (>= 2.4.0, < 3.0) - rubocop-ast (1.32.3) + unicode-display_width (>= 2.4.0, < 4.0) + rubocop-ast (1.37.0) parser (>= 3.3.1.0) - rubocop-performance (1.22.1) + rubocop-performance (1.23.0) rubocop (>= 1.48.1, < 2.0) rubocop-ast (>= 1.31.1, < 2.0) - rubocop-rails (2.26.2) + rubocop-rails (2.27.0) activesupport (>= 4.2.0) rack (>= 1.1) rubocop (>= 1.52.0, < 2.0) rubocop-ast (>= 1.31.1, < 2.0) - rubocop-rspec (3.1.0) + rubocop-rspec (3.3.0) rubocop (~> 1.61) ruby-progressbar (1.13.0) ruby-vips (2.2.2) ffi (~> 1.12) logger ruby2_keywords (0.0.5) - rubyXL (3.4.27) + rubyXL (3.4.33) nokogiri (>= 1.10.8) rubyzip (>= 1.3.0) rubyzip (2.3.2) - securerandom (0.3.1) - semantic_range (3.0.0) + securerandom (0.4.1) + semantic_range (3.1.0) shakapacker (8.0.2) activesupport (>= 5.2) package_json rack-proxy (>= 0.6.1) railties (>= 5.2) semantic_range (>= 2.3.0) - sidekiq (7.3.2) - concurrent-ruby (< 2) + sidekiq (7.3.7) connection_pool (>= 2.3.0) logger rack (>= 2.2.4) @@ -517,17 +520,17 @@ GEM simplecov-html (0.13.1) simplecov_json_formatter (0.1.4) smart_properties (1.17.0) - sqlite3 (2.1.0) + sqlite3 (2.4.1) mini_portile2 (~> 2.8.0) - sqlite3 (2.1.0-aarch64-linux-gnu) - sqlite3 (2.1.0-x86_64-linux-gnu) - stringio (3.1.1) - strip_attributes (1.13.0) - activemodel (>= 3.0, < 8.0) + sqlite3 (2.4.1-aarch64-linux-gnu) + sqlite3 (2.4.1-x86_64-linux-gnu) + stringio (3.1.2) + strip_attributes (1.14.1) + activemodel (>= 3.0, < 9.0) thor (1.3.2) - timeout (0.4.1) + timeout (0.4.3) trailblazer-option (0.1.2) - turbo-rails (2.0.10) + turbo-rails (2.0.11) actionpack (>= 6.0.0) railties (>= 6.0.0) twitter_cldr (6.12.1) @@ -539,9 +542,12 @@ GEM tzinfo-data (1.2024.2) tzinfo (>= 1.0.0) uber (0.1.0) - unicode-display_width (2.6.0) + unicode-display_width (3.1.2) + unicode-emoji (~> 4.0, >= 4.0.4) + unicode-emoji (4.0.4) uniform_notifier (1.16.0) - useragent (0.16.10) + uri (1.0.2) + useragent (0.16.11) warden (1.2.9) rack (>= 2.0.9) web-console (4.2.1) @@ -553,13 +559,13 @@ GEM addressable (>= 2.8.0) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) - webrick (1.8.2) + webrick (1.9.1) websocket-driver (0.7.6) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) xpath (3.2.0) nokogiri (~> 1.8) - zeitwerk (2.7.0) + zeitwerk (2.7.1) PLATFORMS aarch64-linux diff --git a/app/controllers/api/templates_controller.rb b/app/controllers/api/templates_controller.rb index 94d2c8f35..a695e7289 100644 --- a/app/controllers/api/templates_controller.rb +++ b/app/controllers/api/templates_controller.rb @@ -100,7 +100,7 @@ def template_params :name, :external_id, { - submitters: [%i[name uuid is_requester invite_by_uuid linked_to_uuid email]], + submitters: [%i[name uuid is_requester invite_by_uuid optional_invite_by_uuid linked_to_uuid email]], fields: [[:uuid, :submitter_uuid, :name, :type, :required, :readonly, :default_value, :title, :description, diff --git a/app/controllers/console_redirect_controller.rb b/app/controllers/console_redirect_controller.rb index d37cf08b7..dd80e9fea 100644 --- a/app/controllers/console_redirect_controller.rb +++ b/app/controllers/console_redirect_controller.rb @@ -6,7 +6,7 @@ class ConsoleRedirectController < ApplicationController def index if request.path == '/upgrade' - params[:redir] = Docuseal.multitenant? ? "#{Docuseal::CONSOLE_URL}/plans" : "#{Docuseal::CONSOLE_URL}/on_premise" + params[:redir] = Docuseal.multitenant? ? "#{Docuseal::CONSOLE_URL}/plans" : "#{Docuseal::CONSOLE_URL}/on_premises" end params[:redir] = "#{Docuseal::CONSOLE_URL}/manage" if request.path == '/manage' diff --git a/app/controllers/embed_scripts_controller.rb b/app/controllers/embed_scripts_controller.rb index 874c21f31..a40ed79c9 100644 --- a/app/controllers/embed_scripts_controller.rb +++ b/app/controllers/embed_scripts_controller.rb @@ -9,7 +9,7 @@ class EmbedScriptsController < ActionController::Metal

Upgrade to Pro

Unlock embedded components by upgrading to Pro

- + Learn More
diff --git a/app/controllers/submit_form_invite_controller.rb b/app/controllers/submit_form_invite_controller.rb index 2d32425dc..1d42779ce 100644 --- a/app/controllers/submit_form_invite_controller.rb +++ b/app/controllers/submit_form_invite_controller.rb @@ -9,13 +9,15 @@ def create return head :unprocessable_entity unless can_invite?(submitter) - invite_submitters = filter_invite_submitters(submitter) + invite_submitters = filter_invite_submitters(submitter, 'invite_by_uuid') + optional_invite_submitters = filter_invite_submitters(submitter, 'optional_invite_by_uuid') ApplicationRecord.transaction do - invite_submitters.each do |item| + (invite_submitters + optional_invite_submitters).each do |item| attrs = submitters_attributes.find { |e| e[:uuid] == item['uuid'] } next unless attrs + next if attrs[:email].blank? submitter.submission.submitters.create!(**attrs, account_id: submitter.account_id) @@ -46,9 +48,9 @@ def can_invite?(submitter) !submitter.submission.template.archived_at? end - def filter_invite_submitters(submitter) + def filter_invite_submitters(submitter, key = 'invite_by_uuid') (submitter.submission.template_submitters || submitter.submission.template.submitters).select do |s| - s['invite_by_uuid'] == submitter.uuid && submitter.submission.submitters.none? { |e| e.uuid == s['uuid'] } + s[key] == submitter.uuid && submitter.submission.submitters.none? { |e| e.uuid == s['uuid'] } end end diff --git a/app/controllers/templates_controller.rb b/app/controllers/templates_controller.rb index c023a5e7a..3010d4abb 100644 --- a/app/controllers/templates_controller.rb +++ b/app/controllers/templates_controller.rb @@ -111,7 +111,7 @@ def template_params params.require(:template).permit( :name, { schema: [[:attachment_uuid, :name, { conditions: [%i[field_uuid value action operation]] }]], - submitters: [%i[name uuid is_requester linked_to_uuid invite_by_uuid email]], + submitters: [%i[name uuid is_requester linked_to_uuid invite_by_uuid optional_invite_by_uuid email]], fields: [[:uuid, :submitter_uuid, :name, :type, :required, :readonly, :default_value, :title, :description, diff --git a/app/controllers/templates_recipients_controller.rb b/app/controllers/templates_recipients_controller.rb index 745fee93f..a12980947 100644 --- a/app/controllers/templates_recipients_controller.rb +++ b/app/controllers/templates_recipients_controller.rb @@ -17,38 +17,47 @@ def create private def submitters_params - params.require(:template).permit( - submitters: [%i[name uuid is_requester invite_by_uuid linked_to_uuid email option]] - ).fetch(:submitters, {}).values.filter_map do |s| + permit_params = { submitters: [%i[name uuid is_requester optional_invite_by_uuid + invite_by_uuid linked_to_uuid email option]] } + + params.require(:template).permit(permit_params).fetch(:submitters, {}).values.filter_map do |s| next if s[:uuid].blank? - if s[:is_requester] == '1' && s[:invite_by_uuid].blank? + if s[:is_requester] == '1' && s[:invite_by_uuid].blank? && s[:optional_invite_by_uuid].blank? s[:is_requester] = true else s.delete(:is_requester) end s.delete(:invite_by_uuid) if s[:invite_by_uuid].blank? + s.delete(:optional_invite_by_uuid) if s[:optional_invite_by_uuid].blank? - option = s.delete(:option) - - if option.present? - case option - when 'is_requester' - s[:is_requester] = true - when 'not_set' - s.delete(:is_requester) - s.delete(:email) - s.delete(:linked_to_uuid) - s.delete(:invite_by_uuid) - when /\Alinked_to_(.*)\z/ - s[:linked_to_uuid] = ::Regexp.last_match(-1) - when /\Ainvite_by_(.*)\z/ - s[:invite_by_uuid] = ::Regexp.last_match(-1) - end - end + normalize_option_value(s) + end + end - s + def normalize_option_value(attrs) + option = attrs.delete(:option) + + if option.present? + case option + when 'is_requester' + attrs[:is_requester] = true + when 'not_set' + attrs.delete(:is_requester) + attrs.delete(:email) + attrs.delete(:linked_to_uuid) + attrs.delete(:invite_by_uuid) + attrs.delete(:optional_invite_by_uuid) + when /\Alinked_to_(.*)\z/ + attrs[:linked_to_uuid] = ::Regexp.last_match(-1) + when /\Aoptional_invite_by_(.*)\z/ + attrs[:optional_invite_by_uuid] = ::Regexp.last_match(-1) + when /\Ainvite_by_(.*)\z/ + attrs[:invite_by_uuid] = ::Regexp.last_match(-1) + end end + + attrs end end diff --git a/app/javascript/application.js b/app/javascript/application.js index 2239d6389..d35641ad1 100644 --- a/app/javascript/application.js +++ b/app/javascript/application.js @@ -31,6 +31,7 @@ import LinkedInput from './elements/linked_input' import CheckboxGroup from './elements/checkbox_group' import MaskedInput from './elements/masked_input' import SetDateButton from './elements/set_date_button' +import IndeterminateCheckbox from './elements/indeterminate_checkbox' import * as TurboInstantClick from './lib/turbo_instant_click' @@ -99,6 +100,7 @@ safeRegisterElement('linked-input', LinkedInput) safeRegisterElement('checkbox-group', CheckboxGroup) safeRegisterElement('masked-input', MaskedInput) safeRegisterElement('set-date-button', SetDateButton) +safeRegisterElement('indeterminate-checkbox', IndeterminateCheckbox) safeRegisterElement('template-builder', class extends HTMLElement { connectedCallback () { diff --git a/app/javascript/elements/indeterminate_checkbox.js b/app/javascript/elements/indeterminate_checkbox.js new file mode 100644 index 000000000..24648943b --- /dev/null +++ b/app/javascript/elements/indeterminate_checkbox.js @@ -0,0 +1,35 @@ +export default class extends HTMLElement { + connectedCallback () { + if (this.dataset.indeterminate === 'true') { + this.checkbox.indeterminate = true + this.checkbox.readOnly = true + } + + this.checkbox.addEventListener('click', () => { + this.checkbox.setAttribute('name', this.dataset.name) + + if (this.showIndeterminateEl) { + this.showIndeterminateEl.classList.add('hidden') + } + + if (this.checkbox.readOnly) { + this.checkbox.checked = this.checkbox.readOnly = false + } else if (!this.checkbox.checked) { + if (this.showIndeterminateEl) { + this.showIndeterminateEl.classList.remove('hidden') + } + + this.checkbox.setAttribute('name', this.dataset.indeterminateName) + this.checkbox.checked = this.checkbox.readOnly = this.checkbox.indeterminate = true + } + }) + } + + get checkbox () { + return this.querySelector('input[type="checkbox"]') + } + + get showIndeterminateEl () { + return document.getElementById(this.dataset.showIndeterminateId) + } +} diff --git a/app/javascript/form.js b/app/javascript/form.js index f9583da62..2e07f0334 100644 --- a/app/javascript/form.js +++ b/app/javascript/form.js @@ -15,6 +15,7 @@ safeRegisterElement('submission-form', class extends HTMLElement { this.app = createApp(Form, { submitter: JSON.parse(this.dataset.submitter), inviteSubmitters: JSON.parse(this.dataset.inviteSubmitters), + optionalInviteSubmitters: JSON.parse(this.dataset.optionalInviteSubmitters), schema: JSON.parse(this.dataset.schema), canSendEmail: this.dataset.canSendEmail === 'true', previousSignatureValue: this.dataset.previousSignatureValue, diff --git a/app/javascript/submission_form/form.vue b/app/javascript/submission_form/form.vue index f8c4031db..4468e4948 100644 --- a/app/javascript/submission_form/form.vue +++ b/app/javascript/submission_form/form.vue @@ -498,6 +498,7 @@ [] }, + optionalInviteSubmitters: { + type: Array, + required: false, + default: () => [] + }, withSignatureId: { type: Boolean, required: false, @@ -637,6 +643,11 @@ export default { required: false, default: '-80px' }, + orderAsOnPage: { + type: Boolean, + required: false, + default: false + }, requireSigningReason: { type: Boolean, required: false, @@ -956,6 +967,38 @@ export default { return acc }, []) + if (this.orderAsOnPage) { + const fieldAreasIndex = {} + const attachmentUuids = Object.keys(this.attachmentConditionsIndex) + + const sortArea = (aArea, bArea) => { + if (aArea.attachment_uuid === bArea.attachment_uuid) { + if (aArea.page === bArea.page) { + if (Math.abs(aArea.y - bArea.y) < 0.01) { + if (aArea.x === bArea.x) { + return 0 + } else { + return aArea.x - bArea.x + } + } else { + return aArea.y - bArea.y + } + } else { + return aArea.page - bArea.page + } + } else { + return attachmentUuids.indexOf(aArea.attachment_uuid) - attachmentUuids.indexOf(bArea.attachment_uuid) + } + } + + sortedFields.sort((aField, bField) => { + const aArea = (fieldAreasIndex[aField.uuid] ||= [...(aField.areas || [])].sort(sortArea)[0]) + const bArea = (fieldAreasIndex[bField.uuid] ||= [...(bField.areas || [])].sort(sortArea)[0]) + + return sortArea(aArea, bArea) + }) + } + if (verificationFields.length) { sortedFields.push(verificationFields.pop()) } @@ -1320,7 +1363,7 @@ export default { const formData = new FormData(this.$refs.form) const isLastStep = (submitStep === this.stepFields.length - 1) || forceComplete - if (isLastStep && !emptyRequiredField && !this.inviteSubmitters.length) { + if (isLastStep && !emptyRequiredField && !this.inviteSubmitters.length && !this.optionalInviteSubmitters.length) { formData.append('completed', 'true') } @@ -1359,7 +1402,7 @@ export default { if (emptyRequiredField === nextStep) { this.showFillAllRequiredFields = true } - } else if (this.inviteSubmitters.length) { + } else if (this.inviteSubmitters.length || this.optionalInviteSubmitters.length) { this.isInvite = true } else { this.performComplete(response) diff --git a/app/javascript/submission_form/i18n.js b/app/javascript/submission_form/i18n.js index 3db90855d..ab1e06852 100644 --- a/app/javascript/submission_form/i18n.js +++ b/app/javascript/submission_form/i18n.js @@ -93,7 +93,8 @@ const en = { reupload: 'Reupload', upload: 'Upload', files: 'Files', - signature_is_too_small_please_redraw: 'Signature is too small. Please redraw.' + signature_is_too_small_please_redraw: 'Signature is too small. Please redraw.', + wait_countdown_seconds: 'Wait {countdown} seconds' } const es = { @@ -190,7 +191,8 @@ const es = { reupload: 'Volver a subir', upload: 'Subir', files: 'Archivos', - signature_is_too_small_please_redraw: 'La firma es demasiado pequeña. Por favor, dibújala de nuevo.' + signature_is_too_small_please_redraw: 'La firma es demasiado pequeña. Por favor, dibújala de nuevo.', + wait_countdown_seconds: 'Espera {countdown} segundos' } const it = { @@ -287,7 +289,8 @@ const it = { reupload: 'Ricarica', upload: 'Carica', files: 'File', - signature_is_too_small_please_redraw: 'La firma è troppo piccola. Ridisegnala per favore.' + signature_is_too_small_please_redraw: 'La firma è troppo piccola. Ridisegnala per favore.', + wait_countdown_seconds: 'Attendi {countdown} secondi' } const de = { @@ -384,7 +387,8 @@ const de = { reupload: 'Erneut hochladen', upload: 'Hochladen', files: 'Dateien', - signature_is_too_small_please_redraw: 'Die Unterschrift ist zu klein. Bitte erneut zeichnen.' + signature_is_too_small_please_redraw: 'Die Unterschrift ist zu klein. Bitte erneut zeichnen.', + wait_countdown_seconds: 'Warte {countdown} Sekunden' } const fr = { @@ -481,7 +485,8 @@ const fr = { reupload: 'Recharger', upload: 'Télécharger', files: 'Fichiers', - signature_is_too_small_please_redraw: 'La signature est trop petite. Veuillez la redessiner.' + signature_is_too_small_please_redraw: 'La signature est trop petite. Veuillez la redessiner.', + wait_countdown_seconds: 'Attendez {countdown} secondes' } const pl = { @@ -675,7 +680,8 @@ const uk = { reupload: 'Перезавантажити', upload: 'Завантажити', files: 'Файли', - signature_is_too_small_please_redraw: 'Підпис занадто малий. Будь ласка, перемалюйте його.' + signature_is_too_small_please_redraw: 'Підпис занадто малий. Будь ласка, перемалюйте його.', + wait_countdown_seconds: 'Зачекайте {countdown} секунд' } const cs = { @@ -772,7 +778,8 @@ const cs = { reupload: 'Znovu nahrát', upload: 'Nahrát', files: 'Soubory', - signature_is_too_small_please_redraw: 'Podpis je příliš malý. Prosím, překreslete ho.' + signature_is_too_small_please_redraw: 'Podpis je příliš malý. Prosím, překreslete ho.', + wait_countdown_seconds: 'Počkejte {countdown} sekund' } const pt = { @@ -869,7 +876,8 @@ const pt = { reupload: 'Reenviar', upload: 'Carregar', files: 'Arquivos', - signature_is_too_small_please_redraw: 'A assinatura é muito pequena. Por favor, redesenhe-a.' + signature_is_too_small_please_redraw: 'A assinatura é muito pequena. Por favor, redesenhe-a.', + wait_countdown_seconds: 'Aguarde {countdown} segundos' } const he = { @@ -967,7 +975,8 @@ const he = { reupload: 'העלה שוב', upload: 'העלאה', files: 'קבצים', - signature_is_too_small_please_redraw: 'החתימה קטנה מדי. אנא צייר מחדש.' + signature_is_too_small_please_redraw: 'החתימה קטנה מדי. אנא צייר מחדש.', + wait_countdown_seconds: 'המתן {countdown} שניות' } const nl = { @@ -1065,7 +1074,8 @@ const nl = { reupload: 'Opnieuw uploaden', upload: 'Uploaden', files: 'Bestanden', - signature_is_too_small_please_redraw: 'De handtekening is te klein. Teken deze opnieuw, alstublieft.' + signature_is_too_small_please_redraw: 'De handtekening is te klein. Teken deze opnieuw, alstublieft.', + wait_countdown_seconds: 'Wacht {countdown} seconden' } const ar = { @@ -1162,7 +1172,8 @@ const ar = { reupload: 'إعادة التحميل', upload: 'تحميل', files: 'الملفات', - signature_is_too_small_please_redraw: 'التوقيع صغير جدًا. يرجى إعادة الرسم.' + signature_is_too_small_please_redraw: 'التوقيع صغير جدًا. يرجى إعادة الرسم.', + wait_countdown_seconds: 'انتظر {countdown} ثانية' } const ko = { @@ -1258,7 +1269,8 @@ const ko = { reupload: '다시 업로드', upload: '업로드', files: '파일', - signature_is_too_small_please_redraw: '서명이 너무 작습니다. 다시 그려주세요.' + signature_is_too_small_please_redraw: '서명이 너무 작습니다. 다시 그려주세요.', + wait_countdown_seconds: '{countdown}초 기다리세요' } const i18n = { en, es, it, de, fr, pl, uk, cs, pt, he, nl, ar, ko } diff --git a/app/javascript/submission_form/invite_form.vue b/app/javascript/submission_form/invite_form.vue index c32cbd539..eefe76616 100644 --- a/app/javascript/submission_form/invite_form.vue +++ b/app/javascript/submission_form/invite_form.vue @@ -12,7 +12,7 @@ :value="authenticityToken" >
@@ -26,7 +26,7 @@ dir="auto" class="label text-2xl" > - {{ t('invite') }} {{ submitter.name }} + {{ t('invite') }} {{ submitter.name }} @@ -53,7 +53,7 @@ class="mr-1 animate-spin" /> - {{ t('submit') }} + {{ t('complete') }} [] + }, url: { type: String, required: true diff --git a/app/javascript/submission_form/phone_step.vue b/app/javascript/submission_form/phone_step.vue index 844eca009..c6c320e47 100644 --- a/app/javascript/submission_form/phone_step.vue +++ b/app/javascript/submission_form/phone_step.vue @@ -52,7 +52,14 @@ > {{ t('change_phone_number') }} + + {{ t('wait_countdown_seconds').replace('{countdown}', resendCodeCountdown) }} + country.tz.includes(tz)) || this.countries[0] } }, + beforeUnmount () { + if (this.interval) { + clearInterval(this.interval) + } + }, methods: { emitSubmit: throttle(function (e) { this.$emit('submit') @@ -246,13 +260,28 @@ export default { } }, resendCode () { - this.isResendLoading = true + if (this.codeSentAt && Date.now() - this.codeSentAt < 15000) { + this.startResendCodeCountdown() + } else { + this.isResendLoading = true + + this.sendVerificationCode().then(() => { + alert(this.t('verification_code_has_been_resent')) + }).finally(() => { + this.isResendLoading = false + }) + } + }, + startResendCodeCountdown () { + this.resendCodeCountdown = 15 - parseInt((Date.now() - this.codeSentAt) / 1000) - this.sendVerificationCode().finally(() => { - alert(this.t('verification_code_has_been_resent')) + this.interval = setInterval(() => { + this.resendCodeCountdown-- - this.isResendLoading = false - }) + if (this.resendCodeCountdown <= 0) { + clearInterval(this.interval) + } + }, 1000) }, sendVerificationCode () { return fetch(this.baseUrl + '/api/send_phone_verification_code', { @@ -264,12 +293,20 @@ export default { }), headers: { 'Content-Type': 'application/json' } }).then(async (resp) => { - if (resp.status === 422) { + if ([422, 429].includes(resp.status)) { const data = await resp.json() - alert(this.t('number_phone_is_invalid').replace('{number}', this.fullInternationalPhoneValue)) + if (resp.status === 422) { + alert(this.t('number_phone_is_invalid').replace('{number}', this.fullInternationalPhoneValue)) + } else if (resp.status === 429) { + alert(data.error) + } return Promise.reject(new Error(data.error)) + } else if (resp.ok) { + this.codeSentAt = Date.now() + + return resp } }) }, diff --git a/app/javascript/submission_form/signature_step.vue b/app/javascript/submission_form/signature_step.vue index 4a8fbe9c8..f1f6a5be0 100644 --- a/app/javascript/submission_form/signature_step.vue +++ b/app/javascript/submission_form/signature_step.vue @@ -20,7 +20,7 @@
@@ -37,7 +37,7 @@ @@ -54,7 +54,7 @@ @@ -81,10 +81,10 @@ @click.prevent="remove" > - {{ t('redraw') }} + {{ t(format === 'upload' ? 'reupload' : 'redraw') }} @@ -131,7 +131,18 @@ :src="attachmentsIndex[modelValue || computedPreviousValue].url" class="mx-auto bg-white border border-base-300 rounded max-h-44" > -
+ +
-