mirror of
https://github.com/docusealco/docuseal
synced 2026-11-05 06:42:32 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d870b2305 | ||
|
|
27d8cd57bd | ||
|
|
353b2e7a94 | ||
|
|
d77bd87acb | ||
|
|
bae7b924ae | ||
|
|
89867fdde3 | ||
|
|
c9a1f067ac | ||
|
|
eb26835d57 | ||
|
|
7301661468 | ||
|
|
e4fe984a70 | ||
|
|
12ccf622c1 | ||
|
|
6c35ff6d1c | ||
|
|
1d7c84302e | ||
|
|
f3e8889710 | ||
|
|
4b2f9ff306 | ||
|
|
b4ba642f8e | ||
|
|
5c69b965c0 | ||
|
|
cba196385c | ||
|
|
00abad56cb | ||
|
|
d1070c4955 | ||
|
|
e84be523c6 | ||
|
|
5d598e8268 | ||
|
|
0e9d922204 | ||
|
|
80c1b04e83 | ||
|
|
b2fca3edf3 | ||
|
|
2a292c8e80 | ||
|
|
e6052ad0d8 | ||
|
|
8646e17d5a | ||
|
|
e6785b0afd | ||
|
|
009033a411 | ||
|
|
fdc3d7742a | ||
|
|
f9ccca1376 | ||
|
|
0a00fe6ab8 | ||
|
|
6253e80a70 | ||
|
|
3dcb7c813f | ||
|
|
39cdc41a67 | ||
|
|
d3000487f4 | ||
|
|
80254da1c1 | ||
|
|
73fc2d92f7 | ||
|
|
f69aa33f3d | ||
|
|
e16202c9f7 | ||
|
|
7453d0d274 | ||
|
|
c102828c93 | ||
|
|
afeb6dd318 | ||
|
|
6156623f0e | ||
|
|
ccc06fcc3d | ||
|
|
bbe3942056 | ||
|
|
49cea59b94 |
No files matched your search
+1
-1
@@ -43,7 +43,7 @@ RUN apk add --no-cache build-base sqlite-dev libpq-dev mariadb-dev vips-dev vips
|
|||||||
|
|
||||||
COPY ./Gemfile ./Gemfile.lock ./
|
COPY ./Gemfile ./Gemfile.lock ./
|
||||||
|
|
||||||
RUN bundle update --bundler && bundle install && rm -rf ~/.bundle /usr/local/bundle/cache && find /usr/local/bundle -type d \( -name "rdoc" -o -name "spec" \) -exec rm -rf {} +
|
RUN bundle update --bundler && bundle install && rm -rf ~/.bundle /usr/local/bundle/cache && ruby -e "puts Dir['/usr/local/bundle/**/{spec,rdoc,resources/shared,resources/collation,resources/locales}']" | xargs rm -rf
|
||||||
|
|
||||||
COPY ./bin ./bin
|
COPY ./bin ./bin
|
||||||
COPY ./app ./app
|
COPY ./app ./app
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ gem 'pagy'
|
|||||||
gem 'pdf-reader'
|
gem 'pdf-reader'
|
||||||
gem 'pg', require: false
|
gem 'pg', require: false
|
||||||
gem 'premailer-rails'
|
gem 'premailer-rails'
|
||||||
|
gem 'pretender'
|
||||||
gem 'puma'
|
gem 'puma'
|
||||||
gem 'rack'
|
gem 'rack'
|
||||||
gem 'rails'
|
gem 'rails'
|
||||||
|
|||||||
@@ -354,6 +354,8 @@ GEM
|
|||||||
actionmailer (>= 3)
|
actionmailer (>= 3)
|
||||||
net-smtp
|
net-smtp
|
||||||
premailer (~> 1.7, >= 1.7.9)
|
premailer (~> 1.7, >= 1.7.9)
|
||||||
|
pretender (0.5.0)
|
||||||
|
actionpack (>= 6.1)
|
||||||
pry (0.14.2)
|
pry (0.14.2)
|
||||||
coderay (~> 1.1)
|
coderay (~> 1.1)
|
||||||
method_source (~> 1.0)
|
method_source (~> 1.0)
|
||||||
@@ -596,6 +598,7 @@ DEPENDENCIES
|
|||||||
pdf-reader
|
pdf-reader
|
||||||
pg
|
pg
|
||||||
premailer-rails
|
premailer-rails
|
||||||
|
pretender
|
||||||
pry-rails
|
pry-rails
|
||||||
puma
|
puma
|
||||||
rack
|
rack
|
||||||
|
|||||||
@@ -37,6 +37,18 @@ class AccountsController < ApplicationController
|
|||||||
render :show, status: :unprocessable_entity
|
render :show, status: :unprocessable_entity
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
authorize!(:manage, current_account)
|
||||||
|
|
||||||
|
true_user.update!(locked_at: Time.current)
|
||||||
|
|
||||||
|
render turbo_stream: turbo_stream.replace(
|
||||||
|
:account_delete_button,
|
||||||
|
html: helpers.tag.p('Your account removal request will be processed within 2 weeks. ' \
|
||||||
|
'Please contact us if you want to keep your account.')
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def load_account
|
def load_account
|
||||||
|
|||||||
@@ -8,11 +8,17 @@ module Api
|
|||||||
DEFAULT_LIMIT = 10
|
DEFAULT_LIMIT = 10
|
||||||
MAX_LIMIT = 100
|
MAX_LIMIT = 100
|
||||||
|
|
||||||
|
impersonates :user, with: ->(uuid) { User.find_by(uuid:) }
|
||||||
|
|
||||||
wrap_parameters false
|
wrap_parameters false
|
||||||
|
|
||||||
before_action :authenticate_user!
|
before_action :authenticate_user!
|
||||||
check_authorization
|
check_authorization
|
||||||
|
|
||||||
|
rescue_from Params::BaseValidator::InvalidParameterError do |e|
|
||||||
|
render json: { error: e.message }, status: :unprocessable_entity
|
||||||
|
end
|
||||||
|
|
||||||
if Rails.env.production?
|
if Rails.env.production?
|
||||||
rescue_from CanCan::AccessDenied do |e|
|
rescue_from CanCan::AccessDenied do |e|
|
||||||
Rollbar.error(e) if defined?(Rollbar)
|
Rollbar.error(e) if defined?(Rollbar)
|
||||||
@@ -44,7 +50,7 @@ module Api
|
|||||||
if request.headers['X-Auth-Token'].present?
|
if request.headers['X-Auth-Token'].present?
|
||||||
sha256 = Digest::SHA256.hexdigest(request.headers['X-Auth-Token'])
|
sha256 = Digest::SHA256.hexdigest(request.headers['X-Auth-Token'])
|
||||||
|
|
||||||
User.joins(:access_token).find_by(access_token: { sha256: })
|
User.joins(:access_token).active.find_by(access_token: { sha256: })
|
||||||
end
|
end
|
||||||
|
|
||||||
render json: { error: 'Not authenticated' }, status: :unauthorized unless current_user
|
render json: { error: 'Not authenticated' }, status: :unauthorized unless current_user
|
||||||
|
|||||||
@@ -63,6 +63,8 @@ module Api
|
|||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
|
Params::SubmissionCreateValidator.call(params, dry_run: !Docuseal.multitenant?)
|
||||||
|
|
||||||
return render json: { error: 'Template not found' }, status: :unprocessable_entity if @template.nil?
|
return render json: { error: 'Template not found' }, status: :unprocessable_entity if @template.nil?
|
||||||
|
|
||||||
if @template.fields.blank?
|
if @template.fields.blank?
|
||||||
@@ -94,7 +96,8 @@ module Api
|
|||||||
def create_submissions(template, params)
|
def create_submissions(template, params)
|
||||||
is_send_email = !params[:send_email].in?(['false', false])
|
is_send_email = !params[:send_email].in?(['false', false])
|
||||||
|
|
||||||
if (emails = (params[:emails] || params[:email]).presence) && params[:submission].blank?
|
if (emails = (params[:emails] || params[:email]).presence) &&
|
||||||
|
(params[:submission].blank? && params[:submitters].blank?)
|
||||||
Submissions.create_from_emails(template:,
|
Submissions.create_from_emails(template:,
|
||||||
user: current_user,
|
user: current_user,
|
||||||
source: :api,
|
source: :api,
|
||||||
@@ -138,20 +141,27 @@ module Api
|
|||||||
end
|
end
|
||||||
|
|
||||||
def submissions_params
|
def submissions_params
|
||||||
key = params.key?(:submission) ? :submission : :submissions
|
permitted_attrs = [
|
||||||
|
:send_email, :send_sms, :bcc_completed, :completed_redirect_url,
|
||||||
|
{
|
||||||
|
message: %i[subject body],
|
||||||
|
submitters: [[:send_email, :send_sms, :completed_redirect_url, :uuid, :name, :email, :role,
|
||||||
|
:completed, :phone, :application_key,
|
||||||
|
{ values: {}, readonly_fields: [], message: %i[subject body],
|
||||||
|
fields: [%i[name default_value title description
|
||||||
|
readonly validation_pattern invalid_message]] }]]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
params.permit(
|
if params.key?(:submitters)
|
||||||
key => [
|
params.permit(*permitted_attrs)
|
||||||
[:send_email, :send_sms, :bcc_completed, :completed_redirect_url, {
|
else
|
||||||
message: %i[subject body],
|
key = params.key?(:submission) ? :submission : :submissions
|
||||||
submitters: [[:send_email, :send_sms, :completed_redirect_url, :uuid, :name, :email, :role,
|
|
||||||
:completed, :phone, :application_key,
|
params.permit(
|
||||||
{ values: {}, readonly_fields: [], message: %i[subject body],
|
key => [permitted_attrs]
|
||||||
fields: [%i[name default_value title description
|
).fetch(key, [])
|
||||||
readonly validation_pattern invalid_message]] }]]
|
end
|
||||||
}]
|
|
||||||
]
|
|
||||||
).fetch(key, [])
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -44,7 +44,7 @@ module Api
|
|||||||
|
|
||||||
Submissions::CreateFromSubmitters.maybe_set_template_fields(@submitter.submission,
|
Submissions::CreateFromSubmitters.maybe_set_template_fields(@submitter.submission,
|
||||||
[normalized_params],
|
[normalized_params],
|
||||||
submitter_uuid: @submitter.uuid)
|
default_submitter_uuid: @submitter.uuid)
|
||||||
|
|
||||||
assign_submitter_attrs(@submitter, normalized_params)
|
assign_submitter_attrs(@submitter, normalized_params)
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,11 @@ module Api
|
|||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
@template.update!(archived_at: Time.current)
|
if params[:permanently] == 'true' && !Docuseal.multitenant?
|
||||||
|
@template.destroy!
|
||||||
|
else
|
||||||
|
@template.update!(archived_at: Time.current)
|
||||||
|
end
|
||||||
|
|
||||||
render json: @template.as_json(only: %i[id archived_at])
|
render json: @template.as_json(only: %i[id archived_at])
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ class ApplicationController < ActionController::Base
|
|||||||
:current_account,
|
:current_account,
|
||||||
:svg_icon
|
:svg_icon
|
||||||
|
|
||||||
|
impersonates :user, with: ->(uuid) { User.find_by(uuid:) }
|
||||||
|
|
||||||
rescue_from Pagy::OverflowError do
|
rescue_from Pagy::OverflowError do
|
||||||
redirect_to request.path
|
redirect_to request.path
|
||||||
end
|
end
|
||||||
@@ -24,7 +26,7 @@ class ApplicationController < ActionController::Base
|
|||||||
rescue_from CanCan::AccessDenied do |e|
|
rescue_from CanCan::AccessDenied do |e|
|
||||||
Rollbar.error(e) if defined?(Rollbar)
|
Rollbar.error(e) if defined?(Rollbar)
|
||||||
|
|
||||||
redirect_back(fallback_location: root_path, alert: e.message)
|
redirect_to root_path, alert: e.message
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -32,6 +34,15 @@ class ApplicationController < ActionController::Base
|
|||||||
Docuseal.default_url_options
|
Docuseal.default_url_options
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def impersonate_user(user)
|
||||||
|
raise ArgumentError unless user
|
||||||
|
raise Pretender::Error unless true_user
|
||||||
|
|
||||||
|
@impersonated_user = user
|
||||||
|
|
||||||
|
request.session[:impersonated_user_id] = user.uuid
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def with_browser_locale(&)
|
def with_browser_locale(&)
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ class ConsoleRedirectController < ApplicationController
|
|||||||
skip_authorization_check
|
skip_authorization_check
|
||||||
|
|
||||||
def index
|
def index
|
||||||
return redirect_to(new_user_session_path({ redir: params[:redir] }.compact)) if current_user.blank?
|
return redirect_to(new_user_session_path({ redir: params[:redir] }.compact)) if true_user.blank?
|
||||||
|
|
||||||
auth = JsonWebToken.encode(uuid: current_user.uuid,
|
auth = JsonWebToken.encode(uuid: true_user.uuid,
|
||||||
scope: :console,
|
scope: :console,
|
||||||
exp: 1.minute.from_now.to_i)
|
exp: 1.minute.from_now.to_i)
|
||||||
|
|
||||||
|
|||||||
@@ -11,9 +11,7 @@ class SubmittersSendEmailController < ApplicationController
|
|||||||
alert: 'Email has been sent already.')
|
alert: 'Email has been sent already.')
|
||||||
end
|
end
|
||||||
|
|
||||||
SubmitterMailer.invitation_email(@submitter).deliver_later!
|
SendSubmitterInvitationEmailJob.perform_later('submitter_id' => @submitter.id)
|
||||||
|
|
||||||
SubmissionEvent.create!(submitter: @submitter, event_type: 'send_email')
|
|
||||||
|
|
||||||
@submitter.sent_at ||= Time.current
|
@submitter.sent_at ||= Time.current
|
||||||
@submitter.save!
|
@submitter.save!
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class TestingAccountsController < ApplicationController
|
||||||
|
skip_authorization_check only: :destroy
|
||||||
|
|
||||||
|
def show
|
||||||
|
authorize!(:manage, current_account)
|
||||||
|
authorize!(:manage, current_user)
|
||||||
|
|
||||||
|
impersonate_user(Accounts.find_or_create_testing_user(current_account))
|
||||||
|
|
||||||
|
redirect_back(fallback_location: root_path)
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
stop_impersonating_user
|
||||||
|
|
||||||
|
redirect_back(fallback_location: root_path)
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class TestingApiSettingsController < ApplicationController
|
||||||
|
def index
|
||||||
|
authorize!(:manage, current_user.access_token)
|
||||||
|
|
||||||
|
@webhook_config =
|
||||||
|
current_account.encrypted_configs.find_or_initialize_by(key: EncryptedConfig::WEBHOOK_URL_KEY)
|
||||||
|
|
||||||
|
authorize!(:manage, @webhook_config)
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -89,6 +89,7 @@ window.customElements.define('template-builder', class extends HTMLElement {
|
|||||||
withPhone: this.dataset.withPhone === 'true',
|
withPhone: this.dataset.withPhone === 'true',
|
||||||
withLogo: this.dataset.withLogo !== 'false',
|
withLogo: this.dataset.withLogo !== 'false',
|
||||||
withPayment: this.dataset.withPayment === 'true',
|
withPayment: this.dataset.withPayment === 'true',
|
||||||
|
currencies: (this.dataset.currencies || '').split(',').filter(Boolean),
|
||||||
acceptFileTypes: this.dataset.acceptFileTypes,
|
acceptFileTypes: this.dataset.acceptFileTypes,
|
||||||
isDirectUpload: this.dataset.isDirectUpload === 'true'
|
isDirectUpload: this.dataset.isDirectUpload === 'true'
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ window.customElements.define('submission-form', class extends HTMLElement {
|
|||||||
goToLast: this.dataset.goToLast === 'true',
|
goToLast: this.dataset.goToLast === 'true',
|
||||||
isDemo: this.dataset.isDemo === 'true',
|
isDemo: this.dataset.isDemo === 'true',
|
||||||
attribution: this.dataset.attribution !== 'false',
|
attribution: this.dataset.attribution !== 'false',
|
||||||
withConfetti: true,
|
withConfetti: this.dataset.withConfetti !== 'false',
|
||||||
withTypedSignature: this.dataset.withTypedSignature !== 'false',
|
withTypedSignature: this.dataset.withTypedSignature !== 'false',
|
||||||
values: reactive(JSON.parse(this.dataset.values)),
|
values: reactive(JSON.parse(this.dataset.values)),
|
||||||
completedButton: JSON.parse(this.dataset.completedButton),
|
completedButton: JSON.parse(this.dataset.completedButton),
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="flex absolute lg:text-base"
|
class="flex absolute lg:text-base -outline-offset-1"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
:style="computedStyle"
|
:style="computedStyle"
|
||||||
:class="{ 'text-[1.5vw] lg:text-base': !textOverflowChars, 'text-[1.0vw] lg:text-xs': textOverflowChars, 'cursor-default': !submittable, 'bg-red-100 border cursor-pointer ': submittable, 'border-red-100': !isActive && submittable, 'bg-opacity-80': !isActive && !isValueSet && submittable, 'border-red-500 border-dashed border-2 z-10': isActive && submittable, 'bg-opacity-40': (isActive || isValueSet) && submittable }"
|
:class="{ 'text-[1.5vw] lg:text-base': !textOverflowChars, 'text-[1.0vw] lg:text-xs': textOverflowChars, 'cursor-default': !submittable, 'border border-red-100 bg-red-100 cursor-pointer': submittable, 'border border-red-100': !isActive && submittable, 'bg-opacity-80': !isActive && !isValueSet && submittable, 'outline-red-500 outline-dashed outline-2 z-10': isActive && submittable, 'bg-opacity-40': (isActive || isValueSet) && submittable }"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-if="!isActive && !isValueSet && field.type !== 'checkbox' && submittable && !area.option_uuid"
|
v-if="!isActive && !isValueSet && field.type !== 'checkbox' && submittable && !area.option_uuid"
|
||||||
@@ -81,7 +81,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-else-if="field.type === 'checkbox'"
|
v-else-if="field.type === 'checkbox'"
|
||||||
class="w-full p-[0.2vw] flex items-center justify-center"
|
class="w-full p-[1px] flex items-center justify-center"
|
||||||
|
@click="$event.target.querySelector('input')?.click()"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
v-if="submittable"
|
v-if="submittable"
|
||||||
@@ -100,14 +101,15 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-else-if="field.type === 'radio' && area.option_uuid"
|
v-else-if="field.type === 'radio' && area.option_uuid"
|
||||||
class="w-full p-[0.2vw] flex items-center justify-center"
|
class="w-full p-[1px] flex items-center justify-center"
|
||||||
|
@click="$event.target.querySelector('input')?.click()"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
v-if="submittable"
|
v-if="submittable"
|
||||||
type="radio"
|
type="radio"
|
||||||
:value="false"
|
:value="false"
|
||||||
class="aspect-square base-radio"
|
class="aspect-square checked:checkbox checked:checkbox-xs"
|
||||||
:class="{ '!w-auto !h-full': area.w > area.h, '!w-full !h-auto': area.w <= area.h }"
|
:class="{ 'base-radio': !modelValue || modelValue !== optionValue(option), '!w-auto !h-full': area.w > area.h, '!w-full !h-auto': area.w <= area.h }"
|
||||||
:checked="!!modelValue && modelValue === optionValue(option)"
|
:checked="!!modelValue && modelValue === optionValue(option)"
|
||||||
@click="$emit('update:model-value', optionValue(option))"
|
@click="$emit('update:model-value', optionValue(option))"
|
||||||
>
|
>
|
||||||
@@ -119,7 +121,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-else-if="field.type === 'multiple' && area.option_uuid"
|
v-else-if="field.type === 'multiple' && area.option_uuid"
|
||||||
class="w-full p-[0.2vw] flex items-center justify-center"
|
class="w-full p-[1px] flex items-center justify-center"
|
||||||
|
@click="$event.target.querySelector('input')?.click()"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
v-if="submittable"
|
v-if="submittable"
|
||||||
@@ -233,20 +236,20 @@ export default {
|
|||||||
computed: {
|
computed: {
|
||||||
fieldNames () {
|
fieldNames () {
|
||||||
return {
|
return {
|
||||||
text: 'Text',
|
text: this.t('text'),
|
||||||
cells: 'Text',
|
signature: this.t('signature'),
|
||||||
signature: 'Signature',
|
initials: this.t('initials'),
|
||||||
date: 'Date',
|
date: this.t('date'),
|
||||||
image: 'Image',
|
image: this.t('image'),
|
||||||
stamp: 'Stamp',
|
file: this.t('file'),
|
||||||
initials: 'Initials',
|
select: this.t('select'),
|
||||||
file: 'File',
|
checkbox: this.t('checkbox'),
|
||||||
select: 'Select',
|
multiple: this.t('multiple'),
|
||||||
checkbox: 'Checkbox',
|
radio: this.t('radio'),
|
||||||
radio: 'Radio',
|
cells: this.t('cells'),
|
||||||
multiple: 'Multiple Select',
|
stamp: this.t('stamp'),
|
||||||
phone: 'Phone',
|
payment: this.t('payment'),
|
||||||
payment: 'Payment'
|
phone: this.t('phone')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
option () {
|
option () {
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
>
|
>
|
||||||
</template>
|
</template>
|
||||||
<FileDropzone
|
<FileDropzone
|
||||||
:message="`Upload ${field.name || 'Attachments'}${field.required ? '' : ' (optional)'}`"
|
:message="`${t('upload')} ${field.name || t('files')}${field.required ? '' : ` (${t('optional')})`}`"
|
||||||
:submitter-slug="submitterSlug"
|
:submitter-slug="submitterSlug"
|
||||||
:is-direct-upload="isDirectUpload"
|
:is-direct-upload="isDirectUpload"
|
||||||
:multiple="true"
|
:multiple="true"
|
||||||
@@ -62,6 +62,7 @@ export default {
|
|||||||
IconPaperclip,
|
IconPaperclip,
|
||||||
IconTrashX
|
IconTrashX
|
||||||
},
|
},
|
||||||
|
inject: ['t'],
|
||||||
props: {
|
props: {
|
||||||
field: {
|
field: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<label
|
<label
|
||||||
:for="field.uuid"
|
:for="field.uuid"
|
||||||
class="label text-2xl"
|
class="label text-2xl"
|
||||||
>{{ field.name || t('date') }}
|
>{{ field.name && showFieldNames ? field.name : t('date') }}
|
||||||
<template v-if="!field.required">({{ t('optional') }})</template>
|
<template v-if="!field.required">({{ t('optional') }})</template>
|
||||||
</label>
|
</label>
|
||||||
<button
|
<button
|
||||||
@@ -46,6 +46,11 @@ export default {
|
|||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
showFieldNames: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
modelValue: {
|
modelValue: {
|
||||||
type: String,
|
type: String,
|
||||||
required: false,
|
required: false,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
:steps="stepFields"
|
:steps="stepFields"
|
||||||
:values="values"
|
:values="values"
|
||||||
:attachments-index="attachmentsIndex"
|
:attachments-index="attachmentsIndex"
|
||||||
:with-label="!isAnonymousChecboxes"
|
:with-label="!isAnonymousChecboxes && showFieldNames"
|
||||||
:current-step="currentStepFields"
|
:current-step="currentStepFields"
|
||||||
@focus-step="[saveStep(), goToStep($event, false, true), currentField.type !== 'checkbox' ? isFormVisible = true : '']"
|
@focus-step="[saveStep(), goToStep($event, false, true), currentField.type !== 'checkbox' ? isFormVisible = true : '']"
|
||||||
/>
|
/>
|
||||||
@@ -64,6 +64,7 @@
|
|||||||
<TextStep
|
<TextStep
|
||||||
:key="currentField.uuid"
|
:key="currentField.uuid"
|
||||||
v-model="values[currentField.uuid]"
|
v-model="values[currentField.uuid]"
|
||||||
|
:show-field-names="showFieldNames"
|
||||||
:field="currentField"
|
:field="currentField"
|
||||||
@focus="scrollIntoField(currentField)"
|
@focus="scrollIntoField(currentField)"
|
||||||
/>
|
/>
|
||||||
@@ -72,12 +73,13 @@
|
|||||||
v-else-if="currentField.type === 'date'"
|
v-else-if="currentField.type === 'date'"
|
||||||
:key="currentField.uuid"
|
:key="currentField.uuid"
|
||||||
v-model="values[currentField.uuid]"
|
v-model="values[currentField.uuid]"
|
||||||
|
:show-field-names="showFieldNames"
|
||||||
:field="currentField"
|
:field="currentField"
|
||||||
@focus="scrollIntoField(currentField)"
|
@focus="scrollIntoField(currentField)"
|
||||||
/>
|
/>
|
||||||
<div v-else-if="currentField.type === 'select'">
|
<div v-else-if="currentField.type === 'select'">
|
||||||
<label
|
<label
|
||||||
v-if="currentField.name"
|
v-if="showFieldNames && currentField.name"
|
||||||
:for="currentField.uuid"
|
:for="currentField.uuid"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
class="label text-2xl mb-2"
|
class="label text-2xl mb-2"
|
||||||
@@ -116,15 +118,28 @@
|
|||||||
</div>
|
</div>
|
||||||
<div v-else-if="currentField.type === 'radio'">
|
<div v-else-if="currentField.type === 'radio'">
|
||||||
<label
|
<label
|
||||||
v-if="currentField.name"
|
v-if="showFieldNames && currentField.name"
|
||||||
:for="currentField.uuid"
|
:for="currentField.uuid"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
class="label text-2xl mb-2"
|
class="label text-2xl mb-2"
|
||||||
>{{ currentField.name }}
|
>{{ currentField.name }}
|
||||||
<template v-if="!currentField.required">({{ t('optional') }})</template>
|
<template v-if="!currentField.required">({{ t('optional') }})</template>
|
||||||
</label>
|
</label>
|
||||||
<div class="flex w-full">
|
<div class="flex w-full max-h-44 overflow-y-auto">
|
||||||
<div class="space-y-3.5 mx-auto">
|
<div
|
||||||
|
v-if="!showFieldNames || (currentField.options.every((e) => !e.value) && currentField.options.length > 4)"
|
||||||
|
class="text-xl px-1"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
@click="scrollIntoField(currentField)"
|
||||||
|
>
|
||||||
|
{{ t('complete_hightlighted_checkboxes_and_click') }} <span class="font-semibold">{{ stepFields.length === currentStep + 1 ? t('submit') : t('next') }}</span>.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="space-y-3.5 mx-auto"
|
||||||
|
:class="{ hidden: !showFieldNames || (currentField.options.every((e) => !e.value) && currentField.options.length > 4) }"
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
v-for="(option, index) in currentField.options"
|
v-for="(option, index) in currentField.options"
|
||||||
:key="option.uuid"
|
:key="option.uuid"
|
||||||
@@ -154,6 +169,8 @@
|
|||||||
v-else-if="currentField.type === 'multiple'"
|
v-else-if="currentField.type === 'multiple'"
|
||||||
:key="currentField.uuid"
|
:key="currentField.uuid"
|
||||||
v-model="values[currentField.uuid]"
|
v-model="values[currentField.uuid]"
|
||||||
|
:show-field-names="showFieldNames"
|
||||||
|
:is-last-step="stepFields.length === currentStep + 1"
|
||||||
:field="currentField"
|
:field="currentField"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
@@ -168,7 +185,7 @@
|
|||||||
<div
|
<div
|
||||||
class="space-y-3.5 mx-auto"
|
class="space-y-3.5 mx-auto"
|
||||||
>
|
>
|
||||||
<template v-if="isAnonymousChecboxes">
|
<template v-if="isAnonymousChecboxes || !showFieldNames">
|
||||||
<span class="text-xl">
|
<span class="text-xl">
|
||||||
{{ t('complete_hightlighted_checkboxes_and_click') }} <span class="font-semibold">{{ stepFields.length === currentStep + 1 ? t('submit') : t('next') }}</span>.
|
{{ t('complete_hightlighted_checkboxes_and_click') }} <span class="font-semibold">{{ stepFields.length === currentStep + 1 ? t('submit') : t('next') }}</span>.
|
||||||
</span>
|
</span>
|
||||||
@@ -228,6 +245,7 @@
|
|||||||
:is-direct-upload="isDirectUpload"
|
:is-direct-upload="isDirectUpload"
|
||||||
:attachments-index="attachmentsIndex"
|
:attachments-index="attachmentsIndex"
|
||||||
:submitter-slug="submitterSlug"
|
:submitter-slug="submitterSlug"
|
||||||
|
:show-field-names="showFieldNames"
|
||||||
@attached="[attachments.push($event), scrollIntoField(currentField)]"
|
@attached="[attachments.push($event), scrollIntoField(currentField)]"
|
||||||
/>
|
/>
|
||||||
<SignatureStep
|
<SignatureStep
|
||||||
@@ -241,6 +259,7 @@
|
|||||||
:with-typed-signature="withTypedSignature"
|
:with-typed-signature="withTypedSignature"
|
||||||
:attachments-index="attachmentsIndex"
|
:attachments-index="attachmentsIndex"
|
||||||
:submitter-slug="submitterSlug"
|
:submitter-slug="submitterSlug"
|
||||||
|
:show-field-names="showFieldNames"
|
||||||
@attached="attachments.push($event)"
|
@attached="attachments.push($event)"
|
||||||
@start="scrollIntoField(currentField)"
|
@start="scrollIntoField(currentField)"
|
||||||
@minimize="isFormVisible = false"
|
@minimize="isFormVisible = false"
|
||||||
@@ -254,6 +273,7 @@
|
|||||||
:previous-value="previousInitialsValue"
|
:previous-value="previousInitialsValue"
|
||||||
:is-direct-upload="isDirectUpload"
|
:is-direct-upload="isDirectUpload"
|
||||||
:attachments-index="attachmentsIndex"
|
:attachments-index="attachmentsIndex"
|
||||||
|
:show-field-names="showFieldNames"
|
||||||
:submitter-slug="submitterSlug"
|
:submitter-slug="submitterSlug"
|
||||||
@attached="attachments.push($event)"
|
@attached="attachments.push($event)"
|
||||||
@start="scrollIntoField(currentField)"
|
@start="scrollIntoField(currentField)"
|
||||||
@@ -276,6 +296,7 @@
|
|||||||
:key="currentField.uuid"
|
:key="currentField.uuid"
|
||||||
v-model="values[currentField.uuid]"
|
v-model="values[currentField.uuid]"
|
||||||
:field="currentField"
|
:field="currentField"
|
||||||
|
:show-field-names="showFieldNames"
|
||||||
:default-value="submitter.phone"
|
:default-value="submitter.phone"
|
||||||
:submitter-slug="submitterSlug"
|
:submitter-slug="submitterSlug"
|
||||||
@focus="scrollIntoField(currentField)"
|
@focus="scrollIntoField(currentField)"
|
||||||
@@ -376,7 +397,7 @@ import DateStep from './date_step'
|
|||||||
import FormCompleted from './completed'
|
import FormCompleted from './completed'
|
||||||
import { IconInnerShadowTop, IconArrowsDiagonal, IconArrowsDiagonalMinimize2 } from '@tabler/icons-vue'
|
import { IconInnerShadowTop, IconArrowsDiagonal, IconArrowsDiagonalMinimize2 } from '@tabler/icons-vue'
|
||||||
import AppearsOn from './appears_on'
|
import AppearsOn from './appears_on'
|
||||||
import { t } from './i18n'
|
import i18n from './i18n'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'SubmissionForm',
|
name: 'SubmissionForm',
|
||||||
@@ -401,6 +422,7 @@ export default {
|
|||||||
return {
|
return {
|
||||||
baseUrl: this.baseUrl,
|
baseUrl: this.baseUrl,
|
||||||
scrollIntoArea: this.scrollIntoArea,
|
scrollIntoArea: this.scrollIntoArea,
|
||||||
|
scrollIntoField: this.scrollIntoField,
|
||||||
t: this.t
|
t: this.t
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -431,6 +453,11 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
|
showFieldNames: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
withTypedSignature: {
|
withTypedSignature: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -481,6 +508,11 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: true
|
default: true
|
||||||
},
|
},
|
||||||
|
language: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
values: {
|
values: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -522,6 +554,9 @@ export default {
|
|||||||
currentStepFields () {
|
currentStepFields () {
|
||||||
return this.stepFields[this.currentStep]
|
return this.stepFields[this.currentStep]
|
||||||
},
|
},
|
||||||
|
browserLanguage () {
|
||||||
|
return (navigator.language || navigator.userLanguage || 'en').split('-')[0]
|
||||||
|
},
|
||||||
queryParams () {
|
queryParams () {
|
||||||
return new URLSearchParams(window.location.search)
|
return new URLSearchParams(window.location.search)
|
||||||
},
|
},
|
||||||
@@ -639,7 +674,9 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
t,
|
t (key) {
|
||||||
|
return i18n[this.language?.toLowerCase()]?.[key] || i18n[this.browserLanguage]?.[key] || i18n.en[key] || key
|
||||||
|
},
|
||||||
maybeTrackEmailClick () {
|
maybeTrackEmailClick () {
|
||||||
const { queryParams } = this
|
const { queryParams } = this
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,19 @@
|
|||||||
const en = {
|
const en = {
|
||||||
|
text: 'Text',
|
||||||
|
signature: 'Signature',
|
||||||
|
initials: 'Initials',
|
||||||
|
date: 'Date',
|
||||||
|
image: 'Image',
|
||||||
|
take_photo: 'Take photo',
|
||||||
|
file: 'File',
|
||||||
|
select: 'Select',
|
||||||
|
checkbox: 'Checkbox',
|
||||||
|
multiple: 'Multiple',
|
||||||
|
radio: 'Radio',
|
||||||
|
cells: 'Cells',
|
||||||
|
stamp: 'Stamp',
|
||||||
|
payment: 'Payment',
|
||||||
|
phone: 'Phone',
|
||||||
submit_form: 'Submit Form',
|
submit_form: 'Submit Form',
|
||||||
type_here_: 'Type here...',
|
type_here_: 'Type here...',
|
||||||
optional: 'optional',
|
optional: 'optional',
|
||||||
@@ -13,8 +28,6 @@ const en = {
|
|||||||
or_drag_and_drop_files: 'or drag and drop files',
|
or_drag_and_drop_files: 'or drag and drop files',
|
||||||
send_copy_via_email: 'Send copy via email',
|
send_copy_via_email: 'Send copy via email',
|
||||||
download: 'Download',
|
download: 'Download',
|
||||||
signature: 'Signature',
|
|
||||||
initials: 'Initials',
|
|
||||||
clear: 'Clear',
|
clear: 'Clear',
|
||||||
redraw: 'Redraw',
|
redraw: 'Redraw',
|
||||||
draw_initials: 'Draw initials',
|
draw_initials: 'Draw initials',
|
||||||
@@ -40,13 +53,30 @@ const en = {
|
|||||||
draw: 'Draw',
|
draw: 'Draw',
|
||||||
type: 'Type',
|
type: 'Type',
|
||||||
type_text: 'Type text',
|
type_text: 'Type text',
|
||||||
date: 'Date',
|
|
||||||
email_has_been_sent: 'Email has been sent',
|
email_has_been_sent: 'Email has been sent',
|
||||||
processing: 'Processing',
|
processing: 'Processing',
|
||||||
pay_with_strip: 'Pay with Stripe'
|
pay_with_strip: 'Pay with Stripe',
|
||||||
|
reupload: 'Reupload',
|
||||||
|
upload: 'Upload',
|
||||||
|
files: 'Files'
|
||||||
}
|
}
|
||||||
|
|
||||||
const es = {
|
const es = {
|
||||||
|
text: 'Texto',
|
||||||
|
signature: 'Firma',
|
||||||
|
initials: 'Iniciales',
|
||||||
|
date: 'Fecha',
|
||||||
|
image: 'Imagen',
|
||||||
|
file: 'Archivo',
|
||||||
|
select: 'Seleccionar',
|
||||||
|
checkbox: 'Casilla',
|
||||||
|
multiple: 'Múltiple',
|
||||||
|
radio: 'Radio',
|
||||||
|
cells: 'Celdas',
|
||||||
|
stamp: 'Sello',
|
||||||
|
payment: 'Pago',
|
||||||
|
phone: 'Teléfono',
|
||||||
|
take_photo: 'Tomar foto',
|
||||||
submit_form: 'Enviar Formulario',
|
submit_form: 'Enviar Formulario',
|
||||||
type_here_: 'Escribe aquí...',
|
type_here_: 'Escribe aquí...',
|
||||||
optional: 'opcional',
|
optional: 'opcional',
|
||||||
@@ -60,8 +90,6 @@ const es = {
|
|||||||
or_drag_and_drop_files: 'o arrastra y suelta archivos',
|
or_drag_and_drop_files: 'o arrastra y suelta archivos',
|
||||||
send_copy_via_email: 'Enviar copia por correo electrónico',
|
send_copy_via_email: 'Enviar copia por correo electrónico',
|
||||||
download: 'Descargar',
|
download: 'Descargar',
|
||||||
signature: 'Firma',
|
|
||||||
initials: 'Iniciales',
|
|
||||||
clear: 'Borrar',
|
clear: 'Borrar',
|
||||||
redraw: 'Redibujar',
|
redraw: 'Redibujar',
|
||||||
draw_initials: 'Dibujar iniciales',
|
draw_initials: 'Dibujar iniciales',
|
||||||
@@ -81,7 +109,6 @@ const es = {
|
|||||||
verification_code_has_been_resent: 'El código de verificación ha sido reenviado por SMS',
|
verification_code_has_been_resent: 'El código de verificación ha sido reenviado por SMS',
|
||||||
please_fill_all_required_fields: 'Por favor, complete todos los campos obligatorios',
|
please_fill_all_required_fields: 'Por favor, complete todos los campos obligatorios',
|
||||||
set_today: 'Establecer Hoy',
|
set_today: 'Establecer Hoy',
|
||||||
date: 'Fecha',
|
|
||||||
toggle_multiline_text: 'Alternar Texto Multilínea',
|
toggle_multiline_text: 'Alternar Texto Multilínea',
|
||||||
draw_signature: 'Dibujar firma',
|
draw_signature: 'Dibujar firma',
|
||||||
type_initial: 'Escribir iniciales',
|
type_initial: 'Escribir iniciales',
|
||||||
@@ -90,15 +117,33 @@ const es = {
|
|||||||
type_text: 'Escribir texto',
|
type_text: 'Escribir texto',
|
||||||
email_has_been_sent: 'El correo electrónico ha sido enviado',
|
email_has_been_sent: 'El correo electrónico ha sido enviado',
|
||||||
processing: 'Procesando',
|
processing: 'Procesando',
|
||||||
pay_with_strip: 'Pagar con Stripe'
|
pay_with_strip: 'Pagar con Stripe',
|
||||||
|
reupload: 'Volver a subir',
|
||||||
|
upload: 'Subir',
|
||||||
|
files: 'Archivos'
|
||||||
}
|
}
|
||||||
|
|
||||||
const it = {
|
const it = {
|
||||||
|
text: 'Testo',
|
||||||
|
signature: 'Firma',
|
||||||
|
initials: 'Iniziali',
|
||||||
|
date: 'Data',
|
||||||
|
image: 'Immagine',
|
||||||
|
file: 'File',
|
||||||
|
select: 'Seleziona',
|
||||||
|
checkbox: 'Casella di controllo',
|
||||||
|
multiple: 'Multiplo',
|
||||||
|
radio: 'Radio',
|
||||||
|
cells: 'Celle',
|
||||||
|
stamp: 'Timbro',
|
||||||
|
payment: 'Pagamento',
|
||||||
|
phone: 'Telefono',
|
||||||
submit_form: 'Invia Modulo',
|
submit_form: 'Invia Modulo',
|
||||||
type_here_: 'Digita qui...',
|
type_here_: 'Digita qui...',
|
||||||
optional: 'opzionale',
|
optional: 'opzionale',
|
||||||
appears_on: 'Compare su',
|
appears_on: 'Compare su',
|
||||||
page: 'Pagina',
|
page: 'Pagina',
|
||||||
|
take_photo: 'Scattare una foto',
|
||||||
select_your_option: 'Seleziona la tua opzione',
|
select_your_option: 'Seleziona la tua opzione',
|
||||||
complete_hightlighted_checkboxes_and_click: 'Completa le caselle evidenziate e fai clic',
|
complete_hightlighted_checkboxes_and_click: 'Completa le caselle evidenziate e fai clic',
|
||||||
submit: 'invia',
|
submit: 'invia',
|
||||||
@@ -107,8 +152,6 @@ const it = {
|
|||||||
or_drag_and_drop_files: 'oppure trascina e rilascia i file',
|
or_drag_and_drop_files: 'oppure trascina e rilascia i file',
|
||||||
send_copy_via_email: 'Invia copia via email',
|
send_copy_via_email: 'Invia copia via email',
|
||||||
download: 'Scarica',
|
download: 'Scarica',
|
||||||
signature: 'Firma',
|
|
||||||
initials: 'Iniziali',
|
|
||||||
clear: 'Cancella',
|
clear: 'Cancella',
|
||||||
redraw: 'Ridisegna',
|
redraw: 'Ridisegna',
|
||||||
draw_initials: 'Disegna iniziali',
|
draw_initials: 'Disegna iniziali',
|
||||||
@@ -128,7 +171,6 @@ const it = {
|
|||||||
verification_code_has_been_resent: 'Il codice di verifica è stato rinviato tramite SMS',
|
verification_code_has_been_resent: 'Il codice di verifica è stato rinviato tramite SMS',
|
||||||
please_fill_all_required_fields: 'Si prega di compilare tutti i campi obbligatori',
|
please_fill_all_required_fields: 'Si prega di compilare tutti i campi obbligatori',
|
||||||
set_today: 'Imposta Oggi',
|
set_today: 'Imposta Oggi',
|
||||||
date: 'Data',
|
|
||||||
draw_signature: 'Disegna firma',
|
draw_signature: 'Disegna firma',
|
||||||
type_initial: 'Inserisci iniziali',
|
type_initial: 'Inserisci iniziali',
|
||||||
draw: 'Disegna',
|
draw: 'Disegna',
|
||||||
@@ -137,15 +179,33 @@ const it = {
|
|||||||
toggle_multiline_text: 'Attiva Testo Multilinea',
|
toggle_multiline_text: 'Attiva Testo Multilinea',
|
||||||
email_has_been_sent: "L'email è stata inviata",
|
email_has_been_sent: "L'email è stata inviata",
|
||||||
processing: 'Elaborazione',
|
processing: 'Elaborazione',
|
||||||
pay_with_strip: 'Paga con Stripe'
|
pay_with_strip: 'Paga con Stripe',
|
||||||
|
reupload: 'Ricarica',
|
||||||
|
upload: 'Carica',
|
||||||
|
files: 'File'
|
||||||
}
|
}
|
||||||
|
|
||||||
const de = {
|
const de = {
|
||||||
|
text: 'Text',
|
||||||
|
signature: 'Unterschrift',
|
||||||
|
initials: 'Initialen',
|
||||||
|
date: 'Datum',
|
||||||
|
image: 'Bild',
|
||||||
|
file: 'Datei',
|
||||||
|
select: 'Auswählen',
|
||||||
|
checkbox: 'Checkbox',
|
||||||
|
multiple: 'Mehrere',
|
||||||
|
radio: 'Radio',
|
||||||
|
cells: 'Zellen',
|
||||||
|
stamp: 'Stempel',
|
||||||
|
payment: 'Zahlung',
|
||||||
|
phone: 'Telefon',
|
||||||
submit_form: 'Formular absenden',
|
submit_form: 'Formular absenden',
|
||||||
type_here_: 'Hier eingeben...',
|
type_here_: 'Hier eingeben...',
|
||||||
optional: 'optional',
|
optional: 'optional',
|
||||||
appears_on: 'Erscheint auf',
|
appears_on: 'Erscheint auf',
|
||||||
page: 'Seite',
|
page: 'Seite',
|
||||||
|
take_photo: 'Foto aufnehmen',
|
||||||
select_your_option: 'Wähle deine Option',
|
select_your_option: 'Wähle deine Option',
|
||||||
complete_hightlighted_checkboxes_and_click: 'Markierte Kontrollkästchen ausfüllen und klicken',
|
complete_hightlighted_checkboxes_and_click: 'Markierte Kontrollkästchen ausfüllen und klicken',
|
||||||
submit: 'absenden',
|
submit: 'absenden',
|
||||||
@@ -154,8 +214,6 @@ const de = {
|
|||||||
or_drag_and_drop_files: 'oder Dateien hierher ziehen und ablegen',
|
or_drag_and_drop_files: 'oder Dateien hierher ziehen und ablegen',
|
||||||
send_copy_via_email: 'Kopie per E-Mail senden',
|
send_copy_via_email: 'Kopie per E-Mail senden',
|
||||||
download: 'Herunterladen',
|
download: 'Herunterladen',
|
||||||
signature: 'Unterschrift',
|
|
||||||
initials: 'Initialen',
|
|
||||||
clear: 'Löschen',
|
clear: 'Löschen',
|
||||||
redraw: 'Neu zeichnen',
|
redraw: 'Neu zeichnen',
|
||||||
draw_initials: 'Initialen zeichnen',
|
draw_initials: 'Initialen zeichnen',
|
||||||
@@ -175,7 +233,6 @@ const de = {
|
|||||||
verification_code_has_been_resent: 'Die Verifizierungscode wurde erneut per SMS gesendet',
|
verification_code_has_been_resent: 'Die Verifizierungscode wurde erneut per SMS gesendet',
|
||||||
please_fill_all_required_fields: 'Bitte füllen Sie alle erforderlichen Felder aus',
|
please_fill_all_required_fields: 'Bitte füllen Sie alle erforderlichen Felder aus',
|
||||||
set_today: 'Heute einstellen',
|
set_today: 'Heute einstellen',
|
||||||
date: 'Datum',
|
|
||||||
draw_signature: 'Unterschrift zeichnen',
|
draw_signature: 'Unterschrift zeichnen',
|
||||||
type_initial: 'Initialen eingeben',
|
type_initial: 'Initialen eingeben',
|
||||||
draw: 'Zeichnen',
|
draw: 'Zeichnen',
|
||||||
@@ -184,15 +241,33 @@ const de = {
|
|||||||
toggle_multiline_text: 'Mehrzeiligen Text umschalten',
|
toggle_multiline_text: 'Mehrzeiligen Text umschalten',
|
||||||
email_has_been_sent: 'Die E-Mail wurde gesendet',
|
email_has_been_sent: 'Die E-Mail wurde gesendet',
|
||||||
processing: 'Verarbeitung',
|
processing: 'Verarbeitung',
|
||||||
pay_with_strip: 'Mit Stripe bezahlen'
|
pay_with_strip: 'Mit Stripe bezahlen',
|
||||||
|
reupload: 'Erneut hochladen',
|
||||||
|
upload: 'Hochladen',
|
||||||
|
files: 'Dateien'
|
||||||
}
|
}
|
||||||
|
|
||||||
const fr = {
|
const fr = {
|
||||||
|
text: 'Texte',
|
||||||
|
signature: 'Signature',
|
||||||
|
initials: 'Initiales',
|
||||||
|
date: 'Date',
|
||||||
|
image: 'Image',
|
||||||
|
file: 'Fichier',
|
||||||
|
select: 'Choisir',
|
||||||
|
checkbox: 'Coche',
|
||||||
|
multiple: 'Multiple',
|
||||||
|
radio: 'Radio',
|
||||||
|
cells: 'Cellules',
|
||||||
|
stamp: 'Tampon',
|
||||||
|
payment: 'Paiement',
|
||||||
|
phone: 'Téléphone',
|
||||||
submit_form: 'Envoyer le Formulaire',
|
submit_form: 'Envoyer le Formulaire',
|
||||||
type_here_: 'Tapez ici...',
|
type_here_: 'Tapez ici...',
|
||||||
optional: 'facultatif',
|
optional: 'facultatif',
|
||||||
appears_on: 'Apparaît sur',
|
appears_on: 'Apparaît sur',
|
||||||
page: 'Page',
|
page: 'Page',
|
||||||
|
take_photo: 'Prendre une photo',
|
||||||
select_your_option: 'Sélectionnez votre option',
|
select_your_option: 'Sélectionnez votre option',
|
||||||
complete_hightlighted_checkboxes_and_click: 'Complétez les cases à cocher en surbrillance et cliquez',
|
complete_hightlighted_checkboxes_and_click: 'Complétez les cases à cocher en surbrillance et cliquez',
|
||||||
submit: 'envoyer',
|
submit: 'envoyer',
|
||||||
@@ -201,8 +276,6 @@ const fr = {
|
|||||||
or_drag_and_drop_files: 'ou faites glisser-déposer les fichiers',
|
or_drag_and_drop_files: 'ou faites glisser-déposer les fichiers',
|
||||||
send_copy_via_email: 'Envoyer une copie par e-mail',
|
send_copy_via_email: 'Envoyer une copie par e-mail',
|
||||||
download: 'Télécharger',
|
download: 'Télécharger',
|
||||||
signature: 'Signature',
|
|
||||||
initials: 'Initiales',
|
|
||||||
clear: 'Effacer',
|
clear: 'Effacer',
|
||||||
redraw: 'Redessiner',
|
redraw: 'Redessiner',
|
||||||
draw_initials: 'Dessiner les initiales',
|
draw_initials: 'Dessiner les initiales',
|
||||||
@@ -222,7 +295,6 @@ const fr = {
|
|||||||
verification_code_has_been_resent: 'Le code de vérification a été renvoyé par SMS',
|
verification_code_has_been_resent: 'Le code de vérification a été renvoyé par SMS',
|
||||||
please_fill_all_required_fields: 'Veuillez remplir tous les champs obligatoires',
|
please_fill_all_required_fields: 'Veuillez remplir tous les champs obligatoires',
|
||||||
set_today: "Définir Aujourd'hui",
|
set_today: "Définir Aujourd'hui",
|
||||||
date: 'Date',
|
|
||||||
draw_signature: 'Dessiner une signature',
|
draw_signature: 'Dessiner une signature',
|
||||||
type_initial: 'Saisir les initiales',
|
type_initial: 'Saisir les initiales',
|
||||||
draw: 'Dessiner',
|
draw: 'Dessiner',
|
||||||
@@ -231,10 +303,27 @@ const fr = {
|
|||||||
toggle_multiline_text: 'Basculer le Texte Multiligne',
|
toggle_multiline_text: 'Basculer le Texte Multiligne',
|
||||||
email_has_been_sent: "L'email a été envoyé",
|
email_has_been_sent: "L'email a été envoyé",
|
||||||
processing: 'Traitement',
|
processing: 'Traitement',
|
||||||
pay_with_strip: 'Paiement avec Stripe'
|
pay_with_strip: 'Paiement avec Stripe',
|
||||||
|
reupload: 'Recharger',
|
||||||
|
upload: 'Télécharger',
|
||||||
|
files: 'Fichiers'
|
||||||
}
|
}
|
||||||
|
|
||||||
const pl = {
|
const pl = {
|
||||||
|
text: 'Tekst',
|
||||||
|
signature: 'Podpis',
|
||||||
|
initials: 'Inicjały',
|
||||||
|
date: 'Data',
|
||||||
|
image: 'Obraz',
|
||||||
|
file: 'Plik',
|
||||||
|
select: 'Wybierz',
|
||||||
|
checkbox: 'Pole wyboru',
|
||||||
|
multiple: 'Wielokrotne',
|
||||||
|
radio: 'Radio',
|
||||||
|
cells: 'Komórki',
|
||||||
|
stamp: 'Pieczątka',
|
||||||
|
payment: 'Płatność',
|
||||||
|
phone: 'Telefon',
|
||||||
submit_form: 'Wyślij Formularz',
|
submit_form: 'Wyślij Formularz',
|
||||||
type_here_: 'Wpisz tutaj...',
|
type_here_: 'Wpisz tutaj...',
|
||||||
optional: 'opcjonalny',
|
optional: 'opcjonalny',
|
||||||
@@ -244,12 +333,11 @@ const pl = {
|
|||||||
complete_hightlighted_checkboxes_and_click: 'Wypełnij zaznaczone pola wyboru i kliknij',
|
complete_hightlighted_checkboxes_and_click: 'Wypełnij zaznaczone pola wyboru i kliknij',
|
||||||
submit: 'wyślij',
|
submit: 'wyślij',
|
||||||
next: 'dalej',
|
next: 'dalej',
|
||||||
|
take_photo: 'Zrób zdjęcie',
|
||||||
click_to_upload: 'Kliknij, aby przesłać',
|
click_to_upload: 'Kliknij, aby przesłać',
|
||||||
or_drag_and_drop_files: 'lub przeciągnij i upuść pliki',
|
or_drag_and_drop_files: 'lub przeciągnij i upuść pliki',
|
||||||
send_copy_via_email: 'Wyślij kopię drogą mailową',
|
send_copy_via_email: 'Wyślij kopię drogą mailową',
|
||||||
download: 'Pobierz',
|
download: 'Pobierz',
|
||||||
signature: 'Podpis',
|
|
||||||
initials: 'Inicjały',
|
|
||||||
clear: 'Wyczyść',
|
clear: 'Wyczyść',
|
||||||
redraw: 'Przerysuj',
|
redraw: 'Przerysuj',
|
||||||
draw_initials: 'Narysuj inicjały',
|
draw_initials: 'Narysuj inicjały',
|
||||||
@@ -269,7 +357,6 @@ const pl = {
|
|||||||
verification_code_has_been_resent: 'Kod weryfikacyjny został ponownie wysłany',
|
verification_code_has_been_resent: 'Kod weryfikacyjny został ponownie wysłany',
|
||||||
please_fill_all_required_fields: 'Proszę wypełnić wszystkie wymagane pola',
|
please_fill_all_required_fields: 'Proszę wypełnić wszystkie wymagane pola',
|
||||||
set_today: 'Ustaw Dziś',
|
set_today: 'Ustaw Dziś',
|
||||||
date: 'Data',
|
|
||||||
draw_signature: 'Rysuj podpis',
|
draw_signature: 'Rysuj podpis',
|
||||||
type_initial: 'Wprowadź inicjały',
|
type_initial: 'Wprowadź inicjały',
|
||||||
draw: 'Rysuj',
|
draw: 'Rysuj',
|
||||||
@@ -278,15 +365,33 @@ const pl = {
|
|||||||
toggle_multiline_text: 'Przełącz Tekst Wielolinijkowy',
|
toggle_multiline_text: 'Przełącz Tekst Wielolinijkowy',
|
||||||
email_has_been_sent: 'E-mail został wysłany',
|
email_has_been_sent: 'E-mail został wysłany',
|
||||||
processing: 'Przetwarzanie',
|
processing: 'Przetwarzanie',
|
||||||
pay_with_strip: 'Płatność za pomocą Stripe'
|
pay_with_strip: 'Płatność za pomocą Stripe',
|
||||||
|
reupload: 'Ponowne przesłanie',
|
||||||
|
upload: 'Przesyłanie',
|
||||||
|
files: 'Pliki'
|
||||||
}
|
}
|
||||||
|
|
||||||
const uk = {
|
const uk = {
|
||||||
|
text: 'Текст',
|
||||||
|
signature: 'Підпис',
|
||||||
|
initials: 'Ініціали',
|
||||||
|
date: 'Дата',
|
||||||
|
image: 'Зображення',
|
||||||
|
file: 'Файл',
|
||||||
|
select: 'Вибрати',
|
||||||
|
checkbox: 'Прапорець',
|
||||||
|
multiple: 'Множинний',
|
||||||
|
radio: 'Радіо',
|
||||||
|
cells: 'Клітинки',
|
||||||
|
stamp: 'Штамп',
|
||||||
|
payment: 'Платіж',
|
||||||
|
phone: 'Телефон',
|
||||||
submit_form: 'Надіслати Форму',
|
submit_form: 'Надіслати Форму',
|
||||||
type_here_: 'Введіть тут',
|
type_here_: 'Введіть тут',
|
||||||
optional: 'необов’язково',
|
optional: 'необов’язково',
|
||||||
appears_on: "З'являється на",
|
appears_on: "З'являється на",
|
||||||
page: 'Сторінка',
|
page: 'Сторінка',
|
||||||
|
take_photo: 'Зробити фото',
|
||||||
select_your_option: 'Виберіть варіант',
|
select_your_option: 'Виберіть варіант',
|
||||||
complete_hightlighted_checkboxes_and_click: 'Заповніть позначені прапорці та натисніть',
|
complete_hightlighted_checkboxes_and_click: 'Заповніть позначені прапорці та натисніть',
|
||||||
submit: 'надіслати',
|
submit: 'надіслати',
|
||||||
@@ -295,8 +400,6 @@ const uk = {
|
|||||||
or_drag_and_drop_files: 'або перетягніть файли сюди',
|
or_drag_and_drop_files: 'або перетягніть файли сюди',
|
||||||
send_copy_via_email: 'Надіслати копію електронною поштою',
|
send_copy_via_email: 'Надіслати копію електронною поштою',
|
||||||
download: 'Завантажити',
|
download: 'Завантажити',
|
||||||
signature: 'Підпис',
|
|
||||||
initials: 'Ініціали',
|
|
||||||
clear: 'Очистити',
|
clear: 'Очистити',
|
||||||
redraw: 'Перемалювати',
|
redraw: 'Перемалювати',
|
||||||
draw_initials: 'Намалювати ініціали',
|
draw_initials: 'Намалювати ініціали',
|
||||||
@@ -316,7 +419,6 @@ const uk = {
|
|||||||
verification_code_has_been_resent: 'Код підтвердження був повторно надісланий',
|
verification_code_has_been_resent: 'Код підтвердження був повторно надісланий',
|
||||||
please_fill_all_required_fields: "Будь ласка, заповніть всі обов'язкові поля",
|
please_fill_all_required_fields: "Будь ласка, заповніть всі обов'язкові поля",
|
||||||
set_today: 'Задати Сьогодні',
|
set_today: 'Задати Сьогодні',
|
||||||
date: 'Дата',
|
|
||||||
draw_signature: 'Намалюйте підпис',
|
draw_signature: 'Намалюйте підпис',
|
||||||
type_initial: 'Введіть ініціали',
|
type_initial: 'Введіть ініціали',
|
||||||
draw: 'Підпис',
|
draw: 'Підпис',
|
||||||
@@ -325,16 +427,34 @@ const uk = {
|
|||||||
toggle_multiline_text: 'Перемкнути Багаторядковий Текст',
|
toggle_multiline_text: 'Перемкнути Багаторядковий Текст',
|
||||||
email_has_been_sent: 'Електронний лист був відправлений',
|
email_has_been_sent: 'Електронний лист був відправлений',
|
||||||
processing: 'Обробка',
|
processing: 'Обробка',
|
||||||
pay_with_strip: 'Сплатити за допомогою Stripe'
|
pay_with_strip: 'Сплатити за допомогою Stripe',
|
||||||
|
reupload: 'Перезавантажити',
|
||||||
|
upload: 'Завантажити',
|
||||||
|
files: 'Файли'
|
||||||
}
|
}
|
||||||
|
|
||||||
const cs = {
|
const cs = {
|
||||||
|
text: 'Text',
|
||||||
|
signature: 'Podpis',
|
||||||
|
initials: 'Iniciály',
|
||||||
|
date: 'Datum',
|
||||||
|
image: 'Obrázek',
|
||||||
|
file: 'Soubor',
|
||||||
|
select: 'Vybrat',
|
||||||
|
checkbox: 'Zaškrtávací políčko',
|
||||||
|
multiple: 'Více možností',
|
||||||
|
radio: 'Rádio',
|
||||||
|
cells: 'Buňky',
|
||||||
|
stamp: 'Razítko',
|
||||||
|
payment: 'Platba',
|
||||||
|
phone: 'Telefon',
|
||||||
submit_form: 'Odeslat formulář',
|
submit_form: 'Odeslat formulář',
|
||||||
type_here_: 'Zadejte zde',
|
type_here_: 'Zadejte zde',
|
||||||
optional: 'volitelné',
|
optional: 'volitelné',
|
||||||
appears_on: 'Zobrazuje se na',
|
appears_on: 'Zobrazuje se na',
|
||||||
page: 'Stránka',
|
page: 'Stránka',
|
||||||
select_your_option: 'Vyberte svou volbu',
|
select_your_option: 'Vyberte svou volbu',
|
||||||
|
take_photo: 'Fotit',
|
||||||
complete_hightlighted_checkboxes_and_click: 'Označte zvýrazněné zaškrtávací políčka a klikněte na',
|
complete_hightlighted_checkboxes_and_click: 'Označte zvýrazněné zaškrtávací políčka a klikněte na',
|
||||||
submit: 'odeslat',
|
submit: 'odeslat',
|
||||||
next: 'další',
|
next: 'další',
|
||||||
@@ -342,8 +462,6 @@ const cs = {
|
|||||||
or_drag_and_drop_files: 'nebo přetáhněte soubory sem',
|
or_drag_and_drop_files: 'nebo přetáhněte soubory sem',
|
||||||
send_copy_via_email: 'Odeslat kopii e-mailem',
|
send_copy_via_email: 'Odeslat kopii e-mailem',
|
||||||
download: 'Stáhnout',
|
download: 'Stáhnout',
|
||||||
signature: 'Podpis',
|
|
||||||
initials: 'Iniciály',
|
|
||||||
clear: 'Smazat',
|
clear: 'Smazat',
|
||||||
redraw: 'Překreslit',
|
redraw: 'Překreslit',
|
||||||
draw_initials: 'Nakreslit iniciály',
|
draw_initials: 'Nakreslit iniciály',
|
||||||
@@ -363,7 +481,6 @@ const cs = {
|
|||||||
verification_code_has_been_resent: 'Ověřovací kód byl znovu odeslán',
|
verification_code_has_been_resent: 'Ověřovací kód byl znovu odeslán',
|
||||||
please_fill_all_required_fields: 'Prosím vyplňte všechny povinné položky',
|
please_fill_all_required_fields: 'Prosím vyplňte všechny povinné položky',
|
||||||
set_today: 'Nastavit Dnes',
|
set_today: 'Nastavit Dnes',
|
||||||
date: 'Datum',
|
|
||||||
draw_signature: 'Nakreslit podpis',
|
draw_signature: 'Nakreslit podpis',
|
||||||
type_initial: 'Zadat iniciály',
|
type_initial: 'Zadat iniciály',
|
||||||
draw: 'Kreslit',
|
draw: 'Kreslit',
|
||||||
@@ -372,15 +489,33 @@ const cs = {
|
|||||||
toggle_multiline_text: 'Přepnout Víceřádkový Text',
|
toggle_multiline_text: 'Přepnout Víceřádkový Text',
|
||||||
email_has_been_sent: 'E-mail byl odeslán',
|
email_has_been_sent: 'E-mail byl odeslán',
|
||||||
processing: 'Zpracování',
|
processing: 'Zpracování',
|
||||||
pay_with_strip: 'Zaplacení přes Stripe'
|
pay_with_strip: 'Zaplacení přes Stripe',
|
||||||
|
reupload: 'Znovu nahrát',
|
||||||
|
upload: 'Nahrát',
|
||||||
|
files: 'Soubory'
|
||||||
}
|
}
|
||||||
|
|
||||||
const pt = {
|
const pt = {
|
||||||
|
text: 'Texto',
|
||||||
|
signature: 'Assinatura',
|
||||||
|
initials: 'Iniciais',
|
||||||
|
date: 'Data',
|
||||||
|
image: 'Imagem',
|
||||||
|
file: 'Arquivo',
|
||||||
|
select: 'Selecionar',
|
||||||
|
checkbox: 'Caixa',
|
||||||
|
multiple: 'Múltiplo',
|
||||||
|
radio: 'Rádio',
|
||||||
|
cells: 'Células',
|
||||||
|
stamp: 'Carimbo',
|
||||||
|
payment: 'Pagamento',
|
||||||
|
phone: 'Telefone',
|
||||||
submit_form: 'Enviar Formulário',
|
submit_form: 'Enviar Formulário',
|
||||||
type_here_: 'Digite aqui',
|
type_here_: 'Digite aqui',
|
||||||
optional: 'opcional',
|
optional: 'opcional',
|
||||||
appears_on: 'Aparece em',
|
appears_on: 'Aparece em',
|
||||||
page: 'Página',
|
page: 'Página',
|
||||||
|
take_photo: 'Tirar foto',
|
||||||
select_your_option: 'Selecione sua opção',
|
select_your_option: 'Selecione sua opção',
|
||||||
complete_hightlighted_checkboxes_and_click: 'Complete as caixas de seleção destacadas e clique',
|
complete_hightlighted_checkboxes_and_click: 'Complete as caixas de seleção destacadas e clique',
|
||||||
submit: 'enviar',
|
submit: 'enviar',
|
||||||
@@ -389,8 +524,6 @@ const pt = {
|
|||||||
or_drag_and_drop_files: 'ou arraste e solte arquivos',
|
or_drag_and_drop_files: 'ou arraste e solte arquivos',
|
||||||
send_copy_via_email: 'Enviar cópia por e-mail',
|
send_copy_via_email: 'Enviar cópia por e-mail',
|
||||||
download: 'Baixar',
|
download: 'Baixar',
|
||||||
signature: 'Assinatura',
|
|
||||||
initials: 'Iniciais',
|
|
||||||
clear: 'Limpar',
|
clear: 'Limpar',
|
||||||
redraw: 'Redesenhar',
|
redraw: 'Redesenhar',
|
||||||
draw_initials: 'Desenhar iniciais',
|
draw_initials: 'Desenhar iniciais',
|
||||||
@@ -410,7 +543,6 @@ const pt = {
|
|||||||
verification_code_has_been_resent: 'O código de verificação foi reenviado via SMS',
|
verification_code_has_been_resent: 'O código de verificação foi reenviado via SMS',
|
||||||
please_fill_all_required_fields: 'Por favor, preencha todos os campos obrigatórios',
|
please_fill_all_required_fields: 'Por favor, preencha todos os campos obrigatórios',
|
||||||
set_today: 'Definir Hoje',
|
set_today: 'Definir Hoje',
|
||||||
date: 'Data',
|
|
||||||
draw_signature: 'Desenhar assinatura',
|
draw_signature: 'Desenhar assinatura',
|
||||||
type_initial: 'Inserir iniciais',
|
type_initial: 'Inserir iniciais',
|
||||||
draw: 'Desenhar',
|
draw: 'Desenhar',
|
||||||
@@ -419,16 +551,34 @@ const pt = {
|
|||||||
toggle_multiline_text: 'Alternar Texto Multilinha',
|
toggle_multiline_text: 'Alternar Texto Multilinha',
|
||||||
email_has_been_sent: 'Email enviado',
|
email_has_been_sent: 'Email enviado',
|
||||||
processing: 'Processamento',
|
processing: 'Processamento',
|
||||||
pay_with_strip: 'Pagar com Stripe'
|
pay_with_strip: 'Pagar com Stripe',
|
||||||
|
reupload: 'Reenviar',
|
||||||
|
upload: 'Carregar',
|
||||||
|
files: 'Arquivos'
|
||||||
}
|
}
|
||||||
|
|
||||||
const he = {
|
const he = {
|
||||||
|
text: 'טקסט',
|
||||||
|
signature: 'חתימה',
|
||||||
|
initials: 'ראשי תיקיות',
|
||||||
|
date: 'תאריך',
|
||||||
|
image: 'תמונה',
|
||||||
|
file: 'קובץ',
|
||||||
|
select: 'בחר',
|
||||||
|
checkbox: 'תיק בחירה',
|
||||||
|
multiple: 'רב ערכים',
|
||||||
|
radio: 'רדיו',
|
||||||
|
cells: 'תאים',
|
||||||
|
stamp: 'חותמת',
|
||||||
|
payment: 'תשלום',
|
||||||
|
phone: 'טלפון',
|
||||||
submit_form: 'שלח טופס',
|
submit_form: 'שלח טופס',
|
||||||
type_here_: 'הקלד כאן',
|
type_here_: 'הקלד כאן',
|
||||||
optional: 'אופציונלי',
|
optional: 'אופציונלי',
|
||||||
option: 'אפשרות',
|
option: 'אפשרות',
|
||||||
appears_on: 'מופיע ב',
|
appears_on: 'מופיע ב',
|
||||||
page: 'עמוד',
|
page: 'עמוד',
|
||||||
|
take_photo: 'צלם תמונה',
|
||||||
select_your_option: 'בחר את האפשרות שלך',
|
select_your_option: 'בחר את האפשרות שלך',
|
||||||
complete_hightlighted_checkboxes_and_click: 'השלם את תיבות הסימון המודגשות ולחץ',
|
complete_hightlighted_checkboxes_and_click: 'השלם את תיבות הסימון המודגשות ולחץ',
|
||||||
submit: 'שליחה',
|
submit: 'שליחה',
|
||||||
@@ -437,8 +587,6 @@ const he = {
|
|||||||
or_drag_and_drop_files: 'או גרור ושחרר קבצים',
|
or_drag_and_drop_files: 'או גרור ושחרר קבצים',
|
||||||
send_copy_via_email: 'שלח עותק באמצעות אימייל',
|
send_copy_via_email: 'שלח עותק באמצעות אימייל',
|
||||||
download: 'הורדה',
|
download: 'הורדה',
|
||||||
signature: 'חתימה',
|
|
||||||
initials: 'ציוני ראשי תיבות',
|
|
||||||
clear: 'נקה',
|
clear: 'נקה',
|
||||||
redraw: 'צייר מחדש',
|
redraw: 'צייר מחדש',
|
||||||
draw_initials: 'צייר ציוני ראשי תיבות',
|
draw_initials: 'צייר ציוני ראשי תיבות',
|
||||||
@@ -464,17 +612,77 @@ const he = {
|
|||||||
draw: 'צייר',
|
draw: 'צייר',
|
||||||
type: 'הקלד',
|
type: 'הקלד',
|
||||||
type_text: 'הקלד טקסט',
|
type_text: 'הקלד טקסט',
|
||||||
date: 'תאריך',
|
|
||||||
email_has_been_sent: 'האימייל נשלח',
|
email_has_been_sent: 'האימייל נשלח',
|
||||||
processing: 'מעבד',
|
processing: 'מעבד',
|
||||||
pay_with_strip: 'שלם עם סטרייפ'
|
pay_with_strip: 'שלם עם סטרייפ',
|
||||||
|
reupload: 'העלה שוב',
|
||||||
|
upload: 'העלאה',
|
||||||
|
files: 'קבצים'
|
||||||
}
|
}
|
||||||
|
|
||||||
const i18n = { en, es, it, de, fr, pl, uk, cs, pt, he }
|
const nl = {
|
||||||
|
text: 'Tekst',
|
||||||
|
signature: 'Handtekening',
|
||||||
|
initials: 'Initialen',
|
||||||
|
date: 'Datum',
|
||||||
|
image: 'Afbeelding',
|
||||||
|
take_photo: 'Maak een foto',
|
||||||
|
file: 'Bestand',
|
||||||
|
select: 'Selecteer',
|
||||||
|
checkbox: 'Aankruisvakje',
|
||||||
|
multiple: 'Meerdere',
|
||||||
|
radio: 'Radio',
|
||||||
|
cells: 'Cellen',
|
||||||
|
stamp: 'Stempel',
|
||||||
|
payment: 'Betaling',
|
||||||
|
phone: 'Telefoon',
|
||||||
|
submit_form: 'Formulier verzenden',
|
||||||
|
type_here_: 'Typ hier...',
|
||||||
|
optional: 'Optioneel',
|
||||||
|
option: 'Optie',
|
||||||
|
appears_on: 'Verschijnt op',
|
||||||
|
page: 'Pagina',
|
||||||
|
select_your_option: 'Selecteer uw optie',
|
||||||
|
complete_hightlighted_checkboxes_and_click: 'Voltooi de gemarkeerde selectievakjes en klik',
|
||||||
|
submit: 'Verzenden',
|
||||||
|
next: 'Volgende',
|
||||||
|
click_to_upload: 'Klik om te uploaden',
|
||||||
|
or_drag_and_drop_files: 'of sleep en plaats bestanden',
|
||||||
|
send_copy_via_email: 'Stuur kopie per e-mail',
|
||||||
|
download: 'Downloaden',
|
||||||
|
clear: 'Wissen',
|
||||||
|
redraw: 'Opnieuw tekenen',
|
||||||
|
draw_initials: 'Initialen tekenen',
|
||||||
|
type_signature_here: 'Typ hier uw handtekening',
|
||||||
|
type_initial_here: 'Typ hier uw initialen',
|
||||||
|
form_has_been_completed: 'Formulier is voltooid!',
|
||||||
|
create_a_free_account: 'Maak een gratis account aan',
|
||||||
|
signed_with: 'Ondertekend met',
|
||||||
|
please_check_the_box_to_continue: 'Vink het vakje aan om door te gaan',
|
||||||
|
open_source_documents_software: 'Open source documenten software',
|
||||||
|
verified_phone_number: 'Geverifieerd telefoonnummer',
|
||||||
|
use_international_format: 'Gebruik internationaal formaat: +1xxx',
|
||||||
|
six_digits_code: '6-cijferige code',
|
||||||
|
change_phone_number: 'Wijzig telefoonnummer',
|
||||||
|
sending: 'Verzenden...',
|
||||||
|
resend_code: 'Code opnieuw verzenden',
|
||||||
|
verification_code_has_been_resent: 'Verificatiecode is opnieuw verzonden via SMS',
|
||||||
|
please_fill_all_required_fields: 'Vul alle verplichte velden in',
|
||||||
|
set_today: 'Stel vandaag in',
|
||||||
|
toggle_multiline_text: 'Schakel meerregelige tekst in/uit',
|
||||||
|
draw_signature: 'Handtekening tekenen',
|
||||||
|
type_initial: 'Typ initialen',
|
||||||
|
draw: 'Tekenen',
|
||||||
|
type: 'Typen',
|
||||||
|
type_text: 'Typ tekst',
|
||||||
|
email_has_been_sent: 'E-mail is verzonden',
|
||||||
|
processing: 'Verwerken',
|
||||||
|
pay_with_strip: 'Betalen met Stripe',
|
||||||
|
reupload: 'Opnieuw uploaden',
|
||||||
|
upload: 'Uploaden',
|
||||||
|
files: 'Bestanden'
|
||||||
|
}
|
||||||
|
|
||||||
const browserLanguage = (navigator.language || navigator.userLanguage || 'en').split('-')[0]
|
const i18n = { en, es, it, de, fr, pl, uk, cs, pt, he, nl }
|
||||||
|
|
||||||
const t = (key) => i18n[browserLanguage]?.[key] || i18n.en[key] || key
|
|
||||||
|
|
||||||
export default i18n
|
export default i18n
|
||||||
export { t }
|
|
||||||
@@ -3,13 +3,13 @@
|
|||||||
<div class="flex justify-between items-center w-full mb-2">
|
<div class="flex justify-between items-center w-full mb-2">
|
||||||
<label
|
<label
|
||||||
class="label text-2xl"
|
class="label text-2xl"
|
||||||
>{{ field.name || 'Image' }}</label>
|
>{{ showFieldNames && field.name ? field.name : t('image') }}</label>
|
||||||
<button
|
<button
|
||||||
class="btn btn-outline btn-sm"
|
class="btn btn-outline btn-sm"
|
||||||
@click.prevent="remove"
|
@click.prevent="remove"
|
||||||
>
|
>
|
||||||
<IconReload :width="16" />
|
<IconReload :width="16" />
|
||||||
Reupload
|
{{ t('reupload') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<FileDropzone
|
<FileDropzone
|
||||||
v-if="!modelValue"
|
v-if="!modelValue"
|
||||||
:message="`Upload ${field.name || 'Image'}${field.required ? '' : ' (optional)'}`"
|
:message="`${t('upload')} ${field.name || t('image')}${field.required ? '' : ` (${t('optional')})`}`"
|
||||||
:submitter-slug="submitterSlug"
|
:submitter-slug="submitterSlug"
|
||||||
:accept="'image/*'"
|
:accept="'image/*'"
|
||||||
:is-direct-upload="isDirectUpload"
|
:is-direct-upload="isDirectUpload"
|
||||||
@@ -46,6 +46,7 @@ export default {
|
|||||||
FileDropzone,
|
FileDropzone,
|
||||||
IconReload
|
IconReload
|
||||||
},
|
},
|
||||||
|
inject: ['t'],
|
||||||
props: {
|
props: {
|
||||||
field: {
|
field: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@@ -56,6 +57,11 @@ export default {
|
|||||||
required: true,
|
required: true,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
|
showFieldNames: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
submitterSlug: {
|
submitterSlug: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<div class="flex justify-between items-center w-full mb-2">
|
<div class="flex justify-between items-center w-full mb-2">
|
||||||
<label
|
<label
|
||||||
class="label text-2xl"
|
class="label text-2xl"
|
||||||
>{{ field.name || t('initials') }}</label>
|
>{{ showFieldNames && field.name ? field.name : t('initials') }}</label>
|
||||||
<div class="space-x-2 flex">
|
<div class="space-x-2 flex">
|
||||||
<span
|
<span
|
||||||
v-if="isDrawInitials"
|
v-if="isDrawInitials"
|
||||||
@@ -125,6 +125,11 @@ export default {
|
|||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
showFieldNames: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
isDirectUpload: {
|
isDirectUpload: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: true,
|
required: true,
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<label
|
<label
|
||||||
v-if="field.name"
|
v-if="showFieldNames && field.name"
|
||||||
:for="field.uuid"
|
:for="field.uuid"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
class="label text-2xl mb-2"
|
class="label text-2xl mb-2"
|
||||||
>{{ field.name }}</label>
|
>{{ field.name }}</label>
|
||||||
<div class="flex w-full">
|
<div class="flex w-full max-h-44 overflow-y-auto">
|
||||||
<input
|
<input
|
||||||
v-if="modelValue.length === 0"
|
v-if="modelValue.length === 0"
|
||||||
type="text"
|
type="text"
|
||||||
@@ -13,7 +13,18 @@
|
|||||||
:value="''"
|
:value="''"
|
||||||
class="hidden"
|
class="hidden"
|
||||||
>
|
>
|
||||||
<div class="space-y-3.5 mx-auto">
|
<div
|
||||||
|
v-if="!showOptions"
|
||||||
|
class="text-xl px-1"
|
||||||
|
>
|
||||||
|
<span @click="scrollIntoField(field)">
|
||||||
|
{{ t('complete_hightlighted_checkboxes_and_click') }} <span class="font-semibold">{{ isLastStep ? t('submit') : t('next') }}</span>.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="space-y-3.5 mx-auto"
|
||||||
|
:class="{ hidden: !showOptions }"
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
v-for="(option, index) in field.options"
|
v-for="(option, index) in field.options"
|
||||||
:key="option.uuid"
|
:key="option.uuid"
|
||||||
@@ -44,12 +55,22 @@
|
|||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: 'MultiSelectStep',
|
name: 'MultiSelectStep',
|
||||||
inject: ['t'],
|
inject: ['t', 'scrollIntoField'],
|
||||||
props: {
|
props: {
|
||||||
field: {
|
field: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
isLastStep: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
showFieldNames: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
modelValue: {
|
modelValue: {
|
||||||
type: Array,
|
type: Array,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -62,6 +83,11 @@ export default {
|
|||||||
inputRefs: []
|
inputRefs: []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
showOptions () {
|
||||||
|
return this.showFieldNames && (this.field.options.some((e) => e.value) || this.field.options.length < 5)
|
||||||
|
}
|
||||||
|
},
|
||||||
beforeUpdate () {
|
beforeUpdate () {
|
||||||
this.inputRefs = []
|
this.inputRefs = []
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<label
|
<label
|
||||||
:for="isCodeSent ? 'one_time_code' : field.uuid"
|
:for="isCodeSent ? 'one_time_code' : field.uuid"
|
||||||
class="label text-2xl mb-2"
|
class="label text-2xl mb-2"
|
||||||
>{{ field.name || t('verified_phone_number') }}
|
>{{ showFieldNames && field.name ? field.name : t('verified_phone_number') }}
|
||||||
<template v-if="!field.required">({{ t('optional') }})</template>
|
<template v-if="!field.required">({{ t('optional') }})</template>
|
||||||
</label>
|
</label>
|
||||||
<div>
|
<div>
|
||||||
@@ -93,6 +93,11 @@ export default {
|
|||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
showFieldNames: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
modelValue: {
|
modelValue: {
|
||||||
type: String,
|
type: String,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -140,7 +145,11 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
async submit () {
|
async submit () {
|
||||||
if (!this.isCodeSent) {
|
if (!this.$refs.phone.value.toString().startsWith('+')) {
|
||||||
|
alert(this.t('use_international_format'))
|
||||||
|
|
||||||
|
return Promise.reject(new Error('phone invalid'))
|
||||||
|
} else if (!this.isCodeSent) {
|
||||||
this.sendVerificationCode()
|
this.sendVerificationCode()
|
||||||
|
|
||||||
this.$emit('update:model-value', this.$refs.phone.value)
|
this.$emit('update:model-value', this.$refs.phone.value)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<div class="flex justify-between items-center w-full mb-2">
|
<div class="flex justify-between items-center w-full mb-2">
|
||||||
<label
|
<label
|
||||||
class="label text-2xl"
|
class="label text-2xl"
|
||||||
>{{ field.name || t('signature') }}</label>
|
>{{ showFieldNames && field.name ? field.name : t('signature') }}</label>
|
||||||
<div class="space-x-2 flex">
|
<div class="space-x-2 flex">
|
||||||
<span
|
<span
|
||||||
v-if="isTextSignature"
|
v-if="isTextSignature"
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
class="tooltip"
|
class="tooltip"
|
||||||
data-tip="Take photo"
|
:data-tip="t('take_photo')"
|
||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
class="btn btn-outline btn-sm font-medium"
|
class="btn btn-outline btn-sm font-medium"
|
||||||
@@ -147,6 +147,11 @@ export default {
|
|||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
showFieldNames: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
isDirectUpload: {
|
isDirectUpload: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: true,
|
required: true,
|
||||||
@@ -221,7 +226,7 @@ export default {
|
|||||||
},
|
},
|
||||||
loadFont () {
|
loadFont () {
|
||||||
if (!isFontLoaded) {
|
if (!isFontLoaded) {
|
||||||
const font = new FontFace('Dancing Script', `url(${this.baseUrl}/fonts/DancingScript.otf) format("opentype")`)
|
const font = new FontFace('Dancing Script', `url(${this.baseUrl}/fonts/DancingScript-Regular.otf) format("opentype")`)
|
||||||
|
|
||||||
font.load().then((loadedFont) => {
|
font.load().then((loadedFont) => {
|
||||||
document.fonts.add(loadedFont)
|
document.fonts.add(loadedFont)
|
||||||
@@ -260,6 +265,7 @@ export default {
|
|||||||
},
|
},
|
||||||
toggleTextInput () {
|
toggleTextInput () {
|
||||||
this.remove()
|
this.remove()
|
||||||
|
this.clear()
|
||||||
this.isTextSignature = !this.isTextSignature
|
this.isTextSignature = !this.isTextSignature
|
||||||
|
|
||||||
if (this.isTextSignature) {
|
if (this.isTextSignature) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<label
|
<label
|
||||||
v-if="field.name"
|
v-if="showFieldNames && field.name"
|
||||||
:for="field.uuid"
|
:for="field.uuid"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
class="label text-2xl"
|
class="label text-2xl"
|
||||||
@@ -26,7 +26,8 @@
|
|||||||
v-model="text"
|
v-model="text"
|
||||||
:maxlength="cellsMaxLegth"
|
:maxlength="cellsMaxLegth"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
class="base-input !text-2xl w-full !pr-11 -mr-10"
|
class="base-input !text-2xl w-full"
|
||||||
|
:class="{ '!pr-11 -mr-10': !field.validation?.pattern }"
|
||||||
:required="field.required"
|
:required="field.required"
|
||||||
:pattern="field.validation?.pattern"
|
:pattern="field.validation?.pattern"
|
||||||
:oninvalid="field.validation?.message ? `this.setCustomValidity(${JSON.stringify(field.validation.message)})` : ''"
|
:oninvalid="field.validation?.message ? `this.setCustomValidity(${JSON.stringify(field.validation.message)})` : ''"
|
||||||
@@ -50,7 +51,7 @@
|
|||||||
@focus="$emit('focus')"
|
@focus="$emit('focus')"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
v-if="!isTextArea && field.type !== 'cells'"
|
v-if="!isTextArea && field.type !== 'cells' && !field.validation?.pattern"
|
||||||
class="tooltip"
|
class="tooltip"
|
||||||
:data-tip="t('toggle_multiline_text')"
|
:data-tip="t('toggle_multiline_text')"
|
||||||
>
|
>
|
||||||
@@ -81,6 +82,11 @@ export default {
|
|||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
showFieldNames: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
modelValue: {
|
modelValue: {
|
||||||
type: String,
|
type: String,
|
||||||
required: false,
|
required: false,
|
||||||
|
|||||||
@@ -43,7 +43,8 @@
|
|||||||
class="border-r"
|
class="border-r"
|
||||||
:compact="true"
|
:compact="true"
|
||||||
:editable="editable && !defaultField"
|
:editable="editable && !defaultField"
|
||||||
:menu-classes="'dropdown-content bg-white menu menu-xs p-2 shadow rounded-box w-52 rounded-t-none -left-[1px]'"
|
:allow-add-new="!defaultSubmitters.length"
|
||||||
|
:menu-classes="'dropdown-content bg-white menu menu-xs p-2 shadow rounded-box w-52 rounded-t-none -left-[1px] mt-[1px]'"
|
||||||
:submitters="template.submitters"
|
:submitters="template.submitters"
|
||||||
@update:model-value="save"
|
@update:model-value="save"
|
||||||
@click="selectedAreaRef.value = area"
|
@click="selectedAreaRef.value = area"
|
||||||
@@ -166,6 +167,11 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: null
|
default: null
|
||||||
},
|
},
|
||||||
|
defaultSubmitters: {
|
||||||
|
type: Array,
|
||||||
|
required: false,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
editable: {
|
editable: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
|
|||||||
@@ -112,7 +112,7 @@
|
|||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
class="sticky bottom-0 py-2"
|
class="sticky bottom-0 py-2"
|
||||||
:style="withStickySubmitters ? { backgroundColor } : {}"
|
:style="{ backgroundColor }"
|
||||||
>
|
>
|
||||||
<Upload
|
<Upload
|
||||||
v-if="sortedDocuments.length && editable && withUploadButton"
|
v-if="sortedDocuments.length && editable && withUploadButton"
|
||||||
@@ -148,6 +148,7 @@
|
|||||||
:is-drag="!!dragField"
|
:is-drag="!!dragField"
|
||||||
:default-fields="defaultFields"
|
:default-fields="defaultFields"
|
||||||
:allow-draw="!onlyDefinedFields"
|
:allow-draw="!onlyDefinedFields"
|
||||||
|
:default-submitters="defaultSubmitters"
|
||||||
:draw-field="drawField"
|
:draw-field="drawField"
|
||||||
:editable="editable"
|
:editable="editable"
|
||||||
:base-url="baseUrl"
|
:base-url="baseUrl"
|
||||||
@@ -185,25 +186,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<MobileDrawField
|
|
||||||
v-if="drawField && isBreakpointLg"
|
|
||||||
:draw-field="drawField"
|
|
||||||
:fields="template.fields"
|
|
||||||
:submitters="template.submitters"
|
|
||||||
:selected-submitter="selectedSubmitter"
|
|
||||||
class="md:hidden"
|
|
||||||
:editable="editable"
|
|
||||||
@cancel="[drawField = null, drawOption = null]"
|
|
||||||
@change-submitter="[selectedSubmitter = $event, drawField.submitter_uuid = $event.uuid]"
|
|
||||||
/>
|
|
||||||
<MobileFields
|
|
||||||
v-if="sortedDocuments.length && !drawField && editable"
|
|
||||||
:fields="template.fields"
|
|
||||||
:default-fields="defaultFields"
|
|
||||||
:field-types="fieldTypes"
|
|
||||||
:selected-submitter="selectedSubmitter"
|
|
||||||
@select="startFieldDraw($event)"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="withFieldsList"
|
v-if="withFieldsList"
|
||||||
@@ -259,6 +241,27 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="sticky bottom-0">
|
||||||
|
<MobileDrawField
|
||||||
|
v-if="drawField && isBreakpointLg"
|
||||||
|
:draw-field="drawField"
|
||||||
|
:fields="template.fields"
|
||||||
|
:submitters="template.submitters"
|
||||||
|
:selected-submitter="selectedSubmitter"
|
||||||
|
class="md:hidden"
|
||||||
|
:editable="editable"
|
||||||
|
@cancel="[drawField = null, drawOption = null]"
|
||||||
|
@change-submitter="[selectedSubmitter = $event, drawField.submitter_uuid = $event.uuid]"
|
||||||
|
/>
|
||||||
|
<MobileFields
|
||||||
|
v-if="sortedDocuments.length && !drawField && editable"
|
||||||
|
:fields="template.fields"
|
||||||
|
:default-fields="defaultFields"
|
||||||
|
:field-types="fieldTypes"
|
||||||
|
:selected-submitter="selectedSubmitter"
|
||||||
|
@select="startFieldDraw($event)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -301,6 +304,7 @@ export default {
|
|||||||
template: this.template,
|
template: this.template,
|
||||||
save: this.save,
|
save: this.save,
|
||||||
t: this.t,
|
t: this.t,
|
||||||
|
currencies: this.currencies,
|
||||||
baseFetch: this.baseFetch,
|
baseFetch: this.baseFetch,
|
||||||
fieldTypes: this.fieldTypes,
|
fieldTypes: this.fieldTypes,
|
||||||
backgroundColor: this.backgroundColor,
|
backgroundColor: this.backgroundColor,
|
||||||
@@ -349,6 +353,11 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: () => []
|
default: () => []
|
||||||
},
|
},
|
||||||
|
currencies: {
|
||||||
|
type: Array,
|
||||||
|
required: false,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
fieldTypes: {
|
fieldTypes: {
|
||||||
type: Array,
|
type: Array,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -884,7 +893,7 @@ export default {
|
|||||||
this.save()
|
this.save()
|
||||||
},
|
},
|
||||||
onDocumentRemove (item) {
|
onDocumentRemove (item) {
|
||||||
if (window.confirm('Are you sure?')) {
|
if (window.confirm(this.t('are_you_sure'))) {
|
||||||
this.template.schema.splice(this.template.schema.indexOf(item), 1)
|
this.template.schema.splice(this.template.schema.indexOf(item), 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
:allow-draw="allowDraw"
|
:allow-draw="allowDraw"
|
||||||
:is-drag="isDrag"
|
:is-drag="isDrag"
|
||||||
:default-fields="defaultFields"
|
:default-fields="defaultFields"
|
||||||
|
:default-submitters="defaultSubmitters"
|
||||||
:draw-field="drawField"
|
:draw-field="drawField"
|
||||||
:selected-submitter="selectedSubmitter"
|
:selected-submitter="selectedSubmitter"
|
||||||
:image="image"
|
:image="image"
|
||||||
@@ -42,6 +43,11 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: () => []
|
default: () => []
|
||||||
},
|
},
|
||||||
|
defaultSubmitters: {
|
||||||
|
type: Array,
|
||||||
|
required: false,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
allowDraw: {
|
allowDraw: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
|
|||||||
@@ -61,7 +61,7 @@
|
|||||||
<button
|
<button
|
||||||
v-if="submitters.length > 1 && editable"
|
v-if="submitters.length > 1 && editable"
|
||||||
class="px-2"
|
class="px-2"
|
||||||
@click.stop="remove(submitter)"
|
@click.prevent.stop="remove(submitter)"
|
||||||
>
|
>
|
||||||
<IconTrashX :width="18" />
|
<IconTrashX :width="18" />
|
||||||
</button>
|
</button>
|
||||||
@@ -125,7 +125,8 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span class="flex items-center transition-all duration-75 group-hover:border border-base-content/20 border-dashed w-6 h-6 flex justify-center items-center rounded">
|
<span class="flex items-center transition-all duration-75 group-hover:border border-base-content/20 border-dashed w-6 h-6 flex justify-center items-center rounded">
|
||||||
<IconPlus
|
<component
|
||||||
|
:is="editable ? 'IconPlus' : 'IconChevronDown'"
|
||||||
width="18"
|
width="18"
|
||||||
height="18"
|
height="18"
|
||||||
/>
|
/>
|
||||||
@@ -165,7 +166,7 @@
|
|||||||
title="Up"
|
title="Up"
|
||||||
class="relative w-2"
|
class="relative w-2"
|
||||||
style="font-size: 10px; margin-bottom: -4px"
|
style="font-size: 10px; margin-bottom: -4px"
|
||||||
@click.stop="[move(submitter, -1), $refs.label.focus()] "
|
@click.prevent.stop="[move(submitter, -1), $refs.label.focus()] "
|
||||||
>
|
>
|
||||||
▲
|
▲
|
||||||
</button>
|
</button>
|
||||||
@@ -173,7 +174,7 @@
|
|||||||
title="Down"
|
title="Down"
|
||||||
class="relative w-2"
|
class="relative w-2"
|
||||||
style="font-size: 10px; margin-top: -4px"
|
style="font-size: 10px; margin-top: -4px"
|
||||||
@click.stop="[move(submitter, 1), $refs.label.focus()] "
|
@click.prevent.stop="[move(submitter, 1), $refs.label.focus()] "
|
||||||
>
|
>
|
||||||
▼
|
▼
|
||||||
</button>
|
</button>
|
||||||
@@ -181,14 +182,14 @@
|
|||||||
<button
|
<button
|
||||||
v-if="!compact && submitters.length > 1 && editable"
|
v-if="!compact && submitters.length > 1 && editable"
|
||||||
class="hidden group-hover:block px-2"
|
class="hidden group-hover:block px-2"
|
||||||
@click.stop="remove(submitter)"
|
@click.prevent.stop="remove(submitter)"
|
||||||
>
|
>
|
||||||
<IconTrashX :width="18" />
|
<IconTrashX :width="18" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li v-if="submitters.length < 10 && editable">
|
<li v-if="submitters.length < 10 && editable && allowAddNew">
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
class="flex px-2"
|
class="flex px-2"
|
||||||
@@ -208,7 +209,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { IconUserPlus, IconTrashX, IconPlus, IconChevronUp } from '@tabler/icons-vue'
|
import { IconUserPlus, IconTrashX, IconPlus, IconChevronUp, IconChevronDown } from '@tabler/icons-vue'
|
||||||
import Contenteditable from './contenteditable'
|
import Contenteditable from './contenteditable'
|
||||||
import { v4 } from 'uuid'
|
import { v4 } from 'uuid'
|
||||||
|
|
||||||
@@ -216,6 +217,7 @@ export default {
|
|||||||
name: 'FieldSubmitter',
|
name: 'FieldSubmitter',
|
||||||
components: {
|
components: {
|
||||||
IconUserPlus,
|
IconUserPlus,
|
||||||
|
IconChevronDown,
|
||||||
Contenteditable,
|
Contenteditable,
|
||||||
IconPlus,
|
IconPlus,
|
||||||
IconTrashX,
|
IconTrashX,
|
||||||
@@ -242,6 +244,11 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
|
allowAddNew: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
modelValue: {
|
modelValue: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true
|
||||||
@@ -296,7 +303,7 @@ export default {
|
|||||||
this.$emit('update:model-value', submitter.uuid)
|
this.$emit('update:model-value', submitter.uuid)
|
||||||
},
|
},
|
||||||
remove (submitter) {
|
remove (submitter) {
|
||||||
if (window.confirm('Are you sure?')) {
|
if (window.confirm(this.t('are_you_sure'))) {
|
||||||
this.$emit('remove', submitter)
|
this.$emit('remove', submitter)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
const en = {
|
const en = {
|
||||||
|
are_you_sure: 'Are you sure?',
|
||||||
sign_yourself: 'Sign Yourself',
|
sign_yourself: 'Sign Yourself',
|
||||||
send: 'Send',
|
send: 'Send',
|
||||||
save: 'Save',
|
save: 'Save',
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="fixed text-center w-full bottom-0 pr-6 mb-4">
|
<div class="absolute text-center w-full bottom-0 pr-3 mb-4">
|
||||||
<span class="w-full bg-base-200 px-4 py-2 rounded-md inline-flex space-x-2 mx-auto items-center justify-between mb-2 z-20 md:hidden">
|
<span class="w-full bg-base-200 px-4 py-2 rounded-md inline-flex space-x-2 mx-auto items-center justify-between mb-2 z-20 md:hidden">
|
||||||
<div class="flex items-center space-x-2">
|
<div class="flex items-center space-x-2">
|
||||||
<component
|
<component
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<span
|
<span
|
||||||
class="dropdown dropdown-top dropdown-end fixed bottom-4 right-4 z-10 md:hidden"
|
class="dropdown dropdown-top dropdown-end absolute bottom-4 right-4 z-10 md:hidden"
|
||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
class="btn btn-neutral text-white btn-circle btn-lg group"
|
class="btn btn-neutral text-white btn-circle btn-lg group"
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
:field="item.field"
|
:field="item.field"
|
||||||
:editable="editable"
|
:editable="editable"
|
||||||
:default-field="defaultFields.find((f) => f.name === item.field.name)"
|
:default-field="defaultFields.find((f) => f.name === item.field.name)"
|
||||||
|
:default-submitters="defaultSubmitters"
|
||||||
@start-resize="resizeDirection = $event"
|
@start-resize="resizeDirection = $event"
|
||||||
@stop-resize="resizeDirection = null"
|
@stop-resize="resizeDirection = null"
|
||||||
@start-drag="isMove = true"
|
@start-drag="isMove = true"
|
||||||
@@ -85,6 +86,11 @@ export default {
|
|||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
defaultSubmitters: {
|
||||||
|
type: Array,
|
||||||
|
required: false,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
drawField: {
|
drawField: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: false,
|
required: false,
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
@change="save"
|
@change="save"
|
||||||
>
|
>
|
||||||
<option
|
<option
|
||||||
v-for="currency in currencies"
|
v-for="currency in currenciesList"
|
||||||
:key="currency"
|
:key="currency"
|
||||||
:value="currency"
|
:value="currency"
|
||||||
>
|
>
|
||||||
@@ -167,7 +167,7 @@ export default {
|
|||||||
IconInnerShadowTop,
|
IconInnerShadowTop,
|
||||||
IconBrandStripe
|
IconBrandStripe
|
||||||
},
|
},
|
||||||
inject: ['backgroundColor', 'save'],
|
inject: ['backgroundColor', 'save', 'currencies'],
|
||||||
props: {
|
props: {
|
||||||
field: {
|
field: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@@ -187,9 +187,12 @@ export default {
|
|||||||
redirectUri () {
|
redirectUri () {
|
||||||
return document.location.origin + '/auth/stripe_connect/callback'
|
return document.location.origin + '/auth/stripe_connect/callback'
|
||||||
},
|
},
|
||||||
currencies () {
|
defaultCurrencies () {
|
||||||
return ['USD', 'EUR', 'GBP']
|
return ['USD', 'EUR', 'GBP']
|
||||||
},
|
},
|
||||||
|
currenciesList () {
|
||||||
|
return this.currencies.length ? this.currencies : this.defaultCurrencies
|
||||||
|
},
|
||||||
authenticityToken () {
|
authenticityToken () {
|
||||||
return document.querySelector('meta[name="csrf-token"]')?.content
|
return document.querySelector('meta[name="csrf-token"]')?.content
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,7 +4,11 @@ class SendSubmitterInvitationEmailJob < ApplicationJob
|
|||||||
def perform(params = {})
|
def perform(params = {})
|
||||||
submitter = Submitter.find(params['submitter_id'])
|
submitter = Submitter.find(params['submitter_id'])
|
||||||
|
|
||||||
SubmitterMailer.invitation_email(submitter).deliver_now!
|
mail = SubmitterMailer.invitation_email(submitter)
|
||||||
|
|
||||||
|
Submitters::ValidateSending.call(submitter, mail)
|
||||||
|
|
||||||
|
mail.deliver_now!
|
||||||
|
|
||||||
SubmissionEvent.create!(submitter:, event_type: 'send_email')
|
SubmissionEvent.create!(submitter:, event_type: 'send_email')
|
||||||
|
|
||||||
|
|||||||
@@ -22,12 +22,26 @@ class Account < ApplicationRecord
|
|||||||
class_name: 'TemplateFolder', dependent: :destroy, inverse_of: :account
|
class_name: 'TemplateFolder', dependent: :destroy, inverse_of: :account
|
||||||
has_many :submissions, through: :templates
|
has_many :submissions, through: :templates
|
||||||
has_many :submitters, through: :submissions
|
has_many :submitters, through: :submissions
|
||||||
|
has_many :account_linked_accounts, dependent: :destroy
|
||||||
|
has_many :account_testing_accounts, -> { testing }, dependent: :destroy,
|
||||||
|
class_name: 'AccountLinkedAccount',
|
||||||
|
inverse_of: :account
|
||||||
|
has_one :linked_account_account, dependent: :destroy,
|
||||||
|
foreign_key: :linked_account_id,
|
||||||
|
class_name: 'AccountLinkedAccount',
|
||||||
|
inverse_of: :linked_account
|
||||||
|
has_many :linked_accounts, through: :account_linked_accounts
|
||||||
|
has_many :testing_accounts, through: :account_testing_accounts, source: :linked_account
|
||||||
has_many :active_users, -> { active }, dependent: :destroy,
|
has_many :active_users, -> { active }, dependent: :destroy,
|
||||||
inverse_of: :account, class_name: 'User'
|
inverse_of: :account, class_name: 'User'
|
||||||
|
|
||||||
attribute :timezone, :string, default: 'UTC'
|
attribute :timezone, :string, default: 'UTC'
|
||||||
attribute :locale, :string, default: 'en-US'
|
attribute :locale, :string, default: 'en-US'
|
||||||
|
|
||||||
|
def testing?
|
||||||
|
linked_account_account&.testing?
|
||||||
|
end
|
||||||
|
|
||||||
def default_template_folder
|
def default_template_folder
|
||||||
super || build_default_template_folder(name: TemplateFolder::DEFAULT_NAME,
|
super || build_default_template_folder(name: TemplateFolder::DEFAULT_NAME,
|
||||||
author_id: users.minimum(:id)).tap(&:save!)
|
author_id: users.minimum(:id)).tap(&:save!)
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ class AccountConfig < ApplicationRecord
|
|||||||
ALLOW_TYPED_SIGNATURE = 'allow_typed_signature'
|
ALLOW_TYPED_SIGNATURE = 'allow_typed_signature'
|
||||||
SUBMITTER_REMAILERS = 'submitter_reminders'
|
SUBMITTER_REMAILERS = 'submitter_reminders'
|
||||||
FORM_COMPLETED_BUTTON_KEY = 'form_completed_button'
|
FORM_COMPLETED_BUTTON_KEY = 'form_completed_button'
|
||||||
|
FORM_WITH_CONFETTI_KEY = 'form_with_confetti'
|
||||||
ESIGNING_PREFERENCE_KEY = 'esigning_preference'
|
ESIGNING_PREFERENCE_KEY = 'esigning_preference'
|
||||||
|
|
||||||
DEFAULT_VALUES = {
|
DEFAULT_VALUES = {
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# == Schema Information
|
||||||
|
#
|
||||||
|
# Table name: account_linked_accounts
|
||||||
|
#
|
||||||
|
# id :bigint not null, primary key
|
||||||
|
# account_type :text not null
|
||||||
|
# created_at :datetime not null
|
||||||
|
# updated_at :datetime not null
|
||||||
|
# account_id :bigint not null
|
||||||
|
# linked_account_id :bigint not null
|
||||||
|
#
|
||||||
|
# Indexes
|
||||||
|
#
|
||||||
|
# idx_on_account_id_linked_account_id_48ab9f79d2 (account_id,linked_account_id) UNIQUE
|
||||||
|
# index_account_linked_accounts_on_account_id (account_id)
|
||||||
|
# index_account_linked_accounts_on_linked_account_id (linked_account_id)
|
||||||
|
#
|
||||||
|
# Foreign Keys
|
||||||
|
#
|
||||||
|
# fk_rails_... (account_id => accounts.id)
|
||||||
|
# fk_rails_... (linked_account_id => accounts.id)
|
||||||
|
#
|
||||||
|
class AccountLinkedAccount < ApplicationRecord
|
||||||
|
belongs_to :account
|
||||||
|
belongs_to :linked_account, class_name: 'Account'
|
||||||
|
|
||||||
|
attribute :account_type, :string, default: 'testing'
|
||||||
|
|
||||||
|
scope :testing, -> { where(account_type: :testing) }
|
||||||
|
|
||||||
|
def testing?
|
||||||
|
account_type == 'testing'
|
||||||
|
end
|
||||||
|
end
|
||||||
+2
-2
@@ -59,7 +59,7 @@ class User < ApplicationRecord
|
|||||||
has_many :encrypted_configs, dependent: :destroy, class_name: 'EncryptedUserConfig'
|
has_many :encrypted_configs, dependent: :destroy, class_name: 'EncryptedUserConfig'
|
||||||
has_many :email_messages, dependent: :destroy, foreign_key: :author_id, inverse_of: :author
|
has_many :email_messages, dependent: :destroy, foreign_key: :author_id, inverse_of: :author
|
||||||
|
|
||||||
devise :two_factor_authenticatable, :recoverable, :rememberable, :validatable, :trackable
|
devise :two_factor_authenticatable, :recoverable, :rememberable, :validatable, :trackable, :lockable
|
||||||
|
|
||||||
attribute :role, :string, default: ADMIN_ROLE
|
attribute :role, :string, default: ADMIN_ROLE
|
||||||
attribute :uuid, :string, default: -> { SecureRandom.uuid }
|
attribute :uuid, :string, default: -> { SecureRandom.uuid }
|
||||||
@@ -72,7 +72,7 @@ class User < ApplicationRecord
|
|||||||
end
|
end
|
||||||
|
|
||||||
def active_for_authentication?
|
def active_for_authentication?
|
||||||
!archived_at?
|
super && !archived_at?
|
||||||
end
|
end
|
||||||
|
|
||||||
def remember_me
|
def remember_me
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% encrypted_config = @encrypted_config || EncryptedConfig.find_or_initialize_by(account: current_account, key: EncryptedConfig::APP_URL_KEY) %>
|
<% encrypted_config = @encrypted_config || EncryptedConfig.find_or_initialize_by(account: current_account, key: EncryptedConfig::APP_URL_KEY) %>
|
||||||
<% if !Docuseal.multitenant? && can?(:manage, encrypted_config) %>
|
<% if !Docuseal.multitenant? && can?(:manage, encrypted_config) && !current_account.testing? %>
|
||||||
<%= f.fields_for encrypted_config do |ff| %>
|
<%= f.fields_for encrypted_config do |ff| %>
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<%= ff.label :value, 'App URL', class: 'label' %>
|
<%= ff.label :value, 'App URL', class: 'label' %>
|
||||||
@@ -65,6 +65,12 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
<% if can?(:manage, current_account) && Docuseal.multitenant? && true_user == current_user %>
|
||||||
|
<div class="px-1 mt-8">
|
||||||
|
<h2 class="text-2xl font-bold mb-2">Danger Zone</h2>
|
||||||
|
<%= button_to button_title(title: 'Delete my account'), settings_account_path, class: 'btn btn-outline btn-error block', data: { turbo_confirm: 'Schedule account for deletion?' }, method: :delete, id: :account_delete_button %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-0 md:w-52"></div>
|
<div class="w-0 md:w-52"></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1,14 +1,19 @@
|
|||||||
<div class="flex flex-wrap space-y-4 md:flex-nowrap md:space-y-0 md:space-x-10">
|
<div class="flex flex-wrap space-y-4 md:flex-nowrap md:space-y-0 md:space-x-10">
|
||||||
<%= render 'shared/settings_nav' %>
|
<%= render 'shared/settings_nav' %>
|
||||||
<div class="flex-grow">
|
<div class="flex-grow">
|
||||||
<h1 class="text-4xl font-bold mb-4">API</h1>
|
<div class="flex justify-between">
|
||||||
|
<h1 class="text-4xl font-bold mb-4">API</h1>
|
||||||
|
<%= render 'shared/test_mode_toggle' %>
|
||||||
|
</div>
|
||||||
<div class="card bg-base-200">
|
<div class="card bg-base-200">
|
||||||
<div class="card-body p-6">
|
<div class="card-body p-6">
|
||||||
<label for="api_key" class="text-sm font-semibold">X-Auth-Token</label>
|
<label for="api_key" class="text-sm font-semibold">X-Auth-Token</label>
|
||||||
<div class="flex flex-row space-x-4">
|
<div class="flex flex-col md:flex-row gap-4">
|
||||||
<input id="api_key" type="text" value="<%= current_user.access_token.token %>" class="input font-mono input-bordered w-full" autocomplete="off" readonly>
|
<div class="flex w-full space-x-4">
|
||||||
<%= render 'shared/clipboard_copy', icon: 'copy', text: current_user.access_token.token, class: 'base-button', icon_class: 'w-6 h-6 text-white', copy_title: 'Copy', copied_title: 'Copied' %>
|
<input id="api_key" type="text" value="<%= current_user.access_token.token %>" class="input font-mono input-bordered w-full" autocomplete="off" readonly>
|
||||||
<%= button_to button_title(title: 'Rotate', disabled_with: 'Rotate', icon: svg_icon('reload', class: 'w-6 h-6')), settings_api_index_path, class: 'white-button', data: { turbo_confirm: 'Remove existing API token and generated a new one. Are you sure?' } %>
|
<%= render 'shared/clipboard_copy', icon: 'copy', text: current_user.access_token.token, class: 'base-button', icon_class: 'w-6 h-6 text-white', copy_title: 'Copy', copied_title: 'Copied' %>
|
||||||
|
</div>
|
||||||
|
<%= button_to button_title(title: 'Rotate', disabled_with: 'Rotate', icon: svg_icon('reload', class: 'w-6 h-6')), settings_api_index_path, class: 'white-button w-full', data: { turbo_confirm: 'Remove existing API token and generated a new one. Are you sure?' } %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -30,20 +35,16 @@
|
|||||||
--header 'X-Auth-Token: <%= current_user.access_token.token %>' \
|
--header 'X-Auth-Token: <%= current_user.access_token.token %>' \
|
||||||
--data-raw '{
|
--data-raw '{
|
||||||
"template_id": <%= current_account.templates.last&.id || 1 %>,
|
"template_id": <%= current_account.templates.last&.id || 1 %>,
|
||||||
"submission": [
|
"submitters": [
|
||||||
{
|
{
|
||||||
"submitters": [
|
"name": "John Doe",
|
||||||
{
|
"role": "<%= current_account.templates.last ? current_account.templates.last.submitters.first['name'] : 'First Party' %>",
|
||||||
"name": "John Doe",
|
"email": "<%= current_user.email.sub('@', '+test@') %>",
|
||||||
"role": "<%= current_account.templates.last ? current_account.templates.last.submitters.first['name'] : 'First Party' %>",
|
"values": {
|
||||||
"email": "<%= current_user.email.sub('@', '+test@') %>",
|
"Form Text Field Name": "Default Value"
|
||||||
"values": {
|
}
|
||||||
"Form Text Field Name": "Default Value"
|
},
|
||||||
}
|
{ "role": "Second Submitter", "email": "<%= current_user.email.sub('@', '+test2@') %>" }
|
||||||
},
|
|
||||||
{ "role": "Second Submitter", "email": "<%= current_user.email.sub('@', '+test2@') %>" }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
}'<% end.to_str %>
|
}'<% end.to_str %>
|
||||||
<span class="top-0 right-0 absolute">
|
<span class="top-0 right-0 absolute">
|
||||||
|
|||||||
@@ -1,101 +1,80 @@
|
|||||||
<% if Docuseal.demo? %><%= render 'shared/demo_alert' %><% end %>
|
<% if Docuseal.demo? %><%= render 'shared/demo_alert' %><% end %>
|
||||||
<% if @pagy.count > 0 || params[:q].present? || @template_folders.present? %>
|
<div class="flex justify-between mb-4 items-center">
|
||||||
<div class="flex justify-between mb-4 items-center">
|
<h1 class="text-4xl font-bold"><span class="hidden md:inline">Document</span> Templates</h1>
|
||||||
<h1 class="text-4xl font-bold"><span class="hidden md:inline">Document</span> Templates</h1>
|
<div class="flex space-x-2">
|
||||||
<div class="flex space-x-2">
|
<% if params[:q].present? || @pagy.pages > 1 || @template_folders.present? %>
|
||||||
<% if params[:q].present? || @pagy.pages > 1 || @template_folders.present? %>
|
<%= render 'shared/search_input' %>
|
||||||
<%= render 'shared/search_input' %>
|
<% end %>
|
||||||
|
<% if can?(:create, ::Template) %>
|
||||||
|
<%= render 'templates/upload_button' %>
|
||||||
|
<%= link_to new_template_path, class: 'white-button !border gap-2', data: { turbo_frame: :modal } do %>
|
||||||
|
<%= svg_icon('plus', class: 'w-6 h-6 stroke-2') %>
|
||||||
|
<span class="hidden md:block">Create</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if can?(:create, ::Template) %>
|
<% end %>
|
||||||
<%= render 'templates/upload_button' %>
|
|
||||||
<%= link_to new_template_path, class: 'white-button !border gap-2', data: { turbo_frame: :modal } do %>
|
|
||||||
<%= svg_icon('plus', class: 'w-6 h-6 stroke-2') %>
|
|
||||||
<span class="hidden md:block">Create</span>
|
|
||||||
<% end %>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<% view_archived_html = capture do %>
|
</div>
|
||||||
<% if current_account.templates.where.not(archived_at: nil).exists? %>
|
<% view_archived_html = capture do %>
|
||||||
<div>
|
<% if current_account.templates.where.not(archived_at: nil).exists? %>
|
||||||
<a href="<%= templates_archived_index_path %>" class="link text-sm">View Archived</a>
|
<div>
|
||||||
</div>
|
<a href="<%= templates_archived_index_path %>" class="link text-sm">View Archived</a>
|
||||||
<% end %>
|
|
||||||
<% end %>
|
|
||||||
<% if @template_folders.present? %>
|
|
||||||
<div class="grid gap-4 md:grid-cols-3 <%= 'mb-6' if @templates.present? %>">
|
|
||||||
<%= render partial: 'template_folders/folder', collection: @template_folders, as: :folder %>
|
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if @templates.present? %>
|
<% end %>
|
||||||
<div class="grid gap-4 md:grid-cols-3">
|
<% if @template_folders.present? %>
|
||||||
<%= render partial: 'templates/template', collection: @templates %>
|
<div class="grid gap-4 md:grid-cols-3 <%= 'mb-6' if @templates.present? %>">
|
||||||
</div>
|
<%= render partial: 'template_folders/folder', collection: @template_folders, as: :folder %>
|
||||||
<% end %>
|
</div>
|
||||||
<% if params[:q].blank? && @pagy.pages == 1 && ((@template_folders.size < 10 && @templates.size.zero?) || (@template_folders.size < 7 && @templates.size < 4) || (@template_folders.size < 4 && @templates.size < 7)) %>
|
<% end %>
|
||||||
<%= form_for '', url: templates_upload_path, id: form_id = SecureRandom.uuid, method: :post, class: 'mt-8 block', html: { enctype: 'multipart/form-data' } do %>
|
<% if @templates.present? %>
|
||||||
<input type="hidden" name="form_id" value="<%= form_id %>">
|
<div class="grid gap-4 md:grid-cols-3">
|
||||||
<button type="submit" class="hidden"></button>
|
<%= render partial: 'templates/template', collection: @templates %>
|
||||||
<file-dropzone data-submit-on-upload="true" class="w-full">
|
</div>
|
||||||
<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">
|
<% end %>
|
||||||
<div class="absolute top-0 right-0 left-0 bottom-0 flex items-center justify-center">
|
<% if params[:q].blank? && @pagy.pages == 1 && ((@template_folders.size < 10 && @templates.size.zero?) || (@template_folders.size < 7 && @templates.size < 4) || (@template_folders.size < 4 && @templates.size < 7)) %>
|
||||||
<div class="flex flex-col items-center">
|
<%= form_for '', url: templates_upload_path, id: form_id = SecureRandom.uuid, method: :post, class: 'mt-8 block', html: { enctype: 'multipart/form-data' } do %>
|
||||||
<span data-target="file-dropzone.icon" class="flex flex-col items-center">
|
<input type="hidden" name="form_id" value="<%= form_id %>">
|
||||||
<span>
|
<button type="submit" class="hidden"></button>
|
||||||
<%= svg_icon('cloud_upload', class: 'w-10 h-10') %>
|
<file-dropzone data-submit-on-upload="true" class="w-full">
|
||||||
</span>
|
<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="font-medium mb-1">
|
<div class="absolute top-0 right-0 left-0 bottom-0 flex items-center justify-center">
|
||||||
Upload New Document
|
<div class="flex flex-col items-center">
|
||||||
</div>
|
<span data-target="file-dropzone.icon" class="flex flex-col items-center">
|
||||||
<div class="text-xs">
|
<span>
|
||||||
<span class="font-medium">Click to upload</span> or drag and drop
|
<%= svg_icon('cloud_upload', class: 'w-10 h-10') %>
|
||||||
</div>
|
|
||||||
</span>
|
</span>
|
||||||
<span data-target="file-dropzone.loading" class="flex flex-col items-center hidden">
|
<div class="font-medium mb-1">
|
||||||
<%= svg_icon('loader', class: 'w-10 h-10 animate-spin') %>
|
Upload New Document
|
||||||
<div class="font-medium mb-1">
|
</div>
|
||||||
Uploading...
|
<div class="text-xs">
|
||||||
</div>
|
<span class="font-medium">Click to upload</span> or drag and drop
|
||||||
</span>
|
</div>
|
||||||
</div>
|
</span>
|
||||||
<input id="file_dropzone_input" name="files[]" class="hidden" data-action="change:file-dropzone#onSelectFiles" data-target="file-dropzone.input" type="file" accept="image/*, application/pdf<%= ', .docx, .doc, .xlsx, .xls' if Docuseal.multitenant? %>" multiple>
|
<span data-target="file-dropzone.loading" class="flex flex-col items-center hidden">
|
||||||
</div>
|
<%= svg_icon('loader', class: 'w-10 h-10 animate-spin') %>
|
||||||
</label>
|
<div class="font-medium mb-1">
|
||||||
</file-dropzone>
|
Uploading...
|
||||||
<% end %>
|
</div>
|
||||||
<% end %>
|
</span>
|
||||||
<% if @templates.present? || params[:q].blank? %>
|
|
||||||
<% if @pagy.pages > 1 %>
|
|
||||||
<%= render 'shared/pagination', pagy: @pagy, items_name: 'templates', left_additional_html: view_archived_html %>
|
|
||||||
<% else %>
|
|
||||||
<div class="mt-2">
|
|
||||||
<%= view_archived_html %>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
<% elsif params[:q].present? %>
|
|
||||||
<div class="text-center">
|
|
||||||
<div class="mt-16 text-3xl font-semibold">
|
|
||||||
Templates not Found
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
<% else %>
|
|
||||||
<div class="card bg-base-200 h-96 mb-2">
|
|
||||||
<div class="card-body text-center">
|
|
||||||
<div class="flex items-center h-full">
|
|
||||||
<div class="mx-auto">
|
|
||||||
<div class="max-w-xl mx-auto">
|
|
||||||
<h1 class="text-5xl font-bold text-base-content">👋 Welcome to <%= Docuseal.product_name %></h1>
|
|
||||||
</div>
|
|
||||||
<div class="max-w-lg mx-auto">
|
|
||||||
<p class="py-6 text-gray-600">Streamline document workflows, from creating customizable templates to filling and signing document forms</p>
|
|
||||||
<%= link_to new_template_path, class: 'base-button', data: { turbo_frame: :modal } do %>
|
|
||||||
<%= svg_icon('plus', class: 'w-6 h-6 stroke-2') %>
|
|
||||||
<span class="mr-1">Create Template</span>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
</div>
|
||||||
|
<input id="file_dropzone_input" name="files[]" class="hidden" data-action="change:file-dropzone#onSelectFiles" data-target="file-dropzone.input" type="file" accept="image/*, application/pdf<%= ', .docx, .doc, .xlsx, .xls' if Docuseal.multitenant? %>" multiple>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</label>
|
||||||
|
</file-dropzone>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
<% if @templates.present? || params[:q].blank? %>
|
||||||
|
<% if @pagy.pages > 1 %>
|
||||||
|
<%= render 'shared/pagination', pagy: @pagy, items_name: 'templates', left_additional_html: view_archived_html %>
|
||||||
|
<% else %>
|
||||||
|
<div class="mt-2">
|
||||||
|
<%= view_archived_html %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
<% elsif params[:q].present? %>
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="mt-16 text-3xl font-semibold">
|
||||||
|
Templates not Found
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
@@ -17,15 +17,19 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
<% if devise_mapping.omniauthable? %>
|
<% if devise_mapping.omniauthable? %>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<%= form_for '', url: omniauth_authorize_path(resource_name, :google_oauth2), data: { turbo: false }, method: :post do |f| %>
|
<% if User.omniauth_providers.include?(:google_oauth2) %>
|
||||||
<set-timezone data-input-id="state" data-params="true"></set-timezone>
|
<%= form_for '', url: omniauth_authorize_path(resource_name, :google_oauth2), data: { turbo: false }, method: :post do |f| %>
|
||||||
<%= hidden_field_tag :state, { redir: params[:redir].to_s }.compact_blank.to_query %>
|
<set-timezone data-input-id="state" data-params="true"></set-timezone>
|
||||||
<%= f.button button_title(title: 'Sign up with Google', icon: svg_icon('brand_google', class: 'w-6 h-6')), class: 'white-button w-full mt-4' %>
|
<%= hidden_field_tag :state, { redir: params[:redir].to_s }.compact_blank.to_query %>
|
||||||
|
<%= f.button button_title(title: 'Sign up with Google', icon: svg_icon('brand_google', class: 'w-6 h-6')), class: 'white-button w-full mt-4' %>
|
||||||
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= form_for '', url: omniauth_authorize_path(resource_name, :microsoft_office365), data: { turbo: false }, method: :post do |f| %>
|
<% if User.omniauth_providers.include?(:microsoft_office365) %>
|
||||||
<set-timezone data-input-id="state_microsoft" data-params="true"></set-timezone>
|
<%= form_for '', url: omniauth_authorize_path(resource_name, :microsoft_office365), data: { turbo: false }, method: :post do |f| %>
|
||||||
<%= hidden_field_tag :state, { redir: params[:redir].to_s }.compact_blank.to_query, id: 'state_microsoft' %>
|
<set-timezone data-input-id="state_microsoft" data-params="true"></set-timezone>
|
||||||
<%= f.button button_title(title: 'Sign up with Microsoft', icon: svg_icon('brand_microsoft', class: 'w-6 h-6')), class: 'white-button w-full' %>
|
<%= hidden_field_tag :state, { redir: params[:redir].to_s }.compact_blank.to_query, id: 'state_microsoft' %>
|
||||||
|
<%= f.button button_title(title: 'Sign up with Microsoft', icon: svg_icon('brand_microsoft', class: 'w-6 h-6')), class: 'white-button w-full' %>
|
||||||
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -21,15 +21,19 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
<% if devise_mapping.omniauthable? %>
|
<% if devise_mapping.omniauthable? %>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<%= form_for '', url: omniauth_authorize_path(resource_name, :google_oauth2), data: { turbo: false }, method: :post do |f| %>
|
<% if User.omniauth_providers.include?(:google_oauth2) %>
|
||||||
<set-timezone data-input-id="state" data-params="true"></set-timezone>
|
<%= form_for '', url: omniauth_authorize_path(resource_name, :google_oauth2), data: { turbo: false }, method: :post do |f| %>
|
||||||
<%= hidden_field_tag :state, { redir: params[:redir].to_s }.compact_blank.to_query %>
|
<set-timezone data-input-id="state" data-params="true"></set-timezone>
|
||||||
<%= f.button button_title(title: 'Sign in with Google', icon: svg_icon('brand_google', class: 'w-6 h-6')), class: 'white-button w-full mt-4' %>
|
<%= hidden_field_tag :state, { redir: params[:redir].to_s }.compact_blank.to_query %>
|
||||||
|
<%= f.button button_title(title: 'Sign in with Google', icon: svg_icon('brand_google', class: 'w-6 h-6')), class: 'white-button w-full mt-4' %>
|
||||||
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= form_for '', url: omniauth_authorize_path(resource_name, :microsoft_office365), data: { turbo: false }, method: :post do |f| %>
|
<% if User.omniauth_providers.include?(:microsoft_office365) %>
|
||||||
<set-timezone data-input-id="state_microsoft" data-params="true"></set-timezone>
|
<%= form_for '', url: omniauth_authorize_path(resource_name, :microsoft_office365), data: { turbo: false }, method: :post do |f| %>
|
||||||
<%= hidden_field_tag :state, { redir: params[:redir].to_s }.compact_blank.to_query, id: 'state_microsoft' %>
|
<set-timezone data-input-id="state_microsoft" data-params="true"></set-timezone>
|
||||||
<%= f.button button_title(title: 'Sign in with Microsoft', icon: svg_icon('brand_microsoft', class: 'w-6 h-6')), class: 'white-button w-full' %>
|
<%= hidden_field_tag :state, { redir: params[:redir].to_s }.compact_blank.to_query, id: 'state_microsoft' %>
|
||||||
|
<%= f.button button_title(title: 'Sign in with Microsoft', icon: svg_icon('brand_microsoft', class: 'w-6 h-6')), class: 'white-button w-full' %>
|
||||||
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<%= f.label :otp_attempt, 'Two-Factor Code', class: 'label' %>
|
<%= f.label :otp_attempt, 'Two-Factor Code from Authenticator App', class: 'label' %>
|
||||||
<%= f.text_field :otp_attempt, autofocus: true, placeholder: 'XXX-XXX', required: true, class: 'base-input' %>
|
<%= f.text_field :otp_attempt, autofocus: true, placeholder: 'XXX-XXX', required: true, class: 'base-input' %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="<%= local_assigns[:class] %>" width="44" height="44" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<path d="M4 13h5" />
|
||||||
|
<path d="M12 16v-8h3a2 2 0 0 1 2 2v1a2 2 0 0 1 -2 2h-3" />
|
||||||
|
<path d="M20 8v8" />
|
||||||
|
<path d="M9 16v-5.5a2.5 2.5 0 0 0 -5 0v5.5" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 439 B |
@@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="<%= local_assigns[:class] %>" width="44" height="44" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<path d="M15 6l-6 6l6 6" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 313 B |
@@ -0,0 +1,7 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="<%= local_assigns[:class] %>" width="44" height="44" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<path d="M8.5 13.5l-1.5 -1.5l1.5 -1.5" />
|
||||||
|
<path d="M15.5 10.5l1.5 1.5l-1.5 1.5" />
|
||||||
|
<path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" />
|
||||||
|
<path d="M13 9.5l-2 5.5" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 458 B |
@@ -3,7 +3,7 @@
|
|||||||
Use an authenticator mobile app like Google Authenticator or 1Password to scan the QR code below.
|
Use an authenticator mobile app like Google Authenticator or 1Password to scan the QR code below.
|
||||||
</p>
|
</p>
|
||||||
<div>
|
<div>
|
||||||
<%== RQRCode::QRCode.new(@provision_url).as_svg(viewbox: true, svg_attributes: { class: 'w-80 h-80 my-4 mx-auto' }) %>
|
<%== RQRCode::QRCode.new(@provision_url).as_svg(viewbox: true, svg_attributes: { class: 'md:w-80 md:h-80 my-4 mx-auto' }) %>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-control my-6 space-y-2">
|
<div class="form-control my-6 space-y-2">
|
||||||
<%= f.text_field :otp_attempt, required: true, placeholder: 'XXX-XXX', class: 'base-input text-center' %>
|
<%= f.text_field :otp_attempt, required: true, placeholder: 'XXX-XXX', class: 'base-input text-center' %>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<%= f.hidden_field :key %>
|
<%= f.hidden_field :key %>
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<% record = Struct.new(:first_duration, :second_duration, :third_duration).new(*(f.object.value || {}).values_at('first_duration', 'second_duration', 'third_duration')) %>
|
<% record = Struct.new(:first_duration, :second_duration, :third_duration).new(*(f.object.value || {}).values_at('first_duration', 'second_duration', 'third_duration')) %>
|
||||||
<div class="flex space-x-4">
|
<div class="flex flex-col md:flex-row gap-4">
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<%= f.fields_for :value, record do |ff| %>
|
<%= f.fields_for :value, record do |ff| %>
|
||||||
<%= ff.label :first_duration, 'First reminder in', class: 'label' %>
|
<%= ff.label :first_duration, 'First reminder in', class: 'label' %>
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
<%= link_to Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CONSOLE_URL}/on_premise", class: 'hidden md:inline-flex btn btn-warning btn-sm', data: { prefetch: false } do %>
|
<%= link_to Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CONSOLE_URL}/on_premise", class: 'hidden md:inline-flex btn btn-warning btn-sm', data: { prefetch: false } do %>
|
||||||
Upgrade
|
Upgrade
|
||||||
<% end %>
|
<% end %>
|
||||||
|
<% if signed_in? && current_user != true_user %>
|
||||||
|
<span class="hidden md:inline-flex h-3 border-r border-base-content"></span>
|
||||||
|
<%= render 'shared/test_alert' %>
|
||||||
|
<% end %>
|
||||||
<span class="hidden md:inline-flex h-3 border-r border-base-content"></span>
|
<span class="hidden md:inline-flex h-3 border-r border-base-content"></span>
|
||||||
@@ -11,17 +11,17 @@
|
|||||||
<%= link_to 'Account', settings_account_path, class: 'text-base hover:bg-base-300' %>
|
<%= link_to 'Account', settings_account_path, class: 'text-base hover:bg-base-300' %>
|
||||||
</li>
|
</li>
|
||||||
<% unless Docuseal.multitenant? %>
|
<% unless Docuseal.multitenant? %>
|
||||||
<% if can?(:read, EncryptedConfig.new(key: EncryptedConfig::EMAIL_SMTP_KEY, account: current_account)) && ENV['SMTP_ADDRESS'].blank? %>
|
<% if can?(:read, EncryptedConfig.new(key: EncryptedConfig::EMAIL_SMTP_KEY, account: current_account)) && ENV['SMTP_ADDRESS'].blank? && true_user == current_user %>
|
||||||
<li>
|
<li>
|
||||||
<%= link_to 'Email', settings_email_index_path, class: 'text-base hover:bg-base-300' %>
|
<%= link_to 'Email', settings_email_index_path, class: 'text-base hover:bg-base-300' %>
|
||||||
</li>
|
</li>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if can?(:read, EncryptedConfig.new(key: EncryptedConfig::FILES_STORAGE_KEY, account: current_account)) %>
|
<% if can?(:read, EncryptedConfig.new(key: EncryptedConfig::FILES_STORAGE_KEY, account: current_account)) && true_user == current_user %>
|
||||||
<li>
|
<li>
|
||||||
<%= link_to 'Storage', settings_storage_index_path, class: 'text-base hover:bg-base-300' %>
|
<%= link_to 'Storage', settings_storage_index_path, class: 'text-base hover:bg-base-300' %>
|
||||||
</li>
|
</li>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if can?(:read, EncryptedConfig.new(key: 'submitter_invitation_sms', account: current_account)) %>
|
<% if can?(:read, EncryptedConfig.new(key: 'submitter_invitation_sms', account: current_account)) && true_user == current_user %>
|
||||||
<li>
|
<li>
|
||||||
<%= link_to 'SMS', settings_sms_path, class: 'text-base hover:bg-base-300' %>
|
<%= link_to 'SMS', settings_sms_path, class: 'text-base hover:bg-base-300' %>
|
||||||
</li>
|
</li>
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
</li>
|
</li>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% unless Docuseal.multitenant? %>
|
<% unless Docuseal.multitenant? %>
|
||||||
<% if can?(:read, EncryptedConfig.new(key: 'saml_configs', account: current_account)) %>
|
<% if can?(:read, EncryptedConfig.new(key: 'saml_configs', account: current_account)) && true_user == current_user %>
|
||||||
<li>
|
<li>
|
||||||
<%= link_to 'SSO', settings_sso_index_path, class: 'text-base hover:bg-base-300' %>
|
<%= link_to 'SSO', settings_sso_index_path, class: 'text-base hover:bg-base-300' %>
|
||||||
</li>
|
</li>
|
||||||
@@ -88,10 +88,22 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
</li>
|
</li>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
<% if (can?(:manage, EncryptedConfig) && current_user == true_user) || (current_user != true_user && current_account.testing?) %>
|
||||||
|
<li>
|
||||||
|
<%= form_for '', url: testing_account_path, method: current_account.testing? ? :delete : :get, html: { class: 'flex w-full' } do |f| %>
|
||||||
|
<label class="flex items-center text-base hover:bg-base-300 w-full justify-between" for="testing_toggle">
|
||||||
|
<span class="mr-2 w-full">
|
||||||
|
Test Environment
|
||||||
|
</span>
|
||||||
|
<%= f.check_box :testing_toggle, class: 'toggle toggle-sm', checked: current_account.testing?, onchange: 'this.form.requestSubmit()' %>
|
||||||
|
</label>
|
||||||
|
<% end %>
|
||||||
|
</li>
|
||||||
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</ul>
|
</ul>
|
||||||
</menu-active>
|
</menu-active>
|
||||||
<% if !can?(:manage, :tenants) %>
|
<% if Docuseal.multitenant? || cannot?(:manage, :tenants) %>
|
||||||
<div class="mx-4 border-t border-base-300 hidden md:block">
|
<div class="mx-4 border-t border-base-300 hidden md:block">
|
||||||
<div class="text-sm mt-3">
|
<div class="text-sm mt-3">
|
||||||
Need help? Ask a question:
|
Need help? Ask a question:
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<% if signed_in? && current_user != true_user && current_account.testing? %>
|
||||||
|
<div class="alert py-1 text-sm font-medium gap-x-2 flex whitespace-nowrap">
|
||||||
|
<a href="<%= testing_api_settings_path %>" data-turbo-frame="modal" class="link font-semibold flex">
|
||||||
|
<%= svg_icon('code_circle', class: 'w-5 h-5 mr-1') %>Testing Environment
|
||||||
|
</a>
|
||||||
|
<span>
|
||||||
|
|
|
||||||
|
</span>
|
||||||
|
<%= button_to testing_account_path, method: :delete, class: 'inline flex' do %>
|
||||||
|
<% title = capture do %>
|
||||||
|
<span class="link">Exit</span>
|
||||||
|
<span>×</span>
|
||||||
|
<% end %>
|
||||||
|
<%= button_title(title:, disabled_with: 'Leave') %>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<% if can?(:manage, EncryptedConfig) || (current_user != true_user && current_account.testing?) %>
|
||||||
|
<%= form_for '', url: testing_account_path, method: current_account.testing? ? :delete : :get, html: { class: 'flex' } do |f| %>
|
||||||
|
<label class="flex items-center justify-between py-2.5" for="testing_toggle">
|
||||||
|
<span class="mr-2 text-lg">
|
||||||
|
Test Environment
|
||||||
|
</span>
|
||||||
|
<%= f.check_box :testing_toggle, class: 'toggle', checked: current_account.testing?, onchange: 'this.form.requestSubmit()' %>
|
||||||
|
</label>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
<%= fff.label :endpoint, 'Endpoint (optional)', class: 'label' %>
|
<%= fff.label :endpoint, 'Endpoint (optional)', class: 'label' %>
|
||||||
<%= fff.text_field :endpoint, value: configs['endpoint'], type: :url, class: 'base-input' %>
|
<%= fff.text_field :endpoint, value: configs['endpoint'], type: :url, class: 'base-input' %>
|
||||||
<label class="label">
|
<label class="label">
|
||||||
<span class="label-text-alt">For AWS S3 compantiable APIs like Minio.</span>
|
<span class="label-text-alt">For AWS S3 compatible APIs like Minio.</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<% options.each_with_index do |(label, value), index| %>
|
<% options.each_with_index do |(label, value), index| %>
|
||||||
<span>
|
<span>
|
||||||
<%= radio_button_tag 'option', value, value == 'email', class: 'peer hidden', data: { action: 'change:toggle-visible#trigger' } %>
|
<%= radio_button_tag 'option', value, value == 'email', class: 'peer hidden', data: { action: 'change:toggle-visible#trigger' } %>
|
||||||
<label for="option_<%= value %>" class="<%= '!rounded-s-full' if index.zero? %> btn btn-focus btn-sm join-item w-28 peer-checked:btn-active normal-case">
|
<label for="option_<%= value %>" class="<%= '!rounded-s-full' if index.zero? %> btn btn-focus btn-sm join-item md:w-28 peer-checked:btn-active normal-case">
|
||||||
<%= label %>
|
<%= label %>
|
||||||
</label>
|
</label>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -41,7 +41,7 @@
|
|||||||
</a>
|
</a>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full overflow-y-auto overflow-x-hidden mt-0.5 pt-0.5">
|
<div id="document_view" class="w-full overflow-y-auto overflow-x-hidden mt-0.5 pt-0.5">
|
||||||
<div class="pr-3.5 pl-0.5">
|
<div class="pr-3.5 pl-0.5">
|
||||||
<% fields_index = Templates.build_field_areas_index(@submission.template_fields || @submission.template.fields) %>
|
<% fields_index = Templates.build_field_areas_index(@submission.template_fields || @submission.template.fields) %>
|
||||||
<% values = @submission.submitters.reduce({}) { |acc, sub| acc.merge(sub.values) } %>
|
<% values = @submission.submitters.reduce({}) { |acc, sub| acc.merge(sub.values) } %>
|
||||||
@@ -71,7 +71,7 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="hidden md:block relative w-80 flex-none pt-0.5 pr-4 pl-0.5 overflow-auto space">
|
<div id="parties_view" class="hidden md:block relative w-full md:w-80 flex-none pt-0.5 pr-4 pl-0.5 overflow-auto space">
|
||||||
<% colors = %w[bg-red-500 bg-sky-500 bg-emerald-500 bg-yellow-300 bg-purple-600 bg-pink-500 bg-cyan-500 bg-orange-500 bg-lime-500 bg-indigo-500] %>
|
<% colors = %w[bg-red-500 bg-sky-500 bg-emerald-500 bg-yellow-300 bg-purple-600 bg-pink-500 bg-cyan-500 bg-orange-500 bg-lime-500 bg-indigo-500] %>
|
||||||
<% submitter_fields_index = (@submission.template_fields || @submission.template.fields).group_by { |f| f['submitter_uuid'] } %>
|
<% submitter_fields_index = (@submission.template_fields || @submission.template.fields).group_by { |f| f['submitter_uuid'] } %>
|
||||||
<% submitter_field_counters = Hash.new { 0 } %>
|
<% submitter_field_counters = Hash.new { 0 } %>
|
||||||
@@ -173,5 +173,16 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<label class="md:hidden btn btn-sm btn-neutral text-white text-base z-10 fixed bottom-2 right-2 h-16 shadow-lg">
|
||||||
|
<input type="checkbox" class="peer hidden" onclick="[document_view.classList.toggle('hidden'), parties_view.classList.toggle('hidden')]">
|
||||||
|
<span class="peer-checked:hidden flex items-center space-x-2">
|
||||||
|
<%= svg_icon('users', class: 'w-8 h-8') %>
|
||||||
|
<span>Signers</span>
|
||||||
|
</span>
|
||||||
|
<span class="hidden peer-checked:flex items-center">
|
||||||
|
<%= svg_icon('chevron_left', class: 'w-8 h-8') %>
|
||||||
|
<span>Back</span>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<%= render 'scripts/autosize_field' %>
|
<%= render 'scripts/autosize_field' %>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<% data_attachments = attachments_index.values.select { |e| e.record_id == submitter.id }.to_json(only: %i[uuid], methods: %i[url filename content_type]) %>
|
<% data_attachments = attachments_index.values.select { |e| e.record_id == submitter.id }.to_json(only: %i[uuid], methods: %i[url filename content_type]) %>
|
||||||
<% data_fields = (submitter.submission.template_fields || submitter.submission.template.fields).select { |f| f['submitter_uuid'] == submitter.uuid }.to_json %>
|
<% data_fields = (submitter.submission.template_fields || submitter.submission.template.fields).select { |f| f['submitter_uuid'] == submitter.uuid }.to_json %>
|
||||||
<% configs = Submitters::FormConfigs.call(submitter) %>
|
<% configs = Submitters::FormConfigs.call(submitter) %>
|
||||||
<submission-form data-is-demo="<%= Docuseal.demo? %>" data-completed-redirect-url="<%= submitter.preferences['completed_redirect_url'] %>" data-completed-button="<%= configs[:completed_button].to_json %>" data-go-to-last="<%= submitter.opened_at? %>" data-is-direct-upload="<%= Docuseal.active_storage_public? %>" data-submitter="<%= submitter.to_json(only: %i[uuid slug name phone email]) %>" data-can-send-email="<%= Accounts.can_send_emails?(Struct.new(:id).new(@submitter.submission.template.account_id)) %>" data-attachments="<%= data_attachments %>" data-fields="<%= data_fields %>" data-values="<%= submitter.values.to_json %>" data-with-typed-signature="<%= configs[:with_typed_signature] %>"></submission-form>
|
<submission-form data-is-demo="<%= Docuseal.demo? %>" data-with-confetti="<%= configs[:with_confetti] %>" data-completed-redirect-url="<%= submitter.preferences['completed_redirect_url'] %>" data-completed-button="<%= configs[:completed_button].to_json %>" data-go-to-last="<%= submitter.opened_at? %>" data-is-direct-upload="<%= Docuseal.active_storage_public? %>" data-submitter="<%= submitter.to_json(only: %i[uuid slug name phone email]) %>" data-can-send-email="<%= Accounts.can_send_emails?(Struct.new(:id).new(@submitter.submission.template.account_id)) %>" data-attachments="<%= data_attachments %>" data-fields="<%= data_fields %>" data-values="<%= submitter.values.to_json %>" data-with-typed-signature="<%= configs[:with_typed_signature] %>"></submission-form>
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
</span>
|
</span>
|
||||||
</download-button>
|
</download-button>
|
||||||
</div>
|
</div>
|
||||||
<% if @submitter.submission.template.submitters.size == 1 %>
|
<% if @submitter.submission.template.submitters.size == 1 && %w[api embed].exclude?(@submitter.submission.source) %>
|
||||||
<div class="divider uppercase"><%= t('or') %></div>
|
<div class="divider uppercase"><%= t('or') %></div>
|
||||||
<div>
|
<div>
|
||||||
<%= 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, form: { onsubmit: 'event.submitter.disabled = true' }, 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(@submitter.submission.template.slug), params: { submitter: { email: @submitter.email, phone: @submitter.phone, name: @submitter.name }, resubmit: @submitter.slug }, method: :put, form: { onsubmit: 'event.submitter.disabled = true' }, class: 'white-button w-full' %>
|
||||||
|
|||||||
@@ -11,8 +11,8 @@
|
|||||||
<%= svg_icon('folder', class: 'w-6 h-6') %>
|
<%= svg_icon('folder', class: 'w-6 h-6') %>
|
||||||
</a>
|
</a>
|
||||||
<folder-autocomplete class="flex justify-between w-full">
|
<folder-autocomplete class="flex justify-between w-full">
|
||||||
<input id="folder_name" placeholder="Folder Name" type="text" class="w-64 outline-none border-transparent focus:border-transparent focus:ring-0 bg-base-100 px-1 peer" name="folder_name" value="<%= params[:folder_name].presence || @base_template&.folder&.name || TemplateFolder::DEFAULT_NAME %>" onblur="window.folder_name.value = window.folder_name.value || 'Default'" autocomplete="off">
|
<input id="folder_name" placeholder="Folder Name" type="text" class="w-full outline-none border-transparent focus:border-transparent focus:ring-0 bg-base-100 px-1 peer" name="folder_name" value="<%= params[:folder_name].presence || @base_template&.folder&.name || TemplateFolder::DEFAULT_NAME %>" onblur="window.folder_name.value = window.folder_name.value || 'Default'" autocomplete="off">
|
||||||
<a href="#" onclick="[event.preventDefault(), window.folder_name.value = '', window.folder_name.focus()]" class="link peer-focus:hidden mr-1.5">
|
<a href="#" onclick="[event.preventDefault(), window.folder_name.value = '', window.folder_name.focus()]" class="shrink-0 link peer-focus:hidden mr-1.5">
|
||||||
Change Folder
|
Change Folder
|
||||||
</a>
|
</a>
|
||||||
</folder-autocomplete>
|
</folder-autocomplete>
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<%= render 'shared/turbo_modal', title: 'Testing Environment' do %>
|
||||||
|
<div>
|
||||||
|
<label class="text-sm font-semibold" for="api_key">
|
||||||
|
x-Auth-Token
|
||||||
|
</label>
|
||||||
|
<div class="flex gap-2 mb-4 mt-2">
|
||||||
|
<input id="api_key" type="text" value="<%= current_user.access_token.token %>" class="base-input w-full" autocomplete="off" readonly>
|
||||||
|
<%= render 'shared/clipboard_copy', icon: 'copy', text: current_user.access_token.token, class: 'base-button', icon_class: 'w-6 h-6 text-white', copy_title: 'Copy', copied_title: 'Copied' %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<%= form_for @webhook_config, url: settings_webhooks_path, method: :post, html: { autocomplete: 'off' }, data: { turbo_frame: :_top } do |f| %>
|
||||||
|
<%= f.label :value, 'Webhook URL', class: 'text-sm font-semibold' %>
|
||||||
|
<div class="space-y-2 md:flex-nowrap mt-2">
|
||||||
|
<%= f.url_field :value, class: 'base-input w-full', placeholder: 'https://example.com/hook' %>
|
||||||
|
<%= f.button button_title(title: 'Save', disabled_with: 'Saving'), class: 'base-button w-full' %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
<% options.each_with_index do |(label, value), index| %>
|
<% options.each_with_index do |(label, value), index| %>
|
||||||
<span>
|
<span>
|
||||||
<%= radio_button_tag 'option', value, value == 'draw', class: 'peer hidden', data: { action: 'change:toggle-visible#trigger' } %>
|
<%= radio_button_tag 'option', value, value == 'draw', class: 'peer hidden', data: { action: 'change:toggle-visible#trigger' } %>
|
||||||
<label for="option_<%= value %>" class="<%= '!rounded-s-full' if index.zero? %> btn btn-focus btn-sm join-item w-28 peer-checked:btn-active normal-case">
|
<label for="option_<%= value %>" class="<%= '!rounded-s-full' if index.zero? %> btn btn-focus btn-sm join-item md:w-28 peer-checked:btn-active normal-case">
|
||||||
<%= label %>
|
<%= label %>
|
||||||
</label>
|
</label>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
<div class="flex flex-wrap space-y-4 md:flex-nowrap md:space-y-0 md:space-x-10">
|
<div class="flex flex-wrap space-y-4 md:flex-nowrap md:space-y-0 md:space-x-10">
|
||||||
<%= render 'shared/settings_nav' %>
|
<%= render 'shared/settings_nav' %>
|
||||||
<div class="flex-grow">
|
<div class="flex-grow">
|
||||||
<h1 class="text-4xl font-bold mb-4">Webhooks</h1>
|
<div class="flex justify-between">
|
||||||
|
<h1 class="text-4xl font-bold mb-4">Webhooks</h1>
|
||||||
|
<%= render 'shared/test_mode_toggle' %>
|
||||||
|
</div>
|
||||||
<div class="card bg-base-200">
|
<div class="card bg-base-200">
|
||||||
<div class="card-body p-6">
|
<div class="card-body p-6">
|
||||||
<%= form_for @encrypted_config, url: settings_webhooks_path, method: :post, html: { autocomplete: 'off' } do |f| %>
|
<%= form_for @encrypted_config, url: settings_webhooks_path, method: :post, html: { autocomplete: 'off' } do |f| %>
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ module DocuSeal
|
|||||||
|
|
||||||
config.active_storage.routes_prefix = ''
|
config.active_storage.routes_prefix = ''
|
||||||
|
|
||||||
config.i18n.available_locales = %i[en en-US en-GB es-ES fr-FR pt-PT de-DE es it de fr pl uk cs pt he]
|
config.i18n.available_locales = %i[en en-US en-GB es-ES fr-FR pt-PT de-DE es it de fr pl uk cs pt he nl]
|
||||||
config.i18n.fallbacks = [:en]
|
config.i18n.fallbacks = [:en]
|
||||||
|
|
||||||
config.exceptions_app = ->(env) { ErrorsController.action(:show).call(env) }
|
config.exceptions_app = ->(env) { ErrorsController.action(:show).call(env) }
|
||||||
|
|||||||
+22
-5
@@ -20,7 +20,6 @@ en: &en
|
|||||||
completed_successfully: Completed Successfully
|
completed_successfully: Completed Successfully
|
||||||
|
|
||||||
es: &es
|
es: &es
|
||||||
digitally_signed_by: Firmado digitalmente por
|
|
||||||
role: Rol
|
role: Rol
|
||||||
email: Correo electrónico
|
email: Correo electrónico
|
||||||
digitally_signed_by: Firmado digitalmente por
|
digitally_signed_by: Firmado digitalmente por
|
||||||
@@ -42,7 +41,6 @@ es: &es
|
|||||||
completed_successfully: Completado exitosamente
|
completed_successfully: Completado exitosamente
|
||||||
|
|
||||||
it:
|
it:
|
||||||
digitally_signed_by: Signé numériquement par
|
|
||||||
role: Rôle
|
role: Rôle
|
||||||
email: Email
|
email: Email
|
||||||
digitally_signed_by: Firmato digitalmente da
|
digitally_signed_by: Firmato digitalmente da
|
||||||
@@ -84,7 +82,6 @@ fr: &fr
|
|||||||
completed_successfully: Terminé avec succès
|
completed_successfully: Terminé avec succès
|
||||||
|
|
||||||
pt: &pt
|
pt: &pt
|
||||||
digitally_signed_by: Assinado digitalmente por
|
|
||||||
role: Função
|
role: Função
|
||||||
email: Email
|
email: Email
|
||||||
digitally_signed_by: Assinado digitalmente por
|
digitally_signed_by: Assinado digitalmente por
|
||||||
@@ -106,10 +103,9 @@ pt: &pt
|
|||||||
completed_successfully: Concluído com sucesso
|
completed_successfully: Concluído com sucesso
|
||||||
|
|
||||||
de: &de
|
de: &de
|
||||||
digitally_signed_by: Digital signiert von
|
|
||||||
role: Rolle
|
role: Rolle
|
||||||
email: E-Mail
|
email: E-Mail
|
||||||
digitally_signed_by: Digital unterzeichnet von
|
digitally_signed_by: Digital signiert von
|
||||||
role: Rolle
|
role: Rolle
|
||||||
provide_your_email_to_start: Gib deine E-Mail-Adresse ein, um zu starten
|
provide_your_email_to_start: Gib deine E-Mail-Adresse ein, um zu starten
|
||||||
start: Starten
|
start: Starten
|
||||||
@@ -207,6 +203,27 @@ he:
|
|||||||
downloading: מוריד
|
downloading: מוריד
|
||||||
completed_successfully: הושלם בהצלחה
|
completed_successfully: הושלם בהצלחה
|
||||||
|
|
||||||
|
nl:
|
||||||
|
email: E-mail
|
||||||
|
digitally_signed_by: Digitaal ondertekend door
|
||||||
|
role: Rol
|
||||||
|
provide_your_email_to_start: Geef uw e-mailadres om te beginnen
|
||||||
|
start: Start
|
||||||
|
starting: Starten
|
||||||
|
form_has_been_deleted_by_html: 'Formulier is verwijderd door <span class="font-semibold">%{name}</span>.'
|
||||||
|
invited_by_html: 'Uitgenodigd door <span class="font-semibold">%{name}</span>'
|
||||||
|
you_have_been_invited_to_submit_a_form: U bent uitgenodigd om een formulier in te dienen
|
||||||
|
signed_on_time: 'Ondertekend op %{time}'
|
||||||
|
form_has_been_submitted_already: Formulier is al ingediend
|
||||||
|
send_copy_to_email: Stuur kopie naar E-mail
|
||||||
|
sending: Verzenden
|
||||||
|
resubmit: Opnieuw indienen
|
||||||
|
form_has_been_deleted_by_html: 'Formulier is verwijderd door <span class="font-semibold">%{name}</span>.'
|
||||||
|
or: of
|
||||||
|
download_documents: Documenten downloaden
|
||||||
|
downloading: Downloaden
|
||||||
|
completed_successfully: Succesvol voltooid
|
||||||
|
|
||||||
en-US:
|
en-US:
|
||||||
<<: *en
|
<<: *en
|
||||||
date:
|
date:
|
||||||
|
|||||||
+5
-5
@@ -62,6 +62,8 @@ Rails.application.routes.draw do
|
|||||||
resource :user_signature, only: %i[edit update destroy]
|
resource :user_signature, only: %i[edit update destroy]
|
||||||
resources :submissions, only: %i[show destroy]
|
resources :submissions, only: %i[show destroy]
|
||||||
resources :console_redirect, only: %i[index]
|
resources :console_redirect, only: %i[index]
|
||||||
|
resource :testing_account, only: %i[show destroy]
|
||||||
|
resources :testing_api_settings, only: %i[index]
|
||||||
resource :templates_upload, only: %i[create]
|
resource :templates_upload, only: %i[create]
|
||||||
authenticated do
|
authenticated do
|
||||||
resource :templates_upload, only: %i[show], path: 'new'
|
resource :templates_upload, only: %i[show], path: 'new'
|
||||||
@@ -112,11 +114,9 @@ Rails.application.routes.draw do
|
|||||||
resource :esign, only: %i[show create new update destroy], controller: 'esign_settings'
|
resource :esign, only: %i[show create new update destroy], controller: 'esign_settings'
|
||||||
resources :users, only: %i[index]
|
resources :users, only: %i[index]
|
||||||
resource :personalization, only: %i[show create], controller: 'personalization_settings'
|
resource :personalization, only: %i[show create], controller: 'personalization_settings'
|
||||||
if !Docuseal.multitenant? || Docuseal.demo?
|
resources :api, only: %i[index create], controller: 'api_settings'
|
||||||
resources :api, only: %i[index create], controller: 'api_settings'
|
resource :webhooks, only: %i[show create update], controller: 'webhook_settings'
|
||||||
resource :webhooks, only: %i[show create update], controller: 'webhook_settings'
|
resource :account, only: %i[show update destroy]
|
||||||
end
|
|
||||||
resource :account, only: %i[show update]
|
|
||||||
resources :profile, only: %i[index] do
|
resources :profile, only: %i[index] do
|
||||||
collection do
|
collection do
|
||||||
patch :update_contact
|
patch :update_contact
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class CreateAccountLinkedAccounts < ActiveRecord::Migration[7.1]
|
||||||
|
def change
|
||||||
|
create_table :account_linked_accounts do |t|
|
||||||
|
t.references :account, null: false, foreign_key: true
|
||||||
|
t.references :linked_account, null: false, foreign_key: { to_table: :accounts }
|
||||||
|
t.text :account_type, null: false
|
||||||
|
|
||||||
|
t.index %i[account_id linked_account_id], unique: true
|
||||||
|
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
+14
-1
@@ -10,7 +10,7 @@
|
|||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema[7.1].define(version: 2023_12_29_220819) do
|
ActiveRecord::Schema[7.1].define(version: 2024_01_20_192055) do
|
||||||
# These are extensions that must be enabled in order to support this database
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "plpgsql"
|
enable_extension "plpgsql"
|
||||||
|
|
||||||
@@ -34,6 +34,17 @@ ActiveRecord::Schema[7.1].define(version: 2023_12_29_220819) do
|
|||||||
t.index ["account_id"], name: "index_account_configs_on_account_id"
|
t.index ["account_id"], name: "index_account_configs_on_account_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
create_table "account_linked_accounts", force: :cascade do |t|
|
||||||
|
t.bigint "account_id", null: false
|
||||||
|
t.bigint "linked_account_id", null: false
|
||||||
|
t.text "account_type", null: false
|
||||||
|
t.datetime "created_at", null: false
|
||||||
|
t.datetime "updated_at", null: false
|
||||||
|
t.index ["account_id", "linked_account_id"], name: "idx_on_account_id_linked_account_id_48ab9f79d2", unique: true
|
||||||
|
t.index ["account_id"], name: "index_account_linked_accounts_on_account_id"
|
||||||
|
t.index ["linked_account_id"], name: "index_account_linked_accounts_on_linked_account_id"
|
||||||
|
end
|
||||||
|
|
||||||
create_table "accounts", force: :cascade do |t|
|
create_table "accounts", force: :cascade do |t|
|
||||||
t.string "name", null: false
|
t.string "name", null: false
|
||||||
t.string "timezone", null: false
|
t.string "timezone", null: false
|
||||||
@@ -242,6 +253,8 @@ ActiveRecord::Schema[7.1].define(version: 2023_12_29_220819) do
|
|||||||
|
|
||||||
add_foreign_key "access_tokens", "users"
|
add_foreign_key "access_tokens", "users"
|
||||||
add_foreign_key "account_configs", "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"
|
||||||
add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"
|
add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"
|
||||||
add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id"
|
add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id"
|
||||||
add_foreign_key "document_generation_events", "submitters"
|
add_foreign_key "document_generation_events", "submitters"
|
||||||
|
|||||||
+22
-1
@@ -31,6 +31,26 @@ module Accounts
|
|||||||
new_account
|
new_account
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def find_or_create_testing_user(account)
|
||||||
|
user = User.find_by(account: account.testing_accounts)
|
||||||
|
|
||||||
|
return user if user
|
||||||
|
|
||||||
|
testing_account = account.dup.tap { |a| a.name = "Testing - #{a.name}" }
|
||||||
|
|
||||||
|
ApplicationRecord.transaction do
|
||||||
|
account.testing_accounts << testing_account
|
||||||
|
|
||||||
|
testing_account.users.create!(
|
||||||
|
email: account.users.order(:id).first.email.sub('@', '+test@'),
|
||||||
|
first_name: 'Testing',
|
||||||
|
last_name: 'Environment',
|
||||||
|
password: SecureRandom.hex,
|
||||||
|
role: :admin
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def create_default_template(account)
|
def create_default_template(account)
|
||||||
template = Template.find(1)
|
template = Template.find(1)
|
||||||
|
|
||||||
@@ -61,7 +81,8 @@ module Accounts
|
|||||||
if Docuseal.multitenant?
|
if Docuseal.multitenant?
|
||||||
EncryptedConfig.find_by(account:, key: EncryptedConfig::ESIGN_CERTS_KEY)&.value || Docuseal::CERTS
|
EncryptedConfig.find_by(account:, key: EncryptedConfig::ESIGN_CERTS_KEY)&.value || Docuseal::CERTS
|
||||||
else
|
else
|
||||||
EncryptedConfig.find_by(key: EncryptedConfig::ESIGN_CERTS_KEY).value
|
EncryptedConfig.find_by(account:, key: EncryptedConfig::ESIGN_CERTS_KEY)&.value ||
|
||||||
|
EncryptedConfig.find_by(key: EncryptedConfig::ESIGN_CERTS_KEY).value
|
||||||
end
|
end
|
||||||
|
|
||||||
if (default_cert = cert_data['custom']&.find { |e| e['status'] == 'default' })
|
if (default_cert = cert_data['custom']&.find { |e| e['status'] == 'default' })
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ class ApiPathConsiderJsonMiddleware
|
|||||||
|
|
||||||
def call(env)
|
def call(env)
|
||||||
if env['PATH_INFO'].starts_with?('/api') &&
|
if env['PATH_INFO'].starts_with?('/api') &&
|
||||||
!env['PATH_INFO'].ends_with?('/documents') &&
|
(!env['PATH_INFO'].ends_with?('/documents') || env['REQUEST_METHOD'] != 'POST') &&
|
||||||
!env['PATH_INFO'].ends_with?('/attachments')
|
!env['PATH_INFO'].ends_with?('/attachments')
|
||||||
env['CONTENT_TYPE'] = 'application/json'
|
env['CONTENT_TYPE'] = 'application/json'
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -0,0 +1,120 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Params
|
||||||
|
class BaseValidator
|
||||||
|
InvalidParameterError = Class.new(StandardError)
|
||||||
|
|
||||||
|
def self.call(...)
|
||||||
|
validator = new(...)
|
||||||
|
|
||||||
|
validator.call
|
||||||
|
rescue InvalidParameterError => e
|
||||||
|
Rollbar.error(e) if defined?(Rollbar)
|
||||||
|
|
||||||
|
raise e unless validator.dry_run?
|
||||||
|
rescue StandardError => e
|
||||||
|
Rollbar.error(e) if defined?(Rollbar)
|
||||||
|
|
||||||
|
raise e unless Rails.env.production?
|
||||||
|
end
|
||||||
|
|
||||||
|
attr_reader :params, :dry_run
|
||||||
|
|
||||||
|
alias dry_run? dry_run
|
||||||
|
|
||||||
|
def initialize(params, dry_run: false)
|
||||||
|
@params = params
|
||||||
|
@dry_run = dry_run
|
||||||
|
@current_path = ''
|
||||||
|
end
|
||||||
|
|
||||||
|
def call
|
||||||
|
raise NotImplementedError
|
||||||
|
end
|
||||||
|
|
||||||
|
def raise_error(message)
|
||||||
|
message += " in `#{@current_path}`." if @current_path.present?
|
||||||
|
|
||||||
|
raise InvalidParameterError, message unless dry_run?
|
||||||
|
end
|
||||||
|
|
||||||
|
def required(params, keys, message: nil)
|
||||||
|
keys = Array.wrap(keys)
|
||||||
|
|
||||||
|
return if keys.any? { |key| params&.dig(key).present? }
|
||||||
|
|
||||||
|
raise_error(message || "#{keys.join(' or ')} is required")
|
||||||
|
end
|
||||||
|
|
||||||
|
def type(params, key, type, message: nil)
|
||||||
|
return if params.blank?
|
||||||
|
return if params[key].blank?
|
||||||
|
|
||||||
|
return if params[key].is_a?(type) || (type == Hash && params[key].is_a?(ActionController::Parameters))
|
||||||
|
|
||||||
|
type = 'Object' if type == Hash
|
||||||
|
|
||||||
|
raise_error(message || "#{key} must be a #{type}")
|
||||||
|
end
|
||||||
|
|
||||||
|
def format(params, key, regexp, message: nil)
|
||||||
|
return if params.blank?
|
||||||
|
return if params[key].blank?
|
||||||
|
return if regexp.match?(params[key].to_s)
|
||||||
|
|
||||||
|
raise_error(message || "#{key} must follow the #{regexp.source} format")
|
||||||
|
end
|
||||||
|
|
||||||
|
def in_path(params, path = [])
|
||||||
|
old_path = @current_path
|
||||||
|
|
||||||
|
@current_path = [old_path, *path].compact_blank.map(&:to_s).join('.')
|
||||||
|
|
||||||
|
param = params.dig(*path)
|
||||||
|
|
||||||
|
yield params.dig(*path) if param
|
||||||
|
|
||||||
|
@current_path = old_path
|
||||||
|
end
|
||||||
|
|
||||||
|
def in_path_each(params, path = [])
|
||||||
|
old_path = @current_path
|
||||||
|
|
||||||
|
params.dig(*path)&.each_with_index do |item, index|
|
||||||
|
@current_path = [old_path, [*path].map(&:to_s).join('.') + "[#{index}]"].compact_blank.join('.')
|
||||||
|
|
||||||
|
yield item if item
|
||||||
|
end
|
||||||
|
|
||||||
|
@current_path = old_path
|
||||||
|
end
|
||||||
|
|
||||||
|
def boolean(params, key, message: nil)
|
||||||
|
return if params.blank?
|
||||||
|
return unless params.key?(key)
|
||||||
|
return if params[key].nil?
|
||||||
|
|
||||||
|
value = ActiveModel::Type::Boolean.new.cast(params[key])
|
||||||
|
|
||||||
|
return if value.is_a?(TrueClass) || value.is_a?(FalseClass)
|
||||||
|
|
||||||
|
raise_error(message || "#{key} must be true or false")
|
||||||
|
end
|
||||||
|
|
||||||
|
def value_in(params, key, values, allow_nil: false, message: nil)
|
||||||
|
return if params.blank?
|
||||||
|
return if allow_nil && params[key].nil?
|
||||||
|
return if values.include?(params[key])
|
||||||
|
|
||||||
|
raise_error(message || "#{key} must be one of #{values.join(', ')}")
|
||||||
|
end
|
||||||
|
|
||||||
|
def more_than(params, key, value, message: nil)
|
||||||
|
return if params.blank?
|
||||||
|
return if params[key].blank?
|
||||||
|
return if params[key].to_f > value
|
||||||
|
|
||||||
|
raise_error(message || "#{key} must be more than #{value}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Params
|
||||||
|
class SubmissionCreateValidator < BaseValidator
|
||||||
|
def call
|
||||||
|
if params[:submission].blank? && (params[:emails].present? || params[:email].present?)
|
||||||
|
validate_creation_from_emails(params)
|
||||||
|
elsif params[:submitters].present?
|
||||||
|
validate_creation_from_submitters(params)
|
||||||
|
else
|
||||||
|
validate_creation_from_submission(params)
|
||||||
|
end
|
||||||
|
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate_creation_from_emails(params)
|
||||||
|
required(params, :template_id)
|
||||||
|
required(params, %i[emails email])
|
||||||
|
|
||||||
|
type(params, :emails, String)
|
||||||
|
boolean(params, :send_email)
|
||||||
|
type(params, :message, Hash)
|
||||||
|
|
||||||
|
in_path(params, :message) do |message_params|
|
||||||
|
type(message_params, :subject, String)
|
||||||
|
type(message_params, :body, String)
|
||||||
|
|
||||||
|
required(message_params, :body)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate_creation_from_submitters(params)
|
||||||
|
required(params, :template_id)
|
||||||
|
required(params, :submitters)
|
||||||
|
|
||||||
|
boolean(params, :send_email)
|
||||||
|
boolean(params, :send_sms)
|
||||||
|
type(params, :order, String)
|
||||||
|
type(params, :completed_redirect_url, String)
|
||||||
|
type(params, :bcc_completed, String)
|
||||||
|
type(params, :message, Hash)
|
||||||
|
|
||||||
|
in_path(params, :message) do |message_params|
|
||||||
|
type(message_params, :subject, String)
|
||||||
|
type(message_params, :body, String)
|
||||||
|
|
||||||
|
required(message_params, :body)
|
||||||
|
end
|
||||||
|
|
||||||
|
value_in(params, :order, %w[preserved random], allow_nil: true)
|
||||||
|
|
||||||
|
in_path_each(params, :submitters) do |submitter_params|
|
||||||
|
validate_submitter(submitter_params)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate_submitter(submitter_params)
|
||||||
|
required(submitter_params, %i[email phone name])
|
||||||
|
|
||||||
|
type(submitter_params, :name, String)
|
||||||
|
type(submitter_params, :email, String)
|
||||||
|
format(submitter_params, :email, /@/, message: 'email is invalid')
|
||||||
|
type(submitter_params, :phone, String)
|
||||||
|
format(submitter_params, :phone, /\A\+\d+\z/,
|
||||||
|
message: 'phone should start with +<country code> and contain only digits')
|
||||||
|
type(submitter_params, :values, Hash)
|
||||||
|
boolean(submitter_params, :send_email)
|
||||||
|
boolean(submitter_params, :send_sms)
|
||||||
|
type(submitter_params, :completed_redirect_url, String)
|
||||||
|
type(submitter_params, :fields, Array)
|
||||||
|
|
||||||
|
in_path_each(submitter_params, :fields) do |field_params|
|
||||||
|
required(field_params, :name)
|
||||||
|
|
||||||
|
type(field_params, :name, String)
|
||||||
|
type(field_params, :validation_pattern, String)
|
||||||
|
type(field_params, :invalid_message, String)
|
||||||
|
boolean(field_params, :readonly)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate_creation_from_submission(params)
|
||||||
|
required(params, :template_id)
|
||||||
|
required(params, %i[submission submissions])
|
||||||
|
|
||||||
|
boolean(params, :send_email)
|
||||||
|
boolean(params, :send_sms)
|
||||||
|
type(params, :order, String)
|
||||||
|
type(params, :completed_redirect_url, String)
|
||||||
|
type(params, :bcc_completed, String)
|
||||||
|
type(params, :message, Hash)
|
||||||
|
|
||||||
|
in_path(params, :message) do |message_params|
|
||||||
|
type(message_params, :subject, String)
|
||||||
|
type(message_params, :body, String)
|
||||||
|
|
||||||
|
required(message_params, :body)
|
||||||
|
end
|
||||||
|
|
||||||
|
value_in(params, :order, %w[preserved random], allow_nil: true)
|
||||||
|
|
||||||
|
return true if params[:submission].is_a?(Array)
|
||||||
|
|
||||||
|
in_path(params, :submission) do |submission_params|
|
||||||
|
required(submission_params, :submitters) if params[:submission]
|
||||||
|
end
|
||||||
|
|
||||||
|
in_path_each(params, %i[submission submitters]) do |submitter_params|
|
||||||
|
validate_submitter(submitter_params)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -39,11 +39,11 @@ module Submissions
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def maybe_set_template_fields(submission, submitters_attrs, submitter_uuid: nil)
|
def maybe_set_template_fields(submission, submitters_attrs, default_submitter_uuid: nil)
|
||||||
template_fields = (submission.template_fields || submission.template.fields).deep_dup
|
template_fields = (submission.template_fields || submission.template.fields).deep_dup
|
||||||
|
|
||||||
submitters_attrs.each_with_index do |submitter_attrs, index|
|
submitters_attrs.each_with_index do |submitter_attrs, index|
|
||||||
submitter_uuid ||= find_submitter_uuid(submission.template, submitter_attrs, index)
|
submitter_uuid = default_submitter_uuid || find_submitter_uuid(submission.template, submitter_attrs, index)
|
||||||
|
|
||||||
process_readonly_fields_param(submitter_attrs[:readonly_fields], template_fields, submitter_uuid)
|
process_readonly_fields_param(submitter_attrs[:readonly_fields], template_fields, submitter_uuid)
|
||||||
process_field_values_param(submitter_attrs[:values], template_fields, submitter_uuid)
|
process_field_values_param(submitter_attrs[:values], template_fields, submitter_uuid)
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ module Submissions
|
|||||||
composer.column(columns: 1) do |column|
|
composer.column(columns: 1) do |column|
|
||||||
add_logo(column, submission)
|
add_logo(column, submission)
|
||||||
|
|
||||||
column.text('Audit Log',
|
column.text(account.linked_account_account&.testing? ? 'Testing Log - Not for Production Use' : 'Audit Log',
|
||||||
font_size: 16,
|
font_size: 16,
|
||||||
padding: [15, 0, 0, 0],
|
padding: [15, 0, 0, 0],
|
||||||
position: :float, position_hint: :right)
|
position: :float, position_hint: :right)
|
||||||
|
|||||||
@@ -7,13 +7,15 @@ module Submitters
|
|||||||
def call(submitter)
|
def call(submitter)
|
||||||
configs = submitter.submission.template.account.account_configs
|
configs = submitter.submission.template.account.account_configs
|
||||||
.where(key: [AccountConfig::FORM_COMPLETED_BUTTON_KEY,
|
.where(key: [AccountConfig::FORM_COMPLETED_BUTTON_KEY,
|
||||||
|
AccountConfig::FORM_WITH_CONFETTI_KEY,
|
||||||
AccountConfig::ALLOW_TYPED_SIGNATURE])
|
AccountConfig::ALLOW_TYPED_SIGNATURE])
|
||||||
|
|
||||||
completed_button = configs.find { |e| e.key == AccountConfig::FORM_COMPLETED_BUTTON_KEY }&.value || {}
|
completed_button = configs.find { |e| e.key == AccountConfig::FORM_COMPLETED_BUTTON_KEY }&.value || {}
|
||||||
|
|
||||||
with_typed_signature = configs.find { |e| e.key == AccountConfig::ALLOW_TYPED_SIGNATURE }&.value != false
|
with_typed_signature = configs.find { |e| e.key == AccountConfig::ALLOW_TYPED_SIGNATURE }&.value != false
|
||||||
|
with_confetti = configs.find { |e| e.key == AccountConfig::FORM_WITH_CONFETTI_KEY }&.value != false
|
||||||
|
|
||||||
{ completed_button:, with_typed_signature: }
|
{ completed_button:, with_typed_signature:, with_confetti: }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -19,14 +19,16 @@ module Submitters
|
|||||||
|
|
||||||
normalized_values = values.to_h.filter_map do |key, value|
|
normalized_values = values.to_h.filter_map do |key, value|
|
||||||
if fields_uuid_index[key].blank?
|
if fields_uuid_index[key].blank?
|
||||||
key = fields_name_index[key]&.dig('uuid')
|
original_key = key
|
||||||
|
|
||||||
raise(UnknownFieldName, "Unknown field: #{key}") if key.blank? && throw_errors
|
key = fields_name_index[key]&.dig('uuid') || fields_name_index[key.to_s.downcase]&.dig('uuid')
|
||||||
|
|
||||||
|
raise(UnknownFieldName, "Unknown field: #{original_key}") if key.blank? && throw_errors
|
||||||
end
|
end
|
||||||
|
|
||||||
next if key.blank?
|
next if key.blank?
|
||||||
|
|
||||||
if fields_uuid_index[key]['type'].in?(%w[initials signature image file])
|
if fields_uuid_index[key]['type'].in?(%w[initials signature image file]) && value.present?
|
||||||
new_value, new_attachments = normalize_attachment_value(value, template.account, for_submitter)
|
new_value, new_attachments = normalize_attachment_value(value, template.account, for_submitter)
|
||||||
|
|
||||||
attachments.push(*new_attachments)
|
attachments.push(*new_attachments)
|
||||||
@@ -59,7 +61,9 @@ module Submitters
|
|||||||
end
|
end
|
||||||
|
|
||||||
def build_fields_index(fields)
|
def build_fields_index(fields)
|
||||||
fields.index_by { |e| e['name'] }.merge(fields.index_by { |e| e['name'].to_s.parameterize.underscore })
|
fields.index_by { |e| e['name'] }
|
||||||
|
.merge(fields.index_by { |e| e['name'].to_s.parameterize.underscore })
|
||||||
|
.merge(fields.index_by { |e| e['name'].to_s.downcase })
|
||||||
end
|
end
|
||||||
|
|
||||||
def normalize_attachment_value(value, account, for_submitter = nil)
|
def normalize_attachment_value(value, account, for_submitter = nil)
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Submitters
|
||||||
|
module ValidateSending
|
||||||
|
InvalidEmail = Class.new(StandardError)
|
||||||
|
|
||||||
|
module_function
|
||||||
|
|
||||||
|
def call(submitter, _mail)
|
||||||
|
raise InvalidEmail unless submitter.email.to_s.include?('@')
|
||||||
|
|
||||||
|
true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -29,10 +29,10 @@ module Templates
|
|||||||
{
|
{
|
||||||
'type' => 'external_link',
|
'type' => 'external_link',
|
||||||
'value' => annot[:A][:URI],
|
'value' => annot[:A][:URI],
|
||||||
'x' => left / page.width,
|
'x' => left / page.width.to_f,
|
||||||
'y' => (page.height - top) / page.height,
|
'y' => (page.height - top) / page.height.to_f,
|
||||||
'w' => (right - left) / page.width,
|
'w' => (right - left) / page.width.to_f,
|
||||||
'h' => (top - bottom) / page.height
|
'h' => (top - bottom) / page.height.to_f
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -14,9 +14,7 @@ RSpec.describe 'Dashboard Page' do
|
|||||||
it 'shows empty state' do
|
it 'shows empty state' do
|
||||||
visit root_path
|
visit root_path
|
||||||
|
|
||||||
expect(page).to have_content('Welcome to DocuSeal')
|
expect(page).to have_link('Create', href: new_template_path)
|
||||||
expect(page).to have_content('Streamline document workflows, from creating customizable')
|
|
||||||
expect(page).to have_link('Create Template', href: new_template_path)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ RSpec.describe 'App Setup' do
|
|||||||
sign_in(user)
|
sign_in(user)
|
||||||
visit setup_index_path
|
visit setup_index_path
|
||||||
|
|
||||||
expect(page).to have_content('Welcome to DocuSeal')
|
expect(page).to have_link('Create', href: new_template_path)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user