Compare commits

...
48 Commits
Author SHA1 Message Date
Alex Turchyn 93f84abce4 Merge from docusealco/wip 2025-09-29 13:14:29 +03:00
Pete Matsyburka 3c6a32caba payment link 2025-09-29 10:23:11 +03:00
Pete Matsyburka 72c08e5fdb fix settings 2025-09-29 08:59:36 +03:00
Pete Matsyburka 022d80b5ce adjust reminders 2025-09-29 07:31:46 +03:00
Pete Matsyburka f48da51802 10k stars 2025-09-27 16:12:00 +03:00
Pete Matsyburka 709d04b33f toggle submit element 2025-09-27 11:40:16 +03:00
Pete Matsyburka 3a251dd063 development url ttl 2025-09-27 11:08:14 +03:00
Pete Matsyburka 9cc92b59b3 add strikethrough 2025-09-27 10:40:29 +03:00
Pete Matsyburka e0469b10a7 hide mobile folder upload 2025-09-26 09:35:32 +03:00
Pete Matsyburka 39eb67b162 refactor csp 2025-09-26 08:20:03 +03:00
Pete Matsyburka 8d9bea3b0f adjust date format 2025-09-25 20:32:41 +03:00
Pete Matsyburka 7015d8dde8 fix csp 2025-09-25 18:30:07 +03:00
Pete Matsyburka 6b54072cb5 check formula field condition 2025-09-25 14:00:51 +03:00
Pete Matsyburka b898708306 fix formula condition 2025-09-25 13:12:36 +03:00
Pete Matsyburka 1b1cf36839 fix verification step 2025-09-25 10:40:33 +03:00
Pete Matsyburka 18b89edc19 fix search input 2025-09-25 09:01:04 +03:00
Pete Matsyburka 70c7ef4247 adjust reminders 2025-09-25 08:47:31 +03:00
Pete Matsyburka a8196709be remove csp meta tag 2025-09-24 23:23:42 +03:00
Pete Matsyburka a9d249ccf0 fix variables schema 2025-09-24 19:51:37 +03:00
Pete Matsyburka cd83d918e5 fix csp 2025-09-24 15:45:04 +03:00
Pete Matsyburka b64a84a362 add csp 2025-09-24 12:14:18 +03:00
Pete Matsyburka f4426a8ee0 adjust payment title 2025-09-23 10:05:51 +03:00
Pete Matsyburka df8c44d2ce quantity formula 2025-09-23 09:03:43 +03:00
Pete Matsyburka 65ce3d4822 add use direct file links 2025-09-22 17:52:24 +03:00
Alex Turchyn e5b1d94579 Merge from docusealco/wip 2025-09-22 13:11:28 +03:00
Pete Matsyburka 0b4754ca24 update email events index 2025-09-22 08:40:12 +03:00
Pete Matsyburka b3dc8d55be fix submission name 2025-09-19 21:36:05 +03:00
Pete Matsyburka e5ab7667b9 adjust verification step 2025-09-18 14:15:16 +03:00
Pete Matsyburka 2a4e6435f1 adjust api settings 2025-09-18 10:24:38 +03:00
Pete Matsyburka 804431c44a adjust account preferences 2025-09-18 10:05:04 +03:00
Alex Turchyn f3b26bf7a0 add tooltips to account preferences 2025-09-18 09:26:21 +03:00
Pete Matsyburka aa27d90017 update gem 2025-09-18 08:48:48 +03:00
Pete Matsyburka 0fa8cf2dd0 fix result generatrion 2025-09-18 08:33:34 +03:00
Pete Matsyburka 634b5c37ba fix form fields import 2025-09-17 13:26:58 +03:00
Pete Matsyburka 6e16f81fc6 add folder filter 2025-09-16 16:19:36 +03:00
Pete Matsyburka ffc4024f70 rubocop 2025-09-16 13:03:02 +03:00
Alex Turchyn a6981185b2 Merge from docusealco/wip 2025-09-15 13:13:58 +03:00
Pete Matsyburka 0361fabb66 fix combined download 2025-09-15 11:39:43 +03:00
Pete Matsyburka 04e7f101be add lock events 2025-09-15 10:10:39 +03:00
Pete Matsyburka 86d680cfdf fix variables schema 2025-09-15 08:57:54 +03:00
Pete Matsyburka 7bc2152057 expire disk storage urls 2025-09-13 18:10:47 +03:00
Pete Matsyburka 74ed9a5040 update docs 2025-09-13 11:23:34 +03:00
Alex Turchyn 30115fc0dc adjust login OTP form 2025-09-13 09:34:50 +03:00
Pete Matsyburka 8e8e5e0e29 add template variables 2025-09-12 18:22:45 +03:00
Alex Turchyn b58ce1b571 add translations 2025-09-10 08:25:36 +03:00
Pete Matsyburka df5c4e4210 fix email 2fa resubmit 2025-09-09 19:29:09 +03:00
Pete Matsyburka ba2e0999f1 add with file links option 2025-09-09 09:53:21 +03:00
Pete Matsyburka a1f5dd08c7 fix typo 2025-09-08 14:42:25 +03:00
137 changed files with 10182 additions and 874 deletions

No files matched your search

+3
View File
@@ -68,6 +68,9 @@ RSpec/ExampleLength:
RSpec/MultipleMemoizedHelpers:
Max: 15
RSpec/AnyInstance:
Enabled: false
Metrics/BlockNesting:
Max: 5
+1 -1
View File
@@ -461,7 +461,7 @@ GEM
actionpack (>= 5.2)
railties (>= 5.2)
retriable (3.1.2)
rexml (3.4.0)
rexml (3.4.4)
rotp (6.3.0)
rouge (4.5.2)
rqrcode (2.2.0)
@@ -19,6 +19,7 @@ class AccountConfigsController < ApplicationController
AccountConfig::FORCE_SSO_AUTH_KEY,
AccountConfig::FLATTEN_RESULT_PDF_KEY,
AccountConfig::ENFORCE_SIGNING_ORDER_KEY,
AccountConfig::WITH_FILE_LINKS_KEY,
AccountConfig::WITH_SIGNATURE_ID,
AccountConfig::COMBINE_PDF_RESULT_KEY,
AccountConfig::REQUIRE_SIGNING_REASON_KEY,
@@ -43,7 +43,7 @@ module Api
end
if @submission.audit_trail_attachment.blank? && submitters.all?(&:completed_at?)
@submission.audit_trail_attachment = Submissions::GenerateAuditTrail.call(@submission)
@submission.audit_trail_attachment = Submissions::EnsureAuditGenerated.call(@submission)
end
render json: Submissions::SerializeForApi.call(@submission, submitters, params)
@@ -184,6 +184,7 @@ module Api
:send_email, :send_sms, :bcc_completed, :completed_redirect_url, :reply_to, :go_to_last,
:require_phone_2fa, :expire_at, :name,
{
variables: {},
message: %i[subject body],
submitters: [[:send_email, :send_sms, :completed_redirect_url, :uuid, :name, :email, :role,
:completed, :phone, :application_key, :external_id, :reply_to, :go_to_last,
+19
View File
@@ -13,6 +13,8 @@ class ApplicationController < ActionController::Base
before_action :maybe_redirect_to_setup, unless: :signed_in?
before_action :authenticate_user!, unless: :devise_controller?
before_action :set_csp, if: -> { request.get? && !request.headers['HTTP_X_TURBO'] }
helper_method :button_title,
:current_account,
:form_link_host,
@@ -123,4 +125,21 @@ class ApplicationController < ActionController::Base
redirect_to request.url.gsub('.co/', '.com/'), allow_other_host: true, status: :moved_permanently
end
def set_csp
request.content_security_policy = current_content_security_policy.tap do |policy|
policy.default_src :self
policy.script_src :self
policy.style_src :self, :unsafe_inline
policy.img_src :self, :https, :http, :blob, :data
policy.font_src :self, :https, :http, :blob, :data
policy.manifest_src :self
policy.media_src :self
policy.frame_src :self
policy.worker_src :self, :blob
policy.connect_src :self
policy.directives['connect-src'] << 'ws:' if Rails.env.development?
end
end
end
@@ -70,7 +70,7 @@ class SubmissionsDownloadController < ApplicationController
return if submitter.submission.submitters.order(:completed_at).last != submitter
attachment = submitter.submission.combined_document_attachment
attachment ||= Submissions::GenerateCombinedAttachment.call(submitter)
attachment ||= Submissions::EnsureCombinedGenerated.call(submitter)
filename_format = AccountConfig.find_or_initialize_by(account_id: submitter.account_id,
key: AccountConfig::DOCUMENT_FILENAME_FORMAT_KEY)&.value
@@ -3,6 +3,7 @@
class SubmissionsFiltersController < ApplicationController
ALLOWED_NAMES = %w[
author
folder
completed_at
status
created_at
+23
View File
@@ -23,6 +23,7 @@ import SignatureForm from './elements/signature_form'
import SubmitForm from './elements/submit_form'
import PromptPassword from './elements/prompt_password'
import EmailsTextarea from './elements/emails_textarea'
import ToggleSubmit from './elements/toggle_submit'
import ToggleOnSubmit from './elements/toggle_on_submit'
import CheckOnClick from './elements/check_on_click'
import PasswordInput from './elements/password_input'
@@ -34,11 +35,20 @@ import MaskedInput from './elements/masked_input'
import SetDateButton from './elements/set_date_button'
import IndeterminateCheckbox from './elements/indeterminate_checkbox'
import AppTour from './elements/app_tour'
import AppTourStart from './elements/app_tour_start'
import DashboardDropzone from './elements/dashboard_dropzone'
import RequiredCheckboxGroup from './elements/required_checkbox_group'
import PageContainer from './elements/page_container'
import EmailEditor from './elements/email_editor'
import MountOnClick from './elements/mount_on_click'
import RemoveOnEvent from './elements/remove_on_event'
import ScrollTo from './elements/scroll_to'
import SetValue from './elements/set_value'
import ReviewForm from './elements/review_form'
import ShowOnValue from './elements/show_on_value'
import CustomValidation from './elements/custom_validation'
import ToggleClasses from './elements/toggle_classes'
import AutosizeField from './elements/autosize_field'
import * as TurboInstantClick from './lib/turbo_instant_click'
@@ -55,6 +65,9 @@ document.addEventListener('keyup', (e) => {
})
document.addEventListener('turbo:before-fetch-request', encodeMethodIntoRequestBody)
document.addEventListener('turbo:before-fetch-request', (event) => {
event.detail.fetchOptions.headers['X-Turbo'] = 'true'
})
document.addEventListener('turbo:submit-end', async (event) => {
const resp = event.detail?.formSubmission?.result?.fetchResponse?.response
@@ -97,6 +110,7 @@ safeRegisterElement('submit-form', SubmitForm)
safeRegisterElement('prompt-password', PromptPassword)
safeRegisterElement('emails-textarea', EmailsTextarea)
safeRegisterElement('toggle-cookies', ToggleCookies)
safeRegisterElement('toggle-submit', ToggleSubmit)
safeRegisterElement('toggle-on-submit', ToggleOnSubmit)
safeRegisterElement('password-input', PasswordInput)
safeRegisterElement('search-input', SearchInput)
@@ -107,12 +121,21 @@ safeRegisterElement('masked-input', MaskedInput)
safeRegisterElement('set-date-button', SetDateButton)
safeRegisterElement('indeterminate-checkbox', IndeterminateCheckbox)
safeRegisterElement('app-tour', AppTour)
safeRegisterElement('app-tour-start', AppTourStart)
safeRegisterElement('dashboard-dropzone', DashboardDropzone)
safeRegisterElement('check-on-click', CheckOnClick)
safeRegisterElement('required-checkbox-group', RequiredCheckboxGroup)
safeRegisterElement('page-container', PageContainer)
safeRegisterElement('email-editor', EmailEditor)
safeRegisterElement('mount-on-click', MountOnClick)
safeRegisterElement('remove-on-event', RemoveOnEvent)
safeRegisterElement('scroll-to', ScrollTo)
safeRegisterElement('set-value', SetValue)
safeRegisterElement('review-form', ReviewForm)
safeRegisterElement('show-on-value', ShowOnValue)
safeRegisterElement('custom-validation', CustomValidation)
safeRegisterElement('toggle-classes', ToggleClasses)
safeRegisterElement('autosize-field', AutosizeField)
safeRegisterElement('template-builder', class extends HTMLElement {
connectedCallback () {
+2 -2
View File
@@ -19,7 +19,7 @@ button .disabled {
display: none;
}
button[disabled] .disabled {
button[disabled] .disabled, button.btn-disabled .disabled {
display: initial;
}
@@ -27,7 +27,7 @@ button .enabled {
display: initial;
}
button[disabled] .enabled {
button[disabled] .enabled, button.btn-disabled .enabled {
display: none;
}
@@ -0,0 +1,7 @@
export default class extends HTMLElement {
connectedCallback () {
this.querySelector('form').addEventListener('submit', () => {
window.app_tour.start()
})
}
}
+21
View File
@@ -0,0 +1,21 @@
export default class extends HTMLElement {
connectedCallback () {
const originalFontValue = this.field.style.fontSize
if (this.field.scrollHeight > this.field.clientHeight) {
this.field.style.fontSize = `calc(${originalFontValue} / 1.5)`
this.field.style.lineHeight = `calc(${this.field.style.fontSize} * 1.3)`
if (this.field.scrollHeight > this.field.clientHeight) {
this.field.style.fontSize = `calc(${originalFontValue} / 2.0)`
this.field.style.lineHeight = `calc(${this.field.style.fontSize} * 1.3)`
}
}
this.field.classList.remove('hidden')
}
get field () {
return this.closest('field-value')
}
}
@@ -0,0 +1,14 @@
export default class extends HTMLElement {
connectedCallback () {
const input = this.querySelector('input')
const invalidMessage = this.dataset.invalidMessage || ''
input.addEventListener('invalid', () => {
input.setCustomValidity(input.value ? invalidMessage : '')
})
input.addEventListener('input', () => {
input.setCustomValidity('')
})
}
}
@@ -0,0 +1,15 @@
export default class extends HTMLElement {
connectedCallback () {
const eventType = this.dataset.on || 'click'
const selector = document.getElementById(this.dataset.selectorId) || this
const eventElement = eventType === 'submit' ? this.querySelector('form') : this
eventElement.addEventListener(eventType, (event) => {
if (eventType === 'click') {
event.preventDefault()
}
selector.remove()
})
}
}
+19
View File
@@ -0,0 +1,19 @@
export default class extends HTMLElement {
connectedCallback () {
this.querySelectorAll('input[type="radio"]').forEach(radio => {
radio.addEventListener('change', (event) => {
const rating = parseInt(event.target.value)
if (rating === 10) {
window.review_comment.value = ''
window.review_comment.classList.add('hidden')
window.review_submit.classList.add('hidden')
event.target.form.submit()
} else {
window.review_comment.classList.remove('hidden')
window.review_submit.classList.remove('hidden')
}
})
})
}
}
+10
View File
@@ -0,0 +1,10 @@
export default class extends HTMLElement {
connectedCallback () {
this.selector = document.getElementById(this.dataset.selectorId)
this.addEventListener('click', () => {
this.selector.scrollIntoView({ behavior: 'smooth', block: 'start' })
history.replaceState(null, null, `#${this.dataset.selectorId}`)
})
}
}
+12
View File
@@ -13,6 +13,14 @@ export default class extends HTMLElement {
this.input.classList.remove('w-60')
}
})
this.button.addEventListener('click', (event) => {
if (!this.input.value && document.activeElement !== this.input) {
event.preventDefault()
this.input.focus()
}
})
}
get input () {
@@ -22,4 +30,8 @@ export default class extends HTMLElement {
get title () {
return document.querySelector(this.dataset.title)
}
get button () {
return this.querySelector('button')
}
}
+11
View File
@@ -0,0 +1,11 @@
export default class extends HTMLElement {
connectedCallback () {
const input = this.dataset.inputId ? document.getElementById(this.dataset.inputId) : this.querySelector('input')
this.firstElementChild.addEventListener(this.dataset.on || 'click', () => {
if (this.dataset.emptyOnly !== 'true' || !input.value) {
input.value = this.dataset.value
}
})
}
}
+17
View File
@@ -0,0 +1,17 @@
export default class extends HTMLElement {
connectedCallback () {
this.addEventListener('change', (event) => {
const targetValue = this.dataset.value
const selectorId = this.dataset.selectorId
const targetElement = document.getElementById(selectorId)
if (event.target.value === targetValue) {
targetElement.classList.remove('hidden')
} else {
targetElement.classList.add('hidden')
targetElement.value = ''
event.target.form.requestSubmit()
}
})
}
}
+18 -2
View File
@@ -1,11 +1,27 @@
export default class extends HTMLElement {
connectedCallback () {
const form = this.querySelector('form') || (this.querySelector('input, button, select') || this.lastElementChild).form
if (this.dataset.interval) {
this.interval = setInterval(() => {
this.querySelector('form').requestSubmit()
form.requestSubmit()
}, parseInt(this.dataset.interval))
} else if (this.dataset.on) {
this.lastElementChild.addEventListener(this.dataset.on, (event) => {
if (this.dataset.disable === 'true') {
form.querySelector('[type="submit"]')?.setAttribute('disabled', true)
}
if (this.dataset.submitIfValue === 'true') {
if (event.target.value) {
form.requestSubmit()
}
} else {
form.requestSubmit()
}
})
} else {
this.querySelector('form').requestSubmit()
form.requestSubmit()
}
}
+11
View File
@@ -0,0 +1,11 @@
export default class extends HTMLElement {
connectedCallback () {
const button = this.querySelector('a, button')
button.addEventListener('click', () => {
this.dataset.classes.split(' ').forEach((cls) => {
button.classList.toggle(cls)
})
})
}
}
+9 -3
View File
@@ -4,9 +4,15 @@ export default actionable(class extends HTMLElement {
trigger (event) {
const elementIds = JSON.parse(this.dataset.elementIds)
elementIds.forEach((elementId) => {
document.getElementById(elementId).classList.toggle('hidden', (event.target.dataset.toggleId || event.target.value) !== elementId)
})
if (event.target.type === 'checkbox') {
elementIds.forEach((elementId) => {
document.getElementById(elementId)?.classList.toggle('hidden')
})
} else {
elementIds.forEach((elementId) => {
document.getElementById(elementId).classList.toggle('hidden', (event.target.dataset.toggleId || event.target.value) !== elementId)
})
}
if (this.dataset.focusId) {
document.getElementById(this.dataset.focusId)?.focus()
+2
View File
@@ -6,6 +6,7 @@ import ToggleSubmit from './elements/toggle_submit'
import FetchForm from './elements/fetch_form'
import ScrollButtons from './elements/scroll_buttons'
import PageContainer from './elements/page_container'
import SubmitForm from './elements/submit_form'
const safeRegisterElement = (name, element, options = {}) => !window.customElements.get(name) && window.customElements.define(name, element, options)
@@ -14,6 +15,7 @@ safeRegisterElement('toggle-submit', ToggleSubmit)
safeRegisterElement('fetch-form', FetchForm)
safeRegisterElement('scroll-buttons', ScrollButtons)
safeRegisterElement('page-container', PageContainer)
safeRegisterElement('submit-form', SubmitForm)
safeRegisterElement('submission-form', class extends HTMLElement {
connectedCallback () {
this.appElem = document.createElement('div')
+2 -2
View File
@@ -19,7 +19,7 @@ button .disabled {
display: none;
}
button[disabled] .disabled {
button[disabled] .disabled, button.btn-disabled .disabled {
display: initial;
}
@@ -27,7 +27,7 @@ button .enabled {
display: initial;
}
button[disabled] .enabled {
button[disabled] .enabled, button.btn-disabled .enabled {
display: none;
}
+1 -1
View File
@@ -56,7 +56,7 @@ function mouseoverListener (event) {
const requestOptions = {
credentials: 'same-origin',
headers: { Accept: 'text/html, application/xhtml+xml', 'VND.PREFETCH': 'true' },
headers: { Accept: 'text/html, application/xhtml+xml', 'VND.PREFETCH': 'true', 'X-Turbo': 'true' },
method: 'GET',
redirect: 'follow'
}
+59
View File
@@ -219,6 +219,48 @@
>
{{ formatNumber(modelValue, field.preferences?.format) }}
</span>
<span
v-else-if="field.type === 'strikethrough'"
class="w-full h-full flex items-center justify-center"
>
<svg
v-if="(((1000.0 / pageWidth) * pageHeight) * area.h) < 40.0"
xmlns="http://www.w3.org/2000/svg"
width="100%"
height="100%"
>
<line
x1="0"
y1="50%"
x2="100%"
y2="50%"
:stroke="field.preferences?.color || 'red'"
:stroke-width="strikethroughWidth"
/>
</svg>
<svg
v-else
xmlns="http://www.w3.org/2000/svg"
:style="{ overflow: 'visible', width: `calc(100% - ${strikethroughWidth})`, height: `calc(100% - ${strikethroughWidth})` }"
>
<line
x1="0"
y1="0"
x2="100%"
y2="100%"
:stroke="field.preferences?.color || 'red'"
:stroke-width="strikethroughWidth"
/>
<line
x1="100%"
y1="0"
x2="0"
y2="100%"
:stroke="field.preferences?.color || 'red'"
:stroke-width="strikethroughWidth"
/>
</svg>
</span>
<span
v-else
class="whitespace-pre-wrap"
@@ -260,6 +302,16 @@ export default {
required: false,
default: false
},
pageWidth: {
type: Number,
required: false,
default: 0
},
pageHeight: {
type: Number,
required: false,
default: 0
},
isValueSet: {
type: Boolean,
required: false,
@@ -342,6 +394,13 @@ export default {
verification: this.t('verify_id')
}
},
strikethroughWidth () {
if (this.isInlineSize) {
return '0.6cqmin'
} else {
return 'clamp(0px, 0.5vw, 6px)'
}
},
isShowSignatureId () {
if ([true, false].includes(this.field.preferences?.with_signature_id)) {
return this.field.preferences.with_signature_id
+30 -23
View File
@@ -11,30 +11,37 @@
v-for="(area, areaIndex) in field.areas"
:key="areaIndex"
>
<Teleport
v-if="findPageElementForArea(area)"
:to="findPageElementForArea(area)"
<template
v-for="(pageElem, index) in [findPageElementForArea(area)]"
:key="index"
>
<FieldArea
:ref="setAreaRef"
v-model="values[field.uuid]"
:values="values"
:field="field"
:area="area"
:submittable="submittable"
:field-index="fieldIndex"
:is-inline-size="isInlineSize"
:scroll-padding="scrollPadding"
:submitter="submitter"
:with-field-placeholder="withFieldPlaceholder"
:with-signature-id="withSignatureId"
:is-active="currentStep === step"
:with-label="withLabel && !withFieldPlaceholder && step.length < 2"
:is-value-set="step.some((f) => f.uuid in values)"
:attachments-index="attachmentsIndex"
@click="[$emit('focus-step', stepIndex), maybeScrollOnClick(field, area)]"
/>
</Teleport>
<Teleport
v-if="pageElem"
:to="pageElem"
>
<FieldArea
:ref="setAreaRef"
v-model="values[field.uuid]"
:values="values"
:field="field"
:area="area"
:submittable="submittable"
:page-width="1400"
:page-height="(1400.0 / pageElem.offsetWidth) * pageElem.offsetHeight"
:field-index="fieldIndex"
:is-inline-size="isInlineSize"
:scroll-padding="scrollPadding"
:submitter="submitter"
:with-field-placeholder="withFieldPlaceholder"
:with-signature-id="withSignatureId"
:is-active="currentStep === step"
:with-label="withLabel && !withFieldPlaceholder && step.length < 2"
:is-value-set="step.some((f) => f.uuid in values)"
:attachments-index="attachmentsIndex"
@click="[$emit('focus-step', stepIndex), maybeScrollOnClick(field, area)]"
/>
</Teleport>
</template>
</template>
</template>
</template>
+17 -5
View File
@@ -23,7 +23,7 @@
<FormulaFieldAreas
v-if="formulaFields.length"
:fields="formulaFields"
:readonly-values="readonlyConditionalFieldValues"
:readonly-values="readonlyFieldValues"
:values="values"
/>
<Teleport
@@ -349,10 +349,10 @@
:id="field.uuid"
type="checkbox"
class="base-checkbox !h-7 !w-7"
:oninvalid="`this.setCustomValidity('${t('please_check_the_box_to_continue')}')`"
:onchange="`this.setCustomValidity(validity.valueMissing ? '${t('please_check_the_box_to_continue')}' : '');`"
:required="field.required"
:checked="!!values[field.uuid]"
@invalid="$event.target.setCustomValidity(t('please_check_the_box_to_continue'))"
@change="$event.target.setCustomValidity($event.target.validity.valueMissing ? t('please_check_the_box_to_continue') : '')"
@click="[scrollIntoField(field), values[field.uuid] = !values[field.uuid]]"
>
<span
@@ -454,7 +454,9 @@
v-model="values[currentField.uuid]"
:field="currentField"
:submitter-slug="submitterSlug"
:fields="formulaFields"
:values="values"
:readonly-values="readonlyFieldValues"
@attached="attachments.push($event)"
@focus="scrollIntoField(currentField)"
@submit="!isSubmitting && submitStep()"
@@ -872,7 +874,14 @@ export default {
},
readonlyConditionalFieldValues () {
return this.readonlyConditionalFields.reduce((acc, f) => {
acc[f.uuid] = (this.values[f.uuid] || f.default_value)
acc[f.uuid] = isEmpty(this.values[f.uuid]) ? f.default_value : this.values[f.uuid]
return acc
}, {})
},
readonlyFieldValues () {
return this.readonlyFields.reduce((acc, f) => {
acc[f.uuid] = isEmpty(this.values[f.uuid]) ? f.default_value : this.values[f.uuid]
return acc
}, {})
@@ -972,7 +981,10 @@ export default {
return this.currentStepFields[0]
},
readonlyConditionalFields () {
return this.fields.filter((f) => f.readonly && f.conditions?.length && this.checkFieldConditions(f) && this.checkFieldDocumentsConditions(f))
return this.readonlyFields.filter((f) => f.conditions?.length)
},
readonlyFields () {
return this.fields.filter((f) => f.readonly && this.checkFieldConditions(f) && this.checkFieldDocumentsConditions(f))
},
stepFields () {
const verificationFields = []
@@ -92,10 +92,20 @@ export default {
type: Object,
required: true
},
readonlyValues: {
type: Object,
required: false,
default: () => ({})
},
values: {
type: Object,
required: true
},
fields: {
type: Array,
required: false,
default: () => []
},
submitterSlug: {
type: String,
required: true
@@ -109,6 +119,13 @@ export default {
}
},
computed: {
fieldsUuidIndex () {
return this.fields.reduce((acc, field) => {
acc[field.uuid] = field
return acc
}, {})
},
queryParams () {
return new URLSearchParams(window.location.search)
},
@@ -116,6 +133,10 @@ export default {
return this.queryParams.get('stripe_session_id')
},
defaultName () {
if (this.field.preferences?.price_id || this.field.preferences?.payment_link_id) {
return ''
}
const { price, currency } = this.field.preferences || {}
const formatter = new Intl.NumberFormat([], {
@@ -178,12 +199,23 @@ export default {
},
methods: {
calculateFormula () {
const transformedFormula = this.field.preferences.formula.replace(/{{(.*?)}}/g, (match, uuid) => {
return this.values[uuid] || 0.0
const transformedFormula = this.normalizeFormula(this.field.preferences.formula).replace(/{{(.*?)}}/g, (match, uuid) => {
return this.readonlyValues[uuid] || this.values[uuid] || 0.0
})
return this.math.evaluate(transformedFormula.toLowerCase())
},
normalizeFormula (formula, depth = 0) {
if (depth > 10) return formula
return formula.replace(/{{(.*?)}}/g, (match, uuid) => {
if (this.fieldsUuidIndex[uuid]) {
return `(${this.normalizeFormula(this.fieldsUuidIndex[uuid].preferences.formula, depth + 1)})`
} else {
return match
}
})
},
async submit () {
if (this.sessionId) {
return fetch(this.baseUrl + '/api/stripe_payments/' + this.sessionId, {
@@ -14,7 +14,7 @@
<MarkdownContent :string="field.description" />
</div>
<div
v-if="emptyValueRequiredStep && emptyValueRequiredStep[0] !== field"
v-if="isRequiredFieldEmpty"
class="px-1 field-description-text"
>
{{ t('complete_all_required_fields_to_proceed_with_identity_verification') }}
@@ -100,6 +100,9 @@ export default {
}
},
computed: {
isRequiredFieldEmpty () {
return this.emptyValueRequiredStep && this.emptyValueRequiredStep[0] !== this.field
},
countryCode () {
const browserTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone
const browserTz = browserTimeZone.split('/')[1]
@@ -131,17 +134,19 @@ export default {
}
},
async mounted () {
this.isLoading = true
if (!this.isRequiredFieldEmpty) {
this.isLoading = true
if (new URLSearchParams(window.location.search).get('submit') === 'true') {
this.$emit('submit')
} else {
Promise.all([
import('@eid-easy/eideasy-widget'),
this.start()
]).finally(() => {
this.isLoading = false
})
if (new URLSearchParams(window.location.search).get('submit') === 'true') {
this.$emit('submit')
} else {
Promise.all([
import('@eid-easy/eideasy-widget'),
this.start()
]).finally(() => {
this.isLoading = false
})
}
}
},
methods: {
@@ -156,6 +161,10 @@ export default {
}).then(async (resp) => {
this.eidEasyData = await resp.json()
if (this.eidEasyData.check_completed) {
this.$emit('submit')
}
if (this.eidEasyData.available_methods[0] === 'itsme-qes-signature' &&
this.eidEasyData.available_methods.length === 1) {
const redirectUrl = new URL('https://id.eideasy.com/sign_contract_external')
@@ -166,7 +175,7 @@ export default {
redirectUrl.searchParams.append('lang', this.locale)
this.redirectUrl = redirectUrl.toString()
} else {
} else if (this.$refs.widgetContainer) {
const eidEasyWidget = document.createElement('eideasy-widget')
for (const key in this.widgetSettings) {
@@ -179,15 +188,23 @@ export default {
})
},
async submit () {
return fetch(this.baseUrl + '/api/identity_verification', {
const resp = await fetch(this.baseUrl + '/api/identity_verification', {
method: 'POST',
body: JSON.stringify({
submitter_slug: this.submitterSlug
}),
headers: { 'Content-Type': 'application/json' }
}).then(async (resp) => {
return resp
})
if (resp.status === 404) {
throw new Error('Verification not completed yet')
}
if (resp.ok) {
return resp
} else {
throw new Error('Verification failed')
}
}
}
}
+93 -8
View File
@@ -10,7 +10,7 @@
<div
v-if="isSelected || isDraw"
class="top-0 bottom-0 right-0 left-0 absolute border border-1.5 pointer-events-none"
:class="field.type === 'heading' ? '' : borderColors[submitterIndex % borderColors.length]"
:class="activeBorderClasses"
/>
<div
v-if="field.type === 'cells' && (isSelected || isDraw)"
@@ -39,7 +39,7 @@
@pointerdown.stop
>
<FieldSubmitter
v-if="field.type != 'heading'"
v-if="field.type != 'heading' && field.type != 'strikethrough'"
v-model="field.submitter_uuid"
class="border-r roles-dropdown"
:compact="true"
@@ -164,16 +164,58 @@
ref="touchValueTarget"
class="flex h-full w-full field-area"
dir="auto"
:class="[isValueInput ? 'cursor-text' : '', isValueInput || isCheckboxInput || isSelectInput ? 'bg-opacity-50' : 'bg-opacity-80', field.type === 'heading' ? 'bg-gray-50' : bgColors[submitterIndex % bgColors.length], isDefaultValuePresent || isValueInput || (withFieldPlaceholder && field.areas) ? fontClasses : 'justify-center items-center']"
:class="[isValueInput ? 'cursor-text' : '', isValueInput || isCheckboxInput || isSelectInput ? 'bg-opacity-50' : 'bg-opacity-80', bgClasses, isDefaultValuePresent || isValueInput || (withFieldPlaceholder && field.areas) ? fontClasses : 'justify-center items-center']"
@click="focusValueInput"
>
<span
v-if="field"
class="flex justify-center items-center space-x-1"
:class="{ 'w-full': isWFullType, 'h-full': !isValueInput && !isDefaultValuePresent }"
:class="{ 'w-full': isWFullType, 'h-full': !isValueInput && (!isDefaultValuePresent || field.type === 'strikethrough') }"
>
<div
v-if="isDefaultValuePresent || isValueInput || isSelectInput || (withFieldPlaceholder && field.areas && field.type !== 'checkbox')"
v-if="field.type === 'strikethrough'"
class="w-full h-full flex items-center justify-center"
>
<svg
v-if="(((basePageWidth / pageWidth) * pageHeight) * area.h) < 41.6"
xmlns="http://www.w3.org/2000/svg"
width="100%"
height="100%"
>
<line
x1="0"
y1="50%"
x2="100%"
y2="50%"
:stroke="field.preferences?.color || 'red'"
:stroke-width="strikethroughWidth"
/>
</svg>
<svg
v-else
xmlns="http://www.w3.org/2000/svg"
:style="{ overflow: 'visible', width: `calc(100% - ${strikethroughWidth})`, height: `calc(100% - ${strikethroughWidth})` }"
>
<line
x1="0"
y1="0"
x2="100%"
y2="100%"
:stroke="field.preferences?.color || 'red'"
:stroke-width="strikethroughWidth"
/>
<line
x1="100%"
y1="0"
x2="0"
y2="100%"
:stroke="field.preferences?.color || 'red'"
:stroke-width="strikethroughWidth"
/>
</svg>
</div>
<div
v-else-if="isDefaultValuePresent || isValueInput || isSelectInput || (withFieldPlaceholder && field.areas && field.type !== 'checkbox')"
:class="{ 'w-full h-full': isWFullType }"
:style="fontStyle"
>
@@ -397,6 +439,16 @@ export default {
required: false,
default: false
},
pageWidth: {
type: Number,
required: false,
default: 0
},
pageHeight: {
type: Number,
required: false,
default: 0
},
defaultSubmitters: {
type: Array,
required: false,
@@ -436,8 +488,33 @@ export default {
fieldNames: FieldType.computed.fieldNames,
fieldLabels: FieldType.computed.fieldLabels,
fieldIcons: FieldType.computed.fieldIcons,
bgClasses () {
if (this.field.type === 'heading') {
return 'bg-gray-50'
} else if (this.field.type === 'strikethrough') {
return 'bg-transparent'
} else {
return this.bgColors[this.submitterIndex % this.bgColors.length]
}
},
activeBorderClasses () {
if (this.field.type === 'heading') {
return ''
} else if (this.field.type === 'strikethrough') {
return 'border-dashed border-gray-300'
} else {
return this.borderColors[this.submitterIndex % this.borderColors.length]
}
},
isWFullType () {
return ['cells', 'checkbox', 'radio', 'multiple', 'select'].includes(this.field.type)
return ['cells', 'checkbox', 'radio', 'multiple', 'select', 'strikethrough'].includes(this.field.type)
},
strikethroughWidth () {
if (this.isInlineSize) {
return '0.6cqmin'
} else {
return 'clamp(0px, 0.5vw, 6px)'
}
},
fontStyle () {
let fontSize = ''
@@ -471,8 +548,11 @@ export default {
lineHeight () {
return 1.3
},
basePageWidth () {
return 1040.0
},
fontScale () {
return 1040 / 612.0
return this.basePageWidth / 612.0
},
isDefaultValuePresent () {
return this.field?.default_value || this.field?.default_value === 0
@@ -742,10 +822,15 @@ export default {
delete this.field.options
}
if (['heading'].includes(this.field.type)) {
if (this.field.type === 'heading') {
this.field.readonly = true
}
if (this.field.type === 'strikethrough') {
this.field.readonly = true
this.field.default_value = true
}
if (['select', 'multiple', 'radio'].includes(this.field.type)) {
this.field.options ||= [{ value: '', uuid: v4() }]
}
+23 -3
View File
@@ -402,7 +402,10 @@
:style="{ backgroundColor }"
>
<div class="bg-base-200 rounded-lg p-5 text-center space-y-4 draw-field-container">
<p>
<p v-if="(drawField?.type || drawFieldType) === 'strikethrough'">
{{ t('draw_strikethrough_the_document') }}
</p>
<p v-else>
{{ t('draw_field_on_the_document') }}
</p>
<div>
@@ -413,7 +416,7 @@
{{ t('cancel') }}
</button>
<a
v-if="!drawField && !drawOption && !['stamp', 'signature', 'initials', 'heading'].includes(drawField?.type || drawFieldType)"
v-if="!drawField && !drawOption && !['stamp', 'signature', 'initials', 'heading', 'strikethrough'].includes(drawField?.type || drawFieldType)"
href="#"
class="link block mt-3 text-sm"
@click.prevent="[addField(drawFieldType), drawField = null, drawOption = null, withSelectedFieldType ? '' : drawFieldType = '', showDrawField = false]"
@@ -1084,6 +1087,11 @@ export default {
}
}
if (field.type === 'strikethrough') {
field.readonly = true
field.default_value = true
}
if (type === 'signature' && [true, false].includes(this.withSignatureId)) {
field.preferences ||= {}
field.preferences.with_signature_id = this.withSignatureId
@@ -1357,6 +1365,9 @@ export default {
} else if (type === 'initials') {
area.w = pageMask.clientWidth / 10 / pageMask.clientWidth
area.h = (pageMask.clientWidth / 35 / pageMask.clientWidth)
} else if (type === 'strikethrough') {
area.w = pageMask.clientWidth / 5 / pageMask.clientWidth
area.h = (pageMask.clientWidth / 70 / pageMask.clientWidth)
} else {
area.w = pageMask.clientWidth / 5 / pageMask.clientWidth
area.h = (pageMask.clientWidth / 35 / pageMask.clientWidth)
@@ -1500,8 +1511,12 @@ export default {
field.default_value = '{{date}}'
}
if (['stamp', 'heading'].includes(field.type)) {
if (['stamp', 'heading', 'strikethrough'].includes(field.type)) {
field.readonly = true
if (field.type === 'strikethrough') {
field.default_value = true
}
}
if (field.type === 'date') {
@@ -1589,6 +1604,11 @@ export default {
w: area.maskW / 10 / area.maskW,
h: area.maskW / 35 / area.maskW
}
} else if (fieldType === 'strikethrough') {
baseArea = {
w: area.maskW / 5 / area.maskW,
h: area.maskW / 70 / area.maskW
}
} else {
baseArea = {
w: area.maskW / 5 / area.maskW,
@@ -177,10 +177,13 @@ export default {
}
},
computed: {
excludeTypes () {
return ['heading', 'strikethrough']
},
fields () {
if (this.item.submitter_uuid) {
return this.template.fields.reduce((acc, f) => {
if (f !== this.item && (!f.conditions?.length || !f.conditions.find((c) => c.field_uuid === this.item.uuid))) {
if (f !== this.item && !this.excludeTypes.includes(f.type) && (!f.conditions?.length || !f.conditions.find((c) => c.field_uuid === this.item.uuid))) {
acc.push(f)
}
+9 -4
View File
@@ -14,7 +14,7 @@
<div class="flex items-center p-1 space-x-1">
<FieldType
v-model="field.type"
:editable="editable && !defaultField && field.type != 'heading'"
:editable="editable && !defaultField"
:button-width="20"
:menu-classes="'mt-1.5'"
:menu-style="{ backgroundColor: dropdownBgColor }"
@@ -97,7 +97,7 @@
@click-formula="isShowFormulaModal = true"
/>
<span
v-else-if="field.type !== 'heading'"
v-else
class="dropdown dropdown-end field-settings-dropdown"
@mouseenter="renderDropdown = true"
@touchstart="renderDropdown = true"
@@ -421,7 +421,7 @@ export default {
} else {
const typeIndex = fields.filter((f) => f.type === field.type).indexOf(field)
if (field.type === 'heading') {
if (field.type === 'heading' || field.type === 'strikethrough') {
return `${this.fieldNames[field.type]} ${typeIndex + 1}`
} else {
return `${this.fieldLabels[field.type]} ${typeIndex + 1}`
@@ -485,10 +485,15 @@ export default {
this.field.options ||= [{ value: '', uuid: v4() }]
}
if (['heading'].includes(this.field.type)) {
if (this.field.type === 'heading') {
this.field.readonly = true
}
if (this.field.type === 'strikethrough') {
this.field.readonly = true
this.field.default_value = true
}
(this.field.areas || []).forEach((area) => {
if (this.field.type === 'cells') {
area.cell_w = area.w * 2 / Math.floor(area.w / area.h)
@@ -380,7 +380,7 @@
</label>
</li>
<li
v-if="withRequired && field.type !== 'phone' && field.type !== 'stamp' && field.type !== 'verification'"
v-if="withRequired && field.type !== 'phone' && field.type !== 'stamp' && field.type !== 'verification' && field.type !== 'strikethrough' && field.type !== 'heading'"
@click.stop
>
<label class="cursor-pointer py-1.5">
@@ -470,7 +470,7 @@
v-if="field.type != 'stamp'"
class="pb-0.5 mt-0.5"
>
<li v-if="['text', 'number', 'date', 'select'].includes(field.type)">
<li v-if="['text', 'number', 'date', 'select', 'heading'].includes(field.type)">
<label
class="label-text cursor-pointer text-center w-full flex items-center"
@click="$emit('click-font')"
@@ -484,7 +484,7 @@
</label>
</li>
<li
v-if="field.type != 'stamp'"
v-if="field.type != 'stamp' && field.type != 'heading' && field.type != 'strikethrough'"
>
<label
class="label-text cursor-pointer text-center w-full flex items-center"
@@ -499,7 +499,7 @@
</label>
</li>
<li
v-if="field.type != 'stamp'"
v-if="field.type != 'stamp' && field.type != 'heading'"
>
<label
class="label-text cursor-pointer text-center w-full flex items-center"
@@ -51,7 +51,7 @@
</template>
<script>
import { IconTextSize, IconWritingSign, IconCalendarEvent, IconPhoto, IconCheckbox, IconPaperclip, IconSelect, IconCircleDot, IconChecks, IconColumns3, IconPhoneCheck, IconLetterCaseUpper, IconCreditCard, IconRubberStamp, IconSquareNumber1, IconHeading, IconId, IconCalendarCheck } from '@tabler/icons-vue'
import { IconTextSize, IconWritingSign, IconCalendarEvent, IconPhoto, IconCheckbox, IconPaperclip, IconSelect, IconCircleDot, IconChecks, IconColumns3, IconPhoneCheck, IconLetterCaseUpper, IconCreditCard, IconRubberStamp, IconSquareNumber1, IconHeading, IconId, IconCalendarCheck, IconStrikethrough } from '@tabler/icons-vue'
export default {
name: 'FiledTypeDropdown',
@@ -97,6 +97,7 @@ export default {
fieldNames () {
return {
heading: this.t('heading'),
strikethrough: this.t('strikeout'),
text: this.t('text'),
signature: this.t('signature'),
initials: this.t('initials'),
@@ -139,6 +140,7 @@ export default {
fieldIcons () {
return {
heading: IconHeading,
strikethrough: IconStrikethrough,
text: IconTextSize,
signature: IconWritingSign,
initials: IconLetterCaseUpper,
@@ -158,6 +160,9 @@ export default {
verification: IconId
}
},
skipTypes () {
return ['heading', 'datenow', 'strikethrough']
},
fieldIconsSorted () {
if (this.fieldTypes.length) {
return this.fieldTypes.reduce((acc, type) => {
@@ -166,7 +171,7 @@ export default {
return acc
}, {})
} else {
return Object.fromEntries(Object.entries(this.fieldIcons).filter(([key]) => key !== 'heading' && key !== 'datenow'))
return Object.fromEntries(Object.entries(this.fieldIcons).filter(([key]) => !this.skipTypes.includes(key)))
}
}
},
+4 -1
View File
@@ -351,6 +351,9 @@ export default {
return acc
}, {})
},
skipTypes () {
return ['heading', 'datenow', 'strikethrough']
},
fieldIconsSorted () {
if (this.fieldTypes.length) {
return this.fieldTypes.reduce((acc, type) => {
@@ -359,7 +362,7 @@ export default {
return acc
}, {})
} else {
return Object.fromEntries(Object.entries(this.fieldIcons).filter(([key]) => key !== 'heading' && key !== 'datenow'))
return Object.fromEntries(Object.entries(this.fieldIcons).filter(([key]) => !this.skipTypes.includes(key)))
}
},
submitterFields () {
+24
View File
@@ -1,4 +1,8 @@
const en = {
payment_link: 'Payment link',
strikeout: 'Strikeout',
draw_strikethrough_the_document: 'Draw strikethrough the document',
quantity: 'Quantity',
prefillable: 'Prefillable',
signature_id: 'Signature ID',
error_message: 'Error message',
@@ -176,6 +180,10 @@ const en = {
}
const es = {
payment_link: 'Enlace de pago',
strikeout: 'Tachar',
draw_strikethrough_the_document: 'Dibujar una línea de tachado en el documento',
quantity: 'Cantidad',
prefillable: 'Rellenable',
signature_id: 'ID de Firma',
error_message: 'Mensaje de error',
@@ -353,6 +361,10 @@ const es = {
}
const it = {
payment_link: 'Link di pagamento',
strikeout: 'Barrato',
draw_strikethrough_the_document: 'Disegna una linea barrata sul documento',
quantity: 'Quantità',
prefillable: 'Precompilabile',
signature_id: 'ID firma',
error_message: 'Messaggio di errore',
@@ -530,6 +542,10 @@ const it = {
}
const pt = {
payment_link: 'Link de pagamento',
strikeout: 'Tachado',
draw_strikethrough_the_document: 'Desenhe uma linha de tachado no documento',
quantity: 'Quantidade',
prefillable: 'Pré-preenchível',
signature_id: 'ID da Assinatura',
error_message: 'Mensagem de erro',
@@ -707,6 +723,10 @@ const pt = {
}
const fr = {
payment_link: 'Lien de paiement',
strikeout: 'Barrer',
draw_strikethrough_the_document: 'Tracer une ligne de suppression sur le document',
quantity: 'Quantité',
prefillable: 'Pré-remplissable',
signature_id: 'ID de signature',
error_message: 'Message d\'erreur',
@@ -884,6 +904,10 @@ const fr = {
}
const de = {
payment_link: 'Zahlungslink',
strikeout: 'Streichung',
draw_strikethrough_the_document: 'Ziehe eine Streichung auf das Dokument',
quantity: 'Menge',
prefillable: 'Vorausfüllbar',
signature_id: 'Signatur-ID',
error_message: 'Fehlermeldung',
+4
View File
@@ -24,6 +24,8 @@
:ref="setAreaRefs"
:area="item.area"
:input-mode="inputMode"
:page-width="width"
:page-height="height"
:field="item.field"
:editable="editable"
:with-field-placeholder="withFieldPlaceholder"
@@ -40,6 +42,8 @@
<FieldArea
v-if="newArea"
:is-draw="true"
:page-width="width"
:page-height="height"
:field="{ submitter_uuid: selectedSubmitter.uuid, type: drawField?.type || dragFieldPlaceholder?.type || defaultFieldType }"
:area="newArea"
/>
@@ -1,7 +1,7 @@
<template>
<span
class="dropdown dropdown-end field-settings-dropdown"
:class="{ 'dropdown-open': ((!field.preferences?.price && !field.preferences?.formula) || !isConnected) && !isLoading }"
:class="{ 'dropdown-open': ((!field.preferences?.price && !field.preferences?.formula && !field.preferences?.price_id && !field.preferences?.payment_link_id) || !isConnected) && !isLoading }"
>
<label
tabindex="0"
@@ -21,7 +21,7 @@
@click="closeDropdown"
>
<div
v-if="!('price_id' in field.preferences)"
v-if="!('price_id' in field.preferences) && !('payment_link_id' in field.preferences)"
class="py-1.5 px-1 relative"
@click.stop
>
@@ -52,10 +52,9 @@
@click.stop
>
<input
v-if="field.preferences.formula"
type="number"
:placeholder="t('price')"
disabled="true"
v-if="'payment_link_id' in field.preferences"
v-model="field.preferences.payment_link_id"
placeholder="plink_XXXXX"
class="input input-bordered input-xs w-full max-w-xs h-7 !outline-0"
@blur="save"
>
@@ -66,6 +65,14 @@
class="input input-bordered input-xs w-full max-w-xs h-7 !outline-0"
@blur="save"
>
<input
v-else-if="field.preferences.formula"
type="number"
:placeholder="t('price')"
disabled="true"
class="input input-bordered input-xs w-full max-w-xs h-7 !outline-0"
@blur="save"
>
<input
v-else
v-model="field.preferences.price"
@@ -75,29 +82,41 @@
@blur="save"
>
<label
v-if="field.preferences.price && !field.preferences.formula"
v-if="(field.preferences.price || field.preferences.price_id || field.preferences.payment_link_id) && (!field.preferences.formula || ('price_id' in field.preferences) || ('payment_link_id' in field.preferences))"
:style="{ backgroundColor: backgroundColor }"
class="absolute -top-1 left-2.5 px-1 h-4"
style="font-size: 8px"
>
{{ t('price') }}
{{ 'payment_link_id' in field.preferences ? t('payment_link') : t('price') }}
</label>
<div class="flex items-center justify-center">
<a
href="#"
class="hover:underline"
style="font-size: 11px"
:class="{'underline': !('price_id' in field.preferences)}"
@click="delete field.preferences.price_id"
:class="{'underline': !('payment_link_id' in field.preferences)}"
@click="[delete field.preferences.price_id, delete field.preferences.payment_link_id]"
>{{ t('one_off') }}</a>
<span class="h-2.5 border-l border-base-content mx-1" />
<template
v-if="field.preferences.price_id"
>
<a
href="#"
class="hover:underline"
style="font-size: 11px"
:class="{'underline': ('price_id' in field.preferences)}"
@click="field.preferences.payment_link_id ??= ''"
>{{ t('recurrent') }}</a>
<span class="h-2.5 border-l border-base-content mx-1" />
</template>
<a
href="#"
class="hover:underline"
style="font-size: 11px"
:class="{'underline': ('price_id' in field.preferences)}"
@click="field.preferences.price_id ??= ''"
>{{ t('recurrent') }}</a>
:class="{'underline': ('payment_link_id' in field.preferences)}"
@click="[delete field.preferences.price_id, field.preferences.payment_link_id ??= '']"
>{{ t('payment_link') }}</a>
</div>
</div>
<div
@@ -184,7 +203,6 @@
>{{ t('learn_more') }}</a>
</div>
<li
v-if="!('price_id' in field.preferences)"
class="mb-1"
>
<label
@@ -195,7 +213,7 @@
width="18"
/>
<span class="text-sm">
{{ t('formula') }}
{{ 'payment_link_id' in field.preferences ? t('quantity') : t('formula') }}
</span>
</label>
</li>
+2 -2
View File
@@ -14,10 +14,10 @@ class ProcessSubmitterCompletionJob
if is_all_completed && submitter.completed_at == submitter.submission.submitters.maximum(:completed_at)
if submitter.submission.account.account_configs.exists?(key: AccountConfig::COMBINE_PDF_RESULT_KEY, value: true)
Submissions::GenerateCombinedAttachment.call(submitter)
Submissions::EnsureCombinedGenerated.call(submitter)
end
Submissions::GenerateAuditTrail.call(submitter.submission)
Submissions::EnsureAuditGenerated.call(submitter.submission)
enqueue_completed_emails(submitter)
end
+1
View File
@@ -42,6 +42,7 @@ class AccountConfig < ApplicationRecord
FORCE_SSO_AUTH_KEY = 'force_sso_auth'
FLATTEN_RESULT_PDF_KEY = 'flatten_result_pdf'
WITH_SIGNATURE_ID = 'with_signature_id'
WITH_FILE_LINKS_KEY = 'with_file_links'
WITH_SIGNATURE_ID_REASON_KEY = 'with_signature_id_reason'
WITH_AUDIT_VALUES_KEY = 'with_audit_values'
WITH_SUBMITTER_TIMEZONE_KEY = 'with_submitter_timezone'
+1 -1
View File
@@ -20,7 +20,7 @@
#
# index_email_events_on_account_id_and_event_datetime (account_id,event_datetime)
# index_email_events_on_email (email)
# index_email_events_on_email_event_types (email) WHERE ((event_type)::text = ANY ((ARRAY['bounce'::character varying, 'soft_bounce'::character varying, 'complaint'::character varying, 'soft_complaint'::character varying])::text[]))
# index_email_events_on_email_event_types (email) WHERE ((event_type)::text = ANY ((ARRAY['bounce'::character varying, 'soft_bounce'::character varying, 'permanent_bounce'::character varying, 'complaint'::character varying, 'soft_complaint'::character varying])::text[]))
# index_email_events_on_emailable (emailable_type,emailable_id)
# index_email_events_on_message_id (message_id)
#
+25
View File
@@ -0,0 +1,25 @@
# frozen_string_literal: true
# == Schema Information
#
# Table name: lock_events
#
# id :bigint not null, primary key
# event_name :string not null
# key :string not null
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# index_lock_events_on_event_name_and_key (event_name,key) UNIQUE WHERE ((event_name)::text = ANY ((ARRAY['start'::character varying, 'complete'::character varying])::text[]))
# index_lock_events_on_key (key)
#
class LockEvent < ApplicationRecord
enum :event_name, {
complete: 'complete',
fail: 'fail',
start: 'start',
retry: 'retry'
}, scope: false
end
+4
View File
@@ -15,6 +15,8 @@
# template_fields :text
# template_schema :text
# template_submitters :text
# variables :text
# variables_schema :text
# created_at :datetime not null
# updated_at :datetime not null
# account_id :bigint not null
@@ -50,6 +52,8 @@ class Submission < ApplicationRecord
serialize :template_fields, coder: JSON
serialize :template_schema, coder: JSON
serialize :template_submitters, coder: JSON
serialize :variables_schema, coder: JSON
serialize :variables, coder: JSON
serialize :preferences, coder: JSON
attribute :source, :string, default: 'link'
+18 -16
View File
@@ -4,22 +4,23 @@
#
# Table name: templates
#
# id :bigint not null, primary key
# archived_at :datetime
# fields :text not null
# name :string not null
# preferences :text not null
# schema :text not null
# shared_link :boolean default(FALSE), not null
# slug :string not null
# source :text not null
# submitters :text not null
# created_at :datetime not null
# updated_at :datetime not null
# account_id :bigint not null
# author_id :bigint not null
# external_id :string
# folder_id :bigint not null
# id :bigint not null, primary key
# archived_at :datetime
# fields :text not null
# name :string not null
# preferences :text not null
# schema :text not null
# shared_link :boolean default(FALSE), not null
# slug :string not null
# source :text not null
# submitters :text not null
# variables_schema :text
# created_at :datetime not null
# updated_at :datetime not null
# account_id :bigint not null
# author_id :bigint not null
# external_id :string
# folder_id :bigint not null
#
# Indexes
#
@@ -57,6 +58,7 @@ class Template < ApplicationRecord
serialize :preferences, coder: JSON
serialize :fields, coder: JSON
serialize :variables_schema, coder: JSON
serialize :schema, coder: JSON
serialize :submitters, coder: JSON
+129 -58
View File
@@ -50,11 +50,16 @@
<% if can?(:manage, account_config) %>
<%= form_for account_config, url: account_configs_path, method: :post do |f| %>
<%= f.hidden_field :key %>
<div class="flex items-center justify-between py-2.5">
<span>
<%= t('force_2fa_with_authenticator_app') %>
</span>
<%= f.check_box :value, class: 'toggle', checked: account_config.value, onchange: 'this.form.requestSubmit()' %>
<div class="flex items-center justify-between gap-4 py-2.5">
<div class="flex items-center space-x-1">
<span class="text-left"><%= t('force_2fa_with_authenticator_app') %></span>
<span class="tooltip tooltip-top flex cursor-pointer" data-tip="<%= t('require_two_factor_authentication_2fa_with_an_authenticator_app_e_g_google_authenticator_authy_all_users_signing_documents_must_pass_the_second_factor_verification_using_a_secure_code_in_addition_to_their_password') %>">
<%= svg_icon('info_circle', class: 'hidden md:inline-block w-4 h-4 shrink-0') %>
</span>
</div>
<submit-form data-on="change" class="flex">
<%= f.check_box :value, class: 'toggle', checked: account_config.value %>
</submit-form>
</div>
<% end %>
<% end %>
@@ -62,11 +67,16 @@
<% if can?(:manage, account_config) %>
<%= form_for account_config, url: account_configs_path, method: :post do |f| %>
<%= f.hidden_field :key %>
<div class="flex items-center justify-between py-2.5">
<span>
<%= t('add_signature_id_to_the_documents') %>
</span>
<%= f.check_box :value, class: 'toggle', checked: account_config.value, onchange: 'this.form.requestSubmit()', disabled: can?(:manage, :cfr) %>
<div class="flex items-center justify-between gap-4 py-2.5">
<div class="flex items-center space-x-1">
<span class="text-left"><%= t('add_signature_id_to_the_documents') %></span>
<span class="tooltip tooltip-top flex cursor-pointer" data-tip="<%= t('add_a_unique_signature_id_and_timestamp_to_each_signature_for_audit_and_traceability_purposes_along_with_the_timestamp_part_of_docuseals_21_cfr_part_11_compliance_settings') %>">
<%= svg_icon('info_circle', class: 'hidden md:inline-block w-4 h-4 shrink-0') %>
</span>
</div>
<submit-form data-on="change" class="flex">
<%= f.check_box :value, class: 'toggle', checked: account_config.value, disabled: can?(:manage, :cfr) %>
</submit-form>
</div>
<% end %>
<% end %>
@@ -74,11 +84,16 @@
<% if can?(:manage, account_config) %>
<%= form_for account_config, url: account_configs_path, method: :post do |f| %>
<%= f.hidden_field :key %>
<div class="flex items-center justify-between py-2.5">
<span>
<%= t('require_signing_reason') %>
</span>
<%= f.check_box :value, class: 'toggle', checked: account_config.value, onchange: 'this.form.requestSubmit()', disabled: can?(:manage, :cfr) %>
<div class="flex items-center justify-between gap-4 py-2.5">
<div class="flex items-center space-x-1">
<span class="text-left"><%= t('require_signing_reason') %></span>
<span class="tooltip tooltip-top flex cursor-pointer" data-tip="<%= t('require_signer_to_provide_a_reason_for_signing_before_completing_their_signature_e_g_approvals_certifications_part_of_docuseals_21_cfr_part_11_compliance_settings') %>">
<%= svg_icon('info_circle', class: 'hidden md:inline-block w-4 h-4 shrink-0') %>
</span>
</div>
<submit-form data-on="change" class="flex">
<%= f.check_box :value, class: 'toggle', checked: account_config.value, disabled: can?(:manage, :cfr) %>
</submit-form>
</div>
<% end %>
<% end %>
@@ -86,11 +101,16 @@
<% if can?(:manage, account_config) %>
<%= form_for account_config, url: account_configs_path, method: :post do |f| %>
<%= f.hidden_field :key %>
<div class="flex items-center justify-between py-2.5">
<span>
<%= t('allow_typed_text_signatures') %>
</span>
<%= f.check_box :value, class: 'toggle', checked: account_config.value != false, onchange: 'this.form.requestSubmit()' %>
<div class="flex items-center justify-between gap-4 py-2.5">
<div class="flex items-center space-x-1">
<span class="text-left"><%= t('allow_typed_text_signatures') %></span>
<span class="tooltip tooltip-top flex cursor-pointer" data-tip="<%= t('allow_signers_to_create_signatures_by_typing_their_name_instead_of_drawing_or_uploading_one') %>">
<%= svg_icon('info_circle', class: 'hidden md:inline-block w-4 h-4 shrink-0') %>
</span>
</div>
<submit-form data-on="change" class="flex">
<%= f.check_box :value, class: 'toggle', checked: account_config.value != false %>
</submit-form>
</div>
<% end %>
<% end %>
@@ -98,11 +118,16 @@
<% if can?(:manage, account_config) %>
<%= form_for account_config, url: account_configs_path, method: :post do |f| %>
<%= f.hidden_field :key %>
<div class="flex items-center justify-between py-2.5">
<span>
<%= t('allow_to_resubmit_completed_forms') %>
</span>
<%= f.check_box :value, class: 'toggle', checked: account_config.value != false, onchange: 'this.form.requestSubmit()' %>
<div class="flex items-center justify-between gap-4 py-2.5">
<div class="flex items-center space-x-1">
<span class="text-left"><%= t('allow_to_resubmit_completed_forms') %></span>
<span class="tooltip tooltip-top flex cursor-pointer" data-tip="<%= t('allow_signers_to_resubmit_forms_after_completion_useful_when_corrections_or_multiple_submissions_are_needed') %>">
<%= svg_icon('info_circle', class: 'hidden md:inline-block w-4 h-4 shrink-0') %>
</span>
</div>
<submit-form data-on="change" class="flex">
<%= f.check_box :value, class: 'toggle', checked: account_config.value != false %>
</submit-form>
</div>
<% end %>
<% end %>
@@ -110,11 +135,16 @@
<% if can?(:manage, account_config) %>
<%= form_for account_config, url: account_configs_path, method: :post do |f| %>
<%= f.hidden_field :key %>
<div class="flex items-center justify-between py-2.5">
<span>
<%= t('allow_to_decline_documents') %>
</span>
<%= f.check_box :value, class: 'toggle', checked: account_config.value != false, onchange: 'this.form.requestSubmit()' %>
<div class="flex items-center justify-between gap-4 py-2.5">
<div class="flex items-center space-x-1">
<span class="text-left"><%= t('allow_to_decline_documents') %></span>
<span class="tooltip tooltip-top flex cursor-pointer" data-tip="<%= t('allow_recipients_to_decline_signing_a_document_the_decline_reason_notification_will_be_sent_to_the_signature_requester') %>">
<%= svg_icon('info_circle', class: 'hidden md:inline-block w-4 h-4 shrink-0') %>
</span>
</div>
<submit-form data-on="change" class="flex">
<%= f.check_box :value, class: 'toggle', checked: account_config.value != false %>
</submit-form>
</div>
<% end %>
<% end %>
@@ -122,11 +152,16 @@
<% if can?(:manage, account_config) %>
<%= form_for account_config, url: account_configs_path, method: :post do |f| %>
<%= f.hidden_field :key %>
<div class="flex items-center justify-between py-2.5">
<span>
<%= t('remember_and_pre_fill_signatures') %>
</span>
<%= f.check_box :value, class: 'toggle', checked: account_config.value != false, onchange: 'this.form.requestSubmit()' %>
<div class="flex items-center justify-between gap-4 py-2.5">
<div class="flex items-center space-x-1">
<span class="text-left"><%= t('remember_and_pre_fill_signatures') %></span>
<span class="tooltip tooltip-top flex cursor-pointer" data-tip="<%= t('save_a_users_signature_and_automatically_pre_fill_it_in_future_signing_sessions') %>">
<%= svg_icon('info_circle', class: 'hidden md:inline-block w-4 h-4 shrink-0') %>
</span>
</div>
<submit-form data-on="change" class="flex">
<%= f.check_box :value, class: 'toggle', checked: account_config.value != false %>
</submit-form>
</div>
<% end %>
<% end %>
@@ -134,11 +169,16 @@
<% if can?(:manage, account_config) %>
<%= form_for account_config, url: account_configs_path, method: :post do |f| %>
<%= f.hidden_field :key %>
<div class="flex items-center justify-between py-2.5">
<span>
<%= t('expirable_file_download_links') %>
</span>
<%= f.check_box :value, class: 'toggle', checked: account_config.value != false, onchange: 'this.form.requestSubmit()' %>
<div class="flex items-center justify-between gap-4 py-2.5">
<div class="flex items-center space-x-1">
<span class="text-left"><%= t('expirable_file_download_links') %></span>
<span class="tooltip tooltip-top flex cursor-pointer" data-tip="<%= t('make_document_download_links_expire_after_40_minutes_to_prevent_long_term_access_and_enhance_security') %>">
<%= svg_icon('info_circle', class: 'hidden md:inline-block w-4 h-4 shrink-0') %>
</span>
</div>
<submit-form data-on="change" class="flex">
<%= f.check_box :value, class: 'toggle', checked: account_config.value != false %>
</submit-form>
</div>
<% end %>
<% end %>
@@ -146,11 +186,16 @@
<% if can?(:manage, account_config) %>
<%= form_for account_config, url: account_configs_path, method: :post do |f| %>
<%= f.hidden_field :key %>
<div class="flex items-center justify-between py-2.5">
<span>
<%= t('require_authentication_for_file_download_links') %>
</span>
<%= f.check_box :value, class: 'toggle', checked: account_config.value, onchange: 'this.form.requestSubmit()' %>
<div class="flex items-center justify-between gap-4 py-2.5">
<div class="flex items-center space-x-1">
<span class="text-left"><%= t('require_authentication_for_file_download_links') %></span>
<span class="tooltip tooltip-top flex cursor-pointer" data-tip="<%= t('require_authentication_with_user_login_or_api_key_to_access_the_document_download_links') %>">
<%= svg_icon('info_circle', class: 'hidden md:inline-block w-4 h-4 shrink-0') %>
</span>
</div>
<submit-form data-on="change" class="flex">
<%= f.check_box :value, class: 'toggle', checked: account_config.value %>
</submit-form>
</div>
<% end %>
<% end %>
@@ -158,11 +203,16 @@
<% if can?(:manage, account_config) %>
<%= form_for account_config, url: account_configs_path, method: :post do |f| %>
<%= f.hidden_field :key %>
<div class="flex items-center justify-between py-2.5">
<span>
<%= t('combine_completed_documents_and_audit_log') %>
</span>
<%= f.check_box :value, class: 'toggle', checked: account_config.value, onchange: 'this.form.requestSubmit()' %>
<div class="flex items-center justify-between gap-4 py-2.5">
<div class="flex items-center space-x-1">
<span class="text-left"><%= t('combine_completed_documents_and_audit_log') %></span>
<span class="tooltip tooltip-top flex cursor-pointer" data-tip="<%= t('combine_signed_documents_and_the_audit_log_into_a_single_pdf_file_for_easier_recordkeeping_and_compliance') %>">
<%= svg_icon('info_circle', class: 'hidden md:inline-block w-4 h-4 shrink-0') %>
</span>
</div>
<submit-form data-on="change" class="flex">
<%= f.check_box :value, class: 'toggle', checked: account_config.value %>
</submit-form>
</div>
<% end %>
<% end %>
@@ -171,22 +221,43 @@
<% if can?(:manage, account_config) %>
<%= form_for account_config, url: account_configs_path, method: :post do |f| %>
<%= f.hidden_field :key %>
<div class="flex items-center justify-between py-2.5">
<span>
<%= t('always_enforce_signing_order') %>
</span>
<%= f.check_box :value, class: 'toggle', checked: account_config.value, onchange: 'this.form.requestSubmit()' %>
<div class="flex items-center justify-between gap-4 py-2.5">
<div class="flex items-center space-x-1">
<span class="text-left"><%= t('always_enforce_signing_order') %></span>
<span class="tooltip tooltip-top flex cursor-pointer" data-tip="<%= t('make_the_recipients_signing_order_always_enforced_so_that_the_second_signer_can_start_signing_their_part_only_after_the_first_signer_has_completed_signing') %>">
<%= svg_icon('info_circle', class: 'hidden md:inline-block w-4 h-4 shrink-0') %>
</span>
</div>
<submit-form data-on="change" class="flex">
<%= f.check_box :value, class: 'toggle', checked: account_config.value %>
</submit-form>
</div>
<% end %>
<% end %>
<% end %>
<% if can?(:manage, :personalization_advanced) %>
<% account_config = AccountConfig.find_or_initialize_by(account: current_account, key: AccountConfig::WITH_FILE_LINKS_KEY) %>
<% if can?(:manage, account_config) %>
<%= form_for account_config, url: account_configs_path, method: :post do |f| %>
<%= f.hidden_field :key %>
<div class="flex items-center justify-between gap-4 py-2.5">
<div class="flex items-center space-x-1">
<span class="text-left"><%= t('use_direct_file_attachment_links_in_the_documents') %></span>
</div>
<submit-form data-on="change" class="flex">
<%= f.check_box :value, class: 'toggle', checked: account_config.value %>
</submit-form>
</div>
<% end %>
<% end %>
<% end %>
<%= render 'extra_preferences' %>
<% if !Docuseal.multitenant? && SearchEntry.table_exists? && (!Docuseal.fulltext_search? || params[:reindex] == 'true') && can?(:manage, EncryptedConfig) %>
<div class="flex items-center justify-between py-2.5">
<div class="flex items-center justify-between gap-4 py-2.5">
<span>
Efficient search with search index
<%= t('efficient_search_with_search_index') %>
</span>
<%= button_to params[:reindex] == 'true' ? 'Reindex' : 'Build Search Index', settings_search_entries_reindex_index_path, method: :post, class: 'btn btn-sm btn-neutral text-white px-4' %>
<%= button_to params[:reindex] == 'true' ? t('reindex') : t('build_search_index'), settings_search_entries_reindex_index_path, method: :post, class: 'btn btn-sm btn-neutral text-white px-4' %>
</div>
<% end %>
</div>
+3 -3
View File
@@ -46,7 +46,7 @@
<div class="collapse-content" style="display: inherit">
<div class="mockup-code overflow-hidden">
<% text = capture do %>curl --location '<%= api_submissions_url %>' \
--header 'X-Auth-Token: <%= current_user.access_token.token %>' \
--header 'X-Auth-Token: API_TOKEN' \
--data-raw '{
"template_id": <%= current_account.templates.last&.id || 1 %>,
"submitters": [
@@ -82,7 +82,7 @@
<div class="collapse-content" style="display: inherit">
<div class="mockup-code overflow-hidden">
<% text = capture do %>curl --location '<%= api_submissions_emails_url %>' \
--header 'X-Auth-Token: <%= current_user.access_token.token %>' \
--header 'X-Auth-Token: API_TOKEN' \
--data-raw '{
"template_id": <%= current_account.templates.last&.id || 1 %>,
"emails": "<%= current_user.email.sub('@', '+test@') %>, <%= current_user.email.sub('@', '+test2@') %>"
@@ -108,7 +108,7 @@
<div class="collapse-content" style="display: inherit">
<div class="mockup-code overflow-hidden">
<% text = capture do %>curl '<%= api_template_url(current_account.templates&.last || 1) %>' \
--header 'X-Auth-Token: <%= current_user.access_token.token %>'<% end.to_str %>
--header 'X-Auth-Token: API_TOKEN'<% end.to_str %>
<span class="top-0 right-0 absolute">
<%= render 'shared/clipboard_copy', icon: 'copy', text:, class: 'btn btn-ghost text-white', icon_class: 'w-6 h-6 text-white', copy_title: t('copy'), copied_title: t('copied') %>
</span>
+16 -9
View File
@@ -2,15 +2,22 @@
<h1 class="text-4xl font-bold text-center mt-8">
<%= t('sign_in') %>
</h1>
<%= form_for(resource, as: resource_name, html: { class: 'space-y-6' }, data: { turbo: params[:redir].blank? }, url: session_path(resource_name)) do |f| %>
<%= f.hidden_field :email %>
<%= f.hidden_field :password %>
<% if params[:redir].present? %>
<%= hidden_field_tag :redir, params[:redir] %>
<% end %>
<%= render 'otp_form', **local_assigns %>
<div class="form-control">
<%= f.button button_title(title: t('sign_in'), disabled_with: t('signing_in')), class: 'base-button' %>
<% if local_assigns[:access_error].present? %>
<div class="alert mt-6">
<%= svg_icon('x_circle', class: 'w-6 h-6 text-red-500') %>
<span><%= local_assigns[:access_error] %></span>
</div>
<% else %>
<%= form_for(resource, as: resource_name, html: { class: 'space-y-6' }, data: { turbo: params[:redir].blank? }, url: session_path(resource_name)) do |f| %>
<%= f.hidden_field :email %>
<%= f.hidden_field :password %>
<% if params[:redir].present? %>
<%= hidden_field_tag :redir, params[:redir] %>
<% end %>
<%= render 'otp_form', **local_assigns %>
<div class="form-control">
<%= f.button button_title(title: t('sign_in'), disabled_with: t('signing_in')), class: 'base-button' %>
</div>
<% end %>
<% end %>
</div>
+3 -1
View File
@@ -14,6 +14,8 @@
<% if params[:redir].present? %>
<%= hidden_field_tag :redir, params[:redir] %>
<% end %>
<%= select_tag :lang, options_for_select((I18n.available_locales - %i[en pt-PT de-DE fr-FR it-IT es-ES]).map { |code| [t("language_#{code}"), code] }, I18n.locale), onchange: 'this.form.requestSubmit();', class: 'select select-sm border-base-content/30 text-base' %>
<submit-form data-on="change">
<%= select_tag :lang, options_for_select((I18n.available_locales - %i[en pt-PT de-DE fr-FR it-IT es-ES]).map { |code| [t("language_#{code}"), code] }, I18n.locale), class: 'select select-sm border-base-content/30 text-base' %>
</submit-form>
<% end %>
</div>
@@ -1,52 +0,0 @@
<% eu_server = request.host == 'docuseal.eu' %>
<server-selector>
<div id="global_server_selector" class="text-center hidden">
<div class="join">
<a href="https://docuseal.com<%= request.fullpath.gsub('docuseal.eu', 'docuseal.com') %>" class="btn bg-base-200 join-item w-40 <%= 'bg-base-300' unless eu_server %>">
<%= svg_icon 'world', class: 'w-5 h-5' %>
Global
</a>
<a href="https://docuseal.eu<%= request.fullpath.gsub('docuseal.com', 'docuseal.eu') %>" class="relative btn bg-base-200 join-item w-40 <%= 'bg-base-300' if eu_server %>">
<%= svg_icon 'eu_flag', class: 'w-5 h-5' %>
Europe
<% unless eu_server %>
<span id="eu_server_alert" class="absolute flex space-x-0.5 hidden" style="top: -1.5rem;">
<span class="text-xs font-normal leading-none text-base-content normal-case">
<%= t('eu_data_residency') %>
</span>
<%= svg_icon 'corner_right_down', class: 'w-4 h-5 stroke-1 shrink-0 pt-1' %>
</span>
<% end %>
</a>
</div>
</div>
<div id="server_selector" class="flex justify-center <%= 'hidden' unless eu_server %>">
<div class="dropdown">
<label tabindex="0" class="relative btn btn-sm bg-transparent font-medium normal-case border-base-content/20 justify-start" style="width: 141px; padding: 0 20px">
<% if eu_server %>
<%= svg_icon 'eu_flag', class: 'w-5 h-5' %>
<span>EU Cloud</span>
<% else %>
<%= svg_icon 'usa_flag', class: 'w-5 h-5' %>
<span>US Cloud</span>
<% end %>
<%= svg_icon 'chevron_down', class: 'mr-1 w-4 h-4 absolute right-1' %>
</label>
<ul tabindex="0" class="dropdown-content z-[1] menu border border-base-content/20 mt-1 bg-base-100 rounded-box w-36">
<li>
<a href="https://docuseal.com<%= request.fullpath.gsub('docuseal.eu', 'docuseal.com') %>" class="flex items-center space-x-2 <%= 'bg-base-300' unless eu_server %>">
<%= svg_icon 'usa_flag', class: 'w-5 h-5' %>
US Cloud
</a>
</li>
<li>
<a href="https://docuseal.eu<%= request.fullpath.gsub('docuseal.com', 'docuseal.eu') %>" class="flex items-center space-x-2 <%= 'bg-base-300' if eu_server %>">
<%= svg_icon 'eu_flag', class: 'w-5 h-5' %>
EU Cloud
</a>
</li>
</ul>
</div>
</div>
</server-selector>
<%= render 'scripts/server_selector' %>
+9 -5
View File
@@ -148,7 +148,9 @@
<span>
<%= t('apply_multiple_pdf_digital_signatures_in_the_document_per_each_signer') %>
</span>
<%= f.check_box :value, { class: 'toggle', checked: account_config.value == 'multiple', onchange: 'this.form.requestSubmit()' }, 'multiple', 'single' %>
<submit-form data-on="change" class="flex">
<%= f.check_box :value, { class: 'toggle', checked: account_config.value == 'multiple' }, 'multiple', 'single' %>
</submit-form>
</div>
<% end %>
<% end %>
@@ -160,7 +162,9 @@
<span>
<%= t('remove_pdf_form_fillable_fields_from_the_signed_pdf_flatten_form') %>
</span>
<%= f.check_box :value, { class: 'toggle', checked: account_config.value != false, onchange: 'this.form.requestSubmit()' } %>
<submit-form data-on="change" class="flex">
<%= f.check_box :value, { class: 'toggle', checked: account_config.value != false } %>
</submit-form>
</div>
<% end %>
<% end %>
@@ -172,9 +176,9 @@
<span>
<%= t('document_download_filename_format') %>
</span>
<div class="mt-3">
<%= f.select :value, [["#{I18n.t('document_name')}.pdf", '{document.name}'], ["#{I18n.t('document_name')} - #{I18n.t(:signed)}.pdf", '{document.name} - {submission.status}'], ["#{I18n.t('document_name')} - name@domain.com.pdf", '{document.name} - {submission.submitters}'], ["#{I18n.t('document_name')} - name@domain.com - #{I18n.l(Time.current.beginning_of_year.in_time_zone(current_account.timezone), format: :short)}.pdf", '{document.name} - {submission.submitters} - {submission.completed_at}']], {}, class: 'base-select', onchange: 'this.form.requestSubmit()' %>
</div>
<submit-form data-on="change" class="block mt-3">
<%= f.select :value, [["#{I18n.t('document_name')}.pdf", '{document.name}'], ["#{I18n.t('document_name')} - #{I18n.t(:signed)}.pdf", '{document.name} - {submission.status}'], ["#{I18n.t('document_name')} - name@domain.com.pdf", '{document.name} - {submission.submitters}'], ["#{I18n.t('document_name')} - name@domain.com - #{I18n.l(Time.current.beginning_of_year.in_time_zone(current_account.timezone), format: :short)}.pdf", '{document.name} - {submission.submitters} - {submission.completed_at}']], {}, class: 'base-select' %>
</submit-form>
</div>
<% end %>
<% end %>
+1 -2
View File
@@ -7,7 +7,6 @@
<% end %>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<% if ENV['ROLLBAR_CLIENT_TOKEN'] %>
<meta name="rollbar-token" content="<%= ENV.fetch('ROLLBAR_CLIENT_TOKEN', nil) %>">
<%= javascript_pack_tag 'rollbar', 'application', defer: true %>
@@ -18,7 +17,6 @@
<link href="<%= canonical_url %>" rel="canonical">
<% end %>
<%= stylesheet_pack_tag 'application', media: 'all' %>
<%= render 'shared/plausible' if !signed_in? && ENV['PLAUSIBLE_DOMAIN'] %>
</head>
<body>
<turbo-frame id="modal"></turbo-frame>
@@ -28,5 +26,6 @@
<div class="max-w-6xl mx-auto px-4 md:px-2 mb-8">
<%= yield %>
</div>
<%= render 'shared/body_scripts' %>
</body>
</html>
-1
View File
@@ -4,7 +4,6 @@
<%= render 'layouts/head_tags' %>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<% if ENV['ROLLBAR_CLIENT_TOKEN'] %>
<meta name="rollbar-token" content="<%= ENV.fetch('ROLLBAR_CLIENT_TOKEN', nil) %>">
<%= javascript_pack_tag 'rollbar', 'form', defer: true %>
-1
View File
@@ -4,7 +4,6 @@
<%= render 'layouts/head_tags' %>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<% if ENV['ROLLBAR_CLIENT_TOKEN'] %>
<meta name="rollbar-token" content="<%= ENV.fetch('ROLLBAR_CLIENT_TOKEN', nil) %>">
<%= javascript_pack_tag 'rollbar', 'application', defer: true %>
@@ -13,7 +13,9 @@
<span>
<%= t('receive_notification_emails_on_completed_submission') %>
</span>
<%= f.check_box :value, class: 'toggle', checked: user_config.value != false, onchange: 'this.form.requestSubmit()' %>
<submit-form data-on="change" class="flex">
<%= f.check_box :value, class: 'toggle', checked: user_config.value != false %>
</submit-form>
</div>
<% end %>
<% end %>
@@ -7,7 +7,9 @@
<span>
<%= t('show_confetti_on_successful_completion') %>
</span>
<%= f.check_box :value, { class: 'toggle', checked: account_config.value != false, onchange: 'this.form.requestSubmit()' }, '1', '0' %>
<submit-form data-on="change" class="flex">
<%= f.check_box :value, { class: 'toggle', checked: account_config.value != false }, '1', '0' %>
</submit-form>
</div>
<% end %>
</div>
+3 -1
View File
@@ -1,4 +1,4 @@
<script>
<script id="autosize_script" nonce="<%= content_security_policy_nonce %>">
if (!window.customElements.get('autosize-field')) {
window.customElements.define('autosize-field', class extends HTMLElement {
connectedCallback() {
@@ -19,4 +19,6 @@
}
});
}
window.autosize_script.remove()
</script>
@@ -1,26 +0,0 @@
<script>
if (!window.customElements.get('server-selector')) {
customElements.define('server-selector', class extends HTMLElement {
connectedCallback() {
const serverSelector = this.querySelector('#server_selector');
const globalServerSelector = this.querySelector('#global_server_selector');
const euServerAlert = this.querySelector('#eu_server_alert');
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
const usTimezones = /^(?:America\/(?:New_York|Detroit|Kentucky|Indiana|Chicago|Menominee|North_Dakota|Denver|Boise|Phoenix|Los_Angeles|Anchorage|Juneau|Sitka|Metlakatla|Yakutat|Nome|Adak)|Pacific\/Honolulu)/;
if (!serverSelector.classList.contains('hidden')) {
return
}
if (usTimezones.test(timezone)) {
serverSelector.classList.remove('hidden');
} else if (timezone.includes('Europe')) {
globalServerSelector.classList.remove('hidden');
euServerAlert?.classList?.remove('hidden');
} else {
globalServerSelector.classList.remove('hidden');
}
}
});
}
</script>
Whitespace-only changes.
+1 -1
View File
@@ -11,7 +11,7 @@
<span><%= flash[:notice] || flash[:alert] %></span>
</div>
</div>
<a href="#" onclick="[event.preventDefault(), window.flash.remove()]" class="mr-1">&times;</a>
<remove-on-event data-event-type="click" data-selector-id="flash" class="mr-1 cursor-pointer">&times;</remove-on-event>
</div>
</div>
</div>
+1 -1
View File
@@ -1,6 +1,6 @@
<a target="_blank" href="<%= Docuseal::GITHUB_URL %>" rel="noopener noreferrer nofollow" class="relative flex items-center rounded-full px-2 py-0.5 text-xs leading-4 mt-1 text-base-content border border-base-300 tooltip tooltip-bottom" data-tip="Give a star on GitHub">
<span class="flex items-center justify-between space-x-0.5 font-medium">
<%= svg_icon('start', class: 'h-3 w-3') %>
<span>9k</span>
<span>10k</span>
</span>
</a>
+3 -1
View File
@@ -61,7 +61,9 @@
<% if (can?(:manage, EncryptedConfig) && current_user == true_user) || (current_user != true_user && current_account.testing?) %>
<%= form_for '', url: testing_account_path, method: current_account.testing? ? :delete : :get, html: { class: 'w-full py-1' } do |f| %>
<label class="flex items-center pl-6 pr-4 py-2 border-y border-base-300 -ml-2 -mr-2" for="testing_toggle">
<%= f.check_box :testing_toggle, class: 'toggle', checked: current_account.testing?, onchange: 'this.form.requestSubmit()', style: 'height: 0.885rem; width: 1.35rem; --handleoffset: 0.395rem; margin-left: -2px; margin-right: 8px' %>
<submit-form data-on="change" class="flex">
<%= f.check_box :testing_toggle, class: 'toggle', checked: current_account.testing?, style: 'height: 0.885rem; width: 1.35rem; --handleoffset: 0.395rem; margin-left: -2px; margin-right: 8px' %>
</submit-form>
<span class="whitespace-nowrap">
<%= t('test_mode') %>
</span>
+9 -9
View File
@@ -14,15 +14,15 @@
</a>
</div>
<% end %>
<search-input data-title="<%= local_assigns[:title_selector] || 'h1' %>">
<search-input data-title="<%= local_assigns[:title_selector] || 'h1' %>" class="flex items-center">
<input id="search" name="q" value="<%= params[:q] %>" class="input text-lg pr-10 -mr-12 w-0 md:w-60 <%= 'pl-8 input-outlined w-60' if params[:q].present? %>" placeholder="<%= local_assigns[:placeholder] %>">
<button type="submit" title="<%= t('search') %>" class="btn btn-ghost btn-circle">
<span class="enabled">
<%= svg_icon('search', class: 'w-6 h-6 stroke-2') %>
</span>
<span class="disabled">
<%= svg_icon('loader', class: 'w-5 h-5 animate-spin') %>
</span>
</button>
</search-input>
<button type="submit" title="<%= t('search') %>" class="btn btn-ghost btn-circle" onclick="window.search.value || document.activeElement === window.search ? null : [event.preventDefault(), window.search.focus()]">
<span class="enabled">
<%= svg_icon('search', class: 'w-6 h-6 stroke-2') %>
</span>
<span class="disabled">
<%= svg_icon('loader', class: 'w-5 h-5 animate-spin') %>
</span>
</button>
</form>
+3 -1
View File
@@ -98,7 +98,9 @@
<span class="mr-2 w-full">
<%= t('test_mode') %>
</span>
<%= f.check_box :testing_toggle, class: 'toggle toggle-sm', checked: current_account.testing?, onchange: 'this.form.requestSubmit()' %>
<submit-form data-on="change" class="flex">
<%= f.check_box :testing_toggle, class: 'toggle toggle-sm', checked: current_account.testing? %>
</submit-form>
</label>
</li>
<% end %>
+3 -1
View File
@@ -4,7 +4,9 @@
<span class="mr-2 text-lg">
<%= t('test_mode') %>
</span>
<%= f.check_box :testing_toggle, class: 'toggle', checked: current_account.testing?, onchange: 'this.form.requestSubmit()' %>
<submit-form data-on="change" class="flex">
<%= f.check_box :testing_toggle, class: 'toggle', checked: current_account.testing? %>
</submit-form>
</label>
<% end %>
<% end %>
@@ -25,6 +25,7 @@
</div>
<%= form_for '', url: start_form_path(@template.slug), method: :put, html: { class: 'space-y-4', id: 'code_form' } do |f| %>
<div dir="auto" class="form-control !mt-0">
<%= f.hidden_field 'resubmit', value: params[:resubmit] %>
<%= f.hidden_field 'submitter[name]', value: params[:name] || @submitter&.name %>
<%= f.hidden_field 'submitter[email]', value: params[:email] || @submitter&.email %>
<%= f.hidden_field 'submitter[phone]', value: params[:phone] || @submitter&.phone %>
+3 -1
View File
@@ -52,7 +52,9 @@
<% if link_form_fields.include?('phone') %>
<div dir="auto" class="form-control !mt-0">
<%= f.label :phone, t('phone'), class: 'label' %>
<%= f.telephone_field :phone, value: params[:phone] || @submitter.phone, pattern: '^\+[0-9\s\-]+$', oninvalid: "this.value ? this.setCustomValidity('#{t('use_international_format_1xxx_')}') : ''", oninput: "this.setCustomValidity('')", required: true, class: 'base-input', placeholder: t(multiple_fields ? 'provide_your_phone_in_international_format' : 'provide_your_phone_in_international_format_to_start') %>
<custom-validation data-invalid-message="<%= t('use_international_format_1xxx_') %>">
<%= f.telephone_field :phone, value: params[:phone] || @submitter.phone, pattern: '^\+[0-9\s\-]+$', required: true, class: 'base-input w-full', placeholder: t(multiple_fields ? 'provide_your_phone_in_international_format' : 'provide_your_phone_in_international_format_to_start') %>
</custom-validation>
</div>
<% end %>
<toggle-submit dir="auto" class="form-control">
+14 -10
View File
@@ -33,11 +33,13 @@
</linked-input>
</submitters-autocomplete>
<% has_phone_field = true %>
<submitters-autocomplete data-field="phone">
<linked-input data-target-id="<%= "detailed_phone_#{item['linked_to_uuid']}" if item['linked_to_uuid'].present? %>">
<%= tag.input type: 'tel', pattern: '^\+[0-9\s\-]+$', oninvalid: "this.value ? this.setCustomValidity('#{t('use_international_format_1xxx_')}') : ''", oninput: "this.setCustomValidity('')", name: 'submission[1][submitters][][phone]', autocomplete: 'off', class: 'base-input !h-10 mt-1.5 w-full', placeholder: local_assigns[:require_phone_2fa] == true ? t(:phone) : "#{t('phone')} (#{t('optional')})", id: "detailed_phone_#{item['uuid']}", required: local_assigns[:require_phone_2fa] == true %>
</linked-input>
</submitters-autocomplete>
<custom-validation data-invalid-message="<%= t('use_international_format_1xxx_') %>">
<submitters-autocomplete data-field="phone">
<linked-input data-target-id="<%= "detailed_phone_#{item['linked_to_uuid']}" if item['linked_to_uuid'].present? %>">
<%= tag.input type: 'tel', pattern: '^\+[0-9\s\-]+$', autocomplete: 'off', class: 'base-input !h-10 mt-1.5 w-full', placeholder: local_assigns[:require_phone_2fa] == true ? t(:phone) : "#{t('phone')} (#{t('optional')})", id: "detailed_phone_#{item['uuid']}", required: local_assigns[:require_phone_2fa] == true %>
</linked-input>
</submitters-autocomplete>
</custom-validation>
</div>
<% end %>
<% if prefillable_fields.present? %>
@@ -48,11 +50,13 @@
</submitters-autocomplete>
<% if local_assigns[:require_phone_2fa] == true || prefillable_fields.any? { |f| f['type'] == 'phone' } %>
<% has_phone_field = true %>
<submitters-autocomplete data-field="phone">
<linked-input data-target-id="<%= "detailed_phone_#{item['linked_to_uuid']}" if item['linked_to_uuid'].present? %>">
<%= tag.input type: 'tel', pattern: '^\+[0-9\s\-]+$', oninvalid: "this.value ? this.setCustomValidity('#{t('use_international_format_1xxx_')}') : ''", oninput: "this.setCustomValidity('')", name: 'submission[1][submitters][][phone]', autocomplete: 'off', class: 'base-input !h-10 mt-1.5 w-full', placeholder: t(:phone), id: "detailed_phone_#{item['uuid']}", required: true %>
</linked-input>
</submitters-autocomplete>
<custom-validation data-invalid-message="<%= t('use_international_format_1xxx_') %>">
<submitters-autocomplete data-field="phone">
<linked-input data-target-id="<%= "detailed_phone_#{item['linked_to_uuid']}" if item['linked_to_uuid'].present? %>">
<%= tag.input type: 'tel', pattern: '^\+[0-9\s\-]+$', name: 'submission[1][submitters][][phone]', autocomplete: 'off', class: 'base-input !h-10 mt-1.5 w-full', placeholder: t(:phone), id: "detailed_phone_#{item['uuid']}", required: true %>
</linked-input>
</submitters-autocomplete>
</custom-validation>
<% end %>
<% prefillable_fields.each do |field| %>
<% if field['type'] == 'checkbox' %>
+7 -5
View File
@@ -19,11 +19,13 @@
</label>
<% end %>
<input type="hidden" name="submission[1][submitters][][uuid]" value="<%= item['uuid'] %>">
<submitters-autocomplete data-field="phone">
<linked-input data-target-id="<%= "phone_phone_#{item['linked_to_uuid']}" if item['linked_to_uuid'].present? %>">
<%= tag.input type: 'tel', pattern: '^\+[0-9\s\-]+$', oninvalid: "this.value ? this.setCustomValidity('#{t('use_international_format_1xxx_')}') : ''", oninput: "this.setCustomValidity('')", name: 'submission[1][submitters][][phone]', autocomplete: 'off', class: 'base-input !h-10 w-full', placeholder: t('phone'), required: index.zero? || template.preferences['require_all_submitters'], id: "phone_phone_#{item['uuid']}" %>
</linked-input>
</submitters-autocomplete>
<custom-validation data-invalid-message="<%= t('use_international_format_1xxx_') %>">
<submitters-autocomplete data-field="phone">
<linked-input data-target-id="<%= "phone_phone_#{item['linked_to_uuid']}" if item['linked_to_uuid'].present? %>">
<%= tag.input type: 'tel', pattern: '^\+[0-9\s\-]+$', name: 'submission[1][submitters][][phone]', autocomplete: 'off', class: 'base-input !h-10 w-full', placeholder: t('phone'), required: index.zero? || template.preferences['require_all_submitters'], id: "phone_phone_#{item['uuid']}" %>
</linked-input>
</submitters-autocomplete>
</custom-validation>
<% if submitters.size > 1 %>
<submitters-autocomplete data-field="name">
<linked-input data-target-id="<%= "phone_name_#{item['linked_to_uuid']}" if item['linked_to_uuid'].present? %>">
+6 -4
View File
@@ -11,10 +11,12 @@
<% if can_send_emails %>
<%= render 'submissions/email_stats' %>
<%= content_for(:edit_button) || capture do %>
<label>
<%= f.check_box :is_custom_message, onchange: "[this.form.querySelector('#message_field').classList.toggle('hidden', !event.currentTarget.checked)]", checked: false, class: 'hidden peer' %>
<span class="link peer-checked:hidden"><%= t('edit_message') %></span>
</label>
<toggle-visible data-element-ids="<%= %w[message_field].to_json %>" class="flex">
<label>
<%= f.check_box :is_custom_message, checked: false, class: 'hidden peer', data: { action: 'change:toggle-visible#trigger', type: 'checkbox' } %>
<span class="link peer-checked:hidden"><%= t('edit_message') %></span>
</label>
</toggle-visible>
<% end %>
<% end %>
</div>
+13
View File
@@ -76,6 +76,19 @@
<div class="flex w-full px-0.5 whitespace-nowrap <%= valign == 'top' ? 'items-start' : (valign == 'bottom' ? 'items-end' : 'items-center') %>">
<div class="w-full"><%= NumberUtils.format_number(value, field.dig('preferences', 'format')) %></div>
</div>
<% elsif field['type'] == 'strikethrough' %>
<div class="w-full h-full flex items-center justify-center">
<% if (((1000.0 / local_assigns[:page_width]) * local_assigns[:page_height]) * area['h']) < 40 %>
<svg width="100%" height="100%">
<line x1="0" y1="50%" x2="100%" y2="50%" stroke="<%= field.dig('preferences', 'color').presence || 'red' %>" style="stroke-width: clamp(0px, 0.5vw, 6px); stroke-width: 0.6cqmin"></line>
</svg>
<% else %>
<svg xmlns="http://www.w3.org/2000/svg" style="overflow: visible; width: calc(100% - 6px); height: calc(100% - 6px); width: calc(100% - 0.6cqmin); height: calc(100% - 0.6cqmin)">
<line x1="0" y1="0" x2="100%" y2="100%" stroke="<%= field.dig('preferences', 'color').presence || 'red' %>" style="stroke-width: clamp(0px, 0.5vw, 6px); stroke-width: 0.6cqmin"></line>
<line x1="100%" y1="0" x2="0" y2="100%" stroke="<%= field.dig('preferences', 'color').presence || 'red' %>" style="stroke-width: clamp(0px, 0.5vw, 6px); stroke-width: 0.6cqmin"></line>
</svg>
<% end %>
</div>
<% else %>
<autosize-field></autosize-field>
<div class="flex w-full px-0.5 whitespace-pre-wrap <%= valign == 'top' ? 'items-start' : (valign == 'bottom' ? 'items-end' : 'items-center') %>">
+20 -16
View File
@@ -80,12 +80,12 @@
<% schema.each do |item| %>
<% document = @submission.schema_documents.find { |a| item['attachment_uuid'] == a.uuid } %>
<% if document.preview_images.first %>
<a href="#<%= "page-#{document.uuid}-0" %>" onclick="[event.preventDefault(), window[event.target.closest('a').href.split('#')[1]].scrollIntoView({ behavior: 'smooth', block: 'start' })]" class="block cursor-pointer">
<scroll-to data-selector-id="page-<%= document.uuid %>-0" class="block cursor-pointer">
<img src="<%= Docuseal::URL_CACHE.fetch([document.id, document.uuid, 0].join(':'), expires_in: 10.minutes) { document.preview_images.first.url } %>" width="<%= document.preview_images.first.metadata['width'] %>" height="<%= document.preview_images.first.metadata['height'] %>" class="rounded border" loading="lazy">
<div class="pb-2 pt-1.5 text-center" dir="auto">
<%= item['name'].presence || document.filename.base %>
</div>
</a>
</scroll-to>
<% end %>
<% end %>
</div>
@@ -123,7 +123,7 @@
</span>
</span>
<% else %>
<%= render 'submissions/value', font_scale:, area:, field:, attachments_index:, value: mask.present? ? Array.wrap(value).map { |e| TextUtils.mask_value(e, mask) }.join(', ') : value, locale: @submission.account.locale, timezone: @submission.account.timezone, submitter:, with_signature_id:, with_submitter_timezone:, with_signature_id_reason: %>
<%= render 'submissions/value', page_width: width, page_height: height, font_scale:, area:, field:, attachments_index:, value: mask.present? ? Array.wrap(value).map { |e| TextUtils.mask_value(e, mask) }.join(', ') : value, locale: @submission.account.locale, timezone: @submission.account.timezone, submitter:, with_signature_id:, with_submitter_timezone:, with_signature_id_reason: %>
<% end %>
<% end %>
</div>
@@ -240,7 +240,7 @@
<% submitter_field_counters[field['type']] += 1 %>
<% value = values[field['uuid']].presence || (field['default_value'] != '{{date}}' && field['readonly'] == true && field['default_value'].present? ? Submitters::SubmitValues.template_default_value_for_submitter(field['default_value'], @submission.submitters.find { |e| e.uuid == field['submitter_uuid'] }, with_time: false) : nil) %>
<% next if value.blank? %>
<% next if field['type'] == 'heading' %>
<% next if field['type'] == 'heading' || field['type'] == 'strikethrough' %>
<div class="pt-2.5 border-b border-base-300">
<div class="text-xs font-medium uppercase mb-0.5" dir="auto">
<%= field['name'].presence || "#{t("#{field['type']}_field")} #{submitter_field_counters[field['type']]}" %>
@@ -288,16 +288,20 @@
<% end %>
</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><%= t('signers') %></span>
</span>
<span class="hidden peer-checked:flex items-center">
<%= svg_icon('chevron_left', class: 'w-8 h-8') %>
<span><%= t('back') %></span>
</span>
</label>
<toggle-visible data-element-ids="<%= %w[document_view parties_view].to_json %>">
<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" data-action="click:toggle-visible#trigger">
<span class="peer-checked:hidden flex items-center space-x-2">
<%= svg_icon('users', class: 'w-8 h-8') %>
<span><%= t('signers') %></span>
</span>
<span class="hidden peer-checked:flex items-center">
<%= svg_icon('chevron_left', class: 'w-8 h-8') %>
<span><%= t('back') %></span>
</span>
</label>
</toggle-visible>
</div>
<%= render 'scripts/autosize_field' %>
<% unless request.headers['HTTP_X_TURBO'] %>
<%= render 'scripts/autosize_field' %>
<% end %>
@@ -23,6 +23,12 @@
<span><%= t('status') %></span>
<% end %>
</li>
<li class="flex">
<%= link_to submissions_filter_path('folder', query_params.merge(path: url_for)), data: { turbo_frame: 'modal' } do %>
<%= svg_icon('folder', class: 'w-5 h-5 flex-shrink-0 stroke-2') %>
<span><%= t('folder') %></span>
<% end %>
</li>
<li class="flex">
<%= link_to submissions_filter_path('author', query_params.merge(path: url_for)), data: { turbo_frame: 'modal' } do %>
<%= svg_icon('user', class: 'w-5 h-5 flex-shrink-0 stroke-2') %>
@@ -0,0 +1,9 @@
<%= render 'filter_modal', title: t('folder'), default_params: params.permit(*(Submissions::Filter::ALLOWED_PARAMS - ['folder'])) do %>
<div class="space-y-2">
<div class="form-control mt-6">
<folder-autocomplete class="flex justify-between w-full">
<input id="folder_name" placeholder="<%= t('folder_name') %>" type="text" class="base-input w-full" name="folder" value="<%= params[:folder] %>" autocomplete="off">
</folder-autocomplete>
</div>
</div>
<% end %>
+1 -1
View File
@@ -84,7 +84,7 @@
<% next if field['conditions'].present? && values[field['uuid']].blank? && field['submitter_uuid'] != @submitter.uuid %>
<% next if field['conditions'].present? && field['submitter_uuid'] == @submitter.uuid %>
<% next if field.dig('preferences', 'formula').present? && field['submitter_uuid'] == @submitter.uuid %>
<%= render 'submissions/value', font_scale:, area:, field:, attachments_index: @attachments_index, value: field.dig('preferences', 'mask').present? ? TextUtils.mask_value(value, field.dig('preferences', 'mask')) : value, locale: @submitter.account.locale, timezone: @submitter.account.timezone, submitter: submitters_index[field['submitter_uuid']], with_signature_id: @form_configs[:with_signature_id], with_submitter_timezone: @form_configs[:with_submitter_timezone], with_signature_id_reason: @form_configs[:with_signature_id_reason] %>
<%= render 'submissions/value', page_width: width, page_height: height, font_scale:, area:, field:, attachments_index: @attachments_index, value: field.dig('preferences', 'mask').present? ? TextUtils.mask_value(value, field.dig('preferences', 'mask')) : value, locale: @submitter.account.locale, timezone: @submitter.account.timezone, submitter: submitters_index[field['submitter_uuid']], with_signature_id: @form_configs[:with_signature_id], with_submitter_timezone: @form_configs[:with_submitter_timezone], with_signature_id_reason: @form_configs[:with_signature_id_reason] %>
<% end %>
</div>
</page-container>
@@ -4,7 +4,6 @@
<%= render 'layouts/head_tags' %>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<% if ENV['ROLLBAR_CLIENT_TOKEN'] %>
<meta name="rollbar-token" content="<%= ENV.fetch('ROLLBAR_CLIENT_TOKEN', nil) %>">
<%= javascript_pack_tag 'rollbar', 'draw', defer: true %>
+5 -3
View File
@@ -10,9 +10,11 @@
<submitters-autocomplete data-field="email">
<%= email_field_tag 'submitter[email]', @submitter.email, autocomplete: 'off', class: 'base-input !h-10 mt-1.5 w-full', placeholder: "#{t('email')} (#{t('optional')})" %>
</submitters-autocomplete>
<submitters-autocomplete data-field="phone">
<%= telephone_field_tag 'submitter[phone]', @submitter.phone, autocomplete: 'off', pattern: '^\+[0-9\s\-]+$', class: 'base-input !h-10 mt-1.5 w-full', placeholder: "#{t('phone')} (#{t('optional')})", oninvalid: "this.value ? this.setCustomValidity('#{t('use_international_format_1xxx_')}') : ''", oninput: "this.setCustomValidity('')" %>
</submitters-autocomplete>
<custom-validation data-invalid-message="<%= t('use_international_format_1xxx_') %>">
<submitters-autocomplete data-field="phone">
<%= telephone_field_tag 'submitter[phone]', @submitter.phone, autocomplete: 'off', pattern: '^\+[0-9\s\-]+$', class: 'base-input !h-10 mt-1.5 w-full', placeholder: "#{t('phone')} (#{t('optional')})" %>
</submitters-autocomplete>
</custom-validation>
</div>
</submitter-item>
</div>
+3 -1
View File
@@ -34,7 +34,9 @@
<%= render 'shared/search_input' %>
<% end %>
<% if can?(:create, ::Template) %>
<%= render 'templates/upload_button', folder_name: @template_folder.full_name %>
<span class="hidden sm:block">
<%= render 'templates/upload_button', folder_name: @template_folder.full_name %>
</span>
<%= link_to new_template_path(folder_name: @template_folder.full_name), 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"><%= t('create') %></span>
+9 -6
View File
@@ -1,19 +1,22 @@
<%= form_for '', url: templates_upload_path, id: form_id = SecureRandom.uuid, method: :post, class: 'inline', html: { enctype: 'multipart/form-data' } do %>
<button id="templates_upload_button" type="submit" class="btn btn-ghost text-base" onclick="[event.preventDefault(), window.upload_template.click()]">
<span class="enabled">
<label for="upload_template" id="templates_upload_button" class="btn btn-ghost text-base">
<button type="submit" class="hidden peer"></button>
<span class="peer-disabled:hidden">
<span class="flex items-center justify-center space-x-2">
<%= svg_icon('upload', class: 'w-6 h-6 stroke-2') %>
<span class="hidden md:block"><%= t('upload') %></span>
</span>
</span>
<span class="disabled">
<span class="peer-enabled:hidden">
<span class="flex items-center justify-center space-x-2">
<%= local_assigns[:icon_disabled] || svg_icon('loader', class: 'w-5 h-5 animate-spin') %>
<span class="hidden md:block"><%= t('uploading') %>...</span>
<span class="hidden md:block"><%= t('upload') %>...</span>
</span>
</span>
</button>
</label>
<input type="hidden" name="form_id" value="<%= form_id %>">
<input id="upload_template" name="files[]" class="hidden" onchange="this.form.requestSubmit()" type="file" accept="image/*, application/pdf, application/zip<%= ", #{Templates::CreateAttachments::DOCUMENT_EXTENSIONS.join(', ')}" if Docuseal.advanced_formats? %>" multiple>
<submit-form data-on="change" data-disable="true">
<input id="upload_template" name="files[]" class="hidden" type="file" accept="image/*, application/pdf, application/zip<%= ", #{Templates::CreateAttachments::DOCUMENT_EXTENSIONS.join(', ')}" if Docuseal.advanced_formats? %>" multiple>
</submit-form>
<input hidden name="folder_name" value="<%= local_assigns[:folder_name] %>">
<% end %>
+10 -6
View File
@@ -12,14 +12,18 @@
<%= f.text_field :name, required: true, placeholder: t('document_name'), class: 'base-input', dir: 'auto' %>
</div>
<div class="mt-3 mb-4 flex items-center justify-between">
<a href="#" onclick="[event.preventDefault(), window.folder_name.focus()]">
<label for="folder_name" class="cursor-pointer">
<%= svg_icon('folder', class: 'w-6 h-6') %>
</a>
</label>
<folder-autocomplete class="flex justify-between w-full">
<input id="folder_name" placeholder="<%= t('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&.full_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="shrink-0 link peer-focus:hidden mr-1.5">
<%= t('change_folder') %>
</a>
<set-value data-on="blur" data-value="<%= TemplateFolder::DEFAULT_NAME %>" data-empty-only="true" class="peer w-full whitespace-nowrap">
<input id="folder_name" placeholder="<%= t('folder_name') %>" type="text" class="w-full outline-none border-transparent focus:border-transparent focus:ring-0 bg-base-100 px-1" name="folder_name" value="<%= params[:folder_name].presence || @base_template&.folder&.full_name || TemplateFolder::DEFAULT_NAME %>" autocomplete="off">
</set-value>
<set-value data-on="click" data-value="" data-input-id="folder_name" class="peer-focus-within:hidden whitespace-nowrap">
<label for="folder_name" data-clear-on-focus="true" class="shrink-0 link mr-1.5 cursor-pointer">
<%= t('change_folder') %>
</label>
</set-value>
</folder-autocomplete>
</div>
<div class="form-control">
+3 -1
View File
@@ -25,7 +25,9 @@
<span>
<%= t('share_template_with_test_mode') %>
</span>
<%= f.check_box :value, class: 'toggle', checked: @template.template_sharings.exists?(account_id: current_account.testing_accounts), onchange: 'this.form.requestSubmit()' %>
<submit-form data-on="change">
<%= f.check_box :value, class: 'toggle', checked: @template.template_sharings.exists?(account_id: current_account.testing_accounts) %>
</submit-form>
</div>
<% end %>
<div class="mb-4">
+7 -3
View File
@@ -63,7 +63,7 @@
<div class="hidden md:block">
<app-tour id="app_tour" data-show-tour="<%= params[:tour] == 'true' || user_config.value %>" data-type="dashboard" data-next-page-path="<%= @templates.first && can?(:edit, @templates.first) ? edit_template_path(@templates.first, params.permit(:tour)) : settings_account_path %>" data-i18n="<%= t('app_tour').to_json %>"></app-tour>
<% if user_config.new_record? && !params.key?(:tour) %>
<div class="h-36 rounded-2xl pt-3 px-7 w-full border border-dashed border-base-300">
<div id="app_tour_manager" class="h-36 rounded-2xl pt-3 px-7 w-full border border-dashed border-base-300">
<div class="text-xl text-center font-semibold text-base-content">
<%= t('welcome_to_docuseal') %>
</div>
@@ -71,8 +71,12 @@
<%= t('start_a_quick_tour_to_learn_how_to_create_an_send_your_first_document') %>
</div>
<div class="flex gap-2 mt-3 w-full">
<%= button_to button_title(title: t('skip'), icon_disabled: svg_icon('loader', class: 'w-4 h-4 animate-spin')), user_configs_path, params: { user_config: { key: UserConfig::SHOW_APP_TOUR, value: false } }, class: 'btn btn-sm btn-outline w-full', form_class: 'flex-1', method: :post, form: { onsubmit: 'window.app_tour.parentNode.remove()' } %>
<%= button_to t('start_tour'), user_configs_path, params: { user_config: { key: UserConfig::SHOW_APP_TOUR, value: true } }, class: 'btn btn-sm btn-warning w-full', form_class: 'flex-1', method: :post, form: { onsubmit: 'window.app_tour.start()' } %>
<remove-on-event data-on="submit" data-selector-id="app_tour_manager" class="block w-full">
<%= button_to button_title(title: t('skip'), icon_disabled: svg_icon('loader', class: 'w-4 h-4 animate-spin')), user_configs_path, params: { user_config: { key: UserConfig::SHOW_APP_TOUR, value: false } }, class: 'btn btn-sm btn-outline w-full', form_class: 'flex-1', method: :post %>
</remove-on-event>
<app-tour-start class="block w-full">
<%= button_to t('start_tour'), user_configs_path, params: { user_config: { key: UserConfig::SHOW_APP_TOUR, value: true } }, class: 'btn btn-sm btn-warning w-full', form_class: 'flex-1', method: :post %>
</app-tour-start>
</div>
</div>
<% end %>
@@ -6,13 +6,15 @@
<div class="flex items-center" style="margin-left: 20px; flex-shrink: 0">
<% if @template.submitters.size > 1 %>
<form action="<%= template_form_path(@template) %>" method="get" class="mr-3">
<select onchange="this.form.submit()" name="uuid" class="select base-input text-center font-normal" style="width: 180px; flex-shrink: 0;">
<% @template.submitters.each do |submitter| %>
<%= tag.option(value: submitter['uuid'], selected: submitter['uuid'] == @submitter.uuid) do %>
<%= submitter['name'] %>
<submit-form data-on="change">
<select name="uuid" class="select base-input text-center font-normal" style="width: 180px; flex-shrink: 0;">
<% @template.submitters.each do |submitter| %>
<%= tag.option(value: submitter['uuid'], selected: submitter['uuid'] == @submitter.uuid) do %>
<%= submitter['name'] %>
<% end %>
<% end %>
<% end %>
</select>
</select>
</submit-form>
</form>
<% end %>
<a href="<%= edit_template_path(@template) %>" class="base-button" data-turbo="false" style="flex-shrink: 0; padding: 0px 24px;">
@@ -73,7 +73,9 @@
<%= t('enforce_recipients_order') %>
</span>
<%= f.fields_for :preferences, Struct.new(:submitters_order).new(template.preferences['submitters_order']) do |ff| %>
<%= ff.check_box :submitters_order, { class: 'toggle', onchange: 'this.form.requestSubmit()' }, 'preserved', '' %>
<submit-form data-on="change" class="flex">
<%= ff.check_box :submitters_order, { class: 'toggle' }, 'preserved', '' %>
</submit-form>
<% end %>
</div>
<% end %>
@@ -85,7 +87,9 @@
<%= t('ensure_unique_recipients') %>
</span>
<%= f.fields_for :preferences, Struct.new(:validate_unique_submitters).new(template.preferences['validate_unique_submitters']) do |ff| %>
<%= ff.check_box :validate_unique_submitters, { class: 'toggle', onchange: 'this.form.requestSubmit()' }, 'true', '' %>
<submit-form data-on="change" class="flex">
<%= ff.check_box :validate_unique_submitters, { class: 'toggle' }, 'true', '' %>
</submit-form>
<% end %>
</div>
<% end %>
+34 -12
View File
@@ -46,9 +46,13 @@
<div class="form-control">
<% duration_options = Templates::EXPIRATION_DURATIONS.keys.map { |duration| [t(duration), duration] } + [[t('specified_date'), 'specified_date']] %>
<%= ff.label :default_expire_at_duration, t('default_expiration'), class: 'label pt-0' %>
<div class="flex items-center gap-2">
<%= ff.select :default_expire_at_duration, duration_options, { include_blank: t('none') }, required: false, class: 'base-select flex-1', dir: 'auto', autocomplete: 'off', onchange: "this.value == 'specified_date' ? window.template_preferences_default_expire_at.classList.remove('hidden') : [window.template_preferences_default_expire_at.classList.add('hidden'), window.template_preferences_default_expire_at.value = '', this.form.requestSubmit()]" %>
<%= ff.datetime_field :default_expire_at, required: false, class: ['base-input flex-1', ff.object.default_expire_at.blank? && 'hidden'].compact_blank.join(' '), dir: 'auto', autocomplete: 'off', onchange: 'this.value && this.form.requestSubmit()' %>
<div class="flex flex-col md:flex-row md:items-center gap-2">
<show-on-value data-value="specified_date" data-selector-id="template_preferences_default_expire_at" class="flex w-full">
<%= ff.select :default_expire_at_duration, duration_options, { include_blank: t('none') }, required: false, class: 'base-select flex-1', dir: 'auto', autocomplete: 'off' %>
</show-on-value>
<submit-form data-on="change" data-submit-if-value="true" class="flex">
<%= ff.datetime_field :default_expire_at, required: false, class: ['base-input flex-1', ff.object.default_expire_at.blank? && 'hidden'].compact_blank.join(' '), dir: 'auto', autocomplete: 'off' %>
</submit-form>
</div>
</div>
<% end %>
@@ -168,7 +172,9 @@
<span>
<%= 'Send signature request email' %>
</span>
<%= ff.check_box :request_email_enabled, { checked: ff.object.request_email_enabled != false, class: 'toggle', onchange: 'this.form.requestSubmit()' }, 'true', 'false' %>
<submit-form data-on="change" class="flex">
<%= ff.check_box :request_email_enabled, { checked: ff.object.request_email_enabled != false, class: 'toggle' }, 'true', 'false' %>
</submit-form>
</div>
<% end %>
<div class="form-control pt-2">
@@ -215,19 +221,25 @@
<span>
<%= t('attach_documents_to_the_email') %>
</span>
<%= ff.check_box :documents_copy_email_attach_documents, { checked: ff.object.documents_copy_email_attach_documents != false, class: 'toggle', onchange: 'this.form.requestSubmit()', disabled: configs['attach_documents'] == false }, 'true', 'false' %>
<submit-form data-on="change" class="flex">
<%= ff.check_box :documents_copy_email_attach_documents, { checked: ff.object.documents_copy_email_attach_documents != false, class: 'toggle', disabled: configs['attach_documents'] == false }, 'true', 'false' %>
</submit-form>
</div>
<div class="flex items-center justify-between pt-2.5 px-1 mb-2">
<span>
<%= t('attach_audit_log_pdf_to_the_email') %>
</span>
<%= ff.check_box :documents_copy_email_attach_audit, { checked: ff.object.documents_copy_email_attach_audit != false, class: 'toggle', onchange: 'this.form.requestSubmit()', disabled: configs['attach_audit_log'] == false }, 'true', 'false' %>
<submit-form data-on="change" class="flex">
<%= ff.check_box :documents_copy_email_attach_audit, { checked: ff.object.documents_copy_email_attach_audit != false, class: 'toggle', disabled: configs['attach_audit_log'] == false }, 'true', 'false' %>
</submit-form>
</div>
<div class="flex items-center justify-between py-2.5 px-1 mb-2">
<span>
<%= t('send_emails_automatically_on_completion') %>
</span>
<%= ff.check_box :documents_copy_email_enabled, { checked: ff.object.documents_copy_email_enabled != false && configs['enabled'] != false, class: 'toggle', onchange: 'this.form.requestSubmit()', disabled: configs['enabled'] == false }, 'true', 'false' %>
<submit-form data-on="change" class="flex">
<%= ff.check_box :documents_copy_email_enabled, { checked: ff.object.documents_copy_email_enabled != false && configs['enabled'] != false, class: 'toggle', disabled: configs['enabled'] == false }, 'true', 'false' %>
</submit-form>
</div>
<% end %>
<div class="form-control pt-2">
@@ -268,19 +280,25 @@
<span>
<%= t('attach_documents_to_the_email') %>
</span>
<%= ff.check_box :completed_notification_email_attach_documents, { checked: ff.object.completed_notification_email_attach_documents != false, class: 'toggle', onchange: 'this.form.requestSubmit()', disabled: configs['attach_documents'] == false }, 'true', 'false' %>
<submit-form data-on="change" class="flex">
<%= ff.check_box :completed_notification_email_attach_documents, { checked: ff.object.completed_notification_email_attach_documents != false, class: 'toggle', disabled: configs['attach_documents'] == false }, 'true', 'false' %>
</submit-form>
</div>
<div class="flex items-center justify-between pt-2.5 px-1 mb-2">
<span>
<%= t('attach_audit_log_pdf_to_the_email') %>
</span>
<%= ff.check_box :completed_notification_email_attach_audit, { checked: ff.object.completed_notification_email_attach_audit != false, class: 'toggle', onchange: 'this.form.requestSubmit()', disabled: configs['attach_audit_log'] == false }, 'true', 'false' %>
<submit-form data-on="change" class="flex">
<%= ff.check_box :completed_notification_email_attach_audit, { checked: ff.object.completed_notification_email_attach_audit != false, class: 'toggle', disabled: configs['attach_audit_log'] == false }, 'true', 'false' %>
</submit-form>
</div>
<div class="flex items-center justify-between py-2.5 px-1 mb-2">
<span>
<%= t('send_emails_automatically_on_completion') %>
</span>
<%= ff.check_box :completed_notification_email_enabled, { checked: ff.object.completed_notification_email_enabled != false, class: 'toggle', onchange: 'this.form.requestSubmit()' }, 'true', 'false' %>
<submit-form data-on="change" class="flex">
<%= ff.check_box :completed_notification_email_enabled, { checked: ff.object.completed_notification_email_enabled != false, class: 'toggle', disabled: configs['enabled'] == false }, 'true', 'false' %>
</submit-form>
</div>
<% end %>
<div class="form-control pt-2">
@@ -325,7 +343,9 @@
</div>
<div class="flex items-center justify-between gap-1 pt-3">
<span><%= t('enable_shared_link') %></span>
<%= f.check_box :shared_link, { class: 'toggle', onchange: 'this.form.requestSubmit()' }, 'true', 'false' %>
<submit-form data-on="change" class="flex">
<%= f.check_box :shared_link, { class: 'toggle' }, 'true', 'false' %>
</submit-form>
</div>
<% end %>
</div>
@@ -339,7 +359,9 @@
<span>
<%= t('share_template_with_test_mode') %>
</span>
<%= f.check_box :value, class: 'toggle', checked: @template.template_sharings.exists?(account_id: current_account.testing_accounts), onchange: 'this.form.requestSubmit()' %>
<submit-form data-on="change" class="flex">
<%= f.check_box :value, class: 'toggle', checked: @template.template_sharings.exists?(account_id: current_account.testing_accounts) %>
</submit-form>
</div>
<% end %>
<div class="mb-4">
+6 -2
View File
@@ -5,7 +5,9 @@
<%= form_for @template, url: template_share_link_path(@template), method: :post, html: { id: 'shared_link_form', autocomplete: 'off', class: 'mt-3' }, data: { close_on_submit: false } do |f| %>
<label for="template_shared_link" class="flex items-center my-4 justify-between gap-1 alert bg-base-100 border-base-300">
<span><%= t('enable_shared_link') %></span>
<%= f.check_box :shared_link, { disabled: !can?(:update, @template), class: 'toggle', onchange: 'this.form.requestSubmit()' }, 'true', 'false' %>
<submit-form data-on="change" class="flex">
<%= f.check_box :shared_link, { disabled: !can?(:update, @template), class: 'toggle' }, 'true', 'false' %>
</submit-form>
</label>
<div class="flex gap-2 mt-3">
<input id="embedding_url" type="text" value="<%= start_form_url(slug: @template.slug) %>" class="base-input w-full" autocomplete="off" readonly>
@@ -63,7 +65,9 @@
<%= f.fields_for :preferences, Struct.new(:shared_link_2fa).new(@template.preferences['shared_link_2fa'] == true) do |ff| %>
<label for="template_preferences_shared_link_2fa" class="flex items-center mt-4 h-14 justify-between gap-1 alert bg-base-100 border-base-300">
<span><%= t('request_email_otp_verification_with_shared_link') %></span>
<%= ff.check_box :shared_link_2fa, { checked: ff.object.shared_link_2fa == true, disabled: !can?(:update, @template), class: 'toggle', onchange: 'this.form.requestSubmit()' }, 'true', 'false' %>
<submit-form data-on="change" class="flex">
<%= ff.check_box :shared_link_2fa, { checked: ff.object.shared_link_2fa == true, disabled: !can?(:update, @template), class: 'toggle' }, 'true', 'false' %>
</submit-form>
</label>
<% end %>
<% end %>
@@ -51,7 +51,9 @@
</ol>
<% unless webhook_event.status == 'pending' %>
<div class="absolute right-4 top-3">
<%= button_to button_title(title: t('resend'), disabled_with: t('awaiting'), icon: svg_icon('rotate', class: 'w-4 h-4'), icon_disabled: svg_icon('loader', class: 'w-4 h-4 animate-spin')), resend_settings_webhook_event_path(webhook_url.id, webhook_event.uuid), form: { id: button_uuid = SecureRandom.uuid }, params: { button_id: button_uuid }, class: 'btn btn-neutral btn-sm text-white', method: :post, onclick: '[this.form.requestSubmit(), this.disabled = true]' %>
<toggle-classes data-classes="btn-disabled">
<%= button_to button_title(title: t('resend'), disabled_with: t('awaiting'), icon: svg_icon('rotate', class: 'w-4 h-4'), icon_disabled: svg_icon('loader', class: 'w-4 h-4 animate-spin')), resend_settings_webhook_event_path(webhook_url.id, webhook_event.uuid), form: { id: button_uuid = SecureRandom.uuid }, params: { button_id: button_uuid }, class: 'btn btn-neutral btn-sm text-white', method: :post %>
</toggle-classes>
</div>
<% end %>
</div>
+3 -1
View File
@@ -21,7 +21,9 @@
<div><%= webhook_event.event_type %></div>
</div>
<div class="flex items-center gap-3">
<%= button_to button_title(title: t('resend'), disabled_with: t('awaiting'), icon: svg_icon('rotate', class: 'w-4 h-4'), icon_disabled: svg_icon('loader', class: 'w-4 h-4 animate-spin')), resend_settings_webhook_event_path(webhook_url.id, webhook_event.uuid), form: { id: button_uuid = SecureRandom.uuid }, params: { button_id: button_uuid }, class: 'btn btn-neutral btn-xs h-2 text-white relative z-[1] hidden md:group-hover:inline-block', data: { turbo_frame: :drawer }, method: :post, onclick: "[this.form.requestSubmit(), this.disabled = true, this.classList.remove('hidden')]" %>
<toggle-classes data-classes="btn-disabled hidden">
<%= button_to button_title(title: t('resend'), disabled_with: t('awaiting'), icon: svg_icon('rotate', class: 'w-4 h-4'), icon_disabled: svg_icon('loader', class: 'w-4 h-4 animate-spin')), resend_settings_webhook_event_path(webhook_url.id, webhook_event.uuid), form: { id: button_uuid = SecureRandom.uuid }, params: { button_id: button_uuid }, class: 'btn btn-neutral btn-xs h-2 text-white relative z-[1] hidden md:group-hover:inline-block', data: { turbo_frame: :drawer }, method: :post %>
</toggle-classes>
<span><%= l(webhook_event.created_at, locale: current_account.locale, format: :short) %></span>
</div>
</div>
+4 -2
View File
@@ -66,7 +66,9 @@
<%= f.fields_for :events do |ff| %>
<div class="flex">
<label class="flex items-center cursor-pointer">
<%= ff.check_box event, class: 'base-checkbox', checked: @webhook_url.events.include?(event), onchange: 'this.form.requestSubmit()' %>
<submit-form data-on="change" class="flex">
<%= ff.check_box event, class: 'base-checkbox', checked: @webhook_url.events.include?(event) %>
</submit-form>
<span class="ml-2"><%= event %></span>
</label>
</div>
@@ -84,7 +86,7 @@
<h2 id="log" class="text-3xl font-bold"><%= t('events_log') %></h2>
<div class="tabs border-b mt-4">
<%= link_to t('all'), url_for(params.to_unsafe_h.except(:status)), style: 'margin-bottom: -1px', class: "tab h-10 text-base #{params[:status].blank? ? 'tab-active tab-bordered' : 'pb-[3px]'}" %>
<%= link_to t('successed'), url_for(params.to_unsafe_h.merge(status: 'success')), style: 'margin-bottom: -1px', class: "tab h-10 text-base #{params[:status] == 'success' ? 'tab-active tab-bordered' : 'pb-[3px]'}" %>
<%= link_to t('succeeded'), url_for(params.to_unsafe_h.merge(status: 'success')), style: 'margin-bottom: -1px', class: "tab h-10 text-base #{params[:status] == 'success' ? 'tab-active tab-bordered' : 'pb-[3px]'}" %>
<%= link_to t('failed'), url_for(params.to_unsafe_h.merge(status: 'error')), style: 'margin-bottom: -1px', class: "tab h-10 text-base #{params[:status] == 'error' ? 'tab-active tab-bordered' : 'pb-[3px]'}" %>
</div>
<% if @webhook_events.present? %>
+3
View File
@@ -31,6 +31,9 @@ module DocuSeal
config.exceptions_app = ->(env) { ErrorsController.action(:show).call(env) }
config.content_security_policy_nonce_generator = ->(_) { SecureRandom.base64(16) }
config.content_security_policy_nonce_directives = %w[script-src]
config.action_view.frozen_string_literal = true
config.middleware.insert_before ActionDispatch::Static, Rack::Deflater
+2
View File
@@ -66,6 +66,8 @@ Rails.application.configure do
# Raise exceptions for disallowed deprecations.
config.active_support.disallowed_deprecation = :raise
config.active_storage.service_urls_expire_in = 240.minutes
# Tell Active Support which deprecation messages to disallow.
config.active_support.disallowed_deprecation_warnings = []
+164 -6
View File
@@ -27,6 +27,7 @@ en: &en
enabled: Enabled
disabled: Disabled
party: Party
use_direct_file_attachment_links_in_the_documents: Use direct file attachment links in the documents
click_here_to_send_a_reset_password_email_html: '<label class="link" for="resend_password_button">Click here</label> to send a reset password email.'
edit_order: Edit Order
expirable_file_download_links: Expirable file download links
@@ -592,6 +593,8 @@ en: &en
four_days: 4 days
eight_days: 8 days
fifteen_days: 15 days
twenty_one_days: 21 days
thirty_days: 30 days
free: Free
unlimited_documents_storage: Unlimited documents storage
users_management: Users management
@@ -772,7 +775,7 @@ en: &en
at_least_one_field_must_be_displayed_in_the_form: At least one field must be displayed in the form.
this_template_has_multiple_parties_which_prevents_the_use_of_a_sharing_link: This template has multiple parties, which prevents the use of a shared link as it's unclear which party is responsible for specific fields. To resolve this, define the default party details.
events_log: Events Log
successed: Successed
succeeded: Succeeded
failed: Failed
there_are_no_events: There are no events
resend: Resend
@@ -805,6 +808,27 @@ en: &en
current_password: Current password
dont_remember_your_current_password_click_here_to_reset_it_html: 'Don''t remember your current password? <label class="link" for="resend_password_button">Click here</label> to reset it.'
an_email_with_password_reset_instructions_has_been_sent: An email with password reset instructions has been sent.
api_key_access_code: API key access code
use_otp_code_to_access_the_api_key_html: Use <b>%{code}</b> code to access the API key.
please_reply_to_this_email_if_you_dont_recognize_this_request: Please reply to this email if you don't recognize this request.
your_user_account_has_been_archived_contact_your_administrator_to_restore_access_to_your_account: Your user account has been archived. Contact your administrator to restore access to your account.
your_email_could_not_be_reached_this_may_happen_if_there_was_a_typo_in_your_address_or_if_your_mailbox_is_not_available_please_contact_support_email_to_log_in: Your email could not be reached. This may happen if there was a typo in your address or if your mailbox is not available. Please contact support@docuseal.com to log in.
efficient_search_with_search_index: Efficient search with search index
reindex: Reindex
build_search_index: Build Search Index
require_two_factor_authentication_2fa_with_an_authenticator_app_e_g_google_authenticator_authy_all_users_signing_documents_must_pass_the_second_factor_verification_using_a_secure_code_in_addition_to_their_password: Require two-factor authentication (2FA) with an authenticator app (e.g., Google Authenticator, Authy). All users signing documents must pass the second factor verification using a secure code in addition to their password.
add_a_unique_signature_id_and_timestamp_to_each_signature_for_audit_and_traceability_purposes_along_with_the_timestamp_part_of_docuseals_21_cfr_part_11_compliance_settings: "Add a unique Signature ID and timestamp to each signature for audit and traceability purposes along with the timestamp. Part of DocuSeal's 21 CFR Part 11 compliance settings."
require_signer_to_provide_a_reason_for_signing_before_completing_their_signature_e_g_approvals_certifications_part_of_docuseals_21_cfr_part_11_compliance_settings: "Require signers to provide a reason for signing before completing their signature (e.g., approvals, certifications). Part of DocuSeal's 21 CFR Part 11 compliance settings."
allow_signers_to_create_signatures_by_typing_their_name_instead_of_drawing_or_uploading_one: Allow signers to create signatures by typing their name instead of drawing or uploading one.
allow_signers_to_resubmit_forms_after_completion_useful_when_corrections_or_multiple_submissions_are_needed: Allow signers to resubmit forms after completion, useful when corrections or multiple submissions are needed.
allow_recipients_to_decline_signing_a_document_the_decline_reason_notification_will_be_sent_to_the_signature_requester: Allow recipients to decline signing a document. The decline reason notification will be sent to the signature requester.
save_a_users_signature_and_automatically_pre_fill_it_in_future_signing_sessions: "Save a user's signature and automatically pre-fill it in future signing sessions."
make_document_download_links_expire_after_40_minutes_to_prevent_long_term_access_and_enhance_security: Make document download links expire after 40 minutes to prevent long-term access and enhance security.
require_authentication_with_user_login_or_api_key_to_access_the_document_download_links: Require authentication with user login or API key to access the document download links.
combine_signed_documents_and_the_audit_log_into_a_single_pdf_file_for_easier_recordkeeping_and_compliance: Combine signed documents and the Audit Log into a single PDF file for easier recordkeeping and compliance.
require_a_jwt_authorization_to_preview_embedded_forms_ensuring_only_authorized_users_can_view_them: Require a JWT authorization to preview embedded forms, ensuring only authorized users can view them.
make_all_newly_created_templates_private_to_their_creator_by_default: Make all newly created templates private to their creator by default.
make_the_recipients_signing_order_always_enforced_so_that_the_second_signer_can_start_signing_their_part_only_after_the_first_signer_has_completed_signing: Make the recipients signing order always enforced, so that the second signer can start signing their part only after the first signer has completed signing.
submission_sources:
api: API
bulk: Bulk Send
@@ -911,6 +935,7 @@ en: &en
range_without_total: "%{from}-%{to} events"
es: &es
use_direct_file_attachment_links_in_the_documents: Usar enlaces directos de archivos adjuntos en los documentos
enabled: Habilitado
disabled: Deshabilitado
expirable_file_download_links: Enlaces de descarga de archivos con vencimiento
@@ -1483,6 +1508,8 @@ es: &es
four_days: 4 días
eight_days: 8 días
fifteen_days: 15 días
twenty_one_days: 21 días
thirty_days: 30 días
free: Gratis
unlimited_documents_storage: Almacenamiento ilimitado de documentos
users_management: Gestión de usuarios
@@ -1662,7 +1689,7 @@ es: &es
at_least_one_field_must_be_displayed_in_the_form: Al menos un campo debe mostrarse en el formulario.
this_template_has_multiple_parties_which_prevents_the_use_of_a_sharing_link: Esta plantilla tiene varias partes, lo que impide el uso de un enlace compartido porque no está claro qué parte es responsable de campos específicos. Para resolverlo, define los detalles predeterminados de la parte.
events_log: Registro de eventos
successed: Exitoso
succeeded: Exitoso
failed: Fallido
there_are_no_events: No hay eventos
resend: Reenviar
@@ -1695,6 +1722,27 @@ es: &es
current_password: Contraseña actual
dont_remember_your_current_password_click_here_to_reset_it_html: '¿No recuerdas tu contraseña actual? <label class="link" for="resend_password_button">Haz clic aquí</label> para restablecerla.'
an_email_with_password_reset_instructions_has_been_sent: Se enviará un correo electrónico con las instrucciones para restablecer tu contraseña en unos minutos.
api_key_access_code: Código de acceso de la clave API
use_otp_code_to_access_the_api_key_html: Usa el código <b>%{code}</b> para acceder a la clave API.
please_reply_to_this_email_if_you_dont_recognize_this_request: Responde a este correo si no reconoces esta solicitud.
your_user_account_has_been_archived_contact_your_administrator_to_restore_access_to_your_account: Tu cuenta de usuario ha sido archivada. Contacta a tu administrador para restaurar el acceso a tu cuenta.
your_email_could_not_be_reached_this_may_happen_if_there_was_a_typo_in_your_address_or_if_your_mailbox_is_not_available_please_contact_support_email_to_log_in: No se pudo acceder a tu correo electrónico. Esto puede ocurrir si hubo un error tipográfico en tu dirección o si tu buzón no está disponible. Por favor, contacta a support@docuseal.com para iniciar sesión.
efficient_search_with_search_index: Búsqueda eficiente con índice de búsqueda
reindex: Reindexar
build_search_index: Construir índice de búsqueda
require_two_factor_authentication_2fa_with_an_authenticator_app_e_g_google_authenticator_authy_all_users_signing_documents_must_pass_the_second_factor_verification_using_a_secure_code_in_addition_to_their_password: Requerir autenticación de dos factores (2FA) con una aplicación de autenticación (p. ej., Google Authenticator, Authy). Todos los usuarios que firman documentos deben pasar la verificación del segundo factor usando un código seguro además de su contraseña.
add_a_unique_signature_id_and_timestamp_to_each_signature_for_audit_and_traceability_purposes_along_with_the_timestamp_part_of_docuseals_21_cfr_part_11_compliance_settings: Agregar un ID de firma único y una marca de tiempo a cada firma para fines de auditoría y trazabilidad junto con la marca de tiempo. Parte de la configuración de cumplimiento 21 CFR Parte 11 de DocuSeal.
require_signer_to_provide_a_reason_for_signing_before_completing_their_signature_e_g_approvals_certifications_part_of_docuseals_21_cfr_part_11_compliance_settings: Requerir que los firmantes proporcionen un motivo para firmar antes de completar su firma (p. ej., aprobaciones, certificaciones). Parte de la configuración de cumplimiento 21 CFR Parte 11 de DocuSeal.
allow_signers_to_create_signatures_by_typing_their_name_instead_of_drawing_or_uploading_one: Permitir que los firmantes creen firmas escribiendo su nombre en lugar de dibujar o subir una.
allow_signers_to_resubmit_forms_after_completion_useful_when_corrections_or_multiple_submissions_are_needed: Permitir que los firmantes vuelvan a enviar formularios después de completarlos, útil cuando se necesitan correcciones o múltiples envíos.
allow_recipients_to_decline_signing_a_document_the_decline_reason_notification_will_be_sent_to_the_signature_requester: Permitir que los destinatarios rechacen firmar un documento. La notificación del motivo del rechazo se enviará al solicitante de la firma.
save_a_users_signature_and_automatically_pre_fill_it_in_future_signing_sessions: Guardar la firma de un usuario y rellenarla automáticamente en futuras sesiones de firma.
make_document_download_links_expire_after_40_minutes_to_prevent_long_term_access_and_enhance_security: Hacer que los enlaces de descarga de documentos caduquen después de 40 minutos para evitar el acceso a largo plazo y mejorar la seguridad.
require_authentication_with_user_login_or_api_key_to_access_the_document_download_links: Requerir autenticación con inicio de sesión de usuario o clave API para acceder a los enlaces de descarga de documentos.
combine_signed_documents_and_the_audit_log_into_a_single_pdf_file_for_easier_recordkeeping_and_compliance: Combinar documentos firmados y el registro de auditoría en un solo archivo PDF para una gestión y cumplimiento más fáciles.
require_a_jwt_authorization_to_preview_embedded_forms_ensuring_only_authorized_users_can_view_them: Requerir autorización JWT para previsualizar formularios incrustados, garantizando que solo los usuarios autorizados puedan verlos.
make_all_newly_created_templates_private_to_their_creator_by_default: Hacer que todas las plantillas creadas sean privadas para su creador por defecto.
make_the_recipients_signing_order_always_enforced_so_that_the_second_signer_can_start_signing_their_part_only_after_the_first_signer_has_completed_signing: Hacer que el orden de firma de los destinatarios se aplique siempre, de modo que el segundo firmante pueda comenzar a firmar solo después de que el primero haya completado la firma.
submission_sources:
api: API
bulk: Envío masivo
@@ -1801,6 +1849,7 @@ es: &es
range_without_total: "%{from}-%{to} eventos"
it: &it
use_direct_file_attachment_links_in_the_documents: Usa i link diretti per gli allegati nei documenti
click_here_to_send_a_reset_password_email_html: '<label class="link" for="resend_password_button">Clicca qui</label> per inviare una email per reimpostare la password.'
enabled: Abilitato
disabled: Disabilitato
@@ -2372,6 +2421,8 @@ it: &it
four_days: 4 giorni
eight_days: 8 giorni
fifteen_days: 15 giorni
twenty_one_days: 21 giorni
thirty_days: 30 giorni
free: Gratuito
unlimited_documents_storage: Archiviazione illimitata di documenti
users_management: Gestione utenti
@@ -2552,7 +2603,7 @@ it: &it
at_least_one_field_must_be_displayed_in_the_form: Almeno un campo deve essere visualizzato nel modulo.
this_template_has_multiple_parties_which_prevents_the_use_of_a_sharing_link: Questo modello ha più parti, il che impedisce l’uso di un link di condivisione perché non è chiaro quale parte sia responsabile di campi specifici. Per risolvere, definisci i dettagli predefiniti della parte.
events_log: Registro eventi
successed: Riuscito
succeeded: Riuscito
failed: Fallito
there_are_no_events: Nessun evento
resend: Invia di nuovo
@@ -2586,6 +2637,27 @@ it: &it
current_password: Password attuale
dont_remember_your_current_password_click_here_to_reset_it_html: 'Non ricordi la tua password attuale? <label class="link" for="resend_password_button">Clicca qui</label> per reimpostarla.'
an_email_with_password_reset_instructions_has_been_sent: Un'email con le istruzioni per reimpostare la password ti è stata inviata e arriverà entro pochi minuti.
api_key_access_code: Codice di accesso della chiave API
use_otp_code_to_access_the_api_key_html: Usa il codice <b>%{code}</b> per accedere alla chiave API.
please_reply_to_this_email_if_you_dont_recognize_this_request: Rispondi a questa email se non riconosci questa richiesta.
your_user_account_has_been_archived_contact_your_administrator_to_restore_access_to_your_account: Il tuo account utente è stato archiviato. Contatta il tuo amministratore per ripristinare l'accesso al tuo account.
your_email_could_not_be_reached_this_may_happen_if_there_was_a_typo_in_your_address_or_if_your_mailbox_is_not_available_please_contact_support_email_to_log_in: Non è stato possibile raggiungere la tua email. Questo può accadere se c'è stato un errore di digitazione nell'indirizzo o se la tua casella di posta non è disponibile. Contatta support@docuseal.com per accedere.
efficient_search_with_search_index: Ricerca efficiente con indice di ricerca
reindex: Reindicizza
build_search_index: Crea indice di ricerca
require_two_factor_authentication_2fa_with_an_authenticator_app_e_g_google_authenticator_authy_all_users_signing_documents_must_pass_the_second_factor_verification_using_a_secure_code_in_addition_to_their_password: "Richiedere l'autenticazione a due fattori (2FA) con un'app di autenticazione (es. Google Authenticator, Authy). Tutti gli utenti che firmano documenti devono superare la verifica del secondo fattore utilizzando un codice sicuro oltre alla password."
add_a_unique_signature_id_and_timestamp_to_each_signature_for_audit_and_traceability_purposes_along_with_the_timestamp_part_of_docuseals_21_cfr_part_11_compliance_settings: Aggiungere un ID firma univoco e una marca temporale a ogni firma per scopi di audit e tracciabilità insieme alla marca temporale. Parte delle impostazioni di conformità 21 CFR Parte 11 di DocuSeal.
require_signer_to_provide_a_reason_for_signing_before_completing_their_signature_e_g_approvals_certifications_part_of_docuseals_21_cfr_part_11_compliance_settings: Richiedere ai firmatari di fornire una motivazione prima di completare la firma (es. approvazioni, certificazioni). Parte delle impostazioni di conformità 21 CFR Parte 11 di DocuSeal.
allow_signers_to_create_signatures_by_typing_their_name_instead_of_drawing_or_uploading_one: Consentire ai firmatari di creare firme digitando il proprio nome invece di disegnarle o caricarle.
allow_signers_to_resubmit_forms_after_completion_useful_when_corrections_or_multiple_submissions_are_needed: Consentire ai firmatari di reinviare i moduli dopo il completamento, utile quando sono necessarie correzioni o più invii.
allow_recipients_to_decline_signing_a_document_the_decline_reason_notification_will_be_sent_to_the_signature_requester: Consentire ai destinatari di rifiutare di firmare un documento. La notifica del motivo del rifiuto verrà inviata al richiedente della firma.
save_a_users_signature_and_automatically_pre_fill_it_in_future_signing_sessions: Salvare la firma di un utente e compilarla automaticamente nelle future sessioni di firma.
make_document_download_links_expire_after_40_minutes_to_prevent_long_term_access_and_enhance_security: "Fare in modo che i link di download dei documenti scadano dopo 40 minuti per prevenire l'accesso a lungo termine e migliorare la sicurezza."
require_authentication_with_user_login_or_api_key_to_access_the_document_download_links: "Richiedere l'autenticazione con login utente o chiave API per accedere ai link di download dei documenti."
combine_signed_documents_and_the_audit_log_into_a_single_pdf_file_for_easier_recordkeeping_and_compliance: Combinare documenti firmati e registro di audit in un unico file PDF per una gestione e conformità più semplici.
require_a_jwt_authorization_to_preview_embedded_forms_ensuring_only_authorized_users_can_view_them: "Richiedere un'autorizzazione JWT per visualizzare in anteprima i moduli incorporati, garantendo che solo gli utenti autorizzati possano vederli."
make_all_newly_created_templates_private_to_their_creator_by_default: Rendere tutte le nuove template private per il creatore per impostazione predefinita.
make_the_recipients_signing_order_always_enforced_so_that_the_second_signer_can_start_signing_their_part_only_after_the_first_signer_has_completed_signing: "Rendere sempre obbligatorio l'ordine di firma dei destinatari, in modo che il secondo firmatario possa iniziare solo dopo che il primo ha completato la firma."
submission_sources:
api: API
bulk: Invio massivo
@@ -2692,6 +2764,7 @@ it: &it
range_without_total: "%{from}-%{to} eventi"
fr: &fr
use_direct_file_attachment_links_in_the_documents: Utiliser des liens directs pour les pièces jointes dans les documents
click_here_to_send_a_reset_password_email_html: '<label class="link" for="resend_password_button">Cliquez ici</label> pour envoyer un e-mail de réinitialisation du mot de passe.'
enabled: Activé
disabled: Désactivé
@@ -3266,6 +3339,8 @@ fr: &fr
four_days: 4 jours
eight_days: 8 jours
fifteen_days: 15 jours
twenty_one_days: 21 jours
thirty_days: 30 jours
free: Gratuit
unlimited_documents_storage: Stockage illimité de documents
users_management: Gestion des utilisateurs
@@ -3446,7 +3521,7 @@ fr: &fr
at_least_one_field_must_be_displayed_in_the_form: Au moins un champ doit être affiché dans le formulaire.
this_template_has_multiple_parties_which_prevents_the_use_of_a_sharing_link: Ce modèle contient plusieurs parties, ce qui empêche l’utilisation d’un lien de partage car il n’est pas clair quelle partie est responsable de certains champs. Pour résoudre cela, définissez les détails de la partie par défaut.
events_log: Journal des événements
successed: Réussi
succeeded: Réussi
failed: Échoué
there_are_no_events: Aucun événement
resend: Renvoyer
@@ -3480,6 +3555,27 @@ fr: &fr
current_password: Mot de passe actuel
dont_remember_your_current_password_click_here_to_reset_it_html: 'Vous ne vous souvenez plus de votre mot de passe actuel ? <label class="link" for="resend_password_button">Cliquez ici</label> pour le réinitialiser.'
an_email_with_password_reset_instructions_has_been_sent: Un e-mail contenant les instructions pour réinitialiser votre mot de passe vous sera envoyé dans quelques minutes.
api_key_access_code: Code d'accès de la clé API
use_otp_code_to_access_the_api_key_html: Utilisez le code <b>%{code}</b> pour accéder à la clé API.
please_reply_to_this_email_if_you_dont_recognize_this_request: Veuillez répondre à cet e-mail si vous ne reconnaissez pas cette demande.
your_user_account_has_been_archived_contact_your_administrator_to_restore_access_to_your_account: Votre compte utilisateur a été archivé. Contactez votre administrateur pour rétablir l'accès à votre compte.
your_email_could_not_be_reached_this_may_happen_if_there_was_a_typo_in_your_address_or_if_your_mailbox_is_not_available_please_contact_support_email_to_log_in: Votre e-mail n'a pas pu être atteint. Cela peut arriver s'il y a eu une faute de frappe dans votre adresse ou si votre boîte aux lettres n'est pas disponible. Veuillez contacter support@docuseal.com pour vous connecter.
efficient_search_with_search_index: Recherche efficace avec index de recherche
reindex: Réindexer
build_search_index: Construire un index de recherche
require_two_factor_authentication_2fa_with_an_authenticator_app_e_g_google_authenticator_authy_all_users_signing_documents_must_pass_the_second_factor_verification_using_a_secure_code_in_addition_to_their_password: "Exiger une authentification à deux facteurs (2FA) avec une application d'authentification (ex. Google Authenticator, Authy). Tous les utilisateurs signant des documents doivent passer la vérification du second facteur en utilisant un code sécurisé en plus de leur mot de passe."
add_a_unique_signature_id_and_timestamp_to_each_signature_for_audit_and_traceability_purposes_along_with_the_timestamp_part_of_docuseals_21_cfr_part_11_compliance_settings: "Ajouter un identifiant de signature unique et un horodatage à chaque signature à des fins d'audit et de traçabilité avec l'horodatage. Fait partie des paramètres de conformité 21 CFR Partie 11 de DocuSeal."
require_signer_to_provide_a_reason_for_signing_before_completing_their_signature_e_g_approvals_certifications_part_of_docuseals_21_cfr_part_11_compliance_settings: "Exiger des signataires qu'ils fournissent une raison avant de compléter leur signature (ex. approbations, certifications). Fait partie des paramètres de conformité 21 CFR Partie 11 de DocuSeal."
allow_signers_to_create_signatures_by_typing_their_name_instead_of_drawing_or_uploading_one: Permettre aux signataires de créer des signatures en tapant leur nom au lieu de les dessiner ou de les télécharger.
allow_signers_to_resubmit_forms_after_completion_useful_when_corrections_or_multiple_submissions_are_needed: Permettre aux signataires de soumettre à nouveau des formulaires après achèvement, utile lorsque des corrections ou plusieurs envois sont nécessaires.
allow_recipients_to_decline_signing_a_document_the_decline_reason_notification_will_be_sent_to_the_signature_requester: Permettre aux destinataires de refuser de signer un document. La notification de la raison du refus sera envoyée au demandeur de la signature.
save_a_users_signature_and_automatically_pre_fill_it_in_future_signing_sessions: "Enregistrer la signature d'un utilisateur et la pré-remplir automatiquement dans les futures sessions de signature."
make_document_download_links_expire_after_40_minutes_to_prevent_long_term_access_and_enhance_security: Faire expirer les liens de téléchargement de documents après 40 minutes pour éviter un accès à long terme et améliorer la sécurité.
require_authentication_with_user_login_or_api_key_to_access_the_document_download_links: Exiger une authentification avec identifiant utilisateur ou clé API pour accéder aux liens de téléchargement de documents.
combine_signed_documents_and_the_audit_log_into_a_single_pdf_file_for_easier_recordkeeping_and_compliance: "Combiner les documents signés et le journal d'audit dans un seul fichier PDF pour une gestion et une conformité plus faciles."
require_a_jwt_authorization_to_preview_embedded_forms_ensuring_only_authorized_users_can_view_them: Exiger une autorisation JWT pour prévisualiser les formulaires intégrés, garantissant que seuls les utilisateurs autorisés puissent les voir.
make_all_newly_created_templates_private_to_their_creator_by_default: Rendre toutes les nouvelles modèles privées à leur créateur par défaut.
make_the_recipients_signing_order_always_enforced_so_that_the_second_signer_can_start_signing_their_part_only_after_the_first_signer_has_completed_signing: "Rendre l'ordre de signature des destinataires toujours obligatoire, de sorte que le deuxième signataire ne puisse commencer qu'après que le premier a terminé."
submission_sources:
api: API
bulk: Envoi en masse
@@ -3586,6 +3682,7 @@ fr: &fr
range_without_total: "%{from} à %{to} événements"
pt: &pt
use_direct_file_attachment_links_in_the_documents: Usar links diretos de anexos de arquivos nos documentos
click_here_to_send_a_reset_password_email_html: '<label class="link" for="resend_password_button">Clique aqui</label> para enviar um e-mail de redefinição de senha.'
enabled: Ativado
disabled: Desativado
@@ -4159,6 +4256,8 @@ pt: &pt
four_days: 4 dias
eight_days: 8 dias
fifteen_days: 15 dias
twenty_one_days: 21 dias
thirty_days: 30 dias
free: Gratuito
unlimited_documents_storage: Armazenamento ilimitado de documentos
users_management: Gerenciamento de usuários
@@ -4339,7 +4438,7 @@ pt: &pt
at_least_one_field_must_be_displayed_in_the_form: Pelo menos um campo deve ser exibido no formulário.
this_template_has_multiple_parties_which_prevents_the_use_of_a_sharing_link: Este modelo tem várias partes, o que impede o uso de um link de compartilhamento, pois não está claro qual parte é responsável por campos específicos. Para resolver isso, defina os detalhes padrão da parte.
events_log: Registro de eventos
successed: Sucesso
succeeded: Sucesso
failed: Falhou
there_are_no_events: Nenhum evento
resend: Reenviar
@@ -4372,6 +4471,27 @@ pt: &pt
current_password: Senha atual
dont_remember_your_current_password_click_here_to_reset_it_html: 'Não se lembra da sua senha atual? <label class="link" for="resend_password_button">Clique aqui</label> para redefini-la.'
an_email_with_password_reset_instructions_has_been_sent: Um e-mail com instruções para redefinir sua senha será enviado em alguns minutos.
api_key_access_code: Código de acesso da chave API
use_otp_code_to_access_the_api_key_html: Use o código <b>%{code}</b> para acessar a chave API.
please_reply_to_this_email_if_you_dont_recognize_this_request: Responda a este e-mail se você não reconhecer esta solicitação.
your_user_account_has_been_archived_contact_your_administrator_to_restore_access_to_your_account: Sua conta de usuário foi arquivada. Entre em contato com o administrador para restaurar o acesso à sua conta.
your_email_could_not_be_reached_this_may_happen_if_there_was_a_typo_in_your_address_or_if_your_mailbox_is_not_available_please_contact_support_email_to_log_in: Seu e-mail não pôde ser acessado. Isso pode acontecer se houve um erro de digitação no endereço ou se sua caixa de correio não estiver disponível. Entre em contato com support@docuseal.com para fazer login.
efficient_search_with_search_index: Pesquisa eficiente com índice de busca
reindex: Reindexar
build_search_index: Construir índice de busca
require_two_factor_authentication_2fa_with_an_authenticator_app_e_g_google_authenticator_authy_all_users_signing_documents_must_pass_the_second_factor_verification_using_a_secure_code_in_addition_to_their_password: "Exigir autenticação de dois fatores (2FA) com um aplicativo autenticador (ex.: Google Authenticator, Authy). Todos os usuários que assinam documentos devem passar pela verificação do segundo fator usando um código seguro além da senha."
add_a_unique_signature_id_and_timestamp_to_each_signature_for_audit_and_traceability_purposes_along_with_the_timestamp_part_of_docuseals_21_cfr_part_11_compliance_settings: Adicionar um ID de assinatura exclusivo e um carimbo de data/hora a cada assinatura para fins de auditoria e rastreabilidade junto com o carimbo de data/hora. Parte das configurações de conformidade 21 CFR Parte 11 do DocuSeal.
require_signer_to_provide_a_reason_for_signing_before_completing_their_signature_e_g_approvals_certifications_part_of_docuseals_21_cfr_part_11_compliance_settings: "Exigir que os signatários forneçam uma razão antes de completar a assinatura (ex.: aprovações, certificações). Parte das configurações de conformidade 21 CFR Parte 11 do DocuSeal."
allow_signers_to_create_signatures_by_typing_their_name_instead_of_drawing_or_uploading_one: Permitir que os signatários criem assinaturas digitando seu nome em vez de desenhá-las ou carregá-las.
allow_signers_to_resubmit_forms_after_completion_useful_when_corrections_or_multiple_submissions_are_needed: Permitir que os signatários reenviem formulários após o término, útil quando são necessárias correções ou múltiplos envios.
allow_recipients_to_decline_signing_a_document_the_decline_reason_notification_will_be_sent_to_the_signature_requester: Permitir que os destinatários recusem assinar um documento. A notificação do motivo da recusa será enviada ao solicitante da assinatura.
save_a_users_signature_and_automatically_pre_fill_it_in_future_signing_sessions: Salvar a assinatura de um usuário e preenchê-la automaticamente em futuras sessões de assinatura.
make_document_download_links_expire_after_40_minutes_to_prevent_long_term_access_and_enhance_security: Fazer os links de download de documentos expirarem após 40 minutos para evitar o acesso prolongado e aumentar a segurança.
require_authentication_with_user_login_or_api_key_to_access_the_document_download_links: Exigir autenticação com login do usuário ou chave API para acessar os links de download de documentos.
combine_signed_documents_and_the_audit_log_into_a_single_pdf_file_for_easier_recordkeeping_and_compliance: Combinar documentos assinados e o registro de auditoria em um único arquivo PDF para facilitar o arquivamento e a conformidade.
require_a_jwt_authorization_to_preview_embedded_forms_ensuring_only_authorized_users_can_view_them: Exigir autorização JWT para visualizar formulários incorporados, garantindo que apenas usuários autorizados possam vê-los.
make_all_newly_created_templates_private_to_their_creator_by_default: Tornar todos os modelos recém-criados privados para seu criador por padrão.
make_the_recipients_signing_order_always_enforced_so_that_the_second_signer_can_start_signing_their_part_only_after_the_first_signer_has_completed_signing: Tornar a ordem de assinatura dos destinatários sempre obrigatória, para que o segundo signatário só possa assinar após o primeiro concluir.
submission_sources:
api: API
bulk: Envio em massa
@@ -4478,6 +4598,7 @@ pt: &pt
range_without_total: "%{from}-%{to} eventos"
de: &de
use_direct_file_attachment_links_in_the_documents: Verwenden Sie direkte Dateianhang-Links in den Dokumenten
click_here_to_send_a_reset_password_email_html: '<label class="link" for="resend_password_button">Klicken Sie hier</label>, um eine E-Mail zum Zurücksetzen des Passworts zu senden.'
enabled: Aktiviert
disabled: Deaktiviert
@@ -5051,6 +5172,8 @@ de: &de
four_days: 4 Tage
eight_days: 8 Tage
fifteen_days: 15 Tage
twenty_one_days: 21 Tage
thirty_days: 30 Tage
free: Kostenlos
unlimited_documents_storage: Unbegrenzter Dokumentenspeicher
users_management: Benutzerverwaltung
@@ -5231,7 +5354,7 @@ de: &de
at_least_one_field_must_be_displayed_in_the_form: Mindestens ein Feld muss im Formular angezeigt werden.
this_template_has_multiple_parties_which_prevents_the_use_of_a_sharing_link: Diese Vorlage enthält mehrere Parteien, was die Verwendung eines Freigabelinks verhindert, da unklar ist, welche Partei für bestimmte Felder verantwortlich ist. Um dies zu beheben, definieren Sie die Standardparteidetails.
events_log: Ereignisprotokoll
successed: Erfolgreich
succeeded: Erfolgreich
failed: Fehlgeschlagen
there_are_no_events: Keine Ereignisse vorhanden
resend: Erneut senden
@@ -5264,6 +5387,25 @@ de: &de
current_password: Aktuelles Passwort
dont_remember_your_current_password_click_here_to_reset_it_html: 'Sie erinnern sich nicht an Ihr aktuelles Passwort? <label class="link" for="resend_password_button">Klicken Sie hier</label>, um es zurückzusetzen.'
an_email_with_password_reset_instructions_has_been_sent: Eine E-Mail mit Anweisungen zum Zurücksetzen Ihres Passworts wurde Ihnen in wenigen Minuten zugesendet.
api_key_access_code: API-Schlüssel-Zugangscode
use_otp_code_to_access_the_api_key_html: Verwenden Sie den Code <b>%{code}</b>, um auf den API-Schlüssel zuzugreifen.
please_reply_to_this_email_if_you_dont_recognize_this_request: Bitte antworten Sie auf diese E-Mail, wenn Sie diese Anfrage nicht erkennen.
efficient_search_with_search_index: Effiziente Suche mit Suchindex
reindex: Neu indexieren
build_search_index: Suchindex erstellen
require_two_factor_authentication_2fa_with_an_authenticator_app_e_g_google_authenticator_authy_all_users_signing_documents_must_pass_the_second_factor_verification_using_a_secure_code_in_addition_to_their_password: Zwei-Faktor-Authentifizierung (2FA) mit einer Authentifizierungs-App (z. B. Google Authenticator, Authy) erforderlich. Alle Benutzer, die Dokumente unterzeichnen, müssen die zweite Faktorprüfung mit einem sicheren Code zusätzlich zu ihrem Passwort bestehen.
add_a_unique_signature_id_and_timestamp_to_each_signature_for_audit_and_traceability_purposes_along_with_the_timestamp_part_of_docuseals_21_cfr_part_11_compliance_settings: Eine eindeutige Signatur-ID und einen Zeitstempel zu jeder Signatur hinzufügen, um Prüfungs- und Nachverfolgbarkeitszwecke zusammen mit dem Zeitstempel zu erfüllen. Teil der 21 CFR Teil 11-Compliance-Einstellungen von DocuSeal.
require_signer_to_provide_a_reason_for_signing_before_completing_their_signature_e_g_approvals_certifications_part_of_docuseals_21_cfr_part_11_compliance_settings: Unterzeichner müssen einen Grund für die Unterzeichnung angeben, bevor sie ihre Signatur abschließen (z. B. Genehmigungen, Zertifizierungen). Teil der 21 CFR Teil 11-Compliance-Einstellungen von DocuSeal.
allow_signers_to_create_signatures_by_typing_their_name_instead_of_drawing_or_uploading_one: Unterzeichner dürfen Signaturen erstellen, indem sie ihren Namen eingeben, anstatt eine zu zeichnen oder hochzuladen.
allow_signers_to_resubmit_forms_after_completion_useful_when_corrections_or_multiple_submissions_are_needed: Unterzeichner dürfen Formulare nach Abschluss erneut einreichen, was nützlich ist, wenn Korrekturen oder mehrere Einreichungen erforderlich sind.
allow_recipients_to_decline_signing_a_document_the_decline_reason_notification_will_be_sent_to_the_signature_requester: Empfänger dürfen die Unterzeichnung eines Dokuments ablehnen. Die Benachrichtigung über den Ablehnungsgrund wird an den Anforderer der Signatur gesendet.
save_a_users_signature_and_automatically_pre_fill_it_in_future_signing_sessions: Die Signatur eines Benutzers speichern und sie in zukünftigen Signatursitzungen automatisch ausfüllen.
make_document_download_links_expire_after_40_minutes_to_prevent_long_term_access_and_enhance_security: Download-Links für Dokumente nach 40 Minuten ablaufen lassen, um langfristigen Zugriff zu verhindern und die Sicherheit zu erhöhen.
require_authentication_with_user_login_or_api_key_to_access_the_document_download_links: Authentifizierung mit Benutzeranmeldung oder API-Schlüssel erforderlich, um auf die Download-Links für Dokumente zuzugreifen.
combine_signed_documents_and_the_audit_log_into_a_single_pdf_file_for_easier_recordkeeping_and_compliance: Unterzeichnete Dokumente und das Prüfprotokoll in einer einzigen PDF-Datei kombinieren, um die Aufbewahrung und Compliance zu erleichtern.
require_a_jwt_authorization_to_preview_embedded_forms_ensuring_only_authorized_users_can_view_them: JWT-Autorisierung erforderlich, um eingebettete Formulare anzuzeigen, sodass nur autorisierte Benutzer sie sehen können.
make_all_newly_created_templates_private_to_their_creator_by_default: Alle neu erstellten Vorlagen standardmäßig für ihren Ersteller privat machen.
make_the_recipients_signing_order_always_enforced_so_that_the_second_signer_can_start_signing_their_part_only_after_the_first_signer_has_completed_signing: Die Unterzeichnungsreihenfolge der Empfänger immer erzwingen, sodass der zweite Unterzeichner erst nach Abschluss der Unterschrift durch den ersten beginnen kann.
submission_sources:
api: API
bulk: Massenversand
@@ -5457,6 +5599,8 @@ pl:
email_verification: Weryfikacja e-mail
your_verification_code_to_access_the_name: 'Twój kod weryfikacyjny do uzyskania dostępu do "%{name}":'
please_reply_to_this_email_if_you_didnt_request_this: Odpowiedz na ten e-mail, jeśli nie prosiłeś o to.
your_user_account_has_been_archived_contact_your_administrator_to_restore_access_to_your_account: Twoje konto użytkownika zostało zarchiwizowane. Skontaktuj się z administratorem, aby przywrócić dostęp do konta.
your_email_could_not_be_reached_this_may_happen_if_there_was_a_typo_in_your_address_or_if_your_mailbox_is_not_available_please_contact_support_email_to_log_in: Nie udało się uzyskać dostępu do Twojego adresu e-mail. Może to być spowodowane literówką w adresie lub niedostępnością skrzynki. Skontaktuj się z support@docuseal.com, aby się zalogować.
uk:
require_phone_2fa_to_open: Вимагати двофакторну автентифікацію через телефон для відкриття
@@ -5546,6 +5690,8 @@ uk:
email_verification: Підтвердження електронної пошти
your_verification_code_to_access_the_name: 'Ваш код доступу до "%{name}":'
please_reply_to_this_email_if_you_didnt_request_this: Відповідайте на цей лист, якщо ви цього не запитували.
your_user_account_has_been_archived_contact_your_administrator_to_restore_access_to_your_account: Ваш обліковий запис було архівовано. Зверніться до адміністратора, щоб відновити доступ.
your_email_could_not_be_reached_this_may_happen_if_there_was_a_typo_in_your_address_or_if_your_mailbox_is_not_available_please_contact_support_email_to_log_in: Не вдалося отримати доступ до вашої електронної пошти. Це може статися, якщо була допущена помилка в адресі або ваша скринька недоступна. Зверніться до support@docuseal.com, щоб увійти.
cs:
require_phone_2fa_to_open: Vyžadovat otevření pomocí telefonního 2FA
@@ -5635,6 +5781,8 @@ cs:
email_verification: Ověření e-mailu
your_verification_code_to_access_the_name: 'Váš ověřovací kód pro přístup k "%{name}":'
please_reply_to_this_email_if_you_didnt_request_this: Odpovězte na tento e-mail, pokud jste o to nežádali.
your_user_account_has_been_archived_contact_your_administrator_to_restore_access_to_your_account: Váš uživatelský účet byl archivován. Kontaktujte svého administrátora pro obnovení přístupu.
your_email_could_not_be_reached_this_may_happen_if_there_was_a_typo_in_your_address_or_if_your_mailbox_is_not_available_please_contact_support_email_to_log_in: Nepodařilo se dosáhnout na váš e-mail. To se může stát, pokud je v adrese překlep nebo vaše schránka není dostupná. Kontaktujte support@docuseal.com pro přihlášení.
he:
require_phone_2fa_to_open: דרוש אימות דו-שלבי באמצעות טלפון לפתיחה
@@ -5724,6 +5872,8 @@ he:
email_verification: 'אימות דוא"ל'
your_verification_code_to_access_the_name: 'קוד האימות שלך לגישה ל-%{name}:'
please_reply_to_this_email_if_you_didnt_request_this: 'השב למייל זה אם לא ביקשת זאת.'
your_user_account_has_been_archived_contact_your_administrator_to_restore_access_to_your_account: החשבון שלך הועבר לארכיון. פנה למנהל המערכת כדי לשחזר את הגישה.
your_email_could_not_be_reached_this_may_happen_if_there_was_a_typo_in_your_address_or_if_your_mailbox_is_not_available_please_contact_support_email_to_log_in: לא ניתן היה לגשת לדוא"ל שלך. ייתכן שזה קרה עקב שגיאת כתיב בכתובת או אם תיבת הדואר אינה זמינה. אנא פנה ל־support@docuseal.com כדי להתחבר.
nl:
require_phone_2fa_to_open: Vereis telefoon 2FA om te openen
@@ -5813,6 +5963,8 @@ nl:
email_verification: E-mailverificatie
your_verification_code_to_access_the_name: 'Je verificatiecode voor toegang tot "%{name}":'
please_reply_to_this_email_if_you_didnt_request_this: Reageer op deze e-mail als je dit niet hebt aangevraagd.
your_user_account_has_been_archived_contact_your_administrator_to_restore_access_to_your_account: Je gebruikersaccount is gearchiveerd. Neem contact op met je beheerder om de toegang te herstellen.
your_email_could_not_be_reached_this_may_happen_if_there_was_a_typo_in_your_address_or_if_your_mailbox_is_not_available_please_contact_support_email_to_log_in: Je e-mailadres kon niet worden bereikt. Dit kan gebeuren door een typefout in je adres of als je mailbox niet beschikbaar is. Neem contact op met support@docuseal.com om in te loggen.
ar:
require_phone_2fa_to_open: "تطلب فتح عبر تحقق الهاتف ذو العاملين"
@@ -5902,6 +6054,8 @@ ar:
email_verification: 'التحقق من البريد الإلكتروني'
your_verification_code_to_access_the_name: 'رمز التحقق الخاص بك للوصول إلى "%{name}":'
please_reply_to_this_email_if_you_didnt_request_this: 'يرجى الرد على هذا البريد إذا لم تطلب ذلك.'
your_user_account_has_been_archived_contact_your_administrator_to_restore_access_to_your_account: تم أرشفة حسابك. يرجى التواصل مع المسؤول لاستعادة الوصول إلى حسابك.
your_email_could_not_be_reached_this_may_happen_if_there_was_a_typo_in_your_address_or_if_your_mailbox_is_not_available_please_contact_support_email_to_log_in: تعذّر الوصول إلى بريدك الإلكتروني. قد يحدث هذا في حال وجود خطأ في العنوان أو إذا كانت صندوق البريد غير متاح. يرجى التواصل مع support@docuseal.com لتسجيل الدخول.
ko:
require_phone_2fa_to_open: 휴대폰 2FA를 열 때 요구함
@@ -5991,6 +6145,8 @@ ko:
email_verification: 이메일 인증
your_verification_code_to_access_the_name: '"%{name}"에 액세스하기 위한 인증 코드:'
please_reply_to_this_email_if_you_didnt_request_this: 요청하지 않았다면 이 이메일에 회신하세요.
your_user_account_has_been_archived_contact_your_administrator_to_restore_access_to_your_account: 사용자 계정이 보관되었습니다. 계정 접근을 복원하려면 관리자에게 문의하세요.
your_email_could_not_be_reached_this_may_happen_if_there_was_a_typo_in_your_address_or_if_your_mailbox_is_not_available_please_contact_support_email_to_log_in: 이메일에 접근할 수 없습니다. 주소에 오타가 있거나 메일박스가 사용 불가능한 경우 발생할 수 있습니다. 로그인하려면 support@docuseal.com에 문의하세요.
ja:
require_phone_2fa_to_open: 電話による2段階認証が必要です
@@ -6080,6 +6236,8 @@ ja:
email_verification: メール認証
your_verification_code_to_access_the_name: '"%{name}"へのアクセスコード:'
please_reply_to_this_email_if_you_didnt_request_this: このリクエストを行っていない場合は、このメールに返信してください。
your_user_account_has_been_archived_contact_your_administrator_to_restore_access_to_your_account: あなたのユーザーアカウントはアーカイブされました。アクセスを復元するには管理者に連絡してください。
your_email_could_not_be_reached_this_may_happen_if_there_was_a_typo_in_your_address_or_if_your_mailbox_is_not_available_please_contact_support_email_to_log_in: メールにアクセスできませんでした。アドレスの入力ミスやメールボックスが利用できない場合に発生することがあります。ログインするには support@docuseal.com に連絡してください。
en-US:
<<: *en
+4 -2
View File
@@ -170,6 +170,10 @@ Rails.application.routes.draw do
resources :search_entries_reindex, only: %i[create]
resources :sms, only: %i[index], controller: 'sms_settings'
end
if Docuseal.demo? || !Docuseal.multitenant?
resources :api, only: %i[index create], controller: 'api_settings'
resource :reveal_access_token, only: %i[show create], controller: 'reveal_access_token'
end
resources :email, only: %i[index create], controller: 'email_smtp_settings'
resources :sso, only: %i[index], controller: 'sso_settings'
resources :notifications, only: %i[index create], controller: 'notifications_settings'
@@ -180,8 +184,6 @@ Rails.application.routes.draw do
resources :integration_users, only: %i[index], path: 'users/:status', controller: 'users',
defaults: { status: :integration }
resource :personalization, only: %i[show create], controller: 'personalization_settings'
resources :api, only: %i[index create], controller: 'api_settings'
resource :reveal_access_token, only: %i[show create], controller: 'reveal_access_token'
resources :webhooks, only: %i[index show new create update destroy], controller: 'webhook_settings' do
post :resend
+1 -1
View File
@@ -1,7 +1,7 @@
disk:
service: Disk
root: <%= ENV['WORKDIR'] || '.' %>/attachments
public: true
public: <%= ENV['ACTIVE_STORAGE_PUBLIC'] == 'true' %>
aws_s3:
service: S3
@@ -0,0 +1,9 @@
# frozen_string_literal: true
class AddTemplateVariables < ActiveRecord::Migration[8.0]
def change
add_column :templates, :variables_schema, :text
add_column :submissions, :variables_schema, :text
add_column :submissions, :variables, :text
end
end
@@ -0,0 +1,14 @@
# frozen_string_literal: true
class CreateLockEvents < ActiveRecord::Migration[8.0]
def change
create_table :lock_events do |t|
t.string :key, index: true, null: false
t.string :event_name, null: false
t.index %i[event_name key], unique: true, where: "event_name IN ('start', 'complete')"
t.timestamps
end
end
end
@@ -0,0 +1,12 @@
# frozen_string_literal: true
class UpdateEmailEventTypeIndex < ActiveRecord::Migration[8.0]
def change
remove_index :email_events, :email, where: "event_type IN ('bounce', 'soft_bounce', 'complaint', 'soft_complaint')",
name: 'index_email_events_on_email_event_types'
add_index :email_events, :email,
where: "event_type IN ('bounce', 'soft_bounce', 'permanent_bounce', 'complaint', 'soft_complaint')",
name: 'index_email_events_on_email_event_types'
end
end
+14 -2
View File
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[8.0].define(version: 2025_09_01_110606) do
ActiveRecord::Schema[8.0].define(version: 2025_09_22_053744) do
# These are extensions that must be enabled in order to support this database
enable_extension "btree_gin"
enable_extension "plpgsql"
@@ -178,7 +178,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_09_01_110606) do
t.datetime "created_at", null: false
t.index ["account_id", "event_datetime"], name: "index_email_events_on_account_id_and_event_datetime"
t.index ["email"], name: "index_email_events_on_email"
t.index ["email"], name: "index_email_events_on_email_event_types", where: "((event_type)::text = ANY ((ARRAY['bounce'::character varying, 'soft_bounce'::character varying, 'complaint'::character varying, 'soft_complaint'::character varying])::text[]))"
t.index ["email"], name: "index_email_events_on_email_event_types", where: "((event_type)::text = ANY ((ARRAY['bounce'::character varying, 'soft_bounce'::character varying, 'permanent_bounce'::character varying, 'complaint'::character varying, 'soft_complaint'::character varying])::text[]))"
t.index ["emailable_type", "emailable_id"], name: "index_email_events_on_emailable"
t.index ["message_id"], name: "index_email_events_on_message_id"
end
@@ -217,6 +217,15 @@ ActiveRecord::Schema[8.0].define(version: 2025_09_01_110606) do
t.index ["user_id"], name: "index_encrypted_user_configs_on_user_id"
end
create_table "lock_events", force: :cascade do |t|
t.string "key", null: false
t.string "event_name", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["event_name", "key"], name: "index_lock_events_on_event_name_and_key", unique: true, where: "((event_name)::text = ANY ((ARRAY['start'::character varying, 'complete'::character varying])::text[]))"
t.index ["key"], name: "index_lock_events_on_key"
end
create_table "oauth_access_grants", force: :cascade do |t|
t.bigint "resource_owner_id", null: false
t.bigint "application_id", null: false
@@ -305,6 +314,8 @@ ActiveRecord::Schema[8.0].define(version: 2025_09_01_110606) do
t.bigint "account_id", null: false
t.datetime "expire_at"
t.text "name"
t.text "variables_schema"
t.text "variables"
t.index ["account_id", "id"], name: "index_submissions_on_account_id_and_id"
t.index ["account_id", "template_id", "id"], name: "index_submissions_on_account_id_and_template_id_and_id", where: "(archived_at IS NULL)"
t.index ["account_id", "template_id", "id"], name: "index_submissions_on_account_id_and_template_id_and_id_archived", where: "(archived_at IS NOT NULL)"
@@ -389,6 +400,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_09_01_110606) do
t.string "external_id"
t.text "preferences", null: false
t.boolean "shared_link", default: false, null: false
t.text "variables_schema"
t.index ["account_id", "folder_id", "id"], name: "index_templates_on_account_id_and_folder_id_and_id", where: "(archived_at IS NULL)"
t.index ["account_id", "id"], name: "index_templates_on_account_id_and_id_archived", where: "(archived_at IS NOT NULL)"
t.index ["account_id"], name: "index_templates_on_account_id"
+703 -37
View File
@@ -523,6 +523,15 @@ var response = client.Execute(request);
"type": "string",
"description": "Submitter specific URL to redirect to after the submission completion."
},
"order": {
"type": "integer",
"description": "The order of the submitter in the workflow (e.g., 0 for the first signer, 1 for the second, etc.). Use the same order number to create order groups. By default, submitters are ordered as in the submitters array."
},
"require_phone_2fa": {
"type": "boolean",
"description": "Set to `true` to require phone 2FA verification via a one-time code sent to the phone number in order to access the documents.",
"default": false
},
"message": {
"type": "object",
"properties": {
@@ -598,14 +607,45 @@ var response = client.Execute(request);
"type": "string",
"description": "Field description displayed on the signing form. Supports Markdown."
},
"validation_pattern": {
"type": "string",
"description": "HTML field validation pattern string based on https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/pattern specification.",
"example": "[A-Z]{4}"
},
"invalid_message": {
"type": "string",
"description": "A custom message to display on pattern validation failure."
"validation": {
"type": "object",
"properties": {
"pattern": {
"type": "string",
"description": "HTML field validation pattern string based on https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/pattern specification.",
"example": "[A-Z]{4}"
},
"message": {
"type": "string",
"description": "A custom error message to display on validation failure."
},
"min": {
"oneOf": [
{
"type": "number"
},
{
"type": "string"
}
],
"description": "Minimum allowed number value or date depending on field type."
},
"max": {
"oneOf": [
{
"type": "number"
},
{
"type": "string"
}
],
"description": "Maximum allowed number value or date depending on field type."
},
"step": {
"type": "number",
"description": "Increment step for number field. Pass 1 to accept only integers, or 0.01 to accept decimal currency."
}
}
},
"preferences": {
"type": "object",
@@ -1025,10 +1065,6 @@ var response = client.Execute(request);
"type": "string",
"description": "Specify Reply-To address to use in the notification emails."
},
"completed_redirect_url": {
"type": "string",
"description": "Submitter specific URL to redirect to after the submission completion."
},
"completed": {
"type": "boolean",
"description": "Pass `true` to mark submitter as completed and auto-signed via API."
@@ -1038,6 +1074,15 @@ var response = client.Execute(request);
"description": "Metadata object with additional submitter information.",
"example": "{ \"customField\": \"value\" }"
},
"completed_redirect_url": {
"type": "string",
"description": "Submitter specific URL to redirect to after the submission completion."
},
"require_phone_2fa": {
"type": "boolean",
"description": "Set to `true` to require phone 2FA verification via a one-time code sent to the phone number in order to access the documents.",
"default": false
},
"message": {
"type": "object",
"properties": {
@@ -1105,14 +1150,45 @@ var response = client.Execute(request);
"type": "boolean",
"description": "Set `true` to make the field required."
},
"validation_pattern": {
"type": "string",
"description": "HTML field validation pattern string based on https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/pattern specification.",
"example": "[A-Z]{4}"
},
"invalid_message": {
"type": "string",
"description": "A custom message to display on pattern validation failure."
"validation": {
"type": "object",
"properties": {
"pattern": {
"type": "string",
"description": "HTML field validation pattern string based on https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/pattern specification.",
"example": "[A-Z]{4}"
},
"message": {
"type": "string",
"description": "A custom error message to display on validation failure."
},
"min": {
"oneOf": [
{
"type": "number"
},
{
"type": "string"
}
],
"description": "Minimum allowed number value or date depending on field type."
},
"max": {
"oneOf": [
{
"type": "number"
},
{
"type": "string"
}
],
"description": "Maximum allowed number value or date depending on field type."
},
"step": {
"type": "number",
"description": "Increment step for number field. Pass 1 to accept only integers, or 0.01 to accept decimal currency."
}
}
},
"preferences": {
"type": "object",
@@ -1775,6 +1851,46 @@ var response = client.Execute(request);
"Option B"
]
},
"validation": {
"type": "object",
"properties": {
"pattern": {
"type": "string",
"description": "HTML field validation pattern string based on https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/pattern specification.",
"example": "[A-Z]{4}"
},
"message": {
"type": "string",
"description": "A custom error message to display on validation failure."
},
"min": {
"oneOf": [
{
"type": "number"
},
{
"type": "string"
}
],
"description": "Minimum allowed number value or date depending on field type."
},
"max": {
"oneOf": [
{
"type": "number"
},
{
"type": "string"
}
],
"description": "Maximum allowed number value or date depending on field type."
},
"step": {
"type": "number",
"description": "Increment step for number field. Pass 1 to accept only integers, or 0.01 to accept decimal currency."
}
}
},
"preferences": {
"type": "object",
"properties": {
@@ -2253,7 +2369,7 @@ var response = client.Execute(request);
### Create a submission from PDF
The API endpoint provides the functionality to create one-off submission request from a PDF or DOCX file. Use <code>{{Field Name;role=Signer1;type=date}}</code> text tags to define fillable fields in the document. See <a href="https://www.docuseal.com/examples/fieldtags.pdf" target="_blank" class="link font-bold">https://www.docuseal.com/examples/fieldtags.pdf</a> for more text tag formats. Or specify the exact pixel coordinates of the document fields using `fields` param.<br><b>Related Guides</b><br><a href="https://www.docuseal.com/guides/use-embedded-text-field-tags-in-the-pdf-to-create-a-fillable-form" class="link">Use embedded text field tags to create a fillable form</a>
The API endpoint provides the functionality to create one-off submission request from a PDF. Use <code>{{Field Name;role=Signer1;type=date}}</code> text tags to define fillable fields in the document. See <a href="https://www.docuseal.com/examples/fieldtags.pdf" target="_blank" class="link font-bold">https://www.docuseal.com/examples/fieldtags.pdf</a> for more text tag formats. Or specify the exact pixel coordinates of the document fields using `fields` param.<br><b>Related Guides</b><br><a href="https://www.docuseal.com/guides/use-embedded-text-field-tags-in-the-pdf-to-create-a-fillable-form" class="link">Use embedded text field tags to create a fillable form</a>
```csharp
@@ -2531,6 +2647,15 @@ var response = client.Execute(request);
"type": "string",
"description": "Submitter specific URL to redirect to after the submission completion."
},
"order": {
"type": "integer",
"description": "The order of the submitter in the workflow (e.g., 0 for the first signer, 1 for the second, etc.). Use the same order number to create order groups. By default, submitters are ordered as in the submitters array."
},
"require_phone_2fa": {
"type": "boolean",
"description": "Set to `true` to require phone 2FA verification via a one-time code sent to the phone number in order to access the documents.",
"default": false
},
"fields": {
"type": "array",
"description": "A list of configurations for document form fields.",
@@ -2593,14 +2718,45 @@ var response = client.Execute(request);
"type": "string",
"description": "Field description displayed on the signing form. Supports Markdown."
},
"validation_pattern": {
"type": "string",
"description": "HTML field validation pattern string based on https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/pattern specification.",
"example": "[A-Z]{4}"
},
"invalid_message": {
"type": "string",
"description": "A custom message to display on pattern validation failure."
"validation": {
"type": "object",
"properties": {
"pattern": {
"type": "string",
"description": "HTML field validation pattern string based on https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/pattern specification.",
"example": "[A-Z]{4}"
},
"message": {
"type": "string",
"description": "A custom error message to display on validation failure."
},
"min": {
"oneOf": [
{
"type": "number"
},
{
"type": "string"
}
],
"description": "Minimum allowed number value or date depending on field type."
},
"max": {
"oneOf": [
{
"type": "number"
},
{
"type": "string"
}
],
"description": "Maximum allowed number value or date depending on field type."
},
"step": {
"type": "number",
"description": "Increment step for number field. Pass 1 to accept only integers, or 0.01 to accept decimal currency."
}
}
},
"preferences": {
"type": "object",
@@ -2946,6 +3102,15 @@ var response = client.Execute(request);
"type": "string",
"description": "Submitter specific URL to redirect to after the submission completion."
},
"order": {
"type": "integer",
"description": "The order of the submitter in the workflow (e.g., 0 for the first signer, 1 for the second, etc.). Use the same order number to create order groups. By default, submitters are ordered as in the submitters array."
},
"require_phone_2fa": {
"type": "boolean",
"description": "Set to `true` to require phone 2FA verification via a one-time code sent to the phone number in order to access the documents.",
"default": false
},
"fields": {
"type": "array",
"description": "A list of configurations for document form fields.",
@@ -3008,14 +3173,45 @@ var response = client.Execute(request);
"type": "string",
"description": "Field description displayed on the signing form. Supports Markdown."
},
"validation_pattern": {
"type": "string",
"description": "HTML field validation pattern string based on https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/pattern specification.",
"example": "[A-Z]{4}"
},
"invalid_message": {
"type": "string",
"description": "A custom message to display on pattern validation failure."
"validation": {
"type": "object",
"properties": {
"pattern": {
"type": "string",
"description": "HTML field validation pattern string based on https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/pattern specification.",
"example": "[A-Z]{4}"
},
"message": {
"type": "string",
"description": "A custom error message to display on validation failure."
},
"min": {
"oneOf": [
{
"type": "number"
},
{
"type": "string"
}
],
"description": "Minimum allowed number value or date depending on field type."
},
"max": {
"oneOf": [
{
"type": "number"
},
{
"type": "string"
}
],
"description": "Maximum allowed number value or date depending on field type."
},
"step": {
"type": "number",
"description": "Increment step for number field. Pass 1 to accept only integers, or 0.01 to accept decimal currency."
}
}
},
"preferences": {
"type": "object",
@@ -3323,6 +3519,46 @@ var response = client.Execute(request);
"Option B"
]
},
"validation": {
"type": "object",
"properties": {
"pattern": {
"type": "string",
"description": "HTML field validation pattern string based on https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/pattern specification.",
"example": "[A-Z]{4}"
},
"message": {
"type": "string",
"description": "A custom error message to display on validation failure."
},
"min": {
"oneOf": [
{
"type": "number"
},
{
"type": "string"
}
],
"description": "Minimum allowed number value or date depending on field type."
},
"max": {
"oneOf": [
{
"type": "number"
},
{
"type": "string"
}
],
"description": "Maximum allowed number value or date depending on field type."
},
"step": {
"type": "number",
"description": "Increment step for number field. Pass 1 to accept only integers, or 0.01 to accept decimal currency."
}
}
},
"preferences": {
"type": "object",
"properties": {
@@ -3439,3 +3675,433 @@ var response = client.Execute(request);
}
```
### Create a submission from DOCX
The API endpoint provides functionality to create a one-off submission request from a DOCX file with dynamic content variables. Use <code>[[variable_name]]</code> text tags to define dynamic content variables in the document. See <a href="https://www.docuseal.com/examples/demo_template.docx" target="_blank" class="link font-bold">https://www.docuseal.com/examples/demo_template.docx</a> for the specific text variable syntax, including dynamic content tables and list. You can also use the <code>{{signature}}</code> fillable field syntax to define fillable fields, as in a PDF.<br><b>Related Guides</b><br><a href="https://www.docuseal.com/guides/use-embedded-text-field-tags-in-the-pdf-to-create-a-fillable-form" class="link">Use embedded text field tags to create a fillable form</a>
```csharp
var client = new RestClient("https://api.docuseal.com/submissions/docx");
var request = new RestRequest("", Method.Post);
request.AddHeader("X-Auth-Token", "API_KEY");
request.AddHeader("content-type", "application/json");
request.AddParameter("application/json", "{\"name\":\"Test Submission Document\",\"variables\":{\"variable_name\":\"value\"},\"documents\":[{\"name\":\"string\",\"file\":\"base64\"}],\"submitters\":[{\"role\":\"First Party\",\"email\":\"john.doe@example.com\"}]}", ParameterType.RequestBody);
var response = client.Execute(request);
```
```json
{
"security": [
{
"AuthToken": []
}
],
"tags": [
"Submissions"
],
"summary": "Create a submission from DOCX",
"operationId": "createSubmissionFromDocx",
"parameters": [],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"documents",
"submitters"
],
"properties": {
"name": {
"type": "string",
"description": "Name of the document submission.",
"example": "Test Submission Document"
},
"send_email": {
"type": "boolean",
"description": "Set `false` to disable signature request emails sending.",
"default": true
},
"send_sms": {
"type": "boolean",
"description": "Set `true` to send signature request via phone number and SMS.",
"default": false
},
"variables": {
"type": "object",
"description": "Dynamic content variables object",
"example": {
"variable_name": "value"
}
},
"order": {
"type": "string",
"description": "Pass 'random' to send signature request emails to all parties right away. The order is 'preserved' by default so the second party will receive a signature request email only after the document is signed by the first party.",
"default": "preserved",
"enum": [
"preserved",
"random"
]
},
"completed_redirect_url": {
"type": "string",
"description": "Specify URL to redirect to after the submission completion."
},
"bcc_completed": {
"type": "string",
"description": "Specify BCC address to send signed documents to after the completion."
},
"reply_to": {
"type": "string",
"description": "Specify Reply-To address to use in the notification emails."
},
"expire_at": {
"type": "string",
"description": "Specify the expiration date and time after which the submission becomes unavailable for signature.",
"example": "2024-09-01 12:00:00 UTC"
},
"template_ids": {
"type": "array",
"description": "An optional array of template IDs to use in the submission along with the provided documents. This can be used to create multi-document submissions when some of the required documents exist within templates.",
"items": {
"type": "integer",
"description": "The ID of the template to use for the submission."
}
},
"documents": {
"type": "array",
"items": {
"type": "object",
"required": [
"name",
"file"
],
"properties": {
"name": {
"type": "string",
"description": "Name of the document."
},
"file": {
"example": "base64",
"type": "string",
"format": "base64",
"description": "Base64-encoded content of the PDF or DOCX file or downloadable file URL."
},
"position": {
"type": "integer",
"description": "Document position in the submission. If not specified, the document will be added in the order it appears in the documents array."
}
}
}
},
"submitters": {
"type": "array",
"description": "The list of submitters for the submission.",
"items": {
"type": "object",
"required": [
"email"
],
"properties": {
"name": {
"type": "string",
"description": "The name of the submitter."
},
"role": {
"type": "string",
"description": "The role name or title of the submitter.",
"example": "First Party"
},
"email": {
"type": "string",
"description": "The email address of the submitter.",
"format": "email",
"example": "john.doe@example.com"
},
"phone": {
"type": "string",
"description": "The phone number of the submitter, formatted according to the E.164 standard.",
"example": "+1234567890"
},
"values": {
"type": "object",
"description": "An object with pre-filled values for the submission. Use field names for keys of the object. For more configurations see `fields` param."
},
"external_id": {
"type": "string",
"description": "Your application-specific unique string key to identify this submitter within your app."
},
"completed": {
"type": "boolean",
"description": "Pass `true` to mark submitter as completed and auto-signed via API."
},
"metadata": {
"type": "object",
"description": "Metadata object with additional submitter information.",
"example": "{ \"customField\": \"value\" }"
},
"send_email": {
"type": "boolean",
"description": "Set `false` to disable signature request emails sending only for this submitter.",
"default": true
},
"send_sms": {
"type": "boolean",
"description": "Set `true` to send signature request via phone number and SMS.",
"default": false
},
"reply_to": {
"type": "string",
"description": "Specify Reply-To address to use in the notification emails for this submitter."
},
"completed_redirect_url": {
"type": "string",
"description": "Submitter specific URL to redirect to after the submission completion."
},
"order": {
"type": "integer",
"description": "The order of the submitter in the workflow (e.g., 0 for the first signer, 1 for the second, etc.). Use the same order number to create order groups. By default, submitters are ordered as in the submitters array."
},
"require_phone_2fa": {
"type": "boolean",
"description": "Set to `true` to require phone 2FA verification via a one-time code sent to the phone number in order to access the documents.",
"default": false
},
"fields": {
"type": "array",
"description": "A list of configurations for document form fields.",
"items": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"description": "Document field name.",
"example": "First Name"
},
"default_value": {
"oneOf": [
{
"type": "string"
},
{
"type": "number"
},
{
"type": "boolean"
},
{
"type": "array",
"items": {
"oneOf": [
{
"type": "string"
},
{
"type": "number"
},
{
"type": "boolean"
}
]
}
}
],
"description": "Default value of the field. Use base64 encoded file or a public URL to the image file to set default signature or image fields.",
"example": "Acme"
},
"readonly": {
"type": "boolean",
"description": "Set `true` to make it impossible for the submitter to edit predefined field value.",
"default": false
},
"required": {
"type": "boolean",
"description": "Set `true` to make the field required."
},
"title": {
"type": "string",
"description": "Field title displayed to the user instead of the name, shown on the signing form. Supports Markdown."
},
"description": {
"type": "string",
"description": "Field description displayed on the signing form. Supports Markdown."
},
"validation": {
"type": "object",
"properties": {
"pattern": {
"type": "string",
"description": "HTML field validation pattern string based on https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/pattern specification.",
"example": "[A-Z]{4}"
},
"message": {
"type": "string",
"description": "A custom error message to display on validation failure."
},
"min": {
"oneOf": [
{
"type": "number"
},
{
"type": "string"
}
],
"description": "Minimum allowed number value or date depending on field type."
},
"max": {
"oneOf": [
{
"type": "number"
},
{
"type": "string"
}
],
"description": "Maximum allowed number value or date depending on field type."
},
"step": {
"type": "number",
"description": "Increment step for number field. Pass 1 to accept only integers, or 0.01 to accept decimal currency."
}
}
},
"preferences": {
"type": "object",
"properties": {
"font_size": {
"type": "integer",
"description": "Font size of the field value in pixels.",
"example": 12
},
"font_type": {
"type": "string",
"description": "Font type of the field value.",
"enum": [
"bold",
"italic",
"bold_italic"
]
},
"font": {
"type": "string",
"description": "Font family of the field value.",
"enum": [
"Times",
"Helvetica",
"Courier"
]
},
"color": {
"type": "string",
"description": "Font color of the field value.",
"enum": [
"black",
"white",
"blue"
],
"default": "black"
},
"align": {
"type": "string",
"description": "Horizontal alignment of the field text value.",
"enum": [
"left",
"center",
"right"
],
"default": "left"
},
"valign": {
"type": "string",
"description": "Vertical alignment of the field text value.",
"enum": [
"top",
"center",
"bottom"
],
"default": "center"
},
"format": {
"type": "string",
"description": "The data format for different field types.<br>- Date field: accepts formats such as DD/MM/YYYY (default: MM/DD/YYYY).<br>- Signature field: accepts drawn, typed, drawn_or_typed (default), or upload.<br>- Number field: accepts currency formats such as usd, eur, gbp.",
"example": "DD/MM/YYYY"
},
"price": {
"type": "number",
"description": "Price value of the payment field. Only for payment fields.",
"example": 99.99
},
"currency": {
"type": "string",
"description": "Currency value of the payment field. Only for payment fields.",
"enum": [
"USD",
"EUR",
"GBP",
"CAD",
"AUD"
],
"default": "USD"
},
"mask": {
"description": "Set `true` to make sensitive data masked on the document.",
"oneOf": [
{
"type": "integer"
},
{
"type": "boolean"
}
],
"default": false
}
}
}
}
}
},
"roles": {
"type": "array",
"description": "A list of roles for the submitter. Use this param to merge multiple roles into one submitter.",
"items": {
"type": "string"
}
}
}
}
},
"message": {
"type": "object",
"properties": {
"subject": {
"type": "string",
"description": "Custom signature request email subject."
},
"body": {
"type": "string",
"description": "Custom signature request email body. Can include the following variables: {{submission.name}}, {{submitter.link}}, {{account.name}}."
}
}
},
"merge_documents": {
"type": "boolean",
"description": "Set `true` to merge the documents into a single PDF file.",
"default": false
},
"remove_tags": {
"type": "boolean",
"description": "Pass `false` to disable the removal of {{text}} tags from the PDF. This can be used along with transparent text tags for faster and more robust PDF processing.",
"default": true
}
}
}
}
}
}
}
```
+726 -37
View File
@@ -653,6 +653,15 @@ func main() {
"type": "string",
"description": "Submitter specific URL to redirect to after the submission completion."
},
"order": {
"type": "integer",
"description": "The order of the submitter in the workflow (e.g., 0 for the first signer, 1 for the second, etc.). Use the same order number to create order groups. By default, submitters are ordered as in the submitters array."
},
"require_phone_2fa": {
"type": "boolean",
"description": "Set to `true` to require phone 2FA verification via a one-time code sent to the phone number in order to access the documents.",
"default": false
},
"message": {
"type": "object",
"properties": {
@@ -728,14 +737,45 @@ func main() {
"type": "string",
"description": "Field description displayed on the signing form. Supports Markdown."
},
"validation_pattern": {
"type": "string",
"description": "HTML field validation pattern string based on https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/pattern specification.",
"example": "[A-Z]{4}"
},
"invalid_message": {
"type": "string",
"description": "A custom message to display on pattern validation failure."
"validation": {
"type": "object",
"properties": {
"pattern": {
"type": "string",
"description": "HTML field validation pattern string based on https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/pattern specification.",
"example": "[A-Z]{4}"
},
"message": {
"type": "string",
"description": "A custom error message to display on validation failure."
},
"min": {
"oneOf": [
{
"type": "number"
},
{
"type": "string"
}
],
"description": "Minimum allowed number value or date depending on field type."
},
"max": {
"oneOf": [
{
"type": "number"
},
{
"type": "string"
}
],
"description": "Maximum allowed number value or date depending on field type."
},
"step": {
"type": "number",
"description": "Increment step for number field. Pass 1 to accept only integers, or 0.01 to accept decimal currency."
}
}
},
"preferences": {
"type": "object",
@@ -1285,10 +1325,6 @@ func main() {
"type": "string",
"description": "Specify Reply-To address to use in the notification emails."
},
"completed_redirect_url": {
"type": "string",
"description": "Submitter specific URL to redirect to after the submission completion."
},
"completed": {
"type": "boolean",
"description": "Pass `true` to mark submitter as completed and auto-signed via API."
@@ -1298,6 +1334,15 @@ func main() {
"description": "Metadata object with additional submitter information.",
"example": "{ \"customField\": \"value\" }"
},
"completed_redirect_url": {
"type": "string",
"description": "Submitter specific URL to redirect to after the submission completion."
},
"require_phone_2fa": {
"type": "boolean",
"description": "Set to `true` to require phone 2FA verification via a one-time code sent to the phone number in order to access the documents.",
"default": false
},
"message": {
"type": "object",
"properties": {
@@ -1365,14 +1410,45 @@ func main() {
"type": "boolean",
"description": "Set `true` to make the field required."
},
"validation_pattern": {
"type": "string",
"description": "HTML field validation pattern string based on https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/pattern specification.",
"example": "[A-Z]{4}"
},
"invalid_message": {
"type": "string",
"description": "A custom message to display on pattern validation failure."
"validation": {
"type": "object",
"properties": {
"pattern": {
"type": "string",
"description": "HTML field validation pattern string based on https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/pattern specification.",
"example": "[A-Z]{4}"
},
"message": {
"type": "string",
"description": "A custom error message to display on validation failure."
},
"min": {
"oneOf": [
{
"type": "number"
},
{
"type": "string"
}
],
"description": "Minimum allowed number value or date depending on field type."
},
"max": {
"oneOf": [
{
"type": "number"
},
{
"type": "string"
}
],
"description": "Maximum allowed number value or date depending on field type."
},
"step": {
"type": "number",
"description": "Increment step for number field. Pass 1 to accept only integers, or 0.01 to accept decimal currency."
}
}
},
"preferences": {
"type": "object",
@@ -2148,6 +2224,46 @@ func main() {
"Option B"
]
},
"validation": {
"type": "object",
"properties": {
"pattern": {
"type": "string",
"description": "HTML field validation pattern string based on https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/pattern specification.",
"example": "[A-Z]{4}"
},
"message": {
"type": "string",
"description": "A custom error message to display on validation failure."
},
"min": {
"oneOf": [
{
"type": "number"
},
{
"type": "string"
}
],
"description": "Minimum allowed number value or date depending on field type."
},
"max": {
"oneOf": [
{
"type": "number"
},
{
"type": "string"
}
],
"description": "Maximum allowed number value or date depending on field type."
},
"step": {
"type": "number",
"description": "Increment step for number field. Pass 1 to accept only integers, or 0.01 to accept decimal currency."
}
}
},
"preferences": {
"type": "object",
"properties": {
@@ -2672,7 +2788,7 @@ func main() {
### Create a submission from PDF
The API endpoint provides the functionality to create one-off submission request from a PDF or DOCX file. Use <code>{{Field Name;role=Signer1;type=date}}</code> text tags to define fillable fields in the document. See <a href="https://www.docuseal.com/examples/fieldtags.pdf" target="_blank" class="link font-bold">https://www.docuseal.com/examples/fieldtags.pdf</a> for more text tag formats. Or specify the exact pixel coordinates of the document fields using `fields` param.<br><b>Related Guides</b><br><a href="https://www.docuseal.com/guides/use-embedded-text-field-tags-in-the-pdf-to-create-a-fillable-form" class="link">Use embedded text field tags to create a fillable form</a>
The API endpoint provides the functionality to create one-off submission request from a PDF. Use <code>{{Field Name;role=Signer1;type=date}}</code> text tags to define fillable fields in the document. See <a href="https://www.docuseal.com/examples/fieldtags.pdf" target="_blank" class="link font-bold">https://www.docuseal.com/examples/fieldtags.pdf</a> for more text tag formats. Or specify the exact pixel coordinates of the document fields using `fields` param.<br><b>Related Guides</b><br><a href="https://www.docuseal.com/guides/use-embedded-text-field-tags-in-the-pdf-to-create-a-fillable-form" class="link">Use embedded text field tags to create a fillable form</a>
```go
@@ -2973,6 +3089,15 @@ func main() {
"type": "string",
"description": "Submitter specific URL to redirect to after the submission completion."
},
"order": {
"type": "integer",
"description": "The order of the submitter in the workflow (e.g., 0 for the first signer, 1 for the second, etc.). Use the same order number to create order groups. By default, submitters are ordered as in the submitters array."
},
"require_phone_2fa": {
"type": "boolean",
"description": "Set to `true` to require phone 2FA verification via a one-time code sent to the phone number in order to access the documents.",
"default": false
},
"fields": {
"type": "array",
"description": "A list of configurations for document form fields.",
@@ -3035,14 +3160,45 @@ func main() {
"type": "string",
"description": "Field description displayed on the signing form. Supports Markdown."
},
"validation_pattern": {
"type": "string",
"description": "HTML field validation pattern string based on https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/pattern specification.",
"example": "[A-Z]{4}"
},
"invalid_message": {
"type": "string",
"description": "A custom message to display on pattern validation failure."
"validation": {
"type": "object",
"properties": {
"pattern": {
"type": "string",
"description": "HTML field validation pattern string based on https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/pattern specification.",
"example": "[A-Z]{4}"
},
"message": {
"type": "string",
"description": "A custom error message to display on validation failure."
},
"min": {
"oneOf": [
{
"type": "number"
},
{
"type": "string"
}
],
"description": "Minimum allowed number value or date depending on field type."
},
"max": {
"oneOf": [
{
"type": "number"
},
{
"type": "string"
}
],
"description": "Maximum allowed number value or date depending on field type."
},
"step": {
"type": "number",
"description": "Increment step for number field. Pass 1 to accept only integers, or 0.01 to accept decimal currency."
}
}
},
"preferences": {
"type": "object",
@@ -3411,6 +3567,15 @@ func main() {
"type": "string",
"description": "Submitter specific URL to redirect to after the submission completion."
},
"order": {
"type": "integer",
"description": "The order of the submitter in the workflow (e.g., 0 for the first signer, 1 for the second, etc.). Use the same order number to create order groups. By default, submitters are ordered as in the submitters array."
},
"require_phone_2fa": {
"type": "boolean",
"description": "Set to `true` to require phone 2FA verification via a one-time code sent to the phone number in order to access the documents.",
"default": false
},
"fields": {
"type": "array",
"description": "A list of configurations for document form fields.",
@@ -3473,14 +3638,45 @@ func main() {
"type": "string",
"description": "Field description displayed on the signing form. Supports Markdown."
},
"validation_pattern": {
"type": "string",
"description": "HTML field validation pattern string based on https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/pattern specification.",
"example": "[A-Z]{4}"
},
"invalid_message": {
"type": "string",
"description": "A custom message to display on pattern validation failure."
"validation": {
"type": "object",
"properties": {
"pattern": {
"type": "string",
"description": "HTML field validation pattern string based on https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/pattern specification.",
"example": "[A-Z]{4}"
},
"message": {
"type": "string",
"description": "A custom error message to display on validation failure."
},
"min": {
"oneOf": [
{
"type": "number"
},
{
"type": "string"
}
],
"description": "Minimum allowed number value or date depending on field type."
},
"max": {
"oneOf": [
{
"type": "number"
},
{
"type": "string"
}
],
"description": "Maximum allowed number value or date depending on field type."
},
"step": {
"type": "number",
"description": "Increment step for number field. Pass 1 to accept only integers, or 0.01 to accept decimal currency."
}
}
},
"preferences": {
"type": "object",
@@ -3811,6 +4007,46 @@ func main() {
"Option B"
]
},
"validation": {
"type": "object",
"properties": {
"pattern": {
"type": "string",
"description": "HTML field validation pattern string based on https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/pattern specification.",
"example": "[A-Z]{4}"
},
"message": {
"type": "string",
"description": "A custom error message to display on validation failure."
},
"min": {
"oneOf": [
{
"type": "number"
},
{
"type": "string"
}
],
"description": "Minimum allowed number value or date depending on field type."
},
"max": {
"oneOf": [
{
"type": "number"
},
{
"type": "string"
}
],
"description": "Maximum allowed number value or date depending on field type."
},
"step": {
"type": "number",
"description": "Increment step for number field. Pass 1 to accept only integers, or 0.01 to accept decimal currency."
}
}
},
"preferences": {
"type": "object",
"properties": {
@@ -3927,3 +4163,456 @@ func main() {
}
```
### Create a submission from DOCX
The API endpoint provides functionality to create a one-off submission request from a DOCX file with dynamic content variables. Use <code>[[variable_name]]</code> text tags to define dynamic content variables in the document. See <a href="https://www.docuseal.com/examples/demo_template.docx" target="_blank" class="link font-bold">https://www.docuseal.com/examples/demo_template.docx</a> for the specific text variable syntax, including dynamic content tables and list. You can also use the <code>{{signature}}</code> fillable field syntax to define fillable fields, as in a PDF.<br><b>Related Guides</b><br><a href="https://www.docuseal.com/guides/use-embedded-text-field-tags-in-the-pdf-to-create-a-fillable-form" class="link">Use embedded text field tags to create a fillable form</a>
```go
package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.docuseal.com/submissions/docx"
payload := strings.NewReader("{\"name\":\"Test Submission Document\",\"variables\":{\"variable_name\":\"value\"},\"documents\":[{\"name\":\"string\",\"file\":\"base64\"}],\"submitters\":[{\"role\":\"First Party\",\"email\":\"john.doe@example.com\"}]}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-Auth-Token", "API_KEY")
req.Header.Add("content-type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(res)
fmt.Println(string(body))
}
```
```json
{
"security": [
{
"AuthToken": []
}
],
"tags": [
"Submissions"
],
"summary": "Create a submission from DOCX",
"operationId": "createSubmissionFromDocx",
"parameters": [],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"documents",
"submitters"
],
"properties": {
"name": {
"type": "string",
"description": "Name of the document submission.",
"example": "Test Submission Document"
},
"send_email": {
"type": "boolean",
"description": "Set `false` to disable signature request emails sending.",
"default": true
},
"send_sms": {
"type": "boolean",
"description": "Set `true` to send signature request via phone number and SMS.",
"default": false
},
"variables": {
"type": "object",
"description": "Dynamic content variables object",
"example": {
"variable_name": "value"
}
},
"order": {
"type": "string",
"description": "Pass 'random' to send signature request emails to all parties right away. The order is 'preserved' by default so the second party will receive a signature request email only after the document is signed by the first party.",
"default": "preserved",
"enum": [
"preserved",
"random"
]
},
"completed_redirect_url": {
"type": "string",
"description": "Specify URL to redirect to after the submission completion."
},
"bcc_completed": {
"type": "string",
"description": "Specify BCC address to send signed documents to after the completion."
},
"reply_to": {
"type": "string",
"description": "Specify Reply-To address to use in the notification emails."
},
"expire_at": {
"type": "string",
"description": "Specify the expiration date and time after which the submission becomes unavailable for signature.",
"example": "2024-09-01 12:00:00 UTC"
},
"template_ids": {
"type": "array",
"description": "An optional array of template IDs to use in the submission along with the provided documents. This can be used to create multi-document submissions when some of the required documents exist within templates.",
"items": {
"type": "integer",
"description": "The ID of the template to use for the submission."
}
},
"documents": {
"type": "array",
"items": {
"type": "object",
"required": [
"name",
"file"
],
"properties": {
"name": {
"type": "string",
"description": "Name of the document."
},
"file": {
"example": "base64",
"type": "string",
"format": "base64",
"description": "Base64-encoded content of the PDF or DOCX file or downloadable file URL."
},
"position": {
"type": "integer",
"description": "Document position in the submission. If not specified, the document will be added in the order it appears in the documents array."
}
}
}
},
"submitters": {
"type": "array",
"description": "The list of submitters for the submission.",
"items": {
"type": "object",
"required": [
"email"
],
"properties": {
"name": {
"type": "string",
"description": "The name of the submitter."
},
"role": {
"type": "string",
"description": "The role name or title of the submitter.",
"example": "First Party"
},
"email": {
"type": "string",
"description": "The email address of the submitter.",
"format": "email",
"example": "john.doe@example.com"
},
"phone": {
"type": "string",
"description": "The phone number of the submitter, formatted according to the E.164 standard.",
"example": "+1234567890"
},
"values": {
"type": "object",
"description": "An object with pre-filled values for the submission. Use field names for keys of the object. For more configurations see `fields` param."
},
"external_id": {
"type": "string",
"description": "Your application-specific unique string key to identify this submitter within your app."
},
"completed": {
"type": "boolean",
"description": "Pass `true` to mark submitter as completed and auto-signed via API."
},
"metadata": {
"type": "object",
"description": "Metadata object with additional submitter information.",
"example": "{ \"customField\": \"value\" }"
},
"send_email": {
"type": "boolean",
"description": "Set `false` to disable signature request emails sending only for this submitter.",
"default": true
},
"send_sms": {
"type": "boolean",
"description": "Set `true` to send signature request via phone number and SMS.",
"default": false
},
"reply_to": {
"type": "string",
"description": "Specify Reply-To address to use in the notification emails for this submitter."
},
"completed_redirect_url": {
"type": "string",
"description": "Submitter specific URL to redirect to after the submission completion."
},
"order": {
"type": "integer",
"description": "The order of the submitter in the workflow (e.g., 0 for the first signer, 1 for the second, etc.). Use the same order number to create order groups. By default, submitters are ordered as in the submitters array."
},
"require_phone_2fa": {
"type": "boolean",
"description": "Set to `true` to require phone 2FA verification via a one-time code sent to the phone number in order to access the documents.",
"default": false
},
"fields": {
"type": "array",
"description": "A list of configurations for document form fields.",
"items": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"description": "Document field name.",
"example": "First Name"
},
"default_value": {
"oneOf": [
{
"type": "string"
},
{
"type": "number"
},
{
"type": "boolean"
},
{
"type": "array",
"items": {
"oneOf": [
{
"type": "string"
},
{
"type": "number"
},
{
"type": "boolean"
}
]
}
}
],
"description": "Default value of the field. Use base64 encoded file or a public URL to the image file to set default signature or image fields.",
"example": "Acme"
},
"readonly": {
"type": "boolean",
"description": "Set `true` to make it impossible for the submitter to edit predefined field value.",
"default": false
},
"required": {
"type": "boolean",
"description": "Set `true` to make the field required."
},
"title": {
"type": "string",
"description": "Field title displayed to the user instead of the name, shown on the signing form. Supports Markdown."
},
"description": {
"type": "string",
"description": "Field description displayed on the signing form. Supports Markdown."
},
"validation": {
"type": "object",
"properties": {
"pattern": {
"type": "string",
"description": "HTML field validation pattern string based on https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/pattern specification.",
"example": "[A-Z]{4}"
},
"message": {
"type": "string",
"description": "A custom error message to display on validation failure."
},
"min": {
"oneOf": [
{
"type": "number"
},
{
"type": "string"
}
],
"description": "Minimum allowed number value or date depending on field type."
},
"max": {
"oneOf": [
{
"type": "number"
},
{
"type": "string"
}
],
"description": "Maximum allowed number value or date depending on field type."
},
"step": {
"type": "number",
"description": "Increment step for number field. Pass 1 to accept only integers, or 0.01 to accept decimal currency."
}
}
},
"preferences": {
"type": "object",
"properties": {
"font_size": {
"type": "integer",
"description": "Font size of the field value in pixels.",
"example": 12
},
"font_type": {
"type": "string",
"description": "Font type of the field value.",
"enum": [
"bold",
"italic",
"bold_italic"
]
},
"font": {
"type": "string",
"description": "Font family of the field value.",
"enum": [
"Times",
"Helvetica",
"Courier"
]
},
"color": {
"type": "string",
"description": "Font color of the field value.",
"enum": [
"black",
"white",
"blue"
],
"default": "black"
},
"align": {
"type": "string",
"description": "Horizontal alignment of the field text value.",
"enum": [
"left",
"center",
"right"
],
"default": "left"
},
"valign": {
"type": "string",
"description": "Vertical alignment of the field text value.",
"enum": [
"top",
"center",
"bottom"
],
"default": "center"
},
"format": {
"type": "string",
"description": "The data format for different field types.<br>- Date field: accepts formats such as DD/MM/YYYY (default: MM/DD/YYYY).<br>- Signature field: accepts drawn, typed, drawn_or_typed (default), or upload.<br>- Number field: accepts currency formats such as usd, eur, gbp.",
"example": "DD/MM/YYYY"
},
"price": {
"type": "number",
"description": "Price value of the payment field. Only for payment fields.",
"example": 99.99
},
"currency": {
"type": "string",
"description": "Currency value of the payment field. Only for payment fields.",
"enum": [
"USD",
"EUR",
"GBP",
"CAD",
"AUD"
],
"default": "USD"
},
"mask": {
"description": "Set `true` to make sensitive data masked on the document.",
"oneOf": [
{
"type": "integer"
},
{
"type": "boolean"
}
],
"default": false
}
}
}
}
}
},
"roles": {
"type": "array",
"description": "A list of roles for the submitter. Use this param to merge multiple roles into one submitter.",
"items": {
"type": "string"
}
}
}
}
},
"message": {
"type": "object",
"properties": {
"subject": {
"type": "string",
"description": "Custom signature request email subject."
},
"body": {
"type": "string",
"description": "Custom signature request email body. Can include the following variables: {{submission.name}}, {{submitter.link}}, {{account.name}}."
}
}
},
"merge_documents": {
"type": "boolean",
"description": "Set `true` to merge the documents into a single PDF file.",
"default": false
},
"remove_tags": {
"type": "boolean",
"description": "Pass `false` to disable the removal of {{text}} tags from the PDF. This can be used along with transparent text tags for faster and more robust PDF processing.",
"default": true
}
}
}
}
}
}
}
```
Loaded 100 of 137 files, more files were not shown because too many files have changed in this diff. Show more