Merge from docusealco/wip
This commit is contained in:
+1
-1
@@ -50,7 +50,7 @@ ENV OPENSSL_CONF=/app/openssl_legacy.cnf
|
|||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
RUN echo '@edge https://dl-cdn.alpinelinux.org/alpine/edge/community' >> /etc/apk/repositories && apk add --no-cache sqlite-dev libpq-dev mariadb-dev vips-dev@edge redis libheif@edge vips-heif gcompat ttf-freefont && mkdir /fonts && rm /usr/share/fonts/freefont/FreeSans.otf
|
RUN echo '@edge https://dl-cdn.alpinelinux.org/alpine/edge/community' >> /etc/apk/repositories && apk add --no-cache sqlite-dev libpq-dev mariadb-dev vips-dev@edge redis libheif@edge vips-heif@edge gcompat ttf-freefont && mkdir /fonts && rm /usr/share/fonts/freefont/FreeSans.otf
|
||||||
|
|
||||||
RUN echo $'.include = /etc/ssl/openssl.cnf\n\
|
RUN echo $'.include = /etc/ssl/openssl.cnf\n\
|
||||||
\n\
|
\n\
|
||||||
|
|||||||
@@ -100,6 +100,7 @@ module Api
|
|||||||
permitted_params = [
|
permitted_params = [
|
||||||
:name,
|
:name,
|
||||||
:external_id,
|
:external_id,
|
||||||
|
:shared_link,
|
||||||
{
|
{
|
||||||
submitters: [%i[name uuid is_requester invite_by_uuid optional_invite_by_uuid linked_to_uuid email]],
|
submitters: [%i[name uuid is_requester invite_by_uuid optional_invite_by_uuid linked_to_uuid email]],
|
||||||
fields: [[:uuid, :submitter_uuid, :name, :type,
|
fields: [[:uuid, :submitter_uuid, :name, :type,
|
||||||
|
|||||||
@@ -55,6 +55,14 @@ class ApplicationController < ActionController::Base
|
|||||||
request.session[:impersonated_user_id] = user.uuid
|
request.session[:impersonated_user_id] = user.uuid
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def pagy_auto(collection, **keyword_args)
|
||||||
|
if current_ability.can?(:manage, :countless)
|
||||||
|
pagy_countless(collection, **keyword_args)
|
||||||
|
else
|
||||||
|
pagy(collection, **keyword_args)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def with_locale(&)
|
def with_locale(&)
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ class EmailSmtpSettingsController < ApplicationController
|
|||||||
|
|
||||||
def create
|
def create
|
||||||
if @encrypted_config.update(email_configs)
|
if @encrypted_config.update(email_configs)
|
||||||
SettingsMailer.smtp_successful_setup(@encrypted_config.value['from_email'] || current_user.email).deliver_now!
|
unless Docuseal.multitenant?
|
||||||
|
SettingsMailer.smtp_successful_setup(@encrypted_config.value['from_email'] || current_user.email).deliver_now!
|
||||||
|
end
|
||||||
|
|
||||||
redirect_to settings_email_index_path, notice: I18n.t('changes_have_been_saved')
|
redirect_to settings_email_index_path, notice: I18n.t('changes_have_been_saved')
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -11,15 +11,15 @@ class SendSubmissionEmailController < ApplicationController
|
|||||||
|
|
||||||
def create
|
def create
|
||||||
if params[:template_slug]
|
if params[:template_slug]
|
||||||
@submitter = Submitter.joins(submission: :template).find_by!(email: params[:email].to_s.downcase,
|
@submitter = Submitter.completed.joins(submission: :template).find_by!(email: params[:email].to_s.downcase,
|
||||||
template: { slug: params[:template_slug] })
|
template: { slug: params[:template_slug] })
|
||||||
elsif params[:submission_slug]
|
elsif params[:submission_slug]
|
||||||
@submitter = Submitter.joins(:submission).find_by(email: params[:email].to_s.downcase,
|
@submitter = Submitter.completed.joins(:submission).find_by(email: params[:email].to_s.downcase,
|
||||||
submission: { slug: params[:submission_slug] })
|
submission: { slug: params[:submission_slug] })
|
||||||
|
|
||||||
return redirect_to submissions_preview_completed_path(params[:submission_slug], status: :error) unless @submitter
|
return redirect_to submissions_preview_completed_path(params[:submission_slug], status: :error) unless @submitter
|
||||||
else
|
else
|
||||||
@submitter = Submitter.find_by!(slug: params[:submitter_slug])
|
@submitter = Submitter.completed.find_by!(slug: params[:submitter_slug])
|
||||||
end
|
end
|
||||||
|
|
||||||
RateLimit.call("send-email-#{@submitter.id}", limit: 2, ttl: 5.minutes)
|
RateLimit.call("send-email-#{@submitter.id}", limit: 2, ttl: 5.minutes)
|
||||||
|
|||||||
@@ -8,20 +8,28 @@ class StartFormController < ApplicationController
|
|||||||
|
|
||||||
around_action :with_browser_locale, only: %i[show completed]
|
around_action :with_browser_locale, only: %i[show completed]
|
||||||
before_action :maybe_redirect_com, only: %i[show completed]
|
before_action :maybe_redirect_com, only: %i[show completed]
|
||||||
|
before_action :load_resubmit_submitter, only: :update
|
||||||
before_action :load_template
|
before_action :load_template
|
||||||
|
before_action :authorize_start!, only: :update
|
||||||
|
|
||||||
def show
|
def show
|
||||||
raise ActionController::RoutingError, I18n.t('not_found') if @template.preferences['require_phone_2fa'] == true
|
raise ActionController::RoutingError, I18n.t('not_found') if @template.preferences['require_phone_2fa']
|
||||||
|
|
||||||
@submitter = @template.submissions.new(account_id: @template.account_id)
|
if @template.shared_link?
|
||||||
.submitters.new(account_id: @template.account_id,
|
@submitter = @template.submissions.new(account_id: @template.account_id)
|
||||||
uuid: (filter_undefined_submitters(@template).first ||
|
.submitters.new(account_id: @template.account_id,
|
||||||
@template.submitters.first)['uuid'])
|
uuid: (filter_undefined_submitters(@template).first ||
|
||||||
|
@template.submitters.first)['uuid'])
|
||||||
|
else
|
||||||
|
Rollbar.warning("Not shared template: #{@template.id}") if defined?(Rollbar)
|
||||||
|
|
||||||
|
return render :private if current_user && current_ability.can?(:read, @template)
|
||||||
|
|
||||||
|
raise ActionController::RoutingError, I18n.t('not_found')
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
return redirect_to start_form_path(@template.slug) if @template.archived_at?
|
|
||||||
|
|
||||||
@submitter = find_or_initialize_submitter(@template, submitter_params)
|
@submitter = find_or_initialize_submitter(@template, submitter_params)
|
||||||
|
|
||||||
if @submitter.completed_at?
|
if @submitter.completed_at?
|
||||||
@@ -59,6 +67,8 @@ class StartFormController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def completed
|
def completed
|
||||||
|
return redirect_to start_form_path(@template.slug) if !@template.shared_link? || @template.archived_at?
|
||||||
|
|
||||||
@submitter = Submitter.where(submission: @template.submissions)
|
@submitter = Submitter.where(submission: @template.submissions)
|
||||||
.where.not(completed_at: nil)
|
.where.not(completed_at: nil)
|
||||||
.find_by!(email: params[:email])
|
.find_by!(email: params[:email])
|
||||||
@@ -66,6 +76,24 @@ class StartFormController < ApplicationController
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def load_resubmit_submitter
|
||||||
|
@resubmit_submitter =
|
||||||
|
if params[:resubmit].present? && !params[:resubmit].in?([true, 'true'])
|
||||||
|
Submitter.find_by(slug: params[:resubmit])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def authorize_start!
|
||||||
|
return redirect_to start_form_path(@template.slug) if @template.archived_at?
|
||||||
|
|
||||||
|
return if @resubmit_submitter
|
||||||
|
return if @template.shared_link? || (current_user && current_ability.can?(:read, @template))
|
||||||
|
|
||||||
|
Rollbar.warning("Not shared template: #{@template.id}") if defined?(Rollbar)
|
||||||
|
|
||||||
|
redirect_to start_form_path(@template.slug)
|
||||||
|
end
|
||||||
|
|
||||||
def enqueue_submission_create_webhooks(submitter)
|
def enqueue_submission_create_webhooks(submitter)
|
||||||
WebhookUrls.for_account_id(submitter.account_id, 'submission.created').each do |webhook_url|
|
WebhookUrls.for_account_id(submitter.account_id, 'submission.created').each do |webhook_url|
|
||||||
SendSubmissionCreatedWebhookRequestJob.perform_async('submission_id' => submitter.submission_id,
|
SendSubmissionCreatedWebhookRequestJob.perform_async('submission_id' => submitter.submission_id,
|
||||||
@@ -74,31 +102,29 @@ class StartFormController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def find_or_initialize_submitter(template, submitter_params)
|
def find_or_initialize_submitter(template, submitter_params)
|
||||||
Submitter.where(submission: template.submissions.where(expire_at: Time.current..)
|
Submitter
|
||||||
.or(template.submissions.where(expire_at: nil)).where(archived_at: nil))
|
.where(submission: template.submissions.where(expire_at: Time.current..)
|
||||||
.order(id: :desc)
|
.or(template.submissions.where(expire_at: nil)).where(archived_at: nil))
|
||||||
.where(declined_at: nil)
|
.order(id: :desc)
|
||||||
.where(external_id: nil)
|
.where(declined_at: nil)
|
||||||
.where(ip: [nil, request.remote_ip])
|
.where(external_id: nil)
|
||||||
.then { |rel| params[:resubmit].present? ? rel.where(completed_at: nil) : rel }
|
.where(ip: [nil, request.remote_ip])
|
||||||
.find_or_initialize_by(email: submitter_params[:email], **submitter_params.compact_blank)
|
.then { |rel| params[:resubmit].present? || params[:selfsign].present? ? rel.where(completed_at: nil) : rel }
|
||||||
|
.find_or_initialize_by(email: submitter_params[:email], **submitter_params.compact_blank)
|
||||||
end
|
end
|
||||||
|
|
||||||
def assign_submission_attributes(submitter, template)
|
def assign_submission_attributes(submitter, template)
|
||||||
resubmit_submitter =
|
|
||||||
(Submitter.where(submission: template.submissions).find_by(slug: params[:resubmit]) if params[:resubmit].present?)
|
|
||||||
|
|
||||||
submitter.assign_attributes(
|
submitter.assign_attributes(
|
||||||
uuid: (filter_undefined_submitters(template).first || @template.submitters.first)['uuid'],
|
uuid: (filter_undefined_submitters(template).first || @template.submitters.first)['uuid'],
|
||||||
ip: request.remote_ip,
|
ip: request.remote_ip,
|
||||||
ua: request.user_agent,
|
ua: request.user_agent,
|
||||||
values: resubmit_submitter&.preferences&.fetch('default_values', nil) || {},
|
values: @resubmit_submitter&.preferences&.fetch('default_values', nil) || {},
|
||||||
preferences: resubmit_submitter&.preferences.presence || { 'send_email' => true },
|
preferences: @resubmit_submitter&.preferences.presence || { 'send_email' => true },
|
||||||
metadata: resubmit_submitter&.metadata.presence || {}
|
metadata: @resubmit_submitter&.metadata.presence || {}
|
||||||
)
|
)
|
||||||
|
|
||||||
if submitter.values.present?
|
if submitter.values.present?
|
||||||
resubmit_submitter.attachments.each do |attachment|
|
@resubmit_submitter.attachments.each do |attachment|
|
||||||
submitter.attachments << attachment.dup if submitter.values.value?(attachment.uuid)
|
submitter.attachments << attachment.dup if submitter.values.value?(attachment.uuid)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -120,15 +146,21 @@ class StartFormController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def submitter_params
|
def submitter_params
|
||||||
|
return current_user.slice(:email) if params[:selfsign]
|
||||||
|
return @resubmit_submitter.slice(:name, :phone, :email) if @resubmit_submitter.present?
|
||||||
|
|
||||||
params.require(:submitter).permit(:email, :phone, :name).tap do |attrs|
|
params.require(:submitter).permit(:email, :phone, :name).tap do |attrs|
|
||||||
attrs[:email] = Submissions.normalize_email(attrs[:email])
|
attrs[:email] = Submissions.normalize_email(attrs[:email])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def load_template
|
def load_template
|
||||||
slug = params[:slug] || params[:start_form_slug]
|
@template =
|
||||||
|
if @resubmit_submitter
|
||||||
@template = Template.find_by!(slug:)
|
@resubmit_submitter.template
|
||||||
|
else
|
||||||
|
Template.find_by!(slug: params[:slug] || params[:start_form_slug])
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def multiple_submitters_error_message
|
def multiple_submitters_error_message
|
||||||
|
|||||||
@@ -18,6 +18,6 @@ class SubmissionsArchivedController < ApplicationController
|
|||||||
@submissions.order(id: :desc)
|
@submissions.order(id: :desc)
|
||||||
end
|
end
|
||||||
|
|
||||||
@pagy, @submissions = pagy(@submissions.preload(submitters: :start_form_submission_events))
|
@pagy, @submissions = pagy_auto(@submissions.preload(submitters: :start_form_submission_events))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -19,6 +19,6 @@ class SubmissionsDashboardController < ApplicationController
|
|||||||
@submissions.order(id: :desc)
|
@submissions.order(id: :desc)
|
||||||
end
|
end
|
||||||
|
|
||||||
@pagy, @submissions = pagy(@submissions.preload(submitters: :start_form_submission_events))
|
@pagy, @submissions = pagy_auto(@submissions.preload(submitters: :start_form_submission_events))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ class TemplateFoldersController < ApplicationController
|
|||||||
@templates = Templates.search(@templates, params[:q])
|
@templates = Templates.search(@templates, params[:q])
|
||||||
@templates = Templates::Order.call(@templates, current_user, cookies.permanent[:dashboard_templates_order])
|
@templates = Templates::Order.call(@templates, current_user, cookies.permanent[:dashboard_templates_order])
|
||||||
|
|
||||||
@pagy, @templates = pagy(@templates, limit: 12)
|
@pagy, @templates = pagy_auto(@templates, limit: 12)
|
||||||
end
|
end
|
||||||
|
|
||||||
def edit; end
|
def edit; end
|
||||||
|
|||||||
@@ -7,6 +7,6 @@ class TemplatesArchivedController < ApplicationController
|
|||||||
@templates = @templates.where.not(archived_at: nil).preload(:author, :folder, :template_accesses).order(id: :desc)
|
@templates = @templates.where.not(archived_at: nil).preload(:author, :folder, :template_accesses).order(id: :desc)
|
||||||
@templates = Templates.search(@templates, params[:q])
|
@templates = Templates.search(@templates, params[:q])
|
||||||
|
|
||||||
@pagy, @templates = pagy(@templates, limit: 12)
|
@pagy, @templates = pagy_auto(@templates, limit: 12)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ class TemplatesArchivedSubmissionsController < ApplicationController
|
|||||||
@submissions.order(id: :desc)
|
@submissions.order(id: :desc)
|
||||||
end
|
end
|
||||||
|
|
||||||
@pagy, @submissions = pagy(@submissions.preload(submitters: :start_form_submission_events))
|
@pagy, @submissions = pagy_auto(@submissions.preload(submitters: :start_form_submission_events))
|
||||||
rescue ActiveRecord::RecordNotFound
|
rescue ActiveRecord::RecordNotFound
|
||||||
redirect_to root_path
|
redirect_to root_path
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class TemplatesController < ApplicationController
|
|||||||
submissions.order(id: :desc)
|
submissions.order(id: :desc)
|
||||||
end
|
end
|
||||||
|
|
||||||
@pagy, @submissions = pagy(submissions.preload(:template_accesses, submitters: :start_form_submission_events))
|
@pagy, @submissions = pagy_auto(submissions.preload(:template_accesses, submitters: :start_form_submission_events))
|
||||||
rescue ActiveRecord::RecordNotFound
|
rescue ActiveRecord::RecordNotFound
|
||||||
redirect_to root_path
|
redirect_to root_path
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ class TemplatesDashboardController < ApplicationController
|
|||||||
|
|
||||||
@pagy, @template_folders = pagy(
|
@pagy, @template_folders = pagy(
|
||||||
@template_folders,
|
@template_folders,
|
||||||
items: FOLDERS_PER_PAGE,
|
limit: FOLDERS_PER_PAGE,
|
||||||
page: @template_folders.count > SHOW_TEMPLATES_FOLDERS_THRESHOLD ? params[:page] : 1
|
page: @template_folders.count > SHOW_TEMPLATES_FOLDERS_THRESHOLD ? params[:page] : 1
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ class TemplatesDashboardController < ApplicationController
|
|||||||
(@template_folders.size < 7 ? 9 : 6)
|
(@template_folders.size < 7 ? 9 : 6)
|
||||||
end
|
end
|
||||||
|
|
||||||
@pagy, @templates = pagy(@templates, limit:)
|
@pagy, @templates = pagy_auto(@templates, limit:)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class TemplatesShareLinkController < ApplicationController
|
||||||
|
load_and_authorize_resource :template
|
||||||
|
|
||||||
|
def show; end
|
||||||
|
|
||||||
|
def create
|
||||||
|
authorize!(:update, @template)
|
||||||
|
|
||||||
|
@template.update!(template_params)
|
||||||
|
|
||||||
|
head :ok
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def template_params
|
||||||
|
params.require(:template).permit(:shared_link)
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -24,6 +24,7 @@ import SubmitForm from './elements/submit_form'
|
|||||||
import PromptPassword from './elements/prompt_password'
|
import PromptPassword from './elements/prompt_password'
|
||||||
import EmailsTextarea from './elements/emails_textarea'
|
import EmailsTextarea from './elements/emails_textarea'
|
||||||
import ToggleOnSubmit from './elements/toggle_on_submit'
|
import ToggleOnSubmit from './elements/toggle_on_submit'
|
||||||
|
import CheckOnClick from './elements/check_on_click'
|
||||||
import PasswordInput from './elements/password_input'
|
import PasswordInput from './elements/password_input'
|
||||||
import SearchInput from './elements/search_input'
|
import SearchInput from './elements/search_input'
|
||||||
import ToggleAttribute from './elements/toggle_attribute'
|
import ToggleAttribute from './elements/toggle_attribute'
|
||||||
@@ -103,6 +104,7 @@ safeRegisterElement('set-date-button', SetDateButton)
|
|||||||
safeRegisterElement('indeterminate-checkbox', IndeterminateCheckbox)
|
safeRegisterElement('indeterminate-checkbox', IndeterminateCheckbox)
|
||||||
safeRegisterElement('app-tour', AppTour)
|
safeRegisterElement('app-tour', AppTour)
|
||||||
safeRegisterElement('dashboard-dropzone', DashboardDropzone)
|
safeRegisterElement('dashboard-dropzone', DashboardDropzone)
|
||||||
|
safeRegisterElement('check-on-click', CheckOnClick)
|
||||||
|
|
||||||
safeRegisterElement('template-builder', class extends HTMLElement {
|
safeRegisterElement('template-builder', class extends HTMLElement {
|
||||||
connectedCallback () {
|
connectedCallback () {
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
export default class extends HTMLElement {
|
||||||
|
connectedCallback () {
|
||||||
|
this.addEventListener('click', () => {
|
||||||
|
if (!this.element.checked) {
|
||||||
|
this.element.checked = true
|
||||||
|
this.element.dispatchEvent(new Event('change', { bubbles: true }))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
get element () {
|
||||||
|
return document.getElementById(this.dataset.elementId)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,8 +3,6 @@ export default class extends HTMLElement {
|
|||||||
this.clearChecked()
|
this.clearChecked()
|
||||||
|
|
||||||
this.addEventListener('click', (e) => {
|
this.addEventListener('click', (e) => {
|
||||||
e.stopPropagation()
|
|
||||||
|
|
||||||
const text = this.dataset.text || this.innerText.trim()
|
const text = this.dataset.text || this.innerText.trim()
|
||||||
|
|
||||||
if (navigator.clipboard) {
|
if (navigator.clipboard) {
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="isActive && withLabel && (!area.option_uuid || !option.value)"
|
v-if="isActive && withLabel && (!area.option_uuid || !option.value)"
|
||||||
class="absolute -top-7 rounded bg-base-content text-base-100 px-2 text-sm whitespace-nowrap pointer-events-none"
|
class="absolute -top-7 rounded bg-base-content text-base-100 px-2 text-sm whitespace-nowrap pointer-events-none field-area-active-label"
|
||||||
>
|
>
|
||||||
<template v-if="area.option_uuid && !option.value">
|
<template v-if="area.option_uuid && !option.value">
|
||||||
{{ optionValue(option) }}
|
{{ optionValue(option) }}
|
||||||
@@ -55,12 +55,12 @@
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-else-if="field.type === 'signature' && signature"
|
v-else-if="field.type === 'signature' && signature"
|
||||||
class="flex justify-between h-full gap-1 overflow-hidden"
|
class="flex justify-between h-full gap-1 overflow-hidden w-full"
|
||||||
:class="isNarrow ? 'flex-row' : 'flex-col'"
|
:class="isNarrow && (withSignatureId || field.preferences?.reason_field_uuid) ? 'flex-row' : 'flex-col'"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="flex overflow-hidden"
|
class="flex overflow-hidden"
|
||||||
:class="isNarrow ? 'w-1/2' : 'flex-grow'"
|
:class="isNarrow && (withSignatureId || field.preferences?.reason_field_uuid) ? 'w-1/2' : 'flex-grow'"
|
||||||
style="min-height: 50%"
|
style="min-height: 50%"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
@@ -69,7 +69,7 @@
|
|||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="withSignatureId"
|
v-if="withSignatureId || field.preferences?.reason_field_uuid"
|
||||||
class="text-[1vw] lg:text-[0.55rem] lg:leading-[0.65rem]"
|
class="text-[1vw] lg:text-[0.55rem] lg:leading-[0.65rem]"
|
||||||
:class="isNarrow ? 'w-1/2' : 'w-full'"
|
:class="isNarrow ? 'w-1/2' : 'w-full'"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -211,7 +211,7 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
computedPreviousValue () {
|
computedPreviousValue () {
|
||||||
if (this.isUsePreviousValue) {
|
if (this.isUsePreviousValue && this.field.required === true) {
|
||||||
return this.previousValue
|
return this.previousValue
|
||||||
} else {
|
} else {
|
||||||
return null
|
return null
|
||||||
|
|||||||
@@ -414,7 +414,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
computedPreviousValue () {
|
computedPreviousValue () {
|
||||||
if (this.isUsePreviousValue) {
|
if (this.isUsePreviousValue && this.field.required === true) {
|
||||||
return this.previousValue
|
return this.previousValue
|
||||||
} else {
|
} else {
|
||||||
return null
|
return null
|
||||||
|
|||||||
@@ -151,8 +151,10 @@ export default {
|
|||||||
resizeTextarea () {
|
resizeTextarea () {
|
||||||
const textarea = this.$refs.textarea
|
const textarea = this.$refs.textarea
|
||||||
|
|
||||||
textarea.style.height = 'auto'
|
if (textarea) {
|
||||||
textarea.style.height = Math.min(250, textarea.scrollHeight) + 'px'
|
textarea.style.height = 'auto'
|
||||||
|
textarea.style.height = Math.min(250, textarea.scrollHeight) + 'px'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
toggleTextArea () {
|
toggleTextArea () {
|
||||||
this.isTextArea = true
|
this.isTextArea = true
|
||||||
|
|||||||
@@ -79,13 +79,52 @@
|
|||||||
name="buttons"
|
name="buttons"
|
||||||
/>
|
/>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
|
<form
|
||||||
|
v-if="withSignYourselfButton && template.submitters.length < 2"
|
||||||
|
target="_blank"
|
||||||
|
data-turbo="false"
|
||||||
|
class="inline"
|
||||||
|
method="post"
|
||||||
|
:action="`/d/${template.slug}`"
|
||||||
|
@submit="maybeShowErrorTemplateAlert"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="hidden"
|
||||||
|
name="_method"
|
||||||
|
value="put"
|
||||||
|
autocomplete="off"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="hidden"
|
||||||
|
name="authenticity_token"
|
||||||
|
:value="authenticityToken"
|
||||||
|
autocomplete="off"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="hidden"
|
||||||
|
name="selfsign"
|
||||||
|
value="true"
|
||||||
|
autocomplete="off"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="btn btn-primary btn-ghost text-base hidden md:flex"
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
<IconWritingSign
|
||||||
|
width="22"
|
||||||
|
class="inline"
|
||||||
|
/>
|
||||||
|
<span class="hidden md:inline">
|
||||||
|
{{ t('sign_yourself') }}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
<a
|
<a
|
||||||
v-if="withSignYourselfButton"
|
v-else-if="withSignYourselfButton"
|
||||||
id="sign_yourself_button"
|
id="sign_yourself_button"
|
||||||
:href="template.submitters.length > 1 ? `/templates/${template.id}/submissions/new?selfsign=true` : `/d/${template.slug}`"
|
:href="`/templates/${template.id}/submissions/new?selfsign=true`"
|
||||||
class="btn btn-primary btn-ghost text-base hidden md:flex"
|
class="btn btn-primary btn-ghost text-base hidden md:flex"
|
||||||
:target="template.submitters.length > 1 ? '' : '_blank'"
|
data-turbo-frame="modal"
|
||||||
:data-turbo-frame="template.submitters.length > 1 ? 'modal' : ''"
|
|
||||||
@click="maybeShowErrorTemplateAlert"
|
@click="maybeShowErrorTemplateAlert"
|
||||||
>
|
>
|
||||||
<IconWritingSign
|
<IconWritingSign
|
||||||
|
|||||||
@@ -18,10 +18,10 @@
|
|||||||
#
|
#
|
||||||
# Indexes
|
# Indexes
|
||||||
#
|
#
|
||||||
# index_email_events_on_account_id (account_id)
|
# index_email_events_on_account_id_and_event_datetime (account_id,event_datetime)
|
||||||
# index_email_events_on_email (email)
|
# index_email_events_on_email (email)
|
||||||
# index_email_events_on_emailable (emailable_type,emailable_id)
|
# index_email_events_on_emailable (emailable_type,emailable_id)
|
||||||
# index_email_events_on_message_id (message_id)
|
# index_email_events_on_message_id (message_id)
|
||||||
#
|
#
|
||||||
# Foreign Keys
|
# Foreign Keys
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -65,12 +65,18 @@ class Submission < ApplicationRecord
|
|||||||
|
|
||||||
scope :active, -> { where(archived_at: nil) }
|
scope :active, -> { where(archived_at: nil) }
|
||||||
scope :archived, -> { where.not(archived_at: nil) }
|
scope :archived, -> { where.not(archived_at: nil) }
|
||||||
scope :pending, -> { joins(:submitters).where(submitters: { completed_at: nil }).group(:id) }
|
scope :pending, lambda {
|
||||||
|
where(Submitter.where(Submitter.arel_table[:submission_id].eq(Submission.arel_table[:id])
|
||||||
|
.and(Submitter.arel_table[:completed_at].eq(nil))).select(1).arel.exists)
|
||||||
|
}
|
||||||
scope :completed, lambda {
|
scope :completed, lambda {
|
||||||
where.not(Submitter.where(Submitter.arel_table[:submission_id].eq(Submission.arel_table[:id])
|
where.not(Submitter.where(Submitter.arel_table[:submission_id].eq(Submission.arel_table[:id])
|
||||||
.and(Submitter.arel_table[:completed_at].eq(nil))).select(1).arel.exists)
|
.and(Submitter.arel_table[:completed_at].eq(nil))).select(1).arel.exists)
|
||||||
}
|
}
|
||||||
scope :declined, -> { joins(:submitters).where.not(submitters: { declined_at: nil }).group(:id) }
|
scope :declined, lambda {
|
||||||
|
where(Submitter.where(Submitter.arel_table[:submission_id].eq(Submission.arel_table[:id])
|
||||||
|
.and(Submitter.arel_table[:declined_at].not_eq(nil))).select(1).arel.exists)
|
||||||
|
}
|
||||||
scope :expired, -> { pending.where(expire_at: ..Time.current) }
|
scope :expired, -> { pending.where(expire_at: ..Time.current) }
|
||||||
|
|
||||||
enum :source, {
|
enum :source, {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
# name :string not null
|
# name :string not null
|
||||||
# preferences :text not null
|
# preferences :text not null
|
||||||
# schema :text not null
|
# schema :text not null
|
||||||
|
# shared_link :boolean default(FALSE), not null
|
||||||
# slug :string not null
|
# slug :string not null
|
||||||
# source :text not null
|
# source :text not null
|
||||||
# submitters :text not null
|
# submitters :text not null
|
||||||
|
|||||||
@@ -73,6 +73,8 @@ class User < ApplicationRecord
|
|||||||
scope :archived, -> { where.not(archived_at: nil) }
|
scope :archived, -> { where.not(archived_at: nil) }
|
||||||
scope :admins, -> { where(role: ADMIN_ROLE) }
|
scope :admins, -> { where(role: ADMIN_ROLE) }
|
||||||
|
|
||||||
|
validates :email, format: { with: /\A[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\z/ }
|
||||||
|
|
||||||
def access_token
|
def access_token
|
||||||
super || build_access_token.tap(&:save!)
|
super || build_access_token.tap(&:save!)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -2,7 +2,11 @@
|
|||||||
<% if @pagy.pages > 1 %>
|
<% if @pagy.pages > 1 %>
|
||||||
<div class="flex my-6 justify-center md:justify-between">
|
<div class="flex my-6 justify-center md:justify-between">
|
||||||
<div class="hidden md:block text-sm">
|
<div class="hidden md:block text-sm">
|
||||||
<%= @pagy.from %>-<%= local_assigns.fetch(:to, @pagy.to) %> of <%= local_assigns.fetch(:count, @pagy.count) %> <%= local_assigns[:items_name] || 'items' %>
|
<% if @pagy.count.nil? %>
|
||||||
|
<%= t("pagination.#{local_assigns.fetch(:items_name, 'items')}.range_without_total", from: local_assigns.fetch(:from, @pagy.from), to: local_assigns.fetch(:to, @pagy.to)) %>
|
||||||
|
<% else %>
|
||||||
|
<%= t("pagination.#{local_assigns.fetch(:items_name, 'items')}.range_with_total", from: local_assigns.fetch(:from, @pagy.from), to: local_assigns.fetch(:to, @pagy.to), count: local_assigns.fetch(:count, @pagy.count)) %>
|
||||||
|
<% end %>
|
||||||
<%= local_assigns[:left_additional_html] %>
|
<%= local_assigns[:left_additional_html] %>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center space-x-1.5">
|
<div class="flex items-center space-x-1.5">
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<% if configs = account.account_configs.find_by(key: AccountConfig::POLICY_LINKS_KEY) %>
|
||||||
|
<div class="max-w-md mx-auto flex flex-wrap gap-1 justify-center text-sm text-base-content/60 mt-2">
|
||||||
|
<%= auto_link(MarkdownToHtml.call(configs.value)) %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
@@ -23,12 +23,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<% if Docuseal.multitenant? || Accounts.can_send_emails?(@submitter.account) %>
|
<% if Docuseal.multitenant? || Accounts.can_send_emails?(@submitter.account) %>
|
||||||
<toggle-submit class="block">
|
<toggle-submit class="block">
|
||||||
<%= button_to button_title(title: t('send_copy_to_email'), disabled_with: t('sending'), icon: svg_icon('mail_forward', class: 'w-6 h-6')), send_submission_email_index_path, params: { submitter_slug: @submitter.slug }, class: 'base-button w-full' %>
|
<%= button_to button_title(title: t('send_copy_to_email'), disabled_with: t('sending'), icon: svg_icon('mail_forward', class: 'w-6 h-6')), send_submission_email_index_path, params: { template_slug: @template.slug, email: params[:email] }, class: 'base-button w-full' %>
|
||||||
</toggle-submit>
|
</toggle-submit>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if Templates.filter_undefined_submitters(@template).size == 1 && %w[api embed].exclude?(@submitter.submission.source) && @submitter.account.account_configs.find_or_initialize_by(key: AccountConfig::ALLOW_TO_RESUBMIT).value != false %>
|
<% if Templates.filter_undefined_submitters(@template).size == 1 && %w[api embed].exclude?(@submitter.submission.source) && @submitter.account.account_configs.find_or_initialize_by(key: AccountConfig::ALLOW_TO_RESUBMIT).value != false && @template.shared_link? %>
|
||||||
<toggle-submit class="block">
|
<toggle-submit class="block">
|
||||||
<%= button_to button_title(title: t('resubmit'), disabled_with: t('resubmit'), icon: svg_icon('reload', class: 'w-6 h-6')), start_form_path(@template.slug), params: { submitter: { email: params[:email] }, resubmit: @submitter.slug }, method: :put, class: 'white-button w-full' %>
|
<%= button_to button_title(title: t('resubmit'), disabled_with: t('resubmit'), icon: svg_icon('reload', class: 'w-6 h-6')), start_form_path(@template.slug), params: { submitter: { email: params[:email] }, resubmit: true }, method: :put, class: 'white-button w-full' %>
|
||||||
</toggle-submit>
|
</toggle-submit>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<% content_for(:html_title, "#{@template.name} | DocuSeal") %>
|
||||||
|
<% content_for(:html_description, t('share_link_is_currently_disabled')) %>
|
||||||
|
<div class="max-w-md mx-auto px-2 mt-12 mb-4">
|
||||||
|
<div class="space-y-6 mx-auto">
|
||||||
|
<div class="space-y-6">
|
||||||
|
<div class="text-center w-full space-y-6">
|
||||||
|
<%= render 'banner' %>
|
||||||
|
<p class="text-xl font-semibold text-center">
|
||||||
|
<%= t('share_link_is_currently_disabled') %>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center bg-base-200 rounded-xl p-4 mb-4">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="mr-3">
|
||||||
|
<%= svg_icon('writing_sign', class: 'w-10 h-10') %>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p class="text-lg font-bold mb-1"><%= @template.name %></p>
|
||||||
|
<% if @template.archived_at? %>
|
||||||
|
<p dir="auto" class="text-sm"><%= t('form_has_been_deleted_by_html', name: @template.account.name) %></p>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<%= render 'shared/attribution', link_path: '/start', account: @template.account %>
|
||||||
@@ -42,3 +42,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<%= render 'shared/attribution', link_path: '/start', account: @template.account %>
|
<%= render 'shared/attribution', link_path: '/start', account: @template.account %>
|
||||||
|
<%= render 'start_form/policy', account: @template.account %>
|
||||||
|
|||||||
@@ -6,11 +6,11 @@
|
|||||||
<field-value dir="auto" class="flex absolute text-[1.6vw] lg:text-base <%= 'font-mono' if font == 'Courier' %> <%= 'font-serif' if font == 'Times' %> <%= 'font-bold' if font_type == 'bold' || font_type == 'bold_italic' %> <%= 'italic' if font_type == 'italic' || font_type == 'bold_italic' %> <%= align == 'right' ? 'text-right' : (align == 'center' ? 'text-center' : '') %>" style="<%= "color: #{color}; " if color.present? %>width: <%= area['w'] * 100 %>%; height: <%= area['h'] * 100 %>%; left: <%= area['x'] * 100 %>%; top: <%= area['y'] * 100 %>%; <%= "font-size: clamp(4pt, 1.6vw, #{field['preferences']['font_size'].to_i * 1.23}pt); line-height: `clamp(6pt, 2.0vw, #{(field['preferences']['font_size'].to_i * 1.23) + 3}pt)`" if field.dig('preferences', 'font_size') %>">
|
<field-value dir="auto" class="flex absolute text-[1.6vw] lg:text-base <%= 'font-mono' if font == 'Courier' %> <%= 'font-serif' if font == 'Times' %> <%= 'font-bold' if font_type == 'bold' || font_type == 'bold_italic' %> <%= 'italic' if font_type == 'italic' || font_type == 'bold_italic' %> <%= align == 'right' ? 'text-right' : (align == 'center' ? 'text-center' : '') %>" style="<%= "color: #{color}; " if color.present? %>width: <%= area['w'] * 100 %>%; height: <%= area['h'] * 100 %>%; left: <%= area['x'] * 100 %>%; top: <%= area['y'] * 100 %>%; <%= "font-size: clamp(4pt, 1.6vw, #{field['preferences']['font_size'].to_i * 1.23}pt); line-height: `clamp(6pt, 2.0vw, #{(field['preferences']['font_size'].to_i * 1.23) + 3}pt)`" if field.dig('preferences', 'font_size') %>">
|
||||||
<% if field['type'] == 'signature' %>
|
<% if field['type'] == 'signature' %>
|
||||||
<% is_narrow = area['h']&.positive? && (area['w'].to_f / area['h']) > 6 %>
|
<% is_narrow = area['h']&.positive? && (area['w'].to_f / area['h']) > 6 %>
|
||||||
<div class="flex justify-between w-full h-full gap-1 <%= is_narrow ? 'flex-row' : 'flex-col' %>">
|
<div class="flex justify-between w-full h-full gap-1 <%= is_narrow && (local_assigns[:with_signature_id] || field.dig('preferences', 'reason_field_uuid').present?) ? 'flex-row' : 'flex-col' %>">
|
||||||
<div class="flex overflow-hidden <%= is_narrow ? 'w-1/2' : 'flex-grow' %>" style="min-height: 50%">
|
<div class="flex overflow-hidden <%= is_narrow && (local_assigns[:with_signature_id] || field.dig('preferences', 'reason_field_uuid').present?) ? 'w-1/2' : 'flex-grow' %>" style="min-height: 50%">
|
||||||
<img class="object-contain mx-auto" src="<%= attachments_index[value].url %>">
|
<img class="object-contain mx-auto" src="<%= attachments_index[value].url %>">
|
||||||
</div>
|
</div>
|
||||||
<% if local_assigns[:with_signature_id] && attachment = attachments_index[value] %>
|
<% if (local_assigns[:with_signature_id] || field.dig('preferences', 'reason_field_uuid').present?) && attachment = attachments_index[value] %>
|
||||||
<div class="text-[1vw] lg:text-[0.55rem] lg:leading-[0.65rem] <%= is_narrow ? 'w-1/2' : 'w-full' %>">
|
<div class="text-[1vw] lg:text-[0.55rem] lg:leading-[0.65rem] <%= is_narrow ? 'w-1/2' : 'w-full' %>">
|
||||||
<div class="truncate uppercase">
|
<div class="truncate uppercase">
|
||||||
ID: <%= attachment.uuid %>
|
ID: <%= attachment.uuid %>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<%= render 'submissions_filters/filter_button', filter_params: %>
|
<%= render 'submissions_filters/filter_button', filter_params: %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% if @pagy.count > 0 %>
|
<% if @pagy.count.nil? || @pagy.count > 0 %>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<%= render partial: 'templates/submission', collection: @submissions, locals: { with_template: true, archived: true } %>
|
<%= render partial: 'templates/submission', collection: @submissions, locals: { with_template: true, archived: true } %>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<% filter_params = params.permit(Submissions::Filter::ALLOWED_PARAMS).compact_blank %>
|
<% filter_params = params.permit(Submissions::Filter::ALLOWED_PARAMS).compact_blank %>
|
||||||
<% is_show_tabs = @pagy.count >= 5 || params[:status].present? || filter_params.present? %>
|
<% is_show_tabs = (@pagy.count.nil? || @pagy.count >= 5) || params[:status].present? || filter_params.present? %>
|
||||||
<% if Docuseal.demo? %><%= render 'shared/demo_alert' %><% end %>
|
<% if Docuseal.demo? %><%= render 'shared/demo_alert' %><% end %>
|
||||||
<div class="flex justify-between items-center w-full mb-4">
|
<div class="flex justify-between items-center w-full mb-4">
|
||||||
<div class="flex items-center flex-grow min-w-0">
|
<div class="flex items-center flex-grow min-w-0">
|
||||||
@@ -61,12 +61,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if @pagy.count > 0 %>
|
<% if @pagy.count.nil? || @pagy.count > 0 %>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<%= render partial: 'templates/submission', collection: @submissions, locals: { with_template: true } %>
|
<%= render partial: 'templates/submission', collection: @submissions, locals: { with_template: true } %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if params[:q].blank? && params[:status].blank? && filter_params.blank? && @pagy.count < 5 %>
|
<% if params[:q].blank? && params[:status].blank? && filter_params.blank? && @pagy.count.present? && @pagy.count < 5 %>
|
||||||
<%= render 'templates/dropzone' %>
|
<%= render 'templates/dropzone' %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if @submissions.present? || (params[:q].blank? && filter_params.blank?) %>
|
<% if @submissions.present? || (params[:q].blank? && filter_params.blank?) %>
|
||||||
|
|||||||
@@ -43,10 +43,10 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% undefined_submitters = Templates.filter_undefined_submitters(@submitter.submission.template) %>
|
<% undefined_submitters = Templates.filter_undefined_submitters(@submitter.submission.template) %>
|
||||||
<% if undefined_submitters.size == 1 && undefined_submitters.first['uuid'] == @submitter.uuid && %w[api embed].exclude?(@submitter.submission.source) && @submitter.account.account_configs.find_or_initialize_by(key: AccountConfig::ALLOW_TO_RESUBMIT).value != false && !@submitter.template.archived_at? %>
|
<% if undefined_submitters.size == 1 && undefined_submitters.first['uuid'] == @submitter.uuid && %w[api embed].exclude?(@submitter.submission.source) && @submitter.account.account_configs.find_or_initialize_by(key: AccountConfig::ALLOW_TO_RESUBMIT).value != false %>
|
||||||
<div class="divider uppercase"><%= t('or') %></div>
|
<div class="divider uppercase"><%= t('or') %></div>
|
||||||
<toggle-submit class="block">
|
<toggle-submit class="block">
|
||||||
<%= button_to button_title(title: t('resubmit'), disabled_with: t('resubmit'), icon: svg_icon('reload', class: 'w-6 h-6')), start_form_path(@submitter.submission.template.slug), params: { submitter: { email: @submitter.email, phone: @submitter.phone, name: @submitter.name }, resubmit: @submitter.slug }, method: :put, class: 'white-button w-full' %>
|
<%= button_to button_title(title: t('resubmit'), disabled_with: t('resubmit'), icon: svg_icon('reload', class: 'w-6 h-6')), resubmit_form_path, params: { resubmit: @submitter.slug }, method: :put, class: 'white-button w-full' %>
|
||||||
</toggle-submit>
|
</toggle-submit>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -39,13 +39,13 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% if @pagy.count > 0 %>
|
<% if @pagy.count.nil? || @pagy.count > 0 %>
|
||||||
<div class="grid gap-4 md:grid-cols-3">
|
<div class="grid gap-4 md:grid-cols-3">
|
||||||
<%= render partial: 'templates/template', collection: @templates %>
|
<%= render partial: 'templates/template', collection: @templates %>
|
||||||
</div>
|
</div>
|
||||||
<% templates_order_select_html = capture do %>
|
<% templates_order_select_html = capture do %>
|
||||||
<% if params[:q].blank? && @pagy.pages > 1 %>
|
<% if params[:q].blank? && @pagy.pages > 1 %>
|
||||||
<%= render('shared/templates_order_select', with_recently_used: @pagy.count < 10_000) %>
|
<%= render('shared/templates_order_select', with_recently_used: @pagy.count.present? && @pagy.count < 10_000) %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= render 'shared/pagination', pagy: @pagy, items_name: 'templates', right_additional_html: templates_order_select_html %>
|
<%= render 'shared/pagination', pagy: @pagy, items_name: 'templates', right_additional_html: templates_order_select_html %>
|
||||||
|
|||||||
@@ -49,7 +49,12 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= render 'shared/clipboard_copy', text: start_form_url(slug: @template.slug), id: 'share_link_clipboard', class: 'absolute md:relative bottom-0 right-0 btn btn-xs md:btn-sm whitespace-nowrap btn-neutral text-white mt-1 px-2', icon_class: 'w-4 h-4 md:w-6 md:h-6 text-white', copy_title: t('link'), copied_title: t('copied'), copy_title_md: t('link'), copied_title_md: t('copied') %>
|
<%= link_to template_share_link_path(template), class: 'absolute md:relative bottom-0 right-0 btn btn-xs md:btn-sm whitespace-nowrap btn-neutral text-white mt-1 px-2', data: { turbo_frame: :modal } do %>
|
||||||
|
<span class="flex items-center justify-center space-x-2">
|
||||||
|
<%= svg_icon('link', class: 'w-4 h-4 md:w-6 md:h-6 text-white') %>
|
||||||
|
<span><%= t('link') %></span>
|
||||||
|
</span>
|
||||||
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<%= render 'title', template: @template %>
|
<%= render 'title', template: @template %>
|
||||||
<% filter_params = params.permit(Submissions::Filter::ALLOWED_PARAMS).compact_blank %>
|
<% filter_params = params.permit(Submissions::Filter::ALLOWED_PARAMS).compact_blank %>
|
||||||
<% is_show_tabs = @pagy.pages > 1 || params[:q].present? || params[:status].present? || filter_params.present? %>
|
<% is_show_tabs = @pagy.pages > 1 || params[:q].present? || params[:status].present? || filter_params.present? %>
|
||||||
<% if !@pagy.count.zero? || params[:q].present? || params[:status].present? || filter_params.present? %>
|
<% if @pagy.count.nil? || !@pagy.count.zero? || params[:q].present? || params[:status].present? || filter_params.present? %>
|
||||||
<div class="<%= is_show_tabs ? 'mb-4' : 'mb-6' %>">
|
<div class="<%= is_show_tabs ? 'mb-4' : 'mb-6' %>">
|
||||||
<div class="flex justify-between items-center md:items-end">
|
<div class="flex justify-between items-center md:items-end">
|
||||||
<div>
|
<div>
|
||||||
@@ -35,27 +35,33 @@
|
|||||||
<%= svg_icon('list', class: 'w-5 h-5') %>
|
<%= svg_icon('list', class: 'w-5 h-5') %>
|
||||||
<span class="font-normal"><%= t('all') %></span>
|
<span class="font-normal"><%= t('all') %></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="badge badge-neutral badge-outline font-medium">
|
<% unless can?(:manage, :countless) %>
|
||||||
<%= params[:status].blank? && filter_params.blank? ? @pagy.count : @base_submissions.unscope(:group, :order).select(:id).distinct.count %>
|
<div class="badge badge-neutral badge-outline font-medium">
|
||||||
</div>
|
<%= params[:status].blank? && filter_params.blank? ? @pagy.count : @base_submissions.unscope(:group, :order).select(:id).distinct.count %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
</a>
|
</a>
|
||||||
<a href="<%= url_for(params.to_unsafe_h.merge(status: :pending)) %>" class="<%= params[:status] == 'pending' ? 'border-neutral-700' : 'border-neutral-300' %> flex h-10 px-2 py-1 text-lg items-center justify-between border text-center text-neutral font-semibold rounded-xl w-full md:w-48 hover:border-neutral-700">
|
<a href="<%= url_for(params.to_unsafe_h.merge(status: :pending)) %>" class="<%= params[:status] == 'pending' ? 'border-neutral-700' : 'border-neutral-300' %> flex h-10 px-2 py-1 text-lg items-center justify-between border text-center text-neutral font-semibold rounded-xl w-full md:w-48 hover:border-neutral-700">
|
||||||
<div class="flex items-center space-x-1">
|
<div class="flex items-center space-x-1">
|
||||||
<%= svg_icon('clock', class: 'w-5 h-5') %>
|
<%= svg_icon('clock', class: 'w-5 h-5') %>
|
||||||
<span class="font-normal"><%= t('pending') %></span>
|
<span class="font-normal"><%= t('pending') %></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="badge badge-neutral badge-outline font-medium">
|
<% unless can?(:manage, :countless) %>
|
||||||
<%= params[:status] == 'pending' && filter_params.blank? ? @pagy.count : @base_submissions.pending.unscope(:group, :order).select(:id).distinct.count %>
|
<div class="badge badge-neutral badge-outline font-medium">
|
||||||
</div>
|
<%= params[:status] == 'pending' && filter_params.blank? ? @pagy.count : @base_submissions.pending.unscope(:group, :order).select(:id).distinct.count %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
</a>
|
</a>
|
||||||
<a href="<%= url_for(params.to_unsafe_h.merge(status: :completed)) %>" class="<%= params[:status] == 'completed' ? 'border-neutral-700' : 'border-neutral-300' %> flex h-10 px-2 py-1 text-lg items-center justify-between border text-center text-neutral font-semibold rounded-xl w-full md:w-48 hover:border-neutral-700">
|
<a href="<%= url_for(params.to_unsafe_h.merge(status: :completed)) %>" class="<%= params[:status] == 'completed' ? 'border-neutral-700' : 'border-neutral-300' %> flex h-10 px-2 py-1 text-lg items-center justify-between border text-center text-neutral font-semibold rounded-xl w-full md:w-48 hover:border-neutral-700">
|
||||||
<div class="flex items-center space-x-1">
|
<div class="flex items-center space-x-1">
|
||||||
<%= svg_icon('circle_check', class: 'w-5 h-5') %>
|
<%= svg_icon('circle_check', class: 'w-5 h-5') %>
|
||||||
<span class="font-normal"><%= t('completed') %></span>
|
<span class="font-normal"><%= t('completed') %></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="badge badge-neutral badge-outline font-medium">
|
<% unless can?(:manage, :countless) %>
|
||||||
<%= params[:status] == 'completed' && filter_params.blank? ? @pagy.count : @base_submissions.completed.unscope(:group, :order).select(:id).distinct.count %>
|
<div class="badge badge-neutral badge-outline font-medium">
|
||||||
</div>
|
<%= params[:status] == 'completed' && filter_params.blank? ? @pagy.count : @base_submissions.completed.unscope(:group, :order).select(:id).distinct.count %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-end flex-col md:flex-row gap-2 w-full md:w-fit">
|
<div class="flex items-end flex-col md:flex-row gap-2 w-full md:w-fit">
|
||||||
@@ -89,7 +95,7 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if @template.submitters.size == 1 %>
|
<% if @template.submitters.size == 1 %>
|
||||||
<%= link_to start_form_url(slug: @template.slug), id: 'sign_yourself_button', class: 'white-button mt-6', target: '_blank', rel: 'noopener' do %>
|
<%= button_to start_form_path(@template.slug), params: { selfsign: true }, method: :put, class: 'white-button w-full', form: { style: 'display: inline', target: '_blank', data: { turbo: false } } do %>
|
||||||
<%= svg_icon('writing', class: 'w-6 h-6') %>
|
<%= svg_icon('writing', class: 'w-6 h-6') %>
|
||||||
<span class="mr-1"><%= t('sign_it_yourself') %></span>
|
<span class="mr-1"><%= t('sign_it_yourself') %></span>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<%= render 'shared/search_input', placeholder: "#{t('search')}..." %>
|
<%= render 'shared/search_input', placeholder: "#{t('search')}..." %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% if @pagy.count > 0 %>
|
<% if @pagy.count.nil? || @pagy.count > 0 %>
|
||||||
<div class="grid gap-4 md:grid-cols-3">
|
<div class="grid gap-4 md:grid-cols-3">
|
||||||
<%= render partial: 'templates/template', collection: @templates %>
|
<%= render partial: 'templates/template', collection: @templates %>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% if @pagy.count > 0 %>
|
<% if @pagy.count.nil? || @pagy.count > 0 %>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<%= render partial: 'templates/submission', collection: @submissions, locals: { template: @template, archived: true } %>
|
<%= render partial: 'templates/submission', collection: @submissions, locals: { template: @template, archived: true } %>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<%= render 'templates/dashboard_dropzone', style: 'height: 114px' %>
|
<%= render 'templates/dashboard_dropzone', style: 'height: 114px' %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<div class="flex items-center flex-grow min-w-0">
|
<div class="flex items-center flex-grow min-w-0">
|
||||||
<% if has_archived || @pagy.count > 0 || @template_folders.present? %>
|
<% if has_archived || @pagy.count.nil? || @pagy.count > 0 || @template_folders.present? %>
|
||||||
<div class="mr-2">
|
<div class="mr-2">
|
||||||
<%= render 'dashboard/toggle_view', selected: 'templates' %>
|
<%= render 'dashboard/toggle_view', selected: 'templates' %>
|
||||||
</div>
|
</div>
|
||||||
@@ -45,7 +45,7 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
<% templates_order_select_html = capture do %>
|
<% templates_order_select_html = capture do %>
|
||||||
<% if params[:q].blank? && @pagy.pages > 1 %>
|
<% if params[:q].blank? && @pagy.pages > 1 %>
|
||||||
<%= render('shared/templates_order_select', with_recently_used: @pagy.count < 10_000) %>
|
<%= render('shared/templates_order_select', with_recently_used: @pagy.count.present? && @pagy.count < 10_000) %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if @template_folders.present? %>
|
<% if @template_folders.present? %>
|
||||||
@@ -84,9 +84,9 @@
|
|||||||
<% if show_dropzone %>
|
<% if show_dropzone %>
|
||||||
<%= render 'templates/dropzone' %>
|
<%= render 'templates/dropzone' %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if @templates.present? || params[:q].blank? %>
|
<% if @templates.present? || @template_folders.present? || params[:q].blank? %>
|
||||||
<% if @pagy.pages > 1 %>
|
<% if @pagy.pages > 1 %>
|
||||||
<%= render 'shared/pagination', pagy: @pagy, items_name: 'templates', left_additional_html: view_archived_html, right_additional_html: templates_order_select_html %>
|
<%= render 'shared/pagination', pagy: @pagy, items_name: @templates.present? ? 'templates' : 'template_folders', left_additional_html: view_archived_html, right_additional_html: templates_order_select_html %>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<%= view_archived_html %>
|
<%= view_archived_html %>
|
||||||
|
|||||||
@@ -380,8 +380,18 @@
|
|||||||
<%= t('embedding_url') %>
|
<%= t('embedding_url') %>
|
||||||
</label>
|
</label>
|
||||||
<div class="flex gap-2 mb-4 mt-2">
|
<div class="flex gap-2 mb-4 mt-2">
|
||||||
<input id="embedding_url" type="text" value="<%= start_form_url(slug: @template.slug) %>" class="base-input w-full" autocomplete="off" readonly>
|
<%= form_for @template, url: template_share_link_path(@template), method: :post, html: { id: 'shared_link_form', autocomplete: 'off', class: 'w-full mt-1' }, data: { close_on_submit: false } do |f| %>
|
||||||
<%= render 'shared/clipboard_copy', icon: 'copy', text: start_form_url(slug: @template.slug), class: 'base-button', icon_class: 'w-6 h-6 text-white', copy_title: t('copy'), copied_title: t('copied') %>
|
<div class="flex gap-2">
|
||||||
|
<input id="embedding_url" type="text" value="<%= start_form_url(slug: @template.slug) %>" class="base-input w-full" autocomplete="off" readonly>
|
||||||
|
<check-on-click data-element-id="template_shared_link">
|
||||||
|
<%= render 'shared/clipboard_copy', icon: 'copy', text: start_form_url(slug: @template.slug), class: 'base-button', icon_class: 'w-6 h-6 text-white', copy_title: t('copy'), copied_title: t('copied') %>
|
||||||
|
</check-on-click>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center justify-between gap-1 pt-3">
|
||||||
|
<span><%= t('enable_shared_link') %></span>
|
||||||
|
<%= f.check_box :shared_link, { class: 'toggle', onchange: 'this.form.requestSubmit()' }, 'true', 'false' %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<%= render 'templates_code_modal/placeholder' %>
|
<%= render 'templates_code_modal/placeholder' %>
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<%= render 'shared/turbo_modal_large', title: t('share_link') do %>
|
||||||
|
<div class="mt-2 mb-4 px-5">
|
||||||
|
<%= form_for @template, url: template_share_link_path(@template), method: :post, html: { id: 'shared_link_form', autocomplete: 'off', class: 'mt-3' }, data: { close_on_submit: false } do |f| %>
|
||||||
|
<div class="flex items-center justify-between gap-1 px-1">
|
||||||
|
<span><%= t('enable_shared_link') %></span>
|
||||||
|
<%= f.check_box :shared_link, { disabled: !can?(:update, @template), class: 'toggle', onchange: 'this.form.requestSubmit()' }, 'true', 'false' %>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-2 mt-3">
|
||||||
|
<input id="embedding_url" type="text" value="<%= start_form_url(slug: @template.slug) %>" class="base-input w-full" autocomplete="off" readonly>
|
||||||
|
<check-on-click data-element-id="template_shared_link">
|
||||||
|
<%= render 'shared/clipboard_copy', icon: 'copy', text: start_form_url(slug: @template.slug), class: 'base-button', icon_class: 'w-6 h-6 text-white', copy_title: t('copy'), copied_title: t('copied') %>
|
||||||
|
</check-on-click>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'pagy/extras/countless'
|
||||||
|
|
||||||
Pagy::DEFAULT[:limit] = 10
|
Pagy::DEFAULT[:limit] = 10
|
||||||
Pagy::DEFAULT.freeze
|
Pagy::DEFAULT.freeze
|
||||||
|
|
||||||
|
|||||||
+138
-2
@@ -745,6 +745,10 @@ en: &en
|
|||||||
three_months: 3 months
|
three_months: 3 months
|
||||||
eu_data_residency: EU data residency
|
eu_data_residency: EU data residency
|
||||||
please_enter_your_email_address_associated_with_the_completed_submission: Please enter your email address associated with the completed submission.
|
please_enter_your_email_address_associated_with_the_completed_submission: Please enter your email address associated with the completed submission.
|
||||||
|
esignature_disclosure: eSignature Disclosure
|
||||||
|
share_link: Share link
|
||||||
|
enable_shared_link: Enable shared link
|
||||||
|
share_link_is_currently_disabled: Share link is currently disabled
|
||||||
submission_sources:
|
submission_sources:
|
||||||
api: API
|
api: API
|
||||||
bulk: Bulk Send
|
bulk: Bulk Send
|
||||||
@@ -829,6 +833,22 @@ en: &en
|
|||||||
scopes:
|
scopes:
|
||||||
write: Update your data
|
write: Update your data
|
||||||
read: Read your data
|
read: Read your data
|
||||||
|
pagination:
|
||||||
|
submissions:
|
||||||
|
range_with_total: "%{from}-%{to} of %{count} submissions"
|
||||||
|
range_without_total: "%{from}-%{to} submissions"
|
||||||
|
templates:
|
||||||
|
range_with_total: "%{from}-%{to} of %{count} templates"
|
||||||
|
range_without_total: "%{from}-%{to} templates"
|
||||||
|
template_folders:
|
||||||
|
range_with_total: "%{from}-%{to} of %{count} folders"
|
||||||
|
range_without_total: "%{from}-%{to} folders"
|
||||||
|
users:
|
||||||
|
range_with_total: "%{from}-%{to} of %{count} users"
|
||||||
|
range_without_total: "%{from}-%{to} users"
|
||||||
|
items:
|
||||||
|
range_with_total: "%{from}-%{to} of %{count} items"
|
||||||
|
range_without_total: "%{from}-%{to} items"
|
||||||
|
|
||||||
es: &es
|
es: &es
|
||||||
stripe_integration: Integración con Stripe
|
stripe_integration: Integración con Stripe
|
||||||
@@ -1557,6 +1577,10 @@ es: &es
|
|||||||
three_months: 3 meses
|
three_months: 3 meses
|
||||||
eu_data_residency: Datos alojados UE
|
eu_data_residency: Datos alojados UE
|
||||||
please_enter_your_email_address_associated_with_the_completed_submission: Por favor, introduce tu dirección de correo electrónico asociada con el envío completado.
|
please_enter_your_email_address_associated_with_the_completed_submission: Por favor, introduce tu dirección de correo electrónico asociada con el envío completado.
|
||||||
|
esignature_disclosure: Uso de firma electrónica
|
||||||
|
share_link: Enlace para compartir
|
||||||
|
enable_shared_link: Habilitar enlace compartido
|
||||||
|
share_link_is_currently_disabled: El enlace compartido está deshabilitado actualmente
|
||||||
submission_sources:
|
submission_sources:
|
||||||
api: API
|
api: API
|
||||||
bulk: Envío masivo
|
bulk: Envío masivo
|
||||||
@@ -1641,6 +1665,22 @@ es: &es
|
|||||||
scopes:
|
scopes:
|
||||||
write: Actualizar tus datos
|
write: Actualizar tus datos
|
||||||
read: Leer tus datos
|
read: Leer tus datos
|
||||||
|
pagination:
|
||||||
|
submissions:
|
||||||
|
range_with_total: "%{from}-%{to} de %{count} envíos"
|
||||||
|
range_without_total: "%{from}-%{to} envíos"
|
||||||
|
templates:
|
||||||
|
range_with_total: "%{from}-%{to} de %{count} plantillas"
|
||||||
|
range_without_total: "%{from}-%{to} plantillas"
|
||||||
|
template_folders:
|
||||||
|
range_with_total: "%{from}-%{to} de %{count} carpetas"
|
||||||
|
range_without_total: "%{from}-%{to} carpetas"
|
||||||
|
users:
|
||||||
|
range_with_total: "%{from}-%{to} de %{count} usuarios"
|
||||||
|
range_without_total: "%{from}-%{to} usuarios"
|
||||||
|
items:
|
||||||
|
range_with_total: "%{from}-%{to} de %{count} elementos"
|
||||||
|
range_without_total: "%{from}-%{to} elementos"
|
||||||
|
|
||||||
it: &it
|
it: &it
|
||||||
stripe_integration: Integrazione Stripe
|
stripe_integration: Integrazione Stripe
|
||||||
@@ -2367,6 +2407,10 @@ it: &it
|
|||||||
three_months: 3 mesi
|
three_months: 3 mesi
|
||||||
eu_data_residency: "Dati nell'UE"
|
eu_data_residency: "Dati nell'UE"
|
||||||
please_enter_your_email_address_associated_with_the_completed_submission: "Inserisci il tuo indirizzo email associato all'invio completato."
|
please_enter_your_email_address_associated_with_the_completed_submission: "Inserisci il tuo indirizzo email associato all'invio completato."
|
||||||
|
esignature_disclosure: Uso della firma elettronica
|
||||||
|
share_link: Link di condivisione
|
||||||
|
enable_shared_link: Abilita link condiviso
|
||||||
|
share_link_is_currently_disabled: Il link condiviso è attualmente disabilitato
|
||||||
submission_sources:
|
submission_sources:
|
||||||
api: API
|
api: API
|
||||||
bulk: Invio massivo
|
bulk: Invio massivo
|
||||||
@@ -2451,6 +2495,22 @@ it: &it
|
|||||||
scopes:
|
scopes:
|
||||||
write: Aggiorna i tuoi dati
|
write: Aggiorna i tuoi dati
|
||||||
read: Leggi i tuoi dati
|
read: Leggi i tuoi dati
|
||||||
|
pagination:
|
||||||
|
submissions:
|
||||||
|
range_with_total: "%{from}-%{to} di %{count} invii"
|
||||||
|
range_without_total: "%{from}-%{to} invii"
|
||||||
|
templates:
|
||||||
|
range_with_total: "%{from}-%{to} di %{count} modelli"
|
||||||
|
range_without_total: "%{from}-%{to} modelli"
|
||||||
|
template_folders:
|
||||||
|
range_with_total: "%{from}-%{to} di %{count} cartelle"
|
||||||
|
range_without_total: "%{from}-%{to} cartelle"
|
||||||
|
users:
|
||||||
|
range_with_total: "%{from}-%{to} di %{count} utenti"
|
||||||
|
range_without_total: "%{from}-%{to} utenti"
|
||||||
|
items:
|
||||||
|
range_with_total: "%{from}-%{to} di %{count} elementi"
|
||||||
|
range_without_total: "%{from}-%{to} elementi"
|
||||||
|
|
||||||
fr: &fr
|
fr: &fr
|
||||||
stripe_integration: Intégration Stripe
|
stripe_integration: Intégration Stripe
|
||||||
@@ -3180,6 +3240,10 @@ fr: &fr
|
|||||||
three_months: 3 mois
|
three_months: 3 mois
|
||||||
eu_data_residency: "Données dans l'UE"
|
eu_data_residency: "Données dans l'UE"
|
||||||
please_enter_your_email_address_associated_with_the_completed_submission: "Veuillez saisir l'adresse e-mail associée à l'envoi complété."
|
please_enter_your_email_address_associated_with_the_completed_submission: "Veuillez saisir l'adresse e-mail associée à l'envoi complété."
|
||||||
|
esignature_disclosure: Divulgation de Signature Électronique
|
||||||
|
share_link: Lien de partage
|
||||||
|
enable_shared_link: Activer le lien de partage
|
||||||
|
share_link_is_currently_disabled: Le lien de partage est actuellement désactivé
|
||||||
submission_sources:
|
submission_sources:
|
||||||
api: API
|
api: API
|
||||||
bulk: Envoi en masse
|
bulk: Envoi en masse
|
||||||
@@ -3264,6 +3328,22 @@ fr: &fr
|
|||||||
scopes:
|
scopes:
|
||||||
write: Mettre à jour vos données
|
write: Mettre à jour vos données
|
||||||
read: Lire vos données
|
read: Lire vos données
|
||||||
|
pagination:
|
||||||
|
submissions:
|
||||||
|
range_with_total: "%{from} à %{to} sur %{count} soumissions"
|
||||||
|
range_without_total: "%{from} à %{to} soumissions"
|
||||||
|
templates:
|
||||||
|
range_with_total: "%{from} à %{to} sur %{count} modèles"
|
||||||
|
range_without_total: "%{from} à %{to} modèles"
|
||||||
|
template_folders:
|
||||||
|
range_with_total: "%{from} à %{to} sur %{count} dossiers"
|
||||||
|
range_without_total: "%{from} à %{to} dossiers"
|
||||||
|
users:
|
||||||
|
range_with_total: "%{from} à %{to} sur %{count} utilisateurs"
|
||||||
|
range_without_total: "%{from} à %{to} utilisateurs"
|
||||||
|
items:
|
||||||
|
range_with_total: "%{from} à %{to} sur %{count} éléments"
|
||||||
|
range_without_total: "%{from} à %{to} éléments"
|
||||||
|
|
||||||
pt: &pt
|
pt: &pt
|
||||||
stripe_integration: Integração com Stripe
|
stripe_integration: Integração com Stripe
|
||||||
@@ -3992,6 +4072,10 @@ pt: &pt
|
|||||||
three_months: 3 meses
|
three_months: 3 meses
|
||||||
eu_data_residency: Dados na UE
|
eu_data_residency: Dados na UE
|
||||||
please_enter_your_email_address_associated_with_the_completed_submission: Por favor, insira seu e-mail associado ao envio concluído.
|
please_enter_your_email_address_associated_with_the_completed_submission: Por favor, insira seu e-mail associado ao envio concluído.
|
||||||
|
esignature_disclosure: Uso de assinatura eletrônica
|
||||||
|
share_link: Link de compartilhamento
|
||||||
|
enable_shared_link: Ativar link compartilhado
|
||||||
|
share_link_is_currently_disabled: O link compartilhado está desativado no momento
|
||||||
submission_sources:
|
submission_sources:
|
||||||
api: API
|
api: API
|
||||||
bulk: Envio em massa
|
bulk: Envio em massa
|
||||||
@@ -4036,8 +4120,8 @@ pt: &pt
|
|||||||
start: Iniciar
|
start: Iniciar
|
||||||
previous: Anterior
|
previous: Anterior
|
||||||
next: Próximo
|
next: Próximo
|
||||||
template_and_submissions: 'Modelos e Envios'
|
template_and_submissions: 'Modelos e Submissões'
|
||||||
template_and_submissions_description: "Você pode selecionar a visualização que melhor se adapta ao seu fluxo de trabalho. Escolha a visualização 'Modelos' para criar modelos reutilizáveis de documentos ou 'Envios' para assinar documentos individuais ou verificar o status de cada solicitação de assinatura."
|
template_and_submissions_description: "Você pode selecionar a visualização que melhor se adapta ao seu fluxo de trabalho. Escolha a visualização 'Modelos' para criar modelos reutilizáveis de documentos ou 'Submissões' para assinar documentos individuais ou verificar o status de cada solicitação de assinatura."
|
||||||
upload_a_pdf_file: 'Enviar um arquivo PDF'
|
upload_a_pdf_file: 'Enviar um arquivo PDF'
|
||||||
upload_a_pdf_file_description: 'Envie um documento PDF para criar um modelo de formulário de assinatura.'
|
upload_a_pdf_file_description: 'Envie um documento PDF para criar um modelo de formulário de assinatura.'
|
||||||
select_a_signer_party: 'Selecionar parte assinante'
|
select_a_signer_party: 'Selecionar parte assinante'
|
||||||
@@ -4077,6 +4161,22 @@ pt: &pt
|
|||||||
scopes:
|
scopes:
|
||||||
write: Atualizar seus dados
|
write: Atualizar seus dados
|
||||||
read: Ler seus dados
|
read: Ler seus dados
|
||||||
|
pagination:
|
||||||
|
submissions:
|
||||||
|
range_with_total: "%{from}-%{to} de %{count} submissões"
|
||||||
|
range_without_total: "%{from}-%{to} submissões"
|
||||||
|
templates:
|
||||||
|
range_with_total: "%{from}-%{to} de %{count} modelos"
|
||||||
|
range_without_total: "%{from}-%{to} modelos"
|
||||||
|
template_folders:
|
||||||
|
range_with_total: "%{from}-%{to} de %{count} pastas"
|
||||||
|
range_without_total: "%{from}-%{to} pastas"
|
||||||
|
users:
|
||||||
|
range_with_total: "%{from}-%{to} de %{count} usuários"
|
||||||
|
range_without_total: "%{from}-%{to} usuários"
|
||||||
|
items:
|
||||||
|
range_with_total: "%{from}-%{to} de %{count} itens"
|
||||||
|
range_without_total: "%{from}-%{to} itens"
|
||||||
|
|
||||||
de: &de
|
de: &de
|
||||||
stripe_integration: Stripe-Integration
|
stripe_integration: Stripe-Integration
|
||||||
@@ -4805,6 +4905,10 @@ de: &de
|
|||||||
three_months: 3 Monate
|
three_months: 3 Monate
|
||||||
eu_data_residency: EU-Datenspeicher
|
eu_data_residency: EU-Datenspeicher
|
||||||
please_enter_your_email_address_associated_with_the_completed_submission: Bitte gib deine E-Mail-Adresse ein, die mit der abgeschlossenen Übermittlung verknüpft ist.
|
please_enter_your_email_address_associated_with_the_completed_submission: Bitte gib deine E-Mail-Adresse ein, die mit der abgeschlossenen Übermittlung verknüpft ist.
|
||||||
|
esignature_disclosure: Nutzung der E-Signatur
|
||||||
|
share_link: Freigabelink
|
||||||
|
enable_shared_link: 'Freigabelink aktivieren'
|
||||||
|
share_link_is_currently_disabled: 'Freigabelink ist derzeit deaktiviert'
|
||||||
submission_sources:
|
submission_sources:
|
||||||
api: API
|
api: API
|
||||||
bulk: Massenversand
|
bulk: Massenversand
|
||||||
@@ -4889,6 +4993,22 @@ de: &de
|
|||||||
scopes:
|
scopes:
|
||||||
write: Aktualisiere deine Daten
|
write: Aktualisiere deine Daten
|
||||||
read: Lese deine Daten
|
read: Lese deine Daten
|
||||||
|
pagination:
|
||||||
|
submissions:
|
||||||
|
range_with_total: "%{from}-%{to} von %{count} Einreichungen"
|
||||||
|
range_without_total: "%{from}-%{to} Einreichungen"
|
||||||
|
templates:
|
||||||
|
range_with_total: "%{from}-%{to} von %{count} Vorlagen"
|
||||||
|
range_without_total: "%{from}-%{to} Vorlagen"
|
||||||
|
template_folders:
|
||||||
|
range_with_total: "%{from}-%{to} von %{count} Ordnern"
|
||||||
|
range_without_total: "%{from}-%{to} Ordner"
|
||||||
|
users:
|
||||||
|
range_with_total: "%{from}-%{to} von %{count} Benutzern"
|
||||||
|
range_without_total: "%{from}-%{to} Benutzer"
|
||||||
|
items:
|
||||||
|
range_with_total: "%{from}-%{to} von %{count} Elementen"
|
||||||
|
range_without_total: "%{from}-%{to} Elemente"
|
||||||
|
|
||||||
pl:
|
pl:
|
||||||
require_phone_2fa_to_open: Wymagaj uwierzytelniania telefonicznego 2FA do otwarcia
|
require_phone_2fa_to_open: Wymagaj uwierzytelniania telefonicznego 2FA do otwarcia
|
||||||
@@ -4958,6 +5078,8 @@ pl:
|
|||||||
open_source_documents_software: 'oprogramowanie do dokumentów open source'
|
open_source_documents_software: 'oprogramowanie do dokumentów open source'
|
||||||
eu_data_residency: Dane w UE
|
eu_data_residency: Dane w UE
|
||||||
please_enter_your_email_address_associated_with_the_completed_submission: Wprowadź adres e-mail powiązany z ukończonym zgłoszeniem.
|
please_enter_your_email_address_associated_with_the_completed_submission: Wprowadź adres e-mail powiązany z ukończonym zgłoszeniem.
|
||||||
|
privacy_policy: Polityka Prywatności
|
||||||
|
esignature_disclosure: Użycie e-podpisu
|
||||||
|
|
||||||
uk:
|
uk:
|
||||||
require_phone_2fa_to_open: Вимагати двофакторну автентифікацію через телефон для відкриття
|
require_phone_2fa_to_open: Вимагати двофакторну автентифікацію через телефон для відкриття
|
||||||
@@ -5027,6 +5149,8 @@ uk:
|
|||||||
open_source_documents_software: 'відкрите програмне забезпечення для документів'
|
open_source_documents_software: 'відкрите програмне забезпечення для документів'
|
||||||
eu_data_residency: 'Зберігання даних в ЄС'
|
eu_data_residency: 'Зберігання даних в ЄС'
|
||||||
please_enter_your_email_address_associated_with_the_completed_submission: "Введіть адресу електронної пошти, пов'язану із завершеним поданням."
|
please_enter_your_email_address_associated_with_the_completed_submission: "Введіть адресу електронної пошти, пов'язану із завершеним поданням."
|
||||||
|
privacy_policy: Політика конфіденційності
|
||||||
|
esignature_disclosure: Використання e-підпису
|
||||||
|
|
||||||
cs:
|
cs:
|
||||||
require_phone_2fa_to_open: Vyžadovat otevření pomocí telefonního 2FA
|
require_phone_2fa_to_open: Vyžadovat otevření pomocí telefonního 2FA
|
||||||
@@ -5096,6 +5220,8 @@ cs:
|
|||||||
open_source_documents_software: 'open source software pro dokumenty'
|
open_source_documents_software: 'open source software pro dokumenty'
|
||||||
eu_data_residency: 'Uložení dat v EU'
|
eu_data_residency: 'Uložení dat v EU'
|
||||||
please_enter_your_email_address_associated_with_the_completed_submission: Zadejte e-mailovou adresu spojenou s dokončeným odesláním.
|
please_enter_your_email_address_associated_with_the_completed_submission: Zadejte e-mailovou adresu spojenou s dokončeným odesláním.
|
||||||
|
privacy_policy: Zásady Ochrany Osobních Údajů
|
||||||
|
esignature_disclosure: Použití e-podpisu
|
||||||
|
|
||||||
he:
|
he:
|
||||||
require_phone_2fa_to_open: דרוש אימות דו-שלבי באמצעות טלפון לפתיחה
|
require_phone_2fa_to_open: דרוש אימות דו-שלבי באמצעות טלפון לפתיחה
|
||||||
@@ -5165,6 +5291,8 @@ he:
|
|||||||
open_source_documents_software: 'תוכנה בקוד פתוח למסמכים'
|
open_source_documents_software: 'תוכנה בקוד פתוח למסמכים'
|
||||||
eu_data_residency: 'נתונים באיחוד האירופי '
|
eu_data_residency: 'נתונים באיחוד האירופי '
|
||||||
please_enter_your_email_address_associated_with_the_completed_submission: 'אנא הזן את כתובת הדוא"ל המשויכת למשלוח שהושלם.'
|
please_enter_your_email_address_associated_with_the_completed_submission: 'אנא הזן את כתובת הדוא"ל המשויכת למשלוח שהושלם.'
|
||||||
|
privacy_policy: 'מדיניות פרטיות'
|
||||||
|
esignature_disclosure: 'גילוי חתימה אלקטרונית'
|
||||||
|
|
||||||
nl:
|
nl:
|
||||||
require_phone_2fa_to_open: Vereis telefoon 2FA om te openen
|
require_phone_2fa_to_open: Vereis telefoon 2FA om te openen
|
||||||
@@ -5234,6 +5362,8 @@ nl:
|
|||||||
open_source_documents_software: 'open source documenten software'
|
open_source_documents_software: 'open source documenten software'
|
||||||
eu_data_residency: Gegevens EU
|
eu_data_residency: Gegevens EU
|
||||||
please_enter_your_email_address_associated_with_the_completed_submission: Voer het e-mailadres in dat is gekoppeld aan de voltooide indiening.
|
please_enter_your_email_address_associated_with_the_completed_submission: Voer het e-mailadres in dat is gekoppeld aan de voltooide indiening.
|
||||||
|
privacy_policy: Privacybeleid
|
||||||
|
esignature_disclosure: Gebruik van e-handtekening
|
||||||
|
|
||||||
ar:
|
ar:
|
||||||
require_phone_2fa_to_open: "تطلب فتح عبر تحقق الهاتف ذو العاملين"
|
require_phone_2fa_to_open: "تطلب فتح عبر تحقق الهاتف ذو العاملين"
|
||||||
@@ -5303,6 +5433,8 @@ ar:
|
|||||||
open_source_documents_software: 'برنامج مستندات مفتوح المصدر'
|
open_source_documents_software: 'برنامج مستندات مفتوح المصدر'
|
||||||
eu_data_residency: 'بيانات في الاتحاد الأوروبي'
|
eu_data_residency: 'بيانات في الاتحاد الأوروبي'
|
||||||
please_enter_your_email_address_associated_with_the_completed_submission: 'يرجى إدخال عنوان البريد الإلكتروني المرتبط بالإرسال المكتمل.'
|
please_enter_your_email_address_associated_with_the_completed_submission: 'يرجى إدخال عنوان البريد الإلكتروني المرتبط بالإرسال المكتمل.'
|
||||||
|
privacy_policy: 'سياسة الخصوصية'
|
||||||
|
esignature_disclosure: 'إفصاح التوقيع الإلكتروني'
|
||||||
|
|
||||||
ko:
|
ko:
|
||||||
require_phone_2fa_to_open: 휴대폰 2FA를 열 때 요구함
|
require_phone_2fa_to_open: 휴대폰 2FA를 열 때 요구함
|
||||||
@@ -5372,6 +5504,8 @@ ko:
|
|||||||
open_source_documents_software: '오픈소스 문서 소프트웨어'
|
open_source_documents_software: '오픈소스 문서 소프트웨어'
|
||||||
eu_data_residency: 'EU 데이터 보관'
|
eu_data_residency: 'EU 데이터 보관'
|
||||||
please_enter_your_email_address_associated_with_the_completed_submission: '완료된 제출과 연결된 이메일 주소를 입력하세요.'
|
please_enter_your_email_address_associated_with_the_completed_submission: '완료된 제출과 연결된 이메일 주소를 입력하세요.'
|
||||||
|
privacy_policy: 개인정보 처리방침
|
||||||
|
esignature_disclosure: 전자서명 공개
|
||||||
|
|
||||||
ja:
|
ja:
|
||||||
require_phone_2fa_to_open: 電話による2段階認証が必要です
|
require_phone_2fa_to_open: 電話による2段階認証が必要です
|
||||||
@@ -5441,6 +5575,8 @@ ja:
|
|||||||
open_source_documents_software: 'オープンソースのドキュメントソフトウェア'
|
open_source_documents_software: 'オープンソースのドキュメントソフトウェア'
|
||||||
eu_data_residency: 'EU データ居住'
|
eu_data_residency: 'EU データ居住'
|
||||||
please_enter_your_email_address_associated_with_the_completed_submission: '完了した提出に関連付けられたメールアドレスを入力してください。'
|
please_enter_your_email_address_associated_with_the_completed_submission: '完了した提出に関連付けられたメールアドレスを入力してください。'
|
||||||
|
privacy_policy: プライバシーポリシー
|
||||||
|
esignature_disclosure: 電子署名に関する開示
|
||||||
|
|
||||||
en-US:
|
en-US:
|
||||||
<<: *en
|
<<: *en
|
||||||
|
|||||||
@@ -106,6 +106,7 @@ Rails.application.routes.draw do
|
|||||||
resource :form, only: %i[show], controller: 'templates_form_preview'
|
resource :form, only: %i[show], controller: 'templates_form_preview'
|
||||||
resource :code_modal, only: %i[show], controller: 'templates_code_modal'
|
resource :code_modal, only: %i[show], controller: 'templates_code_modal'
|
||||||
resource :preferences, only: %i[show create], controller: 'templates_preferences'
|
resource :preferences, only: %i[show create], controller: 'templates_preferences'
|
||||||
|
resource :share_link, only: %i[show create], controller: 'templates_share_link'
|
||||||
resources :recipients, only: %i[create], controller: 'templates_recipients'
|
resources :recipients, only: %i[create], controller: 'templates_recipients'
|
||||||
resources :submissions_export, only: %i[index new]
|
resources :submissions_export, only: %i[index new]
|
||||||
end
|
end
|
||||||
@@ -131,6 +132,8 @@ Rails.application.routes.draw do
|
|||||||
get :completed
|
get :completed
|
||||||
end
|
end
|
||||||
|
|
||||||
|
resource :resubmit_form, controller: 'start_form', only: :update
|
||||||
|
|
||||||
resources :submit_form, only: %i[], path: '' do
|
resources :submit_form, only: %i[], path: '' do
|
||||||
get :success, on: :collection
|
get :success, on: :collection
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AddSharedLinkToTemplates < ActiveRecord::Migration[8.0]
|
||||||
|
disable_ddl_transaction
|
||||||
|
|
||||||
|
class MigrationTemplate < ActiveRecord::Base
|
||||||
|
self.table_name = 'templates'
|
||||||
|
end
|
||||||
|
|
||||||
|
def up
|
||||||
|
add_column :templates, :shared_link, :boolean, if_not_exists: true
|
||||||
|
|
||||||
|
MigrationTemplate.where(shared_link: nil).in_batches.update_all(shared_link: true)
|
||||||
|
|
||||||
|
change_column_default :templates, :shared_link, from: nil, to: false
|
||||||
|
change_column_null :templates, :shared_link, false
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
remove_column :templates, :shared_link
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AddEmailEventsDateIndex < ActiveRecord::Migration[8.0]
|
||||||
|
def change
|
||||||
|
remove_index :email_events, :account_id
|
||||||
|
add_index :email_events, %i[account_id event_datetime]
|
||||||
|
end
|
||||||
|
end
|
||||||
+3
-2
@@ -10,7 +10,7 @@
|
|||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema[8.0].define(version: 2025_05_18_070555) do
|
ActiveRecord::Schema[8.0].define(version: 2025_05_30_080846) do
|
||||||
# These are extensions that must be enabled in order to support this database
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "plpgsql"
|
enable_extension "plpgsql"
|
||||||
|
|
||||||
@@ -174,7 +174,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_05_18_070555) do
|
|||||||
t.text "data", null: false
|
t.text "data", null: false
|
||||||
t.datetime "event_datetime", null: false
|
t.datetime "event_datetime", null: false
|
||||||
t.datetime "created_at", null: false
|
t.datetime "created_at", null: false
|
||||||
t.index ["account_id"], name: "index_email_events_on_account_id"
|
t.index ["account_id", "event_datetime"], name: "index_email_events_on_account_id_and_event_datetime"
|
||||||
t.index ["email"], name: "index_email_events_on_email"
|
t.index ["email"], name: "index_email_events_on_email"
|
||||||
t.index ["emailable_type", "emailable_id"], name: "index_email_events_on_emailable"
|
t.index ["emailable_type", "emailable_id"], name: "index_email_events_on_emailable"
|
||||||
t.index ["message_id"], name: "index_email_events_on_message_id"
|
t.index ["message_id"], name: "index_email_events_on_message_id"
|
||||||
@@ -361,6 +361,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_05_18_070555) do
|
|||||||
t.bigint "folder_id", null: false
|
t.bigint "folder_id", null: false
|
||||||
t.string "external_id"
|
t.string "external_id"
|
||||||
t.text "preferences", null: false
|
t.text "preferences", null: false
|
||||||
|
t.boolean "shared_link", default: false, null: false
|
||||||
t.index ["account_id"], name: "index_templates_on_account_id"
|
t.index ["account_id"], name: "index_templates_on_account_id"
|
||||||
t.index ["author_id"], name: "index_templates_on_author_id"
|
t.index ["author_id"], name: "index_templates_on_author_id"
|
||||||
t.index ["external_id"], name: "index_templates_on_external_id"
|
t.index ["external_id"], name: "index_templates_on_external_id"
|
||||||
|
|||||||
@@ -31,7 +31,9 @@ module Submissions
|
|||||||
def call(submission)
|
def call(submission)
|
||||||
account = submission.account
|
account = submission.account
|
||||||
|
|
||||||
I18n.with_locale(account.locale) do
|
last_submitter = submission.submitters.select(&:completed_at).max_by(&:completed_at)
|
||||||
|
|
||||||
|
I18n.with_locale(last_submitter.metadata.fetch('lang', account.locale)) do
|
||||||
document = build_audit_trail(submission)
|
document = build_audit_trail(submission)
|
||||||
|
|
||||||
pkcs = Accounts.load_signing_pkcs(account)
|
pkcs = Accounts.load_signing_pkcs(account)
|
||||||
@@ -41,8 +43,6 @@ module Submissions
|
|||||||
|
|
||||||
document.trailer.info[:Creator] = "#{Docuseal.product_name} (#{Docuseal::PRODUCT_URL})"
|
document.trailer.info[:Creator] = "#{Docuseal.product_name} (#{Docuseal::PRODUCT_URL})"
|
||||||
|
|
||||||
last_submitter = submission.submitters.select(&:completed_at).max_by(&:completed_at)
|
|
||||||
|
|
||||||
if pkcs
|
if pkcs
|
||||||
sign_params = {
|
sign_params = {
|
||||||
reason: sign_reason,
|
reason: sign_reason,
|
||||||
@@ -322,7 +322,7 @@ module Submissions
|
|||||||
resized_image = image.resize([scale, 1].min)
|
resized_image = image.resize([scale, 1].min)
|
||||||
io = StringIO.new(resized_image.write_to_buffer('.png'))
|
io = StringIO.new(resized_image.write_to_buffer('.png'))
|
||||||
|
|
||||||
width = field['type'] == 'initials' ? 100 : 200
|
width = field['type'] == 'initials' ? 50 : 200
|
||||||
height = resized_image.height * (width.to_f / resized_image.width)
|
height = resized_image.height * (width.to_f / resized_image.width)
|
||||||
|
|
||||||
if height > MAX_IMAGE_HEIGHT
|
if height > MAX_IMAGE_HEIGHT
|
||||||
|
|||||||
@@ -152,7 +152,8 @@ module Submissions
|
|||||||
TESTING_FOOTER
|
TESTING_FOOTER
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
"#{I18n.t('document_id', locale: submitter.account.locale)}: #{document_id}"
|
"#{I18n.t('document_id',
|
||||||
|
locale: submitter.metadata.fetch('lang', submitter.account.locale))}: #{document_id}"
|
||||||
end
|
end
|
||||||
|
|
||||||
text = HexaPDF::Layout::TextFragment.create(
|
text = HexaPDF::Layout::TextFragment.create(
|
||||||
@@ -182,6 +183,8 @@ module Submissions
|
|||||||
|
|
||||||
with_headings = find_last_submitter(submitter.submission, submitter:).blank? if with_headings.nil?
|
with_headings = find_last_submitter(submitter.submission, submitter:).blank? if with_headings.nil?
|
||||||
|
|
||||||
|
locale = submitter.metadata.fetch('lang', account.locale)
|
||||||
|
|
||||||
submitter.submission.template_fields.each do |field|
|
submitter.submission.template_fields.each do |field|
|
||||||
next if field['type'] == 'heading' && !with_headings
|
next if field['type'] == 'heading' && !with_headings
|
||||||
next if field['submitter_uuid'] != submitter.uuid && field['type'] != 'heading'
|
next if field['submitter_uuid'] != submitter.uuid && field['type'] != 'heading'
|
||||||
@@ -258,7 +261,7 @@ module Submissions
|
|||||||
reason_value = submitter.values[field.dig('preferences', 'reason_field_uuid')].presence
|
reason_value = submitter.values[field.dig('preferences', 'reason_field_uuid')].presence
|
||||||
|
|
||||||
reason_string =
|
reason_string =
|
||||||
I18n.with_locale(submitter.account.locale) do
|
I18n.with_locale(locale) do
|
||||||
"#{reason_value ? "#{I18n.t('reason')}: " : ''}#{reason_value || I18n.t('digitally_signed_by')} " \
|
"#{reason_value ? "#{I18n.t('reason')}: " : ''}#{reason_value || I18n.t('digitally_signed_by')} " \
|
||||||
"#{submitter.name}#{submitter.email.present? ? " <#{submitter.email}>" : ''}\n" \
|
"#{submitter.name}#{submitter.email.present? ? " <#{submitter.email}>" : ''}\n" \
|
||||||
"#{I18n.l(attachment.created_at.in_time_zone(submitter.account.timezone), format: :long)} " \
|
"#{I18n.l(attachment.created_at.in_time_zone(submitter.account.timezone), format: :long)} " \
|
||||||
@@ -442,7 +445,7 @@ module Submissions
|
|||||||
option = field['options']&.find { |o| o['uuid'] == area['option_uuid'] }
|
option = field['options']&.find { |o| o['uuid'] == area['option_uuid'] }
|
||||||
|
|
||||||
option_name = option['value'].presence
|
option_name = option['value'].presence
|
||||||
option_name ||= "#{I18n.t('option', locale: account.locale)} #{field['options'].index(option) + 1}"
|
option_name ||= "#{I18n.t('option', locale: locale)} #{field['options'].index(option) + 1}"
|
||||||
|
|
||||||
value = Array.wrap(value).include?(option_name)
|
value = Array.wrap(value).include?(option_name)
|
||||||
end
|
end
|
||||||
@@ -509,7 +512,7 @@ module Submissions
|
|||||||
end
|
end
|
||||||
else
|
else
|
||||||
if field['type'] == 'date'
|
if field['type'] == 'date'
|
||||||
value = TimeUtils.format_date_string(value, field.dig('preferences', 'format'), account.locale)
|
value = TimeUtils.format_date_string(value, field.dig('preferences', 'format'), locale)
|
||||||
end
|
end
|
||||||
|
|
||||||
value = NumberUtils.format_number(value, field.dig('preferences', 'format')) if field['type'] == 'number'
|
value = NumberUtils.format_number(value, field.dig('preferences', 'format')) if field['type'] == 'number'
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ module Templates
|
|||||||
template = original_template.account.templates.new
|
template = original_template.account.templates.new
|
||||||
|
|
||||||
template.external_id = external_id
|
template.external_id = external_id
|
||||||
|
template.shared_link = original_template.shared_link
|
||||||
template.author = author
|
template.author = author
|
||||||
template.name = name.presence || "#{original_template.name} (#{I18n.t('clone')})"
|
template.name = name.presence || "#{original_template.name} (#{I18n.t('clone')})"
|
||||||
|
|
||||||
|
|||||||
@@ -118,10 +118,10 @@ module Templates
|
|||||||
|
|
||||||
page = page.copy(interpretation: :srgb)
|
page = page.copy(interpretation: :srgb)
|
||||||
|
|
||||||
bitdepth = 2**page.stats.to_a[1..3].pluck(2).uniq.size
|
|
||||||
|
|
||||||
data =
|
data =
|
||||||
if format == FORMAT
|
if format == FORMAT
|
||||||
|
bitdepth = 2**page.stats.to_a[1..3].pluck(2).uniq.size
|
||||||
|
|
||||||
page.write_to_buffer(format, compression: 7, filter: 0, bitdepth:,
|
page.write_to_buffer(format, compression: 7, filter: 0, bitdepth:,
|
||||||
palette: true, Q: bitdepth == 8 ? Q : 5, dither: 0)
|
palette: true, Q: bitdepth == 8 ? Q : 5, dither: 0)
|
||||||
else
|
else
|
||||||
@@ -138,6 +138,8 @@ module Templates
|
|||||||
blob
|
blob
|
||||||
rescue Vips::Error, Pdfium::PdfiumError => e
|
rescue Vips::Error, Pdfium::PdfiumError => e
|
||||||
Rollbar.warning(e) if defined?(Rollbar)
|
Rollbar.warning(e) if defined?(Rollbar)
|
||||||
|
|
||||||
|
nil
|
||||||
ensure
|
ensure
|
||||||
doc_page&.close
|
doc_page&.close
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ module Templates
|
|||||||
only: %w[
|
only: %w[
|
||||||
id archived_at fields name preferences schema
|
id archived_at fields name preferences schema
|
||||||
slug source submitters created_at updated_at
|
slug source submitters created_at updated_at
|
||||||
author_id external_id folder_id
|
author_id external_id folder_id shared_link
|
||||||
],
|
],
|
||||||
methods: %i[application_key folder_name],
|
methods: %i[application_key folder_name],
|
||||||
include: { author: { only: %i[id email first_name last_name] } }
|
include: { author: { only: %i[id email first_name last_name] } }
|
||||||
|
|||||||
Vendored
BIN
Binary file not shown.
@@ -83,13 +83,15 @@ describe 'Templates API' do
|
|||||||
end
|
end
|
||||||
|
|
||||||
describe 'PUT /api/templates' do
|
describe 'PUT /api/templates' do
|
||||||
it 'update a template' do
|
let(:template) do
|
||||||
template = create(:template, account:,
|
create(:template, account:,
|
||||||
author:,
|
author:,
|
||||||
folder:,
|
folder:,
|
||||||
external_id: SecureRandom.base58(10),
|
external_id: SecureRandom.base58(10),
|
||||||
preferences: template_preferences)
|
preferences: template_preferences)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'updates a template' do
|
||||||
put "/api/templates/#{template.id}", headers: { 'x-auth-token': author.access_token.token }, params: {
|
put "/api/templates/#{template.id}", headers: { 'x-auth-token': author.access_token.token }, params: {
|
||||||
name: 'Updated Template Name',
|
name: 'Updated Template Name',
|
||||||
external_id: '123456'
|
external_id: '123456'
|
||||||
@@ -106,6 +108,24 @@ describe 'Templates API' do
|
|||||||
updated_at: template.updated_at
|
updated_at: template.updated_at
|
||||||
}.to_json))
|
}.to_json))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "enables the template's shared link" do
|
||||||
|
expect do
|
||||||
|
put "/api/templates/#{template.id}", headers: { 'x-auth-token': author.access_token.token }, params: {
|
||||||
|
shared_link: true
|
||||||
|
}.to_json
|
||||||
|
end.to change { template.reload.shared_link }.from(false).to(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "disables the template's shared link" do
|
||||||
|
template.update(shared_link: true)
|
||||||
|
|
||||||
|
expect do
|
||||||
|
put "/api/templates/#{template.id}", headers: { 'x-auth-token': author.access_token.token }, params: {
|
||||||
|
shared_link: false
|
||||||
|
}.to_json
|
||||||
|
end.to change { template.reload.shared_link }.from(true).to(false)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'DELETE /api/templates/:id' do
|
describe 'DELETE /api/templates/:id' do
|
||||||
@@ -206,6 +226,7 @@ describe 'Templates API' do
|
|||||||
name: 'sample-document'
|
name: 'sample-document'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
shared_link: template.shared_link,
|
||||||
author_id: author.id,
|
author_id: author.id,
|
||||||
archived_at: nil,
|
archived_at: nil,
|
||||||
created_at: template.created_at,
|
created_at: template.created_at,
|
||||||
|
|||||||
@@ -33,6 +33,14 @@ RSpec.describe 'Profile Settings' do
|
|||||||
expect(user.last_name).to eq('Beckham')
|
expect(user.last_name).to eq('Beckham')
|
||||||
expect(user.email).to eq('david.beckham@example.com')
|
expect(user.email).to eq('david.beckham@example.com')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'does not update if email is invalid' do
|
||||||
|
fill_in 'Email', with: 'devid+test@example'
|
||||||
|
|
||||||
|
all(:button, 'Update')[0].click
|
||||||
|
|
||||||
|
expect(page).to have_content('Email is invalid')
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when changes password' do
|
context 'when changes password' do
|
||||||
|
|||||||
@@ -51,6 +51,16 @@ RSpec.describe 'App Setup' do
|
|||||||
end
|
end
|
||||||
|
|
||||||
context 'when invalid information' do
|
context 'when invalid information' do
|
||||||
|
it 'does not setup the app if the email is invalid' do
|
||||||
|
fill_setup_form(form_data.merge(email: 'bob@example-com'))
|
||||||
|
|
||||||
|
expect do
|
||||||
|
click_button 'Submit'
|
||||||
|
end.not_to(change(User, :count))
|
||||||
|
|
||||||
|
expect(page).to have_content('Email is invalid')
|
||||||
|
end
|
||||||
|
|
||||||
it 'does not setup the app if the password is too short' do
|
it 'does not setup the app if the password is too short' do
|
||||||
fill_setup_form(form_data.merge(password: 'pass'))
|
fill_setup_form(form_data.merge(password: 'pass'))
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ RSpec.describe 'Signing Form' do
|
|||||||
let(:author) { create(:user, account:) }
|
let(:author) { create(:user, account:) }
|
||||||
|
|
||||||
context 'when the template form link is opened' do
|
context 'when the template form link is opened' do
|
||||||
let(:template) { create(:template, account:, author:, except_field_types: %w[phone payment stamp]) }
|
let(:template) do
|
||||||
|
create(:template, shared_link: true, account:, author:, except_field_types: %w[phone payment stamp])
|
||||||
|
end
|
||||||
|
|
||||||
before do
|
before do
|
||||||
visit start_form_path(slug: template.slug)
|
visit start_form_path(slug: template.slug)
|
||||||
@@ -811,7 +813,9 @@ RSpec.describe 'Signing Form' do
|
|||||||
end
|
end
|
||||||
|
|
||||||
context 'when the template requires multiple submitters' do
|
context 'when the template requires multiple submitters' do
|
||||||
let(:template) { create(:template, submitter_count: 2, account:, author:, only_field_types: %w[text]) }
|
let(:template) do
|
||||||
|
create(:template, shared_link: true, submitter_count: 2, account:, author:, only_field_types: %w[text])
|
||||||
|
end
|
||||||
|
|
||||||
context 'when default signer details are not defined' do
|
context 'when default signer details are not defined' do
|
||||||
it 'shows an explanation error message if a logged-in user associated with the template account opens the link' do
|
it 'shows an explanation error message if a logged-in user associated with the template account opens the link' do
|
||||||
|
|||||||
@@ -92,6 +92,23 @@ RSpec.describe 'Team Settings' do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'does not allow to create a new user with an invalid email' do
|
||||||
|
click_link 'New User'
|
||||||
|
|
||||||
|
within '#modal' do
|
||||||
|
fill_in 'First name', with: 'Joseph'
|
||||||
|
fill_in 'Last name', with: 'Smith'
|
||||||
|
fill_in 'Email', with: 'joseph.smith@gmail'
|
||||||
|
fill_in 'Password', with: 'password'
|
||||||
|
|
||||||
|
expect do
|
||||||
|
click_button 'Submit'
|
||||||
|
end.not_to change(User, :count)
|
||||||
|
|
||||||
|
expect(page).to have_content('Email is invalid')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
it 'updates a user' do
|
it 'updates a user' do
|
||||||
first(:link, 'Edit').click
|
first(:link, 'Edit').click
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
RSpec.describe 'Template Share Link' do
|
||||||
|
let!(:account) { create(:account) }
|
||||||
|
let!(:author) { create(:user, account:) }
|
||||||
|
let!(:template) { create(:template, account:, author:) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
sign_in(author)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the template is not shareable' do
|
||||||
|
before do
|
||||||
|
visit template_path(template)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'makes the template shareable' do
|
||||||
|
click_on 'Link'
|
||||||
|
|
||||||
|
expect do
|
||||||
|
within '#modal' do
|
||||||
|
check 'template_shared_link'
|
||||||
|
end
|
||||||
|
end.to change { template.reload.shared_link }.from(false).to(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'makes the template shareable on toggle' do
|
||||||
|
click_on 'Link'
|
||||||
|
|
||||||
|
expect do
|
||||||
|
within '#modal' do
|
||||||
|
find('#template_shared_link').click
|
||||||
|
end
|
||||||
|
end.to change { template.reload.shared_link }.from(false).to(true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the template is already shareable' do
|
||||||
|
before do
|
||||||
|
template.update(shared_link: true)
|
||||||
|
visit template_path(template)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'makes the template unshareable' do
|
||||||
|
click_on 'Link'
|
||||||
|
|
||||||
|
expect do
|
||||||
|
within '#modal' do
|
||||||
|
uncheck 'template_shared_link'
|
||||||
|
end
|
||||||
|
end.to change { template.reload.shared_link }.from(true).to(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -16,7 +16,7 @@ RSpec.describe 'Template' do
|
|||||||
expect(page).to have_content(template.name)
|
expect(page).to have_content(template.name)
|
||||||
expect(page).to have_content('There are no Submissions')
|
expect(page).to have_content('There are no Submissions')
|
||||||
expect(page).to have_content('Send an invitation to fill and complete the form')
|
expect(page).to have_content('Send an invitation to fill and complete the form')
|
||||||
expect(page).to have_link('Sign it Yourself')
|
expect(page).to have_button('Sign it Yourself')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user