Compare commits

..
1 Commits
Author SHA1 Message Date
Alex Turchyn 6fbc81fae2 increase tokens length 2023-07-08 21:05:29 +03:00
73 changed files with 439 additions and 1164 deletions

No files matched your search

+18
View File
@@ -0,0 +1,18 @@
spec:
name: docuseal
services:
- name: docuseal
dockerfile_path: Dockerfile
git:
repo_clone_url: https://github.com/docusealco/docuseal.git
branch: master
envs:
- key: SECRET_KEY_BASE
value: "- 64 characters string 64 characters string 64 characters string -"
type: SECRET
scope: RUN_TIME
- key: DATABASE_URL
scope: RUN_TIME
value: ${docuseal-db.DATABASE_URL}
databases:
- name: docuseal-db
-2
View File
@@ -30,5 +30,3 @@ linters:
Enabled: false Enabled: false
Style/ZeroLengthPredicate: Style/ZeroLengthPredicate:
Enabled: false Enabled: false
Style/RedundantConstantBase:
Enabled: false
-3
View File
@@ -61,8 +61,5 @@ RSpec/ExampleLength:
Rails/I18nLocaleTexts: Rails/I18nLocaleTexts:
Enabled: false Enabled: false
Rails/SkipsModelValidations:
Enabled: false
Rails/ApplicationController: Rails/ApplicationController:
Enabled: false Enabled: false
-3
View File
@@ -16,8 +16,6 @@ gem 'image_processing'
gem 'lograge' gem 'lograge'
gem 'mysql2', require: false gem 'mysql2', require: false
gem 'oj' gem 'oj'
gem 'omniauth-google-oauth2'
gem 'omniauth-rails_csrf_protection'
gem 'pagy' gem 'pagy'
gem 'pg', require: false gem 'pg', require: false
gem 'premailer-rails' gem 'premailer-rails'
@@ -27,7 +25,6 @@ gem 'rails-i18n'
gem 'rollbar', require: ENV.key?('ROLLBAR_ACCESS_TOKEN') gem 'rollbar', require: ENV.key?('ROLLBAR_ACCESS_TOKEN')
gem 'ruby-vips' gem 'ruby-vips'
gem 'shakapacker' gem 'shakapacker'
gem 'sidekiq', require: ENV.key?('REDIS_URL')
gem 'sqlite3', require: false gem 'sqlite3', require: false
gem 'strip_attributes' gem 'strip_attributes'
gem 'turbo-rails' gem 'turbo-rails'
-40
View File
@@ -232,7 +232,6 @@ 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)
@@ -283,7 +282,6 @@ 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)
@@ -300,29 +298,7 @@ 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)
@@ -350,8 +326,6 @@ 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)
@@ -388,8 +362,6 @@ 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)
@@ -460,11 +432,6 @@ 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)
@@ -477,9 +444,6 @@ 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)
@@ -498,7 +462,6 @@ 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)
@@ -544,8 +507,6 @@ DEPENDENCIES
lograge lograge
mysql2 mysql2
oj oj
omniauth-google-oauth2
omniauth-rails_csrf_protection
pagy pagy
pg pg
premailer-rails premailer-rails
@@ -561,7 +522,6 @@ DEPENDENCIES
rubocop-rspec rubocop-rspec
ruby-vips ruby-vips
shakapacker shakapacker
sidekiq
simplecov simplecov
sqlite3 sqlite3
strip_attributes strip_attributes
+5 -16
View File
@@ -14,7 +14,7 @@
<a href="https://hub.docker.com/r/docuseal/docuseal"> <a href="https://hub.docker.com/r/docuseal/docuseal">
<img alt="Docker releases" src="https://img.shields.io/docker/v/docuseal/docuseal"> <img alt="Docker releases" src="https://img.shields.io/docker/v/docuseal/docuseal">
</a> </a>
<a href="https://discord.gg/qygYCDGck9"> <a href="https://discord.com/invite/B5wg5wKk">
<img src="https://img.shields.io/discord/1125112641170448454?logo=discord"/> <img src="https://img.shields.io/discord/1125112641170448454?logo=discord"/>
</a> </a>
<a href="https://twitter.com/intent/follow?screen_name=docusealco"> <a href="https://twitter.com/intent/follow?screen_name=docusealco">
@@ -26,8 +26,6 @@ DocuSeal is an open source platform that provides secure and efficient digital d
</p> </p>
<h2 align="center"> <h2 align="center">
<a href="https://demo.docuseal.co">✨ Live Demo</a> <a href="https://demo.docuseal.co">✨ Live Demo</a>
<span>|</span>
<a href="https://docuseal.co/sign_up">☁️ Try in Cloud</a>
</h2> </h2>
[![Demo](https://github.com/docusealco/docuseal/assets/5418788/d8703ea3-361a-423f-8bfe-eff1bd9dbe14)](https://demo.docuseal.co) [![Demo](https://github.com/docusealco/docuseal/assets/5418788/d8703ea3-361a-423f-8bfe-eff1bd9dbe14)](https://demo.docuseal.co)
@@ -46,11 +44,9 @@ DocuSeal is an open source platform that provides secure and efficient digital d
## Deploy ## Deploy
|Heroku|Railway| | Heroku | Railway | DigitalOcean |
|:--:|:---:| | ---------------------------------------------------------------------------- | --------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
| [<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)| | [<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) | [<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/tree/master&refcode=421d50f53990) |
|**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)
#### Docker #### Docker
@@ -68,18 +64,11 @@ Download docker-compose.yml into your private server:
curl https://raw.githubusercontent.com/docusealco/docuseal/master/docker-compose.yml > docker-compose.yml 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): Run docker compose under a custom domain over https (make sure your DNS points to the server to automatically issue ssl certs with Caddy):
```sh ```sh
HOST=your-domain-name.com docker-compose up 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.
[![Book an Integration Demo](https://cal.com/book-with-cal-dark.svg)](https://cal.com/docuseal)
## License ## License
DocuSeal is released under the GNU Affero General Public License v3.0. DocuSeal is released under the GNU Affero General Public License v3.0.
-2
View File
@@ -40,8 +40,6 @@ 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,8 +3,6 @@
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 =
+1 -1
View File
@@ -23,7 +23,7 @@ class ApplicationController < ActionController::Base
private private
def sign_in_for_demo def sign_in_for_demo
sign_in(User.order('random()').take) unless signed_in? sign_in(User.first) unless signed_in?
end end
def current_account def current_account
-1
View File
@@ -4,7 +4,6 @@ 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)
@@ -1,16 +0,0 @@
# 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
+2 -22
View File
@@ -1,44 +1,24 @@
# 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).compact_blank.tap do |attrs| params.require(:user).permit(:first_name, :last_name, :email, :password)
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).compact_blank params.require(:account).permit(:name, :timezone)
end end
end end
-20
View File
@@ -1,20 +0,0 @@
# 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
+2 -4
View File
@@ -22,16 +22,14 @@ 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'
submitters.each do |submitter| submissions.flat_map(&: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: "#{submitters.size} #{'recipient'.pluralize(submitters.size)} added" notice: "#{submissions.size} #{'recipient'.pluralize(submissions.size)} added"
end end
def destroy def destroy
@@ -23,10 +23,6 @@ class SubmitFormController < ApplicationController
submitter.save! submitter.save!
Submissions.update_template_fields!(submitter.submission) if submitter.submission.template_fields.blank?
submitter.submission.save!
if submitter.completed_at? if submitter.completed_at?
GenerateSubmitterResultAttachmentsJob.perform_later(submitter) GenerateSubmitterResultAttachmentsJob.perform_later(submitter)
+3 -9
View File
@@ -24,30 +24,24 @@ 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
-1
View File
@@ -14,7 +14,6 @@ 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)
+2 -2
View File
@@ -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' && !field.name"> <template v-if="field.type === 'checkbox'">
{{ fieldIndex + 1 }} {{ fieldIndex + 1 }}
</template> </template>
<template v-else-if="!field.required && field.type !== 'checkbox'"> <template v-else-if="!field.required">
(optional) (optional)
</template> </template>
</div> </div>
+1 -4
View File
@@ -11,10 +11,7 @@
v-for="(area, areaIndex) in field.areas" v-for="(area, areaIndex) in field.areas"
:key="areaIndex" :key="areaIndex"
> >
<Teleport <Teleport :to="`#page-${area.attachment_uuid}-${area.page}`">
v-if="attachmentsIndex[area.attachment_uuid]"
:to="`#page-${area.attachment_uuid}-${area.page}`"
>
<FieldArea <FieldArea
:ref="setAreaRef" :ref="setAreaRef"
v-model="values[field.uuid]" v-model="values[field.uuid]"
+2 -30
View File
@@ -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 && !isDemo" v-if="canSendEmail"
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,27 +40,6 @@
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>
<div class="text-center mt-4"> <div class="text-center mt-4">
Signed with Signed with
@@ -74,16 +53,14 @@
</template> </template>
<script> <script>
import { IconCircleCheck, IconBrandGithub, IconMail, IconDownload, IconInnerShadowTop, IconLogin } from '@tabler/icons-vue' import { IconCircleCheck, IconMail, IconDownload, IconInnerShadowTop } 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: {
@@ -91,11 +68,6 @@ 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,
+230 -270
View File
@@ -5,292 +5,260 @@
:values="values" :values="values"
:attachments-index="attachmentsIndex" :attachments-index="attachmentsIndex"
:current-step="currentStepFields" :current-step="currentStepFields"
@focus-step="[saveStep(), goToStep($event, false, true), currentField.type !== 'checkbox' ? isFormVisible = true : '']" @focus-step="[saveStep(), goToStep($event, false, true)]"
/> />
<button <div>
v-if="!isFormVisible" <form
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"
class="absolute right-0 mr-2 mt-2 top-0 hidden md:block" ref="form"
title="Minimize" :action="submitPath"
@click.prevent="isFormVisible = false" method="post"
class="md:mx-16"
@submit.prevent="submitStep"
> >
<IconArrowsDiagonalMinimize2 <input
:width="20" type="hidden"
:height="20" name="authenticity_token"
/> :value="authenticityToken"
</button>
<div>
<form
v-if="!isCompleted"
ref="form"
:action="submitPath"
method="post"
class="md:mx-16"
@submit.prevent="submitStep"
> >
<input <input
type="hidden" value="put"
name="authenticity_token" name="_method"
:value="authenticityToken" type="hidden"
> >
<input <div class="md:mt-4">
value="put" <div v-if="['cells', 'text'].includes(currentField.type)">
name="_method" <label
type="hidden" v-if="currentField.name"
> :for="currentField.uuid"
<div class="md:mt-4"> class="label text-2xl mb-2"
<div v-if="['cells', 'text'].includes(currentField.type)"> >{{ currentField.name }}
<label <template v-if="!currentField.required">(optional)</template>
v-if="currentField.name" </label>
:for="currentField.uuid" <div
class="label text-2xl mb-2" v-else
>{{ currentField.name }} class="py-1"
<template v-if="!currentField.required">(optional)</template> />
</label> <div>
<div <input
v-else
class="py-1"
/>
<div>
<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"
class="select base-input !text-2xl w-full text-center font-normal" :placeholder="`Type here...${currentField.required ? '' : ' (optional)'}`"
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)"
> >
<option </div>
value="" </div>
:selected="!values[currentField.uuid]" <div v-else-if="currentField.type === 'date'">
> <label
Select your option v-if="currentField.name"
</option> :for="currentField.uuid"
<option 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"
: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="field.uuid" :for="currentField.uuid + option"
class="flex items-center space-x-3" class="flex items-center space-x-3"
> >
<input <input
type="hidden" :id="currentField.uuid + option"
:name="`values[${field.uuid}]`" v-model="values[currentField.uuid]"
:value="!!values[field.uuid]" type="radio"
> class="base-radio !h-7 !w-7"
<input :name="`values[${currentField.uuid}]`"
:id="field.uuid" :value="option"
type="checkbox" :required="currentField.required"
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">
{{ field.name || field.type + ' ' + (index + 1) }} {{ option }}
</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>
<div class="mt-6 md:mt-8"> <MultiSelectStep
<button v-else-if="currentField.type === 'multiple'"
type="submit" v-model="values[currentField.uuid]"
class="base-button w-full flex justify-center" :field="currentField"
:disabled="isButtonDisabled" />
<div
v-else-if="currentField.type === 'checkbox'"
class="flex w-full"
>
<input
type="hidden"
name="cast_boolean"
value="true"
> >
<span class="flex"> <div
<IconInnerShadowTop class="space-y-3.5 mx-auto"
v-if="isSubmitting" >
class="mr-1 animate-spin" <div
/> v-for="(field, index) in currentStepFields"
<span v-if="stepFields.length === currentStep + 1"> :key="field.uuid"
Submit >
</span> <label
<span v-else> :for="field.uuid"
Next class="flex items-center space-x-3"
</span><span >
v-if="isSubmitting" <input
class="w-6 flex justify-start mr-1" type="hidden"
><span>...</span></span> :name="`values[${field.uuid}]`"
: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-6 md: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>
</button> <span v-else>
</div> Next
</form> </span><span
<FormCompleted v-if="isSubmitting"
v-else class="w-6 flex justify-start mr-1"
:is-demo="isDemo" ><span>...</span></span>
:can-send-email="canSendEmail" </span>
:submitter-slug="submitterSlug" </button>
/> </div>
<div class="flex justify-center"> </form>
<div class="flex items-center mt-5 mb-1"> <FormCompleted
<a v-else
v-for="(step, index) in stepFields" :can-send-email="canSendEmail"
:key="step[0].uuid" :submitter-slug="submitterSlug"
href="#" />
class="inline border border-base-300 h-3 w-3 rounded-full mx-1" <div class="flex justify-center">
:class="{ 'bg-base-300': index === currentStep, 'bg-base-content': index < currentStep || isCompleted, 'bg-white': index > currentStep }" <div class="flex items-center mt-5 mb-1">
@click.prevent="isCompleted ? '' : [saveStep(), goToStep(step, true)]" <a
/> v-for="(step, index) in stepFields"
</div> :key="step[0].uuid"
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 ? '' : [saveStep(), goToStep(step, true)]"
/>
</div> </div>
</div> </div>
</div> </div>
@@ -303,7 +271,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, IconArrowsDiagonal, IconArrowsDiagonalMinimize2 } from '@tabler/icons-vue' import { IconInnerShadowTop } from '@tabler/icons-vue'
export default { export default {
name: 'SubmissionForm', name: 'SubmissionForm',
@@ -314,8 +282,6 @@ export default {
AttachmentStep, AttachmentStep,
MultiSelectStep, MultiSelectStep,
IconInnerShadowTop, IconInnerShadowTop,
IconArrowsDiagonal,
IconArrowsDiagonalMinimize2,
FormCompleted FormCompleted
}, },
props: { props: {
@@ -351,11 +317,6 @@ 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,
@@ -365,7 +326,6 @@ export default {
data () { data () {
return { return {
isCompleted: false, isCompleted: false,
isFormVisible: true,
currentStep: 0, currentStep: 0,
isSubmitting: false isSubmitting: false
} }
@@ -430,7 +390,7 @@ 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] && ['file', 'image'].includes(this.currentField.type)) { if (clickUpload && !this.values[this.currentField.uuid]) {
this.$refs.form.querySelector('input[type="file"]')?.click() this.$refs.form.querySelector('input[type="file"]')?.click()
} }
}) })
+23 -206
View File
@@ -4,62 +4,22 @@
<label <label
class="label text-2xl" class="label text-2xl"
>{{ field.name || 'Signature' }}</label> >{{ field.name || 'Signature' }}</label>
<div class="space-x-2 flex"> <button
<span v-if="modelValue"
class="tooltip" class="btn btn-outline btn-sm"
data-tip="Type text" @click.prevent="remove"
> >
<button <IconReload :width="16" />
class="btn btn-sm btn-circle" Redraw
:class="{ 'btn-neutral': isTextSignature, 'btn-outline': !isTextSignature }" </button>
@click.prevent="toggleTextInput" <button
> v-else
<IconTextSize :width="16" /> class="btn btn-outline btn-sm"
</button> @click.prevent="clear"
</span> >
<span <IconReload :width="16" />
class="tooltip" Clear
data-tip="Take photo" </button>
>
<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"
@@ -69,35 +29,23 @@
<img <img
v-if="modelValue" v-if="modelValue"
:src="attachmentsIndex[modelValue].url" :src="attachmentsIndex[modelValue].url"
class="mx-auto bg-white border border-base-300 rounded max-h-72" class="w-full bg-white border border-base-300 rounded"
> >
<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, IconCamera, IconTextSize, IconArrowsDiagonalMinimize2 } from '@tabler/icons-vue' import { IconReload } from '@tabler/icons-vue'
export default { export default {
name: 'SignatureStep', name: 'SignatureStep',
components: { components: {
IconReload, IconReload
IconCamera,
IconTextSize,
IconArrowsDiagonalMinimize2
}, },
props: { props: {
field: { field: {
@@ -124,11 +72,10 @@ export default {
default: '' default: ''
} }
}, },
emits: ['attached', 'update:model-value', 'start', 'minimize'], emits: ['attached', 'update:model-value'],
data () { data () {
return { return {
isSignatureStarted: false, isSignatureStarted: false
isTextSignature: false
} }
}, },
async mounted () { async mounted () {
@@ -147,8 +94,6 @@ export default {
this.pad.addEventListener('beginStroke', () => { this.pad.addEventListener('beginStroke', () => {
this.isSignatureStarted = true this.isSignatureStarted = true
this.$emit('start')
}) })
}, },
methods: { methods: {
@@ -159,134 +104,6 @@ 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) {
@@ -294,7 +111,7 @@ export default {
} }
return new Promise((resolve) => { return new Promise((resolve) => {
this.cropCanvasAndExportToPNG(this.$refs.canvas).then(async (blob) => { this.$refs.canvas.toBlob(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) {
@@ -336,7 +153,7 @@ export default {
return resolve(attachment) return resolve(attachment)
}) })
} }
}) }, 'image/png')
}) })
} }
} }
+1 -1
View File
@@ -65,7 +65,7 @@
@blur="onNameBlur" @blur="onNameBlur"
>{{ field.name || defaultName }}</span> >{{ field.name || defaultName }}</span>
<div <div
v-if="isNameFocus && field.type !== 'checkbox'" v-if="isNameFocus"
class="flex items-center ml-1.5" class="flex items-center ml-1.5"
> >
<input <input
+4 -28
View File
@@ -64,11 +64,8 @@
:with-arrows="template.schema.length > 1" :with-arrows="template.schema.length > 1"
:item="item" :item="item"
:document="sortedDocuments[index]" :document="sortedDocuments[index]"
:template="template"
:is-direct-upload="isDirectUpload"
@scroll-to="scrollIntoDocument(item)" @scroll-to="scrollIntoDocument(item)"
@remove="onDocumentRemove" @remove="onDocumentRemove"
@replace="onDocumentReplace"
@up="moveDocument(item, -1)" @up="moveDocument(item, -1)"
@down="moveDocument(item, 1)" @down="moveDocument(item, 1)"
@change="save" @change="save"
@@ -136,7 +133,7 @@
<p> <p>
<button <button
class="base-button" class="base-button"
@click="drawField = null" @click="drawField = false"
> >
Cancel Cancel
</button> </button>
@@ -317,7 +314,7 @@ export default {
const field = { const field = {
name: '', name: '',
uuid: v4(), uuid: v4(),
required: type !== 'checkbox', required: true,
type, type,
submitter_uuid: this.selectedSubmitter.uuid, submitter_uuid: this.selectedSubmitter.uuid,
areas: [area] areas: [area]
@@ -336,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: this.dragFieldType !== 'checkbox' required: true
} }
if (['select', 'multiple', 'radio'].includes(this.dragFieldType)) { if (['select', 'multiple', 'radio'].includes(this.dragFieldType)) {
@@ -356,7 +353,7 @@ export default {
if (this.selectedField?.type === this.dragFieldType) { if (this.selectedField?.type === this.dragFieldType) {
baseArea = this.selectedAreaRef.value baseArea = this.selectedAreaRef.value
} else if (previousField?.areas?.length) { } else if (previousField?.areas) {
baseArea = previousField.areas[previousField.areas.length - 1] baseArea = previousField.areas[previousField.areas.length - 1]
} else { } else {
if (['checkbox'].includes(this.dragFieldType)) { if (['checkbox'].includes(this.dragFieldType)) {
@@ -420,27 +417,6 @@ export default {
this.template.schema.splice(this.template.schema.indexOf(item), 1) this.template.schema.splice(this.template.schema.indexOf(item), 1)
} }
this.template.fields.forEach((field) => {
[...(field.areas || [])].forEach((area) => {
if (area.attachment_uuid === item.attachment_uuid) {
field.areas.splice(field.areas.indexOf(area), 1)
}
})
})
this.save()
},
onDocumentReplace ({ replaceSchemaItem, schema, documents }) {
this.template.schema.splice(this.template.schema.indexOf(replaceSchemaItem), 1, schema[0])
this.template.documents.push(...documents)
this.template.fields.forEach((field) => {
(field.areas || []).forEach((area) => {
if (area.attachment_uuid === replaceSchemaItem.attachment_uuid) {
area.attachment_uuid = schema[0].attachment_uuid
}
})
})
this.save() this.save()
}, },
moveDocument (item, direction) { moveDocument (item, direction) {
@@ -93,11 +93,6 @@ export default {
} }
} }
}, },
mounted () {
if (this.isDirectUpload) {
import('@rails/activestorage')
}
},
methods: { methods: {
upload: Upload.methods.upload, upload: Upload.methods.upload,
onDropFiles (e) { onDropFiles (e) {
+13 -15
View File
@@ -31,21 +31,19 @@
v-if="isNameFocus" v-if="isNameFocus"
class="flex items-center relative" class="flex items-center relative"
> >
<template v-if="field.type !== 'checkbox'"> <input
<input :id="`required-checkbox-${field.uuid}`"
:id="`required-checkbox-${field.uuid}`" v-model="field.required"
v-model="field.required" type="checkbox"
type="checkbox" class="checkbox checkbox-xs no-animation rounded"
class="checkbox checkbox-xs no-animation rounded" @mousedown.prevent
@mousedown.prevent >
> <label
<label :for="`required-checkbox-${field.uuid}`"
:for="`required-checkbox-${field.uuid}`" class="label text-xs"
class="label text-xs" @click.prevent="field.required = !field.required"
@click.prevent="field.required = !field.required" @mousedown.prevent
@mousedown.prevent >Required</label>
>Required</label>
</template>
</div> </div>
<div <div
v-else v-else
+3 -4
View File
@@ -71,7 +71,7 @@
v-if="fields.length < 4" v-if="fields.length < 4"
class="text-xs p-2 border border-base-200 rounded" class="text-xs p-2 border border-base-200 rounded"
> >
<ul class="list-disc list-outside ml-3"> <ui>
<li> <li>
Draw a text field on the page with a mouse Draw a text field on the page with a mouse
</li> </li>
@@ -84,7 +84,7 @@
<li> <li>
Click on the field type above to add it to the form without drawing it on the document Click on the field type above to add it to the form without drawing it on the document
</li> </li>
</ul> </ui>
</div> </div>
</template> </template>
@@ -171,8 +171,7 @@ export default {
const field = { const field = {
name: '', name: '',
uuid: v4(), uuid: v4(),
required: type !== 'checkbox', required: true,
areas: [],
submitter_uuid: this.selectedSubmitter.uuid, submitter_uuid: this.selectedSubmitter.uuid,
type type
} }
+29 -59
View File
@@ -9,51 +9,39 @@
loading="lazy" loading="lazy"
> >
<div <div
class="group flex justify-end cursor-pointer top-0 bottom-0 left-0 right-0 absolute p-1" class="group flex justify-end cursor-pointer top-0 bottom-0 left-0 right-0 absolute"
@click="$emit('scroll-to', item)" @click="$emit('scroll-to', item)"
> >
<div class="flex justify-between w-full"> <div
<div style="width: 26px" /> class="flex flex-col justify-between opacity-0 group-hover:opacity-100"
<div class=""> >
<ReplaceButton <div class="rounded-bl rounded-tr bg-white border">
:is-direct-upload="isDirectUpload" <button
:template-id="template.id" class="rounded-bl rounded-tr hover:text-base-100 hover:bg-base-content w-full transition-colors"
class="opacity-0 group-hover:opacity-100" style="width: 24px"
@click.stop @click.stop="$emit('remove', item)"
@success="$emit('replace', { replaceSchemaItem: item, ...$event })" >
/> &times;
</button>
</div> </div>
<div <div
class="flex flex-col justify-between opacity-0 group-hover:opacity-100" v-if="withArrows"
class="flex flex-col border rounded-br rounded-tl bg-white divide-y"
> >
<div> <button
<button class="rounded-tl hover:text-base-100 hover:bg-base-content w-full transition-colors"
class="btn border-base-200 bg-white text-base-content btn-xs rounded hover:text-base-100 hover:bg-base-content hover:border-base-content w-full transition-colors" style="width: 24px"
style="width: 24px; height: 24px" @click.stop="$emit('up', item)"
@click.stop="$emit('remove', item)"
>
&times;
</button>
</div>
<div
v-if="withArrows"
class="flex flex-col space-y-1"
> >
<button &uarr;
class="btn border-base-200 bg-white text-base-content btn-xs rounded hover:text-base-100 hover:bg-base-content hover:border-base-content w-full transition-colors" </button>
style="width: 24px; height: 24px" <button
@click.stop="$emit('up', item)" class="rounded-br hover:text-base-100 hover:bg-base-content w-full transition-colors"
> style="width: 24px"
&uarr; @click.stop="$emit('down', item)"
</button> >
<button &darr;
class="btn border-base-200 bg-white text-base-content btn-xs rounded hover:text-base-100 hover:bg-base-content hover:border-base-content w-full transition-colors" </button>
style="width: 24px; height: 24px"
@click.stop="$emit('down', item)"
>
&darr;
</button>
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -72,52 +60,34 @@
<script> <script>
import Contenteditable from './contenteditable' import Contenteditable from './contenteditable'
import Upload from './upload'
import ReplaceButton from './replace'
export default { export default {
name: 'DocumentPreview', name: 'DocumentPreview',
components: { components: {
Contenteditable, Contenteditable
ReplaceButton
}, },
props: { props: {
item: { item: {
type: Object, type: Object,
required: true required: true
}, },
template: {
type: Object,
required: true
},
document: { document: {
type: Object, type: Object,
required: true required: true
}, },
isDirectUpload: {
type: Boolean,
required: true,
default: false
},
withArrows: { withArrows: {
type: Boolean, type: Boolean,
required: false, required: false,
default: true default: true
} }
}, },
emits: ['scroll-to', 'change', 'remove', 'up', 'down', 'replace'], emits: ['scroll-to', 'change', 'remove', 'up', 'down'],
computed: { computed: {
previewImage () { previewImage () {
return this.document.preview_images[0] return this.document.preview_images[0]
} }
}, },
mounted () {
if (this.isDirectUpload) {
import('@rails/activestorage')
}
},
methods: { methods: {
upload: Upload.methods.upload,
onUpdateName (value) { onUpdateName (value) {
this.item.name = value this.item.name = value
@@ -1,70 +0,0 @@
<template>
<label
:for="inputId"
class="btn btn-neutral btn-xs text-white transition-none"
:class="{ 'opacity-100': isLoading || isProcessing }"
>
{{ message }}
<form
ref="form"
class="hidden"
>
<input
:id="inputId"
ref="input"
name="files[]"
type="file"
accept="image/*, application/pdf"
@change="upload"
>
</form>
</label>
</template>
<script>
import Upload from './upload'
export default {
name: 'ReplaceDocument',
props: {
templateId: {
type: [Number, String],
required: true
},
isDirectUpload: {
type: Boolean,
required: true,
default: false
}
},
emits: ['success'],
data () {
return {
isLoading: false,
isProcessing: false
}
},
computed: {
inputId () {
return 'el' + Math.random().toString(32).split('.')[1]
},
message () {
if (this.isLoading) {
return 'Uploading...'
} else if (this.isProcessing) {
return 'Processing...'
} else {
return 'Replace'
}
}
},
mounted () {
if (this.isDirectUpload) {
import('@rails/activestorage')
}
},
methods: {
upload: Upload.methods.upload
}
}
</script>
+6 -13
View File
@@ -4,15 +4,12 @@
# #
# Table name: submissions # Table name: submissions
# #
# id :bigint not null, primary key # id :bigint not null, primary key
# deleted_at :datetime # deleted_at :datetime
# template_fields :text # created_at :datetime not null
# template_schema :text # updated_at :datetime not null
# template_submitters :text # created_by_user_id :bigint
# created_at :datetime not null # template_id :bigint not null
# updated_at :datetime not null
# created_by_user_id :bigint
# template_id :bigint not null
# #
# Indexes # Indexes
# #
@@ -30,9 +27,5 @@ class Submission < ApplicationRecord
has_many :submitters, dependent: :destroy has_many :submitters, dependent: :destroy
serialize :template_fields, JSON
serialize :template_schema, JSON
serialize :template_submitters, JSON
scope :active, -> { where(deleted_at: nil) } scope :active, -> { where(deleted_at: nil) }
end end
+2 -2
View File
@@ -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 :text not null # values :string 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(14) } attribute :slug, :string, default: -> { SecureRandom.base58(10) }
serialize :values, JSON serialize :values, JSON
+4 -7
View File
@@ -6,11 +6,11 @@
# #
# id :bigint not null, primary key # id :bigint not null, primary key
# deleted_at :datetime # deleted_at :datetime
# fields :text not null # fields :string not null
# name :string not null # name :string not null
# schema :text not null # schema :string not null
# slug :string not null # slug :string not null
# submitters :text not null # submitters :string not null
# created_at :datetime not null # created_at :datetime not null
# updated_at :datetime not null # updated_at :datetime not null
# account_id :bigint not null # account_id :bigint not null
@@ -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(14) } attribute :slug, :string, default: -> { SecureRandom.base58(10) }
serialize :fields, JSON serialize :fields, JSON
serialize :schema, JSON serialize :schema, JSON
@@ -44,9 +44,6 @@ class Template < ApplicationRecord
has_many_attached :documents has_many_attached :documents
has_many :schema_documents, ->(e) { where(uuid: e.schema.pluck('attachment_uuid')) },
class_name: 'ActiveStorage::Attachment', dependent: :destroy, as: :record, inverse_of: :record
has_many :submissions, dependent: :destroy has_many :submissions, dependent: :destroy
scope :active, -> { where(deleted_at: nil) } scope :active, -> { where(deleted_at: nil) }
+1 -1
View File
@@ -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, :omniauthable, omniauth_providers: [:google_oauth2] if Docuseal.multitenant? devise :registerable if Docuseal.multitenant?
attribute :role, :string, default: 'admin' attribute :role, :string, default: 'admin'
-1
View File
@@ -1,4 +1,3 @@
<% 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>
+7 -7
View File
@@ -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-3xl font-bold text-center my-8">Profile Details</h1> <h1 class="text-4xl font-bold text-center my-8">Sign up</h1>
<%= form_for('', as: resource_name, html: { class: 'space-y-6' }, url: new_registration_path) do |f| %> <%= form_for('', as: resource_name, html: { class: 'space-y-6' }, url: 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 <%= 'hidden' if (params[:oauth_callback] || params[:sign_up]) && resource.email? %>"> <div class="form-control">
<%= 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 (optional)', class: 'label' %> <%= ff.label :name, 'Company name', class: 'label' %>
<%= ff.text_field :name, class: 'base-input' %> <%= ff.text_field :name, required: true, class: 'base-input' %>
</div> </div>
<% end %> <% end %>
<%= f.fields_for resource do |ff| %> <%= f.fields_for resource do |ff| %>
<div class="form-control <%= 'hidden' if params[:oauth_callback] %>"> <div class="form-control">
<%= ff.label :password, class: 'label' %> <%= ff.label :password, class: 'label' %>
<%= ff.password_field :password, required: !params[:oauth_callback], class: 'base-input' %> <%= ff.password_field :password, required: true, class: 'base-input' %>
</div> </div>
<% end %> <% end %>
</div> </div>
@@ -1,18 +0,0 @@
<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>
-3
View File
@@ -23,8 +23,5 @@
<%= 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>
+6 -1
View File
@@ -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 'Create free account', registration_path, class: 'link link-hover' %> <%= link_to 'Sign up', new_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,4 +14,9 @@
<%- 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>
-16
View File
@@ -1,16 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 1.8 KiB

+2 -1
View File
@@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html data-theme="docuseal" lang="en"> <html data-theme="docuseal">
<head> <head>
<title> <title>
DocuSeal | Open Source Document Filling and Signing DocuSeal | Open Source Document Filling and Signing
@@ -15,6 +15,7 @@
<body> <body>
<turbo-frame id="modal"></turbo-frame> <turbo-frame id="modal"></turbo-frame>
<%= render 'shared/navbar' %> <%= render 'shared/navbar' %>
<% if Docuseal.demo? %><%= render 'shared/demo_alert' %><% end %>
<% if flash.present? %><%= render 'shared/flash' %><% end %> <% if flash.present? %><%= render 'shared/flash' %><% end %>
<div class="max-w-6xl mx-auto px-4 md:px-2 mb-8"> <div class="max-w-6xl mx-auto px-4 md:px-2 mb-8">
<%= yield %> <%= yield %>
+1 -1
View File
@@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html data-theme="docuseal" lang="en"> <html data-theme="docuseal">
<head> <head>
<title> <title>
DocuSeal | Open Source Document Filling and Signing DocuSeal | Open Source Document Filling and Signing
+1 -1
View File
@@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html data-theme="docuseal" lang="en"> <html data-theme="docuseal">
<head> <head>
<title> <title>
DocuSeal | Open Source Document Filling and Signing DocuSeal | Open Source Document Filling and Signing
+2 -3
View File
@@ -8,7 +8,7 @@
DocuSeal DocuSeal
</h1> </h1>
</div> </div>
<h2 class="mb-16 text-lg text-center text-gray-600"> <h2 class="mb-16 text-lg text-center text-gray-500">
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,4 +70,3 @@
</div> </div>
</div> </div>
</section> </section>
<%= render 'shared/attribution' %>
+6 -8
View File
@@ -1,11 +1,9 @@
<div class="mx-auto mt-1.5"> <div class="absolute top-0 w-full h-0">
<div class="py-3 rounded-2xl flex items-center justify-between mx-4 md:mx-0"> <div class="max-w-xl mx-auto mt-1.5">
<div class="w-full text-center"> <div class="px-4 py-3 rounded-2xl flex items-center justify-between mx-4 md:mx-0">
<span class="font-bold">Demo Environment</span> <a target="_blank" href="<%= start_form_url(slug: ::Template.first&.slug) %>" class="font-medium w-full text-center underline">
<br> Demo Environment
Feel free to </a>
<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> </div>
</div> </div>
-10
View File
@@ -1,10 +0,0 @@
<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&nbsp</span>GitHub
</span>
</a>
+13 -29
View File
@@ -4,19 +4,13 @@
<%= render 'shared/logo' %> <%= render 'shared/logo' %>
<span>DocuSeal</span> <span>DocuSeal</span>
</a> </a>
<% unless Docuseal.demo? %> <a href="<%= Docuseal::GITHUB_URL %>" target="_blank" class="inline">
<a href="<%= Docuseal::GITHUB_URL %>" target="_blank" class="inline"> <img alt="GitHub Repo stars" src="https://www.docuseal.co/github-badge.svg" style="height: 22px">
<img alt="GitHub Repo stars" src="https://www.docuseal.co/github-badge.svg" style="height: 22px"> </a>
</a>
<% end %>
</div> </div>
<% if signed_in? %> <% if signed_in? %>
<div class="space-x-4 flex items-center"> <div class="space-x-6">
<% if Docuseal.demo? %> <%= link_to 'Settings', settings_profile_index_path, class: 'hidden md:inline font-medium text-lg' %>
<%= 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>
@@ -29,8 +23,8 @@
<% end %> <% end %>
</li> </li>
<li> <li>
<%= button_to destroy_user_session_path, method: :delete, data: { turbo: false } do %> <%= link_to destroy_user_session_path, data: { turbo_method: :delete } do %>
<%= svg_icon('logout', class: 'w-5 h-5 stroke-2 inline') %> <%= svg_icon('logout', class: 'w-5 h-5 stroke-2') %>
<span class="mr-1">Sign out</span> <span class="mr-1">Sign out</span>
<% end %> <% end %>
</li> </li>
@@ -38,21 +32,11 @@
</div> </div>
</div> </div>
<% else %> <% else %>
<div class="flex space-x-6"> <%= link_to new_user_session_path, class: 'font-medium text-lg' do %>
<%= link_to new_user_session_path, class: 'font-medium text-lg' do %> <span class="flex items-center justify-center space-x-1">
<span class="flex items-center justify-center space-x-1"> <%= svg_icon('login', class: 'w-6 h-6') %>
<%= svg_icon('login', class: 'w-6 h-6') %> <span>Sign in</span>
<span>Sign in</span> </span>
</span> <% end %>
<% 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>
+2 -2
View File
@@ -1,7 +1,7 @@
<turbo-frame id="modal"> <turbo-frame id="modal">
<turbo-modal class="modal modal-open items-start !animate-none overflow-y-auto"> <turbo-modal class="modal modal-open items-start !animate-none">
<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 max-h-none"> <div class="modal-box pt-4 pb-6 px-6 mt-20">
<% 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>
+1 -1
View File
@@ -9,7 +9,7 @@
<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-200 rounded-xl p-4 mb-4"> <div class="flex items-center bg-base-300 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_icon('writing_sign', class: 'w-10 h-10') %> <%= svg_icon('writing_sign', class: 'w-10 h-10') %>
+3 -3
View File
@@ -2,7 +2,7 @@
<div class="space-y-6 mx-auto"> <div class="space-y-6 mx-auto">
<div class="space-y-6"> <div class="space-y-6">
<div class="text-center w-full space-y-6"> <div class="text-center w-full space-y-6">
<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>
@@ -10,14 +10,14 @@
</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 a form</p>
</div> </div>
<div class="flex items-center bg-base-200 rounded-xl p-4 mb-4"> <div class="flex items-center bg-base-300 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_icon('writing_sign', class: 'w-10 h-10') %> <%= svg_icon('writing_sign', class: 'w-10 h-10') %>
</div> </div>
<div> <div>
<p class="text-lg font-bold mb-1"><%= @template.name %></p> <p class="text-lg font-bold mb-1"><%= @template.name %></p>
<p class="text-sm">Invited by <span class="font-semibold"><%= @template.account.name %></span></p> <p class="text-sm">Invited by <%= @template.account.name %></p>
</div> </div>
</div> </div>
</div> </div>
+6 -6
View File
@@ -23,7 +23,7 @@
</div> </div>
<div class="flex md:max-h-[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 || @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 } %>
<a href="#<%= "page-#{document.uuid}-0" %>" onclick="[event.preventDefault(), window[event.target.closest('a').href.split('#')[1]].scrollIntoView({ behavior: 'smooth', block: 'start' })]" class="block cursor-pointer"> <a href="#<%= "page-#{document.uuid}-0" %>" onclick="[event.preventDefault(), window[event.target.closest('a').href.split('#')[1]].scrollIntoView({ behavior: 'smooth', block: 'start' })]" class="block cursor-pointer">
<img src="<%= document.preview_images.first.url %>" width="<%= document.preview_images.first.metadata['width'] %>" height="<%= document.preview_images.first.metadata['height'] %>" class="rounded border" loading="lazy"> <img src="<%= document.preview_images.first.url %>" width="<%= document.preview_images.first.metadata['width'] %>" height="<%= document.preview_images.first.metadata['height'] %>" class="rounded border" loading="lazy">
@@ -35,10 +35,10 @@
</div> </div>
<div class="w-full overflow-y-auto overflow-x-hidden mt-0.5 pt-0.5"> <div class="w-full overflow-y-auto overflow-x-hidden mt-0.5 pt-0.5">
<div class="pr-3.5 pl-0.5"> <div class="pr-3.5 pl-0.5">
<% fields_index = Templates.build_field_areas_index(@submission.template_fields || @submission.template.fields) %> <% fields_index = Templates.build_field_areas_index(@submission.template) %>
<% values = @submission.submitters.reduce({}) { |acc, sub| acc.merge(sub.values) } %> <% values = @submission.submitters.reduce({}) { |acc, sub| acc.merge(sub.values) } %>
<% attachments_index = ActiveStorage::Attachment.where(record: @submission.submitters, name: :attachments).preload(:blob).index_by(&:uuid) %> <% attachments_index = ActiveStorage::Attachment.where(record: @submission.submitters, name: :attachments).preload(:blob).index_by(&:uuid) %>
<% (@submission.template_schema || @submission.template.schema).each do |item| %> <% @submission.template.schema.each do |item| %>
<% document = @submission.template.documents.find { |e| e.uuid == item['attachment_uuid'] } %> <% document = @submission.template.documents.find { |e| e.uuid == item['attachment_uuid'] } %>
<% document.preview_images.sort_by { |a| a.filename.base.to_i }.each_with_index do |page, index| %> <% document.preview_images.sort_by { |a| a.filename.base.to_i }.each_with_index do |page, index| %>
<div id="<%= "page-#{document.uuid}-#{index}" %>" class="relative"> <div id="<%= "page-#{document.uuid}-#{index}" %>" class="relative">
@@ -57,15 +57,15 @@
</div> </div>
<div class="hidden md:block relative w-80 flex-none pt-0.5 pr-4 pl-0.5 overflow-auto space"> <div class="hidden md:block relative w-80 flex-none pt-0.5 pr-4 pl-0.5 overflow-auto space">
<% colors = %w[bg-red-500 bg-sky-500 bg-emerald-500 bg-yellow-300 bg-purple-600] %> <% colors = %w[bg-red-500 bg-sky-500 bg-emerald-500 bg-yellow-300 bg-purple-600] %>
<% submitter_fields_index = (@submission.template_fields || @submission.template.fields).group_by { |f| f['submitter_uuid'] } %> <% submitter_fields_index = @submission.template.fields.group_by { |f| f['submitter_uuid'] } %>
<% (@submission.template_submitters || @submission.template.submitters).each_with_index do |item, index| %> <% @submission.template.submitters.each_with_index do |item, index| %>
<% submitter = @submission.submitters.find { |e| e.uuid == item['uuid'] } %> <% submitter = @submission.submitters.find { |e| e.uuid == item['uuid'] } %>
<div class="sticky -top-1 bg-base-100 pt-1 -mt-1 z-10"> <div class="sticky -top-1 bg-base-100 pt-1 -mt-1 z-10">
<div class="border border-base-300 rounded-md px-2 py-1 mb-1"> <div class="border border-base-300 rounded-md px-2 py-1 mb-1">
<div class="flex items-center space-x-1"> <div class="flex items-center space-x-1">
<span class="mx-1 w-3 h-3 rounded-full <%= colors[index] %>"></span> <span class="mx-1 w-3 h-3 rounded-full <%= colors[index] %>"></span>
<span class="text-lg"> <span class="text-lg">
<%= (@submission.template_submitters || @submission.template.submitters).find { |e| e['uuid'] == submitter&.uuid }&.dig('name') || "#{(index + 1).ordinalize} Submitter" %> <%= @submission.template.submitters.find { |e| e['uuid'] == submitter&.uuid }&.dig('name') || "#{(index + 1).ordinalize} Submitter" %>
</span> </span>
</div> </div>
<div class="flex items-center space-x-1 mt-1"> <div class="flex items-center space-x-1 mt-1">
+2 -2
View File
@@ -9,7 +9,7 @@
<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-200 rounded-xl p-4 mb-4"> <div class="flex items-center bg-base-300 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_icon('writing_sign', class: 'w-10 h-10') %> <%= svg_icon('writing_sign', class: 'w-10 h-10') %>
@@ -23,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: 'white-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: 'base-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">
+5 -3
View File
@@ -1,4 +1,4 @@
<% fields_index = Templates.build_field_areas_index(@submitter.submission.template_fields || @submitter.submission.template.fields) %> <% fields_index = Templates.build_field_areas_index(@submitter.submission.template) %>
<% values = @submitter.submission.submitters.where.not(id: @submitter.id).reduce({}) { |acc, sub| acc.merge(sub.values) } %> <% values = @submitter.submission.submitters.where.not(id: @submitter.id).reduce({}) { |acc, sub| acc.merge(sub.values) } %>
<% attachments_index = ActiveStorage::Attachment.where(record: @submitter.submission.submitters, name: :attachments).preload(:blob).index_by(&:uuid) %> <% attachments_index = ActiveStorage::Attachment.where(record: @submitter.submission.submitters, name: :attachments).preload(:blob).index_by(&:uuid) %>
<div style="max-height: -webkit-fill-available;"> <div style="max-height: -webkit-fill-available;">
@@ -10,7 +10,7 @@
<span>DocuSeal</span> <span>DocuSeal</span>
</a> </a>
</div> </div>
<% (@submitter.submission.template_schema || @submitter.submission.template.schema).each do |item| %> <% @submitter.submission.template.schema.each do |item| %>
<% document = @submitter.submission.template.documents.find { |a| a.uuid == item['attachment_uuid'] } %> <% document = @submitter.submission.template.documents.find { |a| a.uuid == item['attachment_uuid'] } %>
<% document.preview_images.sort_by { |a| a.filename.base.to_i }.each_with_index do |page, index| %> <% document.preview_images.sort_by { |a| a.filename.base.to_i }.each_with_index do |page, index| %>
<div class="relative my-4 shadow-md"> <div class="relative my-4 shadow-md">
@@ -32,7 +32,9 @@
<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">
<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 || @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 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-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>
+1 -1
View File
@@ -1 +1 @@
<template-builder data-is-direct-upload="<%= Docuseal.active_storage_public? %>" data-template="<%= @template.as_json.merge(documents: @template.schema_documents.as_json(include: { preview_images: { methods: %i[url metadata filename] } })).to_json %>"></template-builder> <template-builder data-is-direct-upload="<%= Docuseal.active_storage_public? %>" data-template="<%= @template.to_json(include: { documents: { include: { preview_images: { methods: %i[url metadata filename] } } } }) %>"></template-builder>
+6 -12
View File
@@ -37,7 +37,7 @@
<div class="space-y-4"> <div class="space-y-4">
<% @submissions.each do |submission| %> <% @submissions.each do |submission| %>
<a href="<%= submission_path(submission) %>" class="bg-base-200 w-full flex flex-col md:flex-row space-y-4 md:space-y-0 md:justify-between rounded-2xl px-6 py-5 md:items-center"> <a href="<%= submission_path(submission) %>" class="bg-base-200 w-full flex flex-col md:flex-row space-y-4 md:space-y-0 md:justify-between rounded-2xl px-6 py-5 md:items-center">
<% submitters = (submission.template_submitters || submission.template.submitters).filter_map { |item| submission.submitters.find { |e| e.uuid == item['uuid'] } } %> <% submitters = submission.template.submitters.filter_map { |item| submission.submitters.find { |e| e.uuid == item['uuid'] } } %>
<% is_submission_complated = submitters.all?(&:completed_at?) %> <% is_submission_complated = submitters.all?(&:completed_at?) %>
<% if submitters.size == 1 %> <% if submitters.size == 1 %>
<div> <div>
@@ -157,19 +157,13 @@
<% 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-lg mx-auto"> <div class="max-w-md 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 complete the form</p> <p class="text-gray-600">Send an invitation to fill and complete the form</p>
<div class="space-x-2"> <%= link_to new_template_submission_path(@template), class: 'base-button mt-6', data: { turbo_frame: 'modal' } do %>
<%= link_to new_template_submission_path(@template), class: 'base-button mt-6', data: { turbo_frame: 'modal' } do %> <%= svg_icon('plus', class: 'w-6 h-6 stroke-2') %>
<%= svg_icon('plus', class: 'w-6 h-6 stroke-2') %> <span class="mr-1">Add Recipients</span>
<span class="mr-1">Add Recipients</span> <% end %>
<% 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>
-2
View File
@@ -45,8 +45,6 @@ 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
+3 -5
View File
@@ -25,8 +25,6 @@ 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
@@ -76,7 +74,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_ADDRESS'] if ENV['SMTP_USERNAME']
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),
@@ -86,7 +84,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
@@ -112,7 +110,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: Digest::SHA256.hexdigest(encryption_secret) key_derivation_salt: encryption_secret
} }
# Do not dump schema after migrations. # Do not dump schema after migrations.
+2 -2
View File
@@ -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 : 10 config.stretches = Rails.env.test? ? 1 : 12
# 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 :google_oauth2, ENV.fetch('GOOGLE_CLIENT_ID', nil), ENV.fetch('GOOGLE_CLIENT_SECRET', nil), {} # config.omniauth :github, 'APP_ID', 'APP_SECRET', scope: 'user,public_repo'
# ==> 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 -3
View File
@@ -1,5 +1,3 @@
# frozen_string_literal: true # frozen_string_literal: true
Rails.configuration.to_prepare do ActiveRecord::Tasks::DatabaseTasks.migrate if ENV['RAILS_ENV'] == 'production'
ActiveRecord::Tasks::DatabaseTasks.migrate if ENV['RAILS_ENV'] == 'production'
end
-16
View File
@@ -1,16 +0,0 @@
# 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
+2 -10
View File
@@ -2,22 +2,14 @@
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, devise_for :users, path: '/', only: %i[sessions passwords]
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[show], path: 'sign_up' resource :registration, only: %i[create], path: 'sign_up' do
resource :registration, only: %i[create], path: 'new' do
get '' => :new, as: :new get '' => :new, as: :new
end end
end end
-6
View File
@@ -1,6 +0,0 @@
queues:
- [default, 1]
- [mailers, 1]
production:
:concurrency: 15
@@ -1,31 +0,0 @@
# frozen_string_literal: true
class AddTemplateFieldsToSubmission < ActiveRecord::Migration[7.0]
class MigrationTemplate < ApplicationRecord
self.table_name = 'templates'
end
class MigrationSubmission < ApplicationRecord
self.table_name = 'submissions'
end
def up
add_column :submissions, :template_fields, :text
add_column :submissions, :template_schema, :text
add_column :submissions, :template_submitters, :text
MigrationTemplate.all.each do |template|
MigrationSubmission.where(template_id: template.id).each do |submission|
submission.update_columns(template_fields: template.fields,
template_schema: template.schema,
template_submitters: template.submitters)
end
end
end
def down
remove_column :submissions, :template_fields
remove_column :submissions, :template_schema
remove_column :submissions, :template_submitters
end
end
+5 -8
View File
@@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.0].define(version: 2023_07_26_062428) do ActiveRecord::Schema[7.0].define(version: 2023_07_01_075115) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
@@ -77,9 +77,6 @@ ActiveRecord::Schema[7.0].define(version: 2023_07_26_062428) do
t.datetime "deleted_at" t.datetime "deleted_at"
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
t.text "template_fields"
t.text "template_schema"
t.text "template_submitters"
t.index ["created_by_user_id"], name: "index_submissions_on_created_by_user_id" t.index ["created_by_user_id"], name: "index_submissions_on_created_by_user_id"
t.index ["template_id"], name: "index_submissions_on_template_id" t.index ["template_id"], name: "index_submissions_on_template_id"
end end
@@ -89,7 +86,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_07_26_062428) 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.text "values", null: false t.string "values", null: false
t.string "ua" t.string "ua"
t.string "ip" t.string "ip"
t.datetime "sent_at" t.datetime "sent_at"
@@ -105,9 +102,9 @@ ActiveRecord::Schema[7.0].define(version: 2023_07_26_062428) 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.text "schema", null: false t.string "schema", null: false
t.text "fields", null: false t.string "fields", null: false
t.text "submitters", null: false t.string "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"
-39
View File
@@ -3,45 +3,6 @@
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?
-6
View File
@@ -4,12 +4,6 @@ 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')
+1 -1
View File
@@ -6,7 +6,7 @@ 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.gg/qygYCDGck9' DISCORD_URL = 'https://discord.com/invite/B5wg5wKk'
TWITTER_URL = 'https://twitter.com/docusealco' TWITTER_URL = 'https://twitter.com/docusealco'
TWITTER_HANDLE = '@docusealco' TWITTER_HANDLE = '@docusealco'
SUPPORT_EMAIL = 'support@docuseal.co' SUPPORT_EMAIL = 'support@docuseal.co'
-9
View File
@@ -1,13 +1,4 @@
# frozen_string_literal: true # frozen_string_literal: true
module Submissions module Submissions
module_function
def update_template_fields!(submission)
submission.template_fields = submission.template.fields
submission.template_schema = submission.template.schema
submission.template_submitters = submission.template.submitters
submission.save!
end
end end
@@ -27,7 +27,7 @@ module Submissions
pdfs_index = build_pdfs_index(submitter) pdfs_index = build_pdfs_index(submitter)
submitter.submission.template_fields.each do |field| template.fields.each do |field|
next if field['submitter_uuid'] != submitter.uuid next if field['submitter_uuid'] != submitter.uuid
field.fetch('areas', []).each do |area| field.fetch('areas', []).each do |area|
@@ -159,7 +159,7 @@ module Submissions
original_documents = template.documents.preload(:blob) original_documents = template.documents.preload(:blob)
results = results =
submitter.submission.template_schema.map do |item| template.schema.map do |item|
pdf = pdfs_index[item['attachment_uuid']] pdf = pdfs_index[item['attachment_uuid']]
attachment = save_signed_pdf(pdf:, submitter:, certs:, uuid: item['attachment_uuid'], name: item['name']) attachment = save_signed_pdf(pdf:, submitter:, certs:, uuid: item['attachment_uuid'], name: item['name'])
+2 -2
View File
@@ -3,10 +3,10 @@
module Templates module Templates
module_function module_function
def build_field_areas_index(fields) def build_field_areas_index(template)
hash = {} hash = {}
fields.each do |field| template.fields.each do |field|
(field['areas'] || []).each do |area| (field['areas'] || []).each do |area|
hash[area['attachment_uuid']] ||= {} hash[area['attachment_uuid']] ||= {}
acc = (hash[area['attachment_uuid']][area['page']] ||= []) acc = (hash[area['attachment_uuid']][area['page']] ||= [])
-15
View File
@@ -1,15 +0,0 @@
# 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
View File
@@ -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.2.1", "daisyui": "^3.1.5",
"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.

After

Width:  |  Height:  |  Size: 70 KiB

+4 -14
View File
@@ -2300,14 +2300,13 @@ 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.2.1: daisyui@^3.1.5:
version "3.2.1" version "3.1.5"
resolved "https://registry.yarnpkg.com/daisyui/-/daisyui-3.2.1.tgz#7fac542b03023adbde2afc869518840dbef509ee" resolved "https://registry.yarnpkg.com/daisyui/-/daisyui-3.1.5.tgz#49cb961d03504beebd238d58f0169dbe81103c95"
integrity sha512-gIqE6wiqoJt9G8+n3R/SwLeUnpNCE2eDhT73rP0yZYVaM7o6zVcakBH3aEW5RGpx3UkonPiLuvcgxRcb2lE8TA== integrity sha512-3nk5L//IU31ISzsonKEFG8ris8hS/MRAc+PrOTOyRbssjv1fYmNKSdIrOhG6op2ED3RyJL4p6I81JUwd7RWqTw==
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"
@@ -4449,15 +4448,6 @@ 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"