mirror of
https://github.com/docusealco/docuseal
synced 2026-11-04 14:42:32 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a92d1d82c1 | ||
|
|
2dcfc41365 | ||
|
|
15cbdfb20c | ||
|
|
a2cf8fc5b5 | ||
|
|
f94ef3afdc | ||
|
|
9e3a9f0eeb | ||
|
|
1280572e6f | ||
|
|
cba2e84338 | ||
|
|
17ca8f013a | ||
|
|
96ef7ffaf7 | ||
|
|
1bbcdb8073 | ||
|
|
da130c3f4f | ||
|
|
c15f3ecd83 | ||
|
|
df0e0e6a1b | ||
|
|
3d75443d6e | ||
|
|
b9af384cf2 | ||
|
|
c40a8e1a69 | ||
|
|
4c8a706906 | ||
|
|
4c8e38867a | ||
|
|
7ed1b71a6b | ||
|
|
6f3fe1dd7b | ||
|
|
f7ef99a624 | ||
|
|
03ad848d45 | ||
|
|
4ffd198505 | ||
|
|
d595e7d52b | ||
|
|
aabbf2dc7f | ||
|
|
95e4bf945c | ||
|
|
15523d8377 | ||
|
|
e2cda2bd5c | ||
|
|
12b98fdcd4 | ||
|
|
be208a1080 | ||
|
|
9268c60f9e | ||
|
|
52b310e1a8 | ||
|
|
16757acbe0 | ||
|
|
dcbdc9e60c | ||
|
|
254aa9e950 | ||
|
|
28290c4635 | ||
|
|
a72882ce0f | ||
|
|
fd0b57c651 | ||
|
|
780ced3e7f | ||
|
|
f8f11b6fa6 | ||
|
|
bdaaa5276c | ||
|
|
e149e08f33 | ||
|
|
47bd0571f9 | ||
|
|
5352a25ede | ||
|
|
ead316df00 | ||
|
|
dac7594aeb | ||
|
|
f4f226ac63 |
No files matched your search
@@ -30,3 +30,5 @@ linters:
|
|||||||
Enabled: false
|
Enabled: false
|
||||||
Style/ZeroLengthPredicate:
|
Style/ZeroLengthPredicate:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
Style/RedundantConstantBase:
|
||||||
|
Enabled: false
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
*.html linguist-detectable=false
|
||||||
@@ -4,29 +4,31 @@ source 'https://rubygems.org'
|
|||||||
|
|
||||||
ruby '3.2.2'
|
ruby '3.2.2'
|
||||||
|
|
||||||
gem 'audited'
|
gem 'aws-sdk-s3', require: false
|
||||||
gem 'aws-sdk-s3'
|
gem 'azure-storage-blob', require: false
|
||||||
gem 'azure-storage-blob'
|
|
||||||
gem 'bootsnap', require: false
|
gem 'bootsnap', require: false
|
||||||
gem 'devise'
|
gem 'devise'
|
||||||
gem 'dotenv', require: false
|
gem 'dotenv', require: false
|
||||||
gem 'faraday'
|
gem 'faraday'
|
||||||
gem 'google-cloud-storage'
|
gem 'google-cloud-storage', require: false
|
||||||
gem 'hexapdf'
|
gem 'hexapdf'
|
||||||
gem 'image_processing'
|
gem 'image_processing'
|
||||||
gem 'lograge'
|
gem 'lograge'
|
||||||
gem 'mysql2'
|
gem 'mysql2', require: false
|
||||||
gem 'oj'
|
gem 'oj'
|
||||||
|
gem 'omniauth-google-oauth2'
|
||||||
|
gem 'omniauth-rails_csrf_protection'
|
||||||
gem 'pagy'
|
gem 'pagy'
|
||||||
gem 'pg'
|
gem 'pg', require: false
|
||||||
gem 'premailer-rails'
|
gem 'premailer-rails'
|
||||||
gem 'puma'
|
gem 'puma'
|
||||||
gem 'rails'
|
gem 'rails'
|
||||||
gem 'rails-i18n'
|
gem 'rails-i18n'
|
||||||
gem 'rollbar'
|
gem 'rollbar', require: ENV.key?('ROLLBAR_ACCESS_TOKEN')
|
||||||
gem 'ruby-vips'
|
gem 'ruby-vips'
|
||||||
gem 'shakapacker'
|
gem 'shakapacker'
|
||||||
gem 'sqlite3'
|
gem 'sidekiq', require: ENV.key?('REDIS_URL')
|
||||||
|
gem 'sqlite3', require: false
|
||||||
gem 'strip_attributes'
|
gem 'strip_attributes'
|
||||||
gem 'turbo-rails'
|
gem 'turbo-rails'
|
||||||
gem 'tzinfo-data'
|
gem 'tzinfo-data'
|
||||||
|
|||||||
+40
-4
@@ -72,9 +72,6 @@ GEM
|
|||||||
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)
|
||||||
audited (5.3.3)
|
|
||||||
activerecord (>= 5.0, < 7.1)
|
|
||||||
request_store (~> 1.2)
|
|
||||||
aws-eventstream (1.2.0)
|
aws-eventstream (1.2.0)
|
||||||
aws-partitions (1.781.0)
|
aws-partitions (1.781.0)
|
||||||
aws-sdk-core (3.175.0)
|
aws-sdk-core (3.175.0)
|
||||||
@@ -235,6 +232,7 @@ GEM
|
|||||||
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.0.1)
|
||||||
|
hashie (5.0.0)
|
||||||
hexapdf (0.32.2)
|
hexapdf (0.32.2)
|
||||||
cmdparse (~> 3.0, >= 3.0.3)
|
cmdparse (~> 3.0, >= 3.0.3)
|
||||||
geom2d (~> 0.3)
|
geom2d (~> 0.3)
|
||||||
@@ -285,6 +283,7 @@ GEM
|
|||||||
minitest (5.18.1)
|
minitest (5.18.1)
|
||||||
msgpack (1.7.1)
|
msgpack (1.7.1)
|
||||||
multi_json (1.15.0)
|
multi_json (1.15.0)
|
||||||
|
multi_xml (0.6.0)
|
||||||
multipart-post (2.3.0)
|
multipart-post (2.3.0)
|
||||||
mysql2 (0.5.5)
|
mysql2 (0.5.5)
|
||||||
net-http-persistent (4.0.2)
|
net-http-persistent (4.0.2)
|
||||||
@@ -301,7 +300,29 @@ GEM
|
|||||||
nio4r (2.5.9)
|
nio4r (2.5.9)
|
||||||
nokogiri (1.15.2-arm64-darwin)
|
nokogiri (1.15.2-arm64-darwin)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
|
oauth2 (2.0.9)
|
||||||
|
faraday (>= 0.17.3, < 3.0)
|
||||||
|
jwt (>= 1.0, < 3.0)
|
||||||
|
multi_xml (~> 0.5)
|
||||||
|
rack (>= 1.2, < 4)
|
||||||
|
snaky_hash (~> 2.0)
|
||||||
|
version_gem (~> 1.1)
|
||||||
oj (3.15.0)
|
oj (3.15.0)
|
||||||
|
omniauth (2.1.1)
|
||||||
|
hashie (>= 3.4.6)
|
||||||
|
rack (>= 2.2.3)
|
||||||
|
rack-protection
|
||||||
|
omniauth-google-oauth2 (1.1.1)
|
||||||
|
jwt (>= 2.0)
|
||||||
|
oauth2 (~> 2.0.6)
|
||||||
|
omniauth (~> 2.0)
|
||||||
|
omniauth-oauth2 (~> 1.8.0)
|
||||||
|
omniauth-oauth2 (1.8.0)
|
||||||
|
oauth2 (>= 1.4, < 3)
|
||||||
|
omniauth (~> 2.0)
|
||||||
|
omniauth-rails_csrf_protection (1.0.1)
|
||||||
|
actionpack (>= 4.2)
|
||||||
|
omniauth (~> 2.0)
|
||||||
openssl (3.1.0)
|
openssl (3.1.0)
|
||||||
orm_adapter (0.5.0)
|
orm_adapter (0.5.0)
|
||||||
os (1.1.4)
|
os (1.1.4)
|
||||||
@@ -329,6 +350,8 @@ GEM
|
|||||||
nio4r (~> 2.0)
|
nio4r (~> 2.0)
|
||||||
racc (1.7.1)
|
racc (1.7.1)
|
||||||
rack (2.2.7)
|
rack (2.2.7)
|
||||||
|
rack-protection (3.0.6)
|
||||||
|
rack
|
||||||
rack-proxy (0.7.6)
|
rack-proxy (0.7.6)
|
||||||
rack
|
rack
|
||||||
rack-test (2.1.0)
|
rack-test (2.1.0)
|
||||||
@@ -365,6 +388,8 @@ GEM
|
|||||||
zeitwerk (~> 2.5)
|
zeitwerk (~> 2.5)
|
||||||
rainbow (3.1.1)
|
rainbow (3.1.1)
|
||||||
rake (13.0.6)
|
rake (13.0.6)
|
||||||
|
redis-client (0.14.1)
|
||||||
|
connection_pool
|
||||||
regexp_parser (2.8.1)
|
regexp_parser (2.8.1)
|
||||||
reline (0.3.5)
|
reline (0.3.5)
|
||||||
io-console (~> 0.5)
|
io-console (~> 0.5)
|
||||||
@@ -435,6 +460,11 @@ GEM
|
|||||||
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)
|
||||||
|
concurrent-ruby (< 2)
|
||||||
|
connection_pool (>= 2.3.0)
|
||||||
|
rack (>= 2.2.4)
|
||||||
|
redis-client (>= 0.14.0)
|
||||||
signet (0.17.0)
|
signet (0.17.0)
|
||||||
addressable (~> 2.8)
|
addressable (~> 2.8)
|
||||||
faraday (>= 0.17.5, < 3.a)
|
faraday (>= 0.17.5, < 3.a)
|
||||||
@@ -447,6 +477,9 @@ 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)
|
||||||
|
snaky_hash (2.0.1)
|
||||||
|
hashie
|
||||||
|
version_gem (~> 1.1, >= 1.1.1)
|
||||||
sqlite3 (1.5.4)
|
sqlite3 (1.5.4)
|
||||||
mini_portile2 (~> 2.8.0)
|
mini_portile2 (~> 2.8.0)
|
||||||
strip_attributes (1.13.0)
|
strip_attributes (1.13.0)
|
||||||
@@ -465,6 +498,7 @@ GEM
|
|||||||
uber (0.1.0)
|
uber (0.1.0)
|
||||||
unicode-display_width (2.4.2)
|
unicode-display_width (2.4.2)
|
||||||
uniform_notifier (1.16.0)
|
uniform_notifier (1.16.0)
|
||||||
|
version_gem (1.1.3)
|
||||||
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.0)
|
||||||
@@ -489,7 +523,6 @@ PLATFORMS
|
|||||||
|
|
||||||
DEPENDENCIES
|
DEPENDENCIES
|
||||||
annotate
|
annotate
|
||||||
audited
|
|
||||||
aws-sdk-s3
|
aws-sdk-s3
|
||||||
azure-storage-blob
|
azure-storage-blob
|
||||||
better_html
|
better_html
|
||||||
@@ -511,6 +544,8 @@ DEPENDENCIES
|
|||||||
lograge
|
lograge
|
||||||
mysql2
|
mysql2
|
||||||
oj
|
oj
|
||||||
|
omniauth-google-oauth2
|
||||||
|
omniauth-rails_csrf_protection
|
||||||
pagy
|
pagy
|
||||||
pg
|
pg
|
||||||
premailer-rails
|
premailer-rails
|
||||||
@@ -526,6 +561,7 @@ DEPENDENCIES
|
|||||||
rubocop-rspec
|
rubocop-rspec
|
||||||
ruby-vips
|
ruby-vips
|
||||||
shakapacker
|
shakapacker
|
||||||
|
sidekiq
|
||||||
simplecov
|
simplecov
|
||||||
sqlite3
|
sqlite3
|
||||||
strip_attributes
|
strip_attributes
|
||||||
|
|||||||
@@ -1,24 +1,85 @@
|
|||||||
# README
|
<h1 align="center" style="border-bottom: none">
|
||||||
|
<div>
|
||||||
|
<a href="https://www.docuseal.co">
|
||||||
|
<img alt="DocuSeal" src="https://github.com/docusealco/docuseal/assets/5418788/c12cd051-81cd-4402-bc3a-92f2cfdc1b06" width="80" />
|
||||||
|
<br>
|
||||||
|
</a>
|
||||||
|
DocuSeal
|
||||||
|
</div>
|
||||||
|
</h1>
|
||||||
|
<h3 align="center">
|
||||||
|
Open source document filling and signing
|
||||||
|
</h3>
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://hub.docker.com/r/docuseal/docuseal">
|
||||||
|
<img alt="Docker releases" src="https://img.shields.io/docker/v/docuseal/docuseal">
|
||||||
|
</a>
|
||||||
|
<a href="https://discord.gg/qygYCDGck9">
|
||||||
|
<img src="https://img.shields.io/discord/1125112641170448454?logo=discord"/>
|
||||||
|
</a>
|
||||||
|
<a href="https://twitter.com/intent/follow?screen_name=docusealco">
|
||||||
|
<img src="https://img.shields.io/twitter/follow/docusealco?style=social" alt="Follow @docusealco" />
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
DocuSeal is an open source platform that provides secure and efficient digital document signing and processing. Create PDF forms to have them filled and signed online on any device with an easy-to-use, mobile-optimized web tool.
|
||||||
|
</p>
|
||||||
|
<h2 align="center">
|
||||||
|
<a href="https://demo.docuseal.co">✨ Live Demo</a>
|
||||||
|
<span>|</span>
|
||||||
|
<a href="https://docuseal.co/sign_up">☁️ Try in Cloud</a>
|
||||||
|
</h2>
|
||||||
|
|
||||||
This README would normally document whatever steps are necessary to get the
|
[](https://demo.docuseal.co)
|
||||||
application up and running.
|
|
||||||
|
|
||||||
Things you may want to cover:
|
## Features
|
||||||
|
- [x] PDF form fields builder (WYSIWYG)
|
||||||
|
- [x] 10 field types available (Signature, Date, File, Checkbox etc.)
|
||||||
|
- [x] Multiple submitters per document
|
||||||
|
- [x] Automated emails via SMTP
|
||||||
|
- [x] Files storage on AWS S3, Google Storage, or Azure
|
||||||
|
- [x] Automatic PDF eSignature
|
||||||
|
- [x] PDF signature verification
|
||||||
|
- [x] Users management
|
||||||
|
- [x] Mobile-optimized
|
||||||
|
- [x] Easy to deploy in minutes
|
||||||
|
|
||||||
* Ruby version
|
## Deploy
|
||||||
|
|
||||||
* System dependencies
|
|Heroku|Railway|
|
||||||
|
|:--:|:---:|
|
||||||
|
| [<img alt="Deploy on Heroku" src="https://www.herokucdn.com/deploy/button.svg" height="40">](https://heroku.com/deploy?template=https://github.com/docusealco/docuseal-heroku) | [<img alt="Deploy on Railway" src="https://railway.app/button.svg" height="40">](https://railway.app/template/IGoDnc?referralCode=ruU7JR)|
|
||||||
|
|**DigitalOcean**|**Render**|
|
||||||
|
| [<img alt="Deploy on DigitalOcean" src="https://www.deploytodo.com/do-btn-blue.svg" height="40">](https://cloud.digitalocean.com/apps/new?repo=https://github.com/docusealco/docuseal-digitalocean/tree/master&refcode=421d50f53990) | [<img alt="Deploy to Render" src="https://render.com/images/deploy-to-render-button.svg" height="40">](https://render.com/deploy?repo=https://github.com/docusealco/docuseal-render)
|
||||||
|
|
||||||
* Configuration
|
|
||||||
|
|
||||||
* Database creation
|
#### Docker
|
||||||
|
|
||||||
* Database initialization
|
```sh
|
||||||
|
docker run --name docuseal -p 3000:3000 -v.:/data docuseal/docuseal
|
||||||
|
```
|
||||||
|
|
||||||
* How to run the test suite
|
By default DocuSeal docker container uses an SQLite database to store data and configurations. Alternatively, it is possible use PostgreSQL or MySQL databases by specifying the `DATABASE_URL` env variable.
|
||||||
|
|
||||||
* Services (job queues, cache servers, search engines, etc.)
|
#### Docker Compose
|
||||||
|
|
||||||
* Deployment instructions
|
Download docker-compose.yml into your private server:
|
||||||
|
```sh
|
||||||
|
curl https://raw.githubusercontent.com/docusealco/docuseal/master/docker-compose.yml > docker-compose.yml
|
||||||
|
```
|
||||||
|
|
||||||
* ...
|
Run the app under a custom domain over https using docker compose (make sure your DNS points to the server to automatically issue ssl certs with Caddy):
|
||||||
|
```sh
|
||||||
|
HOST=your-domain-name.com docker-compose up
|
||||||
|
```
|
||||||
|
|
||||||
|
## For Companies
|
||||||
|
### Integrate seamless document signing into your web or mobile apps with DocuSeal!
|
||||||
|
|
||||||
|
At DocuSeal we have expertise and technologies to make documents creation, filling, signing and processing seamlessly integrated with your product. We specialize in working with various industries, including **Banking, Healthcare, Transport, eCommerce, KYC, CRM, and other software products** that require bulk document signing. By leveraging DocuSeal, we can assist in reducing the overall cost of developing and processing electronic documents while ensuring security and compliance with local electronic document laws.
|
||||||
|
|
||||||
|
[](https://cal.com/docuseal)
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
DocuSeal is released under the GNU Affero General Public License v3.0.
|
||||||
@@ -40,6 +40,8 @@ class AccountsController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def app_url_params
|
def app_url_params
|
||||||
|
return {} if params[:encrypted_config].blank?
|
||||||
|
|
||||||
params.require(:encrypted_config).permit(:value)
|
params.require(:encrypted_config).permit(:value)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -3,6 +3,8 @@
|
|||||||
module Api
|
module Api
|
||||||
class TemplatesDocumentsController < ApiBaseController
|
class TemplatesDocumentsController < ApiBaseController
|
||||||
def create
|
def create
|
||||||
|
return head :unprocessable_entity if params[:blobs].blank? && params[:files].blank?
|
||||||
|
|
||||||
@template = current_account.templates.find(params[:template_id])
|
@template = current_account.templates.find(params[:template_id])
|
||||||
|
|
||||||
documents =
|
documents =
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class ApplicationController < ActionController::Base
|
|||||||
private
|
private
|
||||||
|
|
||||||
def sign_in_for_demo
|
def sign_in_for_demo
|
||||||
sign_in(User.first) unless signed_in?
|
sign_in(User.order('random()').take) unless signed_in?
|
||||||
end
|
end
|
||||||
|
|
||||||
def current_account
|
def current_account
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ class DashboardController < ApplicationController
|
|||||||
skip_before_action :authenticate_user!, only: %i[index]
|
skip_before_action :authenticate_user!, only: %i[index]
|
||||||
|
|
||||||
def index
|
def index
|
||||||
|
return redirect_to Docuseal::PRODUCT_URL, allow_other_host: true if Docuseal.multitenant? && !signed_in?
|
||||||
return render 'pages/landing' unless signed_in?
|
return render 'pages/landing' unless signed_in?
|
||||||
|
|
||||||
templates = current_account.templates.active.preload(:author).order(id: :desc)
|
templates = current_account.templates.active.preload(:author).order(id: :desc)
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
||||||
|
def google_oauth2
|
||||||
|
@user = Users.from_omniauth(request.env['omniauth.auth'])
|
||||||
|
|
||||||
|
if @user.persisted?
|
||||||
|
flash[:notice] = I18n.t('devise.omniauth_callbacks.success', kind: 'Google')
|
||||||
|
|
||||||
|
sign_in_and_redirect @user, event: :authentication
|
||||||
|
else
|
||||||
|
redirect_to new_registration_path(oauth_callback: true, user: @user.slice(:email, :first_name, :last_name)),
|
||||||
|
notice: 'Please complete registration with Google auth'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,24 +1,44 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class RegistrationsController < Devise::RegistrationsController
|
class RegistrationsController < Devise::RegistrationsController
|
||||||
|
prepend_before_action :require_no_authentication, only: [:show]
|
||||||
|
|
||||||
|
def show; end
|
||||||
|
|
||||||
|
def create
|
||||||
|
super
|
||||||
|
|
||||||
|
Accounts.create_default_template(resource.account) if resource.account.persisted?
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def require_no_authentication
|
||||||
|
super
|
||||||
|
|
||||||
|
flash.clear
|
||||||
|
end
|
||||||
|
|
||||||
def build_resource(_hash = {})
|
def build_resource(_hash = {})
|
||||||
account = Account.new(account_params)
|
account = Account.new(account_params)
|
||||||
account.timezone = Accounts.normalize_timezone(account.timezone)
|
account.timezone = Accounts.normalize_timezone(account.timezone)
|
||||||
|
|
||||||
self.resource = account.users.new(user_params)
|
self.resource = account.users.new(user_params)
|
||||||
|
|
||||||
|
account.name ||= "#{resource.full_name}'s Company" if params[:action] == 'create'
|
||||||
end
|
end
|
||||||
|
|
||||||
def user_params
|
def user_params
|
||||||
return {} if params[:user].blank?
|
return {} if params[:user].blank?
|
||||||
|
|
||||||
params.require(:user).permit(:first_name, :last_name, :email, :password)
|
params.require(:user).permit(:first_name, :last_name, :email, :password).compact_blank.tap do |attrs|
|
||||||
|
attrs[:password] ||= SecureRandom.hex if params[:action] == 'create'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def account_params
|
def account_params
|
||||||
return {} if params[:account].blank?
|
return {} if params[:account].blank?
|
||||||
|
|
||||||
params.require(:account).permit(:name, :timezone)
|
params.require(:account).permit(:name, :timezone).compact_blank
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class SessionsController < Devise::SessionsController
|
||||||
|
def create
|
||||||
|
if Docuseal.multitenant? && !User.exists?(email: sign_in_params[:email])
|
||||||
|
return redirect_to new_registration_path(sign_up: true, user: sign_in_params.slice(:email)),
|
||||||
|
notice: 'Create a new account'
|
||||||
|
end
|
||||||
|
|
||||||
|
super
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def require_no_authentication
|
||||||
|
super
|
||||||
|
|
||||||
|
flash.clear
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -22,14 +22,16 @@ class SubmissionsController < ApplicationController
|
|||||||
create_submissions_from_submitters
|
create_submissions_from_submitters
|
||||||
end
|
end
|
||||||
|
|
||||||
|
submitters = submissions.flat_map(&:submitters)
|
||||||
|
|
||||||
if params[:send_email] == '1'
|
if params[:send_email] == '1'
|
||||||
submissions.flat_map(&:submitters).each do |submitter|
|
submitters.each do |submitter|
|
||||||
SubmitterMailer.invitation_email(submitter, message: params[:message]).deliver_later!
|
SubmitterMailer.invitation_email(submitter, message: params[:message]).deliver_later!
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
redirect_to template_path(@template),
|
redirect_to template_path(@template),
|
||||||
notice: "#{submissions.size} #{'recipient'.pluralize(submissions.size)} added"
|
notice: "#{submitters.size} #{'recipient'.pluralize(submitters.size)} added"
|
||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ class SubmitFormController < ApplicationController
|
|||||||
private
|
private
|
||||||
|
|
||||||
def normalized_values
|
def normalized_values
|
||||||
params[:values].to_unsafe_h.transform_values do |v|
|
params.fetch(:values, {}).to_unsafe_h.transform_values do |v|
|
||||||
if params[:cast_boolean] == 'true'
|
if params[:cast_boolean] == 'true'
|
||||||
v == 'true'
|
v == 'true'
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -24,24 +24,30 @@ class UsersController < ApplicationController
|
|||||||
if @user.save
|
if @user.save
|
||||||
UserMailer.invitation_email(@user).deliver_later!
|
UserMailer.invitation_email(@user).deliver_later!
|
||||||
|
|
||||||
redirect_to settings_users_path, notice: 'User has been invited.'
|
redirect_to settings_users_path, notice: 'User has been invited'
|
||||||
else
|
else
|
||||||
render turbo_stream: turbo_stream.replace(:modal, template: 'users/new'), status: :unprocessable_entity
|
render turbo_stream: turbo_stream.replace(:modal, template: 'users/new'), status: :unprocessable_entity
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
|
return redirect_to settings_users_path, notice: 'Unable to update user.' if Docuseal.demo?
|
||||||
|
|
||||||
if @user.update(user_params.compact_blank)
|
if @user.update(user_params.compact_blank)
|
||||||
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
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
|
if Docuseal.demo? || @user.id == current_user.id
|
||||||
|
return redirect_to settings_users_path, notice: 'Unable to remove user'
|
||||||
|
end
|
||||||
|
|
||||||
@user.update!(deleted_at: Time.current)
|
@user.update!(deleted_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
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ window.customElements.define('submission-form', class extends HTMLElement {
|
|||||||
authenticityToken: this.dataset.authenticityToken,
|
authenticityToken: this.dataset.authenticityToken,
|
||||||
canSendEmail: this.dataset.canSendEmail === 'true',
|
canSendEmail: this.dataset.canSendEmail === 'true',
|
||||||
isDirectUpload: this.dataset.isDirectUpload === 'true',
|
isDirectUpload: this.dataset.isDirectUpload === 'true',
|
||||||
|
isDemo: this.dataset.isDemo === 'true',
|
||||||
values: reactive(JSON.parse(this.dataset.values)),
|
values: reactive(JSON.parse(this.dataset.values)),
|
||||||
attachments: reactive(JSON.parse(this.dataset.attachments)),
|
attachments: reactive(JSON.parse(this.dataset.attachments)),
|
||||||
fields: JSON.parse(this.dataset.fields)
|
fields: JSON.parse(this.dataset.fields)
|
||||||
|
|||||||
@@ -25,10 +25,10 @@
|
|||||||
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"
|
||||||
>
|
>
|
||||||
{{ field.name || fieldNames[field.type] }}
|
{{ field.name || fieldNames[field.type] }}
|
||||||
<template v-if="field.type === 'checkbox'">
|
<template v-if="field.type === 'checkbox' && !field.name">
|
||||||
{{ fieldIndex + 1 }}
|
{{ fieldIndex + 1 }}
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="!field.required">
|
<template v-else-if="!field.required && field.type !== 'checkbox'">
|
||||||
(optional)
|
(optional)
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
</p>
|
</p>
|
||||||
<div class="space-y-3 mt-5">
|
<div class="space-y-3 mt-5">
|
||||||
<button
|
<button
|
||||||
v-if="canSendEmail"
|
v-if="canSendEmail && !isDemo"
|
||||||
class="white-button flex items-center space-x-1 w-full"
|
class="white-button flex items-center space-x-1 w-full"
|
||||||
:disabled="isSendingCopy"
|
:disabled="isSendingCopy"
|
||||||
@click.prevent="sendCopyToEmail"
|
@click.prevent="sendCopyToEmail"
|
||||||
@@ -40,19 +40,50 @@
|
|||||||
Download
|
Download
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
|
<a
|
||||||
|
v-if="isDemo"
|
||||||
|
target="_blank"
|
||||||
|
href="https://github.com/docusealco/docuseal"
|
||||||
|
class="white-button flex items-center space-x-1 w-full"
|
||||||
|
>
|
||||||
|
<IconBrandGithub />
|
||||||
|
<span>
|
||||||
|
Star on Github
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
v-if="isDemo"
|
||||||
|
href="https://docuseal.co/sign_up"
|
||||||
|
class="white-button flex items-center space-x-1 w-full"
|
||||||
|
>
|
||||||
|
<IconLogin />
|
||||||
|
<span>
|
||||||
|
Create a Free Account
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="text-center mt-4">
|
||||||
|
Signed with
|
||||||
|
<a
|
||||||
|
href="https://www.docuseal.co"
|
||||||
|
target="_blank"
|
||||||
|
class="underline"
|
||||||
|
>DocuSeal</a> - open source documents software
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { IconCircleCheck, IconMail, IconDownload, IconInnerShadowTop } from '@tabler/icons-vue'
|
import { IconCircleCheck, IconBrandGithub, IconMail, IconDownload, IconInnerShadowTop, IconLogin } from '@tabler/icons-vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'FormCompleted',
|
name: 'FormCompleted',
|
||||||
components: {
|
components: {
|
||||||
IconCircleCheck,
|
IconCircleCheck,
|
||||||
IconInnerShadowTop,
|
IconInnerShadowTop,
|
||||||
|
IconBrandGithub,
|
||||||
IconMail,
|
IconMail,
|
||||||
|
IconLogin,
|
||||||
IconDownload
|
IconDownload
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
@@ -60,6 +91,11 @@ export default {
|
|||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
isDemo: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
canSendEmail: {
|
canSendEmail: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
{{ message }}
|
{{ message }}
|
||||||
</div>
|
</div>
|
||||||
<div class="text-xs">
|
<div class="text-xs">
|
||||||
<span class="font-medium">Click to upload</span> or drag and drop
|
<span class="font-medium">Click to upload</span> or drag and drop files
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,266 +5,292 @@
|
|||||||
:values="values"
|
:values="values"
|
||||||
:attachments-index="attachmentsIndex"
|
:attachments-index="attachmentsIndex"
|
||||||
:current-step="currentStepFields"
|
:current-step="currentStepFields"
|
||||||
@focus-step="[saveStep(), goToStep($event, false, true)]"
|
@focus-step="[saveStep(), goToStep($event, false, true), currentField.type !== 'checkbox' ? isFormVisible = true : '']"
|
||||||
/>
|
/>
|
||||||
<div>
|
<button
|
||||||
<form
|
v-if="!isFormVisible"
|
||||||
|
class="btn btn-neutral 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"
|
||||||
|
>
|
||||||
|
Submit Form
|
||||||
|
<IconArrowsDiagonal
|
||||||
|
class="absolute right-0 mr-4"
|
||||||
|
:width="20"
|
||||||
|
:height="20"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<div
|
||||||
|
v-show="isFormVisible"
|
||||||
|
id="form_container"
|
||||||
|
class="shadow-md bg-base-100 absolute bottom-0 md:bottom-4 w-full border-base-200 border p-4 rounded"
|
||||||
|
>
|
||||||
|
<button
|
||||||
v-if="!isCompleted"
|
v-if="!isCompleted"
|
||||||
ref="form"
|
class="absolute right-0 mr-2 mt-2 top-0 hidden md:block"
|
||||||
:action="submitPath"
|
title="Minimize"
|
||||||
method="post"
|
@click.prevent="isFormVisible = false"
|
||||||
class="md:mx-16"
|
|
||||||
@submit.prevent="submitStep"
|
|
||||||
>
|
>
|
||||||
<input
|
<IconArrowsDiagonalMinimize2
|
||||||
type="hidden"
|
:width="20"
|
||||||
name="authenticity_token"
|
:height="20"
|
||||||
:value="authenticityToken"
|
/>
|
||||||
|
</button>
|
||||||
|
<div>
|
||||||
|
<form
|
||||||
|
v-if="!isCompleted"
|
||||||
|
ref="form"
|
||||||
|
:action="submitPath"
|
||||||
|
method="post"
|
||||||
|
class="md:mx-16"
|
||||||
|
@submit.prevent="submitStep"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
v-if="currentStep === stepFields.length - 1"
|
type="hidden"
|
||||||
type="hidden"
|
name="authenticity_token"
|
||||||
name="completed"
|
:value="authenticityToken"
|
||||||
value="true"
|
>
|
||||||
>
|
<input
|
||||||
<input
|
value="put"
|
||||||
value="put"
|
name="_method"
|
||||||
name="_method"
|
type="hidden"
|
||||||
type="hidden"
|
>
|
||||||
>
|
<div class="md:mt-4">
|
||||||
<div class="mt-4">
|
<div v-if="['cells', 'text'].includes(currentField.type)">
|
||||||
<div v-if="['cells', 'text'].includes(currentField.type)">
|
<label
|
||||||
<label
|
v-if="currentField.name"
|
||||||
v-if="currentField.name"
|
:for="currentField.uuid"
|
||||||
:for="currentField.uuid"
|
class="label text-2xl mb-2"
|
||||||
class="label text-2xl mb-2"
|
>{{ currentField.name }}
|
||||||
>{{ currentField.name }}
|
<template v-if="!currentField.required">(optional)</template>
|
||||||
<template v-if="!currentField.required">(optional)</template>
|
</label>
|
||||||
</label>
|
<div
|
||||||
<div
|
v-else
|
||||||
v-else
|
class="py-1"
|
||||||
class="py-1"
|
/>
|
||||||
/>
|
<div>
|
||||||
<div>
|
<input
|
||||||
<input
|
:id="currentField.uuid"
|
||||||
|
v-model="values[currentField.uuid]"
|
||||||
|
class="base-input !text-2xl w-full"
|
||||||
|
:required="currentField.required"
|
||||||
|
:placeholder="`Type here...${currentField.required ? '' : ' (optional)'}`"
|
||||||
|
type="text"
|
||||||
|
:name="`values[${currentField.uuid}]`"
|
||||||
|
@focus="$refs.areas.scrollIntoField(currentField)"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="currentField.type === 'date'">
|
||||||
|
<label
|
||||||
|
v-if="currentField.name"
|
||||||
|
:for="currentField.uuid"
|
||||||
|
class="label text-2xl mb-2"
|
||||||
|
>{{ currentField.name }}
|
||||||
|
<template v-if="!currentField.required">(optional)</template>
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
class="py-1"
|
||||||
|
/>
|
||||||
|
<div class="text-center">
|
||||||
|
<input
|
||||||
|
:id="currentField.uuid"
|
||||||
|
v-model="values[currentField.uuid]"
|
||||||
|
class="base-input !text-2xl text-center w-full"
|
||||||
|
:required="currentField.required"
|
||||||
|
type="date"
|
||||||
|
:name="`values[${currentField.uuid}]`"
|
||||||
|
@focus="$refs.areas.scrollIntoField(currentField)"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="currentField.type === 'select'">
|
||||||
|
<label
|
||||||
|
v-if="currentField.name"
|
||||||
|
:for="currentField.uuid"
|
||||||
|
class="label text-2xl mb-2"
|
||||||
|
>{{ currentField.name }}
|
||||||
|
<template v-if="!currentField.required">(optional)</template>
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
class="py-1"
|
||||||
|
/>
|
||||||
|
<select
|
||||||
:id="currentField.uuid"
|
:id="currentField.uuid"
|
||||||
v-model="values[currentField.uuid]"
|
|
||||||
class="base-input !text-2xl w-full"
|
|
||||||
:required="currentField.required"
|
:required="currentField.required"
|
||||||
:placeholder="`Type here...${currentField.required ? '' : ' (optional)'}`"
|
class="select base-input !text-2xl w-full text-center font-normal"
|
||||||
type="text"
|
|
||||||
:name="`values[${currentField.uuid}]`"
|
:name="`values[${currentField.uuid}]`"
|
||||||
|
@change="values[currentField.uuid] = $event.target.value"
|
||||||
@focus="$refs.areas.scrollIntoField(currentField)"
|
@focus="$refs.areas.scrollIntoField(currentField)"
|
||||||
>
|
>
|
||||||
</div>
|
<option
|
||||||
</div>
|
value=""
|
||||||
<div v-else-if="currentField.type === 'date'">
|
:selected="!values[currentField.uuid]"
|
||||||
<label
|
>
|
||||||
v-if="currentField.name"
|
Select your option
|
||||||
:for="currentField.uuid"
|
</option>
|
||||||
class="label text-2xl mb-2"
|
<option
|
||||||
>{{ currentField.name }}
|
|
||||||
<template v-if="!currentField.required">(optional)</template>
|
|
||||||
</label>
|
|
||||||
<div
|
|
||||||
v-else
|
|
||||||
class="py-1"
|
|
||||||
/>
|
|
||||||
<div class="text-center">
|
|
||||||
<input
|
|
||||||
:id="currentField.uuid"
|
|
||||||
v-model="values[currentField.uuid]"
|
|
||||||
class="base-input !text-2xl text-center w-full"
|
|
||||||
:required="currentField.required"
|
|
||||||
type="date"
|
|
||||||
:name="`values[${currentField.uuid}]`"
|
|
||||||
@focus="$refs.areas.scrollIntoField(currentField)"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-else-if="currentField.type === 'select'">
|
|
||||||
<label
|
|
||||||
v-if="currentField.name"
|
|
||||||
:for="currentField.uuid"
|
|
||||||
class="label text-2xl mb-2"
|
|
||||||
>{{ currentField.name }}
|
|
||||||
<template v-if="!currentField.required">(optional)</template>
|
|
||||||
</label>
|
|
||||||
<div
|
|
||||||
v-else
|
|
||||||
class="py-1"
|
|
||||||
/>
|
|
||||||
<select
|
|
||||||
:id="currentField.uuid"
|
|
||||||
:required="currentField.required"
|
|
||||||
class="select base-input !text-2xl w-full text-center font-normal"
|
|
||||||
:name="`values[${currentField.uuid}]`"
|
|
||||||
@change="values[currentField.uuid] = $event.target.value"
|
|
||||||
@focus="$refs.areas.scrollIntoField(currentField)"
|
|
||||||
>
|
|
||||||
<option
|
|
||||||
value=""
|
|
||||||
:selected="!values[currentField.uuid]"
|
|
||||||
>
|
|
||||||
Select your option
|
|
||||||
</option>
|
|
||||||
<option
|
|
||||||
v-for="(option, index) in currentField.options"
|
|
||||||
:key="index"
|
|
||||||
:selected="values[currentField.uuid] == option"
|
|
||||||
:value="option"
|
|
||||||
>
|
|
||||||
{{ option }}
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div v-else-if="currentField.type === 'radio'">
|
|
||||||
<label
|
|
||||||
v-if="currentField.name"
|
|
||||||
:for="currentField.uuid"
|
|
||||||
class="label text-2xl mb-2"
|
|
||||||
>{{ currentField.name }}
|
|
||||||
<template v-if="!currentField.required">(optional)</template>
|
|
||||||
</label>
|
|
||||||
<div class="flex w-full">
|
|
||||||
<div class="space-y-3.5 mx-auto">
|
|
||||||
<div
|
|
||||||
v-for="(option, index) in currentField.options"
|
v-for="(option, index) in currentField.options"
|
||||||
:key="index"
|
:key="index"
|
||||||
|
:selected="values[currentField.uuid] == option"
|
||||||
|
:value="option"
|
||||||
|
>
|
||||||
|
{{ option }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="currentField.type === 'radio'">
|
||||||
|
<label
|
||||||
|
v-if="currentField.name"
|
||||||
|
:for="currentField.uuid"
|
||||||
|
class="label text-2xl mb-2"
|
||||||
|
>{{ currentField.name }}
|
||||||
|
<template v-if="!currentField.required">(optional)</template>
|
||||||
|
</label>
|
||||||
|
<div class="flex w-full">
|
||||||
|
<div class="space-y-3.5 mx-auto">
|
||||||
|
<div
|
||||||
|
v-for="(option, index) in currentField.options"
|
||||||
|
:key="index"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
:for="currentField.uuid + option"
|
||||||
|
class="flex items-center space-x-3"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
:id="currentField.uuid + option"
|
||||||
|
v-model="values[currentField.uuid]"
|
||||||
|
type="radio"
|
||||||
|
class="base-radio !h-7 !w-7"
|
||||||
|
:name="`values[${currentField.uuid}]`"
|
||||||
|
:value="option"
|
||||||
|
:required="currentField.required"
|
||||||
|
>
|
||||||
|
<span class="text-xl">
|
||||||
|
{{ option }}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<MultiSelectStep
|
||||||
|
v-else-if="currentField.type === 'multiple'"
|
||||||
|
v-model="values[currentField.uuid]"
|
||||||
|
:field="currentField"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
v-else-if="currentField.type === 'checkbox'"
|
||||||
|
class="flex w-full"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="hidden"
|
||||||
|
name="cast_boolean"
|
||||||
|
value="true"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="space-y-3.5 mx-auto"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="(field, index) in currentStepFields"
|
||||||
|
:key="field.uuid"
|
||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
:for="currentField.uuid + option"
|
:for="field.uuid"
|
||||||
class="flex items-center space-x-3"
|
class="flex items-center space-x-3"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
:id="currentField.uuid + option"
|
type="hidden"
|
||||||
v-model="values[currentField.uuid]"
|
:name="`values[${field.uuid}]`"
|
||||||
type="radio"
|
:value="!!values[field.uuid]"
|
||||||
class="base-radio !h-7 !w-7"
|
>
|
||||||
:name="`values[${currentField.uuid}]`"
|
<input
|
||||||
:value="option"
|
:id="field.uuid"
|
||||||
:required="currentField.required"
|
type="checkbox"
|
||||||
|
class="base-checkbox !h-7 !w-7"
|
||||||
|
:checked="!!values[field.uuid]"
|
||||||
|
@click="[$refs.areas.scrollIntoField(field), values[field.uuid] = !values[field.uuid]]"
|
||||||
>
|
>
|
||||||
<span class="text-xl">
|
<span class="text-xl">
|
||||||
{{ option }}
|
{{ field.name || field.type + ' ' + (index + 1) }}
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<ImageStep
|
||||||
|
v-else-if="currentField.type === 'image'"
|
||||||
|
v-model="values[currentField.uuid]"
|
||||||
|
:field="currentField"
|
||||||
|
:is-direct-upload="isDirectUpload"
|
||||||
|
:attachments-index="attachmentsIndex"
|
||||||
|
:submitter-slug="submitterSlug"
|
||||||
|
@attached="[attachments.push($event), $refs.areas.scrollIntoField(currentField)]"
|
||||||
|
/>
|
||||||
|
<SignatureStep
|
||||||
|
v-else-if="currentField.type === 'signature'"
|
||||||
|
ref="currentStep"
|
||||||
|
v-model="values[currentField.uuid]"
|
||||||
|
:field="currentField"
|
||||||
|
:is-direct-upload="isDirectUpload"
|
||||||
|
:attachments-index="attachmentsIndex"
|
||||||
|
:submitter-slug="submitterSlug"
|
||||||
|
@attached="attachments.push($event)"
|
||||||
|
@start="$refs.areas.scrollIntoField(currentField)"
|
||||||
|
@minimize="isFormVisible = false"
|
||||||
|
/>
|
||||||
|
<AttachmentStep
|
||||||
|
v-else-if="currentField.type === 'file'"
|
||||||
|
v-model="values[currentField.uuid]"
|
||||||
|
:is-direct-upload="isDirectUpload"
|
||||||
|
:field="currentField"
|
||||||
|
:attachments-index="attachmentsIndex"
|
||||||
|
:submitter-slug="submitterSlug"
|
||||||
|
@attached="[attachments.push($event), $refs.areas.scrollIntoField(currentField)]"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<MultiSelectStep
|
<div class="mt-6 md:mt-8">
|
||||||
v-else-if="currentField.type === 'multiple'"
|
<button
|
||||||
v-model="values[currentField.uuid]"
|
type="submit"
|
||||||
:field="currentField"
|
class="base-button w-full flex justify-center"
|
||||||
/>
|
:disabled="isButtonDisabled"
|
||||||
<div
|
|
||||||
v-else-if="currentField.type === 'checkbox'"
|
|
||||||
class="flex w-full"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="hidden"
|
|
||||||
name="cast_boolean"
|
|
||||||
value="true"
|
|
||||||
>
|
>
|
||||||
<div
|
<span class="flex">
|
||||||
class="space-y-3.5 mx-auto"
|
<IconInnerShadowTop
|
||||||
>
|
v-if="isSubmitting"
|
||||||
<div
|
class="mr-1 animate-spin"
|
||||||
v-for="(field, index) in currentStepFields"
|
/>
|
||||||
:key="field.uuid"
|
<span v-if="stepFields.length === currentStep + 1">
|
||||||
>
|
Submit
|
||||||
<label
|
</span>
|
||||||
:for="field.uuid"
|
<span v-else>
|
||||||
class="flex items-center space-x-3"
|
Next
|
||||||
>
|
</span><span
|
||||||
<input
|
v-if="isSubmitting"
|
||||||
type="hidden"
|
class="w-6 flex justify-start mr-1"
|
||||||
:name="`values[${field.uuid}]`"
|
><span>...</span></span>
|
||||||
:value="!!values[field.uuid]"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
:id="field.uuid"
|
|
||||||
type="checkbox"
|
|
||||||
class="base-checkbox !h-7 !w-7"
|
|
||||||
:checked="!!values[field.uuid]"
|
|
||||||
@click="[$refs.areas.scrollIntoField(field), values[field.uuid] = !values[field.uuid]]"
|
|
||||||
>
|
|
||||||
<span class="text-xl">
|
|
||||||
{{ currentField.name || currentField.type + ' ' + (index + 1) }}
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<ImageStep
|
|
||||||
v-else-if="currentField.type === 'image'"
|
|
||||||
v-model="values[currentField.uuid]"
|
|
||||||
:field="currentField"
|
|
||||||
:is-direct-upload="isDirectUpload"
|
|
||||||
:attachments-index="attachmentsIndex"
|
|
||||||
:submitter-slug="submitterSlug"
|
|
||||||
@attached="[attachments.push($event), $refs.areas.scrollIntoField(currentField)]"
|
|
||||||
/>
|
|
||||||
<SignatureStep
|
|
||||||
v-else-if="currentField.type === 'signature'"
|
|
||||||
ref="currentStep"
|
|
||||||
v-model="values[currentField.uuid]"
|
|
||||||
:field="currentField"
|
|
||||||
:is-direct-upload="isDirectUpload"
|
|
||||||
:attachments-index="attachmentsIndex"
|
|
||||||
:submitter-slug="submitterSlug"
|
|
||||||
@attached="attachments.push($event)"
|
|
||||||
/>
|
|
||||||
<AttachmentStep
|
|
||||||
v-else-if="currentField.type === 'file'"
|
|
||||||
v-model="values[currentField.uuid]"
|
|
||||||
:is-direct-upload="isDirectUpload"
|
|
||||||
:field="currentField"
|
|
||||||
:attachments-index="attachmentsIndex"
|
|
||||||
:submitter-slug="submitterSlug"
|
|
||||||
@attached="[attachments.push($event), $refs.areas.scrollIntoField(currentField)]"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="mt-8">
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
class="base-button w-full flex justify-center"
|
|
||||||
:disabled="isButtonDisabled"
|
|
||||||
>
|
|
||||||
<span class="flex">
|
|
||||||
<IconInnerShadowTop
|
|
||||||
v-if="isSubmitting"
|
|
||||||
class="mr-1 animate-spin"
|
|
||||||
/>
|
|
||||||
<span v-if="stepFields.length === currentStep + 1">
|
|
||||||
Submit
|
|
||||||
</span>
|
</span>
|
||||||
<span v-else>
|
</button>
|
||||||
Next
|
</div>
|
||||||
</span><span
|
</form>
|
||||||
v-if="isSubmitting"
|
<FormCompleted
|
||||||
class="w-6 flex justify-start mr-1"
|
v-else
|
||||||
><span>...</span></span>
|
:is-demo="isDemo"
|
||||||
</span>
|
:can-send-email="canSendEmail"
|
||||||
</button>
|
:submitter-slug="submitterSlug"
|
||||||
</div>
|
/>
|
||||||
</form>
|
<div class="flex justify-center">
|
||||||
<FormCompleted
|
<div class="flex items-center mt-5 mb-1">
|
||||||
v-else
|
<a
|
||||||
:can-send-email="canSendEmail"
|
v-for="(step, index) in stepFields"
|
||||||
:submitter-slug="submitterSlug"
|
:key="step[0].uuid"
|
||||||
/>
|
href="#"
|
||||||
<div class="flex justify-center">
|
class="inline border border-base-300 h-3 w-3 rounded-full mx-1"
|
||||||
<div class="flex items-center mt-5 mb-1">
|
:class="{ 'bg-base-300': index === currentStep, 'bg-base-content': index < currentStep || isCompleted, 'bg-white': index > currentStep }"
|
||||||
<a
|
@click.prevent="isCompleted ? '' : [saveStep(), goToStep(step, true)]"
|
||||||
v-for="(step, index) in stepFields"
|
/>
|
||||||
:key="step[0].uuid"
|
</div>
|
||||||
href="#"
|
|
||||||
class="inline border border-base-300 h-3 w-3 rounded-full mx-1"
|
|
||||||
:class="{ 'bg-base-300': index === currentStep, 'bg-base-content': index < currentStep || isCompleted, 'bg-white': index > currentStep }"
|
|
||||||
@click.prevent="isCompleted ? '' : goToStep(step, true)"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -277,7 +303,7 @@ import SignatureStep from './signature_step'
|
|||||||
import AttachmentStep from './attachment_step'
|
import AttachmentStep from './attachment_step'
|
||||||
import MultiSelectStep from './multi_select_step'
|
import MultiSelectStep from './multi_select_step'
|
||||||
import FormCompleted from './completed'
|
import FormCompleted from './completed'
|
||||||
import { IconInnerShadowTop } from '@tabler/icons-vue'
|
import { IconInnerShadowTop, IconArrowsDiagonal, IconArrowsDiagonalMinimize2 } from '@tabler/icons-vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'SubmissionForm',
|
name: 'SubmissionForm',
|
||||||
@@ -288,6 +314,8 @@ export default {
|
|||||||
AttachmentStep,
|
AttachmentStep,
|
||||||
MultiSelectStep,
|
MultiSelectStep,
|
||||||
IconInnerShadowTop,
|
IconInnerShadowTop,
|
||||||
|
IconArrowsDiagonal,
|
||||||
|
IconArrowsDiagonalMinimize2,
|
||||||
FormCompleted
|
FormCompleted
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
@@ -323,6 +351,11 @@ export default {
|
|||||||
required: true,
|
required: true,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
|
isDemo: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
values: {
|
values: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -332,6 +365,7 @@ export default {
|
|||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
isCompleted: false,
|
isCompleted: false,
|
||||||
|
isFormVisible: true,
|
||||||
currentStep: 0,
|
currentStep: 0,
|
||||||
isSubmitting: false
|
isSubmitting: false
|
||||||
}
|
}
|
||||||
@@ -396,18 +430,18 @@ export default {
|
|||||||
|
|
||||||
this.$refs.form.querySelector('input[type="date"], input[type="text"], select')?.focus()
|
this.$refs.form.querySelector('input[type="date"], input[type="text"], select')?.focus()
|
||||||
|
|
||||||
if (clickUpload && !this.values[this.currentField.uuid]) {
|
if (clickUpload && !this.values[this.currentField.uuid] && ['file', 'image'].includes(this.currentField.type)) {
|
||||||
this.$refs.form.querySelector('input[type="file"]')?.click()
|
this.$refs.form.querySelector('input[type="file"]')?.click()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
saveStep () {
|
saveStep (formData) {
|
||||||
if (this.isCompleted) {
|
if (this.isCompleted) {
|
||||||
return Promise.resolve({})
|
return Promise.resolve({})
|
||||||
} else {
|
} else {
|
||||||
return fetch(this.submitPath, {
|
return fetch(this.submitPath, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: new FormData(this.$refs.form)
|
body: formData || new FormData(this.$refs.form)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -420,7 +454,13 @@ export default {
|
|||||||
|
|
||||||
await stepPromise()
|
await stepPromise()
|
||||||
|
|
||||||
this.saveStep().then(response => {
|
const formData = new FormData(this.$refs.form)
|
||||||
|
|
||||||
|
if (this.currentStep === this.stepFields.length - 1) {
|
||||||
|
formData.append('completed', 'true')
|
||||||
|
}
|
||||||
|
|
||||||
|
this.saveStep(formData).then(response => {
|
||||||
const nextStep = this.stepFields[this.currentStep + 1]
|
const nextStep = this.stepFields[this.currentStep + 1]
|
||||||
|
|
||||||
if (nextStep) {
|
if (nextStep) {
|
||||||
|
|||||||
@@ -4,22 +4,62 @@
|
|||||||
<label
|
<label
|
||||||
class="label text-2xl"
|
class="label text-2xl"
|
||||||
>{{ field.name || 'Signature' }}</label>
|
>{{ field.name || 'Signature' }}</label>
|
||||||
<button
|
<div class="space-x-2 flex">
|
||||||
v-if="modelValue"
|
<span
|
||||||
class="btn btn-outline btn-sm"
|
class="tooltip"
|
||||||
@click.prevent="remove"
|
data-tip="Type text"
|
||||||
>
|
>
|
||||||
<IconReload :width="16" />
|
<button
|
||||||
Redraw
|
class="btn btn-sm btn-circle"
|
||||||
</button>
|
:class="{ 'btn-neutral': isTextSignature, 'btn-outline': !isTextSignature }"
|
||||||
<button
|
@click.prevent="toggleTextInput"
|
||||||
v-else
|
>
|
||||||
class="btn btn-outline btn-sm"
|
<IconTextSize :width="16" />
|
||||||
@click.prevent="clear"
|
</button>
|
||||||
>
|
</span>
|
||||||
<IconReload :width="16" />
|
<span
|
||||||
Clear
|
class="tooltip"
|
||||||
</button>
|
data-tip="Take photo"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="btn btn-outline btn-sm btn-circle"
|
||||||
|
>
|
||||||
|
<IconCamera :width="16" />
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
hidden
|
||||||
|
accept="image/*"
|
||||||
|
@change="drawImage"
|
||||||
|
>
|
||||||
|
</label>
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
v-if="modelValue"
|
||||||
|
class="btn btn-outline btn-sm"
|
||||||
|
@click.prevent="remove"
|
||||||
|
>
|
||||||
|
<IconReload :width="16" />
|
||||||
|
Redraw
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
v-else
|
||||||
|
class="btn btn-outline btn-sm"
|
||||||
|
@click.prevent="clear"
|
||||||
|
>
|
||||||
|
<IconReload :width="16" />
|
||||||
|
Clear
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
title="Minimize"
|
||||||
|
class="py-1.5 inline md:hidden"
|
||||||
|
@click.prevent="$emit('minimize')"
|
||||||
|
>
|
||||||
|
<IconArrowsDiagonalMinimize2
|
||||||
|
:width="20"
|
||||||
|
:height="20"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
:value="modelValue"
|
:value="modelValue"
|
||||||
@@ -29,23 +69,35 @@
|
|||||||
<img
|
<img
|
||||||
v-if="modelValue"
|
v-if="modelValue"
|
||||||
:src="attachmentsIndex[modelValue].url"
|
:src="attachmentsIndex[modelValue].url"
|
||||||
class="w-full bg-white border border-base-300 rounded"
|
class="mx-auto bg-white border border-base-300 rounded max-h-72"
|
||||||
>
|
>
|
||||||
<canvas
|
<canvas
|
||||||
v-show="!modelValue"
|
v-show="!modelValue"
|
||||||
ref="canvas"
|
ref="canvas"
|
||||||
class="bg-white border border-base-300 rounded"
|
class="bg-white border border-base-300 rounded"
|
||||||
/>
|
/>
|
||||||
|
<input
|
||||||
|
v-if="isTextSignature"
|
||||||
|
ref="textInput"
|
||||||
|
class="base-input !text-2xl w-full mt-6"
|
||||||
|
:required="field.required"
|
||||||
|
:placeholder="`Type signature here...`"
|
||||||
|
type="text"
|
||||||
|
@input="updateWrittenSignature"
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { IconReload } from '@tabler/icons-vue'
|
import { IconReload, IconCamera, IconTextSize, IconArrowsDiagonalMinimize2 } from '@tabler/icons-vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'SignatureStep',
|
name: 'SignatureStep',
|
||||||
components: {
|
components: {
|
||||||
IconReload
|
IconReload,
|
||||||
|
IconCamera,
|
||||||
|
IconTextSize,
|
||||||
|
IconArrowsDiagonalMinimize2
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
field: {
|
field: {
|
||||||
@@ -72,10 +124,11 @@ export default {
|
|||||||
default: ''
|
default: ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits: ['attached', 'update:model-value'],
|
emits: ['attached', 'update:model-value', 'start', 'minimize'],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
isSignatureStarted: false
|
isSignatureStarted: false,
|
||||||
|
isTextSignature: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async mounted () {
|
async mounted () {
|
||||||
@@ -94,6 +147,8 @@ export default {
|
|||||||
|
|
||||||
this.pad.addEventListener('beginStroke', () => {
|
this.pad.addEventListener('beginStroke', () => {
|
||||||
this.isSignatureStarted = true
|
this.isSignatureStarted = true
|
||||||
|
|
||||||
|
this.$emit('start')
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -104,6 +159,134 @@ export default {
|
|||||||
this.pad.clear()
|
this.pad.clear()
|
||||||
|
|
||||||
this.isSignatureStarted = false
|
this.isSignatureStarted = false
|
||||||
|
|
||||||
|
if (this.$refs.textInput) {
|
||||||
|
this.$refs.textInput.value = ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateWrittenSignature (e) {
|
||||||
|
this.isSignatureStarted = true
|
||||||
|
|
||||||
|
const canvas = this.$refs.canvas
|
||||||
|
const context = canvas.getContext('2d')
|
||||||
|
|
||||||
|
const fontFamily = 'Arial'
|
||||||
|
const fontSize = '44px'
|
||||||
|
const fontStyle = 'italic'
|
||||||
|
const fontWeight = ''
|
||||||
|
|
||||||
|
context.font = fontStyle + ' ' + fontWeight + ' ' + fontSize + ' ' + fontFamily
|
||||||
|
context.textAlign = 'center'
|
||||||
|
|
||||||
|
context.clearRect(0, 0, canvas.width, canvas.height)
|
||||||
|
context.fillText(e.target.value, canvas.width / 2, canvas.height / 2 + 11)
|
||||||
|
},
|
||||||
|
toggleTextInput () {
|
||||||
|
this.remove()
|
||||||
|
this.isTextSignature = !this.isTextSignature
|
||||||
|
|
||||||
|
if (this.isTextSignature) {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.textInput.focus()
|
||||||
|
|
||||||
|
this.$emit('start')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
drawImage (event) {
|
||||||
|
this.remove()
|
||||||
|
this.isSignatureStarted = true
|
||||||
|
|
||||||
|
const file = event.target.files[0]
|
||||||
|
|
||||||
|
if (file && file.type.match('image.*')) {
|
||||||
|
const reader = new FileReader()
|
||||||
|
|
||||||
|
reader.onload = (event) => {
|
||||||
|
const img = new Image()
|
||||||
|
|
||||||
|
img.src = event.target.result
|
||||||
|
|
||||||
|
img.onload = () => {
|
||||||
|
const canvas = this.$refs.canvas
|
||||||
|
const context = canvas.getContext('2d')
|
||||||
|
|
||||||
|
const aspectRatio = img.width / img.height
|
||||||
|
|
||||||
|
let targetWidth = canvas.width
|
||||||
|
let targetHeight = canvas.height
|
||||||
|
|
||||||
|
if (canvas.width / canvas.height > aspectRatio) {
|
||||||
|
targetWidth = canvas.height * aspectRatio
|
||||||
|
} else {
|
||||||
|
targetHeight = canvas.width / aspectRatio
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetHeight > targetWidth) {
|
||||||
|
const scale = targetHeight / targetWidth
|
||||||
|
targetWidth = targetWidth * scale
|
||||||
|
targetHeight = targetHeight * scale
|
||||||
|
}
|
||||||
|
|
||||||
|
const x = (canvas.width - targetWidth) / 2
|
||||||
|
const y = (canvas.height - targetHeight) / 2
|
||||||
|
|
||||||
|
context.clearRect(0, 0, canvas.width, canvas.height)
|
||||||
|
context.drawImage(img, x, y, targetWidth, targetHeight)
|
||||||
|
|
||||||
|
this.$emit('start')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reader.readAsDataURL(file)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
cropCanvasAndExportToPNG (canvas) {
|
||||||
|
const ctx = canvas.getContext('2d')
|
||||||
|
|
||||||
|
const width = canvas.width
|
||||||
|
const height = canvas.height
|
||||||
|
|
||||||
|
let topmost = height
|
||||||
|
let bottommost = 0
|
||||||
|
let leftmost = width
|
||||||
|
let rightmost = 0
|
||||||
|
|
||||||
|
const imageData = ctx.getImageData(0, 0, width, height)
|
||||||
|
const pixels = imageData.data
|
||||||
|
|
||||||
|
for (let y = 0; y < height; y++) {
|
||||||
|
for (let x = 0; x < width; x++) {
|
||||||
|
const pixelIndex = (y * width + x) * 4
|
||||||
|
const alpha = pixels[pixelIndex + 3]
|
||||||
|
if (alpha !== 0) {
|
||||||
|
topmost = Math.min(topmost, y)
|
||||||
|
bottommost = Math.max(bottommost, y)
|
||||||
|
leftmost = Math.min(leftmost, x)
|
||||||
|
rightmost = Math.max(rightmost, x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const croppedWidth = rightmost - leftmost + 1
|
||||||
|
const croppedHeight = bottommost - topmost + 1
|
||||||
|
|
||||||
|
const croppedCanvas = document.createElement('canvas')
|
||||||
|
croppedCanvas.width = croppedWidth
|
||||||
|
croppedCanvas.height = croppedHeight
|
||||||
|
const croppedCtx = croppedCanvas.getContext('2d')
|
||||||
|
|
||||||
|
croppedCtx.drawImage(canvas, leftmost, topmost, croppedWidth, croppedHeight, 0, 0, croppedWidth, croppedHeight)
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
croppedCanvas.toBlob((blob) => {
|
||||||
|
if (blob) {
|
||||||
|
resolve(blob)
|
||||||
|
} else {
|
||||||
|
reject(new Error('Failed to create a PNG blob.'))
|
||||||
|
}
|
||||||
|
}, 'image/png')
|
||||||
|
})
|
||||||
},
|
},
|
||||||
async submit () {
|
async submit () {
|
||||||
if (this.modelValue) {
|
if (this.modelValue) {
|
||||||
@@ -111,7 +294,7 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
this.$refs.canvas.toBlob(async (blob) => {
|
this.cropCanvasAndExportToPNG(this.$refs.canvas).then(async (blob) => {
|
||||||
const file = new File([blob], 'signature.png', { type: 'image/png' })
|
const file = new File([blob], 'signature.png', { type: 'image/png' })
|
||||||
|
|
||||||
if (this.isDirectUpload) {
|
if (this.isDirectUpload) {
|
||||||
@@ -153,7 +336,7 @@ export default {
|
|||||||
return resolve(attachment)
|
return resolve(attachment)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}, 'image/png')
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,7 +65,7 @@
|
|||||||
@blur="onNameBlur"
|
@blur="onNameBlur"
|
||||||
>{{ field.name || defaultName }}</span>
|
>{{ field.name || defaultName }}</span>
|
||||||
<div
|
<div
|
||||||
v-if="isNameFocus"
|
v-if="isNameFocus && field.type !== 'checkbox'"
|
||||||
class="flex items-center ml-1.5"
|
class="flex items-center ml-1.5"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
|
|||||||
@@ -314,7 +314,7 @@ export default {
|
|||||||
const field = {
|
const field = {
|
||||||
name: '',
|
name: '',
|
||||||
uuid: v4(),
|
uuid: v4(),
|
||||||
required: true,
|
required: type !== 'checkbox',
|
||||||
type,
|
type,
|
||||||
submitter_uuid: this.selectedSubmitter.uuid,
|
submitter_uuid: this.selectedSubmitter.uuid,
|
||||||
areas: [area]
|
areas: [area]
|
||||||
@@ -333,7 +333,7 @@ export default {
|
|||||||
type: this.dragFieldType,
|
type: this.dragFieldType,
|
||||||
uuid: v4(),
|
uuid: v4(),
|
||||||
submitter_uuid: this.selectedSubmitter.uuid,
|
submitter_uuid: this.selectedSubmitter.uuid,
|
||||||
required: true
|
required: this.dragFieldType !== 'checkbox'
|
||||||
}
|
}
|
||||||
|
|
||||||
if (['select', 'multiple', 'radio'].includes(this.dragFieldType)) {
|
if (['select', 'multiple', 'radio'].includes(this.dragFieldType)) {
|
||||||
|
|||||||
@@ -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
|
<span class="font-medium">Click to upload</span> or drag and drop files
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -31,19 +31,21 @@
|
|||||||
v-if="isNameFocus"
|
v-if="isNameFocus"
|
||||||
class="flex items-center relative"
|
class="flex items-center relative"
|
||||||
>
|
>
|
||||||
<input
|
<template v-if="field.type !== 'checkbox'">
|
||||||
:id="`required-checkbox-${field.uuid}`"
|
<input
|
||||||
v-model="field.required"
|
:id="`required-checkbox-${field.uuid}`"
|
||||||
type="checkbox"
|
v-model="field.required"
|
||||||
class="checkbox checkbox-xs no-animation rounded"
|
type="checkbox"
|
||||||
@mousedown.prevent
|
class="checkbox checkbox-xs no-animation rounded"
|
||||||
>
|
@mousedown.prevent
|
||||||
<label
|
>
|
||||||
:for="`required-checkbox-${field.uuid}`"
|
<label
|
||||||
class="label text-xs"
|
:for="`required-checkbox-${field.uuid}`"
|
||||||
@click.prevent="field.required = !field.required"
|
class="label text-xs"
|
||||||
@mousedown.prevent
|
@click.prevent="field.required = !field.required"
|
||||||
>Required</label>
|
@mousedown.prevent
|
||||||
|
>Required</label>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-else
|
v-else
|
||||||
|
|||||||
@@ -67,6 +67,25 @@
|
|||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="fields.length < 4"
|
||||||
|
class="text-xs p-2 border border-base-200 rounded"
|
||||||
|
>
|
||||||
|
<ul class="list-disc list-outside ml-3">
|
||||||
|
<li>
|
||||||
|
Draw a text field on the page with a mouse
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Single click of the page to add a checkbox
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Drag & drop any other field type on the page
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Click on the field type above to add it to the form without drawing it on the document
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -152,7 +171,7 @@ export default {
|
|||||||
const field = {
|
const field = {
|
||||||
name: '',
|
name: '',
|
||||||
uuid: v4(),
|
uuid: v4(),
|
||||||
required: true,
|
required: type !== 'checkbox',
|
||||||
submitter_uuid: this.selectedSubmitter.uuid,
|
submitter_uuid: this.selectedSubmitter.uuid,
|
||||||
type
|
type
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
# slug :string not null
|
# slug :string not null
|
||||||
# ua :string
|
# ua :string
|
||||||
# uuid :string not null
|
# uuid :string not null
|
||||||
# values :string not null
|
# values :text not null
|
||||||
# created_at :datetime not null
|
# created_at :datetime not null
|
||||||
# updated_at :datetime not null
|
# updated_at :datetime not null
|
||||||
# submission_id :bigint not null
|
# submission_id :bigint not null
|
||||||
@@ -32,7 +32,7 @@ class Submitter < ApplicationRecord
|
|||||||
belongs_to :submission
|
belongs_to :submission
|
||||||
|
|
||||||
attribute :values, :string, default: -> { {} }
|
attribute :values, :string, default: -> { {} }
|
||||||
attribute :slug, :string, default: -> { SecureRandom.base58(8) }
|
attribute :slug, :string, default: -> { SecureRandom.base58(14) }
|
||||||
|
|
||||||
serialize :values, JSON
|
serialize :values, JSON
|
||||||
|
|
||||||
|
|||||||
@@ -6,11 +6,11 @@
|
|||||||
#
|
#
|
||||||
# id :bigint not null, primary key
|
# id :bigint not null, primary key
|
||||||
# deleted_at :datetime
|
# deleted_at :datetime
|
||||||
# fields :string not null
|
# fields :text not null
|
||||||
# name :string not null
|
# name :string not null
|
||||||
# schema :string not null
|
# schema :text not null
|
||||||
# slug :string not null
|
# slug :string not null
|
||||||
# submitters :string not null
|
# submitters :text 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
|
||||||
@@ -36,7 +36,7 @@ class Template < ApplicationRecord
|
|||||||
attribute :fields, :string, default: -> { [] }
|
attribute :fields, :string, default: -> { [] }
|
||||||
attribute :schema, :string, default: -> { [] }
|
attribute :schema, :string, default: -> { [] }
|
||||||
attribute :submitters, :string, default: -> { [{ name: DEFAULT_SUBMITTER_NAME, uuid: SecureRandom.uuid }] }
|
attribute :submitters, :string, default: -> { [{ name: DEFAULT_SUBMITTER_NAME, uuid: SecureRandom.uuid }] }
|
||||||
attribute :slug, :string, default: -> { SecureRandom.base58(8) }
|
attribute :slug, :string, default: -> { SecureRandom.base58(14) }
|
||||||
|
|
||||||
serialize :fields, JSON
|
serialize :fields, JSON
|
||||||
serialize :schema, JSON
|
serialize :schema, JSON
|
||||||
|
|||||||
+1
-1
@@ -46,7 +46,7 @@ class User < ApplicationRecord
|
|||||||
belongs_to :account
|
belongs_to :account
|
||||||
|
|
||||||
devise :database_authenticatable, :recoverable, :rememberable, :validatable, :trackable
|
devise :database_authenticatable, :recoverable, :rememberable, :validatable, :trackable
|
||||||
devise :registerable if Docuseal.multitenant?
|
devise :registerable, :omniauthable, omniauth_providers: [:google_oauth2] if Docuseal.multitenant?
|
||||||
|
|
||||||
attribute :role, :string, default: 'admin'
|
attribute :role, :string, default: 'admin'
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
<% if Docuseal.demo? %><%= render 'shared/demo_alert' %><% end %>
|
||||||
<% if @templates.any? %>
|
<% if @templates.any? %>
|
||||||
<div class="flex justify-between mb-4 items-center">
|
<div class="flex justify-between mb-4 items-center">
|
||||||
<h1 class="text-4xl font-bold">Templates</h1>
|
<h1 class="text-4xl font-bold">Templates</h1>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<div class="max-w-xl mx-auto px-2">
|
<div class="max-w-xl mx-auto px-2">
|
||||||
<h1 class="text-4xl font-bold text-center my-8">Sign up</h1>
|
<h1 class="text-3xl font-bold text-center my-8">Profile Details</h1>
|
||||||
<%= form_for('', as: resource_name, html: { class: 'space-y-6' }, url: registration_path) do |f| %>
|
<%= form_for('', as: resource_name, html: { class: 'space-y-6' }, url: new_registration_path) do |f| %>
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<%= render 'devise/shared/error_messages', resource: %>
|
<%= render 'devise/shared/error_messages', resource: %>
|
||||||
<%= f.fields_for resource do |ff| %>
|
<%= f.fields_for resource do |ff| %>
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= f.fields_for resource do |ff| %>
|
<%= f.fields_for resource do |ff| %>
|
||||||
<div class="form-control">
|
<div class="form-control <%= 'hidden' if (params[:oauth_callback] || params[:sign_up]) && resource.email? %>">
|
||||||
<%= ff.label :email, class: 'label' %>
|
<%= ff.label :email, class: 'label' %>
|
||||||
<%= ff.email_field :email, required: true, class: 'base-input' %>
|
<%= ff.email_field :email, required: true, class: 'base-input' %>
|
||||||
</div>
|
</div>
|
||||||
@@ -25,14 +25,14 @@
|
|||||||
<set-timezone data-input-id="_account_timezone"></set-timezone>
|
<set-timezone data-input-id="_account_timezone"></set-timezone>
|
||||||
<%= ff.hidden_field :timezone %>
|
<%= ff.hidden_field :timezone %>
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<%= ff.label :name, 'Company name', class: 'label' %>
|
<%= ff.label :name, 'Company name (optional)', class: 'label' %>
|
||||||
<%= ff.text_field :name, required: true, class: 'base-input' %>
|
<%= ff.text_field :name, class: 'base-input' %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= f.fields_for resource do |ff| %>
|
<%= f.fields_for resource do |ff| %>
|
||||||
<div class="form-control">
|
<div class="form-control <%= 'hidden' if params[:oauth_callback] %>">
|
||||||
<%= ff.label :password, class: 'label' %>
|
<%= ff.label :password, class: 'label' %>
|
||||||
<%= ff.password_field :password, required: true, class: 'base-input' %>
|
<%= ff.password_field :password, required: !params[:oauth_callback], class: 'base-input' %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<div class="max-w-xl mx-auto px-2">
|
||||||
|
<h1 class="text-3xl font-bold text-center my-8">Create Free Account</h1>
|
||||||
|
<%= form_for(User.new, html: { class: 'space-y-6' }, url: new_registration_path, method: :get) do |f| %>
|
||||||
|
<div class="space-y-2">
|
||||||
|
<div class="form-control">
|
||||||
|
<%= f.label :email, class: 'label' %>
|
||||||
|
<%= f.email_field :email, autofocus: true, autocomplete: 'email', class: 'base-input' %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-control">
|
||||||
|
<%= f.button button_title(title: 'Sign up', disabled_with: 'Sign up'), name: 'sign_up', value: true, class: 'base-button' %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
<% if devise_mapping.omniauthable? %>
|
||||||
|
<%= button_to button_title(title: 'Sign up with Google', icon: svg_icon('brand_google', class: 'w-6 h-6')), omniauth_authorize_path(resource_name, :google_oauth2), class: 'white-button w-full mt-4', data: { turbo: false }, method: :post %>
|
||||||
|
<% end %>
|
||||||
|
<%= render 'devise/shared/links' %>
|
||||||
|
</div>
|
||||||
@@ -23,5 +23,8 @@
|
|||||||
<%= f.button button_title(title: 'Log In', disabled_with: 'Logging In'), class: 'base-button' %>
|
<%= f.button button_title(title: 'Log In', disabled_with: 'Logging In'), class: 'base-button' %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
<% if devise_mapping.omniauthable? %>
|
||||||
|
<%= button_to button_title(title: 'Log in with Google', icon: svg_icon('brand_google', class: 'w-6 h-6')), omniauth_authorize_path(resource_name, :google_oauth2), class: 'white-button w-full mt-4', data: { turbo: false }, method: :post %>
|
||||||
|
<% end %>
|
||||||
<%= render 'devise/shared/links' %>
|
<%= render 'devise/shared/links' %>
|
||||||
</div>
|
</div>
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
<%= link_to 'Log in', new_session_path(resource_name), class: 'link link-hover' %>
|
<%= link_to 'Log in', new_session_path(resource_name), class: 'link link-hover' %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%- if devise_mapping.registerable? && controller_name != 'registrations' %>
|
<%- if devise_mapping.registerable? && controller_name != 'registrations' %>
|
||||||
<%= link_to 'Sign up', new_registration_path, class: 'link link-hover' %>
|
<%= link_to 'Create free account', registration_path, class: 'link link-hover' %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %>
|
<%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %>
|
||||||
<%= link_to 'Forgot your password?', new_password_path(resource_name), class: 'link link-hover' %>
|
<%= link_to 'Forgot your password?', new_password_path(resource_name), class: 'link link-hover' %>
|
||||||
@@ -14,9 +14,4 @@
|
|||||||
<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %>
|
<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %>
|
||||||
<%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name), class: 'link link-hover' %>
|
<%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name), class: 'link link-hover' %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%- if devise_mapping.omniauthable? %>
|
|
||||||
<%- resource_class.omniauth_providers.each do |provider| %>
|
|
||||||
<%= button_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider), class: 'link link-hover', data: { turbo: false } %>
|
|
||||||
<% end %>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
</div>
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
Verify Signed PDF
|
Verify Signed PDF
|
||||||
</div>
|
</div>
|
||||||
<div class="text-xs">
|
<div class="text-xs">
|
||||||
<span class="font-medium">Click to upload</span> or drag and drop
|
<span class="font-medium">Click to upload</span> or drag and drop files
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<input id="file" name="files[]" class="hidden" data-action="change:file-dropzone#onSelectFiles" data-target="file-dropzone.input" type="file" accept="application/pdf" multiple>
|
<input id="file" name="files[]" class="hidden" data-action="change:file-dropzone#onSelectFiles" data-target="file-dropzone.input" type="file" accept="application/pdf" multiple>
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<svg width="800" height="800" viewBox="-0.5 0 48 48" version="1.1" xmlns="http://www.w3.org/2000/svg" class="<%= local_assigns[:class] %>">
|
||||||
|
<g id="Icons" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<g id="Color-" transform="translate(-401.000000, -860.000000)">
|
||||||
|
<g id="Google" transform="translate(401.000000, 860.000000)">
|
||||||
|
<path d="M9.82727273,24 C9.82727273,22.4757333 10.0804318,21.0144 10.5322727,19.6437333 L2.62345455,13.6042667 C1.08206818,16.7338667 0.213636364,20.2602667 0.213636364,24 C0.213636364,27.7365333 1.081,31.2608 2.62025,34.3882667 L10.5247955,28.3370667 C10.0772273,26.9728 9.82727273,25.5168 9.82727273,24" id="Fill-1" fill="#FBBC05">
|
||||||
|
</path>
|
||||||
|
<path d="M23.7136364,10.1333333 C27.025,10.1333333 30.0159091,11.3066667 32.3659091,13.2266667 L39.2022727,6.4 C35.0363636,2.77333333 29.6954545,0.533333333 23.7136364,0.533333333 C14.4268636,0.533333333 6.44540909,5.84426667 2.62345455,13.6042667 L10.5322727,19.6437333 C12.3545909,14.112 17.5491591,10.1333333 23.7136364,10.1333333" id="Fill-2" fill="#EB4335">
|
||||||
|
</path>
|
||||||
|
<path d="M23.7136364,37.8666667 C17.5491591,37.8666667 12.3545909,33.888 10.5322727,28.3562667 L2.62345455,34.3946667 C6.44540909,42.1557333 14.4268636,47.4666667 23.7136364,47.4666667 C29.4455,47.4666667 34.9177955,45.4314667 39.0249545,41.6181333 L31.5177727,35.8144 C29.3995682,37.1488 26.7323182,37.8666667 23.7136364,37.8666667" id="Fill-3" fill="#34A853">
|
||||||
|
</path>
|
||||||
|
<path d="M46.1454545,24 C46.1454545,22.6133333 45.9318182,21.12 45.6113636,19.7333333 L23.7136364,19.7333333 L23.7136364,28.8 L36.3181818,28.8 C35.6879545,31.8912 33.9724545,34.2677333 31.5177727,35.8144 L39.0249545,41.6181333 C43.3393409,37.6138667 46.1454545,31.6490667 46.1454545,24" id="Fill-4" fill="#4285F4">
|
||||||
|
</path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,6 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="<%= local_assigns[:class] %>" width="44" height="44" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||||
|
<path d="M3 19c3.333 -2 5 -4 5 -6c0 -3 -1 -3 -2 -3s-2.032 1.085 -2 3c.034 2.048 1.658 2.877 2.5 4c1.5 2 2.5 2.5 3.5 1c.667 -1 1.167 -1.833 1.5 -2.5c1 2.333 2.333 3.5 4 3.5h2.5"></path>
|
||||||
|
<path d="M20 17v-12c0 -1.121 -.879 -2 -2 -2s-2 .879 -2 2v12l2 2l2 -2z"></path>
|
||||||
|
<path d="M16 7h4"></path>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 584 B |
@@ -1,5 +1,5 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html data-theme="docuseal">
|
<html data-theme="docuseal" lang="en">
|
||||||
<head>
|
<head>
|
||||||
<title>
|
<title>
|
||||||
DocuSeal | Open Source Document Filling and Signing
|
DocuSeal | Open Source Document Filling and Signing
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html data-theme="docuseal">
|
<html data-theme="docuseal" lang="en">
|
||||||
<head>
|
<head>
|
||||||
<title>
|
<title>
|
||||||
DocuSeal | Open Source Document Filling and Signing
|
DocuSeal | Open Source Document Filling and Signing
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html data-theme="docuseal">
|
<html data-theme="docuseal" lang="en">
|
||||||
<head>
|
<head>
|
||||||
<title>
|
<title>
|
||||||
DocuSeal | Open Source Document Filling and Signing
|
DocuSeal | Open Source Document Filling and Signing
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
DocuSeal
|
DocuSeal
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
<h2 class="mb-16 text-lg text-center text-gray-500">
|
<h2 class="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>
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<h3 class="mb-4 text-2xl font-semibold">Easy to Start</h3>
|
<h3 class="mb-4 text-2xl font-semibold">Easy to Start</h3>
|
||||||
<p class="text-base text-gray-500">
|
<p class="text-base text-gray-500">
|
||||||
Run on your own host using <a href="https://hub.docker.com/r/docuseal/docuseal" class="link link-neutral font-bold" target="_blank">Docker</a> container, or deploy on your favorite managed PaaS with a single <a href="https:///www.docuseal.co/install" class="link link-neutral font-bold">click</a>.
|
Run on your own host using <a href="https://hub.docker.com/r/docuseal/docuseal" class="link link-neutral font-bold" target="_blank">Docker</a> container, or deploy on your favorite managed PaaS with a single <a href="https://www.docuseal.co/install" class="link link-neutral font-bold">click</a>.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -70,3 +70,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
<%= render 'shared/attribution' %>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<div class="mx-auto mt-1.5">
|
||||||
|
<div class="py-3 rounded-2xl flex items-center justify-between mx-4 md:mx-0">
|
||||||
|
<div class="w-full text-center">
|
||||||
|
<span class="font-bold">Demo Environment</span>
|
||||||
|
<br>
|
||||||
|
Feel free to
|
||||||
|
<a href="<%= new_template_path %>" data-turbo-frame="modal" class="inline underline font-medium">create a new template</a> document form or
|
||||||
|
<a href="<%= start_form_url(slug: ::Template.first&.slug) %>" target="_blank" class="inline underline font-medium">submit the existing one</a> 😊
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<a href="https://github.com/docusealco/docuseal" class="btn btn-neutral btn-sm btn-outline inline-flex items-center justify-center" target="_blank" alt="Star on GitHub" style="height: 37px">
|
||||||
|
<span>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||||
|
<path fill="currentColor" d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<span class="flex">
|
||||||
|
<span class="hidden lg:block">Star on </span>GitHub
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
@@ -4,13 +4,19 @@
|
|||||||
<%= render 'shared/logo' %>
|
<%= render 'shared/logo' %>
|
||||||
<span>DocuSeal</span>
|
<span>DocuSeal</span>
|
||||||
</a>
|
</a>
|
||||||
<a href="<%= Docuseal::GITHUB_URL %>" target="_blank" class="inline">
|
<% unless Docuseal.demo? %>
|
||||||
<img alt="GitHub Repo stars" src="https://img.shields.io/github/stars/docusealco/docuseal?style=social&label=" style="height: 22px">
|
<a href="<%= Docuseal::GITHUB_URL %>" target="_blank" class="inline">
|
||||||
</a>
|
<img alt="GitHub Repo stars" src="https://www.docuseal.co/github-badge.svg" style="height: 22px">
|
||||||
|
</a>
|
||||||
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% if signed_in? %>
|
<% if signed_in? %>
|
||||||
<div class="space-x-6">
|
<div class="space-x-4 flex items-center">
|
||||||
<%= link_to 'Settings', settings_profile_index_path, class: 'hidden md:inline font-medium text-lg' %>
|
<% if Docuseal.demo? %>
|
||||||
|
<%= render 'shared/github_button' %>
|
||||||
|
<% else %>
|
||||||
|
<%= link_to 'Settings', settings_profile_index_path, class: 'hidden md:inline font-medium text-lg mr-2' %>
|
||||||
|
<% end %>
|
||||||
<div class="dropdown dropdown-end">
|
<div class="dropdown dropdown-end">
|
||||||
<label tabindex="0" class="cursor-pointer bg-base-content text-purple-300 rounded-full p-2 w-9 justify-center flex">
|
<label tabindex="0" class="cursor-pointer bg-base-content text-purple-300 rounded-full p-2 w-9 justify-center flex">
|
||||||
<span class="text-sm align-text-top"><%= current_user.initials %></span>
|
<span class="text-sm align-text-top"><%= current_user.initials %></span>
|
||||||
@@ -23,8 +29,8 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<%= link_to destroy_user_session_path, data: { turbo_method: :delete } do %>
|
<%= button_to destroy_user_session_path, method: :delete, data: { turbo: false } do %>
|
||||||
<%= svg_icon('logout', class: 'w-5 h-5 stroke-2') %>
|
<%= svg_icon('logout', class: 'w-5 h-5 stroke-2 inline') %>
|
||||||
<span class="mr-1">Sign out</span>
|
<span class="mr-1">Sign out</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</li>
|
</li>
|
||||||
@@ -32,11 +38,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<%= link_to new_user_session_path, class: 'font-medium text-lg' do %>
|
<div class="flex space-x-6">
|
||||||
<span class="flex items-center justify-center space-x-1">
|
<%= link_to new_user_session_path, class: 'font-medium text-lg' do %>
|
||||||
<%= svg_icon('login', class: 'w-6 h-6') %>
|
<span class="flex items-center justify-center space-x-1">
|
||||||
<span>Sign in</span>
|
<%= svg_icon('login', class: 'w-6 h-6') %>
|
||||||
</span>
|
<span>Sign in</span>
|
||||||
<% end %>
|
</span>
|
||||||
|
<% end %>
|
||||||
|
<% if Docuseal.multitenant? %>
|
||||||
|
<%= link_to registration_path, class: 'font-medium text-lg hidden md:inline' do %>
|
||||||
|
<span class="flex items-center justify-center space-x-1">
|
||||||
|
<%= svg_icon('user_plus', class: 'w-6 h-6') %>
|
||||||
|
<span>Try for Free</span>
|
||||||
|
</span>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
@@ -44,5 +44,8 @@
|
|||||||
<%= svg_icon('send', class: 'h-5 w-5') %>
|
<%= svg_icon('send', class: 'h-5 w-5') %>
|
||||||
Tell about us
|
Tell about us
|
||||||
</a>
|
</a>
|
||||||
|
<a href="mailto:<%= Docuseal::SUPPORT_EMAIL %>" target="_blank" class="w-full block mt-4 underline text-center">
|
||||||
|
<%= Docuseal::SUPPORT_EMAIL %>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<turbo-frame id="modal">
|
<turbo-frame id="modal">
|
||||||
<turbo-modal class="modal modal-open items-start !animate-none">
|
<turbo-modal class="modal modal-open items-start !animate-none overflow-y-auto">
|
||||||
<div class="absolute top-0 bottom-0 right-0 left-0" data-action="click:turbo-modal#close"></div>
|
<div class="absolute top-0 bottom-0 right-0 left-0" data-action="click:turbo-modal#close"></div>
|
||||||
<div class="modal-box pt-4 pb-6 px-6 mt-20">
|
<div class="modal-box pt-4 pb-6 px-6 mt-20 max-h-none">
|
||||||
<% if local_assigns[:title] %>
|
<% if local_assigns[:title] %>
|
||||||
<div class="flex justify-between items-center border-b pb-2 mb-2 font-medium">
|
<div class="flex justify-between items-center border-b pb-2 mb-2 font-medium">
|
||||||
<span>
|
<span>
|
||||||
|
|||||||
@@ -9,15 +9,10 @@
|
|||||||
<h1 class="text-5xl font-bold text-center">DocuSeal</h1>
|
<h1 class="text-5xl font-bold text-center">DocuSeal</h1>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center bg-base-300 rounded-xl p-4 mb-4">
|
<div class="flex items-center bg-base-200 rounded-xl p-4 mb-4">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<div class="mr-3">
|
<div class="mr-3">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-10 h-10" width="44" height="44" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
<%= svg_icon('writing_sign', class: 'w-10 h-10') %>
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
|
||||||
<path d="M3 19c3.333 -2 5 -4 5 -6c0 -3 -1 -3 -2 -3s-2.032 1.085 -2 3c.034 2.048 1.658 2.877 2.5 4c1.5 2 2.5 2.5 3.5 1c.667 -1 1.167 -1.833 1.5 -2.5c1 2.333 2.333 3.5 4 3.5h2.5" />
|
|
||||||
<path d="M20 17v-12c0 -1.121 -.879 -2 -2 -2s-2 .879 -2 2v12l2 2l2 -2z" />
|
|
||||||
<path d="M16 7h4" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p class="text-lg font-bold mb-1"><%= @submitter.submission.template.name %></p>
|
<p class="text-lg font-bold mb-1"><%= @submitter.submission.template.name %></p>
|
||||||
|
|||||||
@@ -1,43 +1,36 @@
|
|||||||
<div class="max-w-xl mx-auto px-2 mt-12 mb-4">
|
<div class="max-w-md mx-auto px-2 mt-12 mb-4">
|
||||||
<div class="card bg-base-200">
|
<div class="space-y-6 mx-auto">
|
||||||
<div class="card-body">
|
<div class="space-y-6">
|
||||||
<div class="space-y-4 w-full md:px-10 mx-auto">
|
<div class="text-center w-full space-y-6">
|
||||||
<div class="space-y-4">
|
<a href="/" class="flex justify-center items-center">
|
||||||
<a href="/" class="flex justify-center">
|
<span class="mr-3">
|
||||||
<span class="mr-3">
|
<%= render 'shared/logo', width: '50px', height: '50px' %>
|
||||||
<%= render 'shared/logo', width: '50px', height: '50px' %>
|
</span>
|
||||||
</span>
|
<h1 class="text-5xl font-bold text-center">DocuSeal</h1>
|
||||||
<h1 class="text-5xl font-bold text-center">DocuSeal</h1>
|
</a>
|
||||||
</a>
|
<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 the form</p>
|
</div>
|
||||||
<div class="flex items-center bg-base-content/10 rounded-xl p-4">
|
<div class="flex items-center bg-base-200 rounded-xl p-4 mb-4">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<div class="mr-3">
|
<div class="mr-3">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-10 h-10" width="44" height="44" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
<%= svg_icon('writing_sign', class: 'w-10 h-10') %>
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
</div>
|
||||||
<path d="M3 19c3.333 -2 5 -4 5 -6c0 -3 -1 -3 -2 -3s-2.032 1.085 -2 3c.034 2.048 1.658 2.877 2.5 4c1.5 2 2.5 2.5 3.5 1c.667 -1 1.167 -1.833 1.5 -2.5c1 2.333 2.333 3.5 4 3.5h2.5" />
|
<div>
|
||||||
<path d="M20 17v-12c0 -1.121 -.879 -2 -2 -2s-2 .879 -2 2v12l2 2l2 -2z" />
|
<p class="text-lg font-bold mb-1"><%= @template.name %></p>
|
||||||
<path d="M16 7h4" />
|
<p class="text-sm">Invited by <span class="font-semibold"><%= @template.account.name %></span></p>
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p class="font-bold mb-1"><%= @template.name %></p>
|
|
||||||
<p class="text-sm">Invited by <%= @template.account.name %></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<%= form_for @submitter, url: start_form_path(@template.slug), data: { turbo_frame: :_top }, method: :put, html: { class: 'space-y-4' } do |f| %>
|
|
||||||
<div class="form-control !mt-0">
|
|
||||||
<%= f.label :email, class: 'label' %>
|
|
||||||
<%= f.email_field :email, required: true, class: 'base-input', placeholder: 'Provide your email to start' %>
|
|
||||||
</div>
|
|
||||||
<div class="form-control">
|
|
||||||
<%= f.button button_title(title: 'Start', disabled_with: 'Starting'), class: 'base-button' %>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<%= form_for @submitter, url: start_form_path(@template.slug), data: { turbo_frame: :_top }, method: :put, html: { class: 'space-y-4' } do |f| %>
|
||||||
|
<div class="form-control !mt-0">
|
||||||
|
<%= f.label :email, class: 'label' %>
|
||||||
|
<%= f.email_field :email, required: true, class: 'base-input', placeholder: 'Provide your email to start' %>
|
||||||
|
</div>
|
||||||
|
<div class="form-control">
|
||||||
|
<%= f.button button_title(title: 'Start', disabled_with: 'Starting'), class: 'base-button' %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<%= render 'shared/attribution' %>
|
<%= render 'shared/attribution' %>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<div style="max-width: 1600px" class="mx-auto pl-4">
|
<div style="max-width: 1600px" class="mx-auto pl-4">
|
||||||
<div class="flex justify-between py-1.5 items-center pr-4">
|
<div class="flex justify-between py-1.5 items-center pr-4 sticky top-0 md:relative z-10 bg-base-100">
|
||||||
<a href="<%= template_path(@submission.template) %>" class="flex space-x-3 py-1">
|
<a href="<%= template_path(@submission.template) %>" class="flex space-x-3 py-1">
|
||||||
<span><%= render 'shared/logo', width: 40, height: 40 %></span>
|
<span><%= render 'shared/logo', width: 40, height: 40 %></span>
|
||||||
<span class="text-3xl font-semibold focus:text-clip"><%= @submission.template.name %></span>
|
<span class="text-3xl font-semibold focus:text-clip"><%= @submission.template.name %></span>
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex" style="max-height: calc(100vh - 60px)">
|
<div class="flex md:max-h-[calc(100vh-60px)]">
|
||||||
<div class="overflow-y-auto overflow-x-hidden hidden lg:block w-52 flex-none pr-3 mt-0.5 pt-0.5">
|
<div class="overflow-y-auto overflow-x-hidden hidden lg:block w-52 flex-none pr-3 mt-0.5 pt-0.5">
|
||||||
<% @submission.template.schema.each do |item| %>
|
<% @submission.template.schema.each do |item| %>
|
||||||
<% document = @submission.template.documents.find { |a| item['attachment_uuid'] == a.uuid } %>
|
<% document = @submission.template.documents.find { |a| item['attachment_uuid'] == a.uuid } %>
|
||||||
@@ -82,7 +82,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<% if submitter && !submitter.completed_at? %>
|
<% if submitter && !submitter.completed_at? %>
|
||||||
<div class="mt-2 mb-1">
|
<div class="mt-2 mb-1">
|
||||||
<a class="btn btn-sm btn-primary w-full" target="_blank" href="<%= submit_form_url(slug: submitter.slug) %>">
|
<a class="btn btn-sm btn-primary w-full" target="_blank" href="<%= submit_form_path(slug: submitter.slug) %>">
|
||||||
Submit Form
|
Submit Form
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,15 +9,10 @@
|
|||||||
<h1 class="text-5xl font-bold text-center">DocuSeal</h1>
|
<h1 class="text-5xl font-bold text-center">DocuSeal</h1>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center bg-base-300 rounded-xl p-4 mb-4">
|
<div class="flex items-center bg-base-200 rounded-xl p-4 mb-4">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<div class="mr-3">
|
<div class="mr-3">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-10 h-10" width="44" height="44" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
<%= svg_icon('writing_sign', class: 'w-10 h-10') %>
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
|
||||||
<path d="M3 19c3.333 -2 5 -4 5 -6c0 -3 -1 -3 -2 -3s-2.032 1.085 -2 3c.034 2.048 1.658 2.877 2.5 4c1.5 2 2.5 2.5 3.5 1c.667 -1 1.167 -1.833 1.5 -2.5c1 2.333 2.333 3.5 4 3.5h2.5" />
|
|
||||||
<path d="M20 17v-12c0 -1.121 -.879 -2 -2 -2s-2 .879 -2 2v12l2 2l2 -2z" />
|
|
||||||
<path d="M16 7h4" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p class="text-lg font-bold mb-1"><%= @submitter.submission.template.name %></p>
|
<p class="text-lg font-bold mb-1"><%= @submitter.submission.template.name %></p>
|
||||||
@@ -28,7 +23,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<% if Accounts.can_send_emails?(@submitter.submission.template.account) %>
|
<% if Accounts.can_send_emails?(@submitter.submission.template.account) %>
|
||||||
<%= button_to button_title(title: 'Send copy to Email', disabled_with: 'Sending', icon: svg_icon('mail_forward', class: 'w-6 h-6')), send_submission_email_index_path, params: { submitter_slug: @submitter.slug }, form: { onsubmit: 'event.submitter.disabled = true' }, class: 'base-button w-full' %>
|
<%= button_to button_title(title: 'Send copy to Email', disabled_with: 'Sending', icon: svg_icon('mail_forward', class: 'w-6 h-6')), send_submission_email_index_path, params: { submitter_slug: @submitter.slug }, form: { onsubmit: 'event.submitter.disabled = true' }, class: 'white-button w-full' %>
|
||||||
<div class="divider">OR</div>
|
<div class="divider">OR</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<download-button data-src="<%= submitter_download_index_path(@submitter.slug) %>" class="base-button w-full">
|
<download-button data-src="<%= submitter_download_index_path(@submitter.slug) %>" class="base-button w-full">
|
||||||
|
|||||||
@@ -32,9 +32,7 @@
|
|||||||
<div class="fixed bottom-0 w-full h-0 z-20">
|
<div class="fixed bottom-0 w-full h-0 z-20">
|
||||||
<div class="mx-auto" style="max-width: 1000px">
|
<div class="mx-auto" style="max-width: 1000px">
|
||||||
<div class="relative md:mx-32">
|
<div class="relative md:mx-32">
|
||||||
<div id="form_container" class="shadow-md bg-base-100 absolute bottom-0 md:bottom-4 w-full border border-base-200 border p-4 rounded">
|
<submission-form data-is-demo="<%= Docuseal.demo? %>" data-is-direct-upload="<%= Docuseal.active_storage_public? %>" data-submitter-uuid="<%= @submitter.uuid %>" data-submitter-slug="<%= @submitter.slug %>" data-can-send-email="<%= Accounts.can_send_emails?(Struct.new(:id).new(@submitter.submission.template.account_id)) %>" data-attachments="<%= attachments_index.values.select { |e| e.record_id == @submitter.id }.to_json(only: %i[uuid], methods: %i[url filename content_type]) %>" data-fields="<%= @submitter.submission.template.fields.select { |f| f['submitter_uuid'] == @submitter.uuid }.to_json %>" data-values="<%= @submitter.values.to_json %>" data-authenticity-token="<%= form_authenticity_token %>"></submission-form>
|
||||||
<submission-form data-is-direct-upload="<%= Docuseal.active_storage_public? %>" data-submitter-uuid="<%= @submitter.uuid %>" data-submitter-slug="<%= @submitter.slug %>" data-can-send-email="<%= Accounts.can_send_emails?(Struct.new(:id).new(@submitter.submission.template.account_id)) %>" data-attachments="<%= attachments_index.values.select { |e| e.record_id == @submitter.id }.to_json(only: %i[uuid], methods: %i[url filename content_type]) %>" data-fields="<%= @submitter.submission.template.fields.select { |f| f['submitter_uuid'] == @submitter.uuid }.to_json %>" data-values="<%= @submitter.values.to_json %>" data-authenticity-token="<%= form_authenticity_token %>"></submission-form>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -157,13 +157,19 @@
|
|||||||
<% else %>
|
<% else %>
|
||||||
<div class="card bg-base-200">
|
<div class="card bg-base-200">
|
||||||
<div class="card-body text-center py-16">
|
<div class="card-body text-center py-16">
|
||||||
<div class="max-w-md 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>
|
||||||
<p class="text-gray-600">Send an invitation to fill and submit the documents</p>
|
<p class="text-gray-600">Send an invitation to fill and complete the form</p>
|
||||||
<%= link_to new_template_submission_path(@template), class: 'base-button mt-6', data: { turbo_frame: 'modal' } do %>
|
<div class="space-x-2">
|
||||||
<%= svg_icon('plus', class: 'w-6 h-6 stroke-2') %>
|
<%= link_to new_template_submission_path(@template), class: 'base-button mt-6', data: { turbo_frame: 'modal' } do %>
|
||||||
<span class="mr-1">Add Recipients</span>
|
<%= svg_icon('plus', class: 'w-6 h-6 stroke-2') %>
|
||||||
<% end %>
|
<span class="mr-1">Add Recipients</span>
|
||||||
|
<% end %>
|
||||||
|
<%= link_to start_form_url(slug: @template.slug), class: 'white-button mt-6', target: '_blank', rel: 'noopener' do %>
|
||||||
|
<%= svg_icon('writing', class: 'w-6 h-6') %>
|
||||||
|
<span class="mr-1">Submit it Yourself</span>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+10
-1
@@ -1,6 +1,7 @@
|
|||||||
default: &default
|
default: &default
|
||||||
adapter: postgresql
|
adapter: postgresql
|
||||||
encoding: unicode
|
encoding: unicode
|
||||||
|
pool: <%= ENV.fetch('RAILS_MAX_THREADS', 15) %>
|
||||||
|
|
||||||
development:
|
development:
|
||||||
<<: *default
|
<<: *default
|
||||||
@@ -10,8 +11,16 @@ test:
|
|||||||
<<: *default
|
<<: *default
|
||||||
database: docuseal_test
|
database: docuseal_test
|
||||||
|
|
||||||
<% if ENV['DATABASE_URL'].to_s.empty? %>
|
|
||||||
production:
|
production:
|
||||||
|
<% if ENV['DATABASE_URL'].to_s.empty? %>
|
||||||
adapter: sqlite3
|
adapter: sqlite3
|
||||||
database: <%= ENV['WORKDIR'] || '.' %>/db.sqlite3
|
database: <%= ENV['WORKDIR'] || '.' %>/db.sqlite3
|
||||||
|
<% elsif ENV['DATABASE_URL'].match?(/\Apostgres/) %>
|
||||||
|
<<: *default
|
||||||
|
url: <%= ENV['DATABASE_URL'] %>
|
||||||
|
<% elsif ENV['DATABASE_URL'].match?(/\Amysql/) %>
|
||||||
|
adapter: mysql2
|
||||||
|
encoding: utf8mb4
|
||||||
|
pool: <%= ENV.fetch('RAILS_MAX_THREADS', 15) %>
|
||||||
|
url: <%= ENV['DATABASE_URL'] %>
|
||||||
<% end %>
|
<% end %>
|
||||||
@@ -45,6 +45,8 @@ Rails.application.configure do
|
|||||||
config.cache_store = :null_store
|
config.cache_store = :null_store
|
||||||
end
|
end
|
||||||
|
|
||||||
|
config.active_job.queue_adapter = :sidekiq if defined?(Sidekiq)
|
||||||
|
|
||||||
# Store uploaded files on the local file system (see config/storage.yml for options).
|
# Store uploaded files on the local file system (see config/storage.yml for options).
|
||||||
config.active_storage.service = :disk
|
config.active_storage.service = :disk
|
||||||
config.active_storage.resolve_model_to_route = :rails_storage_proxy
|
config.active_storage.resolve_model_to_route = :rails_storage_proxy
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ Rails.application.configure do
|
|||||||
config.action_controller.perform_caching = true
|
config.action_controller.perform_caching = true
|
||||||
config.active_record.sqlite3_production_warning = false
|
config.active_record.sqlite3_production_warning = false
|
||||||
|
|
||||||
|
config.active_job.queue_adapter = :sidekiq if defined?(Sidekiq)
|
||||||
|
|
||||||
# Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"]
|
# Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"]
|
||||||
# or in config/master.key. This key is used to decrypt credentials (and other encrypted files).
|
# or in config/master.key. This key is used to decrypt credentials (and other encrypted files).
|
||||||
# config.require_master_key = true
|
# config.require_master_key = true
|
||||||
@@ -74,7 +76,7 @@ Rails.application.configure do
|
|||||||
# Set this to true and configure the email server for immediate delivery to raise delivery errors.
|
# Set this to true and configure the email server for immediate delivery to raise delivery errors.
|
||||||
config.action_mailer.raise_delivery_errors = false
|
config.action_mailer.raise_delivery_errors = false
|
||||||
|
|
||||||
if ENV['SMTP_USERNAME']
|
if ENV['SMTP_ADDRESS']
|
||||||
config.action_mailer.delivery_method = :smtp
|
config.action_mailer.delivery_method = :smtp
|
||||||
config.action_mailer.smtp_settings = {
|
config.action_mailer.smtp_settings = {
|
||||||
address: ENV.fetch('SMTP_ADDRESS', nil),
|
address: ENV.fetch('SMTP_ADDRESS', nil),
|
||||||
@@ -84,7 +86,7 @@ Rails.application.configure do
|
|||||||
password: ENV.fetch('SMTP_PASSWORD', nil),
|
password: ENV.fetch('SMTP_PASSWORD', nil),
|
||||||
authentication: ENV.fetch('SMTP_AUTHENTICATION', 'plain'),
|
authentication: ENV.fetch('SMTP_AUTHENTICATION', 'plain'),
|
||||||
enable_starttls_auto: ENV['SMTP_ENABLE_STARTTLS_AUTO'] == 'true'
|
enable_starttls_auto: ENV['SMTP_ENABLE_STARTTLS_AUTO'] == 'true'
|
||||||
}
|
}.compact
|
||||||
end
|
end
|
||||||
|
|
||||||
# Enable locale fallbacks for I18n (makes lookups for any locale fall back to
|
# Enable locale fallbacks for I18n (makes lookups for any locale fall back to
|
||||||
@@ -110,7 +112,7 @@ Rails.application.configure do
|
|||||||
config.active_record.encryption = {
|
config.active_record.encryption = {
|
||||||
primary_key: encryption_secret.first(32),
|
primary_key: encryption_secret.first(32),
|
||||||
deterministic_key: encryption_secret.last(32),
|
deterministic_key: encryption_secret.last(32),
|
||||||
key_derivation_salt: encryption_secret
|
key_derivation_salt: Digest::SHA256.hexdigest(encryption_secret)
|
||||||
}
|
}
|
||||||
|
|
||||||
# Do not dump schema after migrations.
|
# Do not dump schema after migrations.
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ Devise.setup do |config|
|
|||||||
# a value less than 10 in other environments. Note that, for bcrypt (the default
|
# a value less than 10 in other environments. Note that, for bcrypt (the default
|
||||||
# algorithm), the cost increases exponentially with the number of stretches (e.g.
|
# algorithm), the cost increases exponentially with the number of stretches (e.g.
|
||||||
# a value of 20 is already extremely slow: approx. 60 seconds for 1 calculation).
|
# a value of 20 is already extremely slow: approx. 60 seconds for 1 calculation).
|
||||||
config.stretches = Rails.env.test? ? 1 : 12
|
config.stretches = Rails.env.test? ? 1 : 10
|
||||||
|
|
||||||
# Set up a pepper to generate the hashed password.
|
# Set up a pepper to generate the hashed password.
|
||||||
# Send a notification to the original email when the user's email is changed.
|
# Send a notification to the original email when the user's email is changed.
|
||||||
@@ -266,7 +266,7 @@ Devise.setup do |config|
|
|||||||
# ==> OmniAuth
|
# ==> OmniAuth
|
||||||
# Add a new OmniAuth provider. Check the wiki for more information on setting
|
# Add a new OmniAuth provider. Check the wiki for more information on setting
|
||||||
# up on your models and hooks.
|
# up on your models and hooks.
|
||||||
# config.omniauth :github, 'APP_ID', 'APP_SECRET', scope: 'user,public_repo'
|
config.omniauth :google_oauth2, ENV.fetch('GOOGLE_CLIENT_ID', nil), ENV.fetch('GOOGLE_CLIENT_SECRET', nil), {}
|
||||||
|
|
||||||
# ==> Warden configuration
|
# ==> Warden configuration
|
||||||
# If you want to use other strategies, that are not supported by Devise, or
|
# If you want to use other strategies, that are not supported by Devise, or
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
Rollbar.configure do |config|
|
if defined?(Rollbar)
|
||||||
config.access_token = ENV.fetch('ROLLBAR_ACCESS_TOKEN', nil)
|
Rollbar.configure do |config|
|
||||||
|
config.access_token = ENV.fetch('ROLLBAR_ACCESS_TOKEN', nil)
|
||||||
|
|
||||||
config.enabled = !config.access_token.nil?
|
config.enabled = true
|
||||||
|
|
||||||
config.exception_level_filters['ActionController::RoutingError'] = 'ignore'
|
config.exception_level_filters['ActionController::RoutingError'] = 'ignore'
|
||||||
|
|
||||||
config.environment = ENV['ROLLBAR_ENV'].presence || Rails.env
|
config.environment = ENV['ROLLBAR_ENV'].presence || Rails.env
|
||||||
|
end
|
||||||
end
|
end
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
if defined?(Sidekiq)
|
||||||
|
require 'sidekiq/web'
|
||||||
|
|
||||||
|
Sidekiq::Web.use(Rack::Auth::Basic) do |_, password|
|
||||||
|
next true if Rails.env.development?
|
||||||
|
|
||||||
|
ActiveSupport::SecurityUtils.secure_compare(
|
||||||
|
Digest::SHA256.hexdigest(password),
|
||||||
|
Digest::SHA256.hexdigest(ENV.fetch('SIDEKIQ_BASIC_AUTH_PASSWORD'))
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
Sidekiq.strict_args!
|
||||||
|
end
|
||||||
+10
-2
@@ -2,14 +2,22 @@
|
|||||||
|
|
||||||
Rails.application.routes.draw do
|
Rails.application.routes.draw do
|
||||||
mount LetterOpenerWeb::Engine, at: '/letter_opener' if Rails.env.development?
|
mount LetterOpenerWeb::Engine, at: '/letter_opener' if Rails.env.development?
|
||||||
|
mount Sidekiq::Web => '/sidekiq' if defined?(Sidekiq)
|
||||||
|
|
||||||
root 'dashboard#index'
|
root 'dashboard#index'
|
||||||
|
|
||||||
devise_for :users, path: '/', only: %i[sessions passwords]
|
devise_for :users,
|
||||||
|
path: '/', only: %i[sessions passwords omniauth_callbacks],
|
||||||
|
controllers: begin
|
||||||
|
options = { sessions: 'sessions' }
|
||||||
|
options[:omniauth_callbacks] = 'omniauth_callbacks' if Docuseal.multitenant?
|
||||||
|
options
|
||||||
|
end
|
||||||
|
|
||||||
devise_scope :user do
|
devise_scope :user do
|
||||||
if Docuseal.multitenant?
|
if Docuseal.multitenant?
|
||||||
resource :registration, only: %i[create], path: 'sign_up' do
|
resource :registration, only: %i[show], path: 'sign_up'
|
||||||
|
resource :registration, only: %i[create], path: 'new' do
|
||||||
get '' => :new, as: :new
|
get '' => :new, as: :new
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
queues:
|
||||||
|
- [default, 1]
|
||||||
|
- [mailers, 1]
|
||||||
|
|
||||||
|
production:
|
||||||
|
:concurrency: 15
|
||||||
+4
-4
@@ -86,7 +86,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_07_01_075115) do
|
|||||||
t.string "uuid", null: false
|
t.string "uuid", null: false
|
||||||
t.string "email", null: false
|
t.string "email", null: false
|
||||||
t.string "slug", null: false
|
t.string "slug", null: false
|
||||||
t.string "values", null: false
|
t.text "values", null: false
|
||||||
t.string "ua"
|
t.string "ua"
|
||||||
t.string "ip"
|
t.string "ip"
|
||||||
t.datetime "sent_at"
|
t.datetime "sent_at"
|
||||||
@@ -102,9 +102,9 @@ ActiveRecord::Schema[7.0].define(version: 2023_07_01_075115) do
|
|||||||
create_table "templates", force: :cascade do |t|
|
create_table "templates", force: :cascade do |t|
|
||||||
t.string "slug", null: false
|
t.string "slug", null: false
|
||||||
t.string "name", null: false
|
t.string "name", null: false
|
||||||
t.string "schema", null: false
|
t.text "schema", null: false
|
||||||
t.string "fields", null: false
|
t.text "fields", null: false
|
||||||
t.string "submitters", null: false
|
t.text "submitters", null: false
|
||||||
t.bigint "author_id", null: false
|
t.bigint "author_id", null: false
|
||||||
t.bigint "account_id", null: false
|
t.bigint "account_id", null: false
|
||||||
t.datetime "deleted_at"
|
t.datetime "deleted_at"
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
depends_on:
|
||||||
|
postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
image: docuseal/docuseal:latest
|
||||||
|
ports:
|
||||||
|
- 3000:3000
|
||||||
|
volumes:
|
||||||
|
- .:/data
|
||||||
|
environment:
|
||||||
|
FORCE_SSL: 'true'
|
||||||
|
DATABASE_URL: postgresql://postgres:postgres@postgres:5432/docuseal
|
||||||
|
|
||||||
|
postgres:
|
||||||
|
image: postgres:latest
|
||||||
|
volumes:
|
||||||
|
- './pg_data:/var/lib/postgresql/data'
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: postgres
|
||||||
|
POSTGRES_PASSWORD: postgres
|
||||||
|
POSTGRES_DB: docuseal
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
|
||||||
|
caddy:
|
||||||
|
image: caddy:latest
|
||||||
|
command: caddy reverse-proxy --from $HOST --to app:3000
|
||||||
|
ports:
|
||||||
|
- 80:80
|
||||||
|
- 443:443
|
||||||
|
- 443:443/udp
|
||||||
|
volumes:
|
||||||
|
- .:/data
|
||||||
|
environment:
|
||||||
|
- HOST=${HOST}
|
||||||
@@ -3,6 +3,45 @@
|
|||||||
module Accounts
|
module Accounts
|
||||||
module_function
|
module_function
|
||||||
|
|
||||||
|
def create_duplicate(account)
|
||||||
|
new_account = account.dup
|
||||||
|
|
||||||
|
new_user = account.users.first.dup
|
||||||
|
|
||||||
|
new_user.account = new_account
|
||||||
|
new_user.encrypted_password = SecureRandom.hex
|
||||||
|
new_user.email = "#{SecureRandom.hex}@docuseal.co"
|
||||||
|
|
||||||
|
account.templates.each do |template|
|
||||||
|
new_template = template.dup
|
||||||
|
|
||||||
|
new_template.account = new_account
|
||||||
|
new_template.slug = SecureRandom.base58(14)
|
||||||
|
|
||||||
|
new_template.save!
|
||||||
|
|
||||||
|
Templates::CloneAttachments.call(template: new_template, original_template: template)
|
||||||
|
end
|
||||||
|
|
||||||
|
new_user.save!(validate: false)
|
||||||
|
|
||||||
|
new_account
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_default_template(account)
|
||||||
|
template = Template.find(1)
|
||||||
|
|
||||||
|
new_template = Template.find(1).dup
|
||||||
|
new_template.account_id = account.id
|
||||||
|
new_template.slug = SecureRandom.base58(14)
|
||||||
|
|
||||||
|
new_template.save!
|
||||||
|
|
||||||
|
Templates::CloneAttachments.call(template: new_template, original_template: template)
|
||||||
|
|
||||||
|
new_template
|
||||||
|
end
|
||||||
|
|
||||||
def load_signing_certs(account)
|
def load_signing_certs(account)
|
||||||
certs =
|
certs =
|
||||||
if Docuseal.multitenant?
|
if Docuseal.multitenant?
|
||||||
|
|||||||
@@ -4,6 +4,12 @@ module ActionMailerConfigsInterceptor
|
|||||||
module_function
|
module_function
|
||||||
|
|
||||||
def delivering_email(message)
|
def delivering_email(message)
|
||||||
|
if Docuseal.demo?
|
||||||
|
message.delivery_method(:test)
|
||||||
|
|
||||||
|
return message
|
||||||
|
end
|
||||||
|
|
||||||
if Rails.env.production? && Rails.application.config.action_mailer.delivery_method
|
if Rails.env.production? && Rails.application.config.action_mailer.delivery_method
|
||||||
message.from = ENV.fetch('SMTP_FROM')
|
message.from = ENV.fetch('SMTP_FROM')
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -6,9 +6,10 @@ module Docuseal
|
|||||||
PRODUCT_NAME = 'DocuSeal'
|
PRODUCT_NAME = 'DocuSeal'
|
||||||
DEFAULT_APP_URL = 'http://localhost:3000'
|
DEFAULT_APP_URL = 'http://localhost:3000'
|
||||||
GITHUB_URL = 'https://github.com/docusealco/docuseal'
|
GITHUB_URL = 'https://github.com/docusealco/docuseal'
|
||||||
DISCORD_URL = 'https://discord.com/invite/B5wg5wKk'
|
DISCORD_URL = 'https://discord.gg/qygYCDGck9'
|
||||||
TWITTER_URL = 'https://twitter.com/docusealco'
|
TWITTER_URL = 'https://twitter.com/docusealco'
|
||||||
TWITTER_HANDLE = '@docusealco'
|
TWITTER_HANDLE = '@docusealco'
|
||||||
|
SUPPORT_EMAIL = 'support@docuseal.co'
|
||||||
|
|
||||||
CERTS = JSON.parse(ENV.fetch('CERTS', '{}'))
|
CERTS = JSON.parse(ENV.fetch('CERTS', '{}'))
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ module Submissions
|
|||||||
FONT_NAME = 'Helvetica'
|
FONT_NAME = 'Helvetica'
|
||||||
|
|
||||||
INFO_CREATOR = "#{Docuseal::PRODUCT_NAME} (#{Docuseal::PRODUCT_URL})".freeze
|
INFO_CREATOR = "#{Docuseal::PRODUCT_NAME} (#{Docuseal::PRODUCT_URL})".freeze
|
||||||
SIGN_REASON = 'Signed by %<email>s with docuseal.co'
|
SIGN_REASON = 'Signed by %<email>s with DocuSeal.co'
|
||||||
|
|
||||||
TEXT_LEFT_MARGIN = 1
|
TEXT_LEFT_MARGIN = 1
|
||||||
TEXT_TOP_MARGIN = 1
|
TEXT_TOP_MARGIN = 1
|
||||||
@@ -40,25 +40,29 @@ module Submissions
|
|||||||
|
|
||||||
value = submitter.values[field['uuid']]
|
value = submitter.values[field['uuid']]
|
||||||
|
|
||||||
|
next if Array.wrap(value).compact_blank.blank?
|
||||||
|
|
||||||
canvas = page.canvas(type: :overlay)
|
canvas = page.canvas(type: :overlay)
|
||||||
|
|
||||||
case field['type']
|
case field['type']
|
||||||
when 'image', 'signature'
|
when 'image', 'signature'
|
||||||
attachment = submitter.attachments.find { |a| a.uuid == value }
|
attachment = submitter.attachments.find { |a| a.uuid == value }
|
||||||
|
|
||||||
io = StringIO.new(download_supported_image_data(attachment))
|
image = Vips::Image.new_from_buffer(attachment.download, '').autorot
|
||||||
|
|
||||||
scale = [(area['w'] * width) / attachment.metadata['width'],
|
scale = [(area['w'] * width) / image.width,
|
||||||
(area['h'] * height) / attachment.metadata['height']].min
|
(area['h'] * height) / image.height].min
|
||||||
|
|
||||||
|
io = StringIO.new(image.resize([scale * 4, 1].min).write_to_buffer('.png'))
|
||||||
|
|
||||||
canvas.image(
|
canvas.image(
|
||||||
io,
|
io,
|
||||||
at: [
|
at: [
|
||||||
(area['x'] * width) + (area['w'] * width / 2) - ((attachment.metadata['width'] * scale) / 2),
|
(area['x'] * width) + (area['w'] * width / 2) - ((image.width * scale) / 2),
|
||||||
height - (area['y'] * height) - (attachment.metadata['height'] * scale / 2) - (area['h'] * height / 2)
|
height - (area['y'] * height) - (image.height * scale / 2) - (area['h'] * height / 2)
|
||||||
],
|
],
|
||||||
width: attachment.metadata['width'] * scale,
|
width: image.width * scale,
|
||||||
height: attachment.metadata['height'] * scale
|
height: image.height * scale
|
||||||
)
|
)
|
||||||
when 'file'
|
when 'file'
|
||||||
page[:Annots] ||= []
|
page[:Annots] ||= []
|
||||||
@@ -244,7 +248,7 @@ module Submissions
|
|||||||
page.box.height = attachment.metadata['height'] * scale
|
page.box.height = attachment.metadata['height'] * scale
|
||||||
|
|
||||||
page.canvas.image(
|
page.canvas.image(
|
||||||
StringIO.new(download_supported_image_data(attachment)),
|
StringIO.new(attachment.preview_images.first.download),
|
||||||
at: [0, 0],
|
at: [0, 0],
|
||||||
width: page.box.width,
|
width: page.box.width,
|
||||||
height: page.box.height
|
height: page.box.height
|
||||||
@@ -253,15 +257,6 @@ module Submissions
|
|||||||
pdf
|
pdf
|
||||||
end
|
end
|
||||||
|
|
||||||
def download_supported_image_data(attachment)
|
|
||||||
if SUPPORTED_IMAGE_TYPES.include?(attachment.content_type)
|
|
||||||
attachment.download
|
|
||||||
else
|
|
||||||
Vips::Image.new_from_buffer(attachment.download, '')
|
|
||||||
.write_to_buffer('.png')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def h
|
def h
|
||||||
Rails.application.routes.url_helpers
|
Rails.application.routes.url_helpers
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ module Templates
|
|||||||
ActiveStorage::Attachment.where(name: ATTACHMENT_NAME, record: attachment).destroy_all
|
ActiveStorage::Attachment.where(name: ATTACHMENT_NAME, record: attachment).destroy_all
|
||||||
|
|
||||||
image = Vips::Image.new_from_buffer(binary, '')
|
image = Vips::Image.new_from_buffer(binary, '')
|
||||||
image = image.resize(MAX_WIDTH / image.width.to_f)
|
image = image.autorot.resize(MAX_WIDTH / image.width.to_f)
|
||||||
|
|
||||||
io = StringIO.new(image.write_to_buffer(FORMAT, Q: Q, interlace: true))
|
io = StringIO.new(image.write_to_buffer(FORMAT, Q: Q, interlace: true))
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Users
|
||||||
|
module_function
|
||||||
|
|
||||||
|
def from_omniauth(oauth)
|
||||||
|
user = User.find_by(email: oauth.info.email)
|
||||||
|
|
||||||
|
return user if user
|
||||||
|
|
||||||
|
User.new(email: oauth.info.email,
|
||||||
|
first_name: oauth.extra.id_info.given_name,
|
||||||
|
last_name: oauth.extra.id_info.family_name)
|
||||||
|
end
|
||||||
|
end
|
||||||
+1
-1
@@ -19,7 +19,7 @@
|
|||||||
"compression-webpack-plugin": "10.0.0",
|
"compression-webpack-plugin": "10.0.0",
|
||||||
"css-loader": "^6.7.3",
|
"css-loader": "^6.7.3",
|
||||||
"css-minimizer-webpack-plugin": "^5.0.0",
|
"css-minimizer-webpack-plugin": "^5.0.0",
|
||||||
"daisyui": "^3.1.5",
|
"daisyui": "^3.2.1",
|
||||||
"mini-css-extract-plugin": "^2.7.5",
|
"mini-css-extract-plugin": "^2.7.5",
|
||||||
"postcss": "^8.4.23",
|
"postcss": "^8.4.23",
|
||||||
"postcss-import": "^15.1.0",
|
"postcss-import": "^15.1.0",
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 70 KiB |
@@ -2300,13 +2300,14 @@ csstype@^3.1.1:
|
|||||||
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b"
|
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b"
|
||||||
integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==
|
integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==
|
||||||
|
|
||||||
daisyui@^3.1.5:
|
daisyui@^3.2.1:
|
||||||
version "3.1.5"
|
version "3.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/daisyui/-/daisyui-3.1.5.tgz#49cb961d03504beebd238d58f0169dbe81103c95"
|
resolved "https://registry.yarnpkg.com/daisyui/-/daisyui-3.2.1.tgz#7fac542b03023adbde2afc869518840dbef509ee"
|
||||||
integrity sha512-3nk5L//IU31ISzsonKEFG8ris8hS/MRAc+PrOTOyRbssjv1fYmNKSdIrOhG6op2ED3RyJL4p6I81JUwd7RWqTw==
|
integrity sha512-gIqE6wiqoJt9G8+n3R/SwLeUnpNCE2eDhT73rP0yZYVaM7o6zVcakBH3aEW5RGpx3UkonPiLuvcgxRcb2lE8TA==
|
||||||
dependencies:
|
dependencies:
|
||||||
colord "^2.9"
|
colord "^2.9"
|
||||||
css-selector-tokenizer "^0.8"
|
css-selector-tokenizer "^0.8"
|
||||||
|
postcss "^8"
|
||||||
postcss-js "^4"
|
postcss-js "^4"
|
||||||
tailwindcss "^3"
|
tailwindcss "^3"
|
||||||
|
|
||||||
@@ -4448,6 +4449,15 @@ postcss-value-parser@^4.0.0, postcss-value-parser@^4.1.0, postcss-value-parser@^
|
|||||||
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514"
|
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514"
|
||||||
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
|
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
|
||||||
|
|
||||||
|
postcss@^8:
|
||||||
|
version "8.4.26"
|
||||||
|
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.26.tgz#1bc62ab19f8e1e5463d98cf74af39702a00a9e94"
|
||||||
|
integrity sha512-jrXHFF8iTloAenySjM/ob3gSj7pCu0Ji49hnjqzsgSRa50hkWCKD0HQ+gMNJkW38jBI68MpAAg7ZWwHwX8NMMw==
|
||||||
|
dependencies:
|
||||||
|
nanoid "^3.3.6"
|
||||||
|
picocolors "^1.0.0"
|
||||||
|
source-map-js "^1.0.2"
|
||||||
|
|
||||||
postcss@^8.1.10, postcss@^8.4.21, postcss@^8.4.23:
|
postcss@^8.1.10, postcss@^8.4.21, postcss@^8.4.23:
|
||||||
version "8.4.23"
|
version "8.4.23"
|
||||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.23.tgz#df0aee9ac7c5e53e1075c24a3613496f9e6552ab"
|
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.23.tgz#df0aee9ac7c5e53e1075c24a3613496f9e6552ab"
|
||||||
|
|||||||
Reference in new issue
Block a user