mirror of
https://github.com/docusealco/docuseal
synced 2026-11-04 14:42:32 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
395d43dbeb | ||
|
|
37de5e0d0d | ||
|
|
44e848a1f0 | ||
|
|
f6d59d731c | ||
|
|
87b039e709 | ||
|
|
e0c6a4e08b | ||
|
|
80b06550c2 | ||
|
|
ec8e35ed7a | ||
|
|
4dc325a6bb | ||
|
|
d52c57df72 | ||
|
|
0cb2557447 | ||
|
|
cd05c1fd19 | ||
|
|
5458e82b6e | ||
|
|
c59c58624b | ||
|
|
2da059d440 | ||
|
|
b0aabd9e79 | ||
|
|
f1cd60dfad | ||
|
|
a264a8522a | ||
|
|
794e339425 | ||
|
|
9aaf61e658 | ||
|
|
37faa7ece6 | ||
|
|
666e3fde4c | ||
|
|
995e040a4e | ||
|
|
185ac2cbee | ||
|
|
a4e372ff5c | ||
|
|
b4834c7674 | ||
|
|
c24ae1751e | ||
|
|
9bd0ad70c4 | ||
|
|
f210f1d8bf | ||
|
|
92b61992f2 | ||
|
|
e3f3895aac | ||
|
|
c696890cf5 | ||
|
|
288d204d8e | ||
|
|
5a96f4f1eb | ||
|
|
5ff4f9889e | ||
|
|
78fb93fd02 | ||
|
|
3c83d459bd | ||
|
|
6cdb8e7132 | ||
|
|
e2b93cbcab | ||
|
|
a59cc7c44e | ||
|
|
1984b99e04 | ||
|
|
fe3e8fba3e | ||
|
|
97ecdf2ff0 | ||
|
|
e4c62087d0 | ||
|
|
1f9faddfd0 | ||
|
|
6dff6483bf | ||
|
|
2e43eb189f | ||
|
|
cee471bfa5 | ||
|
|
81521dee81 | ||
|
|
3cac62d0fd | ||
|
|
24ed3f2e7c | ||
|
|
02ad620c9f | ||
|
|
9051d5a835 | ||
|
|
0abd270f56 | ||
|
|
fde9affd70 | ||
|
|
4edee2971a | ||
|
|
313d7f8408 | ||
|
|
db60810272 | ||
|
|
b3507c1a3a | ||
|
|
19b5a07bac | ||
|
|
616aced4e4 | ||
|
|
c59f2a022a |
No files matched your search
@@ -50,7 +50,7 @@ class AccountsController < ApplicationController
|
||||
# rubocop:disable Layout/LineLength
|
||||
render turbo_stream: turbo_stream.replace(
|
||||
:account_delete_button,
|
||||
html: helpers.tag.p(I18n.t('your_account_removal_request_will_be_processed_within_2_weeks_please_contact_us_if_you_want_to_keep_your_account'))
|
||||
html: helpers.tag.p(I18n.t('your_account_removal_request_will_be_processed_within_2_months_please_contact_us_if_you_want_to_keep_your_account'))
|
||||
)
|
||||
# rubocop:enable Layout/LineLength
|
||||
end
|
||||
|
||||
@@ -42,6 +42,7 @@ module Api
|
||||
|
||||
is_authorized = attachment.name.in?(%w[logo preview_images]) ||
|
||||
(current_user && attachment.record.account.id == current_user.account_id) ||
|
||||
(current_user && !Docuseal.multitenant? && current_user.role == 'superadmin') ||
|
||||
!attachment.record.account.account_configs
|
||||
.find_or_initialize_by(key: AccountConfig::DOWNLOAD_LINKS_AUTH_KEY).value
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@ module Api
|
||||
submissions = submissions.joins(template: :folder).where(folder: { name: params[:template_folder] })
|
||||
end
|
||||
|
||||
submissions = Submissions::Filter.call(submissions, current_user, params)
|
||||
|
||||
submissions = paginate(submissions.preload(:created_by_user, :submitters,
|
||||
template: :folder,
|
||||
combined_document_attachment: :blob,
|
||||
@@ -116,7 +118,19 @@ module Api
|
||||
end
|
||||
end
|
||||
|
||||
json = { submitters: json } if request.path.ends_with?('/init')
|
||||
if request.path.ends_with?('/init')
|
||||
json =
|
||||
if submissions.size == 1
|
||||
{
|
||||
id: submissions.first.id,
|
||||
submitters: json,
|
||||
expire_at: submissions.first.expire_at,
|
||||
created_at: submissions.first.created_at
|
||||
}
|
||||
else
|
||||
{ submitters: json }
|
||||
end
|
||||
end
|
||||
|
||||
json
|
||||
end
|
||||
|
||||
@@ -64,6 +64,7 @@ class StartFormController < ApplicationController
|
||||
.or(template.submissions.where(expire_at: nil)).where(archived_at: nil))
|
||||
.order(id: :desc)
|
||||
.where(declined_at: nil)
|
||||
.where(external_id: nil)
|
||||
.then { |rel| params[:resubmit].present? ? rel.where(completed_at: nil) : rel }
|
||||
.find_or_initialize_by(**submitter_params.compact_blank)
|
||||
end
|
||||
|
||||
@@ -20,7 +20,10 @@ class SubmissionsPreviewController < ApplicationController
|
||||
|
||||
@submission ||= Submission.find_by!(slug: params[:slug])
|
||||
|
||||
if @submission.account.archived_at? || (!@submission.submitters.all?(&:completed_at?) && current_user.blank?)
|
||||
raise ActionController::RoutingError if @submission.account.archived_at?
|
||||
|
||||
if !@submission.submitters.all?(&:completed_at?) && !signature_valid &&
|
||||
(!current_user || !current_ability.can?(:read, @submission))
|
||||
raise ActionController::RoutingError, I18n.t('not_found')
|
||||
end
|
||||
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class SubmittersResubmitController < ApplicationController
|
||||
load_and_authorize_resource :submitter, parent: false
|
||||
|
||||
def update
|
||||
return redirect_to submit_form_path(slug: @submitter.slug) if @submitter.email != current_user.email
|
||||
|
||||
submission = @submitter.template.submissions.new(created_by_user: current_user,
|
||||
submitters_order: :preserved,
|
||||
**@submitter.submission.slice(:template_fields,
|
||||
:account_id,
|
||||
:template_schema,
|
||||
:template_submitters,
|
||||
:preferences))
|
||||
|
||||
@submitter.submission.submitters.each do |submitter|
|
||||
new_submitter = submission.submitters.new(submitter.slice(:uuid, :email, :phone, :name,
|
||||
:preferences, :metadata, :account_id))
|
||||
|
||||
next unless submitter.uuid == @submitter.uuid
|
||||
|
||||
assign_submitter_values(new_submitter, submitter)
|
||||
|
||||
@new_submitter ||= new_submitter
|
||||
end
|
||||
|
||||
submission.save!
|
||||
|
||||
redirect_to submit_form_path(slug: @new_submitter.slug)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def assign_submitter_values(new_submitter, submitter)
|
||||
attachments_index = submitter.attachments.index_by(&:uuid)
|
||||
|
||||
submitter.submission.template_fields.each do |field|
|
||||
next if field['submitter_uuid'] != submitter.uuid
|
||||
next if field['default_value'] == '{{date}}'
|
||||
next if field['type'] == 'stamp'
|
||||
next if field['type'] == 'signature'
|
||||
next if field.dig('preferences', 'formula').present?
|
||||
|
||||
value = submitter.values[field['uuid']]
|
||||
|
||||
next if value.blank?
|
||||
|
||||
if field['type'].in?(%w[image file initials])
|
||||
Array.wrap(value).each do |attachment_uuid|
|
||||
new_submitter.attachments << attachments_index[attachment_uuid].dup
|
||||
end
|
||||
end
|
||||
|
||||
new_submitter.values[field['uuid']] = value
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -22,7 +22,7 @@ class TemplatesPreferencesController < ApplicationController
|
||||
preferences: %i[bcc_completed request_email_subject request_email_body
|
||||
documents_copy_email_subject documents_copy_email_body
|
||||
documents_copy_email_enabled documents_copy_email_attach_audit
|
||||
documents_copy_email_attach_documents
|
||||
documents_copy_email_attach_documents documents_copy_email_reply_to
|
||||
completed_notification_email_attach_documents
|
||||
completed_redirect_url
|
||||
submitters_order
|
||||
|
||||
@@ -20,7 +20,8 @@ export default targetable(class extends HTMLElement {
|
||||
fetch(this.dataset.src).then(async (response) => {
|
||||
if (response.ok) {
|
||||
const urls = await response.json()
|
||||
const isSafariIos = /iPhone|iPad|iPod/i.test(navigator.userAgent)
|
||||
const isMobileSafariIos = 'ontouchstart' in window && navigator.maxTouchPoints > 0 && /AppleWebKit/i.test(navigator.userAgent)
|
||||
const isSafariIos = isMobileSafariIos || /iPhone|iPad|iPod/i.test(navigator.userAgent)
|
||||
|
||||
if (isSafariIos && urls.length > 1) {
|
||||
this.downloadSafariIos(urls)
|
||||
|
||||
@@ -197,7 +197,10 @@
|
||||
<span v-else-if="field.type === 'date'">
|
||||
{{ formattedDate }}
|
||||
</span>
|
||||
<span v-else-if="field.type === 'number'">
|
||||
<span
|
||||
v-else-if="field.type === 'number'"
|
||||
class="w-full"
|
||||
>
|
||||
{{ formatNumber(modelValue, field.preferences?.format) }}
|
||||
</span>
|
||||
<span
|
||||
@@ -447,8 +450,18 @@ export default {
|
||||
}
|
||||
},
|
||||
formatNumber (number, format) {
|
||||
if (!number && number !== 0) {
|
||||
return ''
|
||||
}
|
||||
|
||||
if (format === 'comma') {
|
||||
return new Intl.NumberFormat('en-US').format(number)
|
||||
} else if (format === 'usd') {
|
||||
return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(number)
|
||||
} else if (format === 'gbp') {
|
||||
return new Intl.NumberFormat('en-GB', { style: 'currency', currency: 'GBP', minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(number)
|
||||
} else if (format === 'eur') {
|
||||
return new Intl.NumberFormat('fr-FR', { style: 'currency', currency: 'EUR', minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(number)
|
||||
} else if (format === 'dot') {
|
||||
return new Intl.NumberFormat('de-DE').format(number)
|
||||
} else if (format === 'space') {
|
||||
|
||||
@@ -157,13 +157,15 @@ export default {
|
||||
const formContainer = root.getElementById('form_container')
|
||||
const container = root.body || root.querySelector('div')
|
||||
|
||||
const padding = 64
|
||||
const isAndroid = /android/i.test(navigator.userAgent)
|
||||
const padding = isAndroid ? 128 : 64
|
||||
const scrollboxTop = isAndroid ? scrollbox.getBoundingClientRect().top : 0
|
||||
const boxRect = scrollbox.children[0].getBoundingClientRect()
|
||||
const targetRect = target.getBoundingClientRect()
|
||||
|
||||
const targetTopRelativeToBox = targetRect.top - boxRect.top
|
||||
|
||||
scrollbox.scrollTo({ top: targetTopRelativeToBox - container.offsetHeight + formContainer.offsetHeight + target.offsetHeight + padding, behavior: 'smooth' })
|
||||
scrollbox.scrollTo({ top: targetTopRelativeToBox + scrollboxTop - container.offsetHeight + formContainer.offsetHeight + target.offsetHeight + padding, behavior: 'smooth' })
|
||||
},
|
||||
setAreaRef (el) {
|
||||
if (el) {
|
||||
|
||||
@@ -215,7 +215,8 @@ export default {
|
||||
fetch(this.baseUrl + `/submitters/${this.submitterSlug}/download`).then(async (response) => {
|
||||
if (response.ok) {
|
||||
const urls = await response.json()
|
||||
const isSafariIos = /iPhone|iPad|iPod/i.test(navigator.userAgent)
|
||||
const isMobileSafariIos = 'ontouchstart' in window && navigator.maxTouchPoints > 0 && /AppleWebKit/i.test(navigator.userAgent)
|
||||
const isSafariIos = isMobileSafariIos || /iPhone|iPad|iPod/i.test(navigator.userAgent)
|
||||
|
||||
if (isSafariIos && urls.length > 1) {
|
||||
this.downloadSafariIos(urls)
|
||||
|
||||
@@ -128,7 +128,7 @@ export default {
|
||||
}
|
||||
})
|
||||
} else {
|
||||
if (file.type === 'image/bmp') {
|
||||
if (file.type === 'image/bmp' || file.type === 'image/vnd.microsoft.icon') {
|
||||
file = await this.convertBmpToPng(file)
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
/>
|
||||
<FieldAreas
|
||||
:steps="readonlyConditionalFields.map((e) => [e])"
|
||||
:values="readonlyConditionalFields.reduce((acc, f) => { acc[f.uuid] = (values[f.uuid] || f.default_value); return acc }, {})"
|
||||
:values="readonlyConditionalFieldValues"
|
||||
:submitter="submitter"
|
||||
:attachments-index="attachmentsIndex"
|
||||
:submittable="false"
|
||||
@@ -23,6 +23,7 @@
|
||||
<FormulaFieldAreas
|
||||
v-if="formulaFields.length"
|
||||
:fields="formulaFields"
|
||||
:readonly-values="readonlyConditionalFieldValues"
|
||||
:values="values"
|
||||
/>
|
||||
<Teleport
|
||||
@@ -855,7 +856,16 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
isMobile () {
|
||||
return /android|iphone|ipad/i.test(navigator.userAgent)
|
||||
const isMobileSafariIos = 'ontouchstart' in window && navigator.maxTouchPoints > 0 && /AppleWebKit/i.test(navigator.userAgent)
|
||||
|
||||
return isMobileSafariIos || /android|iphone|ipad/i.test(navigator.userAgent)
|
||||
},
|
||||
readonlyConditionalFieldValues () {
|
||||
return this.readonlyConditionalFields.reduce((acc, f) => {
|
||||
acc[f.uuid] = (this.values[f.uuid] || f.default_value)
|
||||
|
||||
return acc
|
||||
}, {})
|
||||
},
|
||||
attachmentConditionsIndex () {
|
||||
return this.schema.reduce((acc, item) => {
|
||||
@@ -1103,7 +1113,9 @@ export default {
|
||||
this.minimizeForm()
|
||||
}
|
||||
|
||||
if (/iPhone|iPad|iPod/i.test(navigator.userAgent)) {
|
||||
const isMobile = 'ontouchstart' in window && navigator.maxTouchPoints > 0 && /AppleWebKit|android/i.test(navigator.userAgent)
|
||||
|
||||
if (isMobile || /iPhone|iPad|iPod/i.test(navigator.userAgent)) {
|
||||
this.$nextTick(() => {
|
||||
const root = this.$root.$el.parentNode.getRootNode()
|
||||
const scrollbox = root.getElementById('scrollbox')
|
||||
|
||||
@@ -38,6 +38,11 @@ export default {
|
||||
required: false,
|
||||
default: () => []
|
||||
},
|
||||
readonlyValues: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => ({})
|
||||
},
|
||||
values: {
|
||||
type: Object,
|
||||
required: false,
|
||||
@@ -87,7 +92,7 @@ export default {
|
||||
},
|
||||
calculateFormula (field) {
|
||||
const transformedFormula = field.preferences.formula.replace(/{{(.*?)}}/g, (match, uuid) => {
|
||||
return this.values[uuid] || 0.0
|
||||
return this.readonlyValues[uuid] || this.values[uuid] || 0.0
|
||||
})
|
||||
|
||||
return this.math.evaluate(transformedFormula.toLowerCase())
|
||||
|
||||
@@ -297,7 +297,7 @@ export default {
|
||||
const data = await resp.json()
|
||||
|
||||
if (resp.status === 422) {
|
||||
alert(this.t('number_phone_is_invalid').replace('{number}', this.fullInternationalPhoneValue))
|
||||
alert(data.error || this.t('number_phone_is_invalid').replace('{number}', this.fullInternationalPhoneValue))
|
||||
} else if (resp.status === 429) {
|
||||
alert(data.error)
|
||||
}
|
||||
|
||||
@@ -125,12 +125,16 @@ export default {
|
||||
async mounted () {
|
||||
this.isLoading = true
|
||||
|
||||
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: {
|
||||
start () {
|
||||
|
||||
@@ -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]"
|
||||
:class="field.type === 'heading' ? '' : borderColors[submitterIndex % borderColors.length]"
|
||||
/>
|
||||
<div
|
||||
v-if="field.type === 'cells' && (isSelected || isDraw)"
|
||||
@@ -20,7 +20,7 @@
|
||||
v-for="(cellW, index) in cells"
|
||||
:key="index"
|
||||
class="absolute top-0 bottom-0 border-r"
|
||||
:class="field.type === 'heading' ? '' : borderColors[submitterIndex]"
|
||||
:class="field.type === 'heading' ? '' : borderColors[submitterIndex % borderColors.length]"
|
||||
:style="{ left: (cellW / area.w * 100) + '%' }"
|
||||
>
|
||||
<span
|
||||
@@ -70,7 +70,7 @@
|
||||
@keydown.enter.prevent="onNameEnter"
|
||||
@focus="onNameFocus"
|
||||
@blur="onNameBlur"
|
||||
>{{ optionIndexText }} {{ (defaultField ? (field.title || field.name) : field.name) || defaultName }}</span>
|
||||
>{{ optionIndexText }} {{ (defaultField ? (defaultField.title || field.title || field.name) : field.name) || defaultName }}</span>
|
||||
<div
|
||||
v-if="isSettingsFocus || (isValueInput && field.type !== 'heading') || (isNameFocus && !['checkbox', 'phone'].includes(field.type))"
|
||||
class="flex items-center ml-1.5"
|
||||
@@ -161,7 +161,7 @@
|
||||
ref="touchValueTarget"
|
||||
class="flex items-center h-full w-full"
|
||||
dir="auto"
|
||||
:class="[isValueInput ? 'bg-opacity-50' : 'bg-opacity-80', field.type === 'heading' ? 'bg-gray-50' : bgColors[submitterIndex], isDefaultValuePresent || isValueInput || (withFieldPlaceholder && field.areas) ? (alignClasses[field.preferences?.align] || '') : 'justify-center']"
|
||||
:class="[isValueInput ? 'bg-opacity-50' : 'bg-opacity-80', field.type === 'heading' ? 'bg-gray-50' : bgColors[submitterIndex % bgColors.length], isDefaultValuePresent || isValueInput || (withFieldPlaceholder && field.areas) ? (alignClasses[field.preferences?.align] || '') : 'justify-center']"
|
||||
@click="focusValueInput"
|
||||
>
|
||||
<span
|
||||
@@ -212,7 +212,7 @@
|
||||
:contenteditable="isValueInput"
|
||||
class="whitespace-pre-wrap outline-none empty:before:content-[attr(placeholder)] before:text-gray-400"
|
||||
:class="{ 'cursor-text': isValueInput }"
|
||||
:placeholder="withFieldPlaceholder && !isValueInput ? field.name || defaultName : t('type_value')"
|
||||
:placeholder="withFieldPlaceholder && !isValueInput ? defaultField?.title || field.title || field.name || defaultName : t('type_value')"
|
||||
@blur="onDefaultValueBlur"
|
||||
@paste.prevent="onPaste"
|
||||
@keydown.enter="onDefaultValueEnter"
|
||||
@@ -361,6 +361,7 @@ export default {
|
||||
isMoved: false,
|
||||
renderDropdown: false,
|
||||
isNameFocus: false,
|
||||
isHeadingSelected: false,
|
||||
textOverflowChars: 0,
|
||||
dragFrom: { x: 0, y: 0 }
|
||||
}
|
||||
@@ -377,7 +378,7 @@ export default {
|
||||
}
|
||||
},
|
||||
isValueInput () {
|
||||
return (this.field.type === 'heading' && this.isSelected) || this.isContenteditable || (this.inputMode && ['text', 'number', 'date'].includes(this.field.type))
|
||||
return (this.field.type === 'heading' && this.isHeadingSelected) || this.isContenteditable || (this.inputMode && ['text', 'number', 'date'].includes(this.field.type))
|
||||
},
|
||||
modalContainerEl () {
|
||||
return this.$el.getRootNode().querySelector('#docuseal_modal_container')
|
||||
@@ -420,16 +421,6 @@ export default {
|
||||
},
|
||||
borderColors () {
|
||||
return [
|
||||
'border-red-500/80',
|
||||
'border-sky-500/80',
|
||||
'border-emerald-500/80',
|
||||
'border-yellow-300/80',
|
||||
'border-purple-600/80',
|
||||
'border-pink-500/80',
|
||||
'border-cyan-500/80',
|
||||
'border-orange-500/80',
|
||||
'border-lime-500/80',
|
||||
'border-indigo-500/80',
|
||||
'border-red-500/80',
|
||||
'border-sky-500/80',
|
||||
'border-emerald-500/80',
|
||||
@@ -444,16 +435,6 @@ export default {
|
||||
},
|
||||
bgColors () {
|
||||
return [
|
||||
'bg-red-100',
|
||||
'bg-sky-100',
|
||||
'bg-emerald-100',
|
||||
'bg-yellow-100',
|
||||
'bg-purple-100',
|
||||
'bg-pink-100',
|
||||
'bg-cyan-100',
|
||||
'bg-orange-100',
|
||||
'bg-lime-100',
|
||||
'bg-indigo-100',
|
||||
'bg-red-100',
|
||||
'bg-sky-100',
|
||||
'bg-emerald-100',
|
||||
@@ -499,13 +480,13 @@ export default {
|
||||
methods: {
|
||||
buildDefaultName: Field.methods.buildDefaultName,
|
||||
closeDropdown () {
|
||||
document.activeElement.blur()
|
||||
this.$el.getRootNode().activeElement.blur()
|
||||
},
|
||||
maybeToggleDefaultValue () {
|
||||
if (['text', 'number'].includes(this.field.type)) {
|
||||
this.isContenteditable = true
|
||||
|
||||
this.$nextTick(() => this.focusValueInput())
|
||||
this.focusValueInput()
|
||||
} else if (this.field.type === 'checkbox') {
|
||||
this.field.readonly = !this.field.readonly
|
||||
this.field.default_value === true ? delete this.field.default_value : this.field.default_value = true
|
||||
@@ -527,20 +508,28 @@ export default {
|
||||
}
|
||||
},
|
||||
focusValueInput (e) {
|
||||
if (this.$refs.defaultValue !== document.activeElement) {
|
||||
this.$refs.defaultValue.focus()
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.defaultValue && this.$refs.defaultValue !== document.activeElement) {
|
||||
this.$refs.defaultValue.focus()
|
||||
|
||||
if (this.$refs.defaultValue.innerText.length && this.$refs.defaultValue !== e?.target) {
|
||||
window.getSelection().collapse(
|
||||
this.$refs.defaultValue.firstChild,
|
||||
this.$refs.defaultValue.innerText.length
|
||||
)
|
||||
if (this.$refs.defaultValue.innerText.length && this.$refs.defaultValue !== e?.target) {
|
||||
window.getSelection().collapse(
|
||||
this.$refs.defaultValue.firstChild,
|
||||
this.$refs.defaultValue.innerText.length
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
formatNumber (number, format) {
|
||||
if (format === 'comma') {
|
||||
return new Intl.NumberFormat('en-US').format(number)
|
||||
} else if (format === 'usd') {
|
||||
return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(number)
|
||||
} else if (format === 'gbp') {
|
||||
return new Intl.NumberFormat('en-GB', { style: 'currency', currency: 'GBP', minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(number)
|
||||
} else if (format === 'eur') {
|
||||
return new Intl.NumberFormat('fr-FR', { style: 'currency', currency: 'EUR', minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(number)
|
||||
} else if (format === 'dot') {
|
||||
return new Intl.NumberFormat('de-DE').format(number)
|
||||
} else if (format === 'space') {
|
||||
@@ -646,6 +635,7 @@ export default {
|
||||
const text = this.$refs.defaultValue.innerText.trim()
|
||||
|
||||
this.isContenteditable = false
|
||||
this.isHeadingSelected = false
|
||||
|
||||
if (text) {
|
||||
if (this.field.type === 'number') {
|
||||
@@ -763,10 +753,6 @@ export default {
|
||||
|
||||
this.selectedAreaRef.value = this.area
|
||||
|
||||
if (this.field.type === 'heading') {
|
||||
this.$nextTick(() => this.focusValueInput())
|
||||
}
|
||||
|
||||
this.dragFrom = { x: rect.left - e.clientX, y: rect.top - e.clientY }
|
||||
|
||||
this.$el.getRootNode().addEventListener('mousemove', this.mouseMove)
|
||||
@@ -801,6 +787,12 @@ export default {
|
||||
this.save()
|
||||
}
|
||||
|
||||
if (this.field.type === 'heading') {
|
||||
this.isHeadingSelected = !this.isMoved
|
||||
|
||||
this.focusValueInput()
|
||||
}
|
||||
|
||||
this.isDragged = false
|
||||
this.isMoved = false
|
||||
|
||||
|
||||
@@ -179,6 +179,7 @@
|
||||
:with-arrows="template.schema.length > 1"
|
||||
:item="item"
|
||||
:document="sortedDocuments[index]"
|
||||
:data-document-uuid="item.attachment_uuid"
|
||||
:accept-file-types="acceptFileTypes"
|
||||
:with-replace-button="withUploadButton"
|
||||
:editable="editable"
|
||||
@@ -187,6 +188,7 @@
|
||||
@remove="onDocumentRemove"
|
||||
@replace="onDocumentReplace"
|
||||
@up="moveDocument(item, -1)"
|
||||
@reorder="reorderFields"
|
||||
@down="moveDocument(item, 1)"
|
||||
@change="save"
|
||||
/>
|
||||
@@ -220,8 +222,8 @@
|
||||
</div>
|
||||
<div
|
||||
id="pages_container"
|
||||
class="w-full overflow-y-hidden overflow-x-hidden mt-0.5 pt-0.5"
|
||||
:class="isMobile ? 'overflow-y-auto' : 'md:overflow-y-auto'"
|
||||
class="w-full overflow-x-hidden mt-0.5 pt-0.5"
|
||||
:class="isMobile ? 'overflow-y-auto' : 'overflow-y-hidden md:overflow-y-auto'"
|
||||
>
|
||||
<div
|
||||
ref="documents"
|
||||
@@ -265,6 +267,7 @@
|
||||
:input-mode="inputMode"
|
||||
:default-fields="[...defaultRequiredFields, ...defaultFields]"
|
||||
:allow-draw="!onlyDefinedFields"
|
||||
:data-document-uuid="document.uuid"
|
||||
:default-submitters="defaultSubmitters"
|
||||
:with-field-placeholder="withFieldPlaceholder"
|
||||
:draw-field="drawField"
|
||||
@@ -700,7 +703,9 @@ export default {
|
||||
return this.locale.split('-')[0].toLowerCase()
|
||||
},
|
||||
isMobile () {
|
||||
return /android|iphone|ipad/i.test(navigator.userAgent)
|
||||
const isMobileSafariIos = 'ontouchstart' in window && navigator.maxTouchPoints > 0 && /AppleWebKit/i.test(navigator.userAgent)
|
||||
|
||||
return isMobileSafariIos || /android|iphone|ipad/i.test(navigator.userAgent)
|
||||
},
|
||||
defaultDateFormat () {
|
||||
const isUsBrowser = Intl.DateTimeFormat().resolvedOptions().locale.endsWith('-US')
|
||||
@@ -769,13 +774,17 @@ export default {
|
||||
}
|
||||
})
|
||||
|
||||
const defineSubmittersUuids = this.defineSubmitters.map((name) => {
|
||||
return this.template.submitters.find(e => e.name === name)?.uuid
|
||||
})
|
||||
|
||||
this.defineSubmitters.forEach((name, index) => {
|
||||
const submitter = (this.template.submitters[index] ||= {})
|
||||
|
||||
submitter.name = name || this.submitterDefaultNames[index]
|
||||
|
||||
if (existingSubmittersUuids.filter(Boolean).length) {
|
||||
submitter.uuid = existingSubmittersUuids[index] || submitter.uuid || v4()
|
||||
if (defineSubmittersUuids.filter(Boolean).length || existingSubmittersUuids.filter(Boolean).length) {
|
||||
submitter.uuid = defineSubmittersUuids[index] || existingSubmittersUuids[index] || submitter.uuid || v4()
|
||||
} else {
|
||||
submitter.uuid ||= v4()
|
||||
}
|
||||
@@ -818,6 +827,89 @@ export default {
|
||||
this.documentRefs = []
|
||||
},
|
||||
methods: {
|
||||
reorderFields (item) {
|
||||
const itemFields = []
|
||||
const fields = []
|
||||
const fieldAreasIndex = {}
|
||||
|
||||
const attachmentUuids = this.template.schema.map((e) => e.attachment_uuid)
|
||||
|
||||
this.template.fields.forEach((f) => {
|
||||
if (f.areas?.length) {
|
||||
const firstArea = f.areas.reduce((min, a) => {
|
||||
return attachmentUuids.indexOf(a.attachment_uuid) < attachmentUuids.indexOf(min.attachment_uuid) ? a : min
|
||||
}, f.areas[0])
|
||||
|
||||
if (firstArea.attachment_uuid === item.attachment_uuid) {
|
||||
itemFields.push(f)
|
||||
} else {
|
||||
fields.push(f)
|
||||
}
|
||||
} else {
|
||||
fields.push(f)
|
||||
}
|
||||
})
|
||||
|
||||
const sortArea = (aArea, bArea) => {
|
||||
if (aArea.attachment_uuid === bArea.attachment_uuid) {
|
||||
if (aArea.page === bArea.page) {
|
||||
if (Math.abs(aArea.y - bArea.y) < 0.01) {
|
||||
if (aArea.x === bArea.x) {
|
||||
return 0
|
||||
} else {
|
||||
return aArea.x - bArea.x
|
||||
}
|
||||
} else {
|
||||
return aArea.y - bArea.y
|
||||
}
|
||||
} else {
|
||||
return aArea.page - bArea.page
|
||||
}
|
||||
} else {
|
||||
return attachmentUuids.indexOf(aArea.attachment_uuid) - attachmentUuids.indexOf(bArea.attachment_uuid)
|
||||
}
|
||||
}
|
||||
|
||||
itemFields.sort((aField, bField) => {
|
||||
const aArea = (fieldAreasIndex[aField.uuid] ||= [...(aField.areas || [])].sort(sortArea)[0])
|
||||
const bArea = (fieldAreasIndex[bField.uuid] ||= [...(bField.areas || [])].sort(sortArea)[0])
|
||||
|
||||
return sortArea(aArea, bArea)
|
||||
})
|
||||
|
||||
const insertBeforeAttachmentUuids = attachmentUuids.slice(this.template.schema.indexOf(item) + 1)
|
||||
|
||||
let sortedFields = []
|
||||
|
||||
if (insertBeforeAttachmentUuids.length) {
|
||||
const insertAfterField = fields.find((f) => {
|
||||
if (f.areas?.length) {
|
||||
return f.areas.find((a) => insertBeforeAttachmentUuids.includes(a.attachment_uuid))
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
if (insertAfterField) {
|
||||
fields.splice(fields.indexOf(insertAfterField), 0, ...itemFields)
|
||||
|
||||
sortedFields = fields
|
||||
} else {
|
||||
sortedFields = fields.concat(itemFields)
|
||||
}
|
||||
} else {
|
||||
if (fields.length && itemFields.length && this.template.fields.indexOf(fields[0]) > this.template.fields.indexOf(itemFields[0])) {
|
||||
sortedFields = itemFields.concat(fields)
|
||||
} else {
|
||||
sortedFields = fields.concat(itemFields)
|
||||
}
|
||||
}
|
||||
|
||||
if (this.template.fields.length === sortedFields.length) {
|
||||
this.template.fields = sortedFields
|
||||
this.save()
|
||||
}
|
||||
},
|
||||
closeDropdown () {
|
||||
document.activeElement.blur()
|
||||
},
|
||||
@@ -1228,7 +1320,11 @@ export default {
|
||||
|
||||
if (!this.fieldsDragFieldRef.value) {
|
||||
if (['select', 'multiple', 'radio'].includes(field.type)) {
|
||||
field.options = [{ value: '', uuid: v4() }]
|
||||
if (this.dragField?.options?.length) {
|
||||
field.options = this.dragField.options.map(option => ({ value: option, uuid: v4() }))
|
||||
} else {
|
||||
field.options = [{ value: '', uuid: v4() }]
|
||||
}
|
||||
}
|
||||
|
||||
if (['stamp', 'heading'].includes(field.type)) {
|
||||
@@ -1331,6 +1427,8 @@ export default {
|
||||
const documentRef = this.documentRefs.find((e) => e.document.uuid === area.attachment_uuid)
|
||||
const areaRef = documentRef.pageRefs[area.page].areaRefs.find((ref) => ref.area === this.selectedAreaRef.value)
|
||||
|
||||
areaRef.isHeadingSelected = true
|
||||
|
||||
areaRef.focusValueInput()
|
||||
})
|
||||
}
|
||||
@@ -1418,29 +1516,29 @@ export default {
|
||||
onDocumentRemove (item) {
|
||||
if (window.confirm(this.t('are_you_sure_'))) {
|
||||
this.template.schema.splice(this.template.schema.indexOf(item), 1)
|
||||
}
|
||||
|
||||
const removedFieldUuids = []
|
||||
const removedFieldUuids = []
|
||||
|
||||
this.template.fields.forEach((field) => {
|
||||
[...(field.areas || [])].forEach((area) => {
|
||||
if (area.attachment_uuid === item.attachment_uuid) {
|
||||
field.areas.splice(field.areas.indexOf(area), 1)
|
||||
this.template.fields.forEach((field) => {
|
||||
[...(field.areas || [])].forEach((area) => {
|
||||
if (area.attachment_uuid === item.attachment_uuid) {
|
||||
field.areas.splice(field.areas.indexOf(area), 1)
|
||||
|
||||
removedFieldUuids.push(field.uuid)
|
||||
}
|
||||
removedFieldUuids.push(field.uuid)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
this.template.fields =
|
||||
this.template.fields.filter((f) => !removedFieldUuids.includes(f.uuid) || f.areas?.length)
|
||||
this.template.fields =
|
||||
this.template.fields.filter((f) => !removedFieldUuids.includes(f.uuid) || f.areas?.length)
|
||||
|
||||
this.save()
|
||||
this.save()
|
||||
}
|
||||
},
|
||||
onDocumentReplace (data) {
|
||||
const { replaceSchemaItem, schema, documents } = data
|
||||
|
||||
this.template.schema.splice(this.template.schema.indexOf(replaceSchemaItem), 1, schema[0])
|
||||
this.template.schema.splice(this.template.schema.indexOf(replaceSchemaItem), 1, { ...replaceSchemaItem, ...schema[0] })
|
||||
this.template.documents.push(...documents)
|
||||
|
||||
if (data.fields) {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
:input-mode="inputMode"
|
||||
:number="index"
|
||||
:editable="editable"
|
||||
:data-page="index"
|
||||
:areas="areasIndex[index]"
|
||||
:allow-draw="allowDraw"
|
||||
:is-drag="isDrag"
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
/>
|
||||
<Contenteditable
|
||||
ref="name"
|
||||
:model-value="(defaultField ? (field.title || field.name) : field.name) || defaultName"
|
||||
:model-value="(defaultField ? (defaultField.title || field.title || field.name) : field.name) || defaultName"
|
||||
:editable="editable && !defaultField && field.type != 'heading'"
|
||||
:icon-inline="true"
|
||||
:icon-width="18"
|
||||
@@ -192,14 +192,14 @@
|
||||
class="w-full input input-primary input-xs text-sm bg-transparent"
|
||||
:placeholder="`${t('option')} ${index + 1}`"
|
||||
type="text"
|
||||
:readonly="!editable"
|
||||
:readonly="!editable || defaultField"
|
||||
required
|
||||
dir="auto"
|
||||
@focus="maybeFocusOnOptionArea(option)"
|
||||
@blur="save"
|
||||
>
|
||||
<button
|
||||
v-if="editable"
|
||||
v-if="editable && !defaultField"
|
||||
class="text-sm w-3.5"
|
||||
tabindex="-1"
|
||||
@click="removeOption(option)"
|
||||
@@ -208,11 +208,11 @@
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
v-if="field.options && !editable"
|
||||
v-if="field.options && (!editable || defaultField)"
|
||||
class="pb-1"
|
||||
/>
|
||||
<button
|
||||
v-else-if="field.options && editable"
|
||||
v-else-if="field.options && editable && !defaultField"
|
||||
class="text-center text-sm w-full pb-1"
|
||||
@click="addOption"
|
||||
>
|
||||
@@ -392,7 +392,7 @@ export default {
|
||||
return this.sortedAreas[0] && this.$emit('scroll-to', this.sortedAreas[0])
|
||||
},
|
||||
closeDropdown () {
|
||||
document.activeElement.blur()
|
||||
this.$el.getRootNode().activeElement.blur()
|
||||
},
|
||||
addOption () {
|
||||
this.field.options.push({ value: '', uuid: v4() })
|
||||
|
||||
@@ -26,6 +26,33 @@
|
||||
{{ t('format') }}
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
v-if="field.type === 'verification'"
|
||||
class="py-1.5 px-1 relative"
|
||||
@click.stop
|
||||
>
|
||||
<select
|
||||
:placeholder="t('method')"
|
||||
class="select select-bordered select-xs font-normal w-full max-w-xs !h-7 !outline-0 bg-transparent"
|
||||
@change="[field.preferences ||= {}, field.preferences.method = $event.target.value, save()]"
|
||||
>
|
||||
<option
|
||||
v-for="method in ['QeS', 'AeS']"
|
||||
:key="method"
|
||||
:value="method.toLowerCase()"
|
||||
:selected="method.toLowerCase() === field.preferences?.method || (method === 'QeS' && !field.preferences?.method)"
|
||||
>
|
||||
{{ method }}
|
||||
</option>
|
||||
</select>
|
||||
<label
|
||||
:style="{ backgroundColor }"
|
||||
class="absolute -top-1 left-2.5 px-1 h-4"
|
||||
style="font-size: 8px"
|
||||
>
|
||||
{{ t('method') }}
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
v-if="['number', 'cells'].includes(field.type)"
|
||||
class="py-1.5 px-1 relative"
|
||||
@@ -229,7 +256,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<li
|
||||
v-if="withRequired && field.type != 'phone' && field.type != 'stamp'"
|
||||
v-if="withRequired && field.type !== 'phone' && field.type !== 'stamp' && field.type !== 'verification'"
|
||||
@click.stop
|
||||
>
|
||||
<label class="cursor-pointer py-1.5">
|
||||
@@ -461,6 +488,9 @@ export default {
|
||||
numberFormats () {
|
||||
return [
|
||||
'none',
|
||||
'usd',
|
||||
'eur',
|
||||
'gbp',
|
||||
'comma',
|
||||
'dot',
|
||||
'space'
|
||||
@@ -541,6 +571,12 @@ export default {
|
||||
formatNumber (number, format) {
|
||||
if (format === 'comma') {
|
||||
return new Intl.NumberFormat('en-US').format(number)
|
||||
} else if (format === 'usd') {
|
||||
return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(number)
|
||||
} else if (format === 'gbp') {
|
||||
return new Intl.NumberFormat('en-GB', { style: 'currency', currency: 'GBP', minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(number)
|
||||
} else if (format === 'eur') {
|
||||
return new Intl.NumberFormat('fr-FR', { style: 'currency', currency: 'EUR', minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(number)
|
||||
} else if (format === 'dot') {
|
||||
return new Intl.NumberFormat('de-DE').format(number)
|
||||
} else if (format === 'space') {
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<div class="flex items-center space-x-2">
|
||||
<span
|
||||
class="w-3 h-3 flex-shrink-0 rounded-full"
|
||||
:class="colors[submitters.indexOf(selectedSubmitter)]"
|
||||
:class="colors[submitters.indexOf(selectedSubmitter) % colors.length]"
|
||||
/>
|
||||
<Contenteditable
|
||||
v-model="selectedSubmitter.name"
|
||||
@@ -53,7 +53,7 @@
|
||||
<span class="py-1 flex items-center">
|
||||
<span
|
||||
class="rounded-full w-3 h-3 ml-1 mr-3"
|
||||
:class="colors[index]"
|
||||
:class="colors[index % colors.length]"
|
||||
/>
|
||||
<span>
|
||||
{{ submitter.name }}
|
||||
@@ -101,7 +101,7 @@
|
||||
>
|
||||
<button
|
||||
class="mx-1 w-3 h-3 rounded-full"
|
||||
:class="colors[submitters.indexOf(selectedSubmitter)]"
|
||||
:class="colors[submitters.indexOf(selectedSubmitter) % colors.length]"
|
||||
/>
|
||||
</label>
|
||||
<label
|
||||
@@ -113,7 +113,7 @@
|
||||
<div class="flex items-center space-x-2">
|
||||
<span
|
||||
class="w-3 h-3 rounded-full"
|
||||
:class="colors[submitters.indexOf(selectedSubmitter)]"
|
||||
:class="colors[submitters.indexOf(selectedSubmitter) % colors.length]"
|
||||
/>
|
||||
<Contenteditable
|
||||
v-model="selectedSubmitter.name"
|
||||
@@ -143,6 +143,7 @@
|
||||
<li
|
||||
v-for="(submitter, index) in submitters"
|
||||
:key="submitter.uuid"
|
||||
class="w-full"
|
||||
>
|
||||
<a
|
||||
href="#"
|
||||
@@ -153,7 +154,7 @@
|
||||
<span class="py-1 flex items-center">
|
||||
<span
|
||||
class="rounded-full w-3 h-3 ml-1 mr-3"
|
||||
:class="colors[index]"
|
||||
:class="colors[index % colors.length]"
|
||||
/>
|
||||
<span>
|
||||
{{ submitter.name }}
|
||||
@@ -191,7 +192,10 @@
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
<li v-if="submitters.length < names.length && editable && allowAddNew">
|
||||
<li
|
||||
v-if="submitters.length < names.length && editable && allowAddNew"
|
||||
class="w-full"
|
||||
>
|
||||
<a
|
||||
href="#"
|
||||
class="flex px-2"
|
||||
@@ -215,6 +219,14 @@ import { IconUserPlus, IconTrashX, IconPlus, IconChevronUp, IconChevronDown } fr
|
||||
import Contenteditable from './contenteditable'
|
||||
import { v4 } from 'uuid'
|
||||
|
||||
function getOrdinalSuffix (num) {
|
||||
if (num % 10 === 1 && num % 100 !== 11) return 'st'
|
||||
if (num % 10 === 2 && num % 100 !== 12) return 'nd'
|
||||
if (num % 10 === 3 && num % 100 !== 13) return 'rd'
|
||||
|
||||
return 'th'
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'FieldSubmitter',
|
||||
components: {
|
||||
@@ -275,16 +287,6 @@ export default {
|
||||
computed: {
|
||||
colors () {
|
||||
return [
|
||||
'bg-red-500',
|
||||
'bg-sky-500',
|
||||
'bg-emerald-500',
|
||||
'bg-yellow-300',
|
||||
'bg-purple-600',
|
||||
'bg-pink-500',
|
||||
'bg-cyan-500',
|
||||
'bg-orange-500',
|
||||
'bg-lime-500',
|
||||
'bg-indigo-500',
|
||||
'bg-red-500',
|
||||
'bg-sky-500',
|
||||
'bg-emerald-500',
|
||||
@@ -298,6 +300,14 @@ export default {
|
||||
]
|
||||
},
|
||||
names () {
|
||||
const generatedNames = []
|
||||
|
||||
for (let i = 21; i < 101; i++) {
|
||||
const suffix = getOrdinalSuffix(i)
|
||||
|
||||
generatedNames.push(`${i}${suffix} ${this.t('party')}`)
|
||||
}
|
||||
|
||||
return [
|
||||
this.t('first_party'),
|
||||
this.t('second_party'),
|
||||
@@ -318,7 +328,8 @@ export default {
|
||||
this.t('seventeenth_party'),
|
||||
this.t('eighteenth_party'),
|
||||
this.t('nineteenth_party'),
|
||||
this.t('twentieth_party')
|
||||
this.t('twentieth_party'),
|
||||
...generatedNames
|
||||
]
|
||||
},
|
||||
lastPartyIndex () {
|
||||
@@ -372,7 +383,7 @@ export default {
|
||||
this.$emit('new-submitter', newSubmitter)
|
||||
},
|
||||
closeDropdown () {
|
||||
document.activeElement.blur()
|
||||
this.$el.getRootNode().activeElement.blur()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,7 +170,7 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
closeDropdown () {
|
||||
document.activeElement.blur()
|
||||
this.$el.getRootNode().activeElement.blur()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
class="roles-dropdown w-full rounded-lg"
|
||||
:style="withStickySubmitters ? { backgroundColor } : {}"
|
||||
:submitters="submitters"
|
||||
:menu-style="{ backgroundColor: ['', null, 'transparent'].includes(backgroundColor) ? 'white' : backgroundColor }"
|
||||
:menu-style="{ overflow: 'auto', display: 'flex', flexDirection: 'row', maxHeight: 'calc(100vh - 120px)', backgroundColor: ['', null, 'transparent'].includes(backgroundColor) ? 'white' : backgroundColor }"
|
||||
:editable="editable && !defaultSubmitters.length"
|
||||
@new-submitter="save"
|
||||
@remove="removeSubmitter"
|
||||
@@ -26,7 +26,7 @@
|
||||
:field="field"
|
||||
:type-index="fields.filter((f) => f.type === field.type).indexOf(field)"
|
||||
:editable="editable && (!fieldsDragFieldRef.value || fieldsDragFieldRef.value !== field)"
|
||||
:default-field="defaultFields.find((f) => f.name === field.name)"
|
||||
:default-field="defaultFieldsIndex[field.name]"
|
||||
:draggable="editable"
|
||||
@dragstart="fieldsDragFieldRef.value = field"
|
||||
@dragend="fieldsDragFieldRef.value = null"
|
||||
@@ -299,6 +299,13 @@ export default {
|
||||
isShowFieldSearch () {
|
||||
return this.submitterDefaultFields.length > 15
|
||||
},
|
||||
defaultFieldsIndex () {
|
||||
return this.defaultFields.reduce((acc, field) => {
|
||||
acc[field.name] = field
|
||||
|
||||
return acc
|
||||
}, {})
|
||||
},
|
||||
fieldIconsSorted () {
|
||||
if (this.fieldTypes.length) {
|
||||
return this.fieldTypes.reduce((acc, type) => {
|
||||
@@ -320,7 +327,7 @@ export default {
|
||||
},
|
||||
filteredSubmitterDefaultFields () {
|
||||
if (this.defaultFieldsSearch) {
|
||||
return this.submitterDefaultFields.filter((f) => f.name.toLowerCase().includes(this.defaultFieldsSearch.toLowerCase()))
|
||||
return this.submitterDefaultFields.filter((f) => (f.title || f.name).toLowerCase().includes(this.defaultFieldsSearch.toLowerCase()))
|
||||
} else {
|
||||
return this.submitterDefaultFields
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
const en = {
|
||||
party: 'Party',
|
||||
method: 'Method',
|
||||
reorder_fields: 'Reorder fields',
|
||||
verify_id: 'Verify ID',
|
||||
obtain_qualified_electronic_signature_with_the_trusted_provider_click_to_learn_more: 'Obtain qualified electronic signature (QeS) with the trusted provider. Click to learn more.',
|
||||
editable: 'Editable',
|
||||
@@ -158,6 +161,9 @@ const en = {
|
||||
}
|
||||
|
||||
const es = {
|
||||
party: 'Parte',
|
||||
method: 'Método',
|
||||
reorder_fields: 'Reordenar campos',
|
||||
verify_id: 'Verificar ID',
|
||||
obtain_qualified_electronic_signature_with_the_trusted_provider_click_to_learn_more: 'Obtenga una firma electrónica cualificada (QeS) con el proveedor de confianza. Haga clic para obtener más información.',
|
||||
recurrent: 'Recurrente',
|
||||
@@ -317,6 +323,9 @@ const es = {
|
||||
}
|
||||
|
||||
const it = {
|
||||
party: 'Parte',
|
||||
method: 'Metodo',
|
||||
reorder_fields: 'Riordina i campi',
|
||||
verify_id: 'Verifica ID',
|
||||
obtain_qualified_electronic_signature_with_the_trusted_provider_click_to_learn_more: 'Ottieni una firma elettronica qualificata (QeS) con il fornitore di fiducia. Clicca per saperne di più.',
|
||||
ricorrente: 'Ricorrente',
|
||||
@@ -476,6 +485,9 @@ const it = {
|
||||
}
|
||||
|
||||
const pt = {
|
||||
party: 'Parte',
|
||||
method: 'Método',
|
||||
reorder_fields: 'Reorganizar campos',
|
||||
verify_id: 'Verificar ID',
|
||||
obtain_qualified_electronic_signature_with_the_trusted_provider_click_to_learn_more: 'Obtenha a assinatura eletrônica qualificada (QeS) com o provedor confiável. Clique para saber mais.',
|
||||
recurrent: 'Recurrente',
|
||||
@@ -635,6 +647,9 @@ const pt = {
|
||||
}
|
||||
|
||||
const fr = {
|
||||
party: 'Partie',
|
||||
method: 'Méthode',
|
||||
reorder_fields: 'Réorganiser les champs',
|
||||
verify_id: "Vérifier l'ID",
|
||||
obtain_qualified_electronic_signature_with_the_trusted_provider_click_to_learn_more: 'Obtenez une signature électronique qualifiée (QeS) avec le fournisseur de confiance. Cliquez pour en savoir plus.',
|
||||
recurrent: 'Récurrent',
|
||||
@@ -794,6 +809,9 @@ const fr = {
|
||||
}
|
||||
|
||||
const de = {
|
||||
party: 'Partei',
|
||||
method: 'Verfahren',
|
||||
reorder_fields: 'Felder neu anordnen',
|
||||
verify_id: 'ID überprüfen',
|
||||
obtain_qualified_electronic_signature_with_the_trusted_provider_click_to_learn_more: 'Erhalten Sie eine qualifizierte elektronische Signatur (QeS) beim vertrauenswürdigen Anbieter. Klicken Sie hier, um mehr zu erfahren.',
|
||||
wiederkehrend: 'Wiederkehrend',
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<div
|
||||
class="relative cursor-crosshair select-none"
|
||||
class="relative select-none"
|
||||
:class="{ 'cursor-crosshair': allowDraw }"
|
||||
:style="drawField ? 'touch-action: none' : ''"
|
||||
>
|
||||
<img
|
||||
@@ -25,7 +26,7 @@
|
||||
:field="item.field"
|
||||
:editable="editable"
|
||||
:with-field-placeholder="withFieldPlaceholder"
|
||||
:default-field="defaultFields.find((f) => f.name === item.field.name)"
|
||||
:default-field="defaultFieldsIndex[item.field.name]"
|
||||
:default-submitters="defaultSubmitters"
|
||||
:max-page="totalPages - 1"
|
||||
@start-resize="resizeDirection = $event"
|
||||
@@ -142,6 +143,13 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
defaultFieldsIndex () {
|
||||
return this.defaultFields.reduce((acc, field) => {
|
||||
acc[field.name] = field
|
||||
|
||||
return acc
|
||||
}, {})
|
||||
},
|
||||
defaultFieldType () {
|
||||
if (this.drawFieldType) {
|
||||
return this.drawFieldType
|
||||
@@ -154,7 +162,9 @@ export default {
|
||||
}
|
||||
},
|
||||
isMobile () {
|
||||
return /android|iphone|ipad/i.test(navigator.userAgent)
|
||||
const isMobileSafariIos = 'ontouchstart' in window && navigator.maxTouchPoints > 0 && /AppleWebKit/i.test(navigator.userAgent)
|
||||
|
||||
return isMobileSafariIos || /android|iphone|ipad/i.test(navigator.userAgent)
|
||||
},
|
||||
resizeDirectionClasses () {
|
||||
return {
|
||||
|
||||
@@ -56,23 +56,39 @@
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
v-if="withArrows"
|
||||
class="flex flex-col space-y-1"
|
||||
>
|
||||
<button
|
||||
class="btn border-base-200 bg-white text-base-content btn-xs rounded hover:text-base-100 hover:bg-base-content hover:border-base-content w-full transition-colors"
|
||||
style="width: 24px; height: 24px"
|
||||
@click.stop="$emit('up', item)"
|
||||
<span
|
||||
:data-tip="t('reorder_fields')"
|
||||
class="tooltip tooltip-left before:text-xs"
|
||||
>
|
||||
↑
|
||||
</button>
|
||||
<button
|
||||
class="btn border-base-200 bg-white text-base-content btn-xs rounded hover:text-base-100 hover:bg-base-content hover:border-base-content w-full transition-colors"
|
||||
style="width: 24px; height: 24px"
|
||||
@click.stop="$emit('down', item)"
|
||||
>
|
||||
↓
|
||||
</button>
|
||||
<button
|
||||
class="btn border-base-200 bg-white text-base-content btn-xs rounded hover:text-base-100 hover:bg-base-content hover:border-base-content w-full transition-colors p-0"
|
||||
@click.stop="$emit('reorder', item)"
|
||||
>
|
||||
<IconSortDescending2
|
||||
:width="18"
|
||||
:height="18"
|
||||
:stroke-width="1.6"
|
||||
/>
|
||||
</button>
|
||||
</span>
|
||||
<template v-if="withArrows">
|
||||
<button
|
||||
class="btn border-base-200 bg-white text-base-content btn-xs rounded hover:text-base-100 hover:bg-base-content hover:border-base-content w-full transition-colors"
|
||||
style="width: 24px; height: 24px"
|
||||
@click.stop="$emit('up', item)"
|
||||
>
|
||||
↑
|
||||
</button>
|
||||
<button
|
||||
class="btn border-base-200 bg-white text-base-content btn-xs rounded hover:text-base-100 hover:bg-base-content hover:border-base-content w-full transition-colors"
|
||||
style="width: 24px; height: 24px"
|
||||
@click.stop="$emit('down', item)"
|
||||
>
|
||||
↓
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -88,23 +104,23 @@
|
||||
@update:model-value="onUpdateName"
|
||||
/>
|
||||
</div>
|
||||
<Teleport
|
||||
v-if="isShowConditionsModal"
|
||||
:to="modalContainerEl"
|
||||
>
|
||||
<ConditionsModal
|
||||
:item="item"
|
||||
:build-default-name="buildDefaultName"
|
||||
@close="isShowConditionsModal = false"
|
||||
/>
|
||||
</Teleport>
|
||||
</div>
|
||||
<Teleport
|
||||
v-if="isShowConditionsModal"
|
||||
:to="modalContainerEl"
|
||||
>
|
||||
<ConditionsModal
|
||||
:item="item"
|
||||
:build-default-name="buildDefaultName"
|
||||
@close="isShowConditionsModal = false"
|
||||
/>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Contenteditable from './contenteditable'
|
||||
import Upload from './upload'
|
||||
import { IconRouteAltLeft } from '@tabler/icons-vue'
|
||||
import { IconRouteAltLeft, IconSortDescending2 } from '@tabler/icons-vue'
|
||||
import ConditionsModal from './conditions_modal'
|
||||
import ReplaceButton from './replace'
|
||||
import Field from './field'
|
||||
@@ -116,7 +132,8 @@ export default {
|
||||
Contenteditable,
|
||||
IconRouteAltLeft,
|
||||
ConditionsModal,
|
||||
ReplaceButton
|
||||
ReplaceButton,
|
||||
IconSortDescending2
|
||||
},
|
||||
inject: ['t'],
|
||||
props: {
|
||||
@@ -153,7 +170,7 @@ export default {
|
||||
default: true
|
||||
}
|
||||
},
|
||||
emits: ['scroll-to', 'change', 'remove', 'up', 'down', 'replace'],
|
||||
emits: ['scroll-to', 'change', 'remove', 'up', 'down', 'replace', 'reorder'],
|
||||
data () {
|
||||
return {
|
||||
isShowConditionsModal: false
|
||||
|
||||
@@ -120,6 +120,8 @@ class ProcessSubmitterCompletionJob
|
||||
submitter.submission.template_submitters.find do |e|
|
||||
sub = submitter.submission.submitters.find { |s| s.uuid == e['uuid'] }
|
||||
|
||||
next unless sub
|
||||
|
||||
sub.completed_at.blank? && sub.sent_at.blank?
|
||||
end
|
||||
|
||||
|
||||
@@ -5,9 +5,7 @@ class SendFormCompletedWebhookRequestJob
|
||||
|
||||
sidekiq_options queue: :webhooks
|
||||
|
||||
USER_AGENT = 'DocuSeal.com Webhook'
|
||||
|
||||
MAX_ATTEMPTS = 10
|
||||
MAX_ATTEMPTS = 20
|
||||
|
||||
def perform(params = {})
|
||||
submitter = Submitter.find(params['submitter_id'])
|
||||
@@ -21,19 +19,8 @@ class SendFormCompletedWebhookRequestJob
|
||||
|
||||
ActiveStorage::Current.url_options = Docuseal.default_url_options
|
||||
|
||||
resp = begin
|
||||
Faraday.post(webhook_url.url,
|
||||
{
|
||||
event_type: 'form.completed',
|
||||
timestamp: Time.current,
|
||||
data: Submitters::SerializeForWebhook.call(submitter)
|
||||
}.to_json,
|
||||
'Content-Type' => 'application/json',
|
||||
'User-Agent' => USER_AGENT,
|
||||
**webhook_url.secret.to_h)
|
||||
rescue Faraday::Error
|
||||
nil
|
||||
end
|
||||
resp = SendWebhookRequest.call(webhook_url, event_type: 'form.completed',
|
||||
data: Submitters::SerializeForWebhook.call(submitter))
|
||||
|
||||
if (resp.nil? || resp.status.to_i >= 400) && attempt <= MAX_ATTEMPTS &&
|
||||
(!Docuseal.multitenant? || submitter.account.account_configs.exists?(key: :plan))
|
||||
|
||||
@@ -5,8 +5,6 @@ class SendFormDeclinedWebhookRequestJob
|
||||
|
||||
sidekiq_options queue: :webhooks
|
||||
|
||||
USER_AGENT = 'DocuSeal.com Webhook'
|
||||
|
||||
MAX_ATTEMPTS = 10
|
||||
|
||||
def perform(params = {})
|
||||
@@ -19,19 +17,8 @@ class SendFormDeclinedWebhookRequestJob
|
||||
|
||||
ActiveStorage::Current.url_options = Docuseal.default_url_options
|
||||
|
||||
resp = begin
|
||||
Faraday.post(webhook_url.url,
|
||||
{
|
||||
event_type: 'form.declined',
|
||||
timestamp: Time.current,
|
||||
data: Submitters::SerializeForWebhook.call(submitter)
|
||||
}.to_json,
|
||||
**webhook_url.secret.to_h,
|
||||
'Content-Type' => 'application/json',
|
||||
'User-Agent' => USER_AGENT)
|
||||
rescue Faraday::Error
|
||||
nil
|
||||
end
|
||||
resp = SendWebhookRequest.call(webhook_url, event_type: 'form.declined',
|
||||
data: Submitters::SerializeForWebhook.call(submitter))
|
||||
|
||||
if (resp.nil? || resp.status.to_i >= 400) && attempt <= MAX_ATTEMPTS &&
|
||||
(!Docuseal.multitenant? || submitter.account.account_configs.exists?(key: :plan))
|
||||
|
||||
@@ -5,8 +5,6 @@ class SendFormStartedWebhookRequestJob
|
||||
|
||||
sidekiq_options queue: :webhooks
|
||||
|
||||
USER_AGENT = 'DocuSeal.com Webhook'
|
||||
|
||||
MAX_ATTEMPTS = 10
|
||||
|
||||
def perform(params = {})
|
||||
@@ -19,19 +17,8 @@ class SendFormStartedWebhookRequestJob
|
||||
|
||||
ActiveStorage::Current.url_options = Docuseal.default_url_options
|
||||
|
||||
resp = begin
|
||||
Faraday.post(webhook_url.url,
|
||||
{
|
||||
event_type: 'form.started',
|
||||
timestamp: Time.current,
|
||||
data: Submitters::SerializeForWebhook.call(submitter)
|
||||
}.to_json,
|
||||
**webhook_url.secret.to_h,
|
||||
'Content-Type' => 'application/json',
|
||||
'User-Agent' => USER_AGENT)
|
||||
rescue Faraday::Error
|
||||
nil
|
||||
end
|
||||
resp = SendWebhookRequest.call(webhook_url, event_type: 'form.started',
|
||||
data: Submitters::SerializeForWebhook.call(submitter))
|
||||
|
||||
if (resp.nil? || resp.status.to_i >= 400) && attempt <= MAX_ATTEMPTS &&
|
||||
(!Docuseal.multitenant? || submitter.account.account_configs.exists?(key: :plan))
|
||||
|
||||
@@ -5,8 +5,6 @@ class SendFormViewedWebhookRequestJob
|
||||
|
||||
sidekiq_options queue: :webhooks
|
||||
|
||||
USER_AGENT = 'DocuSeal.com Webhook'
|
||||
|
||||
MAX_ATTEMPTS = 10
|
||||
|
||||
def perform(params = {})
|
||||
@@ -19,19 +17,8 @@ class SendFormViewedWebhookRequestJob
|
||||
|
||||
ActiveStorage::Current.url_options = Docuseal.default_url_options
|
||||
|
||||
resp = begin
|
||||
Faraday.post(webhook_url.url,
|
||||
{
|
||||
event_type: 'form.viewed',
|
||||
timestamp: Time.current,
|
||||
data: Submitters::SerializeForWebhook.call(submitter)
|
||||
}.to_json,
|
||||
**webhook_url.secret.to_h,
|
||||
'Content-Type' => 'application/json',
|
||||
'User-Agent' => USER_AGENT)
|
||||
rescue Faraday::Error
|
||||
nil
|
||||
end
|
||||
resp = SendWebhookRequest.call(webhook_url, event_type: 'form.viewed',
|
||||
data: Submitters::SerializeForWebhook.call(submitter))
|
||||
|
||||
if (resp.nil? || resp.status.to_i >= 400) && attempt <= MAX_ATTEMPTS &&
|
||||
(!Docuseal.multitenant? || submitter.account.account_configs.exists?(key: :plan))
|
||||
|
||||
@@ -5,8 +5,6 @@ class SendSubmissionArchivedWebhookRequestJob
|
||||
|
||||
sidekiq_options queue: :webhooks
|
||||
|
||||
USER_AGENT = 'DocuSeal.com Webhook'
|
||||
|
||||
MAX_ATTEMPTS = 10
|
||||
|
||||
def perform(params = {})
|
||||
@@ -17,19 +15,8 @@ class SendSubmissionArchivedWebhookRequestJob
|
||||
|
||||
return if webhook_url.url.blank? || webhook_url.events.exclude?('submission.archived')
|
||||
|
||||
resp = begin
|
||||
Faraday.post(webhook_url.url,
|
||||
{
|
||||
event_type: 'submission.archived',
|
||||
timestamp: Time.current,
|
||||
data: submission.as_json(only: %i[id archived_at])
|
||||
}.to_json,
|
||||
**webhook_url.secret.to_h,
|
||||
'Content-Type' => 'application/json',
|
||||
'User-Agent' => USER_AGENT)
|
||||
rescue Faraday::Error
|
||||
nil
|
||||
end
|
||||
resp = SendWebhookRequest.call(webhook_url, event_type: 'submission.archived',
|
||||
data: submission.as_json(only: %i[id archived_at]))
|
||||
|
||||
if (resp.nil? || resp.status.to_i >= 400) && attempt <= MAX_ATTEMPTS &&
|
||||
(!Docuseal.multitenant? || submission.account.account_configs.exists?(key: :plan))
|
||||
|
||||
@@ -5,8 +5,6 @@ class SendSubmissionCompletedWebhookRequestJob
|
||||
|
||||
sidekiq_options queue: :webhooks
|
||||
|
||||
USER_AGENT = 'DocuSeal.com Webhook'
|
||||
|
||||
MAX_ATTEMPTS = 10
|
||||
|
||||
def perform(params = {})
|
||||
@@ -17,19 +15,8 @@ class SendSubmissionCompletedWebhookRequestJob
|
||||
|
||||
return if webhook_url.url.blank? || webhook_url.events.exclude?('submission.completed')
|
||||
|
||||
resp = begin
|
||||
Faraday.post(webhook_url.url,
|
||||
{
|
||||
event_type: 'submission.completed',
|
||||
timestamp: Time.current,
|
||||
data: Submissions::SerializeForApi.call(submission)
|
||||
}.to_json,
|
||||
'Content-Type' => 'application/json',
|
||||
'User-Agent' => USER_AGENT,
|
||||
**webhook_url.secret.to_h)
|
||||
rescue Faraday::Error
|
||||
nil
|
||||
end
|
||||
resp = SendWebhookRequest.call(webhook_url, event_type: 'submission.completed',
|
||||
data: Submissions::SerializeForApi.call(submission))
|
||||
|
||||
if (resp.nil? || resp.status.to_i >= 400) && attempt <= MAX_ATTEMPTS &&
|
||||
(!Docuseal.multitenant? || submission.account.account_configs.exists?(key: :plan))
|
||||
|
||||
@@ -5,8 +5,6 @@ class SendSubmissionCreatedWebhookRequestJob
|
||||
|
||||
sidekiq_options queue: :webhooks
|
||||
|
||||
USER_AGENT = 'DocuSeal.com Webhook'
|
||||
|
||||
MAX_ATTEMPTS = 10
|
||||
|
||||
def perform(params = {})
|
||||
@@ -17,19 +15,8 @@ class SendSubmissionCreatedWebhookRequestJob
|
||||
|
||||
return if webhook_url.url.blank? || webhook_url.events.exclude?('submission.created')
|
||||
|
||||
resp = begin
|
||||
Faraday.post(webhook_url.url,
|
||||
{
|
||||
event_type: 'submission.created',
|
||||
timestamp: Time.current,
|
||||
data: Submissions::SerializeForApi.call(submission)
|
||||
}.to_json,
|
||||
**webhook_url.secret.to_h,
|
||||
'Content-Type' => 'application/json',
|
||||
'User-Agent' => USER_AGENT)
|
||||
rescue Faraday::Error
|
||||
nil
|
||||
end
|
||||
resp = SendWebhookRequest.call(webhook_url, event_type: 'submission.created',
|
||||
data: Submissions::SerializeForApi.call(submission))
|
||||
|
||||
if (resp.nil? || resp.status.to_i >= 400) && attempt <= MAX_ATTEMPTS &&
|
||||
(!Docuseal.multitenant? || submission.account.account_configs.exists?(key: :plan))
|
||||
|
||||
@@ -5,8 +5,6 @@ class SendTemplateCreatedWebhookRequestJob
|
||||
|
||||
sidekiq_options queue: :webhooks
|
||||
|
||||
USER_AGENT = 'DocuSeal.com Webhook'
|
||||
|
||||
MAX_ATTEMPTS = 10
|
||||
|
||||
def perform(params = {})
|
||||
@@ -17,19 +15,8 @@ class SendTemplateCreatedWebhookRequestJob
|
||||
|
||||
return if webhook_url.url.blank? || webhook_url.events.exclude?('template.created')
|
||||
|
||||
resp = begin
|
||||
Faraday.post(webhook_url.url,
|
||||
{
|
||||
event_type: 'template.created',
|
||||
timestamp: Time.current,
|
||||
data: Templates::SerializeForApi.call(template)
|
||||
}.to_json,
|
||||
**webhook_url.secret.to_h,
|
||||
'Content-Type' => 'application/json',
|
||||
'User-Agent' => USER_AGENT)
|
||||
rescue Faraday::Error
|
||||
nil
|
||||
end
|
||||
resp = SendWebhookRequest.call(webhook_url, event_type: 'template.created',
|
||||
data: Templates::SerializeForApi.call(template))
|
||||
|
||||
if (resp.nil? || resp.status.to_i >= 400) && attempt <= MAX_ATTEMPTS &&
|
||||
(!Docuseal.multitenant? || template.account.account_configs.exists?(key: :plan))
|
||||
|
||||
@@ -5,8 +5,6 @@ class SendTemplateUpdatedWebhookRequestJob
|
||||
|
||||
sidekiq_options queue: :webhooks
|
||||
|
||||
USER_AGENT = 'DocuSeal.com Webhook'
|
||||
|
||||
MAX_ATTEMPTS = 10
|
||||
|
||||
def perform(params = {})
|
||||
@@ -17,19 +15,8 @@ class SendTemplateUpdatedWebhookRequestJob
|
||||
|
||||
return if webhook_url.url.blank? || webhook_url.events.exclude?('template.updated')
|
||||
|
||||
resp = begin
|
||||
Faraday.post(webhook_url.url,
|
||||
{
|
||||
event_type: 'template.updated',
|
||||
timestamp: Time.current,
|
||||
data: Templates::SerializeForApi.call(template)
|
||||
}.to_json,
|
||||
**webhook_url.secret.to_h,
|
||||
'Content-Type' => 'application/json',
|
||||
'User-Agent' => USER_AGENT)
|
||||
rescue Faraday::Error
|
||||
nil
|
||||
end
|
||||
resp = SendWebhookRequest.call(webhook_url, event_type: 'template.updated',
|
||||
data: Templates::SerializeForApi.call(template))
|
||||
|
||||
if (resp.nil? || resp.status.to_i >= 400) && attempt <= MAX_ATTEMPTS &&
|
||||
(!Docuseal.multitenant? || template.account.account_configs.exists?(key: :plan))
|
||||
|
||||
@@ -121,7 +121,7 @@ class SubmitterMailer < ApplicationMailer
|
||||
@body ||= @email_config.value['body'] if @email_config
|
||||
|
||||
assign_message_metadata('submitter_documents_copy', @submitter)
|
||||
reply_to = build_submitter_reply_to(submitter)
|
||||
reply_to = build_submitter_reply_to(submitter, email_config: @email_config, documents_copy_email: true)
|
||||
|
||||
I18n.with_locale(@current_account.locale) do
|
||||
subject =
|
||||
@@ -140,8 +140,10 @@ class SubmitterMailer < ApplicationMailer
|
||||
|
||||
private
|
||||
|
||||
def build_submitter_reply_to(submitter)
|
||||
def build_submitter_reply_to(submitter, email_config: nil, documents_copy_email: nil)
|
||||
reply_to = submitter.preferences['reply_to'].presence
|
||||
reply_to ||= submitter.template.preferences['documents_copy_email_reply_to'].presence if documents_copy_email
|
||||
reply_to ||= email_config.value['reply_to'].presence if email_config
|
||||
|
||||
if reply_to.blank? && (submitter.submission.created_by_user || submitter.template.author)&.email != submitter.email
|
||||
reply_to = (submitter.submission.created_by_user || submitter.template.author)&.friendly_name&.sub(/\+\w+@/, '@')
|
||||
|
||||
Whitespace-only changes.
@@ -153,6 +153,7 @@
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<%= render 'compliances' %>
|
||||
<%= render 'integrations' %>
|
||||
<% if can?(:manage, current_account) && Docuseal.multitenant? && true_user == current_user %>
|
||||
<div class="px-1 mt-8">
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<%= hidden_field_tag :redir, params[:redir] if params[:redir].present? %>
|
||||
@@ -0,0 +1,4 @@
|
||||
<div class="form-control">
|
||||
<%= label_tag 'user[otp_attempt]', t('two_factor_code_from_authenticator_app'), class: 'label' %>
|
||||
<%= text_field_tag 'user[otp_attempt]', nil, autofocus: true, autocomplete: 'off', placeholder: 'XXX-XXX', required: true, class: 'base-input' %>
|
||||
</div>
|
||||
@@ -3,9 +3,7 @@
|
||||
<%= render 'devise/shared/select_server' if Docuseal.multitenant? %>
|
||||
<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| %>
|
||||
<% if params[:redir].present? %>
|
||||
<%= hidden_field_tag :redir, params[:redir] %>
|
||||
<% end %>
|
||||
<%= render 'hidden_fields' %>
|
||||
<div class="space-y-2" dir="auto">
|
||||
<div class="form-control">
|
||||
<%= f.label :email, t(:email), class: 'label' %>
|
||||
|
||||
@@ -8,12 +8,7 @@
|
||||
<% if params[:redir].present? %>
|
||||
<%= hidden_field_tag :redir, params[:redir] %>
|
||||
<% end %>
|
||||
<div class="space-y-2">
|
||||
<div class="form-control">
|
||||
<%= f.label :otp_attempt, t('two_factor_code_from_authenticator_app'), class: 'label' %>
|
||||
<%= f.text_field :otp_attempt, autofocus: true, placeholder: 'XXX-XXX', required: true, class: 'base-input' %>
|
||||
</div>
|
||||
</div>
|
||||
<%= 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>
|
||||
|
||||
@@ -173,7 +173,7 @@
|
||||
<%= t('document_download_filename_format') %>
|
||||
</span>
|
||||
<div class="mt-3">
|
||||
<%= f.select :value, [["#{I18n.t('document_name')}.pdf", '{document.name}'], ["#{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()' %>
|
||||
<%= 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>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<div class="collapse-content">
|
||||
<%= form_for AccountConfigs.find_or_initialize_for_key(current_account, AccountConfig::SUBMITTER_DOCUMENTS_COPY_EMAIL_KEY), url: settings_personalization_path, method: :post, html: { autocomplete: 'off', class: 'space-y-4' } do |f| %>
|
||||
<%= f.hidden_field :key %>
|
||||
<%= f.fields_for :value, Struct.new(:subject, :body, :attach_audit_log, :attach_documents).new(*f.object.value.values_at('subject', 'body', 'attach_audit_log', 'attach_documents')) do |ff| %>
|
||||
<%= f.fields_for :value, Struct.new(:subject, :body, :reply_to, :attach_audit_log, :attach_documents).new(*f.object.value.values_at('subject', 'body', 'reply_to', 'attach_audit_log', 'attach_documents')) do |ff| %>
|
||||
<div class="form-control">
|
||||
<%= ff.label :subject, t('subject'), class: 'label' %>
|
||||
<%= ff.text_field :subject, required: true, class: 'base-input', dir: 'auto' %>
|
||||
@@ -24,6 +24,12 @@
|
||||
<%= ff.text_area :body, required: true, class: 'base-input w-full py-2', dir: 'auto' %>
|
||||
</autoresize-textarea>
|
||||
</div>
|
||||
<% if can?(:manage, :reply_to) %>
|
||||
<div class="form-control">
|
||||
<%= ff.label :reply_to, t('reply_to'), class: 'label' %>
|
||||
<%= ff.email_field :reply_to, class: 'base-input', dir: 'auto', placeholder: t(:email) %>
|
||||
</div>
|
||||
<% end %>
|
||||
<div class="flex items-center justify-between pt-2.5 mx-1">
|
||||
<span>
|
||||
<%= t('attach_documents') %>
|
||||
|
||||
@@ -1 +1 @@
|
||||
<% 'stats stat stat-figure stat-title stat-value text-accent' %>
|
||||
<% 'stats stat stat-figure stat-title stat-value text-accent w-fit hover:bg-white' %>
|
||||
@@ -1,3 +1,4 @@
|
||||
<%= render 'shared/navbar_warning' %>
|
||||
<div class="max-w-6xl mb-4 mx-auto px-4 md:px-2 py-3 flex items-center justify-between">
|
||||
<div class="flex items-center space-x-4">
|
||||
<a href="<%= root_path %>" class="text-2xl font-bold items-center flex space-x-2">
|
||||
@@ -49,6 +50,16 @@
|
||||
<% end %>
|
||||
</li>
|
||||
<% end %>
|
||||
<% 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' %>
|
||||
<span class="whitespace-nowrap">
|
||||
<%= t('test_mode') %>
|
||||
</span>
|
||||
</label>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<li>
|
||||
<%= button_to destroy_user_session_path, method: :delete, data: { turbo: false }, class: 'flex items-center' do %>
|
||||
<%= svg_icon('logout', class: 'w-5 h-5 flex-shrink-0 stroke-2 mr-2 inline') %>
|
||||
|
||||
Whitespace-only changes.
Whitespace-only changes.
@@ -90,16 +90,16 @@
|
||||
<% end %>
|
||||
<%= render 'shared/settings_nav_extra2' %>
|
||||
<% if (can?(:manage, EncryptedConfig) && current_user == true_user) || (current_user != true_user && current_account.testing?) %>
|
||||
<li>
|
||||
<%= form_for '', url: testing_account_path, method: current_account.testing? ? :delete : :get, html: { class: 'flex w-full' } do |f| %>
|
||||
<%= form_for '', url: testing_account_path, method: current_account.testing? ? :delete : :get, html: { class: 'w-full' } do |f| %>
|
||||
<li>
|
||||
<label class="flex items-center text-base hover:bg-base-300 w-full justify-between" for="testing_toggle">
|
||||
<span class="mr-2 w-full">
|
||||
<%= t('test_environment') %>
|
||||
<%= t('test_mode') %>
|
||||
</span>
|
||||
<%= f.check_box :testing_toggle, class: 'toggle toggle-sm', checked: current_account.testing?, onchange: 'this.form.requestSubmit()' %>
|
||||
</label>
|
||||
<% end %>
|
||||
</li>
|
||||
</li>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</ul>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="hidden md:flex alert py-1 text-sm font-medium gap-x-2 whitespace-nowrap">
|
||||
<a href="<%= testing_api_settings_path %>" data-turbo-frame="modal" class="link font-semibold flex">
|
||||
<%= svg_icon('code_circle', class: 'w-5 h-5 mr-1') %>
|
||||
<span><%= t('testing_environment') %></span>
|
||||
<span><%= t('test_mode') %></span>
|
||||
</a>
|
||||
<span>
|
||||
|
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<%= form_for '', url: testing_account_path, method: current_account.testing? ? :delete : :get, html: { class: 'flex' } do |f| %>
|
||||
<label class="flex items-center justify-between" for="testing_toggle">
|
||||
<span class="mr-2 text-lg">
|
||||
<%= t('test_environment') %>
|
||||
<%= t('test_mode') %>
|
||||
</span>
|
||||
<%= f.check_box :testing_toggle, class: 'toggle', checked: current_account.testing?, onchange: 'this.form.requestSubmit()' %>
|
||||
</label>
|
||||
|
||||
@@ -118,7 +118,7 @@
|
||||
<div class="group border border-base-300 rounded-md px-2 py-1 mb-1">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center space-x-1">
|
||||
<span class="mx-1 w-3 h-3 shrink-0 rounded-full <%= colors[index] %>"></span>
|
||||
<span class="mx-1 w-3 h-3 shrink-0 rounded-full <%= colors[index % 10] %>"></span>
|
||||
<span class="text-lg" dir="auto">
|
||||
<%= (@submission.template_submitters || @submission.template.submitters).find { |e| e['uuid'] == submitter&.uuid }&.dig('name') || "#{(index + 1).ordinalize} Submitter" %>
|
||||
</span>
|
||||
@@ -190,6 +190,11 @@
|
||||
</a>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if signed_in? && submitter && submitter.completed_at? && submitter.email == current_user.email && submitter.completed_at > 1.month.ago && can?(:update, @submission) %>
|
||||
<div class="mt-2 mb-1">
|
||||
<%= button_to t('resubmit'), submitters_resubmit_path(submitter), method: :put, class: 'btn btn-sm btn-primary w-full', form: { target: '_blank' }, data: { turbo: false } %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-1.5 mb-4">
|
||||
@@ -222,6 +227,8 @@
|
||||
</div>
|
||||
<% elsif field['type'] == 'checkbox' %>
|
||||
<%= svg_icon('check', class: 'w-6 h-6') %>
|
||||
<% elsif field['type'] == 'number' %>
|
||||
<%= NumberUtils.format_number(value, field.dig('preferences', 'format')) %>
|
||||
<% elsif field['type'] == 'date' %>
|
||||
<%= TimeUtils.format_date_string(value, field.dig('preferences', 'format'), @submission.account.locale) %>
|
||||
<% else %>
|
||||
|
||||
@@ -42,7 +42,8 @@
|
||||
</download-button>
|
||||
<% end %>
|
||||
</div>
|
||||
<% if Templates.filter_undefined_submitters(@submitter.submission.template).size == 1 && %w[api embed].exclude?(@submitter.submission.source) && @submitter.account.account_configs.find_or_initialize_by(key: AccountConfig::ALLOW_TO_RESUBMIT).value != false && !@submitter.template.archived_at? %>
|
||||
<% undefined_submitters = Templates.filter_undefined_submitters(@submitter.submission.template) %>
|
||||
<% if undefined_submitters.size == 1 && undefined_submitters.first['uuid'] == @submitter.uuid && %w[api embed].exclude?(@submitter.submission.source) && @submitter.account.account_configs.find_or_initialize_by(key: AccountConfig::ALLOW_TO_RESUBMIT).value != false && !@submitter.template.archived_at? %>
|
||||
<div class="divider uppercase"><%= t('or') %></div>
|
||||
<toggle-submit class="block">
|
||||
<%= button_to button_title(title: t('resubmit'), disabled_with: t('resubmit'), icon: svg_icon('reload', class: 'w-6 h-6')), start_form_path(@submitter.submission.template.slug), params: { submitter: { email: @submitter.email, phone: @submitter.phone, name: @submitter.name }, resubmit: @submitter.slug }, method: :put, class: 'white-button w-full' %>
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<%= f.hidden_field :template_id, value: @template.id %>
|
||||
<div class="flex items-center justify-between">
|
||||
<span>
|
||||
<%= t('share_template_with_test_environment') %>
|
||||
<%= 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()' %>
|
||||
</div>
|
||||
|
||||
@@ -61,3 +61,4 @@
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<%= render 'shared/review_form' %>
|
||||
@@ -118,7 +118,7 @@
|
||||
<%= form_for @template, url: template_preferences_path(@template), method: :post, html: { autocomplete: 'off', class: 'mt-1' }, data: { close_on_submit: false } do |f| %>
|
||||
<toggle-on-submit data-element-id="email_saved_alert2"></toggle-on-submit>
|
||||
<% configs = AccountConfigs.find_or_initialize_for_key(current_account, AccountConfig::SUBMITTER_DOCUMENTS_COPY_EMAIL_KEY).value %>
|
||||
<%= f.fields_for :preferences, Struct.new(:documents_copy_email_subject, :documents_copy_email_body, :documents_copy_email_enabled, :documents_copy_email_attach_audit, :documents_copy_email_attach_documents).new(@template.preferences['documents_copy_email_subject'].presence || configs['subject'], @template.preferences['documents_copy_email_body'].presence || configs['body'], @template.preferences['documents_copy_email_enabled'], configs['attach_audit_log'] != false && @template.preferences['documents_copy_email_attach_audit'] != false, configs['attach_documents'] != false && @template.preferences['documents_copy_email_attach_documents'] != false) do |ff| %>
|
||||
<%= f.fields_for :preferences, Struct.new(:documents_copy_email_reply_to, :documents_copy_email_subject, :documents_copy_email_body, :documents_copy_email_enabled, :documents_copy_email_attach_audit, :documents_copy_email_attach_documents).new(@template.preferences['documents_copy_email_reply_to'].presence || configs['reply_to'], @template.preferences['documents_copy_email_subject'].presence || configs['subject'], @template.preferences['documents_copy_email_body'].presence || configs['body'], @template.preferences['documents_copy_email_enabled'], configs['attach_audit_log'] != false && @template.preferences['documents_copy_email_attach_audit'] != false, configs['attach_documents'] != false && @template.preferences['documents_copy_email_attach_documents'] != false) do |ff| %>
|
||||
<div class="form-control">
|
||||
<%= ff.label :documents_copy_email_subject, t('email_subject'), class: 'label' %>
|
||||
<%= ff.text_field :documents_copy_email_subject, required: true, class: 'base-input', dir: 'auto' %>
|
||||
@@ -134,6 +134,12 @@
|
||||
<%= ff.text_area :documents_copy_email_body, required: true, class: 'base-input w-full py-2', dir: 'auto' %>
|
||||
</autoresize-textarea>
|
||||
</div>
|
||||
<% if can?(:manage, :reply_to) %>
|
||||
<div class="form-control">
|
||||
<%= ff.label :documents_copy_email_reply_to, t('reply_to'), class: 'label' %>
|
||||
<%= ff.email_field :documents_copy_email_reply_to, class: 'base-input', dir: 'auto', placeholder: t(:email) %>
|
||||
</div>
|
||||
<% end %>
|
||||
<div class="flex items-center justify-between pt-2.5 px-1 mb-2">
|
||||
<span>
|
||||
<%= t('attach_documents_to_the_email') %>
|
||||
@@ -308,7 +314,7 @@
|
||||
<%= f.hidden_field :template_id, value: @template.id %>
|
||||
<div class="flex items-center justify-between">
|
||||
<span>
|
||||
<%= t('share_template_with_test_environment') %>
|
||||
<%= 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()' %>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<%= render 'shared/turbo_modal', title: t('testing_environment') do %>
|
||||
<%= render 'shared/turbo_modal', title: t('test_mode') do %>
|
||||
<div>
|
||||
<label class="text-sm font-semibold" for="api_key">
|
||||
x-Auth-Token
|
||||
X-Auth-Token
|
||||
</label>
|
||||
<div class="flex gap-2 mb-4 mt-2">
|
||||
<input id="api_key" type="text" value="<%= current_user.access_token.token %>" class="base-input w-full" autocomplete="off" readonly>
|
||||
|
||||
@@ -5,6 +5,13 @@
|
||||
<option value="editor" disabled><%= t('editor') %></option>
|
||||
<option value="viewer" disabled><%= t('viewer') %></option>
|
||||
<% end %>
|
||||
<% if Docuseal.multitenant? %>
|
||||
<label class="label">
|
||||
<span class="label-text-alt">
|
||||
<%= t('click_here_to_learn_more_about_user_roles_and_permissions_html') %>
|
||||
</span>
|
||||
</label>
|
||||
<% end %>
|
||||
<a class="text-sm mt-3 px-4 py-2 bg-base-300 rounded-full block" target="_blank" href="<%= Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premises" }.to_query}" %>">
|
||||
<%= svg_icon('info_circle', class: 'w-4 h-4 inline align-text-bottom') %>
|
||||
<%= t('unlock_more_user_roles_with_docuseal_pro') %>
|
||||
|
||||
+227
-30
@@ -20,9 +20,11 @@ en: &en
|
||||
language_ko: 한국어
|
||||
hi_there: Hi there
|
||||
thanks: Thanks
|
||||
reply_to: Reply to
|
||||
pending_by_me: Pending by me
|
||||
partially_completed: Partially completed
|
||||
unarchive: Unarchive
|
||||
signed: Signed
|
||||
first_party: 'First Party'
|
||||
remove_filter: Remove filter
|
||||
add: Add
|
||||
@@ -163,8 +165,8 @@ en: &en
|
||||
schedule_account_for_deletion_: Schedule account for deletion?
|
||||
account_information_has_been_updated: Account information has been updated.
|
||||
should_be_a_valid_url: should be a valid URL
|
||||
your_account_removal_request_will_be_processed_within_2_weeks_please_contact_us_if_you_want_to_keep_your_account: Your account removal request will be processed within 2 weeks. Please contact us if you want to keep your account.
|
||||
test_environment: Test Environment
|
||||
your_account_removal_request_will_be_processed_within_2_months_please_contact_us_if_you_want_to_keep_your_account: Your account removal request will be processed within 2 months. Please contact us if you want to keep your account.
|
||||
test_mode: Test mode
|
||||
copy: Copy
|
||||
copied: Copied
|
||||
rotate: Rotate
|
||||
@@ -321,7 +323,6 @@ en: &en
|
||||
embedding: Embedding
|
||||
background_jobs: Background Jobs
|
||||
need_help_ask_a_question_: 'Need help? Ask a question:'
|
||||
testing_environment: Testing Environment
|
||||
exit: Exit
|
||||
leave: Leave
|
||||
impersonated_as: Impersonated as
|
||||
@@ -426,7 +427,7 @@ en: &en
|
||||
api_and_embedding: API and Embedding
|
||||
template_id: Template ID
|
||||
embedding_url: Embedding URL
|
||||
share_template_with_test_environment: Share template with Test Environment
|
||||
share_template_with_test_mode: Share template with Test mode
|
||||
share_template_with_all_tenants: Share template with all Tenants
|
||||
use_following_placeholders_text_: 'Use following placeholders text:'
|
||||
upgrade_plan_to_add_more_users: Upgrade plan to add more users
|
||||
@@ -662,7 +663,33 @@ en: &en
|
||||
policy_links: Policy Links
|
||||
markdown_content_e_g: Markdown content, e.g.
|
||||
privacy_policy: Privacy Policy
|
||||
the_code_has_been_sent_to_your_email: The code has been sent to your email.
|
||||
enter_the_verification_code_from_your_email: Enter the verification code from your email.
|
||||
too_many_attempts: Too many attempts.
|
||||
verification_code: Verification Code
|
||||
resend_code: Resend Code
|
||||
verify_new_sign_in: Verify new sign in
|
||||
use_otp_code_to_sign_in_or_click_the_link_below_html: 'Use <b>%{code}</b> code to sign in or click the link below:'
|
||||
complete_sign_in: Complete Sign In
|
||||
please_reply_to_this_email_if_you_dont_recognize_this_sign_in_attempt: Please reply to this email if you don't recognize this sign in attempt.
|
||||
docuseal_support: DocuSeal Support
|
||||
use_the_edit_form_to_move_it_to_another_team: Use the edit form to move it to another team.
|
||||
too_many_sms_attempts_try_again_in_a_few_seconds: Too many SMS attempts. Try again in a few seconds.
|
||||
number_phone_number_is_invalid: "+%{number} phone number is invalid"
|
||||
make_com_integration: Make.com Integration
|
||||
zapier_integration: Zapier Integration
|
||||
seamlessly_automate_your_document_signing_process_with_make_com: Seamlessly automate your document signing process with Make.com.
|
||||
find_suitable_zapier_templates_to_automate_your_workflow: Find suitable Zapier templates to automate your workflow.
|
||||
get_started: Get started
|
||||
click_here_to_learn_more_about_user_roles_and_permissions_html: '<a href="https://www.docuseal.com/resources/manage-users-and-roles" class="link" rel="noopener noreferrer nofollow" target="_blank">Click here</a> to learn more about user roles and permissions.'
|
||||
count_10_signature_request_emails_sent_this_month_upgrade_to_pro_to_send_unlimited_signature_request_email: '%{count} / 10 signature request emails sent this month. Upgrade to Pro to send unlimited signature request email.'
|
||||
test_mode_emails_limit_will_be_reset_within_24_hours: Test mode emails limit will be reset within 24 hours.
|
||||
on_a_scale_of_1_to_10_how_satisfied_are_you_with_the_docuseal_product_: On a scale of 1 to 10, how satisfied are you with the DocuSeal product?
|
||||
tell_us_more_about_your_experience: Tell us more about your experience
|
||||
extremely_dissatisfied: Extremely Dissatisfied
|
||||
extremely_satisfied: Extremely Satisfied
|
||||
your_pro_plan_payment_is_overdue: Your Pro plan payment is overdue.
|
||||
click_here_to_update_your_payment_details_and_clear_the_invoice_to_ensure_uninterrupted_service_html: <a href="%{url}" class="link">Click here</a> to update your payment details and clear the invoice to ensure uninterrupted service.
|
||||
submission_event_names:
|
||||
send_email_to_html: '<b>Email sent</b> to %{submitter_name}'
|
||||
send_reminder_email_to_html: '<b>Reminder email sent</b> to %{submitter_name}'
|
||||
@@ -701,6 +728,8 @@ en: &en
|
||||
read: Read your data
|
||||
|
||||
es: &es
|
||||
signed: Firmado
|
||||
reply_to: Responder a
|
||||
partially_completed: Parcialmente completado
|
||||
pending_by_me: Pendiente por mi
|
||||
add: Agregar
|
||||
@@ -846,8 +875,8 @@ es: &es
|
||||
schedule_account_for_deletion_: ¿Programar la eliminación de la cuenta?
|
||||
account_information_has_been_updated: La información de la cuenta ha sido actualizada.
|
||||
should_be_a_valid_url: debe ser una URL válida
|
||||
your_account_removal_request_will_be_processed_within_2_weeks_please_contact_us_if_you_want_to_keep_your_account: Tu solicitud de eliminación de cuenta se procesará en un plazo de 2 semanas. Por favor contáctanos si deseas mantener tu cuenta.
|
||||
test_environment: Entorno de prueba
|
||||
your_account_removal_request_will_be_processed_within_2_months_please_contact_us_if_you_want_to_keep_your_account: Tu solicitud de eliminación de cuenta se procesará en un plazo de 2 meses. Por favor contáctanos si deseas mantener tu cuenta.
|
||||
test_mode: Modo de prueba
|
||||
copy: Copiar
|
||||
copied: Copiado
|
||||
rotate: Rotar
|
||||
@@ -1004,7 +1033,6 @@ es: &es
|
||||
embedding: Integración
|
||||
background_jobs: Trabajos asíncronos
|
||||
need_help_ask_a_question_: '¿Necesitas ayuda? Haz una pregunta:'
|
||||
testing_environment: Entorno de pruebas
|
||||
exit: Salir
|
||||
leave: Salir
|
||||
impersonated_as: Impersonado como
|
||||
@@ -1109,7 +1137,7 @@ es: &es
|
||||
api_and_embedding: API e Integración
|
||||
template_id: ID de plantilla
|
||||
embedding_url: URL de integración
|
||||
share_template_with_test_environment: Compartir plantilla con el entorno de pruebas
|
||||
share_template_with_test_mode: Compartir plantilla con el modo de prueba
|
||||
share_template_with_all_tenants: Compartir plantilla con todos los inquilinos
|
||||
use_following_placeholders_text_: 'Usa los siguientes marcadores de posición:'
|
||||
upgrade_plan_to_add_more_users: Actualiza el plan para agregar más usuarios
|
||||
@@ -1345,7 +1373,33 @@ es: &es
|
||||
policy_links: Enlaces de Políticas
|
||||
markdown_content_e_g: Contenido Markdown, por ej.
|
||||
privacy_policy: Política de Privacidad
|
||||
the_code_has_been_sent_to_your_email: El código ha sido enviado a tu correo electrónico.
|
||||
enter_the_verification_code_from_your_email: Ingresa el código de verificación de tu correo electrónico.
|
||||
too_many_attempts: Demasiados intentos.
|
||||
verification_code: Código de Verificación
|
||||
resend_code: Reenviar Código
|
||||
verify_new_sign_in: Verificar nuevo inicio de sesión
|
||||
use_otp_code_to_sign_in_or_click_the_link_below_html: 'Usa el código <b>%{code}</b> para iniciar sesión o haz clic en el enlace a continuación:'
|
||||
complete_sign_in: Completar Inicio de Sesión
|
||||
please_reply_to_this_email_if_you_dont_recognize_this_sign_in_attempt: Responde a este correo electrónico si no reconoces este intento de inicio de sesión.
|
||||
docuseal_support: Soporte DocuSeal
|
||||
use_the_edit_form_to_move_it_to_another_team: Usa el formulario de edición para moverlo a otro equipo.
|
||||
too_many_sms_attempts_try_again_in_a_few_seconds: Demasiados intentos de SMS. Intenta de nuevo en unos segundos.
|
||||
number_phone_number_is_invalid: "El número de teléfono +%{number} no es válido"
|
||||
make_com_integration: Integración con Make.com
|
||||
zapier_integration: Integración con Zapier
|
||||
seamlessly_automate_your_document_signing_process_with_make_com: Automatiza sin problemas tu proceso de firma de documentos con Make.com.
|
||||
find_suitable_zapier_templates_to_automate_your_workflow: Encuentra plantillas de Zapier adecuadas para automatizar tu flujo de trabajo.
|
||||
get_started: Comenzar
|
||||
click_here_to_learn_more_about_user_roles_and_permissions_html: '<a href="https://www.docuseal.com/resources/manage-users-and-roles" class="link" rel="noopener noreferrer nofollow" target="_blank">Haz clic aquí</a> para obtener más información sobre los roles y permisos de usuario.'
|
||||
count_10_signature_request_emails_sent_this_month_upgrade_to_pro_to_send_unlimited_signature_request_email: '%{count} / 10 correos electrónicos de solicitud de firma enviados este mes. Mejora a Pro para enviar solicitudes de firma ilimitadas.'
|
||||
test_mode_emails_limit_will_be_reset_within_24_hours: El límite de correos electrónicos en modo de prueba se restablecerá en 24 horas.
|
||||
on_a_scale_of_1_to_10_how_satisfied_are_you_with_the_docuseal_product_: 'En una escala del 1 al 10, ¿qué tan satisfecho estás con el producto DocuSeal?'
|
||||
tell_us_more_about_your_experience: Cuéntanos más sobre tu experiencia
|
||||
extremely_dissatisfied: Extremadamente insatisfecho
|
||||
extremely_satisfied: Extremadamente satisfecho
|
||||
your_pro_plan_payment_is_overdue: El pago de tu plan Pro está atrasado.
|
||||
click_here_to_update_your_payment_details_and_clear_the_invoice_to_ensure_uninterrupted_service_html: '<a href="%{url}" class="link">Haz clic aquí</a> para actualizar tus datos de pago y liquidar la factura para garantizar un servicio ininterrumpido.'
|
||||
submission_event_names:
|
||||
send_email_to_html: '<b>Correo electrónico enviado</b> a %{submitter_name}'
|
||||
send_reminder_email_to_html: '<b>Correo de recordatorio enviado</b> a %{submitter_name}'
|
||||
@@ -1384,6 +1438,8 @@ es: &es
|
||||
read: Leer tus datos
|
||||
|
||||
it: &it
|
||||
signed: Firmato
|
||||
reply_to: Rispondi a
|
||||
pending_by_me: In sospeso da me
|
||||
add: Aggiungi
|
||||
adding: Aggiungendo
|
||||
@@ -1399,7 +1455,7 @@ it: &it
|
||||
document_name: Nome del Documento
|
||||
unarchive: Ripristina
|
||||
awaiting_completion_by_the_other_party: "In attesa di completamento da parte dell'altra parte"
|
||||
enforce_recipients_order: Aplicar el orden de los destinatarios
|
||||
enforce_recipients_order: "Applicare l'ordine dei destinatari"
|
||||
first_party: 'Prima parte'
|
||||
docuseal_trusted_signature: "Firma Fiduciaria DocuSeal"
|
||||
hello_name: Ciao %{name}
|
||||
@@ -1528,8 +1584,8 @@ it: &it
|
||||
schedule_account_for_deletion_: "Programmare l'eliminazione dell'account?"
|
||||
account_information_has_been_updated: "Le informazioni dell'account sono state aggiornate."
|
||||
should_be_a_valid_url: deve essere un URL valido
|
||||
your_account_removal_request_will_be_processed_within_2_weeks_please_contact_us_if_you_want_to_keep_your_account: "La tua richiesta di rimozione dell'account sarà elaborata entro 2 settimane. Contattaci se desideri mantenere il tuo account."
|
||||
test_environment: Ambiente di test
|
||||
your_account_removal_request_will_be_processed_within_2_months_please_contact_us_if_you_want_to_keep_your_account: "La tua richiesta di rimozione dell'account sarà elaborata entro 2 mesi. Contattaci se desideri mantenere il tuo account."
|
||||
test_mode: Modalità di test
|
||||
copy: Copia
|
||||
copied: Copiato
|
||||
rotate: Ruota
|
||||
@@ -1686,7 +1742,6 @@ it: &it
|
||||
embedding: Incorporazione
|
||||
background_jobs: Attività in background
|
||||
need_help_ask_a_question_: 'Hai bisogno di aiuto? Fai una domanda:'
|
||||
testing_environment: Ambiente di test
|
||||
exit: Esci
|
||||
leave: Lascia
|
||||
impersonated_as: Impersonato come
|
||||
@@ -1791,7 +1846,7 @@ it: &it
|
||||
api_and_embedding: API e Incorporazione
|
||||
template_id: ID modello
|
||||
embedding_url: URL di incorporazione
|
||||
share_template_with_test_environment: "Condividi il modello con l'ambiente di test"
|
||||
share_template_with_test_mode: Condividi modello con la modalità di test
|
||||
share_template_with_all_tenants: Condividi il modello con tutti i tenant
|
||||
use_following_placeholders_text_: 'Usa i seguenti segnaposto:'
|
||||
upgrade_plan_to_add_more_users: Aggiorna il piano per aggiungere più utenti
|
||||
@@ -2027,7 +2082,33 @@ it: &it
|
||||
policy_links: Collegamenti alle Politiche
|
||||
markdown_content_e_g: Contenuto Markdown, ad es.
|
||||
privacy_policy: Politica sulla Privacy
|
||||
the_code_has_been_sent_to_your_email: Il codice è stato inviato alla tua e-mail.
|
||||
enter_the_verification_code_from_your_email: Inserisci il codice di verifica dalla tua e-mail.
|
||||
too_many_attempts: Troppe tentativi.
|
||||
verification_code: Codice di Verifica
|
||||
resend_code: Reinvia Codice
|
||||
verify_new_sign_in: Verifica nuovo accesso
|
||||
use_otp_code_to_sign_in_or_click_the_link_below_html: 'Usa il codice <b>%{code}</b> per accedere o clicca sul link qui sotto:'
|
||||
complete_sign_in: "Completa l'Accesso"
|
||||
please_reply_to_this_email_if_you_dont_recognize_this_sign_in_attempt: Rispondi a questa e-mail se non riconosci questo tentativo di accesso.
|
||||
docuseal_support: Supporto DocuSeal
|
||||
use_the_edit_form_to_move_it_to_another_team: Usa il modulo di modifica per spostarlo in un altro team.
|
||||
too_many_sms_attempts_try_again_in_a_few_seconds: Troppi tentativi di SMS. Riprova tra qualche secondo.
|
||||
number_phone_number_is_invalid: "Il numero di telefono +%{number} non è valido"
|
||||
make_com_integration: Integrazione con Make.com
|
||||
zapier_integration: Integrazione con Zapier
|
||||
seamlessly_automate_your_document_signing_process_with_make_com: Automatizza senza problemi il tuo processo di firma dei documenti con Make.com.
|
||||
find_suitable_zapier_templates_to_automate_your_workflow: Trova modelli Zapier adatti per automatizzare il tuo flusso di lavoro.
|
||||
get_started: Inizia
|
||||
click_here_to_learn_more_about_user_roles_and_permissions_html: '<a href="https://www.docuseal.com/resources/manage-users-and-roles" class="link" rel="noopener noreferrer nofollow" target="_blank">Fai clic qui</a> per saperne di più sui ruoli e le autorizzazioni degli utenti.'
|
||||
count_10_signature_request_emails_sent_this_month_upgrade_to_pro_to_send_unlimited_signature_request_email: '%{count} / 10 e-mail di richiesta di firma inviati questo mese. Passa a Pro per inviare richieste di firma illimitate.'
|
||||
test_mode_emails_limit_will_be_reset_within_24_hours: Il limite di e-mail in modalità di test verrà ripristinato entro 24 ore.
|
||||
on_a_scale_of_1_to_10_how_satisfied_are_you_with_the_docuseal_product_: 'Su una scala da 1 a 10, quanto sei soddisfatto del prodotto DocuSeal?'
|
||||
tell_us_more_about_your_experience: Raccontaci di più sulla tua esperienza
|
||||
extremely_dissatisfied: Estremamente insoddisfatto
|
||||
extremely_satisfied: Estremamente soddisfatto
|
||||
your_pro_plan_payment_is_overdue: Il pagamento del tuo piano Pro è in ritardo.
|
||||
click_here_to_update_your_payment_details_and_clear_the_invoice_to_ensure_uninterrupted_service_html: '<a href="%{url}" class="link">Fai clic qui</a> per aggiornare i tuoi dati di pagamento e saldare la fattura per garantire un servizio ininterrotto.'
|
||||
submission_event_names:
|
||||
send_email_to_html: '<b>E-mail inviato</b> a %{submitter_name}'
|
||||
send_reminder_email_to_html: '<b>E-mail di promemoria inviato</b> a %{submitter_name}'
|
||||
@@ -2066,6 +2147,8 @@ it: &it
|
||||
read: Leggi i tuoi dati
|
||||
|
||||
fr: &fr
|
||||
signed: Signé
|
||||
reply_to: Répondre à
|
||||
partially_completed: Partiellement complété
|
||||
pending_by_me: En attente par moi
|
||||
add: Ajouter
|
||||
@@ -2212,8 +2295,8 @@ fr: &fr
|
||||
schedule_account_for_deletion_: Programmer la suppression du compte?
|
||||
account_information_has_been_updated: Les informations du compte ont été mises à jour.
|
||||
should_be_a_valid_url: doit être une URL valide
|
||||
your_account_removal_request_will_be_processed_within_2_weeks_please_contact_us_if_you_want_to_keep_your_account: Votre demande de suppression du compte sera traitée dans un délai de 2 semaines. Veuillez nous contacter si vous souhaitez conserver votre compte.
|
||||
test_environment: Mode de test
|
||||
your_account_removal_request_will_be_processed_within_2_months_please_contact_us_if_you_want_to_keep_your_account: Votre demande de suppression du compte sera traitée dans un délai de 2 mois. Veuillez nous contacter si vous souhaitez conserver votre compte.
|
||||
test_mode: Mode test
|
||||
copy: Copier
|
||||
copied: Copié
|
||||
rotate: Pivoter
|
||||
@@ -2370,7 +2453,6 @@ fr: &fr
|
||||
embedding: Intégration
|
||||
background_jobs: Tâches en arrière-plan
|
||||
need_help_ask_a_question_: "Besoin d'aide? Posez une question:"
|
||||
testing_environment: Environnement de test
|
||||
exit: Quitter
|
||||
leave: Partir
|
||||
impersonated_as: Usurpé comme
|
||||
@@ -2475,7 +2557,7 @@ fr: &fr
|
||||
api_and_embedding: API et intégration
|
||||
template_id: ID du modèle
|
||||
embedding_url: "URL d'intégration"
|
||||
share_template_with_test_environment: "Partager le modèle avec l'environnement de test"
|
||||
share_template_with_test_mode: Partager le modèle avec le mode test
|
||||
share_template_with_all_tenants: Partager le modèle avec tous les locataires
|
||||
use_following_placeholders_text_: 'Utilisez les espaces réservés suivants:'
|
||||
upgrade_plan_to_add_more_users: "Mettez à jour le plan pour ajouter plus d'utilisateurs"
|
||||
@@ -2685,11 +2767,11 @@ fr: &fr
|
||||
unverified: Non vérifié
|
||||
document: Document
|
||||
completed_at: Terminé le
|
||||
edit_recipient: Modifica Destinatario
|
||||
update_recipient: Aggiorna Destinatario
|
||||
use_international_format_1xxx_: 'Utilizza il formato internazionale: +1xxx...'
|
||||
submitter_cannot_be_updated: Il mittente non può essere aggiornato.
|
||||
at_least_one_field_must_be_filled: Almeno un campo deve essere compilato.
|
||||
edit_recipient: Modifier le destinataire
|
||||
update_recipient: Mettre à jour le destinataire
|
||||
use_international_format_1xxx_: 'Utilisez un format international : +1xxx...'
|
||||
submitter_cannot_be_updated: Le soumissionnaire ne peut pas être mis à jour.
|
||||
at_least_one_field_must_be_filled: Au moins un champ doit être rempli.
|
||||
archived_users: Utilisateurs Archivés
|
||||
embedding_users: Utilisateurs Intégrés
|
||||
view_embedding_users: Voir les Utilisateurs Intégrés
|
||||
@@ -2711,7 +2793,33 @@ fr: &fr
|
||||
policy_links: Liens des Politiques
|
||||
markdown_content_e_g: Contenu Markdown, par ex.
|
||||
privacy_policy: Politique de Confidentialité
|
||||
the_code_has_been_sent_to_your_email: Le code a été envoyé à votre e-mail.
|
||||
enter_the_verification_code_from_your_email: Entrez le code de vérification de votre e-mail.
|
||||
too_many_attempts: Trop de tentatives.
|
||||
verification_code: Code de Vérification
|
||||
resend_code: Renvoyer le Code
|
||||
verify_new_sign_in: Vérifier la nouvelle connexion
|
||||
use_otp_code_to_sign_in_or_click_the_link_below_html: 'Utilisez le code <b>%{code}</b> pour vous connecter ou cliquez sur le lien ci-dessous :'
|
||||
complete_sign_in: Terminer la Connexion
|
||||
please_reply_to_this_email_if_you_dont_recognize_this_sign_in_attempt: Veuillez répondre à cet e-mail si vous ne reconnaissez pas cette tentative de connexion.
|
||||
docuseal_support: Support DocuSeal
|
||||
use_the_edit_form_to_move_it_to_another_team: Utilisez le formulaire de modification pour le déplacer vers une autre équipe.
|
||||
too_many_sms_attempts_try_again_in_a_few_seconds: Trop de tentatives de SMS. Réessayez dans quelques secondes.
|
||||
number_phone_number_is_invalid: "Le numéro de téléphone +%{number} est invalide"
|
||||
make_com_integration: Intégration avec Make.com
|
||||
zapier_integration: Intégration avec Zapier
|
||||
seamlessly_automate_your_document_signing_process_with_make_com: Automatisez facilement votre processus de signature de documents avec Make.com.
|
||||
find_suitable_zapier_templates_to_automate_your_workflow: Trouvez des modèles Zapier adaptés pour automatiser votre flux de travail.
|
||||
get_started: Commencer
|
||||
click_here_to_learn_more_about_user_roles_and_permissions_html: '<a href="https://www.docuseal.com/resources/manage-users-and-roles" class="link" rel="noopener noreferrer nofollow" target="_blank">Cliquez ici</a> pour en savoir plus sur les rôles et les autorisations des utilisateurs.'
|
||||
count_10_signature_request_emails_sent_this_month_upgrade_to_pro_to_send_unlimited_signature_request_email: '%{count} / 10 e-mails de demande de signature envoyés ce mois-ci. Passez à Pro pour envoyer des demandes de signature illimitées.'
|
||||
test_mode_emails_limit_will_be_reset_within_24_hours: La limite d'e-mails en mode test sera réinitialisée dans les 24 heures.
|
||||
on_a_scale_of_1_to_10_how_satisfied_are_you_with_the_docuseal_product_: 'Sur une échelle de 1 à 10, à quel point êtes-vous satisfait du produit DocuSeal?'
|
||||
tell_us_more_about_your_experience: Parlez-nous davantage de votre expérience
|
||||
extremely_dissatisfied: Extrêmement insatisfait
|
||||
extremely_satisfied: Extrêmement satisfait
|
||||
your_pro_plan_payment_is_overdue: Le paiement de votre plan Pro est en retard.
|
||||
click_here_to_update_your_payment_details_and_clear_the_invoice_to_ensure_uninterrupted_service_html: '<a href="%{url}" class="link">Cliquez ici</a> pour mettre à jour vos informations de paiement et régler la facture afin de garantir un service ininterrompu.'
|
||||
submission_event_names:
|
||||
send_email_to_html: '<b>E-mail envoyé</b> à %{submitter_name}'
|
||||
send_reminder_email_to_html: '<b>E-mail de rappel envoyé</b> à %{submitter_name}'
|
||||
@@ -2750,6 +2858,8 @@ fr: &fr
|
||||
read: Lire vos données
|
||||
|
||||
pt: &pt
|
||||
signed: Assinado
|
||||
reply_to: Responder a
|
||||
partially_completed: Parcialmente concluído
|
||||
pending_by_me: Pendente por mim
|
||||
add: Adicionar
|
||||
@@ -2895,8 +3005,8 @@ pt: &pt
|
||||
schedule_account_for_deletion_: Agendar exclusão da conta?
|
||||
account_information_has_been_updated: As informações da conta foram atualizadas.
|
||||
should_be_a_valid_url: deve ser um URL válido
|
||||
your_account_removal_request_will_be_processed_within_2_weeks_please_contact_us_if_you_want_to_keep_your_account: Seu pedido de remoção da conta será processado em até 2 semanas. Entre em contato conosco se você quiser manter sua conta.
|
||||
test_environment: Ambiente de teste
|
||||
your_account_removal_request_will_be_processed_within_2_months_please_contact_us_if_you_want_to_keep_your_account: Seu pedido de remoção da conta será processado em até 2 meses. Entre em contato conosco se você quiser manter sua conta.
|
||||
test_mode: Modo de teste
|
||||
copy: Copiar
|
||||
copied: Copiado
|
||||
rotate: Girar
|
||||
@@ -3053,7 +3163,6 @@ pt: &pt
|
||||
embedding: Incorporação
|
||||
background_jobs: Trabalhos assíncronos
|
||||
need_help_ask_a_question_: 'Precisa de ajuda? Faça uma pergunta:'
|
||||
testing_environment: Ambiente de teste
|
||||
exit: Sair
|
||||
leave: Deixar
|
||||
impersonated_as: Impersonado como
|
||||
@@ -3158,7 +3267,7 @@ pt: &pt
|
||||
api_and_embedding: API e Incorporação
|
||||
template_id: ID do modelo
|
||||
embedding_url: URL de incorporação
|
||||
share_template_with_test_environment: Compartilhar modelo com ambiente de teste
|
||||
share_template_with_test_mode: Compartilhar modelo com o modo de teste
|
||||
share_template_with_all_tenants: Compartilhar modelo com todos os locatários
|
||||
use_following_placeholders_text_: 'Use os seguintes textos de substituição:'
|
||||
upgrade_plan_to_add_more_users: Faça upgrade do plano para adicionar mais usuários
|
||||
@@ -3394,7 +3503,33 @@ pt: &pt
|
||||
policy_links: Links de Políticas
|
||||
markdown_content_e_g: Conteúdo Markdown, ex.
|
||||
privacy_policy: Política de Privacidade
|
||||
the_code_has_been_sent_to_your_email: O código foi enviado para seu e-mail.
|
||||
enter_the_verification_code_from_your_email: Insira o código de verificação do seu e-mail.
|
||||
too_many_attempts: Muitas tentativas.
|
||||
verification_code: Código de Verificação
|
||||
resend_code: Reenviar Código
|
||||
verify_new_sign_in: Verificar novo login
|
||||
use_otp_code_to_sign_in_or_click_the_link_below_html: 'Use o código <b>%{code}</b> para entrar ou clique no link abaixo:'
|
||||
complete_sign_in: Concluir Login
|
||||
please_reply_to_this_email_if_you_dont_recognize_this_sign_in_attempt: Responda a este e-mail se você não reconhecer esta tentativa de login.
|
||||
docuseal_support: Suporte DocuSeal
|
||||
use_the_edit_form_to_move_it_to_another_team: Use o formulário de edição para movê-lo para outra equipe.
|
||||
too_many_sms_attempts_try_again_in_a_few_seconds: Muitas tentativas de SMS. Tente novamente em alguns segundos.
|
||||
number_phone_number_is_invalid: "O número de telefone +%{number} é inválido"
|
||||
make_com_integration: Integração com Make.com
|
||||
zapier_integration: Integração com Zapier
|
||||
seamlessly_automate_your_document_signing_process_with_make_com: Automatize perfeitamente seu processo de assinatura de documentos com o Make.com.
|
||||
find_suitable_zapier_templates_to_automate_your_workflow: Encontre modelos Zapier adequados para automatizar seu fluxo de trabalho.
|
||||
get_started: Começar
|
||||
click_here_to_learn_more_about_user_roles_and_permissions_html: '<a href="https://www.docuseal.com/resources/manage-users-and-roles" class="link" rel="noopener noreferrer nofollow" target="_blank">Clique aqui</a> para saber mais sobre os papéis e permissões dos usuários.'
|
||||
count_10_signature_request_emails_sent_this_month_upgrade_to_pro_to_send_unlimited_signature_request_email: '%{count} / 10 e-mails de solicitação de assinatura enviados este mês. Faça upgrade para Pro para enviar solicitações de assinatura ilimitadas.'
|
||||
test_mode_emails_limit_will_be_reset_within_24_hours: O limite de e-mails no modo de teste será redefinido em 24 horas.
|
||||
on_a_scale_of_1_to_10_how_satisfied_are_you_with_the_docuseal_product_: 'Em uma escala de 1 a 10, quão satisfeito você está com o produto DocuSeal?'
|
||||
tell_us_more_about_your_experience: Conte-nos mais sobre sua experiência
|
||||
extremely_dissatisfied: Extremamente insatisfeito
|
||||
extremely_satisfied: Extremamente satisfeito
|
||||
your_pro_plan_payment_is_overdue: O pagamento do seu plano Pro está atrasado.
|
||||
click_here_to_update_your_payment_details_and_clear_the_invoice_to_ensure_uninterrupted_service_html: '<a href="%{url}" class="link">Clique aqui</a> para atualizar seus dados de pagamento e quitar a fatura para garantir um serviço ininterrupto.'
|
||||
submission_event_names:
|
||||
send_email_to_html: '<b>E-mail enviado</b> para %{submitter_name}'
|
||||
send_reminder_email_to_html: '<b>E-mail de lembrete enviado</b> para %{submitter_name}'
|
||||
@@ -3433,6 +3568,8 @@ pt: &pt
|
||||
read: Ler seus dados
|
||||
|
||||
de: &de
|
||||
signed: Unterschrieben
|
||||
reply_to: Antworten auf
|
||||
partially_completed: Teilweise abgeschlossen
|
||||
pending_by_me: Ausstehend von mir
|
||||
add: Hinzufügen
|
||||
@@ -3578,8 +3715,8 @@ de: &de
|
||||
schedule_account_for_deletion_: Konto zur Löschung einplanen?
|
||||
account_information_has_been_updated: Die Kontoinformationen wurden aktualisiert.
|
||||
should_be_a_valid_url: sollte eine gültige URL sein
|
||||
your_account_removal_request_will_be_processed_within_2_weeks_please_contact_us_if_you_want_to_keep_your_account: Deine Anfrage zur Kontolöschung wird innerhalb von 2 Wochen bearbeitet. Bitte kontaktiere uns, wenn du dein Konto behalten möchtest.
|
||||
test_environment: Testumgebung
|
||||
your_account_removal_request_will_be_processed_within_2_months_please_contact_us_if_you_want_to_keep_your_account: Deine Anfrage zur Kontolöschung wird innerhalb von 2 Monaten bearbeitet. Bitte kontaktiere uns, wenn du dein Konto behalten möchtest.
|
||||
test_mode: Testmodus
|
||||
copy: Kopieren
|
||||
copied: Kopiert
|
||||
rotate: Drehen
|
||||
@@ -3736,7 +3873,6 @@ de: &de
|
||||
embedding: Einbettung
|
||||
background_jobs: Hintergrundaufgaben
|
||||
need_help_ask_a_question_: 'Brauchen Sie Hilfe? Stellen Sie eine Frage:'
|
||||
testing_environment: Testumgebung
|
||||
exit: Beenden
|
||||
leave: Verlassen
|
||||
impersonated_as: Angemeldet als
|
||||
@@ -3841,7 +3977,7 @@ de: &de
|
||||
api_and_embedding: API und Einbettung
|
||||
template_id: Vorlagen-ID
|
||||
embedding_url: Einbettungs-URL
|
||||
share_template_with_test_environment: Vorlage mit Testumgebung teilen
|
||||
share_template_with_test_mode: Vorlage mit dem Testmodus teilen
|
||||
share_template_with_all_tenants: Vorlage mit allen Mietern teilen
|
||||
use_following_placeholders_text_: 'Verwende die folgenden Platzhaltertexte:'
|
||||
upgrade_plan_to_add_more_users: Upgrade des Plans, um weitere Benutzer hinzuzufügen
|
||||
@@ -4077,7 +4213,33 @@ de: &de
|
||||
policy_links: Richtlinien-Links
|
||||
markdown_content_e_g: Markdown-Inhalt, z. B.
|
||||
privacy_policy: Datenschutzrichtlinie
|
||||
the_code_has_been_sent_to_your_email: Der Code wurde an Ihre E-Mail gesendet.
|
||||
enter_the_verification_code_from_your_email: Geben Sie den Verifizierungscode aus Ihrer E-Mail ein.
|
||||
too_many_attempts: Zu viele Versuche.
|
||||
verification_code: Verifizierungscode
|
||||
resend_code: Code erneut senden
|
||||
verify_new_sign_in: Neue Anmeldung überprüfen
|
||||
use_otp_code_to_sign_in_or_click_the_link_below_html: 'Verwenden Sie den Code <b>%{code}</b>, um sich anzumelden, oder klicken Sie auf den untenstehenden Link:'
|
||||
complete_sign_in: Anmeldung abschließen
|
||||
please_reply_to_this_email_if_you_dont_recognize_this_sign_in_attempt: Bitte antworten Sie auf diese E-Mail, wenn Sie diesen Anmeldeversuch nicht erkennen.
|
||||
docuseal_support: DocuSeal Support
|
||||
use_the_edit_form_to_move_it_to_another_team: Verwenden Sie das Bearbeitungsformular, um ihn in ein anderes Team zu verschieben.
|
||||
too_many_sms_attempts_try_again_in_a_few_seconds: Zu viele SMS-Versuche. Versuchen Sie es in ein paar Sekunden erneut.
|
||||
number_phone_number_is_invalid: "Die Telefonnummer +%{number} ist ungültig"
|
||||
make_com_integration: Make.com-Integration
|
||||
zapier_integration: Zapier-Integration
|
||||
seamlessly_automate_your_document_signing_process_with_make_com: Automatisieren Sie Ihren Dokumentenunterzeichnungsprozess nahtlos mit Make.com.
|
||||
find_suitable_zapier_templates_to_automate_your_workflow: Finden Sie passende Zapier-Vorlagen, um Ihren Workflow zu automatisieren.
|
||||
get_started: Loslegen
|
||||
click_here_to_learn_more_about_user_roles_and_permissions_html: '<a href="https://www.docuseal.com/resources/manage-users-and-roles" class="link" rel="noopener noreferrer nofollow" target="_blank">Klicken Sie hier</a>, um mehr über Benutzerrollen und -berechtigungen zu erfahren.'
|
||||
count_10_signature_request_emails_sent_this_month_upgrade_to_pro_to_send_unlimited_signature_request_email: '%{count} / 10 Signaturanfrage-E-Mails wurden diesen Monat gesendet. Upgraden Sie zu Pro, um unbegrenzte Signaturanfragen zu senden.'
|
||||
test_mode_emails_limit_will_be_reset_within_24_hours: Das Limit für E-Mails im Testmodus wird innerhalb von 24 Stunden zurückgesetzt.
|
||||
on_a_scale_of_1_to_10_how_satisfied_are_you_with_the_docuseal_product_: 'Auf einer Skala von 1 bis 10, wie zufrieden sind Sie mit dem DocuSeal-Produkt?'
|
||||
tell_us_more_about_your_experience: Erzählen Sie uns mehr über Ihre Erfahrung
|
||||
extremely_dissatisfied: Extrem unzufrieden
|
||||
extremely_satisfied: Extrem zufrieden
|
||||
your_pro_plan_payment_is_overdue: Ihre Zahlung für den Pro-Plan ist überfällig.
|
||||
click_here_to_update_your_payment_details_and_clear_the_invoice_to_ensure_uninterrupted_service_html: '<a href="%{url}" class="link">Klicken Sie hier</a>, um Ihre Zahlungsdaten zu aktualisieren und die Rechnung zu begleichen, um einen unterbrechungsfreien Service sicherzustellen.'
|
||||
submission_event_names:
|
||||
send_email_to_html: '<b>E-Mail gesendet</b> an %{submitter_name}'
|
||||
send_reminder_email_to_html: '<b>Erinnerungs-E-Mail gesendet</b> an %{submitter_name}'
|
||||
@@ -4168,6 +4330,11 @@ pl:
|
||||
sign_up_with_microsoft: Zarejestruj się przez Microsoft
|
||||
by_creating_an_account_you_agree_to_our_html: 'Tworząc konto, akceptujesz naszą <a target="_blank" href="https://www.docuseal.com/privacy">Politykę Prywatności</a> i <a target="_blank" href="https://www.docuseal.com/terms">Regulamin</a>.'
|
||||
enter_email_to_continue: Wprowadź e-mail, aby kontynuować
|
||||
the_code_has_been_sent_to_your_email: Kod został wysłany na Twój e-mail.
|
||||
enter_the_verification_code_from_your_email: Wprowadź kod weryfikacyjny z Twojego e-maila.
|
||||
too_many_attempts: Zbyt wiele prób.
|
||||
verification_code: Kod Weryfikacyjny
|
||||
resend_code: Wyślij Kod Ponownie
|
||||
|
||||
uk:
|
||||
awaiting_completion_by_the_other_party: "Очікує завершення з боку іншої сторони"
|
||||
@@ -4222,6 +4389,11 @@ uk:
|
||||
sign_up_with_microsoft: Зареєструватися через Microsoft
|
||||
by_creating_an_account_you_agree_to_our_html: 'Створюючи акаунт, ви погоджуєтесь з нашою <a target="_blank" href="https://www.docuseal.com/privacy">Політикою конфіденційності</a> і <a target="_blank" href="https://www.docuseal.com/terms">Умовами надання послуг</a>.'
|
||||
enter_email_to_continue: Введіть електронну пошту, щоб продовжити
|
||||
the_code_has_been_sent_to_your_email: Код було надіслано на вашу електронну пошту.
|
||||
enter_the_verification_code_from_your_email: Введіть код перевірки з вашої електронної пошти.
|
||||
too_many_attempts: Забагато спроб.
|
||||
verification_code: Код перевірки
|
||||
resend_code: Відправити код знову
|
||||
|
||||
cs:
|
||||
awaiting_completion_by_the_other_party: "Čeká se na dokončení druhou stranou"
|
||||
@@ -4276,6 +4448,11 @@ cs:
|
||||
sign_up_with_microsoft: Zaregistrovat se pomocí Microsoftu
|
||||
by_creating_an_account_you_agree_to_our_html: 'Vytvořením účtu souhlasíte s našimi <a target="_blank" href="https://www.docuseal.com/privacy">Zásadami ochrany osobních údajů</a> a <a target="_blank" href="https://www.docuseal.com/terms">Podmínkami služby</a>.'
|
||||
enter_email_to_continue: Zadejte e-mail pro pokračování
|
||||
the_code_has_been_sent_to_your_email: Kód byl odeslán na váš e-mail.
|
||||
enter_the_verification_code_from_your_email: Zadejte ověřovací kód z vašeho e-mailu.
|
||||
too_many_attempts: Příliš mnoho pokusů.
|
||||
verification_code: Ověřovací Kód
|
||||
resend_code: Znovu Odeslat Kód
|
||||
|
||||
he:
|
||||
awaiting_completion_by_the_other_party: "המתנה להשלמה מצד הצד השני"
|
||||
@@ -4330,6 +4507,11 @@ he:
|
||||
sign_up_with_microsoft: הירשם עם מיקרוסופט
|
||||
by_creating_an_account_you_agree_to_our_html: 'על ידי יצירת חשבון, אתה מסכים ל<a target="_blank" href="https://www.docuseal.com/privacy">מדיניות הפרטיות</a> ול<a target="_blank" href="https://www.docuseal.com/terms">תנאי השירות</a> שלנו.'
|
||||
enter_email_to_continue: הכנס דוא"ל כדי להמשיך
|
||||
the_code_has_been_sent_to_your_email: הקוד נשלח לדוא"ל שלך.
|
||||
enter_the_verification_code_from_your_email: הזן את קוד האימות מדוא"ל שלך.
|
||||
too_many_attempts: יותר מדי ניסיונות.
|
||||
verification_code: קוד אימות
|
||||
resend_code: שלח קוד מחדש
|
||||
|
||||
nl:
|
||||
awaiting_completion_by_the_other_party: "In afwachting van voltooiing door de andere partij"
|
||||
@@ -4384,6 +4566,11 @@ nl:
|
||||
sign_up_with_microsoft: Aanmelden met Microsoft
|
||||
by_creating_an_account_you_agree_to_our_html: 'Door een account aan te maken, ga je akkoord met ons <a target="_blank" href="https://www.docuseal.com/privacy">Privacybeleid</a> en onze <a target="_blank" href="https://www.docuseal.com/terms">Gebruiksvoorwaarden</a>.'
|
||||
enter_email_to_continue: Voer e-mail in om door te gaan
|
||||
the_code_has_been_sent_to_your_email: De code is naar uw e-mail verzonden.
|
||||
enter_the_verification_code_from_your_email: Voer de verificatiecode uit uw e-mail in.
|
||||
too_many_attempts: Te veel pogingen.
|
||||
verification_code: Verificatiecode
|
||||
resend_code: Code Opnieuw Verzenden
|
||||
|
||||
ar:
|
||||
awaiting_completion_by_the_other_party: "في انتظار إكتمال الطرف الآخر"
|
||||
@@ -4438,6 +4625,11 @@ ar:
|
||||
sign_up_with_microsoft: الاشتراك باستخدام مايكروسوفت
|
||||
by_creating_an_account_you_agree_to_our_html: 'من خلال إنشاء حساب، فإنك توافق على <a target="_blank" href="https://www.docuseal.com/privacy">سياسة الخصوصية</a> و<a target="_blank" href="https://www.docuseal.com/terms">شروط الخدمة</a> الخاصة بنا.'
|
||||
enter_email_to_continue: أدخل البريد الإلكتروني للمتابعة
|
||||
the_code_has_been_sent_to_your_email: تم إرسال الرمز إلى بريدك الإلكتروني.
|
||||
enter_the_verification_code_from_your_email: أدخل رمز التحقق من بريدك الإلكتروني.
|
||||
too_many_attempts: عدد المحاولات كبير جدًا.
|
||||
verification_code: رمز التحقق
|
||||
resend_code: إعادة إرسال الرمز
|
||||
|
||||
ko:
|
||||
awaiting_completion_by_the_other_party: "다른 당사자의 완료를 기다리고 있습니다"
|
||||
@@ -4492,6 +4684,11 @@ ko:
|
||||
sign_up_with_microsoft: Microsoft로 가입
|
||||
by_creating_an_account_you_agree_to_our_html: '계정을 생성함으로써, 귀하는 우리의 <a target="_blank" href="https://www.docuseal.com/privacy">개인정보 보호정책</a> 및 <a target="_blank" href="https://www.docuseal.com/terms">서비스 약관</a>에 동의하는 것입니다.'
|
||||
enter_email_to_continue: 계속하려면 이메일을 입력하세요
|
||||
the_code_has_been_sent_to_your_email: 코드가 이메일로 전송되었습니다.
|
||||
enter_the_verification_code_from_your_email: 이메일에서 인증 코드를 입력하세요.
|
||||
too_many_attempts: 시도 횟수가 너무 많습니다.
|
||||
verification_code: 인증 코드
|
||||
resend_code: 코드 재전송
|
||||
|
||||
en-US:
|
||||
<<: *en
|
||||
|
||||
@@ -79,6 +79,7 @@ Rails.application.routes.draw do
|
||||
resource :testing_account, only: %i[show destroy]
|
||||
resources :testing_api_settings, only: %i[index]
|
||||
resources :submitters_autocomplete, only: %i[index]
|
||||
resources :submitters_resubmit, only: %i[update]
|
||||
resources :template_folders_autocomplete, only: %i[index]
|
||||
resources :webhook_secret, only: %i[show update]
|
||||
resources :webhook_preferences, only: %i[update]
|
||||
|
||||
+13
-2
@@ -4,7 +4,16 @@ module NumberUtils
|
||||
FORMAT_LOCALES = {
|
||||
'dot' => 'de',
|
||||
'space' => 'fr',
|
||||
'comma' => 'en'
|
||||
'comma' => 'en',
|
||||
'usd' => 'en',
|
||||
'eur' => 'fr',
|
||||
'gbp' => 'en'
|
||||
}.freeze
|
||||
|
||||
CURRENCY_SYMBOLS = {
|
||||
'usd' => '$',
|
||||
'eur' => '€',
|
||||
'gbp' => '£'
|
||||
}.freeze
|
||||
|
||||
module_function
|
||||
@@ -12,7 +21,9 @@ module NumberUtils
|
||||
def format_number(number, format)
|
||||
locale = FORMAT_LOCALES[format]
|
||||
|
||||
if locale
|
||||
if CURRENCY_SYMBOLS[format]
|
||||
ApplicationController.helpers.number_to_currency(number, locale:, precision: 2, unit: CURRENCY_SYMBOLS[format])
|
||||
elsif locale
|
||||
ApplicationController.helpers.number_with_delimiter(number, locale:)
|
||||
else
|
||||
number
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module SendWebhookRequest
|
||||
USER_AGENT = 'DocuSeal.com Webhook'
|
||||
|
||||
module_function
|
||||
|
||||
def call(webhook_url, event_type:, data:)
|
||||
Faraday.post(webhook_url.url) do |req|
|
||||
req.headers['Content-Type'] = 'application/json'
|
||||
req.headers['User-Agent'] = USER_AGENT
|
||||
req.headers.merge!(webhook_url.secret.to_h) if webhook_url.secret.present?
|
||||
|
||||
req.body = {
|
||||
event_type: event_type,
|
||||
timestamp: Time.current,
|
||||
data: data
|
||||
}.to_json
|
||||
|
||||
req.options.read_timeout = 8
|
||||
req.options.open_timeout = 8
|
||||
end
|
||||
rescue Faraday::Error
|
||||
nil
|
||||
end
|
||||
end
|
||||
+18
-11
@@ -107,20 +107,27 @@ module Submissions
|
||||
|
||||
def normalize_email(email)
|
||||
return if email.blank?
|
||||
return email.downcase if email.to_s.include?(',')
|
||||
return email.downcase if email.to_s.include?('.gob')
|
||||
return email.downcase if email.to_s.include?('.om')
|
||||
return email.downcase if email.to_s.include?('.mm')
|
||||
return email.downcase if email.to_s.include?('.cm')
|
||||
return email.downcase if email.to_s.include?('.et')
|
||||
return email.downcase if email.to_s.include?('.mo')
|
||||
return email.downcase if email.to_s.include?('.nz')
|
||||
return email.downcase if email.to_s.include?('.za')
|
||||
return email.downcase unless email.to_s.include?('.')
|
||||
|
||||
return email.downcase if email.to_s.include?(',') ||
|
||||
email.to_s.match?(/\.(?:gob|om|mm|cm|et|mo|nz|za|ie)\z/) ||
|
||||
email.to_s.exclude?('.')
|
||||
|
||||
fixed_email = EmailTypo.call(email.delete_prefix('<'))
|
||||
|
||||
Rails.logger.info("Fixed email #{email.split('@').last}") if fixed_email != email.downcase.delete_prefix('<').strip
|
||||
return fixed_email if fixed_email == email
|
||||
|
||||
domain = email.to_s.split('@').last.to_s.downcase
|
||||
fixed_domain = fixed_email.to_s.split('@').last
|
||||
|
||||
return email.downcase if domain == fixed_domain
|
||||
|
||||
if DidYouMean::Levenshtein.distance(domain, fixed_domain) > 3
|
||||
Rails.logger.info("Skipped email fix #{domain}")
|
||||
|
||||
return email.downcase
|
||||
end
|
||||
|
||||
Rails.logger.info("Fixed email #{domain}") if fixed_email != email.downcase.delete_prefix('<').strip
|
||||
|
||||
fixed_email
|
||||
end
|
||||
|
||||
@@ -31,7 +31,8 @@ module Submissions
|
||||
next if uuid.blank?
|
||||
next if submitter_attrs.slice('email', 'phone', 'name').compact_blank.blank?
|
||||
|
||||
submission.template_submitters << template.submitters.find { |e| e['uuid'] == uuid }
|
||||
template_submitter = template.submitters.find { |e| e['uuid'] == uuid }
|
||||
submission.template_submitters << template_submitter.except('optional_invite_by_uuid', 'invite_by_uuid')
|
||||
|
||||
is_order_sent = submitters_order == 'random' || index.zero?
|
||||
|
||||
@@ -54,11 +55,17 @@ module Submissions
|
||||
# rubocop:enable Metrics/BlockLength
|
||||
|
||||
def maybe_add_invite_submitters(submission, template)
|
||||
template.submitters.each do |item|
|
||||
template.submitters.each_with_index do |item, index|
|
||||
next if item['invite_by_uuid'].blank? && item['optional_invite_by_uuid'].blank?
|
||||
next if submission.template_submitters.any? { |e| e['uuid'] == item['uuid'] }
|
||||
|
||||
submission.template_submitters << item
|
||||
if index.zero?
|
||||
submission.template_submitters.insert(1, item)
|
||||
elsif submission.template_submitters.size > index
|
||||
submission.template_submitters.insert(index, item)
|
||||
else
|
||||
submission.template_submitters << item
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -175,7 +175,7 @@ module Submissions
|
||||
padding: [15, 0, 8, 0],
|
||||
position: :float)
|
||||
|
||||
unless submission.source.in?(%w[embed api])
|
||||
if show_verify?(submission)
|
||||
column.formatted_text([{ link: verify_url, text: I18n.t('verify'), style: :link }],
|
||||
font_size: 9, padding: [15, 0, 10, 0], position: :float, text_align: :right)
|
||||
end
|
||||
@@ -425,6 +425,10 @@ module Submissions
|
||||
|
||||
def maybe_add_background(_canvas, _submission, _page_size); end
|
||||
|
||||
def show_verify?(submission)
|
||||
!submission.source.in?(%w[embed api])
|
||||
end
|
||||
|
||||
def add_logo(column, _submission = nil)
|
||||
column.image(PdfIcons.logo_io, width: 40, height: 40, position: :float)
|
||||
|
||||
|
||||
+13
-1
@@ -123,10 +123,22 @@ module Submitters
|
||||
filename = ReplaceEmailVariables.call(filename_format, submitter:)
|
||||
|
||||
filename = filename.gsub('{document.name}', blob.filename.base)
|
||||
filename = filename.gsub(' - {submission.status}') do
|
||||
if submitter.submission.submitters.all?(&:completed_at?)
|
||||
status =
|
||||
if submitter.submission.template_fields.any? { |f| f['type'] == 'signature' }
|
||||
I18n.t(:signed)
|
||||
else
|
||||
I18n.t(:completed)
|
||||
end
|
||||
|
||||
" - #{status}"
|
||||
end
|
||||
end
|
||||
|
||||
filename = filename.gsub(
|
||||
'{submission.completed_at}',
|
||||
I18n.l(submitter.completed_at.beginning_of_year.in_time_zone(submitter.account.timezone), format: :short)
|
||||
I18n.l(submitter.completed_at.in_time_zone(submitter.account.timezone), format: :short)
|
||||
)
|
||||
|
||||
"#{filename}.#{blob.filename.extension}"
|
||||
|
||||
@@ -54,8 +54,8 @@ module Submitters
|
||||
submitter.ip = request.remote_ip
|
||||
submitter.ua = request.user_agent
|
||||
submitter.values = merge_default_values(submitter)
|
||||
submitter.values = merge_formula_values(submitter)
|
||||
submitter.values = maybe_remove_condition_values(submitter)
|
||||
submitter.values = merge_formula_values(submitter)
|
||||
submitter.values = submitter.values.transform_values do |v|
|
||||
v == '{{date}}' ? Time.current.in_time_zone(submitter.account.timezone).to_date.to_s : v
|
||||
end
|
||||
|
||||
@@ -85,7 +85,7 @@ RSpec.describe SendFormCompletedWebhookRequestJob do
|
||||
stub_request(:post, webhook_url.url).to_return(status: 401)
|
||||
|
||||
expect do
|
||||
described_class.new.perform('submitter_id' => submitter.id, 'webhook_url_id' => webhook_url.id, 'attempt' => 11)
|
||||
described_class.new.perform('submitter_id' => submitter.id, 'webhook_url_id' => webhook_url.id, 'attempt' => 21)
|
||||
end.not_to change(described_class.jobs, :size)
|
||||
|
||||
expect(WebMock).to have_requested(:post, webhook_url.url).once
|
||||
|
||||
@@ -83,7 +83,7 @@ RSpec.describe SendSubmissionCompletedWebhookRequestJob do
|
||||
|
||||
expect do
|
||||
described_class.new.perform('submission_id' => submission.id, 'webhook_url_id' => webhook_url.id,
|
||||
'attempt' => 11)
|
||||
'attempt' => 21)
|
||||
end.not_to change(described_class.jobs, :size)
|
||||
|
||||
expect(WebMock).to have_requested(:post, webhook_url.url).once
|
||||
|
||||
@@ -71,6 +71,10 @@ RSpec.configure do |config|
|
||||
Sidekiq::Testing.inline! if example.metadata[:sidekiq] == :inline
|
||||
end
|
||||
|
||||
config.after do |example|
|
||||
Sidekiq::Testing.fake! if example.metadata[:sidekiq] == :inline
|
||||
end
|
||||
|
||||
config.before(multitenant: true) do
|
||||
allow(Docuseal).to receive(:multitenant?).and_return(true)
|
||||
end
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'Sign In', type: :system do
|
||||
let(:account) { create(:account) }
|
||||
let!(:user) { create(:user, account:, email: 'john.dou@example.com', password: 'strong_password') }
|
||||
|
||||
before do
|
||||
visit new_user_session_path
|
||||
end
|
||||
|
||||
context 'when only with email and password' do
|
||||
it 'signs in successfully with valid email and password' do
|
||||
fill_in 'Email', with: 'john.dou@example.com'
|
||||
fill_in 'Password', with: 'strong_password'
|
||||
click_button 'Sign In'
|
||||
|
||||
expect(page).to have_content('Signed in successfully')
|
||||
expect(page).to have_content('Document Templates')
|
||||
end
|
||||
|
||||
it "doesn't sign in if the email or password are incorrect" do
|
||||
fill_in 'Email', with: 'john.dou@example.com'
|
||||
fill_in 'Password', with: 'wrong_password'
|
||||
click_button 'Sign In'
|
||||
|
||||
expect(page).to have_content('Invalid Email or password')
|
||||
expect(page).not_to have_content('Document Templates')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when 2FA is required' do
|
||||
before do
|
||||
user.update(otp_required_for_login: true, otp_secret: User.generate_otp_secret)
|
||||
end
|
||||
|
||||
it 'signs in successfully with valid OTP code' do
|
||||
fill_in 'Email', with: 'john.dou@example.com'
|
||||
fill_in 'Password', with: 'strong_password'
|
||||
click_button 'Sign In'
|
||||
fill_in 'Two-Factor Code from Authenticator App', with: user.current_otp
|
||||
click_button 'Sign In'
|
||||
|
||||
expect(page).to have_content('Signed in successfully')
|
||||
expect(page).to have_content('Document Templates')
|
||||
end
|
||||
|
||||
it 'fails to sign in with invalid OTP code' do
|
||||
fill_in 'Email', with: 'john.dou@example.com'
|
||||
fill_in 'Password', with: 'strong_password'
|
||||
click_button 'Sign In'
|
||||
fill_in 'Two-Factor Code from Authenticator App', with: '123456'
|
||||
click_button 'Sign In'
|
||||
|
||||
expect(page).to have_content('Invalid Email or password')
|
||||
expect(page).not_to have_content('Document Templates')
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in new issue
Block a user