Compare commits

..
36 Commits
Author SHA1 Message Date
Alex Turchyn 288d204d8e Merge from docusealco/wip 2025-01-13 13:00:50 +02:00
Pete Matsyburka 5a96f4f1eb adjust ios mobile 2025-01-11 14:02:04 +02:00
Alex Turchyn 5ff4f9889e add user reviews 2025-01-11 13:09:38 +02:00
Alex Turchyn 78fb93fd02 fix detection of unknown mobile IOS devices 2025-01-11 13:06:21 +02:00
Pete Matsyburka 3c83d459bd add document page attributes 2025-01-10 20:33:23 +02:00
Pete Matsyburka 6cdb8e7132 fix draw cursor 2025-01-10 20:33:16 +02:00
Pete Matsyburka e2b93cbcab fix colors 2025-01-10 15:12:09 +02:00
Pete Matsyburka a59cc7c44e verification redirect 2025-01-10 11:50:54 +02:00
Pete Matsyburka 1984b99e04 fix formula with conditions 2025-01-09 22:25:46 +02:00
Pete Matsyburka fe3e8fba3e fix readonly formula values 2025-01-09 12:26:56 +02:00
Pete Matsyburka 97ecdf2ff0 fix invite submitters api order 2025-01-09 01:09:26 +02:00
Alex Turchyn e4c62087d0 fix translations 2025-01-08 18:17:46 +02:00
Pete Matsyburka 1f9faddfd0 add reply to 2025-01-07 20:38:56 +02:00
Pete Matsyburka 6dff6483bf fix default field title 2025-01-07 17:30:21 +02:00
Pete Matsyburka 2e43eb189f fix define submitters order 2025-01-07 13:04:59 +02:00
Alex Turchyn cee471bfa5 Merge from docusealco/wip 2025-01-06 12:18:51 +02:00
Pete Matsyburka 81521dee81 add ability to reorder fields 2025-01-04 14:20:47 +02:00
Pete Matsyburka 3cac62d0fd fix typo 2025-01-04 01:42:25 +02:00
Pete Matsyburka 24ed3f2e7c filter submissions api 2025-01-04 01:31:54 +02:00
Alex Turchyn 02ad620c9f add translations 2025-01-04 01:10:22 +02:00
Alex Turchyn 9051d5a835 add a text hint under user roles 2025-01-03 18:39:50 +02:00
Alex Turchyn 0abd270f56 update translations 2025-01-03 01:09:29 +02:00
Pete Matsyburka fde9affd70 fix send to next 2025-01-02 23:32:24 +02:00
Pete Matsyburka 4edee2971a superadmin file download 2025-01-02 22:42:08 +02:00
Pete Matsyburka 313d7f8408 adjust response 2025-01-02 22:42:02 +02:00
Alex Turchyn db60810272 add email OTP 2025-01-01 14:38:24 +02:00
Pete Matsyburka b3507c1a3a adjust test toggle size 2024-12-31 19:57:41 +02:00
Alex Turchyn 19b5a07bac Add 'Test mode' switch to the profile dropdown 2024-12-31 19:36:14 +02:00
Pete Matsyburka 616aced4e4 fix spec 2024-12-30 21:38:02 +02:00
Alex Turchyn cc218fc0b0 Merge from docusealco/wip 2024-12-30 13:32:14 +02:00
Pete Matsyburka c59f2a022a increase retries 2024-12-28 16:58:20 +02:00
Pete Matsyburka 2a240cebeb allow to move fields between pages 2024-12-28 14:03:09 +02:00
Pete Matsyburka b950b1c63e ruby 3.4.1 2024-12-27 01:02:45 +02:00
Pete Matsyburka 4ad0b04b7f remove sleep 2024-12-25 12:51:41 +02:00
Pete Matsyburka 8edf6405b0 adjust param validation 2024-12-24 00:29:25 +02:00
Pete Matsyburka 8e3caae849 render headings 2024-12-23 22:58:24 +02:00
52 changed files with 626 additions and 163 deletions

No files matched your search

+3 -3
View File
@@ -11,7 +11,7 @@ jobs:
- name: Install Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 3.3.4
ruby-version: 3.4.1
- name: Cache gems
uses: actions/cache@v1
with:
@@ -35,7 +35,7 @@ jobs:
- name: Install Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 3.3.4
ruby-version: 3.4.1
- name: Cache gems
uses: actions/cache@v1
with:
@@ -100,7 +100,7 @@ jobs:
- name: Install Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 3.3.4
ruby-version: 3.4.1
- name: Set up Node
uses: actions/setup-node@v1
with:
+3 -3
View File
@@ -1,4 +1,4 @@
FROM ruby:3.3.4-alpine as fonts
FROM ruby:3.4.1-alpine as fonts
WORKDIR /fonts
@@ -6,7 +6,7 @@ RUN apk --no-cache add fontforge wget && wget https://github.com/satbyy/go-noto-
RUN fontforge -lang=py -c 'font1 = fontforge.open("FreeSans.ttf"); font2 = fontforge.open("NotoSansSymbols2-Regular.ttf"); font1.mergeFonts(font2); font1.generate("FreeSans.ttf")'
FROM ruby:3.3.4-alpine as webpack
FROM ruby:3.4.1-alpine as webpack
ENV RAILS_ENV=production
ENV NODE_ENV=production
@@ -32,7 +32,7 @@ COPY ./app/views ./app/views
RUN echo "gem 'shakapacker'" > Gemfile && ./bin/shakapacker
FROM ruby:3.3.4-alpine as app
FROM ruby:3.4.1-alpine as app
ENV RAILS_ENV=production
ENV BUNDLE_WITHOUT="development:test"
+1 -1
View File
@@ -2,7 +2,7 @@
source 'https://rubygems.org'
ruby '3.3.4'
ruby '3.4.1'
gem 'arabic-letter-connector', require: 'arabic-letter-connector/logic'
gem 'aws-sdk-s3', require: false
+1 -1
View File
@@ -634,7 +634,7 @@ DEPENDENCIES
webmock
RUBY VERSION
ruby 3.3.4p94
ruby 3.4.1p0
BUNDLED WITH
2.5.3
@@ -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
+16 -2
View File
@@ -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
@@ -178,7 +192,7 @@ module Api
key = params.key?(:submission) ? :submission : :submissions
params.permit(
key => [permitted_attrs]
{ key => [permitted_attrs] }, { key => permitted_attrs }
).fetch(key, [])
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
+2 -1
View File
@@ -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)
+4 -1
View File
@@ -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
+2 -1
View File
@@ -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)
+15 -3
View 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 isMobileSafariIos = 'ontouchstart' in window && navigator.maxTouchPoints > 0 && /AppleWebKit/i.test(navigator.userAgent)
if (isMobileSafariIos || /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())
@@ -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 () {
+34 -27
View File
@@ -2,6 +2,7 @@
<div
class="absolute overflow-visible group"
:style="positionStyle"
:class="{ 'z-[1]': isMoved || isDragged }"
@pointerdown.stop
@mousedown="startMouseMove"
@touchstart="startTouchDrag"
@@ -9,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)"
@@ -19,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
@@ -69,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"
@@ -160,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
@@ -211,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"
@@ -316,6 +317,11 @@ export default {
required: false,
default: false
},
maxPage: {
type: Number,
required: false,
default: null
},
defaultField: {
type: Object,
required: false,
@@ -414,16 +420,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',
@@ -438,16 +434,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',
@@ -720,12 +706,18 @@ export default {
const rect = page.getBoundingClientRect()
this.area.x = Math.min(Math.max((this.dragFrom.x + e.touches[0].clientX - rect.left) / rect.width, 0), 1 - this.area.w)
this.area.y = Math.min(Math.max((this.dragFrom.y + e.touches[0].clientY - rect.top) / rect.height, 0), 1 - this.area.h)
this.area.y = (this.dragFrom.y + e.touches[0].clientY - rect.top) / rect.height
if ((this.area.page === 0 && this.area.y < 0) || (this.area.page === this.maxPage && this.area.y > 1 - this.area.h)) {
this.area.y = Math.min(Math.max(this.area.y, 0), 1 - this.area.h)
}
},
stopTouchDrag () {
this.$el.getRootNode().removeEventListener('touchmove', this.touchDrag)
this.$el.getRootNode().removeEventListener('touchend', this.stopTouchDrag)
this.maybeChangeAreaPage(this.area)
if (this.isDragged) {
this.save()
}
@@ -773,12 +765,18 @@ export default {
const rect = page.getBoundingClientRect()
this.area.x = Math.min(Math.max((this.dragFrom.x + e.clientX - rect.left) / rect.width, 0), 1 - this.area.w)
this.area.y = Math.min(Math.max((this.dragFrom.y + e.clientY - rect.top) / rect.height, 0), 1 - this.area.h)
this.area.y = (this.dragFrom.y + e.clientY - rect.top) / rect.height
if ((this.area.page === 0 && this.area.y < 0) || (this.area.page === this.maxPage && this.area.y > 1 - this.area.h)) {
this.area.y = Math.min(Math.max(this.area.y, 0), 1 - this.area.h)
}
},
stopMouseMove (e) {
this.$el.getRootNode().removeEventListener('mousemove', this.mouseMove)
this.$el.getRootNode().removeEventListener('mouseup', this.stopMouseMove)
this.maybeChangeAreaPage(this.area)
if (this.isMoved) {
this.save()
}
@@ -788,6 +786,15 @@ export default {
this.$emit('stop-drag')
},
maybeChangeAreaPage (area) {
if (area.y < -(area.h / 2)) {
area.page -= 1
area.y = 1 + area.y + (16.0 / this.$parent.$refs.mask.previousSibling.offsetHeight)
} else if (area.y > 1 - (area.h / 2)) {
area.page += 1
area.y = area.y - 1 - (16.0 / this.$parent.$refs.mask.previousSibling.offsetHeight)
}
},
stopDrag () {
this.$el.getRootNode().removeEventListener('mousemove', this.drag)
this.$el.getRootNode().removeEventListener('mouseup', this.stopDrag)
+95 -3
View File
@@ -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"
/>
@@ -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()
},
@@ -7,6 +7,7 @@
:input-mode="inputMode"
:number="index"
:editable="editable"
:data-page="index"
:areas="areasIndex[index]"
:allow-draw="allowDraw"
:is-drag="isDrag"
@@ -16,6 +17,7 @@
:draw-field="drawField"
:draw-field-type="drawFieldType"
:selected-submitter="selectedSubmitter"
:total-pages="sortedPreviewImages.length"
:image="image"
@drop-field="$emit('drop-field', {...$event, attachment_uuid: document.uuid })"
@remove-area="$emit('remove-area', $event)"
+1 -1
View File
@@ -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"
@@ -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"
@@ -153,7 +153,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 }}
@@ -275,16 +275,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',
+8 -1
View File
@@ -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) => {
+6
View File
@@ -1,4 +1,5 @@
const en = {
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 +159,7 @@ const en = {
}
const es = {
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 +319,7 @@ const es = {
}
const it = {
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 +479,7 @@ const it = {
}
const pt = {
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 +639,7 @@ const pt = {
}
const fr = {
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 +799,7 @@ const fr = {
}
const de = {
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',
+18 -3
View File
@@ -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,8 +26,9 @@
: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"
@stop-resize="resizeDirection = null"
@remove="$emit('remove-area', item.area)"
@@ -88,6 +90,10 @@ export default {
required: false,
default: false
},
totalPages: {
type: Number,
required: true
},
drawFieldType: {
type: String,
required: false,
@@ -137,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
@@ -149,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 {
+44 -27
View File
@@ -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"
>
&uarr;
</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)"
>
&darr;
</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)"
>
&uarr;
</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)"
>
&darr;
</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
@@ -7,7 +7,7 @@ class SendFormCompletedWebhookRequestJob
USER_AGENT = 'DocuSeal.com Webhook'
MAX_ATTEMPTS = 10
MAX_ATTEMPTS = 20
def perform(params = {})
submitter = Submitter.find(params['submitter_id'])
+4 -2
View File
@@ -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+@/, '@')
@@ -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>
+1 -3
View File
@@ -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' %>
+1 -6
View File
@@ -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>
@@ -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
View File
@@ -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' %>
+10
View File
@@ -49,6 +49,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.
+5 -5
View File
@@ -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>
+1 -1
View File
@@ -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>
|
+1 -1
View File
@@ -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>
+1 -1
View File
@@ -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>
+7
View File
@@ -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') %>
+203 -24
View File
@@ -20,6 +20,7 @@ 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
@@ -164,7 +165,7 @@ en: &en
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
test_mode: Test mode
copy: Copy
copied: Copied
rotate: Rotate
@@ -321,7 +322,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 +426,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 +662,31 @@ 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
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 +725,7 @@ en: &en
read: Read your data
es: &es
reply_to: Responder a
partially_completed: Parcialmente completado
pending_by_me: Pendiente por mi
add: Agregar
@@ -847,7 +872,7 @@ es: &es
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
test_mode: Modo de prueba
copy: Copiar
copied: Copiado
rotate: Rotar
@@ -1004,7 +1029,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 +1133,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 +1369,31 @@ 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
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 +1432,7 @@ es: &es
read: Leer tus datos
it: &it
reply_to: Rispondi a
pending_by_me: In sospeso da me
add: Aggiungi
adding: Aggiungendo
@@ -1399,7 +1448,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}
@@ -1529,7 +1578,7 @@ it: &it
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
test_mode: Modalità di test
copy: Copia
copied: Copiato
rotate: Ruota
@@ -1686,7 +1735,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 +1839,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 +2075,31 @@ 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
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 +2138,7 @@ it: &it
read: Leggi i tuoi dati
fr: &fr
reply_to: Répondre à
partially_completed: Partiellement complété
pending_by_me: En attente par moi
add: Ajouter
@@ -2213,7 +2286,7 @@ fr: &fr
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
test_mode: Mode test
copy: Copier
copied: Copié
rotate: Pivoter
@@ -2370,7 +2443,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 +2547,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 +2757,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 +2783,31 @@ 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
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 +2846,7 @@ fr: &fr
read: Lire vos données
pt: &pt
reply_to: Responder a
partially_completed: Parcialmente concluído
pending_by_me: Pendente por mim
add: Adicionar
@@ -2896,7 +2993,7 @@ pt: &pt
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
test_mode: Modo de teste
copy: Copiar
copied: Copiado
rotate: Girar
@@ -3053,7 +3150,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 +3254,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 +3490,31 @@ 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
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 +3553,7 @@ pt: &pt
read: Ler seus dados
de: &de
reply_to: Antworten auf
partially_completed: Teilweise abgeschlossen
pending_by_me: Ausstehend von mir
add: Hinzufügen
@@ -3579,7 +3700,7 @@ de: &de
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
test_mode: Testmodus
copy: Kopieren
copied: Kopiert
rotate: Drehen
@@ -3736,7 +3857,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 +3961,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 +4197,31 @@ 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
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 +4312,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 +4371,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 +4430,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 +4489,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 +4548,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 +4607,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 +4666,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
+10 -3
View File
@@ -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
@@ -26,9 +26,9 @@ module Submissions
submission.submitters.where(completed_at: nil)
end
submitters.preload(attachments_attachments: :blob).each do |s|
submitters.preload(attachments_attachments: :blob).each_with_index do |s, index|
GenerateResultAttachments.fill_submitter_fields(s, submission.account, pdfs_index,
with_signature_id:, is_flatten:)
with_signature_id:, is_flatten:, with_headings: index.zero?)
end
template = submission.template
@@ -147,15 +147,18 @@ module Submissions
fill_submitter_fields(submitter, submitter.account, pdfs_index, with_signature_id:, is_flatten:)
end
def fill_submitter_fields(submitter, account, pdfs_index, with_signature_id:, is_flatten:)
def fill_submitter_fields(submitter, account, pdfs_index, with_signature_id:, is_flatten:, with_headings: nil)
cell_layouter = HexaPDF::Layout::TextLayouter.new(text_valign: :center, text_align: :center)
attachments_data_cache = {}
return pdfs_index if submitter.submission.template_fields.blank?
with_headings = find_last_submitter(submitter.submission, submitter:).blank? if with_headings.nil?
submitter.submission.template_fields.each do |field|
next if field['submitter_uuid'] != submitter.uuid
next if field['type'] == 'heading' && !with_headings
next if field['submitter_uuid'] != submitter.uuid && field['type'] != 'heading'
field.fetch('areas', []).each do |area|
pdf = pdfs_index[area['attachment_uuid']]
@@ -188,6 +191,7 @@ module Submissions
font = pdf.fonts.add(field.dig('preferences', 'font').presence || FONT_NAME)
value = submitter.values[field['uuid']]
value = field['default_value'] if field['type'] == 'heading'
text_align = field.dig('preferences', 'align').to_s.to_sym.presence ||
(value.to_s.match?(RTL_REGEXP) ? :right : :left)
+1 -1
View File
@@ -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
+4
View File
@@ -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
+1 -1
View File
@@ -19,7 +19,7 @@ module SigningFormHelper
ctx.lineTo(150, 150);
ctx.stroke();
JS
sleep 1
sleep 0.5
end
def field_value(submitter, field_name)
+1 -1
View File
@@ -32,7 +32,7 @@ RSpec.describe 'App Setup' do
expect do
click_button 'Submit'
sleep 2
page.driver.wait_for_network_idle
end.to change(Account, :count).by(1).and change(User, :count).by(1).and change(EncryptedConfig, :count).by(2)
user = User.last
+60
View File
@@ -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