Compare commits

...

64 Commits

Author SHA1 Message Date
Alex Turchyn c88715daec Merge from docusealco/wip 2025-06-03 12:22:34 +03:00
Pete Matsyburka 0375dad7c9 smaller audit initial 2025-06-03 10:13:49 +03:00
Pete Matsyburka de3f83db96 use metadata lang 2025-06-03 09:52:46 +03:00
Pete Matsyburka a7bb96464c completed action shared link 2025-06-01 22:42:44 +03:00
Pete Matsyburka 1d48fe9621 fix spec 2025-06-01 15:51:43 +03:00
Pete Matsyburka 9544bb3d76 Revert "shared link true default"
This reverts commit e41fe5fbca.
2025-06-01 15:23:20 +03:00
Pete Matsyburka 4e94664008 refactor start form 2025-06-01 15:23:19 +03:00
Pete Matsyburka e41fe5fbca shared link true default 2025-06-01 13:21:11 +03:00
Pete Matsyburka 24d713dec6 resubmit shared template 2025-06-01 12:27:36 +03:00
Pete Matsyburka f464e1c46a add log 2025-06-01 12:22:16 +03:00
Pete Matsyburka 85d52d1f28 refactor policy 2025-06-01 11:38:08 +03:00
Alex Turchyn e77b5fa2c9 add optional shared link to templates 2025-06-01 11:07:52 +03:00
Alex Turchyn c91b4a765b add Privacy Policy and eSignature Disclosure links 2025-06-01 09:20:42 +03:00
Pete Matsyburka 2a031c14c8 fix js error 2025-05-30 16:58:20 +03:00
Pete Matsyburka f7dfc0b2ed add email event date index 2025-05-30 11:12:33 +03:00
Pete Matsyburka 3b894f5281 fix 2025-05-30 10:36:08 +03:00
Alex Turchyn 9645332e48 add countless pagination 2025-05-30 09:12:42 +03:00
Pete Matsyburka 5670390b37 fix signature reason 2025-05-29 17:31:09 +03:00
Pete Matsyburka 260bd6d5d5 fix signature field size 2025-05-29 17:24:03 +03:00
Alex Turchyn fa0733f480 improve user email validation 2025-05-29 14:24:17 +03:00
Pete Matsyburka 8b9678a434 fix heif 2025-05-28 23:01:34 +03:00
Pete Matsyburka 43669bf095 do not prefill optional signature 2025-05-28 21:51:17 +03:00
Pete Matsyburka 2ef8df3f0c optimize query 2025-05-28 10:23:36 +03:00
Alex Turchyn 1c9d15aa7e add translations for pagination 2025-05-28 10:06:06 +03:00
Alex Turchyn 9fcd8955d3 add 'field-area-active-label' theme class 2025-05-27 21:10:58 +03:00
Pete Matsyburka 5e44ba49b8 refactor 2025-05-26 22:26:14 +03:00
Pete Matsyburka ac1f90e711 do not email smtp settings 2025-05-26 14:46:33 +03:00
Alex Turchyn c6eae66127 Merge from docusealco/wip 2025-05-26 13:26:19 +03:00
Pete Matsyburka 08aa902e07 set from user id 2025-05-26 11:58:47 +03:00
Pete Matsyburka 1690337699 fix pdf image result 2025-05-26 10:11:01 +03:00
Pete Matsyburka f78cf8cc6e pdfium 2025-05-26 10:01:27 +03:00
Pete Matsyburka 343118d00d fix bmp 2025-05-24 11:54:18 +03:00
Alex Turchyn a3bd37c9a2 Improve error message when does not match in completed form 2025-05-24 11:25:49 +03:00
Alex Turchyn 8bc13a5900 fix german translations 2025-05-23 10:52:47 +03:00
Pete Matsyburka 21138c1d02 remove log 2025-05-22 15:41:19 +03:00
Alex Turchyn 38674a9fc9 fix accidental checkbox addition 2025-05-21 22:18:25 +03:00
Pete Matsyburka a7ed0b86a4 dot rspec 2025-05-21 10:20:43 +03:00
Pete Matsyburka 91a96f06c8 remove account config action 2025-05-20 22:50:43 +03:00
Alex Turchyn 23631b75f2 add template_accesses factory 2025-05-20 19:36:45 +03:00
Pete Matsyburka 0fb9c2bbf6 fix gmail normalize 2025-05-20 19:32:23 +03:00
Pete Matsyburka 14fa8fc8e7 fix expired 2025-05-20 11:18:08 +03:00
Pete Matsyburka 047c192988 fix typo 2025-05-20 09:49:17 +03:00
Pete Matsyburka bfa244c8fa add ico and bmp support 2025-05-20 09:49:16 +03:00
Alex Turchyn 019222ae40 Merge from docusealco/wip 2025-05-19 13:07:42 +03:00
Pete Matsyburka 8a10852fe2 docs 2025-05-18 17:38:51 +03:00
Pete Matsyburka 01232aed5a add account access 2025-05-18 15:16:53 +03:00
Alex Turchyn 82f3ff4824 fix narrow signatures 2025-05-18 09:44:55 +03:00
Pete Matsyburka 7be3f5a931 fix android 2025-05-16 18:28:55 +03:00
Pete Matsyburka 9d013a7384 fix email normalize 2025-05-15 22:16:06 +03:00
Alex Turchyn 0c71d7bff8 fix EU server selection note 2025-05-15 18:05:07 +03:00
Pete Matsyburka fdd030652e adjust border 2025-05-15 14:39:25 +03:00
Pete Matsyburka f2bf1461f0 adjust selector 2025-05-15 13:50:02 +03:00
Alex Turchyn fc6f796f30 adjust server selector 2025-05-15 13:16:43 +03:00
Alex Turchyn 5f44806287 adjust dropoze border style 2025-05-15 13:16:40 +03:00
Pete Matsyburka 318b65af18 fix i18n 2025-05-14 21:28:52 +03:00
Pete Matsyburka 91b182b04a adjust email validate 2025-05-14 20:40:59 +03:00
Pete Matsyburka 15b886d4ff update reminder durations 2025-05-14 20:22:34 +03:00
Pete Matsyburka 0122a237e3 remove request helper 2025-05-14 13:21:57 +03:00
Alex Turchyn 2dca7d8a8e add builder 'replace' button specs 2025-05-14 13:06:03 +03:00
Pete Matsyburka 87265e2f22 fix prefill same name field 2025-05-14 13:04:35 +03:00
Pete Matsyburka 3166defa1a add submitter value var 2025-05-13 12:27:27 +03:00
Pete Matsyburka 93ba17f42e expire at webhook 2025-05-12 18:23:17 +03:00
Pete Matsyburka 751be1dae5 fix template filter 2025-05-12 15:38:13 +03:00
Pete Matsyburka 3f8d2831e2 allow double dash email 2025-05-12 13:22:11 +03:00
150 changed files with 36926 additions and 371 deletions
+4 -1
View File
@@ -157,7 +157,10 @@ jobs:
bundle install --jobs 4 --retry 4
yarn install
sudo apt-get update
sudo apt-get install libvips
sudo apt-get install -y libvips
wget -O pdfium-linux.tgz "https://github.com/docusealco/pdfium-binaries/releases/latest/download/pdfium-linux-$(uname -m | sed 's/x86_64/x64/;s/aarch64/arm64/').tgz"
sudo tar -xzf pdfium-linux.tgz --strip-components=1 -C /usr/lib lib/libpdfium.so
rm -f pdfium-linux.tgz
- name: Run
env:
RAILS_ENV: test
+1
View File
@@ -0,0 +1 @@
--require rails_helper
+16 -5
View File
@@ -1,8 +1,17 @@
FROM ruby:3.4.2-alpine AS fonts
FROM ruby:3.4.2-alpine AS download
WORKDIR /fonts
RUN apk --no-cache add fontforge wget && wget https://github.com/satbyy/go-noto-universal/releases/download/v7.0/GoNotoKurrent-Regular.ttf && wget https://github.com/satbyy/go-noto-universal/releases/download/v7.0/GoNotoKurrent-Bold.ttf && wget https://github.com/impallari/DancingScript/raw/master/fonts/DancingScript-Regular.otf && wget https://cdn.jsdelivr.net/gh/notofonts/notofonts.github.io/fonts/NotoSansSymbols2/hinted/ttf/NotoSansSymbols2-Regular.ttf && wget https://github.com/Maxattax97/gnu-freefont/raw/master/ttf/FreeSans.ttf && wget https://github.com/impallari/DancingScript/raw/master/OFL.txt
RUN apk --no-cache add fontforge wget && \
wget https://github.com/satbyy/go-noto-universal/releases/download/v7.0/GoNotoKurrent-Regular.ttf && \
wget https://github.com/satbyy/go-noto-universal/releases/download/v7.0/GoNotoKurrent-Bold.ttf && \
wget https://github.com/impallari/DancingScript/raw/master/fonts/DancingScript-Regular.otf && \
wget https://cdn.jsdelivr.net/gh/notofonts/notofonts.github.io/fonts/NotoSansSymbols2/hinted/ttf/NotoSansSymbols2-Regular.ttf && \
wget https://github.com/Maxattax97/gnu-freefont/raw/master/ttf/FreeSans.ttf && \
wget https://github.com/impallari/DancingScript/raw/master/OFL.txt && \
wget -O pdfium-linux.tgz "https://github.com/docusealco/pdfium-binaries/releases/latest/download/pdfium-linux-$(uname -m | sed 's/x86_64/x64/;s/aarch64/arm64/').tgz" && \
mkdir -p /pdfium-linux && \
tar -xzf pdfium-linux.tgz -C /pdfium-linux
RUN fontforge -lang=py -c 'font1 = fontforge.open("FreeSans.ttf"); font2 = fontforge.open("NotoSansSymbols2-Regular.ttf"); font1.mergeFonts(font2); font1.generate("FreeSans.ttf")'
@@ -41,7 +50,7 @@ ENV OPENSSL_CONF=/app/openssl_legacy.cnf
WORKDIR /app
RUN echo '@edge https://dl-cdn.alpinelinux.org/alpine/edge/community' >> /etc/apk/repositories && apk add --no-cache sqlite-dev libpq-dev mariadb-dev vips-dev vips-poppler poppler-utils redis libheif@edge vips-heif gcompat ttf-freefont && mkdir /fonts && rm /usr/share/fonts/freefont/FreeSans.otf
RUN echo '@edge https://dl-cdn.alpinelinux.org/alpine/edge/community' >> /etc/apk/repositories && apk add --no-cache sqlite-dev libpq-dev mariadb-dev vips-dev@edge redis libheif@edge vips-heif@edge gcompat ttf-freefont && mkdir /fonts && rm /usr/share/fonts/freefont/FreeSans.otf
RUN echo $'.include = /etc/ssl/openssl.cnf\n\
\n\
@@ -70,8 +79,10 @@ COPY ./tmp ./tmp
COPY LICENSE README.md Rakefile config.ru .version ./
COPY .version ./public/version
COPY --from=fonts /fonts/GoNotoKurrent-Regular.ttf /fonts/GoNotoKurrent-Bold.ttf /fonts/DancingScript-Regular.otf /fonts/OFL.txt /fonts
COPY --from=fonts /fonts/FreeSans.ttf /usr/share/fonts/freefont
COPY --from=download /fonts/GoNotoKurrent-Regular.ttf /fonts/GoNotoKurrent-Bold.ttf /fonts/DancingScript-Regular.otf /fonts/OFL.txt /fonts
COPY --from=download /fonts/FreeSans.ttf /usr/share/fonts/freefont
COPY --from=download /pdfium-linux/lib/libpdfium.so /usr/lib/libpdfium.so
COPY --from=download /pdfium-linux/licenses/pdfium.txt /usr/lib/libpdfium-LICENSE.txt
COPY --from=webpack /app/public/packs ./public/packs
RUN ln -s /fonts /app/public/fonts
+12 -2
View File
@@ -1,8 +1,10 @@
# frozen_string_literal: true
class AccountConfigsController < ApplicationController
before_action :load_account_config
authorize_resource :account_config
before_action :load_account_config, only: :create
authorize_resource :account_config, only: :create
load_and_authorize_resource :account_config, only: :destroy
ALLOWED_KEYS = [
AccountConfig::ALLOW_TYPED_SIGNATURE,
@@ -30,6 +32,14 @@ class AccountConfigsController < ApplicationController
head :ok
end
def destroy
raise InvalidKey unless ALLOWED_KEYS.include?(@account_config.key)
@account_config.destroy!
redirect_back(fallback_location: root_path)
end
private
def load_account_config
+1 -1
View File
@@ -162,7 +162,7 @@ module Api
submitters = submitters.where(submission_id: params[:submission_id]) if params[:submission_id].present?
if params[:template_id].present?
submitters = submitters.joins(:submission).where(submission: { template_id: params[:template_id] })
submitters = submitters.joins(:submission).where(submissions: { template_id: params[:template_id] })
end
maybe_filder_by_completed_at(submitters, params)
@@ -100,6 +100,7 @@ module Api
permitted_params = [
:name,
:external_id,
:shared_link,
{
submitters: [%i[name uuid is_requester invite_by_uuid optional_invite_by_uuid linked_to_uuid email]],
fields: [[:uuid, :submitter_uuid, :name, :type,
@@ -55,6 +55,14 @@ class ApplicationController < ActionController::Base
request.session[:impersonated_user_id] = user.uuid
end
def pagy_auto(collection, **keyword_args)
if current_ability.can?(:manage, :countless)
pagy_countless(collection, **keyword_args)
else
pagy(collection, **keyword_args)
end
end
private
def with_locale(&)
@@ -9,7 +9,9 @@ class EmailSmtpSettingsController < ApplicationController
def create
if @encrypted_config.update(email_configs)
SettingsMailer.smtp_successful_setup(@encrypted_config.value['from_email'] || current_user.email).deliver_now!
unless Docuseal.multitenant?
SettingsMailer.smtp_successful_setup(@encrypted_config.value['from_email'] || current_user.email).deliver_now!
end
redirect_to settings_email_index_path, notice: I18n.t('changes_have_been_saved')
else
@@ -10,16 +10,17 @@ class SendSubmissionEmailController < ApplicationController
SEND_DURATION = 30.minutes
def create
@submitter =
if params[:template_slug]
Submitter.joins(submission: :template).find_by!(email: params[:email].to_s.downcase,
template: { slug: params[:template_slug] })
elsif params[:submission_slug]
Submitter.joins(:submission).find_by!(email: params[:email].to_s.downcase,
submission: { slug: params[:submission_slug] })
else
Submitter.find_by!(slug: params[:submitter_slug])
end
if params[:template_slug]
@submitter = Submitter.completed.joins(submission: :template).find_by!(email: params[:email].to_s.downcase,
template: { slug: params[:template_slug] })
elsif params[:submission_slug]
@submitter = Submitter.completed.joins(:submission).find_by(email: params[:email].to_s.downcase,
submission: { slug: params[:submission_slug] })
return redirect_to submissions_preview_completed_path(params[:submission_slug], status: :error) unless @submitter
else
@submitter = Submitter.completed.find_by!(slug: params[:submitter_slug])
end
RateLimit.call("send-email-#{@submitter.id}", limit: 2, ttl: 5.minutes)
+69 -28
View File
@@ -8,20 +8,28 @@ class StartFormController < ApplicationController
around_action :with_browser_locale, only: %i[show completed]
before_action :maybe_redirect_com, only: %i[show completed]
before_action :load_resubmit_submitter, only: :update
before_action :load_template
before_action :authorize_start!, only: :update
def show
raise ActionController::RoutingError, I18n.t('not_found') if @template.preferences['require_phone_2fa'] == true
raise ActionController::RoutingError, I18n.t('not_found') if @template.preferences['require_phone_2fa']
@submitter = @template.submissions.new(account_id: @template.account_id)
.submitters.new(account_id: @template.account_id,
uuid: (filter_undefined_submitters(@template).first ||
@template.submitters.first)['uuid'])
if @template.shared_link?
@submitter = @template.submissions.new(account_id: @template.account_id)
.submitters.new(account_id: @template.account_id,
uuid: (filter_undefined_submitters(@template).first ||
@template.submitters.first)['uuid'])
else
Rollbar.warning("Not shared template: #{@template.id}") if defined?(Rollbar)
return render :private if current_user && current_ability.can?(:read, @template)
raise ActionController::RoutingError, I18n.t('not_found')
end
end
def update
return redirect_to start_form_path(@template.slug) if @template.archived_at?
@submitter = find_or_initialize_submitter(@template, submitter_params)
if @submitter.completed_at?
@@ -43,9 +51,11 @@ class StartFormController < ApplicationController
if @submitter.save
if is_new_record
WebhookUrls.for_account_id(@submitter.account_id, 'submission.created').each do |webhook_url|
SendSubmissionCreatedWebhookRequestJob.perform_async('submission_id' => @submitter.submission_id,
'webhook_url_id' => webhook_url.id)
enqueue_submission_create_webhooks(@submitter)
if @submitter.submission.expire_at?
ProcessSubmissionExpiredJob.perform_at(@submitter.submission.expire_at,
'submission_id' => @submitter.submission_id)
end
end
@@ -57,6 +67,8 @@ class StartFormController < ApplicationController
end
def completed
return redirect_to start_form_path(@template.slug) if !@template.shared_link? || @template.archived_at?
@submitter = Submitter.where(submission: @template.submissions)
.where.not(completed_at: nil)
.find_by!(email: params[:email])
@@ -64,32 +76,55 @@ class StartFormController < ApplicationController
private
def load_resubmit_submitter
@resubmit_submitter =
if params[:resubmit].present? && !params[:resubmit].in?([true, 'true'])
Submitter.find_by(slug: params[:resubmit])
end
end
def authorize_start!
return redirect_to start_form_path(@template.slug) if @template.archived_at?
return if @resubmit_submitter
return if @template.shared_link? || (current_user && current_ability.can?(:read, @template))
Rollbar.warning("Not shared template: #{@template.id}") if defined?(Rollbar)
redirect_to start_form_path(@template.slug)
end
def enqueue_submission_create_webhooks(submitter)
WebhookUrls.for_account_id(submitter.account_id, 'submission.created').each do |webhook_url|
SendSubmissionCreatedWebhookRequestJob.perform_async('submission_id' => submitter.submission_id,
'webhook_url_id' => webhook_url.id)
end
end
def find_or_initialize_submitter(template, submitter_params)
Submitter.where(submission: template.submissions.where(expire_at: Time.current..)
.or(template.submissions.where(expire_at: nil)).where(archived_at: nil))
.order(id: :desc)
.where(declined_at: nil)
.where(external_id: nil)
.where(ip: [nil, request.remote_ip])
.then { |rel| params[:resubmit].present? ? rel.where(completed_at: nil) : rel }
.find_or_initialize_by(email: submitter_params[:email], **submitter_params.compact_blank)
Submitter
.where(submission: template.submissions.where(expire_at: Time.current..)
.or(template.submissions.where(expire_at: nil)).where(archived_at: nil))
.order(id: :desc)
.where(declined_at: nil)
.where(external_id: nil)
.where(ip: [nil, request.remote_ip])
.then { |rel| params[:resubmit].present? || params[:selfsign].present? ? rel.where(completed_at: nil) : rel }
.find_or_initialize_by(email: submitter_params[:email], **submitter_params.compact_blank)
end
def assign_submission_attributes(submitter, template)
resubmit_submitter =
(Submitter.where(submission: template.submissions).find_by(slug: params[:resubmit]) if params[:resubmit].present?)
submitter.assign_attributes(
uuid: (filter_undefined_submitters(template).first || @template.submitters.first)['uuid'],
ip: request.remote_ip,
ua: request.user_agent,
values: resubmit_submitter&.preferences&.fetch('default_values', nil) || {},
preferences: resubmit_submitter&.preferences.presence || { 'send_email' => true },
metadata: resubmit_submitter&.metadata.presence || {}
values: @resubmit_submitter&.preferences&.fetch('default_values', nil) || {},
preferences: @resubmit_submitter&.preferences.presence || { 'send_email' => true },
metadata: @resubmit_submitter&.metadata.presence || {}
)
if submitter.values.present?
resubmit_submitter.attachments.each do |attachment|
@resubmit_submitter.attachments.each do |attachment|
submitter.attachments << attachment.dup if submitter.values.value?(attachment.uuid)
end
end
@@ -111,15 +146,21 @@ class StartFormController < ApplicationController
end
def submitter_params
return current_user.slice(:email) if params[:selfsign]
return @resubmit_submitter.slice(:name, :phone, :email) if @resubmit_submitter.present?
params.require(:submitter).permit(:email, :phone, :name).tap do |attrs|
attrs[:email] = Submissions.normalize_email(attrs[:email])
end
end
def load_template
slug = params[:slug] || params[:start_form_slug]
@template = Template.find_by!(slug:)
@template =
if @resubmit_submitter
@resubmit_submitter.template
else
Template.find_by!(slug: params[:slug] || params[:start_form_slug])
end
end
def multiple_submitters_error_message
@@ -18,6 +18,6 @@ class SubmissionsArchivedController < ApplicationController
@submissions.order(id: :desc)
end
@pagy, @submissions = pagy(@submissions.preload(submitters: :start_form_submission_events))
@pagy, @submissions = pagy_auto(@submissions.preload(submitters: :start_form_submission_events))
end
end
@@ -19,6 +19,6 @@ class SubmissionsDashboardController < ApplicationController
@submissions.order(id: :desc)
end
@pagy, @submissions = pagy(@submissions.preload(submitters: :start_form_submission_events))
@pagy, @submissions = pagy_auto(@submissions.preload(submitters: :start_form_submission_events))
end
end
@@ -1,6 +1,7 @@
# frozen_string_literal: true
class SubmissionsPreviewController < ApplicationController
around_action :with_browser_locale
skip_before_action :authenticate_user!
skip_authorization_check
@@ -9,7 +9,7 @@ class TemplateFoldersController < ApplicationController
@templates = Templates.search(@templates, params[:q])
@templates = Templates::Order.call(@templates, current_user, cookies.permanent[:dashboard_templates_order])
@pagy, @templates = pagy(@templates, limit: 12)
@pagy, @templates = pagy_auto(@templates, limit: 12)
end
def edit; end
@@ -7,6 +7,6 @@ class TemplatesArchivedController < ApplicationController
@templates = @templates.where.not(archived_at: nil).preload(:author, :folder, :template_accesses).order(id: :desc)
@templates = Templates.search(@templates, params[:q])
@pagy, @templates = pagy(@templates, limit: 12)
@pagy, @templates = pagy_auto(@templates, limit: 12)
end
end
@@ -15,7 +15,7 @@ class TemplatesArchivedSubmissionsController < ApplicationController
@submissions.order(id: :desc)
end
@pagy, @submissions = pagy(@submissions.preload(submitters: :start_form_submission_events))
@pagy, @submissions = pagy_auto(@submissions.preload(submitters: :start_form_submission_events))
rescue ActiveRecord::RecordNotFound
redirect_to root_path
end
+1 -1
View File
@@ -21,7 +21,7 @@ class TemplatesController < ApplicationController
submissions.order(id: :desc)
end
@pagy, @submissions = pagy(submissions.preload(:template_accesses, submitters: :start_form_submission_events))
@pagy, @submissions = pagy_auto(submissions.preload(:template_accesses, submitters: :start_form_submission_events))
rescue ActiveRecord::RecordNotFound
redirect_to root_path
end
@@ -17,7 +17,7 @@ class TemplatesDashboardController < ApplicationController
@pagy, @template_folders = pagy(
@template_folders,
items: FOLDERS_PER_PAGE,
limit: FOLDERS_PER_PAGE,
page: @template_folders.count > SHOW_TEMPLATES_FOLDERS_THRESHOLD ? params[:page] : 1
)
@@ -35,7 +35,7 @@ class TemplatesDashboardController < ApplicationController
(@template_folders.size < 7 ? 9 : 6)
end
@pagy, @templates = pagy(@templates, limit:)
@pagy, @templates = pagy_auto(@templates, limit:)
end
end
@@ -0,0 +1,21 @@
# frozen_string_literal: true
class TemplatesShareLinkController < ApplicationController
load_and_authorize_resource :template
def show; end
def create
authorize!(:update, @template)
@template.update!(template_params)
head :ok
end
private
def template_params
params.require(:template).permit(:shared_link)
end
end
+1 -1
View File
@@ -16,7 +16,7 @@ class UsersController < ApplicationController
@users.active.where.not(role: 'integration')
end
@pagy, @users = pagy(@users.where(account: current_account).order(id: :desc))
@pagy, @users = pagy(@users.preload(account: :account_accesses).where(account: current_account).order(id: :desc))
end
def new; end
+2
View File
@@ -24,6 +24,7 @@ import SubmitForm from './elements/submit_form'
import PromptPassword from './elements/prompt_password'
import EmailsTextarea from './elements/emails_textarea'
import ToggleOnSubmit from './elements/toggle_on_submit'
import CheckOnClick from './elements/check_on_click'
import PasswordInput from './elements/password_input'
import SearchInput from './elements/search_input'
import ToggleAttribute from './elements/toggle_attribute'
@@ -103,6 +104,7 @@ safeRegisterElement('set-date-button', SetDateButton)
safeRegisterElement('indeterminate-checkbox', IndeterminateCheckbox)
safeRegisterElement('app-tour', AppTour)
safeRegisterElement('dashboard-dropzone', DashboardDropzone)
safeRegisterElement('check-on-click', CheckOnClick)
safeRegisterElement('template-builder', class extends HTMLElement {
connectedCallback () {
+14
View File
@@ -0,0 +1,14 @@
export default class extends HTMLElement {
connectedCallback () {
this.addEventListener('click', () => {
if (!this.element.checked) {
this.element.checked = true
this.element.dispatchEvent(new Event('change', { bubbles: true }))
}
})
}
get element () {
return document.getElementById(this.dataset.elementId)
}
}
@@ -3,8 +3,6 @@ export default class extends HTMLElement {
this.clearChecked()
this.addEventListener('click', (e) => {
e.stopPropagation()
const text = this.dataset.text || this.innerText.trim()
if (navigator.clipboard) {
@@ -165,11 +165,27 @@ export default targetable(class extends HTMLElement {
onDragover (e) {
if (e.dataTransfer?.types?.includes('Files') || this.dataset.targets !== 'dashboard-dropzone.templateCards') {
this.style.backgroundColor = '#F7F3F0'
if (this.classList.contains('before:border-base-300')) {
this.classList.remove('before:border-base-300')
this.classList.add('before:border-base-content/30')
} else if (this.classList.contains('border-base-300')) {
this.classList.remove('border-base-300')
this.classList.add('border-base-content/30')
}
}
}
onDragleave () {
this.style.backgroundColor = null
if (this.classList.contains('before:border-base-content/30')) {
this.classList.remove('before:border-base-content/30')
this.classList.add('before:border-base-300')
} else if (this.classList.contains('border-base-content/30')) {
this.classList.remove('border-base-content/30')
this.classList.add('border-base-300')
}
}
onWindowDragleave = (e) => {
+22 -4
View File
@@ -5,19 +5,37 @@ export default actionable(targetable(class extends HTMLElement {
static [target.static] = [
'loading',
'icon',
'input'
'input',
'area'
]
connectedCallback () {
this.addEventListener('drop', this.onDrop)
this.addEventListener('dragover', (e) => e.preventDefault())
this.addEventListener('drop', this.onDrop)
document.addEventListener('turbo:submit-end', this.toggleLoading)
this.area?.addEventListener('dragover', this.onDragover)
this.area?.addEventListener('dragleave', this.onDragleave)
}
disconnectedCallback () {
this.removeEventListener('drop', this.onDrop)
document.removeEventListener('turbo:submit-end', this.toggleLoading)
this.area?.removeEventListener('dragover', this.onDragover)
this.area?.removeEventListener('dragleave', this.onDragleave)
}
onDragover (e) {
if (e.dataTransfer?.types?.includes('Files')) {
this.style.backgroundColor = '#F7F3F0'
this.classList.remove('border-base-300', 'hover:bg-base-200/30')
this.classList.add('border-base-content/30')
}
}
onDragleave () {
this.style.backgroundColor = null
this.classList.remove('border-base-content/30')
this.classList.add('border-base-300', 'hover:bg-base-200/30')
}
onDrop (e) {
+11 -5
View File
@@ -23,7 +23,7 @@
</div>
<div
v-if="isActive && withLabel && (!area.option_uuid || !option.value)"
class="absolute -top-7 rounded bg-base-content text-base-100 px-2 text-sm whitespace-nowrap pointer-events-none"
class="absolute -top-7 rounded bg-base-content text-base-100 px-2 text-sm whitespace-nowrap pointer-events-none field-area-active-label"
>
<template v-if="area.option_uuid && !option.value">
{{ optionValue(option) }}
@@ -55,10 +55,12 @@
>
<div
v-else-if="field.type === 'signature' && signature"
class="flex flex-col justify-between h-full overflow-hidden"
class="flex justify-between h-full gap-1 overflow-hidden w-full"
:class="isNarrow && (withSignatureId || field.preferences?.reason_field_uuid) ? 'flex-row' : 'flex-col'"
>
<div
class="flex-grow flex overflow-hidden"
class="flex overflow-hidden"
:class="isNarrow && (withSignatureId || field.preferences?.reason_field_uuid) ? 'w-1/2' : 'flex-grow'"
style="min-height: 50%"
>
<img
@@ -67,8 +69,9 @@
>
</div>
<div
v-if="withSignatureId"
class="w-full mt-1 text-[1vw] lg:text-[0.55rem] lg:leading-[0.65rem]"
v-if="withSignatureId || field.preferences?.reason_field_uuid"
class="text-[1vw] lg:text-[0.55rem] lg:leading-[0.65rem]"
:class="isNarrow ? 'w-1/2' : 'w-full'"
>
<div class="truncate uppercase">
ID: {{ signature.uuid }}
@@ -444,6 +447,9 @@ export default {
}
return style
},
isNarrow () {
return this.area.h > 0 && (this.area.w / this.area.h) > 6
}
},
watch: {
+2 -2
View File
@@ -93,7 +93,7 @@
<div
v-show="isFormVisible"
id="form_container"
class="shadow-md bg-base-100 absolute bottom-0 w-full border-base-200 border p-4 rounded form-container"
class="shadow-md bg-base-100 absolute bottom-0 w-full border-base-200 border p-4 rounded form-container overflow-hidden"
:class="{ 'md:bottom-4': isBreakpointMd }"
:style="{ backgroundColor: backgroundColor }"
>
@@ -1133,7 +1133,7 @@ export default {
parent.style.overflow = 'hidden'
scrollbox.classList.add('h-full', 'overflow-y-auto')
scrollbox.parentNode.classList.add('h-screen', 'overflow-y-auto')
scrollbox.parentNode.classList.add('h-screen', 'h-[100dvh]', 'overflow-y-auto')
scrollbox.parentNode.style.maxHeight = '-webkit-fill-available'
})
}
@@ -211,7 +211,7 @@ export default {
},
computed: {
computedPreviousValue () {
if (this.isUsePreviousValue) {
if (this.isUsePreviousValue && this.field.required === true) {
return this.previousValue
} else {
return null
@@ -414,7 +414,7 @@ export default {
}
},
computedPreviousValue () {
if (this.isUsePreviousValue) {
if (this.isUsePreviousValue && this.field.required === true) {
return this.previousValue
} else {
return null
+4 -2
View File
@@ -151,8 +151,10 @@ export default {
resizeTextarea () {
const textarea = this.$refs.textarea
textarea.style.height = 'auto'
textarea.style.height = Math.min(250, textarea.scrollHeight) + 'px'
if (textarea) {
textarea.style.height = 'auto'
textarea.style.height = Math.min(250, textarea.scrollHeight) + 'px'
}
},
toggleTextArea () {
this.isTextArea = true
+45 -7
View File
@@ -13,7 +13,6 @@
:template-id="template.id"
:accept-file-types="acceptFileTypes"
:with-replace-and-clone="withReplaceAndCloneUpload"
hover-class="bg-base-200/50"
@add="[updateFromUpload($event), isDragFile = false]"
@replace="[onDocumentsReplace($event), isDragFile = false]"
@replace-and-clone="onDocumentsReplaceAndTemplateClone($event)"
@@ -80,13 +79,52 @@
name="buttons"
/>
<template v-else>
<form
v-if="withSignYourselfButton && template.submitters.length < 2"
target="_blank"
data-turbo="false"
class="inline"
method="post"
:action="`/d/${template.slug}`"
@submit="maybeShowErrorTemplateAlert"
>
<input
type="hidden"
name="_method"
value="put"
autocomplete="off"
>
<input
type="hidden"
name="authenticity_token"
:value="authenticityToken"
autocomplete="off"
>
<input
type="hidden"
name="selfsign"
value="true"
autocomplete="off"
>
<button
class="btn btn-primary btn-ghost text-base hidden md:flex"
type="submit"
>
<IconWritingSign
width="22"
class="inline"
/>
<span class="hidden md:inline">
{{ t('sign_yourself') }}
</span>
</button>
</form>
<a
v-if="withSignYourselfButton"
v-else-if="withSignYourselfButton"
id="sign_yourself_button"
:href="template.submitters.length > 1 ? `/templates/${template.id}/submissions/new?selfsign=true` : `/d/${template.slug}`"
:href="`/templates/${template.id}/submissions/new?selfsign=true`"
class="btn btn-primary btn-ghost text-base hidden md:flex"
:target="template.submitters.length > 1 ? '' : '_blank'"
:data-turbo-frame="template.submitters.length > 1 ? 'modal' : ''"
data-turbo-frame="modal"
@click="maybeShowErrorTemplateAlert"
>
<IconWritingSign
@@ -1280,7 +1318,7 @@ export default {
area.h = (pageMask.clientWidth / 35 / pageMask.clientWidth)
}
},
onDraw (area) {
onDraw ({ area, isTooSmall }) {
if (this.drawField) {
if (this.drawOption) {
const areaWithoutOption = this.drawField.areas?.find((a) => !a.option_uuid)
@@ -1375,7 +1413,7 @@ export default {
area.y -= area.h / 2
}
if (area.w) {
if (area.w && (type !== 'checkbox' || this.drawFieldType || !isTooSmall)) {
this.addField(type, area)
this.selectedAreaRef.value = area
+1 -1
View File
@@ -23,7 +23,7 @@
@drop-field="$emit('drop-field', {...$event, attachment_uuid: document.uuid })"
@remove-area="$emit('remove-area', $event)"
@scroll-to="scrollToArea"
@draw="$emit('draw', {...$event, attachment_uuid: document.uuid })"
@draw="$emit('draw', { area: {...$event.area, attachment_uuid: document.uuid }, isTooSmall: $event.isTooSmall })"
/>
</div>
</template>
+6 -6
View File
@@ -10,7 +10,7 @@
id="document_dropzone"
class="w-full relative rounded-md border-2 border-base-content/10 border-dashed"
:for="inputId"
:class="[{ 'opacity-50': isLoading, 'hover:bg-base-200/30': !hoverClass }, isDragEntering && hoverClass ? hoverClass : '']"
:class="{ 'opacity-50': isLoading, 'hover:bg-base-200/50': withHoverClass && !isDragEntering, 'bg-base-200/50 border-base-content/30': isDragEntering }"
>
<div class="absolute top-0 right-0 left-0 bottom-0 flex items-center justify-center pointer-events-none">
<div class="flex flex-col items-center">
@@ -79,16 +79,16 @@ export default {
type: [Number, String],
required: true
},
withHoverClass: {
type: Boolean,
required: false,
default: true
},
icon: {
type: String,
required: false,
default: 'IconCloudUpload'
},
hoverClass: {
type: String,
required: false,
default: null
},
cloneTemplateOnUpload: {
type: Boolean,
required: false,
@@ -6,12 +6,12 @@
<div class="flex flex-col gap-2 p-4 items-center bg-base-100 h-full max-h-[85vh] max-w-6xl rounded-2xl w-full">
<Dropzone
class="flex-1 h-full"
hover-class="bg-base-200/50"
icon="IconFilePlus"
:template-id="templateId"
:accept-file-types="acceptFileTypes"
:with-hover-class="false"
:with-description="false"
:title="t('upload_a_new_document')"
:title="t('add_a_new_document')"
type="add_files"
@loading="isLoading = $event"
@success="$emit('add', $event)"
@@ -20,10 +20,10 @@
<div class="flex-1 flex gap-2 w-full">
<Dropzone
class="flex-1 h-full"
hover-class="bg-base-200/50"
icon="IconFileSymlink"
:template-id="templateId"
:accept-file-types="acceptFileTypes"
:with-hover-class="false"
:with-description="false"
:title="t('replace_existing_document')"
@loading="isLoading = $event"
@@ -33,10 +33,10 @@
<Dropzone
v-if="withReplaceAndClone"
class="flex-1 h-full"
hover-class="bg-base-200/50"
icon="IconFiles"
:template-id="templateId"
:accept-file-types="acceptFileTypes"
:with-hover-class="false"
:with-description="false"
:clone-template-on-upload="true"
:title="t('clone_and_replace_documents')"
+6 -6
View File
@@ -63,7 +63,7 @@ const en = {
processing_: 'Processing...',
add_pdf_documents_or_images: 'Add PDF documents or images',
add_documents_or_images: 'Add documents or images',
upload_a_new_document: 'Upload a new document',
add_a_new_document: 'Add a new document',
replace_existing_document: 'Replace existing document',
clone_and_replace_documents: 'Clone and replace documents',
required: 'Required',
@@ -228,7 +228,7 @@ const es = {
processing_: 'Procesando...',
add_pdf_documents_or_images: 'Agregar documentos PDF o imágenes',
add_documents_or_images: 'Agregar documentos o imágenes',
upload_a_new_document: 'Subir un nuevo documento',
add_a_new_document: 'Agregar un nuevo documento',
replace_existing_document: 'Reemplazar documento existente',
clone_and_replace_documents: 'Clonar y reemplazar documentos',
required: 'Requerido',
@@ -399,7 +399,7 @@ const it = {
processing_: 'Elaborazione...',
add_pdf_documents_or_images: 'Aggiungi documenti PDF o immagini',
add_documents_or_images: 'Aggiungi documenti o immagini',
upload_a_new_document: 'Carica un nuovo documento',
add_a_new_document: 'Aggiungi un nuovo documento',
replace_existing_document: 'Sostituisci documento esistente',
clone_and_replace_documents: 'Clona e sostituisci documenti',
required: 'Obbligatorio',
@@ -564,7 +564,7 @@ const pt = {
processing_: 'Processando...',
add_pdf_documents_or_images: 'Adicionar documentos PDF ou imagens',
add_documents_or_images: 'Adicionar documentos ou imagens',
upload_a_new_document: 'Enviar um novo documento',
add_a_new_document: 'Ajouter un nouveau document',
replace_existing_document: 'Substituir documento existente',
clone_and_replace_documents: 'Clonar e substituir documentos',
required: 'Obrigatório',
@@ -731,7 +731,7 @@ const fr = {
processing_: 'Traitement en cours...',
add_pdf_documents_or_images: 'Ajoutez des documents PDF ou des images',
add_documents_or_images: 'Ajoutez des documents ou des images',
upload_a_new_document: 'Téléverser un nouveau document',
add_a_new_document: 'Adicionar um novo documento',
replace_existing_document: 'Remplacer le document existant',
clone_and_replace_documents: 'Cloner et remplacer les documents',
required: 'Requis',
@@ -899,7 +899,7 @@ const de = {
processing_: 'Verarbeitung...',
add_pdf_documents_or_images: 'PDF-Dokumente oder Bilder hinzufügen',
add_documents_or_images: 'Dokumente oder Bilder hinzufügen',
upload_a_new_document: 'Neues Dokument hochladen',
add_a_new_document: 'Neues Dokument hinzufügen',
replace_existing_document: 'Vorhandenes Dokument ersetzen',
clone_and_replace_documents: 'Dokumente klonen und ersetzen',
required: 'Erforderlich',
+6 -1
View File
@@ -290,7 +290,12 @@ export default {
area.cell_w = this.newArea.cell_w
}
this.$emit('draw', area)
const dx = Math.abs(e.offsetX - this.$refs.mask.clientWidth * this.newArea.initialX)
const dy = Math.abs(e.offsetY - this.$refs.mask.clientHeight * this.newArea.initialY)
const isTooSmall = dx < 8 && dy < 8
this.$emit('draw', { area, isTooSmall })
}
this.showMask = false
+7 -3
View File
@@ -20,7 +20,7 @@ class ApplicationMailer < ActionMailer::Base
end
def set_message_metadata
message.instance_variable_set(:@message_metadata, @message_metadata)
message.instance_variable_set(:@message_metadata, @message_metadata || {})
end
def set_message_uuid
@@ -28,10 +28,14 @@ class ApplicationMailer < ActionMailer::Base
end
def assign_message_metadata(tag, record)
@message_metadata = {
@message_metadata = (@message_metadata || {}).merge(
'tag' => tag,
'record_id' => record.id,
'record_type' => record.class.name
}
)
end
def put_metadata(attrs)
@message_metadata = (@message_metadata || {}).merge(attrs)
end
end
+9 -1
View File
@@ -233,9 +233,17 @@ class SubmitterMailer < ApplicationMailer
def from_address_for_submitter(submitter)
if submitter.submission.source.in?(%w[api embed]) &&
(from_email = AccountConfig.find_by(account: submitter.account, key: 'integration_from_email')&.value.presence)
user = submitter.account.users.find_by(email: from_email)
put_metadata('from_user_id' => user.id)
from_email
else
(submitter.submission.created_by_user || submitter.submission.template.author).friendly_name
user = submitter.submission.created_by_user || submitter.submission.template.author
put_metadata('from_user_id' => user.id)
user.friendly_name
end
end
end
+1
View File
@@ -33,6 +33,7 @@ class Account < ApplicationRecord
has_many :account_linked_accounts, dependent: :destroy
has_many :email_events, dependent: :destroy
has_many :webhook_urls, dependent: :destroy
has_many :account_accesses, dependent: :destroy
has_many :account_testing_accounts, -> { testing }, dependent: :destroy,
class_name: 'AccountLinkedAccount',
inverse_of: :account
+24
View File
@@ -0,0 +1,24 @@
# frozen_string_literal: true
# == Schema Information
#
# Table name: account_accesses
#
# id :bigint not null, primary key
# created_at :datetime not null
# updated_at :datetime not null
# account_id :bigint not null
# user_id :bigint not null
#
# Indexes
#
# index_account_accesses_on_account_id_and_user_id (account_id,user_id) UNIQUE
#
# Foreign Keys
#
# fk_rails_... (account_id => accounts.id)
#
class AccountAccess < ApplicationRecord
belongs_to :account
belongs_to :user
end
+4 -4
View File
@@ -18,10 +18,10 @@
#
# Indexes
#
# index_email_events_on_account_id (account_id)
# index_email_events_on_email (email)
# index_email_events_on_emailable (emailable_type,emailable_id)
# index_email_events_on_message_id (message_id)
# index_email_events_on_account_id_and_event_datetime (account_id,event_datetime)
# index_email_events_on_email (email)
# index_email_events_on_emailable (emailable_type,emailable_id)
# index_email_events_on_message_id (message_id)
#
# Foreign Keys
#
+9 -3
View File
@@ -65,13 +65,19 @@ class Submission < ApplicationRecord
scope :active, -> { where(archived_at: nil) }
scope :archived, -> { where.not(archived_at: nil) }
scope :pending, -> { joins(:submitters).where(submitters: { completed_at: nil }).group(:id) }
scope :pending, lambda {
where(Submitter.where(Submitter.arel_table[:submission_id].eq(Submission.arel_table[:id])
.and(Submitter.arel_table[:completed_at].eq(nil))).select(1).arel.exists)
}
scope :completed, lambda {
where.not(Submitter.where(Submitter.arel_table[:submission_id].eq(Submission.arel_table[:id])
.and(Submitter.arel_table[:completed_at].eq(nil))).select(1).arel.exists)
}
scope :declined, -> { joins(:submitters).where.not(submitters: { declined_at: nil }).group(:id) }
scope :expired, -> { where(expire_at: ..Time.current) }
scope :declined, lambda {
where(Submitter.where(Submitter.arel_table[:submission_id].eq(Submission.arel_table[:id])
.and(Submitter.arel_table[:declined_at].not_eq(nil))).select(1).arel.exists)
}
scope :expired, -> { pending.where(expire_at: ..Time.current) }
enum :source, {
invite: 'invite',
+1
View File
@@ -10,6 +10,7 @@
# name :string not null
# preferences :text not null
# schema :text not null
# shared_link :boolean default(FALSE), not null
# slug :string not null
# source :text not null
# submitters :text not null
+2
View File
@@ -73,6 +73,8 @@ class User < ApplicationRecord
scope :archived, -> { where.not(archived_at: nil) }
scope :admins, -> { where(role: ADMIN_ROLE) }
validates :email, format: { with: /\A[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\z/ }
def access_token
super || build_access_token.tap(&:save!)
end
+51 -11
View File
@@ -1,12 +1,52 @@
<div class="text-center">
<div class="join">
<a href="https://docuseal.com<%= request.fullpath.gsub('docuseal.eu', 'docuseal.com') %>" class="btn bg-base-200 join-item w-32 <%= 'bg-base-300' if request.host == 'docuseal.com' %>">
<%= svg_icon 'world', class: 'w-5 h-5' %>
Global
</a>
<a href="https://docuseal.eu<%= request.fullpath.gsub('docuseal.com', 'docuseal.eu') %>" class="btn bg-base-200 join-item w-32 <%= 'bg-base-300' if request.host == 'docuseal.eu' %>">
<%= svg_icon 'eu_flag', class: 'w-5 h-5' %>
Europe
</a>
<% eu_server = request.host == 'docuseal.eu' %>
<server-selector>
<div id="global_server_selector" class="text-center hidden">
<div class="join">
<a href="https://docuseal.com<%= request.fullpath.gsub('docuseal.eu', 'docuseal.com') %>" class="btn bg-base-200 join-item w-40 <%= 'bg-base-300' unless eu_server %>">
<%= svg_icon 'world', class: 'w-5 h-5' %>
Global
</a>
<a href="https://docuseal.eu<%= request.fullpath.gsub('docuseal.com', 'docuseal.eu') %>" class="relative btn bg-base-200 join-item w-40 <%= 'bg-base-300' if eu_server %>">
<%= svg_icon 'eu_flag', class: 'w-5 h-5' %>
Europe
<% unless eu_server %>
<span id="eu_server_alert" class="absolute flex space-x-0.5 hidden" style="top: -1.5rem;">
<span class="text-xs font-normal leading-none text-base-content normal-case">
<%= t('eu_data_residency') %>
</span>
<%= svg_icon 'corner_right_down', class: 'w-4 h-5 stroke-1 shrink-0 pt-1' %>
</span>
<% end %>
</a>
</div>
</div>
</div>
<div id="us_server_selector" class="flex justify-center hidden">
<div class="dropdown">
<label tabindex="0" class="relative btn btn-sm bg-transparent font-medium normal-case border-base-content/20 justify-start" style="width: 141px; padding: 0 20px">
<% if eu_server %>
<%= svg_icon 'eu_flag', class: 'w-5 h-5' %>
<span>EU Cloud</span>
<% else %>
<%= svg_icon 'usa_flag', class: 'w-5 h-5' %>
<span>US Cloud</span>
<% end %>
<%= svg_icon 'chevron_down', class: 'mr-1 w-4 h-4 absolute right-1' %>
</label>
<ul tabindex="0" class="dropdown-content z-[1] menu border border-base-content/20 mt-1 bg-base-100 rounded-box w-36">
<li>
<a href="https://docuseal.com<%= request.fullpath.gsub('docuseal.eu', 'docuseal.com') %>" class="flex items-center space-x-2 <%= 'bg-base-300' unless eu_server %>">
<%= svg_icon 'usa_flag', class: 'w-5 h-5' %>
US Cloud
</a>
</li>
<li>
<a href="https://docuseal.eu<%= request.fullpath.gsub('docuseal.com', 'docuseal.eu') %>" class="flex items-center space-x-2 <%= 'bg-base-300' if eu_server %>">
<%= svg_icon 'eu_flag', class: 'w-5 h-5' %>
EU Cloud
</a>
</li>
</ul>
</div>
</div>
</server-selector>
<%= render 'scripts/server_selector' %>
@@ -0,0 +1,3 @@
<svg class="<%= local_assigns[:class] %>" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" width="24" height="24" stroke-width="2">
<path d="M6 6h6a3 3 0 0 1 3 3v10l-4 -4m8 0l-4 4"></path>
</svg>

After

Width:  |  Height:  |  Size: 285 B

+9
View File
@@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" class="<%= local_assigns[:class] %>" viewBox="0 0 640 480">
<path fill="#bd3d44" d="M0 0h640v480H0" />
<path stroke="#fff" stroke-width="37" d="M0 55.3h640M0 129h640M0 203h640M0 277h640M0 351h640M0 425h640" />
<path fill="#192f5d" d="M0 0h364.8v258.5H0" />
<marker id="um-a" markerHeight="30" markerWidth="30">
<path fill="#fff" d="m14 0 9 27L0 10h28L5 27z" />
</marker>
<path fill="none" marker-mid="url(#um-a)" d="m0 0 16 11h61 61 61 61 60L47 37h61 61 60 61L16 63h61 61 61 61 60L47 89h61 61 60 61L16 115h61 61 61 61 60L47 141h61 61 60 61L16 166h61 61 61 61 60L47 192h61 61 60 61L16 218h61 61 61 61 60z" />
</svg>

After

Width:  |  Height:  |  Size: 671 B

@@ -0,0 +1,22 @@
<script>
if (!window.customElements.get('server-selector')) {
customElements.define('server-selector', class extends HTMLElement {
connectedCallback() {
const usServerSelector = this.querySelector('#us_server_selector');
const globalServerSelector = this.querySelector('#global_server_selector');
const euServerAlert = this.querySelector('#eu_server_alert');
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
const usTimezones = /^(?:America\/(?:New_York|Detroit|Kentucky|Indiana|Chicago|Menominee|North_Dakota|Denver|Boise|Phoenix|Los_Angeles|Anchorage|Juneau|Sitka|Metlakatla|Yakutat|Nome|Adak)|Pacific\/Honolulu)/;
if (usTimezones.test(timezone)) {
usServerSelector.classList.remove('hidden');
} else if (timezone.includes('Europe')) {
globalServerSelector.classList.remove('hidden');
euServerAlert?.classList?.remove('hidden');
} else {
globalServerSelector.classList.remove('hidden');
}
}
});
}
</script>
+1 -1
View File
@@ -1 +1 @@
<% 'stats stat stat-figure stat-title stat-value text-accent w-fit hover:bg-white dropdown-open' %>
<% 'stats stat stat-figure stat-title stat-value text-accent w-fit hover:bg-white dropdown-open border-base-content/30 before:border-base-content/30' %>
+5 -1
View File
@@ -2,7 +2,11 @@
<% if @pagy.pages > 1 %>
<div class="flex my-6 justify-center md:justify-between">
<div class="hidden md:block text-sm">
<%= @pagy.from %>-<%= local_assigns.fetch(:to, @pagy.to) %> of <%= local_assigns.fetch(:count, @pagy.count) %> <%= local_assigns[:items_name] || 'items' %>
<% if @pagy.count.nil? %>
<%= t("pagination.#{local_assigns.fetch(:items_name, 'items')}.range_without_total", from: local_assigns.fetch(:from, @pagy.from), to: local_assigns.fetch(:to, @pagy.to)) %>
<% else %>
<%= t("pagination.#{local_assigns.fetch(:items_name, 'items')}.range_with_total", from: local_assigns.fetch(:from, @pagy.from), to: local_assigns.fetch(:to, @pagy.to), count: local_assigns.fetch(:count, @pagy.count)) %>
<% end %>
<%= local_assigns[:left_additional_html] %>
</div>
<div class="flex items-center space-x-1.5">
+5
View File
@@ -0,0 +1,5 @@
<% if configs = account.account_configs.find_by(key: AccountConfig::POLICY_LINKS_KEY) %>
<div class="max-w-md mx-auto flex flex-wrap gap-1 justify-center text-sm text-base-content/60 mt-2">
<%= auto_link(MarkdownToHtml.call(configs.value)) %>
</div>
<% end %>
+3 -3
View File
@@ -23,12 +23,12 @@
</div>
<% if Docuseal.multitenant? || Accounts.can_send_emails?(@submitter.account) %>
<toggle-submit class="block">
<%= button_to button_title(title: t('send_copy_to_email'), disabled_with: t('sending'), icon: svg_icon('mail_forward', class: 'w-6 h-6')), send_submission_email_index_path, params: { submitter_slug: @submitter.slug }, class: 'base-button w-full' %>
<%= button_to button_title(title: t('send_copy_to_email'), disabled_with: t('sending'), icon: svg_icon('mail_forward', class: 'w-6 h-6')), send_submission_email_index_path, params: { template_slug: @template.slug, email: params[:email] }, class: 'base-button w-full' %>
</toggle-submit>
<% end %>
<% if Templates.filter_undefined_submitters(@template).size == 1 && %w[api embed].exclude?(@submitter.submission.source) && @submitter.account.account_configs.find_or_initialize_by(key: AccountConfig::ALLOW_TO_RESUBMIT).value != false %>
<% if Templates.filter_undefined_submitters(@template).size == 1 && %w[api embed].exclude?(@submitter.submission.source) && @submitter.account.account_configs.find_or_initialize_by(key: AccountConfig::ALLOW_TO_RESUBMIT).value != false && @template.shared_link? %>
<toggle-submit class="block">
<%= button_to button_title(title: t('resubmit'), disabled_with: t('resubmit'), icon: svg_icon('reload', class: 'w-6 h-6')), start_form_path(@template.slug), params: { submitter: { email: params[:email] }, resubmit: @submitter.slug }, method: :put, class: 'white-button w-full' %>
<%= button_to button_title(title: t('resubmit'), disabled_with: t('resubmit'), icon: svg_icon('reload', class: 'w-6 h-6')), start_form_path(@template.slug), params: { submitter: { email: params[:email] }, resubmit: true }, method: :put, class: 'white-button w-full' %>
</toggle-submit>
<% end %>
</div>
+28
View File
@@ -0,0 +1,28 @@
<% content_for(:html_title, "#{@template.name} | DocuSeal") %>
<% content_for(:html_description, t('share_link_is_currently_disabled')) %>
<div class="max-w-md mx-auto px-2 mt-12 mb-4">
<div class="space-y-6 mx-auto">
<div class="space-y-6">
<div class="text-center w-full space-y-6">
<%= render 'banner' %>
<p class="text-xl font-semibold text-center">
<%= t('share_link_is_currently_disabled') %>
</p>
</div>
<div class="flex items-center bg-base-200 rounded-xl p-4 mb-4">
<div class="flex items-center">
<div class="mr-3">
<%= svg_icon('writing_sign', class: 'w-10 h-10') %>
</div>
<div>
<p class="text-lg font-bold mb-1"><%= @template.name %></p>
<% if @template.archived_at? %>
<p dir="auto" class="text-sm"><%= t('form_has_been_deleted_by_html', name: @template.account.name) %></p>
<% end %>
</div>
</div>
</div>
</div>
</div>
</div>
<%= render 'shared/attribution', link_path: '/start', account: @template.account %>
+1
View File
@@ -42,3 +42,4 @@
</div>
</div>
<%= render 'shared/attribution', link_path: '/start', account: @template.account %>
<%= render 'start_form/policy', account: @template.account %>
+5 -4
View File
@@ -5,12 +5,13 @@
<% font_type = field.dig('preferences', 'font_type') %>
<field-value dir="auto" class="flex absolute text-[1.6vw] lg:text-base <%= 'font-mono' if font == 'Courier' %> <%= 'font-serif' if font == 'Times' %> <%= 'font-bold' if font_type == 'bold' || font_type == 'bold_italic' %> <%= 'italic' if font_type == 'italic' || font_type == 'bold_italic' %> <%= align == 'right' ? 'text-right' : (align == 'center' ? 'text-center' : '') %>" style="<%= "color: #{color}; " if color.present? %>width: <%= area['w'] * 100 %>%; height: <%= area['h'] * 100 %>%; left: <%= area['x'] * 100 %>%; top: <%= area['y'] * 100 %>%; <%= "font-size: clamp(4pt, 1.6vw, #{field['preferences']['font_size'].to_i * 1.23}pt); line-height: `clamp(6pt, 2.0vw, #{(field['preferences']['font_size'].to_i * 1.23) + 3}pt)`" if field.dig('preferences', 'font_size') %>">
<% if field['type'] == 'signature' %>
<div class="flex flex-col justify-between h-full overflow-hidden">
<div class="flex-grow flex overflow-hidden" style="min-height: 50%">
<% is_narrow = area['h']&.positive? && (area['w'].to_f / area['h']) > 6 %>
<div class="flex justify-between w-full h-full gap-1 <%= is_narrow && (local_assigns[:with_signature_id] || field.dig('preferences', 'reason_field_uuid').present?) ? 'flex-row' : 'flex-col' %>">
<div class="flex overflow-hidden <%= is_narrow && (local_assigns[:with_signature_id] || field.dig('preferences', 'reason_field_uuid').present?) ? 'w-1/2' : 'flex-grow' %>" style="min-height: 50%">
<img class="object-contain mx-auto" src="<%= attachments_index[value].url %>">
</div>
<% if local_assigns[:with_signature_id] && attachment = attachments_index[value] %>
<div class="w-full mt-1 text-[1vw] lg:text-[0.55rem] lg:leading-[0.65rem]">
<% if (local_assigns[:with_signature_id] || field.dig('preferences', 'reason_field_uuid').present?) && attachment = attachments_index[value] %>
<div class="text-[1vw] lg:text-[0.55rem] lg:leading-[0.65rem] <%= is_narrow ? 'w-1/2' : 'w-full' %>">
<div class="truncate uppercase">
ID: <%= attachment.uuid %>
</div>
@@ -21,7 +21,7 @@
<%= render 'submissions_filters/filter_button', filter_params: %>
</div>
</div>
<% if @pagy.count > 0 %>
<% if @pagy.count.nil? || @pagy.count > 0 %>
<div class="space-y-4">
<%= render partial: 'templates/submission', collection: @submissions, locals: { with_template: true, archived: true } %>
</div>
@@ -1,5 +1,5 @@
<% filter_params = params.permit(Submissions::Filter::ALLOWED_PARAMS).compact_blank %>
<% is_show_tabs = @pagy.count >= 5 || params[:status].present? || filter_params.present? %>
<% is_show_tabs = (@pagy.count.nil? || @pagy.count >= 5) || params[:status].present? || filter_params.present? %>
<% if Docuseal.demo? %><%= render 'shared/demo_alert' %><% end %>
<div class="flex justify-between items-center w-full mb-4">
<div class="flex items-center flex-grow min-w-0">
@@ -61,12 +61,12 @@
</div>
</div>
<% end %>
<% if @pagy.count > 0 %>
<% if @pagy.count.nil? || @pagy.count > 0 %>
<div class="space-y-4">
<%= render partial: 'templates/submission', collection: @submissions, locals: { with_template: true } %>
</div>
<% end %>
<% if params[:q].blank? && params[:status].blank? && filter_params.blank? && @pagy.count < 5 %>
<% if params[:q].blank? && params[:status].blank? && filter_params.blank? && @pagy.count.present? && @pagy.count < 5 %>
<%= render 'templates/dropzone' %>
<% end %>
<% if @submissions.present? || (params[:q].blank? && filter_params.blank?) %>
@@ -28,6 +28,11 @@
<%= f.hidden_field :submission_slug, value: @submission.slug %>
<%= f.label :email, t('email'), class: 'label' %>
<%= f.email_field :email, value: current_user&.email || params[:email], required: true, class: 'base-input', placeholder: t('send_copy_to_email') %>
<% if params[:status] == 'error' %>
<span class="label-text-alt text-red-400 mt-1">
<%= t('please_enter_your_email_address_associated_with_the_completed_submission') %>
</span>
<% end %>
</div>
<toggle-submit dir="auto" class="form-control">
<%= f.button button_title(title: t('send_copy_to_email'), disabled_with: t('starting')), class: 'base-button' %>
+2 -2
View File
@@ -43,10 +43,10 @@
<% end %>
</div>
<% undefined_submitters = Templates.filter_undefined_submitters(@submitter.submission.template) %>
<% if undefined_submitters.size == 1 && undefined_submitters.first['uuid'] == @submitter.uuid && %w[api embed].exclude?(@submitter.submission.source) && @submitter.account.account_configs.find_or_initialize_by(key: AccountConfig::ALLOW_TO_RESUBMIT).value != false && !@submitter.template.archived_at? %>
<% if undefined_submitters.size == 1 && undefined_submitters.first['uuid'] == @submitter.uuid && %w[api embed].exclude?(@submitter.submission.source) && @submitter.account.account_configs.find_or_initialize_by(key: AccountConfig::ALLOW_TO_RESUBMIT).value != false %>
<div class="divider uppercase"><%= t('or') %></div>
<toggle-submit class="block">
<%= button_to button_title(title: t('resubmit'), disabled_with: t('resubmit'), icon: svg_icon('reload', class: 'w-6 h-6')), start_form_path(@submitter.submission.template.slug), params: { submitter: { email: @submitter.email, phone: @submitter.phone, name: @submitter.name }, resubmit: @submitter.slug }, method: :put, class: 'white-button w-full' %>
<%= button_to button_title(title: t('resubmit'), disabled_with: t('resubmit'), icon: svg_icon('reload', class: 'w-6 h-6')), resubmit_form_path, params: { resubmit: @submitter.slug }, method: :put, class: 'white-button w-full' %>
</toggle-submit>
<% end %>
</div>
+2 -2
View File
@@ -39,13 +39,13 @@
<% end %>
</div>
</div>
<% if @pagy.count > 0 %>
<% if @pagy.count.nil? || @pagy.count > 0 %>
<div class="grid gap-4 md:grid-cols-3">
<%= render partial: 'templates/template', collection: @templates %>
</div>
<% templates_order_select_html = capture do %>
<% if params[:q].blank? && @pagy.pages > 1 %>
<%= render('shared/templates_order_select', with_recently_used: @pagy.count < 10_000) %>
<%= render('shared/templates_order_select', with_recently_used: @pagy.count.present? && @pagy.count < 10_000) %>
<% end %>
<% end %>
<%= render 'shared/pagination', pagy: @pagy, items_name: 'templates', right_additional_html: templates_order_select_html %>
+2 -2
View File
@@ -2,8 +2,8 @@
<input type="hidden" name="form_id" value="<%= form_id %>">
<button type="submit" class="hidden"></button>
<file-dropzone data-submit-on-upload="true" class="w-full">
<label for="file_dropzone_input" class="w-full block h-52 relative hover:bg-base-200/30 rounded-xl border border-2 border-base-300 border-dashed">
<div class="absolute top-0 right-0 left-0 bottom-0 flex items-center justify-center p-2">
<label for="file_dropzone_input" class="w-full block h-52 relative hover:bg-base-200/30 rounded-xl border-2 border-base-300 border-dashed" data-target="file-dropzone.area">
<div class="absolute top-0 right-0 left-0 bottom-0 flex items-center justify-center p-2 pointer-events-none">
<div class="flex flex-col items-center text-center">
<span data-target="file-dropzone.icon" class="flex flex-col items-center">
<span>
+1 -1
View File
@@ -34,7 +34,7 @@
<% submitter = submitters.first %>
<div class="flex items-center space-x-4">
<span class="flex flex-col md:flex-row md:items-center gap-3">
<% if submission.expired? %>
<% if submission.expired? && !submitter.completed_at? && !submitter.declined_at? %>
<div class="tooltip flex" data-tip="<%= l(submission.expire_at.in_time_zone(current_account.timezone), format: :short, locale: current_account.locale) %>">
<span class="badge badge-error md:w-32 bg-opacity-50 badge-lg uppercase text-sm font-semibold">
<%= t('expired') %>
+6 -1
View File
@@ -49,7 +49,12 @@
<% end %>
</div>
<% end %>
<%= render 'shared/clipboard_copy', text: start_form_url(slug: @template.slug), id: 'share_link_clipboard', class: 'absolute md:relative bottom-0 right-0 btn btn-xs md:btn-sm whitespace-nowrap btn-neutral text-white mt-1 px-2', icon_class: 'w-4 h-4 md:w-6 md:h-6 text-white', copy_title: t('link'), copied_title: t('copied'), copy_title_md: t('link'), copied_title_md: t('copied') %>
<%= link_to template_share_link_path(template), class: 'absolute md:relative bottom-0 right-0 btn btn-xs md:btn-sm whitespace-nowrap btn-neutral text-white mt-1 px-2', data: { turbo_frame: :modal } do %>
<span class="flex items-center justify-center space-x-2">
<%= svg_icon('link', class: 'w-4 h-4 md:w-6 md:h-6 text-white') %>
<span><%= t('link') %></span>
</span>
<% end %>
</div>
<% end %>
</div>
+17 -11
View File
@@ -1,7 +1,7 @@
<%= render 'title', template: @template %>
<% filter_params = params.permit(Submissions::Filter::ALLOWED_PARAMS).compact_blank %>
<% is_show_tabs = @pagy.pages > 1 || params[:q].present? || params[:status].present? || filter_params.present? %>
<% if !@pagy.count.zero? || params[:q].present? || params[:status].present? || filter_params.present? %>
<% if @pagy.count.nil? || !@pagy.count.zero? || params[:q].present? || params[:status].present? || filter_params.present? %>
<div class="<%= is_show_tabs ? 'mb-4' : 'mb-6' %>">
<div class="flex justify-between items-center md:items-end">
<div>
@@ -35,27 +35,33 @@
<%= svg_icon('list', class: 'w-5 h-5') %>
<span class="font-normal"><%= t('all') %></span>
</div>
<div class="badge badge-neutral badge-outline font-medium">
<%= params[:status].blank? && filter_params.blank? ? @pagy.count : @base_submissions.unscope(:group, :order).select(:id).distinct.count %>
</div>
<% unless can?(:manage, :countless) %>
<div class="badge badge-neutral badge-outline font-medium">
<%= params[:status].blank? && filter_params.blank? ? @pagy.count : @base_submissions.unscope(:group, :order).select(:id).distinct.count %>
</div>
<% end %>
</a>
<a href="<%= url_for(params.to_unsafe_h.merge(status: :pending)) %>" class="<%= params[:status] == 'pending' ? 'border-neutral-700' : 'border-neutral-300' %> flex h-10 px-2 py-1 text-lg items-center justify-between border text-center text-neutral font-semibold rounded-xl w-full md:w-48 hover:border-neutral-700">
<div class="flex items-center space-x-1">
<%= svg_icon('clock', class: 'w-5 h-5') %>
<span class="font-normal"><%= t('pending') %></span>
</div>
<div class="badge badge-neutral badge-outline font-medium">
<%= params[:status] == 'pending' && filter_params.blank? ? @pagy.count : @base_submissions.pending.unscope(:group, :order).select(:id).distinct.count %>
</div>
<% unless can?(:manage, :countless) %>
<div class="badge badge-neutral badge-outline font-medium">
<%= params[:status] == 'pending' && filter_params.blank? ? @pagy.count : @base_submissions.pending.unscope(:group, :order).select(:id).distinct.count %>
</div>
<% end %>
</a>
<a href="<%= url_for(params.to_unsafe_h.merge(status: :completed)) %>" class="<%= params[:status] == 'completed' ? 'border-neutral-700' : 'border-neutral-300' %> flex h-10 px-2 py-1 text-lg items-center justify-between border text-center text-neutral font-semibold rounded-xl w-full md:w-48 hover:border-neutral-700">
<div class="flex items-center space-x-1">
<%= svg_icon('circle_check', class: 'w-5 h-5') %>
<span class="font-normal"><%= t('completed') %></span>
</div>
<div class="badge badge-neutral badge-outline font-medium">
<%= params[:status] == 'completed' && filter_params.blank? ? @pagy.count : @base_submissions.completed.unscope(:group, :order).select(:id).distinct.count %>
</div>
<% unless can?(:manage, :countless) %>
<div class="badge badge-neutral badge-outline font-medium">
<%= params[:status] == 'completed' && filter_params.blank? ? @pagy.count : @base_submissions.completed.unscope(:group, :order).select(:id).distinct.count %>
</div>
<% end %>
</a>
</div>
<div class="flex items-end flex-col md:flex-row gap-2 w-full md:w-fit">
@@ -89,7 +95,7 @@
<% end %>
<% end %>
<% if @template.submitters.size == 1 %>
<%= link_to start_form_url(slug: @template.slug), id: 'sign_yourself_button', class: 'white-button mt-6', target: '_blank', rel: 'noopener' do %>
<%= button_to start_form_path(@template.slug), params: { selfsign: true }, method: :put, class: 'white-button w-full', form: { style: 'display: inline', target: '_blank', data: { turbo: false } } do %>
<%= svg_icon('writing', class: 'w-6 h-6') %>
<span class="mr-1"><%= t('sign_it_yourself') %></span>
<% end %>
+1 -1
View File
@@ -12,7 +12,7 @@
<%= render 'shared/search_input', placeholder: "#{t('search')}..." %>
<% end %>
</div>
<% if @pagy.count > 0 %>
<% if @pagy.count.nil? || @pagy.count > 0 %>
<div class="grid gap-4 md:grid-cols-3">
<%= render partial: 'templates/template', collection: @templates %>
</div>
@@ -29,7 +29,7 @@
</div>
<% end %>
</div>
<% if @pagy.count > 0 %>
<% if @pagy.count.nil? || @pagy.count > 0 %>
<div class="space-y-4">
<%= render partial: 'templates/submission', collection: @submissions, locals: { template: @template, archived: true } %>
</div>
+5 -5
View File
@@ -12,7 +12,7 @@
<%= render 'templates/dashboard_dropzone', style: 'height: 114px' %>
<% end %>
<div class="flex items-center flex-grow min-w-0">
<% if has_archived || @pagy.count > 0 || @template_folders.present? %>
<% if has_archived || @pagy.count.nil? || @pagy.count > 0 || @template_folders.present? %>
<div class="mr-2">
<%= render 'dashboard/toggle_view', selected: 'templates' %>
</div>
@@ -45,7 +45,7 @@
<% end %>
<% templates_order_select_html = capture do %>
<% if params[:q].blank? && @pagy.pages > 1 %>
<%= render('shared/templates_order_select', with_recently_used: @pagy.count < 10_000) %>
<%= render('shared/templates_order_select', with_recently_used: @pagy.count.present? && @pagy.count < 10_000) %>
<% end %>
<% end %>
<% if @template_folders.present? %>
@@ -56,7 +56,7 @@
<% if @templates.present? %>
<div class="grid gap-4 md:grid-cols-3">
<%= render partial: 'templates/template', collection: @templates %>
<% if show_dropzone && current_user.created_at > 2.weeks.ago || params[:tour] == 'true' %>
<% if show_dropzone && (current_user.created_at > 2.weeks.ago || params[:tour] == 'true') && current_user == true_user %>
<% user_config = current_user.user_configs.find_or_initialize_by(key: UserConfig::SHOW_APP_TOUR) %>
<% if user_config.new_record? || user_config.value || params[:tour] == 'true' %>
<div class="hidden md:block">
@@ -84,9 +84,9 @@
<% if show_dropzone %>
<%= render 'templates/dropzone' %>
<% end %>
<% if @templates.present? || params[:q].blank? %>
<% if @templates.present? || @template_folders.present? || params[:q].blank? %>
<% if @pagy.pages > 1 %>
<%= render 'shared/pagination', pagy: @pagy, items_name: 'templates', left_additional_html: view_archived_html, right_additional_html: templates_order_select_html %>
<%= render 'shared/pagination', pagy: @pagy, items_name: @templates.present? ? 'templates' : 'template_folders', left_additional_html: view_archived_html, right_additional_html: templates_order_select_html %>
<% else %>
<div class="mt-2">
<%= view_archived_html %>
+12 -2
View File
@@ -380,8 +380,18 @@
<%= t('embedding_url') %>
</label>
<div class="flex gap-2 mb-4 mt-2">
<input id="embedding_url" type="text" value="<%= start_form_url(slug: @template.slug) %>" class="base-input w-full" autocomplete="off" readonly>
<%= render 'shared/clipboard_copy', icon: 'copy', text: start_form_url(slug: @template.slug), class: 'base-button', icon_class: 'w-6 h-6 text-white', copy_title: t('copy'), copied_title: t('copied') %>
<%= form_for @template, url: template_share_link_path(@template), method: :post, html: { id: 'shared_link_form', autocomplete: 'off', class: 'w-full mt-1' }, data: { close_on_submit: false } do |f| %>
<div class="flex gap-2">
<input id="embedding_url" type="text" value="<%= start_form_url(slug: @template.slug) %>" class="base-input w-full" autocomplete="off" readonly>
<check-on-click data-element-id="template_shared_link">
<%= render 'shared/clipboard_copy', icon: 'copy', text: start_form_url(slug: @template.slug), class: 'base-button', icon_class: 'w-6 h-6 text-white', copy_title: t('copy'), copied_title: t('copied') %>
</check-on-click>
</div>
<div class="flex items-center justify-between gap-1 pt-3">
<span><%= t('enable_shared_link') %></span>
<%= f.check_box :shared_link, { class: 'toggle', onchange: 'this.form.requestSubmit()' }, 'true', 'false' %>
</div>
<% end %>
</div>
</div>
<%= render 'templates_code_modal/placeholder' %>
@@ -0,0 +1,16 @@
<%= render 'shared/turbo_modal_large', title: t('share_link') do %>
<div class="mt-2 mb-4 px-5">
<%= form_for @template, url: template_share_link_path(@template), method: :post, html: { id: 'shared_link_form', autocomplete: 'off', class: 'mt-3' }, data: { close_on_submit: false } do |f| %>
<div class="flex items-center justify-between gap-1 px-1">
<span><%= t('enable_shared_link') %></span>
<%= f.check_box :shared_link, { disabled: !can?(:update, @template), class: 'toggle', onchange: 'this.form.requestSubmit()' }, 'true', 'false' %>
</div>
<div class="flex gap-2 mt-3">
<input id="embedding_url" type="text" value="<%= start_form_url(slug: @template.slug) %>" class="base-input w-full" autocomplete="off" readonly>
<check-on-click data-element-id="template_shared_link">
<%= render 'shared/clipboard_copy', icon: 'copy', text: start_form_url(slug: @template.slug), class: 'base-button', icon_class: 'w-6 h-6 text-white', copy_title: t('copy'), copied_title: t('copied') %>
</check-on-click>
</div>
<% end %>
</div>
<% end %>
+2 -2
View File
@@ -55,8 +55,8 @@
<%= user.email %>
</td>
<td>
<span class="badge badge-info badge-outline">
<%= user.role %>
<span class="badge badge-info badge-outline whitespace-nowrap">
<%= t(user.role) %>
</span>
</td>
<td>
+2
View File
@@ -1,5 +1,7 @@
# frozen_string_literal: true
require 'pagy/extras/countless'
Pagy::DEFAULT[:limit] = 10
Pagy::DEFAULT.freeze
+205 -12
View File
@@ -22,6 +22,9 @@ en: &en
hi_there: Hi there
thanks: Thanks
private: Private
stripe_integration: Stripe Integration
stripe_account_has_been_connected: Stripe account has been connected.
re_connect_stripe: Re-connect Stripe
bcc_recipients: BCC recipients
resend_pending: Re-send pending
always_enforce_signing_order: Always enforce the signing order
@@ -46,7 +49,7 @@ en: &en
team_member_permissions: Team member permissions
entire_team: Entire team
admin_only: Admin only
accessiable_by: Accessiable by
accessiable_by: Accessible by
team_access: Team access
document_download_filename_format: Document download filename format
document_name: Document Name
@@ -469,7 +472,9 @@ en: &en
upload_initials: Upload Initials
draw: Draw
upload_signature: Upload Signature
integration: Integration
admin: Admin
tenant_admin: Tenant Admin
editor: Editor
viewer: Viewer
member: Member
@@ -653,7 +658,7 @@ en: &en
completed_at: Completed at
edit_recipient: Edit Recipient
update_recipient: Update Recipient
use_international_format_1xxx_: 'Use internatioanl format: +1xxx...'
use_international_format_1xxx_: 'Use international format: +1xxx...'
submitter_cannot_be_updated: Submitter cannot be updated.
at_least_one_field_must_be_filled: At least one field must be filled.
archived_users: Archived Users
@@ -738,11 +743,17 @@ en: &en
one_month: 1 month
two_months: 2 months
three_months: 3 months
eu_data_residency: EU data residency
please_enter_your_email_address_associated_with_the_completed_submission: Please enter your email address associated with the completed submission.
esignature_disclosure: eSignature Disclosure
share_link: Share link
enable_shared_link: Enable shared link
share_link_is_currently_disabled: Share link is currently disabled
submission_sources:
api: API
bulk: Bulk Send
embed: Embedding
invie: Invite
invite: Invite
link: Link
submission_event_names:
send_email_to_html: '<b>Email sent</b> to %{submitter_name}'
@@ -822,8 +833,27 @@ en: &en
scopes:
write: Update your data
read: Read your data
pagination:
submissions:
range_with_total: "%{from}-%{to} of %{count} submissions"
range_without_total: "%{from}-%{to} submissions"
templates:
range_with_total: "%{from}-%{to} of %{count} templates"
range_without_total: "%{from}-%{to} templates"
template_folders:
range_with_total: "%{from}-%{to} of %{count} folders"
range_without_total: "%{from}-%{to} folders"
users:
range_with_total: "%{from}-%{to} of %{count} users"
range_without_total: "%{from}-%{to} users"
items:
range_with_total: "%{from}-%{to} of %{count} items"
range_without_total: "%{from}-%{to} items"
es: &es
stripe_integration: Integración con Stripe
stripe_account_has_been_connected: La cuenta de Stripe ha sido conectada.
re_connect_stripe: Volver a conectar Stripe
private: Privado
resend_pending: Reenviar pendiente
ensure_unique_recipients: Asegurar destinatarios únicos
@@ -845,7 +875,7 @@ es: &es
select_user: Seleccionar usuario
team_member_permissions: Permisos de miembros del equipo
entire_team: Todo el equipo
admin_only: Solo administrador
admin_only: Solo admin
accessiable_by: Accesible por
team_access: Acceso del equipo
remove_filter: Eliminar filtro
@@ -1274,7 +1304,9 @@ es: &es
upload_initials: Subir iniciales
draw: Dibujar
upload_signature: Subir firma
integration: Integración
admin: Administrador
tenant_admin: Tenant Administrador
editor: Editor
viewer: Visor
member: Miembro
@@ -1543,6 +1575,12 @@ es: &es
one_month: 1 mes
two_months: 2 meses
three_months: 3 meses
eu_data_residency: Datos alojados UE
please_enter_your_email_address_associated_with_the_completed_submission: Por favor, introduce tu dirección de correo electrónico asociada con el envío completado.
esignature_disclosure: Uso de firma electrónica
share_link: Enlace para compartir
enable_shared_link: Habilitar enlace compartido
share_link_is_currently_disabled: El enlace compartido está deshabilitado actualmente
submission_sources:
api: API
bulk: Envío masivo
@@ -1627,8 +1665,27 @@ es: &es
scopes:
write: Actualizar tus datos
read: Leer tus datos
pagination:
submissions:
range_with_total: "%{from}-%{to} de %{count} envíos"
range_without_total: "%{from}-%{to} envíos"
templates:
range_with_total: "%{from}-%{to} de %{count} plantillas"
range_without_total: "%{from}-%{to} plantillas"
template_folders:
range_with_total: "%{from}-%{to} de %{count} carpetas"
range_without_total: "%{from}-%{to} carpetas"
users:
range_with_total: "%{from}-%{to} de %{count} usuarios"
range_without_total: "%{from}-%{to} usuarios"
items:
range_with_total: "%{from}-%{to} de %{count} elementos"
range_without_total: "%{from}-%{to} elementos"
it: &it
stripe_integration: Integrazione Stripe
stripe_account_has_been_connected: L'account Stripe è stato collegato.
re_connect_stripe: Ricollega Stripe
private: Privato
resend_pending: Reinvia in sospeso
ensure_unique_recipients: Assicurarsi destinatari unici
@@ -1649,7 +1706,7 @@ it: &it
select_user: Seleziona utente
team_member_permissions: Permessi membri del team
entire_team: Intero team
admin_only: Solo amministratore
admin_only: Solo admin
accessiable_by: Accessibile da
team_access: Accesso al team
remove_filter: Rimuovi filtro
@@ -2078,6 +2135,7 @@ it: &it
upload_initials: Carica iniziali
draw: Disegna
upload_signature: Carica firma
integration: Integrazione
admin: Amministratore
editor: Editor
viewer: Visualizzatore
@@ -2347,6 +2405,12 @@ it: &it
one_month: 1 mese
two_months: 2 mesi
three_months: 3 mesi
eu_data_residency: "Dati nell'UE"
please_enter_your_email_address_associated_with_the_completed_submission: "Inserisci il tuo indirizzo email associato all'invio completato."
esignature_disclosure: Uso della firma elettronica
share_link: Link di condivisione
enable_shared_link: Abilita link condiviso
share_link_is_currently_disabled: Il link condiviso è attualmente disabilitato
submission_sources:
api: API
bulk: Invio massivo
@@ -2431,8 +2495,27 @@ it: &it
scopes:
write: Aggiorna i tuoi dati
read: Leggi i tuoi dati
pagination:
submissions:
range_with_total: "%{from}-%{to} di %{count} invii"
range_without_total: "%{from}-%{to} invii"
templates:
range_with_total: "%{from}-%{to} di %{count} modelli"
range_without_total: "%{from}-%{to} modelli"
template_folders:
range_with_total: "%{from}-%{to} di %{count} cartelle"
range_without_total: "%{from}-%{to} cartelle"
users:
range_with_total: "%{from}-%{to} di %{count} utenti"
range_without_total: "%{from}-%{to} utenti"
items:
range_with_total: "%{from}-%{to} di %{count} elementi"
range_without_total: "%{from}-%{to} elementi"
fr: &fr
stripe_integration: Intégration Stripe
stripe_account_has_been_connected: Le compte Stripe a été connecté.
re_connect_stripe: Reconnecter Stripe
private: Privé
resend_pending: Renvoyer en attente
ensure_unique_recipients: Assurer l'unicité des destinataires
@@ -2454,7 +2537,7 @@ fr: &fr
select_user: Sélectionner un utilisateur
team_member_permissions: Permissions des membres de l'équipe
entire_team: Équipe entière
admin_only: Administrateur uniquement
admin_only: Admin seul
accessiable_by: Accessible par
team_access: Accès à l'équipe
remove_filter: Supprimer le filtre
@@ -2884,7 +2967,9 @@ fr: &fr
upload_initials: Télécharger les initiales
draw: Dessiner
upload_signature: Télécharger la signature
integration: Intégration
admin: Administrateur
tenant_admin: Tenant Administrateur
editor: Éditeur
viewer: Lecteur
member: Membre
@@ -3153,6 +3238,12 @@ fr: &fr
one_month: 1 mois
two_months: 2 mois
three_months: 3 mois
eu_data_residency: "Données dans l'UE"
please_enter_your_email_address_associated_with_the_completed_submission: "Veuillez saisir l'adresse e-mail associée à l'envoi complété."
esignature_disclosure: Divulgation de Signature Électronique
share_link: Lien de partage
enable_shared_link: Activer le lien de partage
share_link_is_currently_disabled: Le lien de partage est actuellement désactivé
submission_sources:
api: API
bulk: Envoi en masse
@@ -3237,8 +3328,27 @@ fr: &fr
scopes:
write: Mettre à jour vos données
read: Lire vos données
pagination:
submissions:
range_with_total: "%{from} à %{to} sur %{count} soumissions"
range_without_total: "%{from} à %{to} soumissions"
templates:
range_with_total: "%{from} à %{to} sur %{count} modèles"
range_without_total: "%{from} à %{to} modèles"
template_folders:
range_with_total: "%{from} à %{to} sur %{count} dossiers"
range_without_total: "%{from} à %{to} dossiers"
users:
range_with_total: "%{from} à %{to} sur %{count} utilisateurs"
range_without_total: "%{from} à %{to} utilisateurs"
items:
range_with_total: "%{from} à %{to} sur %{count} éléments"
range_without_total: "%{from} à %{to} éléments"
pt: &pt
stripe_integration: Integração com Stripe
stripe_account_has_been_connected: Conta Stripe foi conectada.
re_connect_stripe: Reconectar Stripe
private: Privado
resend_pending: Re-enviar pendente
ensure_unique_recipients: Garantir destinatários únicos
@@ -3260,7 +3370,7 @@ pt: &pt
select_user: Selecionar usuário
team_member_permissions: Permissões de membro da equipe
entire_team: Toda a equipe
admin_only: Somente administrador
admin_only: Somente admin
accessiable_by: Acessível por
team_access: Acesso à equipe
remove_filter: Remover filtro
@@ -3689,7 +3799,9 @@ pt: &pt
upload_initials: Enviar iniciais
draw: Desenhar
upload_signature: Enviar assinatura
integration: Integração
admin: Administrador
tenant_admin: Tenant Administrador
editor: Editor
viewer: Visualizador
member: Membro
@@ -3958,6 +4070,12 @@ pt: &pt
one_month: 1 mês
two_months: 2 meses
three_months: 3 meses
eu_data_residency: Dados na UE
please_enter_your_email_address_associated_with_the_completed_submission: Por favor, insira seu e-mail associado ao envio concluído.
esignature_disclosure: Uso de assinatura eletrônica
share_link: Link de compartilhamento
enable_shared_link: Ativar link compartilhado
share_link_is_currently_disabled: O link compartilhado está desativado no momento
submission_sources:
api: API
bulk: Envio em massa
@@ -4002,8 +4120,8 @@ pt: &pt
start: Iniciar
previous: Anterior
next: Próximo
template_and_submissions: 'Modelos e Envios'
template_and_submissions_description: "Você pode selecionar a visualização que melhor se adapta ao seu fluxo de trabalho. Escolha a visualização 'Modelos' para criar modelos reutilizáveis de documentos ou 'Envios' para assinar documentos individuais ou verificar o status de cada solicitação de assinatura."
template_and_submissions: 'Modelos e Submissões'
template_and_submissions_description: "Você pode selecionar a visualização que melhor se adapta ao seu fluxo de trabalho. Escolha a visualização 'Modelos' para criar modelos reutilizáveis de documentos ou 'Submissões' para assinar documentos individuais ou verificar o status de cada solicitação de assinatura."
upload_a_pdf_file: 'Enviar um arquivo PDF'
upload_a_pdf_file_description: 'Envie um documento PDF para criar um modelo de formulário de assinatura.'
select_a_signer_party: 'Selecionar parte assinante'
@@ -4043,8 +4161,27 @@ pt: &pt
scopes:
write: Atualizar seus dados
read: Ler seus dados
pagination:
submissions:
range_with_total: "%{from}-%{to} de %{count} submissões"
range_without_total: "%{from}-%{to} submissões"
templates:
range_with_total: "%{from}-%{to} de %{count} modelos"
range_without_total: "%{from}-%{to} modelos"
template_folders:
range_with_total: "%{from}-%{to} de %{count} pastas"
range_without_total: "%{from}-%{to} pastas"
users:
range_with_total: "%{from}-%{to} de %{count} usuários"
range_without_total: "%{from}-%{to} usuários"
items:
range_with_total: "%{from}-%{to} de %{count} itens"
range_without_total: "%{from}-%{to} itens"
de: &de
stripe_integration: Stripe-Integration
stripe_account_has_been_connected: Stripe-Konto wurde verbunden.
re_connect_stripe: Stripe erneut verbinden
private: Privat
resend_pending: Ausstehende erneut senden
ensure_unique_recipients: Stellen Sie einzigartige Empfänger sicher
@@ -4066,7 +4203,7 @@ de: &de
select_user: Benutzer auswählen
team_member_permissions: Berechtigungen für Teammitglieder
entire_team: Gesamtes Team
admin_only: Nur Administratoren
admin_only: Nur Admins
accessiable_by: Zugänglich für
team_access: Teamzugang
remove_filter: Filter entfernen
@@ -4150,9 +4287,9 @@ de: &de
sending: Senden
resubmit: Erneut einreichen
or: oder
download_documents: Dokumente herunterladen
download_documents: Dokumente downloaden
downloading: Lädt herunter
download: Laden
download: Download
decline: Ablehnen
declined: Abgelehnt
decline_reason: Ablehnungsgrund
@@ -4495,7 +4632,9 @@ de: &de
upload_initials: Initialen hochladen
draw: Zeichnen
upload_signature: Unterschrift hochladen
integration: Integration
admin: Administrator
tenant_admin: Tenant Administrator
editor: Redakteur
viewer: Betrachter
member: Mitglied
@@ -4764,6 +4903,12 @@ de: &de
one_month: 1 Monat
two_months: 2 Monate
three_months: 3 Monate
eu_data_residency: EU-Datenspeicher
please_enter_your_email_address_associated_with_the_completed_submission: Bitte gib deine E-Mail-Adresse ein, die mit der abgeschlossenen Übermittlung verknüpft ist.
esignature_disclosure: Nutzung der E-Signatur
share_link: Freigabelink
enable_shared_link: 'Freigabelink aktivieren'
share_link_is_currently_disabled: 'Freigabelink ist derzeit deaktiviert'
submission_sources:
api: API
bulk: Massenversand
@@ -4848,6 +4993,22 @@ de: &de
scopes:
write: Aktualisiere deine Daten
read: Lese deine Daten
pagination:
submissions:
range_with_total: "%{from}-%{to} von %{count} Einreichungen"
range_without_total: "%{from}-%{to} Einreichungen"
templates:
range_with_total: "%{from}-%{to} von %{count} Vorlagen"
range_without_total: "%{from}-%{to} Vorlagen"
template_folders:
range_with_total: "%{from}-%{to} von %{count} Ordnern"
range_without_total: "%{from}-%{to} Ordner"
users:
range_with_total: "%{from}-%{to} von %{count} Benutzern"
range_without_total: "%{from}-%{to} Benutzer"
items:
range_with_total: "%{from}-%{to} von %{count} Elementen"
range_without_total: "%{from}-%{to} Elemente"
pl:
require_phone_2fa_to_open: Wymagaj uwierzytelniania telefonicznego 2FA do otwarcia
@@ -4915,6 +5076,10 @@ pl:
powered_by: 'Napędzany prze'
count_documents_signed_with_html: '<b>%{count}</b> dokumentów podpisanych za pomocą'
open_source_documents_software: 'oprogramowanie do dokumentów open source'
eu_data_residency: Dane w UE
please_enter_your_email_address_associated_with_the_completed_submission: Wprowadź adres e-mail powiązany z ukończonym zgłoszeniem.
privacy_policy: Polityka Prywatności
esignature_disclosure: Użycie e-podpisu
uk:
require_phone_2fa_to_open: Вимагати двофакторну автентифікацію через телефон для відкриття
@@ -4982,6 +5147,10 @@ uk:
powered_by: 'Працює на базі'
count_documents_signed_with_html: '<b>%{count}</b> документів підписано за допомогою'
open_source_documents_software: 'відкрите програмне забезпечення для документів'
eu_data_residency: 'Зберігання даних в ЄС'
please_enter_your_email_address_associated_with_the_completed_submission: "Введіть адресу електронної пошти, пов'язану із завершеним поданням."
privacy_policy: Політика конфіденційності
esignature_disclosure: Використання e-підпису
cs:
require_phone_2fa_to_open: Vyžadovat otevření pomocí telefonního 2FA
@@ -5049,6 +5218,10 @@ cs:
powered_by: 'Poháněno'
count_documents_signed_with_html: '<b>%{count}</b> dokumentů podepsáno pomocí'
open_source_documents_software: 'open source software pro dokumenty'
eu_data_residency: 'Uložení dat v EU'
please_enter_your_email_address_associated_with_the_completed_submission: Zadejte e-mailovou adresu spojenou s dokončeným odesláním.
privacy_policy: Zásady Ochrany Osobních Údajů
esignature_disclosure: Použití e-podpisu
he:
require_phone_2fa_to_open: דרוש אימות דו-שלבי באמצעות טלפון לפתיחה
@@ -5116,6 +5289,10 @@ he:
powered_by: 'מופעל על ידי'
count_documents_signed_with_html: '<b>%{count}</b> מסמכים נחתמו באמצעות'
open_source_documents_software: 'תוכנה בקוד פתוח למסמכים'
eu_data_residency: 'נתונים באיחוד האירופי '
please_enter_your_email_address_associated_with_the_completed_submission: 'אנא הזן את כתובת הדוא"ל המשויכת למשלוח שהושלם.'
privacy_policy: 'מדיניות פרטיות'
esignature_disclosure: 'גילוי חתימה אלקטרונית'
nl:
require_phone_2fa_to_open: Vereis telefoon 2FA om te openen
@@ -5183,6 +5360,10 @@ nl:
powered_by: 'Aangedreven door'
count_documents_signed_with_html: '<b>%{count}</b> documenten ondertekend met'
open_source_documents_software: 'open source documenten software'
eu_data_residency: Gegevens EU
please_enter_your_email_address_associated_with_the_completed_submission: Voer het e-mailadres in dat is gekoppeld aan de voltooide indiening.
privacy_policy: Privacybeleid
esignature_disclosure: Gebruik van e-handtekening
ar:
require_phone_2fa_to_open: "تطلب فتح عبر تحقق الهاتف ذو العاملين"
@@ -5250,6 +5431,10 @@ ar:
powered_by: 'مشغل بواسطة'
count_documents_signed_with_html: '<b>%{count}</b> مستندات تم توقيعها باستخدام'
open_source_documents_software: 'برنامج مستندات مفتوح المصدر'
eu_data_residency: 'بيانات في الاتحاد الأوروبي'
please_enter_your_email_address_associated_with_the_completed_submission: 'يرجى إدخال عنوان البريد الإلكتروني المرتبط بالإرسال المكتمل.'
privacy_policy: 'سياسة الخصوصية'
esignature_disclosure: 'إفصاح التوقيع الإلكتروني'
ko:
require_phone_2fa_to_open: 휴대폰 2FA를 열 때 요구함
@@ -5317,6 +5502,10 @@ ko:
powered_by: '제공:'
count_documents_signed_with_html: '<b>%{count}</b>개의 문서가 다음을 통해 서명됨'
open_source_documents_software: '오픈소스 문서 소프트웨어'
eu_data_residency: 'EU 데이터 보관'
please_enter_your_email_address_associated_with_the_completed_submission: '완료된 제출과 연결된 이메일 주소를 입력하세요.'
privacy_policy: 개인정보 처리방침
esignature_disclosure: 전자서명 공개
ja:
require_phone_2fa_to_open: 電話による2段階認証が必要です
@@ -5384,6 +5573,10 @@ ja:
powered_by: '提供元:'
count_documents_signed_with_html: '<b>%{count}</b> 件のドキュメントが以下で署名されました'
open_source_documents_software: 'オープンソースのドキュメントソフトウェア'
eu_data_residency: 'EU データ居住'
please_enter_your_email_address_associated_with_the_completed_submission: '完了した提出に関連付けられたメールアドレスを入力してください。'
privacy_policy: プライバシーポリシー
esignature_disclosure: 電子署名に関する開示
en-US:
<<: *en
+4 -1
View File
@@ -57,7 +57,7 @@ Rails.application.routes.draw do
resources :verify_pdf_signature, only: %i[create]
resource :mfa_setup, only: %i[show new edit create destroy], controller: 'mfa_setup'
resources :account_configs, only: %i[create]
resources :account_configs, only: %i[create destroy]
resources :user_configs, only: %i[create]
resources :encrypted_user_configs, only: %i[destroy]
resources :timestamp_server, only: %i[create]
@@ -106,6 +106,7 @@ Rails.application.routes.draw do
resource :form, only: %i[show], controller: 'templates_form_preview'
resource :code_modal, only: %i[show], controller: 'templates_code_modal'
resource :preferences, only: %i[show create], controller: 'templates_preferences'
resource :share_link, only: %i[show create], controller: 'templates_share_link'
resources :recipients, only: %i[create], controller: 'templates_recipients'
resources :submissions_export, only: %i[index new]
end
@@ -131,6 +132,8 @@ Rails.application.routes.draw do
get :completed
end
resource :resubmit_form, controller: 'start_form', only: :update
resources :submit_form, only: %i[], path: '' do
get :success, on: :collection
end
@@ -0,0 +1,14 @@
# frozen_string_literal: true
class AccountAccesses < ActiveRecord::Migration[8.0]
def change
create_table :account_accesses do |t|
t.references :account, null: false, foreign_key: true, index: false
t.references :user, null: false, foreign_key: false, index: false
t.index %i[account_id user_id], unique: true
t.timestamps
end
end
end
@@ -0,0 +1,22 @@
# frozen_string_literal: true
class AddSharedLinkToTemplates < ActiveRecord::Migration[8.0]
disable_ddl_transaction
class MigrationTemplate < ActiveRecord::Base
self.table_name = 'templates'
end
def up
add_column :templates, :shared_link, :boolean, if_not_exists: true
MigrationTemplate.where(shared_link: nil).in_batches.update_all(shared_link: true)
change_column_default :templates, :shared_link, from: nil, to: false
change_column_null :templates, :shared_link, false
end
def down
remove_column :templates, :shared_link
end
end
@@ -0,0 +1,8 @@
# frozen_string_literal: true
class AddEmailEventsDateIndex < ActiveRecord::Migration[8.0]
def change
remove_index :email_events, :account_id
add_index :email_events, %i[account_id event_datetime]
end
end
+12 -2
View File
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[8.0].define(version: 2025_02_25_111255) do
ActiveRecord::Schema[8.0].define(version: 2025_05_30_080846) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -24,6 +24,14 @@ ActiveRecord::Schema[8.0].define(version: 2025_02_25_111255) do
t.index ["user_id"], name: "index_access_tokens_on_user_id"
end
create_table "account_accesses", force: :cascade do |t|
t.bigint "account_id", null: false
t.bigint "user_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["account_id", "user_id"], name: "index_account_accesses_on_account_id_and_user_id", unique: true
end
create_table "account_configs", force: :cascade do |t|
t.bigint "account_id", null: false
t.string "key", null: false
@@ -166,7 +174,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_02_25_111255) do
t.text "data", null: false
t.datetime "event_datetime", null: false
t.datetime "created_at", null: false
t.index ["account_id"], name: "index_email_events_on_account_id"
t.index ["account_id", "event_datetime"], name: "index_email_events_on_account_id_and_event_datetime"
t.index ["email"], name: "index_email_events_on_email"
t.index ["emailable_type", "emailable_id"], name: "index_email_events_on_emailable"
t.index ["message_id"], name: "index_email_events_on_message_id"
@@ -353,6 +361,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_02_25_111255) do
t.bigint "folder_id", null: false
t.string "external_id"
t.text "preferences", null: false
t.boolean "shared_link", default: false, null: false
t.index ["account_id"], name: "index_templates_on_account_id"
t.index ["author_id"], name: "index_templates_on_author_id"
t.index ["external_id"], name: "index_templates_on_external_id"
@@ -415,6 +424,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_02_25_111255) do
end
add_foreign_key "access_tokens", "users"
add_foreign_key "account_accesses", "accounts"
add_foreign_key "account_configs", "accounts"
add_foreign_key "account_linked_accounts", "accounts"
add_foreign_key "account_linked_accounts", "accounts", column: "linked_account_id"
+2238
View File
File diff suppressed because it is too large Load Diff
+2657
View File
File diff suppressed because it is too large Load Diff
+2219
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+2420
View File
File diff suppressed because it is too large Load Diff
+2277
View File
File diff suppressed because it is too large Load Diff
+2334
View File
File diff suppressed because it is too large Load Diff
+2334
View File
File diff suppressed because it is too large Load Diff
+2219
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+513
View File
@@ -0,0 +1,513 @@
# Angular Form Builder
### Example Code
```angular
import { Component, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { DocusealBuilderComponent } from '@docuseal/angular';
@Component({
selector: 'app-root',
standalone: true,
imports: [DocusealBuilderComponent],
template: `
<div class="app">
<ng-container *ngIf="token">
<docuseal-builder [token]="token"></docuseal-builder>
</ng-container>
</div>
`
})
export class AppComponent implements OnInit {
token: string = ''
constructor(private http: HttpClient) {}
ngOnInit() {
this.http.post('/api/docuseal/builder_token', {}).subscribe((data: any) => {
this.token = data.token;
});
}
}
```
```javascript
const jwt = require('jsonwebtoken');
const token = jwt.sign({
user_email: '{{admin_user_email}}',
integration_email: '{{signer_email}}',
external_id: 'TestForm123',
name: 'Integration W-9 Test Form',
document_urls: ['https://www.irs.gov/pub/irs-pdf/fw9.pdf'],
}, '{{api_key}}');
```
### Attributes
```json
{
"token": {
"type": "string",
"doc_type": "object",
"description": "JSON Web Token (JWT HS256) with a payload signed using the API key. <br><b>Ensure that the JWT token is generated on the backend to prevent unauthorized access to your documents</b>.",
"required": true,
"properties": {
"user_email": {
"type": "string",
"required": true,
"description": "Email of the owner of the API signing key - admin user email."
},
"integration_email": {
"type": "string",
"required": false,
"description": "Email of the user to create a template for.",
"example": "signer@example.com"
},
"template_id": {
"type": "number",
"required": false,
"description": "ID of the template to open in the form builder. Optional when `document_urls` are specified."
},
"external_id": {
"type": "string",
"description": "Your application-specific unique string key to identify this template within your app.",
"required": false
},
"folder_name": {
"type": "string",
"description": "The folder name in which the template should be created.",
"required": false
},
"document_urls": {
"type": "array",
"required": false,
"description": "An Array of URLs with PDF files to open in the form builder. Optional when `template_id` is specified.",
"example": "['https://www.irs.gov/pub/irs-pdf/fw9.pdf']"
},
"name": {
"type": "string",
"required": false,
"description": "New template name when creating a template with document_urls specified.",
"example": "Integration W-9 Test Form"
},
"extract_fields": {
"type": "boolean",
"required": false,
"description": "Pass `false` to disable automatic PDF form fields extraction. PDF fields are automatically added by default."
}
}
},
"host": {
"type": "string",
"required": false,
"description": "DocuSeal host domain name. Only use this attribute if you are using the on-premises DocuSeal installation or docuseal.eu Cloud.",
"example": "yourdomain.com"
},
"customButton": {
"type": "object",
"required": false,
"description": "Custom button will be displayed on the top right corner of the form builder.",
"properties": {
"title": {
"type": "string",
"required": true,
"description": "Custom button title."
},
"url": {
"type": "string",
"required": true,
"description": "Custom button URL. Only absolute URLs are supported."
}
}
},
"roles": {
"type": "array",
"required": false,
"description": "Submitter role names to be used by default in the form."
},
"fieldTypes": {
"type": "array",
"required": false,
"description": "Field type names to be used in the form builder. All field types are used by default."
},
"drawFieldType": {
"type": "string",
"required": false,
"default": "text",
"description": "Field type to be used by default with the field drawing tool.",
"example": "signature"
},
"fields": {
"type": "array",
"required": false,
"description": "An array of default custom field properties with `name` to be added to the document.",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"required": true,
"description": "Field name."
},
"type": {
"type": "string",
"required": false,
"description": "Field type.",
"enum": [
"heading",
"text",
"signature",
"initials",
"date",
"number",
"image",
"checkbox",
"multiple",
"file",
"radio",
"select",
"cells",
"stamp",
"payment",
"phone",
"verification"
]
},
"role": {
"type": "string",
"required": false,
"description": "Submitter role name for the field."
},
"default_value": {
"type": "string",
"required": false,
"description": "Default value of the field."
},
"title": {
"type": "string",
"required": false,
"description": "Field title displayed to the user instead of the name, shown on the signing form. Supports Markdown."
},
"description": {
"type": "string",
"required": false,
"description": "Field description displayed on the signing form. Supports Markdown."
},
"width": {
"type": "number",
"required": false,
"description": "Field width in pixels."
},
"height": {
"type": "number",
"required": false,
"description": "Field height in pixels."
},
"format": {
"type": "string",
"required": false,
"description": "Field format. Depends on the field type."
},
"options": {
"type": "array",
"required": false,
"description": "Field options. Required for the select field type."
},
"validation": {
"type": "object",
"required": false,
"description": "Field validation rules.",
"properties": {
"pattern": {
"type": "string",
"required": false,
"description": "Field pattern.",
"example": "^[0-9]{5}$"
},
"message": {
"type": "string",
"required": false,
"description": "Validation error message."
}
}
}
}
}
},
"submitters": {
"type": "array",
"required": false,
"description": "An array of default submitters with `role` name to be added to the document.",
"items": {
"type": "object",
"properties": {
"email": {
"type": "string",
"required": false,
"description": "Submitter email."
},
"role": {
"type": "string",
"required": true,
"description": "Submitter role name."
},
"name": {
"type": "string",
"required": false,
"description": "Submitter name."
},
"phone": {
"type": "string",
"required": false,
"description": "Submitter phone number, formatted according to the E.164 standard."
}
}
}
},
"requiredFields": {
"type": "array",
"required": false,
"description": "An array of default required custom field properties with `name` that should be added to the document.",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"required": true,
"description": "Field name."
},
"type": {
"type": "string",
"required": false,
"description": "Field type.",
"enum": [
"heading",
"text",
"signature",
"initials",
"date",
"number",
"image",
"checkbox",
"multiple",
"file",
"radio",
"select",
"cells",
"stamp",
"payment",
"phone",
"verification"
]
},
"role": {
"type": "string",
"required": false,
"description": "Submitter role name for the field."
},
"default_value": {
"type": "string",
"required": false,
"description": "Default value of the field."
},
"title": {
"type": "string",
"required": false,
"description": "Field title displayed to the user instead of the name, shown on the signing form. Supports Markdown."
},
"description": {
"type": "string",
"required": false,
"description": "Field description displayed on the signing form. Supports Markdown."
},
"width": {
"type": "number",
"required": false,
"description": "Field width in pixels."
},
"height": {
"type": "number",
"required": false,
"description": "Field height in pixels."
},
"format": {
"type": "string",
"required": false,
"description": "Field format. Depends on the field type."
},
"options": {
"type": "array",
"required": false,
"description": "Field options. Required for the select field type."
},
"validation": {
"type": "object",
"required": false,
"description": "Field validation rules.",
"properties": {
"pattern": {
"type": "string",
"required": false,
"description": "Field pattern.",
"example": "^[0-9]{5}$"
},
"message": {
"type": "string",
"required": false,
"description": "Validation error message."
}
}
}
}
}
},
"emailMessage": {
"type": "object",
"required": false,
"description": "Email subject and body for the signature request.",
"properties": {
"subject": {
"type": "string",
"required": true,
"description": "Email subject for the signature request."
},
"body": {
"type": "string",
"required": true,
"description": "Email body for the signature request."
}
}
},
"withTitle": {
"type": "boolean",
"required": false,
"default": true,
"description": "Set `false` to remove document title from the builder."
},
"withSendButton": {
"type": "boolean",
"required": false,
"default": true,
"description": "Show the \"Send\" button."
},
"withUploadButton": {
"type": "boolean",
"required": false,
"default": true,
"description": "Show the \"Upload\" button."
},
"withAddPageButton": {
"type": "boolean",
"required": false,
"default": false,
"description": "Show the \"Add Blank Page\" button."
},
"withSignYourselfButton": {
"type": "boolean",
"required": false,
"default": true,
"description": "Show the \"Sign Yourself\" button."
},
"withDocumentsList": {
"type": "boolean",
"required": false,
"default": true,
"description": "Set `false` to now show the documents list on the left. Documents list is displayed by default."
},
"withFieldsList": {
"type": "boolean",
"required": false,
"default": true,
"description": "Set `false` to now show the fields list on the right. Fields list is displayed by default."
},
"withFieldPlaceholder": {
"type": "boolean",
"required": false,
"default": false,
"description": "Set `true` to display field name placeholders instead of the field type icons."
},
"onlyDefinedFields": {
"type": "boolean",
"required": false,
"default": false,
"description": "Allow to add fields only defined in the `fields` prop."
},
"preview": {
"type": "boolean",
"required": false,
"default": false,
"description": "Show template in preview mode without ability to edit it."
},
"autosave": {
"type": "boolean",
"required": false,
"default": true,
"description": "Set `false` to disable form changes autosaving."
},
"language": {
"type": "string",
"required": false,
"default": "en",
"description": "UI language, 'en', 'es', 'de', 'fr', 'pt', 'he', 'ar' languages are available."
},
"i18n": {
"type": "object",
"required": false,
"default": "{}",
"description": "Object that contains i18n keys to replace the default UI text with custom values. See <a href=\"https://github.com/docusealco/docuseal/blob/master/app/javascript/template_builder/i18n.js\" class=\"link\" target=\"_blank\" rel=\"nofollow\">template_builder/i18n.js</a> for available i18n keys."
},
"backgroundColor": {
"type": "string",
"required": false,
"description": "The form builder background color. Only HEX color codes are supported.",
"example": "#ffffff"
},
"customCss": {
"type": "string",
"required": false,
"description": "Custom CSS styles to be applied to the form builder.",
"example": "#sign_yourself_button { background-color: #FFA500; }"
}
}
```
### Callback
```json
{
"onLoad": {
"type": "event emitter",
"required": false,
"description": "Event emitted on loading the form builder template data.",
"example": "handleLoad($event)"
},
"onUpload": {
"type": "event emitter",
"required": false,
"description": "Event emitted on uploading a document to the template.",
"example": "handleUpload($event)"
},
"onSend": {
"type": "event emitter",
"required": false,
"description": "Event emitted on sending documents for signature to recipients.",
"example": "handleSend($event)"
},
"onChange": {
"type": "function",
"required": false,
"description": "Function to be called when changes are made to the template form.",
"example": "handleChange($event)"
},
"onSave": {
"type": "event emitter",
"required": false,
"description": "Event emitted on saving changes of the template form.",
"example": "handleSave($event)"
}
}
```
+477
View File
@@ -0,0 +1,477 @@
# JavaScript Form Builder
### Example Code
```javascript
<script src="https://docuseal.com/js/builder.js"></script>
<docuseal-builder
data-token="<%= JWT.encode({
user_email: '{{admin_user_email}}',
integration_email: '{{signer_email}}',
name: 'Integration W-9 Test Form',
document_urls: ['https://www.irs.gov/pub/irs-pdf/fw9.pdf'],
}, '{{api_key}}') %>">
</docuseal-builder>
```
### Attributes
```json
{
"data-token": {
"type": "string",
"doc_type": "object",
"description": "JSON Web Token (JWT HS256) with a payload signed using the API key. <br><b>Ensure that the JWT token is generated on the backend to prevent unauthorized access to your documents</b>.",
"required": true,
"properties": {
"user_email": {
"type": "string",
"required": true,
"description": "Email of the owner of the API signing key - admin user email."
},
"integration_email": {
"type": "string",
"required": false,
"description": "Email of the user to create a template for.",
"example": "signer@example.com"
},
"template_id": {
"type": "number",
"required": false,
"description": "ID of the template to open in the form builder. Optional when `document_urls` are specified."
},
"external_id": {
"type": "string",
"description": "Your application-specific unique string key to identify this template within your app.",
"required": false
},
"folder_name": {
"type": "string",
"description": "The folder name in which the template should be created.",
"required": false
},
"document_urls": {
"type": "array",
"required": false,
"description": "An Array of URLs with PDF files to open in the form builder. Optional when `template_id` is specified.",
"example": "['https://www.irs.gov/pub/irs-pdf/fw9.pdf']"
},
"name": {
"type": "string",
"required": false,
"description": "New template name when creating a template with document_urls specified.",
"example": "Integration W-9 Test Form"
},
"extract_fields": {
"type": "boolean",
"required": false,
"description": "Pass `false` to disable automatic PDF form fields extraction. PDF fields are automatically added by default."
}
}
},
"data-host": {
"type": "string",
"required": false,
"description": "DocuSeal host domain name. Only use this attribute if you are using the on-premises DocuSeal installation or docuseal.eu Cloud.",
"example": "yourdomain.com"
},
"data-roles": {
"type": "string",
"required": false,
"description": "Comma separated submitter role names to be used by default in the form.",
"example": "Company,Customer"
},
"data-fields": {
"type": "string",
"doc_type": "array",
"required": false,
"description": "A list of default custom fields with `name` to be added to the document. Should contain an array of field properties as a JSON encoded string.",
"example": "[{ \"name\": \"FIELD_1\", \"type\": \"date\", \"role\": \"Customer\", \"default_value\": \"2021-01-01\" }]",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"required": true,
"description": "Field name."
},
"type": {
"type": "string",
"required": false,
"description": "Field type.",
"enum": [
"heading",
"text",
"signature",
"initials",
"date",
"number",
"image",
"checkbox",
"multiple",
"file",
"radio",
"select",
"cells",
"stamp",
"payment",
"phone",
"verification"
]
},
"role": {
"type": "string",
"required": false,
"description": "Submitter role name for the field."
},
"default_value": {
"type": "string",
"required": false,
"description": "Default value of the field."
},
"title": {
"type": "string",
"required": false,
"description": "Field title displayed to the user instead of the name, shown on the signing form. Supports Markdown."
},
"description": {
"type": "string",
"required": false,
"description": "Field description displayed on the signing form. Supports Markdown."
},
"width": {
"type": "number",
"required": false,
"description": "Field width in pixels."
},
"height": {
"type": "number",
"required": false,
"description": "Field height in pixels."
},
"format": {
"type": "string",
"required": false,
"description": "Field format. Depends on the field type."
},
"options": {
"type": "array",
"required": false,
"description": "Field options. Required for the select field type."
},
"validation": {
"type": "object",
"required": false,
"description": "Field validation rules.",
"properties": {
"pattern": {
"type": "string",
"required": false,
"description": "Field pattern.",
"example": "^[0-9]{5}$"
},
"message": {
"type": "string",
"required": false,
"description": "Validation error message."
}
}
}
}
}
},
"data-submitters": {
"type": "string",
"doc_type": "array",
"required": false,
"description": "A list of default submitters with `role` name to be added to the document. Should contain an array of field properties as a JSON encoded string.",
"example": "[{ \"email\": \"example@company.com\", \"name\": \"John Doe\", \"phone\": \"+1234567890\", \"role\": \"Customer\" }]",
"items": {
"type": "object",
"properties": {
"email": {
"type": "string",
"required": false,
"description": "Submitter email."
},
"role": {
"type": "string",
"required": true,
"description": "Submitter role name."
},
"name": {
"type": "string",
"required": false,
"description": "Submitter name."
},
"phone": {
"type": "string",
"required": false,
"description": "Submitter phone number, formatted according to the E.164 standard."
}
}
}
},
"data-required-fields": {
"type": "string",
"doc_type": "array",
"required": false,
"description": "A list of required default custom fields with `name` that should be added to the document. Should contain an array of field properties as a JSON encoded string.",
"example": "[{ \"name\": \"FIELD_1\", \"type\": \"date\", \"role\": \"Customer\", \"default_value\": \"2021-01-01\" }]",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"required": true,
"description": "Field name."
},
"type": {
"type": "string",
"required": false,
"description": "Field type.",
"enum": [
"heading",
"text",
"signature",
"initials",
"date",
"number",
"image",
"checkbox",
"multiple",
"file",
"radio",
"select",
"cells",
"stamp",
"payment",
"phone",
"verification"
]
},
"role": {
"type": "string",
"required": false,
"description": "Submitter role name for the field."
},
"default_value": {
"type": "string",
"required": false,
"description": "Default value of the field."
},
"title": {
"type": "string",
"required": false,
"description": "Field title displayed to the user instead of the name, shown on the signing form. Supports Markdown."
},
"description": {
"type": "string",
"required": false,
"description": "Field description displayed on the signing form. Supports Markdown."
},
"width": {
"type": "number",
"required": false,
"description": "Field width in pixels."
},
"height": {
"type": "number",
"required": false,
"description": "Field height in pixels."
},
"format": {
"type": "string",
"required": false,
"description": "Field format. Depends on the field type."
},
"options": {
"type": "array",
"required": false,
"description": "Field options. Required for the select field type."
},
"validation": {
"type": "object",
"required": false,
"description": "Field validation rules.",
"properties": {
"pattern": {
"type": "string",
"required": false,
"description": "Field pattern.",
"example": "^[0-9]{5}$"
},
"message": {
"type": "string",
"required": false,
"description": "Validation error message."
}
}
}
}
}
},
"data-field-types": {
"type": "string",
"required": false,
"description": "Comma separated field type names to be used in the form builder. All field types are used by default.",
"example": "text,date"
},
"data-draw-field-type": {
"type": "string",
"required": false,
"default": "text",
"description": "Field type to be used by default with the field drawing tool.",
"example": "signature"
},
"data-custom-button-title": {
"type": "string",
"required": false,
"description": "Custom button title. This button will be displayed on the top right corner of the form builder."
},
"data-custom-button-url": {
"type": "string",
"required": false,
"description": "Custom button URL. Only absolute URLs are supported."
},
"data-with-title": {
"type": "boolean",
"required": false,
"default": true,
"description": "Set `false` to remove document title from the builder."
},
"email-subject": {
"type": "string",
"required": false,
"description": "Email subject for the signature request. Required if `email-body` specified"
},
"email-body": {
"type": "string",
"required": false,
"description": "Email body for the signature request. Required if `email-subject` specified"
},
"data-with-send-button": {
"type": "boolean",
"required": false,
"default": true,
"description": "Show the \"Recipients\" button."
},
"data-with-upload-button": {
"type": "boolean",
"required": false,
"default": true,
"description": "Show the \"Upload\" button."
},
"data-with-add-page-button": {
"type": "boolean",
"required": false,
"default": false,
"description": "Show the \"Add Blank Page\" button."
},
"data-with-sign-yourself-button": {
"type": "boolean",
"required": false,
"default": true,
"description": "Show the \"Sign Yourself\" button."
},
"data-with-documents-list": {
"type": "boolean",
"required": false,
"default": true,
"description": "Set `false` to now show the documents list on the left. Documents list is displayed by default."
},
"data-with-fields-list": {
"type": "boolean",
"required": false,
"default": true,
"description": "Set `false` to now show the fields list on the right. Fields list is displayed by default."
},
"data-with-field-placeholder": {
"type": "boolean",
"required": false,
"default": false,
"description": "Set `true` to display field name placeholders instead of the field type icons."
},
"data-preview": {
"type": "boolean",
"required": false,
"default": false,
"description": "Show template in preview mode without ability to edit it."
},
"data-only-defined-fields": {
"type": "boolean",
"required": false,
"default": false,
"description": "Allow to add fields only defined in the `data-fields` attribute."
},
"data-autosave": {
"type": "boolean",
"required": false,
"default": true,
"description": "Set `false` to disable form changes autosaving."
},
"data-i18n": {
"type": "string",
"required": false,
"default": "{}",
"description": "JSON encoded string that contains i18n keys to replace the default UI text with custom values. See <a href=\"https://github.com/docusealco/docuseal/blob/master/app/javascript/template_builder/i18n.js\" class=\"link\" target=\"_blank\" rel=\"nofollow\">template_builder/i18n.js</a> for available i18n keys."
},
"data-language": {
"type": "string",
"required": false,
"default": "en",
"description": "UI language, 'en', 'es', 'de', 'fr', 'pt', 'he', 'ar' languages are available."
},
"data-background-color": {
"type": "string",
"required": false,
"description": "The form builder background color. Only HEX color codes are supported.",
"example": "#ffffff"
},
"data-custom-css": {
"type": "string",
"required": false,
"description": "Custom CSS styles to be applied to the form builder.",
"example": "#sign_yourself_button { background-color: #FFA500; }"
}
}
```
### Callback
```json
{
"load": {
"type": "event",
"required": false,
"description": "Custom event to be triggered on loading the form builder template data.",
"example": "document.querySelector('docuseal-builder').addEventListener('load', (e) => e.detail)"
},
"upload": {
"type": "event",
"required": false,
"description": "Custom event to be triggered on uploading a document to the template.",
"example": "document.querySelector('docuseal-builder').addEventListener('upload', (e) => e.detail)"
},
"send": {
"type": "event",
"required": false,
"description": "Custom event to be triggered on sending documents for signature to recipients.",
"example": "document.querySelector('docuseal-builder').addEventListener('send', (e) => e.detail)"
},
"change": {
"type": "event",
"required": false,
"description": "Custom event to be triggered every time a change to the template is made.",
"example": "document.querySelector('docuseal-builder').addEventListener('change', (e) => e.detail)"
},
"save": {
"type": "event",
"required": false,
"description": "Custom event to be triggered on saving changes of the template form.",
"example": "document.querySelector('docuseal-builder').addEventListener('save', (e) => e.detail)"
}
}
```
+504
View File
@@ -0,0 +1,504 @@
# React Form Builder
### Example Code
```react
import React, { useState, useEffect } from 'react';
import { DocusealBuilder } from '@docuseal/react'
const App = () => {
const [token, setToken] = useState();
useEffect(() => {
fetch('/api/docuseal/builder_token', {
method: 'POST',
})
.then((response) => response.json())
.then((data) => {
setToken(data.token);
});
}, []);
return token && <DocusealBuilder token={token} />;
};
```
```javascript
const jwt = require('jsonwebtoken');
const token = jwt.sign({
user_email: '{{admin_user_email}}',
integration_email: '{{signer_email}}',
external_id: 'TestForm123',
name: 'Integration W-9 Test Form',
document_urls: ['https://www.irs.gov/pub/irs-pdf/fw9.pdf'],
}, '{{api_key}}');
```
### Attributes
```json
{
"token": {
"type": "string",
"doc_type": "object",
"description": "JSON Web Token (JWT HS256) with a payload signed using the API key. <br><b>Ensure that the JWT token is generated on the backend to prevent unauthorized access to your documents</b>.",
"required": true,
"properties": {
"user_email": {
"type": "string",
"required": true,
"description": "Email of the owner of the API signing key - admin user email."
},
"integration_email": {
"type": "string",
"required": false,
"description": "Email of the user to create a template for.",
"example": "signer@example.com"
},
"template_id": {
"type": "number",
"required": false,
"description": "ID of the template to open in the form builder. Optional when `document_urls` are specified."
},
"external_id": {
"type": "string",
"description": "Your application-specific unique string key to identify this template within your app.",
"required": false
},
"folder_name": {
"type": "string",
"description": "The folder name in which the template should be created.",
"required": false
},
"document_urls": {
"type": "array",
"required": false,
"description": "An Array of URLs with PDF files to open in the form builder. Optional when `template_id` is specified.",
"example": "['https://www.irs.gov/pub/irs-pdf/fw9.pdf']"
},
"name": {
"type": "string",
"required": false,
"description": "New template name when creating a template with document_urls specified.",
"example": "Integration W-9 Test Form"
},
"extract_fields": {
"type": "boolean",
"required": false,
"description": "Pass `false` to disable automatic PDF form fields extraction. PDF fields are automatically added by default."
}
}
},
"host": {
"type": "string",
"required": false,
"description": "DocuSeal host domain name. Only use this attribute if you are using the on-premises DocuSeal installation or docuseal.eu Cloud.",
"example": "yourdomain.com"
},
"customButton": {
"type": "object",
"required": false,
"description": "Custom button will be displayed on the top right corner of the form builder.",
"properties": {
"title": {
"type": "string",
"required": true,
"description": "Custom button title."
},
"url": {
"type": "string",
"required": true,
"description": "Custom button URL. Only absolute URLs are supported."
}
}
},
"roles": {
"type": "array",
"required": false,
"description": "Submitter role names to be used by default in the form."
},
"fieldTypes": {
"type": "array",
"required": false,
"description": "Field type names to be used in the form builder. All field types are used by default."
},
"drawFieldType": {
"type": "string",
"required": false,
"default": "text",
"description": "Field type to be used by default with the field drawing tool.",
"example": "signature"
},
"fields": {
"type": "array",
"required": false,
"description": "An array of default custom field properties with `name` to be added to the document.",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"required": true,
"description": "Field name."
},
"type": {
"type": "string",
"required": false,
"description": "Field type.",
"enum": [
"heading",
"text",
"signature",
"initials",
"date",
"number",
"image",
"checkbox",
"multiple",
"file",
"radio",
"select",
"cells",
"stamp",
"payment",
"phone",
"verification"
]
},
"role": {
"type": "string",
"required": false,
"description": "Submitter role name for the field."
},
"default_value": {
"type": "string",
"required": false,
"description": "Default value of the field."
},
"title": {
"type": "string",
"required": false,
"description": "Field title displayed to the user instead of the name, shown on the signing form. Supports Markdown."
},
"description": {
"type": "string",
"required": false,
"description": "Field description displayed on the signing form. Supports Markdown."
},
"width": {
"type": "number",
"required": false,
"description": "Field width in pixels."
},
"height": {
"type": "number",
"required": false,
"description": "Field height in pixels."
},
"format": {
"type": "string",
"required": false,
"description": "Field format. Depends on the field type."
},
"options": {
"type": "array",
"required": false,
"description": "Field options. Required for the select field type."
},
"validation": {
"type": "object",
"required": false,
"description": "Field validation rules.",
"properties": {
"pattern": {
"type": "string",
"required": false,
"description": "Field pattern.",
"example": "^[0-9]{5}$"
},
"message": {
"type": "string",
"required": false,
"description": "Validation error message."
}
}
}
}
}
},
"submitters": {
"type": "array",
"required": false,
"description": "An array of default submitters with `role` name to be added to the document.",
"items": {
"type": "object",
"properties": {
"email": {
"type": "string",
"required": false,
"description": "Submitter email."
},
"role": {
"type": "string",
"required": true,
"description": "Submitter role name."
},
"name": {
"type": "string",
"required": false,
"description": "Submitter name."
},
"phone": {
"type": "string",
"required": false,
"description": "Submitter phone number, formatted according to the E.164 standard."
}
}
}
},
"requiredFields": {
"type": "array",
"required": false,
"description": "An array of default required custom field properties with `name` that should be added to the document.",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"required": true,
"description": "Field name."
},
"type": {
"type": "string",
"required": false,
"description": "Field type.",
"enum": [
"heading",
"text",
"signature",
"initials",
"date",
"number",
"image",
"checkbox",
"multiple",
"file",
"radio",
"select",
"cells",
"stamp",
"payment",
"phone",
"verification"
]
},
"role": {
"type": "string",
"required": false,
"description": "Submitter role name for the field."
},
"default_value": {
"type": "string",
"required": false,
"description": "Default value of the field."
},
"title": {
"type": "string",
"required": false,
"description": "Field title displayed to the user instead of the name, shown on the signing form. Supports Markdown."
},
"description": {
"type": "string",
"required": false,
"description": "Field description displayed on the signing form. Supports Markdown."
},
"width": {
"type": "number",
"required": false,
"description": "Field width in pixels."
},
"height": {
"type": "number",
"required": false,
"description": "Field height in pixels."
},
"format": {
"type": "string",
"required": false,
"description": "Field format. Depends on the field type."
},
"options": {
"type": "array",
"required": false,
"description": "Field options. Required for the select field type."
},
"validation": {
"type": "object",
"required": false,
"description": "Field validation rules.",
"properties": {
"pattern": {
"type": "string",
"required": false,
"description": "Field pattern.",
"example": "^[0-9]{5}$"
},
"message": {
"type": "string",
"required": false,
"description": "Validation error message."
}
}
}
}
}
},
"emailMessage": {
"type": "object",
"required": false,
"description": "Email subject and body for the signature request.",
"properties": {
"subject": {
"type": "string",
"required": true,
"description": "Email subject for the signature request."
},
"body": {
"type": "string",
"required": true,
"description": "Email body for the signature request."
}
}
},
"withTitle": {
"type": "boolean",
"required": false,
"default": true,
"description": "Set `false` to remove document title from the builder."
},
"withSendButton": {
"type": "boolean",
"required": false,
"default": true,
"description": "Show the \"Send\" button."
},
"withUploadButton": {
"type": "boolean",
"required": false,
"default": true,
"description": "Show the \"Upload\" button."
},
"withAddPageButton": {
"type": "boolean",
"required": false,
"default": false,
"description": "Show the \"Add Blank Page\" button."
},
"withSignYourselfButton": {
"type": "boolean",
"required": false,
"default": true,
"description": "Show the \"Sign Yourself\" button."
},
"withDocumentsList": {
"type": "boolean",
"required": false,
"default": true,
"description": "Set `false` to now show the documents list on the left. Documents list is displayed by default."
},
"withFieldsList": {
"type": "boolean",
"required": false,
"default": true,
"description": "Set `false` to now show the fields list on the right. Fields list is displayed by default."
},
"withFieldPlaceholder": {
"type": "boolean",
"required": false,
"default": false,
"description": "Set `true` to display field name placeholders instead of the field type icons."
},
"onlyDefinedFields": {
"type": "boolean",
"required": false,
"default": false,
"description": "Allow to add fields only defined in the `fields` prop."
},
"preview": {
"type": "boolean",
"required": false,
"default": false,
"description": "Show template in preview mode without ability to edit it."
},
"autosave": {
"type": "boolean",
"required": false,
"default": true,
"description": "Set `false` to disable form changes autosaving."
},
"language": {
"type": "string",
"required": false,
"default": "en",
"description": "UI language, 'en', 'es', 'de', 'fr', 'pt', 'he', 'ar' languages are available."
},
"i18n": {
"type": "object",
"required": false,
"default": "{}",
"description": "Object that contains i18n keys to replace the default UI text with custom values. See <a href=\"https://github.com/docusealco/docuseal/blob/master/app/javascript/template_builder/i18n.js\" class=\"link\" target=\"_blank\" rel=\"nofollow\">template_builder/i18n.js</a> for available i18n keys."
},
"backgroundColor": {
"type": "string",
"required": false,
"description": "The form builder background color. Only HEX color codes are supported.",
"example": "#ffffff"
},
"customCss": {
"type": "string",
"required": false,
"description": "Custom CSS styles to be applied to the form builder.",
"example": "#sign_yourself_button { background-color: #FFA500; }"
}
}
```
### Callback
```json
{
"onLoad": {
"type": "function",
"required": false,
"description": "Function to be called on loading the form builder template data.",
"example": "(data) => { console.log(data) }"
},
"onUpload": {
"type": "function",
"required": false,
"description": "Function to be called on uploading a document to the template.",
"example": "(data) => { console.log(data) }"
},
"onSend": {
"type": "function",
"required": false,
"description": "Function to be called on sending documents for signature to recipients.",
"example": "(data) => { console.log(data) }"
},
"onChange": {
"type": "function",
"required": false,
"description": "Function to be called when changes are made to the template form.",
"example": "(data) => { console.log(data) }"
},
"onSave": {
"type": "function",
"required": false,
"description": "Function to be called on saving changes of the template form.",
"example": "(data) => { console.log(data) }"
}
}
```
+513
View File
@@ -0,0 +1,513 @@
# Vue Form Builder
### Example Code
```vue
<template>
<DocusealBuilder
v-if="token"
:token="token"
/>
</template>
<script>
import { DocusealBuilder } from '@docuseal/vue'
export default {
name: 'App',
components: { DocusealBuilder },
data () {
return { token: '' }
},
mounted () {
fetch('/api/docuseal/builder_token', {
method: 'POST'
}).then(async (resp) => {
const data = await resp.json()
this.token = data.token
})
}
}
</script>
```
```javascript
const jwt = require('jsonwebtoken');
const token = jwt.sign({
user_email: '{{admin_user_email}}',
integration_email: '{{signer_email}}',
external_id: 'TestForm123',
name: 'Integration W-9 Test Form',
document_urls: ['https://www.irs.gov/pub/irs-pdf/fw9.pdf'],
}, '{{api_key}}');
```
### Attributes
```json
{
"token": {
"type": "string",
"doc_type": "object",
"description": "JSON Web Token (JWT HS256) with a payload signed using the API key. <br><b>Ensure that the JWT token is generated on the backend to prevent unauthorized access to your documents</b>.",
"required": true,
"properties": {
"user_email": {
"type": "string",
"required": true,
"description": "Email of the owner of the API signing key - admin user email."
},
"integration_email": {
"type": "string",
"required": false,
"description": "Email of the user to create a template for.",
"example": "signer@example.com"
},
"template_id": {
"type": "number",
"required": false,
"description": "ID of the template to open in the form builder. Optional when `document_urls` are specified."
},
"external_id": {
"type": "string",
"description": "Your application-specific unique string key to identify this template within your app.",
"required": false
},
"folder_name": {
"type": "string",
"description": "The folder name in which the template should be created.",
"required": false
},
"document_urls": {
"type": "array",
"required": false,
"description": "An Array of URLs with PDF files to open in the form builder. Optional when `template_id` is specified.",
"example": "['https://www.irs.gov/pub/irs-pdf/fw9.pdf']"
},
"name": {
"type": "string",
"required": false,
"description": "New template name when creating a template with document_urls specified.",
"example": "Integration W-9 Test Form"
},
"extract_fields": {
"type": "boolean",
"required": false,
"description": "Pass `false` to disable automatic PDF form fields extraction. PDF fields are automatically added by default."
}
}
},
"host": {
"type": "string",
"required": false,
"description": "DocuSeal host domain name. Only use this attribute if you are using the on-premises DocuSeal installation or docuseal.eu Cloud.",
"example": "yourdomain.com"
},
"custom-button": {
"type": "object",
"required": false,
"description": "Custom button will be displayed on the top right corner of the form builder.",
"properties": {
"title": {
"type": "string",
"required": true,
"description": "Custom button title."
},
"url": {
"type": "string",
"required": true,
"description": "Custom button URL. Only absolute URLs are supported."
}
}
},
"only-defined-fields": {
"type": "boolean",
"required": false,
"default": false,
"description": "Allow to add fields only defined in the `:fields` prop."
},
"with-send-button": {
"type": "boolean",
"required": false,
"default": true,
"description": "Show the \"Recipients\" button."
},
"roles": {
"type": "array",
"required": false,
"description": "Submitter role names to be used by default in the form."
},
"field-types": {
"type": "array",
"required": false,
"description": "Field type names to be used in the form builder. All field types are used by default."
},
"draw-field-type": {
"type": "string",
"required": false,
"default": "text",
"description": "Field type to be used by default with the field drawing tool.",
"example": "signature"
},
"fields": {
"type": "array",
"required": false,
"description": "An array of default custom field properties with `name` to be added to the document.",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"required": true,
"description": "Field name."
},
"type": {
"type": "string",
"required": false,
"description": "Field type.",
"enum": [
"heading",
"text",
"signature",
"initials",
"date",
"number",
"image",
"checkbox",
"multiple",
"file",
"radio",
"select",
"cells",
"stamp",
"payment",
"phone",
"verification"
]
},
"role": {
"type": "string",
"required": false,
"description": "Submitter role name for the field."
},
"default_value": {
"type": "string",
"required": false,
"description": "Default value of the field."
},
"title": {
"type": "string",
"required": false,
"description": "Field title displayed to the user instead of the name, shown on the signing form. Supports Markdown."
},
"description": {
"type": "string",
"required": false,
"description": "Field description displayed on the signing form. Supports Markdown."
},
"width": {
"type": "number",
"required": false,
"description": "Field width in pixels."
},
"height": {
"type": "number",
"required": false,
"description": "Field height in pixels."
},
"format": {
"type": "string",
"required": false,
"description": "Field format. Depends on the field type."
},
"options": {
"type": "array",
"required": false,
"description": "Field options. Required for the select field type."
},
"validation": {
"type": "object",
"required": false,
"description": "Field validation rules.",
"properties": {
"pattern": {
"type": "string",
"required": false,
"description": "Field pattern.",
"example": "^[0-9]{5}$"
},
"message": {
"type": "string",
"required": false,
"description": "Validation error message."
}
}
}
}
}
},
"submitters": {
"type": "array",
"required": false,
"description": "An array of default submitters with `role` name to be added to the document.",
"items": {
"type": "object",
"properties": {
"email": {
"type": "string",
"required": false,
"description": "Submitter email."
},
"role": {
"type": "string",
"required": true,
"description": "Submitter role name."
},
"name": {
"type": "string",
"required": false,
"description": "Submitter name."
},
"phone": {
"type": "string",
"required": false,
"description": "Submitter phone number, formatted according to the E.164 standard."
}
}
}
},
"required-fields": {
"type": "array",
"required": false,
"description": "An array of default required custom field properties with `name` that should be added to the document.",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"required": true,
"description": "Field name."
},
"type": {
"type": "string",
"required": false,
"description": "Field type.",
"enum": [
"heading",
"text",
"signature",
"initials",
"date",
"number",
"image",
"checkbox",
"multiple",
"file",
"radio",
"select",
"cells",
"stamp",
"payment",
"phone",
"verification"
]
},
"role": {
"type": "string",
"required": false,
"description": "Submitter role name for the field."
},
"default_value": {
"type": "string",
"required": false,
"description": "Default value of the field."
},
"title": {
"type": "string",
"required": false,
"description": "Field title displayed to the user instead of the name, shown on the signing form. Supports Markdown."
},
"description": {
"type": "string",
"required": false,
"description": "Field description displayed on the signing form. Supports Markdown."
},
"width": {
"type": "number",
"required": false,
"description": "Field width in pixels."
},
"height": {
"type": "number",
"required": false,
"description": "Field height in pixels."
},
"format": {
"type": "string",
"required": false,
"description": "Field format. Depends on the field type."
},
"options": {
"type": "array",
"required": false,
"description": "Field options. Required for the select field type."
},
"validation": {
"type": "object",
"required": false,
"description": "Field validation rules.",
"properties": {
"pattern": {
"type": "string",
"required": false,
"description": "Field pattern.",
"example": "^[0-9]{5}$"
},
"message": {
"type": "string",
"required": false,
"description": "Validation error message."
}
}
}
}
}
},
"email-message": {
"type": "object",
"required": false,
"description": "Email subject and body for the signature request.",
"properties": {
"subject": {
"type": "string",
"required": true,
"description": "Email subject for the signature request."
},
"body": {
"type": "string",
"required": true,
"description": "Email body for the signature request."
}
}
},
"with-title": {
"type": "boolean",
"required": false,
"default": true,
"description": "Set `false` to remove document title from the builder."
},
"with-upload-button": {
"type": "boolean",
"required": false,
"default": true,
"description": "Show the \"Upload\" button."
},
"with-add-page-button": {
"type": "boolean",
"required": false,
"default": false,
"description": "Show the \"Add Blank Page\" button."
},
"with-sign-yourself-button": {
"type": "boolean",
"required": false,
"default": true,
"description": "Show the \"Sign Yourself\" button."
},
"with-documents-list": {
"type": "boolean",
"required": false,
"default": true,
"description": "Set `false` to now show the documents list on the left. Documents list is displayed by default."
},
"with-fields-list": {
"type": "boolean",
"required": false,
"default": true,
"description": "Set `false` to now show the fields list on the right. Fields list is displayed by default."
},
"with-field-placeholder": {
"type": "boolean",
"required": false,
"default": false,
"description": "Set `true` to display field name placeholders instead of the field type icons."
},
"autosave": {
"type": "boolean",
"required": false,
"default": true,
"description": "Set `false` to disable form changes autosaving."
},
"preview": {
"type": "boolean",
"required": false,
"default": false,
"description": "Show template in preview mode without ability to edit it."
},
"language": {
"type": "string",
"required": false,
"default": "en",
"description": "UI language, 'en', 'es', 'de', 'fr', 'pt', 'he', 'ar' languages are available."
},
"i18n": {
"type": "object",
"required": false,
"default": "{}",
"description": "Object that contains i18n keys to replace the default UI text with custom values. See <a href=\"https://github.com/docusealco/docuseal/blob/master/app/javascript/template_builder/i18n.js\" class=\"link\" target=\"_blank\" rel=\"nofollow\">template_builder/i18n.js</a> for available i18n keys."
},
"background-color": {
"type": "string",
"required": false,
"description": "The form builder background color. Only HEX color codes are supported.",
"example": "#ffffff"
},
"custom-css": {
"type": "string",
"required": false,
"description": "Custom CSS styles to be applied to the form builder.",
"example": "#sign_yourself_button { background-color: #FFA500; }"
}
}
```
### Callback
```json
{
"@load": {
"type": "function",
"required": false,
"description": "Function to be called on loading the form builder template data.",
"example": "onBuilderLoad"
},
"@upload": {
"type": "function",
"required": false,
"description": "Function to be called on uploading a document to the template.",
"example": "onBuilderUpload"
},
"@send": {
"type": "function",
"required": false,
"description": "Function to be called on sending documents for signature to recipients.",
"example": "onBuilderSend"
},
"@change": {
"type": "function",
"required": false,
"description": "Function to be called when changes are made to the template form.",
"example": "onBuilderChange"
},
"@save": {
"type": "function",
"required": false,
"description": "Function to be called on saving changes of the template form.",
"example": "onBuilderSave"
}
}
```
+295
View File
@@ -0,0 +1,295 @@
# Angular Signing Form
### Example Code
```angular
import { Component } from '@angular/core';
import { DocusealFormComponent } from '@docuseal/angular';
@Component({
selector: 'app-root',
standalone: true,
imports: [DocusealFormComponent],
template: `
<div class="app">
<docuseal-form
[src]="'https://docuseal.com/d/{{template_slug}}'"
[email]="'{{signer_email}}'">
</docuseal-form>
</div>
`
})
export class AppComponent {}
```
### Attributes
```json
{
"src": {
"type": "string",
"required": true,
"description": "Public URL of the document signing form. There are two types of URLs: <li><code>/d/{slug}</code> - template form signing URL can be copied from the template page in the admin dashboard. Also template \"slug\" key can be obtained via the <code>/templates</code> API.</li><li><code>/s/{slug}</code> - individual signer URL. Signer \"slug\" key can be obtained via the <code>/submissions</code> API which is used to initiate signature requests for a template form with recipients.</li>"
},
"email": {
"type": "string",
"required": false,
"description": "Email address of the signer. Additional email form step will be displayed if the email attribute is not specified."
},
"name": {
"type": "string",
"required": false,
"description": "Name of the signer."
},
"role": {
"type": "string",
"required": false,
"description": "The role name or title of the signer.",
"example": "First Party"
},
"expand": {
"type": "boolean",
"required": false,
"description": "Expand form on open.",
"default": true
},
"minimize": {
"type": "boolean",
"required": false,
"description": "Set to `true` to always minimize form fields. Requires to click on the field to expand the form.",
"default": false
},
"orderAsOnPage": {
"type": "boolean",
"required": false,
"default": false,
"description": "Order form fields based on their position on the pages."
},
"externalId": {
"type": "string",
"required": false,
"description": "Your application-specific unique string key to identify signer within your app."
},
"logo": {
"type": "string",
"required": false,
"description": "Public logo image URL to use in the signing form."
},
"language": {
"type": "string",
"required": false,
"description": "UI language: en, es, it, de, fr, nl, pl, uk, cs, pt, he, ar, kr, ja languages are available. Be default the form is displayed in the user browser language automatically."
},
"i18n": {
"type": "object",
"required": false,
"default": "{}",
"description": "Object that contains i18n keys to replace the default UI text with custom values. See <a href=\"https://github.com/docusealco/docuseal/blob/master/app/javascript/submission_form/i18n.js\" class=\"link\" target=\"_blank\" rel=\"nofollow\">submission_form/i18n.js</a> for available i18n keys."
},
"preview": {
"type": "boolean",
"required": false,
"default": false,
"description": "Show form in preview mode without ability to submit it."
},
"goToLast": {
"type": "boolean",
"required": false,
"default": true,
"description": "Navigate to the last unfinished step."
},
"withFieldNames": {
"type": "boolean",
"required": false,
"default": true,
"description": "Set `false` to hide field name. Hidding field names can be useful for when they are not in the human readable format. Field names are displayed by default."
},
"withFieldPlaceholder": {
"type": "boolean",
"required": false,
"default": false,
"description": "Set `true` to display field name placeholders instead of the field type icons."
},
"skipFields": {
"type": "boolean",
"required": false,
"default": false,
"description": "Allow skipping form fields."
},
"autoscrollFields": {
"type": "boolean",
"required": false,
"default": true,
"description": "Set `false` to disable auto-scrolling to the next document field."
},
"sendCopyEmail": {
"type": "boolean",
"required": false,
"description": "Set `false` to disable automatic email sending with signed documents to the signers. Emails with signed documents are sent to the signers by default."
},
"backgroundColor": {
"type": "string",
"required": false,
"description": "Form background color. Only HEX color codes are supported.",
"example": "#d9d9d9"
},
"completedRedirectUrl": {
"type": "string",
"required": false,
"description": "URL to redirect to after the submission completion.",
"example": "https://docuseal.com/success"
},
"completedMessage": {
"type": "object",
"required": false,
"description": "Message displayed after the form completion.",
"properties": {
"title": {
"type": "string",
"required": false,
"description": "Message title.",
"example": "Documents have been signed!"
},
"body": {
"type": "string",
"required": false,
"description": "Message content.",
"example": "If you have any questions, please contact us."
}
}
},
"completedButton": {
"type": "object",
"required": false,
"description": "Customizable button after form completion.",
"properties": {
"title": {
"type": "string",
"required": true,
"description": "Button label.",
"example": "Go Back"
},
"url": {
"type": "string",
"required": true,
"description": "Button link. Only absolute URLs are supported.",
"example": "https://example.com"
}
}
},
"withTitle": {
"type": "boolean",
"required": false,
"default": true,
"description": "Set `false` to remove the document title from the form."
},
"withDecline": {
"type": "boolean",
"required": false,
"default": false,
"description": "Set `true` to display the decline button in the form."
},
"withDownloadButton": {
"type": "boolean",
"required": false,
"default": true,
"description": "Set `false` to remove the signed document download button from the completed form card."
},
"withSendCopyButton": {
"type": "boolean",
"required": false,
"default": true,
"description": "Set `false` to remove the signed document send email button from the completed form card."
},
"withCompleteButton": {
"type": "boolean",
"required": false,
"default": false,
"description": "Set `true` to display the complete button in the form header."
},
"allowToResubmit": {
"type": "boolean",
"required": false,
"default": true,
"description": "Set `false` to disallow user to re-submit the form."
},
"signature": {
"type": "string",
"required": false,
"description": "Allows pre-filling signature fields. The value can be a base64 encoded image string, a public URL to an image, or plain text that will be rendered as a typed signature using a standard font."
},
"rememberSignature": {
"type": "boolean",
"required": false,
"description": "Allows to specify whether the signature should be remembered for future use. Remembered signatures are stored in the signer's browser local storage and can be automatically reused to prefill signature fields in new forms for the signer when the value is set to `true`."
},
"reuseSignature": {
"type": "boolean",
"required": false,
"default": true,
"description": "Set `false` to not reuse the signature in the second signature field and collect a new one."
},
"allowTypedSignature": {
"type": "boolean",
"required": false,
"default": true,
"description": "Set `false` to disallow users to type their signature."
},
"values": {
"type": "object",
"required": false,
"description": "Pre-assigned values for form fields.",
"example": "{ 'First Name': 'Jon', 'Last Name': 'Doe' }"
},
"metadata": {
"type": "object",
"required": false,
"description": "Metadata object with additional signer information.",
"example": "{ customData: 'custom value' }"
},
"readonlyFields": {
"type": "array",
"required": false,
"description": "List of read-only fields.",
"example": "['First Name','Last Name']"
},
"customCss": {
"type": "string",
"required": false,
"description": "Custom CSS styles to be applied to the form.",
"example": "#submit_form_button { background-color: #d9d9d9; }"
}
}
```
### Callback
```json
{
"onInit": {
"type": "event emitter",
"required": false,
"description": "Event emitted on initializing the form component.",
"example": "handleInit($event)"
},
"onLoad": {
"type": "event emitter",
"required": false,
"description": "Event emitted on loading the form data.",
"example": "handleLoad($event)"
},
"onComplete": {
"type": "event emitter",
"required": false,
"description": "Event emitted the form completion.",
"example": "handleComplete($event)"
},
"onDecline": {
"type": "event emitter",
"required": false,
"description": "Event emitted after the form decline.",
"example": "handleDecline($event)"
}
}
```
+275
View File
@@ -0,0 +1,275 @@
# JavaScript Signing Form
### Example Code
```javascript
<script src="https://cdn.docuseal.com/js/form.js"></script>
<docuseal-form
id="docusealForm"
data-src="https://docuseal.com/d/{{template_slug}}"
data-email="{{signer_email}}">
</docuseal-form>
<script>
window.docusealForm.addEventListener('completed', (e) => e.detail)
</script>
```
### Attributes
```json
{
"data-src": {
"type": "string",
"required": true,
"description": "Public URL of the document signing form. There are two types of URLs: <li><code>/d/{slug}</code> - template form signing URL can be copied from the template page in the admin dashboard. Also template \"slug\" key can be obtained via the <code>/templates</code> API.</li><li><code>/s/{slug}</code> - individual signer URL. Signer \"slug\" key can be obtained via the <code>/submissions</code> API which is used to initiate signature requests for a template form with recipients.</li>"
},
"data-email": {
"type": "string",
"required": false,
"description": "Email address of the signer. Additional email form step will be displayed if the email attribute is not specified."
},
"data-name": {
"type": "string",
"required": false,
"description": "Name of the signer."
},
"data-role": {
"type": "string",
"required": false,
"description": "The role name or title of the signer.",
"example": "First Party"
},
"data-expand": {
"type": "boolean",
"required": false,
"description": "Expand form on open.",
"default": true
},
"data-minimize": {
"type": "boolean",
"required": false,
"description": "Set to `true` to always minimize form fields. Requires to click on the field to expand the form.",
"default": false
},
"data-order-as-on-page": {
"type": "boolean",
"required": false,
"default": false,
"description": "Order form fields based on their position on the pages."
},
"data-preview": {
"type": "boolean",
"required": false,
"default": false,
"description": "Show form in preview mode without ability to submit it."
},
"data-logo": {
"type": "string",
"required": false,
"description": "Public logo image URL to use in the signing form."
},
"data-language": {
"type": "string",
"required": false,
"description": "UI language: en, es, it, de, fr, nl, pl, uk, cs, pt, he, ar, kr, ja languages are available. Be default the form is displayed in the user browser language automatically."
},
"data-i18n": {
"type": "string",
"required": false,
"default": "{}",
"description": "JSON encoded string that contains i18n keys to replace the default UI text with custom values. See <a href=\"https://github.com/docusealco/docuseal/blob/master/app/javascript/submission_form/i18n.js\" class=\"link\" target=\"_blank\" rel=\"nofollow\">submission_form/i18n.js</a> for available i18n keys."
},
"data-go-to-last": {
"type": "boolean",
"required": false,
"default": true,
"description": "Navigate to the last unfinished step."
},
"data-skip-fields": {
"type": "boolean",
"required": false,
"default": false,
"description": "Allow skipping form fields."
},
"data-autoscroll-fields": {
"type": "boolean",
"required": false,
"default": true,
"description": "Set `false` to disable auto-scrolling to the next document field."
},
"data-send-copy-email": {
"type": "boolean",
"required": false,
"default": true,
"description": "Set `false` to disable automatic email sending with signed documents to the signers. Emails with signed documents are sent to the signers by default."
},
"data-with-title": {
"type": "boolean",
"required": false,
"default": true,
"description": "Set `false` to remove the document title from the form."
},
"data-with-decline": {
"type": "boolean",
"required": false,
"default": false,
"description": "Set `true` to display the decline button in the form."
},
"data-with-field-names": {
"type": "boolean",
"required": false,
"default": true,
"description": "Set `false` to hide field name. Hidding field names can be useful for when they are not in the human readable format. Field names are displayed by default."
},
"data-with-field-placeholder": {
"type": "boolean",
"required": false,
"default": false,
"description": "Set `true` to display field name placeholders instead of the field type icons."
},
"data-with-download-button": {
"type": "boolean",
"required": false,
"default": true,
"description": "Set `false` to remove the signed document download button from the completed form card."
},
"data-with-send-copy-button": {
"type": "boolean",
"required": false,
"default": true,
"description": "Set `false` to remove the signed document send email button from the completed form card."
},
"data-with-complete-button": {
"type": "boolean",
"required": false,
"default": false,
"description": "Set `true` to display the complete button in the form header."
},
"data-allow-to-resubmit": {
"type": "boolean",
"required": false,
"default": true,
"description": "Set `false` to disallow users to re-submit the form."
},
"data-allow-typed-signature": {
"type": "boolean",
"required": false,
"default": true,
"description": "Set `false` to disallow users to type their signature."
},
"data-signature": {
"type": "string",
"required": false,
"description": "Allows pre-filling signature fields. The value can be a base64 encoded image string, a public URL to an image, or plain text that will be rendered as a typed signature using a standard font."
},
"data-remember-signature": {
"type": "boolean",
"required": false,
"description": "Allows to specify whether the signature should be remembered for future use. Remembered signatures are stored in the signer's browser local storage and can be automatically reused to prefill signature fields in new forms for the signer when the value is set to `true`."
},
"data-reuse-signature": {
"type": "boolean",
"required": false,
"default": true,
"description": "Set `false` to not reuse the signature in the second signature field and collect a new one."
},
"data-background-color": {
"type": "string",
"required": false,
"description": "Form background color. Only HEX color codes are supported.",
"example": "#d9d9d9"
},
"data-values": {
"type": "object",
"required": false,
"description": "Pre-assigned values for form fields.",
"example": "{\"First Name\":\"Jon\",\"Last Name\":\"Doe\"}"
},
"data-external-id": {
"type": "string",
"required": false,
"description": "Your application-specific unique string key to identify signer within your app."
},
"data-metadata": {
"type": "object",
"required": false,
"description": "Signer metadata Object in JSON format. ",
"example": "{\"customData\":\"customValue\"}"
},
"data-readonly-fields": {
"type": "string",
"required": false,
"description": "Comma separated read-only field names",
"example": "First Name,Last Name"
},
"data-completed-redirect-url": {
"type": "string",
"required": false,
"description": "URL to redirect to after the submission completion.",
"example": "https://docuseal.com/success"
},
"data-completed-message-title": {
"type": "string",
"required": false,
"description": "Message title displayed after the form completion.",
"example": "Documents have been completed"
},
"data-completed-message-body": {
"type": "string",
"required": false,
"description": "Message body displayed after the form completion.",
"example": "If you have any questions, please contact us."
},
"data-completed-button-title": {
"type": "string",
"required": false,
"description": "Button title displayed after the form completion.",
"example": "Go Back"
},
"data-completed-button-url": {
"type": "string",
"required": false,
"description": "URL of the button displayed after the form completion.",
"example": "https://example.com"
},
"data-custom-css": {
"type": "string",
"required": false,
"description": "Custom CSS styles to be applied to the form.",
"example": "#submit_form_button { background-color: #d9d9d9; }"
}
}
```
### Callback
```json
{
"init": {
"type": "event",
"required": false,
"description": "Custom event to be triggered on initializing the form component.",
"example": "document.querySelector('docuseal-form').addEventListener('init', () => console.log('init'))"
},
"load": {
"type": "event",
"required": false,
"description": "Custom event to be triggered on loading the form data.",
"example": "document.querySelector('docuseal-form').addEventListener('load', (e) => e.detail)"
},
"completed": {
"type": "event",
"required": false,
"description": "Custom event to be triggered after form completion.",
"example": "document.querySelector('docuseal-form').addEventListener('completed', (e) => e.detail)"
},
"declined": {
"type": "event",
"description": "Custom event to be triggered after form decline.",
"example": "document.querySelector('docuseal-form').addEventListener('declined', (e) => e.detail)"
}
}
```
+292
View File
@@ -0,0 +1,292 @@
# React Signing Form
### Example Code
```react
import React from "react"
import { DocusealForm } from '@docuseal/react'
export function App() {
return (
<div className="app">
<DocusealForm
src="https://docuseal.com/d/{{template_slug}}"
email="{{signer_email}}"
onComplete={(data) => console.log(data)}
/>
</div>
);
}
```
### Attributes
```json
{
"src": {
"type": "string",
"required": true,
"description": "Public URL of the document signing form. There are two types of URLs: <li><code>/d/{slug}</code> - template form signing URL can be copied from the template page in the admin dashboard. Also template \"slug\" key can be obtained via the <code>/templates</code> API.</li><li><code>/s/{slug}</code> - individual signer URL. Signer \"slug\" key can be obtained via the <code>/submissions</code> API which is used to initiate signature requests for a template form with recipients.</li>"
},
"email": {
"type": "string",
"required": false,
"description": "Email address of the signer. Additional email form step will be displayed if the email attribute is not specified."
},
"name": {
"type": "string",
"required": false,
"description": "Name of the signer."
},
"role": {
"type": "string",
"required": false,
"description": "The role name or title of the signer.",
"example": "First Party"
},
"expand": {
"type": "boolean",
"required": false,
"description": "Expand form on open.",
"default": true
},
"minimize": {
"type": "boolean",
"required": false,
"description": "Set to `true` to always minimize form fields. Requires to click on the field to expand the form.",
"default": false
},
"orderAsOnPage": {
"type": "boolean",
"required": false,
"default": false,
"description": "Order form fields based on their position on the pages."
},
"externalId": {
"type": "string",
"required": false,
"description": "Your application-specific unique string key to identify signer within your app."
},
"logo": {
"type": "string",
"required": false,
"description": "Public logo image URL to use in the signing form."
},
"language": {
"type": "string",
"required": false,
"description": "UI language: en, es, it, de, fr, nl, pl, uk, cs, pt, he, ar, kr, ja languages are available. Be default the form is displayed in the user browser language automatically."
},
"i18n": {
"type": "object",
"required": false,
"default": "{}",
"description": "Object that contains i18n keys to replace the default UI text with custom values. See <a href=\"https://github.com/docusealco/docuseal/blob/master/app/javascript/submission_form/i18n.js\" class=\"link\" target=\"_blank\" rel=\"nofollow\">submission_form/i18n.js</a> for available i18n keys."
},
"preview": {
"type": "boolean",
"required": false,
"default": false,
"description": "Show form in preview mode without ability to submit it."
},
"goToLast": {
"type": "boolean",
"required": false,
"default": true,
"description": "Navigate to the last unfinished step."
},
"withFieldNames": {
"type": "boolean",
"required": false,
"default": true,
"description": "Set `false` to hide field name. Hidding field names can be useful for when they are not in the human readable format. Field names are displayed by default."
},
"withFieldPlaceholder": {
"type": "boolean",
"required": false,
"default": false,
"description": "Set `true` to display field name placeholders instead of the field type icons."
},
"skipFields": {
"type": "boolean",
"required": false,
"default": false,
"description": "Allow skipping form fields."
},
"autoscrollFields": {
"type": "boolean",
"required": false,
"default": true,
"description": "Set `false` to disable auto-scrolling to the next document field."
},
"sendCopyEmail": {
"type": "boolean",
"required": false,
"description": "Set `false` to disable automatic email sending with signed documents to the signers. Emails with signed documents are sent to the signers by default."
},
"backgroundColor": {
"type": "string",
"required": false,
"description": "Form background color. Only HEX color codes are supported.",
"example": "#d9d9d9"
},
"completedRedirectUrl": {
"type": "string",
"required": false,
"description": "URL to redirect to after the submission completion.",
"example": "https://docuseal.com/success"
},
"completedMessage": {
"type": "object",
"required": false,
"description": "Message displayed after the form completion.",
"properties": {
"title": {
"type": "string",
"required": false,
"description": "Message title.",
"example": "Documents have been signed!"
},
"body": {
"type": "string",
"required": false,
"description": "Message content.",
"example": "If you have any questions, please contact us."
}
}
},
"completedButton": {
"type": "object",
"required": false,
"description": "Customizable button after form completion.",
"properties": {
"title": {
"type": "string",
"required": true,
"description": "Button label.",
"example": "Go Back"
},
"url": {
"type": "string",
"required": true,
"description": "Button link. Only absolute URLs are supported.",
"example": "https://example.com"
}
}
},
"withTitle": {
"type": "boolean",
"required": false,
"default": true,
"description": "Set `false` to remove the document title from the form."
},
"withDecline": {
"type": "boolean",
"required": false,
"default": false,
"description": "Set `true` to display the decline button in the form."
},
"withDownloadButton": {
"type": "boolean",
"required": false,
"default": true,
"description": "Set `false` to remove the signed document download button from the completed form card."
},
"withSendCopyButton": {
"type": "boolean",
"required": false,
"default": true,
"description": "Set `false` to remove the signed document send email button from the completed form card."
},
"withCompleteButton": {
"type": "boolean",
"required": false,
"default": false,
"description": "Set `true` to display the complete button in the form header."
},
"allowToResubmit": {
"type": "boolean",
"required": false,
"default": true,
"description": "Set `false` to disallow user to re-submit the form."
},
"allowTypedSignature": {
"type": "boolean",
"required": false,
"default": true,
"description": "Set `false` to disallow users to type their signature."
},
"signature": {
"type": "string",
"required": false,
"description": "Allows pre-filling signature fields. The value can be a base64 encoded image string, a public URL to an image, or plain text that will be rendered as a typed signature using a standard font."
},
"rememberSignature": {
"type": "boolean",
"required": false,
"description": "Allows to specify whether the signature should be remembered for future use. Remembered signatures are stored in the signer's browser local storage and can be automatically reused to prefill signature fields in new forms for the signer when the value is set to `true`."
},
"reuseSignature": {
"type": "boolean",
"required": false,
"default": true,
"description": "Set `false` to not reuse the signature in the second signature field and collect a new one."
},
"values": {
"type": "object",
"required": false,
"description": "Pre-assigned values for form fields.",
"example": "{ 'First Name': 'Jon', 'Last Name': 'Doe' }"
},
"metadata": {
"type": "object",
"required": false,
"description": "Metadata object with additional signer information.",
"example": "{ customData: 'custom value' }"
},
"readonlyFields": {
"type": "array",
"required": false,
"description": "List of read-only fields.",
"example": "['First Name','Last Name']"
},
"customCss": {
"type": "string",
"required": false,
"description": "Custom CSS styles to be applied to the form.",
"example": "#submit_form_button { background-color: #d9d9d9; }"
}
}
```
### Callback
```json
{
"onInit": {
"type": "function",
"required": false,
"description": "Function to be called on initializing the form component.",
"example": "() => { console.log(\"Loaded\") }"
},
"onLoad": {
"type": "function",
"required": false,
"description": "Function to be called on loading the form data.",
"example": "(data) => { console.log(data) }"
},
"onComplete": {
"type": "function",
"required": false,
"description": "Function to be called after the form completion.",
"example": "(data) => { console.log(data) }"
},
"onDecline": {
"type": "function",
"required": false,
"description": "Function to be called after the form decline.",
"example": "(data) => { console.log(data) }"
}
}
```
+302
View File
@@ -0,0 +1,302 @@
# Vue Signing Form
### Example Code
```vue
<template>
<DocusealForm
:src="'https://docuseal.com/d/{{template_slug}}'"
:email="'{{signer_email}}'"
@complete="onFormComplete"
/>
</template>
<script>
import { DocusealForm } from '@docuseal/vue'
export default {
name: 'App',
components: {
DocusealForm
},
methods: {
onFormComplete (data) {
console.log(data)
}
}
}
</script>
```
### Attributes
```json
{
"src": {
"type": "string",
"required": true,
"description": "Public URL of the document signing form. There are two types of URLs: <li><code>/d/{slug}</code> - template form signing URL can be copied from the template page in the admin dashboard. Also template \"slug\" key can be obtained via the <code>/templates</code> API.</li><li><code>/s/{slug}</code> - individual signer URL. Signer \"slug\" key can be obtained via the <code>/submissions</code> API which is used to initiate signature requests for a template form with recipients.</li>"
},
"email": {
"type": "string",
"required": false,
"description": "Email address of the signer. Additional email form step will be displayed if the email attribute is not specified."
},
"name": {
"type": "string",
"required": false,
"description": "Name of the signer."
},
"role": {
"type": "string",
"required": false,
"description": "The role name or title of the signer.",
"example": "First Party"
},
"external-id": {
"type": "string",
"required": false,
"description": "Your application-specific unique string key to identify signer within your app."
},
"expand": {
"type": "boolean",
"required": false,
"description": "Expand form on open.",
"default": true
},
"minimize": {
"type": "boolean",
"required": false,
"description": "Set to `true` to always minimize form fields. Requires to click on the field to expand the form.",
"default": false
},
"order-as-on-page": {
"type": "boolean",
"required": false,
"default": false,
"description": "Order form fields based on their position on the pages."
},
"logo": {
"type": "string",
"required": false,
"description": "Public logo image URL to use in the signing form."
},
"language": {
"type": "string",
"required": false,
"description": "UI language: en, es, it, de, fr, nl, pl, uk, cs, pt, he, ar, kr, ja languages are available. Be default the form is displayed in the user browser language automatically."
},
"i18n": {
"type": "object",
"required": false,
"default": "{}",
"description": "Object that contains i18n keys to replace the default UI text with custom values. See <a href=\"https://github.com/docusealco/docuseal/blob/master/app/javascript/submission_form/i18n.js\" class=\"link\" target=\"_blank\" rel=\"nofollow\">submission_form/i18n.js</a> for available i18n keys."
},
"preview": {
"type": "boolean",
"required": false,
"default": false,
"description": "Show form in preview mode without ability to submit it."
},
"go-to-last": {
"type": "boolean",
"required": false,
"default": true,
"description": "Navigate to the last unfinished step."
},
"skip-fields": {
"type": "boolean",
"required": false,
"default": false,
"description": "Allow skipping form fields."
},
"autoscroll-fields": {
"type": "boolean",
"required": false,
"default": true,
"description": "Set `false` to disable auto-scrolling to the next document field."
},
"with-field-names": {
"type": "boolean",
"required": false,
"default": true,
"description": "Set `false` to hide field name. Hidding field names can be useful for when they are not in the human readable format. Field names are displayed by default."
},
"with-field-placeholder": {
"type": "boolean",
"required": false,
"default": false,
"description": "Set `true` to display field name placeholders instead of the field type icons."
},
"send-copy-email": {
"type": "boolean",
"required": false,
"default": true,
"description": "Set `false` to disable automatic email sending with signed documents to the signers. Emails with signed documents are sent to the signers by default."
},
"background-color": {
"type": "string",
"required": false,
"description": "Form background color. Only HEX color codes are supported.",
"example": "#d9d9d9"
},
"with-title": {
"type": "boolean",
"required": false,
"default": true,
"description": "Set `false` to remove the document title from the form."
},
"with-decline": {
"type": "boolean",
"required": false,
"default": false,
"description": "Set `true` to display the decline button in the form."
},
"with-download-button": {
"type": "boolean",
"required": false,
"default": true,
"description": "Set `false` to remove the signed document download button from the completed form card."
},
"with-send-copy-button": {
"type": "boolean",
"required": false,
"default": true,
"description": "Set `false` to remove the signed document send email button from the completed form card."
},
"with-complete-button": {
"type": "boolean",
"required": false,
"default": false,
"description": "Set `true` to display the complete button in the form header."
},
"allow-to-resubmit": {
"type": "boolean",
"required": false,
"default": true,
"description": "Set `false` to disallow user to re-submit the form."
},
"signature": {
"type": "string",
"required": false,
"description": "Allows pre-filling signature fields. The value can be a base64 encoded image string, a public URL to an image, or plain text that will be rendered as a typed signature using a standard font."
},
"remember-signature": {
"type": "boolean",
"required": false,
"description": "Allows to specify whether the signature should be remembered for future use. Remembered signatures are stored in the signer's browser local storage and can be automatically reused to prefill signature fields in new forms for the signer when the value is set to `true`."
},
"reuse-signature": {
"type": "boolean",
"required": false,
"default": true,
"description": "Set `false` to not reuse the signature in the second signature field and collect a new one."
},
"allow-typed-signature": {
"type": "boolean",
"required": false,
"default": true,
"description": "Set `false` to disallow users to type their signature."
},
"completed-redirect-url": {
"type": "string",
"required": false,
"description": "URL to redirect to after the submission completion.",
"example": "https://docuseal.com/success"
},
"completed-message": {
"type": "object",
"required": false,
"description": "Message displayed after the form completion.",
"properties": {
"title": {
"type": "string",
"required": false,
"description": "Message title.",
"example": "Documents have been signed!"
},
"body": {
"type": "string",
"required": false,
"description": "Message content.",
"example": "If you have any questions, please contact us."
}
}
},
"completed-button": {
"type": "object",
"required": false,
"description": "Customizable button after form completion.",
"properties": {
"title": {
"type": "string",
"required": true,
"description": "Button label.",
"example": "Go Back"
},
"url": {
"type": "string",
"required": true,
"description": "Button link. Only absolute URLs are supported.",
"example": "https://example.com"
}
}
},
"values": {
"type": "object",
"required": false,
"description": "Pre-assigned values for form fields.",
"example": "{ 'First Name': 'Jon', 'Last Name': 'Doe' }"
},
"metadata": {
"type": "object",
"required": false,
"description": "Metadata object with additional signer information.",
"example": "{ customData: 'custom value' }"
},
"readonly-fields": {
"type": "array",
"required": false,
"description": "List of read-only fields.",
"example": "['First Name','Last Name']"
},
"custom-css": {
"type": "string",
"required": false,
"description": "Custom CSS styles to be applied to the form.",
"example": "#submit_form_button { background-color: #d9d9d9; }"
}
}
```
### Callback
```json
{
"@init": {
"type": "function",
"required": false,
"description": "Function to be called on initializing the form component.",
"example": "onFormLoad"
},
"@load": {
"type": "function",
"required": false,
"description": "Function to be called on loading the form data.",
"example": "onFormLoad"
},
"@complete": {
"type": "function",
"required": false,
"description": "Function to be called after the form completion.",
"example": "onFormComplete"
},
"@decline": {
"type": "function",
"required": false,
"description": "Function to be called after the form decline.",
"example": "onFormDecline"
}
}
```
+7568
View File
File diff suppressed because it is too large Load Diff
+247
View File
@@ -0,0 +1,247 @@
# Form Webhook
During the form filling and signing process, 3 types of events may occur and are dispatched at different stages:
- **'form.viewed'** event is triggered when the submitter first opens the form.
- **'form.started'** event is triggered when the submitter initiates filling out the form.
- **'form.completed'** event is triggered upon successful form completion and signing by one of the parties.
- **'form.declined'** event is triggered when a signer declines the submission.
It's important to note that each of these events contain information available at the time of dispatch, so some data may be missing or incomplete depending on the specific event. Failed webhook requests (4xx, 5xx) are automatically retried multiple times within 48 hours (every 2^attempt minutes) for all production accounts.
**Related Guides**
[Download Signed Documents](https://www.docuseal.com/guides/download-signed-documents)
```json
{
"event_type": {
"type": "string",
"description": "The event type.",
"enum": [
"form.viewed",
"form.started",
"form.completed"
]
},
"timestamp": {
"type": "string",
"description": "The event timestamp.",
"example": "2023-09-24T11:20:42Z",
"format": "date-time"
},
"data": {
"type": "object",
"description": "Submitted data object.",
"properties": {
"id": {
"type": "number",
"description": "The submitter's unique identifier."
},
"submission_id": {
"type": "number",
"description": "The unique submission identifier."
},
"email": {
"type": "string",
"description": "The submitter's email address",
"format": "email",
"example": "john.doe@example.com"
},
"ua": {
"type": "string",
"description": "The user agent string that provides information about the submitter's web browser.",
"example": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36"
},
"ip": {
"type": "string",
"description": "The submitter's IP address."
},
"name": {
"type": "string",
"description": "The submitter's name."
},
"phone": {
"type": "string",
"description": "The submitter's phone number, formatted according to the E.164 standard.",
"example": "+1234567890"
},
"role": {
"type": "string",
"description": "The submitter's role name or title.",
"example": "First Party"
},
"external_id": {
"type": "string",
"description": "Your application-specific unique string key to identify submitter within your app."
},
"application_key": {
"type": "string",
"description": "Your application-specific unique string key to identify submitter within your app. Backward compatibility with the previous version of the API. Use external_id instead."
},
"decline_reason": {
"type": "string",
"description": "Submitter provided decline message."
},
"sent_at": {
"type": "string",
"format": "date-time"
},
"status": {
"type": "string",
"description": "The submitter status.",
"enum": [
"completed",
"declined",
"opened",
"sent",
"awaiting"
]
},
"opened_at": {
"type": "string",
"format": "date-time"
},
"completed_at": {
"type": "string",
"format": "date-time"
},
"declined_at": {
"type": "string",
"format": "date-time"
},
"created_at": {
"type": "string",
"format": "date-time"
},
"updated_at": {
"type": "string",
"format": "date-time"
},
"submission": {
"type": "object",
"description": "The submission details.",
"properties": {
"id": {
"type": "number",
"description": "The submission's unique identifier."
},
"audit_log_url": {
"type": "string",
"description": "The audit log PDF URL. Available only if the submission was completed by all submitters."
},
"combined_document_url": {
"type": "string",
"description": "The URL of the combined documents with audit log. Combined documents can be enabled via <a href=\"https://docuseal.com/settings/account\" target=\"_blank\" class=\"link\">/settings/accounts</a>."
},
"status": {
"type": "string",
"description": "The submission status.",
"enum": [
"completed",
"declined",
"expired",
"pending"
]
},
"url": {
"type": "string",
"description": "The submission URL."
},
"created_at": {
"type": "string",
"description": "The submission creation date.",
"format": "date-time"
}
}
},
"template": {
"type": "object",
"description": "Base template details.",
"properties": {
"id": {
"type": "number",
"description": "The template's unique identifier."
},
"name": {
"type": "string",
"description": "The template's name."
},
"external_id": {
"type": "string",
"description": "Your application-specific unique string key to identify template within your app."
},
"created_at": {
"type": "string",
"format": "date-time"
},
"updated_at": {
"type": "string",
"format": "date-time"
},
"folder_name": {
"type": "string",
"description": "Template folder name."
}
}
},
"preferences": {
"type": "object",
"properties": {
"send_email": {
"type": "boolean",
"description": "The flag indicating whether the submitter has opted to receive an email."
},
"send_sms": {
"type": "boolean",
"description": "The flag indicating whether the submitter has opted to receive an SMS."
}
}
},
"values": {
"type": "array",
"description": "List of the filled values passed by the submitter.",
"items": {
"type": "object",
"properties": {
"field": {
"type": "string",
"description": "The field name."
},
"values": {
"type": "string",
"description": "The field value."
}
}
}
},
"metadata": {
"type": "object",
"description": "Metadata object with additional submitter information."
},
"audit_log_url": {
"type": "string",
"description": "The audit log PDF URL. Available only if the submission was completed by all submitters."
},
"submission_url": {
"type": "string",
"description": "The submission URL."
},
"documents": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "The document file name."
},
"url": {
"type": "string",
"description": "The document file URL."
}
}
}
}
}
}
}
```
+317
View File
@@ -0,0 +1,317 @@
# Submission Webhook
Get submission creation, completion, expiration, and archiving notifications using these events:
- **'submission.created'** event is triggered when the submission is created.
- **'submission.completed'** event is triggered when the submission is completed by all signing parties.
- **'submission.expired'** event is triggered when the submission expires.
- **'submission.archived'** event is triggered when the submission is archived.
```json
{
"event_type": {
"type": "string",
"description": "The event type.",
"enum": [
"submission.created",
"submission.archived"
]
},
"timestamp": {
"type": "string",
"description": "The event timestamp.",
"example": "2023-09-24T11:20:42Z",
"format": "date-time"
},
"data": {
"type": "object",
"description": "Submitted data object.",
"properties": {
"id": {
"type": "number",
"description": "The submission's unique identifier."
},
"archived_at": {
"type": "string",
"description": "The submission archive date."
},
"created_at": {
"type": "string",
"description": "The submission creation date."
},
"updated_at": {
"type": "string",
"description": "The submission update date."
},
"source": {
"type": "string",
"description": "The submission source.",
"enum": [
"invite",
"bulk",
"api",
"embed",
"link"
]
},
"submitters_order": {
"type": "string",
"description": "The submitters order.",
"enum": [
"random",
"preserved"
]
},
"audit_log_url": {
"type": "string",
"description": "Audit log file URL."
},
"submitters": {
"type": "array",
"description": "The list of submitters for the submission.",
"items": {
"type": "object",
"properties": {
"id": {
"type": "number",
"description": "The submitter's unique identifier."
},
"submission_id": {
"type": "number",
"description": "The unique submission identifier."
},
"uuid": {
"type": "string",
"description": "The submitter UUID."
},
"email": {
"type": "string",
"description": "The email address of the submitter.",
"format": "email",
"example": "john.doe@example.com"
},
"slug": {
"type": "string",
"description": "The unique slug of the document template."
},
"sent_at": {
"type": "string",
"description": "The date and time when the signing request was sent to the submitter."
},
"opened_at": {
"type": "string",
"description": "The date and time when the submitter opened the signing form."
},
"completed_at": {
"type": "string",
"description": "The date and time when the submitter completed the signing form."
},
"declined_at": {
"type": "string",
"description": "The date and time when the submitter declined the signing form."
},
"created_at": {
"type": "string",
"description": "The date and time when the submitter was created."
},
"updated_at": {
"type": "string",
"description": "The date and time when the submitter was last updated."
},
"name": {
"type": "string",
"description": "The name of the submitter."
},
"phone": {
"type": "string",
"description": "The phone number of the submitter, formatted according to the E.164 standard.",
"example": "+1234567890"
},
"role": {
"type": "string",
"description": "The role name or title of the submitter.",
"example": "First Party"
},
"external_id": {
"type": "string",
"description": "Your application-specific unique string key to identify this submitter within your app."
},
"metadata": {
"type": "object",
"description": "Metadata object with additional submitter information.",
"example": "{ 'customField': 'value' }"
},
"status": {
"type": "string",
"description": "The submitter status.",
"enum": [
"completed",
"declined",
"opened",
"sent",
"awaiting"
]
},
"application_key": {
"type": "string",
"description": "Your application-specific unique string key to identify this submitter within your app."
},
"values": {
"type": "object",
"description": "An object with pre-filled values for the submission. Use field names for keys of the object. For more configurations see `fields` param."
},
"documents": {
"type": "array",
"description": "The list of documents for the submission.",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "The document file name."
},
"url": {
"type": "string",
"description": "The document file URL."
}
}
}
},
"preferences": {
"type": "object",
"description": "The submitter preferences."
}
}
}
},
"template": {
"type": "object",
"description": "Base template details.",
"properties": {
"id": {
"type": "number",
"description": "The template's unique identifier."
},
"name": {
"type": "string",
"description": "The template's name."
},
"external_id": {
"type": "string",
"description": "Your application-specific unique string key to identify template within your app."
},
"folder_name": {
"type": "string",
"description": "The folder name."
},
"created_at": {
"type": "string",
"description": "The date and time when the template was created."
},
"updated_at": {
"type": "string",
"description": "The date and time when the template was last updated."
}
}
},
"created_by_user": {
"type": "object",
"properties": {
"id": {
"type": "integer",
"description": "Unique identifier of the user who created the submission."
},
"first_name": {
"type": "string",
"description": "The first name of the user who created the submission."
},
"last_name": {
"type": "string",
"description": "The last name of the user who created the submission."
},
"email": {
"type": "string",
"description": "The email address of the user who created the submission."
}
}
},
"submission_events": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "integer",
"description": "Submission event unique ID number."
},
"submitter_id": {
"type": "integer",
"description": "Unique identifier of the submitter that triggered the event."
},
"event_type": {
"type": "string",
"description": "Event type.",
"enum": [
"send_email",
"bounce_email",
"complaint_email",
"send_reminder_email",
"send_sms",
"send_2fa_sms",
"open_email",
"click_email",
"click_sms",
"phone_verified",
"start_form",
"start_verification",
"complete_verification",
"view_form",
"invite_party",
"complete_form",
"decline_form",
"api_complete_form"
]
},
"event_timestamp": {
"type": "string",
"description": "Date and time when the event was triggered."
}
}
}
},
"documents": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Document name."
},
"url": {
"type": "string",
"description": "Document URL."
}
}
}
},
"status": {
"type": "string",
"description": "The status of the submission.",
"enum": [
"completed",
"declined",
"expired",
"pending"
]
},
"completed_at": {
"type": "string",
"description": "The date and time when the submission was fully completed."
}
}
}
}
```
+235
View File
@@ -0,0 +1,235 @@
# Template Webhook
Get template creation and update notifications using these events:
- **'template.created'** is triggered when the template is created.
- **'tempate.updated'** is triggered when the template is updated.
```json
{
"event_type": {
"type": "string",
"description": "The event type.",
"enum": [
"template.created",
"template.updated"
]
},
"timestamp": {
"type": "string",
"description": "The event timestamp.",
"example": "2023-09-24T11:20:42Z",
"format": "date-time"
},
"data": {
"type": "object",
"description": "Submitted data object.",
"properties": {
"id": {
"type": "number",
"description": "The template's unique identifier."
},
"slug": {
"type": "string",
"description": "The template's unique slug."
},
"name": {
"type": "string",
"description": "The template's name."
},
"schema": {
"type": "array",
"description": "The template document files.",
"items": {
"type": "object",
"properties": {
"attachment_uuid": {
"type": "string",
"description": "The attachment UUID."
},
"name": {
"type": "string",
"description": "The attachment name."
}
}
}
},
"fields": {
"type": "array",
"description": "The template fields.",
"items": {
"type": "object",
"properties": {
"uuid": {
"type": "string",
"description": "The field UUID."
},
"submitter_uuid": {
"type": "string",
"description": "The submitter role UUID."
},
"name": {
"type": "string",
"description": "The field name."
},
"required": {
"type": "boolean",
"description": "The flag indicating whether the field is required."
},
"preferences": {
"type": "object",
"description": "The field preferences."
},
"areas": {
"type": "array",
"description": "List of areas where the field is located in the document.",
"items": {
"type": "object",
"properties": {
"x": {
"type": "number",
"description": "X coordinate of the area where the field is located in the document."
},
"y": {
"type": "number",
"description": "Y coordinate of the area where the field is located in the document."
},
"w": {
"type": "number",
"description": "Width of the area where the field is located in the document."
},
"h": {
"type": "number",
"description": "Height of the area where the field is located in the document."
},
"attachment_uuid": {
"type": "string",
"description": "Unique identifier of the attached document where the field is located."
},
"page": {
"type": "integer",
"description": "Page number of the attached document where the field is located."
}
}
}
}
}
}
},
"submitters": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Submitter name."
},
"uuid": {
"type": "string",
"description": "Unique identifier of the submitter."
}
}
}
},
"author_id": {
"type": "integer",
"description": "Unique identifier of the author of the template."
},
"account_id": {
"type": "integer",
"description": "Unique identifier of the account of the template."
},
"archived_at": {
"type": "string",
"description": "Date and time when the template was archived."
},
"created_at": {
"type": "string",
"description": "Date and time when the template was created."
},
"updated_at": {
"type": "string",
"description": "Date and time when the template was updated."
},
"source": {
"type": "string",
"description": "Source of the template.",
"enum": [
"native",
"api",
"embed"
]
},
"external_id": {
"type": "string",
"description": "Identifier of the template in the external system."
},
"folder_id": {
"type": "integer",
"description": "Unique identifier of the folder where the template is placed."
},
"folder_name": {
"type": "string",
"description": "Folder name where the template is placed."
},
"application_key": {
"type": "string",
"description": "Your application-specific unique string key to identify tempate_id within your app."
},
"author": {
"type": "object",
"properties": {
"id": {
"type": "integer",
"description": "Unique identifier of the author."
},
"first_name": {
"type": "string",
"description": "First name of the author."
},
"last_name": {
"type": "string",
"description": "Last name of the author."
},
"email": {
"type": "string",
"description": "Author email."
}
}
},
"documents": {
"type": "array",
"description": "List of documents attached to the template.",
"items": {
"type": "object",
"properties": {
"id": {
"type": "integer",
"description": "Unique identifier of the document."
},
"uuid": {
"type": "string",
"description": "Unique identifier of the document."
},
"url": {
"type": "string",
"description": "URL of the document."
},
"preview_image_url": {
"type": "string",
"description": "Document preview image URL."
},
"filename": {
"type": "string",
"description": "Document filename."
}
}
}
}
}
}
}
```
+4
View File
@@ -9,7 +9,11 @@ module AccountConfigs
'twelve_hours' => '12 hours',
'twenty_four_hours' => '24 hours',
'two_days' => '2 days',
'three_days' => '3 days',
'four_days' => '4 days',
'five_days' => '5 days',
'six_days' => '6 days',
'seven_days' => '7 days',
'eight_days' => '8 days',
'fifteen_days' => '15 days'
}.freeze

Some files were not shown because too many files have changed in this diff Show More