mirror of
https://github.com/docusealco/docuseal
synced 2026-11-04 14:42:32 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e685bbc32 | ||
|
|
0b3ca0de96 | ||
|
|
aed3f1f141 | ||
|
|
1a7981e919 | ||
|
|
4add805371 | ||
|
|
66c18317b7 | ||
|
|
9edbb885b4 | ||
|
|
c67c80d6bc | ||
|
|
0399531e0c | ||
|
|
54426556d9 | ||
|
|
fa74ee1755 | ||
|
|
eb53af12c4 | ||
|
|
38efcb99c0 | ||
|
|
a92d914674 | ||
|
|
219a698aa5 | ||
|
|
5bed6448dc | ||
|
|
6d2a8ca3d3 | ||
|
|
bfb2e0043d | ||
|
|
922101b12c | ||
|
|
9ff41a0c4a | ||
|
|
071f187605 | ||
|
|
ab6cd0e89c | ||
|
|
97c0dad7ff | ||
|
|
bc0917a190 | ||
|
|
5718acecff | ||
|
|
395d43dbeb | ||
|
|
37de5e0d0d | ||
|
|
44e848a1f0 | ||
|
|
f6d59d731c | ||
|
|
87b039e709 | ||
|
|
e0c6a4e08b | ||
|
|
80b06550c2 | ||
|
|
ec8e35ed7a | ||
|
|
4dc325a6bb | ||
|
|
d52c57df72 | ||
|
|
0cb2557447 | ||
|
|
cd05c1fd19 | ||
|
|
5458e82b6e | ||
|
|
c59c58624b | ||
|
|
2da059d440 | ||
|
|
b0aabd9e79 | ||
|
|
f1cd60dfad | ||
|
|
a264a8522a | ||
|
|
794e339425 | ||
|
|
9aaf61e658 | ||
|
|
37faa7ece6 | ||
|
|
666e3fde4c | ||
|
|
995e040a4e | ||
|
|
185ac2cbee | ||
|
|
a4e372ff5c | ||
|
|
b4834c7674 | ||
|
|
c24ae1751e | ||
|
|
9bd0ad70c4 | ||
|
|
f210f1d8bf | ||
|
|
92b61992f2 | ||
|
|
e3f3895aac | ||
|
|
c696890cf5 | ||
|
|
288d204d8e | ||
|
|
5a96f4f1eb | ||
|
|
5ff4f9889e | ||
|
|
78fb93fd02 | ||
|
|
3c83d459bd | ||
|
|
6cdb8e7132 | ||
|
|
e2b93cbcab | ||
|
|
a59cc7c44e | ||
|
|
1984b99e04 | ||
|
|
fe3e8fba3e | ||
|
|
97ecdf2ff0 | ||
|
|
e4c62087d0 | ||
|
|
1f9faddfd0 | ||
|
|
6dff6483bf | ||
|
|
2e43eb189f | ||
|
|
cee471bfa5 | ||
|
|
81521dee81 | ||
|
|
3cac62d0fd | ||
|
|
24ed3f2e7c | ||
|
|
02ad620c9f | ||
|
|
9051d5a835 | ||
|
|
0abd270f56 | ||
|
|
fde9affd70 | ||
|
|
4edee2971a | ||
|
|
313d7f8408 | ||
|
|
db60810272 | ||
|
|
b3507c1a3a | ||
|
|
19b5a07bac | ||
|
|
616aced4e4 | ||
|
|
cc218fc0b0 | ||
|
|
c59f2a022a | ||
|
|
2a240cebeb | ||
|
|
b950b1c63e | ||
|
|
4ad0b04b7f | ||
|
|
8edf6405b0 | ||
|
|
8e3caae849 | ||
|
|
ac9a5c9556 | ||
|
|
20d09f6dff | ||
|
|
333a758688 | ||
|
|
2c8b4d5816 | ||
|
|
9ce822bce8 | ||
|
|
0a382a688c | ||
|
|
bd3008a35a | ||
|
|
e9bd11cb2e | ||
|
|
ca429bc445 | ||
|
|
21a0c54843 | ||
|
|
2f41d13009 | ||
|
|
f69dcf77de | ||
|
|
32596d738c | ||
|
|
e7e5d0e776 |
No files matched your search
+12
-12
@@ -7,13 +7,13 @@ jobs:
|
||||
name: Rubocop
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: 3.3.4
|
||||
ruby-version: 3.4.1
|
||||
- name: Cache gems
|
||||
uses: actions/cache@v1
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: vendor/bundle
|
||||
key: ${{ runner.os }}-gem-${{ hashFiles('**/Gemfile.lock') }}
|
||||
@@ -31,13 +31,13 @@ jobs:
|
||||
name: Erblint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: 3.3.4
|
||||
ruby-version: 3.4.1
|
||||
- name: Cache gems
|
||||
uses: actions/cache@v1
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: vendor/bundle
|
||||
key: ${{ runner.os }}-gem-${{ hashFiles('**/Gemfile.lock') }}
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
name: ESLint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
@@ -63,7 +63,7 @@ jobs:
|
||||
- name: Cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
- uses: actions/cache@v1
|
||||
- uses: actions/cache@v4
|
||||
id: yarn-cache
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
@@ -96,11 +96,11 @@ jobs:
|
||||
--health-retries 5
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: 3.3.4
|
||||
ruby-version: 3.4.1
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
@@ -110,12 +110,12 @@ jobs:
|
||||
with:
|
||||
chrome-version: 125
|
||||
- name: Cache node_modules
|
||||
uses: actions/cache@v1
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: node_modules
|
||||
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
|
||||
- name: Cache gems
|
||||
uses: actions/cache@v1
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: vendor/bundle
|
||||
key: ${{ runner.os }}-gem-${{ hashFiles('**/Gemfile.lock') }}
|
||||
|
||||
@@ -7,7 +7,7 @@ on:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04-arm
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
@@ -25,22 +25,22 @@ jobs:
|
||||
tags: |
|
||||
type=semver,pattern={{version}}
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Create .version file
|
||||
run: echo ${{ github.ref_name }} > .version
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v4
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
|
||||
+1
-1
@@ -69,7 +69,7 @@ RSpec/MultipleMemoizedHelpers:
|
||||
Max: 9
|
||||
|
||||
Metrics/BlockNesting:
|
||||
Max: 4
|
||||
Max: 5
|
||||
|
||||
Rails/I18nLocaleTexts:
|
||||
Enabled: false
|
||||
|
||||
+4
-4
@@ -1,4 +1,4 @@
|
||||
FROM ruby:3.3.4-alpine as fonts
|
||||
FROM ruby:3.4.1-alpine as fonts
|
||||
|
||||
WORKDIR /fonts
|
||||
|
||||
@@ -6,7 +6,7 @@ RUN apk --no-cache add fontforge wget && wget https://github.com/satbyy/go-noto-
|
||||
|
||||
RUN fontforge -lang=py -c 'font1 = fontforge.open("FreeSans.ttf"); font2 = fontforge.open("NotoSansSymbols2-Regular.ttf"); font1.mergeFonts(font2); font1.generate("FreeSans.ttf")'
|
||||
|
||||
FROM ruby:3.3.4-alpine as webpack
|
||||
FROM ruby:3.4.1-alpine as webpack
|
||||
|
||||
ENV RAILS_ENV=production
|
||||
ENV NODE_ENV=production
|
||||
@@ -32,7 +32,7 @@ COPY ./app/views ./app/views
|
||||
|
||||
RUN echo "gem 'shakapacker'" > Gemfile && ./bin/shakapacker
|
||||
|
||||
FROM ruby:3.3.4-alpine as app
|
||||
FROM ruby:3.4.1-alpine as app
|
||||
|
||||
ENV RAILS_ENV=production
|
||||
ENV BUNDLE_WITHOUT="development:test"
|
||||
@@ -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
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
source 'https://rubygems.org'
|
||||
|
||||
ruby '3.3.4'
|
||||
ruby '3.4.1'
|
||||
|
||||
gem 'arabic-letter-connector', require: 'arabic-letter-connector/logic'
|
||||
gem 'aws-sdk-s3', require: false
|
||||
|
||||
+176
-155
@@ -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)
|
||||
@@ -221,9 +223,12 @@ GEM
|
||||
concurrent-ruby (~> 1.1)
|
||||
webrick (~> 1.7)
|
||||
websocket-driver (~> 0.7)
|
||||
ffi (1.17.0)
|
||||
ffi (1.17.0-aarch64-linux-gnu)
|
||||
ffi (1.17.0-x86_64-linux-gnu)
|
||||
ffi (1.17.1)
|
||||
ffi (1.17.1-aarch64-linux-gnu)
|
||||
ffi (1.17.1-aarch64-linux-musl)
|
||||
ffi (1.17.1-arm64-darwin)
|
||||
ffi (1.17.1-x86_64-linux-gnu)
|
||||
ffi (1.17.1-x86_64-linux-musl)
|
||||
geom2d (0.4.1)
|
||||
globalid (1.2.1)
|
||||
activesupport (>= 6.1)
|
||||
@@ -235,9 +240,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 +250,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 +259,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 +280,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 +299,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 +319,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 +336,34 @@ GEM
|
||||
timeout
|
||||
net-smtp (0.5.0)
|
||||
net-protocol
|
||||
nio4r (2.7.3)
|
||||
nokogiri (1.16.8)
|
||||
nio4r (2.7.4)
|
||||
nokogiri (1.18.2)
|
||||
mini_portile2 (~> 2.8.2)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.16.8-aarch64-linux)
|
||||
nokogiri (1.18.2-aarch64-linux-gnu)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.16.8-x86_64-linux)
|
||||
nokogiri (1.18.2-aarch64-linux-musl)
|
||||
racc (~> 1.4)
|
||||
oj (3.16.6)
|
||||
nokogiri (1.18.2-arm64-darwin)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.2-x86_64-linux-gnu)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.2-x86_64-linux-musl)
|
||||
racc (~> 1.4)
|
||||
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 +374,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 +393,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 +433,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 +450,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 +464,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 +472,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 +529,20 @@ GEM
|
||||
simplecov-html (0.13.1)
|
||||
simplecov_json_formatter (0.1.4)
|
||||
smart_properties (1.17.0)
|
||||
sqlite3 (2.1.0)
|
||||
sqlite3 (2.5.0)
|
||||
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.5.0-aarch64-linux-gnu)
|
||||
sqlite3 (2.5.0-aarch64-linux-musl)
|
||||
sqlite3 (2.5.0-arm64-darwin)
|
||||
sqlite3 (2.5.0-x86_64-linux-gnu)
|
||||
sqlite3 (2.5.0-x86_64-linux-musl)
|
||||
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 +554,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,18 +571,21 @@ 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
|
||||
aarch64-linux-musl
|
||||
arm64-darwin
|
||||
ruby
|
||||
x86_64-linux
|
||||
x86_64-linux-musl
|
||||
|
||||
DEPENDENCIES
|
||||
annotate
|
||||
@@ -628,7 +649,7 @@ DEPENDENCIES
|
||||
webmock
|
||||
|
||||
RUBY VERSION
|
||||
ruby 3.3.4p94
|
||||
ruby 3.4.1p0
|
||||
|
||||
BUNDLED WITH
|
||||
2.5.3
|
||||
@@ -50,7 +50,7 @@ class AccountsController < ApplicationController
|
||||
# rubocop:disable Layout/LineLength
|
||||
render turbo_stream: turbo_stream.replace(
|
||||
:account_delete_button,
|
||||
html: helpers.tag.p(I18n.t('your_account_removal_request_will_be_processed_within_2_weeks_please_contact_us_if_you_want_to_keep_your_account'))
|
||||
html: helpers.tag.p(I18n.t('your_account_removal_request_will_be_processed_within_2_months_please_contact_us_if_you_want_to_keep_your_account'))
|
||||
)
|
||||
# rubocop:enable Layout/LineLength
|
||||
end
|
||||
|
||||
@@ -42,6 +42,7 @@ module Api
|
||||
|
||||
is_authorized = attachment.name.in?(%w[logo preview_images]) ||
|
||||
(current_user && attachment.record.account.id == current_user.account_id) ||
|
||||
(current_user && !Docuseal.multitenant? && current_user.role == 'superadmin') ||
|
||||
!attachment.record.account.account_configs
|
||||
.find_or_initialize_by(key: AccountConfig::DOWNLOAD_LINKS_AUTH_KEY).value
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@ module Api
|
||||
submissions = submissions.joins(template: :folder).where(folder: { name: params[:template_folder] })
|
||||
end
|
||||
|
||||
submissions = Submissions::Filter.call(submissions, current_user, params)
|
||||
|
||||
submissions = paginate(submissions.preload(:created_by_user, :submitters,
|
||||
template: :folder,
|
||||
combined_document_attachment: :blob,
|
||||
@@ -116,7 +118,19 @@ module Api
|
||||
end
|
||||
end
|
||||
|
||||
json = { submitters: json } if request.path.ends_with?('/init')
|
||||
if request.path.ends_with?('/init')
|
||||
json =
|
||||
if submissions.size == 1
|
||||
{
|
||||
id: submissions.first.id,
|
||||
submitters: json,
|
||||
expire_at: submissions.first.expire_at,
|
||||
created_at: submissions.first.created_at
|
||||
}
|
||||
else
|
||||
{ submitters: json }
|
||||
end
|
||||
end
|
||||
|
||||
json
|
||||
end
|
||||
@@ -178,7 +192,7 @@ module Api
|
||||
key = params.key?(:submission) ? :submission : :submissions
|
||||
|
||||
params.permit(
|
||||
key => [permitted_attrs]
|
||||
{ key => [permitted_attrs] }, { key => permitted_attrs }
|
||||
).fetch(key, [])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -100,7 +100,7 @@ module Api
|
||||
: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,
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -9,7 +9,7 @@ class EmbedScriptsController < ActionController::Metal
|
||||
<h2>Upgrade to Pro</h2>
|
||||
<p>Unlock embedded components by upgrading to Pro</p>
|
||||
<div style="margin-top: 40px;">
|
||||
<a href="#{Docuseal::CONSOLE_URL}/on_premise" target="_blank" style="padding: 15px 25px; background-color: #222; color: white; text-decoration: none; border-radius: 5px; font-size: 16px; cursor: pointer;">
|
||||
<a href="#{Docuseal::CONSOLE_URL}/on_premises" target="_blank" style="padding: 15px 25px; background-color: #222; color: white; text-decoration: none; border-radius: 5px; font-size: 16px; cursor: pointer;">
|
||||
Learn More
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class PwaController < ActionController::Base
|
||||
end
|
||||
@@ -64,6 +64,7 @@ class StartFormController < ApplicationController
|
||||
.or(template.submissions.where(expire_at: nil)).where(archived_at: nil))
|
||||
.order(id: :desc)
|
||||
.where(declined_at: nil)
|
||||
.where(external_id: nil)
|
||||
.then { |rel| params[:resubmit].present? ? rel.where(completed_at: nil) : rel }
|
||||
.find_or_initialize_by(**submitter_params.compact_blank)
|
||||
end
|
||||
|
||||
@@ -20,7 +20,10 @@ class SubmissionsPreviewController < ApplicationController
|
||||
|
||||
@submission ||= Submission.find_by!(slug: params[:slug])
|
||||
|
||||
if @submission.account.archived_at? || (!@submission.submitters.all?(&:completed_at?) && current_user.blank?)
|
||||
raise ActionController::RoutingError if @submission.account.archived_at?
|
||||
|
||||
if !@submission.submitters.all?(&:completed_at?) && !signature_valid &&
|
||||
(!current_user || !current_ability.can?(:read, @submission))
|
||||
raise ActionController::RoutingError, I18n.t('not_found')
|
||||
end
|
||||
|
||||
|
||||
@@ -9,13 +9,15 @@ class SubmitFormInviteController < ApplicationController
|
||||
|
||||
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 @@ class SubmitFormInviteController < ApplicationController
|
||||
!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
|
||||
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class SubmittersResubmitController < ApplicationController
|
||||
load_and_authorize_resource :submitter, parent: false
|
||||
|
||||
def update
|
||||
return redirect_to submit_form_path(slug: @submitter.slug) if @submitter.email != current_user.email
|
||||
|
||||
submission = @submitter.template.submissions.new(created_by_user: current_user,
|
||||
submitters_order: :preserved,
|
||||
**@submitter.submission.slice(:template_fields,
|
||||
:account_id,
|
||||
:template_schema,
|
||||
:template_submitters,
|
||||
:preferences))
|
||||
|
||||
@submitter.submission.submitters.each do |submitter|
|
||||
new_submitter = submission.submitters.new(submitter.slice(:uuid, :email, :phone, :name,
|
||||
:preferences, :metadata, :account_id))
|
||||
|
||||
next unless submitter.uuid == @submitter.uuid
|
||||
|
||||
assign_submitter_values(new_submitter, submitter)
|
||||
|
||||
@new_submitter ||= new_submitter
|
||||
end
|
||||
|
||||
submission.save!
|
||||
|
||||
redirect_to submit_form_path(slug: @new_submitter.slug)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def assign_submitter_values(new_submitter, submitter)
|
||||
attachments_index = submitter.attachments.index_by(&:uuid)
|
||||
|
||||
submitter.submission.template_fields.each do |field|
|
||||
next if field['submitter_uuid'] != submitter.uuid
|
||||
next if field['default_value'] == '{{date}}'
|
||||
next if field['type'] == 'stamp'
|
||||
next if field['type'] == 'signature'
|
||||
next if field.dig('preferences', 'formula').present?
|
||||
|
||||
value = submitter.values[field['uuid']]
|
||||
|
||||
next if value.blank?
|
||||
|
||||
if field['type'].in?(%w[image file initials])
|
||||
Array.wrap(value).each do |attachment_uuid|
|
||||
new_submitter.attachments << attachments_index[attachment_uuid].dup
|
||||
end
|
||||
end
|
||||
|
||||
new_submitter.values[field['uuid']] = value
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -111,7 +111,7 @@ class TemplatesController < ApplicationController
|
||||
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,
|
||||
|
||||
@@ -22,7 +22,7 @@ class TemplatesPreferencesController < ApplicationController
|
||||
preferences: %i[bcc_completed request_email_subject request_email_body
|
||||
documents_copy_email_subject documents_copy_email_body
|
||||
documents_copy_email_enabled documents_copy_email_attach_audit
|
||||
documents_copy_email_attach_documents
|
||||
documents_copy_email_attach_documents documents_copy_email_reply_to
|
||||
completed_notification_email_attach_documents
|
||||
completed_redirect_url
|
||||
submitters_order
|
||||
|
||||
@@ -17,38 +17,47 @@ class TemplatesRecipientsController < ApplicationController
|
||||
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
|
||||
|
||||
s
|
||||
normalize_option_value(s)
|
||||
end
|
||||
end
|
||||
|
||||
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
|
||||
@@ -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 () {
|
||||
|
||||
@@ -20,7 +20,8 @@ export default targetable(class extends HTMLElement {
|
||||
fetch(this.dataset.src).then(async (response) => {
|
||||
if (response.ok) {
|
||||
const urls = await response.json()
|
||||
const isSafariIos = /iPhone|iPad|iPod/i.test(navigator.userAgent)
|
||||
const isMobileSafariIos = 'ontouchstart' in window && navigator.maxTouchPoints > 0 && /AppleWebKit/i.test(navigator.userAgent)
|
||||
const isSafariIos = isMobileSafariIos || /iPhone|iPad|iPod/i.test(navigator.userAgent)
|
||||
|
||||
if (isSafariIos && urls.length > 1) {
|
||||
this.downloadSafariIos(urls)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -191,13 +191,22 @@
|
||||
class="whitespace-pre-wrap text-gray-400"
|
||||
:class="{ 'w-full': field.preferences?.align }"
|
||||
>{{ field.name }}</span>
|
||||
<span v-else-if="Array.isArray(modelValue)">
|
||||
<span
|
||||
v-else-if="Array.isArray(modelValue)"
|
||||
:class="{ 'w-full': field.preferences?.align }"
|
||||
>
|
||||
{{ modelValue.join(', ') }}
|
||||
</span>
|
||||
<span v-else-if="field.type === 'date'">
|
||||
<span
|
||||
v-else-if="field.type === 'date'"
|
||||
:class="{ 'w-full': field.preferences?.align }"
|
||||
>
|
||||
{{ formattedDate }}
|
||||
</span>
|
||||
<span v-else-if="field.type === 'number'">
|
||||
<span
|
||||
v-else-if="field.type === 'number'"
|
||||
class="w-full"
|
||||
>
|
||||
{{ formatNumber(modelValue, field.preferences?.format) }}
|
||||
</span>
|
||||
<span
|
||||
@@ -447,8 +456,18 @@ export default {
|
||||
}
|
||||
},
|
||||
formatNumber (number, format) {
|
||||
if (!number && number !== 0) {
|
||||
return ''
|
||||
}
|
||||
|
||||
if (format === 'comma') {
|
||||
return new Intl.NumberFormat('en-US').format(number)
|
||||
} else if (format === 'usd') {
|
||||
return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(number)
|
||||
} else if (format === 'gbp') {
|
||||
return new Intl.NumberFormat('en-GB', { style: 'currency', currency: 'GBP', minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(number)
|
||||
} else if (format === 'eur') {
|
||||
return new Intl.NumberFormat('fr-FR', { style: 'currency', currency: 'EUR', minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(number)
|
||||
} else if (format === 'dot') {
|
||||
return new Intl.NumberFormat('de-DE').format(number)
|
||||
} else if (format === 'space') {
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
:with-label="withLabel && !withFieldPlaceholder && step.length < 2"
|
||||
:is-value-set="step.some((f) => f.uuid in values)"
|
||||
:attachments-index="attachmentsIndex"
|
||||
@click="$emit('focus-step', stepIndex)"
|
||||
@click="[$emit('focus-step', stepIndex), maybeScrollOnClick(field, area)]"
|
||||
/>
|
||||
</Teleport>
|
||||
</template>
|
||||
@@ -109,6 +109,14 @@ export default {
|
||||
areaRefs: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isMobileContainer () {
|
||||
const root = this.$root.$el.parentNode.getRootNode()
|
||||
const container = root.body || root.querySelector('div')
|
||||
|
||||
return container.style.overflow === 'hidden'
|
||||
}
|
||||
},
|
||||
beforeUpdate () {
|
||||
this.areaRefs = []
|
||||
},
|
||||
@@ -121,14 +129,16 @@ export default {
|
||||
this.scrollIntoArea(field.areas[0])
|
||||
}
|
||||
},
|
||||
maybeScrollOnClick (field, area) {
|
||||
if (['text', 'number', 'cells'].includes(field.type) && this.isMobileContainer) {
|
||||
this.scrollIntoArea(area)
|
||||
}
|
||||
},
|
||||
scrollIntoArea (area) {
|
||||
const areaRef = this.areaRefs.find((a) => a.area === area)
|
||||
|
||||
if (areaRef) {
|
||||
const root = this.$root.$el.parentNode.getRootNode()
|
||||
const container = root.body || root.querySelector('div')
|
||||
|
||||
if (container.style.overflow === 'hidden') {
|
||||
if (this.isMobileContainer) {
|
||||
this.scrollInContainer(areaRef.$el)
|
||||
} else {
|
||||
const targetRect = areaRef.$refs.scrollToElem.getBoundingClientRect()
|
||||
@@ -157,13 +167,15 @@ export default {
|
||||
const formContainer = root.getElementById('form_container')
|
||||
const container = root.body || root.querySelector('div')
|
||||
|
||||
const padding = 64
|
||||
const isAndroid = /android/i.test(navigator.userAgent)
|
||||
const padding = isAndroid ? 128 : 64
|
||||
const scrollboxTop = isAndroid ? scrollbox.getBoundingClientRect().top : 0
|
||||
const boxRect = scrollbox.children[0].getBoundingClientRect()
|
||||
const targetRect = target.getBoundingClientRect()
|
||||
|
||||
const targetTopRelativeToBox = targetRect.top - boxRect.top
|
||||
|
||||
scrollbox.scrollTo({ top: targetTopRelativeToBox - container.offsetHeight + formContainer.offsetHeight + target.offsetHeight + padding, behavior: 'smooth' })
|
||||
scrollbox.scrollTo({ top: targetTopRelativeToBox + scrollboxTop - container.offsetHeight + formContainer.offsetHeight + target.offsetHeight + padding, behavior: 'smooth' })
|
||||
},
|
||||
setAreaRef (el) {
|
||||
if (el) {
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
<MarkdownContent :string="field.description" />
|
||||
</div>
|
||||
<FileDropzone
|
||||
:message="`${t('upload')} ${field.name || t('files')}${field.required ? '' : ` (${t('optional')})`}`"
|
||||
:message="`${t('upload')} ${(field.title || field.name) || t('files')}${field.required ? '' : ` (${t('optional')})`}`"
|
||||
:submitter-slug="submitterSlug"
|
||||
:multiple="true"
|
||||
:dry-run="dryRun"
|
||||
|
||||
@@ -215,7 +215,8 @@ export default {
|
||||
fetch(this.baseUrl + `/submitters/${this.submitterSlug}/download`).then(async (response) => {
|
||||
if (response.ok) {
|
||||
const urls = await response.json()
|
||||
const isSafariIos = /iPhone|iPad|iPod/i.test(navigator.userAgent)
|
||||
const isMobileSafariIos = 'ontouchstart' in window && navigator.maxTouchPoints > 0 && /AppleWebKit/i.test(navigator.userAgent)
|
||||
const isSafariIos = isMobileSafariIos || /iPhone|iPad|iPod/i.test(navigator.userAgent)
|
||||
|
||||
if (isSafariIos && urls.length > 1) {
|
||||
this.downloadSafariIos(urls)
|
||||
|
||||
@@ -1,22 +1,28 @@
|
||||
<template>
|
||||
<div dir="auto">
|
||||
<div
|
||||
class="flex justify-between items-center w-full"
|
||||
class="flex justify-between items-end w-full mb-3.5 sm:mb-4"
|
||||
:class="{ 'mb-2': !field.description }"
|
||||
>
|
||||
<label
|
||||
v-if="showFieldNames"
|
||||
:for="field.uuid"
|
||||
class="label text-2xl"
|
||||
class="label text-xl sm:text-2xl py-0"
|
||||
>
|
||||
<MarkdownContent
|
||||
v-if="field.title"
|
||||
:string="field.title"
|
||||
/>
|
||||
<template v-else>
|
||||
{{ field.name && showFieldNames ? field.name : t('date') }}
|
||||
<template v-if="!field.required">
|
||||
{{ field.name || t('date') }}
|
||||
</template>
|
||||
<template v-if="!field.required">
|
||||
<span
|
||||
class="ml-1"
|
||||
:class="{ 'hidden sm:inline': (field.title || field.name || t('date') ).length > 10 }"
|
||||
>
|
||||
({{ t('optional') }})
|
||||
</template>
|
||||
</span>
|
||||
</template>
|
||||
</label>
|
||||
<button
|
||||
|
||||
@@ -128,7 +128,7 @@ export default {
|
||||
}
|
||||
})
|
||||
} else {
|
||||
if (file.type === 'image/bmp') {
|
||||
if (file.type === 'image/bmp' || file.type === 'image/vnd.microsoft.icon') {
|
||||
file = await this.convertBmpToPng(file)
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
/>
|
||||
<FieldAreas
|
||||
:steps="readonlyConditionalFields.map((e) => [e])"
|
||||
:values="readonlyConditionalFields.reduce((acc, f) => { acc[f.uuid] = (values[f.uuid] || f.default_value); return acc }, {})"
|
||||
:values="readonlyConditionalFieldValues"
|
||||
:submitter="submitter"
|
||||
:attachments-index="attachmentsIndex"
|
||||
:submittable="false"
|
||||
@@ -23,6 +23,7 @@
|
||||
<FormulaFieldAreas
|
||||
v-if="formulaFields.length"
|
||||
:fields="formulaFields"
|
||||
:readonly-values="readonlyConditionalFieldValues"
|
||||
:values="values"
|
||||
/>
|
||||
<Teleport
|
||||
@@ -163,7 +164,7 @@
|
||||
v-if="showFieldNames && (currentField.name || currentField.title)"
|
||||
:for="currentField.uuid"
|
||||
dir="auto"
|
||||
class="label text-2xl"
|
||||
class="label text-xl sm:text-2xl py-0 mb-2 sm:mb-3.5"
|
||||
:class="{ 'mb-2': !currentField.description }"
|
||||
>
|
||||
<MarkdownContent
|
||||
@@ -172,7 +173,11 @@
|
||||
/>
|
||||
<template v-else>
|
||||
{{ currentField.name }}
|
||||
<template v-if="!currentField.required">({{ t('optional') }})</template>
|
||||
</template>
|
||||
<template v-if="!currentField.required">
|
||||
<span :class="{ 'hidden sm:inline': (currentField.title || currentField.name).length > 20 }">
|
||||
({{ t('optional') }})
|
||||
</span>
|
||||
</template>
|
||||
</label>
|
||||
<div
|
||||
@@ -220,7 +225,7 @@
|
||||
v-if="showFieldNames && (currentField.name || currentField.title)"
|
||||
:for="currentField.uuid"
|
||||
dir="auto"
|
||||
class="label text-2xl"
|
||||
class="label text-xl sm:text-2xl py-0 mb-2 sm:mb-3.5"
|
||||
:class="{ 'mb-2': !currentField.description }"
|
||||
>
|
||||
<MarkdownContent
|
||||
@@ -229,7 +234,11 @@
|
||||
/>
|
||||
<template v-else>
|
||||
{{ currentField.name }}
|
||||
<template v-if="!currentField.required">({{ t('optional') }})</template>
|
||||
</template>
|
||||
<template v-if="!currentField.required">
|
||||
<span :class="{ 'hidden sm:inline': (currentField.title || currentField.name).length > 20 }">
|
||||
({{ t('optional') }})
|
||||
</span>
|
||||
</template>
|
||||
</label>
|
||||
<div
|
||||
@@ -465,7 +474,7 @@
|
||||
</div>
|
||||
<div
|
||||
v-if="(currentField.type !== 'payment' && currentField.type !== 'verification') || submittedValues[currentField.uuid]"
|
||||
:class="currentField.type === 'signature' ? 'mt-2' : 'mt-6 md:mt-8'"
|
||||
:class="currentField.type === 'signature' ? 'mt-2' : 'mt-4 md:mt-6'"
|
||||
>
|
||||
<button
|
||||
id="submit_form_button"
|
||||
@@ -498,6 +507,7 @@
|
||||
<InviteForm
|
||||
v-else-if="isInvite"
|
||||
:submitters="inviteSubmitters"
|
||||
:optional-submitters="optionalInviteSubmitters"
|
||||
:submitter-slug="submitterSlug"
|
||||
:authenticity-token="authenticityToken"
|
||||
:url="baseUrl + submitPath + '/invite'"
|
||||
@@ -520,9 +530,9 @@
|
||||
/>
|
||||
<div
|
||||
v-if="stepFields.length < 80"
|
||||
class="flex justify-center"
|
||||
class="flex justify-center mt-3 sm:mt-4 mb-0 sm:mb-1"
|
||||
>
|
||||
<div class="flex items-center mt-4 mb-1 flex-wrap">
|
||||
<div class="flex items-center flex-wrap">
|
||||
<a
|
||||
v-for="(step, index) in stepFields"
|
||||
:key="step[0].uuid"
|
||||
@@ -627,6 +637,11 @@ export default {
|
||||
required: false,
|
||||
default: () => []
|
||||
},
|
||||
optionalInviteSubmitters: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => []
|
||||
},
|
||||
withSignatureId: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
@@ -637,6 +652,11 @@ export default {
|
||||
required: false,
|
||||
default: '-80px'
|
||||
},
|
||||
orderAsOnPage: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
requireSigningReason: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
@@ -844,7 +864,16 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
isMobile () {
|
||||
return /android|iphone|ipad/i.test(navigator.userAgent)
|
||||
const isMobileSafariIos = 'ontouchstart' in window && navigator.maxTouchPoints > 0 && /AppleWebKit/i.test(navigator.userAgent)
|
||||
|
||||
return isMobileSafariIos || /android|iphone|ipad/i.test(navigator.userAgent)
|
||||
},
|
||||
readonlyConditionalFieldValues () {
|
||||
return this.readonlyConditionalFields.reduce((acc, f) => {
|
||||
acc[f.uuid] = (this.values[f.uuid] || f.default_value)
|
||||
|
||||
return acc
|
||||
}, {})
|
||||
},
|
||||
attachmentConditionsIndex () {
|
||||
return this.schema.reduce((acc, item) => {
|
||||
@@ -956,6 +985,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())
|
||||
}
|
||||
@@ -1060,7 +1121,9 @@ export default {
|
||||
this.minimizeForm()
|
||||
}
|
||||
|
||||
if (/iPhone|iPad|iPod/i.test(navigator.userAgent)) {
|
||||
const isMobile = 'ontouchstart' in window && navigator.maxTouchPoints > 0 && /AppleWebKit|android/i.test(navigator.userAgent)
|
||||
|
||||
if (isMobile || /iPhone|iPad|iPod/i.test(navigator.userAgent)) {
|
||||
this.$nextTick(() => {
|
||||
const root = this.$root.$el.parentNode.getRootNode()
|
||||
const scrollbox = root.getElementById('scrollbox')
|
||||
@@ -1320,7 +1383,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 +1422,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)
|
||||
|
||||
@@ -38,6 +38,11 @@ export default {
|
||||
required: false,
|
||||
default: () => []
|
||||
},
|
||||
readonlyValues: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => ({})
|
||||
},
|
||||
values: {
|
||||
type: Object,
|
||||
required: false,
|
||||
@@ -87,7 +92,7 @@ export default {
|
||||
},
|
||||
calculateFormula (field) {
|
||||
const transformedFormula = field.preferences.formula.replace(/{{(.*?)}}/g, (match, uuid) => {
|
||||
return this.values[uuid] || 0.0
|
||||
return this.readonlyValues[uuid] || this.values[uuid] || 0.0
|
||||
})
|
||||
|
||||
return this.math.evaluate(transformedFormula.toLowerCase())
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -1,9 +1,19 @@
|
||||
<template>
|
||||
<div v-if="modelValue">
|
||||
<div class="flex justify-between items-center w-full mb-2">
|
||||
<div class="flex justify-between items-end w-full mb-3.5 md:mb-4">
|
||||
<label
|
||||
class="label text-2xl"
|
||||
>{{ showFieldNames && field.name ? field.name : t('image') }}</label>
|
||||
v-if="showFieldNames"
|
||||
:for="field.uuid"
|
||||
class="label text-xl sm:text-2xl py-0"
|
||||
>
|
||||
<MarkdownContent
|
||||
v-if="field.title"
|
||||
:string="field.title"
|
||||
/>
|
||||
<template v-else>
|
||||
{{ field.name || t('image') }}
|
||||
</template>
|
||||
</label>
|
||||
<button
|
||||
class="btn btn-outline btn-sm"
|
||||
@click.prevent="remove"
|
||||
@@ -35,7 +45,7 @@
|
||||
<MarkdownContent :string="field.description" />
|
||||
</div>
|
||||
<FileDropzone
|
||||
:message="`${t('upload')} ${field.name || t('image')}${field.required ? '' : ` (${t('optional')})`}`"
|
||||
:message="`${t('upload')} ${(field.title || field.name) || t('image')}${field.required ? '' : ` (${t('optional')})`}`"
|
||||
:submitter-slug="submitterSlug"
|
||||
:dry-run="dryRun"
|
||||
:accept="'image/*'"
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
<template>
|
||||
<div dir="auto">
|
||||
<div
|
||||
class="flex justify-between items-center w-full"
|
||||
class="flex justify-between items-end w-full mb-3.5 md:mb-4"
|
||||
:class="{ 'mb-2': !field.description }"
|
||||
>
|
||||
<label
|
||||
class="label text-2xl"
|
||||
v-if="showFieldNames"
|
||||
class="label text-xl sm:text-2xl py-0"
|
||||
>
|
||||
<MarkdownContent
|
||||
v-if="field.title"
|
||||
:string="field.title"
|
||||
/>
|
||||
<template v-else>
|
||||
{{ showFieldNames && field.name ? field.name : t('initials') }}
|
||||
{{ field.name || t('initials') }}
|
||||
</template>
|
||||
</label>
|
||||
<div class="space-x-2 flex">
|
||||
<div class="space-x-2 flex flex-none">
|
||||
<span
|
||||
v-if="isDrawInitials"
|
||||
class="tooltip"
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
:value="authenticityToken"
|
||||
>
|
||||
<div
|
||||
v-for="(submitter, index) in submitters"
|
||||
v-for="(submitter, index) in [...submitters, ...optionalSubmitters]"
|
||||
:key="submitter.uuid"
|
||||
:class="{ 'mt-4': index !== 0 }"
|
||||
>
|
||||
@@ -26,7 +26,7 @@
|
||||
dir="auto"
|
||||
class="label text-2xl"
|
||||
>
|
||||
{{ t('invite') }} {{ submitter.name }}
|
||||
{{ t('invite') }} {{ submitter.name }} <template v-if="!submitters.includes(submitter)">({{ t('optional') }})</template>
|
||||
</label>
|
||||
<input
|
||||
:id="submitter.uuid"
|
||||
@@ -34,13 +34,13 @@
|
||||
class="base-input !text-2xl w-full"
|
||||
:placeholder="t('email')"
|
||||
type="email"
|
||||
required
|
||||
:required="submitters.includes(submitter)"
|
||||
autofocus="true"
|
||||
name="submission[submitters][][email]"
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
class="mt-6 md:mt-8"
|
||||
class="mt-4 md:mt-6"
|
||||
>
|
||||
<button
|
||||
type="submit"
|
||||
@@ -53,7 +53,7 @@
|
||||
class="mr-1 animate-spin"
|
||||
/>
|
||||
<span>
|
||||
{{ t('submit') }}
|
||||
{{ t('complete') }}
|
||||
</span><span
|
||||
v-if="isSubmitting"
|
||||
class="w-6 flex justify-start mr-1"
|
||||
@@ -78,6 +78,11 @@ export default {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
optionalSubmitters: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => []
|
||||
},
|
||||
url: {
|
||||
type: String,
|
||||
required: true
|
||||
|
||||
@@ -3,13 +3,19 @@
|
||||
v-if="showFieldNames && (field.name || field.title)"
|
||||
:for="field.uuid"
|
||||
dir="auto"
|
||||
class="label text-2xl"
|
||||
class="label text-xl sm:text-2xl py-0 mb-2 sm:mb-3.5"
|
||||
:class="{ 'mb-2': !field.description }"
|
||||
><MarkdownContent
|
||||
v-if="field.title"
|
||||
:string="field.title"
|
||||
/>
|
||||
>
|
||||
<MarkdownContent
|
||||
v-if="field.title"
|
||||
:string="field.title"
|
||||
/>
|
||||
<template v-else>{{ field.name }}</template>
|
||||
<template v-if="!field.required">
|
||||
<span :class="{ 'hidden sm:inline': (field.title || field.name).length > 20 }">
|
||||
({{ t('optional') }})
|
||||
</span>
|
||||
</template>
|
||||
</label>
|
||||
<div
|
||||
v-if="field.description"
|
||||
|
||||
@@ -3,15 +3,18 @@
|
||||
v-if="showFieldNames && (field.name || field.title)"
|
||||
:for="field.uuid"
|
||||
dir="auto"
|
||||
class="label text-2xl"
|
||||
class="label text-xl sm:text-2xl py-0 mb-2 sm:mb-3.5"
|
||||
:class="{ 'mb-2': !field.description }"
|
||||
><MarkdownContent
|
||||
v-if="field.title"
|
||||
:string="field.title"
|
||||
/>
|
||||
>
|
||||
<MarkdownContent
|
||||
v-if="field.title"
|
||||
:string="field.title"
|
||||
/>
|
||||
<template v-else>{{ field.name }}</template>
|
||||
<template v-if="!field.required">
|
||||
({{ t('optional') }})
|
||||
<span :class="{ 'hidden sm:inline': (field.title || field.name).length > 20 }">
|
||||
({{ t('optional') }})
|
||||
</span>
|
||||
</template>
|
||||
</label>
|
||||
<div
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<label
|
||||
v-if="!modelValue && !sessionId"
|
||||
class="label text-2xl mb-2"
|
||||
class="label text-xl sm:text-2xl py-0 mb-2 sm:mb-3.5"
|
||||
>
|
||||
<MarkdownContent
|
||||
v-if="field.title"
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<template>
|
||||
<div>
|
||||
<label
|
||||
v-if="showFieldNames"
|
||||
:for="isCodeSent ? 'one_time_code' : field.uuid"
|
||||
class="label text-2xl"
|
||||
class="label text-xl sm:text-2xl py-0 mb-2 sm:mb-3.5"
|
||||
:class="{ 'mb-2': !field.description }"
|
||||
>
|
||||
<MarkdownContent
|
||||
@@ -10,10 +11,7 @@
|
||||
:string="field.title"
|
||||
/>
|
||||
<template v-else>
|
||||
{{ showFieldNames && field.name ? field.name : t('verified_phone_number') }}
|
||||
<template v-if="!field.required">
|
||||
({{ t('optional') }})
|
||||
</template>
|
||||
{{ field.name || t('verified_phone_number') }}
|
||||
</template>
|
||||
</label>
|
||||
<div
|
||||
@@ -52,7 +50,14 @@
|
||||
>
|
||||
{{ t('change_phone_number') }}
|
||||
</a>
|
||||
<span
|
||||
v-if="resendCodeCountdown > 0"
|
||||
class="link"
|
||||
>
|
||||
{{ t('wait_countdown_seconds').replace('{countdown}', resendCodeCountdown) }}
|
||||
</span>
|
||||
<a
|
||||
v-else
|
||||
href="#"
|
||||
class="link"
|
||||
@click.prevent="resendCode"
|
||||
@@ -173,6 +178,8 @@ export default {
|
||||
data () {
|
||||
return {
|
||||
isCodeSent: false,
|
||||
codeSentAt: null,
|
||||
resendCodeCountdown: 0,
|
||||
isResendLoading: false,
|
||||
phoneValue: this.modelValue || this.defaultValue || '',
|
||||
selectedCountry: {}
|
||||
@@ -220,6 +227,11 @@ export default {
|
||||
this.selectedCountry = this.countries.find((country) => country.tz.includes(tz)) || this.countries[0]
|
||||
}
|
||||
},
|
||||
beforeUnmount () {
|
||||
if (this.interval) {
|
||||
clearInterval(this.interval)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
emitSubmit: throttle(function (e) {
|
||||
this.$emit('submit')
|
||||
@@ -246,13 +258,28 @@ export default {
|
||||
}
|
||||
},
|
||||
resendCode () {
|
||||
this.isResendLoading = true
|
||||
if (this.codeSentAt && Date.now() - this.codeSentAt < 15000) {
|
||||
this.startResendCodeCountdown()
|
||||
} else {
|
||||
this.isResendLoading = true
|
||||
|
||||
this.sendVerificationCode().finally(() => {
|
||||
alert(this.t('verification_code_has_been_resent'))
|
||||
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.isResendLoading = false
|
||||
})
|
||||
this.interval = setInterval(() => {
|
||||
this.resendCodeCountdown--
|
||||
|
||||
if (this.resendCodeCountdown <= 0) {
|
||||
clearInterval(this.interval)
|
||||
}
|
||||
}, 1000)
|
||||
},
|
||||
sendVerificationCode () {
|
||||
return fetch(this.baseUrl + '/api/send_phone_verification_code', {
|
||||
@@ -264,12 +291,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(data.error || 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
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
@@ -4,23 +4,24 @@
|
||||
class="relative"
|
||||
>
|
||||
<div
|
||||
class="flex justify-between items-center w-full"
|
||||
class="flex justify-between items-end w-full mb-3.5 md:mb-4"
|
||||
:class="{ 'mb-2': !field.description }"
|
||||
>
|
||||
<label
|
||||
class="label text-2xl"
|
||||
v-if="showFieldNames"
|
||||
class="label text-xl sm:text-2xl py-0"
|
||||
>
|
||||
<MarkdownContent
|
||||
v-if="field.title"
|
||||
:string="field.title"
|
||||
/>
|
||||
<template v-else>
|
||||
{{ showFieldNames && field.name ? field.name : t('signature') }}
|
||||
{{ field.name || t('signature') }}
|
||||
</template>
|
||||
</label>
|
||||
<div class="space-x-2 flex">
|
||||
<div class="space-x-2 flex flex-none">
|
||||
<span
|
||||
v-if="isTextSignature && field.preferences?.format !== 'typed'"
|
||||
v-if="isTextSignature && format !== 'typed' && format !== 'upload'"
|
||||
class="tooltip"
|
||||
:data-tip="t('draw_signature')"
|
||||
>
|
||||
@@ -37,8 +38,9 @@
|
||||
</a>
|
||||
</span>
|
||||
<span
|
||||
v-else-if="withTypedSignature && field.preferences?.format !== 'typed' && field.preferences?.format !== 'drawn'"
|
||||
v-else-if="withTypedSignature && format !== 'typed' && format !== 'drawn' && format !== 'upload'"
|
||||
class="tooltip ml-2"
|
||||
:class="{ 'hidden sm:inline': modelValue || computedPreviousValue }"
|
||||
:data-tip="t('type_text')"
|
||||
>
|
||||
<a
|
||||
@@ -54,8 +56,9 @@
|
||||
</a>
|
||||
</span>
|
||||
<span
|
||||
v-if="field.preferences?.format !== 'typed' && field.preferences?.format !== 'drawn'"
|
||||
v-if="format !== 'typed' && format !== 'drawn' && format !== 'upload' && format !== 'drawn_or_typed'"
|
||||
class="tooltip"
|
||||
:class="{ 'hidden sm:inline': modelValue || computedPreviousValue }"
|
||||
:data-tip="t('take_photo')"
|
||||
>
|
||||
<label
|
||||
@@ -81,10 +84,10 @@
|
||||
@click.prevent="remove"
|
||||
>
|
||||
<IconReload :width="16" />
|
||||
{{ t('redraw') }}
|
||||
{{ t(format === 'upload' ? 'reupload' : 'redraw') }}
|
||||
</a>
|
||||
<span
|
||||
v-if="withQrButton && !modelValue && !computedPreviousValue && field.preferences?.format !== 'typed'"
|
||||
v-if="withQrButton && !modelValue && !computedPreviousValue && format !== 'typed' && format !== 'upload'"
|
||||
class=" tooltip"
|
||||
:data-tip="t('drawn_signature_on_a_touchscreen_device')"
|
||||
>
|
||||
@@ -131,7 +134,18 @@
|
||||
:src="attachmentsIndex[modelValue || computedPreviousValue].url"
|
||||
class="mx-auto bg-white border border-base-300 rounded max-h-44"
|
||||
>
|
||||
<div class="relative">
|
||||
<FileDropzone
|
||||
v-if="format === 'upload' && !modelValue"
|
||||
:message="`${t('upload')} ${field.name || t('signature')}`"
|
||||
:submitter-slug="submitterSlug"
|
||||
:dry-run="dryRun"
|
||||
:accept="'image/*'"
|
||||
@upload="[$emit('attached', $event[0]), $emit('update:model-value', $event[0].uuid)]"
|
||||
/>
|
||||
<div
|
||||
v-else
|
||||
class="relative"
|
||||
>
|
||||
<div
|
||||
v-if="!modelValue && !computedPreviousValue && !isShowQr && !isTextSignature && isSignatureStarted"
|
||||
class="absolute top-0.5 right-0.5"
|
||||
@@ -282,6 +296,7 @@ import { IconReload, IconCamera, IconSignature, IconTextSize, IconArrowsDiagonal
|
||||
import { cropCanvasAndExportToPNG } from './crop_canvas'
|
||||
import SignaturePad from 'signature_pad'
|
||||
import AppearsOn from './appears_on'
|
||||
import FileDropzone from './dropzone'
|
||||
import MarkdownContent from './markdown_content'
|
||||
import { v4 } from 'uuid'
|
||||
|
||||
@@ -294,6 +309,7 @@ export default {
|
||||
components: {
|
||||
AppearsOn,
|
||||
IconReload,
|
||||
FileDropzone,
|
||||
IconCamera,
|
||||
IconQrcode,
|
||||
MarkdownContent,
|
||||
@@ -388,6 +404,9 @@ export default {
|
||||
submitterSlug () {
|
||||
return this.submitter.slug
|
||||
},
|
||||
format () {
|
||||
return this.field.preferences?.format
|
||||
},
|
||||
defaultReasons () {
|
||||
return {
|
||||
[this.t('approved_by')]: this.t('approved'),
|
||||
|
||||
@@ -3,15 +3,18 @@
|
||||
v-if="showFieldNames && (field.name || field.title)"
|
||||
:for="field.uuid"
|
||||
dir="auto"
|
||||
class="label text-2xl"
|
||||
class="label text-xl sm:text-2xl py-0 mb-2 sm:mb-3.5"
|
||||
:class="{ 'mb-2': !field.description }"
|
||||
><MarkdownContent
|
||||
v-if="field.title"
|
||||
:string="field.title"
|
||||
/>
|
||||
>
|
||||
<MarkdownContent
|
||||
v-if="field.title"
|
||||
:string="field.title"
|
||||
/>
|
||||
<template v-else>{{ field.name }}</template>
|
||||
<template v-if="!field.required">
|
||||
({{ t('optional') }})
|
||||
<span :class="{ 'hidden sm:inline': (field.title || field.name).length > 20 }">
|
||||
({{ t('optional') }})
|
||||
</span>
|
||||
</template>
|
||||
</label>
|
||||
<div
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<label
|
||||
class="label text-2xl mb-2"
|
||||
class="label text-xl sm:text-2xl py-0 mb-2 sm:mb-3.5"
|
||||
>
|
||||
<MarkdownContent
|
||||
v-if="field.title"
|
||||
@@ -31,6 +31,15 @@
|
||||
class="animate-spin h-10"
|
||||
/>
|
||||
</div>
|
||||
<div v-else-if="redirectUrl">
|
||||
<a
|
||||
:href="redirectUrl"
|
||||
target="_blank"
|
||||
class="white-button w-full"
|
||||
>
|
||||
{{ t('verify_id') }}
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
ref="widgetContainer"
|
||||
/>
|
||||
@@ -87,6 +96,7 @@ export default {
|
||||
return {
|
||||
isCreatingCheckout: false,
|
||||
isMathLoaded: false,
|
||||
redirectUrl: '',
|
||||
isLoading: false,
|
||||
eidEasyData: {}
|
||||
}
|
||||
@@ -125,12 +135,16 @@ export default {
|
||||
async mounted () {
|
||||
this.isLoading = true
|
||||
|
||||
Promise.all([
|
||||
import('@eid-easy/eideasy-widget'),
|
||||
this.start()
|
||||
]).finally(() => {
|
||||
this.isLoading = false
|
||||
})
|
||||
if (new URLSearchParams(window.location.search).get('submit') === 'true') {
|
||||
this.$emit('submit')
|
||||
} else {
|
||||
Promise.all([
|
||||
import('@eid-easy/eideasy-widget'),
|
||||
this.start()
|
||||
]).finally(() => {
|
||||
this.isLoading = false
|
||||
})
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
start () {
|
||||
@@ -143,14 +157,26 @@ export default {
|
||||
}).then(async (resp) => {
|
||||
this.eidEasyData = await resp.json()
|
||||
|
||||
const eidEasyWidget = document.createElement('eideasy-widget')
|
||||
if (this.eidEasyData.available_methods[0] === 'itsme-qes-signature' &&
|
||||
this.eidEasyData.available_methods.length === 1) {
|
||||
const redirectUrl = new URL('https://id.eideasy.com/sign_contract_external')
|
||||
|
||||
for (const key in this.widgetSettings) {
|
||||
eidEasyWidget[key] = this.widgetSettings[key]
|
||||
redirectUrl.searchParams.append('client_id', this.eidEasyData.client_id)
|
||||
redirectUrl.searchParams.append('doc_id', this.eidEasyData.doc_id)
|
||||
redirectUrl.searchParams.append('country', this.countryCode)
|
||||
redirectUrl.searchParams.append('lang', this.locale)
|
||||
|
||||
this.redirectUrl = redirectUrl.toString()
|
||||
} else {
|
||||
const eidEasyWidget = document.createElement('eideasy-widget')
|
||||
|
||||
for (const key in this.widgetSettings) {
|
||||
eidEasyWidget[key] = this.widgetSettings[key]
|
||||
}
|
||||
|
||||
this.$refs.widgetContainer.innerHTML = ''
|
||||
this.$refs.widgetContainer.appendChild(eidEasyWidget)
|
||||
}
|
||||
|
||||
this.$refs.widgetContainer.innerHTML = ''
|
||||
this.$refs.widgetContainer.appendChild(eidEasyWidget)
|
||||
})
|
||||
},
|
||||
async submit () {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
<div
|
||||
class="absolute overflow-visible group"
|
||||
:style="positionStyle"
|
||||
:class="{ 'z-[1]': isMoved || isDragged }"
|
||||
@pointerdown.stop
|
||||
@mousedown="startMouseMove"
|
||||
@touchstart="startTouchDrag"
|
||||
@@ -9,7 +10,7 @@
|
||||
<div
|
||||
v-if="isSelected || isDraw"
|
||||
class="top-0 bottom-0 right-0 left-0 absolute border border-1.5 pointer-events-none"
|
||||
:class="field.type === 'heading' ? '' : borderColors[submitterIndex]"
|
||||
:class="field.type === 'heading' ? '' : borderColors[submitterIndex % borderColors.length]"
|
||||
/>
|
||||
<div
|
||||
v-if="field.type === 'cells' && (isSelected || isDraw)"
|
||||
@@ -19,7 +20,7 @@
|
||||
v-for="(cellW, index) in cells"
|
||||
:key="index"
|
||||
class="absolute top-0 bottom-0 border-r"
|
||||
:class="field.type === 'heading' ? '' : borderColors[submitterIndex]"
|
||||
:class="field.type === 'heading' ? '' : borderColors[submitterIndex % borderColors.length]"
|
||||
:style="{ left: (cellW / area.w * 100) + '%' }"
|
||||
>
|
||||
<span
|
||||
@@ -69,7 +70,7 @@
|
||||
@keydown.enter.prevent="onNameEnter"
|
||||
@focus="onNameFocus"
|
||||
@blur="onNameBlur"
|
||||
>{{ optionIndexText }} {{ (defaultField ? (field.title || field.name) : field.name) || defaultName }}</span>
|
||||
>{{ optionIndexText }} {{ (defaultField ? (defaultField.title || field.title || field.name) : field.name) || defaultName }}</span>
|
||||
<div
|
||||
v-if="isSettingsFocus || (isValueInput && field.type !== 'heading') || (isNameFocus && !['checkbox', 'phone'].includes(field.type))"
|
||||
class="flex items-center ml-1.5"
|
||||
@@ -160,7 +161,7 @@
|
||||
ref="touchValueTarget"
|
||||
class="flex items-center h-full w-full"
|
||||
dir="auto"
|
||||
:class="[isValueInput ? 'bg-opacity-50' : 'bg-opacity-80', field.type === 'heading' ? 'bg-gray-50' : bgColors[submitterIndex], isDefaultValuePresent || isValueInput || (withFieldPlaceholder && field.areas) ? (alignClasses[field.preferences?.align] || '') : 'justify-center']"
|
||||
:class="[isValueInput ? 'bg-opacity-50' : 'bg-opacity-80', field.type === 'heading' ? 'bg-gray-50' : bgColors[submitterIndex % bgColors.length], isDefaultValuePresent || isValueInput || (withFieldPlaceholder && field.areas) ? (alignClasses[field.preferences?.align] || '') : 'justify-center']"
|
||||
@click="focusValueInput"
|
||||
>
|
||||
<span
|
||||
@@ -211,7 +212,7 @@
|
||||
:contenteditable="isValueInput"
|
||||
class="whitespace-pre-wrap outline-none empty:before:content-[attr(placeholder)] before:text-gray-400"
|
||||
:class="{ 'cursor-text': isValueInput }"
|
||||
:placeholder="withFieldPlaceholder && !isValueInput ? field.name || defaultName : t('type_value')"
|
||||
:placeholder="withFieldPlaceholder && !isValueInput ? defaultField?.title || field.title || field.name || defaultName : t('type_value')"
|
||||
@blur="onDefaultValueBlur"
|
||||
@paste.prevent="onPaste"
|
||||
@keydown.enter="onDefaultValueEnter"
|
||||
@@ -316,6 +317,11 @@ export default {
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
maxPage: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
defaultField: {
|
||||
type: Object,
|
||||
required: false,
|
||||
@@ -355,6 +361,7 @@ export default {
|
||||
isMoved: false,
|
||||
renderDropdown: false,
|
||||
isNameFocus: false,
|
||||
isHeadingSelected: false,
|
||||
textOverflowChars: 0,
|
||||
dragFrom: { x: 0, y: 0 }
|
||||
}
|
||||
@@ -371,7 +378,7 @@ export default {
|
||||
}
|
||||
},
|
||||
isValueInput () {
|
||||
return (this.field.type === 'heading' && this.isSelected) || this.isContenteditable || (this.inputMode && ['text', 'number', 'date'].includes(this.field.type))
|
||||
return (this.field.type === 'heading' && this.isHeadingSelected) || this.isContenteditable || (this.inputMode && ['text', 'number', 'date'].includes(this.field.type))
|
||||
},
|
||||
modalContainerEl () {
|
||||
return this.$el.getRootNode().querySelector('#docuseal_modal_container')
|
||||
@@ -414,16 +421,6 @@ export default {
|
||||
},
|
||||
borderColors () {
|
||||
return [
|
||||
'border-red-500/80',
|
||||
'border-sky-500/80',
|
||||
'border-emerald-500/80',
|
||||
'border-yellow-300/80',
|
||||
'border-purple-600/80',
|
||||
'border-pink-500/80',
|
||||
'border-cyan-500/80',
|
||||
'border-orange-500/80',
|
||||
'border-lime-500/80',
|
||||
'border-indigo-500/80',
|
||||
'border-red-500/80',
|
||||
'border-sky-500/80',
|
||||
'border-emerald-500/80',
|
||||
@@ -438,16 +435,6 @@ export default {
|
||||
},
|
||||
bgColors () {
|
||||
return [
|
||||
'bg-red-100',
|
||||
'bg-sky-100',
|
||||
'bg-emerald-100',
|
||||
'bg-yellow-100',
|
||||
'bg-purple-100',
|
||||
'bg-pink-100',
|
||||
'bg-cyan-100',
|
||||
'bg-orange-100',
|
||||
'bg-lime-100',
|
||||
'bg-indigo-100',
|
||||
'bg-red-100',
|
||||
'bg-sky-100',
|
||||
'bg-emerald-100',
|
||||
@@ -493,13 +480,13 @@ export default {
|
||||
methods: {
|
||||
buildDefaultName: Field.methods.buildDefaultName,
|
||||
closeDropdown () {
|
||||
document.activeElement.blur()
|
||||
this.$el.getRootNode().activeElement.blur()
|
||||
},
|
||||
maybeToggleDefaultValue () {
|
||||
if (['text', 'number'].includes(this.field.type)) {
|
||||
this.isContenteditable = true
|
||||
|
||||
this.$nextTick(() => this.focusValueInput())
|
||||
this.focusValueInput()
|
||||
} else if (this.field.type === 'checkbox') {
|
||||
this.field.readonly = !this.field.readonly
|
||||
this.field.default_value === true ? delete this.field.default_value : this.field.default_value = true
|
||||
@@ -521,20 +508,28 @@ export default {
|
||||
}
|
||||
},
|
||||
focusValueInput (e) {
|
||||
if (this.$refs.defaultValue !== document.activeElement) {
|
||||
this.$refs.defaultValue.focus()
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.defaultValue && this.$refs.defaultValue !== document.activeElement) {
|
||||
this.$refs.defaultValue.focus()
|
||||
|
||||
if (this.$refs.defaultValue.innerText.length && this.$refs.defaultValue !== e?.target) {
|
||||
window.getSelection().collapse(
|
||||
this.$refs.defaultValue.firstChild,
|
||||
this.$refs.defaultValue.innerText.length
|
||||
)
|
||||
if (this.$refs.defaultValue.innerText.length && this.$refs.defaultValue !== e?.target) {
|
||||
window.getSelection().collapse(
|
||||
this.$refs.defaultValue.firstChild,
|
||||
this.$refs.defaultValue.innerText.length
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
formatNumber (number, format) {
|
||||
if (format === 'comma') {
|
||||
return new Intl.NumberFormat('en-US').format(number)
|
||||
} else if (format === 'usd') {
|
||||
return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(number)
|
||||
} else if (format === 'gbp') {
|
||||
return new Intl.NumberFormat('en-GB', { style: 'currency', currency: 'GBP', minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(number)
|
||||
} else if (format === 'eur') {
|
||||
return new Intl.NumberFormat('fr-FR', { style: 'currency', currency: 'EUR', minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(number)
|
||||
} else if (format === 'dot') {
|
||||
return new Intl.NumberFormat('de-DE').format(number)
|
||||
} else if (format === 'space') {
|
||||
@@ -640,6 +635,7 @@ export default {
|
||||
const text = this.$refs.defaultValue.innerText.trim()
|
||||
|
||||
this.isContenteditable = false
|
||||
this.isHeadingSelected = false
|
||||
|
||||
if (text) {
|
||||
if (this.field.type === 'number') {
|
||||
@@ -720,12 +716,18 @@ export default {
|
||||
const rect = page.getBoundingClientRect()
|
||||
|
||||
this.area.x = Math.min(Math.max((this.dragFrom.x + e.touches[0].clientX - rect.left) / rect.width, 0), 1 - this.area.w)
|
||||
this.area.y = Math.min(Math.max((this.dragFrom.y + e.touches[0].clientY - rect.top) / rect.height, 0), 1 - this.area.h)
|
||||
this.area.y = (this.dragFrom.y + e.touches[0].clientY - rect.top) / rect.height
|
||||
|
||||
if ((this.area.page === 0 && this.area.y < 0) || (this.area.page === this.maxPage && this.area.y > 1 - this.area.h)) {
|
||||
this.area.y = Math.min(Math.max(this.area.y, 0), 1 - this.area.h)
|
||||
}
|
||||
},
|
||||
stopTouchDrag () {
|
||||
this.$el.getRootNode().removeEventListener('touchmove', this.touchDrag)
|
||||
this.$el.getRootNode().removeEventListener('touchend', this.stopTouchDrag)
|
||||
|
||||
this.maybeChangeAreaPage(this.area)
|
||||
|
||||
if (this.isDragged) {
|
||||
this.save()
|
||||
}
|
||||
@@ -751,10 +753,6 @@ export default {
|
||||
|
||||
this.selectedAreaRef.value = this.area
|
||||
|
||||
if (this.field.type === 'heading') {
|
||||
this.$nextTick(() => this.focusValueInput())
|
||||
}
|
||||
|
||||
this.dragFrom = { x: rect.left - e.clientX, y: rect.top - e.clientY }
|
||||
|
||||
this.$el.getRootNode().addEventListener('mousemove', this.mouseMove)
|
||||
@@ -773,21 +771,42 @@ export default {
|
||||
const rect = page.getBoundingClientRect()
|
||||
|
||||
this.area.x = Math.min(Math.max((this.dragFrom.x + e.clientX - rect.left) / rect.width, 0), 1 - this.area.w)
|
||||
this.area.y = Math.min(Math.max((this.dragFrom.y + e.clientY - rect.top) / rect.height, 0), 1 - this.area.h)
|
||||
this.area.y = (this.dragFrom.y + e.clientY - rect.top) / rect.height
|
||||
|
||||
if ((this.area.page === 0 && this.area.y < 0) || (this.area.page === this.maxPage && this.area.y > 1 - this.area.h)) {
|
||||
this.area.y = Math.min(Math.max(this.area.y, 0), 1 - this.area.h)
|
||||
}
|
||||
},
|
||||
stopMouseMove (e) {
|
||||
this.$el.getRootNode().removeEventListener('mousemove', this.mouseMove)
|
||||
this.$el.getRootNode().removeEventListener('mouseup', this.stopMouseMove)
|
||||
|
||||
this.maybeChangeAreaPage(this.area)
|
||||
|
||||
if (this.isMoved) {
|
||||
this.save()
|
||||
}
|
||||
|
||||
if (this.field.type === 'heading') {
|
||||
this.isHeadingSelected = !this.isMoved
|
||||
|
||||
this.focusValueInput()
|
||||
}
|
||||
|
||||
this.isDragged = false
|
||||
this.isMoved = false
|
||||
|
||||
this.$emit('stop-drag')
|
||||
},
|
||||
maybeChangeAreaPage (area) {
|
||||
if (area.y < -(area.h / 2)) {
|
||||
area.page -= 1
|
||||
area.y = 1 + area.y + (16.0 / this.$parent.$refs.mask.previousSibling.offsetHeight)
|
||||
} else if (area.y > 1 - (area.h / 2)) {
|
||||
area.page += 1
|
||||
area.y = area.y - 1 - (16.0 / this.$parent.$refs.mask.previousSibling.offsetHeight)
|
||||
}
|
||||
},
|
||||
stopDrag () {
|
||||
this.$el.getRootNode().removeEventListener('mousemove', this.drag)
|
||||
this.$el.getRootNode().removeEventListener('mouseup', this.stopDrag)
|
||||
|
||||
@@ -179,6 +179,7 @@
|
||||
:with-arrows="template.schema.length > 1"
|
||||
:item="item"
|
||||
:document="sortedDocuments[index]"
|
||||
:data-document-uuid="item.attachment_uuid"
|
||||
:accept-file-types="acceptFileTypes"
|
||||
:with-replace-button="withUploadButton"
|
||||
:editable="editable"
|
||||
@@ -187,6 +188,7 @@
|
||||
@remove="onDocumentRemove"
|
||||
@replace="onDocumentReplace"
|
||||
@up="moveDocument(item, -1)"
|
||||
@reorder="reorderFields"
|
||||
@down="moveDocument(item, 1)"
|
||||
@change="save"
|
||||
/>
|
||||
@@ -220,8 +222,8 @@
|
||||
</div>
|
||||
<div
|
||||
id="pages_container"
|
||||
class="w-full overflow-y-hidden overflow-x-hidden mt-0.5 pt-0.5"
|
||||
:class="isMobile ? 'overflow-y-auto' : 'md:overflow-y-auto'"
|
||||
class="w-full overflow-x-hidden mt-0.5 pt-0.5"
|
||||
:class="isMobile ? 'overflow-y-auto' : 'overflow-y-hidden md:overflow-y-auto'"
|
||||
>
|
||||
<div
|
||||
ref="documents"
|
||||
@@ -265,6 +267,7 @@
|
||||
:input-mode="inputMode"
|
||||
:default-fields="[...defaultRequiredFields, ...defaultFields]"
|
||||
:allow-draw="!onlyDefinedFields"
|
||||
:data-document-uuid="document.uuid"
|
||||
:default-submitters="defaultSubmitters"
|
||||
:with-field-placeholder="withFieldPlaceholder"
|
||||
:draw-field="drawField"
|
||||
@@ -363,6 +366,7 @@
|
||||
:with-help="withHelp"
|
||||
:default-submitters="defaultSubmitters"
|
||||
:draw-field-type="drawFieldType"
|
||||
:with-fields-search="withFieldsSearch"
|
||||
:default-fields="[...defaultRequiredFields, ...defaultFields]"
|
||||
:template="template"
|
||||
:default-required-fields="defaultRequiredFields"
|
||||
@@ -620,6 +624,11 @@ export default {
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
withFieldsSearch: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
withFieldsList: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
@@ -700,7 +709,9 @@ export default {
|
||||
return this.locale.split('-')[0].toLowerCase()
|
||||
},
|
||||
isMobile () {
|
||||
return /android|iphone|ipad/i.test(navigator.userAgent)
|
||||
const isMobileSafariIos = 'ontouchstart' in window && navigator.maxTouchPoints > 0 && /AppleWebKit/i.test(navigator.userAgent)
|
||||
|
||||
return isMobileSafariIos || /android|iphone|ipad/i.test(navigator.userAgent)
|
||||
},
|
||||
defaultDateFormat () {
|
||||
const isUsBrowser = Intl.DateTimeFormat().resolvedOptions().locale.endsWith('-US')
|
||||
@@ -769,13 +780,17 @@ export default {
|
||||
}
|
||||
})
|
||||
|
||||
const defineSubmittersUuids = this.defineSubmitters.map((name) => {
|
||||
return this.template.submitters.find(e => e.name === name)?.uuid
|
||||
})
|
||||
|
||||
this.defineSubmitters.forEach((name, index) => {
|
||||
const submitter = (this.template.submitters[index] ||= {})
|
||||
|
||||
submitter.name = name || this.submitterDefaultNames[index]
|
||||
|
||||
if (existingSubmittersUuids.filter(Boolean).length) {
|
||||
submitter.uuid = existingSubmittersUuids[index] || submitter.uuid || v4()
|
||||
if (defineSubmittersUuids.filter(Boolean).length || existingSubmittersUuids.filter(Boolean).length) {
|
||||
submitter.uuid = defineSubmittersUuids[index] || existingSubmittersUuids[index] || submitter.uuid || v4()
|
||||
} else {
|
||||
submitter.uuid ||= v4()
|
||||
}
|
||||
@@ -818,6 +833,89 @@ export default {
|
||||
this.documentRefs = []
|
||||
},
|
||||
methods: {
|
||||
reorderFields (item) {
|
||||
const itemFields = []
|
||||
const fields = []
|
||||
const fieldAreasIndex = {}
|
||||
|
||||
const attachmentUuids = this.template.schema.map((e) => e.attachment_uuid)
|
||||
|
||||
this.template.fields.forEach((f) => {
|
||||
if (f.areas?.length) {
|
||||
const firstArea = f.areas.reduce((min, a) => {
|
||||
return attachmentUuids.indexOf(a.attachment_uuid) < attachmentUuids.indexOf(min.attachment_uuid) ? a : min
|
||||
}, f.areas[0])
|
||||
|
||||
if (firstArea.attachment_uuid === item.attachment_uuid) {
|
||||
itemFields.push(f)
|
||||
} else {
|
||||
fields.push(f)
|
||||
}
|
||||
} else {
|
||||
fields.push(f)
|
||||
}
|
||||
})
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
itemFields.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)
|
||||
})
|
||||
|
||||
const insertBeforeAttachmentUuids = attachmentUuids.slice(this.template.schema.indexOf(item) + 1)
|
||||
|
||||
let sortedFields = []
|
||||
|
||||
if (insertBeforeAttachmentUuids.length) {
|
||||
const insertAfterField = fields.find((f) => {
|
||||
if (f.areas?.length) {
|
||||
return f.areas.find((a) => insertBeforeAttachmentUuids.includes(a.attachment_uuid))
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
if (insertAfterField) {
|
||||
fields.splice(fields.indexOf(insertAfterField), 0, ...itemFields)
|
||||
|
||||
sortedFields = fields
|
||||
} else {
|
||||
sortedFields = fields.concat(itemFields)
|
||||
}
|
||||
} else {
|
||||
if (fields.length && itemFields.length && this.template.fields.indexOf(fields[0]) > this.template.fields.indexOf(itemFields[0])) {
|
||||
sortedFields = itemFields.concat(fields)
|
||||
} else {
|
||||
sortedFields = fields.concat(itemFields)
|
||||
}
|
||||
}
|
||||
|
||||
if (this.template.fields.length === sortedFields.length) {
|
||||
this.template.fields = sortedFields
|
||||
this.save()
|
||||
}
|
||||
},
|
||||
closeDropdown () {
|
||||
document.activeElement.blur()
|
||||
},
|
||||
@@ -1228,7 +1326,11 @@ export default {
|
||||
|
||||
if (!this.fieldsDragFieldRef.value) {
|
||||
if (['select', 'multiple', 'radio'].includes(field.type)) {
|
||||
field.options = [{ value: '', uuid: v4() }]
|
||||
if (this.dragField?.options?.length) {
|
||||
field.options = this.dragField.options.map(option => ({ value: option, uuid: v4() }))
|
||||
} else {
|
||||
field.options = [{ value: '', uuid: v4() }]
|
||||
}
|
||||
}
|
||||
|
||||
if (['stamp', 'heading'].includes(field.type)) {
|
||||
@@ -1331,6 +1433,8 @@ export default {
|
||||
const documentRef = this.documentRefs.find((e) => e.document.uuid === area.attachment_uuid)
|
||||
const areaRef = documentRef.pageRefs[area.page].areaRefs.find((ref) => ref.area === this.selectedAreaRef.value)
|
||||
|
||||
areaRef.isHeadingSelected = true
|
||||
|
||||
areaRef.focusValueInput()
|
||||
})
|
||||
}
|
||||
@@ -1418,29 +1522,29 @@ export default {
|
||||
onDocumentRemove (item) {
|
||||
if (window.confirm(this.t('are_you_sure_'))) {
|
||||
this.template.schema.splice(this.template.schema.indexOf(item), 1)
|
||||
}
|
||||
|
||||
const removedFieldUuids = []
|
||||
const removedFieldUuids = []
|
||||
|
||||
this.template.fields.forEach((field) => {
|
||||
[...(field.areas || [])].forEach((area) => {
|
||||
if (area.attachment_uuid === item.attachment_uuid) {
|
||||
field.areas.splice(field.areas.indexOf(area), 1)
|
||||
this.template.fields.forEach((field) => {
|
||||
[...(field.areas || [])].forEach((area) => {
|
||||
if (area.attachment_uuid === item.attachment_uuid) {
|
||||
field.areas.splice(field.areas.indexOf(area), 1)
|
||||
|
||||
removedFieldUuids.push(field.uuid)
|
||||
}
|
||||
removedFieldUuids.push(field.uuid)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
this.template.fields =
|
||||
this.template.fields.filter((f) => !removedFieldUuids.includes(f.uuid) || f.areas?.length)
|
||||
this.template.fields =
|
||||
this.template.fields.filter((f) => !removedFieldUuids.includes(f.uuid) || f.areas?.length)
|
||||
|
||||
this.save()
|
||||
this.save()
|
||||
}
|
||||
},
|
||||
onDocumentReplace (data) {
|
||||
const { replaceSchemaItem, schema, documents } = data
|
||||
|
||||
this.template.schema.splice(this.template.schema.indexOf(replaceSchemaItem), 1, schema[0])
|
||||
this.template.schema.splice(this.template.schema.indexOf(replaceSchemaItem), 1, { ...replaceSchemaItem, ...schema[0] })
|
||||
this.template.documents.push(...documents)
|
||||
|
||||
if (data.fields) {
|
||||
|
||||
@@ -177,7 +177,7 @@ export default {
|
||||
fields () {
|
||||
if (this.item.submitter_uuid) {
|
||||
return this.template.fields.reduce((acc, f) => {
|
||||
if (f !== this.item && f.submitter_uuid === this.item.submitter_uuid) {
|
||||
if (f !== this.item) {
|
||||
acc.push(f)
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
:input-mode="inputMode"
|
||||
:number="index"
|
||||
:editable="editable"
|
||||
:data-page="index"
|
||||
:areas="areasIndex[index]"
|
||||
:allow-draw="allowDraw"
|
||||
:is-drag="isDrag"
|
||||
@@ -16,6 +17,7 @@
|
||||
:draw-field="drawField"
|
||||
:draw-field-type="drawFieldType"
|
||||
:selected-submitter="selectedSubmitter"
|
||||
:total-pages="sortedPreviewImages.length"
|
||||
:image="image"
|
||||
@drop-field="$emit('drop-field', {...$event, attachment_uuid: document.uuid })"
|
||||
@remove-area="$emit('remove-area', $event)"
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
/>
|
||||
<Contenteditable
|
||||
ref="name"
|
||||
:model-value="(defaultField ? (field.title || field.name) : field.name) || defaultName"
|
||||
:model-value="(defaultField ? (defaultField.title || field.title || field.name) : field.name) || defaultName"
|
||||
:editable="editable && !defaultField && field.type != 'heading'"
|
||||
:icon-inline="true"
|
||||
:icon-width="18"
|
||||
@@ -192,14 +192,14 @@
|
||||
class="w-full input input-primary input-xs text-sm bg-transparent"
|
||||
:placeholder="`${t('option')} ${index + 1}`"
|
||||
type="text"
|
||||
:readonly="!editable"
|
||||
:readonly="!editable || defaultField"
|
||||
required
|
||||
dir="auto"
|
||||
@focus="maybeFocusOnOptionArea(option)"
|
||||
@blur="save"
|
||||
>
|
||||
<button
|
||||
v-if="editable"
|
||||
v-if="editable && !defaultField"
|
||||
class="text-sm w-3.5"
|
||||
tabindex="-1"
|
||||
@click="removeOption(option)"
|
||||
@@ -208,11 +208,11 @@
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
v-if="field.options && !editable"
|
||||
v-if="field.options && (!editable || defaultField)"
|
||||
class="pb-1"
|
||||
/>
|
||||
<button
|
||||
v-else-if="field.options && editable"
|
||||
v-else-if="field.options && editable && !defaultField"
|
||||
class="text-center text-sm w-full pb-1"
|
||||
@click="addOption"
|
||||
>
|
||||
@@ -392,7 +392,7 @@ export default {
|
||||
return this.sortedAreas[0] && this.$emit('scroll-to', this.sortedAreas[0])
|
||||
},
|
||||
closeDropdown () {
|
||||
document.activeElement.blur()
|
||||
this.$el.getRootNode().activeElement.blur()
|
||||
},
|
||||
addOption () {
|
||||
this.field.options.push({ value: '', uuid: v4() })
|
||||
|
||||
@@ -26,6 +26,33 @@
|
||||
{{ t('format') }}
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
v-if="field.type === 'verification'"
|
||||
class="py-1.5 px-1 relative"
|
||||
@click.stop
|
||||
>
|
||||
<select
|
||||
:placeholder="t('method')"
|
||||
class="select select-bordered select-xs font-normal w-full max-w-xs !h-7 !outline-0 bg-transparent"
|
||||
@change="[field.preferences ||= {}, field.preferences.method = $event.target.value, save()]"
|
||||
>
|
||||
<option
|
||||
v-for="method in ['QeS', 'AeS']"
|
||||
:key="method"
|
||||
:value="method.toLowerCase()"
|
||||
:selected="method.toLowerCase() === field.preferences?.method || (method === 'QeS' && !field.preferences?.method)"
|
||||
>
|
||||
{{ method }}
|
||||
</option>
|
||||
</select>
|
||||
<label
|
||||
:style="{ backgroundColor }"
|
||||
class="absolute -top-1 left-2.5 px-1 h-4"
|
||||
style="font-size: 8px"
|
||||
>
|
||||
{{ t('method') }}
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
v-if="['number', 'cells'].includes(field.type)"
|
||||
class="py-1.5 px-1 relative"
|
||||
@@ -212,16 +239,12 @@
|
||||
{{ t('any') }}
|
||||
</option>
|
||||
<option
|
||||
value="drawn"
|
||||
:selected="field.preferences?.format === 'drawn'"
|
||||
v-for="type in ['drawn', 'typed', 'drawn_or_typed', 'upload']"
|
||||
:key="type"
|
||||
:value="type"
|
||||
:selected="field.preferences?.format === type"
|
||||
>
|
||||
{{ t('drawn') }}
|
||||
</option>
|
||||
<option
|
||||
value="typed"
|
||||
:selected="field.preferences?.format === 'typed'"
|
||||
>
|
||||
{{ t('typed') }}
|
||||
{{ t(type) }}
|
||||
</option>
|
||||
</select>
|
||||
<label
|
||||
@@ -233,7 +256,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<li
|
||||
v-if="withRequired && field.type != 'phone' && field.type != 'stamp'"
|
||||
v-if="withRequired && field.type !== 'phone' && field.type !== 'stamp' && field.type !== 'verification'"
|
||||
@click.stop
|
||||
>
|
||||
<label class="cursor-pointer py-1.5">
|
||||
@@ -465,6 +488,9 @@ export default {
|
||||
numberFormats () {
|
||||
return [
|
||||
'none',
|
||||
'usd',
|
||||
'eur',
|
||||
'gbp',
|
||||
'comma',
|
||||
'dot',
|
||||
'space'
|
||||
@@ -545,6 +571,12 @@ export default {
|
||||
formatNumber (number, format) {
|
||||
if (format === 'comma') {
|
||||
return new Intl.NumberFormat('en-US').format(number)
|
||||
} else if (format === 'usd') {
|
||||
return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(number)
|
||||
} else if (format === 'gbp') {
|
||||
return new Intl.NumberFormat('en-GB', { style: 'currency', currency: 'GBP', minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(number)
|
||||
} else if (format === 'eur') {
|
||||
return new Intl.NumberFormat('fr-FR', { style: 'currency', currency: 'EUR', minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(number)
|
||||
} else if (format === 'dot') {
|
||||
return new Intl.NumberFormat('de-DE').format(number)
|
||||
} else if (format === 'space') {
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<div class="flex items-center space-x-2">
|
||||
<span
|
||||
class="w-3 h-3 flex-shrink-0 rounded-full"
|
||||
:class="colors[submitters.indexOf(selectedSubmitter)]"
|
||||
:class="colors[submitters.indexOf(selectedSubmitter) % colors.length]"
|
||||
/>
|
||||
<Contenteditable
|
||||
v-model="selectedSubmitter.name"
|
||||
@@ -53,7 +53,7 @@
|
||||
<span class="py-1 flex items-center">
|
||||
<span
|
||||
class="rounded-full w-3 h-3 ml-1 mr-3"
|
||||
:class="colors[index]"
|
||||
:class="colors[index % colors.length]"
|
||||
/>
|
||||
<span>
|
||||
{{ submitter.name }}
|
||||
@@ -101,7 +101,7 @@
|
||||
>
|
||||
<button
|
||||
class="mx-1 w-3 h-3 rounded-full"
|
||||
:class="colors[submitters.indexOf(selectedSubmitter)]"
|
||||
:class="colors[submitters.indexOf(selectedSubmitter) % colors.length]"
|
||||
/>
|
||||
</label>
|
||||
<label
|
||||
@@ -113,7 +113,7 @@
|
||||
<div class="flex items-center space-x-2">
|
||||
<span
|
||||
class="w-3 h-3 rounded-full"
|
||||
:class="colors[submitters.indexOf(selectedSubmitter)]"
|
||||
:class="colors[submitters.indexOf(selectedSubmitter) % colors.length]"
|
||||
/>
|
||||
<Contenteditable
|
||||
v-model="selectedSubmitter.name"
|
||||
@@ -143,6 +143,7 @@
|
||||
<li
|
||||
v-for="(submitter, index) in submitters"
|
||||
:key="submitter.uuid"
|
||||
class="w-full"
|
||||
>
|
||||
<a
|
||||
href="#"
|
||||
@@ -153,7 +154,7 @@
|
||||
<span class="py-1 flex items-center">
|
||||
<span
|
||||
class="rounded-full w-3 h-3 ml-1 mr-3"
|
||||
:class="colors[index]"
|
||||
:class="colors[index % colors.length]"
|
||||
/>
|
||||
<span>
|
||||
{{ submitter.name }}
|
||||
@@ -191,7 +192,10 @@
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
<li v-if="submitters.length < names.length && editable && allowAddNew">
|
||||
<li
|
||||
v-if="submitters.length < names.length && editable && allowAddNew"
|
||||
class="w-full"
|
||||
>
|
||||
<a
|
||||
href="#"
|
||||
class="flex px-2"
|
||||
@@ -215,6 +219,14 @@ import { IconUserPlus, IconTrashX, IconPlus, IconChevronUp, IconChevronDown } fr
|
||||
import Contenteditable from './contenteditable'
|
||||
import { v4 } from 'uuid'
|
||||
|
||||
function getOrdinalSuffix (num) {
|
||||
if (num % 10 === 1 && num % 100 !== 11) return 'st'
|
||||
if (num % 10 === 2 && num % 100 !== 12) return 'nd'
|
||||
if (num % 10 === 3 && num % 100 !== 13) return 'rd'
|
||||
|
||||
return 'th'
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'FieldSubmitter',
|
||||
components: {
|
||||
@@ -275,16 +287,6 @@ export default {
|
||||
computed: {
|
||||
colors () {
|
||||
return [
|
||||
'bg-red-500',
|
||||
'bg-sky-500',
|
||||
'bg-emerald-500',
|
||||
'bg-yellow-300',
|
||||
'bg-purple-600',
|
||||
'bg-pink-500',
|
||||
'bg-cyan-500',
|
||||
'bg-orange-500',
|
||||
'bg-lime-500',
|
||||
'bg-indigo-500',
|
||||
'bg-red-500',
|
||||
'bg-sky-500',
|
||||
'bg-emerald-500',
|
||||
@@ -298,6 +300,14 @@ export default {
|
||||
]
|
||||
},
|
||||
names () {
|
||||
const generatedNames = []
|
||||
|
||||
for (let i = 21; i < 101; i++) {
|
||||
const suffix = getOrdinalSuffix(i)
|
||||
|
||||
generatedNames.push(`${i}${suffix} ${this.t('party')}`)
|
||||
}
|
||||
|
||||
return [
|
||||
this.t('first_party'),
|
||||
this.t('second_party'),
|
||||
@@ -318,7 +328,8 @@ export default {
|
||||
this.t('seventeenth_party'),
|
||||
this.t('eighteenth_party'),
|
||||
this.t('nineteenth_party'),
|
||||
this.t('twentieth_party')
|
||||
this.t('twentieth_party'),
|
||||
...generatedNames
|
||||
]
|
||||
},
|
||||
lastPartyIndex () {
|
||||
@@ -372,7 +383,7 @@ export default {
|
||||
this.$emit('new-submitter', newSubmitter)
|
||||
},
|
||||
closeDropdown () {
|
||||
document.activeElement.blur()
|
||||
this.$el.getRootNode().activeElement.blur()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,7 +170,7 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
closeDropdown () {
|
||||
document.activeElement.blur()
|
||||
this.$el.getRootNode().activeElement.blur()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
class="roles-dropdown w-full rounded-lg"
|
||||
:style="withStickySubmitters ? { backgroundColor } : {}"
|
||||
:submitters="submitters"
|
||||
:menu-style="{ backgroundColor: ['', null, 'transparent'].includes(backgroundColor) ? 'white' : backgroundColor }"
|
||||
:menu-style="{ overflow: 'auto', display: 'flex', flexDirection: 'row', maxHeight: 'calc(100vh - 120px)', backgroundColor: ['', null, 'transparent'].includes(backgroundColor) ? 'white' : backgroundColor }"
|
||||
:editable="editable && !defaultSubmitters.length"
|
||||
@new-submitter="save"
|
||||
@remove="removeSubmitter"
|
||||
@@ -26,7 +26,7 @@
|
||||
:field="field"
|
||||
:type-index="fields.filter((f) => f.type === field.type).indexOf(field)"
|
||||
:editable="editable && (!fieldsDragFieldRef.value || fieldsDragFieldRef.value !== field)"
|
||||
:default-field="defaultFields.find((f) => f.name === field.name)"
|
||||
:default-field="defaultFieldsIndex[field.name]"
|
||||
:draggable="editable"
|
||||
@dragstart="fieldsDragFieldRef.value = field"
|
||||
@dragend="fieldsDragFieldRef.value = null"
|
||||
@@ -229,6 +229,11 @@ export default {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
withFieldsSearch: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
template: {
|
||||
type: Object,
|
||||
required: true
|
||||
@@ -297,7 +302,18 @@ export default {
|
||||
fieldNames: FieldType.computed.fieldNames,
|
||||
fieldIcons: FieldType.computed.fieldIcons,
|
||||
isShowFieldSearch () {
|
||||
return this.submitterDefaultFields.length > 15
|
||||
if (this.withFieldsSearch === false) {
|
||||
return false
|
||||
} else {
|
||||
return this.submitterDefaultFields.length > 15
|
||||
}
|
||||
},
|
||||
defaultFieldsIndex () {
|
||||
return this.defaultFields.reduce((acc, field) => {
|
||||
acc[field.name] = field
|
||||
|
||||
return acc
|
||||
}, {})
|
||||
},
|
||||
fieldIconsSorted () {
|
||||
if (this.fieldTypes.length) {
|
||||
@@ -320,7 +336,7 @@ export default {
|
||||
},
|
||||
filteredSubmitterDefaultFields () {
|
||||
if (this.defaultFieldsSearch) {
|
||||
return this.submitterDefaultFields.filter((f) => f.name.toLowerCase().includes(this.defaultFieldsSearch.toLowerCase()))
|
||||
return this.submitterDefaultFields.filter((f) => (f.title || f.name).toLowerCase().includes(this.defaultFieldsSearch.toLowerCase()))
|
||||
} else {
|
||||
return this.submitterDefaultFields
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
const en = {
|
||||
party: 'Party',
|
||||
method: 'Method',
|
||||
reorder_fields: 'Reorder fields',
|
||||
verify_id: 'Verify ID',
|
||||
obtain_qualified_electronic_signature_with_the_trusted_provider_click_to_learn_more: 'Obtain qualified electronic signature (QeS) with the trusted provider. Click to learn more.',
|
||||
editable: 'Editable',
|
||||
@@ -48,6 +51,8 @@ const en = {
|
||||
cancel: 'Cancel',
|
||||
any: 'Any',
|
||||
drawn: 'Drawn',
|
||||
drawn_or_typed: 'Drawn or Typed',
|
||||
upload: 'Upload',
|
||||
formula: 'Formula',
|
||||
typed: 'Typed',
|
||||
draw_field_on_the_document: 'Draw a field on the document',
|
||||
@@ -156,6 +161,9 @@ const en = {
|
||||
}
|
||||
|
||||
const es = {
|
||||
party: 'Parte',
|
||||
method: 'Método',
|
||||
reorder_fields: 'Reordenar campos',
|
||||
verify_id: 'Verificar ID',
|
||||
obtain_qualified_electronic_signature_with_the_trusted_provider_click_to_learn_more: 'Obtenga una firma electrónica cualificada (QeS) con el proveedor de confianza. Haga clic para obtener más información.',
|
||||
recurrent: 'Recurrente',
|
||||
@@ -289,6 +297,8 @@ const es = {
|
||||
add_document: 'Subir',
|
||||
any: 'Cualquier',
|
||||
drawn: 'Dibujado',
|
||||
drawn_or_typed: 'Dibujado o Escrito',
|
||||
upload: 'Subir',
|
||||
typed: 'Escrito',
|
||||
none: 'Ninguno',
|
||||
ssn: 'SSN',
|
||||
@@ -313,6 +323,9 @@ const es = {
|
||||
}
|
||||
|
||||
const it = {
|
||||
party: 'Parte',
|
||||
method: 'Metodo',
|
||||
reorder_fields: 'Riordina i campi',
|
||||
verify_id: 'Verifica ID',
|
||||
obtain_qualified_electronic_signature_with_the_trusted_provider_click_to_learn_more: 'Ottieni una firma elettronica qualificata (QeS) con il fornitore di fiducia. Clicca per saperne di più.',
|
||||
ricorrente: 'Ricorrente',
|
||||
@@ -362,6 +375,8 @@ const it = {
|
||||
cancel: 'Annulla',
|
||||
any: 'Qualsiasi',
|
||||
drawn: 'Disegnato',
|
||||
drawn_or_typed: 'Disegnato o Digitato',
|
||||
upload: 'Caricare',
|
||||
formula: 'Formula',
|
||||
typed: 'Digitato',
|
||||
draw_field_on_the_document: 'Disegnare un campo sul documento',
|
||||
@@ -470,6 +485,9 @@ const it = {
|
||||
}
|
||||
|
||||
const pt = {
|
||||
party: 'Parte',
|
||||
method: 'Método',
|
||||
reorder_fields: 'Reorganizar campos',
|
||||
verify_id: 'Verificar ID',
|
||||
obtain_qualified_electronic_signature_with_the_trusted_provider_click_to_learn_more: 'Obtenha a assinatura eletrônica qualificada (QeS) com o provedor confiável. Clique para saber mais.',
|
||||
recurrent: 'Recurrente',
|
||||
@@ -603,6 +621,8 @@ const pt = {
|
||||
add_document: 'Enviar',
|
||||
any: 'Qualquer',
|
||||
drawn: 'Desenhado',
|
||||
drawn_or_typed: 'Desenhado ou Digitado',
|
||||
upload: 'Carregar',
|
||||
typed: 'Digitado',
|
||||
none: 'Nenhum',
|
||||
ssn: 'SSN',
|
||||
@@ -627,6 +647,9 @@ const pt = {
|
||||
}
|
||||
|
||||
const fr = {
|
||||
party: 'Partie',
|
||||
method: 'Méthode',
|
||||
reorder_fields: 'Réorganiser les champs',
|
||||
verify_id: "Vérifier l'ID",
|
||||
obtain_qualified_electronic_signature_with_the_trusted_provider_click_to_learn_more: 'Obtenez une signature électronique qualifiée (QeS) avec le fournisseur de confiance. Cliquez pour en savoir plus.',
|
||||
recurrent: 'Récurrent',
|
||||
@@ -760,6 +783,8 @@ const fr = {
|
||||
add_document: 'Télécharger',
|
||||
any: 'Tout',
|
||||
drawn: 'Dessiné',
|
||||
drawn_or_typed: 'Dessiné ou Tapé',
|
||||
upload: 'Téléverser',
|
||||
typed: 'Tapé',
|
||||
none: 'Aucun',
|
||||
ssn: 'SSN',
|
||||
@@ -784,6 +809,9 @@ const fr = {
|
||||
}
|
||||
|
||||
const de = {
|
||||
party: 'Partei',
|
||||
method: 'Verfahren',
|
||||
reorder_fields: 'Felder neu anordnen',
|
||||
verify_id: 'ID überprüfen',
|
||||
obtain_qualified_electronic_signature_with_the_trusted_provider_click_to_learn_more: 'Erhalten Sie eine qualifizierte elektronische Signatur (QeS) beim vertrauenswürdigen Anbieter. Klicken Sie hier, um mehr zu erfahren.',
|
||||
wiederkehrend: 'Wiederkehrend',
|
||||
@@ -917,6 +945,8 @@ const de = {
|
||||
add_document: 'Hochladen',
|
||||
any: 'Jede',
|
||||
drawn: 'Gezeichnet',
|
||||
drawn_or_typed: 'Gezeichnet oder getippt',
|
||||
upload: 'Upload',
|
||||
typed: 'Getippt',
|
||||
none: 'Keine',
|
||||
ssn: 'SSN',
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<div
|
||||
class="relative cursor-crosshair select-none"
|
||||
class="relative select-none"
|
||||
:class="{ 'cursor-crosshair': allowDraw }"
|
||||
:style="drawField ? 'touch-action: none' : ''"
|
||||
>
|
||||
<img
|
||||
@@ -25,8 +26,9 @@
|
||||
:field="item.field"
|
||||
:editable="editable"
|
||||
:with-field-placeholder="withFieldPlaceholder"
|
||||
:default-field="defaultFields.find((f) => f.name === item.field.name)"
|
||||
:default-field="defaultFieldsIndex[item.field.name]"
|
||||
:default-submitters="defaultSubmitters"
|
||||
:max-page="totalPages - 1"
|
||||
@start-resize="resizeDirection = $event"
|
||||
@stop-resize="resizeDirection = null"
|
||||
@remove="$emit('remove-area', item.area)"
|
||||
@@ -88,6 +90,10 @@ export default {
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
totalPages: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
drawFieldType: {
|
||||
type: String,
|
||||
required: false,
|
||||
@@ -137,6 +143,13 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
defaultFieldsIndex () {
|
||||
return this.defaultFields.reduce((acc, field) => {
|
||||
acc[field.name] = field
|
||||
|
||||
return acc
|
||||
}, {})
|
||||
},
|
||||
defaultFieldType () {
|
||||
if (this.drawFieldType) {
|
||||
return this.drawFieldType
|
||||
@@ -149,7 +162,9 @@ export default {
|
||||
}
|
||||
},
|
||||
isMobile () {
|
||||
return /android|iphone|ipad/i.test(navigator.userAgent)
|
||||
const isMobileSafariIos = 'ontouchstart' in window && navigator.maxTouchPoints > 0 && /AppleWebKit/i.test(navigator.userAgent)
|
||||
|
||||
return isMobileSafariIos || /android|iphone|ipad/i.test(navigator.userAgent)
|
||||
},
|
||||
resizeDirectionClasses () {
|
||||
return {
|
||||
|
||||
@@ -56,23 +56,39 @@
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
v-if="withArrows"
|
||||
class="flex flex-col space-y-1"
|
||||
>
|
||||
<button
|
||||
class="btn border-base-200 bg-white text-base-content btn-xs rounded hover:text-base-100 hover:bg-base-content hover:border-base-content w-full transition-colors"
|
||||
style="width: 24px; height: 24px"
|
||||
@click.stop="$emit('up', item)"
|
||||
<span
|
||||
:data-tip="t('reorder_fields')"
|
||||
class="tooltip tooltip-left before:text-xs"
|
||||
>
|
||||
↑
|
||||
</button>
|
||||
<button
|
||||
class="btn border-base-200 bg-white text-base-content btn-xs rounded hover:text-base-100 hover:bg-base-content hover:border-base-content w-full transition-colors"
|
||||
style="width: 24px; height: 24px"
|
||||
@click.stop="$emit('down', item)"
|
||||
>
|
||||
↓
|
||||
</button>
|
||||
<button
|
||||
class="btn border-base-200 bg-white text-base-content btn-xs rounded hover:text-base-100 hover:bg-base-content hover:border-base-content w-full transition-colors p-0"
|
||||
@click.stop="$emit('reorder', item)"
|
||||
>
|
||||
<IconSortDescending2
|
||||
:width="18"
|
||||
:height="18"
|
||||
:stroke-width="1.6"
|
||||
/>
|
||||
</button>
|
||||
</span>
|
||||
<template v-if="withArrows">
|
||||
<button
|
||||
class="btn border-base-200 bg-white text-base-content btn-xs rounded hover:text-base-100 hover:bg-base-content hover:border-base-content w-full transition-colors"
|
||||
style="width: 24px; height: 24px"
|
||||
@click.stop="$emit('up', item)"
|
||||
>
|
||||
↑
|
||||
</button>
|
||||
<button
|
||||
class="btn border-base-200 bg-white text-base-content btn-xs rounded hover:text-base-100 hover:bg-base-content hover:border-base-content w-full transition-colors"
|
||||
style="width: 24px; height: 24px"
|
||||
@click.stop="$emit('down', item)"
|
||||
>
|
||||
↓
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -88,23 +104,23 @@
|
||||
@update:model-value="onUpdateName"
|
||||
/>
|
||||
</div>
|
||||
<Teleport
|
||||
v-if="isShowConditionsModal"
|
||||
:to="modalContainerEl"
|
||||
>
|
||||
<ConditionsModal
|
||||
:item="item"
|
||||
:build-default-name="buildDefaultName"
|
||||
@close="isShowConditionsModal = false"
|
||||
/>
|
||||
</Teleport>
|
||||
</div>
|
||||
<Teleport
|
||||
v-if="isShowConditionsModal"
|
||||
:to="modalContainerEl"
|
||||
>
|
||||
<ConditionsModal
|
||||
:item="item"
|
||||
:build-default-name="buildDefaultName"
|
||||
@close="isShowConditionsModal = false"
|
||||
/>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Contenteditable from './contenteditable'
|
||||
import Upload from './upload'
|
||||
import { IconRouteAltLeft } from '@tabler/icons-vue'
|
||||
import { IconRouteAltLeft, IconSortDescending2 } from '@tabler/icons-vue'
|
||||
import ConditionsModal from './conditions_modal'
|
||||
import ReplaceButton from './replace'
|
||||
import Field from './field'
|
||||
@@ -116,7 +132,8 @@ export default {
|
||||
Contenteditable,
|
||||
IconRouteAltLeft,
|
||||
ConditionsModal,
|
||||
ReplaceButton
|
||||
ReplaceButton,
|
||||
IconSortDescending2
|
||||
},
|
||||
inject: ['t'],
|
||||
props: {
|
||||
@@ -153,7 +170,7 @@ export default {
|
||||
default: true
|
||||
}
|
||||
},
|
||||
emits: ['scroll-to', 'change', 'remove', 'up', 'down', 'replace'],
|
||||
emits: ['scroll-to', 'change', 'remove', 'up', 'down', 'replace', 'reorder'],
|
||||
data () {
|
||||
return {
|
||||
isShowConditionsModal: false
|
||||
|
||||
@@ -120,6 +120,8 @@ class ProcessSubmitterCompletionJob
|
||||
submitter.submission.template_submitters.find do |e|
|
||||
sub = submitter.submission.submitters.find { |s| s.uuid == e['uuid'] }
|
||||
|
||||
next unless sub
|
||||
|
||||
sub.completed_at.blank? && sub.sent_at.blank?
|
||||
end
|
||||
|
||||
|
||||
@@ -5,9 +5,7 @@ class SendFormCompletedWebhookRequestJob
|
||||
|
||||
sidekiq_options queue: :webhooks
|
||||
|
||||
USER_AGENT = 'DocuSeal.com Webhook'
|
||||
|
||||
MAX_ATTEMPTS = 10
|
||||
MAX_ATTEMPTS = 20
|
||||
|
||||
def perform(params = {})
|
||||
submitter = Submitter.find(params['submitter_id'])
|
||||
@@ -21,19 +19,8 @@ class SendFormCompletedWebhookRequestJob
|
||||
|
||||
ActiveStorage::Current.url_options = Docuseal.default_url_options
|
||||
|
||||
resp = begin
|
||||
Faraday.post(webhook_url.url,
|
||||
{
|
||||
event_type: 'form.completed',
|
||||
timestamp: Time.current,
|
||||
data: Submitters::SerializeForWebhook.call(submitter)
|
||||
}.to_json,
|
||||
'Content-Type' => 'application/json',
|
||||
'User-Agent' => USER_AGENT,
|
||||
**webhook_url.secret.to_h)
|
||||
rescue Faraday::Error
|
||||
nil
|
||||
end
|
||||
resp = SendWebhookRequest.call(webhook_url, event_type: 'form.completed',
|
||||
data: Submitters::SerializeForWebhook.call(submitter))
|
||||
|
||||
if (resp.nil? || resp.status.to_i >= 400) && attempt <= MAX_ATTEMPTS &&
|
||||
(!Docuseal.multitenant? || submitter.account.account_configs.exists?(key: :plan))
|
||||
|
||||
@@ -5,8 +5,6 @@ class SendFormDeclinedWebhookRequestJob
|
||||
|
||||
sidekiq_options queue: :webhooks
|
||||
|
||||
USER_AGENT = 'DocuSeal.com Webhook'
|
||||
|
||||
MAX_ATTEMPTS = 10
|
||||
|
||||
def perform(params = {})
|
||||
@@ -19,19 +17,8 @@ class SendFormDeclinedWebhookRequestJob
|
||||
|
||||
ActiveStorage::Current.url_options = Docuseal.default_url_options
|
||||
|
||||
resp = begin
|
||||
Faraday.post(webhook_url.url,
|
||||
{
|
||||
event_type: 'form.declined',
|
||||
timestamp: Time.current,
|
||||
data: Submitters::SerializeForWebhook.call(submitter)
|
||||
}.to_json,
|
||||
**webhook_url.secret.to_h,
|
||||
'Content-Type' => 'application/json',
|
||||
'User-Agent' => USER_AGENT)
|
||||
rescue Faraday::Error
|
||||
nil
|
||||
end
|
||||
resp = SendWebhookRequest.call(webhook_url, event_type: 'form.declined',
|
||||
data: Submitters::SerializeForWebhook.call(submitter))
|
||||
|
||||
if (resp.nil? || resp.status.to_i >= 400) && attempt <= MAX_ATTEMPTS &&
|
||||
(!Docuseal.multitenant? || submitter.account.account_configs.exists?(key: :plan))
|
||||
|
||||
@@ -5,8 +5,6 @@ class SendFormStartedWebhookRequestJob
|
||||
|
||||
sidekiq_options queue: :webhooks
|
||||
|
||||
USER_AGENT = 'DocuSeal.com Webhook'
|
||||
|
||||
MAX_ATTEMPTS = 10
|
||||
|
||||
def perform(params = {})
|
||||
@@ -19,19 +17,8 @@ class SendFormStartedWebhookRequestJob
|
||||
|
||||
ActiveStorage::Current.url_options = Docuseal.default_url_options
|
||||
|
||||
resp = begin
|
||||
Faraday.post(webhook_url.url,
|
||||
{
|
||||
event_type: 'form.started',
|
||||
timestamp: Time.current,
|
||||
data: Submitters::SerializeForWebhook.call(submitter)
|
||||
}.to_json,
|
||||
**webhook_url.secret.to_h,
|
||||
'Content-Type' => 'application/json',
|
||||
'User-Agent' => USER_AGENT)
|
||||
rescue Faraday::Error
|
||||
nil
|
||||
end
|
||||
resp = SendWebhookRequest.call(webhook_url, event_type: 'form.started',
|
||||
data: Submitters::SerializeForWebhook.call(submitter))
|
||||
|
||||
if (resp.nil? || resp.status.to_i >= 400) && attempt <= MAX_ATTEMPTS &&
|
||||
(!Docuseal.multitenant? || submitter.account.account_configs.exists?(key: :plan))
|
||||
|
||||
@@ -5,8 +5,6 @@ class SendFormViewedWebhookRequestJob
|
||||
|
||||
sidekiq_options queue: :webhooks
|
||||
|
||||
USER_AGENT = 'DocuSeal.com Webhook'
|
||||
|
||||
MAX_ATTEMPTS = 10
|
||||
|
||||
def perform(params = {})
|
||||
@@ -19,19 +17,8 @@ class SendFormViewedWebhookRequestJob
|
||||
|
||||
ActiveStorage::Current.url_options = Docuseal.default_url_options
|
||||
|
||||
resp = begin
|
||||
Faraday.post(webhook_url.url,
|
||||
{
|
||||
event_type: 'form.viewed',
|
||||
timestamp: Time.current,
|
||||
data: Submitters::SerializeForWebhook.call(submitter)
|
||||
}.to_json,
|
||||
**webhook_url.secret.to_h,
|
||||
'Content-Type' => 'application/json',
|
||||
'User-Agent' => USER_AGENT)
|
||||
rescue Faraday::Error
|
||||
nil
|
||||
end
|
||||
resp = SendWebhookRequest.call(webhook_url, event_type: 'form.viewed',
|
||||
data: Submitters::SerializeForWebhook.call(submitter))
|
||||
|
||||
if (resp.nil? || resp.status.to_i >= 400) && attempt <= MAX_ATTEMPTS &&
|
||||
(!Docuseal.multitenant? || submitter.account.account_configs.exists?(key: :plan))
|
||||
|
||||
@@ -5,8 +5,6 @@ class SendSubmissionArchivedWebhookRequestJob
|
||||
|
||||
sidekiq_options queue: :webhooks
|
||||
|
||||
USER_AGENT = 'DocuSeal.com Webhook'
|
||||
|
||||
MAX_ATTEMPTS = 10
|
||||
|
||||
def perform(params = {})
|
||||
@@ -17,19 +15,8 @@ class SendSubmissionArchivedWebhookRequestJob
|
||||
|
||||
return if webhook_url.url.blank? || webhook_url.events.exclude?('submission.archived')
|
||||
|
||||
resp = begin
|
||||
Faraday.post(webhook_url.url,
|
||||
{
|
||||
event_type: 'submission.archived',
|
||||
timestamp: Time.current,
|
||||
data: submission.as_json(only: %i[id archived_at])
|
||||
}.to_json,
|
||||
**webhook_url.secret.to_h,
|
||||
'Content-Type' => 'application/json',
|
||||
'User-Agent' => USER_AGENT)
|
||||
rescue Faraday::Error
|
||||
nil
|
||||
end
|
||||
resp = SendWebhookRequest.call(webhook_url, event_type: 'submission.archived',
|
||||
data: submission.as_json(only: %i[id archived_at]))
|
||||
|
||||
if (resp.nil? || resp.status.to_i >= 400) && attempt <= MAX_ATTEMPTS &&
|
||||
(!Docuseal.multitenant? || submission.account.account_configs.exists?(key: :plan))
|
||||
|
||||
@@ -5,8 +5,6 @@ class SendSubmissionCompletedWebhookRequestJob
|
||||
|
||||
sidekiq_options queue: :webhooks
|
||||
|
||||
USER_AGENT = 'DocuSeal.com Webhook'
|
||||
|
||||
MAX_ATTEMPTS = 10
|
||||
|
||||
def perform(params = {})
|
||||
@@ -17,19 +15,8 @@ class SendSubmissionCompletedWebhookRequestJob
|
||||
|
||||
return if webhook_url.url.blank? || webhook_url.events.exclude?('submission.completed')
|
||||
|
||||
resp = begin
|
||||
Faraday.post(webhook_url.url,
|
||||
{
|
||||
event_type: 'submission.completed',
|
||||
timestamp: Time.current,
|
||||
data: Submissions::SerializeForApi.call(submission)
|
||||
}.to_json,
|
||||
'Content-Type' => 'application/json',
|
||||
'User-Agent' => USER_AGENT,
|
||||
**webhook_url.secret.to_h)
|
||||
rescue Faraday::Error
|
||||
nil
|
||||
end
|
||||
resp = SendWebhookRequest.call(webhook_url, event_type: 'submission.completed',
|
||||
data: Submissions::SerializeForApi.call(submission))
|
||||
|
||||
if (resp.nil? || resp.status.to_i >= 400) && attempt <= MAX_ATTEMPTS &&
|
||||
(!Docuseal.multitenant? || submission.account.account_configs.exists?(key: :plan))
|
||||
|
||||
@@ -5,8 +5,6 @@ class SendSubmissionCreatedWebhookRequestJob
|
||||
|
||||
sidekiq_options queue: :webhooks
|
||||
|
||||
USER_AGENT = 'DocuSeal.com Webhook'
|
||||
|
||||
MAX_ATTEMPTS = 10
|
||||
|
||||
def perform(params = {})
|
||||
@@ -17,19 +15,8 @@ class SendSubmissionCreatedWebhookRequestJob
|
||||
|
||||
return if webhook_url.url.blank? || webhook_url.events.exclude?('submission.created')
|
||||
|
||||
resp = begin
|
||||
Faraday.post(webhook_url.url,
|
||||
{
|
||||
event_type: 'submission.created',
|
||||
timestamp: Time.current,
|
||||
data: Submissions::SerializeForApi.call(submission)
|
||||
}.to_json,
|
||||
**webhook_url.secret.to_h,
|
||||
'Content-Type' => 'application/json',
|
||||
'User-Agent' => USER_AGENT)
|
||||
rescue Faraday::Error
|
||||
nil
|
||||
end
|
||||
resp = SendWebhookRequest.call(webhook_url, event_type: 'submission.created',
|
||||
data: Submissions::SerializeForApi.call(submission))
|
||||
|
||||
if (resp.nil? || resp.status.to_i >= 400) && attempt <= MAX_ATTEMPTS &&
|
||||
(!Docuseal.multitenant? || submission.account.account_configs.exists?(key: :plan))
|
||||
|
||||
@@ -8,6 +8,12 @@ class SendSubmitterInvitationEmailJob
|
||||
|
||||
return if submitter.submission.source == 'invite' && !Accounts.can_send_emails?(submitter.account, on_events: true)
|
||||
|
||||
unless Accounts.can_send_invitation_emails?(submitter.account)
|
||||
Rollbar.warning("Skip email: #{submitter.account.id}") if defined?(Rollbar)
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
mail = SubmitterMailer.invitation_email(submitter)
|
||||
|
||||
Submitters::ValidateSending.call(submitter, mail)
|
||||
@@ -17,6 +23,6 @@ class SendSubmitterInvitationEmailJob
|
||||
SubmissionEvent.create!(submitter:, event_type: 'send_email')
|
||||
|
||||
submitter.sent_at ||= Time.current
|
||||
submitter.save
|
||||
submitter.save!
|
||||
end
|
||||
end
|
||||
@@ -5,8 +5,6 @@ class SendTemplateCreatedWebhookRequestJob
|
||||
|
||||
sidekiq_options queue: :webhooks
|
||||
|
||||
USER_AGENT = 'DocuSeal.com Webhook'
|
||||
|
||||
MAX_ATTEMPTS = 10
|
||||
|
||||
def perform(params = {})
|
||||
@@ -17,19 +15,8 @@ class SendTemplateCreatedWebhookRequestJob
|
||||
|
||||
return if webhook_url.url.blank? || webhook_url.events.exclude?('template.created')
|
||||
|
||||
resp = begin
|
||||
Faraday.post(webhook_url.url,
|
||||
{
|
||||
event_type: 'template.created',
|
||||
timestamp: Time.current,
|
||||
data: Templates::SerializeForApi.call(template)
|
||||
}.to_json,
|
||||
**webhook_url.secret.to_h,
|
||||
'Content-Type' => 'application/json',
|
||||
'User-Agent' => USER_AGENT)
|
||||
rescue Faraday::Error
|
||||
nil
|
||||
end
|
||||
resp = SendWebhookRequest.call(webhook_url, event_type: 'template.created',
|
||||
data: Templates::SerializeForApi.call(template))
|
||||
|
||||
if (resp.nil? || resp.status.to_i >= 400) && attempt <= MAX_ATTEMPTS &&
|
||||
(!Docuseal.multitenant? || template.account.account_configs.exists?(key: :plan))
|
||||
|
||||
@@ -5,8 +5,6 @@ class SendTemplateUpdatedWebhookRequestJob
|
||||
|
||||
sidekiq_options queue: :webhooks
|
||||
|
||||
USER_AGENT = 'DocuSeal.com Webhook'
|
||||
|
||||
MAX_ATTEMPTS = 10
|
||||
|
||||
def perform(params = {})
|
||||
@@ -17,19 +15,8 @@ class SendTemplateUpdatedWebhookRequestJob
|
||||
|
||||
return if webhook_url.url.blank? || webhook_url.events.exclude?('template.updated')
|
||||
|
||||
resp = begin
|
||||
Faraday.post(webhook_url.url,
|
||||
{
|
||||
event_type: 'template.updated',
|
||||
timestamp: Time.current,
|
||||
data: Templates::SerializeForApi.call(template)
|
||||
}.to_json,
|
||||
**webhook_url.secret.to_h,
|
||||
'Content-Type' => 'application/json',
|
||||
'User-Agent' => USER_AGENT)
|
||||
rescue Faraday::Error
|
||||
nil
|
||||
end
|
||||
resp = SendWebhookRequest.call(webhook_url, event_type: 'template.updated',
|
||||
data: Templates::SerializeForApi.call(template))
|
||||
|
||||
if (resp.nil? || resp.status.to_i >= 400) && attempt <= MAX_ATTEMPTS &&
|
||||
(!Docuseal.multitenant? || template.account.account_configs.exists?(key: :plan))
|
||||
|
||||
@@ -121,7 +121,7 @@ class SubmitterMailer < ApplicationMailer
|
||||
@body ||= @email_config.value['body'] if @email_config
|
||||
|
||||
assign_message_metadata('submitter_documents_copy', @submitter)
|
||||
reply_to = build_submitter_reply_to(submitter)
|
||||
reply_to = build_submitter_reply_to(submitter, email_config: @email_config, documents_copy_email: true)
|
||||
|
||||
I18n.with_locale(@current_account.locale) do
|
||||
subject =
|
||||
@@ -140,8 +140,10 @@ class SubmitterMailer < ApplicationMailer
|
||||
|
||||
private
|
||||
|
||||
def build_submitter_reply_to(submitter)
|
||||
def build_submitter_reply_to(submitter, email_config: nil, documents_copy_email: nil)
|
||||
reply_to = submitter.preferences['reply_to'].presence
|
||||
reply_to ||= submitter.template.preferences['documents_copy_email_reply_to'].presence if documents_copy_email
|
||||
reply_to ||= email_config.value['reply_to'].presence if email_config
|
||||
|
||||
if reply_to.blank? && (submitter.submission.created_by_user || submitter.template.author)&.email != submitter.email
|
||||
reply_to = (submitter.submission.created_by_user || submitter.template.author)&.friendly_name&.sub(/\+\w+@/, '@')
|
||||
|
||||
@@ -57,6 +57,7 @@ class User < ApplicationRecord
|
||||
|
||||
belongs_to :account
|
||||
has_one :access_token, dependent: :destroy
|
||||
has_many :access_tokens, dependent: :destroy
|
||||
has_many :templates, dependent: :destroy, foreign_key: :author_id, inverse_of: :author
|
||||
has_many :template_folders, dependent: :destroy, foreign_key: :author_id, inverse_of: :author
|
||||
has_many :user_configs, dependent: :destroy
|
||||
|
||||
Whitespace-only changes.
@@ -153,6 +153,7 @@
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<%= render 'compliances' %>
|
||||
<%= render 'integrations' %>
|
||||
<% if can?(:manage, current_account) && Docuseal.multitenant? && true_user == current_user %>
|
||||
<div class="px-1 mt-8">
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<%= hidden_field_tag :redir, params[:redir] if params[:redir].present? %>
|
||||
@@ -0,0 +1,4 @@
|
||||
<div class="form-control">
|
||||
<%= label_tag 'user[otp_attempt]', t('two_factor_code_from_authenticator_app'), class: 'label' %>
|
||||
<%= text_field_tag 'user[otp_attempt]', nil, autofocus: true, autocomplete: 'off', placeholder: 'XXX-XXX', required: true, class: 'base-input' %>
|
||||
</div>
|
||||
@@ -3,9 +3,7 @@
|
||||
<%= render 'devise/shared/select_server' if Docuseal.multitenant? %>
|
||||
<h1 class="text-4xl font-bold text-center mt-8"><%= t('sign_in') %></h1>
|
||||
<%= form_for(resource, as: resource_name, html: { class: 'space-y-6' }, data: { turbo: params[:redir].blank? }, url: session_path(resource_name)) do |f| %>
|
||||
<% if params[:redir].present? %>
|
||||
<%= hidden_field_tag :redir, params[:redir] %>
|
||||
<% end %>
|
||||
<%= render 'hidden_fields' %>
|
||||
<div class="space-y-2" dir="auto">
|
||||
<div class="form-control">
|
||||
<%= f.label :email, t(:email), class: 'label' %>
|
||||
|
||||
@@ -8,12 +8,7 @@
|
||||
<% if params[:redir].present? %>
|
||||
<%= hidden_field_tag :redir, params[:redir] %>
|
||||
<% end %>
|
||||
<div class="space-y-2">
|
||||
<div class="form-control">
|
||||
<%= f.label :otp_attempt, t('two_factor_code_from_authenticator_app'), class: 'label' %>
|
||||
<%= f.text_field :otp_attempt, autofocus: true, placeholder: 'XXX-XXX', required: true, class: 'base-input' %>
|
||||
</div>
|
||||
</div>
|
||||
<%= render 'otp_form', **local_assigns %>
|
||||
<div class="form-control">
|
||||
<%= f.button button_title(title: t('sign_in'), disabled_with: t('signing_in')), class: 'base-button' %>
|
||||
</div>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<a href="<%= "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premise" }.to_query}" %>" class="btn btn-neutral btn-sm text-white">
|
||||
<a href="<%= "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premises" }.to_query}" %>" class="btn btn-neutral btn-sm text-white">
|
||||
<%= t('unlock_with_docuseal_pro') %>
|
||||
</a>
|
||||
</td>
|
||||
|
||||
@@ -173,7 +173,7 @@
|
||||
<%= t('document_download_filename_format') %>
|
||||
</span>
|
||||
<div class="mt-3">
|
||||
<%= f.select :value, [["#{I18n.t('document_name')}.pdf", '{document.name}'], ["#{I18n.t('document_name')} - name@domain.com.pdf", '{document.name} - {submission.submitters}'], ["#{I18n.t('document_name')} - name@domain.com - #{I18n.l(Time.current.beginning_of_year.in_time_zone(current_account.timezone), format: :short)}.pdf", '{document.name} - {submission.submitters} - {submission.completed_at}']], {}, class: 'base-select', onchange: 'this.form.requestSubmit()' %>
|
||||
<%= f.select :value, [["#{I18n.t('document_name')}.pdf", '{document.name}'], ["#{I18n.t('document_name')} - #{I18n.t(:signed)}.pdf", '{document.name} - {submission.status}'], ["#{I18n.t('document_name')} - name@domain.com.pdf", '{document.name} - {submission.submitters}'], ["#{I18n.t('document_name')} - name@domain.com - #{I18n.l(Time.current.beginning_of_year.in_time_zone(current_account.timezone), format: :short)}.pdf", '{document.name} - {submission.submitters} - {submission.completed_at}']], {}, class: 'base-select', onchange: 'this.form.requestSubmit()' %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
<html data-theme="docuseal" lang="<%= I18n.locale %>">
|
||||
<head>
|
||||
<%= render 'layouts/head_tags' %>
|
||||
<% if Docuseal.enable_pwa? %>
|
||||
<link rel="manifest" href="/manifest.json">
|
||||
<% end %>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<%= csrf_meta_tags %>
|
||||
<%= csp_meta_tag %>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<p>
|
||||
<%= t('send_automatic_email_reminders_to_your_recipients') %>
|
||||
<br>
|
||||
<a class="link font-medium" target="_blank" href="<%= Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premise" }.to_query}" %>" data-turbo="false">
|
||||
<a class="link font-medium" target="_blank" href="<%= Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premises" }.to_query}" %>" data-turbo="false">
|
||||
<%= t('learn_more') %>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<div class="collapse-content">
|
||||
<%= form_for AccountConfigs.find_or_initialize_for_key(current_account, AccountConfig::SUBMITTER_DOCUMENTS_COPY_EMAIL_KEY), url: settings_personalization_path, method: :post, html: { autocomplete: 'off', class: 'space-y-4' } do |f| %>
|
||||
<%= f.hidden_field :key %>
|
||||
<%= f.fields_for :value, Struct.new(:subject, :body, :attach_audit_log, :attach_documents).new(*f.object.value.values_at('subject', 'body', 'attach_audit_log', 'attach_documents')) do |ff| %>
|
||||
<%= f.fields_for :value, Struct.new(:subject, :body, :reply_to, :attach_audit_log, :attach_documents).new(*f.object.value.values_at('subject', 'body', 'reply_to', 'attach_audit_log', 'attach_documents')) do |ff| %>
|
||||
<div class="form-control">
|
||||
<%= ff.label :subject, t('subject'), class: 'label' %>
|
||||
<%= ff.text_field :subject, required: true, class: 'base-input', dir: 'auto' %>
|
||||
@@ -24,6 +24,12 @@
|
||||
<%= ff.text_area :body, required: true, class: 'base-input w-full py-2', dir: 'auto' %>
|
||||
</autoresize-textarea>
|
||||
</div>
|
||||
<% if can?(:manage, :reply_to) %>
|
||||
<div class="form-control">
|
||||
<%= ff.label :reply_to, t('reply_to'), class: 'label' %>
|
||||
<%= ff.email_field :reply_to, class: 'base-input', dir: 'auto', placeholder: t(:email) %>
|
||||
</div>
|
||||
<% end %>
|
||||
<div class="flex items-center justify-between pt-2.5 mx-1">
|
||||
<span>
|
||||
<%= t('attach_documents') %>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<p>
|
||||
<%= t('display_your_company_name_and_logo_when_signing_documents') %>
|
||||
<br>
|
||||
<a class="link font-medium" target="_blank" href="<%= Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premise" }.to_query}" %>" data-turbo="false">
|
||||
<a class="link font-medium" target="_blank" href="<%= Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premises" }.to_query}" %>" data-turbo="false">
|
||||
<%= t('learn_more') %>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "<%= Docuseal.product_name %>",
|
||||
"short_name": "<%= Docuseal.product_name %>",
|
||||
"id": "/",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/logo.svg",
|
||||
"type": "image/svg+xml",
|
||||
"sizes": "any"
|
||||
},
|
||||
{
|
||||
"src": "/apple-touch-icon.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
}
|
||||
],
|
||||
"start_url": "/",
|
||||
"display": "standalone",
|
||||
"scope": "/",
|
||||
"orientation": "natural",
|
||||
"description": "<%= Docuseal.product_name %> is an open source platform that provides secure and efficient digital document signing and processing.",
|
||||
"categories": ["productivity", "utilities"],
|
||||
"theme_color": "#FAF7F4",
|
||||
"background_color": "#FAF7F4"
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
<% 'stats stat stat-figure stat-title stat-value text-accent' %>
|
||||
<% 'stats stat stat-figure stat-title stat-value text-accent w-fit hover:bg-white' %>
|
||||
@@ -2,5 +2,9 @@
|
||||
---
|
||||
</p>
|
||||
<p>
|
||||
<%= t('sent_using_product_name_free_document_signing_html', product_url: "#{Docuseal::PRODUCT_EMAIL_URL}/start", product_name: Docuseal.product_name) %>
|
||||
<% if @current_account&.testing? %>
|
||||
<%= t('sent_using_product_name_in_testing_mode_html', product_url: "#{Docuseal::PRODUCT_EMAIL_URL}/start", product_name: Docuseal.product_name) %>
|
||||
<% else %>
|
||||
<%= t('sent_using_product_name_free_document_signing_html', product_url: "#{Docuseal::PRODUCT_EMAIL_URL}/start", product_name: Docuseal.product_name) %>
|
||||
<% end %>
|
||||
</p>
|
||||
@@ -1,3 +1,4 @@
|
||||
<%= 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">
|
||||
<div class="flex items-center space-x-4">
|
||||
<a href="<%= root_path %>" class="text-2xl font-bold items-center flex space-x-2">
|
||||
@@ -49,6 +50,16 @@
|
||||
<% end %>
|
||||
</li>
|
||||
<% end %>
|
||||
<% if (can?(:manage, EncryptedConfig) && current_user == true_user) || (current_user != true_user && current_account.testing?) %>
|
||||
<%= form_for '', url: testing_account_path, method: current_account.testing? ? :delete : :get, html: { class: 'w-full py-1' } do |f| %>
|
||||
<label class="flex items-center pl-6 pr-4 py-2 border-y border-base-300 -ml-2 -mr-2" for="testing_toggle">
|
||||
<%= f.check_box :testing_toggle, class: 'toggle', checked: current_account.testing?, onchange: 'this.form.requestSubmit()', style: 'height: 0.885rem; width: 1.35rem; --handleoffset: 0.395rem; margin-left: -2px; margin-right: 8px' %>
|
||||
<span class="whitespace-nowrap">
|
||||
<%= t('test_mode') %>
|
||||
</span>
|
||||
</label>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<li>
|
||||
<%= button_to destroy_user_session_path, method: :delete, data: { turbo: false }, class: 'flex items-center' do %>
|
||||
<%= svg_icon('logout', class: 'w-5 h-5 flex-shrink-0 stroke-2 mr-2 inline') %>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<%= link_to Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premise" }.to_query}", class: 'hidden md:inline-flex btn btn-warning btn-sm', data: { prefetch: false } do %>
|
||||
<%= 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: 'hidden md:inline-flex btn btn-warning btn-sm', data: { prefetch: false } do %>
|
||||
<%= t('upgrade') %>
|
||||
<% end %>
|
||||
<% if signed_in? && current_user != true_user %>
|
||||
|
||||
Whitespace-only changes.
Whitespace-only changes.
@@ -64,7 +64,7 @@
|
||||
<% end %>
|
||||
<% if !Docuseal.demo? && can?(:manage, EncryptedConfig) && (current_user != true_user || !current_account.linked_account_account) %>
|
||||
<li>
|
||||
<%= link_to Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premise" }.to_query}", class: 'text-base hover:bg-base-300', data: { prefetch: false } do %>
|
||||
<%= 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: { prefetch: false } do %>
|
||||
<%= t('plans') %>
|
||||
<span class="badge badge-warning"><%= t('new') %></span>
|
||||
<% end %>
|
||||
@@ -72,7 +72,7 @@
|
||||
<% end %>
|
||||
<% if !Docuseal.demo? && can?(:manage, EncryptedConfig) && (current_user != true_user || !current_account.testing?) %>
|
||||
<li>
|
||||
<%= link_to Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}#{'/test' if current_account.testing?}/api") : "#{Docuseal::CONSOLE_URL}/on_premise", class: 'text-base hover:bg-base-300', data: { prefetch: false } do %>
|
||||
<%= link_to Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}#{'/test' if current_account.testing?}/api") : "#{Docuseal::CONSOLE_URL}/on_premises", class: 'text-base hover:bg-base-300', data: { prefetch: false } do %>
|
||||
<% if Docuseal.multitenant? %> API <% else %> <%= t('console') %> <% end %>
|
||||
<% end %>
|
||||
</li>
|
||||
@@ -90,16 +90,16 @@
|
||||
<% end %>
|
||||
<%= render 'shared/settings_nav_extra2' %>
|
||||
<% if (can?(:manage, EncryptedConfig) && current_user == true_user) || (current_user != true_user && current_account.testing?) %>
|
||||
<li>
|
||||
<%= form_for '', url: testing_account_path, method: current_account.testing? ? :delete : :get, html: { class: 'flex w-full' } do |f| %>
|
||||
<%= form_for '', url: testing_account_path, method: current_account.testing? ? :delete : :get, html: { class: 'w-full' } do |f| %>
|
||||
<li>
|
||||
<label class="flex items-center text-base hover:bg-base-300 w-full justify-between" for="testing_toggle">
|
||||
<span class="mr-2 w-full">
|
||||
<%= t('test_environment') %>
|
||||
<%= t('test_mode') %>
|
||||
</span>
|
||||
<%= f.check_box :testing_toggle, class: 'toggle toggle-sm', checked: current_account.testing?, onchange: 'this.form.requestSubmit()' %>
|
||||
</label>
|
||||
<% end %>
|
||||
</li>
|
||||
</li>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</ul>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="hidden md:flex alert py-1 text-sm font-medium gap-x-2 whitespace-nowrap">
|
||||
<a href="<%= testing_api_settings_path %>" data-turbo-frame="modal" class="link font-semibold flex">
|
||||
<%= svg_icon('code_circle', class: 'w-5 h-5 mr-1') %>
|
||||
<span><%= t('testing_environment') %></span>
|
||||
<span><%= t('test_mode') %></span>
|
||||
</a>
|
||||
<span>
|
||||
|
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<%= form_for '', url: testing_account_path, method: current_account.testing? ? :delete : :get, html: { class: 'flex' } do |f| %>
|
||||
<label class="flex items-center justify-between" for="testing_toggle">
|
||||
<span class="mr-2 text-lg">
|
||||
<%= t('test_environment') %>
|
||||
<%= t('test_mode') %>
|
||||
</span>
|
||||
<%= f.check_box :testing_toggle, class: 'toggle', checked: current_account.testing?, onchange: 'this.form.requestSubmit()' %>
|
||||
</label>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<p class="text-gray-700">
|
||||
<%= t('unlock_with_docuseal_pro') %>
|
||||
<br>
|
||||
<a class="link font-medium" target="_blank" href="<%= Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premise" }.to_query}" %>" data-turbo="false">
|
||||
<a class="link font-medium" target="_blank" href="<%= Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premises" }.to_query}" %>" data-turbo="false">
|
||||
<%= t('learn_more') %>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<p class="text-gray-700">
|
||||
<%= t('unlock_with_docuseal_pro') %>
|
||||
<br>
|
||||
<a class="link font-medium" target="_blank" href="<%= Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premise" }.to_query}" %>" data-turbo="false">
|
||||
<a class="link font-medium" target="_blank" href="<%= Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premises" }.to_query}" %>" data-turbo="false">
|
||||
<%= t('learn_more') %>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<p class="text-gray-700">
|
||||
<%= t('unlock_with_docuseal_pro') %>
|
||||
<br>
|
||||
<a class="link font-medium" target="_blank" href="<%= Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premise" }.to_query}" %>" data-turbo="false">
|
||||
<a class="link font-medium" target="_blank" href="<%= Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premises" }.to_query}" %>" data-turbo="false">
|
||||
<%= t('learn_more') %>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<%= form_for '', url: template_submissions_path(template), html: { class: 'space-y-4', autocomplete: 'off' }, data: { turbo_frame: :_top } do |f| %>
|
||||
<% submitters = template.submitters.reject { |e| e['invite_by_uuid'].present? } %>
|
||||
<% submitters = template.submitters.reject { |e| e['invite_by_uuid'].present? || e['optional_invite_by_uuid'].present? } %>
|
||||
<dynamic-list class="space-y-4">
|
||||
<div class="space-y-4">
|
||||
<div class="card card-compact bg-base-300/40" data-targets="dynamic-list.items">
|
||||
@@ -20,7 +20,7 @@
|
||||
<input type="hidden" name="submission[1][submitters][][uuid]" value="<%= item['uuid'] %>">
|
||||
<submitters-autocomplete data-field="name">
|
||||
<linked-input data-target-id="<%= "detailed_name_#{item['linked_to_uuid']}" if item['linked_to_uuid'].present? %>">
|
||||
<%= tag.input type: 'text', name: 'submission[1][submitters][][name]', autocomplete: 'off', class: 'base-input !h-10 w-full', placeholder: t('name'), required: index.zero?, value: (params[:selfsign] && index.zero?) || item['is_requester'] ? current_user.full_name : '', dir: 'auto', id: "detailed_name_#{item['uuid']}" %>
|
||||
<%= tag.input type: 'text', name: 'submission[1][submitters][][name]', autocomplete: 'off', class: 'base-input !h-10 w-full', placeholder: t('name'), required: index.zero?, value: item['email'].present? ? current_account.submitters.accessible_by(current_ability).where.not(name: nil).order(id: :desc).find_by(email: item['email'])&.name : ((params[:selfsign] && index.zero?) || item['is_requester'] ? current_user.full_name : ''), dir: 'auto', id: "detailed_name_#{item['uuid']}" %>
|
||||
</linked-input>
|
||||
</submitters-autocomplete>
|
||||
<div class="grid <%= 'md:grid-cols-2 gap-1' if submitters.size == 1 %>">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<%= form_for '', url: template_submissions_path(template), html: { class: 'space-y-4', autocomplete: 'off' }, data: { turbo_frame: :_top } do |f| %>
|
||||
<% submitters = template.submitters.reject { |e| e['invite_by_uuid'].present? } %>
|
||||
<% submitters = template.submitters.reject { |e| e['invite_by_uuid'].present? || e['optional_invite_by_uuid'].present? } %>
|
||||
<% if submitters.size == 1 %>
|
||||
<submitter-item class="form-control">
|
||||
<emails-textarea data-bulk-enabled="<%= Docuseal.demo? || !Docuseal.multitenant? || can?(:manage, :bulk_send) %>" data-limit="<%= Docuseal.multitenant? ? (can?(:manage, :bulk_send) ? 40 : 1) : nil %>">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<%= form_for '', url: template_submissions_path(template), html: { class: 'space-y-4', autocomplete: 'off' }, data: { turbo_frame: :_top } do |f| %>
|
||||
<% submitters = template.submitters.reject { |e| e['invite_by_uuid'].present? } %>
|
||||
<% submitters = template.submitters.reject { |e| e['invite_by_uuid'].present? || e['optional_invite_by_uuid'].present? } %>
|
||||
<dynamic-list class="space-y-4">
|
||||
<div class="space-y-4">
|
||||
<div class="card card-compact bg-base-300/40" data-targets="dynamic-list.items">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<div class="mt-2 mb-1">
|
||||
<div class="tooltip 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_premise" }.to_query}", class: 'btn btn-sm btn-primary text-gray-400 w-full' %>
|
||||
<%= link_to submitter.sent_at? ? t('re_send_sms') : t('send_sms'), Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premises" }.to_query}", class: 'btn btn-sm btn-primary text-gray-400 w-full' %>
|
||||
</div>
|
||||
</div>
|
||||
@@ -98,6 +98,7 @@
|
||||
<% end %>
|
||||
<% fields_index.dig(document.uuid, index)&.each do |(area, field)| %>
|
||||
<% value = values[field['uuid']] %>
|
||||
<% value ||= field['default_value'] if field['type'] == 'heading' %>
|
||||
<% next if value.blank? %>
|
||||
<%= render 'submissions/value', area:, field:, attachments_index:, value:, locale: @submission.account.locale, timezone: @submission.account.timezone, submitter: submitters_index[field['submitter_uuid']], with_signature_id: %>
|
||||
<% end %>
|
||||
@@ -117,7 +118,7 @@
|
||||
<div class="group border border-base-300 rounded-md px-2 py-1 mb-1">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center space-x-1">
|
||||
<span class="mx-1 w-3 h-3 shrink-0 rounded-full <%= colors[index] %>"></span>
|
||||
<span class="mx-1 w-3 h-3 shrink-0 rounded-full <%= colors[index % 10] %>"></span>
|
||||
<span class="text-lg" dir="auto">
|
||||
<%= (@submission.template_submitters || @submission.template.submitters).find { |e| e['uuid'] == submitter&.uuid }&.dig('name') || "#{(index + 1).ordinalize} Submitter" %>
|
||||
</span>
|
||||
@@ -189,6 +190,11 @@
|
||||
</a>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if signed_in? && submitter && submitter.completed_at? && submitter.email == current_user.email && submitter.completed_at > 1.month.ago && can?(:update, @submission) %>
|
||||
<div class="mt-2 mb-1">
|
||||
<%= button_to t('resubmit'), submitters_resubmit_path(submitter), method: :put, class: 'btn btn-sm btn-primary w-full', form: { target: '_blank' }, data: { turbo: false } %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-1.5 mb-4">
|
||||
@@ -221,6 +227,8 @@
|
||||
</div>
|
||||
<% elsif field['type'] == 'checkbox' %>
|
||||
<%= svg_icon('check', class: 'w-6 h-6') %>
|
||||
<% elsif field['type'] == 'number' %>
|
||||
<%= NumberUtils.format_number(value, field.dig('preferences', 'format')) %>
|
||||
<% elsif field['type'] == 'date' %>
|
||||
<%= TimeUtils.format_date_string(value, field.dig('preferences', 'format'), @submission.account.locale) %>
|
||||
<% else %>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<% data_attachments = attachments_index.values.select { |e| e.record_id == submitter.id }.to_json(only: %i[uuid created_at], methods: %i[url filename content_type]) %>
|
||||
<% data_fields = (submitter.submission.template_fields || submitter.submission.template.fields).select { |f| f['submitter_uuid'] == submitter.uuid }.to_json %>
|
||||
<% data_fields = Submissions.filtered_conditions_fields(submitter).to_json %>
|
||||
<% invite_submitters = (submitter.submission.template_submitters || submitter.submission.template.submitters).select { |s| s['invite_by_uuid'] == submitter.uuid && submitter.submission.submitters.none? { |e| e.uuid == s['uuid'] } }.to_json %>
|
||||
<submission-form data-is-demo="<%= Docuseal.demo? %>" data-schema="<%= schema.to_json %>" data-reuse-signature="<%= configs[:reuse_signature] %>" data-require-signing-reason="<%= configs[:require_signing_reason] %>" data-with-signature-id="<%= configs[:with_signature_id] %>" data-with-confetti="<%= configs[:with_confetti] %>" data-completed-redirect-url="<%= submitter.preferences['completed_redirect_url'].presence || submitter.submission.template.preferences['completed_redirect_url'] %>" data-completed-message="<%= (configs[:completed_message]&.compact_blank.presence || submitter.submission.template.preferences['completed_message'] || {}).to_json %>" data-completed-button="<%= configs[:completed_button].to_json %>" data-go-to-last="<%= submitter.preferences.key?('go_to_last') ? submitter.preferences['go_to_last'] : submitter.opened_at? %>" data-submitter="<%= submitter.to_json(only: %i[uuid slug name phone email]) %>" data-can-send-email="<%= Accounts.can_send_emails?(submitter.submission.account) %>" data-invite-submitters="<%= invite_submitters %>" data-attachments="<%= data_attachments %>" data-fields="<%= data_fields %>" data-values="<%= submitter.values.to_json %>" data-with-typed-signature="<%= configs[:with_typed_signature] %>" data-previous-signature-value="<%= local_assigns[:signature_attachment]&.uuid %>" data-remember-signature="<%= configs[:prefill_signature] %>" data-dry-run="<%= local_assigns[:dry_run] %>" data-expand="<%= local_assigns[:expand] %>" data-scroll-padding="<%= local_assigns[:scroll_padding] %>" data-language="<%= I18n.locale.to_s.split('-').first %>"></submission-form>
|
||||
<% optional_invite_submitters = (submitter.submission.template_submitters || submitter.submission.template.submitters).select { |s| s['optional_invite_by_uuid'] == submitter.uuid && submitter.submission.submitters.none? { |e| e.uuid == s['uuid'] } }.to_json %>
|
||||
<submission-form data-is-demo="<%= Docuseal.demo? %>" data-schema="<%= schema.to_json %>" data-reuse-signature="<%= configs[:reuse_signature] %>" data-require-signing-reason="<%= configs[:require_signing_reason] %>" data-with-signature-id="<%= configs[:with_signature_id] %>" data-with-confetti="<%= configs[:with_confetti] %>" data-completed-redirect-url="<%= submitter.preferences['completed_redirect_url'].presence || submitter.submission.template.preferences['completed_redirect_url'] %>" data-completed-message="<%= (configs[:completed_message]&.compact_blank.presence || submitter.submission.template.preferences['completed_message'] || {}).to_json %>" data-completed-button="<%= configs[:completed_button].to_json %>" data-go-to-last="<%= submitter.preferences.key?('go_to_last') ? submitter.preferences['go_to_last'] : submitter.opened_at? %>" data-submitter="<%= submitter.to_json(only: %i[uuid slug name phone email]) %>" data-can-send-email="<%= Accounts.can_send_emails?(submitter.submission.account) %>" data-optional-invite-submitters="<%= optional_invite_submitters %>" data-invite-submitters="<%= invite_submitters %>" data-attachments="<%= data_attachments %>" data-fields="<%= data_fields %>" data-values="<%= submitter.values.to_json %>" data-with-typed-signature="<%= configs[:with_typed_signature] %>" data-previous-signature-value="<%= local_assigns[:signature_attachment]&.uuid %>" data-remember-signature="<%= configs[:prefill_signature] %>" data-dry-run="<%= local_assigns[:dry_run] %>" data-expand="<%= local_assigns[:expand] %>" data-scroll-padding="<%= local_assigns[:scroll_padding] %>" data-language="<%= I18n.locale.to_s.split('-').first %>"></submission-form>
|
||||
@@ -42,7 +42,8 @@
|
||||
</download-button>
|
||||
<% end %>
|
||||
</div>
|
||||
<% if Templates.filter_undefined_submitters(@submitter.submission.template).size == 1 && %w[api embed].exclude?(@submitter.submission.source) && @submitter.account.account_configs.find_or_initialize_by(key: AccountConfig::ALLOW_TO_RESUBMIT).value != false && !@submitter.template.archived_at? %>
|
||||
<% undefined_submitters = Templates.filter_undefined_submitters(@submitter.submission.template) %>
|
||||
<% if undefined_submitters.size == 1 && undefined_submitters.first['uuid'] == @submitter.uuid && %w[api embed].exclude?(@submitter.submission.source) && @submitter.account.account_configs.find_or_initialize_by(key: AccountConfig::ALLOW_TO_RESUBMIT).value != false && !@submitter.template.archived_at? %>
|
||||
<div class="divider uppercase"><%= t('or') %></div>
|
||||
<toggle-submit class="block">
|
||||
<%= button_to button_title(title: t('resubmit'), disabled_with: t('resubmit'), icon: svg_icon('reload', class: 'w-6 h-6')), start_form_path(@submitter.submission.template.slug), params: { submitter: { email: @submitter.email, phone: @submitter.phone, name: @submitter.name }, resubmit: @submitter.slug }, method: :put, class: 'white-button w-full' %>
|
||||
|
||||
Loaded 100 of 142 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user