mirror of
https://github.com/docusealco/docuseal.git
synced 2026-08-07 07:14:43 +00:00
Compare commits
102 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 14c84eff93 | |||
| 01d982b093 | |||
| 35852ef6ab | |||
| 47b235dc5f | |||
| 0e11d2a581 | |||
| e57e11f206 | |||
| 51f639065c | |||
| 6c1d4b466f | |||
| b940c597dc | |||
| 1ce67bcb39 | |||
| 5050a67d08 | |||
| 76321cef3c | |||
| 8f3a203994 | |||
| ea24562861 | |||
| 4a888d048e | |||
| 36331bb993 | |||
| 6f0e7c0ca8 | |||
| 2491b7b7e3 | |||
| 7965c17f46 | |||
| 5519459519 | |||
| d8bf2cc101 | |||
| 2aece98d67 | |||
| f602497255 | |||
| 11db68cdc7 | |||
| bed98f4344 | |||
| 055f7e2bc1 | |||
| dae3b4b6b5 | |||
| f2888a08dc | |||
| 95627c5963 | |||
| 4bb3721db0 | |||
| b12111383b | |||
| 263c0ff939 | |||
| efc8ba6b0a | |||
| 4c530b3f88 | |||
| f6358c9d47 | |||
| b3baea2f6f | |||
| c0594a8b74 | |||
| 1403401b03 | |||
| 700184f776 | |||
| b6a1a3ee4d | |||
| 8cbf5802b5 | |||
| ab9706afef | |||
| f07f9d8cf9 | |||
| 591caecb92 | |||
| ef4c2b7213 | |||
| 18999255a9 | |||
| 0a2a1de6b9 | |||
| 1b857b6eb3 | |||
| e5e6b11c95 | |||
| 3ec3f58d55 | |||
| 3bfce862ec | |||
| c01b67f7ec | |||
| ea0efe9314 | |||
| 28b583691f | |||
| 33811945be | |||
| 9d1860f038 | |||
| f03166b4ea | |||
| c59d948d41 | |||
| 57d63dd2f2 | |||
| c88715daec | |||
| 0375dad7c9 | |||
| de3f83db96 | |||
| a7bb96464c | |||
| 1d48fe9621 | |||
| 9544bb3d76 | |||
| 4e94664008 | |||
| e41fe5fbca | |||
| 24d713dec6 | |||
| f464e1c46a | |||
| 85d52d1f28 | |||
| e77b5fa2c9 | |||
| c91b4a765b | |||
| 2a031c14c8 | |||
| f7dfc0b2ed | |||
| 3b894f5281 | |||
| 9645332e48 | |||
| 5670390b37 | |||
| 260bd6d5d5 | |||
| fa0733f480 | |||
| 8b9678a434 | |||
| 43669bf095 | |||
| 2ef8df3f0c | |||
| 1c9d15aa7e | |||
| 9fcd8955d3 | |||
| 5e44ba49b8 | |||
| ac1f90e711 | |||
| c6eae66127 | |||
| 08aa902e07 | |||
| 1690337699 | |||
| f78cf8cc6e | |||
| 343118d00d | |||
| a3bd37c9a2 | |||
| 8bc13a5900 | |||
| 21138c1d02 | |||
| 38674a9fc9 | |||
| a7ed0b86a4 | |||
| 91a96f06c8 | |||
| 23631b75f2 | |||
| 0fb9c2bbf6 | |||
| 14fa8fc8e7 | |||
| 047c192988 | |||
| bfa244c8fa |
@@ -157,7 +157,10 @@ jobs:
|
||||
bundle install --jobs 4 --retry 4
|
||||
yarn install
|
||||
sudo apt-get update
|
||||
sudo apt-get install libvips
|
||||
sudo apt-get install -y libvips
|
||||
wget -O pdfium-linux.tgz "https://github.com/docusealco/pdfium-binaries/releases/latest/download/pdfium-linux-$(uname -m | sed 's/x86_64/x64/;s/aarch64/arm64/').tgz"
|
||||
sudo tar -xzf pdfium-linux.tgz --strip-components=1 -C /usr/lib lib/libpdfium.so
|
||||
rm -f pdfium-linux.tgz
|
||||
- name: Run
|
||||
env:
|
||||
RAILS_ENV: test
|
||||
|
||||
+16
-5
@@ -1,8 +1,17 @@
|
||||
FROM ruby:3.4.2-alpine AS fonts
|
||||
FROM ruby:3.4.2-alpine AS download
|
||||
|
||||
WORKDIR /fonts
|
||||
|
||||
RUN apk --no-cache add fontforge wget && wget https://github.com/satbyy/go-noto-universal/releases/download/v7.0/GoNotoKurrent-Regular.ttf && wget https://github.com/satbyy/go-noto-universal/releases/download/v7.0/GoNotoKurrent-Bold.ttf && wget https://github.com/impallari/DancingScript/raw/master/fonts/DancingScript-Regular.otf && wget https://cdn.jsdelivr.net/gh/notofonts/notofonts.github.io/fonts/NotoSansSymbols2/hinted/ttf/NotoSansSymbols2-Regular.ttf && wget https://github.com/Maxattax97/gnu-freefont/raw/master/ttf/FreeSans.ttf && wget https://github.com/impallari/DancingScript/raw/master/OFL.txt
|
||||
RUN apk --no-cache add fontforge wget && \
|
||||
wget https://github.com/satbyy/go-noto-universal/releases/download/v7.0/GoNotoKurrent-Regular.ttf && \
|
||||
wget https://github.com/satbyy/go-noto-universal/releases/download/v7.0/GoNotoKurrent-Bold.ttf && \
|
||||
wget https://github.com/impallari/DancingScript/raw/master/fonts/DancingScript-Regular.otf && \
|
||||
wget https://cdn.jsdelivr.net/gh/notofonts/notofonts.github.io/fonts/NotoSansSymbols2/hinted/ttf/NotoSansSymbols2-Regular.ttf && \
|
||||
wget https://github.com/Maxattax97/gnu-freefont/raw/master/ttf/FreeSans.ttf && \
|
||||
wget https://github.com/impallari/DancingScript/raw/master/OFL.txt && \
|
||||
wget -O pdfium-linux.tgz "https://github.com/docusealco/pdfium-binaries/releases/latest/download/pdfium-linux-$(uname -m | sed 's/x86_64/x64/;s/aarch64/arm64/').tgz" && \
|
||||
mkdir -p /pdfium-linux && \
|
||||
tar -xzf pdfium-linux.tgz -C /pdfium-linux
|
||||
|
||||
RUN fontforge -lang=py -c 'font1 = fontforge.open("FreeSans.ttf"); font2 = fontforge.open("NotoSansSymbols2-Regular.ttf"); font1.mergeFonts(font2); font1.generate("FreeSans.ttf")'
|
||||
|
||||
@@ -41,7 +50,7 @@ ENV OPENSSL_CONF=/app/openssl_legacy.cnf
|
||||
|
||||
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 vips-poppler poppler-utils 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\
|
||||
\n\
|
||||
@@ -70,8 +79,10 @@ COPY ./tmp ./tmp
|
||||
COPY LICENSE README.md Rakefile config.ru .version ./
|
||||
COPY .version ./public/version
|
||||
|
||||
COPY --from=fonts /fonts/GoNotoKurrent-Regular.ttf /fonts/GoNotoKurrent-Bold.ttf /fonts/DancingScript-Regular.otf /fonts/OFL.txt /fonts
|
||||
COPY --from=fonts /fonts/FreeSans.ttf /usr/share/fonts/freefont
|
||||
COPY --from=download /fonts/GoNotoKurrent-Regular.ttf /fonts/GoNotoKurrent-Bold.ttf /fonts/DancingScript-Regular.otf /fonts/OFL.txt /fonts
|
||||
COPY --from=download /fonts/FreeSans.ttf /usr/share/fonts/freefont
|
||||
COPY --from=download /pdfium-linux/lib/libpdfium.so /usr/lib/libpdfium.so
|
||||
COPY --from=download /pdfium-linux/licenses/pdfium.txt /usr/lib/libpdfium-LICENSE.txt
|
||||
COPY --from=webpack /app/public/packs ./public/packs
|
||||
|
||||
RUN ln -s /fonts /app/public/fonts
|
||||
|
||||
+1
-1
@@ -387,7 +387,7 @@ GEM
|
||||
puma (6.5.0)
|
||||
nio4r (~> 2.0)
|
||||
racc (1.8.1)
|
||||
rack (3.1.14)
|
||||
rack (3.1.16)
|
||||
rack-proxy (0.7.7)
|
||||
rack
|
||||
rack-session (2.0.0)
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class AccountConfigsController < ApplicationController
|
||||
before_action :load_account_config
|
||||
authorize_resource :account_config
|
||||
before_action :load_account_config, only: :create
|
||||
authorize_resource :account_config, only: :create
|
||||
|
||||
load_and_authorize_resource :account_config, only: :destroy
|
||||
|
||||
ALLOWED_KEYS = [
|
||||
AccountConfig::ALLOW_TYPED_SIGNATURE,
|
||||
@@ -30,6 +32,14 @@ class AccountConfigsController < ApplicationController
|
||||
head :ok
|
||||
end
|
||||
|
||||
def destroy
|
||||
raise InvalidKey unless ALLOWED_KEYS.include?(@account_config.key)
|
||||
|
||||
@account_config.destroy!
|
||||
|
||||
redirect_back(fallback_location: root_path)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_account_config
|
||||
|
||||
@@ -10,7 +10,7 @@ module Api
|
||||
end
|
||||
|
||||
def index
|
||||
submissions = Submissions.search(@submissions, params[:q])
|
||||
submissions = Submissions.search(current_user, @submissions, params[:q])
|
||||
submissions = filter_submissions(submissions, params)
|
||||
|
||||
submissions = paginate(submissions.preload(:created_by_user, :submitters,
|
||||
@@ -80,6 +80,8 @@ module Api
|
||||
end
|
||||
end
|
||||
|
||||
SearchEntries.enqueue_reindex(submissions)
|
||||
|
||||
render json: build_create_json(submissions)
|
||||
rescue Submitters::NormalizeValues::BaseError, Submissions::CreateFromSubmitters::BaseError,
|
||||
DownloadUtils::UnableToDownload => e
|
||||
@@ -110,7 +112,9 @@ module Api
|
||||
submissions = submissions.where(slug: params[:slug]) if params[:slug].present?
|
||||
|
||||
if params[:template_folder].present?
|
||||
submissions = submissions.joins(template: :folder).where(folder: { name: params[:template_folder] })
|
||||
folder = TemplateFolder.accessible_by(current_ability).find_by(name: params[:template_folder])
|
||||
|
||||
submissions = folder ? submissions.joins(:template).where(template: { folder_id: folder.id }) : submissions.none
|
||||
end
|
||||
|
||||
if params.key?(:archived)
|
||||
@@ -183,14 +187,14 @@ module Api
|
||||
def submissions_params
|
||||
permitted_attrs = [
|
||||
:send_email, :send_sms, :bcc_completed, :completed_redirect_url, :reply_to, :go_to_last,
|
||||
:expire_at,
|
||||
:expire_at, :name,
|
||||
{
|
||||
message: %i[subject body],
|
||||
submitters: [[:send_email, :send_sms, :completed_redirect_url, :uuid, :name, :email, :role,
|
||||
:completed, :phone, :application_key, :external_id, :reply_to, :go_to_last,
|
||||
{ metadata: {}, values: {}, roles: [], readonly_fields: [], message: %i[subject body],
|
||||
fields: [:name, :uuid, :default_value, :value, :title, :description,
|
||||
:readonly, :validation_pattern, :invalid_message,
|
||||
:readonly, :required, :validation_pattern, :invalid_message,
|
||||
{ default_value: [], value: [], preferences: {} }] }]]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -5,7 +5,7 @@ module Api
|
||||
load_and_authorize_resource :submitter
|
||||
|
||||
def index
|
||||
submitters = Submitters.search(@submitters, params[:q])
|
||||
submitters = Submitters.search(current_user, @submitters, params[:q])
|
||||
|
||||
submitters = filter_submitters(submitters, params)
|
||||
|
||||
@@ -37,14 +37,16 @@ module Api
|
||||
return render json: { error: 'Submitter has already completed the submission.' }, status: :unprocessable_entity
|
||||
end
|
||||
|
||||
role = @submitter.submission.template_submitters.find { |e| e['uuid'] == @submitter.uuid }['name']
|
||||
submission = @submitter.submission
|
||||
role = submission.template_submitters.find { |e| e['uuid'] == @submitter.uuid }['name']
|
||||
|
||||
normalized_params, new_attachments =
|
||||
Submissions::NormalizeParamUtils.normalize_submitter_params!(submitter_params.merge(role:), @submitter.template,
|
||||
for_submitter: @submitter)
|
||||
normalized_params, new_attachments = Submissions::NormalizeParamUtils.normalize_submitter_params!(
|
||||
submitter_params.merge(role:),
|
||||
@submitter.template || Template.new(submitters: submission.template_submitters, account: @submitter.account),
|
||||
for_submitter: @submitter
|
||||
)
|
||||
|
||||
Submissions::CreateFromSubmitters.maybe_set_template_fields(@submitter.submission,
|
||||
[normalized_params],
|
||||
Submissions::CreateFromSubmitters.maybe_set_template_fields(submission, [normalized_params],
|
||||
default_submitter_uuid: @submitter.uuid)
|
||||
|
||||
assign_submitter_attrs(@submitter, normalized_params)
|
||||
@@ -65,10 +67,10 @@ module Api
|
||||
Submitters.send_signature_requests([@submitter])
|
||||
end
|
||||
|
||||
render json: Submitters::SerializeForApi.call(@submitter, with_template: false,
|
||||
with_urls: true,
|
||||
with_events: false,
|
||||
params:)
|
||||
SearchEntries.enqueue_reindex(@submitter)
|
||||
|
||||
render json: Submitters::SerializeForApi.call(@submitter, with_template: false, with_urls: true,
|
||||
with_events: false, params:)
|
||||
rescue Submitters::NormalizeValues::BaseError, DownloadUtils::UnableToDownload => e
|
||||
Rollbar.warning(e) if defined?(Rollbar)
|
||||
|
||||
@@ -82,7 +84,7 @@ module Api
|
||||
:send_email, :send_sms, :reply_to, :completed_redirect_url, :uuid, :name, :email, :role,
|
||||
:completed, :phone, :application_key, :external_id, :go_to_last,
|
||||
{ metadata: {}, values: {}, readonly_fields: [], message: %i[subject body],
|
||||
fields: [[:name, :uuid, :default_value, :value,
|
||||
fields: [[:name, :uuid, :default_value, :value, :required,
|
||||
:readonly, :validation_pattern, :invalid_message,
|
||||
{ default_value: [], value: [], preferences: {} }]] }
|
||||
)
|
||||
|
||||
@@ -21,17 +21,20 @@ module Api
|
||||
)
|
||||
|
||||
cloned_template.source = :api
|
||||
cloned_template.save!
|
||||
|
||||
schema_documents = Templates::CloneAttachments.call(template: cloned_template,
|
||||
original_template: @template,
|
||||
documents: params[:documents])
|
||||
|
||||
cloned_template.save!
|
||||
|
||||
WebhookUrls.for_account_id(cloned_template.account_id, 'template.created').each do |webhook_url|
|
||||
SendTemplateCreatedWebhookRequestJob.perform_async('template_id' => cloned_template.id,
|
||||
'webhook_url_id' => webhook_url.id)
|
||||
end
|
||||
|
||||
SearchEntries.enqueue_reindex(cloned_template)
|
||||
|
||||
render json: Templates::SerializeForApi.call(cloned_template, schema_documents)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -65,6 +65,8 @@ module Api
|
||||
|
||||
@template.update!(template_params)
|
||||
|
||||
SearchEntries.enqueue_reindex(@template)
|
||||
|
||||
WebhookUrls.for_account_id(@template.account_id, 'template.updated').each do |webhook_url|
|
||||
SendTemplateUpdatedWebhookRequestJob.perform_async('template_id' => @template.id,
|
||||
'webhook_url_id' => webhook_url.id)
|
||||
@@ -86,12 +88,17 @@ module Api
|
||||
private
|
||||
|
||||
def filter_templates(templates, params)
|
||||
templates = Templates.search(templates, params[:q])
|
||||
templates = Templates.search(current_user, templates, params[:q])
|
||||
templates = params[:archived].in?(['true', true]) ? templates.archived : templates.active
|
||||
templates = templates.where(external_id: params[:application_key]) if params[:application_key].present?
|
||||
templates = templates.where(external_id: params[:external_id]) if params[:external_id].present?
|
||||
templates = templates.where(slug: params[:slug]) if params[:slug].present?
|
||||
templates = templates.joins(:folder).where(folder: { name: params[:folder] }) if params[:folder].present?
|
||||
|
||||
if params[:folder].present?
|
||||
folder = TemplateFolder.accessible_by(current_ability).find_by(name: params[:folder])
|
||||
|
||||
templates = folder ? templates.where(folder:) : templates.none
|
||||
end
|
||||
|
||||
templates
|
||||
end
|
||||
@@ -100,6 +107,7 @@ module Api
|
||||
permitted_params = [
|
||||
:name,
|
||||
:external_id,
|
||||
:shared_link,
|
||||
{
|
||||
submitters: [%i[name uuid is_requester invite_by_uuid optional_invite_by_uuid linked_to_uuid email]],
|
||||
fields: [[:uuid, :submitter_uuid, :name, :type,
|
||||
|
||||
@@ -55,6 +55,14 @@ class ApplicationController < ActionController::Base
|
||||
request.session[:impersonated_user_id] = user.uuid
|
||||
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
|
||||
|
||||
def with_locale(&)
|
||||
|
||||
@@ -9,7 +9,9 @@ class EmailSmtpSettingsController < ApplicationController
|
||||
|
||||
def create
|
||||
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')
|
||||
else
|
||||
|
||||
@@ -5,8 +5,12 @@ class ErrorsController < ActionController::Base
|
||||
'This feature is available in Pro Edition: https://www.docuseal.com/pricing'
|
||||
|
||||
ENTERPRISE_PATHS = [
|
||||
'/submissions/html',
|
||||
'/api/submissions/html',
|
||||
'/templates/html',
|
||||
'/api/templates/html',
|
||||
'/submissions/pdf',
|
||||
'/api/submissions/pdf',
|
||||
'/templates/pdf',
|
||||
'/api/templates/pdf',
|
||||
'/templates/doc',
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class SearchEntriesReindexController < ApplicationController
|
||||
def create
|
||||
authorize!(:manage, EncryptedConfig)
|
||||
|
||||
ReindexAllSearchEntriesJob.perform_async
|
||||
|
||||
AccountConfig.find_or_initialize_by(account_id: Account.minimum(:id), key: :fulltext_search)
|
||||
.update!(value: true)
|
||||
|
||||
Docuseal.instance_variable_set(:@fulltext_search, nil)
|
||||
|
||||
redirect_back(fallback_location: settings_account_path,
|
||||
notice: "Started building search index. Visit #{root_url}jobs/busy to check progress.")
|
||||
end
|
||||
end
|
||||
@@ -10,27 +10,37 @@ class SendSubmissionEmailController < ApplicationController
|
||||
SEND_DURATION = 30.minutes
|
||||
|
||||
def create
|
||||
@submitter =
|
||||
if params[:template_slug]
|
||||
Submitter.joins(submission: :template).find_by!(email: params[:email].to_s.downcase,
|
||||
template: { slug: params[:template_slug] })
|
||||
elsif params[:submission_slug]
|
||||
Submitter.joins(:submission).find_by!(email: params[:email].to_s.downcase,
|
||||
submission: { slug: params[:submission_slug] })
|
||||
else
|
||||
Submitter.find_by!(slug: params[:submitter_slug])
|
||||
if params[:template_slug]
|
||||
template = Template.find_by!(slug: params[:template_slug])
|
||||
|
||||
@submitter =
|
||||
Submitter.completed.where(submission: template.submissions).find_by!(email: params[:email].to_s.downcase)
|
||||
elsif params[:submission_slug]
|
||||
submission = Submission.find_by(slug: params[:submission_slug])
|
||||
|
||||
if submission
|
||||
@submitter = Submitter.completed.find_by(submission: submission, email: params[:email].to_s.downcase)
|
||||
end
|
||||
|
||||
return redirect_to submissions_preview_completed_path(params[:submission_slug], status: :error) unless @submitter
|
||||
else
|
||||
@submitter = Submitter.completed.find_by!(slug: params[:submitter_slug])
|
||||
end
|
||||
|
||||
RateLimit.call("send-email-#{@submitter.id}", limit: 2, ttl: 5.minutes)
|
||||
|
||||
unless EmailEvent.exists?(tag: :submitter_documents_copy, email: @submitter.email, emailable: @submitter,
|
||||
event_type: :send, created_at: SEND_DURATION.ago..Time.current)
|
||||
SubmitterMailer.documents_copy_email(@submitter, sig: true).deliver_later!
|
||||
end
|
||||
SubmitterMailer.documents_copy_email(@submitter, sig: true).deliver_later! unless already_sent?(@submitter)
|
||||
|
||||
respond_to do |f|
|
||||
f.html { render :success }
|
||||
f.json { head :ok }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def already_sent?(submitter)
|
||||
EmailEvent.exists?(tag: :submitter_documents_copy, email: submitter.email, emailable: submitter,
|
||||
event_type: :send, created_at: SEND_DURATION.ago..Time.current)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -34,6 +34,7 @@ class SetupController < ApplicationController
|
||||
{ key: EncryptedConfig::ESIGN_CERTS_KEY, value: GenerateCertificate.call.transform_values(&:to_pem) }
|
||||
]
|
||||
@account.encrypted_configs.create!(encrypted_configs)
|
||||
@account.account_configs.create!(key: :fulltext_search, value: true) if SearchEntry.table_exists?
|
||||
|
||||
Docuseal.refresh_default_url_options!
|
||||
|
||||
|
||||
@@ -8,20 +8,28 @@ class StartFormController < ApplicationController
|
||||
|
||||
around_action :with_browser_locale, 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 :authorize_start!, only: :update
|
||||
|
||||
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)
|
||||
.submitters.new(account_id: @template.account_id,
|
||||
uuid: (filter_undefined_submitters(@template).first ||
|
||||
@template.submitters.first)['uuid'])
|
||||
if @template.shared_link?
|
||||
@submitter = @template.submissions.new(account_id: @template.account_id)
|
||||
.submitters.new(account_id: @template.account_id,
|
||||
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
|
||||
|
||||
def update
|
||||
return redirect_to start_form_path(@template.slug) if @template.archived_at?
|
||||
|
||||
@submitter = find_or_initialize_submitter(@template, submitter_params)
|
||||
|
||||
if @submitter.completed_at?
|
||||
@@ -45,6 +53,8 @@ class StartFormController < ApplicationController
|
||||
if is_new_record
|
||||
enqueue_submission_create_webhooks(@submitter)
|
||||
|
||||
SearchEntries.enqueue_reindex(@submitter)
|
||||
|
||||
if @submitter.submission.expire_at?
|
||||
ProcessSubmissionExpiredJob.perform_at(@submitter.submission.expire_at,
|
||||
'submission_id' => @submitter.submission_id)
|
||||
@@ -59,6 +69,8 @@ class StartFormController < ApplicationController
|
||||
end
|
||||
|
||||
def completed
|
||||
return redirect_to start_form_path(@template.slug) if !@template.shared_link? || @template.archived_at?
|
||||
|
||||
@submitter = Submitter.where(submission: @template.submissions)
|
||||
.where.not(completed_at: nil)
|
||||
.find_by!(email: params[:email])
|
||||
@@ -66,6 +78,24 @@ class StartFormController < ApplicationController
|
||||
|
||||
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)
|
||||
WebhookUrls.for_account_id(submitter.account_id, 'submission.created').each do |webhook_url|
|
||||
SendSubmissionCreatedWebhookRequestJob.perform_async('submission_id' => submitter.submission_id,
|
||||
@@ -74,31 +104,29 @@ class StartFormController < ApplicationController
|
||||
end
|
||||
|
||||
def find_or_initialize_submitter(template, submitter_params)
|
||||
Submitter.where(submission: template.submissions.where(expire_at: Time.current..)
|
||||
.or(template.submissions.where(expire_at: nil)).where(archived_at: nil))
|
||||
.order(id: :desc)
|
||||
.where(declined_at: nil)
|
||||
.where(external_id: nil)
|
||||
.where(ip: [nil, request.remote_ip])
|
||||
.then { |rel| params[:resubmit].present? ? rel.where(completed_at: nil) : rel }
|
||||
.find_or_initialize_by(email: submitter_params[:email], **submitter_params.compact_blank)
|
||||
Submitter
|
||||
.where(submission: template.submissions.where(expire_at: Time.current..)
|
||||
.or(template.submissions.where(expire_at: nil)).where(archived_at: nil))
|
||||
.order(id: :desc)
|
||||
.where(declined_at: nil)
|
||||
.where(external_id: nil)
|
||||
.where(ip: [nil, request.remote_ip])
|
||||
.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
|
||||
|
||||
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(
|
||||
uuid: (filter_undefined_submitters(template).first || @template.submitters.first)['uuid'],
|
||||
ip: request.remote_ip,
|
||||
ua: request.user_agent,
|
||||
values: resubmit_submitter&.preferences&.fetch('default_values', nil) || {},
|
||||
preferences: resubmit_submitter&.preferences.presence || { 'send_email' => true },
|
||||
metadata: resubmit_submitter&.metadata.presence || {}
|
||||
values: @resubmit_submitter&.preferences&.fetch('default_values', nil) || {},
|
||||
preferences: @resubmit_submitter&.preferences.presence || { 'send_email' => true },
|
||||
metadata: @resubmit_submitter&.metadata.presence || {}
|
||||
)
|
||||
|
||||
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)
|
||||
end
|
||||
end
|
||||
@@ -116,19 +144,25 @@ class StartFormController < ApplicationController
|
||||
end
|
||||
|
||||
def filter_undefined_submitters(template)
|
||||
Templates.filter_undefined_submitters(template)
|
||||
Templates.filter_undefined_submitters(template.submitters)
|
||||
end
|
||||
|
||||
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|
|
||||
attrs[:email] = Submissions.normalize_email(attrs[:email])
|
||||
end
|
||||
end
|
||||
|
||||
def load_template
|
||||
slug = params[:slug] || params[:start_form_slug]
|
||||
|
||||
@template = Template.find_by!(slug:)
|
||||
@template =
|
||||
if @resubmit_submitter
|
||||
@resubmit_submitter.template
|
||||
else
|
||||
Template.find_by!(slug: params[:slug] || params[:start_form_slug])
|
||||
end
|
||||
end
|
||||
|
||||
def multiple_submitters_error_message
|
||||
|
||||
@@ -4,12 +4,12 @@ class SubmissionsArchivedController < ApplicationController
|
||||
load_and_authorize_resource :submission, parent: false
|
||||
|
||||
def index
|
||||
@submissions = @submissions.joins(:template)
|
||||
@submissions = @submissions.left_joins(:template)
|
||||
@submissions = @submissions.where.not(archived_at: nil)
|
||||
.or(@submissions.where.not(templates: { archived_at: nil }))
|
||||
.preload(:template_accesses, :created_by_user, template: :author)
|
||||
|
||||
@submissions = Submissions.search(@submissions, params[:q], search_template: true)
|
||||
@submissions = Submissions.search(current_user, @submissions, params[:q], search_template: true)
|
||||
@submissions = Submissions::Filter.call(@submissions, current_user, params)
|
||||
|
||||
@submissions = if params[:completed_at_from].present? || params[:completed_at_to].present?
|
||||
@@ -18,6 +18,6 @@ class SubmissionsArchivedController < ApplicationController
|
||||
@submissions.order(id: :desc)
|
||||
end
|
||||
|
||||
@pagy, @submissions = pagy(@submissions.preload(submitters: :start_form_submission_events))
|
||||
@pagy, @submissions = pagy_auto(@submissions.preload(submitters: :start_form_submission_events))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -8,6 +8,10 @@ class SubmissionsController < ApplicationController
|
||||
|
||||
prepend_before_action :maybe_redirect_com, only: %i[show]
|
||||
|
||||
before_action only: :create do
|
||||
authorize!(:create, Submission)
|
||||
end
|
||||
|
||||
def show
|
||||
@submission = Submissions.preload_with_pages(@submission)
|
||||
|
||||
@@ -26,8 +30,6 @@ class SubmissionsController < ApplicationController
|
||||
end
|
||||
|
||||
def create
|
||||
authorize!(:create, Submission)
|
||||
|
||||
save_template_message(@template, params) if params[:save_message] == '1'
|
||||
|
||||
if params[:is_custom_message] != '1'
|
||||
@@ -56,6 +58,8 @@ class SubmissionsController < ApplicationController
|
||||
|
||||
Submissions.send_signature_requests(submissions)
|
||||
|
||||
SearchEntries.enqueue_reindex(submissions)
|
||||
|
||||
redirect_to template_path(@template), notice: I18n.t('new_recipients_have_been_added')
|
||||
rescue Submissions::CreateFromSubmitters::BaseError => e
|
||||
render turbo_stream: turbo_stream.replace(:submitters_error,
|
||||
@@ -81,7 +85,7 @@ class SubmissionsController < ApplicationController
|
||||
I18n.t('submission_has_been_archived')
|
||||
end
|
||||
|
||||
redirect_back(fallback_location: template_path(@submission.template), notice:)
|
||||
redirect_back(fallback_location: @submission.template_id ? template_path(@submission.template) : root_path, notice:)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -4,13 +4,13 @@ class SubmissionsDashboardController < ApplicationController
|
||||
load_and_authorize_resource :submission, parent: false
|
||||
|
||||
def index
|
||||
@submissions = @submissions.joins(:template)
|
||||
@submissions = @submissions.left_joins(:template)
|
||||
|
||||
@submissions = @submissions.where(archived_at: nil)
|
||||
.where(templates: { archived_at: nil })
|
||||
.preload(:template_accesses, :created_by_user, template: :author)
|
||||
|
||||
@submissions = Submissions.search(@submissions, params[:q], search_template: true)
|
||||
@submissions = Submissions.search(current_user, @submissions, params[:q], search_template: true)
|
||||
@submissions = Submissions::Filter.call(@submissions, current_user, params)
|
||||
|
||||
@submissions = if params[:completed_at_from].present? || params[:completed_at_to].present?
|
||||
@@ -19,6 +19,6 @@ class SubmissionsDashboardController < ApplicationController
|
||||
@submissions.order(id: :desc)
|
||||
end
|
||||
|
||||
@pagy, @submissions = pagy(@submissions.preload(submitters: :start_form_submission_events))
|
||||
@pagy, @submissions = pagy_auto(@submissions.preload(submitters: :start_form_submission_events))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class SubmissionsPreviewController < ApplicationController
|
||||
around_action :with_browser_locale
|
||||
skip_before_action :authenticate_user!
|
||||
skip_authorization_check
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ class SubmitFormController < ApplicationController
|
||||
skip_authorization_check
|
||||
|
||||
before_action :load_submitter, only: %i[show update completed]
|
||||
before_action :maybe_render_locked_page, only: :show
|
||||
|
||||
CONFIG_KEYS = [].freeze
|
||||
|
||||
@@ -15,19 +16,14 @@ class SubmitFormController < ApplicationController
|
||||
submission = @submitter.submission
|
||||
|
||||
return redirect_to submit_form_completed_path(@submitter.slug) if @submitter.completed_at?
|
||||
return render :archived if submission.template.archived_at? ||
|
||||
submission.archived_at? ||
|
||||
@submitter.account.archived_at?
|
||||
return render :expired if submission.expired?
|
||||
return render :declined if @submitter.declined_at?
|
||||
|
||||
@form_configs = Submitters::FormConfigs.call(@submitter, CONFIG_KEYS)
|
||||
|
||||
return render :awaiting if (@form_configs[:enforce_signing_order] ||
|
||||
submission.template.preferences['submitters_order'] == 'preserved') &&
|
||||
submission.template&.preferences&.dig('submitters_order') == 'preserved') &&
|
||||
!Submitters.current_submitter_order?(@submitter)
|
||||
|
||||
Submitters.preload_with_pages(@submitter)
|
||||
Submissions.preload_with_pages(submission)
|
||||
|
||||
Submitters::MaybeUpdateDefaultValues.call(@submitter, current_user)
|
||||
|
||||
@@ -54,7 +50,7 @@ class SubmitFormController < ApplicationController
|
||||
return render json: { error: I18n.t('form_has_been_completed_already') }, status: :unprocessable_entity
|
||||
end
|
||||
|
||||
if @submitter.template.archived_at? || @submitter.submission.archived_at?
|
||||
if @submitter.template&.archived_at? || @submitter.submission.archived_at?
|
||||
return render json: { error: I18n.t('form_has_been_archived') }, status: :unprocessable_entity
|
||||
end
|
||||
|
||||
@@ -84,6 +80,15 @@ class SubmitFormController < ApplicationController
|
||||
|
||||
private
|
||||
|
||||
def maybe_render_locked_page
|
||||
return render :archived if @submitter.submission.template&.archived_at? ||
|
||||
@submitter.submission.archived_at? ||
|
||||
@submitter.account.archived_at?
|
||||
return render :expired if @submitter.submission.expired?
|
||||
|
||||
render :declined if @submitter.declined_at?
|
||||
end
|
||||
|
||||
def load_submitter
|
||||
@submitter = Submitter.find_by!(slug: params[:slug] || params[:submit_form_slug])
|
||||
end
|
||||
|
||||
@@ -11,7 +11,7 @@ class SubmitFormDeclineController < ApplicationController
|
||||
submitter.completed_at? ||
|
||||
submitter.submission.archived_at? ||
|
||||
submitter.submission.expired? ||
|
||||
submitter.submission.template.archived_at?
|
||||
submitter.submission.template&.archived_at?
|
||||
|
||||
ApplicationRecord.transaction do
|
||||
submitter.update!(declined_at: Time.current)
|
||||
|
||||
@@ -14,7 +14,10 @@ class SubmitFormDownloadController < ApplicationController
|
||||
return head :unprocessable_entity if @submitter.declined_at? ||
|
||||
@submitter.submission.archived_at? ||
|
||||
@submitter.submission.expired? ||
|
||||
@submitter.submission.template.archived_at?
|
||||
@submitter.submission.template&.archived_at? ||
|
||||
AccountConfig.exists?(account_id: @submitter.account_id,
|
||||
key: AccountConfig::ALLOW_TO_PARTIAL_DOWNLOAD_KEY,
|
||||
value: false)
|
||||
|
||||
last_completed_submitter = @submitter.submission.submitters
|
||||
.where.not(id: @submitter.id)
|
||||
@@ -25,7 +28,7 @@ class SubmitFormDownloadController < ApplicationController
|
||||
if last_completed_submitter
|
||||
Submitters.select_attachments_for_download(last_completed_submitter)
|
||||
else
|
||||
@submitter.submission.template.schema_documents.preload(:blob)
|
||||
@submitter.submission.schema_documents.preload(:blob)
|
||||
end
|
||||
|
||||
urls = attachments.map do |attachment|
|
||||
|
||||
@@ -22,13 +22,17 @@ class SubmittersAutocompleteController < ApplicationController
|
||||
|
||||
def search_submitters(submitters)
|
||||
if SELECT_COLUMNS.include?(params[:field])
|
||||
column = Submitter.arel_table[params[:field].to_sym]
|
||||
if Docuseal.fulltext_search?
|
||||
Submitters.fulltext_search_field(current_user, submitters, params[:q], params[:field])
|
||||
else
|
||||
column = Submitter.arel_table[params[:field].to_sym]
|
||||
|
||||
term = "#{params[:q].downcase}%"
|
||||
term = "#{params[:q].downcase}%"
|
||||
|
||||
submitters.where(column.matches(term))
|
||||
submitters.where(column.matches(term))
|
||||
end
|
||||
else
|
||||
Submitters.search(submitters, params[:q])
|
||||
Submitters.search(current_user, submitters, params[:q])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -38,6 +38,8 @@ class SubmittersController < ApplicationController
|
||||
if @submitter.save
|
||||
maybe_resend_email_sms(@submitter, params)
|
||||
|
||||
SearchEntries.enqueue_reindex(@submitter)
|
||||
|
||||
redirect_back fallback_location: submission_path(submission), notice: I18n.t('changes_have_been_saved')
|
||||
else
|
||||
redirect_back fallback_location: submission_path(submission), alert: I18n.t('unable_to_save')
|
||||
|
||||
@@ -6,13 +6,15 @@ class SubmittersResubmitController < ApplicationController
|
||||
def update
|
||||
return redirect_to submit_form_path(slug: @submitter.slug) if @submitter.email != current_user.email
|
||||
|
||||
submission = @submitter.template.submissions.new(created_by_user: current_user,
|
||||
submitters_order: :preserved,
|
||||
**@submitter.submission.slice(:template_fields,
|
||||
:account_id,
|
||||
:template_schema,
|
||||
:template_submitters,
|
||||
:preferences))
|
||||
submission = @submitter.account.submissions.new(created_by_user: current_user,
|
||||
submitters_order: :preserved,
|
||||
**@submitter.submission.slice(:template_fields,
|
||||
:account_id,
|
||||
:name,
|
||||
:template_id,
|
||||
:template_schema,
|
||||
:template_submitters,
|
||||
:preferences))
|
||||
|
||||
@submitter.submission.submitters.each do |submitter|
|
||||
new_submitter = submission.submitters.new(submitter.slice(:uuid, :email, :phone, :name,
|
||||
@@ -27,6 +29,10 @@ class SubmittersResubmitController < ApplicationController
|
||||
|
||||
submission.save!
|
||||
|
||||
@submitter.submission.documents_attachments.each do |attachment|
|
||||
submission.documents_attachments.create!(uuid: attachment.uuid, blob_id: attachment.blob_id)
|
||||
end
|
||||
|
||||
redirect_to submit_form_path(slug: @new_submitter.slug)
|
||||
end
|
||||
|
||||
|
||||
@@ -6,7 +6,9 @@ class TemplateFoldersAutocompleteController < ApplicationController
|
||||
LIMIT = 100
|
||||
|
||||
def index
|
||||
template_folders = @template_folders.joins(:templates).where(templates: { archived_at: nil }).distinct
|
||||
templates_query = Template.accessible_by(current_ability).where(archived_at: nil)
|
||||
|
||||
template_folders = @template_folders.where(id: templates_query.select(:folder_id))
|
||||
template_folders = TemplateFolders.search(template_folders, params[:q]).limit(LIMIT)
|
||||
|
||||
render json: template_folders.as_json(only: %i[name archived_at])
|
||||
|
||||
@@ -6,10 +6,10 @@ class TemplateFoldersController < ApplicationController
|
||||
def show
|
||||
@templates = @template_folder.templates.active.accessible_by(current_ability)
|
||||
.preload(:author, :template_accesses)
|
||||
@templates = Templates.search(@templates, params[:q])
|
||||
@templates = Templates.search(current_user, @templates, params[:q])
|
||||
@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
|
||||
|
||||
def edit; end
|
||||
|
||||
@@ -5,8 +5,8 @@ class TemplatesArchivedController < ApplicationController
|
||||
|
||||
def index
|
||||
@templates = @templates.where.not(archived_at: nil).preload(:author, :folder, :template_accesses).order(id: :desc)
|
||||
@templates = Templates.search(@templates, params[:q])
|
||||
@templates = Templates.search(current_user, @templates, params[:q])
|
||||
|
||||
@pagy, @templates = pagy(@templates, limit: 12)
|
||||
@pagy, @templates = pagy_auto(@templates, limit: 12)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,7 +6,7 @@ class TemplatesArchivedSubmissionsController < ApplicationController
|
||||
|
||||
def index
|
||||
@submissions = @submissions.where.not(archived_at: nil)
|
||||
@submissions = Submissions.search(@submissions, params[:q], search_values: true)
|
||||
@submissions = Submissions.search(current_user, @submissions, params[:q], search_values: true)
|
||||
@submissions = Submissions::Filter.call(@submissions, current_user, params)
|
||||
|
||||
@submissions = if params[:completed_at_from].present? || params[:completed_at_to].present?
|
||||
@@ -15,7 +15,7 @@ class TemplatesArchivedSubmissionsController < ApplicationController
|
||||
@submissions.order(id: :desc)
|
||||
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
|
||||
redirect_to root_path
|
||||
end
|
||||
|
||||
@@ -22,6 +22,8 @@ class TemplatesCloneAndReplaceController < ApplicationController
|
||||
Templates::CloneAttachments.call(template: cloned_template, original_template: @template,
|
||||
excluded_attachment_uuids: documents.map(&:uuid))
|
||||
|
||||
SearchEntries.enqueue_reindex(cloned_template)
|
||||
|
||||
respond_to do |f|
|
||||
f.html { redirect_to edit_template_path(cloned_template) }
|
||||
f.json { render json: { id: cloned_template.id } }
|
||||
|
||||
@@ -8,7 +8,7 @@ class TemplatesController < ApplicationController
|
||||
def show
|
||||
submissions = @template.submissions.accessible_by(current_ability)
|
||||
submissions = submissions.active if @template.archived_at.blank?
|
||||
submissions = Submissions.search(submissions, params[:q], search_values: true)
|
||||
submissions = Submissions.search(current_user, submissions, params[:q], search_values: true)
|
||||
submissions = Submissions::Filter.call(submissions, current_user, params.except(:status))
|
||||
|
||||
@base_submissions = submissions
|
||||
@@ -21,7 +21,7 @@ class TemplatesController < ApplicationController
|
||||
submissions.order(id: :desc)
|
||||
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
|
||||
redirect_to root_path
|
||||
end
|
||||
@@ -72,6 +72,8 @@ class TemplatesController < ApplicationController
|
||||
if @template.save
|
||||
Templates::CloneAttachments.call(template: @template, original_template: @base_template) if @base_template
|
||||
|
||||
SearchEntries.enqueue_reindex(@template)
|
||||
|
||||
enqueue_template_created_webhooks(@template)
|
||||
|
||||
maybe_redirect_to_template(@template)
|
||||
@@ -81,7 +83,13 @@ class TemplatesController < ApplicationController
|
||||
end
|
||||
|
||||
def update
|
||||
@template.update!(template_params)
|
||||
@template.assign_attributes(template_params)
|
||||
|
||||
is_name_changed = @template.name_changed?
|
||||
|
||||
@template.save!
|
||||
|
||||
SearchEntries.enqueue_reindex(@template) if is_name_changed
|
||||
|
||||
enqueue_template_updated_webhooks(@template)
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ class TemplatesDashboardController < ApplicationController
|
||||
|
||||
@pagy, @template_folders = pagy(
|
||||
@template_folders,
|
||||
items: FOLDERS_PER_PAGE,
|
||||
limit: FOLDERS_PER_PAGE,
|
||||
page: @template_folders.count > SHOW_TEMPLATES_FOLDERS_THRESHOLD ? params[:page] : 1
|
||||
)
|
||||
|
||||
@@ -25,7 +25,7 @@ class TemplatesDashboardController < ApplicationController
|
||||
@templates = @templates.none
|
||||
else
|
||||
@template_folders = @template_folders.reject { |e| e.name == TemplateFolder::DEFAULT_NAME }
|
||||
@templates = filter_templates(@templates)
|
||||
@templates = filter_templates(@templates).preload(:author, :template_accesses)
|
||||
@templates = Templates::Order.call(@templates, current_user, cookies.permanent[:dashboard_templates_order])
|
||||
|
||||
limit =
|
||||
@@ -35,14 +35,16 @@ class TemplatesDashboardController < ApplicationController
|
||||
(@template_folders.size < 7 ? 9 : 6)
|
||||
end
|
||||
|
||||
@pagy, @templates = pagy(@templates, limit:)
|
||||
@pagy, @templates = pagy_auto(@templates, limit:)
|
||||
|
||||
load_related_submissions if params[:q].present? && @templates.blank?
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def filter_templates(templates)
|
||||
rel = templates.active.preload(:author, :template_accesses)
|
||||
rel = templates.active
|
||||
|
||||
if params[:q].blank?
|
||||
if Docuseal.multitenant? ? current_account.testing? : current_account.linked_account_account
|
||||
@@ -51,13 +53,20 @@ class TemplatesDashboardController < ApplicationController
|
||||
|
||||
shared_template_ids = TemplateSharing.where(account_id: shared_account_ids).select(:template_id)
|
||||
|
||||
rel = rel.where(folder_id: current_account.default_template_folder.id).or(rel.where(id: shared_template_ids))
|
||||
rel = Template.where(
|
||||
Template.arel_table[:id].in(
|
||||
Arel::Nodes::Union.new(
|
||||
rel.where(folder_id: current_account.default_template_folder.id).select(:id).arel,
|
||||
shared_template_ids.arel
|
||||
)
|
||||
)
|
||||
)
|
||||
else
|
||||
rel = rel.where(folder_id: current_account.default_template_folder.id)
|
||||
end
|
||||
end
|
||||
|
||||
Templates.search(rel, params[:q])
|
||||
Templates.search(current_user, rel, params[:q])
|
||||
end
|
||||
|
||||
def sort_template_folders(template_folders, current_user, order)
|
||||
@@ -93,4 +102,19 @@ class TemplatesDashboardController < ApplicationController
|
||||
template_folders.order(id: :desc)
|
||||
end
|
||||
end
|
||||
|
||||
def load_related_submissions
|
||||
@related_submissions = Submission.accessible_by(current_ability)
|
||||
.left_joins(:template)
|
||||
.where(archived_at: nil)
|
||||
.where(templates: { archived_at: nil })
|
||||
.preload(:template_accesses, :created_by_user,
|
||||
template: :author,
|
||||
submitters: :start_form_submission_events)
|
||||
|
||||
@related_submissions = Submissions.search(current_user, @related_submissions, params[:q])
|
||||
.order(id: :desc)
|
||||
|
||||
@related_submissions_pagy, @related_submissions = pagy_auto(@related_submissions, limit: 5)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -13,7 +13,7 @@ class TemplatesFormPreviewController < ApplicationController
|
||||
|
||||
@submitter.submission.submitters = @template.submitters.map { |item| Submitter.new(uuid: item['uuid']) }
|
||||
|
||||
Submitters.preload_with_pages(@submitter)
|
||||
Submissions.preload_with_pages(@submitter.submission)
|
||||
|
||||
@attachments_index = ActiveStorage::Attachment.where(record: @submitter.submission.submitters, name: :attachments)
|
||||
.preload(:blob).index_by(&:uuid)
|
||||
|
||||
@@ -25,7 +25,7 @@ class TemplatesPreferencesController < ApplicationController
|
||||
documents_copy_email_attach_documents documents_copy_email_reply_to
|
||||
completed_notification_email_attach_documents
|
||||
completed_redirect_url validate_unique_submitters
|
||||
submitters_order require_phone_2fa
|
||||
require_all_submitters submitters_order require_phone_2fa
|
||||
default_expire_at_duration
|
||||
default_expire_at
|
||||
completed_notification_email_subject completed_notification_email_body
|
||||
|
||||
@@ -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
|
||||
@@ -25,6 +25,8 @@ class TemplatesUploadsController < ApplicationController
|
||||
|
||||
enqueue_template_created_webhooks(@template)
|
||||
|
||||
SearchEntries.enqueue_reindex(@template)
|
||||
|
||||
redirect_to edit_template_path(@template)
|
||||
rescue Templates::CreateAttachments::PdfEncrypted
|
||||
render turbo_stream: turbo_stream.append(params[:form_id], html: helpers.tag.prompt_password)
|
||||
|
||||
@@ -24,6 +24,7 @@ import SubmitForm from './elements/submit_form'
|
||||
import PromptPassword from './elements/prompt_password'
|
||||
import EmailsTextarea from './elements/emails_textarea'
|
||||
import ToggleOnSubmit from './elements/toggle_on_submit'
|
||||
import CheckOnClick from './elements/check_on_click'
|
||||
import PasswordInput from './elements/password_input'
|
||||
import SearchInput from './elements/search_input'
|
||||
import ToggleAttribute from './elements/toggle_attribute'
|
||||
@@ -103,6 +104,7 @@ safeRegisterElement('set-date-button', SetDateButton)
|
||||
safeRegisterElement('indeterminate-checkbox', IndeterminateCheckbox)
|
||||
safeRegisterElement('app-tour', AppTour)
|
||||
safeRegisterElement('dashboard-dropzone', DashboardDropzone)
|
||||
safeRegisterElement('check-on-click', CheckOnClick)
|
||||
|
||||
safeRegisterElement('template-builder', class extends HTMLElement {
|
||||
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.addEventListener('click', (e) => {
|
||||
e.stopPropagation()
|
||||
|
||||
const text = this.dataset.text || this.innerText.trim()
|
||||
|
||||
if (navigator.clipboard) {
|
||||
|
||||
@@ -36,7 +36,9 @@ export default class extends HTMLElement {
|
||||
}
|
||||
|
||||
fetch = (text, resolve) => {
|
||||
const q = text.split(/[;,\s]+/).pop().trim()
|
||||
const q = this.dataset.field === 'email'
|
||||
? text.split(/[;,\s]+/).pop().trim()
|
||||
: text
|
||||
|
||||
if (q) {
|
||||
const queryParams = new URLSearchParams({ q, field: this.dataset.field })
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
</div>
|
||||
<div
|
||||
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">
|
||||
{{ optionValue(option) }}
|
||||
@@ -55,12 +55,12 @@
|
||||
>
|
||||
<div
|
||||
v-else-if="field.type === 'signature' && signature"
|
||||
class="flex justify-between h-full gap-1 overflow-hidden"
|
||||
:class="isNarrow ? 'flex-row' : 'flex-col'"
|
||||
class="flex justify-between h-full gap-1 overflow-hidden w-full"
|
||||
:class="isNarrow && (withSignatureId || field.preferences?.reason_field_uuid) ? 'flex-row' : 'flex-col'"
|
||||
>
|
||||
<div
|
||||
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%"
|
||||
>
|
||||
<img
|
||||
@@ -69,7 +69,7 @@
|
||||
>
|
||||
</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="isNarrow ? 'w-1/2' : 'w-full'"
|
||||
>
|
||||
|
||||
@@ -1390,6 +1390,7 @@ export default {
|
||||
|
||||
if (isLastStep && !emptyRequiredField && !this.inviteSubmitters.length && !this.optionalInviteSubmitters.length) {
|
||||
formData.append('completed', 'true')
|
||||
formData.append('timezone', Intl.DateTimeFormat().resolvedOptions().timeZone)
|
||||
}
|
||||
|
||||
let saveStepRequest
|
||||
|
||||
@@ -211,7 +211,7 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
computedPreviousValue () {
|
||||
if (this.isUsePreviousValue) {
|
||||
if (this.isUsePreviousValue && this.field.required === true) {
|
||||
return this.previousValue
|
||||
} else {
|
||||
return null
|
||||
|
||||
@@ -133,7 +133,7 @@
|
||||
class="mx-auto bg-white border border-base-300 rounded max-h-44"
|
||||
>
|
||||
<FileDropzone
|
||||
v-if="format === 'upload' && !modelValue"
|
||||
v-if="format === 'upload' && !modelValue && !computedPreviousValue"
|
||||
:message="`${t('upload')} ${field.name || t('signature')}`"
|
||||
:submitter-slug="submitterSlug"
|
||||
:dry-run="dryRun"
|
||||
@@ -414,7 +414,7 @@ export default {
|
||||
}
|
||||
},
|
||||
computedPreviousValue () {
|
||||
if (this.isUsePreviousValue) {
|
||||
if (this.isUsePreviousValue && this.field.required === true) {
|
||||
return this.previousValue
|
||||
} else {
|
||||
return null
|
||||
|
||||
@@ -151,8 +151,10 @@ export default {
|
||||
resizeTextarea () {
|
||||
const textarea = this.$refs.textarea
|
||||
|
||||
textarea.style.height = 'auto'
|
||||
textarea.style.height = Math.min(250, textarea.scrollHeight) + 'px'
|
||||
if (textarea) {
|
||||
textarea.style.height = 'auto'
|
||||
textarea.style.height = Math.min(250, textarea.scrollHeight) + 'px'
|
||||
}
|
||||
},
|
||||
toggleTextArea () {
|
||||
this.isTextArea = true
|
||||
|
||||
@@ -79,13 +79,52 @@
|
||||
name="buttons"
|
||||
/>
|
||||
<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
|
||||
v-if="withSignYourselfButton"
|
||||
v-else-if="withSignYourselfButton"
|
||||
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"
|
||||
:target="template.submitters.length > 1 ? '' : '_blank'"
|
||||
:data-turbo-frame="template.submitters.length > 1 ? 'modal' : ''"
|
||||
data-turbo-frame="modal"
|
||||
@click="maybeShowErrorTemplateAlert"
|
||||
>
|
||||
<IconWritingSign
|
||||
@@ -1013,6 +1052,12 @@ export default {
|
||||
field.readonly = true
|
||||
}
|
||||
|
||||
if (type === 'datenow') {
|
||||
field.type = 'date'
|
||||
field.readonly = true
|
||||
field.default_value = '{{date}}'
|
||||
}
|
||||
|
||||
if (type === 'date') {
|
||||
field.preferences = {
|
||||
format: this.defaultDateFormat
|
||||
@@ -1279,7 +1324,7 @@ export default {
|
||||
area.h = (pageMask.clientWidth / 35 / pageMask.clientWidth)
|
||||
}
|
||||
},
|
||||
onDraw (area) {
|
||||
onDraw ({ area, isTooSmall }) {
|
||||
if (this.drawField) {
|
||||
if (this.drawOption) {
|
||||
const areaWithoutOption = this.drawField.areas?.find((a) => !a.option_uuid)
|
||||
@@ -1374,7 +1419,7 @@ export default {
|
||||
area.y -= area.h / 2
|
||||
}
|
||||
|
||||
if (area.w) {
|
||||
if (area.w && (type !== 'checkbox' || this.drawFieldType || !isTooSmall)) {
|
||||
this.addField(type, area)
|
||||
|
||||
this.selectedAreaRef.value = area
|
||||
@@ -1411,6 +1456,12 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
if (field.type === 'datenow') {
|
||||
field.type = 'date'
|
||||
field.readonly = true
|
||||
field.default_value = '{{date}}'
|
||||
}
|
||||
|
||||
if (['stamp', 'heading'].includes(field.type)) {
|
||||
field.readonly = true
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
@drop-field="$emit('drop-field', {...$event, attachment_uuid: document.uuid })"
|
||||
@remove-area="$emit('remove-area', $event)"
|
||||
@scroll-to="scrollToArea"
|
||||
@draw="$emit('draw', {...$event, attachment_uuid: document.uuid })"
|
||||
@draw="$emit('draw', { area: {...$event.area, attachment_uuid: document.uuid }, isTooSmall: $event.isTooSmall })"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -424,7 +424,7 @@
|
||||
</a>
|
||||
</li>
|
||||
</template>
|
||||
<li v-if="field.areas?.length === 1 && ['date', 'signature', 'initials', 'text', 'cells'].includes(field.type)">
|
||||
<li v-if="field.areas?.length === 1 && ['date', 'signature', 'initials', 'text', 'cells', 'stamp'].includes(field.type)">
|
||||
<a
|
||||
href="#"
|
||||
class="text-sm py-1 px-2"
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { IconTextSize, IconWritingSign, IconCalendarEvent, IconPhoto, IconCheckbox, IconPaperclip, IconSelect, IconCircleDot, IconChecks, IconColumns3, IconPhoneCheck, IconLetterCaseUpper, IconCreditCard, IconRubberStamp, IconSquareNumber1, IconHeading, IconId } from '@tabler/icons-vue'
|
||||
import { IconTextSize, IconWritingSign, IconCalendarEvent, IconPhoto, IconCheckbox, IconPaperclip, IconSelect, IconCircleDot, IconChecks, IconColumns3, IconPhoneCheck, IconLetterCaseUpper, IconCreditCard, IconRubberStamp, IconSquareNumber1, IconHeading, IconId, IconCalendarCheck } from '@tabler/icons-vue'
|
||||
|
||||
export default {
|
||||
name: 'FiledTypeDropdown',
|
||||
@@ -101,6 +101,7 @@ export default {
|
||||
signature: this.t('signature'),
|
||||
initials: this.t('initials'),
|
||||
date: this.t('date'),
|
||||
datenow: this.t('date_signed'),
|
||||
number: this.t('number'),
|
||||
image: this.t('image'),
|
||||
file: this.t('file'),
|
||||
@@ -142,6 +143,7 @@ export default {
|
||||
signature: IconWritingSign,
|
||||
initials: IconLetterCaseUpper,
|
||||
date: IconCalendarEvent,
|
||||
datenow: IconCalendarCheck,
|
||||
number: IconSquareNumber1,
|
||||
image: IconPhoto,
|
||||
checkbox: IconCheckbox,
|
||||
@@ -164,7 +166,7 @@ export default {
|
||||
return acc
|
||||
}, {})
|
||||
} else {
|
||||
return Object.fromEntries(Object.entries(this.fieldIcons).filter(([key]) => key !== 'heading'))
|
||||
return Object.fromEntries(Object.entries(this.fieldIcons).filter(([key]) => key !== 'heading' && key !== 'datenow'))
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -347,7 +347,7 @@ export default {
|
||||
return acc
|
||||
}, {})
|
||||
} else {
|
||||
return Object.fromEntries(Object.entries(this.fieldIcons).filter(([key]) => key !== 'heading'))
|
||||
return Object.fromEntries(Object.entries(this.fieldIcons).filter(([key]) => key !== 'heading' && key !== 'datenow'))
|
||||
}
|
||||
},
|
||||
submitterFields () {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const en = {
|
||||
font: 'Font',
|
||||
party: 'Party',
|
||||
date_signed: 'Date Signed',
|
||||
method: 'Method',
|
||||
reorder_fields: 'Reorder fields',
|
||||
verify_id: 'Verify ID',
|
||||
@@ -167,6 +168,7 @@ const en = {
|
||||
}
|
||||
|
||||
const es = {
|
||||
date_signed: 'Fecha actual',
|
||||
fuente: 'Fuente',
|
||||
party: 'Parte',
|
||||
method: 'Método',
|
||||
@@ -335,6 +337,7 @@ const es = {
|
||||
}
|
||||
|
||||
const it = {
|
||||
date_signed: 'Data attuale',
|
||||
font: 'Carattere',
|
||||
party: 'Parte',
|
||||
method: 'Metodo',
|
||||
@@ -503,6 +506,7 @@ const it = {
|
||||
}
|
||||
|
||||
const pt = {
|
||||
date_signed: 'Data atual',
|
||||
fonte: 'Fonte',
|
||||
party: 'Parte',
|
||||
method: 'Método',
|
||||
@@ -671,6 +675,7 @@ const pt = {
|
||||
}
|
||||
|
||||
const fr = {
|
||||
date_signed: 'Date actuelle',
|
||||
font: 'Police',
|
||||
party: 'Partie',
|
||||
method: 'Méthode',
|
||||
@@ -839,6 +844,7 @@ const fr = {
|
||||
}
|
||||
|
||||
const de = {
|
||||
date_now: 'Akt. Datum',
|
||||
font: 'Schriftart',
|
||||
party: 'Partei',
|
||||
method: 'Verfahren',
|
||||
|
||||
@@ -290,7 +290,12 @@ export default {
|
||||
area.cell_w = this.newArea.cell_w
|
||||
}
|
||||
|
||||
this.$emit('draw', area)
|
||||
const dx = Math.abs(e.offsetX - this.$refs.mask.clientWidth * this.newArea.initialX)
|
||||
const dy = Math.abs(e.offsetY - this.$refs.mask.clientHeight * this.newArea.initialY)
|
||||
|
||||
const isTooSmall = dx < 8 && dy < 8
|
||||
|
||||
this.$emit('draw', { area, isTooSmall })
|
||||
}
|
||||
|
||||
this.showMask = false
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class GenerateAttachmentPreviewJob
|
||||
include Sidekiq::Job
|
||||
|
||||
InvalidFormat = Class.new(StandardError)
|
||||
|
||||
sidekiq_options queue: :images
|
||||
|
||||
def perform(params = {})
|
||||
attachment = ActiveStorage::Attachment.find(params['attachment_id'])
|
||||
|
||||
if attachment.content_type == Templates::ProcessDocument::PDF_CONTENT_TYPE
|
||||
Templates::ProcessDocument.generate_pdf_preview_images(attachment, attachment.download)
|
||||
elsif attachment.image?
|
||||
Templates::ProcessDocument.generate_preview_image(attachment, attachment.download)
|
||||
else
|
||||
raise InvalidFormat, attachment.id
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -7,7 +7,7 @@ class ProcessSubmissionExpiredJob
|
||||
submission = Submission.find(params['submission_id'])
|
||||
|
||||
return if submission.archived_at?
|
||||
return if submission.template.archived_at?
|
||||
return if submission.template&.archived_at?
|
||||
return if submission.submitters.where.not(declined_at: nil).exists?
|
||||
return unless submission.submitters.exists?(completed_at: nil)
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ class ProcessSubmitterCompletionJob
|
||||
user = submission.created_by_user || submitter.template.author
|
||||
|
||||
if submitter.account.users.exists?(id: user.id) && submission.preferences['send_email'] != false &&
|
||||
submitter.template.preferences['completed_notification_email_enabled'] != false
|
||||
submitter.template&.preferences&.dig('completed_notification_email_enabled') != false
|
||||
if submission.submitters.map(&:email).exclude?(user.email) &&
|
||||
user.user_configs.find_by(key: UserConfig::RECEIVE_COMPLETED_EMAIL)&.value != false &&
|
||||
user.role != 'integration'
|
||||
@@ -98,7 +98,7 @@ class ProcessSubmitterCompletionJob
|
||||
end
|
||||
|
||||
def maybe_enqueue_copy_emails(submitter)
|
||||
return if submitter.template.preferences['documents_copy_email_enabled'] == false
|
||||
return if submitter.template&.preferences&.dig('documents_copy_email_enabled') == false
|
||||
|
||||
configs = AccountConfigs.find_or_initialize_for_key(submitter.account,
|
||||
AccountConfig::SUBMITTER_DOCUMENTS_COPY_EMAIL_KEY)
|
||||
@@ -119,7 +119,7 @@ class ProcessSubmitterCompletionJob
|
||||
|
||||
def build_bcc_addresses(submission)
|
||||
bcc = submission.preferences['bcc_completed'].presence ||
|
||||
submission.template.preferences['bcc_completed'].presence ||
|
||||
submission.template&.preferences&.dig('bcc_completed').presence ||
|
||||
submission.account.account_configs
|
||||
.find_by(key: AccountConfig::BCC_EMAILS)&.value
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class ReindexAllSearchEntriesJob
|
||||
include Sidekiq::Job
|
||||
|
||||
def perform
|
||||
SearchEntries.reindex_all
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,13 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class ReindexSearchEntryJob
|
||||
include Sidekiq::Job
|
||||
|
||||
InvalidFormat = Class.new(StandardError)
|
||||
|
||||
def perform(params = {})
|
||||
entry = SearchEntry.find_or_initialize_by(params.slice('record_type', 'record_id'))
|
||||
|
||||
SearchEntries.reindex_record(entry.record)
|
||||
end
|
||||
end
|
||||
@@ -20,7 +20,7 @@ class ApplicationMailer < ActionMailer::Base
|
||||
end
|
||||
|
||||
def set_message_metadata
|
||||
message.instance_variable_set(:@message_metadata, @message_metadata)
|
||||
message.instance_variable_set(:@message_metadata, @message_metadata || {})
|
||||
end
|
||||
|
||||
def set_message_uuid
|
||||
@@ -28,10 +28,14 @@ class ApplicationMailer < ActionMailer::Base
|
||||
end
|
||||
|
||||
def assign_message_metadata(tag, record)
|
||||
@message_metadata = {
|
||||
@message_metadata = (@message_metadata || {}).merge(
|
||||
'tag' => tag,
|
||||
'record_id' => record.id,
|
||||
'record_type' => record.class.name
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
def put_metadata(attrs)
|
||||
@message_metadata = (@message_metadata || {}).merge(attrs)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -23,11 +23,11 @@ class SubmitterMailer < ApplicationMailer
|
||||
|
||||
@body = @email_message&.body.presence ||
|
||||
template_submitters_index.dig(@submitter.uuid, 'request_email_body').presence ||
|
||||
@submitter.template.preferences['request_email_body'].presence
|
||||
@submitter.template&.preferences&.dig('request_email_body').presence
|
||||
|
||||
@subject = @email_message&.subject.presence ||
|
||||
template_submitters_index.dig(@submitter.uuid, 'request_email_subject').presence ||
|
||||
@submitter.template.preferences['request_email_subject'].presence
|
||||
@submitter.template&.preferences&.dig('request_email_subject').presence
|
||||
|
||||
@email_config = AccountConfigs.find_for_account(@current_account, AccountConfig::SUBMITTER_INVITATION_EMAIL_KEY)
|
||||
|
||||
@@ -53,6 +53,8 @@ class SubmitterMailer < ApplicationMailer
|
||||
@submission = submitter.submission
|
||||
@user = user
|
||||
|
||||
template_preferences = @submission.template&.preferences || {}
|
||||
|
||||
Submissions::EnsureResultGenerated.call(submitter)
|
||||
|
||||
@email_config = AccountConfigs.find_for_account(@current_account, AccountConfig::SUBMITTER_COMPLETED_EMAIL_KEY)
|
||||
@@ -60,15 +62,15 @@ class SubmitterMailer < ApplicationMailer
|
||||
add_completed_email_attachments!(
|
||||
submitter,
|
||||
with_documents: @email_config&.value&.dig('attach_documents') != false &&
|
||||
@submitter.template.preferences['completed_notification_email_attach_documents'] != false,
|
||||
template_preferences['completed_notification_email_attach_documents'] != false,
|
||||
with_audit_log: @email_config&.value&.dig('attach_audit_log') != false &&
|
||||
@submitter.template.preferences['completed_notification_email_attach_audit'] != false
|
||||
template_preferences['completed_notification_email_attach_audit'] != false
|
||||
)
|
||||
|
||||
@subject = @submitter.template.preferences['completed_notification_email_subject'].presence
|
||||
@subject = template_preferences['completed_notification_email_subject'].presence
|
||||
@subject ||= @email_config.value['subject'] if @email_config
|
||||
|
||||
@body = @submitter.template.preferences['completed_notification_email_body'].presence
|
||||
@body = template_preferences['completed_notification_email_body'].presence
|
||||
@body ||= @email_config.value['body'] if @email_config
|
||||
|
||||
assign_message_metadata('submitter_completed', @submitter)
|
||||
@@ -97,7 +99,7 @@ class SubmitterMailer < ApplicationMailer
|
||||
to: user.role == 'integration' ? user.friendly_name.sub(/\+\w+@/, '@') : user.friendly_name,
|
||||
reply_to: @submitter.friendly_name,
|
||||
subject: I18n.t(:name_declined_by_submitter,
|
||||
name: @submission.template.name.truncate(20),
|
||||
name: (@submission.name || @submission.template.name).truncate(20),
|
||||
submitter: @submitter.name || @submitter.email || @submitter.phone))
|
||||
end
|
||||
end
|
||||
@@ -107,22 +109,24 @@ class SubmitterMailer < ApplicationMailer
|
||||
@submitter = submitter
|
||||
@sig = submitter.signed_id(expires_in: SIGN_TTL, purpose: :download_completed) if sig
|
||||
|
||||
template_preferences = @submitter.template&.preferences || {}
|
||||
|
||||
Submissions::EnsureResultGenerated.call(@submitter)
|
||||
|
||||
@email_config = AccountConfigs.find_for_account(@current_account, AccountConfig::SUBMITTER_DOCUMENTS_COPY_EMAIL_KEY)
|
||||
|
||||
add_completed_email_attachments!(
|
||||
submitter,
|
||||
with_documents: @submitter.template.preferences['documents_copy_email_attach_documents'] != false &&
|
||||
(@email_config.nil? || @email_config.value['attach_documents'] != false),
|
||||
with_audit_log: @submitter.template.preferences['documents_copy_email_attach_audit'] != false &&
|
||||
(@email_config.nil? || @email_config.value['attach_audit_log'] != false)
|
||||
with_documents: template_preferences['documents_copy_email_attach_documents'] != false &&
|
||||
(@email_config.nil? || @email_config.value['attach_documents'] != false),
|
||||
with_audit_log: template_preferences['documents_copy_email_attach_audit'] != false &&
|
||||
(@email_config.nil? || @email_config.value['attach_audit_log'] != false)
|
||||
)
|
||||
|
||||
@subject = @submitter.template.preferences['documents_copy_email_subject'].presence
|
||||
@subject = template_preferences['documents_copy_email_subject'].presence
|
||||
@subject ||= @email_config.value['subject'] if @email_config
|
||||
|
||||
@body = @submitter.template.preferences['documents_copy_email_body'].presence
|
||||
@body = template_preferences['documents_copy_email_body'].presence
|
||||
@body ||= @email_config.value['body'] if @email_config
|
||||
|
||||
assign_message_metadata('submitter_documents_copy', @submitter)
|
||||
@@ -130,11 +134,7 @@ class SubmitterMailer < ApplicationMailer
|
||||
|
||||
I18n.with_locale(@current_account.locale) do
|
||||
subject =
|
||||
if @subject.present?
|
||||
ReplaceEmailVariables.call(@subject, submitter:)
|
||||
else
|
||||
I18n.t(:your_document_copy)
|
||||
end
|
||||
@subject.present? ? ReplaceEmailVariables.call(@subject, submitter:) : I18n.t(:your_document_copy)
|
||||
|
||||
mail(from: from_address_for_submitter(submitter),
|
||||
to: to || @submitter.friendly_name,
|
||||
@@ -147,7 +147,7 @@ class SubmitterMailer < ApplicationMailer
|
||||
|
||||
def build_submitter_reply_to(submitter, email_config: nil, documents_copy_email: nil)
|
||||
reply_to = submitter.preferences['reply_to'].presence
|
||||
reply_to ||= submitter.template.preferences['documents_copy_email_reply_to'].presence if documents_copy_email
|
||||
reply_to ||= submitter.template&.preferences&.dig('documents_copy_email_reply_to').presence if documents_copy_email
|
||||
reply_to ||= email_config.value['reply_to'].presence if email_config
|
||||
|
||||
if reply_to.blank? && (submitter.submission.created_by_user || submitter.template.author)&.email != submitter.email
|
||||
@@ -212,7 +212,7 @@ class SubmitterMailer < ApplicationMailer
|
||||
end
|
||||
|
||||
def build_submitter_preferences_index(submitter)
|
||||
submitter.template.preferences['submitters'].to_a.index_by { |e| e['uuid'] }
|
||||
submitter.template&.preferences&.dig('submitters').to_a.index_by { |e| e['uuid'] }
|
||||
end
|
||||
|
||||
def add_attachments_with_size_limit(submitter, storage_attachments, current_size, filename_format = nil)
|
||||
@@ -233,9 +233,17 @@ class SubmitterMailer < ApplicationMailer
|
||||
def from_address_for_submitter(submitter)
|
||||
if submitter.submission.source.in?(%w[api embed]) &&
|
||||
(from_email = AccountConfig.find_by(account: submitter.account, key: 'integration_from_email')&.value.presence)
|
||||
user = submitter.account.users.find_by(email: from_email)
|
||||
|
||||
put_metadata('from_user_id' => user.id)
|
||||
|
||||
from_email
|
||||
else
|
||||
(submitter.submission.created_by_user || submitter.submission.template.author).friendly_name
|
||||
user = submitter.submission.created_by_user || submitter.submission.template.author
|
||||
|
||||
put_metadata('from_user_id' => user.id)
|
||||
|
||||
user.friendly_name
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -29,6 +29,7 @@ class AccountConfig < ApplicationRecord
|
||||
ALLOW_TYPED_SIGNATURE = 'allow_typed_signature'
|
||||
ALLOW_TO_RESUBMIT = 'allow_to_resubmit'
|
||||
ALLOW_TO_DECLINE_KEY = 'allow_to_decline'
|
||||
ALLOW_TO_PARTIAL_DOWNLOAD_KEY = 'allow_to_partial_download'
|
||||
SUBMITTER_REMINDERS = 'submitter_reminders'
|
||||
ENFORCE_SIGNING_ORDER_KEY = 'enforce_signing_order'
|
||||
FORM_COMPLETED_BUTTON_KEY = 'form_completed_button'
|
||||
@@ -41,6 +42,7 @@ class AccountConfig < ApplicationRecord
|
||||
FLATTEN_RESULT_PDF_KEY = 'flatten_result_pdf'
|
||||
WITH_SIGNATURE_ID = 'with_signature_id'
|
||||
WITH_AUDIT_VALUES_KEY = 'with_audit_values'
|
||||
WITH_AUDIT_SUBMITTER_TIMEZONE_KEY = 'with_audit_submitter_timezone'
|
||||
REQUIRE_SIGNING_REASON_KEY = 'require_signing_reason'
|
||||
REUSE_SIGNATURE_KEY = 'reuse_signature'
|
||||
COMBINE_PDF_RESULT_KEY = 'combine_pdf_result_key'
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
# account_id :bigint not null
|
||||
# submission_id :bigint not null
|
||||
# submitter_id :bigint not null
|
||||
# template_id :bigint not null
|
||||
# template_id :bigint
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
@@ -24,7 +24,7 @@ class CompletedSubmitter < ApplicationRecord
|
||||
belongs_to :submitter
|
||||
belongs_to :submission
|
||||
belongs_to :account
|
||||
belongs_to :template
|
||||
belongs_to :template, optional: true
|
||||
|
||||
has_many :completed_documents, dependent: :destroy,
|
||||
primary_key: :submitter_id,
|
||||
|
||||
@@ -18,10 +18,11 @@
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
# index_email_events_on_account_id (account_id)
|
||||
# index_email_events_on_email (email)
|
||||
# index_email_events_on_emailable (emailable_type,emailable_id)
|
||||
# index_email_events_on_message_id (message_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_event_types (email) WHERE ((event_type)::text = ANY ((ARRAY['bounce'::character varying, 'soft_bounce'::character varying, 'complaint'::character varying, 'soft_complaint'::character varying])::text[]))
|
||||
# index_email_events_on_emailable (emailable_type,emailable_id)
|
||||
# index_email_events_on_message_id (message_id)
|
||||
#
|
||||
# Foreign Keys
|
||||
#
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: search_entries
|
||||
#
|
||||
# id :bigint not null, primary key
|
||||
# ngram :tsvector
|
||||
# record_type :string not null
|
||||
# tsvector :tsvector not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# account_id :bigint not null
|
||||
# record_id :bigint not null
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
# index_search_entries_on_account_id_ngram_submission (account_id,ngram) WHERE ((record_type)::text = 'Submission'::text) USING gin
|
||||
# index_search_entries_on_account_id_ngram_submitter (account_id,ngram) WHERE ((record_type)::text = 'Submitter'::text) USING gin
|
||||
# index_search_entries_on_account_id_ngram_template (account_id,ngram) WHERE ((record_type)::text = 'Template'::text) USING gin
|
||||
# index_search_entries_on_account_id_tsvector_submission (account_id,tsvector) WHERE ((record_type)::text = 'Submission'::text) USING gin
|
||||
# index_search_entries_on_account_id_tsvector_submitter (account_id,tsvector) WHERE ((record_type)::text = 'Submitter'::text) USING gin
|
||||
# index_search_entries_on_account_id_tsvector_template (account_id,tsvector) WHERE ((record_type)::text = 'Template'::text) USING gin
|
||||
# index_search_entries_on_record_id_and_record_type (record_id,record_type) UNIQUE
|
||||
#
|
||||
class SearchEntry < ApplicationRecord
|
||||
belongs_to :record, polymorphic: true
|
||||
belongs_to :account
|
||||
end
|
||||
@@ -7,6 +7,7 @@
|
||||
# id :bigint not null, primary key
|
||||
# archived_at :datetime
|
||||
# expire_at :datetime
|
||||
# name :text
|
||||
# preferences :text not null
|
||||
# slug :string not null
|
||||
# source :text not null
|
||||
@@ -18,14 +19,16 @@
|
||||
# updated_at :datetime not null
|
||||
# account_id :bigint not null
|
||||
# created_by_user_id :bigint
|
||||
# template_id :bigint not null
|
||||
# template_id :bigint
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
# index_submissions_on_account_id_and_id (account_id,id)
|
||||
# index_submissions_on_created_by_user_id (created_by_user_id)
|
||||
# index_submissions_on_slug (slug) UNIQUE
|
||||
# index_submissions_on_template_id (template_id)
|
||||
# index_submissions_on_account_id_and_id (account_id,id)
|
||||
# index_submissions_on_account_id_and_template_id_and_id (account_id,template_id,id) WHERE (archived_at IS NULL)
|
||||
# index_submissions_on_account_id_and_template_id_and_id_archived (account_id,template_id,id) WHERE (archived_at IS NOT NULL)
|
||||
# index_submissions_on_created_by_user_id (created_by_user_id)
|
||||
# index_submissions_on_slug (slug) UNIQUE
|
||||
# index_submissions_on_template_id (template_id)
|
||||
#
|
||||
# Foreign Keys
|
||||
#
|
||||
@@ -33,10 +36,12 @@
|
||||
# fk_rails_... (template_id => templates.id)
|
||||
#
|
||||
class Submission < ApplicationRecord
|
||||
belongs_to :template
|
||||
belongs_to :template, optional: true
|
||||
belongs_to :account
|
||||
belongs_to :created_by_user, class_name: 'User', optional: true
|
||||
|
||||
has_one :search_entry, as: :record, inverse_of: :record, dependent: :destroy
|
||||
|
||||
has_many :submitters, dependent: :destroy
|
||||
has_many :submission_events, dependent: :destroy
|
||||
|
||||
@@ -56,6 +61,7 @@ class Submission < ApplicationRecord
|
||||
has_one_attached :combined_document
|
||||
|
||||
has_many_attached :preview_documents
|
||||
has_many_attached :documents
|
||||
|
||||
has_many :template_accesses, primary_key: :template_id, foreign_key: :template_id, dependent: nil, inverse_of: false
|
||||
|
||||
@@ -65,13 +71,19 @@ class Submission < ApplicationRecord
|
||||
|
||||
scope :active, -> { where(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 {
|
||||
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)
|
||||
}
|
||||
scope :declined, -> { joins(:submitters).where.not(submitters: { declined_at: nil }).group(:id) }
|
||||
scope :expired, -> { where(expire_at: ..Time.current) }
|
||||
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) }
|
||||
|
||||
enum :source, {
|
||||
invite: 'invite',
|
||||
@@ -90,6 +102,14 @@ class Submission < ApplicationRecord
|
||||
expire_at && expire_at <= Time.current
|
||||
end
|
||||
|
||||
def schema_documents
|
||||
if template_id?
|
||||
template_schema_documents
|
||||
else
|
||||
documents_attachments
|
||||
end
|
||||
end
|
||||
|
||||
def fields_uuid_index
|
||||
@fields_uuid_index ||= (template_fields || template.fields).index_by { |f| f['uuid'] }
|
||||
end
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
# preferences :text not null
|
||||
# sent_at :datetime
|
||||
# slug :string not null
|
||||
# timezone :string
|
||||
# ua :string
|
||||
# uuid :string not null
|
||||
# values :text not null
|
||||
@@ -27,11 +28,12 @@
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
# index_submitters_on_account_id_and_id (account_id,id)
|
||||
# index_submitters_on_email (email)
|
||||
# index_submitters_on_external_id (external_id)
|
||||
# index_submitters_on_slug (slug) UNIQUE
|
||||
# index_submitters_on_submission_id (submission_id)
|
||||
# index_submitters_on_account_id_and_id (account_id,id)
|
||||
# index_submitters_on_completed_at_and_account_id (completed_at,account_id)
|
||||
# index_submitters_on_email (email)
|
||||
# index_submitters_on_external_id (external_id)
|
||||
# index_submitters_on_slug (slug) UNIQUE
|
||||
# index_submitters_on_submission_id (submission_id)
|
||||
#
|
||||
# Foreign Keys
|
||||
#
|
||||
@@ -41,6 +43,7 @@ class Submitter < ApplicationRecord
|
||||
belongs_to :submission
|
||||
belongs_to :account
|
||||
has_one :template, through: :submission
|
||||
has_one :search_entry, as: :record, inverse_of: :record, dependent: :destroy
|
||||
|
||||
attribute :values, :string, default: -> { {} }
|
||||
attribute :preferences, :string, default: -> { {} }
|
||||
|
||||
+10
-5
@@ -10,6 +10,7 @@
|
||||
# name :string not null
|
||||
# preferences :text not null
|
||||
# schema :text not null
|
||||
# shared_link :boolean default(FALSE), not null
|
||||
# slug :string not null
|
||||
# source :text not null
|
||||
# submitters :text not null
|
||||
@@ -22,11 +23,13 @@
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
# index_templates_on_account_id (account_id)
|
||||
# index_templates_on_author_id (author_id)
|
||||
# index_templates_on_external_id (external_id)
|
||||
# index_templates_on_folder_id (folder_id)
|
||||
# index_templates_on_slug (slug) UNIQUE
|
||||
# index_templates_on_account_id (account_id)
|
||||
# index_templates_on_account_id_and_folder_id_and_id (account_id,folder_id,id) WHERE (archived_at IS NULL)
|
||||
# index_templates_on_account_id_and_id_archived (account_id,id) WHERE (archived_at IS NOT NULL)
|
||||
# index_templates_on_author_id (author_id)
|
||||
# index_templates_on_external_id (external_id)
|
||||
# index_templates_on_folder_id (folder_id)
|
||||
# index_templates_on_slug (slug) UNIQUE
|
||||
#
|
||||
# Foreign Keys
|
||||
#
|
||||
@@ -41,6 +44,8 @@ class Template < ApplicationRecord
|
||||
belongs_to :account
|
||||
belongs_to :folder, class_name: 'TemplateFolder'
|
||||
|
||||
has_one :search_entry, as: :record, inverse_of: :record, dependent: :destroy
|
||||
|
||||
before_validation :maybe_set_default_folder, on: :create
|
||||
|
||||
attribute :preferences, :string, default: -> { {} }
|
||||
|
||||
@@ -73,6 +73,8 @@ class User < ApplicationRecord
|
||||
scope :archived, -> { where.not(archived_at: nil) }
|
||||
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
|
||||
super || build_access_token.tap(&:save!)
|
||||
end
|
||||
|
||||
@@ -165,6 +165,15 @@
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<%= render 'extra_preferences' %>
|
||||
<% if !Docuseal.multitenant? && SearchEntry.table_exists? && (!Docuseal.fulltext_search? || params[:reindex] == 'true') && can?(:manage, EncryptedConfig) %>
|
||||
<div class="flex items-center justify-between py-2.5">
|
||||
<span>
|
||||
Efficient search with search index
|
||||
</span>
|
||||
<%= button_to params[:reindex] == 'true' ? 'Reindex' : 'Build Search Index', settings_search_entries_reindex_index_path, method: :post, class: 'btn btn-sm btn-neutral text-white px-4' %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<%= render 'compliances' %>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div id="us_server_selector" class="flex justify-center hidden">
|
||||
<div id="server_selector" class="flex justify-center <%= 'hidden' unless eu_server %>">
|
||||
<div class="dropdown">
|
||||
<label tabindex="0" class="relative btn btn-sm bg-transparent font-medium normal-case border-base-content/20 justify-start" style="width: 141px; padding: 0 20px">
|
||||
<% if eu_server %>
|
||||
|
||||
@@ -2,14 +2,18 @@
|
||||
if (!window.customElements.get('server-selector')) {
|
||||
customElements.define('server-selector', class extends HTMLElement {
|
||||
connectedCallback() {
|
||||
const usServerSelector = this.querySelector('#us_server_selector');
|
||||
const serverSelector = this.querySelector('#server_selector');
|
||||
const globalServerSelector = this.querySelector('#global_server_selector');
|
||||
const euServerAlert = this.querySelector('#eu_server_alert');
|
||||
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
const usTimezones = /^(?:America\/(?:New_York|Detroit|Kentucky|Indiana|Chicago|Menominee|North_Dakota|Denver|Boise|Phoenix|Los_Angeles|Anchorage|Juneau|Sitka|Metlakatla|Yakutat|Nome|Adak)|Pacific\/Honolulu)/;
|
||||
|
||||
if (!serverSelector.classList.contains('hidden')) {
|
||||
return
|
||||
}
|
||||
|
||||
if (usTimezones.test(timezone)) {
|
||||
usServerSelector.classList.remove('hidden');
|
||||
serverSelector.classList.remove('hidden');
|
||||
} else if (timezone.includes('Europe')) {
|
||||
globalServerSelector.classList.remove('hidden');
|
||||
euServerAlert?.classList?.remove('hidden');
|
||||
|
||||
@@ -1,23 +1,29 @@
|
||||
<% link = pagy_anchor(@pagy) %>
|
||||
<% if @pagy.pages > 1 %>
|
||||
<% link = pagy_anchor(pagy) %>
|
||||
<% if pagy.pages > 1 %>
|
||||
<div class="flex my-6 justify-center md:justify-between">
|
||||
<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] %>
|
||||
</div>
|
||||
<div class="flex items-center space-x-1.5">
|
||||
<%= local_assigns[:right_additional_html] %>
|
||||
<div class="join">
|
||||
<% if @pagy.prev %>
|
||||
<%== link.call(@pagy.prev, '«', classes: 'join-item btn min-h-full h-10') %>
|
||||
<% if pagy.prev %>
|
||||
<%== link.call(pagy.prev, '«', classes: 'join-item btn min-h-full h-10') %>
|
||||
<% else %>
|
||||
<span class="join-item btn btn-disabled !bg-base-200 min-h-full h-10">«</span>
|
||||
<% end %>
|
||||
<span class="join-item btn font-medium uppercase min-h-full h-10">
|
||||
<%= t('page_number', number: @pagy.page) %>
|
||||
<%= t('page_number', number: pagy.page) %>
|
||||
</span>
|
||||
<% if @pagy.next %>
|
||||
<%== link.call(@pagy.next, '»', classes: 'join-item btn min-h-full h-10') %>
|
||||
<% if local_assigns[:next_page_path].present? %>
|
||||
<%= link_to '»', local_assigns[:next_page_path], class: 'join-item btn min-h-full h-10' %>
|
||||
<% elsif pagy.next %>
|
||||
<%== link.call(pagy.next, '»', classes: 'join-item btn min-h-full h-10') %>
|
||||
<% else %>
|
||||
<span class="join-item btn btn-disabled !bg-base-200 min-h-full h-10">»</span>
|
||||
<% end %>
|
||||
|
||||
@@ -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>
|
||||
<% if Docuseal.multitenant? || Accounts.can_send_emails?(@submitter.account) %>
|
||||
<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>
|
||||
<% 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.submitters).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">
|
||||
<%= 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>
|
||||
<% end %>
|
||||
</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>
|
||||
<%= render 'shared/attribution', link_path: '/start', account: @template.account %>
|
||||
<%= render 'start_form/policy', account: @template.account %>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<input type="hidden" name="submission[1][submitters][][uuid]" value="<%= item['uuid'] %>">
|
||||
<submitters-autocomplete data-field="name">
|
||||
<linked-input data-target-id="<%= "detailed_name_#{item['linked_to_uuid']}" if item['linked_to_uuid'].present? %>">
|
||||
<%= tag.input type: 'text', name: 'submission[1][submitters][][name]', autocomplete: 'off', class: 'base-input !h-10 w-full', placeholder: t('name'), required: index.zero?, value: item['email'].present? ? current_account.submitters.accessible_by(current_ability).where.not(name: nil).order(id: :desc).find_by(email: item['email'])&.name : ((params[:selfsign] && index.zero?) || item['is_requester'] ? current_user.full_name : ''), dir: 'auto', id: "detailed_name_#{item['uuid']}" %>
|
||||
<%= tag.input type: 'text', name: 'submission[1][submitters][][name]', autocomplete: 'off', class: 'base-input !h-10 w-full', placeholder: t('name'), required: index.zero? || template.preferences['require_all_submitters'], value: item['email'].present? ? current_account.submitters.accessible_by(current_ability).where.not(name: nil).order(id: :desc).find_by(email: item['email'])&.name : ((params[:selfsign] && index.zero?) || item['is_requester'] ? current_user.full_name : ''), dir: 'auto', id: "detailed_name_#{item['uuid']}" %>
|
||||
</linked-input>
|
||||
</submitters-autocomplete>
|
||||
<div class="grid <%= 'md:grid-cols-2 gap-1' if submitters.size == 1 %>">
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
<input type="hidden" name="submission[1][submitters][][uuid]" value="<%= item['uuid'] %>">
|
||||
<submitters-autocomplete data-field="email">
|
||||
<linked-input data-target-id="<%= "email_#{item['linked_to_uuid']}" if item['linked_to_uuid'].present? %>">
|
||||
<%= tag.input type: 'email', multiple: true, name: 'submission[1][submitters][][email]', autocomplete: 'off', class: 'base-input !h-10 w-full', placeholder: t('email'), required: index.zero?, value: item['email'].presence || ((params[:selfsign] && index.zero?) || item['is_requester'] ? current_user.email : ''), id: "email_#{item['uuid']}" %>
|
||||
<%= tag.input type: 'email', multiple: true, name: 'submission[1][submitters][][email]', autocomplete: 'off', class: 'base-input !h-10 w-full', placeholder: t('email'), required: index.zero? || template.preferences['require_all_submitters'], value: item['email'].presence || ((params[:selfsign] && index.zero?) || item['is_requester'] ? current_user.email : ''), id: "email_#{item['uuid']}" %>
|
||||
</linked-input>
|
||||
</submitters-autocomplete>
|
||||
</submitter-item>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<input type="hidden" name="submission[1][submitters][][uuid]" value="<%= item['uuid'] %>">
|
||||
<submitters-autocomplete data-field="phone">
|
||||
<linked-input data-target-id="<%= "phone_phone_#{item['linked_to_uuid']}" if item['linked_to_uuid'].present? %>">
|
||||
<%= tag.input type: 'tel', pattern: '^\+[0-9\s\-]+$', oninvalid: "this.value ? this.setCustomValidity('#{t('use_international_format_1xxx_')}') : ''", oninput: "this.setCustomValidity('')", name: 'submission[1][submitters][][phone]', autocomplete: 'off', class: 'base-input !h-10 w-full', placeholder: t('phone'), required: index.zero?, id: "phone_phone_#{item['uuid']}" %>
|
||||
<%= tag.input type: 'tel', pattern: '^\+[0-9\s\-]+$', oninvalid: "this.value ? this.setCustomValidity('#{t('use_international_format_1xxx_')}') : ''", oninput: "this.setCustomValidity('')", name: 'submission[1][submitters][][phone]', autocomplete: 'off', class: 'base-input !h-10 w-full', placeholder: t('phone'), required: index.zero? || template.preferences['require_all_submitters'], id: "phone_phone_#{item['uuid']}" %>
|
||||
</linked-input>
|
||||
</submitters-autocomplete>
|
||||
<% if submitters.size > 1 %>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<% submitter_preferences_index = template.preferences['submitters'].to_a.index_by { |e| e['uuid'] } %>
|
||||
<% submitter_preferences_index = template&.preferences&.dig('submitters').to_a.index_by { |e| e['uuid'] } %>
|
||||
<% template_submitters = local_assigns[:submitter]&.submission&.template_submitters || template.submitters %>
|
||||
<div class="form-control">
|
||||
<% can_send_emails = Accounts.can_send_emails?(current_account) %>
|
||||
<div class="flex justify-between items-center">
|
||||
@@ -37,18 +38,18 @@
|
||||
<% config = AccountConfigs.find_or_initialize_for_key(current_account, AccountConfig::SUBMITTER_INVITATION_EMAIL_KEY) %>
|
||||
<div id="message_field" class="card card-compact bg-base-300/40 hidden">
|
||||
<div class="card-body">
|
||||
<%= tag.input id: toggle_uuid = SecureRandom.uuid, value: '1', name: 'request_email_per_submitter', class: 'peer', type: 'checkbox', hidden: true, checked: local_assigns[:message_per_submitter] != false && template.preferences['submitters'].to_a.size > 1 %>
|
||||
<%= tag.input id: toggle_uuid = SecureRandom.uuid, value: '1', name: 'request_email_per_submitter', class: 'peer', type: 'checkbox', hidden: true, checked: local_assigns[:message_per_submitter] != false && template&.preferences&.dig('submitters').to_a.size > 1 %>
|
||||
<div class="peer-checked:hidden form-control space-y-2">
|
||||
<div class="form-control">
|
||||
<div class="flex justify-between">
|
||||
<%= f.label :subject, t('subject'), class: 'label' %>
|
||||
<% if template.submitters.size > 1 && template.submitters.size < 5 && local_assigns[:message_per_submitter] != false %>
|
||||
<% if template_submitters.size > 1 && template_submitters.size < 5 && local_assigns[:message_per_submitter] != false %>
|
||||
<label for="<%= toggle_uuid %>" class="label underline">
|
||||
<%= t('edit_per_party') %>
|
||||
</label>
|
||||
<% end %>
|
||||
</div>
|
||||
<%= f.text_field :subject, value: local_assigns[:submitter_email_message]&.subject.presence || submitter_preferences_index.dig(local_assigns[:submitter]&.uuid, 'request_email_subject').presence || template.preferences['request_email_subject'].presence || config.value['subject'], required: true, class: '!text-sm base-input w-full', dir: 'auto' %>
|
||||
<%= f.text_field :subject, value: local_assigns[:submitter_email_message]&.subject.presence || submitter_preferences_index.dig(local_assigns[:submitter]&.uuid, 'request_email_subject').presence || template&.preferences&.dig('request_email_subject').presence || config.value['subject'], required: true, class: '!text-sm base-input w-full', dir: 'auto' %>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<div class="flex items-center">
|
||||
@@ -58,7 +59,7 @@
|
||||
</span>
|
||||
</div>
|
||||
<autoresize-textarea>
|
||||
<%= f.text_area :body, value: local_assigns[:submitter_email_message]&.body.presence || submitter_preferences_index.dig(local_assigns[:submitter]&.uuid, 'request_email_body').presence || template.preferences['request_email_body'].presence || config.value['body'], required: true, class: 'base-textarea w-full', rows: 10, dir: 'auto' %>
|
||||
<%= f.text_area :body, value: local_assigns[:submitter_email_message]&.body.presence || submitter_preferences_index.dig(local_assigns[:submitter]&.uuid, 'request_email_body').presence || template&.preferences&.dig('request_email_body').presence || config.value['body'], required: true, class: 'base-textarea w-full', rows: 10, dir: 'auto' %>
|
||||
</autoresize-textarea>
|
||||
<% unless local_assigns.fetch(:disable_save_as_default_template_option, false) %>
|
||||
<label for="<%= uuid = SecureRandom.uuid %>" class="flex items-center cursor-pointer">
|
||||
@@ -69,10 +70,10 @@
|
||||
</div>
|
||||
<%= render 'submissions/message_fields' %>
|
||||
</div>
|
||||
<% if template.submitters.size > 1 && template.submitters.size < 5 && local_assigns[:message_per_submitter] != false %>
|
||||
<% if template_submitters.size > 1 && template_submitters.size < 5 && local_assigns[:message_per_submitter] != false %>
|
||||
<div class="hidden peer-checked:block form-control space-y-2">
|
||||
<% uuid = SecureRandom.uuid %>
|
||||
<% options = template.submitters.map { |e| [e['name'], "request_email_#{uuid}_#{e['uuid']}"] } %>
|
||||
<% options = template_submitters.map { |e| [e['name'], "request_email_#{uuid}_#{e['uuid']}"] } %>
|
||||
<toggle-visible data-element-ids="<%= options.map(&:last).to_json %>" class="flex relative px-1">
|
||||
<ul class="tabs w-full flex flex-nowrap">
|
||||
<% options.each_with_index do |(label, val), index| %>
|
||||
@@ -83,14 +84,14 @@
|
||||
<% end %>
|
||||
</ul>
|
||||
</toggle-visible>
|
||||
<% template.submitters.each_with_index do |submitter, index| %>
|
||||
<% template_submitters.each_with_index do |submitter, index| %>
|
||||
<%= fields_for :submitter_preferences, nil, index: submitter['uuid'] do |ff| %>
|
||||
<div id="request_email_<%= uuid %>_<%= submitter['uuid'] %>" class="<%= 'hidden' if index != 0 %>">
|
||||
<div class="form-control">
|
||||
<div class="flex justify-between">
|
||||
<%= ff.label :subject, t('subject'), class: 'label' %>
|
||||
</div>
|
||||
<%= ff.text_field :subject, value: local_assigns[:submitter_email_message]&.subject.presence || submitter_preferences_index.dig(submitter['uuid'], 'request_email_subject').presence || template.preferences['request_email_subject'].presence || config.value['subject'], required: true, class: '!text-sm base-input w-full', dir: 'auto' %>
|
||||
<%= ff.text_field :subject, value: local_assigns[:submitter_email_message]&.subject.presence || submitter_preferences_index.dig(submitter['uuid'], 'request_email_subject').presence || template&.preferences&.dig('request_email_subject').presence || config.value['subject'], required: true, class: '!text-sm base-input w-full', dir: 'auto' %>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<div class="flex items-center">
|
||||
@@ -100,7 +101,7 @@
|
||||
</span>
|
||||
</div>
|
||||
<autoresize-textarea>
|
||||
<%= ff.text_area :body, value: local_assigns[:submitter_email_message]&.body.presence || submitter_preferences_index.dig(submitter['uuid'], 'request_email_body').presence || template.preferences['request_email_body'].presence || config.value['body'], required: true, class: 'base-textarea w-full', rows: 10, dir: 'auto' %>
|
||||
<%= ff.text_area :body, value: local_assigns[:submitter_email_message]&.body.presence || submitter_preferences_index.dig(submitter['uuid'], 'request_email_body').presence || template&.preferences&.dig('request_email_body').presence || config.value['body'], required: true, class: 'base-textarea w-full', rows: 10, dir: 'auto' %>
|
||||
</autoresize-textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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') %>">
|
||||
<% if field['type'] == 'signature' %>
|
||||
<% 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 overflow-hidden <%= is_narrow ? 'w-1/2' : 'flex-grow' %>" style="min-height: 50%">
|
||||
<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 && (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 %>">
|
||||
</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="truncate uppercase">
|
||||
ID: <%= attachment.uuid %>
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
<% with_signature_id, is_combined_enabled = AccountConfig.where(account_id: @submission.account_id, key: [AccountConfig::COMBINE_PDF_RESULT_KEY, AccountConfig::WITH_SIGNATURE_ID], value: true).then { |configs| [configs.any? { |e| e.key == AccountConfig::WITH_SIGNATURE_ID }, configs.any? { |e| e.key == AccountConfig::COMBINE_PDF_RESULT_KEY }] } %>
|
||||
<div style="max-width: 1600px" class="mx-auto pl-4">
|
||||
<div class="flex justify-between py-1.5 items-center pr-4 sticky top-0 md:relative z-10 bg-base-100">
|
||||
<a href="<%= signed_in? && @submission.account_id == current_account&.id ? template_path(@submission.template) : '/' %>" class="flex items-center space-x-3 py-1">
|
||||
<a href="<%= signed_in? && @submission.account_id == current_account&.id && @submission.template ? template_path(@submission.template) : '/' %>" class="flex items-center space-x-3 py-1">
|
||||
<span><%= render 'submissions/logo' %></span>
|
||||
<span class="text-xl md:text-3xl font-semibold focus:text-clip" style="overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;"><% @submission.template.name.split(/(_)/).each do |item| %><%= item %><wbr><% end %></span>
|
||||
<span class="text-xl md:text-3xl font-semibold focus:text-clip" style="overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;"><% (@submission.name || @submission.template.name).split(/(_)/).each do |item| %><%= item %><wbr><% end %></span>
|
||||
</a>
|
||||
<div class="space-x-3 flex items-center">
|
||||
<% last_submitter = @submission.submitters.to_a.select(&:completed_at?).max_by(&:completed_at) %>
|
||||
@@ -73,7 +73,7 @@
|
||||
<% values = @submission.submitters.reduce({}) { |acc, sub| acc.merge(sub.values) } %>
|
||||
<% schema = Submissions.filtered_conditions_schema(@submission, values:) %>
|
||||
<% schema.each do |item| %>
|
||||
<% document = @submission.template_schema_documents.find { |a| item['attachment_uuid'] == a.uuid } %>
|
||||
<% document = @submission.schema_documents.find { |a| item['attachment_uuid'] == a.uuid } %>
|
||||
<a href="#<%= "page-#{document.uuid}-0" %>" onclick="[event.preventDefault(), window[event.target.closest('a').href.split('#')[1]].scrollIntoView({ behavior: 'smooth', block: 'start' })]" class="block cursor-pointer">
|
||||
<img src="<%= Docuseal::URL_CACHE.fetch([document.id, document.uuid, 0].join(':'), expires_in: 10.minutes) { document.preview_images.first.url } %>" width="<%= document.preview_images.first.metadata['width'] %>" height="<%= document.preview_images.first.metadata['height'] %>" class="rounded border" loading="lazy">
|
||||
<div class="pb-2 pt-1.5 text-center" dir="auto">
|
||||
@@ -89,7 +89,7 @@
|
||||
<% attachments_index = ActiveStorage::Attachment.where(record: @submission.submitters, name: :attachments).preload(:blob).index_by(&:uuid) %>
|
||||
<% page_blob_struct = Struct.new(:url, :metadata, keyword_init: true) %>
|
||||
<% schema.each do |item| %>
|
||||
<% document = @submission.template_schema_documents.find { |e| e.uuid == item['attachment_uuid'] } %>
|
||||
<% document = @submission.schema_documents.find { |e| e.uuid == item['attachment_uuid'] } %>
|
||||
<% document_annots_index = document.metadata.dig('pdf', 'annotations')&.group_by { |e| e['page'] } || {} %>
|
||||
<% preview_images_index = document.preview_images.loaded? ? document.preview_images.index_by { |e| e.filename.base.to_i } : {} %>
|
||||
<% lazyload_metadata = document.preview_images.first.metadata %>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<%= render 'submissions_filters/filter_button', filter_params: %>
|
||||
</div>
|
||||
</div>
|
||||
<% if @pagy.count > 0 %>
|
||||
<% if @pagy.count.nil? || @pagy.count > 0 %>
|
||||
<div class="space-y-4">
|
||||
<%= render partial: 'templates/submission', collection: @submissions, locals: { with_template: true, archived: true } %>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<% 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 %>
|
||||
<div class="flex justify-between items-center w-full mb-4">
|
||||
<div class="flex items-center flex-grow min-w-0">
|
||||
@@ -26,7 +26,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<% view_archived_html = capture do %>
|
||||
<% if current_account.submissions.where.not(archived_at: nil).exists? %>
|
||||
<% if can?(:manage, :countless) || current_account.submissions.where.not(archived_at: nil).exists? %>
|
||||
<div>
|
||||
<a href="<%= submissions_archived_index_path %>" class="link text-sm"><%= t('view_archived') %></a>
|
||||
</div>
|
||||
@@ -61,12 +61,12 @@
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if @pagy.count > 0 %>
|
||||
<% if @pagy.count.nil? || @pagy.count > 0 %>
|
||||
<div class="space-y-4">
|
||||
<%= render partial: 'templates/submission', collection: @submissions, locals: { with_template: true } %>
|
||||
</div>
|
||||
<% 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' %>
|
||||
<% end %>
|
||||
<% if @submissions.present? || (params[:q].blank? && filter_params.blank?) %>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<%= svg_icon('writing_sign', class: 'w-10 h-10') %>
|
||||
</div>
|
||||
<div>
|
||||
<p dir="auto" class="text-lg font-bold mb-1"><%= @submission.template.name %></p>
|
||||
<p dir="auto" class="text-lg font-bold mb-1"><%= @submission.name || @submission.template.name %></p>
|
||||
<% last_submitter = @submission.submitters.completed.order(:completed_at).last %>
|
||||
<% if last_submitter %>
|
||||
<p dir="auto" class="text-sm">
|
||||
@@ -28,6 +28,11 @@
|
||||
<%= f.hidden_field :submission_slug, value: @submission.slug %>
|
||||
<%= f.label :email, t('email'), class: 'label' %>
|
||||
<%= f.email_field :email, value: current_user&.email || params[:email], required: true, class: 'base-input', placeholder: t('send_copy_to_email') %>
|
||||
<% if params[:status] == 'error' %>
|
||||
<span class="label-text-alt text-red-400 mt-1">
|
||||
<%= t('please_enter_your_email_address_associated_with_the_completed_submission') %>
|
||||
</span>
|
||||
<% end %>
|
||||
</div>
|
||||
<toggle-submit dir="auto" class="form-control">
|
||||
<%= f.button button_title(title: t('send_copy_to_email'), disabled_with: t('starting')), class: 'base-button' %>
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
<% data_fields = Submissions.filtered_conditions_fields(submitter).to_json %>
|
||||
<% invite_submitters = (submitter.submission.template_submitters || submitter.submission.template.submitters).select { |s| s['invite_by_uuid'] == submitter.uuid && submitter.submission.submitters.none? { |e| e.uuid == s['uuid'] } }.to_json %>
|
||||
<% optional_invite_submitters = (submitter.submission.template_submitters || submitter.submission.template.submitters).select { |s| s['optional_invite_by_uuid'] == submitter.uuid && submitter.submission.submitters.none? { |e| e.uuid == s['uuid'] } }.to_json %>
|
||||
<submission-form data-is-demo="<%= Docuseal.demo? %>" data-schema="<%= schema.to_json %>" data-reuse-signature="<%= configs[:reuse_signature] %>" data-require-signing-reason="<%= configs[:require_signing_reason] %>" data-with-signature-id="<%= configs[:with_signature_id] %>" data-with-confetti="<%= configs[:with_confetti] %>" data-completed-redirect-url="<%= submitter.preferences['completed_redirect_url'].presence || submitter.submission.template.preferences['completed_redirect_url'] %>" data-completed-message="<%= (configs[:completed_message]&.compact_blank.presence || submitter.submission.template.preferences['completed_message'] || {}).to_json %>" data-completed-button="<%= configs[:completed_button].to_json %>" data-go-to-last="<%= submitter.preferences.key?('go_to_last') ? submitter.preferences['go_to_last'] : submitter.opened_at? %>" data-submitter="<%= submitter.to_json(only: %i[uuid slug name phone email]) %>" data-can-send-email="<%= Accounts.can_send_emails?(submitter.submission.account) %>" data-optional-invite-submitters="<%= optional_invite_submitters %>" data-invite-submitters="<%= invite_submitters %>" data-attachments="<%= data_attachments %>" data-fields="<%= data_fields %>" data-values="<%= submitter.values.to_json %>" data-with-typed-signature="<%= configs[:with_typed_signature] %>" data-previous-signature-value="<%= local_assigns[:signature_attachment]&.uuid %>" data-remember-signature="<%= configs[:prefill_signature] %>" data-dry-run="<%= local_assigns[:dry_run] %>" data-expand="<%= local_assigns[:expand] %>" data-scroll-padding="<%= local_assigns[:scroll_padding] %>" data-language="<%= I18n.locale.to_s.split('-').first %>"></submission-form>
|
||||
<submission-form data-is-demo="<%= Docuseal.demo? %>" data-schema="<%= schema.to_json %>" data-reuse-signature="<%= configs[:reuse_signature] %>" data-require-signing-reason="<%= configs[:require_signing_reason] %>" data-with-signature-id="<%= configs[:with_signature_id] %>" data-with-confetti="<%= configs[:with_confetti] %>" data-completed-redirect-url="<%= submitter.preferences['completed_redirect_url'].presence || submitter.submission.template&.preferences&.dig('completed_redirect_url') %>" data-completed-message="<%= (configs[:completed_message]&.compact_blank.presence || submitter.submission.template&.preferences&.dig('completed_message') || {}).to_json %>" data-completed-button="<%= configs[:completed_button].to_json %>" data-go-to-last="<%= submitter.preferences.key?('go_to_last') ? submitter.preferences['go_to_last'] : submitter.opened_at? %>" data-submitter="<%= submitter.to_json(only: %i[uuid slug name phone email]) %>" data-can-send-email="<%= Accounts.can_send_emails?(submitter.submission.account) %>" data-optional-invite-submitters="<%= optional_invite_submitters %>" data-invite-submitters="<%= invite_submitters %>" data-attachments="<%= data_attachments %>" data-fields="<%= data_fields %>" data-values="<%= submitter.values.to_json %>" data-with-typed-signature="<%= configs[:with_typed_signature] %>" data-previous-signature-value="<%= local_assigns[:signature_attachment]&.uuid %>" data-remember-signature="<%= configs[:prefill_signature] %>" data-dry-run="<%= local_assigns[:dry_run] %>" data-expand="<%= local_assigns[:expand] %>" data-scroll-padding="<%= local_assigns[:scroll_padding] %>" data-language="<%= I18n.locale.to_s.split('-').first %>"></submission-form>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<%= svg_icon('writing_sign', class: 'w-10 h-10') %>
|
||||
</div>
|
||||
<div dir="auto">
|
||||
<p class="text-lg font-bold mb-1"><%= @submitter.submission.template.name %></p>
|
||||
<p class="text-lg font-bold mb-1"><%= @submitter.submission.name || @submitter.submission.template.name %></p>
|
||||
<p class="text-sm"><%= t('form_has_been_deleted_by_html', name: @submitter.account.name) %></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<%= svg_icon('writing_sign', class: 'w-10 h-10') %>
|
||||
</div>
|
||||
<div dir="auto">
|
||||
<p class="text-lg font-bold mb-1"><%= @submitter.submission.template.name %></p>
|
||||
<p class="text-lg font-bold mb-1"><%= @submitter.submission.name || @submitter.submission.template.name %></p>
|
||||
<p class="text-sm"><%= t('awaiting_completion_by_the_other_party') %></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<%= svg_icon('writing_sign', class: 'w-10 h-10') %>
|
||||
</div>
|
||||
<div>
|
||||
<p dir="auto" class="text-lg font-bold mb-1"><%= @submitter.submission.template.name %></p>
|
||||
<p dir="auto" class="text-lg font-bold mb-1"><%= @submitter.submission.name || @submitter.submission.template.name %></p>
|
||||
<p dir="auto" class="text-sm">
|
||||
<%= t(@submitter.with_signature_fields? ? 'signed_on_time' : 'completed_on_time', time: l(@submitter.completed_at.to_date, format: :long)) %>
|
||||
</p>
|
||||
@@ -23,7 +23,7 @@
|
||||
<toggle-submit>
|
||||
<%= 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: 'white-button w-full' %>
|
||||
</toggle-submit>
|
||||
<% if Templates.filter_undefined_submitters(@submitter.submission.template).size != 1 %>
|
||||
<% if Templates.filter_undefined_submitters(@submitter.submission.template_submitters).size != 1 %>
|
||||
<div class="divider uppercase"><%= t('or') %></div>
|
||||
<% else %>
|
||||
<div class="py-2"></div>
|
||||
@@ -42,11 +42,11 @@
|
||||
</download-button>
|
||||
<% end %>
|
||||
</div>
|
||||
<% 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? %>
|
||||
<% undefined_submitters = Templates.filter_undefined_submitters(@submitter.submission.template_submitters) %>
|
||||
<% 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 && !@submitter.template.archived_at? %>
|
||||
<div class="divider uppercase"><%= t('or') %></div>
|
||||
<toggle-submit class="block">
|
||||
<%= button_to button_title(title: t('resubmit'), disabled_with: t('resubmit'), icon: svg_icon('reload', class: 'w-6 h-6')), start_form_path(@submitter.submission.template.slug), params: { submitter: { email: @submitter.email, phone: @submitter.phone, name: @submitter.name }, resubmit: @submitter.slug }, method: :put, class: 'white-button w-full' %>
|
||||
<%= 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>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<%= svg_icon('writing_sign', class: 'w-10 h-10') %>
|
||||
</div>
|
||||
<div dir="auto">
|
||||
<p class="text-lg font-bold mb-1"><%= @submitter.submission.template.name %></p>
|
||||
<p class="text-lg font-bold mb-1"><%= @submitter.submission.name || @submitter.submission.template.name %></p>
|
||||
<p class="text-sm"><%= t('form_has_been_declined_on_html', time: l(@submitter.declined_at, format: :long)) %></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<%= svg_icon('writing_sign', class: 'w-10 h-10') %>
|
||||
</div>
|
||||
<div dir="auto">
|
||||
<p class="text-lg font-bold mb-1"><%= @submitter.submission.template.name %></p>
|
||||
<p class="text-lg font-bold mb-1"><%= @submitter.submission.name || @submitter.submission.template.name %></p>
|
||||
<p class="text-sm"><%= t('form_expired_at_html', time: l(@submitter.submission.expire_at, format: :long)) %></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<% content_for(:html_title, "#{@submitter.submission.template.name} | DocuSeal") %>
|
||||
<% content_for(:html_title, "#{@submitter.submission.name || @submitter.submission.template.name} | DocuSeal") %>
|
||||
<% content_for(:html_description, "#{@submitter.account.name} has invited you to fill and sign documents online effortlessly with a secure, fast, and user-friendly digital document signing solution.") %>
|
||||
<% fields_index = Templates.build_field_areas_index(@submitter.submission.template_fields || @submitter.submission.template.fields) %>
|
||||
<% values = @submitter.submission.submitters.reduce({}) { |acc, sub| acc.merge(sub.values) } %>
|
||||
@@ -13,7 +13,7 @@
|
||||
<%= render('submit_form/banner') %>
|
||||
<div id="signing_form_header" class="sticky min-[1230px]:static top-0 z-50 bg-base-100 py-2 px-2 flex items-center md:-mx-[8px]" style="margin-bottom: -16px">
|
||||
<div class="text-xl md:text-2xl font-medium focus:text-clip" style="width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
|
||||
<%= @submitter.submission.template.name %>
|
||||
<%= @submitter.submission.name || @submitter.submission.template.name %>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2" style="margin-left: 20px; flex-shrink: 0">
|
||||
<% if @form_configs[:with_decline] %>
|
||||
@@ -24,16 +24,18 @@
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<download-button data-src="<%= submit_form_download_index_path(@submitter.slug) %>" class="btn btn-neutral text-white btn-sm !px-4">
|
||||
<span class="flex items-center justify-center" data-target="download-button.defaultButton">
|
||||
<%= svg_icon('download', class: 'w-6 h-6 inline md:hidden') %>
|
||||
<span class="hidden md:inline"><%= t('download') %></span>
|
||||
</span>
|
||||
<span class="flex items-center justify-center space-x-2 hidden" data-target="download-button.loadingButton">
|
||||
<%= svg_icon('loader', class: 'w-6 h-6 animate-spin') %>
|
||||
<span class="hidden md:inline"><%= t('downloading') %></span>
|
||||
</span>
|
||||
</download-button>
|
||||
<% if @form_configs[:with_partial_download] %>
|
||||
<download-button data-src="<%= submit_form_download_index_path(@submitter.slug) %>" class="btn btn-neutral text-white btn-sm !px-4">
|
||||
<span class="flex items-center justify-center" data-target="download-button.defaultButton">
|
||||
<%= svg_icon('download', class: 'w-6 h-6 inline md:hidden') %>
|
||||
<span class="hidden md:inline"><%= t('download') %></span>
|
||||
</span>
|
||||
<span class="flex items-center justify-center space-x-2 hidden" data-target="download-button.loadingButton">
|
||||
<%= svg_icon('loader', class: 'w-6 h-6 animate-spin') %>
|
||||
<span class="hidden md:inline"><%= t('downloading') %></span>
|
||||
</span>
|
||||
</download-button>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<scroll-buttons class="fixed right-5 top-2 hidden md:flex gap-1 z-50 ease-in-out opacity-0 -translate-y-10">
|
||||
@@ -58,11 +60,11 @@
|
||||
</scroll-buttons>
|
||||
<% end %>
|
||||
<% schema.each do |item| %>
|
||||
<% document = @submitter.submission.template_schema_documents.find { |a| a.uuid == item['attachment_uuid'] } %>
|
||||
<% document = @submitter.submission.schema_documents.find { |a| a.uuid == item['attachment_uuid'] } %>
|
||||
<div id="document-<%= document.uuid %>">
|
||||
<% document_annots_index = document.metadata.dig('pdf', 'annotations')&.group_by { |e| e['page'] } || {} %>
|
||||
<% preview_images_index = document.preview_images.loaded? ? document.preview_images.index_by { |e| e.filename.base.to_i } : {} %>
|
||||
<% lazyload_metadata = document.preview_images.last.metadata %>
|
||||
<% lazyload_metadata = document.preview_images.last&.metadata || {} %>
|
||||
<% (document.metadata.dig('pdf', 'number_of_pages') || (document.preview_images.loaded? ? preview_images_index.size : document.preview_images.size)).times do |index| %>
|
||||
<% page = preview_images_index[index] || page_blob_struct.new(metadata: lazyload_metadata, url: preview_document_page_path(document.signed_uuid, "#{index}.jpg")) %>
|
||||
<div class="relative my-4 shadow-md">
|
||||
|
||||
@@ -2,6 +2,6 @@
|
||||
<%= render 'custom_content', content: @body, submitter: @submitter %>
|
||||
<% else %>
|
||||
<p><%= t('hi_there') %>,</p>
|
||||
<p><%= I18n.t(:name_has_been_completed_by_submitters, name: @submitter.submission.template.name, submitters: @submitter.submission.submitters.order(:completed_at).map { |e| e.name || e.email || e.phone }.uniq.join(', ')) %></p>
|
||||
<p><%= I18n.t(:name_has_been_completed_by_submitters, name: @submitter.submission.name || @submitter.submission.template.name, submitters: @submitter.submission.submitters.order(:completed_at).map { |e| e.name || e.email || e.phone }.uniq.join(', ')) %></p>
|
||||
<p><%= link_to submission_url(@submitter.submission), submission_url(@submitter.submission) %></p>
|
||||
<% end %>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<p><%= t('hi_there') %>,</p>
|
||||
<p><%= t('name_declined_by_submitter_with_the_following_reason', name: @submitter.submission.template.name, submitter: @submitter.name || @submitter.email || @submitter.phone) %></p>
|
||||
<p><%= t('name_declined_by_submitter_with_the_following_reason', name: @submitter.submission.name || @submitter.submission.template.name, submitter: @submitter.name || @submitter.email || @submitter.phone) %></p>
|
||||
<%= simple_format(h(@submitter.submission_events.find_by(event_type: :decline_form).data['reason'])) %>
|
||||
<p><%= link_to submission_url(@submitter.submission), submission_url(@submitter.submission) %></p>
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
<%= render 'custom_content', content: @body, submitter: @submitter, sig: @sig %>
|
||||
<% else %>
|
||||
<p><%= t('hi_there') %>,</p>
|
||||
<p><%= t('please_check_the_copy_of_your_name_in_the_email_attachments', name: @submitter.submission.template.name) %>
|
||||
<p><%= t('please_check_the_copy_of_your_name_in_the_email_attachments', name: @submitter.submission.name || @submitter.submission.template.name) %>
|
||||
<p><%= t('alternatively_you_can_review_and_download_your_copy_using_the_link_below') %></p>
|
||||
<p>
|
||||
<%= link_to @submitter.template.name, submissions_preview_url(@submitter.submission.slug, { sig: @sig }.compact) %>
|
||||
<%= link_to @submitter.submission.name || @submitter.submission.template.name, submissions_preview_url(@submitter.submission.slug, { sig: @sig }.compact) %>
|
||||
</p>
|
||||
<p>
|
||||
<%= t('thanks') %>,<br><%= @current_account.name %>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<% end %>
|
||||
<% else %>
|
||||
<p><%= t('hi_there') %>,</p>
|
||||
<p><%= I18n.t(@submitter.with_signature_fields? ? :you_have_been_invited_to_sign_the_name : :you_have_been_invited_to_submit_the_name_form, name: @submitter.submission.template.name) %></p>
|
||||
<p><%= I18n.t(@submitter.with_signature_fields? ? :you_have_been_invited_to_sign_the_name : :you_have_been_invited_to_submit_the_name_form, name: @submitter.submission.name || @submitter.submission.template.name) %></p>
|
||||
<p><%= link_to I18n.t(@submitter.with_signature_fields? ? :review_and_sign : :review_and_submit), submit_form_url(slug: @submitter.slug, t: SubmissionEvents.build_tracking_param(@submitter, 'click_email'), host: ENV.fetch('EMAIL_HOST', Docuseal.default_url_options[:host])) %></p>
|
||||
<p><%= t('please_contact_us_by_replying_to_this_email_if_you_didn_t_request_this') %></p>
|
||||
<p>
|
||||
|
||||
@@ -39,13 +39,13 @@
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<% if @pagy.count > 0 %>
|
||||
<% if @pagy.count.nil? || @pagy.count > 0 %>
|
||||
<div class="grid gap-4 md:grid-cols-3">
|
||||
<%= render partial: 'templates/template', collection: @templates %>
|
||||
</div>
|
||||
<% templates_order_select_html = capture do %>
|
||||
<% 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 %>
|
||||
<%= render 'shared/pagination', pagy: @pagy, items_name: 'templates', right_additional_html: templates_order_select_html %>
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
<div class="bg-base-200 rounded-2xl flex flex-col sm:flex-row items-strech">
|
||||
<% if local_assigns[:with_template] %>
|
||||
<% template = submission.template %>
|
||||
<a href="<%= template_path(template) %>" class="px-5 sm:pr-3 py-3 group sm:rounded-l-2xl sm:rounded-tr-none rounded-t-2xl flex sm:flex-col justify-between sm:w-52 w-full flex-shrink-0 bg-base-300/60 space-x-2 sm:space-x-0">
|
||||
<a href="<%= template ? template_path(template) : submission_path(submission) %>" class="px-5 sm:pr-3 py-3 group sm:rounded-l-2xl sm:rounded-tr-none rounded-t-2xl flex sm:flex-col justify-between sm:w-52 w-full flex-shrink-0 bg-base-300/60 space-x-2 sm:space-x-0">
|
||||
<div>
|
||||
<div class="font-medium items-start w-full group-hover:link text-sm flex space-x-1">
|
||||
<%= svg_icon('file_text', class: 'w-4 h-4 mt-0.5 flex-shrink-0') %>
|
||||
<span style="overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;">
|
||||
<% template.name.split(/(_)/).each do |item| %><%= item %><wbr><% end %>
|
||||
<% (submission.name || template.name).split(/(_)/).each do |item| %><%= item %><wbr><% end %>
|
||||
<%= svg_icon('arrow_right', class: 'w-4 h-4 sm:inline group-hover:visible invisible hidden') %>
|
||||
</span>
|
||||
</div>
|
||||
@@ -34,7 +34,7 @@
|
||||
<% submitter = submitters.first %>
|
||||
<div class="flex items-center space-x-4">
|
||||
<span class="flex flex-col md:flex-row md:items-center gap-3">
|
||||
<% if submission.expired? %>
|
||||
<% if submission.expired? && !submitter.completed_at? && !submitter.declined_at? %>
|
||||
<div class="tooltip flex" data-tip="<%= l(submission.expire_at.in_time_zone(current_account.timezone), format: :short, locale: current_account.locale) %>">
|
||||
<span class="badge badge-error md:w-32 bg-opacity-50 badge-lg uppercase text-sm font-semibold">
|
||||
<%= t('expired') %>
|
||||
@@ -78,7 +78,7 @@
|
||||
</download-button>
|
||||
</div>
|
||||
</div>
|
||||
<% elsif !submission.archived_at? && !template.archived_at? && !submission.expired? && !submitter.declined_at? %>
|
||||
<% elsif !submission.archived_at? && !template&.archived_at? && !submission.expired? && !submitter.declined_at? %>
|
||||
<% if current_user.email == submitter.email %>
|
||||
<div class="flex-1 md:flex-none md:w-36 flex">
|
||||
<a href="<%= submit_form_url(slug: submitter.slug) %>" data-turbo="false" target="_blank" id="sign_yourself_button" class="btn btn-sm btn-neutral btn-outline bg-white w-full md:w-36 flex z-[1]">
|
||||
@@ -101,7 +101,7 @@
|
||||
<%= t('view') %>
|
||||
</a>
|
||||
</div>
|
||||
<% if !submission.archived_at? && !template.archived_at? && can?(:destroy, submission) %>
|
||||
<% if !submission.archived_at? && !template&.archived_at? && can?(:destroy, submission) %>
|
||||
<span data-tip="<%= t('archive') %>" class="sm:tooltip tooltip-top">
|
||||
<%= button_to button_title(title: nil, disabled_with: t(:archive).first(4), icon: svg_icon('archive', class: 'w-6 h-6')), submission_path(submission), class: 'btn btn-outline btn-sm w-full md:w-fit', form: { class: 'flex' }, title: t('archive'), method: :delete %>
|
||||
</span>
|
||||
@@ -164,7 +164,7 @@
|
||||
<span class="inline"><%= t('download')[..-2] %>...</span>
|
||||
</span>
|
||||
</download-button>
|
||||
<% elsif !template.archived_at? && !submission.archived_at? && !is_submission_completed && !submission.expired? && !submitter.declined_at? %>
|
||||
<% elsif !template&.archived_at? && !submission.archived_at? && !is_submission_completed && !submission.expired? && !submitter.declined_at? %>
|
||||
<div class="relative flex items-center space-x-3">
|
||||
<% if current_user.email == submitter.email %>
|
||||
<a href="<%= submit_form_url(slug: submitter.slug) %>" data-turbo="false" target="_blank" id="sign_yourself_button" class="absolute md:relative top-0 right-0 btn btn-xs btn-outline btn-neutral bg-white w-28 md:w-36 z-[1]">
|
||||
@@ -208,7 +208,7 @@
|
||||
<%= t('view') %>
|
||||
</a>
|
||||
</div>
|
||||
<% if !submission.archived_at? && !template.archived_at? %>
|
||||
<% if !submission.archived_at? && !template&.archived_at? %>
|
||||
<span data-tip="<%= t('archive') %>" class="sm:tooltip tooltip-top">
|
||||
<%= button_to button_title(title: nil, disabled_with: t(:archive).first(4), icon: svg_icon('archive', class: 'w-6 h-6')), submission_path(submission), class: 'btn btn-outline btn-sm w-full md:w-fit', form: { class: 'flex' }, title: t('archive'), method: :delete %>
|
||||
</span>
|
||||
|
||||
@@ -49,7 +49,12 @@
|
||||
<% end %>
|
||||
</div>
|
||||
<% 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>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user