mirror of
https://github.com/docusealco/docuseal
synced 2026-11-04 14:42:32 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
44f3910266 | ||
|
|
35c6798a80 | ||
|
|
0995c7ee63 | ||
|
|
0a25f78329 | ||
|
|
bb8fffc491 | ||
|
|
6f6c51fb32 | ||
|
|
39d14c15d6 | ||
|
|
17de28965b | ||
|
|
ae7865e508 | ||
|
|
dc8e8cc5c9 | ||
|
|
5208470c38 | ||
|
|
e7ef7b552f | ||
|
|
a478112b07 | ||
|
|
7d0f32e1f9 | ||
|
|
d35d8bead3 | ||
|
|
ab9cc52bcc | ||
|
|
7dc342a6f9 | ||
|
|
80a3ef9193 | ||
|
|
bb723d20cb | ||
|
|
0c24a2016c | ||
|
|
2124965996 | ||
|
|
6f1e104067 | ||
|
|
b20f40b5a3 | ||
|
|
1093ca5fc4 | ||
|
|
0d99c55d3f | ||
|
|
86dc1a88ed | ||
|
|
968c20d8ca | ||
|
|
1b2d978449 | ||
|
|
30582b2d75 | ||
|
|
38a8037a4d | ||
|
|
67de45f787 | ||
|
|
f358f24abb | ||
|
|
fd4e15003a | ||
|
|
81df249d5c | ||
|
|
ba60997992 | ||
|
|
8961294a29 | ||
|
|
bc2e0a000a | ||
|
|
1a693fba2d | ||
|
|
05b7acf6aa | ||
|
|
de13d8d397 | ||
|
|
6d7d8ae87c | ||
|
|
cfb1bdcd52 | ||
|
|
48d41833f1 | ||
|
|
01535f1afc | ||
|
|
fe845bfa36 | ||
|
|
b94dbc75b7 | ||
|
|
bdf40640d0 | ||
|
|
496f6c706b | ||
|
|
79f97d5555 | ||
|
|
82f679c84c | ||
|
|
9b789889fa | ||
|
|
dd8dd118da | ||
|
|
8222a4b6a4 | ||
|
|
31f8057bfa | ||
|
|
1d8e0417bd | ||
|
|
8100ca46c2 | ||
|
|
94d745cf1c | ||
|
|
a92bd0ad0f |
No files matched your search
@@ -30,6 +30,9 @@ jobs:
|
|||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v2
|
uses: docker/setup-buildx-action@v2
|
||||||
|
|
||||||
|
- name: Create .version file
|
||||||
|
run: echo ${{ github.ref_name }} > .version
|
||||||
|
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v2
|
||||||
with:
|
with:
|
||||||
|
|||||||
+12
-3
@@ -1,4 +1,12 @@
|
|||||||
FROM ruby:3.2.2-alpine as webpack
|
FROM ruby:3.2.2-alpine3.18 as fonts
|
||||||
|
|
||||||
|
WORKDIR /fonts
|
||||||
|
|
||||||
|
RUN apk --no-cache add fontforge wget ttf-liberation && cp /usr/share/fonts/liberation/LiberationSans-Regular.ttf /usr/share/fonts/liberation/LiberationSans-Bold.ttf . && wget https://cdn.jsdelivr.net/gh/notofonts/notofonts.github.io/fonts/NotoSansArabic/hinted/ttf/NotoSansArabic-Regular.ttf && wget https://github.com/impallari/DancingScript/raw/master/fonts/DancingScript-Regular.otf && wget https://github.com/impallari/DancingScript/raw/master/OFL.txt
|
||||||
|
|
||||||
|
RUN fontforge -lang=py -c 'font1 = fontforge.open("LiberationSans-Regular.ttf"); font2 = fontforge.open("NotoSansArabic-Regular.ttf"); font1.mergeFonts(font2); font1.generate("LiberationSans-Regular.ttf")'
|
||||||
|
|
||||||
|
FROM ruby:3.2.2-alpine3.18 as webpack
|
||||||
|
|
||||||
ENV RAILS_ENV=production
|
ENV RAILS_ENV=production
|
||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
@@ -31,7 +39,7 @@ ENV BUNDLE_WITHOUT="development:test"
|
|||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
RUN apk add --no-cache build-base sqlite-dev libpq-dev mariadb-dev vips-dev vips-poppler poppler-utils vips-heif libc6-compat ttf-freefont ttf-liberation && mkdir /fonts && cp /usr/share/fonts/liberation/LiberationSans-Regular.ttf /usr/share/fonts/liberation/LiberationSans-Bold.ttf /fonts && apk del ttf-liberation && wget -O /fonts/DancingScript.otf "https://github.com/impallari/DancingScript/raw/master/fonts/DancingScript-Regular.otf" && wget -O /fonts/DancingScript-License.txt https://github.com/impallari/DancingScript/blob/master/OFL.txt
|
RUN apk add --no-cache build-base sqlite-dev libpq-dev mariadb-dev vips-dev vips-poppler poppler-utils vips-heif libc6-compat ttf-freefont && mkdir /fonts
|
||||||
|
|
||||||
COPY ./Gemfile ./Gemfile.lock ./
|
COPY ./Gemfile ./Gemfile.lock ./
|
||||||
|
|
||||||
@@ -45,8 +53,9 @@ COPY ./log ./log
|
|||||||
COPY ./lib ./lib
|
COPY ./lib ./lib
|
||||||
COPY ./public ./public
|
COPY ./public ./public
|
||||||
COPY ./tmp ./tmp
|
COPY ./tmp ./tmp
|
||||||
COPY LICENSE README.md Rakefile config.ru ./
|
COPY LICENSE README.md Rakefile config.ru .version ./
|
||||||
|
|
||||||
|
COPY --from=fonts /fonts/LiberationSans-Regular.ttf /fonts/LiberationSans-Bold.ttf /fonts/DancingScript-Regular.otf /fonts/OFL.txt /fonts
|
||||||
COPY --from=webpack /app/public/packs ./public/packs
|
COPY --from=webpack /app/public/packs ./public/packs
|
||||||
|
|
||||||
RUN ln -s /fonts /app/public/fonts
|
RUN ln -s /fonts /app/public/fonts
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ gem 'pdf-reader'
|
|||||||
gem 'pg', require: false
|
gem 'pg', require: false
|
||||||
gem 'premailer-rails'
|
gem 'premailer-rails'
|
||||||
gem 'puma'
|
gem 'puma'
|
||||||
|
gem 'rack'
|
||||||
gem 'rails'
|
gem 'rails'
|
||||||
gem 'rails_autolink'
|
gem 'rails_autolink'
|
||||||
gem 'rails-i18n'
|
gem 'rails-i18n'
|
||||||
|
|||||||
+186
-160
@@ -2,93 +2,102 @@ GEM
|
|||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
Ascii85 (1.1.0)
|
Ascii85 (1.1.0)
|
||||||
actioncable (7.0.7)
|
actioncable (7.1.2)
|
||||||
actionpack (= 7.0.7)
|
actionpack (= 7.1.2)
|
||||||
activesupport (= 7.0.7)
|
activesupport (= 7.1.2)
|
||||||
nio4r (~> 2.0)
|
nio4r (~> 2.0)
|
||||||
websocket-driver (>= 0.6.1)
|
websocket-driver (>= 0.6.1)
|
||||||
actionmailbox (7.0.7)
|
zeitwerk (~> 2.6)
|
||||||
actionpack (= 7.0.7)
|
actionmailbox (7.1.2)
|
||||||
activejob (= 7.0.7)
|
actionpack (= 7.1.2)
|
||||||
activerecord (= 7.0.7)
|
activejob (= 7.1.2)
|
||||||
activestorage (= 7.0.7)
|
activerecord (= 7.1.2)
|
||||||
activesupport (= 7.0.7)
|
activestorage (= 7.1.2)
|
||||||
|
activesupport (= 7.1.2)
|
||||||
mail (>= 2.7.1)
|
mail (>= 2.7.1)
|
||||||
net-imap
|
net-imap
|
||||||
net-pop
|
net-pop
|
||||||
net-smtp
|
net-smtp
|
||||||
actionmailer (7.0.7)
|
actionmailer (7.1.2)
|
||||||
actionpack (= 7.0.7)
|
actionpack (= 7.1.2)
|
||||||
actionview (= 7.0.7)
|
actionview (= 7.1.2)
|
||||||
activejob (= 7.0.7)
|
activejob (= 7.1.2)
|
||||||
activesupport (= 7.0.7)
|
activesupport (= 7.1.2)
|
||||||
mail (~> 2.5, >= 2.5.4)
|
mail (~> 2.5, >= 2.5.4)
|
||||||
net-imap
|
net-imap
|
||||||
net-pop
|
net-pop
|
||||||
net-smtp
|
net-smtp
|
||||||
rails-dom-testing (~> 2.0)
|
rails-dom-testing (~> 2.2)
|
||||||
actionpack (7.0.7)
|
actionpack (7.1.2)
|
||||||
actionview (= 7.0.7)
|
actionview (= 7.1.2)
|
||||||
activesupport (= 7.0.7)
|
activesupport (= 7.1.2)
|
||||||
rack (~> 2.0, >= 2.2.4)
|
nokogiri (>= 1.8.5)
|
||||||
|
racc
|
||||||
|
rack (>= 2.2.4)
|
||||||
|
rack-session (>= 1.0.1)
|
||||||
rack-test (>= 0.6.3)
|
rack-test (>= 0.6.3)
|
||||||
rails-dom-testing (~> 2.0)
|
rails-dom-testing (~> 2.2)
|
||||||
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
rails-html-sanitizer (~> 1.6)
|
||||||
actiontext (7.0.7)
|
actiontext (7.1.2)
|
||||||
actionpack (= 7.0.7)
|
actionpack (= 7.1.2)
|
||||||
activerecord (= 7.0.7)
|
activerecord (= 7.1.2)
|
||||||
activestorage (= 7.0.7)
|
activestorage (= 7.1.2)
|
||||||
activesupport (= 7.0.7)
|
activesupport (= 7.1.2)
|
||||||
globalid (>= 0.6.0)
|
globalid (>= 0.6.0)
|
||||||
nokogiri (>= 1.8.5)
|
nokogiri (>= 1.8.5)
|
||||||
actionview (7.0.7)
|
actionview (7.1.2)
|
||||||
activesupport (= 7.0.7)
|
activesupport (= 7.1.2)
|
||||||
builder (~> 3.1)
|
builder (~> 3.1)
|
||||||
erubi (~> 1.4)
|
erubi (~> 1.11)
|
||||||
rails-dom-testing (~> 2.0)
|
rails-dom-testing (~> 2.2)
|
||||||
rails-html-sanitizer (~> 1.1, >= 1.2.0)
|
rails-html-sanitizer (~> 1.6)
|
||||||
activejob (7.0.7)
|
activejob (7.1.2)
|
||||||
activesupport (= 7.0.7)
|
activesupport (= 7.1.2)
|
||||||
globalid (>= 0.3.6)
|
globalid (>= 0.3.6)
|
||||||
activemodel (7.0.7)
|
activemodel (7.1.2)
|
||||||
activesupport (= 7.0.7)
|
activesupport (= 7.1.2)
|
||||||
activerecord (7.0.7)
|
activerecord (7.1.2)
|
||||||
activemodel (= 7.0.7)
|
activemodel (= 7.1.2)
|
||||||
activesupport (= 7.0.7)
|
activesupport (= 7.1.2)
|
||||||
activestorage (7.0.7)
|
timeout (>= 0.4.0)
|
||||||
actionpack (= 7.0.7)
|
activestorage (7.1.2)
|
||||||
activejob (= 7.0.7)
|
actionpack (= 7.1.2)
|
||||||
activerecord (= 7.0.7)
|
activejob (= 7.1.2)
|
||||||
activesupport (= 7.0.7)
|
activerecord (= 7.1.2)
|
||||||
|
activesupport (= 7.1.2)
|
||||||
marcel (~> 1.0)
|
marcel (~> 1.0)
|
||||||
mini_mime (>= 1.1.0)
|
activesupport (7.1.2)
|
||||||
activesupport (7.0.7)
|
base64
|
||||||
|
bigdecimal
|
||||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||||
|
connection_pool (>= 2.2.5)
|
||||||
|
drb
|
||||||
i18n (>= 1.6, < 2)
|
i18n (>= 1.6, < 2)
|
||||||
minitest (>= 5.1)
|
minitest (>= 5.1)
|
||||||
|
mutex_m
|
||||||
tzinfo (~> 2.0)
|
tzinfo (~> 2.0)
|
||||||
addressable (2.8.5)
|
addressable (2.8.6)
|
||||||
public_suffix (>= 2.0.2, < 6.0)
|
public_suffix (>= 2.0.2, < 6.0)
|
||||||
afm (0.2.2)
|
afm (0.2.2)
|
||||||
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)
|
||||||
ast (2.4.2)
|
ast (2.4.2)
|
||||||
aws-eventstream (1.2.0)
|
aws-eventstream (1.3.0)
|
||||||
aws-partitions (1.807.0)
|
aws-partitions (1.874.0)
|
||||||
aws-sdk-core (3.180.3)
|
aws-sdk-core (3.190.1)
|
||||||
aws-eventstream (~> 1, >= 1.0.2)
|
aws-eventstream (~> 1, >= 1.3.0)
|
||||||
aws-partitions (~> 1, >= 1.651.0)
|
aws-partitions (~> 1, >= 1.651.0)
|
||||||
aws-sigv4 (~> 1.5)
|
aws-sigv4 (~> 1.8)
|
||||||
jmespath (~> 1, >= 1.6.1)
|
jmespath (~> 1, >= 1.6.1)
|
||||||
aws-sdk-kms (1.71.0)
|
aws-sdk-kms (1.75.0)
|
||||||
aws-sdk-core (~> 3, >= 3.177.0)
|
aws-sdk-core (~> 3, >= 3.188.0)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.1)
|
||||||
aws-sdk-s3 (1.132.1)
|
aws-sdk-s3 (1.142.0)
|
||||||
aws-sdk-core (~> 3, >= 3.179.0)
|
aws-sdk-core (~> 3, >= 3.189.0)
|
||||||
aws-sdk-kms (~> 1)
|
aws-sdk-kms (~> 1)
|
||||||
aws-sigv4 (~> 1.6)
|
aws-sigv4 (~> 1.8)
|
||||||
aws-sigv4 (1.6.0)
|
aws-sigv4 (1.8.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)
|
||||||
@@ -98,7 +107,8 @@ GEM
|
|||||||
faraday_middleware (~> 1.0, >= 1.0.0.rc1)
|
faraday_middleware (~> 1.0, >= 1.0.0.rc1)
|
||||||
net-http-persistent (~> 4.0)
|
net-http-persistent (~> 4.0)
|
||||||
nokogiri (~> 1, >= 1.10.8)
|
nokogiri (~> 1, >= 1.10.8)
|
||||||
bcrypt (3.1.19)
|
base64 (0.2.0)
|
||||||
|
bcrypt (3.1.20)
|
||||||
better_html (2.0.2)
|
better_html (2.0.2)
|
||||||
actionview (>= 6.0)
|
actionview (>= 6.0)
|
||||||
activesupport (>= 6.0)
|
activesupport (>= 6.0)
|
||||||
@@ -106,11 +116,12 @@ GEM
|
|||||||
erubi (~> 1.4)
|
erubi (~> 1.4)
|
||||||
parser (>= 2.4)
|
parser (>= 2.4)
|
||||||
smart_properties
|
smart_properties
|
||||||
|
bigdecimal (3.1.5)
|
||||||
bindex (0.8.1)
|
bindex (0.8.1)
|
||||||
bootsnap (1.16.0)
|
bootsnap (1.17.0)
|
||||||
msgpack (~> 1.2)
|
msgpack (~> 1.2)
|
||||||
builder (3.2.4)
|
builder (3.2.4)
|
||||||
bullet (7.0.7)
|
bullet (7.1.4)
|
||||||
activesupport (>= 3.0.0)
|
activesupport (>= 3.0.0)
|
||||||
uniform_notifier (~> 1.11)
|
uniform_notifier (~> 1.11)
|
||||||
cancancan (3.5.0)
|
cancancan (3.5.0)
|
||||||
@@ -131,17 +142,17 @@ GEM
|
|||||||
crack (0.4.5)
|
crack (0.4.5)
|
||||||
rexml
|
rexml
|
||||||
crass (1.0.6)
|
crass (1.0.6)
|
||||||
css_parser (1.14.0)
|
css_parser (1.16.0)
|
||||||
addressable
|
addressable
|
||||||
cuprite (0.14.3)
|
cuprite (0.15)
|
||||||
capybara (~> 3.0)
|
capybara (~> 3.0)
|
||||||
ferrum (~> 0.13.0)
|
ferrum (~> 0.14.0)
|
||||||
date (3.3.3)
|
date (3.3.4)
|
||||||
debug (1.8.0)
|
debug (1.9.1)
|
||||||
irb (>= 1.5.0)
|
irb (~> 1.10)
|
||||||
reline (>= 0.3.1)
|
reline (>= 0.3.8)
|
||||||
declarative (0.0.20)
|
declarative (0.0.20)
|
||||||
devise (4.9.2)
|
devise (4.9.3)
|
||||||
bcrypt (~> 3.0)
|
bcrypt (~> 3.0)
|
||||||
orm_adapter (~> 0.1)
|
orm_adapter (~> 0.1)
|
||||||
railties (>= 4.1.0)
|
railties (>= 4.1.0)
|
||||||
@@ -157,8 +168,10 @@ GEM
|
|||||||
rake (>= 12.0.0, < 14.0.0)
|
rake (>= 12.0.0, < 14.0.0)
|
||||||
docile (1.4.0)
|
docile (1.4.0)
|
||||||
dotenv (2.8.1)
|
dotenv (2.8.1)
|
||||||
|
drb (2.2.0)
|
||||||
|
ruby2_keywords
|
||||||
email_typo (0.2.3)
|
email_typo (0.2.3)
|
||||||
erb_lint (0.4.0)
|
erb_lint (0.5.0)
|
||||||
activesupport
|
activesupport
|
||||||
better_html (>= 2.0.1)
|
better_html (>= 2.0.1)
|
||||||
parser (>= 2.7.1.4)
|
parser (>= 2.7.1.4)
|
||||||
@@ -166,12 +179,12 @@ GEM
|
|||||||
rubocop
|
rubocop
|
||||||
smart_properties
|
smart_properties
|
||||||
erubi (1.12.0)
|
erubi (1.12.0)
|
||||||
factory_bot (6.2.1)
|
factory_bot (6.4.4)
|
||||||
activesupport (>= 5.0.0)
|
activesupport (>= 5.0.0)
|
||||||
factory_bot_rails (6.2.0)
|
factory_bot_rails (6.4.2)
|
||||||
factory_bot (~> 6.2.0)
|
factory_bot (~> 6.4)
|
||||||
railties (>= 5.0.0)
|
railties (>= 5.0.0)
|
||||||
faker (3.2.1)
|
faker (3.2.2)
|
||||||
i18n (>= 1.8.11, < 2)
|
i18n (>= 1.8.11, < 2)
|
||||||
faraday (1.10.3)
|
faraday (1.10.3)
|
||||||
faraday-em_http (~> 1.0)
|
faraday-em_http (~> 1.0)
|
||||||
@@ -200,16 +213,16 @@ GEM
|
|||||||
faraday-retry (1.0.3)
|
faraday-retry (1.0.3)
|
||||||
faraday_middleware (1.2.0)
|
faraday_middleware (1.2.0)
|
||||||
faraday (~> 1.0)
|
faraday (~> 1.0)
|
||||||
ferrum (0.13)
|
ferrum (0.14)
|
||||||
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.6, < 0.8)
|
||||||
ffi (1.15.5)
|
ffi (1.16.3)
|
||||||
geom2d (0.4.1)
|
geom2d (0.4.1)
|
||||||
globalid (1.1.0)
|
globalid (1.2.1)
|
||||||
activesupport (>= 5.0)
|
activesupport (>= 6.1)
|
||||||
google-apis-core (0.11.1)
|
google-apis-core (0.11.2)
|
||||||
addressable (~> 2.5, >= 2.5.1)
|
addressable (~> 2.5, >= 2.5.1)
|
||||||
googleauth (>= 0.16.2, < 2.a)
|
googleauth (>= 0.16.2, < 2.a)
|
||||||
httpclient (>= 2.8.1, < 3.a)
|
httpclient (>= 2.8.1, < 3.a)
|
||||||
@@ -220,30 +233,30 @@ GEM
|
|||||||
webrick
|
webrick
|
||||||
google-apis-iamcredentials_v1 (0.17.0)
|
google-apis-iamcredentials_v1 (0.17.0)
|
||||||
google-apis-core (>= 0.11.0, < 2.a)
|
google-apis-core (>= 0.11.0, < 2.a)
|
||||||
google-apis-storage_v1 (0.19.0)
|
google-apis-storage_v1 (0.29.0)
|
||||||
google-apis-core (>= 0.9.0, < 2.a)
|
google-apis-core (>= 0.11.0, < 2.a)
|
||||||
google-cloud-core (1.6.0)
|
google-cloud-core (1.6.1)
|
||||||
google-cloud-env (~> 1.0)
|
google-cloud-env (>= 1.0, < 3.a)
|
||||||
google-cloud-errors (~> 1.0)
|
google-cloud-errors (~> 1.0)
|
||||||
google-cloud-env (1.6.0)
|
google-cloud-env (2.1.0)
|
||||||
faraday (>= 0.17.3, < 3.0)
|
faraday (>= 1.0, < 3.a)
|
||||||
google-cloud-errors (1.3.1)
|
google-cloud-errors (1.3.1)
|
||||||
google-cloud-storage (1.44.0)
|
google-cloud-storage (1.45.0)
|
||||||
addressable (~> 2.8)
|
addressable (~> 2.8)
|
||||||
digest-crc (~> 0.4)
|
digest-crc (~> 0.4)
|
||||||
google-apis-iamcredentials_v1 (~> 0.1)
|
google-apis-iamcredentials_v1 (~> 0.1)
|
||||||
google-apis-storage_v1 (~> 0.19.0)
|
google-apis-storage_v1 (~> 0.29.0)
|
||||||
google-cloud-core (~> 1.6)
|
google-cloud-core (~> 1.6)
|
||||||
googleauth (>= 0.16.2, < 2.a)
|
googleauth (>= 0.16.2, < 2.a)
|
||||||
mini_mime (~> 1.0)
|
mini_mime (~> 1.0)
|
||||||
googleauth (1.7.0)
|
googleauth (1.9.1)
|
||||||
faraday (>= 0.17.3, < 3.a)
|
faraday (>= 1.0, < 3.a)
|
||||||
|
google-cloud-env (~> 2.1)
|
||||||
jwt (>= 1.4, < 3.0)
|
jwt (>= 1.4, < 3.0)
|
||||||
memoist (~> 0.16)
|
|
||||||
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.0.1)
|
hashdiff (1.1.0)
|
||||||
hashery (2.1.2)
|
hashery (2.1.2)
|
||||||
hexapdf (0.34.1)
|
hexapdf (0.34.1)
|
||||||
cmdparse (~> 3.0, >= 3.0.3)
|
cmdparse (~> 3.0, >= 3.0.3)
|
||||||
@@ -256,11 +269,12 @@ GEM
|
|||||||
image_processing (1.12.2)
|
image_processing (1.12.2)
|
||||||
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.6.0)
|
io-console (0.7.1)
|
||||||
irb (1.7.4)
|
irb (1.11.0)
|
||||||
reline (>= 0.3.6)
|
rdoc
|
||||||
|
reline (>= 0.3.8)
|
||||||
jmespath (1.6.2)
|
jmespath (1.6.2)
|
||||||
json (2.6.3)
|
json (2.7.1)
|
||||||
jwt (2.7.1)
|
jwt (2.7.1)
|
||||||
language_server-protocol (3.17.0.3)
|
language_server-protocol (3.17.0.3)
|
||||||
launchy (2.5.2)
|
launchy (2.5.2)
|
||||||
@@ -272,12 +286,12 @@ GEM
|
|||||||
letter_opener (~> 1.7)
|
letter_opener (~> 1.7)
|
||||||
railties (>= 5.2)
|
railties (>= 5.2)
|
||||||
rexml
|
rexml
|
||||||
lograge (0.13.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.21.3)
|
loofah (2.22.0)
|
||||||
crass (~> 1.0.2)
|
crass (~> 1.0.2)
|
||||||
nokogiri (>= 1.12.0)
|
nokogiri (>= 1.12.0)
|
||||||
mail (2.8.1)
|
mail (2.8.1)
|
||||||
@@ -287,49 +301,48 @@ GEM
|
|||||||
net-smtp
|
net-smtp
|
||||||
marcel (1.0.2)
|
marcel (1.0.2)
|
||||||
matrix (0.4.2)
|
matrix (0.4.2)
|
||||||
memoist (0.16.2)
|
|
||||||
method_source (1.0.0)
|
method_source (1.0.0)
|
||||||
mini_magick (4.12.0)
|
mini_magick (4.12.0)
|
||||||
mini_mime (1.1.5)
|
mini_mime (1.1.5)
|
||||||
mini_portile2 (2.8.4)
|
mini_portile2 (2.8.5)
|
||||||
minitest (5.20.0)
|
minitest (5.20.0)
|
||||||
msgpack (1.7.2)
|
msgpack (1.7.2)
|
||||||
multi_json (1.15.0)
|
multi_json (1.15.0)
|
||||||
multipart-post (2.3.0)
|
multipart-post (2.3.0)
|
||||||
|
mutex_m (0.2.0)
|
||||||
mysql2 (0.5.5)
|
mysql2 (0.5.5)
|
||||||
net-http-persistent (4.0.2)
|
net-http-persistent (4.0.2)
|
||||||
connection_pool (~> 2.2)
|
connection_pool (~> 2.2)
|
||||||
net-imap (0.3.7)
|
net-imap (0.4.9)
|
||||||
date
|
date
|
||||||
net-protocol
|
net-protocol
|
||||||
net-pop (0.1.2)
|
net-pop (0.1.2)
|
||||||
net-protocol
|
net-protocol
|
||||||
net-protocol (0.2.1)
|
net-protocol (0.2.2)
|
||||||
timeout
|
timeout
|
||||||
net-smtp (0.3.3)
|
net-smtp (0.4.0)
|
||||||
net-protocol
|
net-protocol
|
||||||
nio4r (2.5.9)
|
nio4r (2.7.0)
|
||||||
nokogiri (1.15.4)
|
nokogiri (1.15.5)
|
||||||
mini_portile2 (~> 2.8.2)
|
mini_portile2 (~> 2.8.2)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
nokogiri (1.15.4-arm64-darwin)
|
oj (3.16.3)
|
||||||
racc (~> 1.4)
|
bigdecimal (>= 3.0)
|
||||||
oj (3.16.0)
|
|
||||||
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)
|
||||||
pagy (6.0.4)
|
pagy (6.2.0)
|
||||||
parallel (1.23.0)
|
parallel (1.24.0)
|
||||||
parser (3.2.2.4)
|
parser (3.2.2.4)
|
||||||
ast (~> 2.4.1)
|
ast (~> 2.4.1)
|
||||||
racc
|
racc
|
||||||
pdf-reader (2.11.0)
|
pdf-reader (2.12.0)
|
||||||
Ascii85 (~> 1.0)
|
Ascii85 (~> 1.0)
|
||||||
afm (~> 0.2.1)
|
afm (~> 0.2.1)
|
||||||
hashery (~> 2.0)
|
hashery (~> 2.0)
|
||||||
ruby-rc4
|
ruby-rc4
|
||||||
ttfunk
|
ttfunk
|
||||||
pg (1.5.3)
|
pg (1.5.4)
|
||||||
premailer (1.21.0)
|
premailer (1.21.0)
|
||||||
addressable
|
addressable
|
||||||
css_parser (>= 1.12.0)
|
css_parser (>= 1.12.0)
|
||||||
@@ -343,29 +356,36 @@ GEM
|
|||||||
method_source (~> 1.0)
|
method_source (~> 1.0)
|
||||||
pry-rails (0.3.9)
|
pry-rails (0.3.9)
|
||||||
pry (>= 0.10.4)
|
pry (>= 0.10.4)
|
||||||
public_suffix (5.0.3)
|
psych (5.1.2)
|
||||||
puma (6.3.1)
|
stringio
|
||||||
|
public_suffix (5.0.4)
|
||||||
|
puma (6.4.0)
|
||||||
nio4r (~> 2.0)
|
nio4r (~> 2.0)
|
||||||
racc (1.7.1)
|
racc (1.7.3)
|
||||||
rack (2.2.8)
|
rack (2.2.8)
|
||||||
rack-proxy (0.7.6)
|
rack-proxy (0.7.7)
|
||||||
rack
|
rack
|
||||||
|
rack-session (1.0.2)
|
||||||
|
rack (< 3)
|
||||||
rack-test (2.1.0)
|
rack-test (2.1.0)
|
||||||
rack (>= 1.3)
|
rack (>= 1.3)
|
||||||
rails (7.0.7)
|
rackup (1.0.0)
|
||||||
actioncable (= 7.0.7)
|
rack (< 3)
|
||||||
actionmailbox (= 7.0.7)
|
webrick
|
||||||
actionmailer (= 7.0.7)
|
rails (7.1.2)
|
||||||
actionpack (= 7.0.7)
|
actioncable (= 7.1.2)
|
||||||
actiontext (= 7.0.7)
|
actionmailbox (= 7.1.2)
|
||||||
actionview (= 7.0.7)
|
actionmailer (= 7.1.2)
|
||||||
activejob (= 7.0.7)
|
actionpack (= 7.1.2)
|
||||||
activemodel (= 7.0.7)
|
actiontext (= 7.1.2)
|
||||||
activerecord (= 7.0.7)
|
actionview (= 7.1.2)
|
||||||
activestorage (= 7.0.7)
|
activejob (= 7.1.2)
|
||||||
activesupport (= 7.0.7)
|
activemodel (= 7.1.2)
|
||||||
|
activerecord (= 7.1.2)
|
||||||
|
activestorage (= 7.1.2)
|
||||||
|
activesupport (= 7.1.2)
|
||||||
bundler (>= 1.15.0)
|
bundler (>= 1.15.0)
|
||||||
railties (= 7.0.7)
|
railties (= 7.1.2)
|
||||||
rails-dom-testing (2.2.0)
|
rails-dom-testing (2.2.0)
|
||||||
activesupport (>= 5.0.0)
|
activesupport (>= 5.0.0)
|
||||||
minitest
|
minitest
|
||||||
@@ -380,19 +400,22 @@ GEM
|
|||||||
actionview (> 3.1)
|
actionview (> 3.1)
|
||||||
activesupport (> 3.1)
|
activesupport (> 3.1)
|
||||||
railties (> 3.1)
|
railties (> 3.1)
|
||||||
railties (7.0.7)
|
railties (7.1.2)
|
||||||
actionpack (= 7.0.7)
|
actionpack (= 7.1.2)
|
||||||
activesupport (= 7.0.7)
|
activesupport (= 7.1.2)
|
||||||
method_source
|
irb
|
||||||
|
rackup (>= 1.0.0)
|
||||||
rake (>= 12.2)
|
rake (>= 12.2)
|
||||||
thor (~> 1.0)
|
thor (~> 1.0, >= 1.2.2)
|
||||||
zeitwerk (~> 2.5)
|
zeitwerk (~> 2.6)
|
||||||
rainbow (3.1.1)
|
rainbow (3.1.1)
|
||||||
rake (13.0.6)
|
rake (13.1.0)
|
||||||
redis-client (0.16.0)
|
rdoc (6.6.2)
|
||||||
|
psych (>= 4.0.0)
|
||||||
|
redis-client (0.19.1)
|
||||||
connection_pool
|
connection_pool
|
||||||
regexp_parser (2.8.2)
|
regexp_parser (2.8.3)
|
||||||
reline (0.3.8)
|
reline (0.4.1)
|
||||||
io-console (~> 0.5)
|
io-console (~> 0.5)
|
||||||
representable (3.2.0)
|
representable (3.2.0)
|
||||||
declarative (< 0.1.0)
|
declarative (< 0.1.0)
|
||||||
@@ -400,12 +423,12 @@ GEM
|
|||||||
uber (< 0.2.0)
|
uber (< 0.2.0)
|
||||||
request_store (1.5.1)
|
request_store (1.5.1)
|
||||||
rack (>= 1.4)
|
rack (>= 1.4)
|
||||||
responders (3.1.0)
|
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.2.6)
|
||||||
rollbar (3.4.0)
|
rollbar (3.4.2)
|
||||||
rotp (6.3.0)
|
rotp (6.3.0)
|
||||||
rqrcode (2.2.0)
|
rqrcode (2.2.0)
|
||||||
chunky_png (~> 1.0)
|
chunky_png (~> 1.0)
|
||||||
@@ -419,7 +442,7 @@ GEM
|
|||||||
rspec-mocks (3.12.6)
|
rspec-mocks (3.12.6)
|
||||||
diff-lcs (>= 1.2.0, < 2.0)
|
diff-lcs (>= 1.2.0, < 2.0)
|
||||||
rspec-support (~> 3.12.0)
|
rspec-support (~> 3.12.0)
|
||||||
rspec-rails (6.0.3)
|
rspec-rails (6.1.0)
|
||||||
actionpack (>= 6.1)
|
actionpack (>= 6.1)
|
||||||
activesupport (>= 6.1)
|
activesupport (>= 6.1)
|
||||||
railties (>= 6.1)
|
railties (>= 6.1)
|
||||||
@@ -428,7 +451,7 @@ GEM
|
|||||||
rspec-mocks (~> 3.12)
|
rspec-mocks (~> 3.12)
|
||||||
rspec-support (~> 3.12)
|
rspec-support (~> 3.12)
|
||||||
rspec-support (3.12.1)
|
rspec-support (3.12.1)
|
||||||
rubocop (1.57.2)
|
rubocop (1.59.0)
|
||||||
json (~> 2.3)
|
json (~> 2.3)
|
||||||
language_server-protocol (>= 3.17.0)
|
language_server-protocol (>= 3.17.0)
|
||||||
parallel (~> 1.10)
|
parallel (~> 1.10)
|
||||||
@@ -436,7 +459,7 @@ GEM
|
|||||||
rainbow (>= 2.2.2, < 4.0)
|
rainbow (>= 2.2.2, < 4.0)
|
||||||
regexp_parser (>= 1.8, < 3.0)
|
regexp_parser (>= 1.8, < 3.0)
|
||||||
rexml (>= 3.2.5, < 4.0)
|
rexml (>= 3.2.5, < 4.0)
|
||||||
rubocop-ast (>= 1.28.1, < 2.0)
|
rubocop-ast (>= 1.30.0, < 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.30.0)
|
rubocop-ast (1.30.0)
|
||||||
@@ -445,20 +468,21 @@ GEM
|
|||||||
rubocop (~> 1.41)
|
rubocop (~> 1.41)
|
||||||
rubocop-factory_bot (2.24.0)
|
rubocop-factory_bot (2.24.0)
|
||||||
rubocop (~> 1.33)
|
rubocop (~> 1.33)
|
||||||
rubocop-performance (1.19.1)
|
rubocop-performance (1.20.1)
|
||||||
rubocop (>= 1.7.0, < 2.0)
|
rubocop (>= 1.48.1, < 2.0)
|
||||||
rubocop-ast (>= 0.4.0)
|
rubocop-ast (>= 1.30.0, < 2.0)
|
||||||
rubocop-rails (2.22.1)
|
rubocop-rails (2.23.1)
|
||||||
activesupport (>= 4.2.0)
|
activesupport (>= 4.2.0)
|
||||||
rack (>= 1.1)
|
rack (>= 1.1)
|
||||||
rubocop (>= 1.33.0, < 2.0)
|
rubocop (>= 1.33.0, < 2.0)
|
||||||
|
rubocop-ast (>= 1.30.0, < 2.0)
|
||||||
rubocop-rspec (2.25.0)
|
rubocop-rspec (2.25.0)
|
||||||
rubocop (~> 1.40)
|
rubocop (~> 1.40)
|
||||||
rubocop-capybara (~> 2.17)
|
rubocop-capybara (~> 2.17)
|
||||||
rubocop-factory_bot (~> 2.22)
|
rubocop-factory_bot (~> 2.22)
|
||||||
ruby-progressbar (1.13.0)
|
ruby-progressbar (1.13.0)
|
||||||
ruby-rc4 (0.1.5)
|
ruby-rc4 (0.1.5)
|
||||||
ruby-vips (2.1.4)
|
ruby-vips (2.2.0)
|
||||||
ffi (~> 1.12)
|
ffi (~> 1.12)
|
||||||
ruby2_keywords (0.0.5)
|
ruby2_keywords (0.0.5)
|
||||||
rubyXL (3.4.25)
|
rubyXL (3.4.25)
|
||||||
@@ -466,17 +490,17 @@ GEM
|
|||||||
rubyzip (>= 1.3.0)
|
rubyzip (>= 1.3.0)
|
||||||
rubyzip (2.3.2)
|
rubyzip (2.3.2)
|
||||||
semantic_range (3.0.0)
|
semantic_range (3.0.0)
|
||||||
shakapacker (7.0.3)
|
shakapacker (7.1.0)
|
||||||
activesupport (>= 5.2)
|
activesupport (>= 5.2)
|
||||||
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.1.2)
|
sidekiq (7.2.0)
|
||||||
concurrent-ruby (< 2)
|
concurrent-ruby (< 2)
|
||||||
connection_pool (>= 2.3.0)
|
connection_pool (>= 2.3.0)
|
||||||
rack (>= 2.2.4)
|
rack (>= 2.2.4)
|
||||||
redis-client (>= 0.14.0)
|
redis-client (>= 0.14.0)
|
||||||
signet (0.17.0)
|
signet (0.18.0)
|
||||||
addressable (~> 2.8)
|
addressable (~> 2.8)
|
||||||
faraday (>= 0.17.5, < 3.a)
|
faraday (>= 0.17.5, < 3.a)
|
||||||
jwt (>= 1.5, < 3.0)
|
jwt (>= 1.5, < 3.0)
|
||||||
@@ -488,34 +512,35 @@ GEM
|
|||||||
simplecov-html (0.12.3)
|
simplecov-html (0.12.3)
|
||||||
simplecov_json_formatter (0.1.4)
|
simplecov_json_formatter (0.1.4)
|
||||||
smart_properties (1.17.0)
|
smart_properties (1.17.0)
|
||||||
sqlite3 (1.6.3)
|
sqlite3 (1.7.0)
|
||||||
mini_portile2 (~> 2.8.0)
|
mini_portile2 (~> 2.8.0)
|
||||||
sqlite3 (1.6.3-arm64-darwin)
|
sqlite3 (1.7.0-arm64-darwin)
|
||||||
|
stringio (3.1.0)
|
||||||
strip_attributes (1.13.0)
|
strip_attributes (1.13.0)
|
||||||
activemodel (>= 3.0, < 8.0)
|
activemodel (>= 3.0, < 8.0)
|
||||||
thor (1.2.2)
|
thor (1.3.0)
|
||||||
timeout (0.4.0)
|
timeout (0.4.1)
|
||||||
trailblazer-option (0.1.2)
|
trailblazer-option (0.1.2)
|
||||||
ttfunk (1.7.0)
|
ttfunk (1.7.0)
|
||||||
turbo-rails (1.4.0)
|
turbo-rails (1.5.0)
|
||||||
actionpack (>= 6.0.0)
|
actionpack (>= 6.0.0)
|
||||||
activejob (>= 6.0.0)
|
activejob (>= 6.0.0)
|
||||||
railties (>= 6.0.0)
|
railties (>= 6.0.0)
|
||||||
tzinfo (2.0.6)
|
tzinfo (2.0.6)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
tzinfo-data (1.2023.3)
|
tzinfo-data (1.2023.4)
|
||||||
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.5.0)
|
||||||
uniform_notifier (1.16.0)
|
uniform_notifier (1.16.0)
|
||||||
warden (1.2.9)
|
warden (1.2.9)
|
||||||
rack (>= 2.0.9)
|
rack (>= 2.0.9)
|
||||||
web-console (4.2.0)
|
web-console (4.2.1)
|
||||||
actionview (>= 6.0.0)
|
actionview (>= 6.0.0)
|
||||||
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.18.1)
|
webmock (3.19.1)
|
||||||
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)
|
||||||
@@ -525,7 +550,7 @@ GEM
|
|||||||
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.11)
|
zeitwerk (2.6.12)
|
||||||
|
|
||||||
PLATFORMS
|
PLATFORMS
|
||||||
arm64-darwin-22
|
arm64-darwin-22
|
||||||
@@ -565,6 +590,7 @@ DEPENDENCIES
|
|||||||
premailer-rails
|
premailer-rails
|
||||||
pry-rails
|
pry-rails
|
||||||
puma
|
puma
|
||||||
|
rack
|
||||||
rails
|
rails
|
||||||
rails-i18n
|
rails-i18n
|
||||||
rails_autolink
|
rails_autolink
|
||||||
@@ -592,4 +618,4 @@ RUBY VERSION
|
|||||||
ruby 3.2.2p53
|
ruby 3.2.2p53
|
||||||
|
|
||||||
BUNDLED WITH
|
BUNDLED WITH
|
||||||
2.4.10
|
2.5.3
|
||||||
@@ -19,6 +19,12 @@ module Api
|
|||||||
|
|
||||||
render json: { error: e.message }, status: :forbidden
|
render json: { error: e.message }, status: :forbidden
|
||||||
end
|
end
|
||||||
|
|
||||||
|
rescue_from JSON::ParserError do |e|
|
||||||
|
Rollbar.error(e) if defined?(Rollbar)
|
||||||
|
|
||||||
|
render json: { error: "JSON parse error: #{e.message}" }, status: :unprocessable_entity
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
@@ -27,8 +33,8 @@ module Api
|
|||||||
result = relation.order(id: :desc)
|
result = relation.order(id: :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]) if params[:after].present?
|
result = result.where(relation.arel_table[:id].lt(params[:after])) if params[:after].present?
|
||||||
result = result.where('id > ?', params[:before]) if params[:before].present?
|
result = result.where(relation.arel_table[:id].gt(params[:before])) if params[:before].present?
|
||||||
|
|
||||||
result
|
result
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -63,6 +63,8 @@ module Api
|
|||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
|
return render json: { error: 'Template not found' }, status: :unprocessable_entity if @template.nil?
|
||||||
|
|
||||||
params[:send_email] = true unless params.key?(:send_email)
|
params[:send_email] = true unless params.key?(:send_email)
|
||||||
params[:send_sms] = false unless params.key?(:send_sms)
|
params[:send_sms] = false unless params.key?(:send_sms)
|
||||||
|
|
||||||
@@ -76,7 +78,7 @@ module Api
|
|||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
@submission.update!(deleted_at: Time.current)
|
@submission.update!(archived_at: Time.current)
|
||||||
|
|
||||||
render json: @submission.as_json(only: %i[id], methods: %i[archived_at])
|
render json: @submission.as_json(only: %i[id], methods: %i[archived_at])
|
||||||
end
|
end
|
||||||
@@ -116,8 +118,8 @@ module Api
|
|||||||
|
|
||||||
def serialize_params
|
def serialize_params
|
||||||
{
|
{
|
||||||
only: %i[id source submitters_order created_at updated_at],
|
only: %i[id source submitters_order created_at updated_at archived_at],
|
||||||
methods: %i[audit_log_url archived_at],
|
methods: %i[audit_log_url],
|
||||||
include: {
|
include: {
|
||||||
submitters: { only: %i[id slug uuid name email phone
|
submitters: { only: %i[id slug uuid name email phone
|
||||||
completed_at opened_at sent_at
|
completed_at opened_at sent_at
|
||||||
|
|||||||
@@ -80,18 +80,26 @@ module Api
|
|||||||
def assign_submitter_attrs(submitter, attrs)
|
def assign_submitter_attrs(submitter, attrs)
|
||||||
submitter.email = Submissions.normalize_email(attrs[:email]) if attrs.key?(:email)
|
submitter.email = Submissions.normalize_email(attrs[:email]) if attrs.key?(:email)
|
||||||
submitter.phone = attrs[:phone].to_s.gsub(/[^0-9+]/, '') if attrs.key?(:phone)
|
submitter.phone = attrs[:phone].to_s.gsub(/[^0-9+]/, '') if attrs.key?(:phone)
|
||||||
submitter.values = submitter.values.merge(attrs[:values].to_unsafe_h) if attrs[:values]
|
submitter.values = submitter.values.merge(attrs[:values].to_unsafe_h) if attrs[:values].present?
|
||||||
submitter.completed_at = attrs[:completed] ? Time.current : submitter.completed_at
|
submitter.completed_at = attrs[:completed] ? Time.current : submitter.completed_at
|
||||||
submitter.application_key = attrs[:application_key] if attrs.key?(:application_key)
|
submitter.application_key = attrs[:application_key] if attrs.key?(:application_key)
|
||||||
|
|
||||||
|
assign_submission_fields(submitter.submission)
|
||||||
assign_preferences(submitter, attrs)
|
assign_preferences(submitter, attrs)
|
||||||
|
|
||||||
submitter
|
submitter
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def assign_submission_fields(submission)
|
||||||
|
submission.template_fields ||= submission.template.fields
|
||||||
|
submission.template_schema ||= submission.template.schema
|
||||||
|
end
|
||||||
|
|
||||||
def assign_preferences(submitter, attrs)
|
def assign_preferences(submitter, attrs)
|
||||||
submitter_preferences = Submitters.normalize_preferences(submitter.account, current_user, attrs)
|
submitter_preferences = Submitters.normalize_preferences(submitter.account, current_user, attrs)
|
||||||
|
|
||||||
|
submitter.preferences['default_values'] = attrs[:values].to_unsafe_h if attrs[:values].present?
|
||||||
|
|
||||||
if submitter_preferences.key?('send_email')
|
if submitter_preferences.key?('send_email')
|
||||||
submitter.preferences['send_email'] = submitter_preferences['send_email']
|
submitter.preferences['send_email'] = submitter_preferences['send_email']
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ module Api
|
|||||||
LIMIT = 100
|
LIMIT = 100
|
||||||
|
|
||||||
def index
|
def index
|
||||||
template_folders = @template_folders.joins(:templates).where(templates: { deleted_at: nil }).distinct
|
template_folders = @template_folders.joins(:templates).where(templates: { archived_at: nil }).distinct
|
||||||
template_folders = TemplateFolders.search(template_folders, params[:q]).limit(LIMIT)
|
template_folders = TemplateFolders.search(template_folders, params[:q]).limit(LIMIT)
|
||||||
|
|
||||||
render json: template_folders.as_json(only: %i[name deleted_at])
|
render json: template_folders.as_json(only: %i[name archived_at])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -38,9 +38,9 @@ module Api
|
|||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
@template.update!(deleted_at: Time.current)
|
@template.update!(archived_at: Time.current)
|
||||||
|
|
||||||
render json: @template.as_json(only: %i[id deleted_at])
|
render json: @template.as_json(only: %i[id archived_at])
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|||||||
@@ -4,4 +4,14 @@ class ApiSettingsController < ApplicationController
|
|||||||
def index
|
def index
|
||||||
authorize!(:read, current_user.access_token)
|
authorize!(:read, current_user.access_token)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def create
|
||||||
|
authorize!(:manage, current_user.access_token)
|
||||||
|
|
||||||
|
current_user.access_token.token = SecureRandom.base58(AccessToken::TOKEN_LENGTH)
|
||||||
|
|
||||||
|
current_user.access_token.save!
|
||||||
|
|
||||||
|
redirect_back(fallback_location: settings_api_index_path, notice: 'API token as been updated.')
|
||||||
|
end
|
||||||
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 Enterprise Edition: https://www.docuseal.co/pricing'
|
'This feature is available in Pro Edition: https://www.docuseal.co/pricing'
|
||||||
|
|
||||||
ENTERPRISE_PATHS = [
|
ENTERPRISE_PATHS = [
|
||||||
'/templates/html',
|
'/templates/html',
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ class StartFormController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
@submitter = Submitter.where(submission: @template.submissions.where(deleted_at: nil))
|
@submitter = Submitter.where(submission: @template.submissions.where(archived_at: nil))
|
||||||
.order(id: :desc)
|
.order(id: :desc)
|
||||||
.then { |rel| params[:resubmit].present? ? rel.where(completed_at: nil) : rel }
|
.then { |rel| params[:resubmit].present? ? rel.where(completed_at: nil) : rel }
|
||||||
.find_or_initialize_by(**submitter_params.compact_blank)
|
.find_or_initialize_by(**submitter_params.compact_blank)
|
||||||
@@ -46,13 +46,25 @@ class StartFormController < ApplicationController
|
|||||||
private
|
private
|
||||||
|
|
||||||
def assign_submission_attributes(submitter, template)
|
def assign_submission_attributes(submitter, template)
|
||||||
|
resubmit_submitter =
|
||||||
|
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: template.submitters.first['uuid'],
|
||||||
ip: request.remote_ip,
|
ip: request.remote_ip,
|
||||||
ua: request.user_agent,
|
ua: request.user_agent,
|
||||||
preferences: { 'send_email' => true }
|
values: resubmit_submitter&.preferences&.fetch('default_values', nil) || {},
|
||||||
|
preferences: resubmit_submitter&.preferences.presence || { 'send_email' => true }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if submitter.values.present?
|
||||||
|
resubmit_submitter.attachments.each do |attachment|
|
||||||
|
submitter.attachments << attachment.dup if submitter.values.value?(attachment.uuid)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
submitter.submission ||= Submission.new(template:,
|
submitter.submission ||= Submission.new(template:,
|
||||||
template_submitters: template.submitters,
|
template_submitters: template.submitters,
|
||||||
source: :link)
|
source: :link)
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ class SubmissionsController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
@submission.update!(deleted_at: Time.current)
|
@submission.update!(archived_at: Time.current)
|
||||||
|
|
||||||
redirect_back(fallback_location: template_path(@submission.template), notice: 'Submission has been archived')
|
redirect_back(fallback_location: template_path(@submission.template), notice: 'Submission has been archived')
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class SubmitFormController < ApplicationController
|
|||||||
|
|
||||||
cookies[:submitter_sid] = @submitter.signed_id
|
cookies[:submitter_sid] = @submitter.signed_id
|
||||||
|
|
||||||
render @submitter.submission.template.deleted_at? ? :archived : :show
|
render @submitter.submission.template.archived_at? ? :archived : :show
|
||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
@@ -42,7 +42,7 @@ class SubmitFormController < ApplicationController
|
|||||||
return render json: { error: 'Form has been completed already.' }, status: :unprocessable_entity
|
return render json: { error: 'Form has been completed already.' }, status: :unprocessable_entity
|
||||||
end
|
end
|
||||||
|
|
||||||
if submitter.template.deleted_at? || submitter.submission.deleted_at?
|
if submitter.template.archived_at? || submitter.submission.archived_at?
|
||||||
Rollbar.info("Archived template: #{submitter.template.id}") if defined?(Rollbar)
|
Rollbar.info("Archived template: #{submitter.template.id}") if defined?(Rollbar)
|
||||||
|
|
||||||
return render json: { error: 'Form has been archived.' }, status: :unprocessable_entity
|
return render json: { error: 'Form has been archived.' }, status: :unprocessable_entity
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ 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(deleted_at: nil).preload(:author, :folder).order(id: :desc)
|
@templates = @templates.where.not(archived_at: nil).preload(:author, :folder).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, items: 12)
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ class TemplatesArchivedSubmissionsController < ApplicationController
|
|||||||
load_and_authorize_resource :submission, through: :template, parent: false
|
load_and_authorize_resource :submission, through: :template, parent: false
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@submissions = @submissions.where.not(deleted_at: nil)
|
@submissions = @submissions.where.not(archived_at: nil)
|
||||||
@submissions = Submissions.search(@submissions, params[:q])
|
@submissions = Submissions.search(@submissions, params[:q])
|
||||||
|
|
||||||
@pagy, @submissions = pagy(@submissions.preload(:submitters).order(id: :desc))
|
@pagy, @submissions = pagy(@submissions.preload(:submitters).order(id: :desc))
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ class TemplatesController < ApplicationController
|
|||||||
|
|
||||||
def show
|
def show
|
||||||
submissions = @template.submissions
|
submissions = @template.submissions
|
||||||
submissions = submissions.active if @template.deleted_at.blank?
|
submissions = submissions.active if @template.archived_at.blank?
|
||||||
submissions = Submissions.search(submissions, params[:q])
|
submissions = Submissions.search(submissions, params[:q])
|
||||||
|
|
||||||
@base_submissions = submissions
|
@base_submissions = submissions
|
||||||
@@ -70,7 +70,7 @@ class TemplatesController < ApplicationController
|
|||||||
|
|
||||||
'Template has been removed.'
|
'Template has been removed.'
|
||||||
else
|
else
|
||||||
@template.update!(deleted_at: Time.current)
|
@template.update!(archived_at: Time.current)
|
||||||
|
|
||||||
'Template has been archived.'
|
'Template has been archived.'
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ class TemplatesRestoreController < ApplicationController
|
|||||||
load_and_authorize_resource :template
|
load_and_authorize_resource :template
|
||||||
|
|
||||||
def create
|
def create
|
||||||
@template.update!(deleted_at: nil)
|
@template.update!(archived_at: nil)
|
||||||
|
|
||||||
redirect_to template_path(@template), notice: 'Template has been unarchived'
|
redirect_to template_path(@template), notice: 'Template has been unarchived'
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -28,6 +28,12 @@ class UserSignaturesController < ApplicationController
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
@user_config.destroy
|
||||||
|
|
||||||
|
redirect_to settings_profile_index_path, notice: 'Signature has been removed'
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def load_user_config
|
def load_user_config
|
||||||
|
|||||||
@@ -28,9 +28,9 @@ class UsersController < ApplicationController
|
|||||||
return redirect_to settings_users_path, notice: 'Unable to update user.' if Docuseal.demo?
|
return redirect_to settings_users_path, notice: 'Unable to update user.' if Docuseal.demo?
|
||||||
|
|
||||||
attrs = user_params.compact_blank
|
attrs = user_params.compact_blank
|
||||||
attrs.delete(:role) if User::ROLES.exclude?(attrs[:role])
|
attrs.delete(:role) if !role_valid?(attrs[:role]) || current_user == @user
|
||||||
|
|
||||||
if @user.update(attrs.except(current_user == @user ? :role : nil))
|
if @user.update(attrs)
|
||||||
redirect_to settings_users_path, notice: 'User has been updated'
|
redirect_to settings_users_path, notice: '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
|
||||||
@@ -42,17 +42,21 @@ class UsersController < ApplicationController
|
|||||||
return redirect_to settings_users_path, notice: 'Unable to remove user'
|
return redirect_to settings_users_path, notice: 'Unable to remove user'
|
||||||
end
|
end
|
||||||
|
|
||||||
@user.update!(deleted_at: Time.current)
|
@user.update!(archived_at: Time.current)
|
||||||
|
|
||||||
redirect_to settings_users_path, notice: 'User has been removed'
|
redirect_to settings_users_path, notice: 'User has been removed'
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def role_valid?(role)
|
||||||
|
User::ROLES.include?(role)
|
||||||
|
end
|
||||||
|
|
||||||
def build_user
|
def build_user
|
||||||
@user = current_account.users.find_by(email: user_params[:email])&.tap do |user|
|
@user = current_account.users.find_by(email: user_params[:email])&.tap do |user|
|
||||||
user.assign_attributes(user_params)
|
user.assign_attributes(user_params)
|
||||||
user.deleted_at = nil
|
user.archived_at = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
@user ||= current_account.users.new(user_params)
|
@user ||= current_account.users.new(user_params)
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ window.customElements.define('template-builder', class extends HTMLElement {
|
|||||||
backgroundColor: '#faf7f5',
|
backgroundColor: '#faf7f5',
|
||||||
withPhone: this.dataset.withPhone === 'true',
|
withPhone: this.dataset.withPhone === 'true',
|
||||||
withLogo: this.dataset.withLogo !== 'false',
|
withLogo: this.dataset.withLogo !== 'false',
|
||||||
withPayment: this.dataset.withPayment !== 'false',
|
withPayment: this.dataset.withPayment === 'true',
|
||||||
acceptFileTypes: this.dataset.acceptFileTypes,
|
acceptFileTypes: this.dataset.acceptFileTypes,
|
||||||
isDirectUpload: this.dataset.isDirectUpload === 'true'
|
isDirectUpload: this.dataset.isDirectUpload === 'true'
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -75,6 +75,23 @@ button[disabled] .enabled {
|
|||||||
@apply select base-input w-full font-normal;
|
@apply select base-input w-full font-normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tooltip-bottom-start:before {
|
||||||
|
transform: translateX(-30%);
|
||||||
|
top: var(--tooltip-offset);
|
||||||
|
left: 100%;
|
||||||
|
right: auto;
|
||||||
|
bottom: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip-bottom-start:after {
|
||||||
|
transform: translateX(-75%);
|
||||||
|
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;
|
||||||
|
|||||||
@@ -18,27 +18,64 @@ export default targetable(class extends HTMLElement {
|
|||||||
this.toggleState()
|
this.toggleState()
|
||||||
|
|
||||||
fetch(this.dataset.src).then((response) => response.json()).then((urls) => {
|
fetch(this.dataset.src).then((response) => response.json()).then((urls) => {
|
||||||
const fileRequests = urls.map((url) => {
|
const isSafariIos = /iPhone|iPad|iPod/i.test(navigator.userAgent)
|
||||||
return () => {
|
|
||||||
return fetch(url).then(async (resp) => {
|
|
||||||
const blobUrl = URL.createObjectURL(await resp.blob())
|
|
||||||
const link = document.createElement('a')
|
|
||||||
|
|
||||||
link.href = blobUrl
|
if (isSafariIos && urls.length > 1) {
|
||||||
link.setAttribute('download', decodeURI(url.split('/').pop()))
|
this.downloadSafariIos(urls)
|
||||||
|
} else {
|
||||||
|
this.downloadUrls(urls)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
link.click()
|
downloadUrls (urls) {
|
||||||
|
const fileRequests = urls.map((url) => {
|
||||||
|
return () => {
|
||||||
|
return fetch(url).then(async (resp) => {
|
||||||
|
const blobUrl = URL.createObjectURL(await resp.blob())
|
||||||
|
const link = document.createElement('a')
|
||||||
|
|
||||||
URL.revokeObjectURL(url)
|
link.href = blobUrl
|
||||||
})
|
link.setAttribute('download', decodeURI(url.split('/').pop()))
|
||||||
}
|
|
||||||
|
link.click()
|
||||||
|
|
||||||
|
URL.revokeObjectURL(blobUrl)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
fileRequests.reduce(
|
||||||
|
(prevPromise, request) => prevPromise.then(() => request()),
|
||||||
|
Promise.resolve()
|
||||||
|
)
|
||||||
|
|
||||||
|
this.toggleState()
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadSafariIos (urls) {
|
||||||
|
const fileRequests = urls.map((url) => {
|
||||||
|
return fetch(url).then(async (resp) => {
|
||||||
|
const blob = await resp.blob()
|
||||||
|
const blobUrl = URL.createObjectURL(blob.slice(0, blob.size, 'application/octet-stream'))
|
||||||
|
const link = document.createElement('a')
|
||||||
|
|
||||||
|
link.href = blobUrl
|
||||||
|
link.setAttribute('download', decodeURI(url.split('/').pop()))
|
||||||
|
|
||||||
|
return link
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
|
||||||
fileRequests.reduce(
|
Promise.all(fileRequests).then((links) => {
|
||||||
(prevPromise, request) => prevPromise.then(() => request()),
|
links.forEach((link, index) => {
|
||||||
Promise.resolve()
|
setTimeout(() => {
|
||||||
)
|
link.click()
|
||||||
|
|
||||||
|
URL.revokeObjectURL(link.href)
|
||||||
|
}, index * 50)
|
||||||
|
})
|
||||||
|
}).finally(() => {
|
||||||
this.toggleState()
|
this.toggleState()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ export default class extends HTMLElement {
|
|||||||
render = (item) => {
|
render = (item) => {
|
||||||
const div = document.createElement('div')
|
const div = document.createElement('div')
|
||||||
|
|
||||||
|
div.setAttribute('dir', 'auto')
|
||||||
|
|
||||||
div.textContent = item.name
|
div.textContent = item.name
|
||||||
|
|
||||||
return div
|
return div
|
||||||
|
|||||||
@@ -56,6 +56,8 @@ export default class extends HTMLElement {
|
|||||||
render = (item) => {
|
render = (item) => {
|
||||||
const div = document.createElement('div')
|
const div = document.createElement('div')
|
||||||
|
|
||||||
|
div.setAttribute('dir', 'auto')
|
||||||
|
|
||||||
div.textContent = item[this.dataset.field]
|
div.textContent = item[this.dataset.field]
|
||||||
|
|
||||||
return div
|
return div
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
v-if="uniqueAreas.length > 1"
|
||||||
|
class="mb-2"
|
||||||
|
>
|
||||||
|
<div class="flex space-x-1 text-sm ml-1 -mt-3">
|
||||||
|
<span>
|
||||||
|
{{ t('appears_on') }}:
|
||||||
|
</span>
|
||||||
|
<a
|
||||||
|
v-for="area in uniqueAreas"
|
||||||
|
:key="area.uuid"
|
||||||
|
href="#"
|
||||||
|
class="link pr-0.5"
|
||||||
|
@click.prevent="scrollIntoArea(area)"
|
||||||
|
>
|
||||||
|
{{ t('page') }} <template v-if="isMultipleDocs">{{ attachmentUuids.indexOf(area.attachment_uuid) + 1 }}-</template>{{ area.page + 1 }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'AppearsOn',
|
||||||
|
inject: ['t', 'scrollIntoArea'],
|
||||||
|
props: {
|
||||||
|
field: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
isMultipleDocs () {
|
||||||
|
return this.attachmentUuids.length > 1
|
||||||
|
},
|
||||||
|
attachmentUuids () {
|
||||||
|
return [...new Set(this.uniqueAreas.map((e) => e.attachment_uuid))]
|
||||||
|
},
|
||||||
|
uniqueAreas () {
|
||||||
|
const areas = {}
|
||||||
|
|
||||||
|
this.field.areas.forEach((area) => {
|
||||||
|
areas[area.attachment_uuid + area.page] ||= area
|
||||||
|
})
|
||||||
|
|
||||||
|
return Object.values(areas).slice(0, 6)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="flex absolute lg:text-base"
|
class="flex absolute lg:text-base"
|
||||||
|
dir="auto"
|
||||||
:style="computedStyle"
|
:style="computedStyle"
|
||||||
:class="{ 'text-[1.5vw] lg:text-base': !textOverflowChars, 'text-[1.0vw] lg:text-xs': textOverflowChars, 'cursor-default': !submittable, 'bg-red-100 border cursor-pointer ': submittable, 'border-red-100': !isActive && submittable, 'bg-opacity-80': !isActive && !isValueSet && submittable, 'border-red-500 border-dashed z-10': isActive && submittable, 'bg-opacity-40': (isActive || isValueSet) && submittable }"
|
:class="{ 'text-[1.5vw] lg:text-base': !textOverflowChars, 'text-[1.0vw] lg:text-xs': textOverflowChars, 'cursor-default': !submittable, 'bg-red-100 border cursor-pointer ': submittable, 'border-red-100': !isActive && submittable, 'bg-opacity-80': !isActive && !isValueSet && submittable, 'border-red-500 border-dashed border-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="!isActive && !isValueSet && field.type !== 'checkbox' && submittable && !area.option_uuid"
|
||||||
@@ -21,15 +22,20 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="isActive && withLabel && !area.option_uuid"
|
v-if="isActive && withLabel && (!area.option_uuid || !option.value)"
|
||||||
class="absolute -top-7 rounded bg-base-content text-base-100 px-2 text-sm whitespace-nowrap"
|
class="absolute -top-7 rounded bg-base-content text-base-100 px-2 text-sm whitespace-nowrap pointer-events-none"
|
||||||
>
|
>
|
||||||
{{ field.name || fieldNames[field.type] }}
|
<template v-if="area.option_uuid && !option.value">
|
||||||
<template v-if="field.type === 'checkbox' && !field.name">
|
{{ optionValue(option) }}
|
||||||
{{ fieldIndex + 1 }}
|
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="!field.required && field.type !== 'checkbox'">
|
<template v-else>
|
||||||
(optional)
|
{{ field.name || fieldNames[field.type] }}
|
||||||
|
<template v-if="field.type === 'checkbox' && !field.name">
|
||||||
|
{{ fieldIndex + 1 }}
|
||||||
|
</template>
|
||||||
|
<template v-else-if="!field.required && field.type !== 'checkbox'">
|
||||||
|
(optional)
|
||||||
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -102,11 +108,11 @@
|
|||||||
:value="false"
|
:value="false"
|
||||||
class="aspect-square base-radio"
|
class="aspect-square base-radio"
|
||||||
:class="{ '!w-auto !h-full': area.w > area.h, '!w-full !h-auto': area.w <= area.h }"
|
:class="{ '!w-auto !h-full': area.w > area.h, '!w-full !h-auto': area.w <= area.h }"
|
||||||
:checked="!!modelValue && modelValue === field.options.find((o) => o.uuid === area.option_uuid)?.value"
|
:checked="!!modelValue && modelValue === optionValue(option)"
|
||||||
@click="$emit('update:model-value', field.options.find((o) => o.uuid === area.option_uuid)?.value)"
|
@click="$emit('update:model-value', optionValue(option))"
|
||||||
>
|
>
|
||||||
<IconCheck
|
<IconCheck
|
||||||
v-else-if="!!modelValue && modelValue === field.options.find((o) => o.uuid === area.option_uuid)?.value"
|
v-else-if="!!modelValue && modelValue === optionValue(option)"
|
||||||
class="aspect-square"
|
class="aspect-square"
|
||||||
:class="{ '!w-auto !h-full': area.w > area.h, '!w-full !h-auto': area.w <= area.h }"
|
:class="{ '!w-auto !h-full': area.w > area.h, '!w-full !h-auto': area.w <= area.h }"
|
||||||
/>
|
/>
|
||||||
@@ -121,11 +127,11 @@
|
|||||||
:value="false"
|
:value="false"
|
||||||
class="aspect-square base-checkbox"
|
class="aspect-square base-checkbox"
|
||||||
:class="{ '!w-auto !h-full': area.w > area.h, '!w-full !h-auto': area.w <= area.h }"
|
:class="{ '!w-auto !h-full': area.w > area.h, '!w-full !h-auto': area.w <= area.h }"
|
||||||
:checked="!!modelValue && modelValue.includes(field.options.find((o) => o.uuid === area.option_uuid)?.value)"
|
:checked="!!modelValue && modelValue.includes(optionValue(option))"
|
||||||
@change="updateMultipleSelectValue(field.options.find((o) => o.uuid === area.option_uuid)?.value)"
|
@change="updateMultipleSelectValue(optionValue(option))"
|
||||||
>
|
>
|
||||||
<IconCheck
|
<IconCheck
|
||||||
v-else-if="!!modelValue && modelValue.includes(field.options.find((o) => o.uuid === area.option_uuid)?.value)"
|
v-else-if="!!modelValue && modelValue.includes(optionValue(option))"
|
||||||
class="aspect-square"
|
class="aspect-square"
|
||||||
:class="{ '!w-auto !h-full': area.w > area.h, '!w-full !h-auto': area.w <= area.h }"
|
:class="{ '!w-auto !h-full': area.w > area.h, '!w-full !h-auto': area.w <= area.h }"
|
||||||
/>
|
/>
|
||||||
@@ -146,7 +152,8 @@
|
|||||||
<div
|
<div
|
||||||
v-else
|
v-else
|
||||||
ref="textContainer"
|
ref="textContainer"
|
||||||
class="flex items-center px-0.5"
|
dir="auto"
|
||||||
|
class="flex items-center px-0.5 w-full"
|
||||||
>
|
>
|
||||||
<span v-if="Array.isArray(modelValue)">
|
<span v-if="Array.isArray(modelValue)">
|
||||||
{{ modelValue.join(', ') }}
|
{{ modelValue.join(', ') }}
|
||||||
@@ -171,6 +178,7 @@ export default {
|
|||||||
IconPaperclip,
|
IconPaperclip,
|
||||||
IconCheck
|
IconCheck
|
||||||
},
|
},
|
||||||
|
inject: ['t'],
|
||||||
props: {
|
props: {
|
||||||
field: {
|
field: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@@ -241,6 +249,9 @@ export default {
|
|||||||
payment: 'Payment'
|
payment: 'Payment'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
option () {
|
||||||
|
return this.field.options.find((o) => o.uuid === this.area.option_uuid)
|
||||||
|
},
|
||||||
fieldIcons () {
|
fieldIcons () {
|
||||||
return {
|
return {
|
||||||
text: IconTextSize,
|
text: IconTextSize,
|
||||||
@@ -335,6 +346,17 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
optionValue (option) {
|
||||||
|
if (option) {
|
||||||
|
if (option.value) {
|
||||||
|
return option.value
|
||||||
|
} else {
|
||||||
|
const index = this.field.options.indexOf(option)
|
||||||
|
|
||||||
|
return `${this.t('option')} ${index + 1}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
formatDate (date, format) {
|
formatDate (date, format) {
|
||||||
const monthFormats = {
|
const monthFormats = {
|
||||||
M: 'numeric',
|
M: 'numeric',
|
||||||
|
|||||||
@@ -83,22 +83,25 @@ export default {
|
|||||||
return (this.$root.$el?.parentNode?.getRootNode() || document).getElementById(`page-${area.attachment_uuid}-${area.page}`)
|
return (this.$root.$el?.parentNode?.getRootNode() || document).getElementById(`page-${area.attachment_uuid}-${area.page}`)
|
||||||
},
|
},
|
||||||
scrollIntoField (field) {
|
scrollIntoField (field) {
|
||||||
this.areaRefs.find((area) => {
|
if (field?.areas) {
|
||||||
if (area.field === field) {
|
this.scrollIntoArea(field.areas[0])
|
||||||
const root = this.$root.$el.parentNode.getRootNode()
|
}
|
||||||
const container = root.body || root.querySelector('div')
|
},
|
||||||
|
scrollIntoArea (area) {
|
||||||
|
const areaRef = this.areaRefs.find((a) => a.area === area)
|
||||||
|
|
||||||
if (container.style.overflow === 'hidden') {
|
if (areaRef) {
|
||||||
this.scrollInContainer(area.$el)
|
const root = this.$root.$el.parentNode.getRootNode()
|
||||||
} else {
|
const container = root.body || root.querySelector('div')
|
||||||
area.$refs.scrollToElem.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
if (container.style.overflow === 'hidden') {
|
||||||
|
this.scrollInContainer(areaRef.$el)
|
||||||
} else {
|
} else {
|
||||||
return null
|
areaRef.$refs.scrollToElem.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
scrollInContainer (target) {
|
scrollInContainer (target) {
|
||||||
const root = this.$root.$el.parentNode.getRootNode()
|
const root = this.$root.$el.parentNode.getRootNode()
|
||||||
|
|||||||
@@ -179,27 +179,62 @@ export default {
|
|||||||
this.isDownloading = true
|
this.isDownloading = true
|
||||||
|
|
||||||
fetch(this.baseUrl + `/submitters/${this.submitterSlug}/download`).then((response) => response.json()).then((urls) => {
|
fetch(this.baseUrl + `/submitters/${this.submitterSlug}/download`).then((response) => response.json()).then((urls) => {
|
||||||
const fileRequests = urls.map((url) => {
|
const isSafariIos = /iPhone|iPad|iPod/i.test(navigator.userAgent)
|
||||||
return () => {
|
|
||||||
return fetch(url).then(async (resp) => {
|
|
||||||
const blobUrl = URL.createObjectURL(await resp.blob())
|
|
||||||
const link = document.createElement('a')
|
|
||||||
|
|
||||||
link.href = blobUrl
|
if (isSafariIos && urls.length > 1) {
|
||||||
link.setAttribute('download', decodeURI(url.split('/').pop()))
|
this.downloadSafariIos(urls)
|
||||||
|
} else {
|
||||||
|
this.downloadUrls(urls)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
downloadUrls (urls) {
|
||||||
|
const fileRequests = urls.map((url) => {
|
||||||
|
return () => {
|
||||||
|
return fetch(url).then(async (resp) => {
|
||||||
|
const blobUrl = URL.createObjectURL(await resp.blob())
|
||||||
|
const link = document.createElement('a')
|
||||||
|
|
||||||
link.click()
|
link.href = blobUrl
|
||||||
|
link.setAttribute('download', decodeURI(url.split('/').pop()))
|
||||||
|
|
||||||
URL.revokeObjectURL(url)
|
link.click()
|
||||||
})
|
|
||||||
}
|
URL.revokeObjectURL(blobUrl)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
fileRequests.reduce(
|
||||||
|
(prevPromise, request) => prevPromise.then(() => request()),
|
||||||
|
Promise.resolve()
|
||||||
|
)
|
||||||
|
|
||||||
|
this.isDownloading = false
|
||||||
|
},
|
||||||
|
downloadSafariIos (urls) {
|
||||||
|
const fileRequests = urls.map((url) => {
|
||||||
|
return fetch(url).then(async (resp) => {
|
||||||
|
const blob = await resp.blob()
|
||||||
|
const blobUrl = URL.createObjectURL(blob.slice(0, blob.size, 'application/octet-stream'))
|
||||||
|
const link = document.createElement('a')
|
||||||
|
|
||||||
|
link.href = blobUrl
|
||||||
|
link.setAttribute('download', decodeURI(url.split('/').pop()))
|
||||||
|
|
||||||
|
return link
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
|
||||||
fileRequests.reduce(
|
Promise.all(fileRequests).then((links) => {
|
||||||
(prevPromise, request) => prevPromise.then(() => request()),
|
links.forEach((link, index) => {
|
||||||
Promise.resolve()
|
setTimeout(() => {
|
||||||
)
|
link.click()
|
||||||
|
|
||||||
|
URL.revokeObjectURL(link.href)
|
||||||
|
}, index * 50)
|
||||||
|
})
|
||||||
|
}).finally(() => {
|
||||||
this.isDownloading = false
|
this.isDownloading = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div dir="auto">
|
||||||
<div class="flex justify-between items-center w-full mb-2">
|
<div class="flex justify-between items-center w-full mb-2">
|
||||||
<label
|
<label
|
||||||
:for="field.uuid"
|
:for="field.uuid"
|
||||||
@@ -15,6 +15,7 @@
|
|||||||
{{ t('set_today') }}
|
{{ t('set_today') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<AppearsOn :field="field" />
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<input
|
<input
|
||||||
ref="input"
|
ref="input"
|
||||||
@@ -31,11 +32,13 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { IconCalendarCheck } from '@tabler/icons-vue'
|
import { IconCalendarCheck } from '@tabler/icons-vue'
|
||||||
|
import AppearsOn from './appears_on'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'DateStep',
|
name: 'DateStep',
|
||||||
components: {
|
components: {
|
||||||
IconCalendarCheck
|
IconCalendarCheck,
|
||||||
|
AppearsOn
|
||||||
},
|
},
|
||||||
inject: ['t'],
|
inject: ['t'],
|
||||||
props: {
|
props: {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
v-if="!isFormVisible"
|
v-if="!isFormVisible"
|
||||||
|
id="expand_form_button"
|
||||||
class="btn btn-neutral flex text-white absolute rounded-none border-x-0 md:border md:rounded-full bottom-0 w-full md:mb-4 text-base"
|
class="btn btn-neutral flex text-white absolute rounded-none border-x-0 md:border md:rounded-full bottom-0 w-full md:mb-4 text-base"
|
||||||
@click.prevent="isFormVisible = true"
|
@click.prevent="isFormVisible = true"
|
||||||
>
|
>
|
||||||
@@ -23,11 +24,13 @@
|
|||||||
<div
|
<div
|
||||||
v-show="isFormVisible"
|
v-show="isFormVisible"
|
||||||
id="form_container"
|
id="form_container"
|
||||||
class="shadow-md bg-base-100 absolute bottom-0 md:bottom-4 w-full border-base-200 border p-4 rounded"
|
class="shadow-md bg-base-100 absolute bottom-0 w-full border-base-200 border p-4 rounded"
|
||||||
|
:class="{ 'md:bottom-4': isBreakpointMd }"
|
||||||
:style="{ backgroundColor: backgroundColor }"
|
:style="{ backgroundColor: backgroundColor }"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
v-if="!isCompleted"
|
v-if="!isCompleted"
|
||||||
|
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="Minimize"
|
title="Minimize"
|
||||||
@click.prevent="isFormVisible = false"
|
@click.prevent="isFormVisible = false"
|
||||||
@@ -43,7 +46,7 @@
|
|||||||
ref="form"
|
ref="form"
|
||||||
:action="submitPath"
|
:action="submitPath"
|
||||||
method="post"
|
method="post"
|
||||||
class="md:mx-16"
|
:class="{ 'md:mx-16': isBreakpointMd }"
|
||||||
@submit.prevent="submitStep"
|
@submit.prevent="submitStep"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
@@ -62,7 +65,7 @@
|
|||||||
:key="currentField.uuid"
|
:key="currentField.uuid"
|
||||||
v-model="values[currentField.uuid]"
|
v-model="values[currentField.uuid]"
|
||||||
:field="currentField"
|
:field="currentField"
|
||||||
@focus="$refs.areas.scrollIntoField(currentField)"
|
@focus="scrollIntoField(currentField)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<DateStep
|
<DateStep
|
||||||
@@ -70,12 +73,13 @@
|
|||||||
:key="currentField.uuid"
|
:key="currentField.uuid"
|
||||||
v-model="values[currentField.uuid]"
|
v-model="values[currentField.uuid]"
|
||||||
:field="currentField"
|
:field="currentField"
|
||||||
@focus="$refs.areas.scrollIntoField(currentField)"
|
@focus="scrollIntoField(currentField)"
|
||||||
/>
|
/>
|
||||||
<div v-else-if="currentField.type === 'select'">
|
<div v-else-if="currentField.type === 'select'">
|
||||||
<label
|
<label
|
||||||
v-if="currentField.name"
|
v-if="currentField.name"
|
||||||
:for="currentField.uuid"
|
:for="currentField.uuid"
|
||||||
|
dir="auto"
|
||||||
class="label text-2xl mb-2"
|
class="label text-2xl mb-2"
|
||||||
>{{ currentField.name }}
|
>{{ currentField.name }}
|
||||||
<template v-if="!currentField.required">({{ t('optional') }})</template>
|
<template v-if="!currentField.required">({{ t('optional') }})</template>
|
||||||
@@ -84,13 +88,15 @@
|
|||||||
v-else
|
v-else
|
||||||
class="py-1"
|
class="py-1"
|
||||||
/>
|
/>
|
||||||
|
<AppearsOn :field="currentField" />
|
||||||
<select
|
<select
|
||||||
:id="currentField.uuid"
|
:id="currentField.uuid"
|
||||||
|
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"
|
||||||
:name="`values[${currentField.uuid}]`"
|
:name="`values[${currentField.uuid}]`"
|
||||||
@change="values[currentField.uuid] = $event.target.value"
|
@change="values[currentField.uuid] = $event.target.value"
|
||||||
@focus="$refs.areas.scrollIntoField(currentField)"
|
@focus="scrollIntoField(currentField)"
|
||||||
>
|
>
|
||||||
<option
|
<option
|
||||||
value=""
|
value=""
|
||||||
@@ -112,6 +118,7 @@
|
|||||||
<label
|
<label
|
||||||
v-if="currentField.name"
|
v-if="currentField.name"
|
||||||
:for="currentField.uuid"
|
:for="currentField.uuid"
|
||||||
|
dir="auto"
|
||||||
class="label text-2xl mb-2"
|
class="label text-2xl mb-2"
|
||||||
>{{ currentField.name }}
|
>{{ currentField.name }}
|
||||||
<template v-if="!currentField.required">({{ t('optional') }})</template>
|
<template v-if="!currentField.required">({{ t('optional') }})</template>
|
||||||
@@ -119,7 +126,7 @@
|
|||||||
<div class="flex w-full">
|
<div class="flex w-full">
|
||||||
<div class="space-y-3.5 mx-auto">
|
<div class="space-y-3.5 mx-auto">
|
||||||
<div
|
<div
|
||||||
v-for="option in currentField.options"
|
v-for="(option, index) in currentField.options"
|
||||||
:key="option.uuid"
|
:key="option.uuid"
|
||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
@@ -132,11 +139,11 @@
|
|||||||
type="radio"
|
type="radio"
|
||||||
class="base-radio !h-7 !w-7"
|
class="base-radio !h-7 !w-7"
|
||||||
:name="`values[${currentField.uuid}]`"
|
:name="`values[${currentField.uuid}]`"
|
||||||
:value="option.value"
|
:value="option.value || `${t('option')} ${index + 1}`"
|
||||||
:required="currentField.required"
|
:required="currentField.required"
|
||||||
>
|
>
|
||||||
<span class="text-xl">
|
<span class="text-xl">
|
||||||
{{ option.value }}
|
{{ option.value || `${t('option')} ${index + 1}` }}
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@@ -195,7 +202,7 @@
|
|||||||
:onchange="`this.setCustomValidity(validity.valueMissing ? '${t('please_check_the_box_to_continue')}' : '');`"
|
:onchange="`this.setCustomValidity(validity.valueMissing ? '${t('please_check_the_box_to_continue')}' : '');`"
|
||||||
:required="field.required"
|
:required="field.required"
|
||||||
:checked="!!values[field.uuid]"
|
:checked="!!values[field.uuid]"
|
||||||
@click="[$refs.areas.scrollIntoField(field), values[field.uuid] = !values[field.uuid]]"
|
@click="[scrollIntoField(field), values[field.uuid] = !values[field.uuid]]"
|
||||||
>
|
>
|
||||||
<span class="text-xl">
|
<span class="text-xl">
|
||||||
{{ field.name || field.type + ' ' + (index + 1) }}
|
{{ field.name || field.type + ' ' + (index + 1) }}
|
||||||
@@ -213,7 +220,7 @@
|
|||||||
:is-direct-upload="isDirectUpload"
|
:is-direct-upload="isDirectUpload"
|
||||||
:attachments-index="attachmentsIndex"
|
:attachments-index="attachmentsIndex"
|
||||||
:submitter-slug="submitterSlug"
|
:submitter-slug="submitterSlug"
|
||||||
@attached="[attachments.push($event), $refs.areas.scrollIntoField(currentField)]"
|
@attached="[attachments.push($event), scrollIntoField(currentField)]"
|
||||||
/>
|
/>
|
||||||
<SignatureStep
|
<SignatureStep
|
||||||
v-else-if="currentField.type === 'signature'"
|
v-else-if="currentField.type === 'signature'"
|
||||||
@@ -227,7 +234,7 @@
|
|||||||
:attachments-index="attachmentsIndex"
|
:attachments-index="attachmentsIndex"
|
||||||
:submitter-slug="submitterSlug"
|
:submitter-slug="submitterSlug"
|
||||||
@attached="attachments.push($event)"
|
@attached="attachments.push($event)"
|
||||||
@start="$refs.areas.scrollIntoField(currentField)"
|
@start="scrollIntoField(currentField)"
|
||||||
@minimize="isFormVisible = false"
|
@minimize="isFormVisible = false"
|
||||||
/>
|
/>
|
||||||
<InitialsStep
|
<InitialsStep
|
||||||
@@ -241,8 +248,8 @@
|
|||||||
:attachments-index="attachmentsIndex"
|
:attachments-index="attachmentsIndex"
|
||||||
:submitter-slug="submitterSlug"
|
:submitter-slug="submitterSlug"
|
||||||
@attached="attachments.push($event)"
|
@attached="attachments.push($event)"
|
||||||
@start="$refs.areas.scrollIntoField(currentField)"
|
@start="scrollIntoField(currentField)"
|
||||||
@focus="$refs.areas.scrollIntoField(currentField)"
|
@focus="scrollIntoField(currentField)"
|
||||||
@minimize="isFormVisible = false"
|
@minimize="isFormVisible = false"
|
||||||
/>
|
/>
|
||||||
<AttachmentStep
|
<AttachmentStep
|
||||||
@@ -253,7 +260,7 @@
|
|||||||
:field="currentField"
|
:field="currentField"
|
||||||
:attachments-index="attachmentsIndex"
|
:attachments-index="attachmentsIndex"
|
||||||
:submitter-slug="submitterSlug"
|
:submitter-slug="submitterSlug"
|
||||||
@attached="[attachments.push($event), $refs.areas.scrollIntoField(currentField)]"
|
@attached="[attachments.push($event), scrollIntoField(currentField)]"
|
||||||
/>
|
/>
|
||||||
<PhoneStep
|
<PhoneStep
|
||||||
v-else-if="currentField.type === 'phone'"
|
v-else-if="currentField.type === 'phone'"
|
||||||
@@ -263,7 +270,7 @@
|
|||||||
:field="currentField"
|
:field="currentField"
|
||||||
:default-value="submitter.phone"
|
:default-value="submitter.phone"
|
||||||
:submitter-slug="submitterSlug"
|
:submitter-slug="submitterSlug"
|
||||||
@focus="$refs.areas.scrollIntoField(currentField)"
|
@focus="scrollIntoField(currentField)"
|
||||||
@submit="submitStep"
|
@submit="submitStep"
|
||||||
/>
|
/>
|
||||||
<PaymentStep
|
<PaymentStep
|
||||||
@@ -274,7 +281,7 @@
|
|||||||
:field="currentField"
|
:field="currentField"
|
||||||
:submitter-slug="submitterSlug"
|
:submitter-slug="submitterSlug"
|
||||||
@attached="attachments.push($event)"
|
@attached="attachments.push($event)"
|
||||||
@focus="$refs.areas.scrollIntoField(currentField)"
|
@focus="scrollIntoField(currentField)"
|
||||||
@submit="submitStep"
|
@submit="submitStep"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -283,6 +290,7 @@
|
|||||||
class="mt-6 md:mt-8"
|
class="mt-6 md:mt-8"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
|
id="submit_form_button"
|
||||||
ref="submitButton"
|
ref="submitButton"
|
||||||
type="submit"
|
type="submit"
|
||||||
class="base-button w-full flex justify-center"
|
class="base-button w-full flex justify-center"
|
||||||
@@ -323,18 +331,25 @@
|
|||||||
:can-send-email="canSendEmail && !!submitter.email"
|
:can-send-email="canSendEmail && !!submitter.email"
|
||||||
:submitter-slug="submitterSlug"
|
:submitter-slug="submitterSlug"
|
||||||
/>
|
/>
|
||||||
<div class="flex justify-center">
|
<div
|
||||||
<div class="flex items-center mt-5 mb-1">
|
v-if="stepFields.length < 80"
|
||||||
|
class="flex justify-center"
|
||||||
|
>
|
||||||
|
<div class="flex items-center mt-4 mb-1 flex-wrap">
|
||||||
<a
|
<a
|
||||||
v-for="(step, index) in stepFields"
|
v-for="(step, index) in stepFields"
|
||||||
:key="step[0].uuid"
|
:key="step[0].uuid"
|
||||||
href="#"
|
href="#"
|
||||||
class="inline border border-base-300 h-3 w-3 rounded-full mx-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(step, true)]"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
class="mt-5"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -352,6 +367,7 @@ import TextStep from './text_step'
|
|||||||
import DateStep from './date_step'
|
import DateStep from './date_step'
|
||||||
import FormCompleted from './completed'
|
import FormCompleted from './completed'
|
||||||
import { IconInnerShadowTop, IconArrowsDiagonal, IconArrowsDiagonalMinimize2 } from '@tabler/icons-vue'
|
import { IconInnerShadowTop, IconArrowsDiagonal, IconArrowsDiagonalMinimize2 } from '@tabler/icons-vue'
|
||||||
|
import AppearsOn from './appears_on'
|
||||||
import { t } from './i18n'
|
import { t } from './i18n'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -360,6 +376,7 @@ export default {
|
|||||||
FieldAreas,
|
FieldAreas,
|
||||||
ImageStep,
|
ImageStep,
|
||||||
SignatureStep,
|
SignatureStep,
|
||||||
|
AppearsOn,
|
||||||
AttachmentStep,
|
AttachmentStep,
|
||||||
InitialsStep,
|
InitialsStep,
|
||||||
MultiSelectStep,
|
MultiSelectStep,
|
||||||
@@ -375,6 +392,7 @@ export default {
|
|||||||
provide () {
|
provide () {
|
||||||
return {
|
return {
|
||||||
baseUrl: this.baseUrl,
|
baseUrl: this.baseUrl,
|
||||||
|
scrollIntoArea: this.scrollIntoArea,
|
||||||
t: this.t
|
t: this.t
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -410,6 +428,11 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: true
|
default: true
|
||||||
},
|
},
|
||||||
|
isBreakpointMd: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
baseUrl: {
|
baseUrl: {
|
||||||
type: String,
|
type: String,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -511,7 +534,7 @@ export default {
|
|||||||
isButtonDisabled () {
|
isButtonDisabled () {
|
||||||
if (this.recalculateButtonDisabledKey) {
|
if (this.recalculateButtonDisabledKey) {
|
||||||
return this.isSubmitting ||
|
return this.isSubmitting ||
|
||||||
(this.currentField.required && ['image', 'file'].includes(this.currentField.type) && !this.values[this.currentField.uuid]?.length) ||
|
(this.currentField.required && ['image', 'file', 'multiple'].includes(this.currentField.type) && !this.values[this.currentField.uuid]?.length) ||
|
||||||
(this.currentField.required && this.currentField.type === 'signature' && !this.values[this.currentField.uuid]?.length && this.$refs.currentStep && !this.$refs.currentStep.isSignatureStarted) ||
|
(this.currentField.required && this.currentField.type === 'signature' && !this.values[this.currentField.uuid]?.length && this.$refs.currentStep && !this.$refs.currentStep.isSignatureStarted) ||
|
||||||
(this.currentField.required && this.currentField.type === 'initials' && !this.values[this.currentField.uuid]?.length && this.$refs.currentStep && !this.$refs.currentStep.isInitialsStarted)
|
(this.currentField.required && this.currentField.type === 'initials' && !this.values[this.currentField.uuid]?.length && this.$refs.currentStep && !this.$refs.currentStep.isInitialsStarted)
|
||||||
} else {
|
} else {
|
||||||
@@ -671,7 +694,7 @@ export default {
|
|||||||
this.recalculateButtonDisabledKey = Math.random()
|
this.recalculateButtonDisabledKey = Math.random()
|
||||||
|
|
||||||
if (scrollToArea) {
|
if (scrollToArea) {
|
||||||
this.$refs.areas.scrollIntoField(step[0])
|
this.scrollIntoField(step[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$refs.form.querySelector('input[type="date"], input[type="text"], select')?.focus()
|
this.$refs.form.querySelector('input[type="date"], input[type="text"], select')?.focus()
|
||||||
@@ -699,6 +722,12 @@ export default {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
scrollIntoField (field) {
|
||||||
|
return this.$refs.areas.scrollIntoField(field)
|
||||||
|
},
|
||||||
|
scrollIntoArea (area) {
|
||||||
|
return this.$refs.areas.scrollIntoArea(area)
|
||||||
|
},
|
||||||
async submitStep () {
|
async submitStep () {
|
||||||
this.isSubmitting = true
|
this.isSubmitting = true
|
||||||
|
|
||||||
@@ -708,7 +737,7 @@ export default {
|
|||||||
|
|
||||||
stepPromise().then(async () => {
|
stepPromise().then(async () => {
|
||||||
const emptyRequiredField = this.stepFields.find((fields, index) => {
|
const emptyRequiredField = this.stepFields.find((fields, index) => {
|
||||||
return index < this.currentStep && fields[0].required && (fields[0].type === 'phone' || !this.allowToSkip) && !this.submittedValues[fields[0].uuid]
|
return index < this.currentStep && fields[0].required && (fields[0].type === 'phone' || !this.allowToSkip) && !this.submittedValues[fields[0].uuid]?.length
|
||||||
})
|
})
|
||||||
|
|
||||||
const formData = new FormData(this.$refs.form)
|
const formData = new FormData(this.$refs.form)
|
||||||
|
|||||||
@@ -2,6 +2,9 @@ const en = {
|
|||||||
submit_form: 'Submit Form',
|
submit_form: 'Submit Form',
|
||||||
type_here: 'Type here',
|
type_here: 'Type here',
|
||||||
optional: 'optional',
|
optional: 'optional',
|
||||||
|
option: 'Option',
|
||||||
|
appears_on: 'Appears on',
|
||||||
|
page: 'Page',
|
||||||
select_your_option: 'Select your option',
|
select_your_option: 'Select your option',
|
||||||
complete_hightlighted_checkboxes_and_click: 'Complete hightlighted checkboxes and click',
|
complete_hightlighted_checkboxes_and_click: 'Complete hightlighted checkboxes and click',
|
||||||
submit: 'submit',
|
submit: 'submit',
|
||||||
@@ -47,6 +50,8 @@ const es = {
|
|||||||
submit_form: 'Enviar Formulario',
|
submit_form: 'Enviar Formulario',
|
||||||
type_here: 'Escribe aquí',
|
type_here: 'Escribe aquí',
|
||||||
optional: 'opcional',
|
optional: 'opcional',
|
||||||
|
appears_on: 'Aparece en',
|
||||||
|
page: 'Página',
|
||||||
select_your_option: 'Selecciona tu opción',
|
select_your_option: 'Selecciona tu opción',
|
||||||
complete_hightlighted_checkboxes_and_click: 'Completa las casillas resaltadas y haz clic',
|
complete_hightlighted_checkboxes_and_click: 'Completa las casillas resaltadas y haz clic',
|
||||||
submit: 'enviar',
|
submit: 'enviar',
|
||||||
@@ -92,6 +97,8 @@ const it = {
|
|||||||
submit_form: 'Invia Modulo',
|
submit_form: 'Invia Modulo',
|
||||||
type_here: 'Digita qui',
|
type_here: 'Digita qui',
|
||||||
optional: 'opzionale',
|
optional: 'opzionale',
|
||||||
|
appears_on: 'Compare su',
|
||||||
|
page: 'Pagina',
|
||||||
select_your_option: 'Seleziona la tua opzione',
|
select_your_option: 'Seleziona la tua opzione',
|
||||||
complete_hightlighted_checkboxes_and_click: 'Completa le caselle evidenziate e fai clic',
|
complete_hightlighted_checkboxes_and_click: 'Completa le caselle evidenziate e fai clic',
|
||||||
submit: 'invia',
|
submit: 'invia',
|
||||||
@@ -137,6 +144,8 @@ const de = {
|
|||||||
submit_form: 'Formular absenden',
|
submit_form: 'Formular absenden',
|
||||||
type_here: 'Hier eingeben',
|
type_here: 'Hier eingeben',
|
||||||
optional: 'optional',
|
optional: 'optional',
|
||||||
|
appears_on: 'Erscheint auf',
|
||||||
|
page: 'Seite',
|
||||||
select_your_option: 'Wähle deine Option',
|
select_your_option: 'Wähle deine Option',
|
||||||
complete_hightlighted_checkboxes_and_click: 'Markierte Kontrollkästchen ausfüllen und klicken',
|
complete_hightlighted_checkboxes_and_click: 'Markierte Kontrollkästchen ausfüllen und klicken',
|
||||||
submit: 'absenden',
|
submit: 'absenden',
|
||||||
@@ -182,6 +191,8 @@ const fr = {
|
|||||||
submit_form: 'Envoyer le Formulaire',
|
submit_form: 'Envoyer le Formulaire',
|
||||||
type_here: 'Tapez ici',
|
type_here: 'Tapez ici',
|
||||||
optional: 'facultatif',
|
optional: 'facultatif',
|
||||||
|
appears_on: 'Apparaît sur',
|
||||||
|
page: 'Page',
|
||||||
select_your_option: 'Sélectionnez votre option',
|
select_your_option: 'Sélectionnez votre option',
|
||||||
complete_hightlighted_checkboxes_and_click: 'Complétez les cases à cocher en surbrillance et cliquez',
|
complete_hightlighted_checkboxes_and_click: 'Complétez les cases à cocher en surbrillance et cliquez',
|
||||||
submit: 'envoyer',
|
submit: 'envoyer',
|
||||||
@@ -227,6 +238,8 @@ const pl = {
|
|||||||
submit_form: 'Wyślij Formularz',
|
submit_form: 'Wyślij Formularz',
|
||||||
type_here: 'Wpisz tutaj',
|
type_here: 'Wpisz tutaj',
|
||||||
optional: 'opcjonalny',
|
optional: 'opcjonalny',
|
||||||
|
appears_on: 'Pojawia się na',
|
||||||
|
page: 'Strona',
|
||||||
select_your_option: 'Wybierz swoją opcję',
|
select_your_option: 'Wybierz swoją opcję',
|
||||||
complete_hightlighted_checkboxes_and_click: 'Wypełnij zaznaczone pola wyboru i kliknij',
|
complete_hightlighted_checkboxes_and_click: 'Wypełnij zaznaczone pola wyboru i kliknij',
|
||||||
submit: 'wyślij',
|
submit: 'wyślij',
|
||||||
@@ -272,6 +285,8 @@ const uk = {
|
|||||||
submit_form: 'Надіслати Форму',
|
submit_form: 'Надіслати Форму',
|
||||||
type_here: 'Введіть тут',
|
type_here: 'Введіть тут',
|
||||||
optional: 'необов’язково',
|
optional: 'необов’язково',
|
||||||
|
appears_on: "З'являється на",
|
||||||
|
page: 'Сторінка',
|
||||||
select_your_option: 'Виберіть варіант',
|
select_your_option: 'Виберіть варіант',
|
||||||
complete_hightlighted_checkboxes_and_click: 'Заповніть позначені прапорці та натисніть',
|
complete_hightlighted_checkboxes_and_click: 'Заповніть позначені прапорці та натисніть',
|
||||||
submit: 'надіслати',
|
submit: 'надіслати',
|
||||||
@@ -317,6 +332,8 @@ const cs = {
|
|||||||
submit_form: 'Odeslat formulář',
|
submit_form: 'Odeslat formulář',
|
||||||
type_here: 'Zadejte zde',
|
type_here: 'Zadejte zde',
|
||||||
optional: 'volitelné',
|
optional: 'volitelné',
|
||||||
|
appears_on: 'Zobrazuje se na',
|
||||||
|
page: 'Stránka',
|
||||||
select_your_option: 'Vyberte svou volbu',
|
select_your_option: 'Vyberte svou volbu',
|
||||||
complete_hightlighted_checkboxes_and_click: 'Označte zvýrazněné zaškrtávací políčka a klikněte na',
|
complete_hightlighted_checkboxes_and_click: 'Označte zvýrazněné zaškrtávací políčka a klikněte na',
|
||||||
submit: 'odeslat',
|
submit: 'odeslat',
|
||||||
@@ -362,6 +379,8 @@ const pt = {
|
|||||||
submit_form: 'Enviar Formulário',
|
submit_form: 'Enviar Formulário',
|
||||||
type_here: 'Digite aqui',
|
type_here: 'Digite aqui',
|
||||||
optional: 'opcional',
|
optional: 'opcional',
|
||||||
|
appears_on: 'Aparece em',
|
||||||
|
page: 'Página',
|
||||||
select_your_option: 'Selecione sua opção',
|
select_your_option: 'Selecione sua opção',
|
||||||
complete_hightlighted_checkboxes_and_click: 'Complete as caixas de seleção destacadas e clique',
|
complete_hightlighted_checkboxes_and_click: 'Complete as caixas de seleção destacadas e clique',
|
||||||
submit: 'enviar',
|
submit: 'enviar',
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div dir="auto">
|
||||||
<div class="flex justify-between items-center w-full mb-2">
|
<div class="flex justify-between items-center w-full mb-2">
|
||||||
<label
|
<label
|
||||||
class="label text-2xl"
|
class="label text-2xl"
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
v-else
|
v-else
|
||||||
class="tooltip"
|
class="tooltip ml-2"
|
||||||
:data-tip="t('draw_initials')"
|
:data-tip="t('draw_initials')"
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
@@ -70,6 +70,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<AppearsOn :field="field" />
|
||||||
<input
|
<input
|
||||||
:value="modelValue || computedPreviousValue"
|
:value="modelValue || computedPreviousValue"
|
||||||
type="hidden"
|
type="hidden"
|
||||||
@@ -103,10 +104,12 @@
|
|||||||
import { cropCanvasAndExportToPNG } from './crop_canvas'
|
import { cropCanvasAndExportToPNG } from './crop_canvas'
|
||||||
import { IconReload, IconTextSize, IconSignature, IconArrowsDiagonalMinimize2 } from '@tabler/icons-vue'
|
import { IconReload, IconTextSize, IconSignature, IconArrowsDiagonalMinimize2 } from '@tabler/icons-vue'
|
||||||
import SignaturePad from 'signature_pad'
|
import SignaturePad from 'signature_pad'
|
||||||
|
import AppearsOn from './appears_on'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'InitialsStep',
|
name: 'InitialsStep',
|
||||||
components: {
|
components: {
|
||||||
|
AppearsOn,
|
||||||
IconReload,
|
IconReload,
|
||||||
IconTextSize,
|
IconTextSize,
|
||||||
IconSignature,
|
IconSignature,
|
||||||
|
|||||||
@@ -2,12 +2,20 @@
|
|||||||
<label
|
<label
|
||||||
v-if="field.name"
|
v-if="field.name"
|
||||||
:for="field.uuid"
|
:for="field.uuid"
|
||||||
|
dir="auto"
|
||||||
class="label text-2xl mb-2"
|
class="label text-2xl mb-2"
|
||||||
>{{ field.name }}</label>
|
>{{ field.name }}</label>
|
||||||
<div class="flex w-full">
|
<div class="flex w-full">
|
||||||
|
<input
|
||||||
|
v-if="modelValue.length === 0"
|
||||||
|
type="text"
|
||||||
|
:name="`values[${field.uuid}][]`"
|
||||||
|
:value="''"
|
||||||
|
class="hidden"
|
||||||
|
>
|
||||||
<div class="space-y-3.5 mx-auto">
|
<div class="space-y-3.5 mx-auto">
|
||||||
<div
|
<div
|
||||||
v-for="option in field.options"
|
v-for="(option, index) in field.options"
|
||||||
:key="option.uuid"
|
:key="option.uuid"
|
||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
@@ -19,13 +27,13 @@
|
|||||||
:ref="setInputRef"
|
:ref="setInputRef"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
:name="`values[${field.uuid}][]`"
|
:name="`values[${field.uuid}][]`"
|
||||||
:value="option.value"
|
:value="optionValue(option, index)"
|
||||||
class="base-checkbox !h-7 !w-7"
|
class="base-checkbox !h-7 !w-7"
|
||||||
:checked="(modelValue || []).includes(option.value)"
|
:checked="(modelValue || []).includes(optionValue(option, index))"
|
||||||
@change="onChange"
|
@change="onChange"
|
||||||
>
|
>
|
||||||
<span class="text-xl">
|
<span class="text-xl">
|
||||||
{{ option.value }}
|
{{ optionValue(option, index) }}
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@@ -36,6 +44,7 @@
|
|||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: 'MultiSelectStep',
|
name: 'MultiSelectStep',
|
||||||
|
inject: ['t'],
|
||||||
props: {
|
props: {
|
||||||
field: {
|
field: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@@ -62,8 +71,15 @@ export default {
|
|||||||
this.inputRefs.push(el)
|
this.inputRefs.push(el)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
optionValue (option, index) {
|
||||||
|
if (option.value) {
|
||||||
|
return option.value
|
||||||
|
} else {
|
||||||
|
return `${this.t('option')} ${index + 1}`
|
||||||
|
}
|
||||||
|
},
|
||||||
onChange () {
|
onChange () {
|
||||||
this.$emit('update:model-value', this.inputRefs.filter(e => e.checked).map(e => e.value))
|
this.$emit('update:model-value', this.inputRefs.filter(e => e.checked).map((e, index) => this.optionValue(e, index)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div dir="auto">
|
||||||
<div class="flex justify-between items-center w-full mb-2">
|
<div class="flex justify-between items-center w-full mb-2">
|
||||||
<label
|
<label
|
||||||
class="label text-2xl"
|
class="label text-2xl"
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
v-else-if="withTypedSignature"
|
v-else-if="withTypedSignature"
|
||||||
class="tooltip"
|
class="tooltip ml-2"
|
||||||
:data-tip="t('type_text')"
|
:data-tip="t('type_text')"
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
@@ -89,6 +89,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<AppearsOn :field="field" />
|
||||||
<input
|
<input
|
||||||
:value="modelValue || computedPreviousValue"
|
:value="modelValue || computedPreviousValue"
|
||||||
type="hidden"
|
type="hidden"
|
||||||
@@ -122,12 +123,14 @@
|
|||||||
import { IconReload, IconCamera, IconSignature, IconTextSize, IconArrowsDiagonalMinimize2 } from '@tabler/icons-vue'
|
import { IconReload, IconCamera, IconSignature, IconTextSize, IconArrowsDiagonalMinimize2 } from '@tabler/icons-vue'
|
||||||
import { cropCanvasAndExportToPNG } from './crop_canvas'
|
import { cropCanvasAndExportToPNG } from './crop_canvas'
|
||||||
import SignaturePad from 'signature_pad'
|
import SignaturePad from 'signature_pad'
|
||||||
|
import AppearsOn from './appears_on'
|
||||||
|
|
||||||
let isFontLoaded = false
|
let isFontLoaded = false
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'SignatureStep',
|
name: 'SignatureStep',
|
||||||
components: {
|
components: {
|
||||||
|
AppearsOn,
|
||||||
IconReload,
|
IconReload,
|
||||||
IconCamera,
|
IconCamera,
|
||||||
IconTextSize,
|
IconTextSize,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
<label
|
<label
|
||||||
v-if="field.name"
|
v-if="field.name"
|
||||||
:for="field.uuid"
|
:for="field.uuid"
|
||||||
|
dir="auto"
|
||||||
class="label text-2xl mb-2"
|
class="label text-2xl mb-2"
|
||||||
>{{ field.name }}
|
>{{ field.name }}
|
||||||
<template v-if="!field.required">({{ t('optional') }})</template>
|
<template v-if="!field.required">({{ t('optional') }})</template>
|
||||||
@@ -10,12 +11,14 @@
|
|||||||
v-else
|
v-else
|
||||||
class="py-1"
|
class="py-1"
|
||||||
/>
|
/>
|
||||||
|
<AppearsOn :field="field" />
|
||||||
<div class="items-center flex">
|
<div class="items-center flex">
|
||||||
<input
|
<input
|
||||||
v-if="!isTextArea"
|
v-if="!isTextArea"
|
||||||
:id="field.uuid"
|
:id="field.uuid"
|
||||||
v-model="text"
|
v-model="text"
|
||||||
:maxlength="cellsMaxLegth"
|
:maxlength="cellsMaxLegth"
|
||||||
|
dir="auto"
|
||||||
class="base-input !text-2xl w-full !pr-11 -mr-10"
|
class="base-input !text-2xl w-full !pr-11 -mr-10"
|
||||||
:required="field.required"
|
:required="field.required"
|
||||||
:pattern="field.validation?.pattern"
|
:pattern="field.validation?.pattern"
|
||||||
@@ -56,11 +59,13 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { IconAlignBoxLeftTop } from '@tabler/icons-vue'
|
import { IconAlignBoxLeftTop } from '@tabler/icons-vue'
|
||||||
|
import AppearsOn from './appears_on'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'TextStep',
|
name: 'TextStep',
|
||||||
components: {
|
components: {
|
||||||
IconAlignBoxLeftTop
|
IconAlignBoxLeftTop,
|
||||||
|
AppearsOn
|
||||||
},
|
},
|
||||||
inject: ['t'],
|
inject: ['t'],
|
||||||
props: {
|
props: {
|
||||||
|
|||||||
@@ -61,6 +61,7 @@
|
|||||||
v-if="field.type !== 'checkbox' || field.name"
|
v-if="field.type !== 'checkbox' || field.name"
|
||||||
ref="name"
|
ref="name"
|
||||||
:contenteditable="editable && !defaultField"
|
:contenteditable="editable && !defaultField"
|
||||||
|
dir="auto"
|
||||||
class="pr-1 cursor-text outline-none block"
|
class="pr-1 cursor-text outline-none block"
|
||||||
style="min-width: 2px"
|
style="min-width: 2px"
|
||||||
@keydown.enter.prevent="onNameEnter"
|
@keydown.enter.prevent="onNameEnter"
|
||||||
@@ -83,7 +84,7 @@
|
|||||||
class="label text-xs"
|
class="label text-xs"
|
||||||
@click.prevent="field.required = !field.required"
|
@click.prevent="field.required = !field.required"
|
||||||
@mousedown.prevent
|
@mousedown.prevent
|
||||||
>Required</label>
|
>{{ t('required') }}</label>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
v-else-if="editable"
|
v-else-if="editable"
|
||||||
@@ -149,7 +150,7 @@ export default {
|
|||||||
FieldSubmitter,
|
FieldSubmitter,
|
||||||
IconX
|
IconX
|
||||||
},
|
},
|
||||||
inject: ['template', 'selectedAreaRef', 'save'],
|
inject: ['template', 'selectedAreaRef', 'save', 't'],
|
||||||
props: {
|
props: {
|
||||||
area: {
|
area: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<div
|
<div
|
||||||
v-if="$slots.buttons || withTitle"
|
v-if="$slots.buttons || withTitle"
|
||||||
class="flex justify-between py-1.5 items-center pr-4 top-0 z-10"
|
class="flex justify-between py-1.5 items-center pr-4 top-0 z-10"
|
||||||
:class="{ sticky: withStickySubmitters }"
|
:class="{ sticky: withStickySubmitters || isBreakpointLg }"
|
||||||
:style="{ backgroundColor }"
|
:style="{ backgroundColor }"
|
||||||
>
|
>
|
||||||
<div class="flex items-center space-x-3">
|
<div class="flex items-center space-x-3">
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
class="inline"
|
class="inline"
|
||||||
/>
|
/>
|
||||||
<span class="hidden md:inline">
|
<span class="hidden md:inline">
|
||||||
Sign Yourself
|
{{ t('sign_yourself') }}
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
@@ -57,7 +57,7 @@
|
|||||||
class="inline"
|
class="inline"
|
||||||
/>
|
/>
|
||||||
<span class="hidden md:inline">
|
<span class="hidden md:inline">
|
||||||
Send
|
{{ t('send') }}
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
<button
|
<button
|
||||||
@@ -76,7 +76,7 @@
|
|||||||
width="22"
|
width="22"
|
||||||
/>
|
/>
|
||||||
<span class="hidden md:inline">
|
<span class="hidden md:inline">
|
||||||
Save
|
{{ t('save') }}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
@@ -147,6 +147,7 @@
|
|||||||
:document="document"
|
:document="document"
|
||||||
:is-drag="!!dragField"
|
:is-drag="!!dragField"
|
||||||
:default-fields="defaultFields"
|
:default-fields="defaultFields"
|
||||||
|
:allow-draw="!onlyDefinedFields"
|
||||||
:draw-field="drawField"
|
:draw-field="drawField"
|
||||||
:editable="editable"
|
:editable="editable"
|
||||||
:base-url="baseUrl"
|
:base-url="baseUrl"
|
||||||
@@ -194,51 +195,44 @@
|
|||||||
@cancel="[drawField = null, drawOption = null]"
|
@cancel="[drawField = null, drawOption = null]"
|
||||||
@change-submitter="[selectedSubmitter = $event, drawField.submitter_uuid = $event.uuid]"
|
@change-submitter="[selectedSubmitter = $event, drawField.submitter_uuid = $event.uuid]"
|
||||||
/>
|
/>
|
||||||
<FieldType
|
<MobileFields
|
||||||
v-if="sortedDocuments.length && !drawField && editable"
|
v-if="sortedDocuments.length && !drawField && editable"
|
||||||
class="dropdown-top dropdown-end fixed bottom-4 right-4 z-10 md:hidden"
|
:fields="template.fields"
|
||||||
:model-value="''"
|
:default-fields="defaultFields"
|
||||||
@update:model-value="startFieldDraw($event)"
|
:selected-submitter="selectedSubmitter"
|
||||||
>
|
@select="startFieldDraw($event)"
|
||||||
<label
|
/>
|
||||||
class="btn btn-neutral text-white btn-circle btn-lg group"
|
|
||||||
tabindex="0"
|
|
||||||
>
|
|
||||||
<IconPlus
|
|
||||||
class="group-focus:hidden"
|
|
||||||
width="28"
|
|
||||||
height="28"
|
|
||||||
/>
|
|
||||||
<IconX
|
|
||||||
class="hidden group-focus:inline"
|
|
||||||
width="28"
|
|
||||||
height="28"
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
</FieldType>
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="withFieldsList"
|
v-if="withFieldsList"
|
||||||
class="relative w-80 flex-none mt-1 pr-4 pl-0.5 hidden md:block"
|
class="relative w-80 flex-none mt-1 pr-4 pl-0.5 hidden md:block"
|
||||||
:class="drawField ? 'overflow-hidden' : 'overflow-auto'"
|
:class="drawField ? 'overflow-hidden' : 'overflow-y-auto overflow-x-hidden'"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-if="drawField"
|
v-if="drawField"
|
||||||
class="sticky inset-0 h-full"
|
class="sticky inset-0 h-full z-20"
|
||||||
:style="{ backgroundColor }"
|
:style="{ backgroundColor }"
|
||||||
>
|
>
|
||||||
<div class="bg-base-300 rounded-lg p-5 text-center space-y-4">
|
<div class="bg-base-300 rounded-lg p-5 text-center space-y-4">
|
||||||
<p>
|
<p>
|
||||||
Draw {{ drawField.name }} field on the document
|
{{ t('draw_field_on_the_document').replace('{field}', drawField.name) }}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<div>
|
||||||
<button
|
<button
|
||||||
class="base-button"
|
class="base-button"
|
||||||
@click="[drawField = null, drawOption = null]"
|
@click="clearDrawField"
|
||||||
>
|
>
|
||||||
Cancel
|
{{ t('cancel') }}
|
||||||
</button>
|
</button>
|
||||||
</p>
|
<a
|
||||||
|
v-if="!drawOption && !drawField.areas.length && !['stamp', 'signature', 'initials'].includes(drawField.type)"
|
||||||
|
href="#"
|
||||||
|
class="link block mt-3 text-sm"
|
||||||
|
@click.prevent="[drawField = null, drawOption = null]"
|
||||||
|
>
|
||||||
|
{{ t('or_add_field_without_drawing') }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -247,9 +241,11 @@
|
|||||||
:fields="template.fields"
|
:fields="template.fields"
|
||||||
:submitters="template.submitters"
|
:submitters="template.submitters"
|
||||||
:selected-submitter="selectedSubmitter"
|
:selected-submitter="selectedSubmitter"
|
||||||
|
:with-help="withHelp"
|
||||||
:default-submitters="defaultSubmitters"
|
:default-submitters="defaultSubmitters"
|
||||||
:default-fields="defaultFields"
|
:default-fields="defaultFields"
|
||||||
:with-sticky-submitters="withStickySubmitters"
|
:with-sticky-submitters="withStickySubmitters"
|
||||||
|
:only-defined-fields="onlyDefinedFields"
|
||||||
:editable="editable"
|
:editable="editable"
|
||||||
@set-draw="[drawField = $event.field, drawOption = $event.option]"
|
@set-draw="[drawField = $event.field, drawOption = $event.option]"
|
||||||
@set-drag="dragField = $event"
|
@set-drag="dragField = $event"
|
||||||
@@ -273,10 +269,11 @@ import Logo from './logo'
|
|||||||
import Contenteditable from './contenteditable'
|
import Contenteditable from './contenteditable'
|
||||||
import DocumentPreview from './preview'
|
import DocumentPreview from './preview'
|
||||||
import DocumentControls from './controls'
|
import DocumentControls from './controls'
|
||||||
import FieldType from './field_type'
|
import MobileFields from './mobile_fields'
|
||||||
import { IconUsersPlus, IconDeviceFloppy, IconWritingSign, IconInnerShadowTop, IconPlus, IconX } from '@tabler/icons-vue'
|
import { IconUsersPlus, IconDeviceFloppy, IconWritingSign, IconInnerShadowTop } from '@tabler/icons-vue'
|
||||||
import { v4 } from 'uuid'
|
import { v4 } from 'uuid'
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
|
import { en as i18nEn } from './i18n'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'TemplateBuilder',
|
name: 'TemplateBuilder',
|
||||||
@@ -285,10 +282,8 @@ export default {
|
|||||||
Document,
|
Document,
|
||||||
Fields,
|
Fields,
|
||||||
MobileDrawField,
|
MobileDrawField,
|
||||||
IconPlus,
|
|
||||||
FieldType,
|
|
||||||
IconX,
|
|
||||||
IconWritingSign,
|
IconWritingSign,
|
||||||
|
MobileFields,
|
||||||
Logo,
|
Logo,
|
||||||
Dropzone,
|
Dropzone,
|
||||||
DocumentPreview,
|
DocumentPreview,
|
||||||
@@ -302,6 +297,7 @@ export default {
|
|||||||
return {
|
return {
|
||||||
template: this.template,
|
template: this.template,
|
||||||
save: this.save,
|
save: this.save,
|
||||||
|
t: this.t,
|
||||||
baseFetch: this.baseFetch,
|
baseFetch: this.baseFetch,
|
||||||
backgroundColor: this.backgroundColor,
|
backgroundColor: this.backgroundColor,
|
||||||
withPhone: this.withPhone,
|
withPhone: this.withPhone,
|
||||||
@@ -319,6 +315,11 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
|
i18n: {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
backgroundColor: {
|
backgroundColor: {
|
||||||
type: String,
|
type: String,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -329,6 +330,16 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: true
|
default: true
|
||||||
},
|
},
|
||||||
|
withHelp: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
autosave: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
defaultFields: {
|
defaultFields: {
|
||||||
type: Array,
|
type: Array,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -396,6 +407,11 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
|
onlyDefinedFields: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
fetchOptions: {
|
fetchOptions: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -414,8 +430,6 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
fieldIcons: FieldType.computed.fieldIcons,
|
|
||||||
fieldNames: FieldType.computed.fieldNames,
|
|
||||||
selectedAreaRef: () => ref(),
|
selectedAreaRef: () => ref(),
|
||||||
fieldAreasIndex () {
|
fieldAreasIndex () {
|
||||||
const areas = {}
|
const areas = {}
|
||||||
@@ -489,31 +503,41 @@ export default {
|
|||||||
this.documentRefs = []
|
this.documentRefs = []
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
startFieldDraw (type) {
|
t (key) {
|
||||||
const field = {
|
return this.i18n[key] || i18nEn[key] || key
|
||||||
name: '',
|
},
|
||||||
uuid: v4(),
|
startFieldDraw ({ name, type }) {
|
||||||
required: type !== 'checkbox',
|
const existingField = this.template.fields?.find((f) => f.submitter_uuid === this.selectedSubmitter.uuid && name && name === f.name)
|
||||||
areas: [],
|
|
||||||
submitter_uuid: this.selectedSubmitter.uuid,
|
|
||||||
type
|
|
||||||
}
|
|
||||||
|
|
||||||
if (['select', 'multiple', 'radio'].includes(type)) {
|
if (existingField) {
|
||||||
field.options = [{ value: '', uuid: v4() }]
|
this.drawField = existingField
|
||||||
}
|
} else {
|
||||||
|
const field = {
|
||||||
if (type === 'stamp') {
|
name: name || '',
|
||||||
field.readonly = true
|
uuid: v4(),
|
||||||
}
|
required: type !== 'checkbox',
|
||||||
|
areas: [],
|
||||||
if (type === 'date') {
|
submitter_uuid: this.selectedSubmitter.uuid,
|
||||||
field.preferences = {
|
type
|
||||||
format: Intl.DateTimeFormat().resolvedOptions().locale.endsWith('-US') ? 'MM/DD/YYYY' : 'DD/MM/YYYY'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (['select', 'multiple', 'radio'].includes(type)) {
|
||||||
|
field.options = [{ value: '', uuid: v4() }]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'stamp') {
|
||||||
|
field.readonly = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'date') {
|
||||||
|
field.preferences = {
|
||||||
|
format: Intl.DateTimeFormat().resolvedOptions().locale.endsWith('-US') ? 'MM/DD/YYYY' : 'DD/MM/YYYY'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.drawField = field
|
||||||
}
|
}
|
||||||
|
|
||||||
this.drawField = field
|
|
||||||
this.drawOption = null
|
this.drawOption = null
|
||||||
},
|
},
|
||||||
undo () {
|
undo () {
|
||||||
@@ -561,10 +585,21 @@ export default {
|
|||||||
|
|
||||||
ref.$el.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
ref.$el.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
||||||
},
|
},
|
||||||
|
clearDrawField () {
|
||||||
|
if (this.drawField && !this.drawOption && this.drawField.areas.length === 0) {
|
||||||
|
const fieldIndex = this.template.fields.indexOf(this.drawField)
|
||||||
|
|
||||||
|
if (fieldIndex !== -1) {
|
||||||
|
this.template.fields.splice(fieldIndex, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.drawField = null
|
||||||
|
this.drawOption = null
|
||||||
|
},
|
||||||
onKeyUp (e) {
|
onKeyUp (e) {
|
||||||
if (e.code === 'Escape') {
|
if (e.code === 'Escape') {
|
||||||
this.drawField = null
|
this.clearDrawField()
|
||||||
this.drawOption = null
|
|
||||||
this.selectedAreaRef.value = null
|
this.selectedAreaRef.value = null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -622,14 +657,14 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (area.w === 0 || area.h === 0) {
|
if (area.w === 0 || area.h === 0) {
|
||||||
const previousField = [...this.template.fields].reverse().find((f) => f.type === this.drawField.type && f !== this.drawField)
|
const previousArea = this.drawField.areas?.[this.drawField.areas.length - 1]
|
||||||
|
|
||||||
if (this.selectedField?.type === this.drawField.type) {
|
if (this.selectedField?.type === this.drawField.type) {
|
||||||
area.w = this.selectedAreaRef.value.w
|
area.w = this.selectedAreaRef.value.w
|
||||||
area.h = this.selectedAreaRef.value.h
|
area.h = this.selectedAreaRef.value.h
|
||||||
} else if (previousField?.areas?.length) {
|
} else if (previousArea) {
|
||||||
area.w = previousField.areas[0].w
|
area.w = previousArea.w
|
||||||
area.h = previousField.areas[0].h
|
area.h = previousArea.h
|
||||||
} else {
|
} else {
|
||||||
const documentRef = this.documentRefs.find((e) => e.document.uuid === area.attachment_uuid)
|
const documentRef = this.documentRefs.find((e) => e.document.uuid === area.attachment_uuid)
|
||||||
const pageMask = documentRef.pageRefs[area.page].$refs.mask
|
const pageMask = documentRef.pageRefs[area.page].$refs.mask
|
||||||
@@ -657,7 +692,16 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.drawField.areas ||= []
|
this.drawField.areas ||= []
|
||||||
this.drawField.areas.push(area)
|
|
||||||
|
const insertBeforeAreaIndex = this.drawField.areas.findIndex((a) => {
|
||||||
|
return a.attachment_uuid === area.attachment_uuid && a.page > area.page
|
||||||
|
})
|
||||||
|
|
||||||
|
if (insertBeforeAreaIndex !== -1) {
|
||||||
|
this.drawField.areas.splice(insertBeforeAreaIndex, 0, area)
|
||||||
|
} else {
|
||||||
|
this.drawField.areas.push(area)
|
||||||
|
}
|
||||||
|
|
||||||
if (this.template.fields.indexOf(this.drawField) === -1) {
|
if (this.template.fields.indexOf(this.drawField) === -1) {
|
||||||
this.template.fields.push(this.drawField)
|
this.template.fields.push(this.drawField)
|
||||||
@@ -899,11 +943,17 @@ export default {
|
|||||||
headers: { ...this.fetchOptions.headers, ...options.headers }
|
headers: { ...this.fetchOptions.headers, ...options.headers }
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
save () {
|
save ({ force } = { force: false }) {
|
||||||
if (this.$el.closest('template-builder')) {
|
if (!this.autosave && !force) {
|
||||||
this.$el.closest('template-builder').dataset.template = JSON.stringify(this.template)
|
return Promise.resolve({})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.$nextTick(() => {
|
||||||
|
if (this.$el.closest('template-builder')) {
|
||||||
|
this.$el.closest('template-builder').dataset.template = JSON.stringify(this.template)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
this.pushUndo()
|
this.pushUndo()
|
||||||
|
|
||||||
return this.baseFetch(`/api/templates/${this.template.id}`, {
|
return this.baseFetch(`/api/templates/${this.template.id}`, {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
ref="contenteditable"
|
ref="contenteditable"
|
||||||
|
dir="auto"
|
||||||
:contenteditable="editable"
|
:contenteditable="editable"
|
||||||
style="min-width: 2px"
|
style="min-width: 2px"
|
||||||
:class="iconInline ? 'inline' : 'block'"
|
:class="iconInline ? 'inline' : 'block'"
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
:number="index"
|
:number="index"
|
||||||
:editable="editable"
|
:editable="editable"
|
||||||
:areas="areasIndex[index]"
|
:areas="areasIndex[index]"
|
||||||
|
:allow-draw="allowDraw"
|
||||||
:is-drag="isDrag"
|
:is-drag="isDrag"
|
||||||
:default-fields="defaultFields"
|
:default-fields="defaultFields"
|
||||||
:draw-field="drawField"
|
:draw-field="drawField"
|
||||||
@@ -41,6 +42,11 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: () => []
|
default: () => []
|
||||||
},
|
},
|
||||||
|
allowDraw: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
selectedSubmitter: {
|
selectedSubmitter: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
{{ message }}
|
{{ message }}
|
||||||
</div>
|
</div>
|
||||||
<div class="text-sm">
|
<div class="text-sm">
|
||||||
<span class="font-medium">Click to upload</span> or drag and drop files
|
<span class="font-medium">{{ t('click_to_upload') }}</span> {{ t('or_drag_and_drop_files') }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -61,7 +61,7 @@ export default {
|
|||||||
IconCloudUpload,
|
IconCloudUpload,
|
||||||
IconInnerShadowTop
|
IconInnerShadowTop
|
||||||
},
|
},
|
||||||
inject: ['baseFetch'],
|
inject: ['baseFetch', 't'],
|
||||||
props: {
|
props: {
|
||||||
templateId: {
|
templateId: {
|
||||||
type: [Number, String],
|
type: [Number, String],
|
||||||
@@ -91,13 +91,13 @@ export default {
|
|||||||
},
|
},
|
||||||
message () {
|
message () {
|
||||||
if (this.isLoading) {
|
if (this.isLoading) {
|
||||||
return 'Uploading...'
|
return this.t('uploading')
|
||||||
} else if (this.isProcessing) {
|
} else if (this.isProcessing) {
|
||||||
return 'Processing...'
|
return this.t('processing_')
|
||||||
} else if (this.acceptFileTypes === 'image/*, application/pdf') {
|
} else if (this.acceptFileTypes === 'image/*, application/pdf') {
|
||||||
return 'Add PDF documents or images'
|
return this.t('add_pdf_documents_or_images')
|
||||||
} else {
|
} else {
|
||||||
return 'Add documents or images'
|
return this.t('add_documents_or_images')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -47,7 +47,7 @@
|
|||||||
class="label text-xs"
|
class="label text-xs"
|
||||||
@click.prevent="field.required = !field.required"
|
@click.prevent="field.required = !field.required"
|
||||||
@mousedown.prevent
|
@mousedown.prevent
|
||||||
>Required</label>
|
>{{ t('required') }}</label>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -70,8 +70,10 @@
|
|||||||
:field="field"
|
:field="field"
|
||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
v-else-if="!defaultField"
|
v-else
|
||||||
class="dropdown dropdown-end"
|
class="dropdown dropdown-end"
|
||||||
|
@mouseenter="renderDropdown = true"
|
||||||
|
@touchstart="renderDropdown = true"
|
||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
@@ -84,6 +86,7 @@
|
|||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<ul
|
<ul
|
||||||
|
v-if="renderDropdown"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
class="mt-1.5 dropdown-content menu menu-xs p-2 shadow bg-base-100 rounded-box w-52 z-10"
|
class="mt-1.5 dropdown-content menu menu-xs p-2 shadow bg-base-100 rounded-box w-52 z-10"
|
||||||
draggable="true"
|
draggable="true"
|
||||||
@@ -91,14 +94,15 @@
|
|||||||
@click="closeDropdown"
|
@click="closeDropdown"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-if="field.type === 'text'"
|
v-if="field.type === 'text' && !defaultField"
|
||||||
class="py-1.5 px-1 relative"
|
class="py-1.5 px-1 relative"
|
||||||
@click.stop
|
@click.stop
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
v-model="field.default_value"
|
v-model="field.default_value"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Default value"
|
:placeholder="t('default_value')"
|
||||||
|
dir="auto"
|
||||||
class="input input-bordered input-xs w-full max-w-xs h-7 !outline-0"
|
class="input input-bordered input-xs w-full max-w-xs h-7 !outline-0"
|
||||||
@blur="save"
|
@blur="save"
|
||||||
>
|
>
|
||||||
@@ -108,7 +112,7 @@
|
|||||||
class="absolute -top-1 left-2.5 px-1 h-4"
|
class="absolute -top-1 left-2.5 px-1 h-4"
|
||||||
style="font-size: 8px"
|
style="font-size: 8px"
|
||||||
>
|
>
|
||||||
Default value
|
{{ t('default_value') }}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -118,7 +122,7 @@
|
|||||||
>
|
>
|
||||||
<select
|
<select
|
||||||
v-model="field.preferences.format"
|
v-model="field.preferences.format"
|
||||||
placeholder="Format"
|
:placeholder="t('format')"
|
||||||
class="select select-bordered select-xs font-normal w-full max-w-xs !h-7 !outline-0"
|
class="select select-bordered select-xs font-normal w-full max-w-xs !h-7 !outline-0"
|
||||||
@change="save"
|
@change="save"
|
||||||
>
|
>
|
||||||
@@ -135,7 +139,7 @@
|
|||||||
class="absolute -top-1 left-2.5 px-1 h-4"
|
class="absolute -top-1 left-2.5 px-1 h-4"
|
||||||
style="font-size: 8px"
|
style="font-size: 8px"
|
||||||
>
|
>
|
||||||
Format
|
{{ t('format') }}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<li
|
<li
|
||||||
@@ -149,11 +153,11 @@
|
|||||||
class="toggle toggle-xs"
|
class="toggle toggle-xs"
|
||||||
@update:model-value="save"
|
@update:model-value="save"
|
||||||
>
|
>
|
||||||
<span class="label-text">Required</span>
|
<span class="label-text">{{ t('required') }}</span>
|
||||||
</label>
|
</label>
|
||||||
</li>
|
</li>
|
||||||
<li
|
<li
|
||||||
v-if="field.type === 'text'"
|
v-if="field.type === 'text' && !defaultField"
|
||||||
@click.stop
|
@click.stop
|
||||||
>
|
>
|
||||||
<label class="cursor-pointer py-1.5">
|
<label class="cursor-pointer py-1.5">
|
||||||
@@ -163,7 +167,7 @@
|
|||||||
class="toggle toggle-xs"
|
class="toggle toggle-xs"
|
||||||
@update:model-value="save"
|
@update:model-value="save"
|
||||||
>
|
>
|
||||||
<span class="label-text">Read-only</span>
|
<span class="label-text">{{ t('read_only') }}</span>
|
||||||
</label>
|
</label>
|
||||||
</li>
|
</li>
|
||||||
<hr class="pb-0.5 mt-0.5">
|
<hr class="pb-0.5 mt-0.5">
|
||||||
@@ -180,7 +184,7 @@
|
|||||||
:width="20"
|
:width="20"
|
||||||
:stroke-width="1.6"
|
:stroke-width="1.6"
|
||||||
/>
|
/>
|
||||||
Page {{ area.page + 1 }}
|
{{ t('page') }} {{ area.page + 1 }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li v-if="!field.areas?.length || !['radio', 'multiple'].includes(field.type)">
|
<li v-if="!field.areas?.length || !['radio', 'multiple'].includes(field.type)">
|
||||||
@@ -193,7 +197,7 @@
|
|||||||
:width="20"
|
:width="20"
|
||||||
:stroke-width="1.6"
|
:stroke-width="1.6"
|
||||||
/>
|
/>
|
||||||
Draw New Area
|
{{ t('draw_new_area') }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li v-if="field.areas?.length === 1 && ['date', 'signature', 'initials', 'text', 'cells'].includes(field.type)">
|
<li v-if="field.areas?.length === 1 && ['date', 'signature', 'initials', 'text', 'cells'].includes(field.type)">
|
||||||
@@ -206,7 +210,7 @@
|
|||||||
:width="20"
|
:width="20"
|
||||||
:stroke-width="1.6"
|
:stroke-width="1.6"
|
||||||
/>
|
/>
|
||||||
Copy to All Pages
|
{{ t('copy_to_all_pages') }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -246,8 +250,9 @@
|
|||||||
v-model="option.value"
|
v-model="option.value"
|
||||||
class="w-full input input-primary input-xs text-sm bg-transparent !pr-7 -mr-6"
|
class="w-full input input-primary input-xs text-sm bg-transparent !pr-7 -mr-6"
|
||||||
type="text"
|
type="text"
|
||||||
|
dir="auto"
|
||||||
required
|
required
|
||||||
:placeholder="`Option ${index + 1}`"
|
:placeholder="`${t('option')} ${index + 1}`"
|
||||||
@blur="save"
|
@blur="save"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
@@ -265,9 +270,10 @@
|
|||||||
v-else
|
v-else
|
||||||
v-model="option.value"
|
v-model="option.value"
|
||||||
class="w-full input input-primary input-xs text-sm bg-transparent"
|
class="w-full input input-primary input-xs text-sm bg-transparent"
|
||||||
:placeholder="`Option ${index + 1}`"
|
:placeholder="`${t('option')} ${index + 1}`"
|
||||||
type="text"
|
type="text"
|
||||||
required
|
required
|
||||||
|
dir="auto"
|
||||||
@focus="maybeFocusOnOptionArea(option)"
|
@focus="maybeFocusOnOptionArea(option)"
|
||||||
@blur="save"
|
@blur="save"
|
||||||
>
|
>
|
||||||
@@ -284,7 +290,7 @@
|
|||||||
class="text-center text-sm w-full pb-1"
|
class="text-center text-sm w-full pb-1"
|
||||||
@click="addOption"
|
@click="addOption"
|
||||||
>
|
>
|
||||||
+ Add option
|
+ {{ t('add_option') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -310,7 +316,7 @@ export default {
|
|||||||
IconCopy,
|
IconCopy,
|
||||||
FieldType
|
FieldType
|
||||||
},
|
},
|
||||||
inject: ['template', 'save', 'backgroundColor', 'selectedAreaRef'],
|
inject: ['template', 'save', 'backgroundColor', 'selectedAreaRef', 't'],
|
||||||
props: {
|
props: {
|
||||||
field: {
|
field: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@@ -331,7 +337,8 @@ export default {
|
|||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
isNameFocus: false,
|
isNameFocus: false,
|
||||||
showPaymentModal: false
|
showPaymentModal: false,
|
||||||
|
renderDropdown: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -362,7 +369,7 @@ export default {
|
|||||||
} else {
|
} else {
|
||||||
const typeIndex = this.template.fields.filter((f) => f.type === this.field.type).indexOf(this.field)
|
const typeIndex = this.template.fields.filter((f) => f.type === this.field.type).indexOf(this.field)
|
||||||
|
|
||||||
const suffix = { multiple: 'Select', radio: 'Group' }[this.field.type] || 'Field'
|
const suffix = { multiple: this.t('select'), radio: this.t('group') }[this.field.type] || this.t('field')
|
||||||
|
|
||||||
return `${this.fieldNames[this.field.type]} ${suffix} ${typeIndex + 1}`
|
return `${this.fieldNames[this.field.type]} ${suffix} ${typeIndex + 1}`
|
||||||
}
|
}
|
||||||
@@ -415,7 +422,7 @@ export default {
|
|||||||
this.template.documents.forEach((attachment) => {
|
this.template.documents.forEach((attachment) => {
|
||||||
attachment.preview_images.forEach((page) => {
|
attachment.preview_images.forEach((page) => {
|
||||||
if (!field.areas.find((area) => area.attachment_uuid === attachment.uuid && area.page === parseInt(page.filename))) {
|
if (!field.areas.find((area) => area.attachment_uuid === attachment.uuid && area.page === parseInt(page.filename))) {
|
||||||
field.areas.push({ ...JSON.parse(areaString), page: parseInt(page.filename) })
|
field.areas.push({ ...JSON.parse(areaString), attachment_uuid: attachment.uuid, page: parseInt(page.filename) })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="mobileView">
|
<div
|
||||||
|
v-if="mobileView"
|
||||||
|
@mouseenter="renderDropdown = true"
|
||||||
|
@touchstart="renderDropdown = true"
|
||||||
|
>
|
||||||
<div class="flex space-x-2 items-end">
|
<div class="flex space-x-2 items-end">
|
||||||
<div class="group/contenteditable-container bg-base-100 rounded-md p-2 border border-base-300 w-full flex justify-between items-end">
|
<div class="group/contenteditable-container bg-base-100 rounded-md p-2 border border-base-300 w-full flex justify-between items-end">
|
||||||
<div class="flex items-center space-x-2">
|
<div class="flex items-center space-x-2">
|
||||||
@@ -29,7 +33,7 @@
|
|||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<ul
|
<ul
|
||||||
v-if="editable"
|
v-if="editable && renderDropdown"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
class="rounded-md min-w-max mb-2"
|
class="rounded-md min-w-max mb-2"
|
||||||
:class="menuClasses"
|
:class="menuClasses"
|
||||||
@@ -74,7 +78,7 @@
|
|||||||
:stroke-width="1.6"
|
:stroke-width="1.6"
|
||||||
/>
|
/>
|
||||||
<span class="py-1">
|
<span class="py-1">
|
||||||
Add {{ names[submitters.length] }}
|
{{ t('add') }} {{ names[submitters.length] }}
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
@@ -85,6 +89,8 @@
|
|||||||
<div
|
<div
|
||||||
v-else
|
v-else
|
||||||
class="dropdown"
|
class="dropdown"
|
||||||
|
@mouseenter="renderDropdown = true"
|
||||||
|
@touchstart="renderDropdown = true"
|
||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
v-if="compact"
|
v-if="compact"
|
||||||
@@ -99,6 +105,7 @@
|
|||||||
</label>
|
</label>
|
||||||
<label
|
<label
|
||||||
v-else
|
v-else
|
||||||
|
ref="label"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
class="group cursor-pointer group/contenteditable-container rounded-md p-2 border border-base-300 hover:border-content w-full flex justify-between"
|
class="group cursor-pointer group/contenteditable-container rounded-md p-2 border border-base-300 hover:border-content w-full flex justify-between"
|
||||||
>
|
>
|
||||||
@@ -125,7 +132,7 @@
|
|||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
<ul
|
<ul
|
||||||
v-if="editable || !compact"
|
v-if="(editable || !compact) && renderDropdown"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
:class="menuClasses"
|
:class="menuClasses"
|
||||||
@click="closeDropdown"
|
@click="closeDropdown"
|
||||||
@@ -149,13 +156,36 @@
|
|||||||
{{ submitter.name }}
|
{{ submitter.name }}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<button
|
<div
|
||||||
v-if="!compact && submitters.length > 1 && editable"
|
v-if="!compact && submitters.length > 1 && editable"
|
||||||
class="hidden group-hover:block px-2"
|
class="flex"
|
||||||
@click.stop="remove(submitter)"
|
|
||||||
>
|
>
|
||||||
<IconTrashX :width="18" />
|
<div class="flex-col pr-1 hidden group-hover:flex -mt-1 h-0">
|
||||||
</button>
|
<button
|
||||||
|
title="Up"
|
||||||
|
class="relative w-2"
|
||||||
|
style="font-size: 10px; margin-bottom: -4px"
|
||||||
|
@click.stop="[move(submitter, -1), $refs.label.focus()] "
|
||||||
|
>
|
||||||
|
▲
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
title="Down"
|
||||||
|
class="relative w-2"
|
||||||
|
style="font-size: 10px; margin-top: -4px"
|
||||||
|
@click.stop="[move(submitter, 1), $refs.label.focus()] "
|
||||||
|
>
|
||||||
|
▼
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
v-if="!compact && submitters.length > 1 && editable"
|
||||||
|
class="hidden group-hover:block px-2"
|
||||||
|
@click.stop="remove(submitter)"
|
||||||
|
>
|
||||||
|
<IconTrashX :width="18" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li v-if="submitters.length < 10 && editable">
|
<li v-if="submitters.length < 10 && editable">
|
||||||
@@ -169,7 +199,7 @@
|
|||||||
:stroke-width="1.6"
|
:stroke-width="1.6"
|
||||||
/>
|
/>
|
||||||
<span class="py-1">
|
<span class="py-1">
|
||||||
Add {{ names[submitters.length] }}
|
{{ t('add') }} {{ names[submitters.length] }}
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
@@ -191,6 +221,7 @@ export default {
|
|||||||
IconTrashX,
|
IconTrashX,
|
||||||
IconChevronUp
|
IconChevronUp
|
||||||
},
|
},
|
||||||
|
inject: ['t', 'save'],
|
||||||
props: {
|
props: {
|
||||||
submitters: {
|
submitters: {
|
||||||
type: Array,
|
type: Array,
|
||||||
@@ -222,6 +253,11 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits: ['update:model-value', 'remove', 'new-submitter', 'name-change'],
|
emits: ['update:model-value', 'remove', 'new-submitter', 'name-change'],
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
renderDropdown: false
|
||||||
|
}
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
colors () {
|
colors () {
|
||||||
return [
|
return [
|
||||||
@@ -239,16 +275,16 @@ export default {
|
|||||||
},
|
},
|
||||||
names () {
|
names () {
|
||||||
return [
|
return [
|
||||||
'First Party',
|
this.t('first_party'),
|
||||||
'Second Party',
|
this.t('second_party'),
|
||||||
'Third Party',
|
this.t('third_party'),
|
||||||
'Fourth Party',
|
this.t('fourth_party'),
|
||||||
'Fifth Party',
|
this.t('fifth_party'),
|
||||||
'Sixth Party',
|
this.t('sixth_party'),
|
||||||
'Seventh Party',
|
this.t('seventh_party'),
|
||||||
'Eighth Party',
|
this.t('eighth_party'),
|
||||||
'Ninth Party',
|
this.t('ninth_party'),
|
||||||
'Tenth Party'
|
this.t('tenth_party')
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
selectedSubmitter () {
|
selectedSubmitter () {
|
||||||
@@ -264,6 +300,23 @@ export default {
|
|||||||
this.$emit('remove', submitter)
|
this.$emit('remove', submitter)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
move (submitter, direction) {
|
||||||
|
const currentIndex = this.submitters.indexOf(submitter)
|
||||||
|
|
||||||
|
this.submitters.splice(currentIndex, 1)
|
||||||
|
|
||||||
|
if (currentIndex + direction > this.submitters.length) {
|
||||||
|
this.submitters.unshift(submitter)
|
||||||
|
} else if (currentIndex + direction < 0) {
|
||||||
|
this.submitters.push(submitter)
|
||||||
|
} else {
|
||||||
|
this.submitters.splice(currentIndex + direction, 0, submitter)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.selectSubmitter(submitter)
|
||||||
|
|
||||||
|
this.save()
|
||||||
|
},
|
||||||
addSubmitter () {
|
addSubmitter () {
|
||||||
const newSubmitter = {
|
const newSubmitter = {
|
||||||
name: this.names[this.submitters.length],
|
name: this.names[this.submitters.length],
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<span class="dropdown">
|
<span
|
||||||
|
class="dropdown"
|
||||||
|
@mouseenter="renderDropdown = true"
|
||||||
|
@touchstart="renderDropdown = true"
|
||||||
|
>
|
||||||
<slot>
|
<slot>
|
||||||
<label
|
<label
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
@@ -15,7 +19,7 @@
|
|||||||
</label>
|
</label>
|
||||||
</slot>
|
</slot>
|
||||||
<ul
|
<ul
|
||||||
v-if="editable"
|
v-if="editable && renderDropdown"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
class="dropdown-content menu menu-xs p-2 shadow rounded-box w-52 z-10 mb-3"
|
class="dropdown-content menu menu-xs p-2 shadow rounded-box w-52 z-10 mb-3"
|
||||||
:class="menuClasses"
|
:class="menuClasses"
|
||||||
@@ -25,7 +29,7 @@
|
|||||||
v-for="(icon, type) in fieldIcons"
|
v-for="(icon, type) in fieldIcons"
|
||||||
:key="type"
|
:key="type"
|
||||||
>
|
>
|
||||||
<li v-if="withPhone || withPayment || !['phone', 'payment'].includes(type)">
|
<li v-if="(withPhone || type != 'phone') && (withPayment || type != 'payment')">
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
class="text-sm py-1 px-2"
|
class="text-sm py-1 px-2"
|
||||||
@@ -50,7 +54,7 @@ import { IconTextSize, IconWritingSign, IconCalendarEvent, IconPhoto, IconCheckb
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'FiledTypeDropdown',
|
name: 'FiledTypeDropdown',
|
||||||
inject: ['withPhone', 'withPayment'],
|
inject: ['withPhone', 'withPayment', 't'],
|
||||||
props: {
|
props: {
|
||||||
modelValue: {
|
modelValue: {
|
||||||
type: String,
|
type: String,
|
||||||
@@ -78,23 +82,28 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits: ['update:model-value'],
|
emits: ['update:model-value'],
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
renderDropdown: false
|
||||||
|
}
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
fieldNames () {
|
fieldNames () {
|
||||||
return {
|
return {
|
||||||
text: 'Text',
|
text: this.t('text'),
|
||||||
signature: 'Signature',
|
signature: this.t('signature'),
|
||||||
initials: 'Initials',
|
initials: this.t('initials'),
|
||||||
date: 'Date',
|
date: this.t('date'),
|
||||||
image: 'Image',
|
image: this.t('image'),
|
||||||
file: 'File',
|
file: this.t('file'),
|
||||||
select: 'Select',
|
select: this.t('select'),
|
||||||
checkbox: 'Checkbox',
|
checkbox: this.t('checkbox'),
|
||||||
multiple: 'Multiple',
|
multiple: this.t('multiple'),
|
||||||
radio: 'Radio',
|
radio: this.t('radio'),
|
||||||
cells: 'Cells',
|
cells: this.t('cells'),
|
||||||
stamp: 'Stamp',
|
stamp: this.t('stamp'),
|
||||||
payment: 'Payment',
|
payment: this.t('payment'),
|
||||||
phone: 'Phone'
|
phone: this.t('phone')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
fieldIcons () {
|
fieldIcons () {
|
||||||
|
|||||||
@@ -13,9 +13,10 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
ref="fields"
|
||||||
class="mb-1 mt-2"
|
class="mb-1 mt-2"
|
||||||
@dragover.prevent="onFieldDragover"
|
@dragover.prevent="onFieldDragover"
|
||||||
@drop="save"
|
@drop="reorderFields"
|
||||||
>
|
>
|
||||||
<Field
|
<Field
|
||||||
v-for="field in submitterFields"
|
v-for="field in submitterFields"
|
||||||
@@ -23,7 +24,7 @@
|
|||||||
:data-uuid="field.uuid"
|
:data-uuid="field.uuid"
|
||||||
:field="field"
|
:field="field"
|
||||||
:type-index="fields.filter((f) => f.type === field.type).indexOf(field)"
|
:type-index="fields.filter((f) => f.type === field.type).indexOf(field)"
|
||||||
:editable="editable && !dragField"
|
:editable="editable && (!dragField || dragField !== field)"
|
||||||
:default-field="defaultFields.find((f) => f.name === field.name)"
|
:default-field="defaultFields.find((f) => f.name === field.name)"
|
||||||
:draggable="editable"
|
:draggable="editable"
|
||||||
@dragstart="dragField = field"
|
@dragstart="dragField = field"
|
||||||
@@ -63,7 +64,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="editable"
|
v-if="editable && !onlyDefinedFields"
|
||||||
class="grid grid-cols-3 gap-1 pb-2"
|
class="grid grid-cols-3 gap-1 pb-2"
|
||||||
>
|
>
|
||||||
<template
|
<template
|
||||||
@@ -92,6 +93,7 @@
|
|||||||
<div
|
<div
|
||||||
v-else-if="type == 'phone'"
|
v-else-if="type == 'phone'"
|
||||||
class="tooltip tooltip-bottom flex"
|
class="tooltip tooltip-bottom flex"
|
||||||
|
:class="{'tooltip-bottom-start': !withPayment, 'tooltip-bottom': withPayment }"
|
||||||
data-tip="Unlock SMS-verified phone number field with paid plan. Use text field for phone numbers without verification."
|
data-tip="Unlock SMS-verified phone number field with paid plan. Use text field for phone numbers without verification."
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
@@ -118,7 +120,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="fields.length < 4 && editable"
|
v-if="fields.length < 4 && editable && withHelp"
|
||||||
class="text-xs p-2 border border-base-200 rounded"
|
class="text-xs p-2 border border-base-200 rounded"
|
||||||
>
|
>
|
||||||
<ul class="list-disc list-outside ml-3">
|
<ul class="list-disc list-outside ml-3">
|
||||||
@@ -129,7 +131,7 @@
|
|||||||
Drag & drop any other field type on the page
|
Drag & drop any other field type on the page
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
Click on the field type above to add it to the form without drawing it on the document
|
Click on the field type above to start drawing it
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@@ -152,12 +154,17 @@ export default {
|
|||||||
IconDrag,
|
IconDrag,
|
||||||
IconLock
|
IconLock
|
||||||
},
|
},
|
||||||
inject: ['save', 'backgroundColor', 'withPhone', 'withPayment'],
|
inject: ['save', 'backgroundColor', 'withPhone', 'withPayment', 't'],
|
||||||
props: {
|
props: {
|
||||||
fields: {
|
fields: {
|
||||||
type: Array,
|
type: Array,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
withHelp: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
editable: {
|
editable: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -168,6 +175,11 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: () => []
|
default: () => []
|
||||||
},
|
},
|
||||||
|
onlyDefinedFields: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
defaultSubmitters: {
|
defaultSubmitters: {
|
||||||
type: Array,
|
type: Array,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -210,23 +222,33 @@ export default {
|
|||||||
this.$emit('set-drag', field)
|
this.$emit('set-drag', field)
|
||||||
},
|
},
|
||||||
onFieldDragover (e) {
|
onFieldDragover (e) {
|
||||||
const targetFieldUuid = e.target.closest('[data-uuid]')?.dataset?.uuid
|
const targetField = e.target.closest('[data-uuid]')
|
||||||
|
const dragField = this.$refs.fields.querySelector(`[data-uuid="${this.dragField.uuid}"]`)
|
||||||
|
|
||||||
if (this.dragField && targetFieldUuid && this.dragField.uuid !== targetFieldUuid) {
|
if (dragField && targetField && targetField !== dragField) {
|
||||||
const field = this.fields.find((f) => f.uuid === targetFieldUuid)
|
const fields = Array.from(this.$refs.fields.children)
|
||||||
|
const currentIndex = fields.indexOf(dragField)
|
||||||
const currentIndex = this.fields.indexOf(this.dragField)
|
const targetIndex = fields.indexOf(targetField)
|
||||||
const targetIndex = this.fields.indexOf(field)
|
|
||||||
|
|
||||||
if (currentIndex < targetIndex) {
|
if (currentIndex < targetIndex) {
|
||||||
this.fields.splice(targetIndex + 1, 0, this.dragField)
|
targetField.after(dragField)
|
||||||
this.fields.splice(currentIndex, 1)
|
|
||||||
} else {
|
} else {
|
||||||
this.fields.splice(targetIndex, 0, this.dragField)
|
targetField.before(dragField)
|
||||||
this.fields.splice(currentIndex + 1, 1)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
reorderFields () {
|
||||||
|
Array.from(this.$refs.fields.children).forEach((el, index) => {
|
||||||
|
if (el.dataset.uuid !== this.fields[index].uuid) {
|
||||||
|
const field = this.fields.find((f) => f.uuid === el.dataset.uuid)
|
||||||
|
|
||||||
|
this.fields.splice(this.fields.indexOf(field), 1)
|
||||||
|
this.fields.splice(index, 0, field)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
this.save()
|
||||||
|
},
|
||||||
removeSubmitter (submitter) {
|
removeSubmitter (submitter) {
|
||||||
[...this.fields].forEach((field) => {
|
[...this.fields].forEach((field) => {
|
||||||
if (field.submitter_uuid === submitter.uuid) {
|
if (field.submitter_uuid === submitter.uuid) {
|
||||||
@@ -273,7 +295,7 @@ export default {
|
|||||||
|
|
||||||
this.fields.push(field)
|
this.fields.push(field)
|
||||||
|
|
||||||
if (['signature', 'initials', 'cells', 'stamp'].includes(type)) {
|
if (!['payment', 'file'].includes(type)) {
|
||||||
this.$emit('set-draw', { field })
|
this.$emit('set-draw', { field })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
const en = {
|
||||||
|
sign_yourself: 'Sign Yourself',
|
||||||
|
send: 'Send',
|
||||||
|
save: 'Save',
|
||||||
|
cancel: 'Cancel',
|
||||||
|
draw_field_on_the_document: 'Draw {field} field on the document',
|
||||||
|
click_to_upload: 'Click to upload',
|
||||||
|
or_drag_and_drop_files: 'or drag and drop files',
|
||||||
|
uploading: 'Uploading',
|
||||||
|
processing_: 'Processing...',
|
||||||
|
add_pdf_documents_or_images: 'Add PDF documents or images',
|
||||||
|
add_documents_or_images: 'Add documents or images',
|
||||||
|
required: 'Required',
|
||||||
|
default_value: 'Default value',
|
||||||
|
format: 'Format',
|
||||||
|
read_only: 'Read-only',
|
||||||
|
page: 'Page',
|
||||||
|
draw_new_area: 'Draw New Area',
|
||||||
|
copy_to_all_pages: 'Copy to All Pages',
|
||||||
|
add_option: 'Add option',
|
||||||
|
option: 'Option',
|
||||||
|
first_party: 'First Party',
|
||||||
|
second_party: 'Second Party',
|
||||||
|
third_party: 'Third Party',
|
||||||
|
fourth_party: 'Fourth Party',
|
||||||
|
fifth_party: 'Fifth Party',
|
||||||
|
sixth_party: 'Sixth Party',
|
||||||
|
seventh_party: 'Seventh Party',
|
||||||
|
eighth_party: 'Eighth Party',
|
||||||
|
ninth_party: 'Ninth Party',
|
||||||
|
tenth_party: 'Tenth Party',
|
||||||
|
add: 'Add',
|
||||||
|
or_add_field_without_drawing: 'Or add field without drawing',
|
||||||
|
text: 'Text',
|
||||||
|
signature: 'Signature',
|
||||||
|
initials: 'Initials',
|
||||||
|
date: 'Date',
|
||||||
|
image: 'Image',
|
||||||
|
file: 'File',
|
||||||
|
select: 'Select',
|
||||||
|
checkbox: 'Checkbox',
|
||||||
|
multiple: 'Multiple',
|
||||||
|
radio: 'Radio',
|
||||||
|
cells: 'Cells',
|
||||||
|
stamp: 'Stamp',
|
||||||
|
payment: 'Payment',
|
||||||
|
phone: 'Phone',
|
||||||
|
field: 'Field',
|
||||||
|
group: 'Group',
|
||||||
|
draw_a_text_field_on_the_page_with_a_mouse: 'Draw a text field on the page with a mouse',
|
||||||
|
draw_field: 'Draw {field} Field',
|
||||||
|
replace: 'Replace',
|
||||||
|
uploading_: 'Uploading...',
|
||||||
|
add_document: 'Add Document'
|
||||||
|
}
|
||||||
|
|
||||||
|
export { en }
|
||||||
@@ -9,14 +9,14 @@
|
|||||||
class="inline"
|
class="inline"
|
||||||
:stroke-width="1.6"
|
:stroke-width="1.6"
|
||||||
/>
|
/>
|
||||||
<span> Draw {{ fieldNames[drawField.type] }} Field </span>
|
<span>{{ t('draw_field').replace('{field}', fieldNames[drawField.type]) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
class="link block text-center"
|
class="link block text-center"
|
||||||
@click.prevent="$emit('cancel')"
|
@click.prevent="$emit('cancel')"
|
||||||
>
|
>
|
||||||
Cancel
|
{{ t('cancel') }}
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
<FieldSubmitter
|
<FieldSubmitter
|
||||||
@@ -43,7 +43,7 @@ export default {
|
|||||||
Field,
|
Field,
|
||||||
FieldSubmitter
|
FieldSubmitter
|
||||||
},
|
},
|
||||||
inject: ['save'],
|
inject: ['save', 't'],
|
||||||
props: {
|
props: {
|
||||||
drawField: {
|
drawField: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
|||||||
@@ -0,0 +1,121 @@
|
|||||||
|
<template>
|
||||||
|
<span
|
||||||
|
class="dropdown dropdown-top dropdown-end fixed bottom-4 right-4 z-10 md:hidden"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="btn btn-neutral text-white btn-circle btn-lg group"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<IconPlus
|
||||||
|
class="group-focus:hidden"
|
||||||
|
width="28"
|
||||||
|
height="28"
|
||||||
|
/>
|
||||||
|
<IconX
|
||||||
|
class="hidden group-focus:inline"
|
||||||
|
width="28"
|
||||||
|
height="28"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<ul
|
||||||
|
tabindex="0"
|
||||||
|
class="dropdown-content menu menu-xs p-2 shadow rounded-box w-52 z-10 mb-3 mt-1.5 bg-base-100"
|
||||||
|
@click="closeDropdown"
|
||||||
|
>
|
||||||
|
<template v-if="submitterDefaultFields.length">
|
||||||
|
<template
|
||||||
|
v-for="field in submitterDefaultFields"
|
||||||
|
:key="field.name"
|
||||||
|
>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
class="text-sm py-1 px-2"
|
||||||
|
@click.prevent="$emit('select', { name: field.name || '', type: field.type || 'text' })"
|
||||||
|
>
|
||||||
|
<component
|
||||||
|
:is="fieldIcons[field.type || 'text']"
|
||||||
|
:stroke-width="1.6"
|
||||||
|
:width="20"
|
||||||
|
/>
|
||||||
|
{{ field.name }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<template
|
||||||
|
v-for="(icon, type) in fieldIcons"
|
||||||
|
:key="type"
|
||||||
|
>
|
||||||
|
<li v-if="(withPhone || type != 'phone') && (withPayment || type != 'payment')">
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
class="text-sm py-1 px-2"
|
||||||
|
:class="{ 'active': type === modelValue }"
|
||||||
|
@click.prevent="$emit('select', { type })"
|
||||||
|
>
|
||||||
|
<component
|
||||||
|
:is="icon"
|
||||||
|
:stroke-width="1.6"
|
||||||
|
:width="20"
|
||||||
|
/>
|
||||||
|
{{ fieldNames[type] }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</ul>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import { IconPlus, IconX } from '@tabler/icons-vue'
|
||||||
|
import FieldType from './field_type'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'MobileFields',
|
||||||
|
components: {
|
||||||
|
IconPlus,
|
||||||
|
IconX
|
||||||
|
},
|
||||||
|
inject: ['withPhone', 'withPayment', 'backgroundColor', 't'],
|
||||||
|
props: {
|
||||||
|
modelValue: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
fields: {
|
||||||
|
type: Array,
|
||||||
|
required: false,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
selectedSubmitter: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
defaultFields: {
|
||||||
|
type: Array,
|
||||||
|
required: false,
|
||||||
|
default: () => []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emits: ['select'],
|
||||||
|
computed: {
|
||||||
|
...FieldType.computed,
|
||||||
|
submitterFields () {
|
||||||
|
return this.fields.filter((f) => f.submitter_uuid === this.selectedSubmitter.uuid)
|
||||||
|
},
|
||||||
|
submitterDefaultFields () {
|
||||||
|
return this.defaultFields.filter((f) => {
|
||||||
|
return (!f.role || f.role === this.selectedSubmitter.name)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
closeDropdown () {
|
||||||
|
document.activeElement.blur()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -75,6 +75,11 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: () => []
|
default: () => []
|
||||||
},
|
},
|
||||||
|
allowDraw: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
selectedSubmitter: {
|
selectedSubmitter: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true
|
||||||
@@ -149,6 +154,10 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
onStartDraw (e) {
|
onStartDraw (e) {
|
||||||
|
if (!this.allowDraw) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (this.isMobile && !this.drawField) {
|
if (this.isMobile && !this.drawField) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -141,6 +141,13 @@
|
|||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
<a
|
||||||
|
v-if="!isConnected"
|
||||||
|
class="block link text-center mt-1"
|
||||||
|
href="https://www.docuseal.co/blog/accept-payments-and-request-signatures-with-ease"
|
||||||
|
target="_blank"
|
||||||
|
data-turbo="false"
|
||||||
|
>Learn more</a>
|
||||||
</div>
|
</div>
|
||||||
</ul>
|
</ul>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import Upload from './upload'
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ReplaceDocument',
|
name: 'ReplaceDocument',
|
||||||
inject: ['baseFetch'],
|
inject: ['baseFetch', 't'],
|
||||||
props: {
|
props: {
|
||||||
templateId: {
|
templateId: {
|
||||||
type: [Number, String],
|
type: [Number, String],
|
||||||
@@ -56,11 +56,11 @@ export default {
|
|||||||
},
|
},
|
||||||
message () {
|
message () {
|
||||||
if (this.isLoading) {
|
if (this.isLoading) {
|
||||||
return 'Uploading...'
|
return this.t('uploading_')
|
||||||
} else if (this.isProcessing) {
|
} else if (this.isProcessing) {
|
||||||
return 'Processing...'
|
return this.t('processing_')
|
||||||
} else {
|
} else {
|
||||||
return 'Replace'
|
return this.t('replace')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -15,13 +15,13 @@
|
|||||||
width="20"
|
width="20"
|
||||||
/>
|
/>
|
||||||
<span v-if="isLoading">
|
<span v-if="isLoading">
|
||||||
Uploading...
|
{{ t('uploading_') }}
|
||||||
</span>
|
</span>
|
||||||
<span v-else-if="isProcessing">
|
<span v-else-if="isProcessing">
|
||||||
Processing...
|
{{ t('processing_') }}
|
||||||
</span>
|
</span>
|
||||||
<span v-else>
|
<span v-else>
|
||||||
Add Document
|
{{ t('add_document') }}
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
<form
|
<form
|
||||||
@@ -50,7 +50,7 @@ export default {
|
|||||||
IconUpload,
|
IconUpload,
|
||||||
IconInnerShadowTop
|
IconInnerShadowTop
|
||||||
},
|
},
|
||||||
inject: ['baseFetch'],
|
inject: ['baseFetch', 't'],
|
||||||
props: {
|
props: {
|
||||||
templateId: {
|
templateId: {
|
||||||
type: [Number, String],
|
type: [Number, String],
|
||||||
|
|||||||
@@ -26,18 +26,24 @@ class ProcessSubmitterCompletionJob < ApplicationJob
|
|||||||
|
|
||||||
if submitter.template.account.users.exists?(id: user.id) &&
|
if submitter.template.account.users.exists?(id: user.id) &&
|
||||||
submitter.submission.preferences['send_email'] != false
|
submitter.submission.preferences['send_email'] != false
|
||||||
bcc = submitter.submission.template.account.account_configs
|
SubmitterMailer.completed_email(submitter, user).deliver_later!
|
||||||
.find_by(key: AccountConfig::BCC_EMAILS)&.value
|
|
||||||
|
|
||||||
SubmitterMailer.completed_email(submitter, user, bcc:).deliver_later!
|
bcc = submitter.submission.template.account.account_configs
|
||||||
|
.find_by(key: AccountConfig::BCC_EMAILS)&.value.presence
|
||||||
|
|
||||||
|
SubmitterMailer.completed_email(submitter, user, to: bcc).deliver_later! if bcc
|
||||||
end
|
end
|
||||||
|
|
||||||
to = submitter.submission.submitters.reject { |e| e.preferences['send_email'] == false }
|
to = build_to_addresses(submitter)
|
||||||
.sort_by(&:completed_at).select(&:email?).map(&:friendly_name).join(', ')
|
|
||||||
|
|
||||||
SubmitterMailer.documents_copy_email(submitter, to:).deliver_later! if to.present?
|
SubmitterMailer.documents_copy_email(submitter, to:).deliver_later! if to.present?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def build_to_addresses(submitter)
|
||||||
|
submitter.submission.submitters.reject { |e| e.preferences['send_email'] == false }
|
||||||
|
.sort_by(&:completed_at).select(&:email?).map(&:friendly_name).join(', ')
|
||||||
|
end
|
||||||
|
|
||||||
def enqueue_next_submitter_request_notification(submitter)
|
def enqueue_next_submitter_request_notification(submitter)
|
||||||
next_submitter_item =
|
next_submitter_item =
|
||||||
submitter.submission.template_submitters.find do |e|
|
submitter.submission.template_submitters.find do |e|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ class SubmitterMailer < ApplicationMailer
|
|||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def completed_email(submitter, user, bcc: nil)
|
def completed_email(submitter, user, to: nil)
|
||||||
@current_account = submitter.submission.template.account
|
@current_account = submitter.submission.template.account
|
||||||
@submitter = submitter
|
@submitter = submitter
|
||||||
@submission = submitter.submission
|
@submission = submitter.submission
|
||||||
@@ -55,8 +55,7 @@ class SubmitterMailer < ApplicationMailer
|
|||||||
end
|
end
|
||||||
|
|
||||||
mail(from: from_address_for_submitter(submitter),
|
mail(from: from_address_for_submitter(submitter),
|
||||||
to: user.role == 'integration' ? user.friendly_name.sub(/\+\w+@/, '@') : user.friendly_name,
|
to: to || (user.role == 'integration' ? user.friendly_name.sub(/\+\w+@/, '@') : user.friendly_name),
|
||||||
bcc:,
|
|
||||||
subject:)
|
subject:)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -60,5 +60,5 @@ class AccountConfig < ApplicationRecord
|
|||||||
|
|
||||||
belongs_to :account
|
belongs_to :account
|
||||||
|
|
||||||
serialize :value, JSON
|
serialize :value, coder: JSON
|
||||||
end
|
end
|
||||||
@@ -34,5 +34,5 @@ class EncryptedConfig < ApplicationRecord
|
|||||||
|
|
||||||
encrypts :value
|
encrypts :value
|
||||||
|
|
||||||
serialize :value, JSON
|
serialize :value, coder: JSON
|
||||||
end
|
end
|
||||||
@@ -25,5 +25,5 @@ class EncryptedUserConfig < ApplicationRecord
|
|||||||
|
|
||||||
encrypts :value
|
encrypts :value
|
||||||
|
|
||||||
serialize :value, JSON
|
serialize :value, coder: JSON
|
||||||
end
|
end
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
# Table name: submissions
|
# Table name: submissions
|
||||||
#
|
#
|
||||||
# id :bigint not null, primary key
|
# id :bigint not null, primary key
|
||||||
# deleted_at :datetime
|
# archived_at :datetime
|
||||||
# preferences :text not null
|
# preferences :text not null
|
||||||
# slug :string not null
|
# slug :string not null
|
||||||
# source :text not null
|
# source :text not null
|
||||||
@@ -39,10 +39,10 @@ class Submission < ApplicationRecord
|
|||||||
|
|
||||||
attribute :preferences, :string, default: -> { {} }
|
attribute :preferences, :string, default: -> { {} }
|
||||||
|
|
||||||
serialize :template_fields, JSON
|
serialize :template_fields, coder: JSON
|
||||||
serialize :template_schema, JSON
|
serialize :template_schema, coder: JSON
|
||||||
serialize :template_submitters, JSON
|
serialize :template_submitters, coder: JSON
|
||||||
serialize :preferences, JSON
|
serialize :preferences, coder: JSON
|
||||||
|
|
||||||
attribute :source, :string, default: 'link'
|
attribute :source, :string, default: 'link'
|
||||||
attribute :submitters_order, :string, default: 'random'
|
attribute :submitters_order, :string, default: 'random'
|
||||||
@@ -55,7 +55,7 @@ class Submission < ApplicationRecord
|
|||||||
->(e) { where(uuid: (e.template_schema.presence || e.template.schema).pluck('attachment_uuid')) },
|
->(e) { where(uuid: (e.template_schema.presence || e.template.schema).pluck('attachment_uuid')) },
|
||||||
through: :template, source: :documents_attachments
|
through: :template, source: :documents_attachments
|
||||||
|
|
||||||
scope :active, -> { where(deleted_at: nil) }
|
scope :active, -> { where(archived_at: nil) }
|
||||||
scope :pending, -> { joins(:submitters).where(submitters: { completed_at: nil }).distinct }
|
scope :pending, -> { joins(:submitters).where(submitters: { completed_at: nil }).distinct }
|
||||||
scope :completed, -> { where.not(id: pending.select(:submission_id)) }
|
scope :completed, -> { where.not(id: pending.select(:submission_id)) }
|
||||||
|
|
||||||
@@ -71,10 +71,6 @@ class Submission < ApplicationRecord
|
|||||||
preserved: 'preserved'
|
preserved: 'preserved'
|
||||||
}, scope: false, prefix: true
|
}, scope: false, prefix: true
|
||||||
|
|
||||||
def archived_at
|
|
||||||
deleted_at
|
|
||||||
end
|
|
||||||
|
|
||||||
def audit_trail_url
|
def audit_trail_url
|
||||||
return if audit_trail.blank?
|
return if audit_trail.blank?
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ class SubmissionEvent < ApplicationRecord
|
|||||||
attribute :data, :string, default: -> { {} }
|
attribute :data, :string, default: -> { {} }
|
||||||
attribute :event_timestamp, :datetime, default: -> { Time.current }
|
attribute :event_timestamp, :datetime, default: -> { Time.current }
|
||||||
|
|
||||||
serialize :data, JSON
|
serialize :data, coder: JSON
|
||||||
|
|
||||||
before_validation :set_submission_id, on: :create
|
before_validation :set_submission_id, on: :create
|
||||||
|
|
||||||
|
|||||||
@@ -41,8 +41,8 @@ class Submitter < ApplicationRecord
|
|||||||
attribute :preferences, :string, default: -> { {} }
|
attribute :preferences, :string, default: -> { {} }
|
||||||
attribute :slug, :string, default: -> { SecureRandom.base58(14) }
|
attribute :slug, :string, default: -> { SecureRandom.base58(14) }
|
||||||
|
|
||||||
serialize :values, JSON
|
serialize :values, coder: JSON
|
||||||
serialize :preferences, JSON
|
serialize :preferences, coder: JSON
|
||||||
|
|
||||||
has_many_attached :documents
|
has_many_attached :documents
|
||||||
has_many_attached :attachments
|
has_many_attached :attachments
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
#
|
#
|
||||||
# id :bigint not null, primary key
|
# id :bigint not null, primary key
|
||||||
# application_key :string
|
# application_key :string
|
||||||
# deleted_at :datetime
|
# archived_at :datetime
|
||||||
# fields :text not null
|
# fields :text not null
|
||||||
# name :string not null
|
# name :string not null
|
||||||
# schema :text not null
|
# schema :text not null
|
||||||
@@ -47,9 +47,9 @@ class Template < ApplicationRecord
|
|||||||
attribute :slug, :string, default: -> { SecureRandom.base58(14) }
|
attribute :slug, :string, default: -> { SecureRandom.base58(14) }
|
||||||
attribute :source, :string, default: 'native'
|
attribute :source, :string, default: 'native'
|
||||||
|
|
||||||
serialize :fields, JSON
|
serialize :fields, coder: JSON
|
||||||
serialize :schema, JSON
|
serialize :schema, coder: JSON
|
||||||
serialize :submitters, JSON
|
serialize :submitters, coder: JSON
|
||||||
|
|
||||||
has_many_attached :documents
|
has_many_attached :documents
|
||||||
|
|
||||||
@@ -58,8 +58,8 @@ class Template < ApplicationRecord
|
|||||||
|
|
||||||
has_many :submissions, dependent: :destroy
|
has_many :submissions, dependent: :destroy
|
||||||
|
|
||||||
scope :active, -> { where(deleted_at: nil) }
|
scope :active, -> { where(archived_at: nil) }
|
||||||
scope :archived, -> { where.not(deleted_at: nil) }
|
scope :archived, -> { where.not(archived_at: nil) }
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
|||||||
@@ -4,13 +4,13 @@
|
|||||||
#
|
#
|
||||||
# Table name: template_folders
|
# Table name: template_folders
|
||||||
#
|
#
|
||||||
# id :bigint not null, primary key
|
# id :bigint not null, primary key
|
||||||
# deleted_at :datetime
|
# archived_at :datetime
|
||||||
# name :string not null
|
# name :string not null
|
||||||
# created_at :datetime not null
|
# created_at :datetime not null
|
||||||
# updated_at :datetime not null
|
# updated_at :datetime not null
|
||||||
# account_id :bigint not null
|
# account_id :bigint not null
|
||||||
# author_id :bigint not null
|
# author_id :bigint not null
|
||||||
#
|
#
|
||||||
# Indexes
|
# Indexes
|
||||||
#
|
#
|
||||||
@@ -29,10 +29,10 @@ class TemplateFolder < ApplicationRecord
|
|||||||
belongs_to :account
|
belongs_to :account
|
||||||
|
|
||||||
has_many :templates, dependent: :destroy, foreign_key: :folder_id, inverse_of: :folder
|
has_many :templates, dependent: :destroy, foreign_key: :folder_id, inverse_of: :folder
|
||||||
has_many :active_templates, -> { where(deleted_at: nil) },
|
has_many :active_templates, -> { where(archived_at: nil) },
|
||||||
class_name: 'Template', dependent: :destroy, foreign_key: :folder_id, inverse_of: :folder
|
class_name: 'Template', dependent: :destroy, foreign_key: :folder_id, inverse_of: :folder
|
||||||
|
|
||||||
scope :active, -> { where(deleted_at: nil) }
|
scope :active, -> { where(archived_at: nil) }
|
||||||
|
|
||||||
def default?
|
def default?
|
||||||
name == DEFAULT_NAME
|
name == DEFAULT_NAME
|
||||||
|
|||||||
+5
-3
@@ -5,10 +5,10 @@
|
|||||||
# Table name: users
|
# Table name: users
|
||||||
#
|
#
|
||||||
# id :bigint not null, primary key
|
# id :bigint not null, primary key
|
||||||
|
# archived_at :datetime
|
||||||
# consumed_timestep :integer
|
# consumed_timestep :integer
|
||||||
# current_sign_in_at :datetime
|
# current_sign_in_at :datetime
|
||||||
# current_sign_in_ip :string
|
# current_sign_in_ip :string
|
||||||
# deleted_at :datetime
|
|
||||||
# email :string not null
|
# email :string not null
|
||||||
# encrypted_password :string not null
|
# encrypted_password :string not null
|
||||||
# failed_attempts :integer default(0), not null
|
# failed_attempts :integer default(0), not null
|
||||||
@@ -49,6 +49,8 @@ class User < ApplicationRecord
|
|||||||
|
|
||||||
EMAIL_REGEXP = /[^@;,<>\s]+@[^@;,<>\s]+/
|
EMAIL_REGEXP = /[^@;,<>\s]+@[^@;,<>\s]+/
|
||||||
|
|
||||||
|
has_one_attached :signature
|
||||||
|
|
||||||
belongs_to :account
|
belongs_to :account
|
||||||
has_one :access_token, dependent: :destroy
|
has_one :access_token, dependent: :destroy
|
||||||
has_many :templates, dependent: :destroy, foreign_key: :author_id, inverse_of: :author
|
has_many :templates, dependent: :destroy, foreign_key: :author_id, inverse_of: :author
|
||||||
@@ -62,7 +64,7 @@ class User < ApplicationRecord
|
|||||||
attribute :role, :string, default: ADMIN_ROLE
|
attribute :role, :string, default: ADMIN_ROLE
|
||||||
attribute :uuid, :string, default: -> { SecureRandom.uuid }
|
attribute :uuid, :string, default: -> { SecureRandom.uuid }
|
||||||
|
|
||||||
scope :active, -> { where(deleted_at: nil) }
|
scope :active, -> { where(archived_at: nil) }
|
||||||
scope :admins, -> { where(role: ADMIN_ROLE) }
|
scope :admins, -> { where(role: ADMIN_ROLE) }
|
||||||
|
|
||||||
def access_token
|
def access_token
|
||||||
@@ -70,7 +72,7 @@ class User < ApplicationRecord
|
|||||||
end
|
end
|
||||||
|
|
||||||
def active_for_authentication?
|
def active_for_authentication?
|
||||||
!deleted_at?
|
!archived_at?
|
||||||
end
|
end
|
||||||
|
|
||||||
def remember_me
|
def remember_me
|
||||||
|
|||||||
@@ -25,5 +25,5 @@ class UserConfig < ApplicationRecord
|
|||||||
|
|
||||||
belongs_to :user
|
belongs_to :user
|
||||||
|
|
||||||
serialize :value, JSON
|
serialize :value, coder: JSON
|
||||||
end
|
end
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
<%= f.fields_for current_account do |ff| %>
|
<%= f.fields_for current_account do |ff| %>
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<%= ff.label :name, 'Company Name', class: 'label' %>
|
<%= ff.label :name, 'Company Name', class: 'label' %>
|
||||||
<%= ff.text_field :name, required: true, class: 'base-input' %>
|
<%= ff.text_field :name, required: true, class: 'base-input', dir: 'auto' %>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid md:grid-cols-2 gap-4">
|
<div class="grid md:grid-cols-2 gap-4">
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
<div class="flex flex-row space-x-4">
|
<div class="flex flex-row space-x-4">
|
||||||
<input id="api_key" type="text" value="<%= current_user.access_token.token %>" class="input font-mono input-bordered w-full" autocomplete="off" readonly>
|
<input id="api_key" type="text" value="<%= current_user.access_token.token %>" class="input font-mono input-bordered w-full" autocomplete="off" readonly>
|
||||||
<%= render 'shared/clipboard_copy', icon: 'copy', text: current_user.access_token.token, class: 'base-button', icon_class: 'w-6 h-6 text-white', copy_title: 'Copy', copied_title: 'Copied' %>
|
<%= render 'shared/clipboard_copy', icon: 'copy', text: current_user.access_token.token, class: 'base-button', icon_class: 'w-6 h-6 text-white', copy_title: 'Copy', copied_title: 'Copied' %>
|
||||||
|
<%= button_to button_title(title: 'Rotate', disabled_with: 'Rotate', icon: svg_icon('reload', class: 'w-6 h-6')), settings_api_index_path, class: 'white-button', data: { turbo_confirm: 'Remove existing API token and generated a new one. Are you sure?' } %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% view_archived_html = capture do %>
|
<% view_archived_html = capture do %>
|
||||||
<% if current_account.templates.where.not(deleted_at: nil).exists? %>
|
<% if current_account.templates.where.not(archived_at: nil).exists? %>
|
||||||
<div>
|
<div>
|
||||||
<a href="<%= templates_archived_index_path %>" class="link text-sm">View Archived</a>
|
<a href="<%= templates_archived_index_path %>" class="link text-sm">View Archived</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<style>
|
<style>
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body dir="auto">
|
||||||
<%= yield %>
|
<%= yield %>
|
||||||
<%= render partial: 'shared/mailer_attribution' %>
|
<%= render partial: 'shared/mailer_attribution' %>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -4,11 +4,16 @@
|
|||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<div class="relative flex flex-col items-center">
|
<div class="relative flex flex-col items-center">
|
||||||
<%= render 'shared/logo', width: '100', height: '100' %>
|
<%= render 'shared/logo', width: '100', height: '100' %>
|
||||||
<h1 class="text-6xl font-bold mt-4 mb-8">
|
<h1 class="text-6xl font-bold mt-4 mb-4">
|
||||||
DocuSeal
|
DocuSeal
|
||||||
</h1>
|
</h1>
|
||||||
|
<% if Docuseal.version.present? %>
|
||||||
|
<a href="https://github.com/docusealco/docuseal/releases" target="_blank" class="badge badge-outline badge-lg block mx-auto">
|
||||||
|
v<%= Docuseal.version %>
|
||||||
|
</a>
|
||||||
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<h2 class="mb-16 text-lg text-center text-gray-600">
|
<h2 class="mt-4 mb-16 text-lg text-center text-gray-600">
|
||||||
A self-hosted and open-source web platform that provides secure and efficient digital document signing and processing.
|
A self-hosted and open-source web platform that provides secure and efficient digital document signing and processing.
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
@@ -62,7 +67,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<h3 class="mb-4 text-2xl font-semibold">Open Source</h3>
|
<h3 class="mb-4 text-2xl font-semibold">Open Source</h3>
|
||||||
<p class="text-base text-gray-500">
|
<p class="text-base text-gray-500">
|
||||||
Source code is available under <a href="<%= Docuseal::GITHUB_URL %>" class="link link-neutral font-bold" target="_blank">github.com/docusealco</a>.<br>Open-source contributors are always ready to help!
|
Source code is available under <a href="<%= Docuseal::GITHUB_URL %>" class="link link-neutral font-bold" target="_blank">github.com/docusealco</a>.<br>
|
||||||
|
Open-source contributors are always ready to help!
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<div class="collapse collapse-plus bg-base-200">
|
<div class="collapse collapse-plus bg-base-200 overflow-visible">
|
||||||
<input type="checkbox">
|
<input type="checkbox">
|
||||||
<div class="collapse-title text-xl font-medium">
|
<div class="collapse-title text-xl font-medium">
|
||||||
<div>
|
<div>
|
||||||
@@ -11,12 +11,17 @@
|
|||||||
<%= f.fields_for :value, Struct.new(:subject, :body).new(*f.object.value.values_at('subject', 'body')) do |ff| %>
|
<%= f.fields_for :value, Struct.new(:subject, :body).new(*f.object.value.values_at('subject', 'body')) do |ff| %>
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<%= ff.label :subject, class: 'label' %>
|
<%= ff.label :subject, class: 'label' %>
|
||||||
<%= ff.text_field :subject, required: true, class: 'base-input' %>
|
<%= ff.text_field :subject, required: true, class: 'base-input', dir: 'auto' %>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<%= ff.label :body, class: 'label' %>
|
<div class="flex items-center">
|
||||||
|
<%= ff.label :body, class: 'label' %>
|
||||||
|
<span class="tooltip" data-tip="Use following placeholders text: <%= AccountConfig::DEFAULT_VALUES.dig(AccountConfig::SUBMITTER_DOCUMENTS_COPY_EMAIL_KEY, 'body').scan(/{{.*?}}/).join(', ') %>">
|
||||||
|
<%= svg_icon('info_circle', class: 'w-4 h-4') %>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
<autoresize-textarea>
|
<autoresize-textarea>
|
||||||
<%= ff.text_area :body, required: true, class: 'base-input w-full py-2' %>
|
<%= ff.text_area :body, required: true, class: 'base-input w-full py-2', dir: 'auto' %>
|
||||||
</autoresize-textarea>
|
</autoresize-textarea>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<%= f.fields_for :value, Struct.new(:title, :url).new(*(f.object.value || {}).values_at('title', 'url')) do |ff| %>
|
<%= f.fields_for :value, Struct.new(:title, :url).new(*(f.object.value || {}).values_at('title', 'url')) do |ff| %>
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<%= ff.label :title, 'Button title', class: 'label' %>
|
<%= ff.label :title, 'Button title', class: 'label' %>
|
||||||
<%= ff.text_field :title, class: 'base-input' %>
|
<%= ff.text_field :title, class: 'base-input', dir: 'auto' %>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<%= ff.label :url, 'Button URL', class: 'label' %>
|
<%= ff.label :url, 'Button URL', class: 'label' %>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<div class="collapse collapse-plus bg-base-200">
|
<div class="collapse collapse-plus bg-base-200 overflow-visible">
|
||||||
<input type="checkbox">
|
<input type="checkbox">
|
||||||
<div class="collapse-title text-xl font-medium">
|
<div class="collapse-title text-xl font-medium">
|
||||||
<div>
|
<div>
|
||||||
@@ -11,12 +11,17 @@
|
|||||||
<%= f.fields_for :value, Struct.new(:subject, :body).new(*f.object.value.values_at('subject', 'body')) do |ff| %>
|
<%= f.fields_for :value, Struct.new(:subject, :body).new(*f.object.value.values_at('subject', 'body')) do |ff| %>
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<%= ff.label :subject, class: 'label' %>
|
<%= ff.label :subject, class: 'label' %>
|
||||||
<%= ff.text_field :subject, required: true, class: 'base-input' %>
|
<%= ff.text_field :subject, required: true, class: 'base-input', dir: 'auto' %>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<%= ff.label :body, class: 'label' %>
|
<div class="flex items-center">
|
||||||
|
<%= ff.label :body, class: 'label' %>
|
||||||
|
<span class="tooltip" data-tip="Use following placeholders text: <%= AccountConfig::DEFAULT_VALUES.dig(AccountConfig::SUBMITTER_INVITATION_EMAIL_KEY, 'body').scan(/{{.*?}}/).join(', ') %>">
|
||||||
|
<%= svg_icon('info_circle', class: 'w-4 h-4') %>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
<autoresize-textarea>
|
<autoresize-textarea>
|
||||||
<%= ff.text_area :body, required: true, class: 'base-input w-full py-2' %>
|
<%= ff.text_area :body, required: true, class: 'base-input w-full py-2', dir: 'auto' %>
|
||||||
</autoresize-textarea>
|
</autoresize-textarea>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<div class="collapse collapse-plus bg-base-200">
|
<div class="collapse collapse-plus bg-base-200 overflow-visible">
|
||||||
<input type="checkbox">
|
<input type="checkbox">
|
||||||
<div class="collapse-title text-xl font-medium">
|
<div class="collapse-title text-xl font-medium">
|
||||||
<div>
|
<div>
|
||||||
@@ -10,13 +10,23 @@
|
|||||||
<%= f.hidden_field :key %>
|
<%= f.hidden_field :key %>
|
||||||
<%= f.fields_for :value, Struct.new(:subject, :body).new(*f.object.value.values_at('subject', 'body')) do |ff| %>
|
<%= f.fields_for :value, Struct.new(:subject, :body).new(*f.object.value.values_at('subject', 'body')) do |ff| %>
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<%= ff.label :subject, class: 'label' %>
|
<div class="flex items-center">
|
||||||
<%= ff.text_field :subject, required: true, class: 'base-input' %>
|
<%= ff.label :subject, class: 'label' %>
|
||||||
|
<span class="tooltip" data-tip="Use following placeholders text: <%= AccountConfig::DEFAULT_VALUES.dig(AccountConfig::SUBMITTER_COMPLETED_EMAIL_KEY, 'subject').scan(/{{.*?}}/).join(', ') %>">
|
||||||
|
<%= svg_icon('info_circle', class: 'w-4 h-4') %>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<%= ff.text_field :subject, required: true, class: 'base-input', dir: 'auto' %>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<%= ff.label :body, class: 'label' %>
|
<div class="flex items-center">
|
||||||
|
<%= ff.label :body, class: 'label' %>
|
||||||
|
<span class="tooltip" data-tip="Use following placeholders text: <%= AccountConfig::DEFAULT_VALUES.dig(AccountConfig::SUBMITTER_COMPLETED_EMAIL_KEY, 'body').scan(/{{.*?}}/).join(', ') %>">
|
||||||
|
<%= svg_icon('info_circle', class: 'w-4 h-4') %>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
<autoresize-textarea>
|
<autoresize-textarea>
|
||||||
<%= ff.text_area :body, required: true, class: 'base-input w-full py-2' %>
|
<%= ff.text_area :body, required: true, class: 'base-input w-full py-2', dir: 'auto' %>
|
||||||
</autoresize-textarea>
|
</autoresize-textarea>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -6,11 +6,11 @@
|
|||||||
<div class="grid md:grid-cols-2 gap-4">
|
<div class="grid md:grid-cols-2 gap-4">
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<%= f.label :first_name, class: 'label' %>
|
<%= f.label :first_name, class: 'label' %>
|
||||||
<%= f.text_field :first_name, required: true, class: 'base-input' %>
|
<%= f.text_field :first_name, required: true, class: 'base-input', dir: 'auto' %>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<%= f.label :last_name, class: 'label' %>
|
<%= f.label :last_name, class: 'label' %>
|
||||||
<%= f.text_field :last_name, required: true, class: 'base-input' %>
|
<%= f.text_field :last_name, required: true, class: 'base-input', dir: 'auto' %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
@@ -25,7 +25,8 @@
|
|||||||
<p class="text-2xl font-bold mt-8 mb-4">Signature</p>
|
<p class="text-2xl font-bold mt-8 mb-4">Signature</p>
|
||||||
<% signature = UserConfigs.load_signature(current_user) %>
|
<% signature = UserConfigs.load_signature(current_user) %>
|
||||||
<% if signature %>
|
<% if signature %>
|
||||||
<div class="flex justify-center mb-4">
|
<div class="flex justify-center mb-4 relative">
|
||||||
|
<%= button_to button_title(title: 'Remove', disabled_with: 'Removing'), user_signature_path, method: :delete, class: 'right-0 top-0 absolute link' %>
|
||||||
<img src="<%= signature.url %>" style="max-height: 200px; width: auto" width="<%= signature.metadata['width'] %>" height="<%= signature.metadata['height'] %>">
|
<img src="<%= signature.url %>" style="max-height: 200px; width: auto" width="<%= signature.metadata['width'] %>" height="<%= signature.metadata['height'] %>">
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -116,6 +116,11 @@
|
|||||||
<a href="mailto:<%= Docuseal::SUPPORT_EMAIL %>" target="_blank" class="w-full block mt-4 underline text-center">
|
<a href="mailto:<%= Docuseal::SUPPORT_EMAIL %>" target="_blank" class="w-full block mt-4 underline text-center">
|
||||||
<%= Docuseal::SUPPORT_EMAIL %>
|
<%= Docuseal::SUPPORT_EMAIL %>
|
||||||
</a>
|
</a>
|
||||||
|
<% if Docuseal.version.present? %>
|
||||||
|
<a href="https://github.com/docusealco/docuseal/releases" target="_blank" class="badge badge-outline text-xs block mx-auto mt-4">
|
||||||
|
v<%= Docuseal.version %>
|
||||||
|
</a>
|
||||||
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<p class="font-bold">Send signature requests via SMS</p>
|
<p class="font-bold">Send signature requests via SMS</p>
|
||||||
<p class="text-gray-700">
|
<p class="text-gray-700">
|
||||||
Unlock with DocuSeal Enterprise
|
Unlock with DocuSeal Pro
|
||||||
<br>
|
<br>
|
||||||
<a class="link font-medium" target="_blank" href="<%= "#{Docuseal::CONSOLE_URL}/#{Docuseal.multitenant? ? 'plans' : 'on_premise'}" %>">Learn More</a>
|
<a class="link font-medium" target="_blank" href="<%= "#{Docuseal::CONSOLE_URL}/#{Docuseal.multitenant? ? 'plans' : 'on_premise'}" %>">Learn More</a>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<p class="font-bold">Single Sign On with SAML 2.0</p>
|
<p class="font-bold">Single Sign On with SAML 2.0</p>
|
||||||
<p class="text-gray-700">
|
<p class="text-gray-700">
|
||||||
Unlock with DocuSeal Enterprise
|
Unlock with DocuSeal Pro
|
||||||
<br>
|
<br>
|
||||||
<a class="link font-medium" target="_blank" href="<%= "#{Docuseal::CONSOLE_URL}/#{Docuseal.multitenant? ? 'plans' : 'on_premise'}" %>">Learn More</a>
|
<a class="link font-medium" target="_blank" href="<%= "#{Docuseal::CONSOLE_URL}/#{Docuseal.multitenant? ? 'plans' : 'on_premise'}" %>">Learn More</a>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
<% if @template.submitters.to_a.size == 1 %>
|
<% if @template.submitters.to_a.size == 1 %>
|
||||||
<div>
|
<div>
|
||||||
<%= button_to button_title(title: 'Resubmit', disabled_with: 'Resubmit', icon: svg_icon('reload', class: 'w-6 h-6')), start_form_path(@template.slug), params: { submitter: { email: params[:email] }, resubmit: true }, method: :put, form: { onsubmit: 'event.submitter.disabled = true' }, class: 'white-button w-full' %>
|
<%= button_to button_title(title: 'Resubmit', disabled_with: 'Resubmit', icon: svg_icon('reload', class: 'w-6 h-6')), start_form_path(@template.slug), params: { submitter: { email: params[:email] }, resubmit: @submitter.slug }, method: :put, form: { onsubmit: 'event.submitter.disabled = true' }, class: 'white-button w-full' %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
<div class="text-center w-full space-y-6">
|
<div class="text-center w-full space-y-6">
|
||||||
<%= render 'banner' %>
|
<%= render 'banner' %>
|
||||||
<% unless @template.deleted_at? %>
|
<% unless @template.archived_at? %>
|
||||||
<p class="text-xl font-semibold text-center">You have been invited to submit a form</p>
|
<p class="text-xl font-semibold text-center">You have been invited to submit a form</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p class="text-lg font-bold mb-1"><%= @template.name %></p>
|
<p class="text-lg font-bold mb-1"><%= @template.name %></p>
|
||||||
<% if @template.deleted_at? %>
|
<% if @template.archived_at? %>
|
||||||
<p class="text-sm">Form has been deleted by <span class="font-semibold"><%= @template.account.name %></span>.</p>
|
<p class="text-sm">Form has been deleted by <span class="font-semibold"><%= @template.account.name %></span>.</p>
|
||||||
<% else %>
|
<% else %>
|
||||||
<p class="text-sm">Invited by <span class="font-semibold"><%= @template.account.name %></span></p>
|
<p class="text-sm">Invited by <span class="font-semibold"><%= @template.account.name %></span></p>
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% unless @template.deleted_at? %>
|
<% unless @template.archived_at? %>
|
||||||
<%= form_for @submitter, url: start_form_path(@template.slug), data: { turbo_frame: :_top }, method: :put, html: { class: 'space-y-4', onsubmit: 'event.submitter.disabled = true' } do |f| %>
|
<%= form_for @submitter, url: start_form_path(@template.slug), data: { turbo_frame: :_top }, method: :put, html: { class: 'space-y-4', onsubmit: 'event.submitter.disabled = true' } do |f| %>
|
||||||
<div class="form-control !mt-0">
|
<div class="form-control !mt-0">
|
||||||
<%= f.label :email, class: 'label' %>
|
<%= f.label :email, class: 'label' %>
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
<input type="hidden" name="submission[1][submitters][][uuid]" value="<%= item['uuid'] %>">
|
<input type="hidden" name="submission[1][submitters][][uuid]" value="<%= item['uuid'] %>">
|
||||||
<submitters-autocomplete data-field="name">
|
<submitters-autocomplete data-field="name">
|
||||||
<%= tag.input type: 'text', name: 'submission[1][submitters][][name]', autocomplete: 'off', class: 'input input-sm input-bordered w-full', placeholder: 'Name', required: index.zero?, value: params[:selfsign] && index.zero? ? current_user.full_name : '' %>
|
<%= tag.input type: 'text', name: 'submission[1][submitters][][name]', autocomplete: 'off', class: 'input input-sm input-bordered w-full', placeholder: 'Name', required: index.zero?, value: params[:selfsign] && index.zero? ? current_user.full_name : '', dir: 'auto' %>
|
||||||
</submitters-autocomplete>
|
</submitters-autocomplete>
|
||||||
<div class="grid <%= 'md:grid-cols-2 gap-1' if template.submitters.size == 1 %>">
|
<div class="grid <%= 'md:grid-cols-2 gap-1' if template.submitters.size == 1 %>">
|
||||||
<submitters-autocomplete data-field="email">
|
<submitters-autocomplete data-field="email">
|
||||||
|
|||||||
@@ -23,14 +23,14 @@
|
|||||||
</submitters-autocomplete>
|
</submitters-autocomplete>
|
||||||
<% if template.submitters.size > 1 %>
|
<% if template.submitters.size > 1 %>
|
||||||
<submitters-autocomplete data-field="name">
|
<submitters-autocomplete data-field="name">
|
||||||
<input type="text" name="submission[1][submitters][][name]" autocomplete="off" class="input input-sm input-bordered mt-1.5 w-full" placeholder="Name (optional)" value="<%= params[:selfsign] && index.zero? ? current_user.full_name : '' %>">
|
<input type="text" name="submission[1][submitters][][name]" autocomplete="off" class="input input-sm input-bordered mt-1.5 w-full" placeholder="Name (optional)" value="<%= params[:selfsign] && index.zero? ? current_user.full_name : '' %>" dir="auto">
|
||||||
</submitters-autocomplete>
|
</submitters-autocomplete>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% if template.submitters.size == 1 %>
|
<% if template.submitters.size == 1 %>
|
||||||
<div class="form-control flex">
|
<div class="form-control flex">
|
||||||
<submitters-autocomplete data-field="name">
|
<submitters-autocomplete data-field="name">
|
||||||
<input type="text" name="submission[1][submitters][][name]" autocomplete="off" class="input input-sm input-bordered w-full" placeholder="Name (optional)" value="<%= params[:selfsign] && index.zero? ? current_user.full_name : '' %>">
|
<input type="text" name="submission[1][submitters][][name]" autocomplete="off" class="input input-sm input-bordered w-full" placeholder="Name (optional)" value="<%= params[:selfsign] && index.zero? ? current_user.full_name : '' %>" dir="auto">
|
||||||
</submitters-autocomplete>
|
</submitters-autocomplete>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -33,12 +33,17 @@
|
|||||||
<div class="form-control space-y-2">
|
<div class="form-control space-y-2">
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<%= f.label :subject, class: 'label' %>
|
<%= f.label :subject, class: 'label' %>
|
||||||
<%= f.text_field :subject, value: config.value['subject'], required: true, class: '!text-sm base-input w-full' %>
|
<%= f.text_field :subject, value: config.value['subject'], required: true, class: '!text-sm base-input w-full', dir: 'auto' %>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<%= f.label :message, 'Body', class: 'label' %>
|
<div class="flex items-center">
|
||||||
|
<%= f.label :message, 'Body', class: 'label' %>
|
||||||
|
<span class="tooltip tooltip-right" data-tip="Use following placeholders text: <%= AccountConfig::DEFAULT_VALUES.dig(AccountConfig::SUBMITTER_INVITATION_EMAIL_KEY, 'body').scan(/{{.*?}}/).join(', ') %>">
|
||||||
|
<%= svg_icon('info_circle', class: 'w-4 h-4') %>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
<autoresize-textarea>
|
<autoresize-textarea>
|
||||||
<%= f.text_area :body, value: config.value['body'], required: true, class: 'base-textarea w-full', rows: 10 %>
|
<%= f.text_area :body, value: config.value['body'], required: true, class: 'base-textarea w-full', rows: 10, dir: 'auto' %>
|
||||||
</autoresize-textarea>
|
</autoresize-textarea>
|
||||||
</div>
|
</div>
|
||||||
<%= render 'message_fields' %>
|
<%= render 'message_fields' %>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<field-value class="flex absolute text-[1.5vw] lg:text-base" style="width: <%= area['w'] * 100 %>%; height: <%= area['h'] * 100 %>%; left: <%= area['x'] * 100 %>%; top: <%= area['y'] * 100 %>%">
|
<field-value dir="auto" class="flex absolute text-[1.5vw] lg:text-base" style="width: <%= area['w'] * 100 %>%; height: <%= area['h'] * 100 %>%; left: <%= area['x'] * 100 %>%; top: <%= area['y'] * 100 %>%">
|
||||||
<% if field['type'].in?(['signature', 'image', 'initials', 'stamp']) %>
|
<% if field['type'].in?(['signature', 'image', 'initials', 'stamp']) %>
|
||||||
<img class="object-contain mx-auto" src="<%= attachments_index[value].url %>" loading="lazy">
|
<img class="object-contain mx-auto" src="<%= attachments_index[value].url %>" loading="lazy">
|
||||||
<% elsif field['type'].in?(['file', 'payment']) %>
|
<% elsif field['type'].in?(['file', 'payment']) %>
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
<%= svg_icon('check', class: "aspect-square #{area['w'] > area['h'] ? '!w-auto !h-full' : '!w-full !h-auto'}") %>
|
<%= svg_icon('check', class: "aspect-square #{area['w'] > area['h'] ? '!w-auto !h-full' : '!w-full !h-auto'}") %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% elsif field['type'] == 'cells' %>
|
<% elsif field['type'] == 'cells' && area['cell_w'] %>
|
||||||
<% cell_width = area['cell_w'] / area['w'] * 100 %>
|
<% cell_width = area['cell_w'] / area['w'] * 100 %>
|
||||||
<div class="w-full flex items-center">
|
<div class="w-full flex items-center">
|
||||||
<% (0..(area['w'] / area['cell_w']).ceil).each do |index| %>
|
<% (0..(area['w'] / area['cell_w']).ceil).each do |index| %>
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
<% document = @submission.template_schema_documents.find { |a| item['attachment_uuid'] == a.uuid } %>
|
<% document = @submission.template_schema_documents.find { |a| item['attachment_uuid'] == a.uuid } %>
|
||||||
<a href="#<%= "page-#{document.uuid}-0" %>" onclick="[event.preventDefault(), window[event.target.closest('a').href.split('#')[1]].scrollIntoView({ behavior: 'smooth', block: 'start' })]" class="block cursor-pointer">
|
<a href="#<%= "page-#{document.uuid}-0" %>" onclick="[event.preventDefault(), window[event.target.closest('a').href.split('#')[1]].scrollIntoView({ behavior: 'smooth', block: 'start' })]" class="block cursor-pointer">
|
||||||
<img src="<%= document.preview_images.first.url %>" width="<%= document.preview_images.first.metadata['width'] %>" height="<%= document.preview_images.first.metadata['height'] %>" class="rounded border" loading="lazy">
|
<img src="<%= document.preview_images.first.url %>" width="<%= document.preview_images.first.metadata['width'] %>" height="<%= document.preview_images.first.metadata['height'] %>" class="rounded border" loading="lazy">
|
||||||
<div class="pb-2 pt-1.5 text-center">
|
<div class="pb-2 pt-1.5 text-center" dir="auto">
|
||||||
<%= item['name'].presence || document.filename.base %>
|
<%= item['name'].presence || document.filename.base %>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
@@ -74,13 +74,14 @@
|
|||||||
<div class="hidden md:block relative w-80 flex-none pt-0.5 pr-4 pl-0.5 overflow-auto space">
|
<div class="hidden md:block relative w-80 flex-none pt-0.5 pr-4 pl-0.5 overflow-auto space">
|
||||||
<% colors = %w[bg-red-500 bg-sky-500 bg-emerald-500 bg-yellow-300 bg-purple-600 bg-pink-500 bg-cyan-500 bg-orange-500 bg-lime-500 bg-indigo-500] %>
|
<% colors = %w[bg-red-500 bg-sky-500 bg-emerald-500 bg-yellow-300 bg-purple-600 bg-pink-500 bg-cyan-500 bg-orange-500 bg-lime-500 bg-indigo-500] %>
|
||||||
<% submitter_fields_index = (@submission.template_fields || @submission.template.fields).group_by { |f| f['submitter_uuid'] } %>
|
<% submitter_fields_index = (@submission.template_fields || @submission.template.fields).group_by { |f| f['submitter_uuid'] } %>
|
||||||
|
<% submitter_field_counters = Hash.new { 0 } %>
|
||||||
<% (@submission.template_submitters || @submission.template.submitters).each_with_index do |item, index| %>
|
<% (@submission.template_submitters || @submission.template.submitters).each_with_index do |item, index| %>
|
||||||
<% submitter = @submission.submitters.find { |e| e.uuid == item['uuid'] } %>
|
<% submitter = @submission.submitters.find { |e| e.uuid == item['uuid'] } %>
|
||||||
<div class="sticky -top-1 bg-base-100 pt-1 -mt-1 z-10">
|
<div class="sticky -top-1 bg-base-100 pt-1 -mt-1 z-10">
|
||||||
<div class="border border-base-300 rounded-md px-2 py-1 mb-1">
|
<div class="border border-base-300 rounded-md px-2 py-1 mb-1">
|
||||||
<div class="flex items-center space-x-1">
|
<div class="flex items-center space-x-1">
|
||||||
<span class="mx-1 w-3 h-3 rounded-full <%= colors[index] %>"></span>
|
<span class="mx-1 w-3 h-3 rounded-full <%= colors[index] %>"></span>
|
||||||
<span class="text-lg">
|
<span class="text-lg" dir="auto">
|
||||||
<%= (@submission.template_submitters || @submission.template.submitters).find { |e| e['uuid'] == submitter&.uuid }&.dig('name') || "#{(index + 1).ordinalize} Submitter" %>
|
<%= (@submission.template_submitters || @submission.template.submitters).find { |e| e['uuid'] == submitter&.uuid }&.dig('name') || "#{(index + 1).ordinalize} Submitter" %>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -132,16 +133,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="px-1.5 mb-4">
|
<div class="px-1.5 mb-4">
|
||||||
<% submitter_field_counters = Hash.new { 0 } %>
|
|
||||||
<% submitter_fields_index[item['uuid']].to_a.each_with_index do |field, index| %>
|
<% submitter_fields_index[item['uuid']].to_a.each_with_index do |field, index| %>
|
||||||
<% submitter_field_counters[field['type']] += 1 %>
|
<% submitter_field_counters[field['type']] += 1 %>
|
||||||
<% value = values[field['uuid']] %>
|
<% value = values[field['uuid']] %>
|
||||||
<% next if value.blank? %>
|
<% next if value.blank? %>
|
||||||
<div class="pt-2.5 border-b border-base-300">
|
<div class="pt-2.5 border-b border-base-300">
|
||||||
<div class="text-xs font-medium uppercase mb-0.5">
|
<div class="text-xs font-medium uppercase mb-0.5" dir="auto">
|
||||||
<%= field['name'].presence || "#{field['type'].titleize} Field #{submitter_field_counters[field['type']]}" %>
|
<%= field['name'].presence || "#{field['type'].titleize} Field #{submitter_field_counters[field['type']]}" %>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div dir="auto">
|
||||||
<% if field['type'].in?(%w[signature initials]) %>
|
<% if field['type'].in?(%w[signature initials]) %>
|
||||||
<div class="w-full bg-base-300 py-1">
|
<div class="w-full bg-base-300 py-1">
|
||||||
<img class="object-contain mx-auto" height="<%= attachments_index[value].metadata['height'] %>" width="<%= attachments_index[value].metadata['width'] %>" src="<%= attachments_index[value].url %>" loading="lazy">
|
<img class="object-contain mx-auto" height="<%= attachments_index[value].metadata['height'] %>" width="<%= attachments_index[value].metadata['width'] %>" src="<%= attachments_index[value].url %>" loading="lazy">
|
||||||
|
|||||||
@@ -39,7 +39,7 @@
|
|||||||
<% if @submitter.submission.template.submitters.size == 1 %>
|
<% if @submitter.submission.template.submitters.size == 1 %>
|
||||||
<div class="divider">OR</div>
|
<div class="divider">OR</div>
|
||||||
<div>
|
<div>
|
||||||
<%= button_to button_title(title: 'Resubmit', disabled_with: 'Resubmit', icon: svg_icon('reload', class: 'w-6 h-6')), start_form_path(@submitter.submission.template.slug), params: { submitter: { email: @submitter.email, phone: @submitter.phone, name: @submitter.name }, resubmit: true }, method: :put, form: { onsubmit: 'event.submitter.disabled = true' }, class: 'white-button w-full' %>
|
<%= button_to button_title(title: 'Resubmit', disabled_with: 'Resubmit', icon: svg_icon('reload', class: 'w-6 h-6')), start_form_path(@submitter.submission.template.slug), params: { submitter: { email: @submitter.email, phone: @submitter.phone, name: @submitter.name }, resubmit: @submitter.slug }, method: :put, form: { onsubmit: 'event.submitter.disabled = true' }, class: 'white-button w-full' %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<%= render 'shared/turbo_modal', title: 'Rename Folder' do %>
|
<%= render 'shared/turbo_modal', title: 'Rename Folder' do %>
|
||||||
<%= form_for @template_folder, url: folder_path(@template_folder), data: { turbo_frame: :_top }, html: { autocomplete: :off } do |f| %>
|
<%= form_for @template_folder, url: folder_path(@template_folder), data: { turbo_frame: :_top }, html: { autocomplete: :off } do |f| %>
|
||||||
<div class="form-control my-6">
|
<div class="form-control my-6">
|
||||||
<%= f.text_field :name, required: true, placeholder: 'Folder Name...', class: 'base-input w-full', autofocus: true %>
|
<%= f.text_field :name, required: true, placeholder: 'Folder Name...', class: 'base-input w-full', autofocus: true, dir: 'auto' %>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<%= f.button button_title(title: 'Rename', disabled_with: 'Saving'), class: 'base-button' %>
|
<%= f.button button_title(title: 'Rename', disabled_with: 'Saving'), class: 'base-button' %>
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
<% if can?(:create, ::Template) %>
|
<% if can?(:create, ::Template) %>
|
||||||
<%= render 'templates/upload_button', folder_name: @template_folder.name %>
|
<%= render 'templates/upload_button', folder_name: @template_folder.name %>
|
||||||
<%= link_to new_template_path(folder_name: @template_folder.name), class: 'btn btn-primary text-base btn-md gap-2', data: { turbo_frame: :modal } do %>
|
<%= link_to new_template_path(folder_name: @template_folder.name), class: 'white-button !border gap-2', data: { turbo_frame: :modal } do %>
|
||||||
<%= svg_icon('plus', class: 'w-6 h-6 stroke-2') %>
|
<%= svg_icon('plus', class: 'w-6 h-6 stroke-2') %>
|
||||||
<span class="hidden md:block">Create</span>
|
<span class="hidden md:block">Create</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<% status_badges = { 'awaiting' => 'badge-info', 'sent' => 'badge-info', 'completed' => 'badge-success', 'opened' => 'badge-warning' } %>
|
<% status_badges = { 'awaiting' => 'badge-info', 'sent' => 'badge-info', 'completed' => 'badge-success', 'opened' => 'badge-warning' } %>
|
||||||
<a href="<%= submission_path(submission) %>" class="bg-base-200 w-full flex flex-col md:flex-row space-y-4 md:space-y-0 md:justify-between rounded-2xl px-6 py-5 md:items-center">
|
<a href="<%= submission_path(submission) %>" class="bg-base-200 w-full flex flex-col md:flex-row space-y-4 md:space-y-0 md:justify-between rounded-2xl px-6 py-5 md:items-center">
|
||||||
<% submitters = (submission.template_submitters || submission.template.submitters).filter_map { |item| submission.submitters.find { |e| e.uuid == item['uuid'] } } %>
|
<% submitters = (submission.template_submitters || submission.template.submitters).filter_map { |item| submission.submitters.find { |e| e.uuid == item['uuid'] } } %>
|
||||||
<% is_submission_completed = submitters.all?(&:completed_at?) %>
|
<% is_submission_completed = submitters.all?(&:completed_at?) && submitters.size.positive? %>
|
||||||
<% if submitters.size == 1 %>
|
<% if submitters.size == 1 %>
|
||||||
<div>
|
<div>
|
||||||
<% submitter = submitters.first %>
|
<% submitter = submitters.first %>
|
||||||
@@ -49,7 +49,7 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<span class="btn btn-outline btn-sm w-20 md:w-24">View</span>
|
<span class="btn btn-outline btn-sm w-20 md:w-24">View</span>
|
||||||
<% if !submission.deleted_at? && can?(:destroy, submission) %>
|
<% if !submission.archived_at? && can?(:destroy, submission) %>
|
||||||
<%= button_to button_title(title: nil, disabled_with: 'Remov', icon: svg_icon('trash', class: 'w-6 h-6')), submission_path(submission), class: 'btn btn-outline btn-sm', form: { class: 'flex' }, title: 'Delete', method: :delete, data: { turbo_confirm: 'Are you sure?' }, onclick: 'event.stopPropagation()' %>
|
<%= button_to button_title(title: nil, disabled_with: 'Remov', icon: svg_icon('trash', class: 'w-6 h-6')), submission_path(submission), class: 'btn btn-outline btn-sm', form: { class: 'flex' }, title: 'Delete', method: :delete, data: { turbo_confirm: 'Are you sure?' }, onclick: 'event.stopPropagation()' %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
@@ -134,7 +134,7 @@
|
|||||||
</form>
|
</form>
|
||||||
<% end %>
|
<% end %>
|
||||||
<span class="btn btn-outline btn-sm w-20 md:w-24">View</span>
|
<span class="btn btn-outline btn-sm w-20 md:w-24">View</span>
|
||||||
<% unless submission.deleted_at? %>
|
<% unless submission.archived_at? %>
|
||||||
<%= button_to button_title(title: nil, disabled_with: 'Remov', icon: svg_icon('trash', class: 'w-6 h-6')), submission_path(submission), class: 'btn btn-outline btn-sm', form: { class: 'flex' }, title: 'Delete', method: :delete, data: { turbo_confirm: 'Are you sure?' }, onclick: 'event.stopPropagation()' %>
|
<%= button_to button_title(title: nil, disabled_with: 'Remov', icon: svg_icon('trash', class: 'w-6 h-6')), submission_path(submission), class: 'btn btn-outline btn-sm', form: { class: 'flex' }, title: 'Delete', method: :delete, data: { turbo_confirm: 'Are you sure?' }, onclick: 'event.stopPropagation()' %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
<%= svg_icon('calendar', class: 'w-4 h-4') %>
|
<%= svg_icon('calendar', class: 'w-4 h-4') %>
|
||||||
<span><%= l(template.created_at.in_time_zone(current_account.timezone), format: :short, locale: current_account.locale) %></span>
|
<span><%= l(template.created_at.in_time_zone(current_account.timezone), format: :short, locale: current_account.locale) %></span>
|
||||||
</span>
|
</span>
|
||||||
<% if template.deleted_at? %>
|
<% if template.archived_at? %>
|
||||||
<span class="flex items-center space-x-1 w-1/2">
|
<span class="flex items-center space-x-1 w-1/2">
|
||||||
<%= svg_icon('folder', class: 'w-4 h-4 flex-shrink-0') %>
|
<%= svg_icon('folder', class: 'w-4 h-4 flex-shrink-0') %>
|
||||||
<span class="truncate"><%= template.folder.name %></span>
|
<span class="truncate"><%= template.folder.name %></span>
|
||||||
@@ -24,14 +24,14 @@
|
|||||||
</a>
|
</a>
|
||||||
<div class="absolute top-0 bottom-0 w-0 flex items-center hidden md:group-hover:flex" style="right: 37px">
|
<div class="absolute top-0 bottom-0 w-0 flex items-center hidden md:group-hover:flex" style="right: 37px">
|
||||||
<div class="space-y-1">
|
<div class="space-y-1">
|
||||||
<% if can?(:update, template) && !template.deleted_at? %>
|
<% if can?(:update, template) && !template.archived_at? %>
|
||||||
<span class="tooltip tooltip-left" data-tip="Move">
|
<span class="tooltip tooltip-left" data-tip="Move">
|
||||||
<a href="<%= edit_template_folder_path(template.id) %>" data-turbo-frame="modal" class="btn btn-xs hover:btn-outline bg-base-200 btn-circle">
|
<a href="<%= edit_template_folder_path(template.id) %>" data-turbo-frame="modal" class="btn btn-xs hover:btn-outline bg-base-200 btn-circle">
|
||||||
<%= svg_icon('folder_share', class: 'w-4 h-4') %>
|
<%= svg_icon('folder_share', class: 'w-4 h-4') %>
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if template.deleted_at? && can?(:update, template) %>
|
<% if template.archived_at? && can?(:update, template) %>
|
||||||
<span class="tooltip tooltip-left" data-tip="Restore">
|
<span class="tooltip tooltip-left" data-tip="Restore">
|
||||||
<%= button_to template_restore_index_path(template), class: 'btn btn-xs hover:btn-outline bg-base-200 btn-circle' do %>
|
<%= button_to template_restore_index_path(template), class: 'btn btn-xs hover:btn-outline bg-base-200 btn-circle' do %>
|
||||||
<%= svg_icon('rotate', class: 'w-4 h-4 enabled') %>
|
<%= svg_icon('rotate', class: 'w-4 h-4 enabled') %>
|
||||||
@@ -53,9 +53,9 @@
|
|||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if can?(:destroy, template) %>
|
<% if can?(:destroy, template) %>
|
||||||
<span class="tooltip tooltip-left" data-tip="<%= template.deleted_at? ? 'Delete' : 'Archive' %>">
|
<span class="tooltip tooltip-left" data-tip="<%= template.archived_at? ? 'Delete' : 'Archive' %>">
|
||||||
<%= button_to template_path(template), data: { turbo_confirm: template.deleted_at? ? 'Template deletion is irreversible and will permanently remove all associated signed documents with it. Are you sure?' : 'Are you sure?' }, params: { permanently: template.deleted_at? }.compact_blank, method: :delete, class: 'btn btn-xs hover:btn-outline bg-base-200 btn-circle', aria_label: 'Restore' do %>
|
<%= button_to template_path(template), data: { turbo_confirm: template.archived_at? ? 'Template deletion is irreversible and will permanently remove all associated signed documents with it. Are you sure?' : 'Are you sure?' }, params: { permanently: template.archived_at? }.compact_blank, method: :delete, class: 'btn btn-xs hover:btn-outline bg-base-200 btn-circle', aria_label: 'Restore' do %>
|
||||||
<%= svg_icon(template.deleted_at? ? 'trash' : 'archive', class: 'w-4 h-4 enabled') %>
|
<%= svg_icon(template.archived_at? ? 'trash' : 'archive', class: 'w-4 h-4 enabled') %>
|
||||||
<%= svg_icon('loader', class: 'w-4 h-4 animate-spin disabled') %>
|
<%= svg_icon('loader', class: 'w-4 h-4 animate-spin disabled') %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<h1 class="text-4xl font-semibold mr-4" style="overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;">
|
<h1 class="text-4xl font-semibold mr-4" style="overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;">
|
||||||
<%= template.name %>
|
<%= template.name %>
|
||||||
<% if template.deleted_at? %>
|
<% if template.archived_at? %>
|
||||||
<span class="badge badge-outline badge-lg align-middle">Archived</span>
|
<span class="badge badge-outline badge-lg align-middle">Archived</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</h1>
|
</h1>
|
||||||
@@ -23,10 +23,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex md:justify-between space-x-2 flex-none pt-1">
|
<div class="flex md:justify-between space-x-2 flex-none pt-1">
|
||||||
<% if !template.deleted_at? %>
|
<% if !template.archived_at? %>
|
||||||
<%= render 'shared/clipboard_copy', text: start_form_url(slug: @template.slug), class: 'btn btn-sm btn-neutral text-white', icon_class: 'w-6 h-6 text-white', copy_title: 'Copy Link', copied_title: 'Copied', copy_title_md: 'Link', copied_title_md: 'Copied' %>
|
<%= render 'shared/clipboard_copy', text: start_form_url(slug: @template.slug), class: 'btn btn-sm btn-neutral text-white', icon_class: 'w-6 h-6 text-white', copy_title: 'Copy Link', copied_title: 'Copied', copy_title_md: 'Link', copied_title_md: 'Copied' %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if !template.deleted_at? && can?(:destroy, template) %>
|
<% if !template.archived_at? && can?(:destroy, template) %>
|
||||||
<%= button_to button_title(title: 'Archive', disabled_with: 'Archiving', title_class: 'hidden md:inline', icon: svg_icon('archive', class: 'w-6 h-6')), template_path(template), class: 'btn btn-outline btn-sm', method: :delete, data: { turbo_confirm: 'Are you sure?' } %>
|
<%= button_to button_title(title: 'Archive', disabled_with: 'Archiving', title_class: 'hidden md:inline', icon: svg_icon('archive', class: 'w-6 h-6')), template_path(template), class: 'btn btn-outline btn-sm', method: :delete, data: { turbo_confirm: 'Are you sure?' } %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if can?(:create, template) %>
|
<% if can?(:create, template) %>
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
<span class="hidden md:inline">Clone</span>
|
<span class="hidden md:inline">Clone</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if !template.deleted_at? && can?(:update, template) %>
|
<% if !template.archived_at? && can?(:update, template) %>
|
||||||
<%= link_to edit_template_path(template), class: 'btn btn-outline btn-sm' do %>
|
<%= link_to edit_template_path(template), class: 'btn btn-outline btn-sm' do %>
|
||||||
<span class="flex items-center justify-center space-x-2">
|
<span class="flex items-center justify-center space-x-2">
|
||||||
<%= svg_icon('pencil', class: 'w-6 h-6') %>
|
<%= svg_icon('pencil', class: 'w-6 h-6') %>
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if template.deleted_at? && can?(:create, template) %>
|
<% if template.archived_at? && can?(:create, template) %>
|
||||||
<%= button_to button_title(title: 'Restore', disabled_with: 'Restoring', icon: svg_icon('rotate', class: 'w-6 h-6')), template_restore_index_path(template), class: 'btn btn-outline btn-sm' %>
|
<%= button_to button_title(title: 'Restore', disabled_with: 'Restoring', icon: svg_icon('rotate', class: 'w-6 h-6')), template_restore_index_path(template), class: 'btn btn-outline btn-sm' %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<%= hidden_field_tag :base_template_id, @base_template.id %>
|
<%= hidden_field_tag :base_template_id, @base_template.id %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<div class="form-control mt-6">
|
<div class="form-control mt-6">
|
||||||
<%= f.text_field :name, required: true, placeholder: 'Document Name', class: 'base-input' %>
|
<%= f.text_field :name, required: true, placeholder: 'Document Name', class: 'base-input', dir: 'auto' %>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-3 mb-4 flex items-center justify-between">
|
<div class="mt-3 mb-4 flex items-center justify-between">
|
||||||
<a href="#" onclick="[event.preventDefault(), window.folder_name.focus()]">
|
<a href="#" onclick="[event.preventDefault(), window.folder_name.focus()]">
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<%= svg_icon('download', class: 'w-6 h-6 stroke-2') %>
|
<%= svg_icon('download', class: 'w-6 h-6 stroke-2') %>
|
||||||
<span>Export</span>
|
<span>Export</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if !@template.deleted_at? && can?(:create, Submission) %>
|
<% if !@template.archived_at? && can?(:create, Submission) %>
|
||||||
<%= link_to new_template_submission_path(@template), class: 'white-button !border', data: { turbo_frame: 'modal' } do %>
|
<%= link_to new_template_submission_path(@template), class: 'white-button !border', data: { turbo_frame: 'modal' } do %>
|
||||||
<%= svg_icon('plus', class: 'w-6 h-6 stroke-2') %>
|
<%= svg_icon('plus', class: 'w-6 h-6 stroke-2') %>
|
||||||
<span>Add <span class="hidden md:inline">Recipients</span></span>
|
<span>Add <span class="hidden md:inline">Recipients</span></span>
|
||||||
@@ -56,7 +56,7 @@
|
|||||||
<%= render partial: 'submission', collection: @submissions %>
|
<%= render partial: 'submission', collection: @submissions %>
|
||||||
</div>
|
</div>
|
||||||
<% view_archived_html = capture do %>
|
<% view_archived_html = capture do %>
|
||||||
<% if @template.submissions.where.not(deleted_at: nil).exists? && !@template.deleted_at? %>
|
<% if @template.submissions.where.not(archived_at: nil).exists? && !@template.archived_at? %>
|
||||||
<div>
|
<div>
|
||||||
<a href="<%= template_archived_index_path(@template) %>" class="link text-sm">View Archived</a>
|
<a href="<%= template_archived_index_path(@template) %>" class="link text-sm">View Archived</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -74,7 +74,7 @@
|
|||||||
<div class="card-body text-center py-16">
|
<div class="card-body text-center py-16">
|
||||||
<div class="max-w-lg mx-auto">
|
<div class="max-w-lg mx-auto">
|
||||||
<p class="text-3xl font-bold text-base-content mb-4">There are no Submissions</p>
|
<p class="text-3xl font-bold text-base-content mb-4">There are no Submissions</p>
|
||||||
<% if @template.deleted_at.blank? && params[:q].blank? %>
|
<% if @template.archived_at.blank? && params[:q].blank? %>
|
||||||
<p>Send an invitation to fill and complete the form</p>
|
<p>Send an invitation to fill and complete the form</p>
|
||||||
<div class="space-y-2 flex flex-col">
|
<div class="space-y-2 flex flex-col">
|
||||||
<% if can?(:create, Submission) %>
|
<% if can?(:create, Submission) %>
|
||||||
|
|||||||
@@ -2,11 +2,11 @@
|
|||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<%= f.label :first_name, class: 'label' %>
|
<%= f.label :first_name, class: 'label' %>
|
||||||
<%= f.text_field :first_name, required: true, class: 'base-input' %>
|
<%= f.text_field :first_name, required: true, class: 'base-input', dir: 'auto' %>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<%= f.label :last_name, class: 'label' %>
|
<%= f.label :last_name, class: 'label' %>
|
||||||
<%= f.text_field :last_name, required: true, class: 'base-input' %>
|
<%= f.text_field :last_name, required: true, class: 'base-input', dir: 'auto' %>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<%= f.label :email, class: 'label' %>
|
<%= f.label :email, class: 'label' %>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
<a class="text-sm mt-3 px-4 py-2 bg-base-300 rounded-full block" target="_blank" href="<%= "#{Docuseal::CONSOLE_URL}/#{Docuseal.multitenant? ? 'plans' : 'on_premise'}" %>">
|
<a class="text-sm mt-3 px-4 py-2 bg-base-300 rounded-full block" target="_blank" href="<%= "#{Docuseal::CONSOLE_URL}/#{Docuseal.multitenant? ? 'plans' : 'on_premise'}" %>">
|
||||||
<%= svg_icon('info_circle', class: 'w-4 h-4 inline align-text-bottom') %>
|
<%= svg_icon('info_circle', class: 'w-4 h-4 inline align-text-bottom') %>
|
||||||
Unlock more user roles with DocuSeal Enterprise.
|
Unlock more user roles with DocuSeal Pro.
|
||||||
<span class="link font-medium">Learn More</a>
|
<span class="link font-medium">Learn More</a>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -5,7 +5,7 @@ require "fileutils"
|
|||||||
APP_ROOT = File.expand_path("..", __dir__)
|
APP_ROOT = File.expand_path("..", __dir__)
|
||||||
|
|
||||||
def system!(*args)
|
def system!(*args)
|
||||||
system(*args) || abort("\n== Command #{args} failed ==")
|
system(*args, exception: true)
|
||||||
end
|
end
|
||||||
|
|
||||||
FileUtils.chdir APP_ROOT do
|
FileUtils.chdir APP_ROOT do
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ require 'action_controller/railtie'
|
|||||||
require 'action_view/railtie'
|
require 'action_view/railtie'
|
||||||
require 'action_mailer/railtie'
|
require 'action_mailer/railtie'
|
||||||
require 'active_job/railtie'
|
require 'active_job/railtie'
|
||||||
|
require 'rails/health_controller'
|
||||||
|
|
||||||
require_relative '../lib/api_path_consider_json_middleware'
|
require_relative '../lib/api_path_consider_json_middleware'
|
||||||
|
|
||||||
@@ -15,14 +16,13 @@ Bundler.require(*Rails.groups)
|
|||||||
|
|
||||||
module DocuSeal
|
module DocuSeal
|
||||||
class Application < Rails::Application
|
class Application < Rails::Application
|
||||||
config.load_defaults 7.0
|
config.load_defaults 7.1
|
||||||
|
|
||||||
config.autoload_paths << Rails.root.join('lib')
|
config.autoload_lib(ignore: %w[assets tasks])
|
||||||
config.eager_load_paths << Rails.root.join('lib')
|
|
||||||
|
|
||||||
config.active_storage.routes_prefix = ''
|
config.active_storage.routes_prefix = ''
|
||||||
|
|
||||||
config.i18n.available_locales = %i[en en-US en-GB es-ES fr-FR pt-PT de-DE]
|
config.i18n.available_locales = %i[en en-US en-GB es-ES fr-FR pt-PT de-DE pt]
|
||||||
config.i18n.fallbacks = [:en]
|
config.i18n.fallbacks = [:en]
|
||||||
|
|
||||||
config.exceptions_app = ->(env) { ErrorsController.action(:show).call(env) }
|
config.exceptions_app = ->(env) { ErrorsController.action(:show).call(env) }
|
||||||
@@ -32,6 +32,10 @@ module DocuSeal
|
|||||||
config.middleware.insert_before ActionDispatch::Static, Rack::Deflater
|
config.middleware.insert_before ActionDispatch::Static, Rack::Deflater
|
||||||
config.middleware.insert_before ActionDispatch::Static, ApiPathConsiderJsonMiddleware
|
config.middleware.insert_before ActionDispatch::Static, ApiPathConsiderJsonMiddleware
|
||||||
|
|
||||||
|
config.generators.system_tests = nil
|
||||||
|
|
||||||
|
autoloaders.once.do_not_eager_load("#{Turbo::Engine.root}/app/channels") # https://github.com/hotwired/turbo-rails/issues/512
|
||||||
|
|
||||||
ActiveSupport.run_load_hooks(:application_config, self)
|
ActiveSupport.run_load_hooks(:application_config, self)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -17,7 +17,7 @@ Rails.application.configure do
|
|||||||
# In the development environment your application's code is reloaded any time
|
# In the development environment your application's code is reloaded any time
|
||||||
# it changes. This slows down response time but is perfect for development
|
# it changes. This slows down response time but is perfect for development
|
||||||
# since you don't have to restart the web server when you make code changes.
|
# since you don't have to restart the web server when you make code changes.
|
||||||
config.cache_classes = false
|
config.enable_reloading = true
|
||||||
|
|
||||||
# Do not eager load code on boot.
|
# Do not eager load code on boot.
|
||||||
config.eager_load = false
|
config.eager_load = false
|
||||||
@@ -79,6 +79,8 @@ Rails.application.configure do
|
|||||||
key_derivation_salt: 'dev key derivation salt'
|
key_derivation_salt: 'dev key derivation salt'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ActiveRecord::Encryption.configure(**config.active_record.encryption)
|
||||||
|
|
||||||
# Raises error for missing translations.
|
# Raises error for missing translations.
|
||||||
# config.i18n.raise_on_missing_translations = true
|
# config.i18n.raise_on_missing_translations = true
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ Rails.application.configure do
|
|||||||
# Settings specified here will take precedence over those in config/application.rb.
|
# Settings specified here will take precedence over those in config/application.rb.
|
||||||
|
|
||||||
# Code is not reloaded between requests.
|
# Code is not reloaded between requests.
|
||||||
config.cache_classes = true
|
config.enable_reloading = false
|
||||||
|
|
||||||
config.public_file_server.headers = {
|
config.public_file_server.headers = {
|
||||||
'Cache-Control' => 'public, s-maxage=31536000, max-age=15552000',
|
'Cache-Control' => 'public, s-maxage=31536000, max-age=15552000',
|
||||||
@@ -115,6 +115,8 @@ Rails.application.configure do
|
|||||||
key_derivation_salt: Digest::SHA256.hexdigest(encryption_secret)
|
key_derivation_salt: Digest::SHA256.hexdigest(encryption_secret)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ActiveRecord::Encryption.configure(**config.active_record.encryption)
|
||||||
|
|
||||||
# Do not dump schema after migrations.
|
# Do not dump schema after migrations.
|
||||||
config.active_record.dump_schema_after_migration = false
|
config.active_record.dump_schema_after_migration = false
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ Rails.application.configure do
|
|||||||
# Settings specified here will take precedence over those in config/application.rb.
|
# Settings specified here will take precedence over those in config/application.rb.
|
||||||
|
|
||||||
# Turn false under Spring and add config.action_view.cache_template_loading = true.
|
# Turn false under Spring and add config.action_view.cache_template_loading = true.
|
||||||
config.cache_classes = true
|
config.enable_reloading = false
|
||||||
|
|
||||||
# Eager loading loads your whole application. When running a single test locally,
|
# Eager loading loads your whole application. When running a single test locally,
|
||||||
# this probably isn't necessary. It's a good idea to do in a continuous integration
|
# this probably isn't necessary. It's a good idea to do in a continuous integration
|
||||||
@@ -67,6 +67,8 @@ Rails.application.configure do
|
|||||||
key_derivation_salt: 'test key derivation salt'
|
key_derivation_salt: 'test key derivation salt'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ActiveRecord::Encryption.configure(**config.active_record.encryption)
|
||||||
|
|
||||||
config.middleware.use(Class.new do
|
config.middleware.use(Class.new do
|
||||||
def initialize(app)
|
def initialize(app)
|
||||||
@app = app
|
@app = app
|
||||||
|
|||||||
Loaded 100 of 119 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user