mirror of
https://github.com/docusealco/docuseal
synced 2026-11-04 14:42:32 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dd18c51384 | ||
|
|
c2014a1549 | ||
|
|
46edc7dc37 | ||
|
|
d62d83ef8e | ||
|
|
79350c8fba | ||
|
|
6702dd52b6 | ||
|
|
c8b74a2c43 | ||
|
|
1746920eb5 | ||
|
|
bacbc4309c | ||
|
|
4429d742b3 | ||
|
|
38b5eccb65 | ||
|
|
d8126c4a47 | ||
|
|
b1c31672ee | ||
|
|
975f3a95fe | ||
|
|
e3bed5ae67 | ||
|
|
4737b103c0 | ||
|
|
c5178b96d3 | ||
|
|
39a2159196 | ||
|
|
5724159b7b | ||
|
|
05b7cc9fa6 | ||
|
|
18b13b5b1e | ||
|
|
d25555b9b0 | ||
|
|
eec266ee83 | ||
|
|
75683631f0 | ||
|
|
36c64d1de4 | ||
|
|
2552b24f21 | ||
|
|
e6ca6dfd85 | ||
|
|
fa2d86b20a | ||
|
|
8e212e4586 | ||
|
|
a59c18f04b | ||
|
|
1ab0f1c90c | ||
|
|
9beaaa0851 | ||
|
|
b86aac86d1 | ||
|
|
c2eb94b571 | ||
|
|
a377e45a63 | ||
|
|
58574abc6b | ||
|
|
cbae888971 | ||
|
|
944e1f31db | ||
|
|
711b3b3190 | ||
|
|
e7abadcefb | ||
|
|
18d60f5522 | ||
|
|
6d5c6e086d | ||
|
|
5a48ab0360 | ||
|
|
599785b6d1 | ||
|
|
f2fd5fb82b | ||
|
|
f2782bd84e | ||
|
|
e52a721fc9 | ||
|
|
b035123ff9 | ||
|
|
ca8de23082 | ||
|
|
f514654c40 | ||
|
|
99bc528a6c | ||
|
|
f3c77d0177 | ||
|
|
db2b5f35bd | ||
|
|
5728381725 | ||
|
|
a7affb1a49 | ||
|
|
21bf5ed9f7 | ||
|
|
8ddd4ee916 | ||
|
|
0e2fd8ee4f | ||
|
|
27901063ed | ||
|
|
ce31ebde1e | ||
|
|
0c2abf211e | ||
|
|
8ecaa4fd7a | ||
|
|
6759c26d86 | ||
|
|
76f1f140ac | ||
|
|
ca7ab5aab7 | ||
|
|
fd3530ac62 | ||
|
|
4ea863592b | ||
|
|
3f87d0336d | ||
|
|
e251482481 | ||
|
|
c79fca75f3 | ||
|
|
0b0f0b497e | ||
|
|
e9034d8da7 | ||
|
|
8fbb8b8f6e | ||
|
|
4635fdd32b | ||
|
|
6dd4382446 | ||
|
|
50acff2e81 | ||
|
|
cb6d24dfca | ||
|
|
ab4a22d8ba | ||
|
|
bc40a15de1 | ||
|
|
2542f26d96 | ||
|
|
84edb6dbda | ||
|
|
88776e7316 | ||
|
|
f669045362 | ||
|
|
68d3efbdba | ||
|
|
22d4833496 | ||
|
|
ff14caa9f7 | ||
|
|
05647135bc | ||
|
|
ae76c2a4a8 | ||
|
|
fb2b3d44fe | ||
|
|
3868557efd | ||
|
|
9bef28c8ba | ||
|
|
f25fd3b03d | ||
|
|
d1d7571ee4 | ||
|
|
6a298a068c | ||
|
|
d4aef79b68 | ||
|
|
05d578d88f | ||
|
|
8773b30b10 | ||
|
|
1b636c5878 | ||
|
|
46e948ab99 | ||
|
|
889103aa5e | ||
|
|
558a14a5f2 | ||
|
|
72bbdb38bf | ||
|
|
3975a03cb4 | ||
|
|
e398327fb7 | ||
|
|
9266694d65 | ||
|
|
350c4b1c2a | ||
|
|
c3a933d87d | ||
|
|
970e53a5a3 | ||
|
|
520bac2ec7 | ||
|
|
e1eddd31c4 | ||
|
|
3c616a824c | ||
|
|
90d86b58c2 | ||
|
|
4332b4ebb8 | ||
|
|
e2f930f6f7 | ||
|
|
c303159c63 | ||
|
|
c1ce2bec00 | ||
|
|
3d2046f227 | ||
|
|
babaf1aa48 | ||
|
|
3f03d343ca | ||
|
|
110cdb5123 | ||
|
|
440d1114be | ||
|
|
9032517d4e | ||
|
|
7c71fefc47 | ||
|
|
e8bd8ff76f | ||
|
|
fed420cae1 | ||
|
|
910490528d | ||
|
|
ab810be250 | ||
|
|
178ee47093 | ||
|
|
6f90147a11 | ||
|
|
3ebaacbb7a | ||
|
|
64b39b53a2 | ||
|
|
2948910c25 | ||
|
|
1d20c79cf4 | ||
|
|
48f3b13f3e | ||
|
|
1fc1e9af16 | ||
|
|
ce70016603 | ||
|
|
e0e79e343d | ||
|
|
6568f81ee9 | ||
|
|
386f130d7a | ||
|
|
3ee41975fd | ||
|
|
d9f314898b | ||
|
|
4fc7412cac | ||
|
|
9adbfc52c2 | ||
|
|
1ad8d9196a | ||
|
|
e9172392b5 | ||
|
|
d6d94de328 | ||
|
|
77261f457c | ||
|
|
c258915031 | ||
|
|
c1db8bdd3b | ||
|
|
c700484ce8 | ||
|
|
b62bd200ed | ||
|
|
a164fe8dd8 | ||
|
|
bb40e872b3 | ||
|
|
bab80e9488 | ||
|
|
d090d79ebf | ||
|
|
2299ba4e29 | ||
|
|
d6474f0e6e | ||
|
|
e0a297cf81 | ||
|
|
40f5fa4217 | ||
|
|
94e552d170 | ||
|
|
cf1b0a4c16 | ||
|
|
9aa0a8ecbf | ||
|
|
fd4bef1706 | ||
|
|
d32b0e6d20 | ||
|
|
4a846be214 | ||
|
|
3dd3877ea9 | ||
|
|
b4dba0d809 | ||
|
|
dea80a82d8 | ||
|
|
eefa994724 | ||
|
|
35cce24539 | ||
|
|
e6c8203959 | ||
|
|
9479771fc8 | ||
|
|
711427521c | ||
|
|
5dae742361 | ||
|
|
d8ccd4beee | ||
|
|
c532a0115d | ||
|
|
c9c3364764 | ||
|
|
1ff23d7c40 | ||
|
|
95e9b34033 | ||
|
|
6b42dcf91b | ||
|
|
28962c949b | ||
|
|
4ecbbf8410 | ||
|
|
990b35be18 | ||
|
|
8002764855 | ||
|
|
b80982658c | ||
|
|
443ac23c29 | ||
|
|
37bb987e3e | ||
|
|
c7b08e8d30 | ||
|
|
dd502e8cef | ||
|
|
a554182e57 | ||
|
|
0c039ee64f | ||
|
|
c6b004f107 | ||
|
|
2fcb08d27d | ||
|
|
75fe5ce381 | ||
|
|
fd7d0eaf53 | ||
|
|
7e537e4232 | ||
|
|
a892975010 | ||
|
|
05ec92186b | ||
|
|
8b035f80b3 | ||
|
|
c717293306 | ||
|
|
b3f3cf2aa6 | ||
|
|
7406334f77 | ||
|
|
3e1350a221 | ||
|
|
961e0a1c89 | ||
|
|
2ce9bbecb9 | ||
|
|
704da3a2ec | ||
|
|
2f530ccfca | ||
|
|
e6c20f5ff6 | ||
|
|
d267978437 | ||
|
|
e9d728fa4a | ||
|
|
3c60fb4ee6 | ||
|
|
f1e494b669 | ||
|
|
f7a8e4102b | ||
|
|
311c2b0e34 | ||
|
|
5dd59f4f7c | ||
|
|
571f5d8221 | ||
|
|
136b0eb5ed | ||
|
|
2d813e184b | ||
|
|
fd23674e87 | ||
|
|
3b9d35acf1 | ||
|
|
17fc6e2f1c | ||
|
|
2e132d8b5a | ||
|
|
53bef85a63 | ||
|
|
99ad6e36ac | ||
|
|
bfa41ac35f | ||
|
|
bc4414a368 | ||
|
|
26ae5f1d80 | ||
|
|
be4962a02a | ||
|
|
e72090c2e0 | ||
|
|
ad8bca8bf4 | ||
|
|
22be3b37cf | ||
|
|
cfc32ee522 | ||
|
|
10fbdf6612 | ||
|
|
3604fb6461 | ||
|
|
24392d0258 | ||
|
|
4ab1065773 | ||
|
|
50b0f7e5ed | ||
|
|
be4a0505bc | ||
|
|
bd853c6265 | ||
|
|
5a1efd0e27 | ||
|
|
71b8cc7172 | ||
|
|
126a36dbf3 | ||
|
|
90db64cc03 | ||
|
|
227a067c77 | ||
|
|
dc24df0d4c | ||
|
|
2598962c5b | ||
|
|
b4884e94f6 | ||
|
|
4741e64178 | ||
|
|
25fe35e212 | ||
|
|
92909cea09 | ||
|
|
b16322dd5b | ||
|
|
a04c04141c | ||
|
|
90f21d3ed9 | ||
|
|
f31225e8c2 | ||
|
|
eead1b2bc4 | ||
|
|
f90a4e5576 | ||
|
|
50987ce38b | ||
|
|
26113ac3a6 | ||
|
|
0b7b72ce16 | ||
|
|
95d48b2808 | ||
|
|
8fecf94a28 | ||
|
|
66dd87b290 | ||
|
|
d20e8cc37d | ||
|
|
c189e2ed20 | ||
|
|
57f8f45320 | ||
|
|
5684baf1e0 | ||
|
|
b6f307690d | ||
|
|
66c44f8ff5 | ||
|
|
8f3592c481 | ||
|
|
dc8b1e0741 | ||
|
|
b806a6afc5 | ||
|
|
d43f8f4dd8 | ||
|
|
37c9bf5741 | ||
|
|
a0d37f516f | ||
|
|
2764a18252 | ||
|
|
96cf228e74 | ||
|
|
ff6ff3a51e | ||
|
|
9472a6b5b0 | ||
|
|
5f3a8bf391 | ||
|
|
a0a0fdd1b5 | ||
|
|
7a6f901b09 | ||
|
|
c4f05fbd9e | ||
|
|
50c342ec5c | ||
|
|
a9cfd2bcfc | ||
|
|
e2bdb2acee | ||
|
|
b8828b503c | ||
|
|
d63f62109b | ||
|
|
c04483f0ea | ||
|
|
1efaf30fb7 | ||
|
|
2e462e9633 | ||
|
|
838df5f41d | ||
|
|
32445f0e19 | ||
|
|
5f9067baee | ||
|
|
ad56d5ee14 | ||
|
|
a091d121d9 | ||
|
|
cd9c3ee3c1 | ||
|
|
534c3a2e10 | ||
|
|
7bc8e194df | ||
|
|
de5c4d5bcd | ||
|
|
1e6b5c684f | ||
|
|
4651f6b652 | ||
|
|
e5b0a2355f | ||
|
|
db22fe7518 | ||
|
|
0dcd97f49b | ||
|
|
de14330449 | ||
|
|
173658138c | ||
|
|
000f3c72fc | ||
|
|
561b16e9fb | ||
|
|
507ca49112 | ||
|
|
94d5ab4ace | ||
|
|
91d44a3e31 | ||
|
|
478167ea4f | ||
|
|
6b1c3bba19 | ||
|
|
73841f751c | ||
|
|
22dbe3291f | ||
|
|
1dac3fe434 | ||
|
|
d0c49ac04c | ||
|
|
3877dbf83f | ||
|
|
d15fd1fb84 | ||
|
|
0a105490fe | ||
|
|
0bd35b526c | ||
|
|
3a2910f717 | ||
|
|
c4a91c2b34 | ||
|
|
5643380916 | ||
|
|
cf6b378ccb | ||
|
|
66bc8cfb5f | ||
|
|
994bd1fe5c | ||
|
|
12528b6527 | ||
|
|
d135c93939 | ||
|
|
637011d55a | ||
|
|
b94e6bf54a | ||
|
|
8f975c2898 | ||
|
|
d25c4fa766 | ||
|
|
43db15b350 | ||
|
|
09074e2ac0 | ||
|
|
04295f4540 | ||
|
|
11c48a495f | ||
|
|
942c961cd1 | ||
|
|
9193511d5f | ||
|
|
e0ddf97771 | ||
|
|
2bd249bd5a | ||
|
|
b72c99dbce | ||
|
|
fc0504f91e | ||
|
|
1da2196b0e | ||
|
|
4bdb249a2b | ||
|
|
cb38549480 | ||
|
|
ee9ee6ee32 | ||
|
|
748eaaa98d | ||
|
|
9e4ec11e33 | ||
|
|
93d2b76eae | ||
|
|
2c8942e888 | ||
|
|
4424840c81 | ||
|
|
68392b4e74 | ||
|
|
0b4e345ad6 | ||
|
|
1cea5f5a99 | ||
|
|
cdaadbab20 | ||
|
|
d1ec8c6837 | ||
|
|
cdffa66db1 | ||
|
|
fca21ab476 | ||
|
|
b1d2d92f54 | ||
|
|
10bb9112c7 | ||
|
|
58b337a3ee | ||
|
|
de52f2f5e5 | ||
|
|
f10e941529 | ||
|
|
78b1864d7d | ||
|
|
ab49af9bcb | ||
|
|
286b8d7067 | ||
|
|
21c087cba7 | ||
|
|
ddd881a52f | ||
|
|
12c69578af | ||
|
|
e96cb5eb8a | ||
|
|
c01cfe83be | ||
|
|
b92d1ee38c | ||
|
|
b90c67b184 | ||
|
|
edb29977dd | ||
|
|
e55c8f251d | ||
|
|
f1d6bbdc67 | ||
|
|
8da4c2fa10 | ||
|
|
e872fa2497 | ||
|
|
165bfef9b9 | ||
|
|
8a50c755d2 | ||
|
|
002bc315f6 | ||
|
|
e87522eeab | ||
|
|
377244f948 | ||
|
|
73423a6f44 | ||
|
|
d219e3cd7e | ||
|
|
09b0022195 | ||
|
|
8aac935641 | ||
|
|
16e5c13633 | ||
|
|
fff1831768 | ||
|
|
f91a318e94 | ||
|
|
ed06e17b23 | ||
|
|
63e1be7dfe | ||
|
|
16044612bd | ||
|
|
27d8880414 | ||
|
|
a0aae6a9de | ||
|
|
749043dfbb | ||
|
|
320fe02e35 | ||
|
|
6f4b9d4a4e | ||
|
|
a589cf9471 | ||
|
|
7fc7283191 | ||
|
|
af03560494 | ||
|
|
988d1b93a5 | ||
|
|
ab0278198a | ||
|
|
fbc649ab59 | ||
|
|
6b5adc2504 | ||
|
|
7c4307ef10 | ||
|
|
de744e2f6c | ||
|
|
c171597c7f |
No files matched your search
@@ -11,7 +11,7 @@ jobs:
|
|||||||
- name: Install Ruby
|
- name: Install Ruby
|
||||||
uses: ruby/setup-ruby@v1
|
uses: ruby/setup-ruby@v1
|
||||||
with:
|
with:
|
||||||
ruby-version: 3.3.1
|
ruby-version: 3.3.4
|
||||||
- name: Cache gems
|
- name: Cache gems
|
||||||
uses: actions/cache@v1
|
uses: actions/cache@v1
|
||||||
with:
|
with:
|
||||||
@@ -35,7 +35,7 @@ jobs:
|
|||||||
- name: Install Ruby
|
- name: Install Ruby
|
||||||
uses: ruby/setup-ruby@v1
|
uses: ruby/setup-ruby@v1
|
||||||
with:
|
with:
|
||||||
ruby-version: 3.3.1
|
ruby-version: 3.3.4
|
||||||
- name: Cache gems
|
- name: Cache gems
|
||||||
uses: actions/cache@v1
|
uses: actions/cache@v1
|
||||||
with:
|
with:
|
||||||
@@ -100,13 +100,15 @@ jobs:
|
|||||||
- name: Install Ruby
|
- name: Install Ruby
|
||||||
uses: ruby/setup-ruby@v1
|
uses: ruby/setup-ruby@v1
|
||||||
with:
|
with:
|
||||||
ruby-version: 3.3.1
|
ruby-version: 3.3.4
|
||||||
- name: Set up Node
|
- name: Set up Node
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: 20.9.0
|
node-version: 20.9.0
|
||||||
- name: Install Chrome
|
- name: Install Chrome
|
||||||
uses: browser-actions/setup-chrome@latest
|
uses: browser-actions/setup-chrome@latest
|
||||||
|
with:
|
||||||
|
chrome-version: 125
|
||||||
- name: Cache node_modules
|
- name: Cache node_modules
|
||||||
uses: actions/cache@v1
|
uses: actions/cache@v1
|
||||||
with:
|
with:
|
||||||
|
|||||||
+12
-8
@@ -11,6 +11,7 @@ AllCops:
|
|||||||
- bin/*
|
- bin/*
|
||||||
- vendor/**/*
|
- vendor/**/*
|
||||||
TargetRubyVersion: '3.3'
|
TargetRubyVersion: '3.3'
|
||||||
|
SuggestExtensions: false
|
||||||
|
|
||||||
Metrics/BlockLength:
|
Metrics/BlockLength:
|
||||||
Exclude:
|
Exclude:
|
||||||
@@ -33,6 +34,7 @@ Metrics/MethodLength:
|
|||||||
Max: 30
|
Max: 30
|
||||||
Exclude:
|
Exclude:
|
||||||
- 'db/migrate/**'
|
- 'db/migrate/**'
|
||||||
|
- 'spec/**/*'
|
||||||
|
|
||||||
Metrics/CyclomaticComplexity:
|
Metrics/CyclomaticComplexity:
|
||||||
Max: 15
|
Max: 15
|
||||||
@@ -45,24 +47,29 @@ Layout/LineLength:
|
|||||||
|
|
||||||
Metrics/AbcSize:
|
Metrics/AbcSize:
|
||||||
Max: 45
|
Max: 45
|
||||||
|
Exclude:
|
||||||
|
- spec/**/*
|
||||||
|
|
||||||
Metrics/ModuleLength:
|
Metrics/ModuleLength:
|
||||||
Max: 500
|
Max: 1000
|
||||||
|
|
||||||
Metrics/ClassLength:
|
Metrics/ClassLength:
|
||||||
Max: 500
|
Max: 1000
|
||||||
|
|
||||||
RSpec/NestedGroups:
|
RSpec/NestedGroups:
|
||||||
Max: 6
|
Max: 6
|
||||||
|
|
||||||
RSpec/MultipleExpectations:
|
RSpec/MultipleExpectations:
|
||||||
Max: 20
|
Max: 25
|
||||||
|
|
||||||
RSpec/ExampleLength:
|
RSpec/ExampleLength:
|
||||||
Max: 40
|
Max: 50
|
||||||
|
|
||||||
RSpec/MultipleMemoizedHelpers:
|
RSpec/MultipleMemoizedHelpers:
|
||||||
Max: 6
|
Max: 9
|
||||||
|
|
||||||
|
Metrics/BlockNesting:
|
||||||
|
Max: 4
|
||||||
|
|
||||||
Rails/I18nLocaleTexts:
|
Rails/I18nLocaleTexts:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
@@ -78,6 +85,3 @@ Rails/ApplicationController:
|
|||||||
|
|
||||||
Rails/Output:
|
Rails/Output:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
Capybara/ClickLinkOrButtonStyle:
|
|
||||||
Enabled: false
|
|
||||||
+6
-6
@@ -1,12 +1,12 @@
|
|||||||
FROM ruby:3.3.1-alpine as fonts
|
FROM ruby:3.3.4-alpine as fonts
|
||||||
|
|
||||||
WORKDIR /fonts
|
WORKDIR /fonts
|
||||||
|
|
||||||
RUN apk --no-cache add fontforge wget && wget https://github.com/satbyy/go-noto-universal/releases/download/v7.0/GoNotoKurrent-Regular.ttf && wget https://github.com/satbyy/go-noto-universal/releases/download/v7.0/GoNotoKurrent-Bold.ttf && wget https://github.com/impallari/DancingScript/raw/master/fonts/v2031/DancingScript-Regular.otf && wget https://cdn.jsdelivr.net/gh/notofonts/notofonts.github.io/fonts/NotoSansSymbols2/hinted/ttf/NotoSansSymbols2-Regular.ttf && wget https://github.com/Maxattax97/gnu-freefont/raw/master/ttf/FreeSans.ttf && wget https://github.com/impallari/DancingScript/raw/master/OFL.txt
|
RUN apk --no-cache add fontforge wget && wget https://github.com/satbyy/go-noto-universal/releases/download/v7.0/GoNotoKurrent-Regular.ttf && wget https://github.com/satbyy/go-noto-universal/releases/download/v7.0/GoNotoKurrent-Bold.ttf && wget https://github.com/impallari/DancingScript/raw/master/fonts/DancingScript-Regular.otf && wget https://cdn.jsdelivr.net/gh/notofonts/notofonts.github.io/fonts/NotoSansSymbols2/hinted/ttf/NotoSansSymbols2-Regular.ttf && wget https://github.com/Maxattax97/gnu-freefont/raw/master/ttf/FreeSans.ttf && wget https://github.com/impallari/DancingScript/raw/master/OFL.txt
|
||||||
|
|
||||||
RUN fontforge -lang=py -c 'font1 = fontforge.open("FreeSans.ttf"); font2 = fontforge.open("NotoSansSymbols2-Regular.ttf"); font1.mergeFonts(font2); font1.generate("FreeSans.ttf")'
|
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.1-alpine as webpack
|
FROM ruby:3.3.4-alpine as webpack
|
||||||
|
|
||||||
ENV RAILS_ENV=production
|
ENV RAILS_ENV=production
|
||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
@@ -32,7 +32,7 @@ COPY ./app/views ./app/views
|
|||||||
|
|
||||||
RUN echo "gem 'shakapacker'" > Gemfile && ./bin/shakapacker
|
RUN echo "gem 'shakapacker'" > Gemfile && ./bin/shakapacker
|
||||||
|
|
||||||
FROM ruby:3.3.1-alpine as app
|
FROM ruby:3.3.4-alpine as app
|
||||||
|
|
||||||
ENV RAILS_ENV=production
|
ENV RAILS_ENV=production
|
||||||
ENV BUNDLE_WITHOUT="development:test"
|
ENV BUNDLE_WITHOUT="development:test"
|
||||||
@@ -41,7 +41,7 @@ ENV OPENSSL_CONF=/app/openssl_legacy.cnf
|
|||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
RUN apk add --no-cache sqlite-dev libpq-dev mariadb-dev vips-dev vips-poppler poppler-utils redis vips-heif gcompat ttf-freefont && mkdir /fonts && rm /usr/share/fonts/freefont/FreeSans.otf
|
RUN echo '@edge https://dl-cdn.alpinelinux.org/alpine/edge/community' >> /etc/apk/repositories && apk add --no-cache sqlite-dev libpq-dev mariadb-dev vips-dev vips-poppler poppler-utils redis libheif@edge vips-heif gcompat ttf-freefont && mkdir /fonts && rm /usr/share/fonts/freefont/FreeSans.otf
|
||||||
|
|
||||||
RUN echo $'.include = /etc/ssl/openssl.cnf\n\
|
RUN echo $'.include = /etc/ssl/openssl.cnf\n\
|
||||||
\n\
|
\n\
|
||||||
@@ -57,7 +57,7 @@ activate = 1' >> /app/openssl_legacy.cnf
|
|||||||
|
|
||||||
COPY ./Gemfile ./Gemfile.lock ./
|
COPY ./Gemfile ./Gemfile.lock ./
|
||||||
|
|
||||||
RUN apk add --no-cache build-base && bundle install && apk del build-base && rm -rf ~/.bundle /usr/local/bundle/cache && ruby -e "puts Dir['/usr/local/bundle/**/{spec,rdoc,resources/shared,resources/collation,resources/locales}']" | xargs rm -rf
|
RUN apk add --no-cache build-base && bundle install && apk del --no-cache build-base && rm -rf ~/.bundle /usr/local/bundle/cache && ruby -e "puts Dir['/usr/local/bundle/**/{spec,rdoc,resources/shared,resources/collation,resources/locales}']" | xargs rm -rf
|
||||||
|
|
||||||
COPY ./bin ./bin
|
COPY ./bin ./bin
|
||||||
COPY ./app ./app
|
COPY ./app ./app
|
||||||
|
|||||||
@@ -2,10 +2,11 @@
|
|||||||
|
|
||||||
source 'https://rubygems.org'
|
source 'https://rubygems.org'
|
||||||
|
|
||||||
ruby '3.3.1'
|
ruby '3.3.4'
|
||||||
|
|
||||||
gem 'arabic-letter-connector', require: 'arabic-letter-connector/logic'
|
gem 'arabic-letter-connector', require: 'arabic-letter-connector/logic'
|
||||||
gem 'aws-sdk-s3', require: false
|
gem 'aws-sdk-s3', require: false
|
||||||
|
gem 'aws-sdk-secretsmanager', require: false
|
||||||
gem 'azure-storage-blob', require: false
|
gem 'azure-storage-blob', require: false
|
||||||
gem 'bootsnap', require: false
|
gem 'bootsnap', require: false
|
||||||
gem 'cancancan'
|
gem 'cancancan'
|
||||||
@@ -38,7 +39,7 @@ gem 'ruby-vips'
|
|||||||
gem 'rubyXL'
|
gem 'rubyXL'
|
||||||
gem 'shakapacker'
|
gem 'shakapacker'
|
||||||
gem 'sidekiq'
|
gem 'sidekiq'
|
||||||
gem 'sqlite3', require: false, force_ruby_platform: true
|
gem 'sqlite3', require: false
|
||||||
gem 'strip_attributes'
|
gem 'strip_attributes'
|
||||||
gem 'turbo-rails'
|
gem 'turbo-rails'
|
||||||
gem 'twitter_cldr', require: false
|
gem 'twitter_cldr', require: false
|
||||||
|
|||||||
+203
-202
@@ -1,102 +1,101 @@
|
|||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
actioncable (7.1.3.2)
|
actioncable (7.2.1.1)
|
||||||
actionpack (= 7.1.3.2)
|
actionpack (= 7.2.1.1)
|
||||||
activesupport (= 7.1.3.2)
|
activesupport (= 7.2.1.1)
|
||||||
nio4r (~> 2.0)
|
nio4r (~> 2.0)
|
||||||
websocket-driver (>= 0.6.1)
|
websocket-driver (>= 0.6.1)
|
||||||
zeitwerk (~> 2.6)
|
zeitwerk (~> 2.6)
|
||||||
actionmailbox (7.1.3.2)
|
actionmailbox (7.2.1.1)
|
||||||
actionpack (= 7.1.3.2)
|
actionpack (= 7.2.1.1)
|
||||||
activejob (= 7.1.3.2)
|
activejob (= 7.2.1.1)
|
||||||
activerecord (= 7.1.3.2)
|
activerecord (= 7.2.1.1)
|
||||||
activestorage (= 7.1.3.2)
|
activestorage (= 7.2.1.1)
|
||||||
activesupport (= 7.1.3.2)
|
activesupport (= 7.2.1.1)
|
||||||
mail (>= 2.7.1)
|
mail (>= 2.8.0)
|
||||||
net-imap
|
actionmailer (7.2.1.1)
|
||||||
net-pop
|
actionpack (= 7.2.1.1)
|
||||||
net-smtp
|
actionview (= 7.2.1.1)
|
||||||
actionmailer (7.1.3.2)
|
activejob (= 7.2.1.1)
|
||||||
actionpack (= 7.1.3.2)
|
activesupport (= 7.2.1.1)
|
||||||
actionview (= 7.1.3.2)
|
mail (>= 2.8.0)
|
||||||
activejob (= 7.1.3.2)
|
|
||||||
activesupport (= 7.1.3.2)
|
|
||||||
mail (~> 2.5, >= 2.5.4)
|
|
||||||
net-imap
|
|
||||||
net-pop
|
|
||||||
net-smtp
|
|
||||||
rails-dom-testing (~> 2.2)
|
rails-dom-testing (~> 2.2)
|
||||||
actionpack (7.1.3.2)
|
actionpack (7.2.1.1)
|
||||||
actionview (= 7.1.3.2)
|
actionview (= 7.2.1.1)
|
||||||
activesupport (= 7.1.3.2)
|
activesupport (= 7.2.1.1)
|
||||||
nokogiri (>= 1.8.5)
|
nokogiri (>= 1.8.5)
|
||||||
racc
|
racc
|
||||||
rack (>= 2.2.4)
|
rack (>= 2.2.4, < 3.2)
|
||||||
rack-session (>= 1.0.1)
|
rack-session (>= 1.0.1)
|
||||||
rack-test (>= 0.6.3)
|
rack-test (>= 0.6.3)
|
||||||
rails-dom-testing (~> 2.2)
|
rails-dom-testing (~> 2.2)
|
||||||
rails-html-sanitizer (~> 1.6)
|
rails-html-sanitizer (~> 1.6)
|
||||||
actiontext (7.1.3.2)
|
useragent (~> 0.16)
|
||||||
actionpack (= 7.1.3.2)
|
actiontext (7.2.1.1)
|
||||||
activerecord (= 7.1.3.2)
|
actionpack (= 7.2.1.1)
|
||||||
activestorage (= 7.1.3.2)
|
activerecord (= 7.2.1.1)
|
||||||
activesupport (= 7.1.3.2)
|
activestorage (= 7.2.1.1)
|
||||||
|
activesupport (= 7.2.1.1)
|
||||||
globalid (>= 0.6.0)
|
globalid (>= 0.6.0)
|
||||||
nokogiri (>= 1.8.5)
|
nokogiri (>= 1.8.5)
|
||||||
actionview (7.1.3.2)
|
actionview (7.2.1.1)
|
||||||
activesupport (= 7.1.3.2)
|
activesupport (= 7.2.1.1)
|
||||||
builder (~> 3.1)
|
builder (~> 3.1)
|
||||||
erubi (~> 1.11)
|
erubi (~> 1.11)
|
||||||
rails-dom-testing (~> 2.2)
|
rails-dom-testing (~> 2.2)
|
||||||
rails-html-sanitizer (~> 1.6)
|
rails-html-sanitizer (~> 1.6)
|
||||||
activejob (7.1.3.2)
|
activejob (7.2.1.1)
|
||||||
activesupport (= 7.1.3.2)
|
activesupport (= 7.2.1.1)
|
||||||
globalid (>= 0.3.6)
|
globalid (>= 0.3.6)
|
||||||
activemodel (7.1.3.2)
|
activemodel (7.2.1.1)
|
||||||
activesupport (= 7.1.3.2)
|
activesupport (= 7.2.1.1)
|
||||||
activerecord (7.1.3.2)
|
activerecord (7.2.1.1)
|
||||||
activemodel (= 7.1.3.2)
|
activemodel (= 7.2.1.1)
|
||||||
activesupport (= 7.1.3.2)
|
activesupport (= 7.2.1.1)
|
||||||
timeout (>= 0.4.0)
|
timeout (>= 0.4.0)
|
||||||
activestorage (7.1.3.2)
|
activestorage (7.2.1.1)
|
||||||
actionpack (= 7.1.3.2)
|
actionpack (= 7.2.1.1)
|
||||||
activejob (= 7.1.3.2)
|
activejob (= 7.2.1.1)
|
||||||
activerecord (= 7.1.3.2)
|
activerecord (= 7.2.1.1)
|
||||||
activesupport (= 7.1.3.2)
|
activesupport (= 7.2.1.1)
|
||||||
marcel (~> 1.0)
|
marcel (~> 1.0)
|
||||||
activesupport (7.1.3.2)
|
activesupport (7.2.1.1)
|
||||||
base64
|
base64
|
||||||
bigdecimal
|
bigdecimal
|
||||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
concurrent-ruby (~> 1.0, >= 1.3.1)
|
||||||
connection_pool (>= 2.2.5)
|
connection_pool (>= 2.2.5)
|
||||||
drb
|
drb
|
||||||
i18n (>= 1.6, < 2)
|
i18n (>= 1.6, < 2)
|
||||||
|
logger (>= 1.4.2)
|
||||||
minitest (>= 5.1)
|
minitest (>= 5.1)
|
||||||
mutex_m
|
securerandom (>= 0.3)
|
||||||
tzinfo (~> 2.0)
|
tzinfo (~> 2.0, >= 2.0.5)
|
||||||
addressable (2.8.6)
|
addressable (2.8.7)
|
||||||
public_suffix (>= 2.0.2, < 6.0)
|
public_suffix (>= 2.0.2, < 7.0)
|
||||||
annotate (3.2.0)
|
annotate (3.2.0)
|
||||||
activerecord (>= 3.2, < 8.0)
|
activerecord (>= 3.2, < 8.0)
|
||||||
rake (>= 10.4, < 14.0)
|
rake (>= 10.4, < 14.0)
|
||||||
arabic-letter-connector (0.1.1)
|
arabic-letter-connector (0.1.1)
|
||||||
ast (2.4.2)
|
ast (2.4.2)
|
||||||
aws-eventstream (1.3.0)
|
aws-eventstream (1.3.0)
|
||||||
aws-partitions (1.913.0)
|
aws-partitions (1.985.0)
|
||||||
aws-sdk-core (3.191.6)
|
aws-sdk-core (3.209.1)
|
||||||
aws-eventstream (~> 1, >= 1.3.0)
|
aws-eventstream (~> 1, >= 1.3.0)
|
||||||
aws-partitions (~> 1, >= 1.651.0)
|
aws-partitions (~> 1, >= 1.651.0)
|
||||||
aws-sigv4 (~> 1.8)
|
aws-sigv4 (~> 1.9)
|
||||||
jmespath (~> 1, >= 1.6.1)
|
jmespath (~> 1, >= 1.6.1)
|
||||||
aws-sdk-kms (1.79.0)
|
aws-sdk-kms (1.94.0)
|
||||||
aws-sdk-core (~> 3, >= 3.191.0)
|
aws-sdk-core (~> 3, >= 3.207.0)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.5)
|
||||||
aws-sdk-s3 (1.146.1)
|
aws-sdk-s3 (1.167.0)
|
||||||
aws-sdk-core (~> 3, >= 3.191.0)
|
aws-sdk-core (~> 3, >= 3.207.0)
|
||||||
aws-sdk-kms (~> 1)
|
aws-sdk-kms (~> 1)
|
||||||
aws-sigv4 (~> 1.8)
|
aws-sigv4 (~> 1.5)
|
||||||
aws-sigv4 (1.8.0)
|
aws-sdk-secretsmanager (1.108.0)
|
||||||
|
aws-sdk-core (~> 3, >= 3.207.0)
|
||||||
|
aws-sigv4 (~> 1.5)
|
||||||
|
aws-sigv4 (1.10.0)
|
||||||
aws-eventstream (~> 1, >= 1.0.2)
|
aws-eventstream (~> 1, >= 1.0.2)
|
||||||
azure-storage-blob (2.0.3)
|
azure-storage-blob (2.0.3)
|
||||||
azure-storage-common (~> 2.0)
|
azure-storage-common (~> 2.0)
|
||||||
@@ -117,14 +116,14 @@ GEM
|
|||||||
smart_properties
|
smart_properties
|
||||||
bigdecimal (3.1.8)
|
bigdecimal (3.1.8)
|
||||||
bindex (0.8.1)
|
bindex (0.8.1)
|
||||||
bootsnap (1.18.3)
|
bootsnap (1.18.4)
|
||||||
msgpack (~> 1.2)
|
msgpack (~> 1.2)
|
||||||
builder (3.2.4)
|
builder (3.3.0)
|
||||||
bullet (7.1.6)
|
bullet (7.2.0)
|
||||||
activesupport (>= 3.0.0)
|
activesupport (>= 3.0.0)
|
||||||
uniform_notifier (~> 1.11)
|
uniform_notifier (~> 1.11)
|
||||||
camertron-eprun (1.1.1)
|
camertron-eprun (1.1.1)
|
||||||
cancancan (3.5.0)
|
cancancan (3.6.1)
|
||||||
capybara (3.40.0)
|
capybara (3.40.0)
|
||||||
addressable
|
addressable
|
||||||
matrix
|
matrix
|
||||||
@@ -134,23 +133,24 @@ GEM
|
|||||||
rack-test (>= 0.6.3)
|
rack-test (>= 0.6.3)
|
||||||
regexp_parser (>= 1.5, < 3.0)
|
regexp_parser (>= 1.5, < 3.0)
|
||||||
xpath (~> 3.2)
|
xpath (~> 3.2)
|
||||||
childprocess (5.0.0)
|
childprocess (5.1.0)
|
||||||
|
logger (~> 1.5)
|
||||||
chunky_png (1.4.0)
|
chunky_png (1.4.0)
|
||||||
cldr-plurals-runtime-rb (1.1.0)
|
cldr-plurals-runtime-rb (1.1.0)
|
||||||
cmdparse (3.0.7)
|
cmdparse (3.0.7)
|
||||||
coderay (1.1.3)
|
coderay (1.1.3)
|
||||||
concurrent-ruby (1.2.3)
|
concurrent-ruby (1.3.4)
|
||||||
connection_pool (2.4.1)
|
connection_pool (2.4.1)
|
||||||
crack (1.0.0)
|
crack (1.0.0)
|
||||||
bigdecimal
|
bigdecimal
|
||||||
rexml
|
rexml
|
||||||
crass (1.0.6)
|
crass (1.0.6)
|
||||||
css_parser (1.17.1)
|
css_parser (1.19.0)
|
||||||
addressable
|
addressable
|
||||||
csv (3.3.0)
|
csv (3.3.0)
|
||||||
cuprite (0.15)
|
cuprite (0.15.1)
|
||||||
capybara (~> 3.0)
|
capybara (~> 3.0)
|
||||||
ferrum (~> 0.14.0)
|
ferrum (~> 0.15.0)
|
||||||
date (3.3.4)
|
date (3.3.4)
|
||||||
debug (1.9.2)
|
debug (1.9.2)
|
||||||
irb (~> 1.10)
|
irb (~> 1.10)
|
||||||
@@ -162,7 +162,7 @@ GEM
|
|||||||
railties (>= 4.1.0)
|
railties (>= 4.1.0)
|
||||||
responders
|
responders
|
||||||
warden (~> 1.2.3)
|
warden (~> 1.2.3)
|
||||||
devise-two-factor (5.0.0)
|
devise-two-factor (6.0.0)
|
||||||
activesupport (~> 7.0)
|
activesupport (~> 7.0)
|
||||||
devise (~> 4.0)
|
devise (~> 4.0)
|
||||||
railties (~> 7.0)
|
railties (~> 7.0)
|
||||||
@@ -170,26 +170,26 @@ GEM
|
|||||||
diff-lcs (1.5.1)
|
diff-lcs (1.5.1)
|
||||||
digest-crc (0.6.5)
|
digest-crc (0.6.5)
|
||||||
rake (>= 12.0.0, < 14.0.0)
|
rake (>= 12.0.0, < 14.0.0)
|
||||||
docile (1.4.0)
|
docile (1.4.1)
|
||||||
dotenv (3.1.0)
|
dotenv (3.1.4)
|
||||||
drb (2.2.1)
|
drb (2.2.1)
|
||||||
email_typo (0.2.3)
|
email_typo (0.2.3)
|
||||||
erb_lint (0.5.0)
|
erb_lint (0.6.0)
|
||||||
activesupport
|
activesupport
|
||||||
better_html (>= 2.0.1)
|
better_html (>= 2.0.1)
|
||||||
parser (>= 2.7.1.4)
|
parser (>= 2.7.1.4)
|
||||||
rainbow
|
rainbow
|
||||||
rubocop
|
rubocop (>= 1)
|
||||||
smart_properties
|
smart_properties
|
||||||
erubi (1.12.0)
|
erubi (1.13.0)
|
||||||
factory_bot (6.4.6)
|
factory_bot (6.5.0)
|
||||||
activesupport (>= 5.0.0)
|
activesupport (>= 5.0.0)
|
||||||
factory_bot_rails (6.4.3)
|
factory_bot_rails (6.4.3)
|
||||||
factory_bot (~> 6.4)
|
factory_bot (~> 6.4)
|
||||||
railties (>= 5.0.0)
|
railties (>= 5.0.0)
|
||||||
faker (3.3.1)
|
faker (3.4.2)
|
||||||
i18n (>= 1.8.11, < 2)
|
i18n (>= 1.8.11, < 2)
|
||||||
faraday (1.10.3)
|
faraday (1.10.4)
|
||||||
faraday-em_http (~> 1.0)
|
faraday-em_http (~> 1.0)
|
||||||
faraday-em_synchrony (~> 1.0)
|
faraday-em_synchrony (~> 1.0)
|
||||||
faraday-excon (~> 1.1)
|
faraday-excon (~> 1.1)
|
||||||
@@ -209,93 +209,96 @@ GEM
|
|||||||
faraday-httpclient (1.0.1)
|
faraday-httpclient (1.0.1)
|
||||||
faraday-multipart (1.0.4)
|
faraday-multipart (1.0.4)
|
||||||
multipart-post (~> 2)
|
multipart-post (~> 2)
|
||||||
faraday-net_http (1.0.1)
|
faraday-net_http (1.0.2)
|
||||||
faraday-net_http_persistent (1.2.0)
|
faraday-net_http_persistent (1.2.0)
|
||||||
faraday-patron (1.0.0)
|
faraday-patron (1.0.0)
|
||||||
faraday-rack (1.0.0)
|
faraday-rack (1.0.0)
|
||||||
faraday-retry (1.0.3)
|
faraday-retry (1.0.3)
|
||||||
faraday_middleware (1.2.0)
|
faraday_middleware (1.2.1)
|
||||||
faraday (~> 1.0)
|
faraday (~> 1.0)
|
||||||
ferrum (0.14)
|
ferrum (0.15)
|
||||||
addressable (~> 2.5)
|
addressable (~> 2.5)
|
||||||
concurrent-ruby (~> 1.1)
|
concurrent-ruby (~> 1.1)
|
||||||
webrick (~> 1.7)
|
webrick (~> 1.7)
|
||||||
websocket-driver (>= 0.6, < 0.8)
|
websocket-driver (~> 0.7)
|
||||||
ffi (1.16.3)
|
ffi (1.17.0)
|
||||||
|
ffi (1.17.0-aarch64-linux-gnu)
|
||||||
|
ffi (1.17.0-x86_64-linux-gnu)
|
||||||
geom2d (0.4.1)
|
geom2d (0.4.1)
|
||||||
globalid (1.2.1)
|
globalid (1.2.1)
|
||||||
activesupport (>= 6.1)
|
activesupport (>= 6.1)
|
||||||
google-apis-core (0.14.1)
|
google-apis-core (0.15.1)
|
||||||
addressable (~> 2.5, >= 2.5.1)
|
addressable (~> 2.5, >= 2.5.1)
|
||||||
googleauth (~> 1.9)
|
googleauth (~> 1.9)
|
||||||
httpclient (>= 2.8.1, < 3.a)
|
httpclient (>= 2.8.3, < 3.a)
|
||||||
mini_mime (~> 1.0)
|
mini_mime (~> 1.0)
|
||||||
|
mutex_m
|
||||||
representable (~> 3.0)
|
representable (~> 3.0)
|
||||||
retriable (>= 2.0, < 4.a)
|
retriable (>= 2.0, < 4.a)
|
||||||
rexml
|
google-apis-iamcredentials_v1 (0.21.0)
|
||||||
google-apis-iamcredentials_v1 (0.20.0)
|
google-apis-core (>= 0.15.0, < 2.a)
|
||||||
google-apis-core (>= 0.14.0, < 2.a)
|
google-apis-storage_v1 (0.46.0)
|
||||||
google-apis-storage_v1 (0.37.0)
|
google-apis-core (>= 0.15.0, < 2.a)
|
||||||
google-apis-core (>= 0.14.0, < 2.a)
|
google-cloud-core (1.7.1)
|
||||||
google-cloud-core (1.7.0)
|
|
||||||
google-cloud-env (>= 1.0, < 3.a)
|
google-cloud-env (>= 1.0, < 3.a)
|
||||||
google-cloud-errors (~> 1.0)
|
google-cloud-errors (~> 1.0)
|
||||||
google-cloud-env (2.1.1)
|
google-cloud-env (2.2.1)
|
||||||
faraday (>= 1.0, < 3.a)
|
faraday (>= 1.0, < 3.a)
|
||||||
google-cloud-errors (1.4.0)
|
google-cloud-errors (1.4.0)
|
||||||
google-cloud-storage (1.49.0)
|
google-cloud-storage (1.52.0)
|
||||||
addressable (~> 2.8)
|
addressable (~> 2.8)
|
||||||
digest-crc (~> 0.4)
|
digest-crc (~> 0.4)
|
||||||
google-apis-core (~> 0.13)
|
google-apis-core (~> 0.13)
|
||||||
google-apis-iamcredentials_v1 (~> 0.18)
|
google-apis-iamcredentials_v1 (~> 0.18)
|
||||||
google-apis-storage_v1 (~> 0.33)
|
google-apis-storage_v1 (~> 0.38)
|
||||||
google-cloud-core (~> 1.6)
|
google-cloud-core (~> 1.6)
|
||||||
googleauth (~> 1.9)
|
googleauth (~> 1.9)
|
||||||
mini_mime (~> 1.0)
|
mini_mime (~> 1.0)
|
||||||
googleauth (1.11.0)
|
googleauth (1.11.1)
|
||||||
faraday (>= 1.0, < 3.a)
|
faraday (>= 1.0, < 3.a)
|
||||||
google-cloud-env (~> 2.1)
|
google-cloud-env (~> 2.1)
|
||||||
jwt (>= 1.4, < 3.0)
|
jwt (>= 1.4, < 3.0)
|
||||||
multi_json (~> 1.11)
|
multi_json (~> 1.11)
|
||||||
os (>= 0.9, < 2.0)
|
os (>= 0.9, < 2.0)
|
||||||
signet (>= 0.16, < 2.a)
|
signet (>= 0.16, < 2.a)
|
||||||
hashdiff (1.1.0)
|
hashdiff (1.1.1)
|
||||||
hexapdf (0.42.0)
|
hexapdf (0.47.0)
|
||||||
cmdparse (~> 3.0, >= 3.0.3)
|
cmdparse (~> 3.0, >= 3.0.3)
|
||||||
geom2d (~> 0.4, >= 0.4.1)
|
geom2d (~> 0.4, >= 0.4.1)
|
||||||
openssl (>= 2.2.1)
|
openssl (>= 2.2.1)
|
||||||
htmlentities (4.3.4)
|
htmlentities (4.3.4)
|
||||||
httpclient (2.8.3)
|
httpclient (2.8.3)
|
||||||
i18n (1.14.5)
|
i18n (1.14.6)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
image_processing (1.12.2)
|
image_processing (1.13.0)
|
||||||
mini_magick (>= 4.9.5, < 5)
|
mini_magick (>= 4.9.5, < 5)
|
||||||
ruby-vips (>= 2.0.17, < 3)
|
ruby-vips (>= 2.0.17, < 3)
|
||||||
io-console (0.7.2)
|
io-console (0.7.2)
|
||||||
irb (1.13.1)
|
irb (1.14.1)
|
||||||
rdoc (>= 4.0.0)
|
rdoc (>= 4.0.0)
|
||||||
reline (>= 0.4.2)
|
reline (>= 0.4.2)
|
||||||
jmespath (1.6.2)
|
jmespath (1.6.2)
|
||||||
json (2.7.2)
|
json (2.7.2)
|
||||||
jwt (2.8.1)
|
jwt (2.9.3)
|
||||||
base64
|
base64
|
||||||
language_server-protocol (3.17.0.3)
|
language_server-protocol (3.17.0.3)
|
||||||
launchy (3.0.0)
|
launchy (3.0.1)
|
||||||
addressable (~> 2.8)
|
addressable (~> 2.8)
|
||||||
childprocess (~> 5.0)
|
childprocess (~> 5.0)
|
||||||
letter_opener (1.10.0)
|
letter_opener (1.10.0)
|
||||||
launchy (>= 2.2, < 4)
|
launchy (>= 2.2, < 4)
|
||||||
letter_opener_web (2.0.0)
|
letter_opener_web (3.0.0)
|
||||||
actionmailer (>= 5.2)
|
actionmailer (>= 6.1)
|
||||||
letter_opener (~> 1.7)
|
letter_opener (~> 1.9)
|
||||||
railties (>= 5.2)
|
railties (>= 6.1)
|
||||||
rexml
|
rexml
|
||||||
|
logger (1.6.1)
|
||||||
lograge (0.14.0)
|
lograge (0.14.0)
|
||||||
actionpack (>= 4)
|
actionpack (>= 4)
|
||||||
activesupport (>= 4)
|
activesupport (>= 4)
|
||||||
railties (>= 4)
|
railties (>= 4)
|
||||||
request_store (~> 1.0)
|
request_store (~> 1.0)
|
||||||
loofah (2.22.0)
|
loofah (2.23.1)
|
||||||
crass (~> 1.0.2)
|
crass (~> 1.0.2)
|
||||||
nokogiri (>= 1.12.0)
|
nokogiri (>= 1.12.0)
|
||||||
mail (2.8.1)
|
mail (2.8.1)
|
||||||
@@ -306,49 +309,51 @@ GEM
|
|||||||
marcel (1.0.4)
|
marcel (1.0.4)
|
||||||
matrix (0.4.2)
|
matrix (0.4.2)
|
||||||
method_source (1.1.0)
|
method_source (1.1.0)
|
||||||
mini_magick (4.12.0)
|
mini_magick (4.13.2)
|
||||||
mini_mime (1.1.5)
|
mini_mime (1.1.5)
|
||||||
mini_portile2 (2.8.6)
|
mini_portile2 (2.8.8)
|
||||||
minitest (5.23.0)
|
minitest (5.25.1)
|
||||||
msgpack (1.7.2)
|
msgpack (1.7.3)
|
||||||
multi_json (1.15.0)
|
multi_json (1.15.0)
|
||||||
multipart-post (2.4.0)
|
multipart-post (2.4.1)
|
||||||
mutex_m (0.2.0)
|
mutex_m (0.2.0)
|
||||||
mysql2 (0.5.6)
|
mysql2 (0.5.6)
|
||||||
net-http-persistent (4.0.2)
|
net-http-persistent (4.0.4)
|
||||||
connection_pool (~> 2.2)
|
connection_pool (~> 2.2)
|
||||||
net-imap (0.4.10)
|
net-imap (0.4.17)
|
||||||
date
|
date
|
||||||
net-protocol
|
net-protocol
|
||||||
net-pop (0.1.2)
|
net-pop (0.1.2)
|
||||||
net-protocol
|
net-protocol
|
||||||
net-protocol (0.2.2)
|
net-protocol (0.2.2)
|
||||||
timeout
|
timeout
|
||||||
net-smtp (0.4.0)
|
net-smtp (0.5.0)
|
||||||
net-protocol
|
net-protocol
|
||||||
nio4r (2.7.1)
|
nio4r (2.7.3)
|
||||||
nokogiri (1.16.5)
|
nokogiri (1.16.8)
|
||||||
mini_portile2 (~> 2.8.2)
|
mini_portile2 (~> 2.8.2)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
nokogiri (1.16.5-aarch64-linux)
|
nokogiri (1.16.8-aarch64-linux)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
nokogiri (1.16.5-x86_64-linux)
|
nokogiri (1.16.8-x86_64-linux)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
oj (3.16.3)
|
oj (3.16.6)
|
||||||
bigdecimal (>= 3.0)
|
bigdecimal (>= 3.0)
|
||||||
|
ostruct (>= 0.2)
|
||||||
openssl (3.2.0)
|
openssl (3.2.0)
|
||||||
orm_adapter (0.5.0)
|
orm_adapter (0.5.0)
|
||||||
os (1.1.4)
|
os (1.1.4)
|
||||||
|
ostruct (0.6.0)
|
||||||
package_json (0.1.0)
|
package_json (0.1.0)
|
||||||
pagy (8.1.2)
|
pagy (9.1.0)
|
||||||
parallel (1.24.0)
|
parallel (1.26.3)
|
||||||
parser (3.3.1.0)
|
parser (3.3.5.0)
|
||||||
ast (~> 2.4.1)
|
ast (~> 2.4.1)
|
||||||
racc
|
racc
|
||||||
pg (1.5.6)
|
pg (1.5.8)
|
||||||
premailer (1.23.0)
|
premailer (1.27.0)
|
||||||
addressable
|
addressable
|
||||||
css_parser (>= 1.12.0)
|
css_parser (>= 1.19.0)
|
||||||
htmlentities (>= 4.0.0)
|
htmlentities (>= 4.0.0)
|
||||||
premailer-rails (1.12.0)
|
premailer-rails (1.12.0)
|
||||||
actionmailer (>= 3)
|
actionmailer (>= 3)
|
||||||
@@ -359,15 +364,15 @@ GEM
|
|||||||
pry (0.14.2)
|
pry (0.14.2)
|
||||||
coderay (~> 1.1)
|
coderay (~> 1.1)
|
||||||
method_source (~> 1.0)
|
method_source (~> 1.0)
|
||||||
pry-rails (0.3.9)
|
pry-rails (0.3.11)
|
||||||
pry (>= 0.10.4)
|
pry (>= 0.13.0)
|
||||||
psych (5.1.2)
|
psych (5.1.2)
|
||||||
stringio
|
stringio
|
||||||
public_suffix (5.0.5)
|
public_suffix (6.0.1)
|
||||||
puma (6.4.2)
|
puma (6.4.3)
|
||||||
nio4r (~> 2.0)
|
nio4r (~> 2.0)
|
||||||
racc (1.8.0)
|
racc (1.8.1)
|
||||||
rack (3.0.11)
|
rack (3.1.8)
|
||||||
rack-proxy (0.7.7)
|
rack-proxy (0.7.7)
|
||||||
rack
|
rack
|
||||||
rack-session (2.0.0)
|
rack-session (2.0.0)
|
||||||
@@ -377,27 +382,27 @@ GEM
|
|||||||
rackup (2.1.0)
|
rackup (2.1.0)
|
||||||
rack (>= 3)
|
rack (>= 3)
|
||||||
webrick (~> 1.8)
|
webrick (~> 1.8)
|
||||||
rails (7.1.3.2)
|
rails (7.2.1.1)
|
||||||
actioncable (= 7.1.3.2)
|
actioncable (= 7.2.1.1)
|
||||||
actionmailbox (= 7.1.3.2)
|
actionmailbox (= 7.2.1.1)
|
||||||
actionmailer (= 7.1.3.2)
|
actionmailer (= 7.2.1.1)
|
||||||
actionpack (= 7.1.3.2)
|
actionpack (= 7.2.1.1)
|
||||||
actiontext (= 7.1.3.2)
|
actiontext (= 7.2.1.1)
|
||||||
actionview (= 7.1.3.2)
|
actionview (= 7.2.1.1)
|
||||||
activejob (= 7.1.3.2)
|
activejob (= 7.2.1.1)
|
||||||
activemodel (= 7.1.3.2)
|
activemodel (= 7.2.1.1)
|
||||||
activerecord (= 7.1.3.2)
|
activerecord (= 7.2.1.1)
|
||||||
activestorage (= 7.1.3.2)
|
activestorage (= 7.2.1.1)
|
||||||
activesupport (= 7.1.3.2)
|
activesupport (= 7.2.1.1)
|
||||||
bundler (>= 1.15.0)
|
bundler (>= 1.15.0)
|
||||||
railties (= 7.1.3.2)
|
railties (= 7.2.1.1)
|
||||||
rails-dom-testing (2.2.0)
|
rails-dom-testing (2.2.0)
|
||||||
activesupport (>= 5.0.0)
|
activesupport (>= 5.0.0)
|
||||||
minitest
|
minitest
|
||||||
nokogiri (>= 1.6)
|
nokogiri (>= 1.6)
|
||||||
rails-html-sanitizer (1.6.0)
|
rails-html-sanitizer (1.6.1)
|
||||||
loofah (~> 2.21)
|
loofah (~> 2.21)
|
||||||
nokogiri (~> 1.14)
|
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 (7.0.9)
|
||||||
i18n (>= 0.7, < 2)
|
i18n (>= 0.7, < 2)
|
||||||
railties (>= 6.0.0, < 8)
|
railties (>= 6.0.0, < 8)
|
||||||
@@ -405,108 +410,101 @@ GEM
|
|||||||
actionview (> 3.1)
|
actionview (> 3.1)
|
||||||
activesupport (> 3.1)
|
activesupport (> 3.1)
|
||||||
railties (> 3.1)
|
railties (> 3.1)
|
||||||
railties (7.1.3.2)
|
railties (7.2.1.1)
|
||||||
actionpack (= 7.1.3.2)
|
actionpack (= 7.2.1.1)
|
||||||
activesupport (= 7.1.3.2)
|
activesupport (= 7.2.1.1)
|
||||||
irb
|
irb (~> 1.13)
|
||||||
rackup (>= 1.0.0)
|
rackup (>= 1.0.0)
|
||||||
rake (>= 12.2)
|
rake (>= 12.2)
|
||||||
thor (~> 1.0, >= 1.2.2)
|
thor (~> 1.0, >= 1.2.2)
|
||||||
zeitwerk (~> 2.6)
|
zeitwerk (~> 2.6)
|
||||||
rainbow (3.1.1)
|
rainbow (3.1.1)
|
||||||
rake (13.2.1)
|
rake (13.2.1)
|
||||||
rdoc (6.6.3.1)
|
rdoc (6.7.0)
|
||||||
psych (>= 4.0.0)
|
psych (>= 4.0.0)
|
||||||
redis-client (0.22.0)
|
redis-client (0.22.2)
|
||||||
connection_pool
|
connection_pool
|
||||||
regexp_parser (2.9.0)
|
regexp_parser (2.9.2)
|
||||||
reline (0.5.7)
|
reline (0.5.10)
|
||||||
io-console (~> 0.5)
|
io-console (~> 0.5)
|
||||||
representable (3.2.0)
|
representable (3.2.0)
|
||||||
declarative (< 0.1.0)
|
declarative (< 0.1.0)
|
||||||
trailblazer-option (>= 0.1.1, < 0.2.0)
|
trailblazer-option (>= 0.1.1, < 0.2.0)
|
||||||
uber (< 0.2.0)
|
uber (< 0.2.0)
|
||||||
request_store (1.6.0)
|
request_store (1.7.0)
|
||||||
rack (>= 1.4)
|
rack (>= 1.4)
|
||||||
responders (3.1.1)
|
responders (3.1.1)
|
||||||
actionpack (>= 5.2)
|
actionpack (>= 5.2)
|
||||||
railties (>= 5.2)
|
railties (>= 5.2)
|
||||||
retriable (3.1.2)
|
retriable (3.1.2)
|
||||||
rexml (3.2.6)
|
rexml (3.3.9)
|
||||||
rotp (6.3.0)
|
rotp (6.3.0)
|
||||||
rqrcode (2.2.0)
|
rqrcode (2.2.0)
|
||||||
chunky_png (~> 1.0)
|
chunky_png (~> 1.0)
|
||||||
rqrcode_core (~> 1.0)
|
rqrcode_core (~> 1.0)
|
||||||
rqrcode_core (1.2.0)
|
rqrcode_core (1.2.0)
|
||||||
rspec-core (3.13.0)
|
rspec-core (3.13.1)
|
||||||
rspec-support (~> 3.13.0)
|
rspec-support (~> 3.13.0)
|
||||||
rspec-expectations (3.13.0)
|
rspec-expectations (3.13.3)
|
||||||
diff-lcs (>= 1.2.0, < 2.0)
|
diff-lcs (>= 1.2.0, < 2.0)
|
||||||
rspec-support (~> 3.13.0)
|
rspec-support (~> 3.13.0)
|
||||||
rspec-mocks (3.13.0)
|
rspec-mocks (3.13.2)
|
||||||
diff-lcs (>= 1.2.0, < 2.0)
|
diff-lcs (>= 1.2.0, < 2.0)
|
||||||
rspec-support (~> 3.13.0)
|
rspec-support (~> 3.13.0)
|
||||||
rspec-rails (6.1.2)
|
rspec-rails (7.0.1)
|
||||||
actionpack (>= 6.1)
|
actionpack (>= 7.0)
|
||||||
activesupport (>= 6.1)
|
activesupport (>= 7.0)
|
||||||
railties (>= 6.1)
|
railties (>= 7.0)
|
||||||
rspec-core (~> 3.13)
|
rspec-core (~> 3.13)
|
||||||
rspec-expectations (~> 3.13)
|
rspec-expectations (~> 3.13)
|
||||||
rspec-mocks (~> 3.13)
|
rspec-mocks (~> 3.13)
|
||||||
rspec-support (~> 3.13)
|
rspec-support (~> 3.13)
|
||||||
rspec-support (3.13.1)
|
rspec-support (3.13.1)
|
||||||
rubocop (1.63.3)
|
rubocop (1.66.1)
|
||||||
json (~> 2.3)
|
json (~> 2.3)
|
||||||
language_server-protocol (>= 3.17.0)
|
language_server-protocol (>= 3.17.0)
|
||||||
parallel (~> 1.10)
|
parallel (~> 1.10)
|
||||||
parser (>= 3.3.0.2)
|
parser (>= 3.3.0.2)
|
||||||
rainbow (>= 2.2.2, < 4.0)
|
rainbow (>= 2.2.2, < 4.0)
|
||||||
regexp_parser (>= 1.8, < 3.0)
|
regexp_parser (>= 2.4, < 3.0)
|
||||||
rexml (>= 3.2.5, < 4.0)
|
rubocop-ast (>= 1.32.2, < 2.0)
|
||||||
rubocop-ast (>= 1.31.1, < 2.0)
|
|
||||||
ruby-progressbar (~> 1.7)
|
ruby-progressbar (~> 1.7)
|
||||||
unicode-display_width (>= 2.4.0, < 3.0)
|
unicode-display_width (>= 2.4.0, < 3.0)
|
||||||
rubocop-ast (1.31.2)
|
rubocop-ast (1.32.3)
|
||||||
parser (>= 3.3.0.4)
|
parser (>= 3.3.1.0)
|
||||||
rubocop-capybara (2.20.0)
|
rubocop-performance (1.22.1)
|
||||||
rubocop (~> 1.41)
|
|
||||||
rubocop-factory_bot (2.25.1)
|
|
||||||
rubocop (~> 1.41)
|
|
||||||
rubocop-performance (1.21.0)
|
|
||||||
rubocop (>= 1.48.1, < 2.0)
|
rubocop (>= 1.48.1, < 2.0)
|
||||||
rubocop-ast (>= 1.31.1, < 2.0)
|
rubocop-ast (>= 1.31.1, < 2.0)
|
||||||
rubocop-rails (2.24.1)
|
rubocop-rails (2.26.2)
|
||||||
activesupport (>= 4.2.0)
|
activesupport (>= 4.2.0)
|
||||||
rack (>= 1.1)
|
rack (>= 1.1)
|
||||||
rubocop (>= 1.33.0, < 2.0)
|
rubocop (>= 1.52.0, < 2.0)
|
||||||
rubocop-ast (>= 1.31.1, < 2.0)
|
rubocop-ast (>= 1.31.1, < 2.0)
|
||||||
rubocop-rspec (2.29.1)
|
rubocop-rspec (3.1.0)
|
||||||
rubocop (~> 1.40)
|
rubocop (~> 1.61)
|
||||||
rubocop-capybara (~> 2.17)
|
|
||||||
rubocop-factory_bot (~> 2.22)
|
|
||||||
rubocop-rspec_rails (~> 2.28)
|
|
||||||
rubocop-rspec_rails (2.28.3)
|
|
||||||
rubocop (~> 1.40)
|
|
||||||
ruby-progressbar (1.13.0)
|
ruby-progressbar (1.13.0)
|
||||||
ruby-vips (2.2.1)
|
ruby-vips (2.2.2)
|
||||||
ffi (~> 1.12)
|
ffi (~> 1.12)
|
||||||
|
logger
|
||||||
ruby2_keywords (0.0.5)
|
ruby2_keywords (0.0.5)
|
||||||
rubyXL (3.4.27)
|
rubyXL (3.4.27)
|
||||||
nokogiri (>= 1.10.8)
|
nokogiri (>= 1.10.8)
|
||||||
rubyzip (>= 1.3.0)
|
rubyzip (>= 1.3.0)
|
||||||
rubyzip (2.3.2)
|
rubyzip (2.3.2)
|
||||||
|
securerandom (0.3.1)
|
||||||
semantic_range (3.0.0)
|
semantic_range (3.0.0)
|
||||||
shakapacker (8.0.0)
|
shakapacker (8.0.2)
|
||||||
activesupport (>= 5.2)
|
activesupport (>= 5.2)
|
||||||
package_json
|
package_json
|
||||||
rack-proxy (>= 0.6.1)
|
rack-proxy (>= 0.6.1)
|
||||||
railties (>= 5.2)
|
railties (>= 5.2)
|
||||||
semantic_range (>= 2.3.0)
|
semantic_range (>= 2.3.0)
|
||||||
sidekiq (7.2.2)
|
sidekiq (7.3.2)
|
||||||
concurrent-ruby (< 2)
|
concurrent-ruby (< 2)
|
||||||
connection_pool (>= 2.3.0)
|
connection_pool (>= 2.3.0)
|
||||||
|
logger
|
||||||
rack (>= 2.2.4)
|
rack (>= 2.2.4)
|
||||||
redis-client (>= 0.19.0)
|
redis-client (>= 0.22.2)
|
||||||
signet (0.19.0)
|
signet (0.19.0)
|
||||||
addressable (~> 2.8)
|
addressable (~> 2.8)
|
||||||
faraday (>= 0.17.5, < 3.a)
|
faraday (>= 0.17.5, < 3.a)
|
||||||
@@ -516,32 +514,34 @@ GEM
|
|||||||
docile (~> 1.1)
|
docile (~> 1.1)
|
||||||
simplecov-html (~> 0.11)
|
simplecov-html (~> 0.11)
|
||||||
simplecov_json_formatter (~> 0.1)
|
simplecov_json_formatter (~> 0.1)
|
||||||
simplecov-html (0.12.3)
|
simplecov-html (0.13.1)
|
||||||
simplecov_json_formatter (0.1.4)
|
simplecov_json_formatter (0.1.4)
|
||||||
smart_properties (1.17.0)
|
smart_properties (1.17.0)
|
||||||
sqlite3 (1.7.3)
|
sqlite3 (2.1.0)
|
||||||
mini_portile2 (~> 2.8.0)
|
mini_portile2 (~> 2.8.0)
|
||||||
stringio (3.1.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)
|
strip_attributes (1.13.0)
|
||||||
activemodel (>= 3.0, < 8.0)
|
activemodel (>= 3.0, < 8.0)
|
||||||
thor (1.3.1)
|
thor (1.3.2)
|
||||||
timeout (0.4.1)
|
timeout (0.4.1)
|
||||||
trailblazer-option (0.1.2)
|
trailblazer-option (0.1.2)
|
||||||
turbo-rails (2.0.5)
|
turbo-rails (2.0.10)
|
||||||
actionpack (>= 6.0.0)
|
actionpack (>= 6.0.0)
|
||||||
activejob (>= 6.0.0)
|
|
||||||
railties (>= 6.0.0)
|
railties (>= 6.0.0)
|
||||||
twitter_cldr (6.12.0)
|
twitter_cldr (6.12.1)
|
||||||
camertron-eprun
|
camertron-eprun
|
||||||
cldr-plurals-runtime-rb (~> 1.1)
|
cldr-plurals-runtime-rb (~> 1.1)
|
||||||
tzinfo
|
tzinfo
|
||||||
tzinfo (2.0.6)
|
tzinfo (2.0.6)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
tzinfo-data (1.2024.1)
|
tzinfo-data (1.2024.2)
|
||||||
tzinfo (>= 1.0.0)
|
tzinfo (>= 1.0.0)
|
||||||
uber (0.1.0)
|
uber (0.1.0)
|
||||||
unicode-display_width (2.5.0)
|
unicode-display_width (2.6.0)
|
||||||
uniform_notifier (1.16.0)
|
uniform_notifier (1.16.0)
|
||||||
|
useragent (0.16.10)
|
||||||
warden (1.2.9)
|
warden (1.2.9)
|
||||||
rack (>= 2.0.9)
|
rack (>= 2.0.9)
|
||||||
web-console (4.2.1)
|
web-console (4.2.1)
|
||||||
@@ -549,17 +549,17 @@ GEM
|
|||||||
activemodel (>= 6.0.0)
|
activemodel (>= 6.0.0)
|
||||||
bindex (>= 0.4.0)
|
bindex (>= 0.4.0)
|
||||||
railties (>= 6.0.0)
|
railties (>= 6.0.0)
|
||||||
webmock (3.23.0)
|
webmock (3.24.0)
|
||||||
addressable (>= 2.8.0)
|
addressable (>= 2.8.0)
|
||||||
crack (>= 0.3.2)
|
crack (>= 0.3.2)
|
||||||
hashdiff (>= 0.4.0, < 2.0.0)
|
hashdiff (>= 0.4.0, < 2.0.0)
|
||||||
webrick (1.8.1)
|
webrick (1.8.2)
|
||||||
websocket-driver (0.7.6)
|
websocket-driver (0.7.6)
|
||||||
websocket-extensions (>= 0.1.0)
|
websocket-extensions (>= 0.1.0)
|
||||||
websocket-extensions (0.1.5)
|
websocket-extensions (0.1.5)
|
||||||
xpath (3.2.0)
|
xpath (3.2.0)
|
||||||
nokogiri (~> 1.8)
|
nokogiri (~> 1.8)
|
||||||
zeitwerk (2.6.14)
|
zeitwerk (2.7.0)
|
||||||
|
|
||||||
PLATFORMS
|
PLATFORMS
|
||||||
aarch64-linux
|
aarch64-linux
|
||||||
@@ -570,6 +570,7 @@ DEPENDENCIES
|
|||||||
annotate
|
annotate
|
||||||
arabic-letter-connector
|
arabic-letter-connector
|
||||||
aws-sdk-s3
|
aws-sdk-s3
|
||||||
|
aws-sdk-secretsmanager
|
||||||
azure-storage-blob
|
azure-storage-blob
|
||||||
better_html
|
better_html
|
||||||
bootsnap
|
bootsnap
|
||||||
@@ -627,7 +628,7 @@ DEPENDENCIES
|
|||||||
webmock
|
webmock
|
||||||
|
|
||||||
RUBY VERSION
|
RUBY VERSION
|
||||||
ruby 3.3.1p55
|
ruby 3.3.4p94
|
||||||
|
|
||||||
BUNDLED WITH
|
BUNDLED WITH
|
||||||
2.5.3
|
2.5.3
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<h1 align="center" style="border-bottom: none">
|
<h1 align="center" style="border-bottom: none">
|
||||||
<div>
|
<div>
|
||||||
<a href="https://www.docuseal.co">
|
<a href="https://www.docuseal.com">
|
||||||
<img alt="DocuSeal" src="https://github.com/docusealco/docuseal/assets/5418788/c12cd051-81cd-4402-bc3a-92f2cfdc1b06" width="80" />
|
<img alt="DocuSeal" src="https://github.com/docusealco/docuseal/assets/5418788/c12cd051-81cd-4402-bc3a-92f2cfdc1b06" width="80" />
|
||||||
<br>
|
<br>
|
||||||
</a>
|
</a>
|
||||||
@@ -25,12 +25,12 @@
|
|||||||
DocuSeal is an open source platform that provides secure and efficient digital document signing and processing. Create PDF forms to have them filled and signed online on any device with an easy-to-use, mobile-optimized web tool.
|
DocuSeal is an open source platform that provides secure and efficient digital document signing and processing. Create PDF forms to have them filled and signed online on any device with an easy-to-use, mobile-optimized web tool.
|
||||||
</p>
|
</p>
|
||||||
<h2 align="center">
|
<h2 align="center">
|
||||||
<a href="https://demo.docuseal.co">✨ Live Demo</a>
|
<a href="https://demo.docuseal.tech">✨ Live Demo</a>
|
||||||
<span>|</span>
|
<span>|</span>
|
||||||
<a href="https://docuseal.co/sign_up">☁️ Try in Cloud</a>
|
<a href="https://docuseal.com/sign_up">☁️ Try in Cloud</a>
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
[](https://demo.docuseal.co)
|
[](https://demo.docuseal.tech)
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
- PDF form fields builder (WYSIWYG)
|
- PDF form fields builder (WYSIWYG)
|
||||||
@@ -42,7 +42,7 @@ DocuSeal is an open source platform that provides secure and efficient digital d
|
|||||||
- PDF signature verification
|
- PDF signature verification
|
||||||
- Users management
|
- Users management
|
||||||
- Mobile-optimized
|
- Mobile-optimized
|
||||||
- Signing available in 13 languages
|
- 6 UI languages with signing available in 13 languages
|
||||||
- API and Webhooks for integrations
|
- API and Webhooks for integrations
|
||||||
- Easy to deploy in minutes
|
- Easy to deploy in minutes
|
||||||
|
|
||||||
@@ -54,11 +54,11 @@ DocuSeal is an open source platform that provides secure and efficient digital d
|
|||||||
- Conditional fields and formulas
|
- Conditional fields and formulas
|
||||||
- Bulk send with CSV, XLSX spreadsheet import
|
- Bulk send with CSV, XLSX spreadsheet import
|
||||||
- SSO / SAML
|
- SSO / SAML
|
||||||
- Template creation with HTML API ([Guide](https://www.docuseal.co/guides/create-pdf-document-fillable-form-with-html-api))
|
- Template creation with HTML API ([Guide](https://www.docuseal.com/guides/create-pdf-document-fillable-form-with-html-api))
|
||||||
- Template creation with PDF or DOCX and field tags API ([Guide](https://www.docuseal.co/guides/use-embedded-text-field-tags-in-the-pdf-to-create-a-fillable-form))
|
- Template creation with PDF or DOCX and field tags API ([Guide](https://www.docuseal.com/guides/use-embedded-text-field-tags-in-the-pdf-to-create-a-fillable-form))
|
||||||
- Embedded signing form ([React](https://github.com/docusealco/docuseal-react), [Vue](https://github.com/docusealco/docuseal-vue), [Angular](https://github.com/docusealco/docuseal-angular) or [JavaScript](https://www.docuseal.co/docs/embedded))
|
- Embedded signing form ([React](https://github.com/docusealco/docuseal-react), [Vue](https://github.com/docusealco/docuseal-vue), [Angular](https://github.com/docusealco/docuseal-angular) or [JavaScript](https://www.docuseal.com/docs/embedded))
|
||||||
- Embedded document form builder ([React](https://github.com/docusealco/docuseal-react), [Vue](https://github.com/docusealco/docuseal-vue), [Angular](https://github.com/docusealco/docuseal-angular) or [JavaScript](https://www.docuseal.co/docs/embedded))
|
- Embedded document form builder ([React](https://github.com/docusealco/docuseal-react), [Vue](https://github.com/docusealco/docuseal-vue), [Angular](https://github.com/docusealco/docuseal-angular) or [JavaScript](https://www.docuseal.com/docs/embedded))
|
||||||
- [Learn more](https://www.docuseal.co/pricing)
|
- [Learn more](https://www.docuseal.com/pricing)
|
||||||
|
|
||||||
## Deploy
|
## Deploy
|
||||||
|
|
||||||
@@ -93,7 +93,7 @@ sudo HOST=your-domain-name.com docker compose up
|
|||||||
|
|
||||||
At DocuSeal we have expertise and technologies to make documents creation, filling, signing and processing seamlessly integrated with your product. We specialize in working with various industries, including **Banking, Healthcare, Transport, Real Estate, eCommerce, KYC, CRM, and other software products** that require bulk document signing. By leveraging DocuSeal, we can assist in reducing the overall cost of developing and processing electronic documents while ensuring security and compliance with local electronic document laws.
|
At DocuSeal we have expertise and technologies to make documents creation, filling, signing and processing seamlessly integrated with your product. We specialize in working with various industries, including **Banking, Healthcare, Transport, Real Estate, eCommerce, KYC, CRM, and other software products** that require bulk document signing. By leveraging DocuSeal, we can assist in reducing the overall cost of developing and processing electronic documents while ensuring security and compliance with local electronic document laws.
|
||||||
|
|
||||||
[Book a Meeting](https://www.docuseal.co/contact)
|
[Book a Meeting](https://www.docuseal.com/contact)
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
@@ -102,6 +102,6 @@ Unless otherwise noted, all files © 2023 DocuSeal LLC.
|
|||||||
|
|
||||||
## Tools
|
## Tools
|
||||||
|
|
||||||
- [Signature Maker](https://www.docuseal.co/online-signature)
|
- [Signature Maker](https://www.docuseal.com/online-signature)
|
||||||
- [Sign Document Online](https://www.docuseal.co/sign-documents-online)
|
- [Sign Document Online](https://www.docuseal.com/sign-documents-online)
|
||||||
- [Fill PDF Online](https://www.docuseal.co/fill-pdf)
|
- [Fill PDF Online](https://www.docuseal.com/fill-pdf)
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
# Reporting a Vulnerability
|
# Reporting a Vulnerability
|
||||||
|
|
||||||
If you come across any security concern or vulnarability, please report the information via email to security@docuseal.co instead of opening a GitHub issue. We will promptly respond and will collaborate with you to validate the issue, and resolve it ASAP.
|
If you come across any security concern or vulnarability, please report the information via email to security@docuseal.com instead of opening a GitHub issue. We will promptly respond and will collaborate with you to validate the issue, and resolve it ASAP.
|
||||||
|
|
||||||
**We have a bug bounty program to reward security researchers.**
|
**We have a bug bounty program to reward security researchers.**
|
||||||
|
|
||||||
|
|||||||
@@ -8,12 +8,17 @@ class AccountConfigsController < ApplicationController
|
|||||||
AccountConfig::ALLOW_TYPED_SIGNATURE,
|
AccountConfig::ALLOW_TYPED_SIGNATURE,
|
||||||
AccountConfig::FORCE_MFA,
|
AccountConfig::FORCE_MFA,
|
||||||
AccountConfig::ALLOW_TO_RESUBMIT,
|
AccountConfig::ALLOW_TO_RESUBMIT,
|
||||||
|
AccountConfig::ALLOW_TO_DECLINE_KEY,
|
||||||
AccountConfig::FORM_PREFILL_SIGNATURE_KEY,
|
AccountConfig::FORM_PREFILL_SIGNATURE_KEY,
|
||||||
AccountConfig::ESIGNING_PREFERENCE_KEY,
|
AccountConfig::ESIGNING_PREFERENCE_KEY,
|
||||||
AccountConfig::FORM_WITH_CONFETTI_KEY,
|
AccountConfig::FORM_WITH_CONFETTI_KEY,
|
||||||
AccountConfig::DOWNLOAD_LINKS_AUTH_KEY,
|
AccountConfig::DOWNLOAD_LINKS_AUTH_KEY,
|
||||||
AccountConfig::FORCE_SSO_AUTH_KEY,
|
AccountConfig::FORCE_SSO_AUTH_KEY,
|
||||||
AccountConfig::FLATTEN_RESULT_PDF_KEY
|
AccountConfig::FLATTEN_RESULT_PDF_KEY,
|
||||||
|
AccountConfig::WITH_SIGNATURE_ID,
|
||||||
|
AccountConfig::COMBINE_PDF_RESULT_KEY,
|
||||||
|
AccountConfig::REQUIRE_SIGNING_REASON_KEY,
|
||||||
|
AccountConfig::DOCUMENT_FILENAME_FORMAT_KEY
|
||||||
].freeze
|
].freeze
|
||||||
|
|
||||||
InvalidKey = Class.new(StandardError)
|
InvalidKey = Class.new(StandardError)
|
||||||
|
|||||||
@@ -4,10 +4,11 @@ class AccountsController < ApplicationController
|
|||||||
LOCALE_OPTIONS = {
|
LOCALE_OPTIONS = {
|
||||||
'en-US' => 'English (United States)',
|
'en-US' => 'English (United States)',
|
||||||
'en-GB' => 'English (United Kingdom)',
|
'en-GB' => 'English (United Kingdom)',
|
||||||
'fr-FR' => 'French (France)',
|
'fr-FR' => 'Français',
|
||||||
'es-ES' => 'Spanish (Spain)',
|
'es-ES' => 'Español',
|
||||||
'pt-PT' => 'Portuguese (Portugal)',
|
'pt-PT' => 'Português',
|
||||||
'de-DE' => 'German (Germany)'
|
'de-DE' => 'Deutsch',
|
||||||
|
'it-IT' => 'Italiano'
|
||||||
}.freeze
|
}.freeze
|
||||||
|
|
||||||
before_action :load_account
|
before_action :load_account
|
||||||
@@ -18,21 +19,25 @@ class AccountsController < ApplicationController
|
|||||||
def update
|
def update
|
||||||
current_account.update!(account_params)
|
current_account.update!(account_params)
|
||||||
|
|
||||||
@encrypted_config = EncryptedConfig.find_or_initialize_by(account: current_account,
|
unless Docuseal.multitenant?
|
||||||
key: EncryptedConfig::APP_URL_KEY)
|
@encrypted_config = EncryptedConfig.find_or_initialize_by(account: current_account,
|
||||||
@encrypted_config.assign_attributes(app_url_params)
|
key: EncryptedConfig::APP_URL_KEY)
|
||||||
|
@encrypted_config.assign_attributes(app_url_params)
|
||||||
|
|
||||||
unless URI.parse(@encrypted_config.value.to_s).class.in?([URI::HTTP, URI::HTTPS])
|
unless URI.parse(@encrypted_config.value.to_s).class.in?([URI::HTTP, URI::HTTPS])
|
||||||
@encrypted_config.errors.add(:value, 'should be a valid URL')
|
@encrypted_config.errors.add(:value, I18n.t('should_be_a_valid_url'))
|
||||||
|
|
||||||
return render :show, status: :unprocessable_entity
|
return render :show, status: :unprocessable_entity
|
||||||
|
end
|
||||||
|
|
||||||
|
@encrypted_config.save!
|
||||||
|
|
||||||
|
Docuseal.refresh_default_url_options!
|
||||||
end
|
end
|
||||||
|
|
||||||
@encrypted_config.save!
|
with_locale do
|
||||||
|
redirect_to settings_account_path, notice: I18n.t('account_information_has_been_updated')
|
||||||
Docuseal.refresh_default_url_options!
|
end
|
||||||
|
|
||||||
redirect_to settings_account_path, notice: 'Account information has been updated'
|
|
||||||
rescue ActiveRecord::RecordInvalid
|
rescue ActiveRecord::RecordInvalid
|
||||||
render :show, status: :unprocessable_entity
|
render :show, status: :unprocessable_entity
|
||||||
end
|
end
|
||||||
@@ -42,11 +47,12 @@ class AccountsController < ApplicationController
|
|||||||
|
|
||||||
true_user.update!(locked_at: Time.current)
|
true_user.update!(locked_at: Time.current)
|
||||||
|
|
||||||
|
# rubocop:disable Layout/LineLength
|
||||||
render turbo_stream: turbo_stream.replace(
|
render turbo_stream: turbo_stream.replace(
|
||||||
:account_delete_button,
|
:account_delete_button,
|
||||||
html: helpers.tag.p('Your account removal request will be processed within 2 weeks. ' \
|
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'))
|
||||||
'Please contact us if you want to keep your account.')
|
|
||||||
)
|
)
|
||||||
|
# rubocop:enable Layout/LineLength
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|||||||
@@ -38,12 +38,12 @@ module Api
|
|||||||
private
|
private
|
||||||
|
|
||||||
def authorization_check!(blob)
|
def authorization_check!(blob)
|
||||||
is_authorized =
|
attachment = blob.attachments.take
|
||||||
blob.attachments.all? do |a|
|
|
||||||
a.name.in?(%w[logo preview_images]) ||
|
is_authorized = attachment.name.in?(%w[logo preview_images]) ||
|
||||||
(current_user && a.record.account.id == current_user.account_id) ||
|
(current_user && attachment.record.account.id == current_user.account_id) ||
|
||||||
!a.record.account.account_configs.find_or_initialize_by(key: AccountConfig::DOWNLOAD_LINKS_AUTH_KEY).value
|
!attachment.record.account.account_configs
|
||||||
end
|
.find_or_initialize_by(key: AccountConfig::DOWNLOAD_LINKS_AUTH_KEY).value
|
||||||
|
|
||||||
return if is_authorized
|
return if is_authorized
|
||||||
|
|
||||||
|
|||||||
@@ -25,9 +25,9 @@ module Api
|
|||||||
render json: { error: 'Too many requests' }, status: :too_many_requests
|
render json: { error: 'Too many requests' }, status: :too_many_requests
|
||||||
end
|
end
|
||||||
|
|
||||||
if Rails.env.production?
|
unless Rails.env.development?
|
||||||
rescue_from CanCan::AccessDenied do |e|
|
rescue_from CanCan::AccessDenied do |e|
|
||||||
render json: { error: e.message }, status: :forbidden
|
render json: { error: access_denied_error_message(e) }, status: :forbidden
|
||||||
end
|
end
|
||||||
|
|
||||||
rescue_from JSON::ParserError do |e|
|
rescue_from JSON::ParserError do |e|
|
||||||
@@ -39,12 +39,44 @@ module Api
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def paginate(relation)
|
def access_denied_error_message(error)
|
||||||
result = relation.order(id: :desc)
|
return 'Not authorized' if request.headers['X-Auth-Token'].blank?
|
||||||
|
return 'Not authorized' unless error.subject.is_a?(ActiveRecord::Base)
|
||||||
|
return 'Not authorized' unless error.subject.respond_to?(:account_id)
|
||||||
|
|
||||||
|
linked_account_record_exists =
|
||||||
|
if current_user.account.testing?
|
||||||
|
current_user.account.linked_account_accounts.where(account_type: 'testing')
|
||||||
|
.exists?(account_id: error.subject.account_id)
|
||||||
|
else
|
||||||
|
current_user.account.testing_accounts.exists?(id: error.subject.account_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
return 'Not authorized' unless linked_account_record_exists
|
||||||
|
|
||||||
|
object_name = error.subject.model_name.human
|
||||||
|
id = error.subject.id
|
||||||
|
|
||||||
|
if current_user.account.testing?
|
||||||
|
"#{object_name} #{id} not found using testing API key; Use production API key to " \
|
||||||
|
"access production #{object_name.downcase.pluralize}."
|
||||||
|
else
|
||||||
|
"#{object_name} #{id} not found using production API key; Use testing API key to " \
|
||||||
|
"access testing #{object_name.downcase.pluralize}."
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def paginate(relation, field: :id)
|
||||||
|
result = relation.order(field => :desc)
|
||||||
.limit([params.fetch(:limit, DEFAULT_LIMIT).to_i, MAX_LIMIT].min)
|
.limit([params.fetch(:limit, DEFAULT_LIMIT).to_i, MAX_LIMIT].min)
|
||||||
|
|
||||||
result = result.where(id: ...params[:after].to_i) if params[:after].present?
|
if field == :id
|
||||||
result = result.where(id: (params[:before].to_i + 1)...) if params[:before].present?
|
result = result.where(id: ...params[:after].to_i) if params[:after].present?
|
||||||
|
result = result.where(id: (params[:before].to_i + 1)...) if params[:before].present?
|
||||||
|
else
|
||||||
|
result = result.where(field => ...params[:after]) if params[:after].present?
|
||||||
|
result = result.where(field => (params[:before] + 1)...) if params[:before].present?
|
||||||
|
end
|
||||||
|
|
||||||
result
|
result
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ module Api
|
|||||||
cookies.encrypted[:signature_uuids] = build_new_cookie_signatures_json(submitter, attachment)
|
cookies.encrypted[:signature_uuids] = build_new_cookie_signatures_json(submitter, attachment)
|
||||||
end
|
end
|
||||||
|
|
||||||
render json: attachment.as_json(only: %i[uuid], methods: %i[url filename content_type])
|
render json: attachment.as_json(only: %i[uuid created_at], methods: %i[url filename content_type])
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_new_cookie_signatures_json(submitter, attachment)
|
def build_new_cookie_signatures_json(submitter, attachment)
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Api
|
||||||
|
class FormEventsController < ApiBaseController
|
||||||
|
load_and_authorize_resource :submitter, parent: false
|
||||||
|
|
||||||
|
def index
|
||||||
|
submitters = @submitters.where.not(completed_at: nil)
|
||||||
|
|
||||||
|
params[:after] = Time.zone.at(params[:after].to_i) if params[:after].present?
|
||||||
|
params[:before] = Time.zone.at(params[:before].to_i) if params[:before].present?
|
||||||
|
|
||||||
|
submitters = paginate(
|
||||||
|
submitters.preload(template: :folder, submission: [:submitters, { audit_trail_attachment: :blob,
|
||||||
|
combined_document_attachment: :blob }],
|
||||||
|
documents_attachments: :blob, attachments_attachments: :blob),
|
||||||
|
field: :completed_at
|
||||||
|
)
|
||||||
|
|
||||||
|
render json: {
|
||||||
|
data: submitters.map do |s|
|
||||||
|
{
|
||||||
|
event_type: 'form.completed',
|
||||||
|
timestamp: s.completed_at,
|
||||||
|
data: Submitters::SerializeForWebhook.call(s)
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
pagination: {
|
||||||
|
count: submitters.size,
|
||||||
|
next: submitters.last&.completed_at&.to_i,
|
||||||
|
prev: submitters.first&.completed_at&.to_i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Api
|
||||||
|
class SubmissionDocumentsController < ApiBaseController
|
||||||
|
load_and_authorize_resource :submission
|
||||||
|
|
||||||
|
def index
|
||||||
|
documents =
|
||||||
|
if @submission.submitters.all?(&:completed_at?)
|
||||||
|
last_submitter = @submission.submitters.max_by(&:completed_at)
|
||||||
|
|
||||||
|
if last_submitter.documents_attachments.blank?
|
||||||
|
last_submitter.documents_attachments = Submissions::EnsureResultGenerated.call(submitter)
|
||||||
|
end
|
||||||
|
|
||||||
|
last_submitter.documents_attachments
|
||||||
|
else
|
||||||
|
values_hash = Submissions::GeneratePreviewAttachments.build_values_hash(@submission)
|
||||||
|
|
||||||
|
if @submission.preview_documents.present? &&
|
||||||
|
@submission.preview_documents.all? { |s| s.metadata['values_hash'] == values_hash }
|
||||||
|
@submission.preview_documents
|
||||||
|
else
|
||||||
|
ApplicationRecord.no_touching do
|
||||||
|
@submission.preview_documents.each(&:destroy)
|
||||||
|
end
|
||||||
|
|
||||||
|
Submissions::GeneratePreviewAttachments.call(@submission, values_hash:)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
ActiveRecord::Associations::Preloader.new(
|
||||||
|
records: documents,
|
||||||
|
associations: [:blob]
|
||||||
|
).call
|
||||||
|
|
||||||
|
render json: {
|
||||||
|
id: @submission.id,
|
||||||
|
documents: documents.map do |attachment|
|
||||||
|
{ name: attachment.filename.base, url: ActiveStorage::Blob.proxy_url(attachment.blob) }
|
||||||
|
end
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Api
|
||||||
|
class SubmissionEventsController < ApiBaseController
|
||||||
|
load_and_authorize_resource :submission, parent: false
|
||||||
|
|
||||||
|
def index
|
||||||
|
submissions = build_completed_query(@submissions)
|
||||||
|
|
||||||
|
params[:after] = Time.zone.at(params[:after].to_i) if params[:after].present?
|
||||||
|
params[:before] = Time.zone.at(params[:before].to_i) if params[:before].present?
|
||||||
|
|
||||||
|
submissions = paginate(submissions.preload(
|
||||||
|
:created_by_user, :submission_events,
|
||||||
|
template: :folder,
|
||||||
|
submitters: { documents_attachments: :blob, attachments_attachments: :blob },
|
||||||
|
audit_trail_attachment: :blob
|
||||||
|
),
|
||||||
|
field: :completed_at)
|
||||||
|
|
||||||
|
render json: {
|
||||||
|
data: submissions.map do |s|
|
||||||
|
{
|
||||||
|
event_type: 'submission.completed',
|
||||||
|
timestamp: s.completed_at,
|
||||||
|
data: Submissions::SerializeForApi.call(s, s.submitters)
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
pagination: {
|
||||||
|
count: submissions.size,
|
||||||
|
next: submissions.last&.completed_at&.to_i,
|
||||||
|
prev: submissions.first&.completed_at&.to_i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def build_completed_query(submissions)
|
||||||
|
submissions = submissions.where(
|
||||||
|
Submitter.where(completed_at: nil).where(
|
||||||
|
Submitter.arel_table[:submission_id].eq(Submission.arel_table[:id])
|
||||||
|
).select(1).arel.exists.not
|
||||||
|
)
|
||||||
|
|
||||||
|
submissions.joins(:submitters)
|
||||||
|
.group(:id)
|
||||||
|
.select(Submission.arel_table[Arel.star],
|
||||||
|
Submitter.arel_table[:completed_at].maximum.as('completed_at'))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -19,10 +19,14 @@ module Api
|
|||||||
|
|
||||||
submissions = paginate(submissions.preload(:created_by_user, :submitters,
|
submissions = paginate(submissions.preload(:created_by_user, :submitters,
|
||||||
template: :folder,
|
template: :folder,
|
||||||
|
combined_document_attachment: :blob,
|
||||||
audit_trail_attachment: :blob))
|
audit_trail_attachment: :blob))
|
||||||
|
|
||||||
render json: {
|
render json: {
|
||||||
data: submissions.as_json(Submissions::SerializeForApi::SERIALIZE_PARAMS),
|
data: submissions.map do |s|
|
||||||
|
Submissions::SerializeForApi.call(s, s.submitters, params,
|
||||||
|
with_events: false, with_documents: false, with_values: false)
|
||||||
|
end,
|
||||||
pagination: {
|
pagination: {
|
||||||
count: submissions.size,
|
count: submissions.size,
|
||||||
next: submissions.last&.id,
|
next: submissions.last&.id,
|
||||||
@@ -44,7 +48,7 @@ module Api
|
|||||||
@submission.audit_trail_attachment = Submissions::GenerateAuditTrail.call(@submission)
|
@submission.audit_trail_attachment = Submissions::GenerateAuditTrail.call(@submission)
|
||||||
end
|
end
|
||||||
|
|
||||||
render json: Submissions::SerializeForApi.call(@submission, submitters)
|
render json: Submissions::SerializeForApi.call(@submission, submitters, params)
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
@@ -63,26 +67,26 @@ module Api
|
|||||||
|
|
||||||
submissions = create_submissions(@template, params)
|
submissions = create_submissions(@template, params)
|
||||||
|
|
||||||
submissions.each do |submission|
|
WebhookUrls.for_account_id(@template.account_id, 'submission.created').each do |webhook_url|
|
||||||
SendSubmissionCreatedWebhookRequestJob.perform_async({ 'submission_id' => submission.id })
|
submissions.each do |submission|
|
||||||
|
SendSubmissionCreatedWebhookRequestJob.perform_async('submission_id' => submission.id,
|
||||||
|
'webhook_url_id' => webhook_url.id)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
Submissions.send_signature_requests(submissions)
|
Submissions.send_signature_requests(submissions)
|
||||||
|
|
||||||
submissions.each do |submission|
|
submissions.each do |submission|
|
||||||
if submission.submitters.all?(&:completed_at?) && submission.submitters.last
|
submission.submitters.each do |submitter|
|
||||||
ProcessSubmitterCompletionJob.perform_async({ 'submitter_id' => submission.submitters.last.id })
|
next unless submitter.completed_at?
|
||||||
|
|
||||||
|
ProcessSubmitterCompletionJob.perform_async('submitter_id' => submitter.id, 'send_invitation_email' => false)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
json = submissions.flat_map do |submission|
|
render json: build_create_json(submissions)
|
||||||
submission.submitters.map do |s|
|
rescue Submitters::NormalizeValues::BaseError, Submissions::CreateFromSubmitters::BaseError,
|
||||||
Submitters::SerializeForApi.call(s, with_documents: false, with_urls: true)
|
DownloadUtils::UnableToDownload => e
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
render json:
|
|
||||||
rescue Submitters::NormalizeValues::BaseError => e
|
|
||||||
Rollbar.warning(e) if defined?(Rollbar)
|
Rollbar.warning(e) if defined?(Rollbar)
|
||||||
|
|
||||||
render json: { error: e.message }, status: :unprocessable_entity
|
render json: { error: e.message }, status: :unprocessable_entity
|
||||||
@@ -94,7 +98,10 @@ module Api
|
|||||||
else
|
else
|
||||||
@submission.update!(archived_at: Time.current)
|
@submission.update!(archived_at: Time.current)
|
||||||
|
|
||||||
SendSubmissionArchivedWebhookRequestJob.perform_async('submission_id' => @submission.id)
|
WebhookUrls.for_account_id(@submission.account_id, 'submission.archived').each do |webhook_url|
|
||||||
|
SendSubmissionArchivedWebhookRequestJob.perform_async('submission_id' => @submission.id,
|
||||||
|
'webhook_url_id' => webhook_url.id)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
render json: @submission.as_json(only: %i[id archived_at])
|
render json: @submission.as_json(only: %i[id archived_at])
|
||||||
@@ -102,6 +109,18 @@ module Api
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def build_create_json(submissions)
|
||||||
|
json = submissions.flat_map do |submission|
|
||||||
|
submission.submitters.map do |s|
|
||||||
|
Submitters::SerializeForApi.call(s, with_documents: false, with_urls: true, params:)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
json = { submitters: json } if request.path.ends_with?('/init')
|
||||||
|
|
||||||
|
json
|
||||||
|
end
|
||||||
|
|
||||||
def create_submissions(template, params)
|
def create_submissions(template, params)
|
||||||
is_send_email = !params[:send_email].in?(['false', false])
|
is_send_email = !params[:send_email].in?(['false', false])
|
||||||
|
|
||||||
@@ -121,7 +140,6 @@ module Api
|
|||||||
template:,
|
template:,
|
||||||
user: current_user,
|
user: current_user,
|
||||||
source: :api,
|
source: :api,
|
||||||
mark_as_sent: is_send_email,
|
|
||||||
submitters_order: params[:submitters_order] || params[:order] || 'preserved',
|
submitters_order: params[:submitters_order] || params[:order] || 'preserved',
|
||||||
submissions_attrs:,
|
submissions_attrs:,
|
||||||
params:
|
params:
|
||||||
@@ -142,13 +160,14 @@ module Api
|
|||||||
def submissions_params
|
def submissions_params
|
||||||
permitted_attrs = [
|
permitted_attrs = [
|
||||||
:send_email, :send_sms, :bcc_completed, :completed_redirect_url, :reply_to, :go_to_last,
|
:send_email, :send_sms, :bcc_completed, :completed_redirect_url, :reply_to, :go_to_last,
|
||||||
|
:expire_at,
|
||||||
{
|
{
|
||||||
message: %i[subject body],
|
message: %i[subject body],
|
||||||
submitters: [[:send_email, :send_sms, :completed_redirect_url, :uuid, :name, :email, :role,
|
submitters: [[:send_email, :send_sms, :completed_redirect_url, :uuid, :name, :email, :role,
|
||||||
:completed, :phone, :application_key, :external_id, :reply_to, :go_to_last,
|
:completed, :phone, :application_key, :external_id, :reply_to, :go_to_last,
|
||||||
{ metadata: {}, values: {}, readonly_fields: [], message: %i[subject body],
|
{ metadata: {}, values: {}, readonly_fields: [], message: %i[subject body],
|
||||||
fields: [:name, :uuid, :default_value, :value, :title, :description,
|
fields: [:name, :uuid, :default_value, :value, :title, :description,
|
||||||
:readonly, :redacted, :validation_pattern, :invalid_message,
|
:readonly, :validation_pattern, :invalid_message,
|
||||||
{ default_value: [], value: [], preferences: {} }] }]]
|
{ default_value: [], value: [], preferences: {} }] }]]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -13,7 +13,10 @@ module Api
|
|||||||
|
|
||||||
SubmissionEvents.create_with_tracking_data(submitter, 'view_form', request)
|
SubmissionEvents.create_with_tracking_data(submitter, 'view_form', request)
|
||||||
|
|
||||||
SendFormViewedWebhookRequestJob.perform_async({ 'submitter_id' => submitter.id })
|
WebhookUrls.for_account_id(submitter.account_id, 'form.viewed').each do |webhook_url|
|
||||||
|
SendFormViewedWebhookRequestJob.perform_async('submitter_id' => submitter.id,
|
||||||
|
'webhook_url_id' => webhook_url.id)
|
||||||
|
end
|
||||||
|
|
||||||
render json: {}
|
render json: {}
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -23,7 +23,9 @@ module Api
|
|||||||
)
|
)
|
||||||
|
|
||||||
render json: {
|
render json: {
|
||||||
data: submitters.map { |s| Submitters::SerializeForApi.call(s, with_template: true, with_events: true) },
|
data: submitters.map do |s|
|
||||||
|
Submitters::SerializeForApi.call(s, with_template: true, with_events: true, params:)
|
||||||
|
end,
|
||||||
pagination: {
|
pagination: {
|
||||||
count: submitters.size,
|
count: submitters.size,
|
||||||
next: submitters.last&.id,
|
next: submitters.last&.id,
|
||||||
@@ -35,7 +37,7 @@ module Api
|
|||||||
def show
|
def show
|
||||||
Submissions::EnsureResultGenerated.call(@submitter) if @submitter.completed_at?
|
Submissions::EnsureResultGenerated.call(@submitter) if @submitter.completed_at?
|
||||||
|
|
||||||
render json: Submitters::SerializeForApi.call(@submitter, with_template: true, with_events: true)
|
render json: Submitters::SerializeForApi.call(@submitter, with_template: true, with_events: true, params:)
|
||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
@@ -66,14 +68,19 @@ module Api
|
|||||||
end
|
end
|
||||||
|
|
||||||
if @submitter.completed_at?
|
if @submitter.completed_at?
|
||||||
ProcessSubmitterCompletionJob.perform_async({ 'submitter_id' => @submitter.id })
|
ProcessSubmitterCompletionJob.perform_async('submitter_id' => @submitter.id)
|
||||||
elsif normalized_params[:send_email] || normalized_params[:send_sms]
|
elsif normalized_params[:send_email] || normalized_params[:send_sms]
|
||||||
Submitters.send_signature_requests([@submitter])
|
Submitters.send_signature_requests([@submitter])
|
||||||
end
|
end
|
||||||
|
|
||||||
render json: Submitters::SerializeForApi.call(@submitter, with_template: false,
|
render json: Submitters::SerializeForApi.call(@submitter, with_template: false,
|
||||||
with_urls: true,
|
with_urls: true,
|
||||||
with_events: false)
|
with_events: false,
|
||||||
|
params:)
|
||||||
|
rescue Submitters::NormalizeValues::BaseError, DownloadUtils::UnableToDownload => e
|
||||||
|
Rollbar.warning(e) if defined?(Rollbar)
|
||||||
|
|
||||||
|
render json: { error: e.message }, status: :unprocessable_entity
|
||||||
end
|
end
|
||||||
|
|
||||||
def submitter_params
|
def submitter_params
|
||||||
@@ -84,7 +91,7 @@ module Api
|
|||||||
:completed, :phone, :application_key, :external_id, :go_to_last,
|
:completed, :phone, :application_key, :external_id, :go_to_last,
|
||||||
{ metadata: {}, values: {}, readonly_fields: [], message: %i[subject body],
|
{ metadata: {}, values: {}, readonly_fields: [], message: %i[subject body],
|
||||||
fields: [[:name, :uuid, :default_value, :value,
|
fields: [[:name, :uuid, :default_value, :value,
|
||||||
:readonly, :redacted, :validation_pattern, :invalid_message,
|
:readonly, :validation_pattern, :invalid_message,
|
||||||
{ default_value: [], value: [], preferences: {} }]] }
|
{ default_value: [], value: [], preferences: {} }]] }
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
@@ -113,6 +120,7 @@ module Api
|
|||||||
end&.dig('uuid')
|
end&.dig('uuid')
|
||||||
|
|
||||||
submitter.email = Submissions.normalize_email(attrs[:email]) if attrs.key?(:email)
|
submitter.email = Submissions.normalize_email(attrs[:email]) if attrs.key?(:email)
|
||||||
|
submitter.name = attrs[:name] if attrs.key?(:name)
|
||||||
|
|
||||||
if attrs.key?(:phone)
|
if attrs.key?(:phone)
|
||||||
submitter.phone = attrs[:phone].to_s.gsub(/[^0-9+]/, '')
|
submitter.phone = attrs[:phone].to_s.gsub(/[^0-9+]/, '')
|
||||||
|
|||||||
@@ -7,6 +7,11 @@ module Api
|
|||||||
def create
|
def create
|
||||||
authorize!(:manage, @template)
|
authorize!(:manage, @template)
|
||||||
|
|
||||||
|
ActiveRecord::Associations::Preloader.new(
|
||||||
|
records: [@template],
|
||||||
|
associations: [schema_documents: :preview_images_attachments]
|
||||||
|
).call
|
||||||
|
|
||||||
cloned_template = Templates::Clone.call(
|
cloned_template = Templates::Clone.call(
|
||||||
@template,
|
@template,
|
||||||
author: current_user,
|
author: current_user,
|
||||||
@@ -18,11 +23,14 @@ module Api
|
|||||||
cloned_template.source = :api
|
cloned_template.source = :api
|
||||||
cloned_template.save!
|
cloned_template.save!
|
||||||
|
|
||||||
Templates::CloneAttachments.call(template: cloned_template, original_template: @template)
|
schema_documents = Templates::CloneAttachments.call(template: cloned_template, original_template: @template)
|
||||||
|
|
||||||
SendTemplateCreatedWebhookRequestJob.perform_async('template_id' => cloned_template.id)
|
WebhookUrls.for_account_id(cloned_template.account_id, 'template.created').each do |webhook_url|
|
||||||
|
SendTemplateCreatedWebhookRequestJob.perform_async('template_id' => cloned_template.id,
|
||||||
|
'webhook_url_id' => webhook_url.id)
|
||||||
|
end
|
||||||
|
|
||||||
render json: Templates::SerializeForApi.call(cloned_template)
|
render json: Templates::SerializeForApi.call(cloned_template, schema_documents)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -18,7 +18,7 @@ module Api
|
|||||||
|
|
||||||
preview_image_attachments =
|
preview_image_attachments =
|
||||||
ActiveStorage::Attachment.joins(:blob)
|
ActiveStorage::Attachment.joins(:blob)
|
||||||
.where(blob: { filename: '0.jpg' })
|
.where(blob: { filename: ['0.png', '0.jpg'] })
|
||||||
.where(record_id: schema_documents.map(&:id),
|
.where(record_id: schema_documents.map(&:id),
|
||||||
record_type: 'ActiveStorage::Attachment',
|
record_type: 'ActiveStorage::Attachment',
|
||||||
name: :preview_images)
|
name: :preview_images)
|
||||||
@@ -65,7 +65,10 @@ module Api
|
|||||||
|
|
||||||
@template.update!(template_params)
|
@template.update!(template_params)
|
||||||
|
|
||||||
SendTemplateUpdatedWebhookRequestJob.perform_async('template_id' => @template.id)
|
WebhookUrls.for_account_id(@template.account_id, 'template.updated').each do |webhook_url|
|
||||||
|
SendTemplateUpdatedWebhookRequestJob.perform_async('template_id' => @template.id,
|
||||||
|
'webhook_url_id' => webhook_url.id)
|
||||||
|
end
|
||||||
|
|
||||||
render json: @template.as_json(only: %i[id updated_at])
|
render json: @template.as_json(only: %i[id updated_at])
|
||||||
end
|
end
|
||||||
@@ -84,7 +87,7 @@ module Api
|
|||||||
|
|
||||||
def filter_templates(templates, params)
|
def filter_templates(templates, params)
|
||||||
templates = Templates.search(templates, params[:q])
|
templates = Templates.search(templates, params[:q])
|
||||||
templates = params[:archived] ? templates.archived : templates.active
|
templates = params[:archived].in?(['true', true]) ? templates.archived : templates.active
|
||||||
templates = templates.where(external_id: params[:application_key]) if params[:application_key].present?
|
templates = templates.where(external_id: params[:application_key]) if params[:application_key].present?
|
||||||
templates = templates.where(external_id: params[:external_id]) if params[:external_id].present?
|
templates = templates.where(external_id: params[:external_id]) if params[:external_id].present?
|
||||||
templates = templates.joins(:folder).where(folder: { name: params[:folder] }) if params[:folder].present?
|
templates = templates.joins(:folder).where(folder: { name: params[:folder] }) if params[:folder].present?
|
||||||
@@ -95,13 +98,14 @@ module Api
|
|||||||
def template_params
|
def template_params
|
||||||
permitted_params = [
|
permitted_params = [
|
||||||
:name,
|
:name,
|
||||||
|
:external_id,
|
||||||
{
|
{
|
||||||
submitters: [%i[name uuid]],
|
submitters: [%i[name uuid is_requester invite_by_uuid linked_to_uuid email]],
|
||||||
fields: [[:uuid, :submitter_uuid, :name, :type,
|
fields: [[:uuid, :submitter_uuid, :name, :type,
|
||||||
:required, :readonly, :default_value,
|
:required, :readonly, :default_value,
|
||||||
:title, :description,
|
:title, :description,
|
||||||
{ preferences: {},
|
{ preferences: {},
|
||||||
conditions: [%i[field_uuid value action]],
|
conditions: [%i[field_uuid value action operation]],
|
||||||
options: [%i[value uuid]],
|
options: [%i[value uuid]],
|
||||||
validation: %i[message pattern],
|
validation: %i[message pattern],
|
||||||
areas: [%i[x y w h cell_w attachment_uuid option_uuid page]] }]]
|
areas: [%i[x y w h cell_w attachment_uuid option_uuid page]] }]]
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Api
|
||||||
|
class ToolsController < ApiBaseController
|
||||||
|
skip_authorization_check
|
||||||
|
|
||||||
|
def merge
|
||||||
|
files = params[:files] || []
|
||||||
|
|
||||||
|
return render json: { error: 'Files are required' }, status: :unprocessable_entity if files.blank?
|
||||||
|
return render json: { error: 'At least 2 files are required' }, status: :unprocessable_entity if files.size < 2
|
||||||
|
|
||||||
|
render json: {
|
||||||
|
data: Base64.encode64(PdfUtils.merge(files.map { |base64| StringIO.new(Base64.decode64(base64)) }).string)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def verify
|
||||||
|
file = Base64.decode64(params[:file])
|
||||||
|
pdf = HexaPDF::Document.new(io: StringIO.new(file))
|
||||||
|
|
||||||
|
trusted_certs = Accounts.load_trusted_certs(current_account)
|
||||||
|
is_checksum_found = CompletedDocument.exists?(sha256: Base64.urlsafe_encode64(Digest::SHA256.digest(file)))
|
||||||
|
|
||||||
|
render json: {
|
||||||
|
checksum_status: is_checksum_found ? 'verified' : 'not_found',
|
||||||
|
signatures: pdf.signatures.map do |sig|
|
||||||
|
{
|
||||||
|
verification_result: sig.verify(trusted_certs:).messages,
|
||||||
|
signer_name: sig.signer_name,
|
||||||
|
signing_reason: sig.signing_reason,
|
||||||
|
signing_time: sig.signing_time,
|
||||||
|
signature_type: sig.signature_type
|
||||||
|
}
|
||||||
|
end
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Api
|
||||||
|
class UsersController < ApiBaseController
|
||||||
|
authorize_resource :current_user
|
||||||
|
|
||||||
|
def show
|
||||||
|
render json: current_user.as_json(only: %i[id first_name last_name email])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -12,6 +12,6 @@ class ApiSettingsController < ApplicationController
|
|||||||
|
|
||||||
current_user.access_token.save!
|
current_user.access_token.save!
|
||||||
|
|
||||||
redirect_back(fallback_location: settings_api_index_path, notice: 'API token as been updated.')
|
redirect_back(fallback_location: settings_api_index_path, notice: I18n.t('api_token_has_been_updated'))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -8,12 +8,14 @@ class ApplicationController < ActionController::Base
|
|||||||
|
|
||||||
check_authorization unless: :devise_controller?
|
check_authorization unless: :devise_controller?
|
||||||
|
|
||||||
|
around_action :with_locale
|
||||||
before_action :sign_in_for_demo, if: -> { Docuseal.demo? }
|
before_action :sign_in_for_demo, if: -> { Docuseal.demo? }
|
||||||
before_action :maybe_redirect_to_setup, unless: :signed_in?
|
before_action :maybe_redirect_to_setup, unless: :signed_in?
|
||||||
before_action :authenticate_user!, unless: :devise_controller?
|
before_action :authenticate_user!, unless: :devise_controller?
|
||||||
|
|
||||||
helper_method :button_title,
|
helper_method :button_title,
|
||||||
:current_account,
|
:current_account,
|
||||||
|
:form_link_host,
|
||||||
:svg_icon
|
:svg_icon
|
||||||
|
|
||||||
impersonates :user, with: ->(uuid) { User.find_by(uuid:) }
|
impersonates :user, with: ->(uuid) { User.find_by(uuid:) }
|
||||||
@@ -37,6 +39,10 @@ class ApplicationController < ActionController::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
def default_url_options
|
def default_url_options
|
||||||
|
if request.domain == 'docuseal.com'
|
||||||
|
return { host: 'docuseal.com', protocol: ENV['FORCE_SSL'].present? ? 'https' : 'http' }
|
||||||
|
end
|
||||||
|
|
||||||
Docuseal.default_url_options
|
Docuseal.default_url_options
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -51,7 +57,18 @@ class ApplicationController < ActionController::Base
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def with_locale(&)
|
||||||
|
return yield unless current_account
|
||||||
|
|
||||||
|
locale = params[:lang].presence if Rails.env.development?
|
||||||
|
locale ||= current_account.locale
|
||||||
|
|
||||||
|
I18n.with_locale(locale, &)
|
||||||
|
end
|
||||||
|
|
||||||
def with_browser_locale(&)
|
def with_browser_locale(&)
|
||||||
|
return yield if I18n.locale != :'en-US' && I18n.locale != :en
|
||||||
|
|
||||||
locale = params[:lang].presence
|
locale = params[:lang].presence
|
||||||
locale ||= request.env['HTTP_ACCEPT_LANGUAGE'].to_s[BROWSER_LOCALE_REGEXP].to_s
|
locale ||= request.env['HTTP_ACCEPT_LANGUAGE'].to_s[BROWSER_LOCALE_REGEXP].to_s
|
||||||
|
|
||||||
@@ -79,7 +96,8 @@ class ApplicationController < ActionController::Base
|
|||||||
redirect_to setup_index_path unless User.exists?
|
redirect_to setup_index_path unless User.exists?
|
||||||
end
|
end
|
||||||
|
|
||||||
def button_title(title: 'Submit', disabled_with: 'Submitting', title_class: '', icon: nil, icon_disabled: nil)
|
def button_title(title: I18n.t('submit'), disabled_with: I18n.t('submitting'), title_class: '', icon: nil,
|
||||||
|
icon_disabled: nil)
|
||||||
render_to_string(partial: 'shared/button_title',
|
render_to_string(partial: 'shared/button_title',
|
||||||
locals: { title:, disabled_with:, title_class:, icon:, icon_disabled: })
|
locals: { title:, disabled_with:, title_class:, icon:, icon_disabled: })
|
||||||
end
|
end
|
||||||
@@ -87,4 +105,14 @@ class ApplicationController < ActionController::Base
|
|||||||
def svg_icon(icon_name, class: '')
|
def svg_icon(icon_name, class: '')
|
||||||
render_to_string(partial: "icons/#{icon_name}", locals: { class: })
|
render_to_string(partial: "icons/#{icon_name}", locals: { class: })
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def form_link_host
|
||||||
|
Docuseal.default_url_options[:host]
|
||||||
|
end
|
||||||
|
|
||||||
|
def maybe_redirect_com
|
||||||
|
return if request.domain != 'docuseal.co'
|
||||||
|
|
||||||
|
redirect_to request.url.gsub('.co/', '.com/'), allow_other_host: true, status: :moved_permanently
|
||||||
|
end
|
||||||
end
|
end
|
||||||
@@ -33,7 +33,7 @@ class DashboardController < ApplicationController
|
|||||||
account_id: current_user.account_id,
|
account_id: current_user.account_id,
|
||||||
key: AccountConfig::FORCE_MFA)
|
key: AccountConfig::FORCE_MFA)
|
||||||
|
|
||||||
redirect_to mfa_setup_path, notice: 'Setup 2FA to continue'
|
redirect_to mfa_setup_path, notice: I18n.t('setup_2fa_to_continue')
|
||||||
end
|
end
|
||||||
|
|
||||||
def maybe_render_landing
|
def maybe_render_landing
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ class EmailSmtpSettingsController < ApplicationController
|
|||||||
|
|
||||||
def create
|
def create
|
||||||
if @encrypted_config.update(email_configs)
|
if @encrypted_config.update(email_configs)
|
||||||
SettingsMailer.smtp_successful_setup(@encrypted_config.value['from_email']).deliver_now!
|
SettingsMailer.smtp_successful_setup(@encrypted_config.value['from_email'] || current_user.email).deliver_now!
|
||||||
|
|
||||||
redirect_to settings_email_index_path, notice: 'Changes have been saved'
|
redirect_to settings_email_index_path, notice: I18n.t('changes_have_been_saved')
|
||||||
else
|
else
|
||||||
render :index, status: :unprocessable_entity
|
render :index, status: :unprocessable_entity
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class EmbedScriptsController < ActionController::Metal
|
||||||
|
DUMMY_SCRIPT = <<~JAVASCRIPT.freeze
|
||||||
|
const DummyBuilder = class extends HTMLElement {
|
||||||
|
connectedCallback() {
|
||||||
|
this.innerHTML = `
|
||||||
|
<div style="text-align: center; padding: 20px; font-family: Arial, sans-serif;">
|
||||||
|
<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;">
|
||||||
|
Learn More
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const DummyForm = class extends DummyBuilder {};
|
||||||
|
|
||||||
|
if (!window.customElements.get('docuseal-builder')) {
|
||||||
|
window.customElements.define('docuseal-builder', DummyBuilder);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!window.customElements.get('docuseal-form')) {
|
||||||
|
window.customElements.define('docuseal-form', DummyForm);
|
||||||
|
}
|
||||||
|
JAVASCRIPT
|
||||||
|
|
||||||
|
def show
|
||||||
|
headers['Content-Type'] = 'application/javascript'
|
||||||
|
|
||||||
|
self.response_body = DUMMY_SCRIPT
|
||||||
|
|
||||||
|
self.status = 200
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
class ErrorsController < ActionController::Base
|
class ErrorsController < ActionController::Base
|
||||||
ENTERPRISE_FEATURE_MESSAGE =
|
ENTERPRISE_FEATURE_MESSAGE =
|
||||||
'This feature is available in Pro Edition: https://www.docuseal.co/pricing'
|
'This feature is available in Pro Edition: https://www.docuseal.com/pricing'
|
||||||
|
|
||||||
ENTERPRISE_PATHS = [
|
ENTERPRISE_PATHS = [
|
||||||
'/templates/html',
|
'/templates/html',
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ class EsignSettingsController < ApplicationController
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
prepend_before_action :maybe_redirect_com, only: %i[show]
|
||||||
|
|
||||||
before_action :load_encrypted_config
|
before_action :load_encrypted_config
|
||||||
authorize_resource :encrypted_config, parent: false, only: %i[new create]
|
authorize_resource :encrypted_config, parent: false, only: %i[new create]
|
||||||
authorize_resource :encrypted_config, only: %i[update destroy show]
|
authorize_resource :encrypted_config, only: %i[update destroy show]
|
||||||
@@ -21,9 +23,9 @@ class EsignSettingsController < ApplicationController
|
|||||||
default_pkcs = GenerateCertificate.load_pkcs(cert_data) if cert_data['cert'].present?
|
default_pkcs = GenerateCertificate.load_pkcs(cert_data) if cert_data['cert'].present?
|
||||||
|
|
||||||
custom_pkcs_list = (cert_data['custom'] || []).map do |e|
|
custom_pkcs_list = (cert_data['custom'] || []).map do |e|
|
||||||
{ 'pkcs' => OpenSSL::PKCS12.new(Base64.urlsafe_decode64(e['data']), e['password'].to_s),
|
pkcs = e['data'].present? ? OpenSSL::PKCS12.new(Base64.urlsafe_decode64(e['data']), e['password'].to_s) : nil
|
||||||
'name' => e['name'],
|
|
||||||
'status' => e['status'] }
|
{ 'pkcs' => pkcs, 'name' => e['name'], 'status' => e['status'] }
|
||||||
end
|
end
|
||||||
|
|
||||||
@pkcs_list = [
|
@pkcs_list = [
|
||||||
@@ -48,7 +50,7 @@ class EsignSettingsController < ApplicationController
|
|||||||
if (@encrypted_config.value && @encrypted_config.value['custom']&.any? { |e| e['name'] == @cert_record.name }) ||
|
if (@encrypted_config.value && @encrypted_config.value['custom']&.any? { |e| e['name'] == @cert_record.name }) ||
|
||||||
@cert_record.name == DEFAULT_CERT_NAME
|
@cert_record.name == DEFAULT_CERT_NAME
|
||||||
|
|
||||||
@cert_record.errors.add(:name, 'already exists')
|
@cert_record.errors.add(:name, I18n.t('already_exists'))
|
||||||
|
|
||||||
return render turbo_stream: turbo_stream.replace(:modal, template: 'esign_settings/new'),
|
return render turbo_stream: turbo_stream.replace(:modal, template: 'esign_settings/new'),
|
||||||
status: :unprocessable_entity
|
status: :unprocessable_entity
|
||||||
@@ -56,7 +58,7 @@ class EsignSettingsController < ApplicationController
|
|||||||
|
|
||||||
save_new_cert!(@encrypted_config, @cert_record)
|
save_new_cert!(@encrypted_config, @cert_record)
|
||||||
|
|
||||||
redirect_to settings_esign_path, notice: 'Certificate has been successfully added!'
|
redirect_to settings_esign_path, notice: I18n.t('certificate_has_been_successfully_added')
|
||||||
rescue OpenSSL::PKCS12::PKCS12Error => e
|
rescue OpenSSL::PKCS12::PKCS12Error => e
|
||||||
Rollbar.error(e) if defined?(Rollbar)
|
Rollbar.error(e) if defined?(Rollbar)
|
||||||
|
|
||||||
@@ -66,14 +68,20 @@ class EsignSettingsController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
@encrypted_config.value['custom'].each { |e| e['status'] = 'validate' }
|
@encrypted_config.value['custom'].to_a.each { |e| e['status'] = 'validate' }
|
||||||
|
|
||||||
custom_cert_data = @encrypted_config.value['custom'].find { |e| e['name'] == params[:name] }
|
custom_cert_data = @encrypted_config.value['custom'].to_a.find { |e| e['name'] == params[:name] }
|
||||||
custom_cert_data['status'] = 'default' if custom_cert_data
|
|
||||||
|
if custom_cert_data
|
||||||
|
custom_cert_data['status'] = 'default'
|
||||||
|
elsif params[:name] == Docuseal::AATL_CERT_NAME
|
||||||
|
@encrypted_config.value['custom'] ||= []
|
||||||
|
@encrypted_config.value['custom'] << { 'name' => params[:name], 'status' => 'default' }
|
||||||
|
end
|
||||||
|
|
||||||
@encrypted_config.save!
|
@encrypted_config.save!
|
||||||
|
|
||||||
redirect_to settings_esign_path, notice: 'Default certificate has been selected'
|
redirect_to settings_esign_path, notice: I18n.t('default_certificate_has_been_selected')
|
||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
@@ -81,7 +89,7 @@ class EsignSettingsController < ApplicationController
|
|||||||
|
|
||||||
@encrypted_config.save!
|
@encrypted_config.save!
|
||||||
|
|
||||||
redirect_to settings_esign_path, notice: 'Certificate has been removed'
|
redirect_to settings_esign_path, notice: I18n.t('certificate_has_been_removed')
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|||||||
@@ -18,11 +18,11 @@ class MfaSetupController < ApplicationController
|
|||||||
current_user.otp_required_for_login = true
|
current_user.otp_required_for_login = true
|
||||||
current_user.save!
|
current_user.save!
|
||||||
|
|
||||||
redirect_to settings_profile_index_path, notice: '2FA has been configured'
|
redirect_to settings_profile_index_path, notice: I18n.t('2fa_has_been_configured')
|
||||||
else
|
else
|
||||||
@provision_url = current_user.otp_provisioning_uri(current_user.email, issuer: Docuseal.product_name)
|
@provision_url = current_user.otp_provisioning_uri(current_user.email, issuer: Docuseal.product_name)
|
||||||
|
|
||||||
@error_message = 'Code is invalid'
|
@error_message = I18n.t('code_is_invalid')
|
||||||
|
|
||||||
render turbo_stream: turbo_stream.replace(:mfa_form, partial: 'mfa_setup/form'), status: :unprocessable_entity
|
render turbo_stream: turbo_stream.replace(:mfa_form, partial: 'mfa_setup/form'), status: :unprocessable_entity
|
||||||
end
|
end
|
||||||
@@ -32,9 +32,9 @@ class MfaSetupController < ApplicationController
|
|||||||
if current_user.validate_and_consume_otp!(params[:otp_attempt])
|
if current_user.validate_and_consume_otp!(params[:otp_attempt])
|
||||||
current_user.update!(otp_required_for_login: false, otp_secret: nil)
|
current_user.update!(otp_required_for_login: false, otp_secret: nil)
|
||||||
|
|
||||||
redirect_to settings_profile_index_path, notice: '2FA has been removed'
|
redirect_to settings_profile_index_path, notice: I18n.t('2fa_has_been_removed')
|
||||||
else
|
else
|
||||||
@error_message = 'Code is invalid'
|
@error_message = I18n.t('code_is_invalid')
|
||||||
|
|
||||||
render turbo_stream: turbo_stream.replace(:modal, template: 'mfa_setup/edit'), status: :unprocessable_entity
|
render turbo_stream: turbo_stream.replace(:modal, template: 'mfa_setup/edit'), status: :unprocessable_entity
|
||||||
end
|
end
|
||||||
@@ -43,7 +43,7 @@ class MfaSetupController < ApplicationController
|
|||||||
private
|
private
|
||||||
|
|
||||||
def set_provision_url
|
def set_provision_url
|
||||||
return redirect_to root_path, alert: '2FA has been set up already' if current_user.otp_required_for_login
|
return redirect_to root_path, alert: I18n.t('2fa_has_been_set_up_already') if current_user.otp_required_for_login
|
||||||
|
|
||||||
current_user.otp_secret ||= User.generate_otp_secret
|
current_user.otp_secret ||= User.generate_otp_secret
|
||||||
|
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ class NotificationsSettingsController < ApplicationController
|
|||||||
|
|
||||||
def create
|
def create
|
||||||
if @account_config.value.present? ? @account_config.save : @account_config.delete
|
if @account_config.value.present? ? @account_config.save : @account_config.delete
|
||||||
redirect_back fallback_location: settings_notifications_path, notice: 'Changes have been saved'
|
redirect_back fallback_location: settings_notifications_path, notice: I18n.t('changes_have_been_saved')
|
||||||
else
|
else
|
||||||
redirect_back fallback_location: settings_notifications_path, alert: 'Unable to save'
|
redirect_back fallback_location: settings_notifications_path, alert: I18n.t('unable_to_save')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ class PersonalizationSettingsController < ApplicationController
|
|||||||
AccountConfig::SUBMITTER_INVITATION_EMAIL_KEY,
|
AccountConfig::SUBMITTER_INVITATION_EMAIL_KEY,
|
||||||
AccountConfig::SUBMITTER_DOCUMENTS_COPY_EMAIL_KEY,
|
AccountConfig::SUBMITTER_DOCUMENTS_COPY_EMAIL_KEY,
|
||||||
AccountConfig::SUBMITTER_COMPLETED_EMAIL_KEY,
|
AccountConfig::SUBMITTER_COMPLETED_EMAIL_KEY,
|
||||||
AccountConfig::FORM_COMPLETED_MESSAGE_KEY
|
AccountConfig::FORM_COMPLETED_MESSAGE_KEY,
|
||||||
|
*(Docuseal.multitenant? ? [] : [AccountConfig::POLICY_LINKS_KEY])
|
||||||
].freeze
|
].freeze
|
||||||
|
|
||||||
InvalidKey = Class.new(StandardError)
|
InvalidKey = Class.new(StandardError)
|
||||||
@@ -30,7 +31,7 @@ class PersonalizationSettingsController < ApplicationController
|
|||||||
@account_config.save!
|
@account_config.save!
|
||||||
end
|
end
|
||||||
|
|
||||||
redirect_back(fallback_location: settings_personalization_path, notice: 'Settings have been saved.')
|
redirect_back(fallback_location: settings_personalization_path, notice: I18n.t('settings_have_been_saved'))
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ class PreviewDocumentPageController < ActionController::API
|
|||||||
|
|
||||||
return head :not_found unless attachment
|
return head :not_found unless attachment
|
||||||
|
|
||||||
preview_image = attachment.preview_images.joins(:blob).find_by(blob: { filename: "#{params[:id]}#{FORMAT}" })
|
preview_image = attachment.preview_images.joins(:blob)
|
||||||
|
.find_by(blob: { filename: ["#{params[:id]}.png", "#{params[:id]}.jpg"] })
|
||||||
|
|
||||||
return redirect_to preview_image.url, allow_other_host: true if preview_image
|
return redirect_to preview_image.url, allow_other_host: true if preview_image
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ class ProfileController < ApplicationController
|
|||||||
|
|
||||||
def update_contact
|
def update_contact
|
||||||
if current_user.update(contact_params)
|
if current_user.update(contact_params)
|
||||||
redirect_to settings_profile_index_path, notice: 'Contact information has been updated'
|
redirect_to settings_profile_index_path, notice: I18n.t('contact_information_has_been_update')
|
||||||
else
|
else
|
||||||
render :index, status: :unprocessable_entity
|
render :index, status: :unprocessable_entity
|
||||||
end
|
end
|
||||||
@@ -18,7 +18,7 @@ class ProfileController < ApplicationController
|
|||||||
def update_password
|
def update_password
|
||||||
if current_user.update(password_params)
|
if current_user.update(password_params)
|
||||||
bypass_sign_in(current_user)
|
bypass_sign_in(current_user)
|
||||||
redirect_to settings_profile_index_path, notice: 'Password has been changed'
|
redirect_to settings_profile_index_path, notice: I18n.t('password_has_been_changed')
|
||||||
else
|
else
|
||||||
render :index, status: :unprocessable_entity
|
render :index, status: :unprocessable_entity
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ class SendSubmissionEmailController < ApplicationController
|
|||||||
skip_before_action :verify_authenticity_token
|
skip_before_action :verify_authenticity_token
|
||||||
skip_authorization_check
|
skip_authorization_check
|
||||||
|
|
||||||
|
SEND_DURATION = 30.minutes
|
||||||
|
|
||||||
def success; end
|
def success; end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
@@ -23,7 +25,10 @@ class SendSubmissionEmailController < ApplicationController
|
|||||||
|
|
||||||
RateLimit.call("send-email-#{@submitter.id}", limit: 2, ttl: 5.minutes)
|
RateLimit.call("send-email-#{@submitter.id}", limit: 2, ttl: 5.minutes)
|
||||||
|
|
||||||
SubmitterMailer.documents_copy_email(@submitter, sig: true).deliver_later!
|
unless EmailEvent.exists?(tag: :submitter_documents_copy, email: @submitter.email, emailable: @submitter,
|
||||||
|
event_type: :send, created_at: SEND_DURATION.ago..Time.current)
|
||||||
|
SubmitterMailer.documents_copy_email(@submitter, sig: true).deliver_later!
|
||||||
|
end
|
||||||
|
|
||||||
respond_to do |f|
|
respond_to do |f|
|
||||||
f.html { redirect_to success_send_submission_email_index_path }
|
f.html { redirect_to success_send_submission_email_index_path }
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ class SessionsController < Devise::SessionsController
|
|||||||
Rollbar.warning('Sign in new user') if defined?(Rollbar)
|
Rollbar.warning('Sign in new user') if defined?(Rollbar)
|
||||||
|
|
||||||
return redirect_to new_registration_path(sign_up: true, user: sign_in_params.slice(:email)),
|
return redirect_to new_registration_path(sign_up: true, user: sign_in_params.slice(:email)),
|
||||||
notice: 'Create a new account'
|
notice: I18n.t('create_a_new_account')
|
||||||
end
|
end
|
||||||
|
|
||||||
if User.exists?(email:, otp_required_for_login: true) && sign_in_params[:otp_attempt].blank?
|
if User.exists?(email:, otp_required_for_login: true) && sign_in_params[:otp_attempt].blank?
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class SetupController < ApplicationController
|
|||||||
@encrypted_config = EncryptedConfig.new(encrypted_config_params)
|
@encrypted_config = EncryptedConfig.new(encrypted_config_params)
|
||||||
|
|
||||||
unless URI.parse(encrypted_config_params[:value].to_s).class.in?([URI::HTTP, URI::HTTPS])
|
unless URI.parse(encrypted_config_params[:value].to_s).class.in?([URI::HTTP, URI::HTTPS])
|
||||||
@encrypted_config.errors.add(:value, 'should be a valid URL')
|
@encrypted_config.errors.add(:value, I18n.t('should_be_a_valid_url'))
|
||||||
|
|
||||||
return render :index, status: :unprocessable_entity
|
return render :index, status: :unprocessable_entity
|
||||||
end
|
end
|
||||||
@@ -56,7 +56,7 @@ class SetupController < ApplicationController
|
|||||||
def account_params
|
def account_params
|
||||||
return {} unless params[:account]
|
return {} unless params[:account]
|
||||||
|
|
||||||
params.require(:account).permit(:name, :timezone)
|
params.require(:account).permit(:name, :timezone, :locale)
|
||||||
end
|
end
|
||||||
|
|
||||||
def encrypted_config_params
|
def encrypted_config_params
|
||||||
@@ -66,10 +66,10 @@ class SetupController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def redirect_to_root_if_signed
|
def redirect_to_root_if_signed
|
||||||
redirect_to root_path, notice: 'You are already signed in'
|
redirect_to root_path, notice: I18n.t('you_are_already_signed_in')
|
||||||
end
|
end
|
||||||
|
|
||||||
def ensure_first_user_not_created!
|
def ensure_first_user_not_created!
|
||||||
redirect_to new_user_session_path, notice: 'Please sign in.' if User.exists?
|
redirect_to new_user_session_path, notice: I18n.t('please_sign_in') if User.exists?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -7,37 +7,41 @@ class StartFormController < ApplicationController
|
|||||||
skip_authorization_check
|
skip_authorization_check
|
||||||
|
|
||||||
around_action :with_browser_locale, only: %i[show completed]
|
around_action :with_browser_locale, only: %i[show completed]
|
||||||
|
before_action :maybe_redirect_com, only: %i[show completed]
|
||||||
before_action :load_template
|
before_action :load_template
|
||||||
|
|
||||||
def show
|
def show
|
||||||
@submitter = @template.submissions.new(account_id: @template.account_id)
|
@submitter = @template.submissions.new(account_id: @template.account_id)
|
||||||
.submitters.new(uuid: @template.submitters.first['uuid'])
|
.submitters.new(uuid: (filter_undefined_submitters(@template).first ||
|
||||||
|
@template.submitters.first)['uuid'])
|
||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
return redirect_to start_form_path(@template.slug) if @template.archived_at?
|
return redirect_to start_form_path(@template.slug) if @template.archived_at?
|
||||||
|
|
||||||
@submitter = Submitter.where(submission: @template.submissions.where(archived_at: nil))
|
@submitter = find_or_initialize_submitter(@template, submitter_params)
|
||||||
.order(id: :desc)
|
|
||||||
.then { |rel| params[:resubmit].present? ? rel.where(completed_at: nil) : rel }
|
|
||||||
.find_or_initialize_by(**submitter_params.compact_blank)
|
|
||||||
|
|
||||||
if @submitter.completed_at?
|
if @submitter.completed_at?
|
||||||
redirect_to start_form_completed_path(@template.slug, email: submitter_params[:email])
|
redirect_to start_form_completed_path(@template.slug, email: submitter_params[:email])
|
||||||
else
|
else
|
||||||
if @template.submitters.to_a.size > 1 && @submitter.new_record?
|
if filter_undefined_submitters(@template).size > 1 && @submitter.new_record?
|
||||||
@error_message = 'Not found'
|
@error_message = I18n.t('not_found')
|
||||||
|
|
||||||
return render :show
|
return render :show
|
||||||
end
|
end
|
||||||
|
|
||||||
assign_submission_attributes(@submitter, @template) if @submitter.new_record?
|
if (is_new_record = @submitter.new_record?)
|
||||||
|
assign_submission_attributes(@submitter, @template)
|
||||||
|
|
||||||
is_new_record = @submitter.new_record?
|
Submissions::AssignDefinedSubmitters.call(@submitter.submission)
|
||||||
|
end
|
||||||
|
|
||||||
if @submitter.save
|
if @submitter.save
|
||||||
if is_new_record
|
if is_new_record
|
||||||
SendSubmissionCreatedWebhookRequestJob.perform_async({ 'submission_id' => @submitter.submission.id })
|
WebhookUrls.for_account_id(@submitter.account_id, 'submission.created').each do |webhook_url|
|
||||||
|
SendSubmissionCreatedWebhookRequestJob.perform_async('submission_id' => @submitter.submission_id,
|
||||||
|
'webhook_url_id' => webhook_url.id)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
redirect_to submit_form_path(@submitter.slug)
|
redirect_to submit_form_path(@submitter.slug)
|
||||||
@@ -55,14 +59,21 @@ class StartFormController < ApplicationController
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def find_or_initialize_submitter(template, submitter_params)
|
||||||
|
Submitter.where(submission: template.submissions.where(expire_at: Time.current..)
|
||||||
|
.or(template.submissions.where(expire_at: nil)).where(archived_at: nil))
|
||||||
|
.order(id: :desc)
|
||||||
|
.where(declined_at: nil)
|
||||||
|
.then { |rel| params[:resubmit].present? ? rel.where(completed_at: nil) : rel }
|
||||||
|
.find_or_initialize_by(**submitter_params.compact_blank)
|
||||||
|
end
|
||||||
|
|
||||||
def assign_submission_attributes(submitter, template)
|
def assign_submission_attributes(submitter, template)
|
||||||
resubmit_submitter =
|
resubmit_submitter =
|
||||||
if params[:resubmit].present?
|
(Submitter.where(submission: template.submissions).find_by(slug: params[:resubmit]) if params[:resubmit].present?)
|
||||||
Submitter.where(submission: @template.submissions).find_by(slug: params[:resubmit])
|
|
||||||
end
|
|
||||||
|
|
||||||
submitter.assign_attributes(
|
submitter.assign_attributes(
|
||||||
uuid: template.submitters.first['uuid'],
|
uuid: (filter_undefined_submitters(template).first || @template.submitters.first)['uuid'],
|
||||||
ip: request.remote_ip,
|
ip: request.remote_ip,
|
||||||
ua: request.user_agent,
|
ua: request.user_agent,
|
||||||
values: resubmit_submitter&.preferences&.fetch('default_values', nil) || {},
|
values: resubmit_submitter&.preferences&.fetch('default_values', nil) || {},
|
||||||
@@ -79,11 +90,18 @@ class StartFormController < ApplicationController
|
|||||||
submitter.submission ||= Submission.new(template:,
|
submitter.submission ||= Submission.new(template:,
|
||||||
account_id: template.account_id,
|
account_id: template.account_id,
|
||||||
template_submitters: template.submitters,
|
template_submitters: template.submitters,
|
||||||
|
submitters: [submitter],
|
||||||
source: :link)
|
source: :link)
|
||||||
|
|
||||||
|
submitter.account_id = submitter.submission.account_id
|
||||||
|
|
||||||
submitter
|
submitter
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def filter_undefined_submitters(template)
|
||||||
|
Templates.filter_undefined_submitters(template)
|
||||||
|
end
|
||||||
|
|
||||||
def submitter_params
|
def submitter_params
|
||||||
params.require(:submitter).permit(:email, :phone, :name).tap do |attrs|
|
params.require(:submitter).permit(:email, :phone, :name).tap do |attrs|
|
||||||
attrs[:email] = Submissions.normalize_email(attrs[:email])
|
attrs[:email] = Submissions.normalize_email(attrs[:email])
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ class StorageSettingsController < ApplicationController
|
|||||||
if @encrypted_config.update(storage_configs)
|
if @encrypted_config.update(storage_configs)
|
||||||
LoadActiveStorageConfigs.reload
|
LoadActiveStorageConfigs.reload
|
||||||
|
|
||||||
redirect_to settings_storage_index_path, notice: 'Changes have been saved'
|
redirect_to settings_storage_index_path, notice: I18n.t('changes_have_been_saved')
|
||||||
else
|
else
|
||||||
render :index, status: :unprocessable_entity
|
render :index, status: :unprocessable_entity
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -8,8 +8,18 @@ class SubmissionsArchivedController < ApplicationController
|
|||||||
@submissions = @submissions.where.not(archived_at: nil)
|
@submissions = @submissions.where.not(archived_at: nil)
|
||||||
.or(@submissions.where.not(templates: { archived_at: nil }))
|
.or(@submissions.where.not(templates: { archived_at: nil }))
|
||||||
.preload(:created_by_user, template: :author)
|
.preload(:created_by_user, template: :author)
|
||||||
@submissions = Submissions.search(@submissions, params[:q], search_template: true)
|
|
||||||
|
|
||||||
@pagy, @submissions = pagy(@submissions.preload(:submitters).order(id: :desc))
|
@submissions = @submissions.preload(:template_accesses) unless current_user.role.in?(%w[admin superadmin])
|
||||||
|
|
||||||
|
@submissions = Submissions.search(@submissions, params[:q], search_template: true)
|
||||||
|
@submissions = Submissions::Filter.call(@submissions, current_user, params)
|
||||||
|
|
||||||
|
@submissions = if params[:completed_at_from].present? || params[:completed_at_to].present?
|
||||||
|
@submissions.order(Submitter.arel_table[:completed_at].maximum.desc)
|
||||||
|
else
|
||||||
|
@submissions.order(id: :desc)
|
||||||
|
end
|
||||||
|
|
||||||
|
@pagy, @submissions = pagy(@submissions.preload(submitters: :start_form_submission_events))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -6,9 +6,18 @@ class SubmissionsController < ApplicationController
|
|||||||
|
|
||||||
load_and_authorize_resource :submission, only: %i[show destroy]
|
load_and_authorize_resource :submission, only: %i[show destroy]
|
||||||
|
|
||||||
|
prepend_before_action :maybe_redirect_com, only: %i[show]
|
||||||
|
|
||||||
def show
|
def show
|
||||||
@submission = Submissions.preload_with_pages(@submission)
|
@submission = Submissions.preload_with_pages(@submission)
|
||||||
|
|
||||||
|
unless @submission.submitters.all?(&:completed_at?)
|
||||||
|
ActiveRecord::Associations::Preloader.new(
|
||||||
|
records: [@submission],
|
||||||
|
associations: [submitters: :start_form_submission_events]
|
||||||
|
).call
|
||||||
|
end
|
||||||
|
|
||||||
render :show, layout: 'plain'
|
render :show, layout: 'plain'
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -39,26 +48,35 @@ class SubmissionsController < ApplicationController
|
|||||||
user: current_user,
|
user: current_user,
|
||||||
source: :invite,
|
source: :invite,
|
||||||
submitters_order: params[:preserve_order] == '1' ? 'preserved' : 'random',
|
submitters_order: params[:preserve_order] == '1' ? 'preserved' : 'random',
|
||||||
mark_as_sent: params[:send_email] == '1',
|
|
||||||
submissions_attrs: submissions_params[:submission].to_h.values,
|
submissions_attrs: submissions_params[:submission].to_h.values,
|
||||||
params: params.merge('send_completed_email' => true))
|
params: params.merge('send_completed_email' => true))
|
||||||
end
|
end
|
||||||
|
|
||||||
submissions.each do |submission|
|
enqueue_submission_created_webhooks(@template, submissions)
|
||||||
SendSubmissionCreatedWebhookRequestJob.perform_async({ 'submission_id' => submission.id })
|
|
||||||
end
|
|
||||||
|
|
||||||
Submissions.send_signature_requests(submissions)
|
Submissions.send_signature_requests(submissions)
|
||||||
|
|
||||||
redirect_to template_path(@template), notice: 'New recipients have been added'
|
redirect_to template_path(@template), notice: I18n.t('new_recipients_have_been_added')
|
||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
@submission.update!(archived_at: Time.current)
|
notice =
|
||||||
|
if params[:permanently].present?
|
||||||
|
@submission.destroy!
|
||||||
|
|
||||||
SendSubmissionArchivedWebhookRequestJob.perform_async('submission_id' => @submission.id)
|
I18n.t('submission_has_been_removed')
|
||||||
|
else
|
||||||
|
@submission.update!(archived_at: Time.current)
|
||||||
|
|
||||||
redirect_back(fallback_location: template_path(@submission.template), notice: 'Submission has been archived')
|
WebhookUrls.for_account_id(@submission.account_id, 'submission.archived').each do |webhook_url|
|
||||||
|
SendSubmissionArchivedWebhookRequestJob.perform_async('submission_id' => @submission.id,
|
||||||
|
'webhook_url_id' => webhook_url.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
I18n.t('submission_has_been_archived')
|
||||||
|
end
|
||||||
|
|
||||||
|
redirect_back(fallback_location: template_path(@submission.template), notice:)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
@@ -70,6 +88,15 @@ class SubmissionsController < ApplicationController
|
|||||||
template.save!
|
template.save!
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def enqueue_submission_created_webhooks(template, submissions)
|
||||||
|
WebhookUrls.for_account_id(template.account_id, 'submission.created').each do |webhook_url|
|
||||||
|
submissions.each do |submission|
|
||||||
|
SendSubmissionCreatedWebhookRequestJob.perform_async('submission_id' => submission.id,
|
||||||
|
'webhook_url_id' => webhook_url.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def submissions_params
|
def submissions_params
|
||||||
params.permit(submission: { submitters: [%i[uuid email phone name]] })
|
params.permit(submission: { submitters: [%i[uuid email phone name]] })
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -10,11 +10,20 @@ class SubmissionsDashboardController < ApplicationController
|
|||||||
.where(templates: { archived_at: nil })
|
.where(templates: { archived_at: nil })
|
||||||
.preload(:created_by_user, template: :author)
|
.preload(:created_by_user, template: :author)
|
||||||
|
|
||||||
|
@submissions = @submissions.preload(:template_accesses) unless current_user.role.in?(%w[admin superadmin])
|
||||||
|
|
||||||
@submissions = Submissions.search(@submissions, params[:q], search_template: true)
|
@submissions = Submissions.search(@submissions, params[:q], search_template: true)
|
||||||
|
@submissions = Submissions::Filter.call(@submissions, current_user, params)
|
||||||
|
|
||||||
@submissions = @submissions.pending if params[:status] == 'pending'
|
@submissions = @submissions.pending if params[:status] == 'pending'
|
||||||
@submissions = @submissions.completed if params[:status] == 'completed'
|
@submissions = @submissions.completed if params[:status] == 'completed'
|
||||||
|
|
||||||
@pagy, @submissions = pagy(@submissions.preload(:submitters).order(id: :desc))
|
@submissions = if params[:completed_at_from].present? || params[:completed_at_to].present?
|
||||||
|
@submissions.order(Submitter.arel_table[:completed_at].maximum.desc)
|
||||||
|
else
|
||||||
|
@submissions.order(id: :desc)
|
||||||
|
end
|
||||||
|
|
||||||
|
@pagy, @submissions = pagy(@submissions.preload(submitters: :start_form_submission_events))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -33,7 +33,7 @@ class SubmissionsDownloadController < ApplicationController
|
|||||||
return head :not_found
|
return head :not_found
|
||||||
end
|
end
|
||||||
|
|
||||||
if params[:combined]
|
if params[:combined] == 'true'
|
||||||
url = build_combined_url(submitter)
|
url = build_combined_url(submitter)
|
||||||
|
|
||||||
if url
|
if url
|
||||||
@@ -53,8 +53,15 @@ class SubmissionsDownloadController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def build_urls(submitter)
|
def build_urls(submitter)
|
||||||
|
filename_format = AccountConfig.find_or_initialize_by(account_id: submitter.account_id,
|
||||||
|
key: AccountConfig::DOCUMENT_FILENAME_FORMAT_KEY)&.value
|
||||||
|
|
||||||
Submitters.select_attachments_for_download(submitter).map do |attachment|
|
Submitters.select_attachments_for_download(submitter).map do |attachment|
|
||||||
ActiveStorage::Blob.proxy_url(attachment.blob, expires_at: FILES_TTL.from_now.to_i)
|
ActiveStorage::Blob.proxy_url(
|
||||||
|
attachment.blob,
|
||||||
|
expires_at: FILES_TTL.from_now.to_i,
|
||||||
|
filename: Submitters.build_document_filename(submitter, attachment.blob, filename_format)
|
||||||
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -65,6 +72,13 @@ class SubmissionsDownloadController < ApplicationController
|
|||||||
attachment = submitter.submission.combined_document_attachment
|
attachment = submitter.submission.combined_document_attachment
|
||||||
attachment ||= Submissions::GenerateCombinedAttachment.call(submitter)
|
attachment ||= Submissions::GenerateCombinedAttachment.call(submitter)
|
||||||
|
|
||||||
ActiveStorage::Blob.proxy_url(attachment.blob, expires_at: FILES_TTL.from_now.to_i)
|
filename_format = AccountConfig.find_or_initialize_by(account_id: submitter.account_id,
|
||||||
|
key: AccountConfig::DOCUMENT_FILENAME_FORMAT_KEY)&.value
|
||||||
|
|
||||||
|
ActiveStorage::Blob.proxy_url(
|
||||||
|
attachment.blob,
|
||||||
|
expires_at: FILES_TTL.from_now.to_i,
|
||||||
|
filename: Submitters.build_document_filename(submitter, attachment.blob, filename_format)
|
||||||
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class SubmissionsFiltersController < ApplicationController
|
||||||
|
ALLOWED_NAMES = %w[
|
||||||
|
author
|
||||||
|
completed_at
|
||||||
|
created_at
|
||||||
|
].freeze
|
||||||
|
|
||||||
|
skip_authorization_check
|
||||||
|
|
||||||
|
def show
|
||||||
|
return head :not_found unless ALLOWED_NAMES.include?(params[:name])
|
||||||
|
|
||||||
|
render params[:name]
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -4,6 +4,8 @@ class SubmissionsPreviewController < ApplicationController
|
|||||||
skip_before_action :authenticate_user!
|
skip_before_action :authenticate_user!
|
||||||
skip_authorization_check
|
skip_authorization_check
|
||||||
|
|
||||||
|
prepend_before_action :maybe_redirect_com, only: %i[show completed]
|
||||||
|
|
||||||
TTL = 40.minutes
|
TTL = 40.minutes
|
||||||
|
|
||||||
def show
|
def show
|
||||||
@@ -18,8 +20,8 @@ class SubmissionsPreviewController < ApplicationController
|
|||||||
|
|
||||||
@submission ||= Submission.find_by!(slug: params[:slug])
|
@submission ||= Submission.find_by!(slug: params[:slug])
|
||||||
|
|
||||||
if !@submission.submitters.all?(&:completed_at?) && current_user.blank?
|
if @submission.account.archived_at? || (!@submission.submitters.all?(&:completed_at?) && current_user.blank?)
|
||||||
raise ActionController::RoutingError, 'Not Found'
|
raise ActionController::RoutingError, I18n.t('not_found')
|
||||||
end
|
end
|
||||||
|
|
||||||
if !submission_valid_ttl?(@submission) && !signature_valid
|
if !submission_valid_ttl?(@submission) && !signature_valid
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class SubmissionsUnarchiveController < ApplicationController
|
||||||
|
load_and_authorize_resource :submission
|
||||||
|
|
||||||
|
def create
|
||||||
|
@submission.update!(archived_at: nil)
|
||||||
|
|
||||||
|
redirect_to submission_path(@submission), notice: I18n.t('submission_has_been_unarchived')
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -8,39 +8,40 @@ class SubmitFormController < ApplicationController
|
|||||||
skip_authorization_check
|
skip_authorization_check
|
||||||
|
|
||||||
CONFIG_KEYS = [].freeze
|
CONFIG_KEYS = [].freeze
|
||||||
PRELOAD_ALL_PAGES_AMOUNT = 200
|
|
||||||
|
|
||||||
def show
|
def show
|
||||||
@submitter = Submitter.find_by!(slug: params[:slug])
|
@submitter = Submitter.find_by!(slug: params[:slug])
|
||||||
|
|
||||||
|
submission = @submitter.submission
|
||||||
|
|
||||||
return redirect_to submit_form_completed_path(@submitter.slug) if @submitter.completed_at?
|
return redirect_to submit_form_completed_path(@submitter.slug) if @submitter.completed_at?
|
||||||
return render :archived if @submitter.submission.template.archived_at? || @submitter.submission.archived_at?
|
return render :archived if submission.template.archived_at? ||
|
||||||
|
submission.archived_at? ||
|
||||||
|
@submitter.account.archived_at?
|
||||||
|
return render :expired if submission.expired?
|
||||||
|
return render :declined if @submitter.declined_at?
|
||||||
|
return render :awaiting if submission.template.preferences['submitters_order'] == 'preserved' &&
|
||||||
|
!Submitters.current_submitter_order?(@submitter)
|
||||||
|
|
||||||
ActiveRecord::Associations::Preloader.new(
|
Submitters.preload_with_pages(@submitter)
|
||||||
records: [@submitter],
|
|
||||||
associations: [submission: [:template, { template_schema_documents: :blob }]]
|
|
||||||
).call
|
|
||||||
|
|
||||||
total_pages =
|
|
||||||
@submitter.submission.template_schema_documents.sum { |e| e.metadata.dig('pdf', 'number_of_pages').to_i }
|
|
||||||
|
|
||||||
if total_pages < PRELOAD_ALL_PAGES_AMOUNT
|
|
||||||
ActiveRecord::Associations::Preloader.new(
|
|
||||||
records: @submitter.submission.template_schema_documents,
|
|
||||||
associations: [:blob, { preview_images_attachments: :blob }]
|
|
||||||
).call
|
|
||||||
end
|
|
||||||
|
|
||||||
Submitters::MaybeUpdateDefaultValues.call(@submitter, current_user)
|
Submitters::MaybeUpdateDefaultValues.call(@submitter, current_user)
|
||||||
|
|
||||||
@attachments_index = ActiveStorage::Attachment.where(record: @submitter.submission.submitters, name: :attachments)
|
@attachments_index = build_attachments_index(submission)
|
||||||
.preload(:blob).index_by(&:uuid)
|
|
||||||
|
|
||||||
@form_configs = Submitters::FormConfigs.call(@submitter, CONFIG_KEYS)
|
@form_configs = Submitters::FormConfigs.call(@submitter, CONFIG_KEYS)
|
||||||
|
|
||||||
return unless @form_configs[:prefill_signature]
|
return unless @form_configs[:prefill_signature]
|
||||||
|
|
||||||
@signature_attachment =
|
if (user_signature = UserConfigs.load_signature(current_user))
|
||||||
|
@signature_attachment = ActiveStorage::Attachment.find_or_create_by!(
|
||||||
|
blob_id: user_signature.blob_id,
|
||||||
|
name: 'attachments',
|
||||||
|
record: @submitter
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
@signature_attachment ||=
|
||||||
Submitters::MaybeAssignDefaultBrowserSignature.call(@submitter, params, cookies, @attachments_index.values)
|
Submitters::MaybeAssignDefaultBrowserSignature.call(@submitter, params, cookies, @attachments_index.values)
|
||||||
|
|
||||||
@attachments_index[@signature_attachment.uuid] = @signature_attachment if @signature_attachment
|
@attachments_index[@signature_attachment.uuid] = @signature_attachment if @signature_attachment
|
||||||
@@ -50,13 +51,20 @@ class SubmitFormController < ApplicationController
|
|||||||
submitter = Submitter.find_by!(slug: params[:slug])
|
submitter = Submitter.find_by!(slug: params[:slug])
|
||||||
|
|
||||||
if submitter.completed_at?
|
if submitter.completed_at?
|
||||||
return render json: { error: 'Form has been completed already.' }, status: :unprocessable_entity
|
return render json: { error: I18n.t('form_has_been_completed_already') }, status: :unprocessable_entity
|
||||||
end
|
end
|
||||||
|
|
||||||
if submitter.template.archived_at? || submitter.submission.archived_at?
|
if submitter.template.archived_at? || submitter.submission.archived_at?
|
||||||
Rollbar.info("Archived template: #{submitter.template.id}") if defined?(Rollbar)
|
return render json: { error: I18n.t('form_has_been_archived') }, status: :unprocessable_entity
|
||||||
|
end
|
||||||
|
|
||||||
return render json: { error: 'Form has been archived.' }, status: :unprocessable_entity
|
if submitter.submission.expired?
|
||||||
|
return render json: { error: I18n.t('form_has_been_expired') }, status: :unprocessable_entity
|
||||||
|
end
|
||||||
|
|
||||||
|
if submitter.declined_at?
|
||||||
|
return render json: { error: I18n.t('form_has_been_declined') },
|
||||||
|
status: :unprocessable_entity
|
||||||
end
|
end
|
||||||
|
|
||||||
Submitters::SubmitValues.call(submitter, params, request)
|
Submitters::SubmitValues.call(submitter, params, request)
|
||||||
@@ -71,4 +79,11 @@ class SubmitFormController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def success; end
|
def success; end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def build_attachments_index(submission)
|
||||||
|
ActiveStorage::Attachment.where(record: submission.submitters, name: :attachments)
|
||||||
|
.preload(:blob).index_by(&:uuid)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class SubmitFormDeclineController < ApplicationController
|
||||||
|
skip_before_action :authenticate_user!
|
||||||
|
skip_authorization_check
|
||||||
|
|
||||||
|
def create
|
||||||
|
submitter = Submitter.find_by!(slug: params[:submit_form_slug])
|
||||||
|
|
||||||
|
return redirect_to submit_form_path(submitter.slug) if submitter.declined_at? ||
|
||||||
|
submitter.completed_at? ||
|
||||||
|
submitter.submission.archived_at? ||
|
||||||
|
submitter.submission.expired? ||
|
||||||
|
submitter.submission.template.archived_at?
|
||||||
|
|
||||||
|
ApplicationRecord.transaction do
|
||||||
|
submitter.update!(declined_at: Time.current)
|
||||||
|
|
||||||
|
SubmissionEvents.create_with_tracking_data(submitter, 'decline_form', request, { reason: params[:reason] })
|
||||||
|
end
|
||||||
|
|
||||||
|
user = submitter.submission.created_by_user || submitter.template.author
|
||||||
|
|
||||||
|
if user.user_configs.find_by(key: UserConfig::RECEIVE_DECLINED_EMAIL)&.value != false
|
||||||
|
SubmitterMailer.declined_email(submitter, user).deliver_later!
|
||||||
|
end
|
||||||
|
|
||||||
|
WebhookUrls.for_account_id(submitter.account_id, 'form.declined').each do |webhook_url|
|
||||||
|
SendFormDeclinedWebhookRequestJob.perform_async('submitter_id' => submitter.id,
|
||||||
|
'webhook_url_id' => webhook_url.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
redirect_to submit_form_path(submitter.slug)
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class SubmitFormDownloadController < ApplicationController
|
||||||
|
skip_before_action :authenticate_user!
|
||||||
|
skip_authorization_check
|
||||||
|
|
||||||
|
FILES_TTL = 5.minutes
|
||||||
|
|
||||||
|
def index
|
||||||
|
submitter = Submitter.find_by!(slug: params[:submit_form_slug])
|
||||||
|
|
||||||
|
return redirect_to submitter_download_index_path(submitter.slug) if submitter.completed_at?
|
||||||
|
|
||||||
|
return head :unprocessable_entity if submitter.declined_at? ||
|
||||||
|
submitter.submission.archived_at? ||
|
||||||
|
submitter.submission.expired? ||
|
||||||
|
submitter.submission.template.archived_at?
|
||||||
|
|
||||||
|
last_completed_submitter = submitter.submission.submitters
|
||||||
|
.where.not(id: submitter.id)
|
||||||
|
.where.not(completed_at: nil)
|
||||||
|
.max_by(&:completed_at)
|
||||||
|
|
||||||
|
attachments =
|
||||||
|
if last_completed_submitter
|
||||||
|
Submitters.select_attachments_for_download(last_completed_submitter)
|
||||||
|
else
|
||||||
|
submitter.submission.template.schema_documents.preload(:blob)
|
||||||
|
end
|
||||||
|
|
||||||
|
urls = attachments.map do |attachment|
|
||||||
|
ActiveStorage::Blob.proxy_url(attachment.blob, expires_at: FILES_TTL.from_now.to_i)
|
||||||
|
end
|
||||||
|
|
||||||
|
render json: urls
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class SubmitFormInviteController < ApplicationController
|
||||||
|
skip_before_action :authenticate_user!
|
||||||
|
skip_authorization_check
|
||||||
|
|
||||||
|
def create
|
||||||
|
submitter = Submitter.find_by!(slug: params[:submit_form_slug])
|
||||||
|
|
||||||
|
return head :unprocessable_entity unless can_invite?(submitter)
|
||||||
|
|
||||||
|
invite_submitters = filter_invite_submitters(submitter)
|
||||||
|
|
||||||
|
ApplicationRecord.transaction do
|
||||||
|
invite_submitters.each do |item|
|
||||||
|
attrs = submitters_attributes.find { |e| e[:uuid] == item['uuid'] }
|
||||||
|
|
||||||
|
next unless attrs
|
||||||
|
|
||||||
|
submitter.submission.submitters.create!(**attrs, account_id: submitter.account_id)
|
||||||
|
|
||||||
|
SubmissionEvents.create_with_tracking_data(submitter, 'invite_party', request, { uuid: submitter.uuid })
|
||||||
|
end
|
||||||
|
|
||||||
|
submitter.submission.update!(submitters_order: :preserved)
|
||||||
|
end
|
||||||
|
|
||||||
|
submitter.submission.submitters.reload
|
||||||
|
|
||||||
|
if invite_submitters.all? { |s| submitter.submission.submitters.any? { |e| e.uuid == s['uuid'] } }
|
||||||
|
Submitters::SubmitValues.call(submitter, ActionController::Parameters.new(completed: 'true'), request)
|
||||||
|
|
||||||
|
head :ok
|
||||||
|
else
|
||||||
|
head :unprocessable_entity
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def can_invite?(submitter)
|
||||||
|
!submitter.declined_at? &&
|
||||||
|
!submitter.completed_at? &&
|
||||||
|
!submitter.submission.archived_at? &&
|
||||||
|
!submitter.submission.expired? &&
|
||||||
|
!submitter.submission.template.archived_at?
|
||||||
|
end
|
||||||
|
|
||||||
|
def filter_invite_submitters(submitter)
|
||||||
|
(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'] }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def submitters_attributes
|
||||||
|
params.require(:submission).permit(submitters: [%i[uuid email]]).fetch(:submitters, [])
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -7,15 +7,17 @@ class SubmitFormValuesController < ApplicationController
|
|||||||
def index
|
def index
|
||||||
submitter = Submitter.find_by!(slug: params[:submit_form_slug])
|
submitter = Submitter.find_by!(slug: params[:submit_form_slug])
|
||||||
|
|
||||||
return render json: {} if submitter.completed_at?
|
return render json: {} if submitter.completed_at? || submitter.declined_at?
|
||||||
return render json: {} if submitter.submission.template.archived_at? || submitter.submission.archived_at?
|
return render json: {} if submitter.submission.template.archived_at? ||
|
||||||
|
submitter.submission.archived_at? ||
|
||||||
|
submitter.submission.expired?
|
||||||
|
|
||||||
value = submitter.values[params['field_uuid']]
|
value = submitter.values[params['field_uuid']]
|
||||||
attachment = submitter.attachments.where(created_at: params[:after]..).find_by(uuid: value) if value.present?
|
attachment = submitter.attachments.where(created_at: params[:after]..).find_by(uuid: value) if value.present?
|
||||||
|
|
||||||
render json: {
|
render json: {
|
||||||
value:,
|
value:,
|
||||||
attachment: attachment&.as_json(only: %i[uuid], methods: %i[url filename content_type])
|
attachment: attachment&.as_json(only: %i[uuid created_at], methods: %i[url filename content_type])
|
||||||
}, head: :ok
|
}, head: :ok
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -9,7 +9,8 @@ class SubmittersAutocompleteController < ApplicationController
|
|||||||
def index
|
def index
|
||||||
submitters = search_submitters(@submitters)
|
submitters = search_submitters(@submitters)
|
||||||
|
|
||||||
values = submitters.limit(LIMIT).group(SELECT_COLUMNS.join(', ')).pluck(SELECT_COLUMNS.join(', '))
|
arel_columns = SELECT_COLUMNS.map { |col| Submitter.arel_table[col] }
|
||||||
|
values = submitters.limit(LIMIT).group(arel_columns).pluck(arel_columns)
|
||||||
|
|
||||||
attrs = values.map { |row| SELECT_COLUMNS.zip(row).to_h }
|
attrs = values.map { |row| SELECT_COLUMNS.zip(row).to_h }
|
||||||
attrs = attrs.uniq { |e| e[params[:field]] } if params[:field].present?
|
attrs = attrs.uniq { |e| e[params[:field]] } if params[:field].present?
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class SubmittersController < ApplicationController
|
||||||
|
load_and_authorize_resource :submitter, only: %i[edit update]
|
||||||
|
|
||||||
|
def edit
|
||||||
|
@submitter_email_message =
|
||||||
|
if @submitter.preferences['email_message_uuid'].present?
|
||||||
|
@submitter.account.email_messages.find_by(uuid: @submitter.preferences['email_message_uuid'])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
submission = @submitter.submission
|
||||||
|
|
||||||
|
if @submitter.submission_events.exists?(event_type: 'start_form') || submission.archived_at? || submission.expired?
|
||||||
|
return redirect_back fallback_location: submission_path(submission), alert: I18n.t('submitter_cannot_be_updated')
|
||||||
|
end
|
||||||
|
|
||||||
|
if submitter_params.values.all?(&:blank?)
|
||||||
|
return redirect_back fallback_location: submission_path(submission),
|
||||||
|
alert: I18n.t('at_least_one_field_must_be_filled')
|
||||||
|
end
|
||||||
|
|
||||||
|
if params[:is_custom_message] != '1'
|
||||||
|
params.delete(:subject)
|
||||||
|
params.delete(:body)
|
||||||
|
end
|
||||||
|
|
||||||
|
submitter_preferences = Submitters.normalize_preferences(@submitter.account, current_user, params)
|
||||||
|
|
||||||
|
if submitter_preferences.key?('email_message_uuid')
|
||||||
|
@submitter.preferences['email_message_uuid'] = submitter_preferences['email_message_uuid']
|
||||||
|
end
|
||||||
|
|
||||||
|
assign_submitter_attrs(@submitter, submitter_params)
|
||||||
|
|
||||||
|
if @submitter.save
|
||||||
|
maybe_resend_email_sms(@submitter, params)
|
||||||
|
|
||||||
|
redirect_back fallback_location: submission_path(submission), notice: I18n.t('changes_have_been_saved')
|
||||||
|
else
|
||||||
|
redirect_back fallback_location: submission_path(submission), alert: I18n.t('unable_to_save')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def maybe_resend_email_sms(submitter, params)
|
||||||
|
if params[:send_email] == '1' && submitter.email.present?
|
||||||
|
is_sent_recently = Docuseal.multitenant? &&
|
||||||
|
EmailEvent.exists?(email: submitter.email,
|
||||||
|
tag: 'submitter_invitation',
|
||||||
|
emailable: submitter,
|
||||||
|
event_type: 'send',
|
||||||
|
created_at: 4.hours.ago..Time.current)
|
||||||
|
|
||||||
|
SendSubmitterInvitationEmailJob.perform_async('submitter_id' => submitter.id) unless is_sent_recently
|
||||||
|
end
|
||||||
|
|
||||||
|
return if submitter.phone.blank?
|
||||||
|
return unless params[:send_sms] == '1'
|
||||||
|
|
||||||
|
SendSubmitterInvitationSmsJob.perform_async('submitter_id' => submitter.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def assign_submitter_attrs(submitter, attrs)
|
||||||
|
submitter.phone = attrs[:phone].to_s.gsub(/[^0-9+]/, '') if attrs.key?(:phone)
|
||||||
|
|
||||||
|
submitter.email = Submissions.normalize_email(attrs[:email]) if attrs.key?(:email)
|
||||||
|
|
||||||
|
submitter.name = attrs[:name] if attrs.key?(:name)
|
||||||
|
|
||||||
|
submitter
|
||||||
|
end
|
||||||
|
|
||||||
|
def submitter_params
|
||||||
|
params.require(:submitter).permit(:email, :name, :phone).transform_values(&:strip)
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -6,11 +6,11 @@ class SubmittersSendEmailController < ApplicationController
|
|||||||
def create
|
def create
|
||||||
if Docuseal.multitenant? && SubmissionEvent.exists?(submitter: @submitter,
|
if Docuseal.multitenant? && SubmissionEvent.exists?(submitter: @submitter,
|
||||||
event_type: 'send_email',
|
event_type: 'send_email',
|
||||||
created_at: 24.hours.ago..Time.current)
|
created_at: 10.hours.ago..Time.current)
|
||||||
Rollbar.warning("Already sent: #{@submitter.id}") if defined?(Rollbar)
|
Rollbar.warning("Already sent: #{@submitter.id}") if defined?(Rollbar)
|
||||||
|
|
||||||
return redirect_back(fallback_location: submission_path(@submitter.submission),
|
return redirect_back(fallback_location: submission_path(@submitter.submission),
|
||||||
alert: 'Email has been sent already.')
|
alert: I18n.t('email_has_been_sent_already'))
|
||||||
end
|
end
|
||||||
|
|
||||||
SendSubmitterInvitationEmailJob.perform_async('submitter_id' => @submitter.id)
|
SendSubmitterInvitationEmailJob.perform_async('submitter_id' => @submitter.id)
|
||||||
@@ -18,6 +18,6 @@ class SubmittersSendEmailController < ApplicationController
|
|||||||
@submitter.sent_at ||= Time.current
|
@submitter.sent_at ||= Time.current
|
||||||
@submitter.save!
|
@submitter.save!
|
||||||
|
|
||||||
redirect_back(fallback_location: submission_path(@submitter.submission), notice: 'Email has been sent')
|
redirect_back(fallback_location: submission_path(@submitter.submission), notice: I18n.t('email_has_been_sent'))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -5,12 +5,12 @@ class TemplateDocumentsController < ApplicationController
|
|||||||
|
|
||||||
def create
|
def create
|
||||||
if params[:blobs].blank? && params[:files].blank?
|
if params[:blobs].blank? && params[:files].blank?
|
||||||
return render json: { error: 'File is missing' }, status: :unprocessable_entity
|
return render json: { error: I18n.t('file_is_missing') }, status: :unprocessable_entity
|
||||||
end
|
end
|
||||||
|
|
||||||
old_fields_hash = @template.fields.hash
|
old_fields_hash = @template.fields.hash
|
||||||
|
|
||||||
documents = Templates::CreateAttachments.call(@template, params)
|
documents = Templates::CreateAttachments.call(@template, params, extract_fields: true)
|
||||||
|
|
||||||
schema = documents.map do |doc|
|
schema = documents.map do |doc|
|
||||||
{ attachment_uuid: doc.uuid, name: doc.filename.base }
|
{ attachment_uuid: doc.uuid, name: doc.filename.base }
|
||||||
@@ -28,6 +28,6 @@ class TemplateDocumentsController < ApplicationController
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
rescue Templates::CreateAttachments::PdfEncrypted
|
rescue Templates::CreateAttachments::PdfEncrypted
|
||||||
render json: { error: 'PDF encrypted' }, status: :unprocessable_entity
|
render json: { error: 'PDF encrypted', status: 'pdf_encrypted' }, status: :unprocessable_entity
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -4,10 +4,10 @@ class TemplateFoldersController < ApplicationController
|
|||||||
load_and_authorize_resource :template_folder
|
load_and_authorize_resource :template_folder
|
||||||
|
|
||||||
def show
|
def show
|
||||||
@templates = @template_folder.templates.active.preload(:author).order(id: :desc)
|
@templates = @template_folder.templates.active.preload(:author, :template_accesses).order(id: :desc)
|
||||||
@templates = Templates.search(@templates, params[:q])
|
@templates = Templates.search(@templates, params[:q])
|
||||||
|
|
||||||
@pagy, @templates = pagy(@templates, items: 12)
|
@pagy, @templates = pagy(@templates, limit: 12)
|
||||||
end
|
end
|
||||||
|
|
||||||
def edit; end
|
def edit; end
|
||||||
@@ -15,9 +15,9 @@ class TemplateFoldersController < ApplicationController
|
|||||||
def update
|
def update
|
||||||
if @template_folder != current_account.default_template_folder &&
|
if @template_folder != current_account.default_template_folder &&
|
||||||
@template_folder.update(template_folder_params)
|
@template_folder.update(template_folder_params)
|
||||||
redirect_to folder_path(@template_folder), notice: 'Folder name has been updated'
|
redirect_to folder_path(@template_folder), notice: I18n.t('folder_name_has_been_updated')
|
||||||
else
|
else
|
||||||
redirect_to folder_path(@template_folder), alert: 'Unable to rename folder'
|
redirect_to folder_path(@template_folder), alert: I18n.t('unable_to_rename_folder')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ class TemplatesArchivedController < ApplicationController
|
|||||||
load_and_authorize_resource :template, parent: false
|
load_and_authorize_resource :template, parent: false
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@templates = @templates.where.not(archived_at: nil).preload(:author, :folder).order(id: :desc)
|
@templates = @templates.where.not(archived_at: nil).preload(:author, :folder, :template_accesses).order(id: :desc)
|
||||||
@templates = Templates.search(@templates, params[:q])
|
@templates = Templates.search(@templates, params[:q])
|
||||||
|
|
||||||
@pagy, @templates = pagy(@templates, items: 12)
|
@pagy, @templates = pagy(@templates, limit: 12)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -7,8 +7,15 @@ class TemplatesArchivedSubmissionsController < ApplicationController
|
|||||||
def index
|
def index
|
||||||
@submissions = @submissions.where.not(archived_at: nil)
|
@submissions = @submissions.where.not(archived_at: nil)
|
||||||
@submissions = Submissions.search(@submissions, params[:q], search_values: true)
|
@submissions = Submissions.search(@submissions, params[:q], search_values: true)
|
||||||
|
@submissions = Submissions::Filter.call(@submissions, current_user, params)
|
||||||
|
|
||||||
@pagy, @submissions = pagy(@submissions.preload(:submitters).order(id: :desc))
|
@submissions = if params[:completed_at_from].present? || params[:completed_at_to].present?
|
||||||
|
@submissions.order(Submitter.arel_table[:completed_at].maximum.desc)
|
||||||
|
else
|
||||||
|
@submissions.order(id: :desc)
|
||||||
|
end
|
||||||
|
|
||||||
|
@pagy, @submissions = pagy(@submissions.preload(submitters: :start_form_submission_events))
|
||||||
rescue ActiveRecord::RecordNotFound
|
rescue ActiveRecord::RecordNotFound
|
||||||
redirect_to root_path
|
redirect_to root_path
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -9,25 +9,34 @@ class TemplatesController < ApplicationController
|
|||||||
submissions = @template.submissions.accessible_by(current_ability)
|
submissions = @template.submissions.accessible_by(current_ability)
|
||||||
submissions = submissions.active if @template.archived_at.blank?
|
submissions = submissions.active if @template.archived_at.blank?
|
||||||
submissions = Submissions.search(submissions, params[:q], search_values: true)
|
submissions = Submissions.search(submissions, params[:q], search_values: true)
|
||||||
|
submissions = Submissions::Filter.call(submissions, current_user, params)
|
||||||
|
|
||||||
@base_submissions = submissions
|
@base_submissions = submissions
|
||||||
|
|
||||||
submissions = submissions.pending if params[:status] == 'pending'
|
submissions = submissions.pending if params[:status] == 'pending'
|
||||||
submissions = submissions.completed if params[:status] == 'completed'
|
submissions = submissions.completed if params[:status] == 'completed'
|
||||||
|
|
||||||
@pagy, @submissions = pagy(submissions.preload(:submitters).order(id: :desc))
|
submissions = if params[:completed_at_from].present? || params[:completed_at_to].present?
|
||||||
|
submissions.order(Submitter.arel_table[:completed_at].maximum.desc)
|
||||||
|
else
|
||||||
|
submissions.order(id: :desc)
|
||||||
|
end
|
||||||
|
|
||||||
|
submissions = submissions.preload(:template_accesses) unless current_user.role.in?(%w[admin superadmin])
|
||||||
|
|
||||||
|
@pagy, @submissions = pagy(submissions.preload(submitters: :start_form_submission_events))
|
||||||
rescue ActiveRecord::RecordNotFound
|
rescue ActiveRecord::RecordNotFound
|
||||||
redirect_to root_path
|
redirect_to root_path
|
||||||
end
|
end
|
||||||
|
|
||||||
def new
|
def new
|
||||||
@template.name = "#{@base_template.name} (Clone)" if @base_template
|
@template.name = "#{@base_template.name} (#{I18n.t('clone')})" if @base_template
|
||||||
end
|
end
|
||||||
|
|
||||||
def edit
|
def edit
|
||||||
ActiveRecord::Associations::Preloader.new(
|
ActiveRecord::Associations::Preloader.new(
|
||||||
records: [@template],
|
records: [@template],
|
||||||
associations: [schema_documents: { preview_images_attachments: :blob }]
|
associations: [schema_documents: [:blob, { preview_images_attachments: :blob }]]
|
||||||
).call
|
).call
|
||||||
|
|
||||||
@template_data =
|
@template_data =
|
||||||
@@ -43,6 +52,11 @@ class TemplatesController < ApplicationController
|
|||||||
|
|
||||||
def create
|
def create
|
||||||
if @base_template
|
if @base_template
|
||||||
|
ActiveRecord::Associations::Preloader.new(
|
||||||
|
records: [@base_template],
|
||||||
|
associations: [schema_documents: :preview_images_attachments]
|
||||||
|
).call
|
||||||
|
|
||||||
@template = Templates::Clone.call(@base_template, author: current_user,
|
@template = Templates::Clone.call(@base_template, author: current_user,
|
||||||
name: params.dig(:template, :name),
|
name: params.dig(:template, :name),
|
||||||
folder_name: params[:folder_name])
|
folder_name: params[:folder_name])
|
||||||
@@ -61,7 +75,7 @@ class TemplatesController < ApplicationController
|
|||||||
if @template.save
|
if @template.save
|
||||||
Templates::CloneAttachments.call(template: @template, original_template: @base_template) if @base_template
|
Templates::CloneAttachments.call(template: @template, original_template: @base_template) if @base_template
|
||||||
|
|
||||||
SendTemplateUpdatedWebhookRequestJob.perform_async('template_id' => @template.id)
|
enqueue_template_created_webhooks(@template)
|
||||||
|
|
||||||
maybe_redirect_to_template(@template)
|
maybe_redirect_to_template(@template)
|
||||||
else
|
else
|
||||||
@@ -72,7 +86,7 @@ class TemplatesController < ApplicationController
|
|||||||
def update
|
def update
|
||||||
@template.update!(template_params)
|
@template.update!(template_params)
|
||||||
|
|
||||||
SendTemplateUpdatedWebhookRequestJob.perform_async('template_id' => @template.id)
|
enqueue_template_updated_webhooks(@template)
|
||||||
|
|
||||||
head :ok
|
head :ok
|
||||||
end
|
end
|
||||||
@@ -82,13 +96,11 @@ class TemplatesController < ApplicationController
|
|||||||
if params[:permanently].present?
|
if params[:permanently].present?
|
||||||
@template.destroy!
|
@template.destroy!
|
||||||
|
|
||||||
Rollbar.info("Remove template: #{@template.id}") if defined?(Rollbar)
|
I18n.t('template_has_been_removed')
|
||||||
|
|
||||||
'Template has been removed.'
|
|
||||||
else
|
else
|
||||||
@template.update!(archived_at: Time.current)
|
@template.update!(archived_at: Time.current)
|
||||||
|
|
||||||
'Template has been archived.'
|
I18n.t('template_has_been_archived')
|
||||||
end
|
end
|
||||||
|
|
||||||
redirect_back(fallback_location: root_path, notice:)
|
redirect_back(fallback_location: root_path, notice:)
|
||||||
@@ -99,13 +111,13 @@ class TemplatesController < ApplicationController
|
|||||||
def template_params
|
def template_params
|
||||||
params.require(:template).permit(
|
params.require(:template).permit(
|
||||||
:name,
|
:name,
|
||||||
{ schema: [%i[attachment_uuid name]],
|
{ schema: [[:attachment_uuid, :name, { conditions: [%i[field_uuid value action operation]] }]],
|
||||||
submitters: [%i[name uuid]],
|
submitters: [%i[name uuid is_requester linked_to_uuid invite_by_uuid email]],
|
||||||
fields: [[:uuid, :submitter_uuid, :name, :type,
|
fields: [[:uuid, :submitter_uuid, :name, :type,
|
||||||
:required, :readonly, :default_value,
|
:required, :readonly, :default_value,
|
||||||
:title, :description,
|
:title, :description,
|
||||||
{ preferences: {},
|
{ preferences: {},
|
||||||
conditions: [%i[field_uuid value action]],
|
conditions: [%i[field_uuid value action operation]],
|
||||||
options: [%i[value uuid]],
|
options: [%i[value uuid]],
|
||||||
validation: %i[message pattern],
|
validation: %i[message pattern],
|
||||||
areas: [%i[x y w h cell_w attachment_uuid option_uuid page]] }]] }
|
areas: [%i[x y w h cell_w attachment_uuid option_uuid page]] }]] }
|
||||||
@@ -113,14 +125,29 @@ class TemplatesController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def authorized_clone_account_id?(account_id)
|
def authorized_clone_account_id?(account_id)
|
||||||
true_user.account_id.to_s == account_id.to_s || true_user.account.linked_accounts.exists?(id: account_id)
|
true_user.account_id.to_s == account_id.to_s ||
|
||||||
|
true_user.account.linked_accounts.accessible_by(current_ability).exists?(id: account_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
def maybe_redirect_to_template(template)
|
def maybe_redirect_to_template(template)
|
||||||
if template.account == current_account
|
if template.account == current_account
|
||||||
redirect_to(edit_template_path(@template))
|
redirect_to(edit_template_path(@template))
|
||||||
else
|
else
|
||||||
redirect_back(fallback_location: root_path, notice: 'Template has been clonned')
|
redirect_back(fallback_location: root_path, notice: I18n.t('template_has_been_cloned'))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def enqueue_template_created_webhooks(template)
|
||||||
|
WebhookUrls.for_account_id(template.account_id, 'template.created').each do |webhook_url|
|
||||||
|
SendTemplateCreatedWebhookRequestJob.perform_async('template_id' => template.id,
|
||||||
|
'webhook_url_id' => webhook_url.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def enqueue_template_updated_webhooks(template)
|
||||||
|
WebhookUrls.for_account_id(template.account_id, 'template.updated').each do |webhook_url|
|
||||||
|
SendTemplateUpdatedWebhookRequestJob.perform_async('template_id' => template.id,
|
||||||
|
'webhook_url_id' => webhook_url.id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -23,14 +23,14 @@ class TemplatesDashboardController < ApplicationController
|
|||||||
@template_folders = @template_folders.reject { |e| e.name == TemplateFolder::DEFAULT_NAME }
|
@template_folders = @template_folders.reject { |e| e.name == TemplateFolder::DEFAULT_NAME }
|
||||||
@templates = filter_templates(@templates)
|
@templates = filter_templates(@templates)
|
||||||
|
|
||||||
items =
|
limit =
|
||||||
if @template_folders.size < 4
|
if @template_folders.size < 4
|
||||||
TEMPLATES_PER_PAGE
|
TEMPLATES_PER_PAGE
|
||||||
else
|
else
|
||||||
(@template_folders.size < 7 ? 9 : 6)
|
(@template_folders.size < 7 ? 9 : 6)
|
||||||
end
|
end
|
||||||
|
|
||||||
@pagy, @templates = pagy(@templates, items:)
|
@pagy, @templates = pagy(@templates, limit:)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ class TemplatesDashboardController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def filter_templates(templates)
|
def filter_templates(templates)
|
||||||
rel = templates.active.preload(:author).order(id: :desc)
|
rel = templates.active.preload(:author, :template_accesses).order(id: :desc)
|
||||||
|
|
||||||
if params[:q].blank?
|
if params[:q].blank?
|
||||||
if Docuseal.multitenant? && !current_account.testing?
|
if Docuseal.multitenant? && !current_account.testing?
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class TemplatesDebugController < ApplicationController
|
||||||
|
load_and_authorize_resource :template
|
||||||
|
|
||||||
|
def show
|
||||||
|
attachment = @template.documents.first
|
||||||
|
|
||||||
|
pdf = HexaPDF::Document.new(io: StringIO.new(attachment.download))
|
||||||
|
|
||||||
|
fields = Templates::FindAcroFields.call(pdf, attachment)
|
||||||
|
|
||||||
|
attachment.metadata['pdf'] ||= {}
|
||||||
|
attachment.metadata['pdf']['fields'] = fields
|
||||||
|
|
||||||
|
@template.update!(fields: Templates::ProcessDocument.normalize_attachment_fields(@template, [attachment]))
|
||||||
|
|
||||||
|
ActiveRecord::Associations::Preloader.new(
|
||||||
|
records: [@template],
|
||||||
|
associations: [schema_documents: { preview_images_attachments: :blob }]
|
||||||
|
).call
|
||||||
|
|
||||||
|
@template_data =
|
||||||
|
@template.as_json.merge(
|
||||||
|
documents: @template.schema_documents.as_json(
|
||||||
|
methods: %i[metadata signed_uuid],
|
||||||
|
include: { preview_images: { methods: %i[url metadata filename] } }
|
||||||
|
)
|
||||||
|
).to_json
|
||||||
|
|
||||||
|
render 'templates/edit', layout: 'plain'
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -9,9 +9,9 @@ class TemplatesFoldersController < ApplicationController
|
|||||||
@template.folder = TemplateFolders.find_or_create_by_name(current_user, params[:name])
|
@template.folder = TemplateFolders.find_or_create_by_name(current_user, params[:name])
|
||||||
|
|
||||||
if @template.save
|
if @template.save
|
||||||
redirect_back(fallback_location: template_path(@template), notice: 'Document template has been moved')
|
redirect_back(fallback_location: template_path(@template), notice: I18n.t('document_template_has_been_moved'))
|
||||||
else
|
else
|
||||||
redirect_back(fallback_location: template_path(@template), notice: 'Unable to move template into folder')
|
redirect_back(fallback_location: template_path(@template), notice: I18n.t('unable_to_move_template_into_folder'))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class TemplatesFormPreviewController < ApplicationController
|
||||||
|
layout 'form'
|
||||||
|
|
||||||
|
load_and_authorize_resource :template
|
||||||
|
|
||||||
|
def show
|
||||||
|
@submitter = Submitter.new(uuid: params[:uuid] || @template.submitters.first['uuid'],
|
||||||
|
account: current_account,
|
||||||
|
submission: @template.submissions.new(template_submitters: @template.submitters,
|
||||||
|
account: current_account))
|
||||||
|
|
||||||
|
@submitter.submission.submitters = @template.submitters.map { |item| Submitter.new(uuid: item['uuid']) }
|
||||||
|
|
||||||
|
Submitters.preload_with_pages(@submitter)
|
||||||
|
|
||||||
|
@attachments_index = ActiveStorage::Attachment.where(record: @submitter.submission.submitters, name: :attachments)
|
||||||
|
.preload(:blob).index_by(&:uuid)
|
||||||
|
|
||||||
|
@form_configs = Submitters::FormConfigs.call(@submitter)
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -9,7 +9,7 @@ class TemplatesPreferencesController < ApplicationController
|
|||||||
authorize!(:update, @template)
|
authorize!(:update, @template)
|
||||||
|
|
||||||
@template.preferences = @template.preferences.merge(template_params[:preferences])
|
@template.preferences = @template.preferences.merge(template_params[:preferences])
|
||||||
@template.preferences = @template.preferences.reject { |_, v| v.is_a?(String) && v.blank? }
|
@template.preferences = @template.preferences.reject { |_, v| (v.is_a?(String) || v.is_a?(Hash)) && v.blank? }
|
||||||
@template.save!
|
@template.save!
|
||||||
|
|
||||||
head :ok
|
head :ok
|
||||||
@@ -22,12 +22,19 @@ class TemplatesPreferencesController < ApplicationController
|
|||||||
preferences: %i[bcc_completed request_email_subject request_email_body
|
preferences: %i[bcc_completed request_email_subject request_email_body
|
||||||
documents_copy_email_subject documents_copy_email_body
|
documents_copy_email_subject documents_copy_email_body
|
||||||
documents_copy_email_enabled documents_copy_email_attach_audit
|
documents_copy_email_enabled documents_copy_email_attach_audit
|
||||||
|
documents_copy_email_attach_documents
|
||||||
|
completed_notification_email_attach_documents
|
||||||
|
completed_redirect_url
|
||||||
|
submitters_order
|
||||||
completed_notification_email_subject completed_notification_email_body
|
completed_notification_email_subject completed_notification_email_body
|
||||||
completed_notification_email_enabled completed_notification_email_attach_audit]
|
completed_notification_email_enabled completed_notification_email_attach_audit] +
|
||||||
|
[completed_message: %i[title body]]
|
||||||
).tap do |attrs|
|
).tap do |attrs|
|
||||||
attrs[:preferences] = attrs[:preferences].transform_values do |value|
|
attrs[:preferences] = attrs[:preferences].transform_values do |value|
|
||||||
if %w[true false].include?(value)
|
if %w[true false].include?(value)
|
||||||
value == 'true'
|
value == 'true'
|
||||||
|
elsif value.respond_to?(:compact_blank)
|
||||||
|
value.compact_blank
|
||||||
else
|
else
|
||||||
value
|
value
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class TemplatesRecipientsController < ApplicationController
|
||||||
|
load_and_authorize_resource :template
|
||||||
|
|
||||||
|
def create
|
||||||
|
authorize!(:update, @template)
|
||||||
|
|
||||||
|
@template.submitters =
|
||||||
|
submitters_params.map { |s| s.reject { |_, v| v.is_a?(String) && v.blank? } }
|
||||||
|
|
||||||
|
@template.save!
|
||||||
|
|
||||||
|
render json: { submitters: @template.submitters }
|
||||||
|
end
|
||||||
|
|
||||||
|
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|
|
||||||
|
next if s[:uuid].blank?
|
||||||
|
|
||||||
|
if s[:is_requester] == '1' && s[: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?
|
||||||
|
|
||||||
|
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
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -6,6 +6,6 @@ class TemplatesRestoreController < ApplicationController
|
|||||||
def create
|
def create
|
||||||
@template.update!(archived_at: nil)
|
@template.update!(archived_at: nil)
|
||||||
|
|
||||||
redirect_to template_path(@template), notice: 'Template has been unarchived'
|
redirect_to template_path(@template), notice: I18n.t('template_has_been_unarchived')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -12,13 +12,18 @@ class TemplatesUploadsController < ApplicationController
|
|||||||
|
|
||||||
save_template!(@template, url_params)
|
save_template!(@template, url_params)
|
||||||
|
|
||||||
documents = Templates::CreateAttachments.call(@template, url_params || params)
|
documents = Templates::CreateAttachments.call(@template, url_params || params, extract_fields: true)
|
||||||
|
|
||||||
schema = documents.map { |doc| { attachment_uuid: doc.uuid, name: doc.filename.base } }
|
schema = documents.map { |doc| { attachment_uuid: doc.uuid, name: doc.filename.base } }
|
||||||
|
|
||||||
|
if @template.fields.blank?
|
||||||
|
@template.fields = Templates::ProcessDocument.normalize_attachment_fields(@template, documents)
|
||||||
|
|
||||||
|
schema.each { |item| item['pending_fields'] = true } if @template.fields.present?
|
||||||
|
end
|
||||||
|
|
||||||
@template.update!(schema:)
|
@template.update!(schema:)
|
||||||
|
|
||||||
SendTemplateCreatedWebhookRequestJob.perform_async('template_id' => @template.id)
|
enqueue_template_created_webhooks(@template)
|
||||||
|
|
||||||
redirect_to edit_template_path(@template)
|
redirect_to edit_template_path(@template)
|
||||||
rescue Templates::CreateAttachments::PdfEncrypted
|
rescue Templates::CreateAttachments::PdfEncrypted
|
||||||
@@ -28,7 +33,7 @@ class TemplatesUploadsController < ApplicationController
|
|||||||
|
|
||||||
raise if Rails.env.local?
|
raise if Rails.env.local?
|
||||||
|
|
||||||
redirect_to root_path, alert: 'Unable to upload file'
|
redirect_to root_path, alert: I18n.t('unable_to_update_file')
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
@@ -47,23 +52,25 @@ class TemplatesUploadsController < ApplicationController
|
|||||||
def create_file_params_from_url
|
def create_file_params_from_url
|
||||||
tempfile = Tempfile.new
|
tempfile = Tempfile.new
|
||||||
tempfile.binmode
|
tempfile.binmode
|
||||||
tempfile.write(conn.get(Addressable::URI.parse(params[:url]).display_uri.to_s).body)
|
tempfile.write(DownloadUtils.call(params[:url]).body)
|
||||||
tempfile.rewind
|
tempfile.rewind
|
||||||
|
|
||||||
|
filename = URI.decode_www_form_component(params[:filename]) if params[:filename].present?
|
||||||
|
filename ||= File.basename(URI.decode_www_form_component(params[:url]))
|
||||||
|
|
||||||
file = ActionDispatch::Http::UploadedFile.new(
|
file = ActionDispatch::Http::UploadedFile.new(
|
||||||
tempfile:,
|
tempfile:,
|
||||||
filename: File.basename(
|
filename:,
|
||||||
URI.decode_www_form_component(params[:filename].presence || params[:url])
|
|
||||||
),
|
|
||||||
type: Marcel::MimeType.for(tempfile)
|
type: Marcel::MimeType.for(tempfile)
|
||||||
)
|
)
|
||||||
|
|
||||||
{ files: [file] }
|
{ files: [file] }
|
||||||
end
|
end
|
||||||
|
|
||||||
def conn
|
def enqueue_template_created_webhooks(template)
|
||||||
Faraday.new do |faraday|
|
WebhookUrls.for_account_id(template.account_id, 'template.created').each do |webhook_url|
|
||||||
faraday.response :follow_redirects
|
SendTemplateCreatedWebhookRequestJob.perform_async('template_id' => template.id,
|
||||||
|
'webhook_url_id' => webhook_url.id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -4,9 +4,8 @@ class TestingApiSettingsController < ApplicationController
|
|||||||
def index
|
def index
|
||||||
authorize!(:manage, current_user.access_token)
|
authorize!(:manage, current_user.access_token)
|
||||||
|
|
||||||
@webhook_config =
|
@webhook_url = current_account.webhook_urls.first_or_initialize
|
||||||
current_account.encrypted_configs.find_or_initialize_by(key: EncryptedConfig::WEBHOOK_URL_KEY)
|
|
||||||
|
|
||||||
authorize!(:manage, @webhook_config)
|
authorize!(:manage, @webhook_url)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -14,12 +14,12 @@ class TimestampServerController < ApplicationController
|
|||||||
test_timeserver_url(@encrypted_config.value) if @encrypted_config.value.present?
|
test_timeserver_url(@encrypted_config.value) if @encrypted_config.value.present?
|
||||||
|
|
||||||
if @encrypted_config.value.present? ? @encrypted_config.save : @encrypted_config.delete
|
if @encrypted_config.value.present? ? @encrypted_config.save : @encrypted_config.delete
|
||||||
redirect_back fallback_location: settings_notifications_path, notice: 'Changes have been saved'
|
redirect_back fallback_location: settings_notifications_path, notice: I18n.t('changes_have_been_saved')
|
||||||
else
|
else
|
||||||
redirect_back fallback_location: settings_notifications_path, alert: 'Unable to save'
|
redirect_back fallback_location: settings_notifications_path, alert: I18n.t('unable_to_save')
|
||||||
end
|
end
|
||||||
rescue SocketError, TimestampError, OpenSSL::Timestamp::TimestampError
|
rescue SocketError, TimestampError, OpenSSL::Timestamp::TimestampError
|
||||||
redirect_back fallback_location: settings_notifications_path, alert: 'Invalid Timeserver'
|
redirect_back fallback_location: settings_notifications_path, alert: t('invalid_timeserver')
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ class UserInitialsController < ApplicationController
|
|||||||
def update
|
def update
|
||||||
file = params[:file]
|
file = params[:file]
|
||||||
|
|
||||||
return redirect_to settings_profile_index_path, notice: 'Unable to save initials' if file.blank?
|
return redirect_to settings_profile_index_path, notice: I18n.t('unable_to_save_initials') if file.blank?
|
||||||
|
|
||||||
blob = ActiveStorage::Blob.create_and_upload!(io: file.open,
|
blob = ActiveStorage::Blob.create_and_upload!(io: file.open,
|
||||||
filename: file.original_filename,
|
filename: file.original_filename,
|
||||||
@@ -22,16 +22,16 @@ class UserInitialsController < ApplicationController
|
|||||||
)
|
)
|
||||||
|
|
||||||
if @user_config.update(value: attachment.uuid)
|
if @user_config.update(value: attachment.uuid)
|
||||||
redirect_to settings_profile_index_path, notice: 'Initials has been saved'
|
redirect_to settings_profile_index_path, notice: I18n.t('initials_has_been_saved')
|
||||||
else
|
else
|
||||||
redirect_to settings_profile_index_path, notice: 'Unable to save initials'
|
redirect_to settings_profile_index_path, notice: I18n.t('unable_to_save_initials')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
@user_config.destroy
|
@user_config.destroy
|
||||||
|
|
||||||
redirect_to settings_profile_index_path, notice: 'Initials has been removed'
|
redirect_to settings_profile_index_path, notice: I18n.t('initials_has_been_removed')
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ class UserSignaturesController < ApplicationController
|
|||||||
def update
|
def update
|
||||||
file = params[:file]
|
file = params[:file]
|
||||||
|
|
||||||
return redirect_to settings_profile_index_path, notice: 'Unable to save signature' if file.blank?
|
return redirect_to settings_profile_index_path, notice: I18n.t('unable_to_save_signature') if file.blank?
|
||||||
|
|
||||||
blob = ActiveStorage::Blob.create_and_upload!(io: file.open,
|
blob = ActiveStorage::Blob.create_and_upload!(io: file.open,
|
||||||
filename: file.original_filename,
|
filename: file.original_filename,
|
||||||
@@ -22,16 +22,16 @@ class UserSignaturesController < ApplicationController
|
|||||||
)
|
)
|
||||||
|
|
||||||
if @user_config.update(value: attachment.uuid)
|
if @user_config.update(value: attachment.uuid)
|
||||||
redirect_to settings_profile_index_path, notice: 'Signature has been saved'
|
redirect_to settings_profile_index_path, notice: I18n.t('signature_has_been_saved')
|
||||||
else
|
else
|
||||||
redirect_to settings_profile_index_path, notice: 'Unable to save signature'
|
redirect_to settings_profile_index_path, notice: I18n.t('unable_to_save_signature')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
@user_config.destroy
|
@user_config.destroy
|
||||||
|
|
||||||
redirect_to settings_profile_index_path, notice: 'Signature has been removed'
|
redirect_to settings_profile_index_path, notice: I18n.t('signature_has_been_removed')
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|||||||
@@ -9,9 +9,11 @@ class UsersController < ApplicationController
|
|||||||
def index
|
def index
|
||||||
@users =
|
@users =
|
||||||
if params[:status] == 'archived'
|
if params[:status] == 'archived'
|
||||||
@users.archived
|
@users.archived.where.not(role: 'integration')
|
||||||
|
elsif params[:status] == 'integration'
|
||||||
|
@users.active.where(role: 'integration')
|
||||||
else
|
else
|
||||||
@users.active
|
@users.active.where.not(role: 'integration')
|
||||||
end
|
end
|
||||||
|
|
||||||
@pagy, @users = pagy(@users.where(account: current_account).order(id: :desc))
|
@pagy, @users = pagy(@users.where(account: current_account).order(id: :desc))
|
||||||
@@ -32,17 +34,19 @@ class UsersController < ApplicationController
|
|||||||
@user = existing_user
|
@user = existing_user
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@user.role = User::ADMIN_ROLE unless role_valid?(@user.role)
|
||||||
|
|
||||||
if @user.save
|
if @user.save
|
||||||
UserMailer.invitation_email(@user).deliver_later!
|
UserMailer.invitation_email(@user).deliver_later!
|
||||||
|
|
||||||
redirect_back fallback_location: settings_users_path, notice: 'User has been invited'
|
redirect_back fallback_location: settings_users_path, notice: I18n.t('user_has_been_invited')
|
||||||
else
|
else
|
||||||
render turbo_stream: turbo_stream.replace(:modal, template: 'users/new'), status: :unprocessable_entity
|
render turbo_stream: turbo_stream.replace(:modal, template: 'users/new'), status: :unprocessable_entity
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
return redirect_to settings_users_path, notice: 'Unable to update user.' if Docuseal.demo?
|
return redirect_to settings_users_path, notice: I18n.t('unable_to_update_user') if Docuseal.demo?
|
||||||
|
|
||||||
attrs = user_params.compact_blank.merge(user_params.slice(:archived_at))
|
attrs = user_params.compact_blank.merge(user_params.slice(:archived_at))
|
||||||
attrs.delete(:role) if !role_valid?(attrs[:role]) || current_user == @user
|
attrs.delete(:role) if !role_valid?(attrs[:role]) || current_user == @user
|
||||||
@@ -56,7 +60,7 @@ class UsersController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
if @user.update(attrs)
|
if @user.update(attrs)
|
||||||
redirect_back fallback_location: settings_users_path, notice: 'User has been updated'
|
redirect_back fallback_location: settings_users_path, notice: I18n.t('user_has_been_updated')
|
||||||
else
|
else
|
||||||
render turbo_stream: turbo_stream.replace(:modal, template: 'users/edit'), status: :unprocessable_entity
|
render turbo_stream: turbo_stream.replace(:modal, template: 'users/edit'), status: :unprocessable_entity
|
||||||
end
|
end
|
||||||
@@ -64,12 +68,12 @@ class UsersController < ApplicationController
|
|||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
if Docuseal.demo? || @user.id == current_user.id
|
if Docuseal.demo? || @user.id == current_user.id
|
||||||
return redirect_to settings_users_path, notice: 'Unable to remove user'
|
return redirect_to settings_users_path, notice: I18n.t('unable_to_remove_user')
|
||||||
end
|
end
|
||||||
|
|
||||||
@user.update!(archived_at: Time.current)
|
@user.update!(archived_at: Time.current)
|
||||||
|
|
||||||
redirect_back fallback_location: settings_users_path, notice: 'User has been removed'
|
redirect_back fallback_location: settings_users_path, notice: I18n.t('user_has_been_removed')
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|||||||
@@ -9,30 +9,11 @@ class VerifyPdfSignatureController < ApplicationController
|
|||||||
HexaPDF::Document.new(io: file.open)
|
HexaPDF::Document.new(io: file.open)
|
||||||
end
|
end
|
||||||
|
|
||||||
cert_data =
|
trusted_certs = Accounts.load_trusted_certs(current_account)
|
||||||
if Docuseal.multitenant?
|
|
||||||
value = EncryptedConfig.find_by(account: current_account, key: EncryptedConfig::ESIGN_CERTS_KEY)&.value || {}
|
|
||||||
|
|
||||||
Docuseal::CERTS.merge(value)
|
|
||||||
else
|
|
||||||
EncryptedConfig.find_by(key: EncryptedConfig::ESIGN_CERTS_KEY)&.value || {}
|
|
||||||
end
|
|
||||||
|
|
||||||
default_pkcs = GenerateCertificate.load_pkcs(cert_data)
|
|
||||||
|
|
||||||
custom_certs = cert_data.fetch('custom', []).map do |e|
|
|
||||||
OpenSSL::PKCS12.new(Base64.urlsafe_decode64(e['data']), e['password'].to_s)
|
|
||||||
end
|
|
||||||
|
|
||||||
trusted_certs = [default_pkcs.certificate,
|
|
||||||
*default_pkcs.ca_certs,
|
|
||||||
*custom_certs.map(&:certificate),
|
|
||||||
*custom_certs.flat_map(&:ca_certs).compact,
|
|
||||||
*Docuseal.trusted_certs]
|
|
||||||
|
|
||||||
render turbo_stream: turbo_stream.replace('result', partial: 'result',
|
render turbo_stream: turbo_stream.replace('result', partial: 'result',
|
||||||
locals: { pdfs:, files: params[:files], trusted_certs: })
|
locals: { pdfs:, files: params[:files], trusted_certs: })
|
||||||
rescue HexaPDF::MalformedPDFError
|
rescue HexaPDF::MalformedPDFError
|
||||||
render turbo_stream: turbo_stream.replace('result', html: helpers.tag.div('Invalid PDF', id: 'result'))
|
render turbo_stream: turbo_stream.replace('result', html: helpers.tag.div(I18n.t('invalid_pdf'), id: 'result'))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -1,36 +1,22 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class WebhookPreferencesController < ApplicationController
|
class WebhookPreferencesController < ApplicationController
|
||||||
EVENTS = %w[
|
load_and_authorize_resource :webhook_url, parent: false
|
||||||
form.viewed
|
|
||||||
form.started
|
|
||||||
form.completed
|
|
||||||
template.created
|
|
||||||
template.updated
|
|
||||||
submission.created
|
|
||||||
submission.archived
|
|
||||||
].freeze
|
|
||||||
|
|
||||||
before_action :load_account_config
|
def update
|
||||||
authorize_resource :account_config, parent: false
|
webhook_preferences_params[:events].each do |event, val|
|
||||||
|
@webhook_url.events.delete(event) if val == '0'
|
||||||
|
@webhook_url.events.push(event) if val == '1' && @webhook_url.events.exclude?(event)
|
||||||
|
end
|
||||||
|
|
||||||
def create
|
@webhook_url.save!
|
||||||
@account_config.value[account_config_params[:event]] = account_config_params[:value] == '1'
|
|
||||||
|
|
||||||
@account_config.save!
|
|
||||||
|
|
||||||
head :ok
|
head :ok
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def load_account_config
|
def webhook_preferences_params
|
||||||
@account_config =
|
params.require(:webhook_url).permit(events: {})
|
||||||
current_account.account_configs.find_or_initialize_by(key: AccountConfig::WEBHOOK_PREFERENCES_KEY)
|
|
||||||
@account_config.value ||= {}
|
|
||||||
end
|
|
||||||
|
|
||||||
def account_config_params
|
|
||||||
params.permit(:event, :value)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class WebhookSecretController < ApplicationController
|
||||||
|
load_and_authorize_resource :webhook_url, parent: false
|
||||||
|
|
||||||
|
def show; end
|
||||||
|
|
||||||
|
def update
|
||||||
|
@webhook_url.update!(secret: {
|
||||||
|
webhook_secret_params[:key] => webhook_secret_params[:value]
|
||||||
|
}.compact_blank)
|
||||||
|
|
||||||
|
redirect_back(fallback_location: settings_webhook_path(@webhook_url),
|
||||||
|
notice: I18n.t('webhook_secret_has_been_saved'))
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def webhook_secret_params
|
||||||
|
params.require(:webhook_url).permit(secret: %i[key value]).fetch(:secret, {})
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,35 +1,60 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class WebhookSettingsController < ApplicationController
|
class WebhookSettingsController < ApplicationController
|
||||||
before_action :load_encrypted_config
|
load_and_authorize_resource :webhook_url, parent: false, only: %i[index show new create update destroy]
|
||||||
authorize_resource :encrypted_config, parent: false
|
load_and_authorize_resource :webhook_url, only: %i[resend], id_param: :webhook_id
|
||||||
|
|
||||||
|
def index
|
||||||
|
@webhook_urls = @webhook_urls.order(id: :desc)
|
||||||
|
@webhook_url = @webhook_urls.first_or_initialize
|
||||||
|
|
||||||
|
render @webhook_urls.size > 1 ? 'index' : 'show'
|
||||||
|
end
|
||||||
|
|
||||||
def show; end
|
def show; end
|
||||||
|
|
||||||
|
def new; end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
@encrypted_config.assign_attributes(encrypted_config_params)
|
@webhook_url.save!
|
||||||
|
|
||||||
@encrypted_config.value.present? ? @encrypted_config.save! : @encrypted_config.delete
|
redirect_to settings_webhooks_path, notice: I18n.t('webhook_url_has_been_saved')
|
||||||
|
|
||||||
redirect_back(fallback_location: settings_webhooks_path, notice: 'Webhook URL has been saved.')
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
|
@webhook_url.update!(update_params)
|
||||||
|
|
||||||
|
redirect_back(fallback_location: settings_webhook_path(@webhook_url),
|
||||||
|
notice: I18n.t('webhook_url_has_been_updated'))
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
@webhook_url.destroy!
|
||||||
|
|
||||||
|
redirect_to settings_webhooks_path, notice: I18n.t('webhook_url_has_been_deleted')
|
||||||
|
end
|
||||||
|
|
||||||
|
def resend
|
||||||
submitter = current_account.submitters.where.not(completed_at: nil).order(:id).last
|
submitter = current_account.submitters.where.not(completed_at: nil).order(:id).last
|
||||||
|
|
||||||
SendFormCompletedWebhookRequestJob.perform_async({ 'submitter_id' => submitter.id })
|
if submitter.blank? || @webhook_url.blank?
|
||||||
|
return redirect_back(fallback_location: settings_webhooks_path,
|
||||||
|
alert: I18n.t('unable_to_resend_webhook_request'))
|
||||||
|
end
|
||||||
|
|
||||||
redirect_back(fallback_location: settings_webhooks_path, notice: 'Webhook request has been sent.')
|
SendFormCompletedWebhookRequestJob.perform_async('submitter_id' => submitter.id,
|
||||||
|
'webhook_url_id' => @webhook_url.id)
|
||||||
|
|
||||||
|
redirect_back(fallback_location: settings_webhooks_path, notice: I18n.t('webhook_request_has_been_sent'))
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def load_encrypted_config
|
def create_params
|
||||||
@encrypted_config =
|
params.require(:webhook_url).permit(:url, events: []).reverse_merge(events: [])
|
||||||
current_account.encrypted_configs.find_or_initialize_by(key: EncryptedConfig::WEBHOOK_URL_KEY)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def encrypted_config_params
|
def update_params
|
||||||
params.require(:encrypted_config).permit(:value)
|
params.require(:webhook_url).permit(:url)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -24,6 +24,13 @@ import SubmitForm from './elements/submit_form'
|
|||||||
import PromptPassword from './elements/prompt_password'
|
import PromptPassword from './elements/prompt_password'
|
||||||
import EmailsTextarea from './elements/emails_textarea'
|
import EmailsTextarea from './elements/emails_textarea'
|
||||||
import ToggleOnSubmit from './elements/toggle_on_submit'
|
import ToggleOnSubmit from './elements/toggle_on_submit'
|
||||||
|
import PasswordInput from './elements/password_input'
|
||||||
|
import SearchInput from './elements/search_input'
|
||||||
|
import ToggleAttribute from './elements/toggle_attribute'
|
||||||
|
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 * as TurboInstantClick from './lib/turbo_instant_click'
|
import * as TurboInstantClick from './lib/turbo_instant_click'
|
||||||
|
|
||||||
@@ -85,9 +92,18 @@ safeRegisterElement('prompt-password', PromptPassword)
|
|||||||
safeRegisterElement('emails-textarea', EmailsTextarea)
|
safeRegisterElement('emails-textarea', EmailsTextarea)
|
||||||
safeRegisterElement('toggle-cookies', ToggleCookies)
|
safeRegisterElement('toggle-cookies', ToggleCookies)
|
||||||
safeRegisterElement('toggle-on-submit', ToggleOnSubmit)
|
safeRegisterElement('toggle-on-submit', ToggleOnSubmit)
|
||||||
|
safeRegisterElement('password-input', PasswordInput)
|
||||||
|
safeRegisterElement('search-input', SearchInput)
|
||||||
|
safeRegisterElement('toggle-attribute', ToggleAttribute)
|
||||||
|
safeRegisterElement('linked-input', LinkedInput)
|
||||||
|
safeRegisterElement('checkbox-group', CheckboxGroup)
|
||||||
|
safeRegisterElement('masked-input', MaskedInput)
|
||||||
|
safeRegisterElement('set-date-button', SetDateButton)
|
||||||
|
|
||||||
safeRegisterElement('template-builder', class extends HTMLElement {
|
safeRegisterElement('template-builder', class extends HTMLElement {
|
||||||
connectedCallback () {
|
connectedCallback () {
|
||||||
|
document.addEventListener('turbo:submit-end', this.onSubmit)
|
||||||
|
|
||||||
this.appElem = document.createElement('div')
|
this.appElem = document.createElement('div')
|
||||||
|
|
||||||
this.appElem.classList.add('md:h-screen')
|
this.appElem.classList.add('md:h-screen')
|
||||||
@@ -95,24 +111,38 @@ safeRegisterElement('template-builder', class extends HTMLElement {
|
|||||||
this.app = createApp(TemplateBuilder, {
|
this.app = createApp(TemplateBuilder, {
|
||||||
template: reactive(JSON.parse(this.dataset.template)),
|
template: reactive(JSON.parse(this.dataset.template)),
|
||||||
backgroundColor: '#faf7f5',
|
backgroundColor: '#faf7f5',
|
||||||
|
locale: this.dataset.locale,
|
||||||
withPhone: this.dataset.withPhone === 'true',
|
withPhone: this.dataset.withPhone === 'true',
|
||||||
|
withVerification: ['true', 'false'].includes(this.dataset.withVerification) ? this.dataset.withVerification === 'true' : null,
|
||||||
withLogo: this.dataset.withLogo !== 'false',
|
withLogo: this.dataset.withLogo !== 'false',
|
||||||
editable: this.dataset.editable !== 'false',
|
editable: this.dataset.editable !== 'false',
|
||||||
authenticityToken: document.querySelector('meta[name="csrf-token"]')?.content,
|
authenticityToken: document.querySelector('meta[name="csrf-token"]')?.content,
|
||||||
withPayment: this.dataset.withPayment === 'true',
|
withPayment: this.dataset.withPayment === 'true',
|
||||||
isPaymentConnected: this.dataset.isPaymentConnected === 'true',
|
isPaymentConnected: this.dataset.isPaymentConnected === 'true',
|
||||||
withFormula: this.dataset.withFormula === 'true',
|
withFormula: this.dataset.withFormula === 'true',
|
||||||
|
withSendButton: this.dataset.withSendButton !== 'false',
|
||||||
|
withSignYourselfButton: this.dataset.withSignYourselfButton !== 'false',
|
||||||
withConditions: this.dataset.withConditions === 'true',
|
withConditions: this.dataset.withConditions === 'true',
|
||||||
currencies: (this.dataset.currencies || '').split(',').filter(Boolean),
|
currencies: (this.dataset.currencies || '').split(',').filter(Boolean),
|
||||||
acceptFileTypes: this.dataset.acceptFileTypes
|
acceptFileTypes: this.dataset.acceptFileTypes
|
||||||
})
|
})
|
||||||
|
|
||||||
this.app.mount(this.appElem)
|
this.component = this.app.mount(this.appElem)
|
||||||
|
|
||||||
this.appendChild(this.appElem)
|
this.appendChild(this.appElem)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onSubmit = (e) => {
|
||||||
|
if (e.detail.success && e.detail?.formSubmission?.formElement?.id === 'submitters_form') {
|
||||||
|
e.detail.fetchResponse.response.json().then((data) => {
|
||||||
|
this.component.template.submitters = data.submitters
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
disconnectedCallback () {
|
disconnectedCallback () {
|
||||||
|
document.removeEventListener('turbo:submit-end', this.onSubmit)
|
||||||
|
|
||||||
this.app?.unmount()
|
this.app?.unmount()
|
||||||
this.appElem?.remove()
|
this.appElem?.remove()
|
||||||
}
|
}
|
||||||
@@ -124,7 +154,9 @@ safeRegisterElement('import-list', class extends HTMLElement {
|
|||||||
|
|
||||||
this.app = createApp(ImportList, {
|
this.app = createApp(ImportList, {
|
||||||
template: JSON.parse(this.dataset.template),
|
template: JSON.parse(this.dataset.template),
|
||||||
authenticityToken: document.querySelector('meta[name="csrf-token"]')?.content
|
multitenant: this.dataset.multitenant === 'true',
|
||||||
|
authenticityToken: document.querySelector('meta[name="csrf-token"]')?.content,
|
||||||
|
i18n: JSON.parse(this.dataset.i18n || '{}')
|
||||||
})
|
})
|
||||||
|
|
||||||
this.app.mount(this.appElem)
|
this.app.mount(this.appElem)
|
||||||
|
|||||||
@@ -97,10 +97,27 @@ button[disabled] .enabled {
|
|||||||
bottom: auto;
|
bottom: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tooltip-bottom-start:before {
|
||||||
|
transform: translateX(-31%);
|
||||||
|
top: var(--tooltip-offset);
|
||||||
|
left: 100%;
|
||||||
|
right: auto;
|
||||||
|
bottom: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip-bottom-start:after {
|
||||||
|
transform: translateX(-25%);
|
||||||
|
border-color: transparent transparent var(--tooltip-color) transparent;
|
||||||
|
top: var(--tooltip-tail-offset);
|
||||||
|
left: 50%;
|
||||||
|
right: auto;
|
||||||
|
bottom: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.autocomplete {
|
.autocomplete {
|
||||||
background: white;
|
background: white;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
font: 14px/22px "-apple-system", BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
font: 16px/25px "-apple-system", BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
@apply border border-base-300 mt-1 rounded-md;
|
@apply border border-base-300 mt-1 rounded-md;
|
||||||
@@ -111,7 +128,7 @@ button[disabled] .enabled {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.autocomplete > div {
|
.autocomplete > div {
|
||||||
@apply px-2 py-1 font-normal text-sm;
|
@apply px-2 py-1.5 font-normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
.autocomplete .group {
|
.autocomplete .group {
|
||||||
@@ -130,7 +147,3 @@ button[disabled] .enabled {
|
|||||||
outline-offset: 3px;
|
outline-offset: 3px;
|
||||||
outline-color: hsl(var(--bc) / 0.2);
|
outline-color: hsl(var(--bc) / 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
select:required:invalid {
|
|
||||||
color: gray !important;
|
|
||||||
}
|
|
||||||
@@ -12,6 +12,10 @@ window.customElements.define('draw-signature', class extends HTMLElement {
|
|||||||
|
|
||||||
this.pad = new SignaturePad(this.canvas)
|
this.pad = new SignaturePad(this.canvas)
|
||||||
|
|
||||||
|
if (this.dataset.color) {
|
||||||
|
this.pad.penColor = this.dataset.color
|
||||||
|
}
|
||||||
|
|
||||||
this.pad.addEventListener('endStroke', () => {
|
this.pad.addEventListener('endStroke', () => {
|
||||||
this.updateSubmitButtonVisibility()
|
this.updateSubmitButtonVisibility()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
export default class extends HTMLElement {
|
||||||
|
connectedCallback () {
|
||||||
|
this.items.forEach((item) => {
|
||||||
|
item.addEventListener('change', (e) => {
|
||||||
|
this.items.forEach((item) => {
|
||||||
|
item.checked = item === e.target && e.target.checked
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
get items () {
|
||||||
|
return this.querySelectorAll('input[type="checkbox"]')
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
export default class extends HTMLElement {
|
||||||
|
connectedCallback () {
|
||||||
|
if (this.target) {
|
||||||
|
this.input.value = this.target.value
|
||||||
|
|
||||||
|
this.target.addEventListener('input', (e) => {
|
||||||
|
this.input.value = e.target.value
|
||||||
|
})
|
||||||
|
|
||||||
|
this.target.addEventListener('linked-input.update', (e) => {
|
||||||
|
this.input.value = e.target.value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get input () {
|
||||||
|
return this.querySelector('input')
|
||||||
|
}
|
||||||
|
|
||||||
|
get target () {
|
||||||
|
if (this.dataset.targetId) {
|
||||||
|
const listItem = this.closest('[data-targets="dynamic-list.items"]')
|
||||||
|
|
||||||
|
if (listItem) {
|
||||||
|
return listItem.querySelector(`#${this.dataset.targetId}`)
|
||||||
|
} else {
|
||||||
|
return document.getElementById(this.dataset.targetId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
export default class extends HTMLElement {
|
||||||
|
connectedCallback () {
|
||||||
|
const maskedToken = this.input.value
|
||||||
|
|
||||||
|
this.input.addEventListener('focus', () => {
|
||||||
|
this.input.value = this.dataset.token
|
||||||
|
this.input.select()
|
||||||
|
})
|
||||||
|
|
||||||
|
this.input.addEventListener('focusout', () => {
|
||||||
|
this.input.value = maskedToken
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
get input () {
|
||||||
|
return this.querySelector('input')
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import { target, targetable } from '@github/catalyst/lib/targetable'
|
||||||
|
|
||||||
|
export default targetable(class extends HTMLElement {
|
||||||
|
static [target.static] = [
|
||||||
|
'visiblePasswordIcon',
|
||||||
|
'hiddenPasswordIcon',
|
||||||
|
'passwordInput',
|
||||||
|
'togglePasswordVisibility'
|
||||||
|
]
|
||||||
|
|
||||||
|
connectedCallback () {
|
||||||
|
this.togglePasswordVisibility.addEventListener('click', this.handleTogglePasswordVisibility)
|
||||||
|
document.addEventListener('turbo:submit-start', this.setInitialPasswordType)
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectedCallback () {
|
||||||
|
this.togglePasswordVisibility.removeEventListener('click', this.handleTogglePasswordVisibility)
|
||||||
|
document.removeEventListener('turbo:submit-start', this.setInitialPasswordType)
|
||||||
|
}
|
||||||
|
|
||||||
|
handleTogglePasswordVisibility = () => {
|
||||||
|
this.passwordInput.type = this.passwordInput.type === 'password' ? 'text' : 'password'
|
||||||
|
this.toggleIcon()
|
||||||
|
}
|
||||||
|
|
||||||
|
setInitialPasswordType = () => {
|
||||||
|
this.passwordInput.type = 'password'
|
||||||
|
this.toggleIcon()
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleIcon = () => {
|
||||||
|
this.visiblePasswordIcon.classList.toggle('hidden', this.passwordInput.type === 'password')
|
||||||
|
this.hiddenPasswordIcon.classList.toggle('hidden', this.passwordInput.type === 'text')
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
export default class extends HTMLElement {
|
||||||
|
connectedCallback () {
|
||||||
|
this.input.addEventListener('focus', () => {
|
||||||
|
if (this.title) {
|
||||||
|
this.title.classList.add('hidden', 'md:block')
|
||||||
|
this.input.classList.add('w-60')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
this.input.addEventListener('blur', (e) => {
|
||||||
|
if (this.title && !e.target.value) {
|
||||||
|
this.title.classList.remove('hidden')
|
||||||
|
this.input.classList.remove('w-60')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
get input () {
|
||||||
|
return this.querySelector('input')
|
||||||
|
}
|
||||||
|
|
||||||
|
get title () {
|
||||||
|
return document.querySelector(this.dataset.title)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
export default class extends HTMLElement {
|
||||||
|
connectedCallback () {
|
||||||
|
this.button.addEventListener('click', () => {
|
||||||
|
this.fromInput.value = this.dataset.fromValue || ''
|
||||||
|
this.toInput.value = this.dataset.toValue || ''
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
get button () {
|
||||||
|
return this.querySelector('button')
|
||||||
|
}
|
||||||
|
|
||||||
|
get fromInput () {
|
||||||
|
return document.getElementById(this.dataset.fromId)
|
||||||
|
}
|
||||||
|
|
||||||
|
get toInput () {
|
||||||
|
return document.getElementById(this.dataset.toId)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,6 +24,7 @@ export default class extends HTMLElement {
|
|||||||
|
|
||||||
if (input && item[field]) {
|
if (input && item[field]) {
|
||||||
input.value = item[field]
|
input.value = item[field]
|
||||||
|
input.dispatchEvent(new CustomEvent('linked-input.update', { bubbles: true }))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (textarea && item[field]) {
|
if (textarea && item[field]) {
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
export default class extends HTMLElement {
|
||||||
|
connectedCallback () {
|
||||||
|
this.input.addEventListener('change', (event) => {
|
||||||
|
if (this.dataset.attribute) {
|
||||||
|
this.target[this.dataset.attribute] = event.target.checked
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.dataset.className) {
|
||||||
|
this.target.classList.toggle(this.dataset.className, event.target.value !== this.dataset.value)
|
||||||
|
if (this.dataset.className === 'hidden' && this.target.tagName === 'INPUT') {
|
||||||
|
this.target.disabled = event.target.value !== this.dataset.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.dataset.attribute === 'disabled') {
|
||||||
|
this.target.value = ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
get input () {
|
||||||
|
return this.querySelector('input[type="checkbox"]') || this.querySelector('select')
|
||||||
|
}
|
||||||
|
|
||||||
|
get target () {
|
||||||
|
return document.getElementById(this.dataset.targetId)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,7 +22,7 @@ export default actionable(class extends HTMLElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onSubmit = (e) => {
|
onSubmit = (e) => {
|
||||||
if (e.detail.success) {
|
if (e.detail.success && e.detail?.formSubmission?.formElement?.dataset?.closeOnSubmit !== 'false') {
|
||||||
this.close()
|
this.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,13 +14,22 @@ safeRegisterElement('submission-form', class extends HTMLElement {
|
|||||||
|
|
||||||
this.app = createApp(Form, {
|
this.app = createApp(Form, {
|
||||||
submitter: JSON.parse(this.dataset.submitter),
|
submitter: JSON.parse(this.dataset.submitter),
|
||||||
|
inviteSubmitters: JSON.parse(this.dataset.inviteSubmitters),
|
||||||
|
schema: JSON.parse(this.dataset.schema),
|
||||||
canSendEmail: this.dataset.canSendEmail === 'true',
|
canSendEmail: this.dataset.canSendEmail === 'true',
|
||||||
previousSignatureValue: this.dataset.previousSignatureValue,
|
previousSignatureValue: this.dataset.previousSignatureValue,
|
||||||
goToLast: this.dataset.goToLast === 'true',
|
goToLast: this.dataset.goToLast === 'true',
|
||||||
isDemo: this.dataset.isDemo === 'true',
|
isDemo: this.dataset.isDemo === 'true',
|
||||||
attribution: this.dataset.attribution !== 'false',
|
attribution: this.dataset.attribution !== 'false',
|
||||||
|
scrollPadding: this.dataset.scrollPadding || '-80px',
|
||||||
|
language: this.dataset.language,
|
||||||
|
dryRun: this.dataset.dryRun === 'true',
|
||||||
|
expand: ['true', 'false'].includes(this.dataset.expand) ? this.dataset.expand === 'true' : null,
|
||||||
|
withSignatureId: this.dataset.withSignatureId === 'true',
|
||||||
|
requireSigningReason: this.dataset.requireSigningReason === 'true',
|
||||||
withConfetti: this.dataset.withConfetti !== 'false',
|
withConfetti: this.dataset.withConfetti !== 'false',
|
||||||
withDisclosure: this.dataset.withDisclosure === 'true',
|
withDisclosure: this.dataset.withDisclosure === 'true',
|
||||||
|
reuseSignature: this.dataset.reuseSignature !== 'false',
|
||||||
withTypedSignature: this.dataset.withTypedSignature !== 'false',
|
withTypedSignature: this.dataset.withTypedSignature !== 'false',
|
||||||
authenticityToken: document.querySelector('meta[name="csrf-token"]')?.content,
|
authenticityToken: document.querySelector('meta[name="csrf-token"]')?.content,
|
||||||
rememberSignature: this.dataset.rememberSignature === 'true',
|
rememberSignature: this.dataset.rememberSignature === 'true',
|
||||||
|
|||||||
@@ -31,10 +31,6 @@ button[disabled] .enabled {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
select:required:invalid {
|
|
||||||
@apply text-gray-300;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-bordered {
|
.input-bordered {
|
||||||
@apply border-base-content/20;
|
@apply border-base-content/20;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,9 +18,15 @@ if (token) {
|
|||||||
transform (payload) {
|
transform (payload) {
|
||||||
payload.body.telemetry = []
|
payload.body.telemetry = []
|
||||||
|
|
||||||
const string = JSON.stringify(payload)
|
if (payload.request.query_string) {
|
||||||
|
payload.request.query_string = ''
|
||||||
|
}
|
||||||
|
|
||||||
return JSON.parse(string.replace(/(\/[des]\/)\w{6}/g, (_, m) => m))
|
if (payload.request.url) {
|
||||||
|
payload.request.url = payload.request.url.replace(/(\/[sdep]\/)(\w{5})[^/]+/, '$1$2')
|
||||||
|
}
|
||||||
|
|
||||||
|
return payload
|
||||||
},
|
},
|
||||||
payload: {
|
payload: {
|
||||||
client: {
|
client: {
|
||||||
|
|||||||
@@ -3,10 +3,10 @@
|
|||||||
class="field-area flex absolute lg:text-base -outline-offset-1"
|
class="field-area flex absolute lg:text-base -outline-offset-1"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
:style="computedStyle"
|
:style="computedStyle"
|
||||||
:class="{ 'text-[1.5vw] lg:text-base': !textOverflowChars, 'text-[1.0vw] lg:text-xs': textOverflowChars, 'cursor-default': !submittable, 'border border-red-100 bg-red-100 cursor-pointer': submittable, 'border border-red-100': !isActive && submittable, 'bg-opacity-80': !isActive && !isValueSet && submittable, 'field-area-active outline-red-500 outline-dashed outline-2 z-10': isActive && submittable, 'bg-opacity-40': (isActive || isValueSet) && submittable }"
|
:class="{ 'font-serif': field.preferences?.font === 'Times', 'text-[1.6vw] lg:text-base': !textOverflowChars, 'text-[1.0vw] lg:text-xs': textOverflowChars, 'cursor-default': !submittable, 'border border-red-100 bg-red-100 cursor-pointer': submittable, 'border border-red-100': !isActive && submittable, 'bg-opacity-80': !isActive && !isValueSet && submittable, 'field-area-active outline-red-500 outline-dashed outline-2 z-10': isActive && submittable, 'bg-opacity-40': (isActive || isValueSet) && submittable }"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-if="!isActive && !isValueSet && field.type !== 'checkbox' && submittable && !area.option_uuid"
|
v-if="(!withFieldPlaceholder || !field.name || field.type === 'cells') && !isActive && !isValueSet && field.type !== 'checkbox' && submittable && !area.option_uuid"
|
||||||
class="absolute top-0 bottom-0 right-0 left-0 items-center justify-center h-full w-full"
|
class="absolute top-0 bottom-0 right-0 left-0 items-center justify-center h-full w-full"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
@@ -29,19 +29,19 @@
|
|||||||
{{ optionValue(option) }}
|
{{ optionValue(option) }}
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
{{ field.name || fieldNames[field.type] }}
|
{{ field.title || field.name || fieldNames[field.type] }}
|
||||||
<template v-if="field.type === 'checkbox' && !field.name">
|
<template v-if="field.type === 'checkbox' && !field.name">
|
||||||
{{ fieldIndex + 1 }}
|
{{ fieldIndex + 1 }}
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="!field.required && field.type !== 'checkbox'">
|
<template v-else-if="!field.required && field.type !== 'checkbox'">
|
||||||
(optional)
|
({{ t('optional') }})
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="isActive"
|
|
||||||
ref="scrollToElem"
|
ref="scrollToElem"
|
||||||
class="absolute -top-20"
|
class="absolute"
|
||||||
|
:style="{ top: scrollPadding }"
|
||||||
/>
|
/>
|
||||||
<img
|
<img
|
||||||
v-if="field.type === 'image' && image"
|
v-if="field.type === 'image' && image"
|
||||||
@@ -53,18 +53,44 @@
|
|||||||
class="object-contain mx-auto"
|
class="object-contain mx-auto"
|
||||||
:src="stamp.url"
|
:src="stamp.url"
|
||||||
>
|
>
|
||||||
<img
|
<div
|
||||||
v-else-if="field.type === 'signature' && signature"
|
v-else-if="field.type === 'signature' && signature"
|
||||||
class="object-contain mx-auto"
|
class="flex flex-col justify-between h-full overflow-hidden"
|
||||||
:src="signature.url"
|
|
||||||
>
|
>
|
||||||
|
<div
|
||||||
|
class="flex-grow flex overflow-hidden"
|
||||||
|
style="min-height: 50%"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
class="object-contain mx-auto"
|
||||||
|
:src="signature.url"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="withSignatureId"
|
||||||
|
class="w-full mt-1 text-[1vw] lg:text-[0.55rem] lg:leading-[0.65rem]"
|
||||||
|
>
|
||||||
|
<div class="truncate uppercase">
|
||||||
|
ID: {{ signature.uuid }}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{{ t('reason') }}: {{ values[field.preferences?.reason_field_uuid] || t('digitally_signed_by') }} {{ submitter.name }}
|
||||||
|
<template v-if="submitter.email">
|
||||||
|
<{{ submitter.email }}>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{{ new Date(signature.created_at).toLocaleString(undefined, { year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute: 'numeric', timeZoneName: 'short' }) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<img
|
<img
|
||||||
v-else-if="field.type === 'initials' && initials"
|
v-else-if="field.type === 'initials' && initials"
|
||||||
class="object-contain mx-auto"
|
class="object-contain mx-auto"
|
||||||
:src="initials.url"
|
:src="initials.url"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-else-if="field.type === 'file' || field.type === 'payment'"
|
v-else-if="(field.type === 'file' || field.type === 'payment') && attachments.length"
|
||||||
class="px-0.5 flex flex-col justify-center"
|
class="px-0.5 flex flex-col justify-center"
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
@@ -74,7 +100,7 @@
|
|||||||
:href="attachment.url"
|
:href="attachment.url"
|
||||||
>
|
>
|
||||||
<IconPaperclip
|
<IconPaperclip
|
||||||
class="inline w-[1.5vw] h-[1.5vw] lg:w-4 lg:h-4"
|
class="inline w-[1.6vw] h-[1.6vw] lg:w-4 lg:h-4"
|
||||||
/>
|
/>
|
||||||
{{ attachment.filename }}
|
{{ attachment.filename }}
|
||||||
</a>
|
</a>
|
||||||
@@ -159,7 +185,12 @@
|
|||||||
class="flex items-center px-0.5 w-full"
|
class="flex items-center px-0.5 w-full"
|
||||||
:class="alignClasses[field.preferences?.align]"
|
:class="alignClasses[field.preferences?.align]"
|
||||||
>
|
>
|
||||||
<span v-if="Array.isArray(modelValue)">
|
<span
|
||||||
|
v-if="field && field.name && withFieldPlaceholder && !modelValue && modelValue !== 0"
|
||||||
|
class="whitespace-pre-wrap text-gray-400"
|
||||||
|
:class="{ 'w-full': field.preferences?.align }"
|
||||||
|
>{{ field.name }}</span>
|
||||||
|
<span v-else-if="Array.isArray(modelValue)">
|
||||||
{{ modelValue.join(', ') }}
|
{{ modelValue.join(', ') }}
|
||||||
</span>
|
</span>
|
||||||
<span v-else-if="field.type === 'date'">
|
<span v-else-if="field.type === 'date'">
|
||||||
@@ -178,7 +209,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { IconTextSize, IconWritingSign, IconCalendarEvent, IconPhoto, IconCheckbox, IconPaperclip, IconSelect, IconCircleDot, IconChecks, IconCheck, IconColumns3, IconPhoneCheck, IconLetterCaseUpper, IconCreditCard, IconRubberStamp, IconSquareNumber1 } from '@tabler/icons-vue'
|
import { IconTextSize, IconWritingSign, IconCalendarEvent, IconPhoto, IconCheckbox, IconPaperclip, IconSelect, IconCircleDot, IconChecks, IconCheck, IconColumns3, IconPhoneCheck, IconLetterCaseUpper, IconCreditCard, IconRubberStamp, IconSquareNumber1, IconId } from '@tabler/icons-vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'FieldArea',
|
name: 'FieldArea',
|
||||||
@@ -192,11 +223,31 @@ export default {
|
|||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
submitter: {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
|
withSignatureId: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
isValueSet: {
|
isValueSet: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
|
scrollPadding: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: '-80px'
|
||||||
|
},
|
||||||
|
withFieldPlaceholder: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
submittable: {
|
submittable: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -207,6 +258,11 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: ''
|
default: ''
|
||||||
},
|
},
|
||||||
|
values: {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
isActive: {
|
isActive: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -255,7 +311,8 @@ export default {
|
|||||||
cells: this.t('cells'),
|
cells: this.t('cells'),
|
||||||
stamp: this.t('stamp'),
|
stamp: this.t('stamp'),
|
||||||
payment: this.t('payment'),
|
payment: this.t('payment'),
|
||||||
phone: this.t('phone')
|
phone: this.t('phone'),
|
||||||
|
verification: this.t('verify_id')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
alignClasses () {
|
alignClasses () {
|
||||||
@@ -284,7 +341,8 @@ export default {
|
|||||||
cells: IconColumns3,
|
cells: IconColumns3,
|
||||||
multiple: IconChecks,
|
multiple: IconChecks,
|
||||||
phone: IconPhoneCheck,
|
phone: IconPhoneCheck,
|
||||||
payment: IconCreditCard
|
payment: IconCreditCard,
|
||||||
|
verification: IconId
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
image () {
|
image () {
|
||||||
@@ -348,7 +406,12 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.field.preferences?.font_size) {
|
if (this.field.preferences?.font_size) {
|
||||||
style.fontSize = this.field.preferences.font_size + 'pt'
|
style.fontSize = `clamp(4pt, 1.6vw, ${parseInt(this.field.preferences.font_size) * 1.23}pt)`
|
||||||
|
style.lineHeight = `clamp(6pt, 2.0vw, ${parseInt(this.field.preferences.font_size) * 1.23 + 3}pt)`
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.field.preferences?.color) {
|
||||||
|
style.color = this.field.preferences.color
|
||||||
}
|
}
|
||||||
|
|
||||||
return style
|
return style
|
||||||
@@ -358,7 +421,7 @@ export default {
|
|||||||
modelValue () {
|
modelValue () {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
if (['date', 'text', 'number'].includes(this.field.type) && this.$refs.textContainer && (this.textOverflowChars === 0 || (this.textOverflowChars - 4) > `${this.modelValue}`.length)) {
|
if (['date', 'text', 'number'].includes(this.field.type) && this.$refs.textContainer && (this.textOverflowChars === 0 || (this.textOverflowChars - 4) > `${this.modelValue}`.length)) {
|
||||||
this.textOverflowChars = this.$refs.textContainer.scrollHeight > this.$refs.textContainer.clientHeight ? `${this.modelValue}`.length : 0
|
this.textOverflowChars = this.$refs.textContainer.scrollHeight > this.$refs.textContainer.clientHeight ? `${this.modelValue || (this.withFieldPlaceholder ? this.field.name : '')}`.length : 0
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -366,7 +429,7 @@ export default {
|
|||||||
mounted () {
|
mounted () {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
if (['date', 'text', 'number'].includes(this.field.type) && this.$refs.textContainer) {
|
if (['date', 'text', 'number'].includes(this.field.type) && this.$refs.textContainer) {
|
||||||
this.textOverflowChars = this.$refs.textContainer.scrollHeight > this.$refs.textContainer.clientHeight ? `${this.modelValue}`.length : 0
|
this.textOverflowChars = this.$refs.textContainer.scrollHeight > this.$refs.textContainer.clientHeight ? `${this.modelValue || (this.withFieldPlaceholder ? this.field.name : '')}`.length : 0
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<template
|
<template
|
||||||
v-for="step in steps"
|
v-for="(step, stepIndex) in steps"
|
||||||
:key="step[0].uuid"
|
:key="step[0].uuid"
|
||||||
>
|
>
|
||||||
<template
|
<template
|
||||||
@@ -18,15 +18,20 @@
|
|||||||
<FieldArea
|
<FieldArea
|
||||||
:ref="setAreaRef"
|
:ref="setAreaRef"
|
||||||
v-model="values[field.uuid]"
|
v-model="values[field.uuid]"
|
||||||
|
:values="values"
|
||||||
:field="field"
|
:field="field"
|
||||||
:area="area"
|
:area="area"
|
||||||
:submittable="true"
|
:submittable="submittable"
|
||||||
:field-index="fieldIndex"
|
:field-index="fieldIndex"
|
||||||
|
:scroll-padding="scrollPadding"
|
||||||
|
:submitter="submitter"
|
||||||
|
:with-field-placeholder="withFieldPlaceholder"
|
||||||
|
:with-signature-id="withSignatureId"
|
||||||
:is-active="currentStep === step"
|
:is-active="currentStep === step"
|
||||||
:with-label="withLabel"
|
:with-label="withLabel && !withFieldPlaceholder && step.length < 2"
|
||||||
:is-value-set="step.some((f) => f.uuid in values)"
|
:is-value-set="step.some((f) => f.uuid in values)"
|
||||||
:attachments-index="attachmentsIndex"
|
:attachments-index="attachmentsIndex"
|
||||||
@click="$emit('focus-step', step)"
|
@click="$emit('focus-step', stepIndex)"
|
||||||
/>
|
/>
|
||||||
</Teleport>
|
</Teleport>
|
||||||
</template>
|
</template>
|
||||||
@@ -48,11 +53,35 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: () => []
|
default: () => []
|
||||||
},
|
},
|
||||||
|
withSignatureId: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
submittable: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
submitter: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
values: {
|
values: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: false,
|
required: false,
|
||||||
default: () => ({})
|
default: () => ({})
|
||||||
},
|
},
|
||||||
|
withFieldPlaceholder: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
scrollPadding: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: '-80px'
|
||||||
|
},
|
||||||
attachmentsIndex: {
|
attachmentsIndex: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -63,6 +92,11 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: true
|
default: true
|
||||||
},
|
},
|
||||||
|
scrollEl: {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
currentStep: {
|
currentStep: {
|
||||||
type: Array,
|
type: Array,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -97,7 +131,20 @@ export default {
|
|||||||
if (container.style.overflow === 'hidden') {
|
if (container.style.overflow === 'hidden') {
|
||||||
this.scrollInContainer(areaRef.$el)
|
this.scrollInContainer(areaRef.$el)
|
||||||
} else {
|
} else {
|
||||||
areaRef.$refs.scrollToElem.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
const targetRect = areaRef.$refs.scrollToElem.getBoundingClientRect()
|
||||||
|
const scrollEl = this.scrollEl || window
|
||||||
|
|
||||||
|
let rootRect = {}
|
||||||
|
|
||||||
|
if (this.scrollEl === document.documentElement) {
|
||||||
|
rootRect = this.scrollEl.getBoundingClientRect()
|
||||||
|
} else {
|
||||||
|
const root = this.$root.$el?.parentNode?.classList?.contains('ds') ? this.$root.$el : document.body
|
||||||
|
|
||||||
|
rootRect = root.getBoundingClientRect()
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollEl.scrollTo({ top: targetRect.top - rootRect.top, behavior: 'smooth' })
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
@@ -116,7 +163,7 @@ export default {
|
|||||||
|
|
||||||
const targetTopRelativeToBox = targetRect.top - boxRect.top
|
const targetTopRelativeToBox = targetRect.top - boxRect.top
|
||||||
|
|
||||||
scrollbox.scrollTop = targetTopRelativeToBox - container.offsetHeight + formContainer.offsetHeight + target.offsetHeight + padding
|
scrollbox.scrollTo({ top: targetTopRelativeToBox - container.offsetHeight + formContainer.offsetHeight + target.offsetHeight + padding, behavior: 'smooth' })
|
||||||
},
|
},
|
||||||
setAreaRef (el) {
|
setAreaRef (el) {
|
||||||
if (el) {
|
if (el) {
|
||||||
|
|||||||
@@ -52,6 +52,7 @@
|
|||||||
:message="`${t('upload')} ${field.name || t('files')}${field.required ? '' : ` (${t('optional')})`}`"
|
:message="`${t('upload')} ${field.name || t('files')}${field.required ? '' : ` (${t('optional')})`}`"
|
||||||
:submitter-slug="submitterSlug"
|
:submitter-slug="submitterSlug"
|
||||||
:multiple="true"
|
:multiple="true"
|
||||||
|
:dry-run="dryRun"
|
||||||
@upload="onUpload"
|
@upload="onUpload"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -80,6 +81,11 @@ export default {
|
|||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
dryRun: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
attachmentsIndex: {
|
attachmentsIndex: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: false,
|
required: false,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
|
id="form_completed"
|
||||||
class="mx-auto max-w-md flex flex-col"
|
class="mx-auto max-w-md flex flex-col"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
>
|
>
|
||||||
@@ -10,7 +11,7 @@
|
|||||||
:height="30"
|
:height="30"
|
||||||
/>
|
/>
|
||||||
<span>
|
<span>
|
||||||
{{ completedMessage.title || t('form_has_been_completed') }}
|
{{ completedMessage.title || (hasSignatureFields ? (hasMultipleDocuments ? t('documents_have_been_signed') : t('document_has_been_signed')) : t('form_has_been_completed')) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -75,7 +76,7 @@
|
|||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
v-if="isDemo"
|
v-if="isDemo"
|
||||||
href="https://docuseal.co/sign_up"
|
href="https://docuseal.com/sign_up"
|
||||||
class="white-button flex items-center space-x-1 w-full"
|
class="white-button flex items-center space-x-1 w-full"
|
||||||
>
|
>
|
||||||
<IconLogin />
|
<IconLogin />
|
||||||
@@ -88,9 +89,9 @@
|
|||||||
v-if="attribution"
|
v-if="attribution"
|
||||||
class="text-center mt-4"
|
class="text-center mt-4"
|
||||||
>
|
>
|
||||||
{{ t('signed_with') }}
|
{{ t('powered_by') }}
|
||||||
<a
|
<a
|
||||||
href="https://www.docuseal.co/start"
|
href="https://www.docuseal.com/start"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
class="underline"
|
class="underline"
|
||||||
>DocuSeal</a> - {{ t('open_source_documents_software') }}
|
>DocuSeal</a> - {{ t('open_source_documents_software') }}
|
||||||
@@ -129,6 +130,16 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: true
|
default: true
|
||||||
},
|
},
|
||||||
|
hasSignatureFields: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
hasMultipleDocuments: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
withDownloadButton: {
|
withDownloadButton: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -181,6 +192,10 @@ export default {
|
|||||||
spread: 140
|
spread: 140
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (window.decline_button) {
|
||||||
|
window.decline_button.setAttribute('disabled', 'true')
|
||||||
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
sendCopyToEmail () {
|
sendCopyToEmail () {
|
||||||
@@ -208,7 +223,7 @@ export default {
|
|||||||
this.downloadUrls(urls)
|
this.downloadUrls(urls)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
alert('Failed to download files')
|
alert(this.t('failed_to_download_files'))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -37,13 +37,16 @@
|
|||||||
<AppearsOn :field="field" />
|
<AppearsOn :field="field" />
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<input
|
<input
|
||||||
|
:id="field.uuid"
|
||||||
ref="input"
|
ref="input"
|
||||||
v-model="value"
|
v-model="value"
|
||||||
class="base-input !text-2xl text-center w-full"
|
class="base-input !text-2xl text-center w-full"
|
||||||
:required="field.required"
|
:required="field.required"
|
||||||
type="date"
|
type="date"
|
||||||
:name="`values[${field.uuid}]`"
|
:name="`values[${field.uuid}]`"
|
||||||
|
@keydown.enter="onEnter"
|
||||||
@focus="$emit('focus')"
|
@focus="$emit('focus')"
|
||||||
|
@paste="onPaste"
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -78,7 +81,7 @@ export default {
|
|||||||
default: ''
|
default: ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits: ['update:model-value', 'focus'],
|
emits: ['update:model-value', 'focus', 'submit'],
|
||||||
computed: {
|
computed: {
|
||||||
value: {
|
value: {
|
||||||
set (value) {
|
set (value) {
|
||||||
@@ -90,6 +93,32 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
onEnter (e) {
|
||||||
|
if (this.modelValue) {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
this.$emit('submit')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onPaste (e) {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
let pasteData = e.clipboardData.getData('text').trim()
|
||||||
|
|
||||||
|
if (pasteData.match(/^\d{2}\.\d{2}\.\d{4}$/)) {
|
||||||
|
pasteData = pasteData.split('.').reverse().join('-')
|
||||||
|
}
|
||||||
|
|
||||||
|
const parsedDate = new Date(pasteData)
|
||||||
|
|
||||||
|
if (!isNaN(parsedDate)) {
|
||||||
|
const inputEl = this.$refs.input
|
||||||
|
|
||||||
|
inputEl.valueAsDate = new Date(parsedDate.getTime() - parsedDate.getTimezoneOffset() * 60000)
|
||||||
|
|
||||||
|
inputEl.dispatchEvent(new Event('input', { bubbles: true }))
|
||||||
|
}
|
||||||
|
},
|
||||||
setCurrentDate () {
|
setCurrentDate () {
|
||||||
const inputEl = this.$refs.input
|
const inputEl = this.$refs.input
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
|
id="dropzone"
|
||||||
class="flex h-32 w-full"
|
class="flex h-32 w-full"
|
||||||
@dragover.prevent
|
@dragover.prevent
|
||||||
@drop.prevent="onDropFiles"
|
@drop.prevent="onDropFiles"
|
||||||
@@ -65,6 +66,11 @@ export default {
|
|||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
dryRun: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
accept: {
|
accept: {
|
||||||
type: String,
|
type: String,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -107,20 +113,36 @@ export default {
|
|||||||
Array.from(files).map(async (file) => {
|
Array.from(files).map(async (file) => {
|
||||||
const formData = new FormData()
|
const formData = new FormData()
|
||||||
|
|
||||||
if (file.type === 'image/bmp') {
|
if (this.dryRun) {
|
||||||
file = await this.convertBmpToPng(file)
|
return new Promise((resolve) => {
|
||||||
|
const reader = new FileReader()
|
||||||
|
|
||||||
|
reader.readAsDataURL(file)
|
||||||
|
|
||||||
|
reader.onloadend = () => {
|
||||||
|
resolve({
|
||||||
|
url: reader.result,
|
||||||
|
uuid: Math.random().toString(),
|
||||||
|
filename: file.name
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
if (file.type === 'image/bmp') {
|
||||||
|
file = await this.convertBmpToPng(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
formData.append('file', file)
|
||||||
|
formData.append('submitter_slug', this.submitterSlug)
|
||||||
|
formData.append('name', 'attachments')
|
||||||
|
|
||||||
|
return fetch(this.baseUrl + '/api/attachments', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
}).then(resp => resp.json()).then((data) => {
|
||||||
|
return data
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
formData.append('file', file)
|
|
||||||
formData.append('submitter_slug', this.submitterSlug)
|
|
||||||
formData.append('name', 'attachments')
|
|
||||||
|
|
||||||
return fetch(this.baseUrl + '/api/attachments', {
|
|
||||||
method: 'POST',
|
|
||||||
body: formData
|
|
||||||
}).then(resp => resp.json()).then((data) => {
|
|
||||||
return data
|
|
||||||
})
|
|
||||||
})).then((result) => {
|
})).then((result) => {
|
||||||
this.$emit('upload', result)
|
this.$emit('upload', result)
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
|
|||||||
@@ -3,16 +3,66 @@
|
|||||||
ref="areas"
|
ref="areas"
|
||||||
:steps="stepFields"
|
:steps="stepFields"
|
||||||
:values="values"
|
:values="values"
|
||||||
|
:with-field-placeholder="withFieldPlaceholder"
|
||||||
|
:submitter="submitter"
|
||||||
|
:scroll-el="scrollEl"
|
||||||
|
:with-signature-id="withSignatureId"
|
||||||
:attachments-index="attachmentsIndex"
|
:attachments-index="attachmentsIndex"
|
||||||
:with-label="!isAnonymousChecboxes && showFieldNames"
|
:with-label="!isAnonymousChecboxes && showFieldNames"
|
||||||
:current-step="currentStepFields"
|
:current-step="currentStepFields"
|
||||||
|
:scroll-padding="scrollPadding"
|
||||||
@focus-step="[saveStep(), goToStep($event, false, true), currentField.type !== 'checkbox' ? isFormVisible = true : '']"
|
@focus-step="[saveStep(), goToStep($event, false, true), currentField.type !== 'checkbox' ? isFormVisible = true : '']"
|
||||||
/>
|
/>
|
||||||
|
<FieldAreas
|
||||||
|
:steps="readonlyConditionalFields.map((e) => [e])"
|
||||||
|
:values="readonlyConditionalFields.reduce((acc, f) => { acc[f.uuid] = (values[f.uuid] || f.default_value); return acc }, {})"
|
||||||
|
:submitter="submitter"
|
||||||
|
:attachments-index="attachmentsIndex"
|
||||||
|
:submittable="false"
|
||||||
|
/>
|
||||||
<FormulaFieldAreas
|
<FormulaFieldAreas
|
||||||
v-if="formulaFields.length"
|
v-if="formulaFields.length"
|
||||||
:fields="formulaFields"
|
:fields="formulaFields"
|
||||||
:values="values"
|
:values="values"
|
||||||
/>
|
/>
|
||||||
|
<Teleport
|
||||||
|
v-if="completeButtonToRef"
|
||||||
|
:to="completeButtonToRef"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
v-if="(emptyValueRequiredStep && ((stepFields.length - 1) !== currentStep || currentStepFields !== emptyValueRequiredStep)) || isCompleted"
|
||||||
|
class="tooltip-left"
|
||||||
|
:class="{ tooltip: !isCompleted }"
|
||||||
|
:data-tip="t('fill_all_required_fields_to_complete')"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="btn btn-sm btn-neutral text-white px-4 w-full flex justify-center btn-disabled pointer-events-auto"
|
||||||
|
@click="[isFormVisible = true, !isCompleted && goToStep(stepFields.indexOf(emptyValueRequiredStep), true, false)]"
|
||||||
|
>
|
||||||
|
{{ t('complete') }}
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
v-else
|
||||||
|
id="complete_form_button"
|
||||||
|
class="btn btn-sm btn-neutral text-white px-4 w-full flex justify-center"
|
||||||
|
form="steps_form"
|
||||||
|
type="submit"
|
||||||
|
name="completed"
|
||||||
|
value="true"
|
||||||
|
:disabled="isSubmittingComplete"
|
||||||
|
>
|
||||||
|
<span class="flex items-center">
|
||||||
|
<IconInnerShadowTop
|
||||||
|
v-if="isSubmittingComplete"
|
||||||
|
class="mr-1 animate-spin w-5 h-5"
|
||||||
|
/>
|
||||||
|
<span>
|
||||||
|
{{ t('complete') }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</Teleport>
|
||||||
<button
|
<button
|
||||||
v-if="!isFormVisible"
|
v-if="!isFormVisible"
|
||||||
id="expand_form_button"
|
id="expand_form_button"
|
||||||
@@ -27,8 +77,11 @@
|
|||||||
<template v-else-if="alwaysMinimize">
|
<template v-else-if="alwaysMinimize">
|
||||||
{{ t('next') }}
|
{{ t('next') }}
|
||||||
</template>
|
</template>
|
||||||
|
<template v-else-if="isShowContinue">
|
||||||
|
{{ t('continue') }}
|
||||||
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
{{ t('submit_form') }}
|
{{ t('start_now') }}
|
||||||
</template>
|
</template>
|
||||||
<IconArrowsDiagonal
|
<IconArrowsDiagonal
|
||||||
class="absolute right-0 mr-4"
|
class="absolute right-0 mr-4"
|
||||||
@@ -48,7 +101,7 @@
|
|||||||
id="minimize_form_button"
|
id="minimize_form_button"
|
||||||
class="absolute right-0 mr-2 mt-2 top-0 hidden md:block"
|
class="absolute right-0 mr-2 mt-2 top-0 hidden md:block"
|
||||||
:title="t('minimize')"
|
:title="t('minimize')"
|
||||||
@click.prevent="isFormVisible = false"
|
@click.prevent="minimizeForm"
|
||||||
>
|
>
|
||||||
<IconArrowsDiagonalMinimize2
|
<IconArrowsDiagonalMinimize2
|
||||||
:width="20"
|
:width="20"
|
||||||
@@ -59,7 +112,8 @@
|
|||||||
:class="{ 'md:px-4': isBreakpointMd }"
|
:class="{ 'md:px-4': isBreakpointMd }"
|
||||||
>
|
>
|
||||||
<form
|
<form
|
||||||
v-if="!isCompleted"
|
v-if="!isCompleted && !isInvite"
|
||||||
|
id="steps_form"
|
||||||
ref="form"
|
ref="form"
|
||||||
:action="submitPath"
|
:action="submitPath"
|
||||||
method="post"
|
method="post"
|
||||||
@@ -101,6 +155,7 @@
|
|||||||
v-model="values[currentField.uuid]"
|
v-model="values[currentField.uuid]"
|
||||||
:show-field-names="showFieldNames"
|
:show-field-names="showFieldNames"
|
||||||
:field="currentField"
|
:field="currentField"
|
||||||
|
@submit="!isSubmitting && submitStep()"
|
||||||
@focus="scrollIntoField(currentField)"
|
@focus="scrollIntoField(currentField)"
|
||||||
/>
|
/>
|
||||||
<div v-else-if="currentField.type === 'select'">
|
<div v-else-if="currentField.type === 'select'">
|
||||||
@@ -137,6 +192,7 @@
|
|||||||
dir="auto"
|
dir="auto"
|
||||||
:required="currentField.required"
|
:required="currentField.required"
|
||||||
class="select base-input !text-2xl w-full text-center font-normal"
|
class="select base-input !text-2xl w-full text-center font-normal"
|
||||||
|
:class="{ 'text-gray-300': !values[currentField.uuid] }"
|
||||||
:name="`values[${currentField.uuid}]`"
|
:name="`values[${currentField.uuid}]`"
|
||||||
@change="values[currentField.uuid] = $event.target.value"
|
@change="values[currentField.uuid] = $event.target.value"
|
||||||
@focus="scrollIntoField(currentField)"
|
@focus="scrollIntoField(currentField)"
|
||||||
@@ -144,6 +200,7 @@
|
|||||||
<option
|
<option
|
||||||
value=""
|
value=""
|
||||||
:selected="!values[currentField.uuid]"
|
:selected="!values[currentField.uuid]"
|
||||||
|
class="text-gray-300"
|
||||||
>
|
>
|
||||||
{{ t('select_your_option') }}
|
{{ t('select_your_option') }}
|
||||||
</option>
|
</option>
|
||||||
@@ -152,6 +209,7 @@
|
|||||||
:key="option.uuid"
|
:key="option.uuid"
|
||||||
:selected="values[currentField.uuid] == option.value"
|
:selected="values[currentField.uuid] == option.value"
|
||||||
:value="option.value"
|
:value="option.value"
|
||||||
|
class="text-base-content"
|
||||||
>
|
>
|
||||||
{{ option.value }}
|
{{ option.value }}
|
||||||
</option>
|
</option>
|
||||||
@@ -189,7 +247,7 @@
|
|||||||
<span
|
<span
|
||||||
@click="scrollIntoField(currentField)"
|
@click="scrollIntoField(currentField)"
|
||||||
>
|
>
|
||||||
{{ t('complete_hightlighted_checkboxes_and_click') }} <span class="font-semibold">{{ stepFields.length === currentStep + 1 ? t('submit') : t('next') }}</span>.
|
{{ t('complete_hightlighted_checkboxes_and_click') }} <span class="font-semibold">{{ submitButtonText }}</span>.
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -253,7 +311,7 @@
|
|||||||
>
|
>
|
||||||
<template v-if="isAnonymousChecboxes || !showFieldNames">
|
<template v-if="isAnonymousChecboxes || !showFieldNames">
|
||||||
<span class="text-xl">
|
<span class="text-xl">
|
||||||
{{ t('complete_hightlighted_checkboxes_and_click') }} <span class="font-semibold">{{ stepFields.length === currentStep + 1 ? t('submit') : t('next') }}</span>.
|
{{ t('complete_hightlighted_checkboxes_and_click') }} <span class="font-semibold">{{ submitButtonText }}</span>.
|
||||||
</span>
|
</span>
|
||||||
<input
|
<input
|
||||||
v-for="field in currentStepFields"
|
v-for="field in currentStepFields"
|
||||||
@@ -310,6 +368,7 @@
|
|||||||
:key="currentField.uuid"
|
:key="currentField.uuid"
|
||||||
v-model="values[currentField.uuid]"
|
v-model="values[currentField.uuid]"
|
||||||
:field="currentField"
|
:field="currentField"
|
||||||
|
:dry-run="dryRun"
|
||||||
:attachments-index="attachmentsIndex"
|
:attachments-index="attachmentsIndex"
|
||||||
:submitter-slug="submitterSlug"
|
:submitter-slug="submitterSlug"
|
||||||
:show-field-names="showFieldNames"
|
:show-field-names="showFieldNames"
|
||||||
@@ -320,19 +379,23 @@
|
|||||||
ref="currentStep"
|
ref="currentStep"
|
||||||
:key="currentField.uuid"
|
:key="currentField.uuid"
|
||||||
v-model="values[currentField.uuid]"
|
v-model="values[currentField.uuid]"
|
||||||
|
:reason="values[currentField.preferences?.reason_field_uuid]"
|
||||||
:field="currentField"
|
:field="currentField"
|
||||||
:previous-value="previousSignatureValueFor(currentField) || previousSignatureValue"
|
:previous-value="previousSignatureValueFor(currentField) || previousSignatureValue"
|
||||||
:with-typed-signature="withTypedSignature"
|
:with-typed-signature="withTypedSignature"
|
||||||
:remember-signature="rememberSignature"
|
:remember-signature="rememberSignature"
|
||||||
:attachments-index="attachmentsIndex"
|
:attachments-index="attachmentsIndex"
|
||||||
:button-text="buttonText"
|
:require-signing-reason="requireSigningReason"
|
||||||
|
:button-text="submitButtonText"
|
||||||
|
:dry-run="dryRun"
|
||||||
:with-disclosure="withDisclosure"
|
:with-disclosure="withDisclosure"
|
||||||
:with-qr-button="withQrButton"
|
:with-qr-button="withQrButton"
|
||||||
:submitter="submitter"
|
:submitter="submitter"
|
||||||
:show-field-names="showFieldNames"
|
:show-field-names="showFieldNames"
|
||||||
|
@update:reason="values[currentField.preferences?.reason_field_uuid] = $event"
|
||||||
@attached="attachments.push($event)"
|
@attached="attachments.push($event)"
|
||||||
@start="scrollIntoField(currentField)"
|
@start="scrollIntoField(currentField)"
|
||||||
@minimize="isFormVisible = false"
|
@minimize="minimizeForm"
|
||||||
/>
|
/>
|
||||||
<InitialsStep
|
<InitialsStep
|
||||||
v-else-if="currentField.type === 'initials'"
|
v-else-if="currentField.type === 'initials'"
|
||||||
@@ -340,6 +403,7 @@
|
|||||||
:key="currentField.uuid"
|
:key="currentField.uuid"
|
||||||
v-model="values[currentField.uuid]"
|
v-model="values[currentField.uuid]"
|
||||||
:field="currentField"
|
:field="currentField"
|
||||||
|
:dry-run="dryRun"
|
||||||
:previous-value="previousInitialsValue"
|
:previous-value="previousInitialsValue"
|
||||||
:attachments-index="attachmentsIndex"
|
:attachments-index="attachmentsIndex"
|
||||||
:show-field-names="showFieldNames"
|
:show-field-names="showFieldNames"
|
||||||
@@ -347,12 +411,13 @@
|
|||||||
@attached="attachments.push($event)"
|
@attached="attachments.push($event)"
|
||||||
@start="scrollIntoField(currentField)"
|
@start="scrollIntoField(currentField)"
|
||||||
@focus="scrollIntoField(currentField)"
|
@focus="scrollIntoField(currentField)"
|
||||||
@minimize="isFormVisible = false"
|
@minimize="minimizeForm"
|
||||||
/>
|
/>
|
||||||
<AttachmentStep
|
<AttachmentStep
|
||||||
v-else-if="currentField.type === 'file'"
|
v-else-if="currentField.type === 'file'"
|
||||||
:key="currentField.uuid"
|
:key="currentField.uuid"
|
||||||
v-model="values[currentField.uuid]"
|
v-model="values[currentField.uuid]"
|
||||||
|
:dry-run="dryRun"
|
||||||
:field="currentField"
|
:field="currentField"
|
||||||
:attachments-index="attachmentsIndex"
|
:attachments-index="attachmentsIndex"
|
||||||
:submitter-slug="submitterSlug"
|
:submitter-slug="submitterSlug"
|
||||||
@@ -370,7 +435,7 @@
|
|||||||
:default-value="submitter.phone"
|
:default-value="submitter.phone"
|
||||||
:submitter-slug="submitterSlug"
|
:submitter-slug="submitterSlug"
|
||||||
@focus="scrollIntoField(currentField)"
|
@focus="scrollIntoField(currentField)"
|
||||||
@submit="submitStep"
|
@submit="!isSubmitting && submitStep()"
|
||||||
/>
|
/>
|
||||||
<PaymentStep
|
<PaymentStep
|
||||||
v-else-if="currentField.type === 'payment'"
|
v-else-if="currentField.type === 'payment'"
|
||||||
@@ -379,13 +444,27 @@
|
|||||||
v-model="values[currentField.uuid]"
|
v-model="values[currentField.uuid]"
|
||||||
:field="currentField"
|
:field="currentField"
|
||||||
:submitter-slug="submitterSlug"
|
:submitter-slug="submitterSlug"
|
||||||
|
:values="values"
|
||||||
@attached="attachments.push($event)"
|
@attached="attachments.push($event)"
|
||||||
@focus="scrollIntoField(currentField)"
|
@focus="scrollIntoField(currentField)"
|
||||||
@submit="submitStep"
|
@submit="!isSubmitting && submitStep()"
|
||||||
|
/>
|
||||||
|
<VerificationStep
|
||||||
|
v-else-if="currentField.type === 'verification'"
|
||||||
|
ref="currentStep"
|
||||||
|
:key="currentField.uuid"
|
||||||
|
:locale="language?.toLowerCase() || browserLanguage"
|
||||||
|
:submitter="submitter"
|
||||||
|
:empty-value-required-step="emptyValueRequiredStep"
|
||||||
|
:field="currentField"
|
||||||
|
:submitter-slug="submitterSlug"
|
||||||
|
:values="values"
|
||||||
|
@focus="scrollIntoField(currentField)"
|
||||||
|
@submit="!isSubmitting && submitStep()"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="currentField.type !== 'payment' || submittedValues[currentField.uuid]"
|
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-6 md:mt-8'"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
@@ -401,7 +480,7 @@
|
|||||||
class="mr-1 animate-spin"
|
class="mr-1 animate-spin"
|
||||||
/>
|
/>
|
||||||
<span>
|
<span>
|
||||||
{{ buttonText }}
|
{{ submitButtonText }}
|
||||||
</span><span
|
</span><span
|
||||||
v-if="isSubmitting"
|
v-if="isSubmitting"
|
||||||
class="w-6 flex justify-start mr-1"
|
class="w-6 flex justify-start mr-1"
|
||||||
@@ -416,14 +495,25 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
<InviteForm
|
||||||
|
v-else-if="isInvite"
|
||||||
|
:submitters="inviteSubmitters"
|
||||||
|
:submitter-slug="submitterSlug"
|
||||||
|
:authenticity-token="authenticityToken"
|
||||||
|
:url="baseUrl + submitPath + '/invite'"
|
||||||
|
:style="{ maxWidth: isBreakpointMd ? '582px' : '' }"
|
||||||
|
@success="[isInvite = false, performComplete($event)]"
|
||||||
|
/>
|
||||||
<FormCompleted
|
<FormCompleted
|
||||||
v-else
|
v-else
|
||||||
:is-demo="isDemo"
|
:is-demo="isDemo"
|
||||||
:attribution="attribution"
|
:attribution="attribution"
|
||||||
|
:has-signature-fields="stepFields.some((fields) => fields.some((f) => ['signature', 'initials'].includes(f.type)))"
|
||||||
|
:has-multiple-documents="hasMultipleDocuments"
|
||||||
:completed-button="completedRedirectUrl ? {} : completedButton"
|
:completed-button="completedRedirectUrl ? {} : completedButton"
|
||||||
:completed-message="completedRedirectUrl ? {} : completedMessage"
|
:completed-message="completedRedirectUrl ? {} : completedMessage"
|
||||||
:with-send-copy-button="withSendCopyButton && !completedRedirectUrl"
|
:with-send-copy-button="withSendCopyButton && !completedRedirectUrl"
|
||||||
:with-download-button="withDownloadButton && !completedRedirectUrl"
|
:with-download-button="withDownloadButton && !completedRedirectUrl && !dryRun"
|
||||||
:with-confetti="withConfetti"
|
:with-confetti="withConfetti"
|
||||||
:can-send-email="canSendEmail && !!submitter.email"
|
:can-send-email="canSendEmail && !!submitter.email"
|
||||||
:submitter-slug="submitterSlug"
|
:submitter-slug="submitterSlug"
|
||||||
@@ -439,7 +529,7 @@
|
|||||||
href="#"
|
href="#"
|
||||||
class="inline border border-base-300 h-3 w-3 rounded-full mx-1 mt-1"
|
class="inline border border-base-300 h-3 w-3 rounded-full mx-1 mt-1"
|
||||||
:class="{ 'bg-base-300': index === currentStep, 'bg-base-content': (index < currentStep && stepFields[index].every((f) => !f.required || ![null, undefined, ''].includes(values[f.uuid]))) || isCompleted, 'bg-white': index > currentStep }"
|
:class="{ 'bg-base-300': index === currentStep, 'bg-base-content': (index < currentStep && stepFields[index].every((f) => !f.required || ![null, undefined, ''].includes(values[f.uuid]))) || isCompleted, 'bg-white': index > currentStep }"
|
||||||
@click.prevent="isCompleted ? '' : [saveStep(), goToStep(step, true)]"
|
@click.prevent="isCompleted ? '' : [saveStep(), goToStep(index, true)]"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -461,10 +551,12 @@ import AttachmentStep from './attachment_step'
|
|||||||
import MultiSelectStep from './multi_select_step'
|
import MultiSelectStep from './multi_select_step'
|
||||||
import PhoneStep from './phone_step'
|
import PhoneStep from './phone_step'
|
||||||
import PaymentStep from './payment_step'
|
import PaymentStep from './payment_step'
|
||||||
|
import VerificationStep from './verification_step'
|
||||||
import TextStep from './text_step'
|
import TextStep from './text_step'
|
||||||
import NumberStep from './number_step'
|
import NumberStep from './number_step'
|
||||||
import DateStep from './date_step'
|
import DateStep from './date_step'
|
||||||
import MarkdownContent from './markdown_content'
|
import MarkdownContent from './markdown_content'
|
||||||
|
import InviteForm from './invite_form'
|
||||||
import FormCompleted from './completed'
|
import FormCompleted from './completed'
|
||||||
import { IconInnerShadowTop, IconArrowsDiagonal, IconWritingSign, IconArrowsDiagonalMinimize2 } from '@tabler/icons-vue'
|
import { IconInnerShadowTop, IconArrowsDiagonal, IconWritingSign, IconArrowsDiagonalMinimize2 } from '@tabler/icons-vue'
|
||||||
import AppearsOn from './appears_on'
|
import AppearsOn from './appears_on'
|
||||||
@@ -502,6 +594,8 @@ export default {
|
|||||||
IconWritingSign,
|
IconWritingSign,
|
||||||
AttachmentStep,
|
AttachmentStep,
|
||||||
InitialsStep,
|
InitialsStep,
|
||||||
|
VerificationStep,
|
||||||
|
InviteForm,
|
||||||
MultiSelectStep,
|
MultiSelectStep,
|
||||||
IconInnerShadowTop,
|
IconInnerShadowTop,
|
||||||
DateStep,
|
DateStep,
|
||||||
@@ -528,16 +622,56 @@ export default {
|
|||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
inviteSubmitters: {
|
||||||
|
type: Array,
|
||||||
|
required: false,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
withSignatureId: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
scrollPadding: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: '-80px'
|
||||||
|
},
|
||||||
|
requireSigningReason: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
canSendEmail: {
|
canSendEmail: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
|
completeButtonToRef: {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
type: Array,
|
||||||
|
required: false,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
attachments: {
|
attachments: {
|
||||||
type: Array,
|
type: Array,
|
||||||
required: false,
|
required: false,
|
||||||
default: () => []
|
default: () => []
|
||||||
},
|
},
|
||||||
|
withFieldPlaceholder: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
scrollEl: {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
onComplete: {
|
onComplete: {
|
||||||
type: Function,
|
type: Function,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -694,12 +828,16 @@ export default {
|
|||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
isCompleted: false,
|
isCompleted: false,
|
||||||
|
isInvite: false,
|
||||||
isFormVisible: this.expand !== false,
|
isFormVisible: this.expand !== false,
|
||||||
showFillAllRequiredFields: false,
|
showFillAllRequiredFields: false,
|
||||||
currentStep: 0,
|
currentStep: 0,
|
||||||
|
isShowContinue: false,
|
||||||
|
enableScrollIntoField: true,
|
||||||
phoneVerifiedValues: {},
|
phoneVerifiedValues: {},
|
||||||
orientation: screen?.orientation?.type,
|
orientation: screen?.orientation?.type,
|
||||||
isSubmitting: false,
|
isSubmitting: false,
|
||||||
|
isSubmittingComplete: false,
|
||||||
submittedValues: {},
|
submittedValues: {},
|
||||||
recalculateButtonDisabledKey: ''
|
recalculateButtonDisabledKey: ''
|
||||||
}
|
}
|
||||||
@@ -708,9 +846,37 @@ export default {
|
|||||||
isMobile () {
|
isMobile () {
|
||||||
return /android|iphone|ipad/i.test(navigator.userAgent)
|
return /android|iphone|ipad/i.test(navigator.userAgent)
|
||||||
},
|
},
|
||||||
buttonText () {
|
attachmentConditionsIndex () {
|
||||||
if (this.alwaysMinimize || this.stepFields.length === this.currentStep + 1) {
|
return this.schema.reduce((acc, item) => {
|
||||||
|
if (item.conditions?.length) {
|
||||||
|
if (item.conditions.every((c) => this.fieldsUuidIndex[c.field_uuid])) {
|
||||||
|
acc[item.attachment_uuid] = this.checkFieldConditions(item)
|
||||||
|
} else {
|
||||||
|
acc[item.attachment_uuid] = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
acc[item.attachment_uuid] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
},
|
||||||
|
emptyValueRequiredStep () {
|
||||||
|
return this.stepFields.find((fields, index) => {
|
||||||
|
return fields.some((f) => {
|
||||||
|
return f.required && isEmpty(this.values[f.uuid])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
submitButtonText () {
|
||||||
|
if (this.alwaysMinimize) {
|
||||||
return this.t('submit')
|
return this.t('submit')
|
||||||
|
} else if (this.stepFields.length === this.currentStep + 1) {
|
||||||
|
if (this.currentField.type === 'signature') {
|
||||||
|
return this.t('sign_and_complete')
|
||||||
|
} else {
|
||||||
|
return this.t('complete')
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return this.t('next')
|
return this.t('next')
|
||||||
}
|
}
|
||||||
@@ -718,6 +884,18 @@ export default {
|
|||||||
alwaysMinimize () {
|
alwaysMinimize () {
|
||||||
return this.minimize || (this.orientation?.includes('landscape') && this.isMobile && parseInt(window.innerHeight) < 550)
|
return this.minimize || (this.orientation?.includes('landscape') && this.isMobile && parseInt(window.innerHeight) < 550)
|
||||||
},
|
},
|
||||||
|
hasMultipleDocuments () {
|
||||||
|
return Object.keys(
|
||||||
|
this.stepFields.reduce((acc, fields) => {
|
||||||
|
fields.forEach((f) => {
|
||||||
|
f.areas?.forEach((a) => {
|
||||||
|
acc[a.attachment_uuid] = 1
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
).filter(Boolean).length > 1
|
||||||
|
},
|
||||||
currentStepFields () {
|
currentStepFields () {
|
||||||
return this.stepFields[this.currentStep] || []
|
return this.stepFields[this.currentStep] || []
|
||||||
},
|
},
|
||||||
@@ -762,11 +940,30 @@ export default {
|
|||||||
currentField () {
|
currentField () {
|
||||||
return this.currentStepFields[0]
|
return this.currentStepFields[0]
|
||||||
},
|
},
|
||||||
|
readonlyConditionalFields () {
|
||||||
|
return this.fields.filter((f) => f.readonly && f.conditions?.length && this.checkFieldConditions(f) && this.checkFieldDocumentsConditions(f))
|
||||||
|
},
|
||||||
stepFields () {
|
stepFields () {
|
||||||
return this.fields.filter((f) => !f.readonly).reduce((acc, f) => {
|
const verificationFields = []
|
||||||
|
|
||||||
|
const sortedFields = this.fields.reduce((acc, f) => {
|
||||||
|
if (f.type === 'verification') {
|
||||||
|
verificationFields.push(f)
|
||||||
|
} else if (!f.readonly) {
|
||||||
|
acc.push(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
if (verificationFields.length) {
|
||||||
|
sortedFields.push(verificationFields.pop())
|
||||||
|
}
|
||||||
|
|
||||||
|
return sortedFields.reduce((acc, f) => {
|
||||||
const prevStep = acc[acc.length - 1]
|
const prevStep = acc[acc.length - 1]
|
||||||
|
|
||||||
if (this.checkFieldConditions(f)) {
|
if (this.checkFieldConditions(f) && this.checkFieldDocumentsConditions(f)) {
|
||||||
if (f.type === 'checkbox' && Array.isArray(prevStep) && prevStep[0].type === 'checkbox' && !f.description) {
|
if (f.type === 'checkbox' && Array.isArray(prevStep) && prevStep[0].type === 'checkbox' && !f.description) {
|
||||||
prevStep.push(f)
|
prevStep.push(f)
|
||||||
} else {
|
} else {
|
||||||
@@ -778,7 +975,7 @@ export default {
|
|||||||
}, [])
|
}, [])
|
||||||
},
|
},
|
||||||
formulaFields () {
|
formulaFields () {
|
||||||
return this.fields.filter((f) => f.preferences?.formula)
|
return this.fields.filter((f) => f.preferences?.formula && f.type !== 'payment')
|
||||||
},
|
},
|
||||||
attachmentsIndex () {
|
attachmentsIndex () {
|
||||||
return this.attachments.reduce((acc, a) => {
|
return this.attachments.reduce((acc, a) => {
|
||||||
@@ -799,6 +996,23 @@ export default {
|
|||||||
if (isEmpty(value) && this.currentStep > 0) {
|
if (isEmpty(value) && this.currentStep > 0) {
|
||||||
this.currentStep -= 1
|
this.currentStep -= 1
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
attachmentConditionsIndex: {
|
||||||
|
deep: true,
|
||||||
|
immediate: true,
|
||||||
|
handler (value) {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
const root = this.$root.$el.parentNode.getRootNode()
|
||||||
|
|
||||||
|
for (const key in value) {
|
||||||
|
const doc = root.querySelector(`[id="document-${key}"`)
|
||||||
|
|
||||||
|
if (doc) {
|
||||||
|
doc.classList.toggle('hidden', !value[key])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
beforeUnmount () {
|
beforeUnmount () {
|
||||||
@@ -807,7 +1021,7 @@ export default {
|
|||||||
mounted () {
|
mounted () {
|
||||||
this.submittedValues = JSON.parse(JSON.stringify(this.values))
|
this.submittedValues = JSON.parse(JSON.stringify(this.values))
|
||||||
|
|
||||||
screen?.orientation.addEventListener('change', this.onOrientationChange)
|
screen?.orientation?.addEventListener('change', this.onOrientationChange)
|
||||||
|
|
||||||
this.fields.forEach((field) => {
|
this.fields.forEach((field) => {
|
||||||
if (field.default_value && !field.readonly) {
|
if (field.default_value && !field.readonly) {
|
||||||
@@ -839,11 +1053,11 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (document.body?.clientWidth >= 768 && this.expand !== true && ['signature', 'initials', 'file', 'image'].includes(this.currentField?.type)) {
|
if (document.body?.clientWidth >= 768 && this.expand !== true && ['signature', 'initials', 'file', 'image'].includes(this.currentField?.type)) {
|
||||||
this.isFormVisible = false
|
this.minimizeForm()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.alwaysMinimize) {
|
if (this.alwaysMinimize) {
|
||||||
this.isFormVisible = false
|
this.minimizeForm()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (/iPhone|iPad|iPod/i.test(navigator.userAgent)) {
|
if (/iPhone|iPad|iPod/i.test(navigator.userAgent)) {
|
||||||
@@ -863,12 +1077,14 @@ export default {
|
|||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.recalculateButtonDisabledKey = Math.random()
|
this.recalculateButtonDisabledKey = Math.random()
|
||||||
|
|
||||||
Promise.all([
|
if (!this.dryRun) {
|
||||||
this.maybeTrackEmailClick(),
|
Promise.all([
|
||||||
this.maybeTrackSmsClick()
|
this.maybeTrackEmailClick(),
|
||||||
]).finally(() => {
|
this.maybeTrackSmsClick()
|
||||||
this.trackViewForm()
|
]).finally(() => {
|
||||||
})
|
this.trackViewForm()
|
||||||
|
})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -878,29 +1094,57 @@ export default {
|
|||||||
onOrientationChange (event) {
|
onOrientationChange (event) {
|
||||||
this.orientation = event.target.type
|
this.orientation = event.target.type
|
||||||
},
|
},
|
||||||
|
checkFieldDocumentsConditions (field) {
|
||||||
|
if (field.areas?.length) {
|
||||||
|
return field.areas.some((area) => {
|
||||||
|
return this.attachmentConditionsIndex[area.attachment_uuid]
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
},
|
||||||
checkFieldConditions (field) {
|
checkFieldConditions (field) {
|
||||||
if (field.conditions?.length) {
|
if (field.conditions?.length) {
|
||||||
return field.conditions.reduce((acc, c) => {
|
const result = field.conditions.reduce((acc, cond) => {
|
||||||
if (['empty', 'unchecked'].includes(c.action)) {
|
if (cond.operation === 'or') {
|
||||||
return acc && isEmpty(this.values[c.field_uuid])
|
acc.push(acc.pop() || this.checkFieldCondition(cond))
|
||||||
} else if (['not_empty', 'checked'].includes(c.action)) {
|
|
||||||
return acc && !isEmpty(this.values[c.field_uuid])
|
|
||||||
} else if (['equal', 'contains'].includes(c.action)) {
|
|
||||||
const field = this.fieldsUuidIndex[c.field_uuid]
|
|
||||||
const option = field.options.find((o) => o.uuid === c.value)
|
|
||||||
const values = [this.values[c.field_uuid]].flat()
|
|
||||||
|
|
||||||
return acc && values.includes(this.optionValue(option, field.options.indexOf(option)))
|
|
||||||
} else if (['not_equal', 'does_not_contain'].includes(c.action)) {
|
|
||||||
const field = this.fieldsUuidIndex[c.field_uuid]
|
|
||||||
const option = field.options.find((o) => o.uuid === c.value)
|
|
||||||
const values = [this.values[c.field_uuid]].flat()
|
|
||||||
|
|
||||||
return acc && !values.includes(this.optionValue(option, field.options.indexOf(option)))
|
|
||||||
} else {
|
} else {
|
||||||
return acc
|
acc.push(this.checkFieldCondition(cond))
|
||||||
}
|
}
|
||||||
}, true)
|
|
||||||
|
return acc
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return !result.includes(false)
|
||||||
|
} else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
checkFieldCondition (condition) {
|
||||||
|
const field = this.fieldsUuidIndex[condition.field_uuid]
|
||||||
|
|
||||||
|
if (['not_empty', 'checked', 'equal', 'contains'].includes(condition.action) && field && !this.checkFieldConditions(field)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (['empty', 'unchecked'].includes(condition.action)) {
|
||||||
|
return isEmpty(this.values[condition.field_uuid])
|
||||||
|
} else if (['not_empty', 'checked'].includes(condition.action)) {
|
||||||
|
return !isEmpty(this.values[condition.field_uuid])
|
||||||
|
} else if (['equal', 'contains'].includes(condition.action) && field) {
|
||||||
|
if (field.options) {
|
||||||
|
const option = field.options.find((o) => o.uuid === condition.value)
|
||||||
|
const values = [this.values[condition.field_uuid]].flat()
|
||||||
|
|
||||||
|
return values.includes(this.optionValue(option, field.options.indexOf(option)))
|
||||||
|
} else {
|
||||||
|
return [this.values[condition.field_uuid]].flat().includes(condition.value)
|
||||||
|
}
|
||||||
|
} else if (['not_equal', 'does_not_contain'].includes(condition.action) && field) {
|
||||||
|
const option = field.options.find((o) => o.uuid === condition.value)
|
||||||
|
const values = [this.values[condition.field_uuid]].flat()
|
||||||
|
|
||||||
|
return !values.includes(this.optionValue(option, field.options.indexOf(option)))
|
||||||
} else {
|
} else {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -982,8 +1226,8 @@ export default {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
goToStep (step, scrollToArea = false, clickUpload = false) {
|
goToStep (stepIndex, scrollToArea = false, clickUpload = false) {
|
||||||
this.currentStep = this.stepFields.indexOf(step)
|
this.currentStep = stepIndex
|
||||||
this.showFillAllRequiredFields = false
|
this.showFillAllRequiredFields = false
|
||||||
|
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
@@ -991,11 +1235,13 @@ export default {
|
|||||||
|
|
||||||
if (!this.isCompleted) {
|
if (!this.isCompleted) {
|
||||||
if (scrollToArea) {
|
if (scrollToArea) {
|
||||||
this.scrollIntoField(step[0])
|
this.scrollIntoField(this.currentField)
|
||||||
|
|
||||||
this.$refs.form.querySelector('input[type="date"], input[type="number"], input[type="text"], select')?.focus()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.enableScrollIntoField = false
|
||||||
|
this.$refs.form.querySelector('input[type="date"], input[type="number"], input[type="text"], select')?.focus()
|
||||||
|
this.enableScrollIntoField = true
|
||||||
|
|
||||||
if (clickUpload && !this.values[this.currentField.uuid] && ['file', 'image'].includes(this.currentField.type)) {
|
if (clickUpload && !this.values[this.currentField.uuid] && ['file', 'image'].includes(this.currentField.type)) {
|
||||||
this.$refs.form.querySelector('input[type="file"]')?.click()
|
this.$refs.form.querySelector('input[type="file"]')?.click()
|
||||||
}
|
}
|
||||||
@@ -1006,7 +1252,17 @@ export default {
|
|||||||
const currentFieldUuids = this.currentStepFields.map((f) => f.uuid)
|
const currentFieldUuids = this.currentStepFields.map((f) => f.uuid)
|
||||||
const currentFieldType = this.currentField.type
|
const currentFieldType = this.currentField.type
|
||||||
|
|
||||||
if (this.isCompleted) {
|
if (!formData && !this.$refs.form.checkValidity()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.dryRun) {
|
||||||
|
currentFieldUuids.forEach((fieldUuid) => {
|
||||||
|
this.submittedValues[fieldUuid] = this.values[fieldUuid]
|
||||||
|
})
|
||||||
|
|
||||||
|
return Promise.resolve({})
|
||||||
|
} else if (this.isCompleted) {
|
||||||
return Promise.resolve({})
|
return Promise.resolve({})
|
||||||
} else {
|
} else {
|
||||||
return fetch(this.baseUrl + this.submitPath, {
|
return fetch(this.baseUrl + this.submitPath, {
|
||||||
@@ -1028,21 +1284,31 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
scrollIntoField (field) {
|
scrollIntoField (field) {
|
||||||
return this.$refs.areas.scrollIntoField(field)
|
if (this.enableScrollIntoField) {
|
||||||
|
return this.$refs.areas.scrollIntoField(field)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
scrollIntoArea (area) {
|
scrollIntoArea (area) {
|
||||||
return this.$refs.areas.scrollIntoArea(area)
|
return this.$refs.areas.scrollIntoArea(area)
|
||||||
},
|
},
|
||||||
async submitStep () {
|
async submitStep (e) {
|
||||||
this.isSubmitting = true
|
this.isSubmitting = true
|
||||||
|
|
||||||
const stepPromise = ['signature', 'phone', 'initials', 'payment'].includes(this.currentField.type)
|
const forceComplete = e?.submitter?.getAttribute('name') === 'completed'
|
||||||
|
|
||||||
|
if (forceComplete) {
|
||||||
|
this.isSubmittingComplete = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const submitStep = this.currentStep
|
||||||
|
|
||||||
|
const stepPromise = ['signature', 'phone', 'initials', 'payment', 'verification'].includes(this.currentField.type)
|
||||||
? this.$refs.currentStep.submit
|
? this.$refs.currentStep.submit
|
||||||
: () => Promise.resolve({})
|
: () => Promise.resolve({})
|
||||||
|
|
||||||
stepPromise().then(async () => {
|
stepPromise().then(async () => {
|
||||||
const emptyRequiredField = this.stepFields.find((fields, index) => {
|
const emptyRequiredField = this.stepFields.find((fields, index) => {
|
||||||
if (index >= this.currentStep) {
|
if (forceComplete ? index === submitStep : index >= submitStep) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1052,9 +1318,9 @@ export default {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const formData = new FormData(this.$refs.form)
|
const formData = new FormData(this.$refs.form)
|
||||||
const isLastStep = this.currentStep === this.stepFields.length - 1
|
const isLastStep = (submitStep === this.stepFields.length - 1) || forceComplete
|
||||||
|
|
||||||
if (isLastStep && !emptyRequiredField && !this.dryRun) {
|
if (isLastStep && !emptyRequiredField && !this.inviteSubmitters.length) {
|
||||||
formData.append('completed', 'true')
|
formData.append('completed', 'true')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1070,54 +1336,69 @@ export default {
|
|||||||
if (response.status === 422 || response.status === 500) {
|
if (response.status === 422 || response.status === 500) {
|
||||||
const data = await response.json()
|
const data = await response.json()
|
||||||
|
|
||||||
alert(data.error || 'Value is invalid')
|
if (data.error) {
|
||||||
|
const i18nKey = data.error.replace(/\s+/g, '_').toLowerCase()
|
||||||
|
|
||||||
|
alert(this.t(i18nKey) !== i18nKey ? this.t(i18nKey) : data.error)
|
||||||
|
} else {
|
||||||
|
alert(this.t('value_is_invalid'))
|
||||||
|
}
|
||||||
|
|
||||||
return Promise.reject(new Error(data.error))
|
return Promise.reject(new Error(data.error))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isLastStep) {
|
const nextStep = (isLastStep && emptyRequiredField) || (forceComplete ? null : this.stepFields[submitStep + 1])
|
||||||
this.isSecondWalkthrough = true
|
|
||||||
}
|
|
||||||
|
|
||||||
const nextStep = (isLastStep && emptyRequiredField) || this.stepFields[this.currentStep + 1]
|
|
||||||
|
|
||||||
if (nextStep) {
|
if (nextStep) {
|
||||||
if (this.alwaysMinimize) {
|
if (this.alwaysMinimize) {
|
||||||
this.isFormVisible = false
|
this.minimizeForm()
|
||||||
}
|
}
|
||||||
|
|
||||||
this.goToStep(nextStep, this.autoscrollFields)
|
this.goToStep(this.stepFields.indexOf(nextStep), this.autoscrollFields)
|
||||||
|
|
||||||
if (emptyRequiredField === nextStep) {
|
if (emptyRequiredField === nextStep) {
|
||||||
this.showFillAllRequiredFields = true
|
this.showFillAllRequiredFields = true
|
||||||
}
|
}
|
||||||
|
} else if (this.inviteSubmitters.length) {
|
||||||
|
this.isInvite = true
|
||||||
} else {
|
} else {
|
||||||
this.isCompleted = true
|
this.performComplete(response)
|
||||||
|
|
||||||
const respData = await response.text()
|
|
||||||
|
|
||||||
if (respData) {
|
|
||||||
this.onComplete(JSON.parse(respData))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.completedRedirectUrl) {
|
|
||||||
window.location.href = this.completedRedirectUrl
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
this.isSubmitting = false
|
this.isSubmitting = false
|
||||||
|
this.isSubmittingComplete = false
|
||||||
})
|
})
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
if (error?.message === 'Image too small') {
|
if (error?.message === 'Image too small') {
|
||||||
alert('Signature is too small - please redraw.')
|
alert(this.t('signature_is_too_small_please_redraw'))
|
||||||
} else {
|
} else {
|
||||||
console.log(error)
|
console.log(error)
|
||||||
}
|
}
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
this.isSubmitting = false
|
this.isSubmitting = false
|
||||||
|
this.isSubmittingComplete = false
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
minimizeForm () {
|
||||||
|
this.isFormVisible = false
|
||||||
|
this.isShowContinue = true
|
||||||
|
},
|
||||||
|
async performComplete (resp) {
|
||||||
|
this.isCompleted = true
|
||||||
|
|
||||||
|
if (resp?.text) {
|
||||||
|
const respData = await resp.text()
|
||||||
|
|
||||||
|
if (respData) {
|
||||||
|
this.onComplete(JSON.parse(respData))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.completedRedirectUrl) {
|
||||||
|
window.location.href = this.completedRedirectUrl
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,10 @@
|
|||||||
const en = {
|
const en = {
|
||||||
|
complete_all_required_fields_to_proceed_with_identity_verification: 'Complete all required fields to proceed with identity verification.',
|
||||||
|
verify_id: 'Verify ID',
|
||||||
|
identity_verification: 'Identity verification',
|
||||||
|
complete: 'Complete',
|
||||||
|
fill_all_required_fields_to_complete: 'Fill all required fields to complete',
|
||||||
|
sign_and_complete: 'Sign and Complete',
|
||||||
text: 'Text',
|
text: 'Text',
|
||||||
by_clicking_you_agree_to_the: 'By clicking "{button}", you agree to the',
|
by_clicking_you_agree_to_the: 'By clicking "{button}", you agree to the',
|
||||||
electronic_signature_disclosure: 'Electronic Signature Disclosure',
|
electronic_signature_disclosure: 'Electronic Signature Disclosure',
|
||||||
@@ -6,12 +12,29 @@ const en = {
|
|||||||
signature: 'Signature',
|
signature: 'Signature',
|
||||||
initials: 'Initials',
|
initials: 'Initials',
|
||||||
drawn_signature_on_a_touchscreen_device: 'Drawn signature on a touchscreen device',
|
drawn_signature_on_a_touchscreen_device: 'Drawn signature on a touchscreen device',
|
||||||
|
approved: 'Approved',
|
||||||
|
reviewed: 'Reviewed',
|
||||||
|
other: 'Other',
|
||||||
|
authored_by_me: 'Authored by me',
|
||||||
|
invite: 'Invite',
|
||||||
|
email: 'Email',
|
||||||
|
approved_by: 'Approved by',
|
||||||
|
reviewed_by: 'Reviewed by',
|
||||||
|
authored_by: 'Authored by',
|
||||||
|
select_a_reason: 'Select a reason',
|
||||||
scan_the_qr_code_with_the_camera_app_to_open_the_form_on_mobile_and_draw_your_signature: 'Scan the QR code with the camera app to open the form on mobile and draw your signature',
|
scan_the_qr_code_with_the_camera_app_to_open_the_form_on_mobile_and_draw_your_signature: 'Scan the QR code with the camera app to open the form on mobile and draw your signature',
|
||||||
date: 'Date',
|
date: 'Date',
|
||||||
number: 'Number',
|
number: 'Number',
|
||||||
|
value_is_invalid: 'Value is invalid',
|
||||||
|
verification_code_is_invalid: 'Verification code is invalid',
|
||||||
|
already_paid: 'Already paid',
|
||||||
image: 'Image',
|
image: 'Image',
|
||||||
|
pay: 'Pay',
|
||||||
take_photo: 'Take photo',
|
take_photo: 'Take photo',
|
||||||
|
number_phone_is_invalid: '{number} phone is invalid',
|
||||||
file: 'File',
|
file: 'File',
|
||||||
|
digitally_signed_by: 'Digitally signed by',
|
||||||
|
reason: 'Reason',
|
||||||
select: 'Select',
|
select: 'Select',
|
||||||
checkbox: 'Checkbox',
|
checkbox: 'Checkbox',
|
||||||
multiple: 'Multiple',
|
multiple: 'Multiple',
|
||||||
@@ -21,7 +44,8 @@ const en = {
|
|||||||
minimize: 'Minimize',
|
minimize: 'Minimize',
|
||||||
payment: 'Payment',
|
payment: 'Payment',
|
||||||
phone: 'Phone',
|
phone: 'Phone',
|
||||||
submit_form: 'Submit Form',
|
start_now: 'Start Now',
|
||||||
|
continue: 'Continue',
|
||||||
sign_now: 'Sign Now',
|
sign_now: 'Sign Now',
|
||||||
type_here_: 'Type here...',
|
type_here_: 'Type here...',
|
||||||
optional: 'optional',
|
optional: 'optional',
|
||||||
@@ -42,9 +66,11 @@ const en = {
|
|||||||
type_signature_here: 'Type signature here',
|
type_signature_here: 'Type signature here',
|
||||||
type_initial_here: 'Type initials here',
|
type_initial_here: 'Type initials here',
|
||||||
form_has_been_completed: 'Form has been completed!',
|
form_has_been_completed: 'Form has been completed!',
|
||||||
|
document_has_been_signed: 'Document has been signed!',
|
||||||
|
documents_have_been_signed: 'Documents have been signed!',
|
||||||
create_a_free_account: 'Create a Free Account',
|
create_a_free_account: 'Create a Free Account',
|
||||||
signed_with: 'Signed with',
|
powered_by: 'Powered by',
|
||||||
please_check_the_box_to_continue: 'Please check the box to continue',
|
please_check_the_box_to_continue: 'Please check the box to continue.',
|
||||||
open_source_documents_software: 'open source documents software',
|
open_source_documents_software: 'open source documents software',
|
||||||
verified_phone_number: 'Verify Phone Number',
|
verified_phone_number: 'Verify Phone Number',
|
||||||
use_international_format: 'Use international format: +1xxx',
|
use_international_format: 'Use international format: +1xxx',
|
||||||
@@ -66,18 +92,41 @@ const en = {
|
|||||||
pay_with_strip: 'Pay with Stripe',
|
pay_with_strip: 'Pay with Stripe',
|
||||||
reupload: 'Reupload',
|
reupload: 'Reupload',
|
||||||
upload: 'Upload',
|
upload: 'Upload',
|
||||||
files: 'Files'
|
files: 'Files',
|
||||||
|
signature_is_too_small_please_redraw: 'Signature is too small. Please redraw.'
|
||||||
}
|
}
|
||||||
|
|
||||||
const es = {
|
const es = {
|
||||||
|
complete_all_required_fields_to_proceed_with_identity_verification: 'Complete todos los campos requeridos para continuar con la verificación de identidad.',
|
||||||
|
verify_id: 'Verificar ID',
|
||||||
|
identity_verification: 'Verificación de identidad',
|
||||||
|
complete: 'Completar',
|
||||||
|
fill_all_required_fields_to_complete: 'Complete todos los campos requeridos para finalizar',
|
||||||
|
sign_and_complete: 'Firmar y Completar',
|
||||||
|
invite: 'Invitar',
|
||||||
|
email: 'Correo electrónico',
|
||||||
|
approved: 'Aprobado',
|
||||||
|
reviewed: 'Revisado',
|
||||||
|
other: 'Otro',
|
||||||
|
authored_by_me: 'Escrito por mí',
|
||||||
|
approved_by: 'Aprobado por',
|
||||||
|
reviewed_by: 'Revisado por',
|
||||||
|
authored_by: 'Escrito por',
|
||||||
|
select_a_reason: 'Selecciona una razón',
|
||||||
|
value_is_invalid: 'El valor no es válido',
|
||||||
|
verification_code_is_invalid: 'El código de verificación no es válido',
|
||||||
drawn_signature_on_a_touchscreen_device: 'Firma dibujada en un dispositivo con pantalla táctil',
|
drawn_signature_on_a_touchscreen_device: 'Firma dibujada en un dispositivo con pantalla táctil',
|
||||||
scan_the_qr_code_with_the_camera_app_to_open_the_form_on_mobile_and_draw_your_signature: 'Escanea el código QR con la aplicación de la cámara para abrir el formulario en el móvil y dibujar tu firma',
|
scan_the_qr_code_with_the_camera_app_to_open_the_form_on_mobile_and_draw_your_signature: 'Escanea el código QR con la aplicación de la cámara para abrir el formulario en el móvil y dibujar tu firma',
|
||||||
by_clicking_you_agree_to_the: 'Al hacer clic en "{button}", usted acepta el',
|
by_clicking_you_agree_to_the: 'Al hacer clic en "{button}", usted acepta el',
|
||||||
electronic_signature_disclosure: 'Divulgación de Firma Electrónica',
|
electronic_signature_disclosure: 'Divulgación de Firma Electrónica',
|
||||||
esignature_disclosure: 'Divulgación de eFirma',
|
esignature_disclosure: 'Divulgación de eFirma',
|
||||||
|
already_paid: 'Ya pagado',
|
||||||
minimize: 'Minimizar',
|
minimize: 'Minimizar',
|
||||||
text: 'Texto',
|
text: 'Texto',
|
||||||
signature: 'Firma',
|
signature: 'Firma',
|
||||||
|
pay: 'Pagar',
|
||||||
|
digitally_signed_by: 'Firmado digitalmente por',
|
||||||
|
reason: 'Razón',
|
||||||
initials: 'Iniciales',
|
initials: 'Iniciales',
|
||||||
date: 'Fecha',
|
date: 'Fecha',
|
||||||
number: 'Número',
|
number: 'Número',
|
||||||
@@ -85,6 +134,7 @@ const es = {
|
|||||||
file: 'Archivo',
|
file: 'Archivo',
|
||||||
select: 'Seleccionar',
|
select: 'Seleccionar',
|
||||||
checkbox: 'Casilla',
|
checkbox: 'Casilla',
|
||||||
|
number_phone_is_invalid: '{number} teléfono es inválido',
|
||||||
multiple: 'Múltiple',
|
multiple: 'Múltiple',
|
||||||
radio: 'Radio',
|
radio: 'Radio',
|
||||||
cells: 'Celdas',
|
cells: 'Celdas',
|
||||||
@@ -92,7 +142,8 @@ const es = {
|
|||||||
payment: 'Pago',
|
payment: 'Pago',
|
||||||
phone: 'Teléfono',
|
phone: 'Teléfono',
|
||||||
take_photo: 'Tomar foto',
|
take_photo: 'Tomar foto',
|
||||||
submit_form: 'Enviar Formulario',
|
start_now: 'Comenzar ahora',
|
||||||
|
continue: 'Continuar',
|
||||||
sign_now: 'Firmar ahora',
|
sign_now: 'Firmar ahora',
|
||||||
type_here_: 'Escribe aquí...',
|
type_here_: 'Escribe aquí...',
|
||||||
optional: 'opcional',
|
optional: 'opcional',
|
||||||
@@ -112,9 +163,11 @@ const es = {
|
|||||||
type_signature_here: 'Escribe la firma aquí',
|
type_signature_here: 'Escribe la firma aquí',
|
||||||
type_initial_here: 'Escribe las iniciales aquí',
|
type_initial_here: 'Escribe las iniciales aquí',
|
||||||
form_has_been_completed: '¡El formulario ha sido completado!',
|
form_has_been_completed: '¡El formulario ha sido completado!',
|
||||||
|
document_has_been_signed: '¡El documento ha sido firmado!',
|
||||||
|
documents_have_been_signed: '¡Los documentos han sido firmados!',
|
||||||
create_a_free_account: 'Crear una Cuenta Gratuita',
|
create_a_free_account: 'Crear una Cuenta Gratuita',
|
||||||
signed_with: 'Firmado con',
|
powered_by: 'Desarrollado por',
|
||||||
please_check_the_box_to_continue: 'Por favor marque la casilla para continuar',
|
please_check_the_box_to_continue: 'Por favor marque la casilla para continuar.',
|
||||||
open_source_documents_software: 'software de documentos de código abierto',
|
open_source_documents_software: 'software de documentos de código abierto',
|
||||||
verified_phone_number: 'Verificar número de teléfono',
|
verified_phone_number: 'Verificar número de teléfono',
|
||||||
use_international_format: 'Usar formato internacional: +1xxx',
|
use_international_format: 'Usar formato internacional: +1xxx',
|
||||||
@@ -136,32 +189,57 @@ const es = {
|
|||||||
pay_with_strip: 'Pagar con Stripe',
|
pay_with_strip: 'Pagar con Stripe',
|
||||||
reupload: 'Volver a subir',
|
reupload: 'Volver a subir',
|
||||||
upload: 'Subir',
|
upload: 'Subir',
|
||||||
files: 'Archivos'
|
files: 'Archivos',
|
||||||
|
signature_is_too_small_please_redraw: 'La firma es demasiado pequeña. Por favor, dibújala de nuevo.'
|
||||||
}
|
}
|
||||||
|
|
||||||
const it = {
|
const it = {
|
||||||
|
complete_all_required_fields_to_proceed_with_identity_verification: "Compila tutti i campi obbligatori per procedere con la verifica dell'identità.",
|
||||||
|
verify_id: 'Verifica ID',
|
||||||
|
identity_verification: "Verifica dell'identità",
|
||||||
|
complete: 'Completa',
|
||||||
|
fill_all_required_fields_to_complete: 'Compila tutti i campi obbligatori per completare',
|
||||||
|
sign_and_complete: 'Firma e Completa',
|
||||||
|
invite: 'Invita',
|
||||||
|
email: 'Email',
|
||||||
|
approved: 'Approvato',
|
||||||
|
reviewed: 'Revisionato',
|
||||||
|
other: 'Altro',
|
||||||
|
authored_by_me: 'Creato da me',
|
||||||
|
approved_by: 'Approvato da',
|
||||||
|
reviewed_by: 'Revisionato da',
|
||||||
|
authored_by: 'Creato da',
|
||||||
|
select_a_reason: 'Seleziona una ragione',
|
||||||
|
value_is_invalid: 'Il valore non è valido',
|
||||||
|
verification_code_is_invalid: 'Il codice di verifica non è valido',
|
||||||
drawn_signature_on_a_touchscreen_device: 'Firma disegnata su un dispositivo con schermo tattile',
|
drawn_signature_on_a_touchscreen_device: 'Firma disegnata su un dispositivo con schermo tattile',
|
||||||
scan_the_qr_code_with_the_camera_app_to_open_the_form_on_mobile_and_draw_your_signature: 'Scansiona il codice QR con l\'app della fotocamera per aprire il modulo sul cellulare e disegnare la tua firma',
|
scan_the_qr_code_with_the_camera_app_to_open_the_form_on_mobile_and_draw_your_signature: 'Scansiona il codice QR con l\'app della fotocamera per aprire il modulo sul cellulare e disegnare la tua firma',
|
||||||
by_clicking_you_agree_to_the: 'Cliccando su "{button}", accetti il',
|
by_clicking_you_agree_to_the: 'Cliccando su "{button}", accetti il',
|
||||||
electronic_signature_disclosure: 'Divulgazione della Firma Elettronica',
|
electronic_signature_disclosure: 'Divulgazione della Firma Elettronica',
|
||||||
esignature_disclosure: 'Divulgazione della eFirma',
|
esignature_disclosure: 'Divulgazione della eFirma',
|
||||||
minimize: 'Minimizza',
|
minimize: 'Minimizza',
|
||||||
|
already_paid: 'Pagato',
|
||||||
text: 'Testo',
|
text: 'Testo',
|
||||||
signature: 'Firma',
|
signature: 'Firma',
|
||||||
|
digitally_signed_by: 'Firmato digitalmente da',
|
||||||
|
reason: 'Motivo',
|
||||||
initials: 'Iniziali',
|
initials: 'Iniziali',
|
||||||
date: 'Data',
|
date: 'Data',
|
||||||
number: 'Numero',
|
number: 'Numero',
|
||||||
image: 'Immagine',
|
image: 'Immagine',
|
||||||
|
pay: 'Pagamento',
|
||||||
file: 'File',
|
file: 'File',
|
||||||
select: 'Seleziona',
|
select: 'Seleziona',
|
||||||
checkbox: 'Casella di controllo',
|
checkbox: 'Checkbox',
|
||||||
multiple: 'Multiplo',
|
multiple: 'Multiplo',
|
||||||
radio: 'Radio',
|
radio: 'Radio',
|
||||||
cells: 'Celle',
|
cells: 'Celle',
|
||||||
stamp: 'Timbro',
|
stamp: 'Timbro',
|
||||||
payment: 'Pagamento',
|
payment: 'Pagamento',
|
||||||
|
number_phone_is_invalid: '{number} telefono non è valido',
|
||||||
phone: 'Telefono',
|
phone: 'Telefono',
|
||||||
submit_form: 'Invia Modulo',
|
start_now: 'Inizia ora',
|
||||||
|
continue: 'Continua',
|
||||||
sign_now: 'Firma ora',
|
sign_now: 'Firma ora',
|
||||||
type_here_: 'Digita qui...',
|
type_here_: 'Digita qui...',
|
||||||
optional: 'opzionale',
|
optional: 'opzionale',
|
||||||
@@ -182,9 +260,11 @@ const it = {
|
|||||||
type_signature_here: 'Scrivi la firma qui',
|
type_signature_here: 'Scrivi la firma qui',
|
||||||
type_initial_here: 'Scrivi le iniziali qui',
|
type_initial_here: 'Scrivi le iniziali qui',
|
||||||
form_has_been_completed: 'Il modulo è stato completato!',
|
form_has_been_completed: 'Il modulo è stato completato!',
|
||||||
|
document_has_been_signed: 'Il documento è stato firmato!',
|
||||||
|
documents_have_been_signed: 'I documenti sono stati firmati!',
|
||||||
create_a_free_account: 'Crea un Account Gratuito',
|
create_a_free_account: 'Crea un Account Gratuito',
|
||||||
signed_with: 'Firmato con',
|
powered_by: 'Desarrollado por',
|
||||||
please_check_the_box_to_continue: 'Si prega di spuntare la casella per continuare',
|
please_check_the_box_to_continue: 'Si prega di spuntare la casella per continuare.',
|
||||||
open_source_documents_software: 'software di documenti open source',
|
open_source_documents_software: 'software di documenti open source',
|
||||||
verified_phone_number: 'Verifica numero di telefono',
|
verified_phone_number: 'Verifica numero di telefono',
|
||||||
use_international_format: 'Usa formato internazionale: +1xxx',
|
use_international_format: 'Usa formato internazionale: +1xxx',
|
||||||
@@ -206,22 +286,45 @@ const it = {
|
|||||||
pay_with_strip: 'Paga con Stripe',
|
pay_with_strip: 'Paga con Stripe',
|
||||||
reupload: 'Ricarica',
|
reupload: 'Ricarica',
|
||||||
upload: 'Carica',
|
upload: 'Carica',
|
||||||
files: 'File'
|
files: 'File',
|
||||||
|
signature_is_too_small_please_redraw: 'La firma è troppo piccola. Ridisegnala per favore.'
|
||||||
}
|
}
|
||||||
|
|
||||||
const de = {
|
const de = {
|
||||||
|
complete_all_required_fields_to_proceed_with_identity_verification: 'Vervollständigen Sie alle erforderlichen Felder, um mit der Identitätsverifizierung fortzufahren.',
|
||||||
|
verify_id: 'ID überprüfen',
|
||||||
|
identity_verification: 'Identitätsüberprüfung',
|
||||||
|
complete: 'Abschließen',
|
||||||
|
fill_all_required_fields_to_complete: 'Alle erforderlichen Felder ausfüllen, um abzuschließen',
|
||||||
|
sign_and_complete: 'Signieren und Abschließen',
|
||||||
|
invite: 'Einladen',
|
||||||
|
email: 'E-Mail',
|
||||||
|
approved: 'Genehmigt',
|
||||||
|
reviewed: 'Überprüft',
|
||||||
|
other: 'Andere',
|
||||||
|
authored_by_me: 'Von mir verfasst',
|
||||||
|
approved_by: 'Genehmigt von',
|
||||||
|
reviewed_by: 'Überprüft von',
|
||||||
|
authored_by: 'Verfasst von',
|
||||||
|
select_a_reason: 'Grund auswählen',
|
||||||
|
value_is_invalid: 'Wert ist ungültig',
|
||||||
|
verification_code_is_invalid: 'Bestätigungscode ist ungültig',
|
||||||
drawn_signature_on_a_touchscreen_device: 'Gezeichnete Unterschrift auf einem Touchscreen-Gerät',
|
drawn_signature_on_a_touchscreen_device: 'Gezeichnete Unterschrift auf einem Touchscreen-Gerät',
|
||||||
scan_the_qr_code_with_the_camera_app_to_open_the_form_on_mobile_and_draw_your_signature: 'Scannen Sie den QR-Code mit der Kamera-App, um das Formular auf dem Handy zu öffnen und Ihre Unterschrift zu zeichnen',
|
scan_the_qr_code_with_the_camera_app_to_open_the_form_on_mobile_and_draw_your_signature: 'Scannen Sie den QR-Code mit der Kamera-App, um das Formular auf dem Handy zu öffnen und Ihre Unterschrift zu zeichnen',
|
||||||
by_clicking_you_agree_to_the: 'Durch Klicken auf "{button}" stimmen Sie zu, dass Sie die',
|
by_clicking_you_agree_to_the: 'Durch Klicken auf "{button}" stimmen Sie zu, dass Sie die',
|
||||||
electronic_signature_disclosure: 'Elektronische Unterschriftenoffenlegung',
|
electronic_signature_disclosure: 'Elektronische Unterschriftenoffenlegung',
|
||||||
esignature_disclosure: 'eSignatur Offenlegung',
|
esignature_disclosure: 'eSignatur Offenlegung',
|
||||||
|
already_paid: 'Bereits bezahlt',
|
||||||
minimize: 'Minimieren',
|
minimize: 'Minimieren',
|
||||||
text: 'Text',
|
text: 'Text',
|
||||||
signature: 'Unterschrift',
|
signature: 'Unterschrift',
|
||||||
|
digitally_signed_by: 'Digital unterschrieben von',
|
||||||
|
reason: 'Grund',
|
||||||
initials: 'Initialen',
|
initials: 'Initialen',
|
||||||
date: 'Datum',
|
date: 'Datum',
|
||||||
number: 'Nummer',
|
number: 'Nummer',
|
||||||
image: 'Bild',
|
image: 'Bild',
|
||||||
|
pay: 'Bezahlen',
|
||||||
file: 'Datei',
|
file: 'Datei',
|
||||||
select: 'Auswählen',
|
select: 'Auswählen',
|
||||||
checkbox: 'Checkbox',
|
checkbox: 'Checkbox',
|
||||||
@@ -230,8 +333,10 @@ const de = {
|
|||||||
cells: 'Zellen',
|
cells: 'Zellen',
|
||||||
stamp: 'Stempel',
|
stamp: 'Stempel',
|
||||||
payment: 'Zahlung',
|
payment: 'Zahlung',
|
||||||
|
number_phone_is_invalid: '{number} Telefonnummer ist ungültig',
|
||||||
phone: 'Telefon',
|
phone: 'Telefon',
|
||||||
submit_form: 'Formular absenden',
|
start_now: 'Jetzt starten',
|
||||||
|
continue: 'Fortsetzen',
|
||||||
sign_now: 'Jetzt unterschreiben',
|
sign_now: 'Jetzt unterschreiben',
|
||||||
type_here_: 'Hier eingeben...',
|
type_here_: 'Hier eingeben...',
|
||||||
optional: 'optional',
|
optional: 'optional',
|
||||||
@@ -252,9 +357,11 @@ const de = {
|
|||||||
type_signature_here: 'Unterschrift hier eingeben',
|
type_signature_here: 'Unterschrift hier eingeben',
|
||||||
type_initial_here: 'Initialen hier eingeben',
|
type_initial_here: 'Initialen hier eingeben',
|
||||||
form_has_been_completed: 'Formular wurde ausgefüllt!',
|
form_has_been_completed: 'Formular wurde ausgefüllt!',
|
||||||
|
document_has_been_signed: 'Dokument wurde unterschrieben!',
|
||||||
|
documents_have_been_signed: 'Dokumente wurden unterschrieben!',
|
||||||
create_a_free_account: 'Kostenloses Konto erstellen',
|
create_a_free_account: 'Kostenloses Konto erstellen',
|
||||||
signed_with: 'Unterschrieben mit',
|
powered_by: 'Bereitgestellt von',
|
||||||
please_check_the_box_to_continue: 'Bitte setzen Sie das Häkchen, um fortzufahren',
|
please_check_the_box_to_continue: 'Bitte setzen Sie das Häkchen, um fortzufahren.',
|
||||||
open_source_documents_software: 'Open-Source-Dokumentensoftware',
|
open_source_documents_software: 'Open-Source-Dokumentensoftware',
|
||||||
verified_phone_number: 'Telefonnummer überprüfen',
|
verified_phone_number: 'Telefonnummer überprüfen',
|
||||||
use_international_format: 'Internationales Format verwenden: +1xxx',
|
use_international_format: 'Internationales Format verwenden: +1xxx',
|
||||||
@@ -276,10 +383,29 @@ const de = {
|
|||||||
pay_with_strip: 'Mit Stripe bezahlen',
|
pay_with_strip: 'Mit Stripe bezahlen',
|
||||||
reupload: 'Erneut hochladen',
|
reupload: 'Erneut hochladen',
|
||||||
upload: 'Hochladen',
|
upload: 'Hochladen',
|
||||||
files: 'Dateien'
|
files: 'Dateien',
|
||||||
|
signature_is_too_small_please_redraw: 'Die Unterschrift ist zu klein. Bitte erneut zeichnen.'
|
||||||
}
|
}
|
||||||
|
|
||||||
const fr = {
|
const fr = {
|
||||||
|
complete_all_required_fields_to_proceed_with_identity_verification: "Veuillez remplir tous les champs obligatoires pour continuer la vérification de l'identité.",
|
||||||
|
verif_id: "Vérification de l'ID",
|
||||||
|
verif_identite: "Vérification de l'identité",
|
||||||
|
complete: 'Terminer',
|
||||||
|
fill_all_required_fields_to_complete: 'Veuillez remplir tous les champs obligatoires pour compléter',
|
||||||
|
sign_and_complete: 'Signer et Terminer',
|
||||||
|
invite: 'Inviter',
|
||||||
|
email: 'Courriel',
|
||||||
|
approved: 'Approuvé',
|
||||||
|
reviewed: 'Révisé',
|
||||||
|
other: 'Autre',
|
||||||
|
authored_by_me: 'Rédigé par moi',
|
||||||
|
approved_by: 'Approuvé par',
|
||||||
|
reviewed_by: 'Révisé par',
|
||||||
|
authored_by: 'Rédigé par',
|
||||||
|
select_a_reason: 'Sélectionnez une raison',
|
||||||
|
value_is_invalid: 'La valeur est invalide',
|
||||||
|
verification_code_is_invalid: 'Le code de vérification est invalide',
|
||||||
drawn_signature_on_a_touchscreen_device: 'Signature dessinée sur un appareil à écran tactile',
|
drawn_signature_on_a_touchscreen_device: 'Signature dessinée sur un appareil à écran tactile',
|
||||||
scan_the_qr_code_with_the_camera_app_to_open_the_form_on_mobile_and_draw_your_signature: 'Scannez le code QR avec l\'application de l\'appareil photo pour ouvrir le formulaire sur mobile et dessiner votre signature',
|
scan_the_qr_code_with_the_camera_app_to_open_the_form_on_mobile_and_draw_your_signature: 'Scannez le code QR avec l\'application de l\'appareil photo pour ouvrir le formulaire sur mobile et dessiner votre signature',
|
||||||
by_clicking_you_agree_to_the: 'En cliquant sur "{button}", vous acceptez la',
|
by_clicking_you_agree_to_the: 'En cliquant sur "{button}", vous acceptez la',
|
||||||
@@ -287,8 +413,12 @@ const fr = {
|
|||||||
esignature_disclosure: 'Divulgation de la eSignature',
|
esignature_disclosure: 'Divulgation de la eSignature',
|
||||||
minimize: 'Réduire',
|
minimize: 'Réduire',
|
||||||
text: 'Texte',
|
text: 'Texte',
|
||||||
|
already_paid: 'Déjà payé',
|
||||||
signature: 'Signature',
|
signature: 'Signature',
|
||||||
initials: 'Initiales',
|
initials: 'Initiales',
|
||||||
|
digitally_signed_by: 'Signé numériquement par',
|
||||||
|
reason: 'Raison',
|
||||||
|
pay: 'Payer',
|
||||||
date: 'Date',
|
date: 'Date',
|
||||||
number: 'Numéro',
|
number: 'Numéro',
|
||||||
image: 'Image',
|
image: 'Image',
|
||||||
@@ -300,8 +430,10 @@ const fr = {
|
|||||||
cells: 'Cellules',
|
cells: 'Cellules',
|
||||||
stamp: 'Tampon',
|
stamp: 'Tampon',
|
||||||
payment: 'Paiement',
|
payment: 'Paiement',
|
||||||
|
number_phone_is_invalid: '{number} téléphone est invalide',
|
||||||
phone: 'Téléphone',
|
phone: 'Téléphone',
|
||||||
submit_form: 'Envoyer le Formulaire',
|
start_now: 'Commencer maintenant',
|
||||||
|
continue: 'Continuer',
|
||||||
sign_now: 'Signer maintenant',
|
sign_now: 'Signer maintenant',
|
||||||
type_here_: 'Tapez ici...',
|
type_here_: 'Tapez ici...',
|
||||||
optional: 'facultatif',
|
optional: 'facultatif',
|
||||||
@@ -322,9 +454,11 @@ const fr = {
|
|||||||
type_signature_here: 'Tapez la signature ici',
|
type_signature_here: 'Tapez la signature ici',
|
||||||
type_initial_here: 'Tapez les initiales ici',
|
type_initial_here: 'Tapez les initiales ici',
|
||||||
form_has_been_completed: 'Le formulaire a été complété !',
|
form_has_been_completed: 'Le formulaire a été complété !',
|
||||||
|
document_has_been_signed: 'Le document a été signé!',
|
||||||
|
documents_have_been_signed: 'Les documents ont été signés!',
|
||||||
create_a_free_account: 'Créer un Compte Gratuit',
|
create_a_free_account: 'Créer un Compte Gratuit',
|
||||||
signed_with: 'Signé avec',
|
powered_by: 'Propulsé par',
|
||||||
please_check_the_box_to_continue: 'Veuillez cocher la case pour continuer',
|
please_check_the_box_to_continue: 'Veuillez cocher la case pour continuer.',
|
||||||
open_source_documents_software: 'logiciel de documents open source',
|
open_source_documents_software: 'logiciel de documents open source',
|
||||||
verified_phone_number: 'Vérifier le numéro de téléphone',
|
verified_phone_number: 'Vérifier le numéro de téléphone',
|
||||||
use_international_format: 'Utiliser le format international : +1xxx',
|
use_international_format: 'Utiliser le format international : +1xxx',
|
||||||
@@ -346,10 +480,29 @@ const fr = {
|
|||||||
pay_with_strip: 'Paiement avec Stripe',
|
pay_with_strip: 'Paiement avec Stripe',
|
||||||
reupload: 'Recharger',
|
reupload: 'Recharger',
|
||||||
upload: 'Télécharger',
|
upload: 'Télécharger',
|
||||||
files: 'Fichiers'
|
files: 'Fichiers',
|
||||||
|
signature_is_too_small_please_redraw: 'La signature est trop petite. Veuillez la redessiner.'
|
||||||
}
|
}
|
||||||
|
|
||||||
const pl = {
|
const pl = {
|
||||||
|
complete_all_required_fields_to_proceed_with_identity_verification: 'Uzupełnij wszystkie wymagane pola, aby kontynuować weryfikację tożsamości.',
|
||||||
|
verify_id: 'Zweryfikuj ID',
|
||||||
|
identity_verification: 'Weryfikacja tożsamości',
|
||||||
|
complete: 'Zakończ',
|
||||||
|
fill_all_required_fields_to_complete: 'Uzupełnij wszystkie wymagane pola, aby zakończyć',
|
||||||
|
sign_and_complete: 'Podpisz i zakończ',
|
||||||
|
invite: 'Zaproś',
|
||||||
|
email: 'E-mail',
|
||||||
|
approved: 'Zaakceptowany',
|
||||||
|
reviewed: 'Przejrzany',
|
||||||
|
other: 'Inny',
|
||||||
|
authored_by_me: 'Napisane przeze mnie',
|
||||||
|
approved_by: 'Zaakceptowany przez',
|
||||||
|
reviewed_by: 'Przejrzany przez',
|
||||||
|
authored_by: 'Napisane przez',
|
||||||
|
select_a_reason: 'Wybierz powód',
|
||||||
|
value_is_invalid: 'Wartość jest nieprawidłowa',
|
||||||
|
verification_code_is_invalid: 'Kod weryfikacyjny jest nieprawidłowy',
|
||||||
drawn_signature_on_a_touchscreen_device: 'Podpis odręczny na urządzeniu z ekranem dotykowym',
|
drawn_signature_on_a_touchscreen_device: 'Podpis odręczny na urządzeniu z ekranem dotykowym',
|
||||||
scan_the_qr_code_with_the_camera_app_to_open_the_form_on_mobile_and_draw_your_signature: 'Zeskanuj kod QR za pomocą aplikacji aparatu, aby otworzyć formularz na telefonie i narysować swój podpis',
|
scan_the_qr_code_with_the_camera_app_to_open_the_form_on_mobile_and_draw_your_signature: 'Zeskanuj kod QR za pomocą aplikacji aparatu, aby otworzyć formularz na telefonie i narysować swój podpis',
|
||||||
by_clicking_you_agree_to_the: 'Klikając na "{button}", zgadzasz się na',
|
by_clicking_you_agree_to_the: 'Klikając na "{button}", zgadzasz się na',
|
||||||
@@ -357,12 +510,16 @@ const pl = {
|
|||||||
esignature_disclosure: 'Ujawnienie ePodpisu',
|
esignature_disclosure: 'Ujawnienie ePodpisu',
|
||||||
minimize: 'Zminimalizuj',
|
minimize: 'Zminimalizuj',
|
||||||
text: 'Tekst',
|
text: 'Tekst',
|
||||||
|
already_paid: 'Już zapłacono',
|
||||||
|
digitally_signed_by: 'Podpisano cyfrowo przez',
|
||||||
|
reason: 'Powód',
|
||||||
signature: 'Podpis',
|
signature: 'Podpis',
|
||||||
initials: 'Inicjały',
|
initials: 'Inicjały',
|
||||||
date: 'Data',
|
date: 'Data',
|
||||||
number: 'Numer',
|
number: 'Numer',
|
||||||
image: 'Obraz',
|
image: 'Obraz',
|
||||||
file: 'Plik',
|
file: 'Plik',
|
||||||
|
pay: 'Zapłać',
|
||||||
select: 'Wybierz',
|
select: 'Wybierz',
|
||||||
checkbox: 'Pole wyboru',
|
checkbox: 'Pole wyboru',
|
||||||
multiple: 'Wielokrotne',
|
multiple: 'Wielokrotne',
|
||||||
@@ -370,8 +527,10 @@ const pl = {
|
|||||||
cells: 'Komórki',
|
cells: 'Komórki',
|
||||||
stamp: 'Pieczątka',
|
stamp: 'Pieczątka',
|
||||||
payment: 'Płatność',
|
payment: 'Płatność',
|
||||||
|
number_phone_is_invalid: '{number} telefon jest nieprawidłowy',
|
||||||
phone: 'Telefon',
|
phone: 'Telefon',
|
||||||
submit_form: 'Wyślij Formularz',
|
start_now: 'Zacznij teraz',
|
||||||
|
continue: 'Kontynuuj',
|
||||||
sign_now: 'Podpisz teraz',
|
sign_now: 'Podpisz teraz',
|
||||||
type_here_: 'Wpisz tutaj...',
|
type_here_: 'Wpisz tutaj...',
|
||||||
optional: 'opcjonalny',
|
optional: 'opcjonalny',
|
||||||
@@ -392,9 +551,11 @@ const pl = {
|
|||||||
type_signature_here: 'Wpisz podpis tutaj',
|
type_signature_here: 'Wpisz podpis tutaj',
|
||||||
type_initial_here: 'Wpisz inicjały tutaj',
|
type_initial_here: 'Wpisz inicjały tutaj',
|
||||||
form_has_been_completed: 'Formularz został wypełniony!',
|
form_has_been_completed: 'Formularz został wypełniony!',
|
||||||
|
document_has_been_signed: 'Dokument został podpisany!',
|
||||||
|
documents_have_been_signed: 'Dokumenty zostały podpisane!',
|
||||||
create_a_free_account: 'Utwórz darmowe konto',
|
create_a_free_account: 'Utwórz darmowe konto',
|
||||||
signed_with: 'Podpisane za pomocą',
|
powered_by: 'Napędzany przez',
|
||||||
please_check_the_box_to_continue: 'Proszę zaznaczyć pole, aby kontynuować',
|
please_check_the_box_to_continue: 'Proszę zaznaczyć pole, aby kontynuować.',
|
||||||
open_source_documents_software: 'oprogramowanie do dokumentów open source',
|
open_source_documents_software: 'oprogramowanie do dokumentów open source',
|
||||||
verified_phone_number: 'Zweryfikuj numer telefonu',
|
verified_phone_number: 'Zweryfikuj numer telefonu',
|
||||||
use_international_format: 'Użyj międzynarodowego formatu: +1xxx',
|
use_international_format: 'Użyj międzynarodowego formatu: +1xxx',
|
||||||
@@ -416,10 +577,29 @@ const pl = {
|
|||||||
pay_with_strip: 'Płatność za pomocą Stripe',
|
pay_with_strip: 'Płatność za pomocą Stripe',
|
||||||
reupload: 'Ponowne przesłanie',
|
reupload: 'Ponowne przesłanie',
|
||||||
upload: 'Przesyłanie',
|
upload: 'Przesyłanie',
|
||||||
files: 'Pliki'
|
files: 'Pliki',
|
||||||
|
signature_is_too_small_please_redraw: 'Podpis jest zbyt mały. Proszę narysować go ponownie.'
|
||||||
}
|
}
|
||||||
|
|
||||||
const uk = {
|
const uk = {
|
||||||
|
complete_all_required_fields_to_proceed_with_identity_verification: "Заповніть всі обов'язкові поля, щоб продовжити перевірку особи.",
|
||||||
|
verify_id: 'Підтвердження ідентичності',
|
||||||
|
identity_verification: 'Ідентифікація особи',
|
||||||
|
complete: 'Завершити',
|
||||||
|
fill_all_required_fields_to_complete: "Заповніть всі обов'язкові поля для завершення",
|
||||||
|
sign_and_complete: 'Підписати і завершити',
|
||||||
|
invite: 'Запросити',
|
||||||
|
email: 'Електронна пошта',
|
||||||
|
approved: 'Затверджено',
|
||||||
|
reviewed: 'Переглянуто',
|
||||||
|
other: 'Інше',
|
||||||
|
authored_by_me: 'Авторство моє',
|
||||||
|
approved_by: 'Затверджено',
|
||||||
|
reviewed_by: 'Переглянуто',
|
||||||
|
authored_by: 'Автор',
|
||||||
|
select_a_reason: 'Виберіть причину',
|
||||||
|
value_is_invalid: 'Значення є неправильним',
|
||||||
|
verification_code_is_invalid: 'Код підтвердження є неправильним',
|
||||||
drawn_signature_on_a_touchscreen_device: 'Підпис на сенсорному пристрої',
|
drawn_signature_on_a_touchscreen_device: 'Підпис на сенсорному пристрої',
|
||||||
scan_the_qr_code_with_the_camera_app_to_open_the_form_on_mobile_and_draw_your_signature: 'Скануйте QR-код за допомогою програми камери, щоб відкрити форму на мобільному пристрої та намалювати свій підпис',
|
scan_the_qr_code_with_the_camera_app_to_open_the_form_on_mobile_and_draw_your_signature: 'Скануйте QR-код за допомогою програми камери, щоб відкрити форму на мобільному пристрої та намалювати свій підпис',
|
||||||
by_clicking_you_agree_to_the: 'Натиснувши на "{button}", ви погоджуєтеся з',
|
by_clicking_you_agree_to_the: 'Натиснувши на "{button}", ви погоджуєтеся з',
|
||||||
@@ -427,11 +607,15 @@ const uk = {
|
|||||||
esignature_disclosure: 'Розголошення еПідпису',
|
esignature_disclosure: 'Розголошення еПідпису',
|
||||||
minimize: 'Зменшити',
|
minimize: 'Зменшити',
|
||||||
text: 'Текст',
|
text: 'Текст',
|
||||||
|
already_paid: 'Вже оплачено',
|
||||||
signature: 'Підпис',
|
signature: 'Підпис',
|
||||||
|
digitally_signed_by: 'Підписано в електронному вигляді',
|
||||||
|
reason: 'Причина',
|
||||||
initials: 'Ініціали',
|
initials: 'Ініціали',
|
||||||
date: 'Дата',
|
date: 'Дата',
|
||||||
number: 'Число',
|
number: 'Число',
|
||||||
image: 'Зображення',
|
image: 'Зображення',
|
||||||
|
pay: 'Оплатити',
|
||||||
file: 'Файл',
|
file: 'Файл',
|
||||||
select: 'Вибрати',
|
select: 'Вибрати',
|
||||||
checkbox: 'Прапорець',
|
checkbox: 'Прапорець',
|
||||||
@@ -440,8 +624,10 @@ const uk = {
|
|||||||
cells: 'Клітинки',
|
cells: 'Клітинки',
|
||||||
stamp: 'Штамп',
|
stamp: 'Штамп',
|
||||||
payment: 'Платіж',
|
payment: 'Платіж',
|
||||||
|
number_phone_is_invalid: '{number} телефон недійсний',
|
||||||
phone: 'Телефон',
|
phone: 'Телефон',
|
||||||
submit_form: 'Надіслати Форму',
|
start_now: 'Почати зараз',
|
||||||
|
continue: 'Продовжити',
|
||||||
sign_now: 'Підписати зараз',
|
sign_now: 'Підписати зараз',
|
||||||
type_here_: 'Введіть тут',
|
type_here_: 'Введіть тут',
|
||||||
optional: 'необов’язково',
|
optional: 'необов’язково',
|
||||||
@@ -462,9 +648,11 @@ const uk = {
|
|||||||
type_signature_here: 'Введіть підпис тут',
|
type_signature_here: 'Введіть підпис тут',
|
||||||
type_initial_here: 'Введіть ініціали тут',
|
type_initial_here: 'Введіть ініціали тут',
|
||||||
form_has_been_completed: 'Форму заповнено!',
|
form_has_been_completed: 'Форму заповнено!',
|
||||||
|
document_has_been_signed: 'Документ був підписаний!',
|
||||||
|
documents_have_been_signed: 'Документи були підписані!',
|
||||||
create_a_free_account: 'Створити безкоштовний обліковий запис',
|
create_a_free_account: 'Створити безкоштовний обліковий запис',
|
||||||
signed_with: 'Підписано за допомогою',
|
powered_by: 'Працює на базі',
|
||||||
please_check_the_box_to_continue: 'Будь ласка, позначте прапорець, щоб продовжити',
|
please_check_the_box_to_continue: 'Будь ласка, позначте прапорець, щоб продовжити.',
|
||||||
open_source_documents_software: 'відкритий програмний засіб для документів',
|
open_source_documents_software: 'відкритий програмний засіб для документів',
|
||||||
verified_phone_number: 'Підтвердіть номер телефону',
|
verified_phone_number: 'Підтвердіть номер телефону',
|
||||||
use_international_format: 'Використовуйте міжнародний формат: +1xxx',
|
use_international_format: 'Використовуйте міжнародний формат: +1xxx',
|
||||||
@@ -486,22 +674,45 @@ const uk = {
|
|||||||
pay_with_strip: 'Сплатити за допомогою Stripe',
|
pay_with_strip: 'Сплатити за допомогою Stripe',
|
||||||
reupload: 'Перезавантажити',
|
reupload: 'Перезавантажити',
|
||||||
upload: 'Завантажити',
|
upload: 'Завантажити',
|
||||||
files: 'Файли'
|
files: 'Файли',
|
||||||
|
signature_is_too_small_please_redraw: 'Підпис занадто малий. Будь ласка, перемалюйте його.'
|
||||||
}
|
}
|
||||||
|
|
||||||
const cs = {
|
const cs = {
|
||||||
|
complete_all_required_fields_to_proceed_with_identity_verification: 'Vyplňte všechna povinná pole, abyste mohli pokračovat v ověření identity.',
|
||||||
|
verify_id: 'Ověřit ID',
|
||||||
|
identity_verification: 'Ověření identity',
|
||||||
|
complete: 'Dokončit',
|
||||||
|
fill_all_required_fields_to_complete: 'Please complete all mandatory fields',
|
||||||
|
sign_and_complete: 'Podepsat a dokončit',
|
||||||
|
invite: 'Pozvat',
|
||||||
|
email: 'E-mail',
|
||||||
|
approved: 'Schváleno',
|
||||||
|
reviewed: 'Zkontrolováno',
|
||||||
|
other: 'Jiné',
|
||||||
|
authored_by_me: 'Autorem jsem já',
|
||||||
|
approved_by: 'Schváleno kým',
|
||||||
|
reviewed_by: 'Zkontrolováno kým',
|
||||||
|
authored_by: 'Autorem',
|
||||||
|
select_a_reason: 'Vyberte důvod',
|
||||||
|
value_is_invalid: 'Hodnota je neplatná',
|
||||||
|
verification_code_is_invalid: 'Ověřovací kód je neplatný',
|
||||||
drawn_signature_on_a_touchscreen_device: 'Namalovaný podpis na dotykovém zařízení',
|
drawn_signature_on_a_touchscreen_device: 'Namalovaný podpis na dotykovém zařízení',
|
||||||
scan_the_qr_code_with_the_camera_app_to_open_the_form_on_mobile_and_draw_your_signature: 'Naskenujte QR kód pomocí aplikace fotoaparátu, abyste otevřeli formulář na mobilním zařízení a nakreslili svůj podpis',
|
scan_the_qr_code_with_the_camera_app_to_open_the_form_on_mobile_and_draw_your_signature: 'Naskenujte QR kód pomocí aplikace fotoaparátu, abyste otevřeli formulář na mobilním zařízení a nakreslili svůj podpis',
|
||||||
by_clicking_you_agree_to_the: 'Kliknutím na "{button}" souhlasíte s',
|
by_clicking_you_agree_to_the: 'Kliknutím na "{button}" souhlasíte s',
|
||||||
electronic_signature_disclosure: 'Zveřejněním Elektronického Podpisu',
|
electronic_signature_disclosure: 'Zveřejněním Elektronického Podpisu',
|
||||||
esignature_disclosure: 'Zveřejnění ePodpisu',
|
esignature_disclosure: 'Zveřejnění ePodpisu',
|
||||||
|
already_paid: 'Už zaplaceno',
|
||||||
minimize: 'Minimalizovat',
|
minimize: 'Minimalizovat',
|
||||||
text: 'Text',
|
text: 'Text',
|
||||||
signature: 'Podpis',
|
signature: 'Podpis',
|
||||||
initials: 'Iniciály',
|
initials: 'Iniciály',
|
||||||
date: 'Datum',
|
date: 'Datum',
|
||||||
number: 'Číslo',
|
number: 'Číslo',
|
||||||
|
digitally_signed_by: 'Digitálně podepsáno',
|
||||||
|
reason: 'Důvod',
|
||||||
image: 'Obrázek',
|
image: 'Obrázek',
|
||||||
|
pay: 'Zaplatit',
|
||||||
file: 'Soubor',
|
file: 'Soubor',
|
||||||
select: 'Vybrat',
|
select: 'Vybrat',
|
||||||
checkbox: 'Zaškrtávací políčko',
|
checkbox: 'Zaškrtávací políčko',
|
||||||
@@ -511,7 +722,9 @@ const cs = {
|
|||||||
stamp: 'Razítko',
|
stamp: 'Razítko',
|
||||||
payment: 'Platba',
|
payment: 'Platba',
|
||||||
phone: 'Telefon',
|
phone: 'Telefon',
|
||||||
submit_form: 'Odeslat formulář',
|
number_phone_is_invalid: '{number} telefon je neplatný',
|
||||||
|
start_now: 'Začít nyní',
|
||||||
|
continue: 'Pokračovat',
|
||||||
sign_now: 'Podepsat nyní',
|
sign_now: 'Podepsat nyní',
|
||||||
type_here_: 'Zadejte zde',
|
type_here_: 'Zadejte zde',
|
||||||
optional: 'volitelné',
|
optional: 'volitelné',
|
||||||
@@ -532,9 +745,11 @@ const cs = {
|
|||||||
type_signature_here: 'Sem zadejte podpis',
|
type_signature_here: 'Sem zadejte podpis',
|
||||||
type_initial_here: 'Sem zadejte iniciály',
|
type_initial_here: 'Sem zadejte iniciály',
|
||||||
form_has_been_completed: 'Formulář byl dokončen!',
|
form_has_been_completed: 'Formulář byl dokončen!',
|
||||||
|
document_has_been_signed: 'Dokument byl podepsán!',
|
||||||
|
documents_have_been_signed: 'Dokumenty byly podepsány!',
|
||||||
create_a_free_account: 'Vytvořit bezplatný účet',
|
create_a_free_account: 'Vytvořit bezplatný účet',
|
||||||
signed_with: 'Podepsáno pomocí',
|
powered_by: 'Poháněno',
|
||||||
please_check_the_box_to_continue: 'Prosím, zaškrtněte políčko pro pokračování',
|
please_check_the_box_to_continue: 'Prosím, zaškrtněte políčko pro pokračování.',
|
||||||
open_source_documents_software: 'open source software pro dokumenty',
|
open_source_documents_software: 'open source software pro dokumenty',
|
||||||
verified_phone_number: 'Ověřte telefonní číslo',
|
verified_phone_number: 'Ověřte telefonní číslo',
|
||||||
use_international_format: 'Použijte mezinárodní formát: +1xxx',
|
use_international_format: 'Použijte mezinárodní formát: +1xxx',
|
||||||
@@ -556,22 +771,45 @@ const cs = {
|
|||||||
pay_with_strip: 'Zaplacení přes Stripe',
|
pay_with_strip: 'Zaplacení přes Stripe',
|
||||||
reupload: 'Znovu nahrát',
|
reupload: 'Znovu nahrát',
|
||||||
upload: 'Nahrát',
|
upload: 'Nahrát',
|
||||||
files: 'Soubory'
|
files: 'Soubory',
|
||||||
|
signature_is_too_small_please_redraw: 'Podpis je příliš malý. Prosím, překreslete ho.'
|
||||||
}
|
}
|
||||||
|
|
||||||
const pt = {
|
const pt = {
|
||||||
|
complete_all_required_fields_to_proceed_with_identity_verification: 'Preencha todos os campos obrigatórios para prosseguir com a verificação de identidade.',
|
||||||
|
verify_id: 'Verificar ID',
|
||||||
|
identity_verification: 'Verificação de identidade',
|
||||||
|
complete: 'Completar',
|
||||||
|
fill_all_required_fields_to_complete: 'Preencher todos os campos obrigatórios para concluir',
|
||||||
|
sign_and_complete: 'Assinar e Completar',
|
||||||
|
invite: 'Convidar',
|
||||||
|
email: 'E-mail',
|
||||||
|
approved: 'Aprovado',
|
||||||
|
reviewed: 'Revisado',
|
||||||
|
other: 'Outro',
|
||||||
|
authored_by_me: 'Autorizado por mim',
|
||||||
|
approved_by: 'Aprovado por',
|
||||||
|
reviewed_by: 'Revisado por',
|
||||||
|
authored_by: 'Autorizado por',
|
||||||
|
select_a_reason: 'Selecione um motivo',
|
||||||
|
value_is_invalid: 'Valor é inválido',
|
||||||
|
verification_code_is_invalid: 'Código de verificação é inválido',
|
||||||
drawn_signature_on_a_touchscreen_device: 'Assinatura desenhada em um dispositivo com tela sensível ao toque',
|
drawn_signature_on_a_touchscreen_device: 'Assinatura desenhada em um dispositivo com tela sensível ao toque',
|
||||||
scan_the_qr_code_with_the_camera_app_to_open_the_form_on_mobile_and_draw_your_signature: 'Escaneie o código QR com o aplicativo da câmera para abrir o formulário no celular e desenhar sua assinatura',
|
scan_the_qr_code_with_the_camera_app_to_open_the_form_on_mobile_and_draw_your_signature: 'Escaneie o código QR com o aplicativo da câmera para abrir o formulário no celular e desenhar sua assinatura',
|
||||||
by_clicking_you_agree_to_the: 'Ao clicar em "{button}", você concorda com o',
|
by_clicking_you_agree_to_the: 'Ao clicar em "{button}", você concorda com o',
|
||||||
electronic_signature_disclosure: 'Divulgação de Assinatura Eletrônica',
|
electronic_signature_disclosure: 'Divulgação de Assinatura Eletrônica',
|
||||||
esignature_disclosure: 'Divulgação da eAssinatura',
|
esignature_disclosure: 'Divulgação da eAssinatura',
|
||||||
|
already_paid: 'Já pago',
|
||||||
minimize: 'Minimizar',
|
minimize: 'Minimizar',
|
||||||
text: 'Texto',
|
text: 'Texto',
|
||||||
signature: 'Assinatura',
|
signature: 'Assinatura',
|
||||||
initials: 'Iniciais',
|
initials: 'Iniciais',
|
||||||
|
digitally_signed_by: 'Assinado digitalmente por',
|
||||||
|
reason: 'Razão',
|
||||||
date: 'Data',
|
date: 'Data',
|
||||||
number: 'Número',
|
number: 'Número',
|
||||||
image: 'Imagem',
|
image: 'Imagem',
|
||||||
|
pay: 'Pagar',
|
||||||
file: 'Arquivo',
|
file: 'Arquivo',
|
||||||
select: 'Selecionar',
|
select: 'Selecionar',
|
||||||
checkbox: 'Caixa',
|
checkbox: 'Caixa',
|
||||||
@@ -580,8 +818,10 @@ const pt = {
|
|||||||
cells: 'Células',
|
cells: 'Células',
|
||||||
stamp: 'Carimbo',
|
stamp: 'Carimbo',
|
||||||
payment: 'Pagamento',
|
payment: 'Pagamento',
|
||||||
|
number_phone_is_invalid: '{number} telefone é inválido',
|
||||||
phone: 'Telefone',
|
phone: 'Telefone',
|
||||||
submit_form: 'Enviar Formulário',
|
start_now: 'Começar agora',
|
||||||
|
continue: 'Continuar',
|
||||||
sign_now: 'Assinar agora',
|
sign_now: 'Assinar agora',
|
||||||
type_here_: 'Digite aqui',
|
type_here_: 'Digite aqui',
|
||||||
optional: 'opcional',
|
optional: 'opcional',
|
||||||
@@ -602,9 +842,11 @@ const pt = {
|
|||||||
type_signature_here: 'Digite a assinatura aqui',
|
type_signature_here: 'Digite a assinatura aqui',
|
||||||
type_initial_here: 'Digite as iniciais aqui',
|
type_initial_here: 'Digite as iniciais aqui',
|
||||||
form_has_been_completed: 'O formulário foi concluído!',
|
form_has_been_completed: 'O formulário foi concluído!',
|
||||||
|
document_has_been_signed: 'O documento foi assinado!',
|
||||||
|
documents_have_been_signed: 'Os documentos foram assinados!',
|
||||||
create_a_free_account: 'Criar uma Conta Gratuita',
|
create_a_free_account: 'Criar uma Conta Gratuita',
|
||||||
signed_with: 'Assinado com',
|
powered_by: 'Desenvolvido por',
|
||||||
please_check_the_box_to_continue: 'Por favor, marque a caixa para continuar',
|
please_check_the_box_to_continue: 'Por favor, marque a caixa para continuar.',
|
||||||
open_source_documents_software: 'software de documentos de código aberto',
|
open_source_documents_software: 'software de documentos de código aberto',
|
||||||
verified_phone_number: 'Verificar Número de Telefone',
|
verified_phone_number: 'Verificar Número de Telefone',
|
||||||
use_international_format: 'Use formato internacional: +1xxx',
|
use_international_format: 'Use formato internacional: +1xxx',
|
||||||
@@ -626,10 +868,29 @@ const pt = {
|
|||||||
pay_with_strip: 'Pagar com Stripe',
|
pay_with_strip: 'Pagar com Stripe',
|
||||||
reupload: 'Reenviar',
|
reupload: 'Reenviar',
|
||||||
upload: 'Carregar',
|
upload: 'Carregar',
|
||||||
files: 'Arquivos'
|
files: 'Arquivos',
|
||||||
|
signature_is_too_small_please_redraw: 'A assinatura é muito pequena. Por favor, redesenhe-a.'
|
||||||
}
|
}
|
||||||
|
|
||||||
const he = {
|
const he = {
|
||||||
|
complete_all_required_fields_to_proceed_with_identity_verification: 'מלא את כל השדות הנדרשים כדי להמשיך עם אימות זהות.',
|
||||||
|
verify_id: 'אמת מזהה',
|
||||||
|
identity_verification: 'אימות זהות',
|
||||||
|
complete: 'השלם',
|
||||||
|
fill_all_required_fields_to_complete: 'נא למלא את כל השדות הנדרשים להשלמה',
|
||||||
|
sign_and_complete: 'חתום והשלם',
|
||||||
|
invite: 'הזמן',
|
||||||
|
email: 'דוא"ל',
|
||||||
|
approved: 'מאושר',
|
||||||
|
reviewed: 'נסקר',
|
||||||
|
other: 'אחר',
|
||||||
|
authored_by_me: 'נכתב על ידי',
|
||||||
|
approved_by: 'אושר על ידי',
|
||||||
|
reviewed_by: 'נסקר על ידי',
|
||||||
|
authored_by: 'נכתב על ידי',
|
||||||
|
select_a_reason: 'בחר סיבה',
|
||||||
|
value_is_invalid: 'ערך לא תקין',
|
||||||
|
verification_code_is_invalid: 'קוד האימות אינו תקין',
|
||||||
drawn_signature_on_a_touchscreen_device: 'חתימה שנוצרה במכשיר עם מסך מגע',
|
drawn_signature_on_a_touchscreen_device: 'חתימה שנוצרה במכשיר עם מסך מגע',
|
||||||
scan_the_qr_code_with_the_camera_app_to_open_the_form_on_mobile_and_draw_your_signature: 'סרוק את קוד ה-QR באמצעות אפליקציית המצלמה כדי לפתוח את הטופס במובייל ולצייר את החתימה שלך',
|
scan_the_qr_code_with_the_camera_app_to_open_the_form_on_mobile_and_draw_your_signature: 'סרוק את קוד ה-QR באמצעות אפליקציית המצלמה כדי לפתוח את הטופס במובייל ולצייר את החתימה שלך',
|
||||||
by_clicking_you_agree_to_the: 'על ידי לחיצה על "{button}", אתה מסכים ל',
|
by_clicking_you_agree_to_the: 'על ידי לחיצה על "{button}", אתה מסכים ל',
|
||||||
@@ -637,12 +898,16 @@ const he = {
|
|||||||
esignature_disclosure: 'חשיפת ה-eחתימה',
|
esignature_disclosure: 'חשיפת ה-eחתימה',
|
||||||
minimize: 'לקטן',
|
minimize: 'לקטן',
|
||||||
text: 'טקסט',
|
text: 'טקסט',
|
||||||
|
already_paid: 'כבר שולם',
|
||||||
signature: 'חתימה',
|
signature: 'חתימה',
|
||||||
initials: 'ראשי תיבות',
|
initials: 'ראשי תיבות',
|
||||||
date: 'תאריך',
|
date: 'תאריך',
|
||||||
|
digitally_signed_by: 'חתום דיגיטלית על ידי',
|
||||||
|
reason: 'סיבה',
|
||||||
number: 'מספר',
|
number: 'מספר',
|
||||||
image: 'תמונה',
|
image: 'תמונה',
|
||||||
file: 'קובץ',
|
file: 'קובץ',
|
||||||
|
pay: 'תשלום',
|
||||||
select: 'בחר',
|
select: 'בחר',
|
||||||
checkbox: 'תיק בחירה',
|
checkbox: 'תיק בחירה',
|
||||||
multiple: 'רב ערכים',
|
multiple: 'רב ערכים',
|
||||||
@@ -651,7 +916,9 @@ const he = {
|
|||||||
stamp: 'חותמת',
|
stamp: 'חותמת',
|
||||||
payment: 'תשלום',
|
payment: 'תשלום',
|
||||||
phone: 'טלפון',
|
phone: 'טלפון',
|
||||||
submit_form: 'שלח טופס',
|
number_phone_is_invalid: '{number} טלפון אינו חוקי',
|
||||||
|
start_now: 'התחל עכשיו',
|
||||||
|
continue: 'המשך',
|
||||||
sign_now: 'חתום כעת',
|
sign_now: 'חתום כעת',
|
||||||
type_here_: 'הקלד כאן',
|
type_here_: 'הקלד כאן',
|
||||||
optional: 'אופציונלי',
|
optional: 'אופציונלי',
|
||||||
@@ -673,9 +940,11 @@ const he = {
|
|||||||
type_signature_here: 'הקלד חתימה כאן',
|
type_signature_here: 'הקלד חתימה כאן',
|
||||||
type_initial_here: 'הקלד ציוני ראשי תיבות כאן',
|
type_initial_here: 'הקלד ציוני ראשי תיבות כאן',
|
||||||
form_has_been_completed: 'הטופס הושלם',
|
form_has_been_completed: 'הטופס הושלם',
|
||||||
|
document_has_been_signed: 'המסמך נחתם!',
|
||||||
|
documents_have_been_signed: 'המסמכים נחתמו!',
|
||||||
create_a_free_account: 'צור חשבון חינם',
|
create_a_free_account: 'צור חשבון חינם',
|
||||||
signed_with: 'חתום באמצעות',
|
powered_by: 'מופעל על ידי',
|
||||||
please_check_the_box_to_continue: 'אנא סמן את התיבה כדי להמשיך',
|
please_check_the_box_to_continue: 'אנא סמן את התיבה כדי להמשיך.',
|
||||||
open_source_documents_software: 'תוכנה פתוחה למסמכים',
|
open_source_documents_software: 'תוכנה פתוחה למסמכים',
|
||||||
verified_phone_number: 'אימות מספר טלפון',
|
verified_phone_number: 'אימות מספר טלפון',
|
||||||
use_international_format: 'השתמש בפורמט בינלאומי: +1xxx',
|
use_international_format: 'השתמש בפורמט בינלאומי: +1xxx',
|
||||||
@@ -697,10 +966,29 @@ const he = {
|
|||||||
pay_with_strip: 'שלם עם סטרייפ',
|
pay_with_strip: 'שלם עם סטרייפ',
|
||||||
reupload: 'העלה שוב',
|
reupload: 'העלה שוב',
|
||||||
upload: 'העלאה',
|
upload: 'העלאה',
|
||||||
files: 'קבצים'
|
files: 'קבצים',
|
||||||
|
signature_is_too_small_please_redraw: 'החתימה קטנה מדי. אנא צייר מחדש.'
|
||||||
}
|
}
|
||||||
|
|
||||||
const nl = {
|
const nl = {
|
||||||
|
complete_all_required_fields_to_proceed_with_identity_verification: 'Vul alle verplichte velden in om door te gaan met de identiteitsverificatie.',
|
||||||
|
verify_id: 'Verifiëren ID',
|
||||||
|
identity_verification: 'Identiteitsverificatie',
|
||||||
|
complete: 'Voltooien',
|
||||||
|
fill_all_required_fields_to_complete: 'Vul alle verplichte velden in om te voltooien',
|
||||||
|
sign_and_complete: 'Ondertekenen en voltooien',
|
||||||
|
invite: 'Uitnodigen',
|
||||||
|
email: 'E-mail',
|
||||||
|
approved: 'Goedgekeurd',
|
||||||
|
reviewed: 'Beoordeeld',
|
||||||
|
other: 'Anders',
|
||||||
|
authored_by_me: 'Door mij geschreven',
|
||||||
|
approved_by: 'Goedgekeurd door',
|
||||||
|
reviewed_by: 'Beoordeeld door',
|
||||||
|
authored_by: 'Geschreven door',
|
||||||
|
select_a_reason: 'Selecteer een reden',
|
||||||
|
value_is_invalid: 'Waarde is ongeldig',
|
||||||
|
verification_code_is_invalid: 'Verificatiecode is ongeldig',
|
||||||
drawn_signature_on_a_touchscreen_device: 'Getekende handtekening op een apparaat met een touchscreen',
|
drawn_signature_on_a_touchscreen_device: 'Getekende handtekening op een apparaat met een touchscreen',
|
||||||
scan_the_qr_code_with_the_camera_app_to_open_the_form_on_mobile_and_draw_your_signature: 'Scan de QR-code met de camera-app om het formulier op mobiel te openen en uw handtekening te tekenen',
|
scan_the_qr_code_with_the_camera_app_to_open_the_form_on_mobile_and_draw_your_signature: 'Scan de QR-code met de camera-app om het formulier op mobiel te openen en uw handtekening te tekenen',
|
||||||
by_clicking_you_agree_to_the: 'Door op "{button}" te klikken, gaat u akkoord met de',
|
by_clicking_you_agree_to_the: 'Door op "{button}" te klikken, gaat u akkoord met de',
|
||||||
@@ -708,7 +996,10 @@ const nl = {
|
|||||||
esignature_disclosure: 'Openbaarmaking van eHandtekening',
|
esignature_disclosure: 'Openbaarmaking van eHandtekening',
|
||||||
minimize: 'Minimaliseren',
|
minimize: 'Minimaliseren',
|
||||||
text: 'Tekst',
|
text: 'Tekst',
|
||||||
|
already_paid: 'Al betaald',
|
||||||
signature: 'Handtekening',
|
signature: 'Handtekening',
|
||||||
|
digitally_signed_by: 'Digitaal ondertekend door',
|
||||||
|
reason: 'Reden',
|
||||||
initials: 'Initialen',
|
initials: 'Initialen',
|
||||||
date: 'Datum',
|
date: 'Datum',
|
||||||
number: 'Nummer',
|
number: 'Nummer',
|
||||||
@@ -718,12 +1009,15 @@ const nl = {
|
|||||||
select: 'Selecteer',
|
select: 'Selecteer',
|
||||||
checkbox: 'Aankruisvakje',
|
checkbox: 'Aankruisvakje',
|
||||||
multiple: 'Meerdere',
|
multiple: 'Meerdere',
|
||||||
|
pay: 'Betaal',
|
||||||
radio: 'Radio',
|
radio: 'Radio',
|
||||||
cells: 'Cellen',
|
cells: 'Cellen',
|
||||||
stamp: 'Stempel',
|
stamp: 'Stempel',
|
||||||
payment: 'Betaling',
|
payment: 'Betaling',
|
||||||
phone: 'Telefoon',
|
phone: 'Telefoon',
|
||||||
submit_form: 'Formulier verzenden',
|
number_phone_is_invalid: '{number} telefoon is ongeldig',
|
||||||
|
start_now: 'Nu beginnen',
|
||||||
|
continue: 'Doorgaan',
|
||||||
sign_now: 'Nu ondertekenen',
|
sign_now: 'Nu ondertekenen',
|
||||||
type_here_: 'Typ hier...',
|
type_here_: 'Typ hier...',
|
||||||
optional: 'Optioneel',
|
optional: 'Optioneel',
|
||||||
@@ -744,9 +1038,11 @@ const nl = {
|
|||||||
type_signature_here: 'Typ hier uw handtekening',
|
type_signature_here: 'Typ hier uw handtekening',
|
||||||
type_initial_here: 'Typ hier uw initialen',
|
type_initial_here: 'Typ hier uw initialen',
|
||||||
form_has_been_completed: 'Formulier is voltooid!',
|
form_has_been_completed: 'Formulier is voltooid!',
|
||||||
|
document_has_been_signed: 'Document is ondertekend!',
|
||||||
|
documents_have_been_signed: 'De documenten zijn ondertekend!',
|
||||||
create_a_free_account: 'Maak een gratis account aan',
|
create_a_free_account: 'Maak een gratis account aan',
|
||||||
signed_with: 'Ondertekend met',
|
powered_by: 'Aangedreven door',
|
||||||
please_check_the_box_to_continue: 'Vink het vakje aan om door te gaan',
|
please_check_the_box_to_continue: 'Vink het vakje aan om door te gaan.',
|
||||||
open_source_documents_software: 'Open source documenten software',
|
open_source_documents_software: 'Open source documenten software',
|
||||||
verified_phone_number: 'Geverifieerd telefoonnummer',
|
verified_phone_number: 'Geverifieerd telefoonnummer',
|
||||||
use_international_format: 'Gebruik internationaal formaat: +1xxx',
|
use_international_format: 'Gebruik internationaal formaat: +1xxx',
|
||||||
@@ -768,10 +1064,29 @@ const nl = {
|
|||||||
pay_with_strip: 'Betalen met Stripe',
|
pay_with_strip: 'Betalen met Stripe',
|
||||||
reupload: 'Opnieuw uploaden',
|
reupload: 'Opnieuw uploaden',
|
||||||
upload: 'Uploaden',
|
upload: 'Uploaden',
|
||||||
files: 'Bestanden'
|
files: 'Bestanden',
|
||||||
|
signature_is_too_small_please_redraw: 'De handtekening is te klein. Teken deze opnieuw, alstublieft.'
|
||||||
}
|
}
|
||||||
|
|
||||||
const ar = {
|
const ar = {
|
||||||
|
complete_all_required_fields_to_proceed_with_identity_verification: 'أكمل جميع الحقول المطلوبة للمتابعة في التحقق من الهوية.',
|
||||||
|
verify_id: 'تحقق من الهوية',
|
||||||
|
identity_verification: 'التحقق من الهوية',
|
||||||
|
complete: 'اكتمال',
|
||||||
|
fill_all_required_fields_to_complete: 'يرجى ملء جميع الحقول المطلوبة لإكمال',
|
||||||
|
sign_and_complete: 'التوقيع والاكتمال',
|
||||||
|
invite: 'دعوة',
|
||||||
|
email: 'البريد الإلكتروني',
|
||||||
|
approved: 'موافق عليه',
|
||||||
|
reviewed: 'تمت مراجعته',
|
||||||
|
other: 'آخر',
|
||||||
|
authored_by_me: 'مؤلف بواسطتي',
|
||||||
|
approved_by: 'موافق عليه من قبل',
|
||||||
|
reviewed_by: 'مراجع من قبل',
|
||||||
|
authored_by: 'مؤلف من قبل',
|
||||||
|
select_a_reason: 'اختر سببًا',
|
||||||
|
value_is_invalid: 'القيمة غير صالحة',
|
||||||
|
verification_code_is_invalid: 'رمز التحقق غير صالح',
|
||||||
drawn_signature_on_a_touchscreen_device: 'توقيع مرسوم على جهاز بشاشة تعمل باللمس',
|
drawn_signature_on_a_touchscreen_device: 'توقيع مرسوم على جهاز بشاشة تعمل باللمس',
|
||||||
scan_the_qr_code_with_the_camera_app_to_open_the_form_on_mobile_and_draw_your_signature: 'امسح رمز الاستجابة السريعة باستخدام تطبيق الكاميرا لفتح النموذج على الهاتف المحمول ورسم توقيعك',
|
scan_the_qr_code_with_the_camera_app_to_open_the_form_on_mobile_and_draw_your_signature: 'امسح رمز الاستجابة السريعة باستخدام تطبيق الكاميرا لفتح النموذج على الهاتف المحمول ورسم توقيعك',
|
||||||
by_clicking_you_agree_to_the: 'بالنقر فوق "{button}"، أنت توافق على',
|
by_clicking_you_agree_to_the: 'بالنقر فوق "{button}"، أنت توافق على',
|
||||||
@@ -782,8 +1097,11 @@ const ar = {
|
|||||||
initials: 'الاختصارات',
|
initials: 'الاختصارات',
|
||||||
date: 'تاريخ',
|
date: 'تاريخ',
|
||||||
number: 'رقم',
|
number: 'رقم',
|
||||||
|
digitally_signed_by: 'تم التوقيع رقميًا بواسطة',
|
||||||
|
reason: 'السبب',
|
||||||
image: 'صورة',
|
image: 'صورة',
|
||||||
take_photo: 'التقاط صورة',
|
take_photo: 'التقاط صورة',
|
||||||
|
pay: 'دفع',
|
||||||
file: 'ملف',
|
file: 'ملف',
|
||||||
select: 'اختيار',
|
select: 'اختيار',
|
||||||
checkbox: 'خانة اختيار',
|
checkbox: 'خانة اختيار',
|
||||||
@@ -794,7 +1112,9 @@ const ar = {
|
|||||||
minimize: 'تصغير',
|
minimize: 'تصغير',
|
||||||
payment: 'الدفع',
|
payment: 'الدفع',
|
||||||
phone: 'هاتف',
|
phone: 'هاتف',
|
||||||
submit_form: 'إرسال النموذج',
|
number_phone_is_invalid: '{number} هاتف غير صالح',
|
||||||
|
start_now: 'ابدأ الآن',
|
||||||
|
continue: 'متابعة',
|
||||||
sign_now: 'وقع الآن',
|
sign_now: 'وقع الآن',
|
||||||
type_here_: 'اكتب هنا',
|
type_here_: 'اكتب هنا',
|
||||||
optional: 'اختياري',
|
optional: 'اختياري',
|
||||||
@@ -815,9 +1135,11 @@ const ar = {
|
|||||||
type_signature_here: 'اكتب التوقيع هنا',
|
type_signature_here: 'اكتب التوقيع هنا',
|
||||||
type_initial_here: 'اكتب الاختصارات هنا',
|
type_initial_here: 'اكتب الاختصارات هنا',
|
||||||
form_has_been_completed: 'تم إكمال النموذج!',
|
form_has_been_completed: 'تم إكمال النموذج!',
|
||||||
|
document_has_been_signed: 'تم توقيع الوثيقة!',
|
||||||
|
documents_have_been_signed: 'تم توقيع الوثائق!',
|
||||||
create_a_free_account: 'إنشاء حساب مجاني',
|
create_a_free_account: 'إنشاء حساب مجاني',
|
||||||
signed_with: 'تم التوقيع بواسطة',
|
powered_by: 'مدعوم من',
|
||||||
please_check_the_box_to_continue: 'الرجاء التحقق من الخانة للمتابعة',
|
please_check_the_box_to_continue: 'الرجاء التحقق من الخانة للمتابعة.',
|
||||||
open_source_documents_software: 'برنامج وثائق مفتوح المصدر',
|
open_source_documents_software: 'برنامج وثائق مفتوح المصدر',
|
||||||
verified_phone_number: 'تحقق من رقم الهاتف',
|
verified_phone_number: 'تحقق من رقم الهاتف',
|
||||||
use_international_format: 'استخدم الشكل الدولي: +1xxx',
|
use_international_format: 'استخدم الشكل الدولي: +1xxx',
|
||||||
@@ -839,20 +1161,41 @@ const ar = {
|
|||||||
pay_with_strip: 'الدفع بواسطة Stripe',
|
pay_with_strip: 'الدفع بواسطة Stripe',
|
||||||
reupload: 'إعادة التحميل',
|
reupload: 'إعادة التحميل',
|
||||||
upload: 'تحميل',
|
upload: 'تحميل',
|
||||||
files: 'الملفات'
|
files: 'الملفات',
|
||||||
|
signature_is_too_small_please_redraw: 'التوقيع صغير جدًا. يرجى إعادة الرسم.'
|
||||||
}
|
}
|
||||||
|
|
||||||
const ko = {
|
const ko = {
|
||||||
|
complete_all_required_fields_to_proceed_with_identity_verification: '신원 확인을 진행하려면 모든 필수 필드를 작성하십시오.',
|
||||||
|
verify_id: '아이디 확인',
|
||||||
|
identity_verification: '신원 확인',
|
||||||
|
complete: '완료',
|
||||||
|
fill_all_required_fields_to_complete: '모든 필수 필드를 작성하여 완료하세요',
|
||||||
|
sign_and_complete: '서명하고 완료하기',
|
||||||
|
invite: '초대하기',
|
||||||
|
email: '이메일',
|
||||||
|
approved: '승인됨',
|
||||||
|
reviewed: '검토됨',
|
||||||
|
other: '기타',
|
||||||
|
authored_by_me: '내가 작성한',
|
||||||
|
approved_by: '승인자',
|
||||||
|
reviewed_by: '검토자',
|
||||||
|
authored_by: '작성자',
|
||||||
|
select_a_reason: '이유 선택',
|
||||||
drawn_signature_on_a_touchscreen_device: '터치스크린 장치에서 그린 서명',
|
drawn_signature_on_a_touchscreen_device: '터치스크린 장치에서 그린 서명',
|
||||||
scan_the_qr_code_with_the_camera_app_to_open_the_form_on_mobile_and_draw_your_signature: '카메라 앱으로 QR 코드를 스캔하여 모바일에서 양식을 열고 서명을 그리세요',
|
scan_the_qr_code_with_the_camera_app_to_open_the_form_on_mobile_and_draw_your_signature: '카메라 앱으로 QR 코드를 스캔하여 모바일에서 양식을 열고 서명을 그리세요',
|
||||||
by_clicking_you_agree_to_the: '"{button}"를 클릭함으로써, 다음에 동의하게 됩니다',
|
by_clicking_you_agree_to_the: '"{button}"를 클릭함으로써, 다음에 동의하게 됩니다',
|
||||||
electronic_signature_disclosure: '전자 서명 공개',
|
electronic_signature_disclosure: '전자 서명 공개',
|
||||||
esignature_disclosure: '전자 서명 공개',
|
esignature_disclosure: '전자 서명 공개',
|
||||||
|
already_paid: '이미 지불됨',
|
||||||
text: '텍스트',
|
text: '텍스트',
|
||||||
signature: '서명',
|
signature: '서명',
|
||||||
initials: '이니셜',
|
initials: '이니셜',
|
||||||
date: '날짜',
|
date: '날짜',
|
||||||
|
digitally_signed_by: '디지털 서명이 완료되었습니다',
|
||||||
|
reason: '이유',
|
||||||
number: '숫자',
|
number: '숫자',
|
||||||
|
pay: '급여',
|
||||||
image: '이미지',
|
image: '이미지',
|
||||||
take_photo: '사진 찍기',
|
take_photo: '사진 찍기',
|
||||||
file: '파일',
|
file: '파일',
|
||||||
@@ -865,7 +1208,9 @@ const ko = {
|
|||||||
minimize: '최소화',
|
minimize: '최소화',
|
||||||
payment: '결제',
|
payment: '결제',
|
||||||
phone: '전화',
|
phone: '전화',
|
||||||
submit_form: '양식 제출',
|
number_phone_is_invalid: '{number} 전화번호가 유효하지 않습니다',
|
||||||
|
start_now: '지금 시작하기',
|
||||||
|
continue: '계속하다',
|
||||||
sign_now: '지금 서명',
|
sign_now: '지금 서명',
|
||||||
type_here_: '여기에 입력...',
|
type_here_: '여기에 입력...',
|
||||||
optional: '선택 사항',
|
optional: '선택 사항',
|
||||||
@@ -886,9 +1231,11 @@ const ko = {
|
|||||||
type_signature_here: '여기에 서명 입력',
|
type_signature_here: '여기에 서명 입력',
|
||||||
type_initial_here: '여기에 이니셜 입력',
|
type_initial_here: '여기에 이니셜 입력',
|
||||||
form_has_been_completed: '양식이 완료되었습니다!',
|
form_has_been_completed: '양식이 완료되었습니다!',
|
||||||
|
document_has_been_signed: '문서가 서명되었습니다!',
|
||||||
|
documents_have_been_signed: '문서가 서명되었습니다!',
|
||||||
create_a_free_account: '무료 계정 생성',
|
create_a_free_account: '무료 계정 생성',
|
||||||
signed_with: '다음으로 서명:',
|
powered_by: '구동',
|
||||||
please_check_the_box_to_continue: '계속하려면 확인란을 선택하십시오',
|
please_check_the_box_to_continue: '계속하려면 확인란을 선택하십시오.',
|
||||||
open_source_documents_software: '오픈 소스 문서 소프트웨어',
|
open_source_documents_software: '오픈 소스 문서 소프트웨어',
|
||||||
verified_phone_number: '전화번호 확인됨',
|
verified_phone_number: '전화번호 확인됨',
|
||||||
use_international_format: '국제 포맷 사용: +1xxx',
|
use_international_format: '국제 포맷 사용: +1xxx',
|
||||||
@@ -910,7 +1257,8 @@ const ko = {
|
|||||||
pay_with_strip: '스트라이프로 결제',
|
pay_with_strip: '스트라이프로 결제',
|
||||||
reupload: '다시 업로드',
|
reupload: '다시 업로드',
|
||||||
upload: '업로드',
|
upload: '업로드',
|
||||||
files: '파일'
|
files: '파일',
|
||||||
|
signature_is_too_small_please_redraw: '서명이 너무 작습니다. 다시 그려주세요.'
|
||||||
}
|
}
|
||||||
|
|
||||||
const i18n = { en, es, it, de, fr, pl, uk, cs, pt, he, nl, ar, ko }
|
const i18n = { en, es, it, de, fr, pl, uk, cs, pt, he, nl, ar, ko }
|
||||||
|
|||||||
@@ -37,6 +37,7 @@
|
|||||||
<FileDropzone
|
<FileDropzone
|
||||||
:message="`${t('upload')} ${field.name || t('image')}${field.required ? '' : ` (${t('optional')})`}`"
|
:message="`${t('upload')} ${field.name || t('image')}${field.required ? '' : ` (${t('optional')})`}`"
|
||||||
:submitter-slug="submitterSlug"
|
:submitter-slug="submitterSlug"
|
||||||
|
:dry-run="dryRun"
|
||||||
:accept="'image/*'"
|
:accept="'image/*'"
|
||||||
@upload="onImageUpload"
|
@upload="onImageUpload"
|
||||||
/>
|
/>
|
||||||
@@ -66,6 +67,11 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: true
|
default: true
|
||||||
},
|
},
|
||||||
|
dryRun: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
submitterSlug: {
|
submitterSlug: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true
|
||||||
|
|||||||
Loaded 100 of 421 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user